Support keyboard led indicator on wayland as well

This commit is contained in:
dec05eba
2025-11-08 03:28:18 +01:00
parent 0995e86e89
commit d1f8db3760
12 changed files with 282 additions and 47 deletions

View File

@@ -237,6 +237,17 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
fprintf(stderr, "Error: failed to write event data to virtual keyboard for exclusively grabbed device\n");
}
if(event.type == EV_LED) {
write(fd, &event, sizeof(event));
const struct input_event syn_event = {
.type = EV_SYN,
.code = 0,
.value = 0
};
write(fd, &syn_event, sizeof(syn_event));
}
if(!extra_data->is_possibly_non_keyboard_device)
return;
@@ -389,7 +400,7 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
if(keyboard_event_has_event_with_dev_input_fd(self, dev_input_id))
return false;
const int fd = open(dev_input_filepath, O_RDONLY);
const int fd = open(dev_input_filepath, O_RDWR);
if(fd == -1)
return false;
@@ -553,10 +564,12 @@ static int setup_virtual_keyboard_input(const char *name) {
if(is_keyboard_key(i) || is_mouse_button(i))
success &= (ioctl(fd, UI_SET_KEYBIT, i) != -1);
}
for(int i = 0; i < REL_MAX; ++i) {
success &= (ioctl(fd, UI_SET_RELBIT, i) != -1);
}
// for(int i = 0; i < LED_MAX; ++i) {
// for(int i = 0; i <= LED_CHARGING; ++i) {
// success &= (ioctl(fd, UI_SET_LEDBIT, i) != -1);
// }
@@ -635,6 +648,22 @@ bool keyboard_event_init(keyboard_event *self, bool exclusive_grab, keyboard_gra
return false;
}
self->event_polls[self->num_event_polls] = (struct pollfd) {
.fd = self->timer_fd,
.events = POLLIN,
.revents = 0
};
self->event_extra_data[self->num_event_polls] = (event_extra_data) {
.dev_input_id = -1,
.grabbed = false,
.key_states = NULL,
.key_presses_grabbed = NULL,
.num_keys_pressed = 0
};
++self->num_event_polls;
/* 0.5 seconds */
const struct itimerspec timer_value = {
.it_value = (struct timespec) {
@@ -653,22 +682,6 @@ bool keyboard_event_init(keyboard_event *self, bool exclusive_grab, keyboard_gra
return false;
}
self->event_polls[self->num_event_polls] = (struct pollfd) {
.fd = self->timer_fd,
.events = POLLIN,
.revents = 0
};
self->event_extra_data[self->num_event_polls] = (event_extra_data) {
.dev_input_id = -1,
.grabbed = false,
.key_states = NULL,
.key_presses_grabbed = NULL,
.num_keys_pressed = 0
};
++self->num_event_polls;
self->uinput_written_time_seconds = clock_get_monotonic_seconds();
if(hotplug_event_init(&self->hotplug_ev)) {
@@ -717,11 +730,6 @@ void keyboard_event_deinit(keyboard_event *self) {
self->uinput_fd = -1;
}
if(self->timer_fd > 0) {
close(self->timer_fd);
self->timer_fd = -1;
}
for(int i = 0; i < self->num_event_polls; ++i) {
if(self->event_polls[i].fd > 0) {
ioctl(self->event_polls[i].fd, EVIOCGRAB, 0);

View File

@@ -0,0 +1,139 @@
#include "leds.h"
/* C stdlib */
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stddef.h>
/* POSIX */
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
/* LINUX */
#include <linux/input.h>
static int get_max_brightness(const char *sys_class_path, const char *filename) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s/max_brightness", sys_class_path, filename);
const int fd = open(path, O_RDONLY);
if(fd == -1) {
fprintf(stderr, "Warning: get_max_brightness open error: %s\n", strerror(errno));
return false;
}
bool success = false;
int max_brightness = 0;
char buffer[32];
const ssize_t num_bytes_read = read(fd, buffer, sizeof(buffer));
if(num_bytes_read > 0) {
buffer[num_bytes_read] = '\0';
success = sscanf(buffer, "%d", &max_brightness) == 1;
}
close(fd);
return success;
}
static bool string_starts_with(const char *str, const char *sub) {
const int str_len = strlen(str);
const int sub_len = strlen(sub);
return str_len >= sub_len && memcmp(str, sub, sub_len) == 0;
}
static bool string_ends_with(const char *str, const char *sub) {
const int str_len = strlen(str);
const int sub_len = strlen(sub);
return str_len >= sub_len && memcmp(str + str_len - sub_len, sub, sub_len) == 0;
}
static int sys_class_led_path_get_event_number(const char *sys_class_path, const char *filename) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s/device", sys_class_path, filename);
DIR *dir = opendir(path);
if(!dir)
return -1;
int event_number = -1;
struct dirent *entry;
while((entry = readdir(dir)) != NULL) {
int v = -1;
if(sscanf(entry->d_name, "event%d", &v) == 1 && v >= 0) {
event_number = v;
break;
}
}
closedir(dir);
return event_number;
}
/*
We have to do this retardation instead of setting /sys/class/leds brightness since it doesn't work with /dev/uinput
and we cant loop all /dev/input devices and open and write to them either since closing a /dev/input is very slow on linux.
So we instead check which devices have the led before opening it.
*/
static bool set_device_leds(const char *led_name_path, bool enabled) {
DIR *dir = opendir("/sys/class/leds");
if(!dir)
return false;
char dev_input_filepath[1024];
struct dirent *entry;
while((entry = readdir(dir)) != NULL) {
if(entry->d_name[0] == '.')
continue;
if(!string_starts_with(entry->d_name, "input") || !string_ends_with(entry->d_name, led_name_path))
continue;
const int event_number = sys_class_led_path_get_event_number("/sys/class/leds", entry->d_name);
if(event_number == -1)
continue;
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/event%d", event_number);
const int device_fd = open(dev_input_filepath, O_WRONLY);
if(device_fd == -1)
continue;
int brightness = 0;
if(enabled) {
brightness = get_max_brightness("/sys/class/leds", entry->d_name);
if(brightness < 0)
brightness = 1;
}
struct input_event led_data = {
.type = EV_LED,
.code = LED_SCROLLL,
.value = brightness
};
write(device_fd, &led_data, sizeof(led_data));
struct input_event syn_data = {
.type = EV_SYN,
.code = 0,
.value = 0
};
write(device_fd, &syn_data, sizeof(syn_data));
close(device_fd);
}
closedir(dir);
return true;
}
bool set_leds(const char *led_name, bool enabled) {
if(strcmp(led_name, "Scroll Lock") == 0) {
return set_device_leds("::scrolllock", enabled);
} else {
fprintf(stderr, "Error: invalid led: \"%s\", expected \"Scroll Lock\"\n", led_name);
return false;
}
}

View File

@@ -0,0 +1,9 @@
#ifndef LEDS_H
#define LEDS_H
/* C stdlib */
#include <stdbool.h>
bool set_leds(const char *led_name, bool enabled);
#endif /* LEDS_H */

View File

@@ -1,19 +1,26 @@
#include "keyboard_event.h"
#include "leds.h"
/* C stdlib */
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <stdbool.h>
/* POSIX */
#include <unistd.h>
static void usage(void) {
fprintf(stderr, "usage: gsr-global-hotkeys [--all|--virtual]\n");
fprintf(stderr, "usage: gsr-global-hotkeys [--all|--virtual|--no-grab|--set-led] [Scroll Lock] [on|off]\n");
fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " --all Grab all devices.\n");
fprintf(stderr, " --virtual Grab all virtual devices only.\n");
fprintf(stderr, " --no-grab Don't grab devices, only listen to them.\n");
fprintf(stderr, " --set-led Turn device led on/off.\n");
fprintf(stderr, "EXAMPLES:\n");
fprintf(stderr, " gsr-global-hotkeys --all\n");
fprintf(stderr, " gsr-global-hotkeys --set-led \"Scroll Lock\" on\n");
fprintf(stderr, " gsr-global-hotkeys --set-led \"Scroll Lock\" off\n");
}
static bool is_gsr_global_hotkeys_already_running(void) {
@@ -38,6 +45,8 @@ int main(int argc, char **argv) {
setlocale(LC_ALL, "C"); /* Sigh... stupid C */
keyboard_grab_type grab_type = KEYBOARD_GRAB_TYPE_ALL;
const uid_t user_id = getuid();
if(argc == 2) {
const char *grab_type_arg = argv[1];
if(strcmp(grab_type_arg, "--all") == 0) {
@@ -46,11 +55,42 @@ int main(int argc, char **argv) {
grab_type = KEYBOARD_GRAB_TYPE_VIRTUAL;
} else if(strcmp(grab_type_arg, "--no-grab") == 0) {
grab_type = KEYBOARD_GRAB_TYPE_NO_GRAB;
} else if(strcmp(grab_type_arg, "--set-led") == 0) {
fprintf(stderr, "Error: missing led name and on/off argument to --set-led\n");
usage();
return 1;
} else {
fprintf(stderr, "gsr-global-hotkeys error: expected --all, --virtual or --no-grab, got %s\n", grab_type_arg);
fprintf(stderr, "gsr-global-hotkeys error: expected --all, --virtual, --no-grab or --set-led, got %s\n", grab_type_arg);
usage();
return 1;
}
} else if(argc == 4) {
/* It's not ideal to use gsr-global-hotkeys for leds, but we do that for now because it's a mess to create another binary for flatpak and distros */
const char *led_name = argv[2];
const char *led_enabled_str = argv[3];
bool led_enabled = false;
if(strcmp(led_enabled_str, "on") == 0) {
led_enabled = true;
} else if(strcmp(led_enabled_str, "off") == 0) {
led_enabled = false;
} else {
fprintf(stderr, "Error: expected \"on\" or \"off\" for --set-led option, got: \"%s\"", led_enabled_str);
usage();
return 1;
}
if(geteuid() != 0) {
if(setuid(0) == -1) {
fprintf(stderr, "gsr-global-hotkeys error: failed to change user to root, global hotkeys will not work. Make sure to set the correct capability on gsr-global-hotkeys\n");
return 1;
}
}
const bool success = set_leds(led_name, led_enabled);
setuid(user_id);
return success ? 0 : 1;
} else if(argc != 1) {
fprintf(stderr, "gsr-global-hotkeys error: expected 0 or 1 arguments, got %d argument(s)\n", argc);
usage();
@@ -62,7 +102,6 @@ int main(int argc, char **argv) {
return 1;
}
const uid_t user_id = getuid();
if(geteuid() != 0) {
if(setuid(0) == -1) {
fprintf(stderr, "gsr-global-hotkeys error: failed to change user to root, global hotkeys will not work. Make sure to set the correct capability on gsr-global-hotkeys\n");