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.

423 lines
14KB

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