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.

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