Add option to disable hotkeys, add gsr-ui-cli tool to control gsr-ui remotely

This commit is contained in:
dec05eba
2025-01-03 22:35:49 +01:00
parent 5439fa8a71
commit 6c03137610
10 changed files with 217 additions and 16 deletions

View File

@@ -37,7 +37,7 @@ There are also additional dependencies needed at runtime:
At the moment different keyboard layouts are not supported. The physical layout of keys are used for global hotkeys. If your Z and Y keys are swapped for example then you need to press Alt+Y instead of Alt+Z to open/hide the UI.\ At the moment different keyboard layouts are not supported. The physical layout of keys are used for global hotkeys. If your Z and Y keys are swapped for example then you need to press Alt+Y instead of Alt+Z to open/hide the UI.\
If you experience this issue then please email dec05eba@protonmail.com to get it fixed.\ If you experience this issue then please email dec05eba@protonmail.com to get it fixed.\
This program has to grab all keyboards and create a virtual keyboard (`gsr-ui virtual keyboard`) to make global hotkeys work on all Wayland compositors. This program has to grab all keyboards and create a virtual keyboard (`gsr-ui virtual keyboard`) to make global hotkeys work on all Wayland compositors.
This might cause issues for you if you use input remapping software. If this is an issue for you then please email dec05eba@protonmail.com and an option to workaround the issue could be added. This might cause issues for you if you use input remapping software. To workaround this you can go into settings and disable hotkeys and use the included `gsr-ui-cli` tool to control the UI remotely. You can combine `gsr-ui-cli` commands to hotkeys in your desktop environments hotkey settings.
# License # License
This software is licensed under GPL3.0-only. Files under `fonts/` directory belong to the Noto Sans Google fonts project and they are licensed under `SIL Open Font License`. This software is licensed under GPL3.0-only. Files under `fonts/` directory belong to the Noto Sans Google fonts project and they are licensed under `SIL Open Font License`.

View File

@@ -43,6 +43,7 @@ namespace gsr {
struct MainConfig { struct MainConfig {
int32_t config_file_version = 0; int32_t config_file_version = 0;
bool software_encoding_warning_shown = false; bool software_encoding_warning_shown = false;
bool enable_hotkeys = true;
std::string tint_color; std::string tint_color;
}; };

View File

@@ -58,6 +58,8 @@ namespace gsr {
bool is_open() const; bool is_open() const;
bool should_exit(std::string &reason) const; bool should_exit(std::string &reason) const;
void exit(); void exit();
const Config& get_config() const;
private: private:
void xi_setup(); void xi_setup();
void handle_xi_events(); void handle_xi_events();

View File

@@ -31,6 +31,7 @@ namespace gsr {
private: private:
std::unique_ptr<Subsection> create_appearance_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_appearance_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_startup_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_startup_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_hotkey_subsection(ScrollablePage *parent_page);
std::unique_ptr<Button> create_exit_program_button(); std::unique_ptr<Button> create_exit_program_button();
std::unique_ptr<Button> create_go_back_to_old_ui_button(); std::unique_ptr<Button> create_go_back_to_old_ui_button();
std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page);
@@ -43,5 +44,6 @@ namespace gsr {
PageStack *page_stack = nullptr; PageStack *page_stack = nullptr;
RadioButton *tint_color_radio_button_ptr = nullptr; RadioButton *tint_color_radio_button_ptr = nullptr;
RadioButton *startup_radio_button_ptr = nullptr; RadioButton *startup_radio_button_ptr = nullptr;
RadioButton *enable_hotkeys_radio_button_ptr = nullptr;
}; };
} }

View File

@@ -74,6 +74,14 @@ executable(
install : true install : true
) )
executable(
'gsr-ui-cli',
[
'tools/gsr-ui-cli/main.c'
],
install : true
)
install_subdir('images', install_dir : gsr_ui_resources_path) install_subdir('images', install_dir : gsr_ui_resources_path)
install_subdir('fonts', install_dir : gsr_ui_resources_path) install_subdir('fonts', install_dir : gsr_ui_resources_path)

View File

@@ -58,6 +58,7 @@ namespace gsr {
return { return {
{"main.config_file_version", &config.main_config.config_file_version}, {"main.config_file_version", &config.main_config.config_file_version},
{"main.software_encoding_warning_shown", &config.main_config.software_encoding_warning_shown}, {"main.software_encoding_warning_shown", &config.main_config.software_encoding_warning_shown},
{"main.enable_hotkeys", &config.main_config.enable_hotkeys},
{"main.tint_color", &config.main_config.tint_color}, {"main.tint_color", &config.main_config.tint_color},
{"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option}, {"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option},

View File

@@ -487,6 +487,7 @@ namespace gsr {
} }
close_gpu_screen_recorder_output(); close_gpu_screen_recorder_output();
deinit_color_theme();
} }
void Overlay::xi_setup() { void Overlay::xi_setup() {
@@ -1261,6 +1262,10 @@ namespace gsr {
do_exit = true; do_exit = true;
} }
const Config& Overlay::get_config() const {
return config;
}
void Overlay::update_notification_process_status() { void Overlay::update_notification_process_status() {
if(notification_process <= 0) if(notification_process <= 0)
return; return;

View File

@@ -88,6 +88,27 @@ namespace gsr {
return std::make_unique<Subsection>("Startup", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); return std::make_unique<Subsection>("Startup", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
} }
std::unique_ptr<Subsection> GlobalSettingsPage::create_hotkey_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
enable_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get();
enable_hotkeys_radio_button->add_item("Enable hotkeys and restart", "enable_hotkeys");
enable_hotkeys_radio_button->add_item("Disable hotkeys and restart", "disable_hotkeys");
enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
if(!on_click_exit_program_button)
return true;
if(id == "enable_hotkeys")
on_click_exit_program_button("restart");
else if(id == "disable_hotkeys")
on_click_exit_program_button("restart");
return true;
};
list->add_widget(std::move(enable_hotkeys_radio_button));
return std::make_unique<Subsection>("Hotkeys", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() { std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() {
auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, "Exit program", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, "Exit program", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
exit_program_button->on_click = [&]() { exit_program_button->on_click = [&]() {
@@ -108,7 +129,6 @@ namespace gsr {
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) { std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL); auto list = std::make_unique<List>(List::Orientation::HORIZONTAL);
list->add_widget(create_exit_program_button()); list->add_widget(create_exit_program_button());
if(inside_flatpak) if(inside_flatpak)
@@ -123,6 +143,7 @@ namespace gsr {
settings_list->set_spacing(0.018f); settings_list->set_spacing(0.018f);
settings_list->add_widget(create_appearance_subsection(scrollable_page.get())); settings_list->add_widget(create_appearance_subsection(scrollable_page.get()));
settings_list->add_widget(create_startup_subsection(scrollable_page.get())); settings_list->add_widget(create_startup_subsection(scrollable_page.get()));
settings_list->add_widget(create_hotkey_subsection(scrollable_page.get()));
settings_list->add_widget(create_application_options_subsection(scrollable_page.get())); settings_list->add_widget(create_application_options_subsection(scrollable_page.get()));
scrollable_page->add_widget(std::move(settings_list)); scrollable_page->add_widget(std::move(settings_list));
@@ -143,10 +164,13 @@ namespace gsr {
std::string stdout_str; std::string stdout_str;
const int exit_status = exec_program_on_host_get_stdout(args, stdout_str); const int exit_status = exec_program_on_host_get_stdout(args, stdout_str);
startup_radio_button_ptr->set_selected_item(exit_status == 0 ? "start_on_system_startup" : "dont_start_on_system_startup", false, false); startup_radio_button_ptr->set_selected_item(exit_status == 0 ? "start_on_system_startup" : "dont_start_on_system_startup", false, false);
enable_hotkeys_radio_button_ptr->set_selected_item(config.main_config.enable_hotkeys ? "enable_hotkeys" : "disable_hotkeys", false, false);
} }
void GlobalSettingsPage::save() { void GlobalSettingsPage::save() {
config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id(); config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id();
config.main_config.enable_hotkeys = enable_hotkeys_radio_button_ptr->get_selected_id() == "enable_hotkeys";
save_config(config); save_config(config);
} }
} }

View File

@@ -1,5 +1,4 @@
#include "../include/GsrInfo.hpp" #include "../include/GsrInfo.hpp"
#include "../include/Theme.hpp"
#include "../include/Overlay.hpp" #include "../include/Overlay.hpp"
#include "../include/GlobalHotkeysX11.hpp" #include "../include/GlobalHotkeysX11.hpp"
#include "../include/GlobalHotkeysLinux.hpp" #include "../include/GlobalHotkeysLinux.hpp"
@@ -134,6 +133,43 @@ static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Over
return global_hotkeys; return global_hotkeys;
} }
static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
rpc->add_handler("show_ui", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->show();
});
rpc->add_handler("toggle-show", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_show();
});
rpc->add_handler("toggle-record", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_record();
});
rpc->add_handler("toggle-pause", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_pause();
});
rpc->add_handler("toggle-stream", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_stream();
});
rpc->add_handler("toggle-replay", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_replay();
});
rpc->add_handler("replay-save", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->save_replay();
});
}
static bool is_gsr_ui_virtual_keyboard_running() { static bool is_gsr_ui_virtual_keyboard_running() {
FILE *f = fopen("/proc/bus/input/devices", "rb"); FILE *f = fopen("/proc/bus/input/devices", "rb");
if(!f) if(!f)
@@ -271,20 +307,19 @@ int main(int argc, char **argv) {
fprintf(stderr, "Info: gsr ui is now ready, waiting for inputs. Press alt+z to show/hide the overlay\n"); fprintf(stderr, "Info: gsr ui is now ready, waiting for inputs. Press alt+z to show/hide the overlay\n");
auto overlay = std::make_unique<gsr::Overlay>(resources_path, std::move(gsr_info), std::move(capture_options), egl_funcs);
if(launch_action == LaunchAction::LAUNCH_SHOW)
overlay->show();
auto rpc = std::make_unique<gsr::Rpc>(); auto rpc = std::make_unique<gsr::Rpc>();
if(!rpc->create("gsr-ui")) if(!rpc->create("gsr-ui"))
fprintf(stderr, "Error: Failed to create rpc, commands won't be received\n"); fprintf(stderr, "Error: Failed to create rpc, commands won't be received\n");
auto overlay = std::make_unique<gsr::Overlay>(resources_path, std::move(gsr_info), std::move(capture_options), egl_funcs); rpc_add_commands(rpc.get(), overlay.get());
rpc->add_handler("show_ui", [&](const std::string&) { std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr;
overlay->show(); if(overlay->get_config().main_config.enable_hotkeys)
}); global_hotkeys = register_linux_hotkeys(overlay.get());
std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = register_linux_hotkeys(overlay.get());
if(launch_action == LaunchAction::LAUNCH_SHOW)
overlay->show();
// TODO: Add hotkeys in Overlay when using x11 global hotkeys. The hotkeys in Overlay should duplicate each key that is used for x11 global hotkeys. // TODO: Add hotkeys in Overlay when using x11 global hotkeys. The hotkeys in Overlay should duplicate each key that is used for x11 global hotkeys.
@@ -296,7 +331,10 @@ int main(int argc, char **argv) {
gsr::set_frame_delta_seconds(frame_delta_seconds); gsr::set_frame_delta_seconds(frame_delta_seconds);
rpc->poll(); rpc->poll();
global_hotkeys->poll_events();
if(global_hotkeys)
global_hotkeys->poll_events();
overlay->handle_events(global_hotkeys.get()); overlay->handle_events(global_hotkeys.get());
if(!overlay->draw()) { if(!overlay->draw()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -306,15 +344,17 @@ int main(int argc, char **argv) {
fprintf(stderr, "Info: shutting down!\n"); fprintf(stderr, "Info: shutting down!\n");
rpc.reset(); rpc.reset();
global_hotkeys.reset(); if(global_hotkeys)
global_hotkeys.reset();
overlay.reset(); overlay.reset();
gsr::deinit_theme();
gsr::deinit_color_theme();
mgl_deinit(); mgl_deinit();
if(exit_reason == "back-to-old-ui") { if(exit_reason == "back-to-old-ui") {
const char *args[] = { "gpu-screen-recorder-gtk", "use-old-ui", nullptr }; const char *args[] = { "gpu-screen-recorder-gtk", "use-old-ui", nullptr };
execvp(args[0], (char* const*)args); execvp(args[0], (char* const*)args);
} else if(exit_reason == "restart") {
const char *args[] = { "gsr-ui", nullptr };
execvp(args[0], (char* const*)args);
} }
return 0; return 0;

118
tools/gsr-ui-cli/main.c Normal file
View File

@@ -0,0 +1,118 @@
#include <limits.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *filename) {
char dir[PATH_MAX];
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
if(runtime_dir)
snprintf(dir, sizeof(dir), "%s", runtime_dir);
else
snprintf(dir, sizeof(dir), "/run/user/%d", geteuid());
if(access(dir, F_OK) != 0)
snprintf(dir, sizeof(dir), "/tmp");
snprintf(buffer, buffer_size, "%s/%s", dir, filename);
}
static FILE* fifo_open_non_blocking(const char *filepath) {
const int fd = open(filepath, O_RDWR | O_NONBLOCK);
if(fd <= 0)
return NULL;
FILE *file = fdopen(fd, "r+");
if(!file) {
close(fd);
return NULL;
}
return file;
}
/* Assumes |str| size is less than 256 */
static void fifo_write_all(FILE *file, const char *str) {
char command[256];
const ssize_t command_size = snprintf(command, sizeof(command), "%s\n", str);
if(command_size >= (ssize_t)sizeof(command)) {
fprintf(stderr, "Error: command too long: %s\n", str);
return;
}
ssize_t offset = 0;
while(offset < (ssize_t)command_size) {
const ssize_t bytes_written = fwrite(str + offset, 1, command_size - offset, file);
fflush(file);
if(bytes_written > 0)
offset += bytes_written;
}
}
static void usage(void) {
printf("usage: gsr-ui-cli <command>\n");
printf("Run commands on the running gsr-ui instance.\n");
printf("\n");
printf("COMMANDS:\n");
printf(" toggle-show Show/hide the UI.\n");
printf(" toggle-record Start/stop recording.\n");
printf(" toggle-pause Pause/unpause recording. Only applies to regular recording.\n");
printf(" toggle-stream Start/stop streaming.\n");
printf(" toggle-replay Start/stop replay.\n");
printf(" replay-save Save replay.\n");
printf("EXAMPLES:\n");
printf(" gsr-ui-cli toggle-show\n");
printf(" gsr-ui-cli toggle-record\n");
exit(1);
}
static bool is_valid_command(const char *command) {
const char *commands[] = {
"toggle-show",
"toggle-record",
"toggle-pause",
"toggle-stream",
"toggle-replay",
"replay-save",
NULL
};
for(int i = 0; commands[i]; ++i) {
if(strcmp(command, commands[i]) == 0)
return true;
}
return false;
}
int main(int argc, char **argv) {
if(argc != 2) {
printf("Error: expected 1 argument, %d provided\n", argc - 1);
usage();
}
const char *command = argv[1];
if(strcmp(command, "-h") == 0 || strcmp(command, "--help") == 0)
usage();
if(!is_valid_command(command)) {
fprintf(stderr, "Error: invalid command: \"%s\"\n", command);
usage();
}
char fifo_filepath[PATH_MAX];
get_runtime_filepath(fifo_filepath, sizeof(fifo_filepath), "gsr-ui");
FILE *fifo_file = fifo_open_non_blocking(fifo_filepath);
if(!fifo_file) {
fprintf(stderr, "Error: failed to open fifo file %s. Maybe gsr-ui is not running?\n", fifo_filepath);
exit(2);
}
fifo_write_all(fifo_file, command);
fclose(fifo_file);
return 0;
}