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.

355 lines
11KB

  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. real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  170. }
  171. gCurrentlyMappedDisplay = display;
  172. gCurrentlyMappedWindow = window;
  173. gCurrentWindowMapped = true;
  174. gCurrentWindowType = fallbackFnType;
  175. if (slo.winId > 0)
  176. XSetTransientForHint(display, window, static_cast<Window>(slo.winId));
  177. if (gCurrentWindowVisible)
  178. {
  179. carla_stdout("JACK application window found, showing it now");
  180. break;
  181. }
  182. gCurrentWindowMapped = false;
  183. carla_stdout("JACK application window found and captured");
  184. return 0;
  185. }
  186. carla_debug("carlaWindowMap(%p, %lu, %i) - not captured", display, window, fallbackFnType);
  187. switch (fallbackFnType)
  188. {
  189. case 1:
  190. return real_XMapWindow(display, window);
  191. case 2:
  192. return real_XMapRaised(display, window);
  193. case 3:
  194. return real_XMapSubwindows(display, window);
  195. default:
  196. return 0;
  197. }
  198. }
  199. // --------------------------------------------------------------------------------------------------------------------
  200. // Our custom X11 functions
  201. CARLA_EXPORT
  202. int XMapWindow(Display* display, Window window)
  203. {
  204. carla_debug("XMapWindow(%p, %lu)", display, window);
  205. return carlaWindowMap(display, window, 1);
  206. }
  207. CARLA_EXPORT
  208. int XMapRaised(Display* display, Window window)
  209. {
  210. carla_debug("XMapRaised(%p, %lu)", display, window);
  211. return carlaWindowMap(display, window, 2);
  212. }
  213. CARLA_EXPORT
  214. int XMapSubwindows(Display* display, Window window)
  215. {
  216. carla_debug("XMapSubwindows(%p, %lu)", display, window);
  217. return carlaWindowMap(display, window, 3);
  218. }
  219. CARLA_EXPORT
  220. int XUnmapWindow(Display* display, Window window)
  221. {
  222. carla_debug("XUnmapWindow(%p, %lu)", display, window);
  223. if (gCurrentlyMappedWindow == window)
  224. {
  225. gCurrentlyMappedDisplay = nullptr;
  226. gCurrentlyMappedWindow = 0;
  227. gCurrentWindowType = 0;
  228. gCurrentWindowMapped = false;
  229. gCurrentWindowVisible = false;
  230. if (gInterposedCallback != nullptr)
  231. gInterposedCallback(1, nullptr);
  232. }
  233. return real_XUnmapWindow(display, window);
  234. }
  235. // --------------------------------------------------------------------------------------------------------------------
  236. // Full control helper
  237. CARLA_EXPORT
  238. int jack_carla_interposed_action(int action, int value, void* ptr)
  239. {
  240. carla_debug("jack_carla_interposed_action(%i, %i, %p)", action, value, ptr);
  241. switch (action)
  242. {
  243. case 1:
  244. // set hints and callback
  245. gInterposedHints = value;
  246. gInterposedCallback = (CarlaInterposedCallback)ptr;
  247. return 1;
  248. case 2:
  249. // session manager
  250. gInterposedSessionManager = value;
  251. return 1;
  252. case 3:
  253. // show gui
  254. if (value != 0)
  255. {
  256. gCurrentWindowVisible = true;
  257. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  258. return 0;
  259. gCurrentWindowMapped = true;
  260. switch (gCurrentWindowType)
  261. {
  262. case 1:
  263. return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  264. case 2:
  265. return real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  266. case 3:
  267. return real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  268. default:
  269. return 0;
  270. }
  271. }
  272. // hide gui
  273. else
  274. {
  275. gCurrentWindowVisible = false;
  276. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  277. return 0;
  278. gCurrentWindowMapped = false;
  279. return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  280. }
  281. break;
  282. case 4: // close everything
  283. gCurrentWindowType = 0;
  284. gCurrentWindowMapped = false;
  285. gCurrentWindowVisible = false;
  286. gCurrentlyMappedDisplay = nullptr;
  287. gCurrentlyMappedWindow = 0;
  288. return 0;
  289. }
  290. return -1;
  291. }
  292. // --------------------------------------------------------------------------------------------------------------------