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.

532 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(event.xconfigure.width, event.xconfigure.height);
  148. break;
  149. case ClientMessage:
  150. type = XGetAtomName(fDisplay, event.xclient.message_type);
  151. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  152. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  153. {
  154. fIsVisible = false;
  155. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  156. fCallback->handlePluginUIClosed();
  157. }
  158. break;
  159. case KeyRelease:
  160. if (event.xkey.keycode == X11Key_Escape)
  161. {
  162. fIsVisible = false;
  163. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  164. fCallback->handlePluginUIClosed();
  165. }
  166. break;
  167. }
  168. if (type != nullptr)
  169. XFree(type);
  170. else if (fEventProc != nullptr)
  171. fEventProc(&event);
  172. }
  173. fIsIdling = false;
  174. }
  175. void focus() override
  176. {
  177. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  178. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  179. XRaiseWindow(fDisplay, fWindow);
  180. XSetInputFocus(fDisplay, fWindow, RevertToPointerRoot, CurrentTime);
  181. XFlush(fDisplay);
  182. }
  183. void setSize(const uint width, const uint height, const bool forceUpdate) override
  184. {
  185. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  186. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  187. XResizeWindow(fDisplay, fWindow, width, height);
  188. if (! fIsResizable)
  189. {
  190. XSizeHints sizeHints;
  191. carla_zeroStruct<XSizeHints>(sizeHints);
  192. sizeHints.flags = PSize|PMinSize|PMaxSize;
  193. sizeHints.width = static_cast<int>(width);
  194. sizeHints.height = static_cast<int>(height);
  195. sizeHints.min_width = static_cast<int>(width);
  196. sizeHints.min_height = static_cast<int>(height);
  197. sizeHints.max_width = static_cast<int>(width);
  198. sizeHints.max_height = static_cast<int>(height);
  199. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  200. }
  201. if (forceUpdate)
  202. XFlush(fDisplay);
  203. }
  204. void setTitle(const char* const title) override
  205. {
  206. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  207. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  208. XStoreName(fDisplay, fWindow, title);
  209. }
  210. void setTransientWinId(const uintptr_t winId) override
  211. {
  212. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  213. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  214. XSetTransientForHint(fDisplay, fWindow, static_cast<Window>(winId));
  215. }
  216. void* getPtr() const noexcept
  217. {
  218. return (void*)fWindow;
  219. }
  220. void* getDisplay() const noexcept
  221. {
  222. return fDisplay;
  223. }
  224. protected:
  225. Window getChildWindow() const
  226. {
  227. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr, 0);
  228. CARLA_SAFE_ASSERT_RETURN(fWindow != 0, 0);
  229. Window rootWindow, parentWindow, ret = 0;
  230. Window* childWindows = nullptr;
  231. uint numChildren = 0;
  232. XQueryTree(fDisplay, fWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  233. if (numChildren > 0 && childWindows != nullptr)
  234. {
  235. ret = childWindows[0];
  236. XFree(childWindows);
  237. }
  238. return ret;
  239. }
  240. private:
  241. Display* fDisplay;
  242. Window fWindow;
  243. bool fIsVisible;
  244. bool fFirstShow;
  245. EventProcPtr fEventProc;
  246. CARLA_DECLARE_NON_COPY_CLASS(X11PluginUI)
  247. };
  248. #endif
  249. // -----------------------------------------------------
  250. bool CarlaPluginUI::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId, const bool centerUI)
  251. {
  252. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  253. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  254. #if defined(CARLA_OS_MAC)
  255. return true;
  256. (void)pid; (void)centerUI;
  257. #elif defined(CARLA_OS_WIN)
  258. return true;
  259. (void)pid; (void)centerUI;
  260. #elif defined(HAVE_X11)
  261. struct ScopedDisplay {
  262. Display* display;
  263. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  264. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  265. // c++ compat stuff
  266. CARLA_PREVENT_HEAP_ALLOCATION
  267. CARLA_DECLARE_NON_COPY_CLASS(ScopedDisplay)
  268. };
  269. struct ScopedFreeData {
  270. union {
  271. char* data;
  272. uchar* udata;
  273. };
  274. ScopedFreeData(char* d) : data(d) {}
  275. ScopedFreeData(uchar* d) : udata(d) {}
  276. ~ScopedFreeData() { XFree(data); }
  277. // c++ compat stuff
  278. CARLA_PREVENT_HEAP_ALLOCATION
  279. CARLA_DECLARE_NON_COPY_CLASS(ScopedFreeData)
  280. };
  281. const ScopedDisplay sd;
  282. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  283. const Window rootWindow(DefaultRootWindow(sd.display));
  284. const Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , False);
  285. const Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", False);
  286. const Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", False);
  287. const Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  288. Atom actualType;
  289. int actualFormat;
  290. ulong numWindows, bytesAfter;
  291. uchar* data = nullptr;
  292. int status = XGetWindowProperty(sd.display, rootWindow, _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  293. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  294. const ScopedFreeData sfd(data);
  295. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  296. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  297. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  298. Window* windows = (Window*)data;
  299. Window lastGoodWindow = 0;
  300. for (ulong i = 0; i < numWindows; i++)
  301. {
  302. const Window window(windows[i]);
  303. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  304. // ------------------------------------------------
  305. // try using pid
  306. if (pid != 0)
  307. {
  308. ulong pidSize;
  309. uchar* pidData = nullptr;
  310. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  311. if (pidData != nullptr)
  312. {
  313. const ScopedFreeData sfd2(pidData);
  314. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  315. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  316. if (*(ulong*)pidData == static_cast<ulong>(pid))
  317. {
  318. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  319. lastGoodWindow = window;
  320. carla_stdout("Match found using pid");
  321. break;
  322. }
  323. }
  324. }
  325. // ------------------------------------------------
  326. // try using name (UTF-8)
  327. ulong nameSize;
  328. uchar* nameData = nullptr;
  329. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  330. if (nameData != nullptr)
  331. {
  332. const ScopedFreeData sfd2(nameData);
  333. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  334. CARLA_SAFE_ASSERT_CONTINUE(nameSize != 0);
  335. if (std::strstr((const char*)nameData, uiTitle) != nullptr)
  336. {
  337. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  338. lastGoodWindow = window;
  339. carla_stdout("Match found using UTF-8 name");
  340. }
  341. }
  342. // ------------------------------------------------
  343. // try using name (simple)
  344. char* wmName = nullptr;
  345. status = XFetchName(sd.display, window, &wmName);
  346. if (wmName != nullptr)
  347. {
  348. const ScopedFreeData sfd2(wmName);
  349. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  350. if (std::strstr(wmName, uiTitle) != nullptr)
  351. {
  352. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  353. lastGoodWindow = window;
  354. carla_stdout("Match found using simple name");
  355. }
  356. }
  357. }
  358. if (lastGoodWindow == 0)
  359. return false;
  360. const Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", False);
  361. const Atom _nws[2] = {
  362. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", False),
  363. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", False)
  364. };
  365. XChangeProperty(sd.display, lastGoodWindow, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  366. const Atom _nwi = XInternAtom(sd.display, "_NET_WM_ICON", False);
  367. XChangeProperty(sd.display, lastGoodWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  368. const Window hostWinId((Window)winId);
  369. XSetTransientForHint(sd.display, lastGoodWindow, hostWinId);
  370. if (centerUI)
  371. {
  372. int hostX, hostY, pluginX, pluginY;
  373. uint hostWidth, hostHeight, pluginWidth, pluginHeight, border, depth;
  374. Window retWindow;
  375. if (XGetGeometry(sd.display, hostWinId, &retWindow, &hostX, &hostY, &hostWidth, &hostHeight, &border, &depth) != 0 &&
  376. XGetGeometry(sd.display, lastGoodWindow, &retWindow, &pluginX, &pluginY, &pluginWidth, &pluginHeight, &border, &depth) != 0)
  377. {
  378. if (XTranslateCoordinates(sd.display, hostWinId, rootWindow, hostX, hostY, &hostX, &hostY, &retWindow) == True &&
  379. XTranslateCoordinates(sd.display, lastGoodWindow, rootWindow, pluginX, pluginY, &pluginX, &pluginY, &retWindow) == True)
  380. {
  381. const int newX = hostX + int(hostWidth/2 - pluginWidth/2);
  382. const int newY = hostY + int(hostHeight/2 - pluginHeight/2);
  383. XMoveWindow(sd.display, lastGoodWindow, newX, newY);
  384. }
  385. }
  386. }
  387. // focusing the host UI and then the plugin UI forces the WM to repaint the plugin window icon
  388. XRaiseWindow(sd.display, hostWinId);
  389. XSetInputFocus(sd.display, hostWinId, RevertToPointerRoot, CurrentTime);
  390. XRaiseWindow(sd.display, lastGoodWindow);
  391. XSetInputFocus(sd.display, lastGoodWindow, RevertToPointerRoot, CurrentTime);
  392. XFlush(sd.display);
  393. return true;
  394. #else
  395. return true;
  396. (void)pid;
  397. #endif
  398. }
  399. // -----------------------------------------------------
  400. #ifdef CARLA_OS_MAC
  401. CarlaPluginUI* CarlaPluginUI::newCocoa(CloseCallback*, uintptr_t)
  402. {
  403. //return new CocoaPluginUi(cb, parentId, false);
  404. return nullptr;
  405. }
  406. #endif
  407. #ifdef CARLA_OS_WIN
  408. CarlaPluginUI* CarlaPluginUI::newWindows(CloseCallback*, uintptr_t)
  409. {
  410. //return new WindowsPluginUi(cb, parentId, false);
  411. return nullptr;
  412. }
  413. #endif
  414. #ifdef HAVE_X11
  415. CarlaPluginUI* CarlaPluginUI::newX11(CloseCallback* cb, uintptr_t parentId, bool isResizable)
  416. {
  417. return new X11PluginUI(cb, parentId, isResizable);
  418. }
  419. #endif
  420. // -----------------------------------------------------