aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2024-12-28 09:30:11 +0100
committerdec05eba <dec05eba@protonmail.com>2024-12-28 09:30:11 +0100
commit81e2fab47f4ec9423fd92b8e5fd013e83a080e2b (patch)
tree6bac9101d880e5f7843febdb3321f6b76d5fe28b /tools
parent49584e3dfc7f9c0f19c9683d80ebe17c4e4d7623 (diff)
Global hotkeys: only grab devices after all keys have been released
Diffstat (limited to 'tools')
-rw-r--r--tools/gsr-global-hotkeys/keyboard_event.c174
-rw-r--r--tools/gsr-global-hotkeys/keyboard_event.h4
2 files changed, 122 insertions, 56 deletions
diff --git a/tools/gsr-global-hotkeys/keyboard_event.c b/tools/gsr-global-hotkeys/keyboard_event.c
index fdd9e80..954fc7e 100644
--- a/tools/gsr-global-hotkeys/keyboard_event.c
+++ b/tools/gsr-global-hotkeys/keyboard_event.c
@@ -5,6 +5,7 @@
#include <string.h>
#include <errno.h>
#include <stdbool.h>
+#include <stdlib.h>
/* POSIX */
#include <fcntl.h>
@@ -22,63 +23,119 @@
#define KEY_PRESS 1
#define KEY_REPEAT 2
-/*
- 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
-*/
+#define KEY_STATES_SIZE (KEY_MAX/8 + 1)
+
+static inline int count_num_bits_set(unsigned char c) {
+ int n = 0;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ c >>= 1;
+ n += (c & 1);
+ return n;
+}
-static bool keyboard_event_has_exclusive_grab(const keyboard_event *self) {
+static inline bool keyboard_event_has_exclusive_grab(const keyboard_event *self) {
return self->uinput_fd > 0;
}
-static void keyboard_event_send_virtual_keyboard_event(keyboard_event *self, uint16_t code, int32_t value) {
- if(self->uinput_fd <= 0)
+static int keyboard_event_get_num_keys_pressed(const unsigned char *key_states) {
+ if(!key_states)
+ return 0;
+
+ int num_keys_pressed = 0;
+ for(int i = 0; i < KEY_STATES_SIZE; ++i) {
+ num_keys_pressed += count_num_bits_set(key_states[i]);
+ }
+ return num_keys_pressed;
+}
+
+static void keyboard_event_fetch_update_key_states(keyboard_event *self, event_extra_data *extra_data, int fd) {
+ fsync(fd);
+ if(!extra_data->key_states)
return;
- struct input_event event = {0};
- event.type = EV_KEY;
- event.code = code;
- event.value = value;
- write(self->uinput_fd, &event, sizeof(event));
+ if(ioctl(fd, EVIOCGKEY(KEY_STATES_SIZE), extra_data->key_states) == -1)
+ fprintf(stderr, "Warning: failed to fetch key states for device: /dev/input/event%d\n", extra_data->dev_input_id);
- event.type = EV_SYN;
- event.code = 0;
- event.value = SYN_REPORT;
- write(self->uinput_fd, &event, sizeof(event));
+ if(!keyboard_event_has_exclusive_grab(self) || extra_data->grabbed)
+ return;
+
+ extra_data->num_keys_pressed = keyboard_event_get_num_keys_pressed(extra_data->key_states);
+ if(extra_data->num_keys_pressed == 0) {
+ extra_data->grabbed = ioctl(fd, EVIOCGRAB, 1) != -1;
+ if(extra_data->grabbed)
+ fprintf(stderr, "Info: grabbed device: /dev/input/event%d\n", extra_data->dev_input_id);
+ else
+ fprintf(stderr, "Warning: failed to exclusively grab device: /dev/input/event%d. The focused application may receive keys used for global hotkeys\n", extra_data->dev_input_id);
+ }
}
-static void keyboard_event_process_input_event_data(keyboard_event *self, const event_extra_data *extra_data, int fd, key_callback callback, void *userdata) {
+static void keyboard_event_process_key_state_change(keyboard_event *self, struct input_event event, event_extra_data *extra_data, int fd) {
+ if(event.type != EV_KEY)
+ return;
+
+ if(!extra_data->key_states || event.code >= KEY_STATES_SIZE * 8)
+ return;
+
+ const unsigned int byte_index = event.code / 8;
+ const unsigned char bit_index = event.code % 8;
+ unsigned char key_byte_state = extra_data->key_states[byte_index];
+ const bool prev_key_pressed = (key_byte_state & (1 << bit_index)) != KEY_RELEASE;
+
+ if(event.value == KEY_RELEASE) {
+ key_byte_state &= ~(1 << bit_index);
+ if(prev_key_pressed)
+ --extra_data->num_keys_pressed;
+ } else {
+ key_byte_state |= (1 << bit_index);
+ if(!prev_key_pressed)
+ ++extra_data->num_keys_pressed;
+ }
+
+ extra_data->key_states[byte_index] = key_byte_state;
+
+ if(!keyboard_event_has_exclusive_grab(self) || extra_data->grabbed)
+ return;
+
+ if(extra_data->num_keys_pressed == 0) {
+ extra_data->grabbed = ioctl(fd, EVIOCGRAB, 1) != -1;
+ if(extra_data->grabbed)
+ fprintf(stderr, "Info: grabbed device: /dev/input/event%d\n", extra_data->dev_input_id);
+ else
+ fprintf(stderr, "Warning: failed to exclusively grab device: /dev/input/event%d. The focused application may receive keys used for global hotkeys\n", extra_data->dev_input_id);
+ }
+}
+
+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)) {
fprintf(stderr, "Error: failed to read input event data\n");
return;
}
+ if(event.type == EV_SYN && event.code == SYN_DROPPED) {
+ /* TODO: Don't do this on every SYN_DROPPED to prevent spamming this, instead wait until the next event or wait for timeout */
+ keyboard_event_fetch_update_key_states(self, extra_data, fd);
+ return;
+ }
+
//if(event.type == EV_KEY && event.code == KEY_A && event.value == KEY_PRESS) {
//fprintf(stderr, "fd: %d, type: %d, pressed %d, value: %d\n", fd, event.type, event.code, event.value);
//}
if(event.type == EV_KEY) {
- /*
- TODO: This is a hack! if a keyboard is grabbed while a key is being repeated then the key release will not be registered properly.
- To deal with this we ignore repeat key that is sent without without pressed first and when the key is released we send key press and key release.
- Maybe this needs to be done for all keys? Find a better solution if there is one!
- */
- const bool first_key_event = !self->has_received_key_event;
- self->has_received_key_event = true;
-
- if(first_key_event && event.value == KEY_REPEAT)
- self->repeat_key_to_ignore = (int32_t)event.code;
-
- if(self->repeat_key_to_ignore > 0 && event.code == self->repeat_key_to_ignore) {
- if(event.value == KEY_RELEASE) {
- self->repeat_key_to_ignore = 0;
- keyboard_event_send_virtual_keyboard_event(self, event.code, KEY_PRESS);
- keyboard_event_send_virtual_keyboard_event(self, event.code, KEY_RELEASE);
- }
- return;
- }
+ keyboard_event_process_key_state_change(self, event, extra_data, fd);
switch(event.code) {
case KEY_LEFTSHIFT:
@@ -130,7 +187,7 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, const
}
}
- if(keyboard_event_has_exclusive_grab(self) && extra_data->grabbed) {
+ if(extra_data->grabbed) {
/* TODO: What to do on error? */
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");
@@ -186,14 +243,8 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
const bool supports_joystick_events = key_bits[BTN_JOYSTICK/8] & (1 << (BTN_JOYSTICK % 8));
const bool supports_wheel_events = key_bits[BTN_WHEEL/8] & (1 << (BTN_WHEEL % 8));
if(supports_key_events && !supports_mouse_events && !supports_joystick_events && !supports_wheel_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);
- }
-
+ unsigned char *key_states = calloc(1, KEY_STATES_SIZE);
+ if(key_states && 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,
@@ -203,9 +254,13 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
self->event_extra_data[self->num_event_polls] = (event_extra_data) {
.dev_input_id = dev_input_id,
- .grabbed = grabbed
+ .grabbed = false,
+ .key_states = key_states,
+ .num_keys_pressed = 0
};
+ keyboard_event_fetch_update_key_states(self, &self->event_extra_data[self->num_event_polls], fd);
+
++self->num_event_polls;
return true;
} else {
@@ -249,9 +304,10 @@ static void keyboard_event_remove_event(keyboard_event *self, int index) {
ioctl(self->event_polls[index].fd, EVIOCGRAB, 0);
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->event_extra_data[j - 1] = self->event_extra_data[j];
+ for(int i = index + 1; i < self->num_event_polls; ++i) {
+ self->event_polls[i - 1] = self->event_polls[i];
+ free(self->event_extra_data[i - 1].key_states);
+ self->event_extra_data[i - 1] = self->event_extra_data[i];
}
--self->num_event_polls;
}
@@ -332,9 +388,12 @@ bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool excl
.events = 0,
.revents = 0
};
+
self->event_extra_data[self->num_event_polls] = (event_extra_data) {
.dev_input_id = -1,
- .grabbed = false
+ .grabbed = false,
+ .key_states = NULL,
+ .num_keys_pressed = 0
};
self->stdout_event_index = self->num_event_polls;
@@ -347,9 +406,12 @@ bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool excl
.events = POLLIN,
.revents = 0
};
+
self->event_extra_data[self->num_event_polls] = (event_extra_data) {
.dev_input_id = -1,
- .grabbed = false
+ .grabbed = false,
+ .key_states = NULL,
+ .num_keys_pressed = 0
};
self->hotplug_event_index = self->num_event_polls;
@@ -378,6 +440,7 @@ void keyboard_event_deinit(keyboard_event *self) {
for(int i = 0; i < self->num_event_polls; ++i) {
ioctl(self->event_polls[i].fd, EVIOCGRAB, 0);
close(self->event_polls[i].fd);
+ free(self->event_extra_data[i].key_states);
}
self->num_event_polls = 0;
@@ -408,11 +471,14 @@ void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds,
if(!(self->event_polls[i].revents & POLLIN))
continue;
- if(i == self->hotplug_event_index)
+ 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
+ } else if(i == self->stdout_event_index) {
+ /* Do nothing, this shouldn't happen anyways since we dont poll for input */
+ } else {
keyboard_event_process_input_event_data(self, &self->event_extra_data[i], self->event_polls[i].fd, callback, userdata);
+ }
}
}
diff --git a/tools/gsr-global-hotkeys/keyboard_event.h b/tools/gsr-global-hotkeys/keyboard_event.h
index 67251fc..d12b684 100644
--- a/tools/gsr-global-hotkeys/keyboard_event.h
+++ b/tools/gsr-global-hotkeys/keyboard_event.h
@@ -32,6 +32,8 @@ typedef enum {
typedef struct {
int dev_input_id;
bool grabbed;
+ unsigned char *key_states;
+ int num_keys_pressed;
} event_extra_data;
typedef struct {
@@ -43,8 +45,6 @@ typedef struct {
int hotplug_event_index;
int uinput_fd;
bool stdout_failed;
- int32_t repeat_key_to_ignore;
- bool has_received_key_event;
hotplug_event hotplug_ev;