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.

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