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"
24 # include <X11/Xlib.h>
27 // --------------------------------------------------------------------------------------------------------------------
29 struct ScopedLibOpen
{
33 ScopedLibOpen() noexcept
35 : handle(dlopen("libjack.dylib", RTLD_NOW
|RTLD_LOCAL
)),
37 : handle(dlopen("libjack.so.0", RTLD_NOW
|RTLD_LOCAL
)),
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)
60 static const ScopedLibOpen
& getInstance() noexcept
62 static const ScopedLibOpen slo
;
66 CARLA_DECLARE_NON_COPYABLE(ScopedLibOpen
);
69 // --------------------------------------------------------------------------------------------------------------------
83 } WindowUnmappingType
;
86 static Display
* gCurrentlyMappedDisplay
= nullptr;
87 static Window gCurrentlyMappedWindow
= 0;
88 static bool gSupportsOptionalGui
= true;
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;
98 // --------------------------------------------------------------------------------------------------------------------
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());
166 if ((gInterposedHints
& LIBJACK_FLAG_CONTROL_WINDOW
) == 0x0)
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
);
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;
208 if (std::strcmp(atomValue
, "_NET_WM_WINDOW_TYPE_NORMAL") == 0)
210 // window is good, use it if no other types are set
215 carla_stdout("=======================================> %s", atomValue
);
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
);
229 Window transientWindow
= 0;
230 if (XGetTransientForHint(display
, window
, &transientWindow
) == Success
&& transientWindow
!= 0)
232 carla_stdout("Window has transient set already, ignoring it");
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
);
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
);
254 // ignore empty windows created after the main one
257 carla_debug("carlaWindowMap(%p, %lu, %i) - ignoring empty window", display
, window
, fallbackFnType
);
261 carla_stdout("NOTICE: XMapWindow now showing previous window");
262 switch (gCurrentWindowType
)
266 case WindowMapNormal
:
267 real_XMapWindow(gCurrentlyMappedDisplay
, gCurrentlyMappedWindow
);
269 case WindowMapRaised
:
270 real_XMapRaised(gCurrentlyMappedDisplay
, gCurrentlyMappedWindow
);
272 case WindowMapSubwindows
:
273 real_XMapSubwindows(gCurrentlyMappedDisplay
, gCurrentlyMappedWindow
);
278 gCurrentlyMappedDisplay
= display
;
279 gCurrentlyMappedWindow
= window
;
280 gCurrentWindowMapped
= true;
281 gCurrentWindowType
= fallbackFnType
;
284 XSetTransientForHint(display
, window
, static_cast<Window
>(slo
.winId
));
286 if (gCurrentWindowVisible
)
288 carla_stdout("JACK application window found, showing it now");
292 gCurrentWindowMapped
= false;
293 carla_stdout("JACK application window found and captured");
295 if (gInterposedSessionManager
== LIBJACK_SESSION_MANAGER_NSM
&& gSupportsOptionalGui
)
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
);
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);
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
);
347 // --------------------------------------------------------------------------------------------------------------------
348 // Our custom X11 functions
351 int XMapWindow(Display
* display
, Window window
)
353 carla_debug("XMapWindow(%p, %lu)", display
, window
);
354 return carlaWindowMap(display
, window
, WindowMapNormal
);
358 int XMapRaised(Display
* display
, Window window
)
360 carla_debug("XMapRaised(%p, %lu)", display
, window
);
361 return carlaWindowMap(display
, window
, WindowMapRaised
);
365 int XMapSubwindows(Display
* display
, Window window
)
367 carla_debug("XMapSubwindows(%p, %lu)", display
, window
);
368 return carlaWindowMap(display
, window
, WindowMapSubwindows
);
372 int XUnmapWindow(Display
* display
, Window window
)
374 carla_debug("XUnmapWindow(%p, %lu)", display
, window
);
375 return carlaWindowUnmap(display
, window
, WindowUnmapNormal
);
379 int XDestroyWindow(Display
* display
, Window window
)
381 carla_debug("XDestroyWindow(%p, %lu)", display
, window
);
382 return carlaWindowUnmap(display
, window
, WindowUnmapDestroy
);
386 int XNextEvent(Display
* display
, XEvent
* event
)
388 const int ret
= real_XNextEvent(display
, event
);
390 if ((gInterposedHints
& LIBJACK_FLAG_CONTROL_WINDOW
) == 0x0)
392 if (gInterposedSessionManager
== LIBJACK_SESSION_MANAGER_NSM
&& gSupportsOptionalGui
)
397 if (gCurrentlyMappedWindow
== 0)
399 if (event
->type
!= ClientMessage
)
401 if (event
->xclient
.window
!= gCurrentlyMappedWindow
)
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)
409 if ((Atom
)event
->xclient
.data
.l
[0] != XInternAtom(display
, "WM_DELETE_WINDOW", False
))
412 gCurrentWindowVisible
= false;
413 gCurrentWindowMapped
= false;
415 if (gInterposedCallback
!= nullptr)
416 gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE
, nullptr);
419 carla_stdout("XNextEvent close event caught, hiding UI instead");
420 return real_XUnmapWindow(display
, gCurrentlyMappedWindow
);
425 // --------------------------------------------------------------------------------------------------------------------
426 // Full control helper
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
);
435 case LIBJACK_INTERPOSER_ACTION_SET_HINTS_AND_CALLBACK
:
436 gInterposedHints
= value
;
437 gInterposedCallback
= (CarlaInterposedCallback
)ptr
;
440 case LIBJACK_INTERPOSER_ACTION_SET_SESSION_MANAGER
:
441 gInterposedSessionManager
= value
;
444 case LIBJACK_INTERPOSER_ACTION_SHOW_HIDE_GUI
:
446 gSupportsOptionalGui
= false;
452 gCurrentWindowVisible
= true;
453 if (gCurrentlyMappedDisplay
== nullptr || gCurrentlyMappedWindow
== 0)
456 carla_stdout("NOTICE: Interposer show-gui request ignored");
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
);
480 gCurrentWindowVisible
= false;
481 if (gCurrentlyMappedDisplay
== nullptr || gCurrentlyMappedWindow
== 0)
484 carla_stdout("NOTICE: Interposer hide-gui request ignored");
489 gCurrentWindowMapped
= false;
490 return real_XUnmapWindow(gCurrentlyMappedDisplay
, gCurrentlyMappedWindow
);
495 case LIBJACK_INTERPOSER_ACTION_CLOSE_EVERYTHING
:
496 gCurrentWindowType
= WindowMapNone
;
497 gCurrentWindowMapped
= false;
498 gCurrentWindowVisible
= false;
500 gCurrentlyMappedDisplay
= nullptr;
501 gCurrentlyMappedWindow
= 0;
509 // --------------------------------------------------------------------------------------------------------------------