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.

257 lines
8.7KB

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