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.

397 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. real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  177. }
  178. gCurrentlyMappedDisplay = display;
  179. gCurrentlyMappedWindow = window;
  180. gCurrentWindowMapped = true;
  181. gCurrentWindowType = fallbackFnType;
  182. if (slo.winId > 0)
  183. XSetTransientForHint(display, window, static_cast<Window>(slo.winId));
  184. if (gCurrentWindowVisible)
  185. {
  186. carla_stdout("JACK application window found, showing it now");
  187. break;
  188. }
  189. gCurrentWindowMapped = false;
  190. carla_stdout("JACK application window found and captured");
  191. return 0;
  192. }
  193. carla_debug("carlaWindowMap(%p, %lu, %i) - not captured", display, window, fallbackFnType);
  194. switch (fallbackFnType)
  195. {
  196. case 1:
  197. return real_XMapWindow(display, window);
  198. case 2:
  199. return real_XMapRaised(display, window);
  200. case 3:
  201. return real_XMapSubwindows(display, window);
  202. default:
  203. return 0;
  204. }
  205. }
  206. // --------------------------------------------------------------------------------------------------------------------
  207. // Our custom X11 functions
  208. CARLA_EXPORT
  209. int XMapWindow(Display* display, Window window)
  210. {
  211. carla_debug("XMapWindow(%p, %lu)", display, window);
  212. return carlaWindowMap(display, window, 1);
  213. }
  214. CARLA_EXPORT
  215. int XMapRaised(Display* display, Window window)
  216. {
  217. carla_debug("XMapRaised(%p, %lu)", display, window);
  218. return carlaWindowMap(display, window, 2);
  219. }
  220. CARLA_EXPORT
  221. int XMapSubwindows(Display* display, Window window)
  222. {
  223. carla_debug("XMapSubwindows(%p, %lu)", display, window);
  224. return carlaWindowMap(display, window, 3);
  225. }
  226. CARLA_EXPORT
  227. int XUnmapWindow(Display* display, Window window)
  228. {
  229. carla_debug("XUnmapWindow(%p, %lu)", display, window);
  230. if (gCurrentlyMappedWindow == window)
  231. {
  232. gCurrentlyMappedDisplay = nullptr;
  233. gCurrentlyMappedWindow = 0;
  234. gCurrentWindowType = 0;
  235. gCurrentWindowMapped = false;
  236. gCurrentWindowVisible = false;
  237. if (gInterposedCallback != nullptr)
  238. gInterposedCallback(1, nullptr);
  239. }
  240. return real_XUnmapWindow(display, window);
  241. }
  242. CARLA_EXPORT
  243. int XNextEvent(Display* display, XEvent* event)
  244. {
  245. const int ret = real_XNextEvent(display, event);
  246. if (ret != 0)
  247. return ret;
  248. if (gCurrentlyMappedWindow == 0)
  249. return ret;
  250. if (event->type != ClientMessage)
  251. return ret;
  252. if (event->xclient.window != gCurrentlyMappedWindow)
  253. return ret;
  254. char* const type = XGetAtomName(display, event->xclient.message_type);
  255. CARLA_SAFE_ASSERT_RETURN(type != nullptr, 0);
  256. if (std::strcmp(type, "WM_PROTOCOLS") != 0)
  257. return ret;
  258. if (event->xclient.data.l[0] != XInternAtom(display, "WM_DELETE_WINDOW", False))
  259. return ret;
  260. gCurrentWindowVisible = false;
  261. gCurrentWindowMapped = false;
  262. if (gInterposedCallback != nullptr)
  263. gInterposedCallback(1, nullptr);
  264. event->type = 0;
  265. carla_stdout("XNextEvent close event catched, hiding UI instead");
  266. return real_XUnmapWindow(display, gCurrentlyMappedWindow);
  267. }
  268. // --------------------------------------------------------------------------------------------------------------------
  269. // Full control helper
  270. CARLA_EXPORT
  271. int jack_carla_interposed_action(int action, int value, void* ptr)
  272. {
  273. carla_debug("jack_carla_interposed_action(%i, %i, %p)", action, value, ptr);
  274. switch (action)
  275. {
  276. case 1:
  277. // set hints and callback
  278. gInterposedHints = value;
  279. gInterposedCallback = (CarlaInterposedCallback)ptr;
  280. return 1;
  281. case 2:
  282. // session manager
  283. gInterposedSessionManager = value;
  284. return 1;
  285. case 3:
  286. // show gui
  287. if (value != 0)
  288. {
  289. gCurrentWindowVisible = true;
  290. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  291. return 0;
  292. gCurrentWindowMapped = true;
  293. switch (gCurrentWindowType)
  294. {
  295. case 1:
  296. return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  297. case 2:
  298. return real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  299. case 3:
  300. return real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  301. default:
  302. return 0;
  303. }
  304. }
  305. // hide gui
  306. else
  307. {
  308. gCurrentWindowVisible = false;
  309. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  310. return 0;
  311. gCurrentWindowMapped = false;
  312. return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  313. }
  314. break;
  315. case 4: // close everything
  316. gCurrentWindowType = 0;
  317. gCurrentWindowMapped = false;
  318. gCurrentWindowVisible = false;
  319. gCurrentlyMappedDisplay = nullptr;
  320. gCurrentlyMappedWindow = 0;
  321. return 0;
  322. }
  323. return -1;
  324. }
  325. // --------------------------------------------------------------------------------------------------------------------