Compare commits

..

13 Commits

Author SHA1 Message Date
dec05eba
d0c581684b 1.10.3 2026-01-24 12:43:10 +01:00
dec05eba
d4dbb27213 Fix window name on x11 2026-01-24 01:09:11 +01:00
dec05eba
bfc7df5c56 Mention that libdbus is a new dependency 2026-01-24 01:01:26 +01:00
dec05eba
9c5688f61b Simplify gsr-hyprland-helper, some cleanups 2026-01-24 00:55:47 +01:00
Andrew
9ccb4dd541 Removed flatpak KWin title blocker in the overlay 2026-01-23 00:03:02 +01:00
Andrew
1e3e76fcee Hyprland and KDE workarounds should work with flatpak now 2026-01-22 10:41:02 +01:00
Andrew
9c9df47d62 Fix to ignore GSR Overlay in KWin workaround 2026-01-22 10:40:58 +01:00
Andrew
00ceaa989d Added KWin workaround to get current window title 2026-01-22 10:40:51 +01:00
Andrew
23b1526092 Added Hyprland workaround to get current window title 2026-01-22 10:40:40 +01:00
dec05eba
9339d6760e Force dont restore session portal when using window capture (portal) with hotkey on wayland 2026-01-21 01:51:25 +01:00
dec05eba
8bf6e533c5 1.10.2 2026-01-20 18:45:56 +01:00
dec05eba
902fc7f6a9 Force h264 for rumble/kick 2026-01-20 18:45:32 +01:00
dec05eba
794064a8b8 Fix incorrect region captured on wayland when using monitor scaling and without letting x11 scale monitors 2026-01-20 18:31:29 +01:00
21 changed files with 977 additions and 286 deletions

3
.gitignore vendored
View File

@@ -6,3 +6,6 @@ compile_commands.json
**/xdg-output-unstable-v1-protocol.c
depends/.wraplock
.cache
build/

View File

@@ -27,6 +27,7 @@ These are the dependencies needed to build GPU Screen Recorder UI:
* linux-api-headers
* libpulse (libpulse-simple)
* libdrm
* libdbus
* wayland (wayland-client, wayland-egl, wayland-scanner)
* setcap (libcap)

4
TODO
View File

@@ -256,4 +256,6 @@ The flatpak version can for some get stuck at shutdown when instant replay is ru
Redesign the UI to allow capturing multiple video sources. Move webcam to capture sources as well then. Maybe design the UI to work more like obs studio then, where you start recording and then add sources at capture time, with a preview.
Add option to choose video container (either flv or mpegts) for youtube livestreaming.
Add option to choose video container (either flv or mpegts) for youtube livestreaming.
Get wayland cursor position for region selector, otherwise the start position before the cursor moves is off.

View File

@@ -1,44 +1,23 @@
#pragma once
#include "CursorTracker.hpp"
#include <stdint.h>
#include <vector>
struct wl_display;
struct wl_registry;
struct wl_output;
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
namespace gsr {
struct WaylandOutput {
uint32_t wl_name;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
mgl::vec2i pos;
mgl::vec2i size;
int32_t transform;
std::string name;
};
class CursorTrackerWayland : public CursorTracker {
public:
CursorTrackerWayland(const char *card_path);
CursorTrackerWayland(const char *card_path, struct wl_display *wayland_dpy);
CursorTrackerWayland(const CursorTrackerWayland&) = delete;
CursorTrackerWayland& operator=(const CursorTrackerWayland&) = delete;
~CursorTrackerWayland();
void update() override;
std::optional<CursorInfo> get_latest_cursor_info() override;
std::vector<WaylandOutput> monitors;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
private:
void clear_monitors();
void set_monitor_outputs_from_xdg_output(struct wl_display *dpy);
private:
int drm_fd = -1;
mgl::vec2i latest_cursor_position; // Position of the cursor within the monitor
int latest_crtc_id = -1;
struct wl_display *wayland_dpy = nullptr;
};
}

View File

@@ -0,0 +1,13 @@
#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

@@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace gsr {
struct ActiveKwinWindow {
std::string title = "Game";
};
void start_kwin_helper_thread();
std::string get_current_kwin_window_title();
}

View File

@@ -24,6 +24,8 @@
#include <array>
struct wl_display;
namespace gsr {
class DropdownButton;
class GlobalHotkeys;
@@ -162,6 +164,9 @@ namespace gsr {
void on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type);
bool update_compositor_texture(const Monitor &monitor);
void add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size);
void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const std::string &region_area_option, RecordForceType force_type = RecordForceType::NONE);
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
void force_window_on_top();
@@ -250,6 +255,8 @@ namespace gsr {
Display *x11_dpy = nullptr;
XEvent x11_mapping_xev;
struct wl_display *wayland_dpy = nullptr;
mgl::Clock replay_save_clock;
bool replay_save_show_notification = false;
ReplayStartupMode replay_startup_mode = ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;

View File

@@ -7,6 +7,8 @@
#include <X11/Xlib.h>
struct wl_display;
namespace gsr {
struct Region {
mgl::vec2i pos;
@@ -28,7 +30,7 @@ namespace gsr {
bool poll_events();
bool take_selection();
bool take_canceled();
Region get_selection() const;
Region get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const;
private:
void on_button_press(const void *de);
void on_button_release(const void *de);

View File

@@ -6,6 +6,8 @@
#include <optional>
#include <X11/Xlib.h>
struct wl_display;
namespace gsr {
enum class WindowCaptureType {
FOCUSED,
@@ -13,8 +15,8 @@ namespace gsr {
};
struct Monitor {
mgl::vec2i position;
mgl::vec2i size;
mgl::vec2i position; // Logical position on Wayland
mgl::vec2i size; // Logical size on Wayland
std::string name;
};
@@ -30,6 +32,7 @@ namespace gsr {
std::string get_window_manager_name(Display *display);
bool is_compositor_running(Display *dpy, int screen);
std::vector<Monitor> get_monitors(Display *dpy);
std::vector<Monitor> get_monitors_wayland(struct wl_display *dpy);
void xi_grab_all_mouse_devices(Display *dpy);
void xi_ungrab_all_mouse_devices(Display *dpy);
void xi_warp_all_mouse_devices(Display *dpy, mgl::vec2i position);

View File

@@ -1,4 +1,4 @@
project('gsr-ui', ['c', 'cpp'], version : '1.10.1', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
project('gsr-ui', ['c', 'cpp'], version : '1.10.3', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
@@ -41,6 +41,8 @@ src = [
'src/CursorTracker/CursorTrackerX11.cpp',
'src/CursorTracker/CursorTrackerWayland.cpp',
'src/Utils.cpp',
'src/HyprlandWorkaround.cpp',
'src/KwinWorkaround.cpp',
'src/WindowUtils.cpp',
'src/RegionSelector.cpp',
'src/WindowSelector.cpp',
@@ -70,6 +72,8 @@ icons_path = join_paths(prefix, datadir, 'icons')
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.12.0"', language: ['c', 'cpp'])
add_project_arguments('-DKWIN_HELPER_SCRIPT_PATH="' + gsr_ui_resources_path + '/gsrkwinhelper.js"', language: ['c', 'cpp'])
executable(
meson.project_name(),
src,
@@ -111,6 +115,31 @@ executable(
install : true
)
executable(
'gsr-kwin-helper',
[
'tools/gsr-kwin-helper/main.cpp'
],
install : true,
dependencies: [
dependency('dbus-1'),
]
)
install_data(
'tools/gsr-kwin-helper/gsrkwinhelper.js',
install_dir: gsr_ui_resources_path,
install_mode: 'rwxr-xr-x'
)
executable(
'gsr-hyprland-helper',
[
'tools/gsr-hyprland-helper/main.c'
],
install : true
)
install_subdir('images', install_dir : gsr_ui_resources_path)
install_subdir('fonts', install_dir : gsr_ui_resources_path)

View File

@@ -1,7 +1,7 @@
[package]
name = "gsr-ui"
type = "executable"
version = "1.10.1"
version = "1.10.3"
platforms = ["posix"]
[lang.cpp]

View File

@@ -1,11 +1,10 @@
#include "../../include/CursorTracker/CursorTrackerWayland.hpp"
#include "../../include/WindowUtils.hpp"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <wayland-client.h>
#include "xdg-output-unstable-v1-client-protocol.h"
namespace gsr {
static const int MAX_CONNECTORS = 32;
@@ -136,177 +135,14 @@ namespace gsr {
}
// Name is the crtc name. TODO: verify if this works on all wayland compositors
static const WaylandOutput* get_wayland_monitor_by_name(const std::vector<WaylandOutput> &monitors, const std::string &name) {
for(const WaylandOutput &monitor : monitors) {
static const Monitor* get_wayland_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) {
for(const Monitor &monitor : monitors) {
if(monitor.name == name)
return &monitor;
}
return nullptr;
}
static WaylandOutput* get_wayland_monitor_by_output(CursorTrackerWayland &cursor_tracker_wayland, struct wl_output *output) {
for(WaylandOutput &monitor : cursor_tracker_wayland.monitors) {
if(monitor.output == output)
return &monitor;
}
return nullptr;
}
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
(void)wl_output;
(void)phys_width;
(void)phys_height;
(void)subpixel;
(void)make;
(void)model;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->pos.x = x;
monitor->pos.y = y;
monitor->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
(void)wl_output;
(void)flags;
(void)refresh;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->size.x = width;
monitor->size.y = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
(void)data;
(void)wl_output;
}
static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
(void)data;
(void)wl_output;
(void)factor;
}
static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
(void)wl_output;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->name = name;
}
static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
(void)data;
(void)wl_output;
(void)description;
}
static const struct wl_output_listener output_listener = {
output_handle_geometry,
output_handle_mode,
output_handle_done,
output_handle_scale,
output_handle_name,
output_handle_description,
};
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n");
return;
}
struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4);
cursor_tracker_wayland->monitors.push_back(
WaylandOutput{
name,
output,
nullptr,
mgl::vec2i{0, 0},
mgl::vec2i{0, 0},
0,
""
});
wl_output_add_listener(output, &output_listener, cursor_tracker_wayland);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n");
return;
}
if(cursor_tracker_wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(cursor_tracker_wayland->xdg_output_manager);
cursor_tracker_wayland->xdg_output_manager = NULL;
}
cursor_tracker_wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
registry_add_object,
registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->pos.x = x;
monitor->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)data;
(void)xdg_output;
(void)width;
(void)height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_logical_position,
xdg_output_handle_logical_size,
xdg_output_handle_done,
xdg_output_handle_name,
xdg_output_handle_description,
};
/* Returns nullptr if not found */
static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) {
for(int i = 0; i < connectors->num_connectors; ++i) {
@@ -390,7 +226,7 @@ namespace gsr {
drmModeFreeResources(resources);
}
CursorTrackerWayland::CursorTrackerWayland(const char *card_path) {
CursorTrackerWayland::CursorTrackerWayland(const char *card_path, struct wl_display *wayland_dpy) : wayland_dpy(wayland_dpy) {
drm_fd = open(card_path, O_RDONLY);
if(drm_fd <= 0) {
fprintf(stderr, "Error: CursorTrackerWayland: failed to open %s\n", card_path);
@@ -402,7 +238,6 @@ namespace gsr {
}
CursorTrackerWayland::~CursorTrackerWayland() {
clear_monitors();
if(drm_fd > 0)
close(drm_fd);
}
@@ -465,80 +300,19 @@ namespace gsr {
drmModeFreePlaneResources(planes);
}
void CursorTrackerWayland::set_monitor_outputs_from_xdg_output(struct wl_display *dpy) {
if(!xdg_output_manager) {
fprintf(stderr, "Warning: CursorTrackerWayland::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n");
return;
}
for(WaylandOutput &monitor : monitors) {
monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, monitor.output);
zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor);
}
// Fetch xdg_output
wl_display_roundtrip(dpy);
}
void CursorTrackerWayland::clear_monitors() {
for(WaylandOutput &monitor : monitors) {
if(monitor.output) {
wl_output_destroy(monitor.output);
monitor.output = nullptr;
}
if(monitor.xdg_output) {
zxdg_output_v1_destroy(monitor.xdg_output);
monitor.xdg_output = nullptr;
}
}
monitors.clear();
}
std::optional<CursorInfo> CursorTrackerWayland::get_latest_cursor_info() {
if(drm_fd <= 0 || latest_crtc_id == -1)
if(drm_fd <= 0 || latest_crtc_id == -1 || !wayland_dpy)
return std::nullopt;
std::string monitor_name = get_monitor_name_from_crtc_id(drm_fd, latest_crtc_id);
if(monitor_name.empty())
return std::nullopt;
struct wl_display *dpy = wl_display_connect(nullptr);
if(!dpy) {
fprintf(stderr, "Error: CursorTrackerWayland::get_latest_cursor_info: failed to connect to the wayland server\n");
const std::vector<Monitor> wayland_monitors = get_monitors_wayland(wayland_dpy);
const Monitor *wayland_monitor = get_wayland_monitor_by_name(wayland_monitors, monitor_name);
if(!wayland_monitor)
return std::nullopt;
}
clear_monitors();
struct wl_registry *registry = wl_display_get_registry(dpy);
wl_registry_add_listener(registry, &registry_listener, this);
// Fetch globals
wl_display_roundtrip(dpy);
// Fetch wl_output
wl_display_roundtrip(dpy);
set_monitor_outputs_from_xdg_output(dpy);
mgl::vec2i cursor_position = latest_cursor_position;
const WaylandOutput *wayland_monitor = get_wayland_monitor_by_name(monitors, monitor_name);
if(!wayland_monitor) {
clear_monitors();
return std::nullopt;
}
cursor_position = wayland_monitor->pos + latest_cursor_position;
clear_monitors();
if(xdg_output_manager) {
zxdg_output_manager_v1_destroy(xdg_output_manager);
xdg_output_manager = nullptr;
}
wl_registry_destroy(registry);
wl_display_disconnect(dpy);
return CursorInfo{ cursor_position, std::move(monitor_name) };
return CursorInfo{ wayland_monitor->position + latest_cursor_position, std::move(monitor_name) };
}
}

View File

@@ -0,0 +1,62 @@
#include "../include/HyprlandWorkaround.hpp"
#include <iostream>
#include <unistd.h>
#include <thread>
namespace gsr {
static ActiveHyprlandWindow active_hyprland_window;
static bool hyprland_listener_thread_started = false;
static void hyprland_listener_thread() {
const bool inside_flatpak = access("/app/manifest.json", F_OK) == 0;
const char *hyprland_helper_bin =
inside_flatpak ?
"flatpak-spawn --host -- /var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/gsr-hyprland-helper"
: "gsr-hyprland-helper";
FILE* pipe = popen(hyprland_helper_bin, "r");
if (!pipe) {
std::cerr << "Failed to start gsr-hyprland-helper process\n";
return;
}
std::cerr << "Started Hyprland helper thread\n";
char buffer[4096];
const std::string prefix = "Window title changed: ";
std::string line;
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
line = buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
size_t pos = line.find(prefix);
if (pos != std::string::npos) {
active_hyprland_window.title = line.substr(pos + prefix.length());
}
}
pclose(pipe);
}
std::string get_current_hyprland_window_title() {
return active_hyprland_window.title;
}
void start_hyprland_listener_thread() {
if (hyprland_listener_thread_started) {
return;
}
hyprland_listener_thread_started = true;
std::thread([&] {
hyprland_listener_thread();
}).detach();
}
}

67
src/KwinWorkaround.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "../include/KwinWorkaround.hpp"
#include <cstddef>
#include <iostream>
#include <sys/types.h>
#include <thread>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
namespace gsr {
static ActiveKwinWindow active_kwin_window;
static bool kwin_helper_thread_started = false;
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 = "Active window title set to: ";
std::string line;
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
line = buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
size_t pos = line.find(prefix);
if (pos != std::string::npos) {
std::string title = line.substr(pos + prefix.length());
if (title == "gsr ui" || title == "gsr notify") {
continue; // ignore the overlay and notification
}
active_kwin_window.title = std::move(title);
}
}
pclose(pipe);
}
std::string get_current_kwin_window_title() {
return active_kwin_window.title;
}
void start_kwin_helper_thread() {
if (kwin_helper_thread_started) {
return;
}
kwin_helper_thread_started = true;
std::thread([&] {
kwin_script_thread();
}).detach();
}
}

View File

@@ -10,6 +10,8 @@
#include "../include/gui/ScreenshotSettingsPage.hpp"
#include "../include/gui/GlobalSettingsPage.hpp"
#include "../include/gui/Utils.hpp"
#include "../include/KwinWorkaround.hpp"
#include "../include/HyprlandWorkaround.hpp"
#include "../include/gui/PageStack.hpp"
#include "../include/WindowUtils.hpp"
#include "../include/GlobalHotkeys/GlobalHotkeys.hpp"
@@ -39,6 +41,9 @@
#include <X11/extensions/XInput2.h>
#include <X11/extensions/shapeconst.h>
#include <X11/Xcursor/Xcursor.h>
#include <wayland-client.h>
#include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/system/Utf8.hpp>
@@ -487,6 +492,14 @@ namespace gsr {
top_bar_background({0.0f, 0.0f}),
close_button_widget({0.0f, 0.0f})
{
if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
wayland_dpy = wl_display_connect(nullptr);
if(!wayland_dpy)
fprintf(stderr, "Warning: failed to connect to the wayland server\n");
} else {
wayland_dpy = nullptr;
}
gsr_icon_path = this->resources_path + "images/gpu_screen_recorder_logo.png";
key_bindings[0].key_event.code = mgl::Keyboard::Escape;
@@ -530,13 +543,21 @@ namespace gsr {
cursor_tracker = std::make_unique<CursorTrackerX11>((Display*)mgl_get_context()->connection);
else if(this->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());
cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str(), wayland_dpy);
if(!config.main_config.wayland_warning_shown) {
config.main_config.wayland_warning_shown = true;
save_config(config);
show_notification("Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
}
const std::string wm_name = get_window_manager_name(x11_dpy);
if (wm_name.find("Hyprland") != std::string::npos) {
start_hyprland_listener_thread();
} else if (wm_name == "KWin") {
start_kwin_helper_thread();
}
}
update_led_indicator_after_settings_change();
@@ -582,6 +603,9 @@ namespace gsr {
if(x11_dpy)
XCloseDisplay(x11_dpy);
if(wayland_dpy)
wl_display_disconnect(wayland_dpy);
}
void Overlay::xi_setup() {
@@ -1078,7 +1102,6 @@ namespace gsr {
window.reset();
return;
}
//window->set_low_latency(true);
unsigned char data = 2; // Prefer being composed to allow transparency
@@ -1950,11 +1973,24 @@ namespace gsr {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
const std::string video_filename = filepath_get_filename(video_filepath.c_str());
const std::string wm_name = get_window_manager_name(display);
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;
std::string focused_window_name;
if (is_hyprland) {
focused_window_name = get_current_hyprland_window_title();
} else if (is_kwin_wayland) {
focused_window_name = get_current_kwin_window_title();
} else {
const Window gsr_ui_window = window ? (Window)window->get_system_handle() : None;
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);
}
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);
if(focused_window_name.empty())
focused_window_name = "Game";
@@ -2507,8 +2543,8 @@ namespace gsr {
return result;
}
static void add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size, const RegionSelector &region_selector) {
Region region = region_selector.get_selection();
void Overlay::add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size) {
Region region = region_selector.get_selection(x11_dpy, wayland_dpy);
if(region.size.x <= 32 && region.size.y <= 32) {
region.size.x = 0;
region.size.y = 0;
@@ -2518,7 +2554,7 @@ namespace gsr {
args.push_back(region_str);
}
static void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const RegionSelector &region_selector, const std::string &region_area_option) {
void Overlay::add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const std::string &region_area_option, RecordForceType force_type) {
if(record_options.video_quality == "custom") {
args.push_back("-bm");
args.push_back("cbr");
@@ -2539,7 +2575,7 @@ namespace gsr {
args.push_back(audio_track.c_str());
}
if(record_options.restore_portal_session) {
if(record_options.restore_portal_session && force_type != RecordForceType::WINDOW) {
args.push_back("-restore-portal-session");
args.push_back("yes");
}
@@ -2550,7 +2586,7 @@ namespace gsr {
}
if(region_area_option == "region")
add_region_command(args, region_str, region_str_size, region_selector);
add_region_command(args, region_str, region_str_size);
}
static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
@@ -2906,7 +2942,7 @@ namespace gsr {
}
char region_str[128];
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector, config.replay_config.record_options.record_area_option);
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), config.replay_config.record_options.record_area_option);
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
args.push_back("-ro");
@@ -3132,8 +3168,17 @@ namespace gsr {
"-o", output_file.c_str()
};
const std::string hotkey_window_capture_portal_session_token_filepath = get_config_dir() + "/gsr-ui-window-capture-token";
if(record_area_option == "portal") {
hide_ui = true;
if(force_type == RecordForceType::WINDOW) {
args.push_back("-portal-session-token-filepath");
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
}
}
char region_str[128];
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector, record_area_option);
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), record_area_option, force_type);
args.push_back(nullptr);
@@ -3166,9 +3211,6 @@ namespace gsr {
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
}
if(record_area_option == "portal")
hide_ui = true;
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
// selected what to capture and accepted it.
recording_duration_clock.restart();
@@ -3187,7 +3229,6 @@ namespace gsr {
url += "rtmp://rtmp.rumble.com/live/";
url += config.streaming_config.rumble.stream_key;
} else if(config.streaming_config.streaming_service == "kick") {
fprintf(stderr, "kick: %s, %s\n", config.streaming_config.kick.stream_url.c_str(), config.streaming_config.kick.stream_key.c_str());
url += config.streaming_config.kick.stream_url;
if(!url.empty() && url.back() != '/')
url += "/";
@@ -3305,6 +3346,10 @@ namespace gsr {
choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
const std::string url = streaming_get_url(config);
if(config.streaming_config.streaming_service == "rumble" || config.streaming_config.streaming_service == "kick") {
fprintf(stderr, "Info: forcing video codec to h264 as rumble/kick supports only h264\n");
video_codec = "h264";
}
char size[64];
size[0] = '\0';
@@ -3331,7 +3376,7 @@ namespace gsr {
};
char region_str[128];
add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector, config.streaming_config.record_options.record_area_option);
add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), config.streaming_config.record_options.record_area_option);
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
args.push_back("-ro");
@@ -3463,7 +3508,7 @@ namespace gsr {
char region_str[128];
if(record_area_option == "region")
add_region_command(args, region_str, sizeof(region_str), region_selector);
add_region_command(args, region_str, sizeof(region_str));
args.push_back(nullptr);

View File

@@ -166,6 +166,62 @@ namespace gsr {
return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
}
static const Monitor* get_monitor_by_region_center(const std::vector<Monitor> &monitors, Region region) {
const mgl::vec2i center = {region.pos.x + region.size.x / 2, region.pos.y + region.size.y / 2};
for(const Monitor &monitor : monitors) {
if(center.x >= monitor.position.x && center.x <= monitor.position.x + monitor.size.x
&& center.y >= monitor.position.y && center.y <= monitor.position.y + monitor.size.y)
{
return &monitor;
}
}
return nullptr;
}
// Name is the x11 name. TODO: verify if this works on all wayland compositors
static const Monitor* get_wayland_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) {
for(const Monitor &monitor : monitors) {
if(monitor.name == name)
return &monitor;
}
return nullptr;
}
static mgl::vec2d to_vec2d(mgl::vec2i v) {
return { (double)v.x, (double)v.y };
}
static Region x11_region_to_wayland_region(Display *dpy, struct wl_display *wayland_dpy, Region x11_region) {
const std::vector<Monitor> x11_monitors = get_monitors(dpy);
const Monitor *x11_selected_monitor = get_monitor_by_region_center(x11_monitors, x11_region);
if(!x11_selected_monitor) {
fprintf(stderr, "Warning: RegionSelector: failed to get x11 monitor\n");
return x11_region;
}
const std::vector<Monitor> wayland_monitors = get_monitors_wayland(wayland_dpy);
const Monitor *wayland_monitor = get_wayland_monitor_by_name(wayland_monitors, x11_selected_monitor->name);
if(!wayland_monitor) {
fprintf(stderr, "Warning: RegionSelector: failed to get wayland monitor\n");
return x11_region;
}
const mgl::vec2d region_relative_pos = {
(double)(x11_region.pos.x - x11_selected_monitor->position.x) / (double)x11_selected_monitor->size.x,
(double)(x11_region.pos.y - x11_selected_monitor->position.y) / (double)x11_selected_monitor->size.y,
};
const mgl::vec2d region_relative_size = {
(double)x11_region.size.x / (double)x11_selected_monitor->size.x,
(double)x11_region.size.y / (double)x11_selected_monitor->size.y,
};
return Region {
wayland_monitor->position + (region_relative_pos * to_vec2d(wayland_monitor->size)).to_vec2i(),
(region_relative_size * to_vec2d(wayland_monitor->size)).to_vec2i(),
};
}
RegionSelector::RegionSelector() {
}
@@ -385,8 +441,11 @@ namespace gsr {
return result;
}
Region RegionSelector::get_selection() const {
return region;
Region RegionSelector::get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const {
Region returned_region = region;
if(is_wayland && x11_dpy && wayland_dpy)
returned_region = x11_region_to_wayland_region(x11_dpy, wayland_dpy, returned_region);
return returned_region;
}
void RegionSelector::on_button_press(const void *de) {

View File

@@ -8,6 +8,9 @@
#include <X11/extensions/shapeconst.h>
#include <X11/extensions/Xrandr.h>
#include <wayland-client.h>
#include "xdg-output-unstable-v1-client-protocol.h"
#include <mglpp/system/Utf8.hpp>
extern "C" {
@@ -23,6 +26,209 @@ extern "C" {
#define MAX_PROPERTY_VALUE_LEN 4096
namespace gsr {
struct WaylandOutput {
uint32_t wl_name;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
mgl::vec2i pos;
mgl::vec2i size;
int32_t transform;
std::string name;
};
struct Wayland {
std::vector<WaylandOutput> outputs;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
};
static WaylandOutput* get_wayland_monitor_by_output(Wayland &wayland, struct wl_output *output) {
for(WaylandOutput &monitor : wayland.outputs) {
if(monitor.output == output)
return &monitor;
}
return nullptr;
}
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
(void)wl_output;
(void)phys_width;
(void)phys_height;
(void)subpixel;
(void)make;
(void)model;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->pos.x = x;
monitor->pos.y = y;
monitor->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
(void)wl_output;
(void)flags;
(void)refresh;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->size.x = width;
monitor->size.y = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
(void)data;
(void)wl_output;
}
static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
(void)data;
(void)wl_output;
(void)factor;
}
static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
(void)wl_output;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->name = name;
}
static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
(void)data;
(void)wl_output;
(void)description;
}
static const struct wl_output_listener output_listener = {
output_handle_geometry,
output_handle_mode,
output_handle_done,
output_handle_scale,
output_handle_name,
output_handle_description,
};
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
Wayland *wayland = (Wayland*)data;
if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n");
return;
}
struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4);
wayland->outputs.push_back(
WaylandOutput{
name,
output,
nullptr,
mgl::vec2i{0, 0},
mgl::vec2i{0, 0},
0,
""
});
wl_output_add_listener(output, &output_listener, wayland);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n");
return;
}
if(wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(wayland->xdg_output_manager);
wayland->xdg_output_manager = NULL;
}
wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
registry_add_object,
registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->pos.x = x;
monitor->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)xdg_output;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->size.x = width;
monitor->size.y = height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_logical_position,
xdg_output_handle_logical_size,
xdg_output_handle_done,
xdg_output_handle_name,
xdg_output_handle_description,
};
static const int transform_90 = 1;
static const int transform_270 = 3;
static void transform_monitors(Wayland &wayland) {
for(WaylandOutput &output : wayland.outputs) {
if(output.transform == transform_90 || output.transform == transform_270)
std::swap(output.size.x, output.size.y);
}
}
static void set_monitor_outputs_from_xdg_output(Wayland &wayland, struct wl_display *dpy) {
if(!wayland.xdg_output_manager) {
fprintf(stderr, "Warning: WindowUtils::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n");
return;
}
for(WaylandOutput &monitor : wayland.outputs) {
monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(wayland.xdg_output_manager, monitor.output);
zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor);
}
// Fetch xdg_output
wl_display_roundtrip(dpy);
}
static unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) {
Atom ret_property_type = None;
int ret_format = 0;
@@ -519,6 +725,47 @@ namespace gsr {
return monitors;
}
std::vector<Monitor> get_monitors_wayland(struct wl_display *dpy) {
Wayland wayland;
struct wl_registry *registry = wl_display_get_registry(dpy);
wl_registry_add_listener(registry, &registry_listener, &wayland);
// Fetch globals
wl_display_roundtrip(dpy);
// Fetch wl_output
wl_display_roundtrip(dpy);
transform_monitors(wayland);
set_monitor_outputs_from_xdg_output(wayland, dpy);
std::vector<Monitor> monitors;
for(WaylandOutput &output : wayland.outputs) {
monitors.push_back(Monitor{output.pos, output.size, std::move(output.name)});
if(output.output) {
wl_output_destroy(output.output);
output.output = nullptr;
}
if(output.xdg_output) {
zxdg_output_v1_destroy(output.xdg_output);
output.xdg_output = nullptr;
}
}
wayland.outputs.clear();
if(wayland.xdg_output_manager) {
zxdg_output_manager_v1_destroy(wayland.xdg_output_manager);
wayland.xdg_output_manager = nullptr;
}
wl_registry_destroy(registry);
return monitors;
}
static bool device_is_mouse(const XIDeviceInfo *dev) {
for(int i = 0; i < dev->num_classes; ++i) {
if(dev->classes[i]->type == XIMasterPointer || dev->classes[i]->type == XISlavePointer)

View File

@@ -1216,9 +1216,9 @@ namespace gsr {
std::unique_ptr<Widget> SettingsPage::create_low_power_mode() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->set_visible(gsr_info->gpu_info.vendor == GpuVendor::AMD);
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record in low-power mode");
checkbox->set_visible(gsr_info->gpu_info.vendor == GpuVendor::AMD);
low_power_mode_checkbox_ptr = checkbox.get();
list->add_widget(std::move(checkbox));

View File

@@ -0,0 +1,95 @@
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
static bool get_hyprland_socket_path(char *path, int path_len) {
const char* xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
const char* instance_sig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!xdg_runtime_dir || !instance_sig) {
fprintf(stderr, "Error: gsr-hypland-helper: environment variables not set\n");
return false;
}
if (snprintf(path, path_len, "%s/hypr/%s/.socket2.sock", xdg_runtime_dir, instance_sig) >= path_len) {
fprintf(stderr, "Error: gsr-hypland-helper: path to hyprland socket (%s/hypr/%s/.socket2.sock) is more than %d characters long\n", xdg_runtime_dir, instance_sig, path_len);
return false;
}
return true;
}
static void print_window_title(const char *window_title) {
if (window_title[0] == 0) {
printf("Window title changed: %s\n", "Desktop");
fflush(stdout);
return;
}
printf("Window title changed: %s\n", window_title);
fflush(stdout);
}
static int handle_ipc(void) {
const int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Error: gsr-hyprland-helper: socket");
return 1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if (!get_hyprland_socket_path(addr.sun_path, sizeof(addr.sun_path))) {
return 1;
}
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("Error: gsr-hyprland-helper: connect");
close(sock_fd);
return 1;
}
fprintf(stderr, "Info: gsr-hyprland-helper: connected to Hyprland socket: %s\n", addr.sun_path);
FILE *sock_file = fdopen(sock_fd, "r");
if (!sock_file) {
perror("Error: gsr-hyprland-helper: fdopen");
close(sock_fd);
return 1;
}
char buffer[1024];
while (fgets(buffer, sizeof(buffer), sock_file)) {
int line_len = strlen(buffer);
if(line_len > 0 && buffer[line_len - 1] == '\n') {
buffer[line_len - 1] = '\0';
line_len -= 1;
}
if(line_len >= 14 && memcmp(buffer, "activewindow>>", 14) == 0) {
char *window_id = buffer + 14;
char *window_title = strchr(buffer + 14, ',');
if(!window_title)
continue;
window_title[0] = '\0';
window_title += 1;
if(strcmp(window_id, "gsr-ui") == 0 || strcmp(window_id, "gsr-notify") == 0)
continue;
print_window_title(window_title);
}
}
fclose(sock_file);
return 0;
}
int main(void) {
return handle_ipc();
}

View File

@@ -0,0 +1,58 @@
const DAEMON_DBUS_NAME = "com.dec05eba.gsr_kwin_helper";
// utils
function sendNewActiveWindowTitle(title) {
callDBus(
DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME,
"setActiveWindowTitle", title
);
}
function sendNewActiveWindowFullscreen(isFullscreen) {
callDBus(
DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME,
"setActiveWindowFullscreen", isFullscreen
);
}
// track handlers to avoid duplicates
const windowEventHandlers = new Map();
function subscribeToClient(client) {
if (!client || windowEventHandlers.has(client)) return;
const emitActiveTitle = () => {
if (workspace.activeWindow === client) {
sendNewActiveWindowTitle(client.caption || "");
}
};
const emitActiveFullscreen = () => {
if (workspace.activeWindow === client) {
sendNewActiveWindowFullscreen(client.fullScreen);
}
};
windowEventHandlers.set(client, {
title: emitActiveTitle,
fs: emitActiveFullscreen,
});
client.captionChanged.connect(emitActiveTitle);
client.fullScreenChanged.connect(emitActiveFullscreen);
}
function updateActiveWindow(client) {
if (!client) return;
sendNewActiveWindowTitle(client.caption || "");
sendNewActiveWindowFullscreen(client.fullScreen);
subscribeToClient(client);
}
// handle window focus changes
workspace.windowActivated.connect(updateActiveWindow);
// handle initial state
if (workspace.activeWindow) {
updateActiveWindow(workspace.activeWindow);
}

View File

@@ -0,0 +1,233 @@
#include <dbus/dbus.h>
#include <unistd.h>
#include <iostream>
#include <string>
static const char* INTROSPECTION_XML =
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
"<node>\n"
" <interface name='com.dec05eba.gsr_kwin_helper'>\n"
" <method name='setActiveWindowTitle'>\n"
" <arg type='s' name='title' direction='in'/>\n"
" </method>\n"
" </interface>\n"
" <interface name='org.freedesktop.DBus.Introspectable'>\n"
" <method name='Introspect'>\n"
" <arg type='s' name='data' direction='out'/>\n"
" </method>\n"
" </interface>\n"
"</node>\n";
class GsrKwinHelper {
public:
std::string active_window_title;
DBusConnection* connection = nullptr;
DBusError err;
bool init() {
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
std::cerr << "Error: gsr-kwin-helper: failed to connect to session bus: " << err.message << "\n";
dbus_error_free(&err);
return false;
}
if (!connection) {
std::cerr << "Error: gsr-kwin-helper: connection is null\n";
return false;
}
int ret = dbus_bus_request_name(connection, "com.dec05eba.gsr_kwin_helper",
DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if (dbus_error_is_set(&err)) {
std::cerr << "Error: gsr-kwin-helper: failed to request name: " << err.message << "\n";
dbus_error_free(&err);
return false;
}
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
std::cerr << "Error: gsr-kwin-helper: not primary owner of the name\n";
return false;
}
std::cerr << "Info: gsr-kwin-helper: DBus server initialized on com.dec05eba.gsr_kwin_helper\n";
const bool inside_flatpak = access("/app/manifest.json", F_OK) == 0;
const char *helper_path =
!inside_flatpak
? KWIN_HELPER_SCRIPT_PATH
: "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/share/gsr-ui/gsrkwinhelper.js";
std::cerr << "Info: gsr-kwin-helper: KWin script path: " << helper_path << std::endl;
if (!load_kwin_script(connection, helper_path)) {
std::cerr << "Warning: gsr-kwin-helper: failed to load KWin script\n";
}
return true;
}
void run() {
while (true) {
dbus_connection_read_write(connection, 100);
DBusMessage* msg = dbus_connection_pop_message(connection);
if (!msg) {
continue;
}
if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) {
handle_introspect(msg);
} else if (dbus_message_is_method_call(msg, "com.dec05eba.gsr_kwin_helper", "setActiveWindowTitle")) {
handle_set_title(msg);
}
dbus_message_unref(msg);
}
}
void handle_introspect(DBusMessage* msg) {
DBusMessage* reply = dbus_message_new_method_return(msg);
if (!reply) return;
DBusMessageIter args;
dbus_message_iter_init_append(reply, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &INTROSPECTION_XML)) {
dbus_message_unref(reply);
return;
}
dbus_connection_send(connection, reply, nullptr);
dbus_connection_flush(connection);
dbus_message_unref(reply);
}
void handle_set_title(DBusMessage* msg) {
DBusMessageIter args;
const char* title = nullptr;
if (!dbus_message_iter_init(msg, &args)) {
send_error_reply(msg, "No arguments provided");
return;
}
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) {
send_error_reply(msg, "Expected string argument");
return;
}
dbus_message_iter_get_basic(&args, &title);
if (title) {
active_window_title = title;
std::cout << "Active window title set to: " << active_window_title << "\n";
std::cout.flush();
send_success_reply(msg);
} else {
send_error_reply(msg, "Failed to read string");
}
}
void send_success_reply(DBusMessage* msg) {
DBusMessage* reply = dbus_message_new_method_return(msg);
if (reply) {
dbus_connection_send(connection, reply, nullptr);
dbus_connection_flush(connection);
dbus_message_unref(reply);
}
}
void send_error_reply(DBusMessage* msg, const char* error_msg) {
DBusMessage* reply = dbus_message_new_error(msg, "com.dec05eba.gsr_kwin_helper.Error", error_msg);
if (reply) {
dbus_connection_send(connection, reply, nullptr);
dbus_connection_flush(connection);
dbus_message_unref(reply);
}
}
bool call_kwin_method(DBusConnection* conn, const char* method,
const char* arg1 = nullptr, const char* arg2 = nullptr) {
DBusMessage* msg = dbus_message_new_method_call(
"org.kde.KWin",
"/Scripting",
"org.kde.kwin.Scripting",
method
);
if (!msg) {
std::cerr << "Error: gsr-kwin-helper: failed to create message for " << method << "\n";
return false;
}
if (arg1) {
dbus_message_append_args(msg, DBUS_TYPE_STRING, &arg1, DBUS_TYPE_INVALID);
if (arg2) {
dbus_message_append_args(msg, DBUS_TYPE_STRING, &arg2, DBUS_TYPE_INVALID);
}
}
DBusError err;
dbus_error_init(&err);
// Send message and wait for reply (with 1 second timeout)
DBusMessage* reply = dbus_connection_send_with_reply_and_block(conn, msg, 1000, &err);
dbus_message_unref(msg);
if (dbus_error_is_set(&err)) {
std::cerr << "Error: gsr-kwin-helper: error calling " << method << ": " << err.message << "\n";
dbus_error_free(&err);
return false;
}
if (reply) {
dbus_message_unref(reply);
}
return true;
}
bool load_kwin_script(DBusConnection* conn, const char* script_path) {
// Unload existing script
call_kwin_method(conn, "unloadScript", "gsrkwinhelper");
if (!call_kwin_method(conn, "loadScript", script_path, "gsrkwinhelper")) {
std::cerr << "Error: gsr-kwin-helper: failed to load KWin script\n";
return false;
}
if (!call_kwin_method(conn, "start")) {
std::cerr << "Error: gsr-kwin-helper: failed to start KWin script\n";
return false;
}
std::cerr << "Info: gsr-kwin-helper: KWin script loaded and started successfully\n";
return true;
}
~GsrKwinHelper() {
if (connection) {
dbus_bus_release_name(connection, "com.dec05eba.gsr_kwin_helper", nullptr);
dbus_connection_unref(connection);
}
}
};
int main() {
GsrKwinHelper helper;
if (!helper.init()) {
return 1;
}
helper.run();
return 0;
}