diff --git a/TODO b/TODO index 2dd81ef..e6b1d2a 100644 --- a/TODO +++ b/TODO @@ -245,4 +245,4 @@ Show the currently recorded capture in the ui, to preview if everything looks ok Show a question mark beside options. When hovering the question mark show a tooltip that explains the options. -Make led indicator work on wayland (set led with /sys/... blabla with root access). Maybe add a command in gsr-global-hotkeys for that, set_led true/false . That should loop all input devices and set their led status for the led name. +Remove all mgl::Clock usage in Overlay. We only need to get the time once per update in Overlay::handle_events. Also get time in other places outside handle_events. \ No newline at end of file diff --git a/include/LedIndicator.hpp b/include/LedIndicator.hpp index f7bb77b..a7d06e6 100644 --- a/include/LedIndicator.hpp +++ b/include/LedIndicator.hpp @@ -1,12 +1,12 @@ #pragma once -#include +#include #include namespace gsr { class LedIndicator { public: - LedIndicator(Display *dpy); + LedIndicator(); LedIndicator(const LedIndicator&) = delete; LedIndicator& operator=(const LedIndicator&) = delete; ~LedIndicator(); @@ -15,10 +15,10 @@ namespace gsr { void blink(); void update(); private: + bool run_gsr_global_hotkeys_set_leds(bool enabled); void update_led(bool new_state); private: - Display *dpy = nullptr; - Atom scroll_lock_atom = None; + pid_t gsr_global_hotkeys_pid = -1; bool led_indicator_on = false; bool led_enabled = false; bool perform_blink = false; diff --git a/meson.build b/meson.build index 8cfe385..ba74e5e 100644 --- a/meson.build +++ b/meson.build @@ -94,6 +94,7 @@ executable( 'tools/gsr-global-hotkeys/hotplug.c', 'tools/gsr-global-hotkeys/keyboard_event.c', 'tools/gsr-global-hotkeys/keys.c', + 'tools/gsr-global-hotkeys/leds.c', 'tools/gsr-global-hotkeys/main.c' ], c_args : '-fstack-protector-all', diff --git a/src/Config.cpp b/src/Config.cpp index 5aa6379..b4b051e 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -231,8 +231,8 @@ namespace gsr { {"record.record_options.overclock", &config.record_config.record_options.overclock}, {"record.record_options.record_cursor", &config.record_config.record_options.record_cursor}, {"record.record_options.restore_portal_session", &config.record_config.record_options.restore_portal_session}, - {"record.record_options.show_notifications", &config.replay_config.record_options.show_notifications}, - {"record.record_options.use_led_indicator", &config.replay_config.record_options.use_led_indicator}, + {"record.record_options.show_notifications", &config.record_config.record_options.show_notifications}, + {"record.record_options.use_led_indicator", &config.record_config.record_options.use_led_indicator}, {"record.save_video_in_game_folder", &config.record_config.save_video_in_game_folder}, {"record.save_directory", &config.record_config.save_directory}, {"record.container", &config.record_config.container}, diff --git a/src/LedIndicator.cpp b/src/LedIndicator.cpp index a72bfe6..cf18fff 100644 --- a/src/LedIndicator.cpp +++ b/src/LedIndicator.cpp @@ -1,15 +1,21 @@ #include "../include/LedIndicator.hpp" -#include +#include +#include +#include +#include namespace gsr { - LedIndicator::LedIndicator(Display *dpy) : dpy(dpy) { - scroll_lock_atom = XInternAtom(dpy, "Scroll Lock", False); - XkbSetNamedIndicator(dpy, scroll_lock_atom, True, False, False, NULL); + LedIndicator::LedIndicator() { + run_gsr_global_hotkeys_set_leds(false); } LedIndicator::~LedIndicator() { - XkbSetNamedIndicator(dpy, scroll_lock_atom, True, False, False, NULL); + run_gsr_global_hotkeys_set_leds(false); + if(gsr_global_hotkeys_pid > 0) { + int status; + waitpid(gsr_global_hotkeys_pid, &status, 0); + } } void LedIndicator::set_led(bool enabled) { @@ -22,12 +28,48 @@ namespace gsr { blink_timer.restart(); } + bool LedIndicator::run_gsr_global_hotkeys_set_leds(bool enabled) { + if(gsr_global_hotkeys_pid > 0) { + int status; + if(waitpid(gsr_global_hotkeys_pid, &status, WNOHANG) == 0) { + // Still running + return false; + } + gsr_global_hotkeys_pid = -1; + } + + const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; + const char *user_homepath = getenv("HOME"); + if(!user_homepath) + user_homepath = "/tmp"; + + gsr_global_hotkeys_pid = vfork(); + if(gsr_global_hotkeys_pid == -1) { + fprintf(stderr, "Error: LedIndicator::run_gsr_global_hotkeys_set_leds: failed to fork\n"); + return false; + } else if(gsr_global_hotkeys_pid == 0) { // Child + if(inside_flatpak) { + const char *args[] = { "flatpak-spawn", "--host", "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/kms-server-proxy", "launch-gsr-global-hotkeys", user_homepath, "--set-led", "Scroll Lock", enabled ? "on" : "off", nullptr }; + execvp(args[0], (char* const*)args); + } else { + const char *args[] = { "gsr-global-hotkeys", "--set-led", "Scroll Lock", enabled ? "on" : "off", nullptr }; + execvp(args[0], (char* const*)args); + } + + perror("gsr-global-hotkeys"); + _exit(127); + return true; + } else { // Parent + return true; + } + } + void LedIndicator::update_led(bool new_state) { if(new_state == led_indicator_on) return; - led_indicator_on = new_state; - XkbSetNamedIndicator(dpy, scroll_lock_atom, True, new_state, False, NULL); + if(run_gsr_global_hotkeys_set_leds(new_state)) + led_indicator_on = new_state; } void LedIndicator::update() { diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 799067d..e2f5060 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -522,8 +522,8 @@ namespace gsr { } } - if(x11_dpy) - led_indicator = std::make_unique(x11_dpy); + // TODO: Only do this if led indicator is enabled (at startup or when changing recording/screenshot settings to enabled it) + led_indicator = std::make_unique(); } Overlay::~Overlay() { diff --git a/src/gui/ScreenshotSettingsPage.cpp b/src/gui/ScreenshotSettingsPage.cpp index 07fd8a6..df01640 100644 --- a/src/gui/ScreenshotSettingsPage.cpp +++ b/src/gui/ScreenshotSettingsPage.cpp @@ -229,7 +229,7 @@ namespace gsr { } std::unique_ptr ScreenshotSettingsPage::create_led_indicator() { - auto checkbox = std::make_unique(&get_theme().body_font, gsr_info->system_info.display_server == DisplayServer::X11 ? "Blink scroll lock led when taking a screenshot" : "Blink scroll lock led when taking a screenshot (not supported by Wayland)"); + auto checkbox = std::make_unique(&get_theme().body_font, "Blink scroll lock led when taking a screenshot"); checkbox->set_checked(true); led_indicator_checkbox_ptr = checkbox.get(); return checkbox; diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 19bf799..4af23c4 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -832,10 +832,7 @@ namespace gsr { std::unique_ptr SettingsPage::create_led_indicator(const char *type) { char label_str[256]; - if(gsr_info->system_info.display_server == DisplayServer::X11) - snprintf(label_str, sizeof(label_str), "Show %s status with scroll lock led", type); - else - snprintf(label_str, sizeof(label_str), "Show %s status with scroll lock led (not supported by Wayland)", type); + snprintf(label_str, sizeof(label_str), "Show %s status with scroll lock led", type); auto checkbox = std::make_unique(&get_theme().body_font, label_str); checkbox->set_checked(false); diff --git a/tools/gsr-global-hotkeys/keyboard_event.c b/tools/gsr-global-hotkeys/keyboard_event.c index 2ebc72c..ac85a68 100644 --- a/tools/gsr-global-hotkeys/keyboard_event.c +++ b/tools/gsr-global-hotkeys/keyboard_event.c @@ -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); diff --git a/tools/gsr-global-hotkeys/leds.c b/tools/gsr-global-hotkeys/leds.c new file mode 100644 index 0000000..cc3550a --- /dev/null +++ b/tools/gsr-global-hotkeys/leds.c @@ -0,0 +1,139 @@ +#include "leds.h" + +/* C stdlib */ +#include +#include +#include +#include + +/* POSIX */ +#include +#include +#include +#include + +/* LINUX */ +#include + +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; + } +} diff --git a/tools/gsr-global-hotkeys/leds.h b/tools/gsr-global-hotkeys/leds.h new file mode 100644 index 0000000..7373e55 --- /dev/null +++ b/tools/gsr-global-hotkeys/leds.h @@ -0,0 +1,9 @@ +#ifndef LEDS_H +#define LEDS_H + +/* C stdlib */ +#include + +bool set_leds(const char *led_name, bool enabled); + +#endif /* LEDS_H */ diff --git a/tools/gsr-global-hotkeys/main.c b/tools/gsr-global-hotkeys/main.c index 2985879..b8c9ca4 100644 --- a/tools/gsr-global-hotkeys/main.c +++ b/tools/gsr-global-hotkeys/main.c @@ -1,19 +1,26 @@ #include "keyboard_event.h" +#include "leds.h" /* C stdlib */ #include #include #include +#include /* POSIX */ #include 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");