diff options
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | src/main.cpp | 22 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/hotplug.c | 2 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/keyboard_event.c | 112 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/keyboard_event.h | 17 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/main.c | 15 |
7 files changed, 137 insertions, 36 deletions
@@ -46,5 +46,4 @@ If you want to donate you can donate via bitcoin or monero. # Known issues * Some games receive mouse input while the UI is open -* Global hotkeys on Wayland can clash with keys used by other applications. This is primarly because Wayland compositors are missing support for global hotkey so this software uses a global hotkey system that works on all Wayland compositors. * When the UI is open the wallpaper is shown instead of the game on Hyprland and Sway. This is an issue with Hyprland and Sway. It cant be fixed until the UI is redesigned to not be a fullscreen overlay. @@ -105,4 +105,6 @@ When support for window capture is enabled on x11 then make sure to not save the Support CJK. -Move ui hover code from ::draw to ::on_event, to properly handle widget event stack.
\ No newline at end of file +Move ui hover code from ::draw to ::on_event, to properly handle widget event stack. + +Save audio devices by name instead of id. This is more robust since audio id can change(?). diff --git a/src/main.cpp b/src/main.cpp index c7a47c5..c210033 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -203,17 +203,17 @@ int main(void) { auto overlay = std::make_unique<gsr::Overlay>(resources_path, std::move(gsr_info), std::move(capture_options), egl_funcs); //overlay.show(); - std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr; - if(display_server == gsr::DisplayServer::X11) { - global_hotkeys = register_x11_hotkeys(overlay.get()); - if(!global_hotkeys) { - fprintf(stderr, "Info: failed to register some x11 hotkeys because they are registered by another program. Will use linux hotkeys instead that can clash with keys used by other applications\n"); - global_hotkeys = register_linux_hotkeys(overlay.get()); - } - } else { - fprintf(stderr, "Info: Global linux hotkeys are used which can clash with keys used by other applications. Use X11 instead if this is an issue for you\n"); - global_hotkeys = register_linux_hotkeys(overlay.get()); - } + // std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr; + // if(display_server == gsr::DisplayServer::X11) { + // global_hotkeys = register_x11_hotkeys(overlay.get()); + // if(!global_hotkeys) { + // fprintf(stderr, "Info: failed to register some x11 hotkeys because they are registered by another program. Will use linux hotkeys instead\n"); + // global_hotkeys = register_linux_hotkeys(overlay.get()); + // } + // } else { + // global_hotkeys = register_linux_hotkeys(overlay.get()); + // } + std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = register_linux_hotkeys(overlay.get()); // 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. diff --git a/tools/gsr-global-hotkeys/hotplug.c b/tools/gsr-global-hotkeys/hotplug.c index 6c82929..ba3ef9c 100644 --- a/tools/gsr-global-hotkeys/hotplug.c +++ b/tools/gsr-global-hotkeys/hotplug.c @@ -24,7 +24,7 @@ bool hotplug_event_init(hotplug_event *self) { if(fd == -1) return false; /* Not root user */ - if(bind(fd, (void *)&nls, sizeof(struct sockaddr_nl))) { + if(bind(fd, (void*)&nls, sizeof(struct sockaddr_nl))) { close(fd); return false; } diff --git a/tools/gsr-global-hotkeys/keyboard_event.c b/tools/gsr-global-hotkeys/keyboard_event.c index 9cc518a..86b7248 100644 --- a/tools/gsr-global-hotkeys/keyboard_event.c +++ b/tools/gsr-global-hotkeys/keyboard_event.c @@ -14,6 +14,9 @@ /* LINUX */ #include <linux/input.h> +#include <linux/uinput.h> + +#define GSR_UI_VIRTUAL_KEYBOARD_NAME "gsr-ui virtual keyboard" /* We could get initial keyboard state with: @@ -21,7 +24,11 @@ ioctl(fd, EVIOCGKEY(sizeof(key_states)), key_states), but ignore that for now */ -static void keyboard_event_process_input_event_data(keyboard_event *self, int fd, key_callback callback, void *userdata) { +static bool keyboard_event_has_exclusive_grab(const keyboard_event *self) { + return self->uinput_fd > 0; +} + +static void keyboard_event_process_input_event_data(keyboard_event *self, const event_extra_data *extra_data, int fd, key_callback callback, void *userdata) { struct input_event event; if(read(fd, &event, sizeof(event)) != sizeof(event)) { fprintf(stderr, "Error: failed to read input event data\n"); @@ -76,11 +83,19 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, int fd if(meta_pressed) modifiers |= KEYBOARD_MODKEY_SUPER; - callback(event.code, modifiers, event.value, userdata); + if(!callback(event.code, modifiers, event.value, userdata)) + return; + break; } } } + + if(keyboard_event_has_exclusive_grab(self) && extra_data->grabbed) { + /* TODO: Error check? */ + if(write(self->uinput_fd, &event, sizeof(event)) != sizeof(event)) + fprintf(stderr, "Error: failed to write event data to virtual keyboard for exclusively grabbed device\n"); + } } /* Returns -1 if invalid format. Expected |dev_input_filepath| to be in format /dev/input/eventN */ @@ -96,7 +111,7 @@ static int get_dev_input_id_from_filepath(const char *dev_input_filepath) { static bool keyboard_event_has_event_with_dev_input_fd(keyboard_event *self, int dev_input_id) { for(int i = 0; i < self->num_event_polls; ++i) { - if(self->dev_input_ids[i] == dev_input_id) + if(self->event_extra_data[i].dev_input_id == dev_input_id) return true; } return false; @@ -120,7 +135,7 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons unsigned long evbit = 0; ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit); - if(evbit & (1 << EV_KEY)) { + if(strcmp(device_name, GSR_UI_VIRTUAL_KEYBOARD_NAME) != 0 && (evbit & (1 << EV_KEY))) { unsigned char key_bits[KEY_MAX/8 + 1] = {0}; ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits); @@ -129,13 +144,24 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons const bool supports_key_events = key_bits[key_test/8] & (1 << (key_test % 8)); if(supports_key_events) { if(self->num_event_polls < MAX_EVENT_POLLS) { + bool grabbed = false; + if(keyboard_event_has_exclusive_grab(self)) { + grabbed = ioctl(fd, EVIOCGRAB, 1) != -1; + if(!grabbed) + fprintf(stderr, "Warning: failed to exclusively grab device %s. The focused application may receive keys used for global hotkeys\n", device_name); + } + //fprintf(stderr, "%s (%s) supports key inputs\n", dev_input_filepath, device_name); self->event_polls[self->num_event_polls] = (struct pollfd) { .fd = fd, .events = POLLIN, .revents = 0 }; - self->dev_input_ids[self->num_event_polls] = dev_input_id; + + self->event_extra_data[self->num_event_polls] = (event_extra_data) { + .dev_input_id = dev_input_id, + .grabbed = grabbed + }; ++self->num_event_polls; return true; @@ -180,23 +206,79 @@ static void keyboard_event_remove_event(keyboard_event *self, int index) { close(self->event_polls[index].fd); for(int j = index + 1; j < self->num_event_polls; ++j) { self->event_polls[j - 1] = self->event_polls[j]; - self->dev_input_ids[j - 1] = self->dev_input_ids[j]; + self->event_extra_data[j - 1] = self->event_extra_data[j]; } --self->num_event_polls; } -bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error) { +/* Returns the fd to the uinput */ +static int setup_virtual_keyboard_input(const char *name) { + /* TODO: O_NONBLOCK? */ + int fd = open("/dev/uinput", O_WRONLY); + if(fd == -1) { + fd = open("/dev/input/uinput", O_WRONLY); + if(fd == -1) { + fprintf(stderr, "Warning: failed to setup virtual device for exclusive grab (failed to open /dev/uinput or /dev/input/uinput), error: %s\n", strerror(errno)); + return -1; + } + } + + bool success = true; + success &= (ioctl(fd, UI_SET_EVBIT, EV_KEY) != -1); + for(int i = 1; i < KEY_MAX; ++i) { + success &= (ioctl(fd, UI_SET_KEYBIT, i) != -1); + } + + int ui_version = 0; + success &= (ioctl(fd, UI_GET_VERSION, &ui_version) != -1); + + if(ui_version >= 5) { + struct uinput_setup usetup; + memset(&usetup, 0, sizeof(usetup)); + usetup.id.bustype = BUS_USB; + usetup.id.vendor = 0xdec0; + usetup.id.product = 0x5eba; + snprintf(usetup.name, sizeof(usetup.name), "%s", name); + success &= (ioctl(fd, UI_DEV_SETUP, &usetup) != -1); + } else { + struct uinput_user_dev uud; + memset(&uud, 0, sizeof(uud)); + snprintf(uud.name, UINPUT_MAX_NAME_SIZE, "%s", name); + if(write(fd, &uud, sizeof(uud)) != sizeof(uud)) + success = false; + } + + success &= (ioctl(fd, UI_DEV_CREATE) != -1); + if(!success) { + close(fd); + return -1; + } + + return fd; +} + +bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab) { memset(self, 0, sizeof(*self)); self->stdout_event_index = -1; self->hotplug_event_index = -1; + if(exclusive_grab) { + // TODO: If this fails, try /dev/input/uinput instead + self->uinput_fd = setup_virtual_keyboard_input(GSR_UI_VIRTUAL_KEYBOARD_NAME); + if(self->uinput_fd <= 0) + fprintf(stderr, "Warning: failed to setup virtual keyboard input for exclusive grab. The focused application will receive keys used for global hotkeys\n"); + } + if(poll_stdout_error) { self->event_polls[self->num_event_polls] = (struct pollfd) { .fd = STDOUT_FILENO, .events = 0, .revents = 0 }; - self->dev_input_ids[self->num_event_polls] = -1; + self->event_extra_data[self->num_event_polls] = (event_extra_data) { + .dev_input_id = -1, + .grabbed = false + }; self->stdout_event_index = self->num_event_polls; ++self->num_event_polls; @@ -208,7 +290,10 @@ bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error) { .events = POLLIN, .revents = 0 }; - self->dev_input_ids[self->num_event_polls] = -1; + self->event_extra_data[self->num_event_polls] = (event_extra_data) { + .dev_input_id = -1, + .grabbed = false + }; self->hotplug_event_index = self->num_event_polls; ++self->num_event_polls; @@ -228,6 +313,11 @@ bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error) { } void keyboard_event_deinit(keyboard_event *self) { + if(self->uinput_fd > 0) { + close(self->uinput_fd); + self->uinput_fd = -1; + } + for(int i = 0; i < self->num_event_polls; ++i) { close(self->event_polls[i].fd); } @@ -264,10 +354,10 @@ void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds, /* Device is added to end of |event_polls| so it's ok to add while iterating it via index */ hotplug_event_process_event_data(&self->hotplug_ev, self->event_polls[i].fd, on_device_added_callback, self); else - keyboard_event_process_input_event_data(self, self->event_polls[i].fd, callback, userdata); + keyboard_event_process_input_event_data(self, &self->event_extra_data[i], self->event_polls[i].fd, callback, userdata); } } -bool keyboard_event_stdout_has_failed(keyboard_event *self) { +bool keyboard_event_stdout_has_failed(const keyboard_event *self) { return self->stdout_failed; } diff --git a/tools/gsr-global-hotkeys/keyboard_event.h b/tools/gsr-global-hotkeys/keyboard_event.h index 5084659..ca96cab 100644 --- a/tools/gsr-global-hotkeys/keyboard_event.h +++ b/tools/gsr-global-hotkeys/keyboard_event.h @@ -30,12 +30,18 @@ typedef enum { } keyboard_button_state; typedef struct { - struct pollfd event_polls[MAX_EVENT_POLLS]; /* Current size is |num_event_polls| */ - int dev_input_ids[MAX_EVENT_POLLS]; /* Current size is |num_event_polls| */ + int dev_input_id; + bool grabbed; +} event_extra_data; + +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| */ int num_event_polls; int stdout_event_index; int hotplug_event_index; + int uinput_fd; bool stdout_failed; hotplug_event hotplug_ev; @@ -51,13 +57,14 @@ typedef struct { } keyboard_event; /* |key| is a KEY_ from linux/input-event-codes.h. |modifiers| is a bitmask of keyboard_modkeys. |press_status| is 0 for released, 1 for pressed and 2 for repeat */ -typedef void (*key_callback)(uint32_t key, uint32_t modifiers, int press_status, void *userdata); +/* 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 keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab); void keyboard_event_deinit(keyboard_event *self); /* If |timeout_milliseconds| is -1 then wait until an event is received */ void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds, key_callback callback, void *userdata); -bool keyboard_event_stdout_has_failed(keyboard_event *self); +bool keyboard_event_stdout_has_failed(const keyboard_event *self); #endif /* KEYBOARD_EVENT_H */ diff --git a/tools/gsr-global-hotkeys/main.c b/tools/gsr-global-hotkeys/main.c index 1b2dc4a..62d34cc 100644 --- a/tools/gsr-global-hotkeys/main.c +++ b/tools/gsr-global-hotkeys/main.c @@ -23,43 +23,46 @@ static global_hotkey global_hotkeys[NUM_GLOBAL_HOTKEYS] = { { .key = KEY_F10, .modifiers = KEYBOARD_MODKEY_ALT, .action = "replay_save" } }; -static void on_key_callback(uint32_t key, uint32_t modifiers, int press_status, void *userdata) { +static bool on_key_callback(uint32_t key, uint32_t modifiers, int press_status, void *userdata) { (void)userdata; if(press_status != 1) /* 1 == Pressed */ - return; + return true; for(int i = 0; i < NUM_GLOBAL_HOTKEYS; ++i) { if(key == global_hotkeys[i].key && modifiers == global_hotkeys[i].modifiers) { puts(global_hotkeys[i].action); fflush(stdout); + return false; } } + + return true; } int main(void) { const uid_t user_id = getuid(); if(geteuid() != 0) { if(setuid(0) == -1) { - fprintf(stderr, "error: failed to change user to root\n"); + fprintf(stderr, "Error: failed to change user to root\n"); return 1; } } keyboard_event keyboard_ev; - if(!keyboard_event_init(&keyboard_ev, true)) { + if(!keyboard_event_init(&keyboard_ev, true, true)) { fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n"); setuid(user_id); return 1; } + fprintf(stderr, "Info: global hotkeys setup, waiting for hotkeys to be pressed\n"); + for(;;) { keyboard_event_poll_events(&keyboard_ev, -1, on_key_callback, NULL); if(keyboard_event_stdout_has_failed(&keyboard_ev)) { fprintf(stderr, "Info: stdout closed (parent process likely closed this process), exiting...\n"); break; } - - } keyboard_event_deinit(&keyboard_ev); |