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.

559 lines
17KB

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