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.

interposer-jack-x11.cpp 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. * Carla Interposer for JACK Applications X11 control
  3. * Copyright (C) 2014-2017 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()
  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()
  35. {
  36. if (handle != nullptr)
  37. {
  38. dlclose(handle);
  39. handle = nullptr;
  40. }
  41. }
  42. };
  43. // ---------------------------------------------------------------------------------------------------------------------
  44. // Function typedefs
  45. typedef int (*XMapWindowFunc)(Display*, Window);
  46. typedef int (*XUnmapWindowFunc)(Display*, Window);
  47. typedef int (*CarlaInterposedCallback)(int, void*);
  48. // ---------------------------------------------------------------------------------------------------------------------
  49. // Current state
  50. static Display* gCurrentlyMappedDisplay = nullptr;
  51. static Window gCurrentlyMappedWindow = 0;
  52. static CarlaInterposedCallback gInterposedCallback = nullptr;
  53. static int gInterposedSessionManager = 0;
  54. static int gInterposedHints = 0;
  55. static bool gCurrentWindowMapped = false;
  56. static bool gCurrentWindowVisible = false;
  57. // ---------------------------------------------------------------------------------------------------------------------
  58. // Calling the real functions
  59. static int real_XMapWindow(Display* display, Window window)
  60. {
  61. static const XMapWindowFunc func = (XMapWindowFunc)::dlsym(RTLD_NEXT, "XMapWindow");
  62. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  63. return func(display, window);
  64. }
  65. static int real_XUnmapWindow(Display* display, Window window)
  66. {
  67. static const XUnmapWindowFunc func = (XUnmapWindowFunc)::dlsym(RTLD_NEXT, "XUnmapWindow");
  68. CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
  69. return func(display, window);
  70. }
  71. // ---------------------------------------------------------------------------------------------------------------------
  72. // Our custom functions
  73. CARLA_EXPORT
  74. int XMapWindow(Display* display, Window window)
  75. {
  76. static const ScopedLibOpen slo;
  77. for (;;)
  78. {
  79. if (slo.winId < 0)
  80. break;
  81. Atom atom;
  82. int atomFormat;
  83. unsigned char* atomPtrs;
  84. unsigned long numItems, ignored;
  85. const Atom wmWindowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True);
  86. if (XGetWindowProperty(display, window, wmWindowType, 0, ~0L, False, AnyPropertyType,
  87. &atom, &atomFormat, &numItems, &ignored, &atomPtrs) != Success)
  88. break;
  89. const Atom* const atomValues = (const Atom*)atomPtrs;
  90. bool isMainWindow = (numItems == 0);
  91. for (ulong i=0; i<numItems; ++i)
  92. {
  93. const char* const atomValue(XGetAtomName(display, atomValues[i]));
  94. CARLA_SAFE_ASSERT_CONTINUE(atomValue != nullptr && atomValue[0] != '\0');
  95. if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_COMBO" ) == 0 ||
  96. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DIALOG" ) == 0 ||
  97. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DND" ) == 0 ||
  98. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DOCK" ) == 0 ||
  99. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") == 0 ||
  100. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_MENU" ) == 0 ||
  101. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NOTIFICATION" ) == 0 ||
  102. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_POPUP_MENU" ) == 0 ||
  103. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_SPLASH" ) == 0 ||
  104. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLBAR" ) == 0 ||
  105. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLTIP" ) == 0 ||
  106. std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_UTILITY" ) == 0)
  107. {
  108. isMainWindow = false;
  109. break;
  110. }
  111. if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NORMAL") == 0)
  112. {
  113. // window is good, use it if no other types are set
  114. isMainWindow = true;
  115. }
  116. else
  117. {
  118. carla_stdout("=======================================> %s", atomValue);
  119. }
  120. }
  121. if (! isMainWindow)
  122. {
  123. // this has always bothered me...
  124. if (gCurrentlyMappedWindow != 0 && gCurrentWindowMapped && gCurrentWindowVisible)
  125. XSetTransientForHint(display, window, gCurrentlyMappedWindow);
  126. break;
  127. }
  128. Window transientWindow = 0;
  129. if (XGetTransientForHint(display, window, &transientWindow) == Success && transientWindow != 0)
  130. {
  131. carla_stdout("Window has transient set already, ignoring it");
  132. break;
  133. }
  134. // got a new window, we may need to forget last one
  135. if (gCurrentlyMappedDisplay != nullptr && gCurrentlyMappedWindow != 0)
  136. {
  137. // ignore requests against the current mapped window
  138. if (gCurrentlyMappedWindow == window)
  139. return 0;
  140. // we already have a mapped window, with carla visible button on, should be a dialog of sorts..
  141. if (gCurrentWindowMapped && gCurrentWindowVisible)
  142. {
  143. XSetTransientForHint(display, window, gCurrentlyMappedWindow);
  144. break;
  145. }
  146. // ignore empty windows created after the main one
  147. if (numItems == 0)
  148. break;
  149. carla_stdout("NOTICE: XMapWindow now showing previous window");
  150. real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  151. }
  152. gCurrentlyMappedDisplay = display;
  153. gCurrentlyMappedWindow = window;
  154. gCurrentWindowMapped = true;
  155. if (slo.winId > 0)
  156. XSetTransientForHint(display, window, static_cast<Window>(slo.winId));
  157. if (gCurrentWindowVisible)
  158. {
  159. carla_stdout("JACK application window found, showing it now");
  160. break;
  161. }
  162. gCurrentWindowMapped = false;
  163. carla_stdout("JACK application window found and captured");
  164. return 0;
  165. }
  166. return real_XMapWindow(display, window);
  167. }
  168. CARLA_EXPORT
  169. int XUnmapWindow(Display* display, Window window)
  170. {
  171. if (gCurrentlyMappedWindow == window)
  172. {
  173. gCurrentlyMappedDisplay = nullptr;
  174. gCurrentlyMappedWindow = 0;
  175. gCurrentWindowMapped = false;
  176. gCurrentWindowVisible = false;
  177. if (gInterposedCallback != nullptr)
  178. gInterposedCallback(1, nullptr);
  179. }
  180. return real_XUnmapWindow(display, window);
  181. }
  182. // ---------------------------------------------------------------------------------------------------------------------
  183. CARLA_EXPORT
  184. int jack_carla_interposed_action(int action, int value, void* ptr)
  185. {
  186. carla_debug("jack_carla_interposed_action(%i, %i, %p)", action, value, ptr);
  187. switch (action)
  188. {
  189. case 1:
  190. // set hints and callback
  191. gInterposedHints = value;
  192. gInterposedCallback = (CarlaInterposedCallback)ptr;
  193. return 1;
  194. case 2:
  195. // session manager
  196. gInterposedSessionManager = value;
  197. return 1;
  198. case 3:
  199. // show gui
  200. if (value != 0)
  201. {
  202. gCurrentWindowVisible = true;
  203. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  204. return 0;
  205. gCurrentWindowMapped = true;
  206. return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  207. }
  208. // hide gui
  209. else
  210. {
  211. gCurrentWindowVisible = false;
  212. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  213. return 0;
  214. gCurrentWindowMapped = false;
  215. return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  216. }
  217. break;
  218. case 4: // close everything
  219. gCurrentWindowMapped = false;
  220. gCurrentWindowVisible = false;
  221. gCurrentlyMappedDisplay = nullptr;
  222. gCurrentlyMappedWindow = 0;
  223. return 0;
  224. }
  225. return -1;
  226. }
  227. // ---------------------------------------------------------------------------------------------------------------------