diff options
-rw-r--r-- | TODO | 7 | ||||
-rw-r--r-- | include/Config.hpp | 1 | ||||
-rw-r--r-- | include/GlobalHotkeysJoystick.hpp | 53 | ||||
-rw-r--r-- | include/Hotplug.hpp | 33 | ||||
-rw-r--r-- | include/Overlay.hpp | 3 | ||||
-rw-r--r-- | include/gui/GlobalSettingsPage.hpp | 13 | ||||
-rw-r--r-- | meson.build | 5 | ||||
-rw-r--r-- | src/Config.cpp | 1 | ||||
-rw-r--r-- | src/GlobalHotkeysJoystick.cpp | 236 | ||||
-rw-r--r-- | src/GlobalHotkeysLinux.cpp | 4 | ||||
-rw-r--r-- | src/GlobalHotkeysX11.cpp | 3 | ||||
-rw-r--r-- | src/Hotplug.cpp | 81 | ||||
-rw-r--r-- | src/Overlay.cpp | 12 | ||||
-rw-r--r-- | src/gui/GlobalSettingsPage.cpp | 108 | ||||
-rw-r--r-- | src/main.cpp | 121 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/hotplug.c | 8 | ||||
-rw-r--r-- | tools/gsr-global-hotkeys/main.c | 23 |
17 files changed, 606 insertions, 106 deletions
@@ -112,4 +112,9 @@ Make gsr-ui flatpak systemd work nicely with non-flatpak gsr-ui. Maybe change Ex When enabling X11 global hotkey again only grab lalt, not ralt. When adding window capture only add it to recording and streaming and do the window selection when recording starts, to make it more ergonomic with hotkeys. - If hotkey for recording/streaming start is pressed on the button for start is clicked then hide the ui if it's visible and show the window selection option (cursor).
\ No newline at end of file + If hotkey for recording/streaming start is pressed on the button for start is clicked then hide the ui if it's visible and show the window selection option (cursor). + +Instead of using x11 in gsr-global-hotkeys do the keysym to keycode mapping in gsr-ui and send that to gsr-global-hotkeys. Also improve gsr-global-hotkeys setup performance + by only grabbing keys after the first button press. + +Show an error that prime run will be disabled when using desktop portal capture option. This can cause issues as the user may have selected a video codec option that isn't available on their iGPU but is available on the prime-run dGPU.
\ No newline at end of file diff --git a/include/Config.hpp b/include/Config.hpp index 6f81c1c..e7b629f 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -44,6 +44,7 @@ namespace gsr { int32_t config_file_version = 0; bool software_encoding_warning_shown = false; std::string hotkeys_enable_option = "enable_hotkeys"; + std::string joystick_hotkeys_enable_option = "disable_hotkeys"; std::string tint_color; }; diff --git a/include/GlobalHotkeysJoystick.hpp b/include/GlobalHotkeysJoystick.hpp new file mode 100644 index 0000000..367de18 --- /dev/null +++ b/include/GlobalHotkeysJoystick.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "GlobalHotkeys.hpp" +#include "Hotplug.hpp" +#include <unordered_map> +#include <thread> +#include <poll.h> +#include <mglpp/system/Clock.hpp> +#include <linux/joystick.h> + +namespace gsr { + static constexpr int max_js_poll_fd = 16; + + class GlobalHotkeysJoystick : public GlobalHotkeys { + class GlobalHotkeysJoystickHotplugDelegate; + public: + GlobalHotkeysJoystick() = default; + GlobalHotkeysJoystick(const GlobalHotkeysJoystick&) = delete; + GlobalHotkeysJoystick& operator=(const GlobalHotkeysJoystick&) = delete; + ~GlobalHotkeysJoystick() override; + + bool start(); + bool bind_action(const std::string &id, GlobalHotkeyCallback callback) override; + void poll_events() override; + private: + void read_events(); + void process_js_event(int fd, js_event &event); + bool add_device(const char *dev_input_filepath, bool print_error = true); + bool remove_device(const char *dev_input_filepath); + bool remove_poll_fd(int index); + // Returns -1 if not found + int get_poll_fd_index_by_dev_input_id(int dev_input_id) const; + private: + struct ExtraData { + int dev_input_id = 0; + }; + + std::unordered_map<std::string, GlobalHotkeyCallback> bound_actions_by_id; + std::thread read_thread; + + pollfd poll_fd[max_js_poll_fd]; + ExtraData extra_data[max_js_poll_fd]; + int num_poll_fd = 0; + int event_fd = -1; + int event_index = -1; + + mgl::Clock double_click_clock; + int num_times_clicked = 0; + bool save_replay = false; + int hotplug_poll_index = -1; + Hotplug hotplug; + }; +}
\ No newline at end of file diff --git a/include/Hotplug.hpp b/include/Hotplug.hpp new file mode 100644 index 0000000..38fe25d --- /dev/null +++ b/include/Hotplug.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <functional> + +namespace gsr { + enum class HotplugAction { + ADD, + REMOVE + }; + + using HotplugEventCallback = std::function<void(HotplugAction hotplug_action, const char *devname)>; + + class Hotplug { + public: + Hotplug() = default; + Hotplug(const Hotplug&) = delete; + Hotplug& operator=(const Hotplug&) = delete; + ~Hotplug(); + + bool start(); + int steal_fd(); + void process_event_data(int fd, const HotplugEventCallback &callback); + private: + void parse_netlink_data(const char *line, const HotplugEventCallback &callback); + private: + int fd = -1; + bool started = false; + bool event_is_add = false; + bool event_is_remove = false; + bool subsystem_is_input = false; + char event_data[1024]; + }; +}
\ No newline at end of file diff --git a/include/Overlay.hpp b/include/Overlay.hpp index 9f1a5ae..2ccfb02 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -61,6 +61,9 @@ namespace gsr { void exit(); const Config& get_config() const; + + std::function<void(const char *hotkey_option)> on_keyboard_hotkey_changed; + std::function<void(const char *hotkey_option)> on_joystick_hotkey_changed; private: void xi_setup(); void handle_xi_events(); diff --git a/include/gui/GlobalSettingsPage.hpp b/include/gui/GlobalSettingsPage.hpp index 06098f0..1066bb5 100644 --- a/include/gui/GlobalSettingsPage.hpp +++ b/include/gui/GlobalSettingsPage.hpp @@ -24,13 +24,15 @@ namespace gsr { void save(); void on_navigate_away_from_page() override; - // Called with (enable, exit_status) - std::function<void(bool, int)> on_startup_changed; - // Called with (reason) - std::function<void(const char*)> on_click_exit_program_button; + std::function<void(bool enable, int exit_status)> on_startup_changed; + std::function<void(const char *reason)> on_click_exit_program_button; + std::function<void(const char *hotkey_option)> on_keyboard_hotkey_changed; + std::function<void(const char *hotkey_option)> on_joystick_hotkey_changed; private: std::unique_ptr<Subsection> create_appearance_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_startup_subsection(ScrollablePage *parent_page); + std::unique_ptr<RadioButton> create_enable_keyboard_hotkeys_button(); + std::unique_ptr<RadioButton> create_enable_joystick_hotkeys_button(); std::unique_ptr<Subsection> create_hotkey_subsection(ScrollablePage *parent_page); std::unique_ptr<Button> create_exit_program_button(); std::unique_ptr<Button> create_go_back_to_old_ui_button(); @@ -44,6 +46,7 @@ namespace gsr { PageStack *page_stack = nullptr; RadioButton *tint_color_radio_button_ptr = nullptr; RadioButton *startup_radio_button_ptr = nullptr; - RadioButton *enable_hotkeys_radio_button_ptr = nullptr; + RadioButton *enable_keyboard_hotkeys_radio_button_ptr = nullptr; + RadioButton *enable_joystick_hotkeys_radio_button_ptr = nullptr; }; }
\ No newline at end of file diff --git a/meson.build b/meson.build index e0be1e7..1afd57b 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,8 @@ src = [ 'src/Overlay.cpp', 'src/GlobalHotkeysX11.cpp', 'src/GlobalHotkeysLinux.cpp', + 'src/GlobalHotkeysJoystick.cpp', + 'src/Hotplug.cpp', 'src/Rpc.cpp', 'src/main.cpp', ] @@ -49,6 +51,9 @@ prefix = get_option('prefix') datadir = get_option('datadir') gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui') +add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp']) +add_project_arguments('-DGSR_FLATPAK_VERSION="5.0.10"', language: ['c', 'cpp']) + executable( meson.project_name(), src, diff --git a/src/Config.cpp b/src/Config.cpp index 4ad1107..8dbe32d 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -59,6 +59,7 @@ namespace gsr { {"main.config_file_version", &config.main_config.config_file_version}, {"main.software_encoding_warning_shown", &config.main_config.software_encoding_warning_shown}, {"main.hotkeys_enable_option", &config.main_config.hotkeys_enable_option}, + {"main.joystick_hotkeys_enable_option", &config.main_config.joystick_hotkeys_enable_option}, {"main.tint_color", &config.main_config.tint_color}, {"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option}, diff --git a/src/GlobalHotkeysJoystick.cpp b/src/GlobalHotkeysJoystick.cpp new file mode 100644 index 0000000..55c6e43 --- /dev/null +++ b/src/GlobalHotkeysJoystick.cpp @@ -0,0 +1,236 @@ +#include "../include/GlobalHotkeysJoystick.hpp" +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/eventfd.h> + +namespace gsr { + static constexpr double double_click_timeout_seconds = 0.33; + + // Returns -1 on error + static int get_js_dev_input_id_from_filepath(const char *dev_input_filepath) { + if(strncmp(dev_input_filepath, "/dev/input/js", 13) != 0) + return -1; + + int dev_input_id = -1; + if(sscanf(dev_input_filepath + 13, "%d", &dev_input_id) == 1) + return dev_input_id; + return -1; + } + + GlobalHotkeysJoystick::~GlobalHotkeysJoystick() { + if(event_fd > 0) { + const uint64_t exit = 1; + write(event_fd, &exit, sizeof(exit)); + } + + if(read_thread.joinable()) + read_thread.join(); + + if(event_fd > 0) + close(event_fd); + + for(int i = 0; i < num_poll_fd; ++i) { + close(poll_fd[i].fd); + } + } + + bool GlobalHotkeysJoystick::start() { + if(num_poll_fd > 0) + return false; + + event_fd = eventfd(0, 0); + if(event_fd <= 0) + return false; + + event_index = num_poll_fd; + poll_fd[num_poll_fd] = { + event_fd, + POLLIN, + 0 + }; + extra_data[num_poll_fd] = { + -1 + }; + ++num_poll_fd; + + if(!hotplug.start()) { + fprintf(stderr, "Warning: failed to setup hotplugging\n"); + } else { + hotplug_poll_index = num_poll_fd; + poll_fd[num_poll_fd] = { + hotplug.steal_fd(), + POLLIN, + 0 + }; + extra_data[num_poll_fd] = { + -1 + }; + ++num_poll_fd; + } + + char dev_input_path[128]; + for(int i = 0; i < 8; ++i) { + snprintf(dev_input_path, sizeof(dev_input_path), "/dev/input/js%d", i); + add_device(dev_input_path, false); + } + + if(num_poll_fd == 0) + fprintf(stderr, "Info: no joysticks found, assuming they might be connected later\n"); + + read_thread = std::thread(&GlobalHotkeysJoystick::read_events, this); + return true; + } + + bool GlobalHotkeysJoystick::bind_action(const std::string &id, GlobalHotkeyCallback callback) { + return bound_actions_by_id.insert(std::make_pair(id, std::move(callback))).second; + } + + void GlobalHotkeysJoystick::poll_events() { + if(num_poll_fd == 0) + return; + + if(save_replay) { + save_replay = false; + auto it = bound_actions_by_id.find("save_replay"); + if(it != bound_actions_by_id.end()) + it->second("save_replay"); + } + } + + void GlobalHotkeysJoystick::read_events() { + js_event event; + while(poll(poll_fd, num_poll_fd, -1) > 0) { + for(int i = 0; i < num_poll_fd; ++i) { + if(poll_fd[i].revents & (POLLHUP|POLLERR|POLLNVAL)) { + if(i == event_index) + goto done; + + if(remove_poll_fd(i)) + --i; // This item was removed so we want to repeat the same index to continue to the next item + + continue; + } + + if(!(poll_fd[i].revents & POLLIN)) + continue; + + if(i == event_index) { + goto done; + } else if(i == hotplug_poll_index) { + hotplug.process_event_data(poll_fd[i].fd, [&](HotplugAction hotplug_action, const char *devname) { + char dev_input_filepath[1024]; + snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/%s", devname); + switch(hotplug_action) { + case HotplugAction::ADD: { + // Cant open the /dev/input device immediately or it fails. + // TODO: Remove this hack when a better solution is found. + usleep(50 * 1000); + add_device(dev_input_filepath); + break; + } + case HotplugAction::REMOVE: { + if(remove_device(dev_input_filepath)) + --i; // This item was removed so we want to repeat the same index to continue to the next item + break; + } + } + }); + } else { + process_js_event(poll_fd[i].fd, event); + } + } + } + + done: + ; + } + + void GlobalHotkeysJoystick::process_js_event(int fd, js_event &event) { + if(read(fd, &event, sizeof(event)) != sizeof(event)) + return; + + if((event.type & JS_EVENT_BUTTON) == 0) + return; + + if(event.number == 8 && event.value == 1) { + ++num_times_clicked; + if(num_times_clicked == 1) + double_click_clock.restart(); + else if(num_times_clicked == 2 && double_click_clock.restart() >= double_click_timeout_seconds) + num_times_clicked = 0; + + if(num_times_clicked == 2) { + save_replay = true; + num_times_clicked = 0; + } + } + } + + bool GlobalHotkeysJoystick::add_device(const char *dev_input_filepath, bool print_error) { + if(num_poll_fd >= max_js_poll_fd) { + fprintf(stderr, "Warning: failed to add joystick device %s, too many joysticks have been added\n", dev_input_filepath); + return false; + } + + const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath); + if(dev_input_id == -1) + return false; + + const int fd = open(dev_input_filepath, O_RDONLY); + if(fd <= 0) { + if(print_error) + fprintf(stderr, "Error: failed to add joystick %s, error: %s\n", dev_input_filepath, strerror(errno)); + return false; + } + + poll_fd[num_poll_fd] = { + fd, + POLLIN, + 0 + }; + + extra_data[num_poll_fd] = { + dev_input_id + }; + + ++num_poll_fd; + fprintf(stderr, "Info: added joystick: %s\n", dev_input_filepath); + return true; + } + + bool GlobalHotkeysJoystick::remove_device(const char *dev_input_filepath) { + const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath); + if(dev_input_id == -1) + return false; + + const int poll_fd_index = get_poll_fd_index_by_dev_input_id(dev_input_id); + if(poll_fd_index == -1) + return false; + + fprintf(stderr, "Info: removed joystick: %s\n", dev_input_filepath); + return remove_poll_fd(poll_fd_index); + } + + bool GlobalHotkeysJoystick::remove_poll_fd(int index) { + if(index < 0 || index >= num_poll_fd) + return false; + + close(poll_fd[index].fd); + for(int i = index + 1; i < num_poll_fd; ++i) { + poll_fd[i - 1] = poll_fd[i]; + extra_data[i - 1] = extra_data[i]; + } + --num_poll_fd; + return true; + } + + int GlobalHotkeysJoystick::get_poll_fd_index_by_dev_input_id(int dev_input_id) const { + for(int i = 0; i < num_poll_fd; ++i) { + if(dev_input_id == extra_data[i].dev_input_id) + return i; + } + return -1; + } +} diff --git a/src/GlobalHotkeysLinux.cpp b/src/GlobalHotkeysLinux.cpp index 418e317..29cf952 100644 --- a/src/GlobalHotkeysLinux.cpp +++ b/src/GlobalHotkeysLinux.cpp @@ -109,12 +109,12 @@ namespace gsr { void GlobalHotkeysLinux::poll_events() { if(process_id <= 0) { - fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, process has not been started yet. Use GlobalHotkeysLinux::start to start the process first\n"); + //fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, process has not been started yet. Use GlobalHotkeysLinux::start to start the process first\n"); return; } if(!read_file) { - fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, read file hasn't opened\n"); + //fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, read file hasn't opened\n"); return; } diff --git a/src/GlobalHotkeysX11.cpp b/src/GlobalHotkeysX11.cpp index 2943397..c8d198c 100644 --- a/src/GlobalHotkeysX11.cpp +++ b/src/GlobalHotkeysX11.cpp @@ -138,6 +138,9 @@ namespace gsr { } void GlobalHotkeysX11::poll_events() { + if(!dpy) + return; + while(XPending(dpy)) { XNextEvent(dpy, &xev); if(xev.type == KeyPress) { diff --git a/src/Hotplug.cpp b/src/Hotplug.cpp new file mode 100644 index 0000000..84ed5bb --- /dev/null +++ b/src/Hotplug.cpp @@ -0,0 +1,81 @@ +#include "../include/Hotplug.hpp" + +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <linux/types.h> +#include <linux/netlink.h> + +namespace gsr { + Hotplug::~Hotplug() { + if(fd > 0) + close(fd); + } + + bool Hotplug::start() { + if(started) + return false; + + struct sockaddr_nl nls = { + AF_NETLINK, + 0, + (unsigned int)getpid(), + (unsigned int)-1 + }; + + fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if(fd == -1) + return false; /* Not root user */ + + if(bind(fd, (const struct sockaddr*)&nls, sizeof(struct sockaddr_nl))) { + close(fd); + fd = -1; + return false; + } + + started = true; + return true; + } + + int Hotplug::steal_fd() { + const int val = fd; + fd = -1; + return val; + } + + void Hotplug::process_event_data(int fd, const HotplugEventCallback &callback) { + const int bytes_read = read(fd, event_data, sizeof(event_data)); + if(bytes_read <= 0) + return; + + /* Hotplug data ends with a newline and a null terminator */ + int data_index = 0; + while(data_index < bytes_read) { + parse_netlink_data(event_data + data_index, callback); + data_index += strlen(event_data + data_index) + 1; /* Skip null terminator as well */ + } + } + + /* TODO: This assumes SUBSYSTEM= is output before DEVNAME=, is that always true? */ + void Hotplug::parse_netlink_data(const char *line, const HotplugEventCallback &callback) { + const char *at_symbol = strchr(line, '@'); + if(at_symbol) { + event_is_add = strncmp(line, "add@", 4) == 0; + event_is_remove = strncmp(line, "remove@", 7) == 0; + subsystem_is_input = false; + } else if(event_is_add || event_is_remove) { + if(strcmp(line, "SUBSYSTEM=input") == 0) + subsystem_is_input = true; + + if(subsystem_is_input && strncmp(line, "DEVNAME=", 8) == 0) { + if(event_is_add) + callback(HotplugAction::ADD, line+8); + else if(event_is_remove) + callback(HotplugAction::REMOVE, line+8); + + event_is_add = false; + event_is_remove = false; + } + } + } +} diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 9e866fd..2f1725d 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -942,10 +942,20 @@ namespace gsr { show_notification("Failed to remove GPU Screen Recorder from system startup", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE); } }; - settings_page->on_click_exit_program_button = [&](const char *reason) { + + settings_page->on_click_exit_program_button = [this](const char *reason) { do_exit = true; exit_reason = reason; }; + + settings_page->on_keyboard_hotkey_changed = [this](const char *hotkey_option) { + on_keyboard_hotkey_changed(hotkey_option); + }; + + settings_page->on_joystick_hotkey_changed = [this](const char *hotkey_option) { + on_joystick_hotkey_changed(hotkey_option); + }; + page_stack.push(std::move(settings_page)); }; front_page_ptr->add_widget(std::move(button)); diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp index d3d440d..8a2a162 100644 --- a/src/gui/GlobalSettingsPage.cpp +++ b/src/gui/GlobalSettingsPage.cpp @@ -9,6 +9,15 @@ #include "../../include/gui/List.hpp" #include "../../include/gui/Label.hpp" #include "../../include/gui/RadioButton.hpp" +#include "../../include/gui/LineSeparator.hpp" + +#ifndef GSR_UI_VERSION +#define GSR_UI_VERSION "unknown" +#endif + +#ifndef GSR_FLATPAK_VERSION +#define GSR_FLATPAK_VERSION "unknown" +#endif namespace gsr { static const char* gpu_vendor_to_color_name(GpuVendor vendor) { @@ -21,6 +30,16 @@ namespace gsr { return "amd"; } + static const char* gpu_vendor_to_string(GpuVendor vendor) { + switch(vendor) { + case GpuVendor::UNKNOWN: return "Unknown"; + case GpuVendor::AMD: return "AMD"; + case GpuVendor::INTEL: return "Intel"; + case GpuVendor::NVIDIA: return "NVIDIA"; + } + return "unknown"; + } + GlobalSettingsPage::GlobalSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), config(config), @@ -88,29 +107,45 @@ namespace gsr { return std::make_unique<Subsection>("Startup", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); } - std::unique_ptr<Subsection> GlobalSettingsPage::create_hotkey_subsection(ScrollablePage *parent_page) { - const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; - - auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL); - enable_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get(); - enable_hotkeys_radio_button->add_item("Enable hotkeys", "enable_hotkeys"); - if(!inside_flatpak) - enable_hotkeys_radio_button->add_item("Disable hotkeys", "disable_hotkeys"); + std::unique_ptr<RadioButton> GlobalSettingsPage::create_enable_keyboard_hotkeys_button() { + auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + enable_keyboard_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get(); + enable_hotkeys_radio_button->add_item("Yes", "enable_hotkeys"); + enable_hotkeys_radio_button->add_item("No", "disable_hotkeys"); enable_hotkeys_radio_button->add_item("Only grab virtual devices (supports input remapping software)", "enable_hotkeys_virtual_devices"); enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) { - if(!on_click_exit_program_button) - return true; - - if(id == "enable_hotkeys") - on_click_exit_program_button("restart"); - else if(id == "disable_hotkeys") - on_click_exit_program_button("restart"); - else if(id == "enable_hotkeys_virtual_devices") - on_click_exit_program_button("restart"); + if(on_keyboard_hotkey_changed) + on_keyboard_hotkey_changed(id.c_str()); + return true; + }; + return enable_hotkeys_radio_button; + } + std::unique_ptr<RadioButton> GlobalSettingsPage::create_enable_joystick_hotkeys_button() { + auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + enable_joystick_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get(); + enable_hotkeys_radio_button->add_item("Yes", "enable_hotkeys"); + enable_hotkeys_radio_button->add_item("No", "disable_hotkeys"); + enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) { + if(on_joystick_hotkey_changed) + on_joystick_hotkey_changed(id.c_str()); return true; }; - return std::make_unique<Subsection>("Hotkeys", std::move(enable_hotkeys_radio_button), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + return enable_hotkeys_radio_button; + } + + std::unique_ptr<Subsection> GlobalSettingsPage::create_hotkey_subsection(ScrollablePage *parent_page) { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + List *list_ptr = list.get(); + auto subsection = std::make_unique<Subsection>("Hotkeys", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Enable keyboard hotkeys?", get_color_theme().text_color)); + list_ptr->add_widget(create_enable_keyboard_hotkeys_button()); + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Enable controller hotkeys?", get_color_theme().text_color)); + list_ptr->add_widget(create_enable_joystick_hotkeys_button()); + list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x)); + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Double-click the share button to save a replay", get_color_theme().text_color)); + return subsection; } std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() { @@ -133,11 +168,32 @@ namespace gsr { std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) { const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; - auto list = std::make_unique<List>(List::Orientation::HORIZONTAL); - list->add_widget(create_exit_program_button()); - if(inside_flatpak) - list->add_widget(create_go_back_to_old_ui_button()); - return std::make_unique<Subsection>("Application options", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + List *list_ptr = list.get(); + auto subsection = std::make_unique<Subsection>("Application options", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + + { + auto buttons_list = std::make_unique<List>(List::Orientation::HORIZONTAL); + buttons_list->add_widget(create_exit_program_button()); + if(inside_flatpak) + buttons_list->add_widget(create_go_back_to_old_ui_button()); + list_ptr->add_widget(std::move(buttons_list)); + } + list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x)); + { + char str[256]; + snprintf(str, sizeof(str), "UI version: %s", GSR_UI_VERSION); + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + + if(inside_flatpak) { + snprintf(str, sizeof(str), "Flatpak version: %s", GSR_FLATPAK_VERSION); + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + } + + snprintf(str, sizeof(str), "GPU vendor: %s", gpu_vendor_to_string(gsr_info->gpu_info.vendor)); + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + } + return subsection; } void GlobalSettingsPage::add_widgets() { @@ -169,12 +225,14 @@ namespace gsr { const int exit_status = exec_program_on_host_get_stdout(args, stdout_str); startup_radio_button_ptr->set_selected_item(exit_status == 0 ? "start_on_system_startup" : "dont_start_on_system_startup", false, false); - enable_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false); + enable_keyboard_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false); + enable_joystick_hotkeys_radio_button_ptr->set_selected_item(config.main_config.joystick_hotkeys_enable_option, false, false); } void GlobalSettingsPage::save() { config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id(); - config.main_config.hotkeys_enable_option = enable_hotkeys_radio_button_ptr->get_selected_id(); + config.main_config.hotkeys_enable_option = enable_keyboard_hotkeys_radio_button_ptr->get_selected_id(); + config.main_config.joystick_hotkeys_enable_option = enable_joystick_hotkeys_radio_button_ptr->get_selected_id(); save_config(config); } }
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index dd507fb..efb3583 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ #include "../include/GsrInfo.hpp" #include "../include/Overlay.hpp" -#include "../include/GlobalHotkeysX11.hpp" #include "../include/GlobalHotkeysLinux.hpp" +#include "../include/GlobalHotkeysJoystick.hpp" #include "../include/gui/Utils.hpp" #include "../include/Process.hpp" #include "../include/Rpc.hpp" @@ -41,62 +41,6 @@ static void disable_prime_run() { unsetenv("DRI_PRIME"); } -static std::unique_ptr<gsr::GlobalHotkeysX11> register_x11_hotkeys(gsr::Overlay *overlay) { - auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysX11>(); - const bool show_hotkey_registered = global_hotkeys->bind_key_press({ XK_z, Mod1Mask }, "show_hide", [overlay](const std::string &id) { - fprintf(stderr, "pressed %s\n", id.c_str()); - overlay->toggle_show(); - }); - - const bool record_hotkey_registered = global_hotkeys->bind_key_press({ XK_F9, Mod1Mask }, "record", [overlay](const std::string &id) { - fprintf(stderr, "pressed %s\n", id.c_str()); - overlay->toggle_record(); - }); - - const bool pause_hotkey_registered = global_hotkeys->bind_key_press({ XK_F7, Mod1Mask }, "pause", [overlay](const std::string &id) { - fprintf(stderr, "pressed %s\n", id.c_str()); - overlay->toggle_pause(); - }); - - const bool stream_hotkey_registered = global_hotkeys->bind_key_press({ XK_F8, Mod1Mask }, "stream", [overlay](const std::string &id) { - fprintf(stderr, "pressed %s\n", id.c_str()); - overlay->toggle_stream(); - }); - - const bool replay_hotkey_registered = global_hotkeys->bind_key_press({ XK_F10, ShiftMask | Mod1Mask }, "replay_start", [overlay](const std::string &id) { - fprintf(stderr, "pressed %s\n", id.c_str()); - overlay->toggle_replay(); - }); - - const bool replay_save_hotkey_registered = global_hotkeys->bind_key_press({ XK_F10, Mod1Mask }, "replay_save", [overlay](const std::string &id) { - fprintf(stderr, "pressed %s\n", id.c_str()); - overlay->save_replay(); - }); - - if(!show_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+z for showing the overlay because the hotkey is registered by another program\n"); - - if(!record_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+f9 for recording because the hotkey is registered by another program\n"); - - if(!pause_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+f7 for pausing because the hotkey is registered by another program\n"); - - if(!stream_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+f8 for streaming because the hotkey is registered by another program\n"); - - if(!replay_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+shift+f10 for starting replay because the hotkey is registered by another program\n"); - - if(!replay_save_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+f10 for saving replay because the hotkey is registered by another program\n"); - - if(!show_hotkey_registered || !record_hotkey_registered || !pause_hotkey_registered || !stream_hotkey_registered || !replay_hotkey_registered || !replay_save_hotkey_registered) - return nullptr; - - return global_hotkeys; -} - static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Overlay *overlay, gsr::GlobalHotkeysLinux::GrabType grab_type) { auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysLinux>(grab_type); if(!global_hotkeys->start()) @@ -135,6 +79,19 @@ static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Over return global_hotkeys; } +static std::unique_ptr<gsr::GlobalHotkeysJoystick> register_joystick_hotkeys(gsr::Overlay *overlay) { + auto global_hotkeys_js = std::make_unique<gsr::GlobalHotkeysJoystick>(); + if(!global_hotkeys_js->start()) + fprintf(stderr, "Warning: failed to start joystick hotkeys\n"); + + global_hotkeys_js->bind_action("save_replay", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay(); + }); + + return global_hotkeys_js; +} + static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) { rpc->add_handler("show_ui", [overlay](const std::string &name) { fprintf(stderr, "rpc command executed: %s\n", name.c_str()); @@ -292,9 +249,6 @@ int main(int argc, char **argv) { return 1; } - // Cant get window texture when prime-run is used - disable_prime_run(); - // Stop nvidia driver from buffering frames setenv("__GL_MaxFramesAllowed", "1", true); // If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context, @@ -307,11 +261,6 @@ int main(int argc, char **argv) { 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"); - exit(1); - } - gsr::GsrInfo gsr_info; // TODO: Show the error in ui gsr::GsrInfoExitStatus gsr_info_exit_status = gsr::get_gpu_screen_recorder_info(&gsr_info); @@ -321,8 +270,17 @@ int main(int argc, char **argv) { } 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"); + if(display_server == gsr::DisplayServer::WAYLAND) { + fprintf(stderr, "Warning: Wayland doesn't support this program properly and XWayland is required. Things may not work as expected. Use X11 if you experience issues.\n"); + } else { + // Cant get window texture when prime-run is used + disable_prime_run(); + } + + if(mgl_init() != 0) { + fprintf(stderr, "Error: failed to initialize mgl. Failed to either connect to the X11 server or setup opengl\n"); + exit(1); + } gsr::SupportedCaptureOptions capture_options = gsr::get_supported_capture_options(gsr_info); @@ -368,6 +326,28 @@ int main(int argc, char **argv) { else if(overlay->get_config().main_config.hotkeys_enable_option == "enable_hotkeys_virtual_devices") global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::VIRTUAL); + overlay->on_keyboard_hotkey_changed = [&](const char *hotkey_option) { + global_hotkeys.reset(); + if(strcmp(hotkey_option, "enable_hotkeys") == 0) + global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::ALL); + else if(strcmp(hotkey_option, "enable_hotkeys_virtual_devices") == 0) + global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::VIRTUAL); + else if(strcmp(hotkey_option, "disable_hotkeys") == 0) + global_hotkeys.reset(); + }; + + std::unique_ptr<gsr::GlobalHotkeysJoystick> global_hotkeys_js = nullptr; + if(overlay->get_config().main_config.joystick_hotkeys_enable_option == "enable_hotkeys") + global_hotkeys_js = register_joystick_hotkeys(overlay.get()); + + overlay->on_joystick_hotkey_changed = [&](const char *hotkey_option) { + global_hotkeys_js.reset(); + if(strcmp(hotkey_option, "enable_hotkeys") == 0) + global_hotkeys_js = register_joystick_hotkeys(overlay.get()); + else if(strcmp(hotkey_option, "disable_hotkeys") == 0) + global_hotkeys_js.reset(); + }; + // TODO: Add hotkeys in Overlay when using x11 global hotkeys. The hotkeys in Overlay should duplicate each key that is used for x11 global hotkeys. std::string exit_reason; @@ -382,6 +362,9 @@ int main(int argc, char **argv) { if(global_hotkeys) global_hotkeys->poll_events(); + if(global_hotkeys_js) + global_hotkeys_js->poll_events(); + overlay->handle_events(global_hotkeys.get()); if(!overlay->draw()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -391,8 +374,8 @@ int main(int argc, char **argv) { fprintf(stderr, "Info: shutting down!\n"); rpc.reset(); - if(global_hotkeys) - global_hotkeys.reset(); + global_hotkeys.reset(); + global_hotkeys_js.reset(); overlay.reset(); mgl_deinit(); diff --git a/tools/gsr-global-hotkeys/hotplug.c b/tools/gsr-global-hotkeys/hotplug.c index ba3ef9c..5ea2978 100644 --- a/tools/gsr-global-hotkeys/hotplug.c +++ b/tools/gsr-global-hotkeys/hotplug.c @@ -22,7 +22,7 @@ bool hotplug_event_init(hotplug_event *self) { const int fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if(fd == -1) - return false; /* Not root user */ + return false; if(bind(fd, (void*)&nls, sizeof(struct sockaddr_nl))) { close(fd); @@ -56,19 +56,21 @@ static void hotplug_event_parse_netlink_data(hotplug_event *self, const char *li if(strcmp(line, "SUBSYSTEM=input") == 0) self->subsystem_is_input = true; - if(self->subsystem_is_input && strncmp(line, "DEVNAME=", 8) == 0) + if(self->subsystem_is_input && strncmp(line, "DEVNAME=", 8) == 0) { callback(line+8, userdata); + self->event_is_add = false; + } } } /* 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 */ + int data_index = 0; 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/main.c b/tools/gsr-global-hotkeys/main.c index b64d60f..d05ca5f 100644 --- a/tools/gsr-global-hotkeys/main.c +++ b/tools/gsr-global-hotkeys/main.c @@ -116,6 +116,24 @@ static x11_context setup_x11_context(void) { return x_context; } +static bool is_gsr_global_hotkeys_already_running(void) { + FILE *f = fopen("/proc/bus/input/devices", "rb"); + if(!f) + return false; + + bool virtual_keyboard_running = false; + char line[1024]; + while(fgets(line, sizeof(line), f)) { + if(strstr(line, "gsr-ui virtual keyboard")) { + virtual_keyboard_running = true; + break; + } + } + + fclose(f); + return virtual_keyboard_running; +} + int main(int argc, char **argv) { keyboard_grab_type grab_type = KEYBOARD_GRAB_TYPE_ALL; if(argc == 2) { @@ -135,6 +153,11 @@ int main(int argc, char **argv) { return 1; } + if(is_gsr_global_hotkeys_already_running()) { + fprintf(stderr, "Error: gsr-global-hotkeys is already running\n"); + return 1; + } + x11_context x_context = setup_x11_context(); const uid_t user_id = getuid(); |