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.

350 lines
10KB

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