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.

626 lines
19KB

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