Add initial bits for Qt6 support
[carla.git] / source / interposer / interposer-jack-x11.cpp
blob4ec5db6aa67931184ead27499c7518773c4c1564
1 /*
2 * Carla Interposer for JACK Applications X11 control
3 * Copyright (C) 2014-2022 Filipe Coelho <falktx@falktx.com>
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.
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.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
18 #include "CarlaLibJackHints.h"
19 #include "CarlaUtils.hpp"
21 #include <dlfcn.h>
23 #ifdef HAVE_X11
24 # include <X11/Xlib.h>
25 #endif
27 // --------------------------------------------------------------------------------------------------------------------
29 struct ScopedLibOpen {
30 void* handle;
31 long long winId;
33 ScopedLibOpen() noexcept
34 #ifdef CARLA_OS_MAC
35 : handle(dlopen("libjack.dylib", RTLD_NOW|RTLD_LOCAL)),
36 #else
37 : handle(dlopen("libjack.so.0", RTLD_NOW|RTLD_LOCAL)),
38 #endif
39 winId(-1)
41 CARLA_SAFE_ASSERT_RETURN(handle != nullptr,);
43 if (const char* const winIdStr = std::getenv("CARLA_FRONTEND_WIN_ID"))
45 CARLA_SAFE_ASSERT_RETURN(winIdStr[0] != '\0',);
47 winId = std::strtoll(winIdStr, nullptr, 16);
51 ~ScopedLibOpen() noexcept
53 if (handle != nullptr)
55 dlclose(handle);
56 handle = nullptr;
60 static const ScopedLibOpen& getInstance() noexcept
62 static const ScopedLibOpen slo;
63 return slo;
66 CARLA_DECLARE_NON_COPYABLE(ScopedLibOpen);
69 // --------------------------------------------------------------------------------------------------------------------
70 // Current state
72 typedef enum {
73 WindowMapNone,
74 WindowMapNormal,
75 WindowMapRaised,
76 WindowMapSubwindows
77 } WindowMappingType;
79 typedef enum {
80 WindowUnmapNone,
81 WindowUnmapNormal,
82 WindowUnmapDestroy
83 } WindowUnmappingType;
85 #ifdef HAVE_X11
86 static Display* gCurrentlyMappedDisplay = nullptr;
87 static Window gCurrentlyMappedWindow = 0;
88 static bool gSupportsOptionalGui = true;
89 #endif
90 static CarlaInterposedCallback gInterposedCallback = nullptr;
91 static uint gInterposedSessionManager = LIBJACK_SESSION_MANAGER_NONE;
92 static uint gInterposedHints = 0x0;
93 static WindowMappingType gCurrentWindowType = WindowMapNone;
94 static bool gCurrentWindowMapped = false;
95 static bool gCurrentWindowVisible = false;
97 #ifdef HAVE_X11
98 // --------------------------------------------------------------------------------------------------------------------
99 // Function typedefs
101 typedef int (*XWindowFunc)(Display*, Window);
102 typedef int (*XNextEventFunc)(Display*, XEvent*);
104 // --------------------------------------------------------------------------------------------------------------------
105 // Calling the real X11 functions
107 static int real_XMapWindow(Display* display, Window window)
109 static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapWindow");
110 CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
112 return func(display, window);
115 static int real_XMapRaised(Display* display, Window window)
117 static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapRaised");
118 CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
120 return func(display, window);
123 static int real_XMapSubwindows(Display* display, Window window)
125 static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapSubwindows");
126 CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
128 return func(display, window);
131 static int real_XUnmapWindow(Display* display, Window window)
133 static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XUnmapWindow");
134 CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
136 return func(display, window);
139 static int real_XDestroyWindow(Display* display, Window window)
141 static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XDestroyWindow");
142 CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
144 return func(display, window);
147 static int real_XNextEvent(Display* display, XEvent* event)
149 static const XNextEventFunc func = (XNextEventFunc)::dlsym(RTLD_NEXT, "XNextEvent");
150 CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
152 return func(display, event);
155 // --------------------------------------------------------------------------------------------------------------------
156 // Custom carla window handling
158 static int carlaWindowMap(Display* const display, const Window window, const WindowMappingType fallbackFnType)
160 const ScopedLibOpen& slo(ScopedLibOpen::getInstance());
162 for (;;)
164 if (slo.winId < 0)
165 break;
166 if ((gInterposedHints & LIBJACK_FLAG_CONTROL_WINDOW) == 0x0)
167 break;
169 Atom atom;
170 int atomFormat;
171 unsigned char* atomPtrs;
172 unsigned long numItems, ignored;
174 const Atom wmWindowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
176 if (XGetWindowProperty(display, window, wmWindowType, 0, ~0L, False, AnyPropertyType,
177 &atom, &atomFormat, &numItems, &ignored, &atomPtrs) != Success)
179 carla_debug("carlaWindowMap(%p, %lu, %i) - XGetWindowProperty failed", display, window, fallbackFnType);
180 break;
183 const Atom* const atomValues = (const Atom*)atomPtrs;
184 bool isMainWindow = (numItems == 0);
186 for (ulong i=0; i<numItems; ++i)
188 const char* const atomValue(XGetAtomName(display, atomValues[i]));
189 CARLA_SAFE_ASSERT_CONTINUE(atomValue != nullptr && atomValue[0] != '\0');
191 if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_COMBO" ) == 0 ||
192 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DIALOG" ) == 0 ||
193 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DND" ) == 0 ||
194 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DOCK" ) == 0 ||
195 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") == 0 ||
196 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_MENU" ) == 0 ||
197 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NOTIFICATION" ) == 0 ||
198 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_POPUP_MENU" ) == 0 ||
199 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_SPLASH" ) == 0 ||
200 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLBAR" ) == 0 ||
201 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLTIP" ) == 0 ||
202 std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_UTILITY" ) == 0)
204 isMainWindow = false;
205 continue;
208 if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NORMAL") == 0)
210 // window is good, use it if no other types are set
211 isMainWindow = true;
213 else
215 carla_stdout("=======================================> %s", atomValue);
219 if (! isMainWindow)
221 carla_debug("carlaWindowMap(%p, %lu, %i) - not main window, ignoring", display, window, fallbackFnType);
223 // this has always bothered me...
224 if (gCurrentlyMappedWindow != 0 && gCurrentWindowMapped && gCurrentWindowVisible)
225 XSetTransientForHint(display, window, gCurrentlyMappedWindow);
226 break;
229 Window transientWindow = 0;
230 if (XGetTransientForHint(display, window, &transientWindow) == Success && transientWindow != 0)
232 carla_stdout("Window has transient set already, ignoring it");
233 break;
236 // got a new window, we may need to forget last one
237 if (gCurrentlyMappedDisplay != nullptr && gCurrentlyMappedWindow != 0)
239 // ignore requests against the current mapped window
240 if (gCurrentlyMappedWindow == window)
242 carla_debug("carlaWindowMap(%p, %lu, %i) - asked to show window, ignoring it",
243 display, window, fallbackFnType);
244 return 0;
247 // we already have a mapped window, with carla visible button on, should be a dialog of sorts..
248 if (gCurrentWindowMapped && gCurrentWindowVisible)
250 XSetTransientForHint(display, window, gCurrentlyMappedWindow);
251 break;
254 // ignore empty windows created after the main one
255 if (numItems == 0)
257 carla_debug("carlaWindowMap(%p, %lu, %i) - ignoring empty window", display, window, fallbackFnType);
258 break;
261 carla_stdout("NOTICE: XMapWindow now showing previous window");
262 switch (gCurrentWindowType)
264 case WindowMapNone:
265 break;
266 case WindowMapNormal:
267 real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
268 break;
269 case WindowMapRaised:
270 real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
271 break;
272 case WindowMapSubwindows:
273 real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
274 break;
278 gCurrentlyMappedDisplay = display;
279 gCurrentlyMappedWindow = window;
280 gCurrentWindowMapped = true;
281 gCurrentWindowType = fallbackFnType;
283 if (slo.winId > 0)
284 XSetTransientForHint(display, window, static_cast<Window>(slo.winId));
286 if (gCurrentWindowVisible)
288 carla_stdout("JACK application window found, showing it now");
289 break;
292 gCurrentWindowMapped = false;
293 carla_stdout("JACK application window found and captured");
295 if (gInterposedSessionManager == LIBJACK_SESSION_MANAGER_NSM && gSupportsOptionalGui)
296 break;
298 return 0;
301 carla_debug("carlaWindowMap(%p, %lu, %i) - not captured", display, window, fallbackFnType);
303 switch (fallbackFnType)
305 case WindowMapNormal:
306 return real_XMapWindow(display, window);
307 case WindowMapRaised:
308 return real_XMapRaised(display, window);
309 case WindowMapSubwindows:
310 return real_XMapSubwindows(display, window);
311 default:
312 return 0;
316 static int carlaWindowUnmap(Display* const display, const Window window, const WindowUnmappingType fallbackFnType)
318 if (gCurrentlyMappedWindow == window)
320 carla_stdout("NOTICE: now hiding previous window");
322 gCurrentlyMappedDisplay = nullptr;
323 gCurrentlyMappedWindow = 0;
324 gCurrentWindowType = WindowMapNone;
325 gCurrentWindowMapped = false;
326 gCurrentWindowVisible = false;
328 if (gInterposedCallback != nullptr)
329 gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE, nullptr);
331 else
333 carla_debug("carlaWindowUnmap(%p, %lu, %i) - not captured", display, window, fallbackFnType);
336 switch (fallbackFnType)
338 case WindowUnmapNormal:
339 return real_XUnmapWindow(display, window);
340 case WindowUnmapDestroy:
341 return real_XDestroyWindow(display, window);
342 default:
343 return 0;
347 // --------------------------------------------------------------------------------------------------------------------
348 // Our custom X11 functions
350 CARLA_PLUGIN_EXPORT
351 int XMapWindow(Display* display, Window window)
353 carla_debug("XMapWindow(%p, %lu)", display, window);
354 return carlaWindowMap(display, window, WindowMapNormal);
357 CARLA_PLUGIN_EXPORT
358 int XMapRaised(Display* display, Window window)
360 carla_debug("XMapRaised(%p, %lu)", display, window);
361 return carlaWindowMap(display, window, WindowMapRaised);
364 CARLA_PLUGIN_EXPORT
365 int XMapSubwindows(Display* display, Window window)
367 carla_debug("XMapSubwindows(%p, %lu)", display, window);
368 return carlaWindowMap(display, window, WindowMapSubwindows);
371 CARLA_PLUGIN_EXPORT
372 int XUnmapWindow(Display* display, Window window)
374 carla_debug("XUnmapWindow(%p, %lu)", display, window);
375 return carlaWindowUnmap(display, window, WindowUnmapNormal);
378 CARLA_PLUGIN_EXPORT
379 int XDestroyWindow(Display* display, Window window)
381 carla_debug("XDestroyWindow(%p, %lu)", display, window);
382 return carlaWindowUnmap(display, window, WindowUnmapDestroy);
385 CARLA_PLUGIN_EXPORT
386 int XNextEvent(Display* display, XEvent* event)
388 const int ret = real_XNextEvent(display, event);
390 if ((gInterposedHints & LIBJACK_FLAG_CONTROL_WINDOW) == 0x0)
391 return ret;
392 if (gInterposedSessionManager == LIBJACK_SESSION_MANAGER_NSM && gSupportsOptionalGui)
393 return ret;
395 if (ret != 0)
396 return ret;
397 if (gCurrentlyMappedWindow == 0)
398 return ret;
399 if (event->type != ClientMessage)
400 return ret;
401 if (event->xclient.window != gCurrentlyMappedWindow)
402 return ret;
404 char* const type = XGetAtomName(display, event->xclient.message_type);
405 CARLA_SAFE_ASSERT_RETURN(type != nullptr, 0);
407 if (std::strcmp(type, "WM_PROTOCOLS") != 0)
408 return ret;
409 if ((Atom)event->xclient.data.l[0] != XInternAtom(display, "WM_DELETE_WINDOW", False))
410 return ret;
412 gCurrentWindowVisible = false;
413 gCurrentWindowMapped = false;
415 if (gInterposedCallback != nullptr)
416 gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE, nullptr);
418 event->type = 0;
419 carla_stdout("XNextEvent close event caught, hiding UI instead");
420 return real_XUnmapWindow(display, gCurrentlyMappedWindow);
423 #endif // HAVE_X11
425 // --------------------------------------------------------------------------------------------------------------------
426 // Full control helper
428 CARLA_PLUGIN_EXPORT
429 int jack_carla_interposed_action(uint action, uint value, void* ptr)
431 carla_debug("jack_carla_interposed_action(%i, %i, %p)", action, value, ptr);
433 switch (action)
435 case LIBJACK_INTERPOSER_ACTION_SET_HINTS_AND_CALLBACK:
436 gInterposedHints = value;
437 gInterposedCallback = (CarlaInterposedCallback)ptr;
438 return 1;
440 case LIBJACK_INTERPOSER_ACTION_SET_SESSION_MANAGER:
441 gInterposedSessionManager = value;
442 return 1;
444 case LIBJACK_INTERPOSER_ACTION_SHOW_HIDE_GUI:
445 #ifdef HAVE_X11
446 gSupportsOptionalGui = false;
447 #endif
448 // show gui
449 if (value != 0)
451 #ifdef HAVE_X11
452 gCurrentWindowVisible = true;
453 if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
454 #endif
456 carla_stdout("NOTICE: Interposer show-gui request ignored");
457 return 0;
460 #ifdef HAVE_X11
461 gCurrentWindowMapped = true;
463 switch (gCurrentWindowType)
465 case WindowMapNormal:
466 return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
467 case WindowMapRaised:
468 return real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
469 case WindowMapSubwindows:
470 return real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
471 default:
472 return 0;
474 #endif
476 // hide gui
477 else
479 #ifdef HAVE_X11
480 gCurrentWindowVisible = false;
481 if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
482 #endif
484 carla_stdout("NOTICE: Interposer hide-gui request ignored");
485 return 0;
488 #ifdef HAVE_X11
489 gCurrentWindowMapped = false;
490 return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
491 #endif
493 break;
495 case LIBJACK_INTERPOSER_ACTION_CLOSE_EVERYTHING:
496 gCurrentWindowType = WindowMapNone;
497 gCurrentWindowMapped = false;
498 gCurrentWindowVisible = false;
499 #ifdef HAVE_X11
500 gCurrentlyMappedDisplay = nullptr;
501 gCurrentlyMappedWindow = 0;
502 #endif
503 return 0;
506 return -1;
509 // --------------------------------------------------------------------------------------------------------------------