diff --git a/CMakeLists.txt b/CMakeLists.txt index de4001db..4ab6e44b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16...3.26) +cmake_minimum_required(VERSION 3.21...3.26) project(advanced-scene-switcher VERSION 1.0.0) @@ -415,6 +415,7 @@ else() set_target_properties(${LIB_NAME} PROPERTIES PREFIX "") set_target_properties(${LIB_NAME} PROPERTIES SOVERSION 1) + find_package(Qt6 REQUIRED COMPONENTS DBus) find_package(X11 REQUIRED COMPONENTS Xss) target_include_directories(${LIB_NAME} PRIVATE "${X11_INCLUDE_DIR}" "${X11_Xss_INCLUDE_PATH}") @@ -446,7 +447,8 @@ else() ) endif() target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}") - target_sources(${LIB_NAME} PRIVATE lib/linux/advanced-scene-switcher-nix.cpp) + target_sources(${LIB_NAME} PRIVATE lib/linux/advanced-scene-switcher-nix.cpp + lib/linux/kwin-helpers.cpp) # Don't include irrelevant folders into sources archive list(APPEND CPACK_SOURCE_IGNORE_FILES "\\.deps/.*") diff --git a/lib/linux/advanced-scene-switcher-nix.cpp b/lib/linux/advanced-scene-switcher-nix.cpp index 7e56962c..30493124 100644 --- a/lib/linux/advanced-scene-switcher-nix.cpp +++ b/lib/linux/advanced-scene-switcher-nix.cpp @@ -32,6 +32,7 @@ #endif #include #include +#include "kwin-helpers.h" namespace advss { @@ -44,6 +45,10 @@ static XScreenSaverAllocInfoFunc allocSSFunc = nullptr; static XScreenSaverQueryInfoFunc querySSFunc = nullptr; bool canGetIdleTime = false; +static bool KWin = false; +static FocusNotifier notifier; +static QString KWinScriptObjectPath; + static QLibrary *libprocps = nullptr; #ifdef PROCPS_AVAILABLE typedef PROCTAB *(*openproc_func)(int flags); @@ -275,6 +280,11 @@ int getActiveWindow(Window *&window) void GetCurrentWindowTitle(std::string &title) { + if (KWin) { + title = FocusNotifier::getActiveWindowTitle(); + return; + } + Window *data = 0; if (getActiveWindow(data) != Success || !data) { return; @@ -286,6 +296,7 @@ void GetCurrentWindowTitle(std::string &title) auto name = getWindowName(data[0]); XFree(data); + if (name.empty()) { return; } @@ -412,6 +423,10 @@ void GetProcessList(QStringList &processes) long getForegroundProcessPid() { + if (KWin) { + return FocusNotifier::getActiveWindowPID(); + } + Window *window; if (getActiveWindow(window) != Success || !window || !*window) { return -1; @@ -437,6 +452,7 @@ long getForegroundProcessPid() pid = *((long *)prop); XFree(prop); + return pid; } @@ -562,6 +578,17 @@ void PlatformInit() return; } + KWin = isKWinAvailable(); + if (!(KWin && startKWinScript(KWinScriptObjectPath) && + registerKWinDBusListener(¬ifier))) { + // something bad happened while trying to initialize + // the KWin script/dbus so disable it + KWin = false; + blog(LOG_INFO, "not using KWin compat"); + } else { + blog(LOG_INFO, "using KWin compat"); + } + initXss(); initProcps(); initProc2(); @@ -583,6 +610,8 @@ void PlatformCleanup() cleanupHelper(libproc2); cleanupDisplay(); XSetErrorHandler(NULL); + if (KWin && !KWinScriptObjectPath.isEmpty()) + stopKWinScript(KWinScriptObjectPath); } } // namespace advss diff --git a/lib/linux/kwin-helpers.cpp b/lib/linux/kwin-helpers.cpp new file mode 100644 index 00000000..90c252e4 --- /dev/null +++ b/lib/linux/kwin-helpers.cpp @@ -0,0 +1,166 @@ +#include "kwin-helpers.h" + +#include "log-helper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace advss { + +int FocusNotifier::activePID = -1; +std::string FocusNotifier::activeTitle = {}; + +int FocusNotifier::getActiveWindowPID() +{ + return activePID; +} + +std::string FocusNotifier::getActiveWindowTitle() +{ + return activeTitle; +} + +void FocusNotifier::focusChanged(const int pid) +{ + activePID = pid; +} + +void FocusNotifier::focusTitle(const QString &title) +{ + activeTitle = title.toStdString(); +} + +bool isKWinAvailable() +{ + const QDBusConnectionInterface *interface = + QDBusConnection::sessionBus().interface(); + if (!interface) + return false; + + const QStringList services = + interface->registeredServiceNames().value(); + return services.contains("org.kde.KWin"); +} + +bool startKWinScript(QString &scriptObjectPath) +{ + const QString scriptPath = + "/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js"; + + const QString script = + R"(workspace.windowActivated.connect(function(client) { +if (!client) return; +if (!client.pid) return; +if (!client.caption) return; + +callDBus( + "com.github.AdvancedSceneSwitcher", + "/com/github/AdvancedSceneSwitcher", + "com.github.AdvancedSceneSwitcher", + "focusChanged", + client.pid +); +callDBus( + "com.github.AdvancedSceneSwitcher", + "/com/github/AdvancedSceneSwitcher", + "com.github.AdvancedSceneSwitcher", + "focusTitle", + client.caption +); +}))"; + + if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) { + blog(LOG_ERROR, "error creating /tmp/AdvancedSceneSwitcher"); + return false; + } + + QFile scriptFile(scriptPath); + if (!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + blog(LOG_ERROR, + "error opening KWinFocusNotifier.js for writing"); + return false; + } + + scriptFile.setPermissions(QFileDevice::ReadOwner | + QFileDevice::WriteOwner); + QTextStream outputStream(&scriptFile); + outputStream << script; + scriptFile.close(); + + const QDBusConnection bus = QDBusConnection::sessionBus(); + + QDBusInterface scriptingIface("org.kde.KWin", "/Scripting", + "org.kde.kwin.Scripting", bus); + if (!scriptingIface.isValid()) { + return false; + } + + const QDBusReply scriptRunningReply = + scriptingIface.call("isScriptLoaded", scriptPath); + if (scriptRunningReply.isValid() && scriptRunningReply.value()) { + // script already registered and running, don't do it again + // this will leave the script running since we do not have + // a valid script id anymore, but at the very least this prevents + // it from running multiple times + return true; + } + + const QDBusReply scriptIdReply = + scriptingIface.call("loadScript", scriptPath); + if (!scriptIdReply.isValid()) { + return false; + } + + const int scriptId = scriptIdReply.value(); + scriptObjectPath = + QString("/Scripting/Script%1").arg(QString::number(scriptId)); + + QDBusInterface scriptRunner("org.kde.KWin", scriptObjectPath, + "org.kde.kwin.Script", bus); + if (!scriptRunner.isValid()) { + return false; + } + + const QDBusReply runReply = scriptRunner.call("run"); + return runReply.isValid(); +} + +bool stopKWinScript(const QString &scriptObjectPath) +{ + QDBusInterface scriptRunner("org.kde.KWin", scriptObjectPath, + "org.kde.kwin.Script", + QDBusConnection::sessionBus()); + if (!scriptRunner.isValid()) { + return false; + } + const QDBusReply stopReply = scriptRunner.call("stop"); + return stopReply.isValid(); +} + +bool registerKWinDBusListener(FocusNotifier *notifier) +{ + static const QString serviceName = "com.github.AdvancedSceneSwitcher"; + static const QString objectPath = "/com/github/AdvancedSceneSwitcher"; + auto bus = QDBusConnection::sessionBus(); + + if (bus.objectRegisteredAt(objectPath)) { + // already registered? + return true; + } + + if (!bus.registerService(serviceName)) { + return false; + } + + if (!bus.registerObject(objectPath, notifier, + QDBusConnection::ExportAllSlots)) { + return false; + } + return true; +} +} // namespace advss diff --git a/lib/linux/kwin-helpers.h b/lib/linux/kwin-helpers.h new file mode 100644 index 00000000..fbfcc438 --- /dev/null +++ b/lib/linux/kwin-helpers.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +namespace advss { + +class FocusNotifier final : public QObject { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher") + + static int activePID; + static std::string activeTitle; + +public: + using QObject::QObject; + + static int getActiveWindowPID(); + static std::string getActiveWindowTitle(); + +public slots: + void focusChanged(const int pid); + void focusTitle(const QString &title); +}; + +bool isKWinAvailable(); +bool startKWinScript(QString &scriptObjectPath); +bool stopKWinScript(const QString &scriptObjectPath); +bool registerKWinDBusListener(FocusNotifier *notifier); +void printDBusError(); + +} // namespace advss