|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- /*
- * Carla Interposer for JACK Applications X11 control
- * Copyright (C) 2014-2019 Filipe Coelho <falktx@falktx.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * For a full copy of the GNU General Public License see the doc/GPL.txt file.
- */
-
- #include "CarlaLibJackHints.h"
- #include "CarlaUtils.hpp"
-
- #include <dlfcn.h>
- #include <X11/Xlib.h>
-
- // --------------------------------------------------------------------------------------------------------------------
-
- struct ScopedLibOpen {
- void* handle;
- long long winId;
-
- ScopedLibOpen() noexcept
- : handle(dlopen("libjack.so.0", RTLD_NOW|RTLD_LOCAL)),
- winId(-1)
- {
- CARLA_SAFE_ASSERT_RETURN(handle != nullptr,);
-
- if (const char* const winIdStr = std::getenv("CARLA_FRONTEND_WIN_ID"))
- {
- CARLA_SAFE_ASSERT_RETURN(winIdStr[0] != '\0',);
-
- winId = std::strtoll(winIdStr, nullptr, 16);
- }
- }
-
- ~ScopedLibOpen() noexcept
- {
- if (handle != nullptr)
- {
- dlclose(handle);
- handle = nullptr;
- }
- }
-
- static const ScopedLibOpen& getInstance() noexcept
- {
- static const ScopedLibOpen slo;
- return slo;
- }
-
- CARLA_DECLARE_NON_COPY_STRUCT(ScopedLibOpen);
- };
-
- // --------------------------------------------------------------------------------------------------------------------
- // Function typedefs
-
- typedef int (*XWindowFunc)(Display*, Window);
- typedef int (*XNextEventFunc)(Display*, XEvent*);
-
- // --------------------------------------------------------------------------------------------------------------------
- // Current state
-
- typedef enum {
- WindowMapNone,
- WindowMapNormal,
- WindowMapRaised,
- WindowMapSubwindows
- } WindowMappingType;
-
- static Display* gCurrentlyMappedDisplay = nullptr;
- static Window gCurrentlyMappedWindow = 0;
- static CarlaInterposedCallback gInterposedCallback = nullptr;
- static uint gInterposedSessionManager = LIBJACK_SESSION_MANAGER_NONE;
- static uint gInterposedHints = 0x0;
- static WindowMappingType gCurrentWindowType = WindowMapNone;
- static bool gCurrentWindowMapped = false;
- static bool gCurrentWindowVisible = false;
-
- // --------------------------------------------------------------------------------------------------------------------
- // Calling the real functions
-
- static int real_XMapWindow(Display* display, Window window)
- {
- static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapWindow");
- CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
-
- return func(display, window);
- }
-
- static int real_XMapRaised(Display* display, Window window)
- {
- static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapRaised");
- CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
-
- return func(display, window);
- }
-
- static int real_XMapSubwindows(Display* display, Window window)
- {
- static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XMapSubwindows");
- CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
-
- return func(display, window);
- }
-
- static int real_XUnmapWindow(Display* display, Window window)
- {
- static const XWindowFunc func = (XWindowFunc)::dlsym(RTLD_NEXT, "XUnmapWindow");
- CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
-
- return func(display, window);
- }
-
- static int real_XNextEvent(Display* display, XEvent* event)
- {
- static const XNextEventFunc func = (XNextEventFunc)::dlsym(RTLD_NEXT, "XNextEvent");
- CARLA_SAFE_ASSERT_RETURN(func != nullptr, 0);
-
- return func(display, event);
- }
-
- // --------------------------------------------------------------------------------------------------------------------
- // Custom carla window handling
-
- static int carlaWindowMap(Display* const display, const Window window, const WindowMappingType fallbackFnType)
- {
- const ScopedLibOpen& slo(ScopedLibOpen::getInstance());
-
- for (;;)
- {
- if (slo.winId < 0)
- break;
-
- Atom atom;
- int atomFormat;
- unsigned char* atomPtrs;
- unsigned long numItems, ignored;
-
- const Atom wmWindowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
-
- if (XGetWindowProperty(display, window, wmWindowType, 0, ~0L, False, AnyPropertyType,
- &atom, &atomFormat, &numItems, &ignored, &atomPtrs) != Success)
- {
- carla_debug("carlaWindowMap(%p, %lu, %i) - XGetWindowProperty failed", display, window, fallbackFnType);
- break;
- }
-
- const Atom* const atomValues = (const Atom*)atomPtrs;
- bool isMainWindow = (numItems == 0);
-
- for (ulong i=0; i<numItems; ++i)
- {
- const char* const atomValue(XGetAtomName(display, atomValues[i]));
- CARLA_SAFE_ASSERT_CONTINUE(atomValue != nullptr && atomValue[0] != '\0');
-
- if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_COMBO" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DIALOG" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DND" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DOCK" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_MENU" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NOTIFICATION" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_POPUP_MENU" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_SPLASH" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLBAR" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_TOOLTIP" ) == 0 ||
- std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_UTILITY" ) == 0)
- {
- isMainWindow = false;
- continue;
- }
-
- if (std::strcmp(atomValue, "_NET_WM_WINDOW_TYPE_NORMAL") == 0)
- {
- // window is good, use it if no other types are set
- isMainWindow = true;
- }
- else
- {
- carla_stdout("=======================================> %s", atomValue);
- }
- }
-
- if (! isMainWindow)
- {
- // this has always bothered me...
- if (gCurrentlyMappedWindow != 0 && gCurrentWindowMapped && gCurrentWindowVisible)
- XSetTransientForHint(display, window, gCurrentlyMappedWindow);
- break;
- }
-
- Window transientWindow = 0;
- if (XGetTransientForHint(display, window, &transientWindow) == Success && transientWindow != 0)
- {
- carla_stdout("Window has transient set already, ignoring it");
- break;
- }
-
- // got a new window, we may need to forget last one
- if (gCurrentlyMappedDisplay != nullptr && gCurrentlyMappedWindow != 0)
- {
- // ignore requests against the current mapped window
- if (gCurrentlyMappedWindow == window)
- return 0;
-
- // we already have a mapped window, with carla visible button on, should be a dialog of sorts..
- if (gCurrentWindowMapped && gCurrentWindowVisible)
- {
- XSetTransientForHint(display, window, gCurrentlyMappedWindow);
- break;
- }
-
- // ignore empty windows created after the main one
- if (numItems == 0)
- break;
-
- carla_stdout("NOTICE: XMapWindow now showing previous window");
- switch (gCurrentWindowType)
- {
- case WindowMapNone:
- break;
- case WindowMapNormal:
- real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- break;
- case WindowMapRaised:
- real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- break;
- case WindowMapSubwindows:
- real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- break;
- }
- }
-
- gCurrentlyMappedDisplay = display;
- gCurrentlyMappedWindow = window;
- gCurrentWindowMapped = true;
- gCurrentWindowType = fallbackFnType;
-
- if (slo.winId > 0)
- XSetTransientForHint(display, window, static_cast<Window>(slo.winId));
-
- if (gCurrentWindowVisible)
- {
- carla_stdout("JACK application window found, showing it now");
- break;
- }
-
- gCurrentWindowMapped = false;
- carla_stdout("JACK application window found and captured");
- return 0;
- }
-
- carla_debug("carlaWindowMap(%p, %lu, %i) - not captured", display, window, fallbackFnType);
-
- switch (fallbackFnType)
- {
- case 1:
- return real_XMapWindow(display, window);
- case 2:
- return real_XMapRaised(display, window);
- case 3:
- return real_XMapSubwindows(display, window);
- default:
- return 0;
- }
- }
-
- // --------------------------------------------------------------------------------------------------------------------
- // Our custom X11 functions
-
- CARLA_EXPORT
- int XMapWindow(Display* display, Window window)
- {
- carla_debug("XMapWindow(%p, %lu)", display, window);
- return carlaWindowMap(display, window, WindowMapNormal);
- }
-
- CARLA_EXPORT
- int XMapRaised(Display* display, Window window)
- {
- carla_debug("XMapRaised(%p, %lu)", display, window);
- return carlaWindowMap(display, window, WindowMapRaised);
- }
-
- CARLA_EXPORT
- int XMapSubwindows(Display* display, Window window)
- {
- carla_debug("XMapSubwindows(%p, %lu)", display, window);
- return carlaWindowMap(display, window, WindowMapSubwindows);
- }
-
- CARLA_EXPORT
- int XUnmapWindow(Display* display, Window window)
- {
- carla_debug("XUnmapWindow(%p, %lu)", display, window);
-
- if (gCurrentlyMappedWindow == window)
- {
- gCurrentlyMappedDisplay = nullptr;
- gCurrentlyMappedWindow = 0;
- gCurrentWindowType = WindowMapNone;
- gCurrentWindowMapped = false;
- gCurrentWindowVisible = false;
-
- if (gInterposedCallback != nullptr)
- gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE, nullptr);
- }
-
- return real_XUnmapWindow(display, window);
- }
-
- CARLA_EXPORT
- int XNextEvent(Display* display, XEvent* event)
- {
- const int ret = real_XNextEvent(display, event);
-
- if (ret != 0)
- return ret;
- if (gCurrentlyMappedWindow == 0)
- return ret;
- if (event->type != ClientMessage)
- return ret;
- if (event->xclient.window != gCurrentlyMappedWindow)
- return ret;
-
- char* const type = XGetAtomName(display, event->xclient.message_type);
- CARLA_SAFE_ASSERT_RETURN(type != nullptr, 0);
-
- if (std::strcmp(type, "WM_PROTOCOLS") != 0)
- return ret;
- if ((Atom)event->xclient.data.l[0] != XInternAtom(display, "WM_DELETE_WINDOW", False))
- return ret;
-
- gCurrentWindowVisible = false;
- gCurrentWindowMapped = false;
-
- if (gInterposedCallback != nullptr)
- gInterposedCallback(LIBJACK_INTERPOSER_CALLBACK_UI_HIDE, nullptr);
-
- event->type = 0;
- carla_stdout("XNextEvent close event caught, hiding UI instead");
- return real_XUnmapWindow(display, gCurrentlyMappedWindow);
- }
-
- // --------------------------------------------------------------------------------------------------------------------
- // Full control helper
-
- CARLA_EXPORT
- int jack_carla_interposed_action(uint action, uint value, void* ptr)
- {
- carla_debug("jack_carla_interposed_action(%i, %i, %p)", action, value, ptr);
-
- switch (action)
- {
- case LIBJACK_INTERPOSER_ACTION_SET_HINTS_AND_CALLBACK:
- gInterposedHints = value;
- gInterposedCallback = (CarlaInterposedCallback)ptr;
- return 1;
-
- case LIBJACK_INTERPOSER_ACTION_SET_SESSION_MANAGER:
- gInterposedSessionManager = value;
- return 1;
-
- case LIBJACK_INTERPOSER_ACTION_SHOW_HIDE_GUI:
- // show gui
- if (value != 0)
- {
- gCurrentWindowVisible = true;
- if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
- {
- carla_stdout("NOTICE: Interposer show-gui request ignored");
- return 0;
- }
-
- gCurrentWindowMapped = true;
-
- switch (gCurrentWindowType)
- {
- case WindowMapNormal:
- return real_XMapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- case WindowMapRaised:
- return real_XMapRaised(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- case WindowMapSubwindows:
- return real_XMapSubwindows(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- default:
- return 0;
- }
- }
- // hide gui
- else
- {
- gCurrentWindowVisible = false;
- if (gCurrentlyMappedDisplay == nullptr || gCurrentlyMappedWindow == 0)
- {
- carla_stdout("NOTICE: Interposer hide-gui request ignored");
- return 0;
- }
-
- gCurrentWindowMapped = false;
- return real_XUnmapWindow(gCurrentlyMappedDisplay, gCurrentlyMappedWindow);
- }
- break;
-
- case LIBJACK_INTERPOSER_ACTION_CLOSE_EVERYTHING:
- gCurrentWindowType = WindowMapNone;
- gCurrentWindowMapped = false;
- gCurrentWindowVisible = false;
- gCurrentlyMappedDisplay = nullptr;
- gCurrentlyMappedWindow = 0;
- return 0;
- }
-
- return -1;
- }
-
- // --------------------------------------------------------------------------------------------------------------------
|