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.

510 lines
15KB

  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) noexcept
  40. : CarlaPluginUI(cb),
  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. fWindow = XCreateWindow(fDisplay, RootWindow(fDisplay, screen),
  55. 0, 0, 300, 300, 0,
  56. DefaultDepth(fDisplay, screen),
  57. InputOutput,
  58. DefaultVisual(fDisplay, screen),
  59. CWBorderPixel|CWEventMask, &attr);
  60. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  61. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fWindow, 1, GrabModeAsync, GrabModeAsync);
  62. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  63. XSetWMProtocols(fDisplay, fWindow, &wmDelete, 1);
  64. const pid_t pid = getpid();
  65. const Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", False);
  66. XChangeProperty(fDisplay, fWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  67. const Atom _nwi = XInternAtom(fDisplay, "_NET_WM_ICON", False);
  68. XChangeProperty(fDisplay, fWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  69. if (parentId != 0)
  70. setTransientWinId(parentId);
  71. }
  72. ~X11PluginUi() override
  73. {
  74. CARLA_SAFE_ASSERT(! fIsVisible);
  75. if (fIsVisible)
  76. {
  77. XUnmapWindow(fDisplay, fWindow);
  78. fIsVisible = false;
  79. }
  80. if (fWindow != 0)
  81. {
  82. XDestroyWindow(fDisplay, fWindow);
  83. fWindow = 0;
  84. }
  85. if (fDisplay != nullptr)
  86. {
  87. XCloseDisplay(fDisplay);
  88. fDisplay = nullptr;
  89. }
  90. }
  91. void show() override
  92. {
  93. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  94. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  95. if (fFirstShow)
  96. {
  97. if (const Window childWindow = getChildWindow())
  98. {
  99. const Atom _xevp = XInternAtom(fDisplay, "_XEventProc", False);
  100. gErrorTriggered = false;
  101. const XErrorHandler oldErrorHandler(XSetErrorHandler(temporaryErrorHandler));
  102. Atom actualType;
  103. int actualFormat;
  104. ulong nitems, bytesAfter;
  105. uchar* data = nullptr;
  106. XGetWindowProperty(fDisplay, childWindow, _xevp, 0, 1, False, AnyPropertyType, &actualType, &actualFormat, &nitems, &bytesAfter, &data);
  107. XSetErrorHandler(oldErrorHandler);
  108. if (nitems == 1 && ! gErrorTriggered)
  109. {
  110. fEventProc = *reinterpret_cast<EventProcPtr*>(data);
  111. XMapRaised(fDisplay, childWindow);
  112. }
  113. }
  114. }
  115. fIsVisible = true;
  116. fFirstShow = false;
  117. XMapRaised(fDisplay, fWindow);
  118. XFlush(fDisplay);
  119. }
  120. void hide() override
  121. {
  122. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  123. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  124. fIsVisible = false;
  125. XUnmapWindow(fDisplay, fWindow);
  126. XFlush(fDisplay);
  127. }
  128. void idle() override
  129. {
  130. // prevent recursion
  131. if (fIsIdling) return;
  132. fIsIdling = true;
  133. for (XEvent event; XPending(fDisplay) > 0;)
  134. {
  135. XNextEvent(fDisplay, &event);
  136. if (! fIsVisible)
  137. continue;
  138. char* type = nullptr;
  139. switch (event.type)
  140. {
  141. case ClientMessage:
  142. type = XGetAtomName(fDisplay, event.xclient.message_type);
  143. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  144. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  145. {
  146. fIsVisible = false;
  147. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  148. fCallback->handlePluginUIClosed();
  149. }
  150. break;
  151. case KeyRelease:
  152. if (event.xkey.keycode == X11Key_Escape)
  153. {
  154. fIsVisible = false;
  155. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  156. fCallback->handlePluginUIClosed();
  157. }
  158. break;
  159. }
  160. if (type != nullptr)
  161. XFree(type);
  162. else if (fEventProc != nullptr)
  163. fEventProc(&event);
  164. }
  165. fIsIdling = false;
  166. }
  167. void focus() override
  168. {
  169. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  170. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  171. XRaiseWindow(fDisplay, fWindow);
  172. XSetInputFocus(fDisplay, fWindow, RevertToPointerRoot, CurrentTime);
  173. XFlush(fDisplay);
  174. }
  175. void setSize(const uint width, const uint height, const bool forceUpdate) override
  176. {
  177. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  178. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  179. XResizeWindow(fDisplay, fWindow, width, height);
  180. XSizeHints sizeHints;
  181. carla_zeroStruct<XSizeHints>(sizeHints);
  182. sizeHints.flags = PSize|PMinSize|PMaxSize;
  183. sizeHints.width = static_cast<int>(width);
  184. sizeHints.height = static_cast<int>(height);
  185. sizeHints.min_width = static_cast<int>(width);
  186. sizeHints.min_height = static_cast<int>(height);
  187. sizeHints.max_width = static_cast<int>(width);
  188. sizeHints.max_height = static_cast<int>(height);
  189. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  190. if (forceUpdate)
  191. XFlush(fDisplay);
  192. }
  193. void setTitle(const char* const title) override
  194. {
  195. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  196. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  197. XStoreName(fDisplay, fWindow, title);
  198. }
  199. void setTransientWinId(const uintptr_t winId) override
  200. {
  201. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  202. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  203. XSetTransientForHint(fDisplay, fWindow, static_cast<Window>(winId));
  204. }
  205. void* getPtr() const noexcept
  206. {
  207. return (void*)fWindow;
  208. }
  209. void* getDisplay() const noexcept
  210. {
  211. return fDisplay;
  212. }
  213. protected:
  214. Window getChildWindow() const
  215. {
  216. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr, 0);
  217. CARLA_SAFE_ASSERT_RETURN(fWindow != 0, 0);
  218. Window rootWindow, parentWindow, ret = 0;
  219. Window* childWindows = nullptr;
  220. uint numChildren = 0;
  221. XQueryTree(fDisplay, fWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  222. if (numChildren > 0 && childWindows != nullptr)
  223. {
  224. ret = childWindows[0];
  225. XFree(childWindows);
  226. }
  227. return ret;
  228. }
  229. private:
  230. Display* fDisplay;
  231. Window fWindow;
  232. bool fIsVisible;
  233. bool fFirstShow;
  234. EventProcPtr fEventProc;
  235. };
  236. #endif
  237. // -----------------------------------------------------
  238. bool CarlaPluginUI::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId, const bool centerUI)
  239. {
  240. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  241. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  242. #if defined(CARLA_OS_MAC)
  243. return true;
  244. (void)pid; (void)centerUI;
  245. #elif defined(CARLA_OS_WIN)
  246. return true;
  247. (void)pid; (void)centerUI;
  248. #elif defined(HAVE_X11)
  249. struct ScopedDisplay {
  250. Display* display;
  251. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  252. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  253. };
  254. struct ScopedFreeData {
  255. union {
  256. char* data;
  257. uchar* udata;
  258. };
  259. ScopedFreeData(char* d) : data(d) {}
  260. ScopedFreeData(uchar* d) : udata(d) {}
  261. ~ScopedFreeData() { XFree(data); }
  262. };
  263. const ScopedDisplay sd;
  264. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  265. const Window rootWindow(DefaultRootWindow(sd.display));
  266. const Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , False);
  267. const Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", False);
  268. const Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", False);
  269. const Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  270. Atom actualType;
  271. int actualFormat;
  272. ulong numWindows, bytesAfter;
  273. uchar* data = nullptr;
  274. int status = XGetWindowProperty(sd.display, rootWindow, _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  275. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  276. const ScopedFreeData sfd(data);
  277. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  278. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  279. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  280. Window* windows = (Window*)data;
  281. Window lastGoodWindow = 0;
  282. for (ulong i = 0; i < numWindows; i++)
  283. {
  284. const Window window(windows[i]);
  285. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  286. // ------------------------------------------------
  287. // try using pid
  288. if (pid != 0)
  289. {
  290. ulong pidSize;
  291. uchar* pidData = nullptr;
  292. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  293. if (pidData != nullptr)
  294. {
  295. const ScopedFreeData sfd2(pidData);
  296. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  297. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  298. if (*(ulong*)pidData == static_cast<ulong>(pid))
  299. {
  300. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  301. lastGoodWindow = window;
  302. carla_stdout("Match found using pid");
  303. break;
  304. }
  305. }
  306. }
  307. // ------------------------------------------------
  308. // try using name (UTF-8)
  309. ulong nameSize;
  310. uchar* nameData = nullptr;
  311. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  312. if (nameData != nullptr)
  313. {
  314. const ScopedFreeData sfd2(nameData);
  315. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  316. CARLA_SAFE_ASSERT_CONTINUE(nameSize != 0);
  317. if (std::strstr((const char*)nameData, uiTitle) != nullptr)
  318. {
  319. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  320. lastGoodWindow = window;
  321. carla_stdout("Match found using UTF-8 name");
  322. }
  323. }
  324. // ------------------------------------------------
  325. // try using name (simple)
  326. char* wmName = nullptr;
  327. status = XFetchName(sd.display, window, &wmName);
  328. if (wmName != nullptr)
  329. {
  330. const ScopedFreeData sfd2(wmName);
  331. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  332. if (std::strstr(wmName, uiTitle) != nullptr)
  333. {
  334. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  335. lastGoodWindow = window;
  336. carla_stdout("Match found using simple name");
  337. }
  338. }
  339. }
  340. if (lastGoodWindow == 0)
  341. return false;
  342. const Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", False);
  343. const Atom _nws[2] = {
  344. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", False),
  345. XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", False)
  346. };
  347. XChangeProperty(sd.display, lastGoodWindow, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  348. const Atom _nwi = XInternAtom(sd.display, "_NET_WM_ICON", False);
  349. XChangeProperty(sd.display, lastGoodWindow, _nwi, XA_CARDINAL, 32, PropModeReplace, (const uchar*)sCarlaX11Icon, sCarlaX11IconSize);
  350. const Window hostWinId((Window)winId);
  351. XSetTransientForHint(sd.display, lastGoodWindow, hostWinId);
  352. if (centerUI)
  353. {
  354. int hostX, hostY, pluginX, pluginY;
  355. uint hostWidth, hostHeight, pluginWidth, pluginHeight, border, depth;
  356. Window retWindow;
  357. if (XGetGeometry(sd.display, hostWinId, &retWindow, &hostX, &hostY, &hostWidth, &hostHeight, &border, &depth) != 0 &&
  358. XGetGeometry(sd.display, lastGoodWindow, &retWindow, &pluginX, &pluginY, &pluginWidth, &pluginHeight, &border, &depth) != 0)
  359. {
  360. if (XTranslateCoordinates(sd.display, hostWinId, rootWindow, hostX, hostY, &hostX, &hostY, &retWindow) == True &&
  361. XTranslateCoordinates(sd.display, lastGoodWindow, rootWindow, pluginX, pluginY, &pluginX, &pluginY, &retWindow) == True)
  362. {
  363. const int newX = hostX + int(hostWidth/2 - pluginWidth/2);
  364. const int newY = hostY + int(hostHeight/2 - pluginHeight/2);
  365. XMoveWindow(sd.display, lastGoodWindow, newX, newY);
  366. }
  367. }
  368. }
  369. // focusing the host UI and then the plugin UI forces the WM to repaint the plugin window icon
  370. XRaiseWindow(sd.display, hostWinId);
  371. XSetInputFocus(sd.display, hostWinId, RevertToPointerRoot, CurrentTime);
  372. XRaiseWindow(sd.display, lastGoodWindow);
  373. XSetInputFocus(sd.display, lastGoodWindow, RevertToPointerRoot, CurrentTime);
  374. XFlush(sd.display);
  375. return true;
  376. #else
  377. return true;
  378. (void)pid;
  379. #endif
  380. }
  381. // -----------------------------------------------------
  382. #ifdef CARLA_OS_MAC
  383. CarlaPluginUI* CarlaPluginUI::newCocoa(CloseCallback*, uintptr_t)
  384. {
  385. //return new CocoaPluginUi(cb, parentId);
  386. return nullptr;
  387. }
  388. #endif
  389. #ifdef CARLA_OS_WIN
  390. CarlaPluginUI* CarlaPluginUI::newWindows(CloseCallback*, uintptr_t)
  391. {
  392. //return new WindowsPluginUi(cb, parentId);
  393. return nullptr;
  394. }
  395. #endif
  396. #ifdef HAVE_X11
  397. CarlaPluginUI* CarlaPluginUI::newX11(CloseCallback* cb, uintptr_t parentId)
  398. {
  399. return new X11PluginUi(cb, parentId);
  400. }
  401. #endif
  402. // -----------------------------------------------------