kwin: emit window fullscreen info + refactor helper script

The helper script was also refactored to minimize the amount of callbacks added and the memory used. There's no need to keep callbacks attached for non-active windows, which happened before.

Also it should be more efficient and simpler to send info over with just a single DBus call (also if more fields were to be added).

Both the script and the helper app will send/print info only if it changed since the previous one. Otherwise we'd keep spamming fullscreen false update when navigating the desktop and so on.
This commit is contained in:
p0358
2026-02-19 22:29:57 +01:00
committed by dec05eba
parent 636eca0d0e
commit 52afad5824
4 changed files with 87 additions and 57 deletions

View File

@@ -5,8 +5,10 @@
namespace gsr { namespace gsr {
struct ActiveKwinWindow { struct ActiveKwinWindow {
std::string title = "Game"; std::string title = "Game";
bool fullscreen = false;
}; };
void start_kwin_helper_thread(); void start_kwin_helper_thread();
std::string get_current_kwin_window_title(); std::string get_current_kwin_window_title();
bool get_current_kwin_window_fullscreen();
} }

View File

@@ -2,6 +2,7 @@
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <string>
#include <sys/types.h> #include <sys/types.h>
#include <thread> #include <thread>
#include <cstdlib> #include <cstdlib>
@@ -24,7 +25,8 @@ namespace gsr {
std::cerr << "Started a KWin helper thread\n"; std::cerr << "Started a KWin helper thread\n";
char buffer[4096]; char buffer[4096];
const std::string prefix = "Active window title set to: "; const std::string prefix_title = "Active window title set to: ";
const std::string prefix_fullscreen = "Active window fullscreen state set to: ";
std::string line; std::string line;
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
@@ -34,15 +36,13 @@ namespace gsr {
line.pop_back(); line.pop_back();
} }
size_t pos = line.find(prefix); size_t pos = std::string::npos;
if (pos != std::string::npos) { if ((pos = line.find(prefix_title)) != std::string::npos) {
std::string title = line.substr(pos + prefix.length()); std::string title = line.substr(pos + prefix_title.length());
if (title == "gsr ui" || title == "gsr notify") {
continue; // ignore the overlay and notification
}
active_kwin_window.title = std::move(title); active_kwin_window.title = std::move(title);
} else if ((pos = line.find(prefix_fullscreen)) != std::string::npos) {
std::string fullscreen = line.substr(pos + prefix_fullscreen.length());
active_kwin_window.fullscreen = fullscreen == "1";
} }
} }
@@ -53,6 +53,10 @@ namespace gsr {
return active_kwin_window.title; return active_kwin_window.title;
} }
bool get_current_kwin_window_fullscreen() {
return active_kwin_window.fullscreen;
}
void start_kwin_helper_thread() { void start_kwin_helper_thread() {
if (kwin_helper_thread_started) { if (kwin_helper_thread_started) {
return; return;

View File

@@ -1,52 +1,51 @@
const DAEMON_DBUS_NAME = "com.dec05eba.gpu_screen_recorder"; const DAEMON_DBUS_NAME = "com.dec05eba.gpu_screen_recorder";
// utils function dbusSendUpdateActiveWindow(title, isFullscreen) {
function sendNewActiveWindowTitle(title) {
callDBus( callDBus(
DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME, DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME,
"setActiveWindowTitle", title "updateActiveWindow",
title, isFullscreen,
); );
} }
function sendNewActiveWindowFullscreen(isFullscreen) { let prevWindow = null;
callDBus( let prevEmitActiveWindowUpdate = null;
DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME,
"setActiveWindowFullscreen", isFullscreen let prevCaption = null;
); let prevFullScreen = null;
function emitActiveWindowUpdate(window) {
if (workspace.activeWindow === window) {
let caption = window.caption || "";
let fullScreen = window.fullScreen || false;
if (caption !== prevCaption || fullScreen !== prevFullScreen) {
dbusSendUpdateActiveWindow(caption, fullScreen);
prevCaption = caption;
prevFullScreen = fullScreen;
}
}
} }
// track handlers to avoid duplicates function subscribeToWindow(window) {
const windowEventHandlers = new Map(); if (!window) return;
if (prevWindow !== window) {
function subscribeToClient(client) { if (prevWindow !== null) {
if (!client || windowEventHandlers.has(client)) return; prevWindow.captionChanged.disconnect(prevEmitActiveWindowUpdate);
prevWindow.fullScreenChanged.disconnect(prevEmitActiveWindowUpdate);
const emitActiveTitle = () => {
if (workspace.activeWindow === client) {
sendNewActiveWindowTitle(client.caption || "");
} }
}; let emitActiveWindowUpdateBound = emitActiveWindowUpdate.bind(null, window);
window.captionChanged.connect(emitActiveWindowUpdateBound);
const emitActiveFullscreen = () => { window.fullScreenChanged.connect(emitActiveWindowUpdateBound);
if (workspace.activeWindow === client) { prevWindow = window;
sendNewActiveWindowFullscreen(client.fullScreen); prevEmitActiveWindowUpdate = emitActiveWindowUpdateBound;
} }
};
windowEventHandlers.set(client, {
title: emitActiveTitle,
fs: emitActiveFullscreen,
});
client.captionChanged.connect(emitActiveTitle);
client.fullScreenChanged.connect(emitActiveFullscreen);
} }
function updateActiveWindow(client) { function updateActiveWindow(window) {
if (!client) return; if (!window) return;
sendNewActiveWindowTitle(client.caption || ""); if (window.resourceName === "gsr-ui" || window.resourceName === "gsr-notify") return; // ignore the overlay and notification
sendNewActiveWindowFullscreen(client.fullScreen); emitActiveWindowUpdate(window);
subscribeToClient(client); subscribeToWindow(window);
} }
// handle window focus changes // handle window focus changes
@@ -55,4 +54,4 @@ workspace.windowActivated.connect(updateActiveWindow);
// handle initial state // handle initial state
if (workspace.activeWindow) { if (workspace.activeWindow) {
updateActiveWindow(workspace.activeWindow); updateActiveWindow(workspace.activeWindow);
} }

View File

@@ -8,8 +8,9 @@ static const char* INTROSPECTION_XML =
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
"<node>\n" "<node>\n"
" <interface name='com.dec05eba.gpu_screen_recorder'>\n" " <interface name='com.dec05eba.gpu_screen_recorder'>\n"
" <method name='setActiveWindowTitle'>\n" " <method name='updateActiveWindow'>\n"
" <arg type='s' name='title' direction='in'/>\n" " <arg type='s' name='title' direction='in'/>\n"
" <arg type='b' name='fullscreen' direction='in'/>\n"
" </method>\n" " </method>\n"
" </interface>\n" " </interface>\n"
" <interface name='org.freedesktop.DBus.Introspectable'>\n" " <interface name='org.freedesktop.DBus.Introspectable'>\n"
@@ -23,6 +24,7 @@ static const char* INTROSPECTION_XML =
class GsrKwinHelper { class GsrKwinHelper {
public: public:
std::string active_window_title; std::string active_window_title;
bool active_window_fullscreen;
DBusConnection* connection = nullptr; DBusConnection* connection = nullptr;
DBusError err; DBusError err;
@@ -83,8 +85,8 @@ public:
if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) {
handle_introspect(msg); handle_introspect(msg);
} else if (dbus_message_is_method_call(msg, "com.dec05eba.gpu_screen_recorder", "setActiveWindowTitle")) { } else if (dbus_message_is_method_call(msg, "com.dec05eba.gpu_screen_recorder", "updateActiveWindow")) {
handle_set_title(msg); handle_update_active_window(msg);
} }
dbus_message_unref(msg); dbus_message_unref(msg);
@@ -108,9 +110,10 @@ public:
dbus_message_unref(reply); dbus_message_unref(reply);
} }
void handle_set_title(DBusMessage* msg) { void handle_update_active_window(DBusMessage* msg) {
DBusMessageIter args; DBusMessageIter args;
const char* title = nullptr; DBusBasicValue title;
DBusBasicValue fullscreen;
if (!dbus_message_iter_init(msg, &args)) { if (!dbus_message_iter_init(msg, &args)) {
send_error_reply(msg, "No arguments provided"); send_error_reply(msg, "No arguments provided");
@@ -123,15 +126,37 @@ public:
} }
dbus_message_iter_get_basic(&args, &title); dbus_message_iter_get_basic(&args, &title);
if (!dbus_message_iter_next(&args)) {
send_error_reply(msg, "Not enough arguments provided");
return;
}
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_BOOLEAN) {
send_error_reply(msg, "Expected boolean argument");
return;
}
dbus_message_iter_get_basic(&args, &fullscreen);
if (title) { if (title.str) {
active_window_title = title; if (active_window_title != title.str) {
std::cout << "Active window title set to: " << active_window_title << "\n"; active_window_title = title.str;
std::cout.flush(); std::cout << "Active window title set to: " << active_window_title << "\n";
send_success_reply(msg); std::cout.flush();
}
} else { } else {
send_error_reply(msg, "Failed to read string"); send_error_reply(msg, "Failed to read string");
return;
} }
if (active_window_fullscreen != fullscreen.bool_val) {
active_window_fullscreen = fullscreen.bool_val;
std::cout << "Active window fullscreen state set to: " << active_window_fullscreen << "\n";
std::cout.flush();
}
send_success_reply(msg);
} }
void send_success_reply(DBusMessage* msg) { void send_success_reply(DBusMessage* msg) {