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.

327 lines
9.5KB

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