Re-add kwin and hyprland helper to get window title, this time without a thread

This commit is contained in:
dec05eba
2026-04-27 00:04:29 +02:00
parent 57c60b6aee
commit 71d7477aea
15 changed files with 318 additions and 187 deletions

View File

@@ -0,0 +1,20 @@
#pragma once
#include <string>
namespace gsr {
class DesktopEnvironment {
public:
DesktopEnvironment() = default;
DesktopEnvironment(const DesktopEnvironment&) = delete;
DesktopEnvironment& operator=(const DesktopEnvironment&) = delete;
virtual ~DesktopEnvironment() = default;
virtual bool start() = 0;
virtual void update() = 0;
// Return an empty string if none
virtual std::string get_focused_window_title() = 0;
// Return an empty string if unknown
//virtual std::string get_focused_monitor_name() = 0;
};
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "DesktopEnvironment.hpp"
#include <sys/types.h>
#include <stdio.h>
namespace gsr {
class DesktopEnvironmentHyprland : public DesktopEnvironment {
public:
DesktopEnvironmentHyprland() = default;
DesktopEnvironmentHyprland(const DesktopEnvironmentHyprland&) = delete;
DesktopEnvironmentHyprland& operator=(const DesktopEnvironmentHyprland&) = delete;
~DesktopEnvironmentHyprland();
bool start() override;
void update() override;
std::string get_focused_window_title() override;
//std::string get_focused_monitor_name() override;
private:
void shutdown();
private:
pid_t process_id = -1;
FILE *stdout_file = nullptr;
int read_fd = -1;
char line_buffer[1024];
std::string line;
std::string window_title;
};
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "DesktopEnvironment.hpp"
namespace gsr {
class DesktopEnvironmentKde : public DesktopEnvironment {
public:
DesktopEnvironmentKde() = default;
DesktopEnvironmentKde(const DesktopEnvironmentKde&) = delete;
DesktopEnvironmentKde& operator=(const DesktopEnvironmentKde&) = delete;
~DesktopEnvironmentKde();
bool start() override;
void update() override;
std::string get_focused_window_title() override;
//std::string get_focused_monitor_name() override;
private:
void shutdown();
private:
pid_t process_id = -1;
FILE *stdout_file = nullptr;
int read_fd = -1;
char line_buffer[1024];
std::string line;
std::string window_title;
std::string monitor_name;
};
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "DesktopEnvironment.hpp"
#include <X11/Xlib.h>
namespace gsr {
class DesktopEnvironmentX11 : public DesktopEnvironment {
public:
DesktopEnvironmentX11(Display *dpy) : dpy(dpy) {}
DesktopEnvironmentX11(const DesktopEnvironmentX11&) = delete;
DesktopEnvironmentX11& operator=(const DesktopEnvironmentX11&) = delete;
~DesktopEnvironmentX11();
bool start() override;
void update() override;
std::string get_focused_window_title() override;
//std::string get_focused_monitor_name() override;
private:
Display *dpy = NULL;
};
}

View File

@@ -1,13 +0,0 @@
#pragma once
#include <string>
namespace gsr {
struct ActiveHyprlandWindow {
std::string window_id = "";
std::string title = "Game";
};
void start_hyprland_listener_thread();
std::string get_current_hyprland_window_title();
}

View File

@@ -1,16 +0,0 @@
#pragma once
#include <string>
namespace gsr {
struct ActiveKwinWindow {
std::string title = "Game";
bool fullscreen = false;
std::string monitorName = "";
};
void start_kwin_helper_thread();
std::string get_current_kwin_window_title();
bool get_current_kwin_window_fullscreen();
std::string get_current_kwin_window_monitor_name();
}

View File

@@ -12,6 +12,7 @@
#include "ClipboardFile.hpp"
#include "LedIndicator.hpp"
#include "CursorTracker/CursorTracker.hpp"
#include "DesktopEnvironment/DesktopEnvironment.hpp"
#include <mglpp/window/Window.hpp>
#include <mglpp/window/Event.hpp>
@@ -304,5 +305,6 @@ namespace gsr {
std::unique_ptr<LedIndicator> led_indicator = nullptr;
bool supports_window_title = false;
std::unique_ptr<DesktopEnvironment> desktop_environment;
};
}

View File

@@ -30,8 +30,8 @@ namespace gsr {
std::optional<std::string> get_window_title(Display *dpy, Window window);
Window get_focused_window(Display *dpy, WindowCaptureType cap_type, bool fallback_cursor_focused = true);
std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type, bool fallback_cursor_focused = true);
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window);
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window);
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, std::string_view ignore_window_title);
std::string get_window_name_at_cursor_position(Display *dpy, std::string_view ignore_window_title);
void set_window_size_not_resizable(Display *dpy, Window window, int width, int height);
Window window_get_target_window_child(Display *display, Window window);
unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size);

View File

@@ -40,9 +40,10 @@ src = [
'src/GlobalHotkeys/GlobalHotkeysJoystick.cpp',
'src/CursorTracker/CursorTrackerX11.cpp',
'src/CursorTracker/CursorTrackerWayland.cpp',
'src/DesktopEnvironment/DesktopEnvironmentX11.cpp',
'src/DesktopEnvironment/DesktopEnvironmentHyprland.cpp',
'src/DesktopEnvironment/DesktopEnvironmentKde.cpp',
'src/Utils.cpp',
'src/HyprlandWorkaround.cpp',
'src/KwinWorkaround.cpp',
'src/WindowUtils.cpp',
'src/RegionSelector.cpp',
'src/Config.cpp',

View File

@@ -1,15 +1,11 @@
#include "../include/HyprlandWorkaround.hpp"
#include "../include/Process.hpp"
#include "../../include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp"
#include "../../include/Process.hpp"
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <thread>
#include <mutex>
namespace gsr {
static ActiveHyprlandWindow active_hyprland_window;
static bool hyprland_listener_thread_started = false;
static std::mutex active_window_mutex;
static constexpr std::string_view prefix = "Window title changed: ";
static bool get_hyprland_socket_path(char *path, int path_len) {
const char* xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
@@ -27,22 +23,27 @@ namespace gsr {
return true;
}
static void hyprland_listener_thread() {
DesktopEnvironmentHyprland::~DesktopEnvironmentHyprland() {
shutdown();
}
bool DesktopEnvironmentHyprland::start() {
if(process_id > 0) {
fprintf(stderr, "Error: DesktopEnvironmentHyprland: already running\n");
return false;
}
char hyprland_socket_path[256];
char buffer[4096];
const std::string prefix = "Window title changed: ";
std::string line;
FILE *stdout_file = nullptr;
// Get path inside the flatpak before flatpak-spawn is called because of a bug in flatpak:
// https://github.com/flatpak/flatpak/issues/6486
// where environment variables are missing in flatpak-spawn --host
if(!get_hyprland_socket_path(hyprland_socket_path, sizeof(hyprland_socket_path))) {
fprintf(stderr, "Error: HyprlandWorkaround: failed to get hyprland socket path\n");
return;
fprintf(stderr, "Error: DesktopEnvironmentHyprland: failed to get hyprland socket path\n");
return false;
}
const bool inside_flatpak = access("/app/manifest.json", F_OK) == 0;
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
size_t arg_index = 0;
const char *args[6];
@@ -59,44 +60,27 @@ namespace gsr {
args[arg_index++] = hyprland_socket_path;
args[arg_index++] = nullptr;
int read_fd = -1;
const pid_t process_id = exec_program(args, &read_fd, false);
process_id = exec_program(args, &read_fd, false);
if(process_id == -1) {
fprintf(stderr, "Error: HyprlandWorkaround: failed to execute gsr-hyprland-helper\n");
return;
fprintf(stderr, "Error: DesktopEnvironmentHyprland: failed to execute gsr-hyprland-helper\n");
return false;
}
fcntl(read_fd, F_SETFL, fcntl(read_fd, F_GETFL) | O_NONBLOCK);
stdout_file = fdopen(read_fd, "r");
if (!stdout_file) {
perror("Error: HyprlandWorkaround: fdopen");
goto done;
return;
perror("Error: DesktopEnvironmentHyprland: fdopen");
shutdown();
return false;
}
read_fd = -1;
fprintf(stderr, "Info: HyprlandWorkaround: started Hyprland helper thread\n");
while (fgets(buffer, sizeof(buffer), stdout_file) != nullptr) {
line = buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
size_t pos = line.find(prefix);
if (pos != std::string::npos) {
std::lock_guard<std::mutex> lock(active_window_mutex);
active_hyprland_window.title = line.substr(pos + prefix.length());
}
}
done:
if(stdout_file)
fclose(stdout_file);
if(read_fd > 0)
close(read_fd);
fprintf(stderr, "Info: DesktopEnvironmentHyprland: started Hyprland helper process\n");
return true;
}
void DesktopEnvironmentHyprland::shutdown() {
if(process_id > 0) {
kill(process_id, SIGKILL);
int status;
@@ -104,23 +88,40 @@ namespace gsr {
perror("waitpid failed");
/* Ignore... */
}
process_id = -1;
}
if(stdout_file) {
fclose(stdout_file);
stdout_file = nullptr;
}
if(read_fd > 0) {
close(read_fd);
read_fd = -1;
}
}
std::string get_current_hyprland_window_title() {
std::lock_guard<std::mutex> lock(active_window_mutex);
return active_hyprland_window.title;
}
void DesktopEnvironmentHyprland::update() {
while (fgets(line_buffer, sizeof(line_buffer), stdout_file) != nullptr) {
line = line_buffer;
void start_hyprland_listener_thread() {
if (hyprland_listener_thread_started) {
return;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
const size_t pos = line.find(prefix);
if (pos != std::string::npos) {
window_title = line.substr(pos + prefix.length());
}
}
hyprland_listener_thread_started = true;
std::thread([&] {
hyprland_listener_thread();
}).detach();
}
std::string DesktopEnvironmentHyprland::get_focused_window_title() {
return window_title;
}
// std::string DesktopEnvironmentHyprland::get_focused_monitor_name() {
// return "";
// }
}

View File

@@ -0,0 +1,91 @@
#include "../../include/DesktopEnvironment/DesktopEnvironmentKde.hpp"
#include "../../include/Process.hpp"
#include <fcntl.h>
#include <sys/wait.h>
namespace gsr {
static constexpr std::string_view prefix_title = "Active window title set to: ";
static constexpr std::string_view prefix_fullscreen = "Active window fullscreen state set to: ";
static constexpr std::string_view prefix_monitor = "Active window monitor name set to: ";
DesktopEnvironmentKde::~DesktopEnvironmentKde() {
shutdown();
}
bool DesktopEnvironmentKde::start() {
if(process_id > 0) {
fprintf(stderr, "Error: DesktopEnvironmentKde: already running\n");
return false;
}
const char *args[] = { "gsr-kwin-helper", NULL };
process_id = exec_program(args, &read_fd, false);
if(process_id == -1) {
fprintf(stderr, "Error: DesktopEnvironmentKde: failed to execute gsr-kwin-helper\n");
return false;
}
fcntl(read_fd, F_SETFL, fcntl(read_fd, F_GETFL) | O_NONBLOCK);
stdout_file = fdopen(read_fd, "r");
if (!stdout_file) {
perror("Error: DesktopEnvironmentKde: fdopen");
shutdown();
return false;
}
read_fd = -1;
fprintf(stderr, "Info: DesktopEnvironmentKde: started kwin helper process\n");
return true;
}
void DesktopEnvironmentKde::shutdown() {
if(process_id > 0) {
kill(process_id, SIGKILL);
int status;
if(waitpid(process_id, &status, 0) == -1) {
perror("waitpid failed");
/* Ignore... */
}
process_id = -1;
}
if(stdout_file) {
fclose(stdout_file);
stdout_file = nullptr;
}
if(read_fd > 0) {
close(read_fd);
read_fd = -1;
}
}
void DesktopEnvironmentKde::update() {
while (fgets(line_buffer, sizeof(line_buffer), stdout_file) != nullptr) {
line = line_buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
size_t pos = std::string::npos;
if ((pos = line.find(prefix_title)) != std::string::npos) {
window_title = line.substr(pos + prefix_title.length());
} else if ((pos = line.find(prefix_fullscreen)) != std::string::npos) {
//fullscreen = line.substr(pos + prefix_fullscreen.length()) == "1";
} else if ((pos = line.find(prefix_monitor)) != std::string::npos) {
monitor_name = line.substr(pos + prefix_monitor.length());
}
}
}
std::string DesktopEnvironmentKde::get_focused_window_title() {
return window_title;
}
// std::string DesktopEnvironmentKde::get_focused_monitor_name() {
// return monitor_name;
// }
}

View File

@@ -0,0 +1,34 @@
#include "../../include/DesktopEnvironment/DesktopEnvironmentX11.hpp"
#include "../../include/WindowUtils.hpp"
namespace gsr {
DesktopEnvironmentX11::~DesktopEnvironmentX11() {
}
bool DesktopEnvironmentX11::start() {
return true;
}
void DesktopEnvironmentX11::update() {
}
std::string DesktopEnvironmentX11::get_focused_window_title() {
std::string focused_window_title;
if(!dpy) {
fprintf(stderr, "Error: DesktopEnvironmentX11: get_focused_window_title: display object is NULL, returning empty string\n");
return focused_window_title;
}
focused_window_title = get_window_name_at_cursor_position(dpy, "gsr-ui");
if(focused_window_title.empty())
focused_window_title = get_focused_window_name(dpy, WindowCaptureType::FOCUSED, false);
return focused_window_title;
}
// std::string DesktopEnvironmentX11::get_focused_monitor_name() {
// return "";
// }
}

View File

@@ -1,80 +0,0 @@
#include "../include/KwinWorkaround.hpp"
#include <cstddef>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <thread>
#include <mutex>
namespace gsr {
static ActiveKwinWindow active_kwin_window;
static bool kwin_helper_thread_started = false;
static std::mutex active_window_mutex;
void kwin_script_thread() {
FILE* pipe = popen("gsr-kwin-helper", "r");
if (!pipe) {
std::cerr << "Failed to start gsr-kwin-helper process\n";
return;
}
std::cerr << "Started a KWin helper thread\n";
char buffer[4096];
const std::string prefix_title = "Active window title set to: ";
const std::string prefix_fullscreen = "Active window fullscreen state set to: ";
const std::string prefix_monitor = "Active window monitor name set to: ";
std::string line;
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
line = buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
std::lock_guard<std::mutex> lock(active_window_mutex);
size_t pos = std::string::npos;
if ((pos = line.find(prefix_title)) != std::string::npos) {
std::string title = line.substr(pos + prefix_title.length());
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";
} else if ((pos = line.find(prefix_monitor)) != std::string::npos) {
std::string monitorName = line.substr(pos + prefix_monitor.length());
active_kwin_window.monitorName = std::move(monitorName);
}
}
pclose(pipe);
}
std::string get_current_kwin_window_title() {
std::lock_guard<std::mutex> lock(active_window_mutex);
return active_kwin_window.title;
}
bool get_current_kwin_window_fullscreen() {
std::lock_guard<std::mutex> lock(active_window_mutex);
return active_kwin_window.fullscreen;
}
std::string get_current_kwin_window_monitor_name() {
std::lock_guard<std::mutex> lock(active_window_mutex);
return active_kwin_window.monitorName;
}
void start_kwin_helper_thread() {
if (kwin_helper_thread_started) {
return;
}
kwin_helper_thread_started = true;
std::thread([&] {
kwin_script_thread();
}).detach();
}
}

View File

@@ -11,8 +11,9 @@
#include "../include/gui/GlobalSettingsPage.hpp"
#include "../include/gui/Utils.hpp"
#include "../include/Translation.hpp"
#include "../include/KwinWorkaround.hpp"
#include "../include/HyprlandWorkaround.hpp"
#include "../include/DesktopEnvironment/DesktopEnvironmentX11.hpp"
#include "../include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp"
#include "../include/DesktopEnvironment/DesktopEnvironmentKde.hpp"
#include "../include/gui/PageStack.hpp"
#include "../include/WindowUtils.hpp"
#include "../include/GlobalHotkeys/GlobalHotkeys.hpp"
@@ -573,11 +574,26 @@ namespace gsr {
if(this->gsr_info.system_info.display_server == DisplayServer::X11) {
cursor_tracker = std::make_unique<CursorTrackerX11>((Display*)mgl_get_context()->connection);
desktop_environment = std::make_unique<DesktopEnvironmentX11>(x11_dpy);
supports_window_title = true;
} else if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
const std::string wm_name = x11_dpy ? get_window_manager_name(x11_dpy) : "";
const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos;
const bool is_kwin_wayland = wm_name == "KWin" && gsr_info.system_info.display_server == DisplayServer::WAYLAND;
if(!this->gsr_info.gpu_info.card_path.empty())
cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str(), wayland_dpy);
if(is_hyprland) {
desktop_environment = std::make_unique<DesktopEnvironmentHyprland>();
supports_window_title = true;
} else if(is_kwin_wayland) {
desktop_environment = std::make_unique<DesktopEnvironmentKde>();
supports_window_title = true;
} else {
desktop_environment = std::make_unique<DesktopEnvironmentX11>(x11_dpy);
}
if(!config.main_config.wayland_warning_shown) {
config.main_config.wayland_warning_shown = true;
save_config(config);
@@ -585,6 +601,7 @@ namespace gsr {
}
}
desktop_environment->start();
update_led_indicator_after_settings_change();
gsr_game_tracker_process_id = launch_gsr_game_tracker(&gsr_game_tracker_process_output_fd);
@@ -855,6 +872,7 @@ namespace gsr {
cursor_tracker->update();
}
desktop_environment->update();
handle_keyboard_mapping_event();
region_selector.poll_events();
@@ -2087,16 +2105,9 @@ namespace gsr {
}
void Overlay::save_video_in_current_game_directory(std::string &video_filepath, NotificationType notification_type) {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
const std::string video_filename = filepath_get_filename(video_filepath.c_str());
const Window gsr_ui_window = window ? (Window)window->get_system_handle() : None;
std::string focused_window_name = get_window_name_at_cursor_position(display, gsr_ui_window);
if(focused_window_name.empty())
focused_window_name = get_focused_window_name(display, WindowCaptureType::FOCUSED, false);
std::string focused_window_name = desktop_environment->get_focused_window_title();
if(focused_window_name.empty())
focused_window_name = "Game";

View File

@@ -476,7 +476,7 @@ namespace gsr {
return result;
}
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window) {
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, std::string_view ignore_window_title) {
std::string result;
Window root;
@@ -487,7 +487,7 @@ namespace gsr {
return result;
for(int i = (int)num_children - 1; i >= 0; --i) {
if(children[i] == ignore_window)
if(get_window_title(dpy, children[i]) == ignore_window_title)
continue;
XWindowAttributes attr;
@@ -498,10 +498,10 @@ namespace gsr {
if(position.x >= attr.x && position.x <= attr.x + attr.width && position.y >= attr.y && position.y <= attr.y + attr.height) {
const Window real_window = window_get_target_window_child(dpy, children[i]);
if(!real_window || real_window == ignore_window)
const std::optional<std::string> window_title = get_window_title(dpy, real_window);
if(!real_window || window_title == ignore_window_title)
continue;
const std::optional<std::string> window_title = get_window_title(dpy, real_window);
if(window_title)
result = strip(window_title.value());
@@ -513,10 +513,10 @@ namespace gsr {
return result;
}
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window) {
std::string get_window_name_at_cursor_position(Display *dpy, std::string_view ignore_window_title) {
Window cursor_window;
const mgl::vec2i cursor_position = get_cursor_position(dpy, &cursor_window);
return get_window_name_at_position(dpy, cursor_position, ignore_window);
return get_window_name_at_position(dpy, cursor_position, ignore_window_title);
}
void set_window_size_not_resizable(Display *dpy, Window window, int width, int height) {