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.

394 lines
12KB

  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. // -----------------------------------------------------
  26. // X11
  27. static const int X11Key_Escape = 9;
  28. class X11PluginUi : public CarlaPluginUi
  29. {
  30. public:
  31. X11PluginUi(CloseCallback* const cb, const uintptr_t parentId) noexcept
  32. : CarlaPluginUi(cb),
  33. fDisplay(nullptr),
  34. fWindow(0),
  35. fIsVisible(false)
  36. {
  37. fDisplay = XOpenDisplay(nullptr);
  38. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  39. const int screen = DefaultScreen(fDisplay);
  40. XSetWindowAttributes attr;
  41. carla_zeroStruct<XSetWindowAttributes>(attr);
  42. attr.border_pixel = 0;
  43. attr.event_mask = KeyPressMask|KeyReleaseMask;
  44. fWindow = XCreateWindow(fDisplay, RootWindow(fDisplay, screen),
  45. 0, 0, 300, 300, 0,
  46. DefaultDepth(fDisplay, screen),
  47. InputOutput,
  48. DefaultVisual(fDisplay, screen),
  49. CWBorderPixel|CWEventMask, &attr);
  50. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  51. XGrabKey(fDisplay, X11Key_Escape, AnyModifier, fWindow, 1, GrabModeAsync, GrabModeAsync);
  52. Atom wmDelete = XInternAtom(fDisplay, "WM_DELETE_WINDOW", True);
  53. XSetWMProtocols(fDisplay, fWindow, &wmDelete, 1);
  54. pid_t pid = getpid();
  55. Atom _nwp = XInternAtom(fDisplay, "_NET_WM_PID", True);
  56. XChangeProperty(fDisplay, fWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);
  57. if (parentId != 0)
  58. setTransientWinId(parentId);
  59. }
  60. ~X11PluginUi() override
  61. {
  62. CARLA_SAFE_ASSERT(! fIsVisible);
  63. if (fIsVisible)
  64. {
  65. XUnmapWindow(fDisplay, fWindow);
  66. fIsVisible = false;
  67. }
  68. if (fWindow != 0)
  69. {
  70. XDestroyWindow(fDisplay, fWindow);
  71. fWindow = 0;
  72. }
  73. if (fDisplay != nullptr)
  74. {
  75. XCloseDisplay(fDisplay);
  76. fDisplay = nullptr;
  77. }
  78. }
  79. void show() override
  80. {
  81. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  82. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  83. fIsVisible = true;
  84. XMapRaised(fDisplay, fWindow);
  85. XFlush(fDisplay);
  86. }
  87. void hide() override
  88. {
  89. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  90. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  91. fIsVisible = false;
  92. XUnmapWindow(fDisplay, fWindow);
  93. XFlush(fDisplay);
  94. }
  95. void idle() override
  96. {
  97. for (XEvent event; XPending(fDisplay) > 0;)
  98. {
  99. XNextEvent(fDisplay, &event);
  100. if (! fIsVisible)
  101. continue;
  102. char* type = nullptr;
  103. switch (event.type)
  104. {
  105. case ClientMessage:
  106. type = XGetAtomName(fDisplay, event.xclient.message_type);
  107. CARLA_SAFE_ASSERT_CONTINUE(type != nullptr);
  108. if (std::strcmp(type, "WM_PROTOCOLS") == 0)
  109. {
  110. fIsVisible = false;
  111. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  112. fCallback->handlePluginUiClosed();
  113. }
  114. break;
  115. case KeyRelease:
  116. if (event.xkey.keycode == X11Key_Escape)
  117. {
  118. fIsVisible = false;
  119. CARLA_SAFE_ASSERT_CONTINUE(fCallback != nullptr);
  120. fCallback->handlePluginUiClosed();
  121. }
  122. break;
  123. }
  124. if (type != nullptr)
  125. XFree(type);
  126. }
  127. }
  128. void focus() override
  129. {
  130. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  131. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  132. XRaiseWindow(fDisplay, fWindow);
  133. XSetInputFocus(fDisplay, fWindow, RevertToPointerRoot, CurrentTime);
  134. XFlush(fDisplay);
  135. }
  136. void setSize(const uint width, const uint height, const bool forceUpdate) override
  137. {
  138. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  139. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  140. XResizeWindow(fDisplay, fWindow, width, height);
  141. XSizeHints sizeHints;
  142. carla_zeroStruct<XSizeHints>(sizeHints);
  143. sizeHints.flags = PSize|PMinSize|PMaxSize;
  144. sizeHints.width = static_cast<int>(width);
  145. sizeHints.height = static_cast<int>(height);
  146. sizeHints.min_width = static_cast<int>(width);
  147. sizeHints.min_height = static_cast<int>(height);
  148. sizeHints.max_width = static_cast<int>(width);
  149. sizeHints.max_height = static_cast<int>(height);
  150. XSetNormalHints(fDisplay, fWindow, &sizeHints);
  151. if (forceUpdate)
  152. XFlush(fDisplay);
  153. }
  154. void setTitle(const char* const title) override
  155. {
  156. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  157. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  158. XStoreName(fDisplay, fWindow, title);
  159. }
  160. void setTransientWinId(const uintptr_t winId) override
  161. {
  162. CARLA_SAFE_ASSERT_RETURN(fDisplay != nullptr,);
  163. CARLA_SAFE_ASSERT_RETURN(fWindow != 0,);
  164. XSetTransientForHint(fDisplay, fWindow, static_cast<Window>(winId));
  165. }
  166. void* getPtr() const noexcept
  167. {
  168. return (void*)fWindow;
  169. }
  170. private:
  171. Display* fDisplay;
  172. Window fWindow;
  173. bool fIsVisible;
  174. };
  175. #endif
  176. // -----------------------------------------------------
  177. bool CarlaPluginUi::tryTransientWinIdMatch(const uintptr_t pid, const char* const uiTitle, const uintptr_t winId)
  178. {
  179. CARLA_SAFE_ASSERT_RETURN(uiTitle != nullptr && uiTitle[0] != '\0', true);
  180. CARLA_SAFE_ASSERT_RETURN(winId != 0, true);
  181. #if defined(CARLA_OS_MAC)
  182. return true;
  183. (void)pid;
  184. #elif defined(CARLA_OS_WIN)
  185. return true;
  186. (void)pid;
  187. #elif defined(HAVE_X11)
  188. struct ScopedDisplay {
  189. Display* display;
  190. ScopedDisplay() : display(XOpenDisplay(nullptr)) {}
  191. ~ScopedDisplay() { if (display!=nullptr) XCloseDisplay(display); }
  192. };
  193. struct ScopedFreeData {
  194. union {
  195. char* data;
  196. uchar* udata;
  197. };
  198. ScopedFreeData(char* d) : data(d) {}
  199. ScopedFreeData(uchar* d) : udata(d) {}
  200. ~ScopedFreeData() { XFree(data); }
  201. };
  202. const ScopedDisplay sd;
  203. CARLA_SAFE_ASSERT_RETURN(sd.display != nullptr, true);
  204. Atom _ncl = XInternAtom(sd.display, "_NET_CLIENT_LIST" , True);
  205. Atom _nwn = XInternAtom(sd.display, "_NET_WM_NAME", True);
  206. Atom _nwp = XInternAtom(sd.display, "_NET_WM_PID", True);
  207. Atom utf8 = XInternAtom(sd.display, "UTF8_STRING", True);
  208. Atom actualType;
  209. int actualFormat;
  210. unsigned long numWindows, bytesAfter;
  211. unsigned char* data = nullptr;
  212. int status = XGetWindowProperty(sd.display, DefaultRootWindow(sd.display), _ncl, 0L, (~0L), False, AnyPropertyType, &actualType, &actualFormat, &numWindows, &bytesAfter, &data);
  213. CARLA_SAFE_ASSERT_RETURN(data != nullptr, true);
  214. const ScopedFreeData sfd(data);
  215. CARLA_SAFE_ASSERT_RETURN(status == Success, true);
  216. CARLA_SAFE_ASSERT_RETURN(actualFormat == 32, true);
  217. CARLA_SAFE_ASSERT_RETURN(numWindows != 0, true);
  218. Window* windows = (Window*)data;
  219. Window lastGoodWindow = 0;
  220. for (ulong i = 0; i < numWindows; i++)
  221. {
  222. const Window window(windows[i]);
  223. CARLA_SAFE_ASSERT_CONTINUE(window != 0);
  224. // ------------------------------------------------
  225. // try using pid
  226. if (pid != 0)
  227. {
  228. unsigned long pidSize;
  229. unsigned char* pidData = nullptr;
  230. status = XGetWindowProperty(sd.display, window, _nwp, 0L, (~0L), False, XA_CARDINAL, &actualType, &actualFormat, &pidSize, &bytesAfter, &pidData);
  231. if (pidData != nullptr)
  232. {
  233. const ScopedFreeData sfd2(pidData);
  234. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  235. CARLA_SAFE_ASSERT_CONTINUE(pidSize != 0);
  236. if (*(ulong*)pidData == static_cast<ulong>(pid))
  237. {
  238. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  239. lastGoodWindow = window;
  240. carla_stdout("Match found using pid");
  241. break;
  242. }
  243. }
  244. }
  245. // ------------------------------------------------
  246. // try using name (UTF-8)
  247. unsigned long nameSize;
  248. unsigned char* nameData = nullptr;
  249. status = XGetWindowProperty(sd.display, window, _nwn, 0L, (~0L), False, utf8, &actualType, &actualFormat, &nameSize, &bytesAfter, &nameData);
  250. if (nameData != nullptr)
  251. {
  252. const ScopedFreeData sfd2(nameData);
  253. CARLA_SAFE_ASSERT_CONTINUE(status == Success);
  254. CARLA_SAFE_ASSERT_CONTINUE(nameSize != 0);
  255. if (std::strstr((const char*)nameData, uiTitle) != nullptr)
  256. {
  257. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  258. lastGoodWindow = window;
  259. carla_stdout("Match found using UTF-8 name");
  260. }
  261. }
  262. // ------------------------------------------------
  263. // try using name (simple)
  264. char* wmName = nullptr;
  265. status = XFetchName(sd.display, window, &wmName);
  266. if (wmName != nullptr)
  267. {
  268. const ScopedFreeData sfd2(wmName);
  269. CARLA_SAFE_ASSERT_CONTINUE(status != 0);
  270. if (std::strstr(wmName, uiTitle) != nullptr)
  271. {
  272. CARLA_SAFE_ASSERT_RETURN(lastGoodWindow == window || lastGoodWindow == 0, true);
  273. lastGoodWindow = window;
  274. carla_stdout("Match found using simple name");
  275. }
  276. }
  277. }
  278. if (lastGoodWindow == 0)
  279. return false;
  280. Atom _nwt = XInternAtom(sd.display ,"_NET_WM_STATE", True);
  281. Atom _nws[2];
  282. _nws[0] = XInternAtom(sd.display, "_NET_WM_STATE_SKIP_TASKBAR", True);
  283. _nws[1] = XInternAtom(sd.display, "_NET_WM_STATE_SKIP_PAGER", True);
  284. XChangeProperty(sd.display, lastGoodWindow, _nwt, XA_ATOM, 32, PropModeAppend, (const uchar*)_nws, 2);
  285. XSetTransientForHint(sd.display, lastGoodWindow, (Window)winId);
  286. XFlush(sd.display);
  287. return true;
  288. #else
  289. return true;
  290. (void)pid;
  291. #endif
  292. }
  293. // -----------------------------------------------------
  294. #ifdef CARLA_OS_MAC
  295. CarlaPluginUi* CarlaPluginUi::newCocoa(CloseCallback*, uintptr_t)
  296. {
  297. //return new CocoaPluginUi(cb, parentId);
  298. return nullptr;
  299. }
  300. #endif
  301. #ifdef CARLA_OS_WIN
  302. CarlaPluginUi* CarlaPluginUi::newWindows(CloseCallback*, uintptr_t)
  303. {
  304. //return new WindowsPluginUi(cb, parentId);
  305. return nullptr;
  306. }
  307. #endif
  308. #ifdef HAVE_X11
  309. CarlaPluginUi* CarlaPluginUi::newX11(CloseCallback* cb, uintptr_t parentId)
  310. {
  311. return new X11PluginUi(cb, parentId);
  312. }
  313. #endif
  314. // -----------------------------------------------------