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.

411 lines
13KB

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