aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2025-01-07 01:15:56 +0100
committerdec05eba <dec05eba@protonmail.com>2025-01-07 01:15:56 +0100
commit9a5c20836a70fd1f1cf9d21fc7e4febfbed38f53 (patch)
tree422d18e9d2cf22e8c9aff2b60481b451905cad03
parentee123ceb0a52331764901386d404a53aead65c23 (diff)
Try and support different keyboard layouts through x11 xkb mapping
-rw-r--r--README.md5
-rw-r--r--tools/gsr-global-hotkeys/keyboard_event.c79
-rw-r--r--tools/gsr-global-hotkeys/keyboard_event.h23
-rw-r--r--tools/gsr-global-hotkeys/main.c75
4 files changed, 172 insertions, 10 deletions
diff --git a/README.md b/README.md
index cc4cdc1..7bd2d88 100644
--- a/README.md
+++ b/README.md
@@ -35,8 +35,6 @@ There are also additional dependencies needed at runtime:
* [GPU Screen Recorder Notification](https://git.dec05eba.com/gpu-screen-recorder-notification/)
## Program behavior notes
-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 Left Alt+Y instead of Left 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 select "Only grab virtual devices"
@@ -57,5 +55,4 @@ If you want to donate you can donate via bitcoin or monero.
# Known issues
* 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.
-* Opening the UI when a game is fullscreened can mess up the game window a bit on Hyprland and Sway. I believe this is an issue in Hyprland and Sway.
-* Different keyboard layouts are not supported at the moment. 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 Left Alt+Y instead of Left Alt+Z to open/hide the UI. If you experience this issue then please email dec05eba@protonmail.com to get it fixed.
+* Opening the UI when a game is fullscreened can mess up the game window a bit on Hyprland and Sway. I believe this is an issue in Hyprland and Sway. \ No newline at end of file
diff --git a/tools/gsr-global-hotkeys/keyboard_event.c b/tools/gsr-global-hotkeys/keyboard_event.c
index 203dc00..bcfd616 100644
--- a/tools/gsr-global-hotkeys/keyboard_event.c
+++ b/tools/gsr-global-hotkeys/keyboard_event.c
@@ -25,6 +25,24 @@
#define KEY_STATES_SIZE (KEY_MAX/8 + 1)
+#define KEYCODE_TO_XKB_KEYCODE(key) ((key) + 8)
+
+#define XK_Shift_L 0xffe1 /* Left shift */
+#define XK_Shift_R 0xffe2 /* Right shift */
+#define XK_Control_L 0xffe3 /* Left control */
+#define XK_Control_R 0xffe4 /* Right control */
+#define XK_Alt_L 0xffe9 /* Left alt */
+#define XK_Alt_R 0xffea /* Right alt */
+#define XK_Super_L 0xffeb /* Left super */
+#define XK_Super_R 0xffec /* Right super */
+
+#define XK_z 0x007a
+#define XK_F7 0xffc4
+#define XK_F8 0xffc5
+#define XK_F9 0xffc6
+#define XK_F10 0xffc7
+#define XK_F11 0xffc8
+
static inline int count_num_bits_set(unsigned char c) {
int n = 0;
n += (c & 1);
@@ -117,6 +135,35 @@ static void keyboard_event_process_key_state_change(keyboard_event *self, struct
}
}
+static uint32_t keycode_to_keysym(keyboard_event *self, uint16_t keycode) {
+ const unsigned long xkb_keycode = KEYCODE_TO_XKB_KEYCODE(keycode);
+ if(self->x_context.display && self->x_context.XKeycodeToKeysym && xkb_keycode <= 255)
+ return self->x_context.XKeycodeToKeysym(self->x_context.display, xkb_keycode, 0);
+ else
+ return 0;
+}
+
+/* TODO: Support more keys when needed */
+static uint32_t keysym_to_keycode(uint32_t keysym) {
+ switch(keysym) {
+ case XK_Control_L: return KEY_LEFTCTRL;
+ case XK_Shift_L: return KEY_LEFTSHIFT;
+ case XK_Alt_L: return KEY_LEFTALT;
+ case XK_Super_L: return KEY_LEFTMETA;
+ case XK_Control_R: return KEY_RIGHTCTRL;
+ case XK_Shift_R: return KEY_RIGHTSHIFT;
+ case XK_Alt_R: return KEY_RIGHTALT;
+ case XK_Super_R: return KEY_RIGHTMETA;
+ case XK_z: return KEY_Z;
+ case XK_F7: return KEY_F7;
+ case XK_F8: return KEY_F8;
+ case XK_F9: return KEY_F9;
+ case XK_F10: return KEY_F10;
+ case XK_F11: return KEY_F11;
+ default: return 0;
+ }
+}
+
static void keyboard_event_process_input_event_data(keyboard_event *self, event_extra_data *extra_data, int fd, key_callback callback, void *userdata) {
struct input_event event;
if(read(fd, &event, sizeof(event)) != sizeof(event)) {
@@ -137,7 +184,12 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
if(event.type == EV_KEY) {
keyboard_event_process_key_state_change(self, event, extra_data, fd);
- switch(event.code) {
+ uint32_t keycode = event.code;
+ const uint32_t keysym = keycode_to_keysym(self, event.code);
+ if(keysym)
+ keycode = keysym_to_keycode(keysym);
+
+ switch(keycode) {
case KEY_LEFTSHIFT:
self->lshift_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
break;
@@ -168,7 +220,7 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
const bool lalt_pressed = self->lalt_button_state == KEYBOARD_BUTTON_PRESSED;
const bool ralt_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,
+ //fprintf(stderr, "pressed key: %d, state: %d, shift: %s, ctrl: %s, alt: %s, meta: %s\n", keycode, 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)
@@ -182,7 +234,7 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
if(meta_pressed)
modifiers |= KEYBOARD_MODKEY_SUPER;
- if(!callback(event.code, modifiers, event.value, userdata))
+ if(!callback(keycode, modifiers, event.value, userdata))
return;
break;
@@ -406,11 +458,12 @@ 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, keyboard_grab_type grab_type) {
+bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab, keyboard_grab_type grab_type, x11_context x_context) {
memset(self, 0, sizeof(*self));
self->stdout_event_index = -1;
self->hotplug_event_index = -1;
self->grab_type = grab_type;
+ self->x_context = x_context;
if(exclusive_grab) {
self->uinput_fd = setup_virtual_keyboard_input(GSR_UI_VIRTUAL_KEYBOARD_NAME);
@@ -490,7 +543,25 @@ static void on_device_added_callback(const char *devname, void *userdata) {
keyboard_event_try_add_device_if_keyboard(keyboard_ev, dev_input_filepath);
}
+#define MappingNotify 34
+
+static void keyboard_event_poll_x11_events(keyboard_event *self) {
+ if(!self->x_context.display || !self->x_context.XPending || !self->x_context.XNextEvent || !self->x_context.XRefreshKeyboardMapping)
+ return;
+
+ XEvent xev;
+ while(self->x_context.XPending(self->x_context.display)) {
+ xev.type = 0;
+ self->x_context.XNextEvent(self->x_context.display, &xev);
+ if(xev.type == MappingNotify)
+ self->x_context.XRefreshKeyboardMapping(xev.data);
+ }
+}
+
void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds, key_callback callback, void *userdata) {
+ /* TODO: Add the x11 connection to the below poll? */
+ keyboard_event_poll_x11_events(self);
+
if(poll(self->event_polls, self->num_event_polls, timeout_milliseconds) <= 0)
return;
diff --git a/tools/gsr-global-hotkeys/keyboard_event.h b/tools/gsr-global-hotkeys/keyboard_event.h
index 0283afd..9904237 100644
--- a/tools/gsr-global-hotkeys/keyboard_event.h
+++ b/tools/gsr-global-hotkeys/keyboard_event.h
@@ -17,6 +17,26 @@
#define MAX_EVENT_POLLS 32
+typedef struct {
+ union {
+ int type;
+ unsigned char data[192];
+ };
+} XEvent;
+
+typedef unsigned long (*XKeycodeToKeysym_FUNC)(void *display, unsigned char keycode, int index);
+typedef int (*XPending_FUNC)(void *display);
+typedef int (*XNextEvent_FUNC)(void *display, XEvent *event_return);
+typedef int (*XRefreshKeyboardMapping_FUNC)(void* event_map);
+
+typedef struct {
+ void *display;
+ XKeycodeToKeysym_FUNC XKeycodeToKeysym;
+ XPending_FUNC XPending;
+ XNextEvent_FUNC XNextEvent;
+ XRefreshKeyboardMapping_FUNC XRefreshKeyboardMapping;
+} x11_context;
+
typedef enum {
KEYBOARD_MODKEY_LALT = 1 << 0,
KEYBOARD_MODKEY_RALT = 1 << 2,
@@ -52,6 +72,7 @@ typedef struct {
int uinput_fd;
bool stdout_failed;
keyboard_grab_type grab_type;
+ x11_context x_context;
hotplug_event hotplug_ev;
@@ -69,7 +90,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, keyboard_grab_type grab_type);
+bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool exclusive_grab, keyboard_grab_type grab_type, x11_context x_context);
void keyboard_event_deinit(keyboard_event *self);
/* If |timeout_milliseconds| is -1 then wait until an event is received */
diff --git a/tools/gsr-global-hotkeys/main.c b/tools/gsr-global-hotkeys/main.c
index 2524b45..b64d60f 100644
--- a/tools/gsr-global-hotkeys/main.c
+++ b/tools/gsr-global-hotkeys/main.c
@@ -7,6 +7,7 @@
/* POSIX */
#include <unistd.h>
+#include <dlfcn.h>
typedef struct {
uint32_t key;
@@ -45,6 +46,76 @@ static void usage(void) {
fprintf(stderr, " --virtual Grab all virtual devices only.\n");
}
+typedef void* (*XOpenDisplay_FUNC)(const char*);
+typedef int (*XErrorHandler_FUNC)(void *display, void* error_event);
+typedef XErrorHandler_FUNC (*XSetErrorHandler_FUNC)(XErrorHandler_FUNC handler);
+
+static int x_ignore_error(void *display, void *ee) {
+ (void)display;
+ (void)ee;
+ return 0;
+}
+
+static x11_context setup_x11_context(void) {
+ x11_context x_context = {0};
+ XSetErrorHandler_FUNC XSetErrorHandler = NULL;
+
+ void *x11_lib = dlopen("libX11.so.6", RTLD_LAZY);
+ if(!x11_lib) {
+ fprintf(stderr, "Warning: dlopen libX11.so.6 failed\n");
+ return x_context;
+ }
+
+ XOpenDisplay_FUNC XOpenDisplay = dlsym(x11_lib, "XOpenDisplay");
+ if(!XOpenDisplay) {
+ fprintf(stderr, "Warning: dlsym XOpenDisplay failed\n");
+ goto fail;
+ }
+
+ x_context.XKeycodeToKeysym = dlsym(x11_lib, "XKeycodeToKeysym");
+ if(!x_context.XKeycodeToKeysym) {
+ fprintf(stderr, "Warning: dlsym XKeycodeToKeysym failed\n");
+ goto fail;
+ }
+
+ x_context.XPending = dlsym(x11_lib, "XPending");
+ if(!x_context.XPending) {
+ fprintf(stderr, "Warning: dlsym XPending failed\n");
+ goto fail;
+ }
+
+ x_context.XNextEvent = dlsym(x11_lib, "XNextEvent");
+ if(!x_context.XNextEvent) {
+ fprintf(stderr, "Warning: dlsym XNextEvent failed\n");
+ goto fail;
+ }
+
+ x_context.XRefreshKeyboardMapping = dlsym(x11_lib, "XRefreshKeyboardMapping");
+ if(!x_context.XRefreshKeyboardMapping) {
+ fprintf(stderr, "Warning: dlsym XRefreshKeyboardMapping failed\n");
+ goto fail;
+ }
+
+ x_context.display = XOpenDisplay(NULL);
+ if(!x_context.display) {
+ fprintf(stderr, "Warning: XOpenDisplay failed\n");
+ goto fail;
+ }
+
+ XSetErrorHandler = dlsym(x11_lib, "XSetErrorHandler");
+ if(XSetErrorHandler)
+ XSetErrorHandler(x_ignore_error);
+ else
+ fprintf(stderr, "Warning: dlsym XSetErrorHandler failed\n");
+
+ return x_context;
+
+ fail:
+ memset(&x_context, 0, sizeof(x_context));
+ dlclose(x11_lib);
+ return x_context;
+}
+
int main(int argc, char **argv) {
keyboard_grab_type grab_type = KEYBOARD_GRAB_TYPE_ALL;
if(argc == 2) {
@@ -64,6 +135,8 @@ int main(int argc, char **argv) {
return 1;
}
+ x11_context x_context = setup_x11_context();
+
const uid_t user_id = getuid();
if(geteuid() != 0) {
if(setuid(0) == -1) {
@@ -73,7 +146,7 @@ int main(int argc, char **argv) {
}
keyboard_event keyboard_ev;
- if(!keyboard_event_init(&keyboard_ev, true, true, grab_type)) {
+ if(!keyboard_event_init(&keyboard_ev, true, true, grab_type, x_context)) {
fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n");
setuid(user_id);
return 1;