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.

443 lines
14KB

  1. /*
  2. * Carla Interposer for JACK Applications X11 control
  3. * Copyright (C) 2014-2019 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 "CarlaLibJackHints.h"
  18. #include "CarlaUtils.hpp"
  19. #include <dlfcn.h>
  20. #ifdef HAVE_X11
  21. # include <X11/Xlib.h>
  22. #endif
  23. // --------------------------------------------------------------------------------------------------------------------
  24. struct ScopedLibOpen {
  25. void* handle;
  26. long long winId;
  27. ScopedLibOpen() noexcept
  28. : handle(dlopen("libjack.so.0", RTLD_NOW|RTLD_LOCAL)),
  29. winId(-1)
  30. {
  31. CARLA_SAFE_ASSERT_RETURN(handle != nullptr,);
  32. if (const char* const winIdStr = std::getenv("CARLA_FRONTEND_WIN_ID"))
  33. {
  34. CARLA_SAFE_ASSERT_RETURN(winIdStr[0] != '\0',);
  35. winId = std::strtoll(winIdStr, nullptr, 16);
  36. }
  37. }
  38. ~ScopedLibOpen() noexcept
  39. {
  40. if (handle != nullptr)
  41. {
  42. dlclose(handle);
  43. handle = nullptr;
  44. }
  45. }
  46. static const ScopedLibOpen& getInstance() noexcept
  47. {
  48. static const ScopedLibOpen slo;
  49. return slo;
  50. }
  51. CARLA_DECLARE_NON_COPY_STRUCT(ScopedLibOpen);
  52. };
  53. // --------------------------------------------------------------------------------------------------------------------
  54. // Current state
  55. typedef enum {
  56. WindowMapNone,
  57. WindowMapNormal,
  58. WindowMapRaised,
  59. WindowMapSubwindows
  60. } WindowMappingType;
  61. #ifdef HAVE_X11
  62. static Display* gCurrentlyMappedDisplay = nullptr;
  63. static Window gCurrentlyMappedWindow = 0;
  64. #endif
  65. static CarlaInterposedCallback gInterposedCallback = nullptr;
  66. static uint gInterposedSessionManager = LIBJACK_SESSION_MANAGER_NONE;
  67. static uint gInterposedHints = 0x0;
  68. static WindowMappingType gCurrentWindowType = WindowMapNone;
  69. static bool gCurrentWindowMapped = false;
  70. static bool gCurrentWindowVisible = false;
  71. #ifdef HAVE_X11
  72. // --------------------------------------------------------------------------------------------------------------------
  73. // Function typedefs
  74. typedef int (*XWindowFunc)(Display*, Window);
  75. typedef int (*XNextEventFunc)(Display*, XEvent*);
  76. // --------------------------------------------------------------------------------------------------------------------
  77. // Calling the real X11 functions
  78. static int real_XMapWindow(Display* display, Window window)
  79. {
  80. static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapWindow");
  81. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  82. return func(display, window);
  83. }
  84. static int real_XMapRaised(Display* display, Window window)
  85. {
  86. static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapRaised");
  87. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  88. return func(display, window);
  89. }
  90. static int real_XMapSubwindows(Display* display, Window window)
  91. {
  92. static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapSubwindows");
  93. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  94. return func(display, window);
  95. }
  96. static int real_XUnmapWindow(Display* display, Window window)
  97. {
  98. static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XUnmapWindow");
  99. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  100. return func(display, window);
  101. }
  102. static int real_XNextEvent(Display* display, XEvent* event)
  103. {
  104. static const XNextEventFunc func = (XNextEventFunc)::dlsym(RTLD_NEXT, "XNextEvent");
  105. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  106. return func(display, event);
  107. }
  108. // --------------------------------------------------------------------------------------------------------------------
  109. // Custom carla window handling
  110. static int carlaWindowMap(Display* const display, const Window window, const WindowMappingType fallbackFnType)
  111. {
  112. const ScopedLibOpen& slo(ScopedLibOpen::getInstance());
  113. for (;;)
  114. {
  115. if (slo.winId < 0)
  116. break;
  117. Atom atom;
  118. int atomFormat;
  119. unsigned char* atomPtrs;
  120. unsigned long numItems, ignored;
  121. const Atom wmWindowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
  122. if (XGetWindowProperty(display, window, wmWindowType, 0, ~0L, False, AnyPropertyType,
  123. &atom, &atomFormat, &numItems, &ignored, &atomPtrs) != Success)
  124. {
  125. carla_debug("carlaWindowMap(%p, %lu, %i) - XGetWindowProperty failed", display, window, fallbackFnType);
  126. break;
  127. }
  128. const Atom* const atomValues = (const Atom*)atomPtrs;
  129. bool isMainWindow = (numItems == 0);
  130. for (ulong i=0; i<numItems; ++i)
  131. {
  132. const char* const atomValue(XGetAtomName(display, atomValues[i]));
  133. CARLA_SAFE_ASSERT_CONTINUE(atomValue != nullptr && atomValue[0] != '\0');
  134. if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_COMBO" ) == 0 ||
  135. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DIALOG" ) == 0 ||
  136. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DND" ) == 0 ||
  137. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DOCK" ) == 0 ||
  138. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") == 0 ||
  139. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_MENU" ) == 0 ||
  140. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NOTIFICATION" ) == 0 ||
  141. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_POPUP_MENU" ) == 0 ||
  142. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_SPLASH" ) == 0 ||
  143. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLBAR" ) == 0 ||
  144. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLTIP" ) == 0 ||
  145. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_UTILITY" ) == 0)
  146. {
  147. isMainWindow = false;
  148. continue;
  149. }
  150. if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NORMAL") == 0)
  151. {
  152. // window is good, use it if no other types are set
  153. isMainWindow = true;
  154. }
  155. else
  156. {
  157. carla_stdout("=======================================> %s", atomValue);
  158. }
  159. }
  160. if (! isMainWindow)
  161. {
  162. // this has always bothered me...
  163. if (gCurrentlyMappedWindow != 0 && gCurrentWindowMapped && gCurrentWindowVisible)
  164. XSetTransientForHint(display, window, gCurrentlyMappedWindow);
  165. break;
  166. }
  167. Window transientWindow = 0;
  168. if (XGetTransientForHint(display, window, &transientWindow) == Success && transientWindow != 0)
  169. {
  170. carla_stdout("Window has transient set already, ignoring it");
  171. break;
  172. }
  173. // got a new window, we may need to forget last one
  174. if (gCurrentlyMappedDisplay != nullptr && gCurrentlyMappedWindow != 0)
  175. {
  176. // ignore requests against the current mapped window
  177. if (gCurrentlyMappedWindow == window)
  178. return 0;
  179. // we already have a mapped window, with carla visible button on, should be a dialog of sorts..
  180. if (gCurrentWindowMapped && gCurrentWindowVisible)
  181. {
  182. XSetTransientForHint(display, window, gCurrentlyMappedWindow);
  183. break;
  184. }
  185. // ignore empty windows created after the main one
  186. if (numItems == 0)
  187. break;
  188. carla_stdout("NOTICE: XMapWindow now showing previous window");
  189. switch (gCurrentWindowType)
  190. {
  191. case WindowMapNone:
  192. break;
  193. case WindowMapNormal:
  194. real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  195. break;
  196. case WindowMapRaised:
  197. real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  198. break;
  199. case WindowMapSubwindows:
  200. real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  201. break;
  202. }
  203. }
  204. gCurrentlyMappedDisplay = display;
  205. gCurrentlyMappedWindow = window;
  206. gCurrentWindowMapped = true;
  207. gCurrentWindowType = fallbackFnType;
  208. if (slo.winId > 0)
  209. XSetTransientForHint(display, window, static_cast<Window>(slo.winId));
  210. if (gCurrentWindowVisible)
  211. {
  212. carla_stdout("JACK application window found, showing it now");
  213. break;
  214. }
  215. gCurrentWindowMapped = false;
  216. carla_stdout("JACK application window found and captured");
  217. return 0;
  218. }
  219. carla_debug("carlaWindowMap(%p, %lu, %i) - not captured", display, window, fallbackFnType);
  220. switch (fallbackFnType)
  221. {
  222. case 1:
  223. return real_XMapWindow(display, window);
  224. case 2:
  225. return real_XMapRaised(display, window);
  226. case 3:
  227. return real_XMapSubwindows(display, window);
  228. default:
  229. return 0;
  230. }
  231. }
  232. // --------------------------------------------------------------------------------------------------------------------
  233. // Our custom X11 functions
  234. CARLA_EXPORT
  235. int XMapWindow(Display* display, Window window)
  236. {
  237. carla_debug("XMapWindow(%p, %lu)", display, window);
  238. return carlaWindowMap(display, window, WindowMapNormal);
  239. }
  240. CARLA_EXPORT
  241. int XMapRaised(Display* display, Window window)
  242. {
  243. carla_debug("XMapRaised(%p, %lu)", display, window);
  244. return carlaWindowMap(display, window, WindowMapRaised);
  245. }
  246. CARLA_EXPORT
  247. int XMapSubwindows(Display* display, Window window)
  248. {
  249. carla_debug("XMapSubwindows(%p, %lu)", display, window);
  250. return carlaWindowMap(display, window, WindowMapSubwindows);
  251. }
  252. CARLA_EXPORT
  253. int XUnmapWindow(Display* display, Window window)
  254. {
  255. carla_debug("XUnmapWindow(%p, %lu)", display, window);
  256. if (gCurrentlyMappedWindow == window)
  257. {
  258. gCurrentlyMappedDisplay = nullptr;
  259. gCurrentlyMappedWindow = 0;
  260. gCurrentWindowType = WindowMapNone;
  261. gCurrentWindowMapped = false;
  262. gCurrentWindowVisible = false;
  263. if (gInterposedCallback != nullptr)
  264. gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE, nullptr);
  265. }
  266. return real_XUnmapWindow(display, window);
  267. }
  268. CARLA_EXPORT
  269. int XNextEvent(Display* display, XEvent* event)
  270. {
  271. const int ret = real_XNextEvent(display, event);
  272. if (ret != 0)
  273. return ret;
  274. if (gCurrentlyMappedWindow == 0)
  275. return ret;
  276. if (event->type != ClientMessage)
  277. return ret;
  278. if (event->xclient.window != gCurrentlyMappedWindow)
  279. return ret;
  280. char* const type = XGetAtomName(display, event->xclient.message_type);
  281. CARLA_SAFE_ASSERT_RETURN(type != nullptr, 0);
  282. if (std::strcmp(type, "WM_PROTOCOLS") != 0)
  283. return ret;
  284. if ((Atom)event->xclient.data.l[0] != XInternAtom(display, "WM_DELETE_WINDOW", False))
  285. return ret;
  286. gCurrentWindowVisible = false;
  287. gCurrentWindowMapped = false;
  288. if (gInterposedCallback != nullptr)
  289. gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE, nullptr);
  290. event->type = 0;
  291. carla_stdout("XNextEvent close event caught, hiding UI instead");
  292. return real_XUnmapWindow(display, gCurrentlyMappedWindow);
  293. }
  294. #endif // HAVE_X11
  295. // --------------------------------------------------------------------------------------------------------------------
  296. // Full control helper
  297. CARLA_EXPORT
  298. int jack_carla_interposed_action(uint action, uint value, void* ptr)
  299. {
  300. carla_debug("jack_carla_interposed_action(%i, %i, %p)", action, value, ptr);
  301. switch (action)
  302. {
  303. case LIBJACK_INTERPOSER_ACTION_SET_HINTS_AND_CALLBACK:
  304. gInterposedHints = value;
  305. gInterposedCallback = (CarlaInterposedCallback)ptr;
  306. return 1;
  307. case LIBJACK_INTERPOSER_ACTION_SET_SESSION_MANAGER:
  308. gInterposedSessionManager = value;
  309. return 1;
  310. case LIBJACK_INTERPOSER_ACTION_SHOW_HIDE_GUI:
  311. // show gui
  312. if (value != 0)
  313. {
  314. #ifdef HAVE_X11
  315. gCurrentWindowVisible = true;
  316. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  317. #endif
  318. {
  319. carla_stdout("NOTICE: Interposer show-gui request ignored");
  320. return 0;
  321. }
  322. #ifdef HAVE_X11
  323. gCurrentWindowMapped = true;
  324. switch (gCurrentWindowType)
  325. {
  326. case WindowMapNormal:
  327. return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  328. case WindowMapRaised:
  329. return real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  330. case WindowMapSubwindows:
  331. return real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  332. default:
  333. return 0;
  334. }
  335. #endif
  336. }
  337. // hide gui
  338. else
  339. {
  340. #ifdef HAVE_X11
  341. gCurrentWindowVisible = false;
  342. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  343. #endif
  344. {
  345. carla_stdout("NOTICE: Interposer hide-gui request ignored");
  346. return 0;
  347. }
  348. #ifdef HAVE_X11
  349. gCurrentWindowMapped = false;
  350. return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  351. #endif
  352. }
  353. break;
  354. case LIBJACK_INTERPOSER_ACTION_CLOSE_EVERYTHING:
  355. gCurrentWindowType = WindowMapNone;
  356. gCurrentWindowMapped = false;
  357. gCurrentWindowVisible = false;
  358. #ifdef HAVE_X11
  359. gCurrentlyMappedDisplay = nullptr;
  360. gCurrentlyMappedWindow = 0;
  361. #endif
  362. return 0;
  363. }
  364. return -1;
  365. }
  366. // --------------------------------------------------------------------------------------------------------------------