Add option to only grab virtual devices, to support input remapping software

This commit is contained in:
dec05eba
2025-01-04 05:39:16 +01:00
parent f379b87b33
commit 52ce22ae22
10 changed files with 108 additions and 28 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.\
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 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.
This might cause issues for you if you use input remapping software. To workaround this you can go into settings and select "Only grab virtual devices"
# 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,7 +43,7 @@ namespace gsr {
struct MainConfig {
int32_t config_file_version = 0;
bool software_encoding_warning_shown = false;
bool enable_hotkeys = true;
std::string hotkeys_enable_option = "enable_hotkeys";
std::string tint_color;
};

View File

@@ -7,7 +7,12 @@
namespace gsr {
class GlobalHotkeysLinux : public GlobalHotkeys {
public:
GlobalHotkeysLinux();
enum class GrabType {
ALL,
VIRTUAL
};
GlobalHotkeysLinux(GrabType grab_type);
GlobalHotkeysLinux(const GlobalHotkeysLinux&) = delete;
GlobalHotkeysLinux& operator=(const GlobalHotkeysLinux&) = delete;
~GlobalHotkeysLinux() override;
@@ -20,5 +25,6 @@ namespace gsr {
int pipes[2];
FILE *read_file = nullptr;
std::unordered_map<std::string, GlobalHotkeyCallback> bound_actions_by_id;
GrabType grab_type;
};
}

View File

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

View File

@@ -9,7 +9,15 @@
#define PIPE_WRITE 1
namespace gsr {
GlobalHotkeysLinux::GlobalHotkeysLinux() {
static const char* grab_type_to_arg(GlobalHotkeysLinux::GrabType grab_type) {
switch(grab_type) {
case GlobalHotkeysLinux::GrabType::ALL: return "--all";
case GlobalHotkeysLinux::GrabType::VIRTUAL: return "--virtual";
}
return "--all";
}
GlobalHotkeysLinux::GlobalHotkeysLinux(GrabType grab_type) : grab_type(grab_type) {
for(int i = 0; i < 2; ++i) {
pipes[i] = -1;
}
@@ -32,6 +40,7 @@ namespace gsr {
}
bool GlobalHotkeysLinux::start() {
const char *grab_type_arg = grab_type_to_arg(grab_type);
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *user_homepath = getenv("HOME");
if(!user_homepath)
@@ -61,10 +70,10 @@ namespace gsr {
}
if(inside_flatpak) {
const char *args[] = { "flatpak-spawn", "--host", "--", gsr_global_hotkeys_flatpak, NULL };
const char *args[] = { "flatpak-spawn", "--host", "--", gsr_global_hotkeys_flatpak, grab_type_arg, nullptr };
execvp(args[0], (char* const*)args);
} else {
const char *args[] = { "gsr-global-hotkeys", NULL };
const char *args[] = { "gsr-global-hotkeys", grab_type_arg, nullptr };
execvp(args[0], (char* const*)args);
}

View File

@@ -89,11 +89,14 @@ namespace gsr {
}
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);
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL);
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->add_item("Enable hotkeys", "enable_hotkeys");
if(!inside_flatpak)
enable_hotkeys_radio_button->add_item("Disable hotkeys", "disable_hotkeys");
enable_hotkeys_radio_button->add_item("Only grab virtual devices (supports input remapping software)", "enable_hotkeys_virtual_devices");
enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
if(!on_click_exit_program_button)
return true;
@@ -102,11 +105,12 @@ namespace gsr {
on_click_exit_program_button("restart");
else if(id == "disable_hotkeys")
on_click_exit_program_button("restart");
else if(id == "enable_hotkeys_virtual_devices")
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));
return std::make_unique<Subsection>("Hotkeys", std::move(enable_hotkeys_radio_button), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() {
@@ -137,15 +141,13 @@ namespace gsr {
}
void GlobalSettingsPage::add_widgets() {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size());
auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL);
settings_list->set_spacing(0.018f);
settings_list->add_widget(create_appearance_subsection(scrollable_page.get()));
settings_list->add_widget(create_startup_subsection(scrollable_page.get()));
if(!inside_flatpak)
settings_list->add_widget(create_hotkey_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()));
scrollable_page->add_widget(std::move(settings_list));
@@ -167,14 +169,12 @@ namespace gsr {
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);
if(enable_hotkeys_radio_button_ptr)
enable_hotkeys_radio_button_ptr->set_selected_item(config.main_config.enable_hotkeys ? "enable_hotkeys" : "disable_hotkeys", false, false);
enable_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false);
}
void GlobalSettingsPage::save() {
config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id();
if(enable_hotkeys_radio_button_ptr)
config.main_config.enable_hotkeys = enable_hotkeys_radio_button_ptr->get_selected_id() == "enable_hotkeys";
config.main_config.hotkeys_enable_option = enable_hotkeys_radio_button_ptr->get_selected_id();
save_config(config);
}
}

View File

@@ -95,8 +95,8 @@ static std::unique_ptr<gsr::GlobalHotkeysX11> register_x11_hotkeys(gsr::Overlay
return global_hotkeys;
}
static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Overlay *overlay) {
auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysLinux>();
static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Overlay *overlay, gsr::GlobalHotkeysLinux::GrabType grab_type) {
auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysLinux>(grab_type);
if(!global_hotkeys->start())
fprintf(stderr, "error: failed to start global hotkeys\n");
@@ -314,8 +314,10 @@ int main(int argc, char **argv) {
rpc_add_commands(rpc.get(), overlay.get());
std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr;
if(overlay->get_config().main_config.enable_hotkeys)
global_hotkeys = register_linux_hotkeys(overlay.get());
if(overlay->get_config().main_config.hotkeys_enable_option == "enable_hotkeys")
global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::ALL);
else if(overlay->get_config().main_config.hotkeys_enable_option == "enable_hotkeys_virtual_devices")
global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::VIRTUAL);
// 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.

View File

@@ -213,11 +213,41 @@ static bool keyboard_event_has_event_with_dev_input_fd(keyboard_event *self, int
return false;
}
/* TODO: Is there a more efficient way to do this? */
static bool dev_input_is_virtual(int dev_input_id) {
DIR *dir = opendir("/sys/devices/virtual/input");
if(!dir)
return false;
bool is_virtual = false;
char virtual_input_filepath[1024];
for(;;) {
struct dirent *entry = readdir(dir);
if(!entry)
break;
if(strncmp(entry->d_name, "input", 5) != 0)
continue;
snprintf(virtual_input_filepath, sizeof(virtual_input_filepath), "/sys/devices/virtual/input/%s/event%d", entry->d_name, dev_input_id);
if(access(virtual_input_filepath, F_OK) == 0) {
is_virtual = true;
break;
}
}
closedir(dir);
return is_virtual;
}
static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, const char *dev_input_filepath) {
const int dev_input_id = get_dev_input_id_from_filepath(dev_input_filepath);
if(dev_input_id == -1)
return false;
if(self->grab_type == KEYBOARD_GRAB_TYPE_VIRTUAL && !dev_input_is_virtual(dev_input_id))
return false;
if(keyboard_event_has_event_with_dev_input_fd(self, dev_input_id))
return false;
@@ -373,10 +403,11 @@ static int setup_virtual_keyboard_input(const char *name) {
return fd;
}
bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab) {
bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab, keyboard_grab_type grab_type) {
memset(self, 0, sizeof(*self));
self->stdout_event_index = -1;
self->hotplug_event_index = -1;
self->grab_type = grab_type;
if(exclusive_grab) {
self->uinput_fd = setup_virtual_keyboard_input(GSR_UI_VIRTUAL_KEYBOARD_NAME);

View File

@@ -36,6 +36,11 @@ typedef struct {
int num_keys_pressed;
} event_extra_data;
typedef enum {
KEYBOARD_GRAB_TYPE_ALL,
KEYBOARD_GRAB_TYPE_VIRTUAL
} keyboard_grab_type;
typedef struct {
struct pollfd event_polls[MAX_EVENT_POLLS]; /* Current size is |num_event_polls| */
event_extra_data event_extra_data[MAX_EVENT_POLLS]; /* Current size is |num_event_polls| */
@@ -45,6 +50,7 @@ typedef struct {
int hotplug_event_index;
int uinput_fd;
bool stdout_failed;
keyboard_grab_type grab_type;
hotplug_event hotplug_ev;
@@ -62,7 +68,7 @@ typedef struct {
/* Return true to allow other applications to receive the key input (when using exclusive grab) */
typedef bool (*key_callback)(uint32_t key, uint32_t modifiers, int press_status, void *userdata);
bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab);
bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab, keyboard_grab_type grab_type);
void keyboard_event_deinit(keyboard_event *self);
/* If |timeout_milliseconds| is -1 then wait until an event is received */

View File

@@ -3,6 +3,7 @@
/* C stdlib */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/* POSIX */
#include <unistd.h>
@@ -37,7 +38,32 @@ static bool on_key_callback(uint32_t key, uint32_t modifiers, int press_status,
return true;
}
int main(void) {
static void usage(void) {
fprintf(stderr, "usage: gsr-global-hotkeys [--all|--virtual]\n");
fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " --all Grab all devices.\n");
fprintf(stderr, " --virtual Grab all virtual devices only.\n");
}
int main(int argc, char **argv) {
keyboard_grab_type grab_type = KEYBOARD_GRAB_TYPE_ALL;
if(argc == 2) {
const char *grab_type_arg = argv[1];
if(strcmp(grab_type_arg, "--all") == 0) {
grab_type = KEYBOARD_GRAB_TYPE_ALL;
} else if(strcmp(grab_type_arg, "--virtual") == 0) {
grab_type = KEYBOARD_GRAB_TYPE_VIRTUAL;
} else {
fprintf(stderr, "Error: expected --all or --virtual, got %s\n", grab_type_arg);
usage();
return 1;
}
} else if(argc != 1) {
fprintf(stderr, "Error: expected 0 or 1 arguments, got %d argument(s)\n", argc);
usage();
return 1;
}
const uid_t user_id = getuid();
if(geteuid() != 0) {
if(setuid(0) == -1) {
@@ -47,7 +73,7 @@ int main(void) {
}
keyboard_event keyboard_ev;
if(!keyboard_event_init(&keyboard_ev, true, true)) {
if(!keyboard_event_init(&keyboard_ev, true, true, grab_type)) {
fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n");
setuid(user_id);
return 1;