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.

256 lines
8.6KB

  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. Window winId;
  23. ScopedLibOpen()
  24. : handle(dlopen("libjack.so.0", RTLD_NOW|RTLD_LOCAL)),
  25. winId(0)
  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 = static_cast<Window>(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. XSetTransientForHint(display, window, slo.winId);
  152. if (gCurrentWindowVisible)
  153. {
  154. carla_stdout("JACK application window found, showing it now");
  155. break;
  156. }
  157. gCurrentWindowMapped = false;
  158. carla_stdout("JACK application window found and captured");
  159. return 0;
  160. }
  161. return real_XMapWindow(display, window);
  162. }
  163. CARLA_EXPORT
  164. int XUnmapWindow(Display* display, Window window)
  165. {
  166. if (gCurrentlyMappedWindow == window)
  167. {
  168. gCurrentlyMappedDisplay = nullptr;
  169. gCurrentlyMappedWindow = 0;
  170. gCurrentWindowMapped = false;
  171. gCurrentWindowVisible = false;
  172. if (gInterposedCallback != nullptr)
  173. gInterposedCallback(1, nullptr);
  174. }
  175. return real_XUnmapWindow(display, window);
  176. }
  177. // ---------------------------------------------------------------------------------------------------------------------
  178. CARLA_EXPORT
  179. int jack_carla_interposed_action(int action, void* ptr)
  180. {
  181. carla_stdout("jack_carla_interposed_action(%i, %p)", action, ptr);
  182. switch (action)
  183. {
  184. case 1: // set callback
  185. gInterposedCallback = (CarlaInterposedCallback)ptr;
  186. break;
  187. case 2: // show gui
  188. gCurrentWindowVisible = true;
  189. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  190. break;
  191. gCurrentWindowMapped = true;
  192. return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  193. case 3: // hide gui
  194. gCurrentWindowVisible = false;
  195. if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
  196. break;
  197. gCurrentWindowMapped = false;
  198. return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
  199. case 4: // close everything
  200. gCurrentWindowMapped = false;
  201. gCurrentWindowVisible = false;
  202. gCurrentlyMappedDisplay = nullptr;
  203. gCurrentlyMappedWindow = 0;
  204. return 0;
  205. }
  206. return -1;
  207. }
  208. // ---------------------------------------------------------------------------------------------------------------------