mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-08 12:24:52 +09:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0c581684b | ||
|
|
d4dbb27213 | ||
|
|
bfc7df5c56 | ||
|
|
9c5688f61b | ||
|
|
9ccb4dd541 | ||
|
|
1e3e76fcee | ||
|
|
9c9df47d62 | ||
|
|
00ceaa989d | ||
|
|
23b1526092 | ||
|
|
9339d6760e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@ compile_commands.json
|
||||
**/xdg-output-unstable-v1-protocol.c
|
||||
|
||||
depends/.wraplock
|
||||
|
||||
.cache
|
||||
build/
|
||||
@@ -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)
|
||||
|
||||
|
||||
13
include/HyprlandWorkaround.hpp
Normal file
13
include/HyprlandWorkaround.hpp
Normal 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();
|
||||
}
|
||||
12
include/KwinWorkaround.hpp
Normal file
12
include/KwinWorkaround.hpp
Normal 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();
|
||||
}
|
||||
@@ -165,7 +165,7 @@ namespace gsr {
|
||||
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 ®ion_area_option);
|
||||
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 ®ion_area_option, RecordForceType force_type = RecordForceType::NONE);
|
||||
|
||||
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
|
||||
|
||||
|
||||
31
meson.build
31
meson.build
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.10.2', 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)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.10.2"
|
||||
version = "1.10.3"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
62
src/HyprlandWorkaround.cpp
Normal file
62
src/HyprlandWorkaround.cpp
Normal 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
67
src/KwinWorkaround.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -548,6 +550,14 @@ namespace gsr {
|
||||
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();
|
||||
@@ -1092,7 +1102,6 @@ namespace gsr {
|
||||
window.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
//window->set_low_latency(true);
|
||||
|
||||
unsigned char data = 2; // Prefer being composed to allow transparency
|
||||
@@ -1964,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";
|
||||
|
||||
@@ -2532,7 +2554,7 @@ namespace gsr {
|
||||
args.push_back(region_str);
|
||||
}
|
||||
|
||||
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 ®ion_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 ®ion_area_option, RecordForceType force_type) {
|
||||
if(record_options.video_quality == "custom") {
|
||||
args.push_back("-bm");
|
||||
args.push_back("cbr");
|
||||
@@ -2553,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");
|
||||
}
|
||||
@@ -3146,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), 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);
|
||||
|
||||
@@ -3180,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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
95
tools/gsr-hyprland-helper/main.c
Normal file
95
tools/gsr-hyprland-helper/main.c
Normal 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();
|
||||
}
|
||||
58
tools/gsr-kwin-helper/gsrkwinhelper.js
Normal file
58
tools/gsr-kwin-helper/gsrkwinhelper.js
Normal 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);
|
||||
}
|
||||
233
tools/gsr-kwin-helper/main.cpp
Normal file
233
tools/gsr-kwin-helper/main.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user