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.

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