diff options
author | dec05eba <dec05eba@protonmail.com> | 2024-12-16 02:21:38 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2024-12-16 02:21:38 +0100 |
commit | ebd8c2726b8caac6adc00cf15c5631e51d05ba1f (patch) | |
tree | fb60d1908154aeec0ff6b568b269e97b9601d70b | |
parent | 970d87975b01cd083749179df489477b94a35f41 (diff) |
Rewrite linux global hotkey to not depend on any libraries (also allows it to work on non-systemd systems(?)), remove unused gsr-window-name
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | meson.build | 19 | ||||
-rw-r--r-- | src/Overlay.cpp | 1 | ||||
-rw-r--r-- | src/main.cpp | 14 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/hotplug.c | 76 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/hotplug.h | 22 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/keyboard_event.c | 273 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/keyboard_event.h | 63 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/main.c | 264 | ||||
-rw-r--r-- | tools/gsr-window-name/main.c | 187 |
11 files changed, 478 insertions, 448 deletions
@@ -21,10 +21,7 @@ These are the dependencies needed to build GPU Screen Recorder UI: * x11 (libx11, libxrandr, libxrender, libxcomposite, libxfixes, libxi) * libglvnd (which provides libgl, libglx and libegl) -* libevdev -* libudev (systemd-libs) -* libinput -* libxkbcommon +* linux-api-headers ## Runtime dependencies There are also additional dependencies needed at runtime: @@ -93,8 +93,6 @@ Run `systemctl status --user gpu-screen-recorder` when starting recording and gi Add option to select which gpu to record with, or list all monitors and automatically use the gpu associated with the monitor. Do the same in gtk application. -Remove all dependencies from tools/gsr-global-hotkeys and roll our own keyboard events code. - Test global hotkeys with azerty instead of qwerty. Fix cursor grab not working in owlboy, need to use xigrab. diff --git a/meson.build b/meson.build index 3b21a93..9aacae9 100644 --- a/meson.build +++ b/meson.build @@ -62,22 +62,13 @@ executable( ) executable( - 'gsr-window-name', - ['tools/gsr-window-name/main.c'], - install : true, - dependencies : [dependency('x11')], -) - -executable( 'gsr-global-hotkeys', - ['tools/gsr-global-hotkeys/main.c'], - install : true, - dependencies : [ - dependency('libevdev'), - dependency('libudev'), - dependency('libinput'), - dependency('xkbcommon') + [ + 'tools/gsr-global-hotkeys/hotplug.c', + 'tools/gsr-global-hotkeys/keyboard_event.c', + 'tools/gsr-global-hotkeys/main.c' ], + install : true ) install_subdir('images', install_dir : gsr_ui_resources_path) diff --git a/src/Overlay.cpp b/src/Overlay.cpp index da3758e..628a862 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -1374,6 +1374,7 @@ namespace gsr { } } + // TODO: Instead of checking power supply status periodically listen to power supply event void Overlay::update_power_supply_status() { if(config.replay_config.turn_on_replay_automatically_mode != "turn_on_at_power_supply_connected") return; diff --git a/src/main.cpp b/src/main.cpp index 98b1ce3..c7a47c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -156,7 +156,7 @@ int main(void) { signal(SIGINT, sigint_handler); if(mgl_init() != 0) { - fprintf(stderr, "error: failed to initialize mgl. Failed to either connect to the X11 server or setup opengl\n"); + fprintf(stderr, "Error: failed to initialize mgl. Failed to either connect to the X11 server or setup opengl\n"); exit(1); } @@ -164,13 +164,13 @@ int main(void) { // TODO: Show the error in ui gsr::GsrInfoExitStatus gsr_info_exit_status = gsr::get_gpu_screen_recorder_info(&gsr_info); if(gsr_info_exit_status != gsr::GsrInfoExitStatus::OK) { - fprintf(stderr, "error: failed to get gpu-screen-recorder info, error: %d\n", (int)gsr_info_exit_status); + fprintf(stderr, "Error: failed to get gpu-screen-recorder info, error: %d\n", (int)gsr_info_exit_status); exit(1); } const gsr::DisplayServer display_server = gsr_info.system_info.display_server; if(display_server == gsr::DisplayServer::WAYLAND) - fprintf(stderr, "warning: Wayland support is experimental and requires XWayland. Things may not work as expected.\n"); + fprintf(stderr, "Warning: Wayland support is experimental and requires XWayland. Things may not work as expected.\n"); gsr::SupportedCaptureOptions capture_options = gsr::get_supported_capture_options(gsr_info); @@ -198,7 +198,7 @@ int main(void) { exit(1); } - 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); //overlay.show(); @@ -207,11 +207,11 @@ int main(void) { 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"); + 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"); + 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()); } @@ -230,7 +230,7 @@ int main(void) { } } - fprintf(stderr, "info: shutting down!\n"); + fprintf(stderr, "Info: shutting down!\n"); overlay.reset(); gsr::deinit_theme(); gsr::deinit_color_theme(); diff --git a/tools/gsr-global-hotkeys/hotplug.c b/tools/gsr-global-hotkeys/hotplug.c new file mode 100644 index 0000000..6c82929 --- /dev/null +++ b/tools/gsr-global-hotkeys/hotplug.c @@ -0,0 +1,76 @@ +#include "hotplug.h" + +/* C stdlib */ +#include <string.h> + +/* POSIX */ +#include <unistd.h> +#include <sys/socket.h> + +/* LINUX */ +#include <linux/types.h> +#include <linux/netlink.h> + +bool hotplug_event_init(hotplug_event *self) { + memset(self, 0, sizeof(*self)); + + struct sockaddr_nl nls = { + .nl_family = AF_NETLINK, + .nl_pid = getpid(), + .nl_groups = -1 + }; + + const int fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if(fd == -1) + return false; /* Not root user */ + + if(bind(fd, (void *)&nls, sizeof(struct sockaddr_nl))) { + close(fd); + return false; + } + + self->fd = fd; + return true; +} + +void hotplug_event_deinit(hotplug_event *self) { + if(self->fd > 0) { + close(self->fd); + self->fd = -1; + } +} + +int hotplug_event_steal_fd(hotplug_event *self) { + const int fd = self->fd; + self->fd = -1; + return fd; +} + +/* TODO: This assumes SUBSYSTEM= is output before DEVNAME=, is that always true? */ +static void hotplug_event_parse_netlink_data(hotplug_event *self, const char *line, hotplug_device_added_callback callback, void *userdata) { + const char *at_symbol = strchr(line, '@'); + if(at_symbol) { + self->event_is_add = strncmp(line, "add@", 4) == 0; + self->subsystem_is_input = false; + } else if(self->event_is_add) { + if(strcmp(line, "SUBSYSTEM=input") == 0) + self->subsystem_is_input = true; + + if(self->subsystem_is_input && strncmp(line, "DEVNAME=", 8) == 0) + callback(line+8, userdata); + } +} + +/* Netlink uevent structure is documented here: https://web.archive.org/web/20160127215232/https://www.kernel.org/doc/pending/hotplug.txt */ +void hotplug_event_process_event_data(hotplug_event *self, int fd, hotplug_device_added_callback callback, void *userdata) { + const int bytes_read = read(fd, self->event_data, sizeof(self->event_data)); + int data_index = 0; + if(bytes_read <= 0) + return; + + /* Hotplug data ends with a newline and a null terminator */ + while(data_index < bytes_read) { + hotplug_event_parse_netlink_data(self, self->event_data + data_index, callback, userdata); + data_index += strlen(self->event_data + data_index) + 1; /* Skip null terminator as well */ + } +} diff --git a/tools/gsr-global-hotkeys/hotplug.h b/tools/gsr-global-hotkeys/hotplug.h new file mode 100644 index 0000000..665485a --- /dev/null +++ b/tools/gsr-global-hotkeys/hotplug.h @@ -0,0 +1,22 @@ +#ifndef HOTPLUG_H +#define HOTPLUG_H + +/* C stdlib */ +#include <stdbool.h> + +typedef struct { + int fd; + bool event_is_add; + bool subsystem_is_input; + char event_data[1024]; +} hotplug_event; + +typedef void (*hotplug_device_added_callback)(const char *devname, void *userdata); + +bool hotplug_event_init(hotplug_event *self); +void hotplug_event_deinit(hotplug_event *self); + +int hotplug_event_steal_fd(hotplug_event *self); +void hotplug_event_process_event_data(hotplug_event *self, int fd, hotplug_device_added_callback callback, void *userdata); + +#endif /* HOTPLUG_H */ diff --git a/tools/gsr-global-hotkeys/keyboard_event.c b/tools/gsr-global-hotkeys/keyboard_event.c new file mode 100644 index 0000000..9cc518a --- /dev/null +++ b/tools/gsr-global-hotkeys/keyboard_event.c @@ -0,0 +1,273 @@ +#include "keyboard_event.h" + +/* C stdlib */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdbool.h> + +/* POSIX */ +#include <fcntl.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/poll.h> + +/* LINUX */ +#include <linux/input.h> + +/* + We could get initial keyboard state with: + unsigned char key_states[KEY_MAX/8 + 1]; + 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) { + struct input_event event; + if(read(fd, &event, sizeof(event)) != sizeof(event)) { + fprintf(stderr, "Error: failed to read input event data\n"); + return; + } + + // value = 1 == key pressed + //if(event.type == EV_KEY && event.code == KEY_A && event.value == 1) { + //fprintf(stderr, "fd: %d, type: %d, pressed %d, value: %d\n", fd, event.type, event.code, event.value); + //} + + if(event.type == EV_KEY) { + switch(event.code) { + case KEY_LEFTSHIFT: + self->lshift_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_RIGHTSHIFT: + self->rshift_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_LEFTCTRL: + self->lctrl_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_RIGHTCTRL: + self->rctrl_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_LEFTALT: + self->lalt_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_RIGHTALT: + self->ralt_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_LEFTMETA: + self->lmeta_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + case KEY_RIGHTMETA: + self->rmeta_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; + break; + default: { + const bool shift_pressed = self->lshift_button_state == KEYBOARD_BUTTON_PRESSED || self->rshift_button_state == KEYBOARD_BUTTON_PRESSED; + const bool ctrl_pressed = self->lctrl_button_state == KEYBOARD_BUTTON_PRESSED || self->rctrl_button_state == KEYBOARD_BUTTON_PRESSED; + const bool alt_pressed = self->lalt_button_state == KEYBOARD_BUTTON_PRESSED || self->ralt_button_state == KEYBOARD_BUTTON_PRESSED; + const bool meta_pressed = self->lmeta_button_state == KEYBOARD_BUTTON_PRESSED || self->rmeta_button_state == KEYBOARD_BUTTON_PRESSED; + //fprintf(stderr, "pressed key: %d, state: %d, shift: %s, ctrl: %s, alt: %s, meta: %s\n", event.code, event.value, + // shift_pressed ? "yes" : "no", ctrl_pressed ? "yes" : "no", alt_pressed ? "yes" : "no", meta_pressed ? "yes" : "no"); + uint32_t modifiers = 0; + if(shift_pressed) + modifiers |= KEYBOARD_MODKEY_SHIFT; + if(ctrl_pressed) + modifiers |= KEYBOARD_MODKEY_CTRL; + if(alt_pressed) + modifiers |= KEYBOARD_MODKEY_ALT; + if(meta_pressed) + modifiers |= KEYBOARD_MODKEY_SUPER; + + callback(event.code, modifiers, event.value, userdata); + break; + } + } + } +} + +/* Returns -1 if invalid format. Expected |dev_input_filepath| to be in format /dev/input/eventN */ +static int get_dev_input_id_from_filepath(const char *dev_input_filepath) { + if(strncmp(dev_input_filepath, "/dev/input/event", 16) != 0) + return -1; + + int dev_input_id = -1; + if(sscanf(dev_input_filepath + 16, "%d", &dev_input_id) == 1) + return dev_input_id; + return -1; +} + +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) + return true; + } + return false; +} + +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(keyboard_event_has_event_with_dev_input_fd(self, dev_input_id)) + return false; + + const int fd = open(dev_input_filepath, O_RDONLY); + if(fd == -1) + return false; + + char device_name[256]; + device_name[0] = '\0'; + ioctl(fd, EVIOCGNAME(sizeof(device_name)), device_name); + + unsigned long evbit = 0; + ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit); + if(evbit & (1 << EV_KEY)) { + unsigned char key_bits[KEY_MAX/8 + 1] = {0}; + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits); + + /* Test if device supports KEY_A since not all devices that support EV_KEY are keyboards, for example even a power button is an EV_KEY */ + const int key_test = KEY_A; + 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) { + //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->num_event_polls; + return true; + } else { + fprintf(stderr, "Warning: the maximum number of keyboard devices have been registered. The newly added keyboard will be ignored\n"); + } + } + } + + close(fd); + return false; +} + +static bool keyboard_event_add_dev_input_devices(keyboard_event *self) { + DIR *dir = opendir("/dev/input"); + if(!dir) { + fprintf(stderr, "error: failed to open /dev/input, error: %s\n", strerror(errno)); + return false; + } + + char dev_input_filepath[1024]; + for(;;) { + struct dirent *entry = readdir(dir); + if(!entry) + break; + + if(strncmp(entry->d_name, "event", 5) != 0) + continue; + + snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/%s", entry->d_name); + keyboard_event_try_add_device_if_keyboard(self, dev_input_filepath); + } + + closedir(dir); + return true; +} + +static void keyboard_event_remove_event(keyboard_event *self, int index) { + if(index < 0 || index >= self->num_event_polls) + return; + + 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->num_event_polls; +} + +bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error) { + memset(self, 0, sizeof(*self)); + self->stdout_event_index = -1; + self->hotplug_event_index = -1; + + 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->stdout_event_index = self->num_event_polls; + ++self->num_event_polls; + } + + if(hotplug_event_init(&self->hotplug_ev)) { + self->event_polls[self->num_event_polls] = (struct pollfd) { + .fd = hotplug_event_steal_fd(&self->hotplug_ev), + .events = POLLIN, + .revents = 0 + }; + self->dev_input_ids[self->num_event_polls] = -1; + + self->hotplug_event_index = self->num_event_polls; + ++self->num_event_polls; + } else { + fprintf(stderr, "Warning: failed to setup hotplugging\n"); + } + + keyboard_event_add_dev_input_devices(self); + + /* Neither hotplugging nor any keyboard devices were found. We will never listen to keyboard events so might as well fail */ + if(self->num_event_polls == 0) { + keyboard_event_deinit(self); + return false; + } + + return true; +} + +void keyboard_event_deinit(keyboard_event *self) { + for(int i = 0; i < self->num_event_polls; ++i) { + close(self->event_polls[i].fd); + } + self->num_event_polls = 0; + + hotplug_event_deinit(&self->hotplug_ev); +} + +static void on_device_added_callback(const char *devname, void *userdata) { + keyboard_event *keyboard_ev = userdata; + char dev_input_filepath[1024]; + snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/%s", devname); + keyboard_event_try_add_device_if_keyboard(keyboard_ev, dev_input_filepath); +} + +void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds, key_callback callback, void *userdata) { + if(poll(self->event_polls, self->num_event_polls, timeout_milliseconds) <= 0) + return; + + for(int i = 0; i < self->num_event_polls; ++i) { + if(i == self->stdout_event_index && self->event_polls[i].revents & (POLLHUP|POLLERR)) + self->stdout_failed = true; + + if(self->event_polls[i].revents & POLLHUP) { /* TODO: What if this is the hotplug fd? */ + keyboard_event_remove_event(self, i); + --i; /* Repeat same index since the current element has been removed */ + continue; + } + + if(!(self->event_polls[i].revents & POLLIN)) + continue; + + if(i == self->hotplug_event_index) + /* 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); + } +} + +bool keyboard_event_stdout_has_failed(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 new file mode 100644 index 0000000..5084659 --- /dev/null +++ b/tools/gsr-global-hotkeys/keyboard_event.h @@ -0,0 +1,63 @@ +#ifndef KEYBOARD_EVENT_H +#define KEYBOARD_EVENT_H + +/* Read keyboard input from linux /dev/input/eventN devices, with hotplug support */ + +#include "hotplug.h" + +/* C stdlib */ +#include <stdbool.h> +#include <stdint.h> + +/* POSIX */ +#include <sys/poll.h> + +/* LINUX */ +#include <linux/input-event-codes.h> + +#define MAX_EVENT_POLLS 32 + +typedef enum { + KEYBOARD_MODKEY_ALT = 1 << 0, + KEYBOARD_MODKEY_SUPER = 1 << 1, + KEYBOARD_MODKEY_CTRL = 1 << 2, + KEYBOARD_MODKEY_SHIFT = 1 << 3 +} keyboard_modkeys; + +typedef enum { + KEYBOARD_BUTTON_RELEASED, + KEYBOARD_BUTTON_PRESSED +} 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 num_event_polls; + + int stdout_event_index; + int hotplug_event_index; + bool stdout_failed; + + hotplug_event hotplug_ev; + + keyboard_button_state lshift_button_state; + keyboard_button_state rshift_button_state; + keyboard_button_state lctrl_button_state; + keyboard_button_state rctrl_button_state; + keyboard_button_state lalt_button_state; + keyboard_button_state ralt_button_state; + keyboard_button_state lmeta_button_state; + keyboard_button_state rmeta_button_state; +} 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); + +bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error); +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); + +#endif /* KEYBOARD_EVENT_H */ diff --git a/tools/gsr-global-hotkeys/main.c b/tools/gsr-global-hotkeys/main.c index 2823487..1be7f2d 100644 --- a/tools/gsr-global-hotkeys/main.c +++ b/tools/gsr-global-hotkeys/main.c @@ -1,28 +1,11 @@ -#include <stdio.h> -#include <unistd.h> -#include <fcntl.h> -#include <string.h> -#include <errno.h> -#include <stdbool.h> -#include <poll.h> - -#include <libudev.h> -#include <libinput.h> -#include <libevdev/libevdev.h> -#include <xkbcommon/xkbcommon.h> +#include "keyboard_event.h" -typedef struct { - struct xkb_context *xkb_context; - struct xkb_keymap *xkb_keymap; - struct xkb_state *xkb_state; -} key_mapper; +/* C stdlib */ +#include <stdio.h> +#include <stdint.h> -typedef enum { - MODKEY_ALT = 1 << 0, - MODKEY_SUPER = 1 << 1, - MODKEY_CTRL = 1 << 2, - MODKEY_SHIFT = 1 << 3 -} modkeys; +/* POSIX */ +#include <unistd.h> typedef struct { uint32_t key; @@ -32,186 +15,28 @@ typedef struct { #define NUM_GLOBAL_HOTKEYS 6 static global_hotkey global_hotkeys[NUM_GLOBAL_HOTKEYS] = { - { .key = XKB_KEY_z, .modifiers = MODKEY_ALT, .action = "show_hide" }, - { .key = XKB_KEY_F9, .modifiers = MODKEY_ALT, .action = "record" }, - { .key = XKB_KEY_F7, .modifiers = MODKEY_ALT, .action = "pause" }, - { .key = XKB_KEY_F8, .modifiers = MODKEY_ALT, .action = "stream" }, - { .key = XKB_KEY_F10, .modifiers = MODKEY_ALT | MODKEY_SHIFT, .action = "replay_start" }, - { .key = XKB_KEY_F10, .modifiers = MODKEY_ALT, .action = "replay_save" } + { .key = KEY_Z, .modifiers = KEYBOARD_MODKEY_ALT, .action = "show_hide" }, + { .key = KEY_F9, .modifiers = KEYBOARD_MODKEY_ALT, .action = "record" }, + { .key = KEY_F7, .modifiers = KEYBOARD_MODKEY_ALT, .action = "pause" }, + { .key = KEY_F8, .modifiers = KEYBOARD_MODKEY_ALT, .action = "stream" }, + { .key = KEY_F10, .modifiers = KEYBOARD_MODKEY_ALT | KEYBOARD_MODKEY_SHIFT, .action = "replay_start" }, + { .key = KEY_F10, .modifiers = KEYBOARD_MODKEY_ALT, .action = "replay_save" } }; -static int open_restricted(const char *path, int flags, void *user_data) { - (void)user_data; - int fd = open(path, flags); - if(fd < 0) - fprintf(stderr, "error: failed to open %s, error: %s\n", path, strerror(errno)); - return fd < 0 ? -errno : fd; -} - -static void close_restricted(int fd, void *user_data) { - (void)user_data; - close(fd); -} - -static const struct libinput_interface interface = { - .open_restricted = open_restricted, - .close_restricted = close_restricted, -}; - -static bool is_mod_key(xkb_keycode_t xkb_key_code) { - return xkb_key_code >= XKB_KEY_Shift_L && xkb_key_code <= XKB_KEY_Hyper_R; -} - -typedef struct { - const char *modname; - modkeys key; -} modname_to_modkey_map; - -static uint32_t xkb_state_to_modifiers(struct xkb_state *xkb_state) { - const modname_to_modkey_map modifier_keys[] = { - { .modname = XKB_MOD_NAME_ALT, .key = MODKEY_ALT }, - { .modname = XKB_MOD_NAME_LOGO, .key = MODKEY_SUPER }, - { .modname = XKB_MOD_NAME_SHIFT, .key = MODKEY_SHIFT }, - { .modname = XKB_MOD_NAME_CTRL, .key = MODKEY_CTRL } - }; - - uint32_t modifiers = 0; - for(int i = 0; i < 4; ++i) { - if(xkb_state_mod_name_is_active(xkb_state, modifier_keys[i].modname, XKB_STATE_MODS_EFFECTIVE) > 0) - modifiers |= modifier_keys[i].key; - } - return modifiers; -} - -#define KEY_CODE_EV_TO_XKB(key) ((key) + 8) - -static int print_key_event(struct libinput_event *event, key_mapper *mapper) { - struct libinput_event_keyboard *keyboard = libinput_event_get_keyboard_event(event); - const uint32_t key_code = libinput_event_keyboard_get_key(keyboard); - enum libinput_key_state state_code = libinput_event_keyboard_get_key_state(keyboard); - - const xkb_keycode_t xkb_key_code = KEY_CODE_EV_TO_XKB(key_code); - xkb_state_update_key(mapper->xkb_state, xkb_key_code, state_code == LIBINPUT_KEY_STATE_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP); - xkb_keysym_t xkb_key_sym = xkb_state_key_get_one_sym(mapper->xkb_state, xkb_key_code); - // char main_key[128]; - // main_key[0] = '\0'; +static void on_key_callback(uint32_t key, uint32_t modifiers, int press_status, void *userdata) { + (void)userdata; + if(press_status != 1) /* 1 == Pressed */ + return; - // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0) - // strcat(main_key, "Super+"); - // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0) - // strcat(main_key, "Ctrl+"); - // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0 && strcmp(main_key, "Meta") != 0) - // strcat(main_key, "Alt+"); - // if(xkb_state_mod_name_is_active(mapper->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0) - // strcat(main_key, "Shift+"); - - // if(!is_mod_key(xkb_key_sym)) { - // char reg_key[64]; - // reg_key[0] = '\0'; - // xkb_keysym_get_name(xkb_key_sym, reg_key, sizeof(reg_key)); - // strcat(main_key, reg_key); - // } - - if(state_code != LIBINPUT_KEY_STATE_PRESSED) - return 0; - - const uint32_t current_modifiers = xkb_state_to_modifiers(mapper->xkb_state); for(int i = 0; i < NUM_GLOBAL_HOTKEYS; ++i) { - if(xkb_key_sym == global_hotkeys[i].key && current_modifiers == global_hotkeys[i].modifiers) { + if(key == global_hotkeys[i].key && modifiers == global_hotkeys[i].modifiers) { puts(global_hotkeys[i].action); fflush(stdout); - break; } } - - return 0; -} - -static int handle_events(struct libinput *libinput, key_mapper *mapper) { - int result = -1; - struct libinput_event *event; - - if(libinput_dispatch(libinput) < 0) - return result; - - while((event = libinput_get_event(libinput)) != NULL) { - if(libinput_event_get_type(event) == LIBINPUT_EVENT_KEYBOARD_KEY) - print_key_event(event, mapper); - - libinput_event_destroy(event); - result = 0; - } - - return result; -} - -static int run_mainloop(struct libinput *libinput, key_mapper *mapper) { - struct pollfd fds[2] = { - { - .fd = libinput_get_fd(libinput), - .events = POLLIN, - .revents = 0 - }, - { - .fd = STDOUT_FILENO, - .events = 0, - .revents = 0 - } - }; - - if(handle_events(libinput, mapper) != 0) { - fprintf(stderr, "error: didn't receive device added events. Is this program not running as root?\n"); - return -1; - } - - while(poll(fds, 2, -1) >= 0) { - if(fds[0].revents & POLLIN) - handle_events(libinput, mapper); - if(fds[1].revents & (POLLHUP|POLLERR)) - break; - } - - return 0; -} - -static bool mapper_refresh_keymap(key_mapper *mapper) { - if(mapper->xkb_keymap != NULL) { - xkb_keymap_unref(mapper->xkb_keymap); - mapper->xkb_keymap = NULL; - } - - // TODO: - struct xkb_rule_names names = { - NULL, NULL, - NULL,//keymap_is_default(mapper->layout) ? NULL : mapper->layout, - NULL,//keymap_is_default(mapper->variant) ? NULL : mapper->variant, - NULL - }; - mapper->xkb_keymap = xkb_keymap_new_from_names(mapper->xkb_context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); - if(mapper->xkb_keymap == NULL) { - fprintf(stderr, "error: failed to create XKB keymap.\n"); - return false; - } - - if(mapper->xkb_state != NULL) { - xkb_state_unref(mapper->xkb_state); - mapper->xkb_state = NULL; - } - - mapper->xkb_state = xkb_state_new(mapper->xkb_keymap); - if(mapper->xkb_state == NULL) { - fprintf(stderr, "error: failed to create XKB state.\n"); - return false; - } - - return true; } int main(void) { - int result = 0; - struct udev *udev = NULL; - struct libinput *libinput = NULL; - const uid_t user_id = getuid(); if(geteuid() != 0) { if(setuid(0) == -1) { @@ -220,53 +45,24 @@ int main(void) { } } - udev = udev_new(); - if(!udev) { - fprintf(stderr, "error: udev_new failed\n"); - result = 1; - goto done; - } - - libinput = libinput_udev_create_context(&interface, NULL, udev); - if(!libinput) { - fprintf(stderr, "error: libinput_udev_create_context failed\n"); - result = 1; - goto done; - } - - if(libinput_udev_assign_seat(libinput, "seat0") != 0) { - fprintf(stderr, "error: libinput_udev_assign_seat with seat0 failed\n"); - result = 1; - goto done; + keyboard_event keyboard_ev; + if(!keyboard_event_init(&keyboard_ev, true)) { + fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n"); + setuid(user_id); + return 1; } - key_mapper mapper; - mapper.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if(!mapper.xkb_context) { - fprintf(stderr, "error: xkb_context_new failed\n"); - result = 1; - goto done; - } + 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; + } - if(!mapper_refresh_keymap(&mapper)) { - fprintf(stderr, "error: key mapper failed\n"); - result = 1; - goto done; - } - if(run_mainloop(libinput, &mapper) < 0) { - fprintf(stderr, "error: failed to start main loop\n"); - result = 1; - goto done; } - done: - if(libinput) - libinput_unref(libinput); - - if(udev) - udev_unref(udev); - + keyboard_event_deinit(&keyboard_ev); setuid(user_id); - return result; + return 0; } diff --git a/tools/gsr-window-name/main.c b/tools/gsr-window-name/main.c deleted file mode 100644 index 8ebf1e0..0000000 --- a/tools/gsr-window-name/main.c +++ /dev/null @@ -1,187 +0,0 @@ -#include <X11/Xlib.h> -#include <X11/Xatom.h> -#include <X11/Xutil.h> - -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -typedef enum { - CAPTURE_TYPE_FOCUSED, - CAPTURE_TYPE_CURSOR -} capture_type; - -static bool window_has_atom(Display *dpy, Window window, Atom atom) { - Atom type; - unsigned long len, bytes_left; - int format; - unsigned char *properties = NULL; - if(XGetWindowProperty(dpy, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success) - return false; - - if(properties) - XFree(properties); - - return type != None; -} - -static bool window_is_user_program(Display *dpy, Window window) { - const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False); - const Atom wm_state_atom = XInternAtom(dpy, "WM_STATE", False); - return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom); -} - -static Window get_window_at_cursor_position(Display *dpy) { - Window root_window = None; - Window window = None; - int dummy_i; - unsigned int dummy_u; - int cursor_pos_x = 0; - int cursor_pos_y = 0; - XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u); - return window; -} - -static Window get_focused_window(Display *dpy, capture_type cap_type) { - const Atom net_active_window_atom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); - Window focused_window = None; - - if(cap_type == CAPTURE_TYPE_FOCUSED) { - // Atom type = None; - // int format = 0; - // unsigned long num_items = 0; - // unsigned long bytes_left = 0; - // unsigned char *data = NULL; - // XGetWindowProperty(dpy, DefaultRootWindow(dpy), net_active_window_atom, 0, 1, False, XA_WINDOW, &type, &format, &num_items, &bytes_left, &data); - - // fprintf(stderr, "focused window: %p\n", (void*)data); - - // if(type == XA_WINDOW && num_items == 1 && data) - // return *(Window*)data; - - int revert_to = 0; - XGetInputFocus(dpy, &focused_window, &revert_to); - if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window)) - return focused_window; - } - - focused_window = get_window_at_cursor_position(dpy); - if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window)) - return focused_window; - - return None; -} - -static char* get_window_title(Display *dpy, Window window) { - const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); - const Atom wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); - const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False); - - Atom type = None; - int format = 0; - unsigned long num_items = 0; - unsigned long bytes_left = 0; - unsigned char *data = NULL; - XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data); - - if(type == utf8_string_atom && format == 8 && data) - return (char*)data; - - type = None; - format = 0; - num_items = 0; - bytes_left = 0; - data = NULL; - XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data); - - if((type == XA_STRING || type == utf8_string_atom) && data) - return (char*)data; - - return NULL; -} - -static const char* strip(const char *str, int *len) { - int str_len = strlen(str); - for(int i = 0; i < str_len; ++i) { - if(str[i] != ' ') { - str += i; - str_len -= i; - break; - } - } - - for(int i = str_len - 1; i >= 0; --i) { - if(str[i] != ' ') { - str_len = i + 1; - break; - } - } - - *len = str_len; - return str; -} - -static void print_str_strip(const char *str) { - int len = 0; - str = strip(str, &len); - printf("%.*s", len, str); -} - -static int x11_ignore_error(Display *dpy, XErrorEvent *error_event) { - (void)dpy; - (void)error_event; - return 0; -} - -static void usage(void) { - fprintf(stderr, "usage: gsr-window-name <focused|cursor>\n"); - fprintf(stderr, "options:\n"); - fprintf(stderr, " focused The class/name of the focused window is returned. If no window is focused then the window beneath the cursor is returned instead\n"); - fprintf(stderr, " cursor The class/name of the window beneath the cursor is returned\n"); - exit(1); -} - -int main(int argc, char **argv) { - if(argc != 2) - usage(); - - const char *cap_type_str = argv[1]; - capture_type cap_type = CAPTURE_TYPE_FOCUSED; - if(strcmp(cap_type_str, "focused") == 0) { - cap_type = CAPTURE_TYPE_FOCUSED; - } else if(strcmp(cap_type_str, "cursor") == 0) { - cap_type = CAPTURE_TYPE_CURSOR; - } else { - fprintf(stderr, "error: invalid option '%s', expected either 'focused' or 'cursor'\n", cap_type_str); - usage(); - } - - Display *dpy = XOpenDisplay(NULL); - if(!dpy) { - fprintf(stderr, "Error: failed to connect to the X11 server\n"); - exit(1); - } - - XSetErrorHandler(x11_ignore_error); - - const Window focused_window = get_focused_window(dpy, cap_type); - if(focused_window == None) - exit(2); - - // Window title is not always ideal (for example for a browser), but for games its pretty much required - char *window_title = get_window_title(dpy, focused_window); - if(window_title) { - print_str_strip(window_title); - exit(0); - } - - XClassHint class_hint = {0}; - XGetClassHint(dpy, focused_window, &class_hint); - if(class_hint.res_class) { - print_str_strip(class_hint.res_class); - exit(0); - } - - return 2; -} |