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.

278 lines
9.1KB

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