Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
16KB

  1. /*
  2. * Carla Plugin UI
  3. * Copyright (C) 2014 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. #include "CarlaPluginUI.hpp"
  18. #ifdef HAVE_X11
  19. # include <sys/types.h>
  20. # include <X11/Xatom.h>
  21. # include <X11/Xlib.h>
  22. # include <X11/Xutil.h>
  23. #endif
  24. #ifdef HAVE_X11
  25. # include "CarlaPluginUI_X11Icon.hpp"
  26. // -----------------------------------------------------
  27. // X11
  28. typedef void (*EventProcPtr)(XEvent* ev);
  29. static const int X11Key_Escape = 9;
  30. static bool gErrorTriggered = false;
  31. static int temporaryErrorHandler(Display*, XErrorEvent*)
  32. {
  33. gErrorTriggered = true;
  34. return 0;
  35. }
  36. class X11PluginUI : public CarlaPluginUI
  37. {
  38. public:
  39. X11PluginUI(CloseCallback* const cb, const uintptr_t parentId, const bool isResizable) noexcept
  40. : CarlaPluginUI(cb, isResizable),
  41. fDisplay(nullptr),
  42. fWindow(0),
  43. fIsVisible(false),
  44. fFirstShow(true),
  45. fEventProc(nullptr)
  46. {
  47. fDisplay = XOpenDisplay(nullptr);
  48. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  49. const int screen = DefaultScreen(fDisplay);
  50. XSetWindowAttributes attr;
  51. carla_zeroStruct<XSetWindowAttributes>(attr);
  52. attr.border_pixel = 0;
  53. attr.event_mask = KeyPressMask|KeyReleaseMask;
  54. if (fIsResizable)
  55. attr.event_mask |= StructureNotifyMask;
  56. fWindow = XCreateWindow(fDisplay, RootWindow(fDisplay, screen),
  57. 0, 0, 300, 300, 0,
  58. DefaultDepth(fDisplay, screen),
  59. InputOutput,
  60. DefaultVisual(fDisplay, screen),
  61. CWBorderPixel|CWEventMask, &attr);
  62. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  63. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fWindow, 1, GrabModeAsync, GrabModeAsync);
  64. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  65. XSetWMProtocols(fDisplay, fWindow, &wmDelete, 1);
  66. const pid_t pid = getpid();
  67. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  68. XChangeProperty(fDisplay, fWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  69. const Atom _nwi = XInternAtom(fDisplay, "_NET_WM_ICON", False);
  70. XChangeProperty(fDisplay, fWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  71. if (parentId != 0)
  72. setTransientWinId(parentId);
  73. }
  74. ~X11PluginUI() override
  75. {
  76. CARLA_SAFE_ASSERT(! fIsVisible);
  77. if (fIsVisible)
  78. {
  79. XUnmapWindow(fDisplay, fWindow);
  80. fIsVisible = false;
  81. }
  82. if (fWindow != 0)
  83. {
  84. XDestroyWindow(fDisplay, fWindow);
  85. fWindow = 0;
  86. }
  87. if (fDisplay != nullptr)
  88. {
  89. XCloseDisplay(fDisplay);
  90. fDisplay = nullptr;
  91. }
  92. }
  93. void show() override
  94. {
  95. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  96. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  97. if (fFirstShow)
  98. {
  99. if (const Window childWindow = getChildWindow())
  100. {
  101. const Atom _xevp = XInternAtom(fDisplay, "_XEventProc", False);
  102. gErrorTriggered = false;
  103. const XErrorHandler oldErrorHandler(XSetErrorHandler(temporaryErrorHandler));
  104. Atom actualType;
  105. int actualFormat;
  106. ulong nitems, bytesAfter;
  107. uchar* data = nullptr;
  108. XGetWindowProperty(fDisplay, childWindow, _xevp, 0, 1, False, AnyPropertyType, &actualType, &actualFormat, &nitems, &bytesAfter, &data);
  109. XSetErrorHandler(oldErrorHandler);
  110. if (nitems == 1 && ! gErrorTriggered)
  111. {
  112. fEventProc = *reinterpret_cast<EventProcPtr*>(data);
  113. XMapRaised(fDisplay, childWindow);
  114. }
  115. }
  116. }
  117. fIsVisible = true;
  118. fFirstShow = false;
  119. XMapRaised(fDisplay, fWindow);
  120. XFlush(fDisplay);
  121. }
  122. void hide() override
  123. {
  124. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  125. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  126. fIsVisible = false;
  127. XUnmapWindow(fDisplay, fWindow);
  128. XFlush(fDisplay);
  129. }
  130. void idle() override
  131. {
  132. // prevent recursion
  133. if (fIsIdling) return;
  134. fIsIdling = true;
  135. for (XEvent event; XPending(fDisplay) > 0;)
  136. {
  137. XNextEvent(fDisplay, &event);
  138. if (! fIsVisible)
  139. continue;
  140. char* type = nullptr;
  141. switch (event.type)
  142. {
  143. case ConfigureNotify:
  144. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  145. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.width > 0);
  146. CARLA_SAFE_ASSERT_CONTINUE(event.xconfigure.height > 0);
  147. fCallback->handlePluginUIResized(static_cast<uint>(event.xconfigure.width),
  148. static_cast<uint>(event.xconfigure.height));
  149. break;
  150. case ClientMessage:
  151. type = XGetAtomName(fDisplay, event.xclient.message_type);
  152. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  153. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  154. {
  155. fIsVisible = false;
  156. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  157. fCallback->handlePluginUIClosed();
  158. }
  159. break;
  160. case KeyRelease:
  161. if (event.xkey.keycode == X11Key_Escape)
  162. {
  163. fIsVisible = false;
  164. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  165. fCallback->handlePluginUIClosed();
  166. }
  167. break;
  168. }
  169. if (type != nullptr)
  170. XFree(type);
  171. else if (fEventProc != nullptr)
  172. fEventProc(&event);
  173. }
  174. fIsIdling = false;
  175. }
  176. void focus() override
  177. {
  178. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  179. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  180. XRaiseWindow(fDisplay, fWindow);
  181. XSetInputFocus(fDisplay, fWindow, RevertToPointerRoot, CurrentTime);
  182. XFlush(fDisplay);
  183. }
  184. void setSize(const uint width, const uint height, const bool forceUpdate) override
  185. {
  186. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  187. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  188. XResizeWindow(fDisplay, fWindow, width, height);
  189. if (! fIsResizable)
  190. {
  191. XSizeHints sizeHints;
  192. carla_zeroStruct<XSizeHints>(sizeHints);
  193. sizeHints.flags = PSize|PMinSize|PMaxSize;
  194. sizeHints.width = static_cast<int>(width);
  195. sizeHints.height = static_cast<int>(height);
  196. sizeHints.min_width = static_cast<int>(width);
  197. sizeHints.min_height = static_cast<int>(height);
  198. sizeHints.max_width = static_cast<int>(width);
  199. sizeHints.max_height = static_cast<int>(height);
  200. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  201. }
  202. if (forceUpdate)
  203. XFlush(fDisplay);
  204. }
  205. void setTitle(const char* const title) override
  206. {
  207. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  208. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  209. XStoreName(fDisplay, fWindow, title);
  210. }
  211. void setTransientWinId(const uintptr_t winId) override
  212. {
  213. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  214. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  215. XSetTransientForHint(fDisplay, fWindow, static_cast<Window>(winId));
  216. }
  217. void* getPtr() const noexcept
  218. {
  219. return (void*)fWindow;
  220. }
  221. void* getDisplay() const noexcept
  222. {
  223. return fDisplay;
  224. }
  225. protected:
  226. Window getChildWindow() const
  227. {
  228. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr, 0);
  229. CARLA_SAFE_ASSERT_RETURN(fWindow != 0, 0);
  230. Window rootWindow, parentWindow, ret = 0;
  231. Window* childWindows = nullptr;
  232. uint numChildren = 0;
  233. XQueryTree(fDisplay, fWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  234. if (numChildren > 0 && childWindows != nullptr)
  235. {
  236. ret = childWindows[0];
  237. XFree(childWindows);
  238. }
  239. return ret;
  240. }
  241. private:
  242. Display* fDisplay;
  243. Window fWindow;
  244. bool fIsVisible;
  245. bool fFirstShow;
  246. EventProcPtr fEventProc;
  247. CARLA_DECLARE_NON_COPY_CLASS(X11PluginUI)
  248. };
  249. #endif
  250. // -----------------------------------------------------
  251. bool CarlaPluginUI::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId, const bool centerUI)
  252. {
  253. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  254. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  255. #if defined(CARLA_OS_MAC)
  256. return true;
  257. (void)pid; (void)centerUI;
  258. #elif defined(CARLA_OS_WIN)
  259. return true;
  260. (void)pid; (void)centerUI;
  261. #elif defined(HAVE_X11)
  262. struct ScopedDisplay {
  263. Display* display;
  264. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  265. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  266. // c++ compat stuff
  267. CARLA_PREVENT_HEAP_ALLOCATION
  268. CARLA_DECLARE_NON_COPY_CLASS(ScopedDisplay)
  269. };
  270. struct ScopedFreeData {
  271. union {
  272. char* data;
  273. uchar* udata;
  274. };
  275. ScopedFreeData(char* d) : data(d) {}
  276. ScopedFreeData(uchar* d) : udata(d) {}
  277. ~ScopedFreeData() { XFree(data); }
  278. // c++ compat stuff
  279. CARLA_PREVENT_HEAP_ALLOCATION
  280. CARLA_DECLARE_NON_COPY_CLASS(ScopedFreeData)
  281. };
  282. const ScopedDisplay sd;
  283. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  284. const Window rootWindow(DefaultRootWindow(sd.display));
  285. const Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , False);
  286. const Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", False);
  287. const Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", False);
  288. const Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  289. Atom actualType;
  290. int actualFormat;
  291. ulong numWindows, bytesAfter;
  292. uchar* data = nullptr;
  293. int status = XGetWindowProperty(sd.display, rootWindow, _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  294. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  295. const ScopedFreeData sfd(data);
  296. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  297. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  298. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  299. Window* windows = (Window*)data;
  300. Window lastGoodWindow = 0;
  301. for (ulong i = 0; i < numWindows; i++)
  302. {
  303. const Window window(windows[i]);
  304. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  305. // ------------------------------------------------
  306. // try using pid
  307. if (pid != 0)
  308. {
  309. ulong pidSize;
  310. uchar* pidData = nullptr;
  311. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  312. if (pidData != nullptr)
  313. {
  314. const ScopedFreeData sfd2(pidData);
  315. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  316. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  317. if (*(ulong*)pidData == static_cast<ulong>(pid))
  318. {
  319. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  320. lastGoodWindow = window;
  321. carla_stdout("Match found using pid");
  322. break;
  323. }
  324. }
  325. }
  326. // ------------------------------------------------
  327. // try using name (UTF-8)
  328. ulong nameSize;
  329. uchar* nameData = nullptr;
  330. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  331. if (nameData != nullptr)
  332. {
  333. const ScopedFreeData sfd2(nameData);
  334. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  335. CARLA_SAFE_ASSERT_CONTINUE(nameSize != 0);
  336. if (std::strstr((const char*)nameData, uiTitle) != nullptr)
  337. {
  338. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  339. lastGoodWindow = window;
  340. carla_stdout("Match found using UTF-8 name");
  341. }
  342. }
  343. // ------------------------------------------------
  344. // try using name (simple)
  345. char* wmName = nullptr;
  346. status = XFetchName(sd.display, window, &wmName);
  347. if (wmName != nullptr)
  348. {
  349. const ScopedFreeData sfd2(wmName);
  350. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  351. if (std::strstr(wmName, uiTitle) != nullptr)
  352. {
  353. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  354. lastGoodWindow = window;
  355. carla_stdout("Match found using simple name");
  356. }
  357. }
  358. }
  359. if (lastGoodWindow == 0)
  360. return false;
  361. const Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", False);
  362. const Atom _nws[2] = {
  363. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", False),
  364. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", False)
  365. };
  366. XChangeProperty(sd.display, lastGoodWindow, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  367. const Atom _nwi = XInternAtom(sd.display, "_NET_WM_ICON", False);
  368. XChangeProperty(sd.display, lastGoodWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  369. const Window hostWinId((Window)winId);
  370. XSetTransientForHint(sd.display, lastGoodWindow, hostWinId);
  371. if (centerUI)
  372. {
  373. int hostX, hostY, pluginX, pluginY;
  374. uint hostWidth, hostHeight, pluginWidth, pluginHeight, border, depth;
  375. Window retWindow;
  376. if (XGetGeometry(sd.display, hostWinId, &retWindow, &hostX, &hostY, &hostWidth, &hostHeight, &border, &depth) != 0 &&
  377. XGetGeometry(sd.display, lastGoodWindow, &retWindow, &pluginX, &pluginY, &pluginWidth, &pluginHeight, &border, &depth) != 0)
  378. {
  379. if (XTranslateCoordinates(sd.display, hostWinId, rootWindow, hostX, hostY, &hostX, &hostY, &retWindow) == True &&
  380. XTranslateCoordinates(sd.display, lastGoodWindow, rootWindow, pluginX, pluginY, &pluginX, &pluginY, &retWindow) == True)
  381. {
  382. const int newX = hostX + int(hostWidth/2 - pluginWidth/2);
  383. const int newY = hostY + int(hostHeight/2 - pluginHeight/2);
  384. XMoveWindow(sd.display, lastGoodWindow, newX, newY);
  385. }
  386. }
  387. }
  388. // focusing the host UI and then the plugin UI forces the WM to repaint the plugin window icon
  389. XRaiseWindow(sd.display, hostWinId);
  390. XSetInputFocus(sd.display, hostWinId, RevertToPointerRoot, CurrentTime);
  391. XRaiseWindow(sd.display, lastGoodWindow);
  392. XSetInputFocus(sd.display, lastGoodWindow, RevertToPointerRoot, CurrentTime);
  393. XFlush(sd.display);
  394. return true;
  395. #else
  396. return true;
  397. (void)pid;
  398. #endif
  399. }
  400. // -----------------------------------------------------
  401. #ifdef CARLA_OS_MAC
  402. CarlaPluginUI* CarlaPluginUI::newCocoa(CloseCallback*, uintptr_t)
  403. {
  404. //return new CocoaPluginUi(cb, parentId, false);
  405. return nullptr;
  406. }
  407. #endif
  408. #ifdef CARLA_OS_WIN
  409. CarlaPluginUI* CarlaPluginUI::newWindows(CloseCallback*, uintptr_t)
  410. {
  411. //return new WindowsPluginUi(cb, parentId, false);
  412. return nullptr;
  413. }
  414. #endif
  415. #ifdef HAVE_X11
  416. CarlaPluginUI* CarlaPluginUI::newX11(CloseCallback* cb, uintptr_t parentId, bool isResizable)
  417. {
  418. return new X11PluginUI(cb, parentId, isResizable);
  419. }
  420. #endif
  421. // -----------------------------------------------------