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.

356 lines
11KB

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