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.

261 lines
8.7KB

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