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.

363 lines
12KB

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