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.

CarlaPluginUI.cpp 15KB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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. // -----------------------------------------------------