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.

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