aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2025-01-23 21:23:19 +0100
committerdec05eba <dec05eba@protonmail.com>2025-01-23 21:25:45 +0100
commit1d9d4d6398e989d0628cb4dab0273293b79b4816 (patch)
treed5c4bd8c6f27d84156177831f1c0e6beb0de762a
parent47ada4d79844d9a98d9689d0de0c92864e0fc372 (diff)
Make hotkeys reconfigurable, faster hotkey startup time, fix some keyboard locale issues
-rw-r--r--README.md13
-rw-r--r--TODO14
m---------depends/mglpp0
-rw-r--r--include/Config.hpp19
-rw-r--r--include/GlobalHotkeys.hpp15
-rw-r--r--include/GlobalHotkeysLinux.hpp6
-rw-r--r--include/Overlay.hpp17
-rw-r--r--include/gui/GlobalSettingsPage.hpp46
-rw-r--r--meson.build3
-rw-r--r--project.conf2
-rw-r--r--src/Config.cpp34
-rw-r--r--src/GlobalHotkeysLinux.cpp140
-rw-r--r--src/GlobalHotkeysX11.cpp39
-rw-r--r--src/Overlay.cpp177
-rw-r--r--src/WindowUtils.cpp100
-rw-r--r--src/gui/GlobalSettingsPage.cpp434
-rw-r--r--src/main.cpp96
-rw-r--r--tools/gsr-global-hotkeys/keyboard_event.c406
-rw-r--r--tools/gsr-global-hotkeys/keyboard_event.h76
-rw-r--r--tools/gsr-global-hotkeys/main.c115
20 files changed, 1186 insertions, 566 deletions
diff --git a/README.md b/README.md
index 1b0651a..3d4a30c 100644
--- a/README.md
+++ b/README.md
@@ -56,18 +56,7 @@ 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. This is an issue with Hyprland. 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. I believe this is an issue with Hyprland.
-* Global hotkeys work incorrectly with different keyboard layouts in the flatpak version when using wayland (keys are not in their physical location). This seems to be a flatpak limitation.
# FAQ
## I get an error when trying to start the gpu-screen-recorder-ui.service systemd service
-If you have previously used the flatpak version of GPU Screen Recorder with the new UI then non-flatpak version of the systemd service will conflict with that.
-Run these commands to first remove the flatpak version of the systemd service:
-```
-systemctl stop --user gpu-screen-recorder-ui
-rm ~/.local/share/systemd/user/gpu-screen-recorder-ui.service
-systemctl --user daemon-reload
-```
-and then start and enable the non-flatpak systemd service:
-```
-systemctl enable --now --user gpu-screen-recorder-ui`
-``` \ No newline at end of file
+If you have previously used the flatpak version of GPU Screen Recorder with the new UI then non-flatpak version of the systemd service will conflict with that. Run `gsr-ui` to fix that.
diff --git a/TODO b/TODO
index 3020e6d..8b9cad0 100644
--- a/TODO
+++ b/TODO
@@ -16,8 +16,6 @@ Add support for window selection in capture.
Add option to record the focused monitor. This works on wayland too when using kms capture since we can get cursor position without root and see which monitor (crtc) the cursor is on. Or use create_window_get_center_position.
-Make hotkeys configurable.
-
Filechooser should have the option to select list view, search bar and common folders/mounted drives on the left side for quick navigation. Also a button to create a new directory.
Restart replay on system start if monitor resolution changes.
@@ -76,8 +74,6 @@ Run `systemctl status --user gpu-screen-recorder` when starting recording and gi
Add option to select which gpu to record with, or list all monitors and automatically use the gpu associated with the monitor. Do the same in gtk application.
-Test global hotkeys with azerty instead of qwerty.
-
Dont allow autostart of replay if capture option is window recording (when window recording is added).
Use global shortcuts desktop portal protocol on wayland when available.
@@ -102,11 +98,6 @@ Show warning if another instance of gpu screen recorder is already running when
Keyboard leds get turned off when stopping gsr-global-hotkeys (for example numlock). The numlock key has to be pressed twice again to make it look correct to match its state.
-Implement hotkey changing in global settings by getting mgl key events. During this time gsr-global-hotkey would either need to be paused or add code in the callback handler for the existing hotkeys since they are grabbing hotkeys.
- This can only be done after gsr-global-hotkeys properly handle different keyboard layouts to make sure mgl keys match gsr-global-hotkey keys.
-
-Re-enable hotkey disable option for flatpak.
-
Make gsr-ui flatpak systemd work nicely with non-flatpak gsr-ui. Maybe change ExecStart to do flatpak run ... || gsr-ui, but make it run as a shell command first with /bin/sh -c "".
When enabling X11 global hotkey again only grab lalt, not ralt.
@@ -114,7 +105,6 @@ 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).
-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.
-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
+Is it possible to configure hotkey and the new hotkey to get triggered immediately? \ No newline at end of file
diff --git a/depends/mglpp b/depends/mglpp
-Subproject e776c85d1986ee9cd7629ec5f04e89f79230378
+Subproject cd258b5f2c6b3c9e41870703e1063a79e3a6abb
diff --git a/include/Config.hpp b/include/Config.hpp
index e7b629f..0d311b2 100644
--- a/include/Config.hpp
+++ b/include/Config.hpp
@@ -6,12 +6,14 @@
#include <vector>
#include <optional>
+#define GSR_CONFIG_FILE_VERSION 1
+
namespace gsr {
struct SupportedCaptureOptions;
struct ConfigHotkey {
- int64_t keysym = 0;
- uint32_t modifiers = 0;
+ int64_t key = 0; // Mgl key
+ uint32_t modifiers = 0; // HotkeyModifier
bool operator==(const ConfigHotkey &other) const;
bool operator!=(const ConfigHotkey &other) const;
@@ -41,11 +43,12 @@ namespace gsr {
};
struct MainConfig {
- int32_t config_file_version = 0;
+ int32_t config_file_version = GSR_CONFIG_FILE_VERSION;
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;
+ ConfigHotkey show_hide_hotkey;
};
struct YoutubeStreamConfig {
@@ -69,7 +72,7 @@ namespace gsr {
YoutubeStreamConfig youtube;
TwitchStreamConfig twitch;
CustomStreamConfig custom;
- ConfigHotkey start_stop_recording_hotkey;
+ ConfigHotkey start_stop_hotkey;
};
struct RecordConfig {
@@ -79,8 +82,8 @@ namespace gsr {
bool show_video_saved_notifications = true;
std::string save_directory;
std::string container = "mp4";
- ConfigHotkey start_stop_recording_hotkey;
- ConfigHotkey pause_unpause_recording_hotkey;
+ ConfigHotkey start_stop_hotkey;
+ ConfigHotkey pause_unpause_hotkey;
};
struct ReplayConfig {
@@ -93,8 +96,8 @@ namespace gsr {
std::string save_directory;
std::string container = "mp4";
int32_t replay_time = 60;
- ConfigHotkey start_stop_recording_hotkey;
- ConfigHotkey save_recording_hotkey;
+ ConfigHotkey start_stop_hotkey;
+ ConfigHotkey save_hotkey;
};
struct Config {
diff --git a/include/GlobalHotkeys.hpp b/include/GlobalHotkeys.hpp
index 27fca07..2927fa7 100644
--- a/include/GlobalHotkeys.hpp
+++ b/include/GlobalHotkeys.hpp
@@ -9,9 +9,20 @@ namespace mgl {
}
namespace gsr {
+ enum HotkeyModifier : uint32_t {
+ HOTKEY_MOD_LSHIFT = 1 << 0,
+ HOTKEY_MOD_RSHIFT = 1 << 1,
+ HOTKEY_MOD_LCTRL = 1 << 2,
+ HOTKEY_MOD_RCTRL = 1 << 3,
+ HOTKEY_MOD_LALT = 1 << 4,
+ HOTKEY_MOD_RALT = 1 << 5,
+ HOTKEY_MOD_LSUPER = 1 << 6,
+ HOTKEY_MOD_RSUPER = 1 << 7
+ };
+
struct Hotkey {
- uint64_t key = 0;
- uint32_t modifiers = 0;
+ uint32_t key = 0; // X11 keysym
+ uint32_t modifiers = 0; // HotkeyModifier
};
using GlobalHotkeyCallback = std::function<void(const std::string &id)>;
diff --git a/include/GlobalHotkeysLinux.hpp b/include/GlobalHotkeysLinux.hpp
index addb849..c9428de 100644
--- a/include/GlobalHotkeysLinux.hpp
+++ b/include/GlobalHotkeysLinux.hpp
@@ -18,11 +18,13 @@ namespace gsr {
~GlobalHotkeysLinux() override;
bool start();
- bool bind_action(const std::string &id, GlobalHotkeyCallback callback) override;
+ bool bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) override;
+ void unbind_all_keys() override;
void poll_events() override;
private:
pid_t process_id = 0;
- int pipes[2];
+ int read_pipes[2];
+ int write_pipes[2];
FILE *read_file = nullptr;
std::unordered_map<std::string, GlobalHotkeyCallback> bound_actions_by_id;
GrabType grab_type;
diff --git a/include/Overlay.hpp b/include/Overlay.hpp
index 2ccfb02..a4e75dc 100644
--- a/include/Overlay.hpp
+++ b/include/Overlay.hpp
@@ -6,6 +6,8 @@
#include "Config.hpp"
#include "window_texture.h"
#include "WindowUtils.hpp"
+#include "GlobalHotkeysLinux.hpp"
+#include "GlobalHotkeysJoystick.hpp"
#include <mglpp/window/Window.hpp>
#include <mglpp/window/Event.hpp>
@@ -42,8 +44,7 @@ namespace gsr {
Overlay& operator=(const Overlay&) = delete;
~Overlay();
- void handle_events(gsr::GlobalHotkeys *global_hotkeys);
- void on_event(mgl::Event &event);
+ void handle_events();
// Returns false if not visible
bool draw();
@@ -62,9 +63,12 @@ namespace gsr {
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;
+ void unbind_all_keyboard_hotkeys();
+ void rebind_all_keyboard_hotkeys();
private:
+ void handle_keyboard_mapping_event();
+ void on_event(mgl::Event &event);
+
void xi_setup();
void handle_xi_events();
void process_key_bindings(mgl::Event &event);
@@ -176,5 +180,10 @@ namespace gsr {
mgl::Clock show_overlay_clock;
double show_overlay_timeout_seconds = 0.0;
+
+ std::unique_ptr<GlobalHotkeys> global_hotkeys = nullptr;
+ std::unique_ptr<GlobalHotkeysJoystick> global_hotkeys_js = nullptr;
+ Display *x11_mapping_display = nullptr;
+ XEvent x11_mapping_xev;
};
} \ No newline at end of file
diff --git a/include/gui/GlobalSettingsPage.hpp b/include/gui/GlobalSettingsPage.hpp
index 1066bb5..92454c8 100644
--- a/include/gui/GlobalSettingsPage.hpp
+++ b/include/gui/GlobalSettingsPage.hpp
@@ -5,18 +5,32 @@
#include "../Config.hpp"
#include <functional>
+#include <mglpp/window/Event.hpp>
namespace gsr {
+ class Overlay;
class GsrPage;
class PageStack;
class ScrollablePage;
class Subsection;
class RadioButton;
class Button;
+ class List;
+ class CustomRendererWidget;
+
+ enum ConfigureHotkeyType {
+ NONE,
+ REPLAY_START_STOP,
+ REPLAY_SAVE,
+ RECORD_START_STOP,
+ RECORD_PAUSE_UNPAUSE,
+ STREAM_START_STOP,
+ SHOW_HIDE
+ };
class GlobalSettingsPage : public StaticPage {
public:
- GlobalSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack);
+ GlobalSettingsPage(Overlay *overlay, const GsrInfo *gsr_info, Config &config, PageStack *page_stack);
GlobalSettingsPage(const GlobalSettingsPage&) = delete;
GlobalSettingsPage& operator=(const GlobalSettingsPage&) = delete;
@@ -24,21 +38,39 @@ namespace gsr {
void save();
void on_navigate_away_from_page() override;
+ bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override;
+
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:
+ void load_hotkeys();
+
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<List> create_show_hide_hotkey_options();
+ std::unique_ptr<List> create_replay_hotkey_options();
+ std::unique_ptr<List> create_record_hotkey_options();
+ std::unique_ptr<List> create_stream_hotkey_options();
+ std::unique_ptr<List> create_hotkey_control_buttons();
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();
std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page);
+ std::unique_ptr<Subsection> create_application_info_subsection(ScrollablePage *parent_page);
void add_widgets();
+
+ Button* configure_hotkey_get_button_by_active_type();
+ ConfigHotkey* configure_hotkey_get_config_by_active_type();
+ void for_each_config_hotkey(std::function<void(ConfigHotkey *config_hotkey)> callback);
+ void configure_hotkey_start(ConfigureHotkeyType hotkey_type);
+ void configure_hotkey_cancel();
+ void configure_hotkey_stop_and_save();
private:
+ Overlay *overlay = nullptr;
Config &config;
const GsrInfo *gsr_info = nullptr;
@@ -48,5 +80,17 @@ namespace gsr {
RadioButton *startup_radio_button_ptr = nullptr;
RadioButton *enable_keyboard_hotkeys_radio_button_ptr = nullptr;
RadioButton *enable_joystick_hotkeys_radio_button_ptr = nullptr;
+
+ Button *turn_replay_on_off_button_ptr = nullptr;
+ Button *save_replay_button_ptr = nullptr;
+ Button *start_stop_recording_button_ptr = nullptr;
+ Button *pause_unpause_recording_button_ptr = nullptr;
+ Button *start_stop_streaming_button_ptr = nullptr;
+ Button *show_hide_button_ptr = nullptr;
+
+ ConfigHotkey configure_config_hotkey;
+ ConfigureHotkeyType configure_hotkey_type = ConfigureHotkeyType::NONE;
+
+ CustomRendererWidget *hotkey_overlay_ptr = nullptr;
};
} \ No newline at end of file
diff --git a/meson.build b/meson.build
index 1afd57b..a16933d 100644
--- a/meson.build
+++ b/meson.build
@@ -1,4 +1,4 @@
-project('gsr-ui', ['c', 'cpp'], version : '1.0.8', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
+project('gsr-ui', ['c', 'cpp'], version : '1.1.0', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
if get_option('buildtype') == 'debug'
add_project_arguments('-g3', language : ['c', 'cpp'])
@@ -60,6 +60,7 @@ executable(
install : true,
dependencies : [
mglpp_dep,
+ dependency('threads'),
dependency('xcomposite'),
dependency('xfixes'),
dependency('xi'),
diff --git a/project.conf b/project.conf
index bc7c781..509409a 100644
--- a/project.conf
+++ b/project.conf
@@ -1,7 +1,7 @@
[package]
name = "gsr-ui"
type = "executable"
-version = "1.0.8"
+version = "1.1.0"
platforms = ["posix"]
[lang.cpp]
diff --git a/src/Config.cpp b/src/Config.cpp
index 8dbe32d..16b371f 100644
--- a/src/Config.cpp
+++ b/src/Config.cpp
@@ -1,21 +1,21 @@
#include "../include/Config.hpp"
#include "../include/Utils.hpp"
#include "../include/GsrInfo.hpp"
+#include "../include/GlobalHotkeys.hpp"
#include <variant>
#include <limits.h>
#include <inttypes.h>
#include <libgen.h>
#include <iostream>
+#include <mglpp/window/Keyboard.hpp>
#define FORMAT_I32 "%" PRIi32
#define FORMAT_I64 "%" PRIi64
#define FORMAT_U32 "%" PRIu32
-#define CONFIG_FILE_VERSION 1
-
namespace gsr {
bool ConfigHotkey::operator==(const ConfigHotkey &other) const {
- return keysym == other.keysym && modifiers == other.modifiers;
+ return key == other.key && modifiers == other.modifiers;
}
bool ConfigHotkey::operator!=(const ConfigHotkey &other) const {
@@ -25,19 +25,26 @@ namespace gsr {
Config::Config(const SupportedCaptureOptions &capture_options) {
const std::string default_save_directory = get_videos_dir();
+ streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT};
streaming_config.record_options.video_quality = "custom";
streaming_config.record_options.audio_tracks.push_back("default_output");
streaming_config.record_options.video_bitrate = 15000;
+ record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT};
+ record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
record_config.save_directory = default_save_directory;
record_config.record_options.audio_tracks.push_back("default_output");
record_config.record_options.video_bitrate = 45000;
+ replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT};
+ replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT};
replay_config.record_options.video_quality = "custom";
replay_config.save_directory = default_save_directory;
replay_config.record_options.audio_tracks.push_back("default_output");
replay_config.record_options.video_bitrate = 45000;
+ main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
+
if(!capture_options.monitors.empty()) {
streaming_config.record_options.record_area_option = capture_options.monitors.front().name;
record_config.record_options.record_area_option = capture_options.monitors.front().name;
@@ -61,6 +68,7 @@ namespace gsr {
{"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},
+ {"main.show_hide_hotkey", &config.main_config.show_hide_hotkey},
{"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option},
{"streaming.record_options.record_area_width", &config.streaming_config.record_options.record_area_width},
@@ -89,7 +97,7 @@ namespace gsr {
{"streaming.twitch.key", &config.streaming_config.twitch.stream_key},
{"streaming.custom.url", &config.streaming_config.custom.url},
{"streaming.custom.container", &config.streaming_config.custom.container},
- {"streaming.start_stop_recording_hotkey", &config.streaming_config.start_stop_recording_hotkey},
+ {"streaming.start_stop_hotkey", &config.streaming_config.start_stop_hotkey},
{"record.record_options.record_area_option", &config.record_config.record_options.record_area_option},
{"record.record_options.record_area_width", &config.record_config.record_options.record_area_width},
@@ -116,8 +124,8 @@ namespace gsr {
{"record.show_video_saved_notifications", &config.record_config.show_video_saved_notifications},
{"record.save_directory", &config.record_config.save_directory},
{"record.container", &config.record_config.container},
- {"record.start_stop_recording_hotkey", &config.record_config.start_stop_recording_hotkey},
- {"record.pause_unpause_recording_hotkey", &config.record_config.pause_unpause_recording_hotkey},
+ {"record.start_stop_hotkey", &config.record_config.start_stop_hotkey},
+ {"record.pause_unpause_hotkey", &config.record_config.pause_unpause_hotkey},
{"replay.record_options.record_area_option", &config.replay_config.record_options.record_area_option},
{"replay.record_options.record_area_width", &config.replay_config.record_options.record_area_width},
@@ -147,8 +155,8 @@ namespace gsr {
{"replay.save_directory", &config.replay_config.save_directory},
{"replay.container", &config.replay_config.container},
{"replay.time", &config.replay_config.replay_time},
- {"replay.start_stop_recording_hotkey", &config.replay_config.start_stop_recording_hotkey},
- {"replay.save_recording_hotkey", &config.replay_config.save_recording_hotkey}
+ {"replay.start_stop_hotkey", &config.replay_config.start_stop_hotkey},
+ {"replay.save_hotkey", &config.replay_config.save_hotkey}
};
}
@@ -229,9 +237,9 @@ namespace gsr {
} else if(std::holds_alternative<ConfigHotkey*>(it->second)) {
std::string value_str(key_value->value);
ConfigHotkey *config_hotkey = std::get<ConfigHotkey*>(it->second);
- if(sscanf(value_str.c_str(), FORMAT_I64 " " FORMAT_U32, &config_hotkey->keysym, &config_hotkey->modifiers) != 2) {
+ if(sscanf(value_str.c_str(), FORMAT_I64 " " FORMAT_U32, &config_hotkey->key, &config_hotkey->modifiers) != 2) {
fprintf(stderr, "Warning: Invalid config option value for %.*s\n", (int)key_value->key.size(), key_value->key.data());
- config_hotkey->keysym = 0;
+ config_hotkey->key = 0;
config_hotkey->modifiers = 0;
}
} else if(std::holds_alternative<std::vector<std::string>*>(it->second)) {
@@ -242,7 +250,7 @@ namespace gsr {
return true;
});
- if(config->main_config.config_file_version != CONFIG_FILE_VERSION) {
+ if(config->main_config.config_file_version != GSR_CONFIG_FILE_VERSION) {
fprintf(stderr, "Info: the config file is outdated, resetting it\n");
config = std::nullopt;
}
@@ -251,7 +259,7 @@ namespace gsr {
}
void save_config(Config &config) {
- config.main_config.config_file_version = CONFIG_FILE_VERSION;
+ config.main_config.config_file_version = GSR_CONFIG_FILE_VERSION;
const std::string config_path = get_config_dir() + "/config_ui";
@@ -280,7 +288,7 @@ namespace gsr {
fprintf(file, "%.*s " FORMAT_I32 "\n", (int)it.first.size(), it.first.data(), *std::get<int32_t*>(it.second));
} else if(std::holds_alternative<ConfigHotkey*>(it.second)) {
const ConfigHotkey *config_hotkey = std::get<ConfigHotkey*>(it.second);
- fprintf(file, "%.*s " FORMAT_I64 " " FORMAT_U32 "\n", (int)it.first.size(), it.first.data(), config_hotkey->keysym, config_hotkey->modifiers);
+ fprintf(file, "%.*s " FORMAT_I64 " " FORMAT_U32 "\n", (int)it.first.size(), it.first.data(), config_hotkey->key, config_hotkey->modifiers);
} else if(std::holds_alternative<std::vector<std::string>*>(it.second)) {
std::vector<std::string> *array = std::get<std::vector<std::string>*>(it.second);
for(const std::string &value : *array) {
diff --git a/src/GlobalHotkeysLinux.cpp b/src/GlobalHotkeysLinux.cpp
index 29cf952..c779777 100644
--- a/src/GlobalHotkeysLinux.cpp
+++ b/src/GlobalHotkeysLinux.cpp
@@ -5,6 +5,12 @@
#include <limits.h>
#include <string.h>
+extern "C" {
+#include <mgl/mgl.h>
+}
+#include <X11/Xlib.h>
+#include <linux/input-event-codes.h>
+
#define PIPE_READ 0
#define PIPE_WRITE 1
@@ -17,16 +23,55 @@ namespace gsr {
return "--all";
}
+ static inline uint8_t x11_keycode_to_linux_keycode(uint8_t code) {
+ return code - 8;
+ }
+
+ static std::vector<uint8_t> modifiers_to_linux_keys(uint32_t modifiers) {
+ std::vector<uint8_t> result;
+ if(modifiers & HOTKEY_MOD_LSHIFT)
+ result.push_back(KEY_LEFTSHIFT);
+ if(modifiers & HOTKEY_MOD_RSHIFT)
+ result.push_back(KEY_RIGHTSHIFT);
+ if(modifiers & HOTKEY_MOD_LCTRL)
+ result.push_back(KEY_LEFTCTRL);
+ if(modifiers & HOTKEY_MOD_RCTRL)
+ result.push_back(KEY_RIGHTCTRL);
+ if(modifiers & HOTKEY_MOD_LALT)
+ result.push_back(KEY_LEFTALT);
+ if(modifiers & HOTKEY_MOD_RALT)
+ result.push_back(KEY_RIGHTALT);
+ if(modifiers & HOTKEY_MOD_LSUPER)
+ result.push_back(KEY_LEFTMETA);
+ if(modifiers & HOTKEY_MOD_RSUPER)
+ result.push_back(KEY_RIGHTMETA);
+ return result;
+ }
+
+ static std::string linux_keys_to_command_string(const uint8_t *keys, size_t size) {
+ std::string result;
+ for(size_t i = 0; i < size; ++i) {
+ if(!result.empty())
+ result += "+";
+ result += std::to_string(keys[i]);
+ }
+ return result;
+ }
+
GlobalHotkeysLinux::GlobalHotkeysLinux(GrabType grab_type) : grab_type(grab_type) {
for(int i = 0; i < 2; ++i) {
- pipes[i] = -1;
+ read_pipes[i] = -1;
+ write_pipes[i] = -1;
}
}
GlobalHotkeysLinux::~GlobalHotkeysLinux() {
for(int i = 0; i < 2; ++i) {
- if(pipes[i] > 0)
- close(pipes[i]);
+ if(read_pipes[i] > 0)
+ close(read_pipes[i]);
+
+ if(write_pipes[i] > 0)
+ close(write_pipes[i]);
}
if(read_file)
@@ -58,21 +103,36 @@ namespace gsr {
if(process_id > 0)
return false;
- if(pipe(pipes) == -1)
+ if(pipe(read_pipes) == -1)
return false;
+ if(pipe(write_pipes) == -1) {
+ for(int i = 0; i < 2; ++i) {
+ close(read_pipes[i]);
+ read_pipes[i] = -1;
+ }
+ return false;
+ }
+
const pid_t pid = vfork();
if(pid == -1) {
perror("Failed to vfork");
for(int i = 0; i < 2; ++i) {
- close(pipes[i]);
- pipes[i] = -1;
+ close(read_pipes[i]);
+ close(write_pipes[i]);
+ read_pipes[i] = -1;
+ write_pipes[i] = -1;
}
return false;
} else if(pid == 0) { /* child */
- dup2(pipes[PIPE_WRITE], STDOUT_FILENO);
+ dup2(read_pipes[PIPE_WRITE], STDOUT_FILENO);
for(int i = 0; i < 2; ++i) {
- close(pipes[i]);
+ close(read_pipes[i]);
+ }
+
+ dup2(write_pipes[PIPE_READ], STDIN_FILENO);
+ for(int i = 0; i < 2; ++i) {
+ close(write_pipes[i]);
}
if(inside_flatpak) {
@@ -87,24 +147,70 @@ namespace gsr {
_exit(127);
} else { /* parent */
process_id = pid;
- close(pipes[PIPE_WRITE]);
- pipes[PIPE_WRITE] = -1;
- const int fdl = fcntl(pipes[PIPE_READ], F_GETFL);
- fcntl(pipes[PIPE_READ], F_SETFL, fdl | O_NONBLOCK);
+ close(read_pipes[PIPE_WRITE]);
+ read_pipes[PIPE_WRITE] = -1;
- read_file = fdopen(pipes[PIPE_READ], "r");
+ close(write_pipes[PIPE_READ]);
+ write_pipes[PIPE_READ] = -1;
+
+ fcntl(read_pipes[PIPE_READ], F_SETFL, fcntl(read_pipes[PIPE_READ], F_GETFL) | O_NONBLOCK);
+ read_file = fdopen(read_pipes[PIPE_READ], "r");
if(read_file)
- pipes[PIPE_READ] = -1;
+ read_pipes[PIPE_READ] = -1;
else
- fprintf(stderr, "fdopen failed, error: %s\n", strerror(errno));
+ fprintf(stderr, "fdopen failed for read, error: %s\n", strerror(errno));
+ }
+
+ return true;
+ }
+
+ bool GlobalHotkeysLinux::bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) {
+ if(bound_actions_by_id.find(id) != bound_actions_by_id.end())
+ return false;
+
+ if(id.find(' ') != std::string::npos || id.find('\n') != std::string::npos) {
+ fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: id \"%s\" contains either space or newline\n", id.c_str());
+ return false;
+ }
+
+ if(hotkey.key == 0) {
+ //fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: hotkey requires a key\n");
+ return false;
}
+ if(hotkey.modifiers == 0) {
+ //fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: hotkey requires a modifier\n");
+ return false;
+ }
+
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+ const uint8_t keycode = x11_keycode_to_linux_keycode(XKeysymToKeycode(display, hotkey.key));
+ const std::vector<uint8_t> modifiers = modifiers_to_linux_keys(hotkey.modifiers);
+ const std::string modifiers_command = linux_keys_to_command_string(modifiers.data(), modifiers.size());
+
+ char command[256];
+ const int command_size = snprintf(command, sizeof(command), "bind %s %d+%s\n", id.c_str(), (int)keycode, modifiers_command.c_str());
+ if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) {
+ fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno));
+ return false;
+ }
+
+ bound_actions_by_id[id] = std::move(callback);
return true;
}
- bool GlobalHotkeysLinux::bind_action(const std::string &id, GlobalHotkeyCallback callback) {
- return bound_actions_by_id.insert(std::make_pair(id, std::move(callback))).second;
+ void GlobalHotkeysLinux::unbind_all_keys() {
+ if(bound_actions_by_id.empty())
+ return;
+
+ char command[32];
+ const int command_size = snprintf(command, sizeof(command), "unbind_all\n");
+ if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) {
+ fprintf(stderr, "Error: GlobalHotkeysLinux::unbind_all_keys: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno));
+ }
+ bound_actions_by_id.clear();
}
void GlobalHotkeysLinux::poll_events() {
diff --git a/src/GlobalHotkeysX11.cpp b/src/GlobalHotkeysX11.cpp
index c8d198c..9af2607 100644
--- a/src/GlobalHotkeysX11.cpp
+++ b/src/GlobalHotkeysX11.cpp
@@ -50,6 +50,27 @@ namespace gsr {
return mask;
}
+ static uint32_t modifiers_to_x11_modifiers(uint32_t modifiers) {
+ uint32_t result = 0;
+ if(modifiers & HOTKEY_MOD_LSHIFT)
+ result |= ShiftMask;
+ if(modifiers & HOTKEY_MOD_RSHIFT)
+ result |= ShiftMask;
+ if(modifiers & HOTKEY_MOD_LCTRL)
+ result |= ControlMask;
+ if(modifiers & HOTKEY_MOD_RCTRL)
+ result |= ControlMask;
+ if(modifiers & HOTKEY_MOD_LALT)
+ result |= Mod1Mask;
+ if(modifiers & HOTKEY_MOD_RALT)
+ result |= Mod5Mask;
+ if(modifiers & HOTKEY_MOD_LSUPER)
+ result |= Mod4Mask;
+ if(modifiers & HOTKEY_MOD_RSUPER)
+ result |= Mod4Mask;
+ return result;
+ }
+
GlobalHotkeysX11::GlobalHotkeysX11() {
dpy = XOpenDisplay(NULL);
if(!dpy)
@@ -74,16 +95,17 @@ namespace gsr {
x_failed = false;
XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error);
+ const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(hotkey.modifiers);
unsigned int numlock_mask = x11_get_numlock_mask(dpy);
unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask };
for(int i = 0; i < 4; ++i) {
- XGrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy), False, GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy), False, GrabModeAsync, GrabModeAsync);
}
XSync(dpy, False);
if(x_failed) {
for(int i = 0; i < 4; ++i) {
- XUngrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy));
+ XUngrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy));
}
XSync(dpy, False);
XSetErrorHandler(prev_xerror);
@@ -106,10 +128,11 @@ namespace gsr {
x_failed = false;
XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error);
+ const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(it->second.hotkey.modifiers);
unsigned int numlock_mask = x11_get_numlock_mask(dpy);
unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask };
for(int i = 0; i < 4; ++i) {
- XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), it->second.hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy));
+ XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy));
}
XSync(dpy, False);
@@ -127,8 +150,9 @@ namespace gsr {
unsigned int numlock_mask = x11_get_numlock_mask(dpy);
unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask };
for(auto it = bound_keys_by_id.begin(); it != bound_keys_by_id.end();) {
+ const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(it->second.hotkey.modifiers);
for(int i = 0; i < 4; ++i) {
- XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), it->second.hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy));
+ XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy));
}
}
bound_keys_by_id.clear();
@@ -145,7 +169,7 @@ namespace gsr {
XNextEvent(dpy, &xev);
if(xev.type == KeyPress) {
const KeySym key_sym = XLookupKeysym(&xev.xkey, 0);
- call_hotkey_callback({ key_sym, xev.xkey.state });
+ call_hotkey_callback({ (uint32_t)key_sym, xev.xkey.state });
}
}
}
@@ -157,7 +181,7 @@ namespace gsr {
// Note: not all keys are mapped in mgl_key_to_key_sym. If more hotkeys are added or changed then add the key mapping there
const KeySym key_sym = mgl_key_to_key_sym(event.key.code);
const uint32_t modifiers = mgl_key_modifiers_to_x11_modifier_mask(event.key);
- return !call_hotkey_callback(Hotkey{key_sym, modifiers});
+ return !call_hotkey_callback(Hotkey{(uint32_t)key_sym, modifiers});
}
static unsigned int key_state_without_locks(unsigned int key_state) {
@@ -165,8 +189,9 @@ namespace gsr {
}
bool GlobalHotkeysX11::call_hotkey_callback(Hotkey hotkey) const {
+ const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(hotkey.modifiers);
for(const auto &[key, val] : bound_keys_by_id) {
- if(val.hotkey.key == hotkey.key && key_state_without_locks(val.hotkey.modifiers) == key_state_without_locks(hotkey.modifiers)) {
+ if(val.hotkey.key == hotkey.key && key_state_without_locks(modifiers_to_x11_modifiers(val.hotkey.modifiers)) == key_state_without_locks(modifiers_x11)) {
val.callback(key);
return true;
}
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
index 2f1725d..a8490ab 100644
--- a/src/Overlay.cpp
+++ b/src/Overlay.cpp
@@ -336,6 +336,79 @@ namespace gsr {
return true;
}
+ static Hotkey config_hotkey_to_hotkey(ConfigHotkey config_hotkey) {
+ return {
+ (uint32_t)mgl::Keyboard::key_to_x11_keysym((mgl::Keyboard::Key)config_hotkey.key),
+ config_hotkey.modifiers
+ };
+ }
+
+ static void bind_linux_hotkeys(GlobalHotkeysLinux *global_hotkeys, Overlay *overlay) {
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().main_config.show_hide_hotkey),
+ "show_hide", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->toggle_show();
+ });
+
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().record_config.start_stop_hotkey),
+ "record", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->toggle_record();
+ });
+
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().record_config.pause_unpause_hotkey),
+ "pause", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->toggle_pause();
+ });
+
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().streaming_config.start_stop_hotkey),
+ "stream", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->toggle_stream();
+ });
+
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().replay_config.start_stop_hotkey),
+ "replay_start", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->toggle_replay();
+ });
+
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().replay_config.save_hotkey),
+ "replay_save", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->save_replay();
+ });
+ }
+
+ static std::unique_ptr<GlobalHotkeysLinux> register_linux_hotkeys(Overlay *overlay, GlobalHotkeysLinux::GrabType grab_type) {
+ auto global_hotkeys = std::make_unique<GlobalHotkeysLinux>(grab_type);
+ if(!global_hotkeys->start())
+ fprintf(stderr, "error: failed to start global hotkeys\n");
+
+ bind_linux_hotkeys(global_hotkeys.get(), overlay);
+ return global_hotkeys;
+ }
+
+ static std::unique_ptr<GlobalHotkeysJoystick> register_joystick_hotkeys(Overlay *overlay) {
+ auto global_hotkeys_js = std::make_unique<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;
+ }
+
Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs) :
resources_path(std::move(resources_path)),
gsr_info(std::move(gsr_info)),
@@ -366,6 +439,20 @@ namespace gsr {
if(config.replay_config.turn_on_replay_automatically_mode == "turn_on_at_system_startup")
on_press_start_replay(true);
+
+ if(config.main_config.hotkeys_enable_option == "enable_hotkeys")
+ global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
+ else if(config.main_config.hotkeys_enable_option == "enable_hotkeys_virtual_devices")
+ global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::VIRTUAL);
+
+ if(config.main_config.joystick_hotkeys_enable_option == "enable_hotkeys")
+ global_hotkeys_js = register_joystick_hotkeys(this);
+
+ x11_mapping_display = XOpenDisplay(nullptr);
+ if(x11_mapping_display)
+ XKeysymToKeycode(x11_mapping_display, XK_F1); // If we dont call we will never get a MappingNotify
+ else
+ fprintf(stderr, "Warning: XOpenDisplay failed to mapping notify\n");
}
Overlay::~Overlay() {
@@ -393,6 +480,9 @@ namespace gsr {
close_gpu_screen_recorder_output();
deinit_color_theme();
+
+ if(x11_mapping_display)
+ XCloseDisplay(x11_mapping_display);
}
void Overlay::xi_setup() {
@@ -535,7 +625,32 @@ namespace gsr {
}
}
- void Overlay::handle_events(gsr::GlobalHotkeys *global_hotkeys) {
+ void Overlay::handle_keyboard_mapping_event() {
+ if(!x11_mapping_display)
+ return;
+
+ bool mapping_updated = false;
+ while(XPending(x11_mapping_display)) {
+ XNextEvent(x11_mapping_display, &x11_mapping_xev);
+ if(x11_mapping_xev.type == MappingNotify) {
+ XRefreshKeyboardMapping(&x11_mapping_xev.xmapping);
+ mapping_updated = true;
+ }
+ }
+
+ if(mapping_updated)
+ rebind_all_keyboard_hotkeys();
+ }
+
+ void Overlay::handle_events() {
+ if(global_hotkeys)
+ global_hotkeys->poll_events();
+
+ if(global_hotkeys_js)
+ global_hotkeys_js->poll_events();
+
+ handle_keyboard_mapping_event();
+
if(!visible || !window)
return;
@@ -742,29 +857,30 @@ namespace gsr {
const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display);
const Monitor *focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value);
- if(is_wlroots) {
+
+ // Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
+ // If the focused window is a wayland application then don't use override redirect and instead create
+ // a fullscreen window for the ui.
+ const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots;
+
+ if(prevent_game_minimizing) {
window_pos = focused_monitor->position;
window_size = focused_monitor->size;
} else {
window_pos = {0, 0};
- window_size = {32, 32};
+ window_size = focused_monitor->size / 2;
}
- // Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
- // If the focused window is a wayland application then don't use override redirect and instead create
- // a fullscreen window for the ui.
- const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window;
-
mgl::Window::CreateParams window_create_params;
window_create_params.size = window_size;
- if(is_wlroots || prevent_game_minimizing) {
+ if(prevent_game_minimizing) {
window_create_params.min_size = window_size;
window_create_params.max_size = window_size;
}
- window_create_params.position = window_pos;
+ window_create_params.position = focused_monitor->position + focused_monitor->size / 2 - window_size / 2;
window_create_params.hidden = prevent_game_minimizing;
window_create_params.override_redirect = prevent_game_minimizing;
- window_create_params.background_color = bg_color;
+ window_create_params.background_color = mgl::Color(0, 0, 0, 0);
window_create_params.support_alpha = true;
window_create_params.hide_decorations = true;
// MGL_WINDOW_TYPE_DIALOG is needed for kde plasma wayland in some cases, otherwise the window will pop up on another activity
@@ -786,6 +902,7 @@ namespace gsr {
data = 1;
XChangeProperty(display, window->get_system_handle(), XInternAtom(display, "GAMESCOPE_EXTERNAL_OVERLAY", False), XA_CARDINAL, 32, PropModeReplace, &data, 1);
+ const auto original_window_size = window_size;
window_pos = focused_monitor->position;
window_size = focused_monitor->size;
if(!init_theme(resources_path)) {
@@ -795,11 +912,11 @@ namespace gsr {
}
get_theme().set_window_size(window_size);
- if(is_wlroots || prevent_game_minimizing) {
+ if(prevent_game_minimizing) {
window->set_size(window_size);
window->set_size_limits(window_size, window_size);
- window->set_position(window_pos);
}
+ window->set_position(focused_monitor->position + focused_monitor->size / 2 - original_window_size / 2);
mgl_window *win = window->internal_window();
win->cursor_position.x = cursor_position.x - window_pos.x;
@@ -927,7 +1044,8 @@ namespace gsr {
button->set_bg_hover_color(mgl::Color(0, 0, 0, 255));
button->set_icon(&get_theme().settings_small_texture);
button->on_click = [&]() {
- auto settings_page = std::make_unique<GlobalSettingsPage>(&gsr_info, config, &page_stack);
+ auto settings_page = std::make_unique<GlobalSettingsPage>(this, &gsr_info, config, &page_stack);
+
settings_page->on_startup_changed = [&](bool enable, int exit_status) {
if(exit_status == 0)
return;
@@ -949,11 +1067,21 @@ namespace gsr {
};
settings_page->on_keyboard_hotkey_changed = [this](const char *hotkey_option) {
- on_keyboard_hotkey_changed(hotkey_option);
+ global_hotkeys.reset();
+ if(strcmp(hotkey_option, "enable_hotkeys") == 0)
+ global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
+ else if(strcmp(hotkey_option, "enable_hotkeys_virtual_devices") == 0)
+ global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::VIRTUAL);
+ else if(strcmp(hotkey_option, "disable_hotkeys") == 0)
+ global_hotkeys.reset();
};
settings_page->on_joystick_hotkey_changed = [this](const char *hotkey_option) {
- on_joystick_hotkey_changed(hotkey_option);
+ global_hotkeys_js.reset();
+ if(strcmp(hotkey_option, "enable_hotkeys") == 0)
+ global_hotkeys_js = register_joystick_hotkeys(this);
+ else if(strcmp(hotkey_option, "disable_hotkeys") == 0)
+ global_hotkeys_js.reset();
};
page_stack.push(std::move(settings_page));
@@ -990,7 +1118,8 @@ namespace gsr {
// The focused application can be an xwayland application but the cursor can hover over a wayland application.
// This is even the case when hovering over the titlebar of the xwayland application.
- if(prevent_game_minimizing)
+ const bool fake_cursor = is_wlroots ? x11_cursor_window != None : prevent_game_minimizing;
+ if(fake_cursor)
xi_setup();
//window->set_fullscreen(true);
@@ -1047,6 +1176,8 @@ namespace gsr {
if(paused)
update_ui_recording_paused();
+ // Wayland compositors have retarded fullscreen animations that we cant disable in a proper way
+ // without messing up window position.
show_overlay_timeout_seconds = prevent_game_minimizing ? 0.0 : 0.15;
show_overlay_clock.restart();
draw();
@@ -1233,6 +1364,18 @@ namespace gsr {
return config;
}
+ void Overlay::unbind_all_keyboard_hotkeys() {
+ if(global_hotkeys)
+ global_hotkeys->unbind_all_keys();
+ }
+
+ void Overlay::rebind_all_keyboard_hotkeys() {
+ unbind_all_keyboard_hotkeys();
+ // TODO: Check if type is GlobalHotkeysLinux
+ if(global_hotkeys)
+ bind_linux_hotkeys(static_cast<GlobalHotkeysLinux*>(global_hotkeys.get()), this);
+ }
+
void Overlay::update_notification_process_status() {
if(notification_process <= 0)
return;
diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp
index ea9dfbd..d8e3508 100644
--- a/src/WindowUtils.cpp
+++ b/src/WindowUtils.cpp
@@ -98,15 +98,16 @@ namespace gsr {
return found_window;
}
- static Window get_window_at_cursor_position(Display *dpy) {
+ mgl::vec2i get_cursor_position(Display *dpy, Window *window) {
Window root_window = None;
- Window window = None;
+ *window = None;
int dummy_i;
unsigned int dummy_u;
- XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &dummy_i, &dummy_i, &dummy_u);
+ mgl::vec2i root_pos;
+ XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
if(window)
- window = window_get_target_window_child(dpy, window);
- return window;
+ *window = window_get_target_window_child(dpy, *window);
+ return root_pos;
}
Window get_focused_window(Display *dpy, WindowCaptureType cap_type) {
@@ -136,7 +137,7 @@ namespace gsr {
return focused_window;
}
- focused_window = get_window_at_cursor_position(dpy);
+ get_cursor_position(dpy, &focused_window);
if(focused_window && focused_window != DefaultRootWindow(dpy))
return focused_window;
@@ -235,35 +236,6 @@ namespace gsr {
return result;
}
- mgl::vec2i get_cursor_position(Display *dpy, Window *window) {
- Window root_window = None;
- *window = None;
- int dummy_i;
- unsigned int dummy_u;
- mgl::vec2i root_pos;
- XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
-
- // This dumb shit is done to satisfy gnome wayland. Only set |window| if a valid x11 window is focused
- if(window) {
- XWindowAttributes attr;
- if(XGetWindowAttributes(dpy, *window, &attr) && attr.override_redirect)
- *window = None;
-
- int revert_to = 0;
- Window input_focus_window = None;
- if(XGetInputFocus(dpy, &input_focus_window, &revert_to)) {
- if(input_focus_window) {
- if(XGetWindowAttributes(dpy, input_focus_window, &attr) && attr.override_redirect)
- *window = None;
- } else {
- *window = None;
- }
- }
- }
-
- return root_pos;
- }
-
typedef struct {
unsigned long flags;
unsigned long functions;
@@ -334,7 +306,7 @@ namespace gsr {
poll_fd.fd = x_fd;
poll_fd.events = POLLIN;
poll_fd.revents = 0;
- const int fds_ready = poll(&poll_fd, 1, 1000);
+ const int fds_ready = poll(&poll_fd, 1, 200);
if(fds_ready == 0) {
fprintf(stderr, "Error: timed out waiting for ConfigureNotify after XCreateWindow\n");
break;
@@ -342,15 +314,18 @@ namespace gsr {
continue;
}
- XNextEvent(display, &xev);
- if(xev.type == ConfigureNotify && xev.xconfigure.window == window) {
- got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0;
- position.x = xev.xconfigure.x + xev.xconfigure.width / 2;
- position.y = xev.xconfigure.y + xev.xconfigure.height / 2;
- break;
+ while(XPending(display)) {
+ XNextEvent(display, &xev);
+ if(xev.type == ConfigureNotify && xev.xconfigure.window == window) {
+ got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0;
+ position.x = xev.xconfigure.x + xev.xconfigure.width / 2;
+ position.y = xev.xconfigure.y + xev.xconfigure.height / 2;
+ goto done;
+ }
}
}
+ done:
XDestroyWindow(display, window);
XFlush(display);
@@ -395,7 +370,7 @@ namespace gsr {
poll_fd.fd = x_fd;
poll_fd.events = POLLIN;
poll_fd.revents = 0;
- const int fds_ready = poll(&poll_fd, 1, 1000);
+ const int fds_ready = poll(&poll_fd, 1, 200);
if(fds_ready == 0) {
fprintf(stderr, "Error: timed out waiting for MapNotify/ConfigureNotify after XCreateWindow\n");
break;
@@ -403,27 +378,30 @@ namespace gsr {
continue;
}
- XNextEvent(display, &xev);
- if(xev.type == MapNotify && xev.xmap.window == window) {
- int x = 0;
- int y = 0;
- Window w = None;
- XTranslateCoordinates(display, window, DefaultRootWindow(display), 0, 0, &x, &y, &w);
-
- got_data = x > 0 && y > 0;
- position.x = x + size / 2;
- position.y = y + size / 2;
- if(got_data)
- break;
- } else if(xev.type == ConfigureNotify && xev.xconfigure.window == window) {
- got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0;
- position.x = xev.xconfigure.x + xev.xconfigure.width / 2;
- position.y = xev.xconfigure.y + xev.xconfigure.height / 2;
- if(got_data)
- break;
+ while(XPending(display)) {
+ XNextEvent(display, &xev);
+ if(xev.type == MapNotify && xev.xmap.window == window) {
+ int x = 0;
+ int y = 0;
+ Window w = None;
+ XTranslateCoordinates(display, window, DefaultRootWindow(display), 0, 0, &x, &y, &w);
+
+ got_data = x > 0 && y > 0;
+ position.x = x + size / 2;
+ position.y = y + size / 2;
+ if(got_data)
+ goto done;
+ } else if(xev.type == ConfigureNotify && xev.xconfigure.window == window) {
+ got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0;
+ position.x = xev.xconfigure.x + xev.xconfigure.width / 2;
+ position.y = xev.xconfigure.y + xev.xconfigure.height / 2;
+ if(got_data)
+ goto done;
+ }
}
}
+ done:
XDestroyWindow(display, window);
XFlush(display);
diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp
index 8a2a162..d4a87c2 100644
--- a/src/gui/GlobalSettingsPage.cpp
+++ b/src/gui/GlobalSettingsPage.cpp
@@ -1,5 +1,7 @@
#include "../../include/gui/GlobalSettingsPage.hpp"
+#include "../../include/Overlay.hpp"
+#include "../../include/GlobalHotkeys.hpp"
#include "../../include/Theme.hpp"
#include "../../include/Process.hpp"
#include "../../include/gui/GsrPage.hpp"
@@ -10,6 +12,16 @@
#include "../../include/gui/Label.hpp"
#include "../../include/gui/RadioButton.hpp"
#include "../../include/gui/LineSeparator.hpp"
+#include "../../include/gui/CustomRendererWidget.hpp"
+
+#include <assert.h>
+#include <X11/Xlib.h>
+extern "C" {
+#include <mgl/mgl.h>
+}
+#include <mglpp/window/Window.hpp>
+#include <mglpp/graphics/Rectangle.hpp>
+#include <mglpp/graphics/Text.hpp>
#ifndef GSR_UI_VERSION
#define GSR_UI_VERSION "unknown"
@@ -40,8 +52,64 @@ namespace gsr {
return "unknown";
}
- GlobalSettingsPage::GlobalSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
+ static uint32_t mgl_modifier_to_hotkey_modifier(mgl::Keyboard::Key modifier_key) {
+ switch(modifier_key) {
+ case mgl::Keyboard::LControl: return HOTKEY_MOD_LCTRL;
+ case mgl::Keyboard::LShift: return HOTKEY_MOD_LSHIFT;
+ case mgl::Keyboard::LAlt: return HOTKEY_MOD_LALT;
+ case mgl::Keyboard::LSystem: return HOTKEY_MOD_LSUPER;
+ case mgl::Keyboard::RControl: return HOTKEY_MOD_RCTRL;
+ case mgl::Keyboard::RShift: return HOTKEY_MOD_RSHIFT;
+ case mgl::Keyboard::RAlt: return HOTKEY_MOD_RALT;
+ case mgl::Keyboard::RSystem: return HOTKEY_MOD_RSUPER;
+ default: return 0;
+ }
+ return 0;
+ }
+
+ static std::vector<mgl::Keyboard::Key> hotkey_modifiers_to_mgl_keys(uint32_t modifiers) {
+ std::vector<mgl::Keyboard::Key> result;
+ if(modifiers & HOTKEY_MOD_LCTRL)
+ result.push_back(mgl::Keyboard::LControl);
+ if(modifiers & HOTKEY_MOD_LSHIFT)
+ result.push_back(mgl::Keyboard::LShift);
+ if(modifiers & HOTKEY_MOD_LALT)
+ result.push_back(mgl::Keyboard::LAlt);
+ if(modifiers & HOTKEY_MOD_LSUPER)
+ result.push_back(mgl::Keyboard::LSystem);
+ if(modifiers & HOTKEY_MOD_RCTRL)
+ result.push_back(mgl::Keyboard::RControl);
+ if(modifiers & HOTKEY_MOD_RSHIFT)
+ result.push_back(mgl::Keyboard::RShift);
+ if(modifiers & HOTKEY_MOD_RALT)
+ result.push_back(mgl::Keyboard::RAlt);
+ if(modifiers & HOTKEY_MOD_RSUPER)
+ result.push_back(mgl::Keyboard::RSystem);
+ return result;
+ }
+
+ static std::string config_hotkey_to_string(ConfigHotkey config_hotkey) {
+ std::string result;
+
+ const std::vector<mgl::Keyboard::Key> modifier_keys = hotkey_modifiers_to_mgl_keys(config_hotkey.modifiers);
+ for(const mgl::Keyboard::Key modifier_key : modifier_keys) {
+ if(!result.empty())
+ result += " + ";
+ result += mgl::Keyboard::key_to_string(modifier_key);
+ }
+
+ if(config_hotkey.key != 0) {
+ if(!result.empty())
+ result += " + ";
+ result += mgl::Keyboard::key_to_string((mgl::Keyboard::Key)config_hotkey.key);
+ }
+
+ return result;
+ }
+
+ GlobalSettingsPage::GlobalSettingsPage(Overlay *overlay, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
+ overlay(overlay),
config(config),
gsr_info(gsr_info),
page_stack(page_stack)
@@ -57,6 +125,45 @@ namespace gsr {
add_widgets();
load();
+
+ auto hotkey_overlay = std::make_unique<CustomRendererWidget>(get_size());
+ hotkey_overlay->draw_handler = [this](mgl::Window &window, mgl::vec2f, mgl::vec2f) {
+ Button *configure_hotkey_button = configure_hotkey_get_button_by_active_type();
+ if(!configure_hotkey_button)
+ return;
+
+ mgl::Text title_text("Press a key combination to use for the hotkey \"Start/stop recording\":", get_theme().title_font);
+ mgl::Text hotkey_text(configure_hotkey_button->get_text(), get_theme().top_bar_font);
+ mgl::Text description_text("The hotkey has to contain one or more of these keys: Alt, Ctrl, Shift and Super. Press Esc to cancel.", get_theme().body_font);
+ const float text_max_width = std::max(title_text.get_bounds().size.x, std::max(hotkey_text.get_bounds().size.x, description_text.get_bounds().size.x));
+
+ const float padding_horizontal = int(get_theme().window_height * 0.01f);
+ const float padding_vertical = int(get_theme().window_height * 0.01f);
+
+ const mgl::vec2f bg_size = mgl::vec2f(text_max_width + padding_horizontal*2.0f, get_theme().window_height * 0.1f).floor();
+ mgl::Rectangle bg_rect(mgl::vec2f(get_theme().window_width*0.5f - bg_size.x*0.5f, get_theme().window_height*0.5f - bg_size.y*0.5f).floor(), bg_size);
+ bg_rect.set_color(get_color_theme().page_bg_color);
+ window.draw(bg_rect);
+
+ const mgl::vec2f tint_size = mgl::vec2f(bg_size.x, 0.004f * get_theme().window_height).floor();
+ mgl::Rectangle tint_rect(bg_rect.get_position() - mgl::vec2f(0.0f, tint_size.y), tint_size);
+ tint_rect.set_color(get_color_theme().tint_color);
+ window.draw(tint_rect);
+
+ title_text.set_position(mgl::vec2f(bg_rect.get_position() + mgl::vec2f(bg_rect.get_size().x*0.5f - title_text.get_bounds().size.x*0.5f, padding_vertical)).floor());
+ window.draw(title_text);
+
+ //const float description_bottom = description_text.get_position().y + description_text.get_bounds().size.y;
+ //const float remaining_height = (bg_rect.get_position().y + bg_rect.get_size().y) - description_bottom;
+ hotkey_text.set_position(mgl::vec2f(bg_rect.get_position() + bg_rect.get_size()*0.5f - hotkey_text.get_bounds().size*0.5f).floor());
+ window.draw(hotkey_text);
+
+ description_text.set_position(mgl::vec2f(bg_rect.get_position() + mgl::vec2f(bg_rect.get_size().x*0.5f - description_text.get_bounds().size.x*0.5f, bg_rect.get_size().y - description_text.get_bounds().size.y - padding_vertical)).floor());
+ window.draw(description_text);
+ };
+ hotkey_overlay->set_visible(false);
+ hotkey_overlay_ptr = hotkey_overlay.get();
+ add_widget(std::move(hotkey_overlay));
}
std::unique_ptr<Subsection> GlobalSettingsPage::create_appearance_subsection(ScrollablePage *parent_page) {
@@ -134,6 +241,117 @@ namespace gsr {
return enable_hotkeys_radio_button;
}
+ std::unique_ptr<List> GlobalSettingsPage::create_show_hide_hotkey_options() {
+ auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
+
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Show/hide UI:", get_color_theme().text_color));
+ auto show_hide_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ show_hide_button_ptr = show_hide_button.get();
+ list->add_widget(std::move(show_hide_button));
+
+ show_hide_button_ptr->on_click = [this] {
+ configure_hotkey_start(ConfigureHotkeyType::SHOW_HIDE);
+ };
+
+
+ return list;
+ }
+
+ std::unique_ptr<List> GlobalSettingsPage::create_replay_hotkey_options() {
+ auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
+
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Turn replay on/off:", get_color_theme().text_color));
+ auto turn_replay_on_off_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ turn_replay_on_off_button_ptr = turn_replay_on_off_button.get();
+ list->add_widget(std::move(turn_replay_on_off_button));
+
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save replay:", get_color_theme().text_color));
+ auto save_replay_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ save_replay_button_ptr = save_replay_button.get();
+ list->add_widget(std::move(save_replay_button));
+
+ turn_replay_on_off_button_ptr->on_click = [this] {
+ configure_hotkey_start(ConfigureHotkeyType::REPLAY_START_STOP);
+ };
+
+ save_replay_button_ptr->on_click = [this] {
+ configure_hotkey_start(ConfigureHotkeyType::REPLAY_SAVE);
+ };
+
+ return list;
+ }
+
+ std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_options() {
+ auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
+
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop recording:", get_color_theme().text_color));
+ auto start_stop_recording_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ start_stop_recording_button_ptr = start_stop_recording_button.get();
+ list->add_widget(std::move(start_stop_recording_button));
+
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Pause/unpause recording:", get_color_theme().text_color));
+ auto pause_unpause_recording_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ pause_unpause_recording_button_ptr = pause_unpause_recording_button.get();
+ list->add_widget(std::move(pause_unpause_recording_button));
+
+ start_stop_recording_button_ptr->on_click = [this] {
+ configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP);
+ };
+
+ pause_unpause_recording_button_ptr->on_click = [this] {
+ configure_hotkey_start(ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE);
+ };
+
+ return list;
+ }
+
+ std::unique_ptr<List> GlobalSettingsPage::create_stream_hotkey_options() {
+ auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
+
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop streaming:", get_color_theme().text_color));
+ auto start_stop_streaming_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ start_stop_streaming_button_ptr = start_stop_streaming_button.get();
+ list->add_widget(std::move(start_stop_streaming_button));
+
+ start_stop_streaming_button_ptr->on_click = [this] {
+ configure_hotkey_start(ConfigureHotkeyType::STREAM_START_STOP);
+ };
+
+ return list;
+ }
+
+ std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() {
+ auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
+
+ // auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ // clear_hotkeys_button->on_click = [this] {
+ // config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
+ // config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
+ // config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0};
+ // config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
+ // config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0};
+ // config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
+ // load_hotkeys();
+ // overlay->rebind_all_keyboard_hotkeys();
+ // };
+ // list->add_widget(std::move(clear_hotkeys_button));
+
+ auto reset_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Reset hotkeys to default", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
+ reset_hotkeys_button->on_click = [this] {
+ config.streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT};
+ config.record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT};
+ config.record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
+ config.replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT};
+ config.replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT};
+ config.main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
+ load_hotkeys();
+ overlay->rebind_all_keyboard_hotkeys();
+ };
+ list->add_widget(std::move(reset_hotkeys_button));
+
+ return list;
+ }
+
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();
@@ -144,7 +362,12 @@ namespace gsr {
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));
+ list_ptr->add_widget(create_show_hide_hotkey_options());
+ list_ptr->add_widget(create_replay_hotkey_options());
+ list_ptr->add_widget(create_record_hotkey_options());
+ list_ptr->add_widget(create_stream_hotkey_options());
+ list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Double-click the controller share button to save a replay", get_color_theme().text_color));
+ list_ptr->add_widget(create_hotkey_control_buttons());
return subsection;
}
@@ -168,32 +391,30 @@ 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));
+ }
+
+ std::unique_ptr<Subsection> GlobalSettingsPage::create_application_info_subsection(ScrollablePage *parent_page) {
+ const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
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));
+ char str[128];
+ snprintf(str, sizeof(str), "UI version: %s", GSR_UI_VERSION);
+ list->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->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
}
- return subsection;
+
+ snprintf(str, sizeof(str), "GPU vendor: %s", gpu_vendor_to_string(gsr_info->gpu_info.vendor));
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
+
+ return std::make_unique<Subsection>("Application info", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
void GlobalSettingsPage::add_widgets() {
@@ -205,6 +426,7 @@ namespace gsr {
settings_list->add_widget(create_startup_subsection(scrollable_page.get()));
settings_list->add_widget(create_hotkey_subsection(scrollable_page.get()));
settings_list->add_widget(create_application_options_subsection(scrollable_page.get()));
+ settings_list->add_widget(create_application_info_subsection(scrollable_page.get()));
scrollable_page->add_widget(std::move(settings_list));
content_page_ptr->add_widget(std::move(scrollable_page));
@@ -227,12 +449,174 @@ namespace gsr {
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);
+
+ load_hotkeys();
+ }
+
+ void GlobalSettingsPage::load_hotkeys() {
+ turn_replay_on_off_button_ptr->set_text(config_hotkey_to_string(config.replay_config.start_stop_hotkey));
+ save_replay_button_ptr->set_text(config_hotkey_to_string(config.replay_config.save_hotkey));
+
+ start_stop_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.start_stop_hotkey));
+ pause_unpause_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.pause_unpause_hotkey));
+
+ start_stop_streaming_button_ptr->set_text(config_hotkey_to_string(config.streaming_config.start_stop_hotkey));
+
+ show_hide_button_ptr->set_text(config_hotkey_to_string(config.main_config.show_hide_hotkey));
}
void GlobalSettingsPage::save() {
+ configure_hotkey_cancel();
config.main_config.tint_color = tint_color_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);
}
+
+ bool GlobalSettingsPage::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) {
+ if(!StaticPage::on_event(event, window, offset))
+ return false;
+
+ if(configure_hotkey_type == ConfigureHotkeyType::NONE)
+ return true;
+
+ Button *configure_hotkey_button = configure_hotkey_get_button_by_active_type();
+ if(!configure_hotkey_button)
+ return true;
+
+ if(event.type == mgl::Event::KeyPressed) {
+ if(event.key.code == mgl::Keyboard::Escape)
+ return false;
+
+ if(mgl::Keyboard::key_is_modifier(event.key.code)) {
+ configure_config_hotkey.modifiers |= mgl_modifier_to_hotkey_modifier(event.key.code);
+ configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey));
+ } else if(configure_config_hotkey.modifiers != 0) {
+ configure_config_hotkey.key = event.key.code;
+ configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey));
+ configure_hotkey_stop_and_save();
+ }
+
+ return false;
+ } else if(event.type == mgl::Event::KeyReleased) {
+ if(event.key.code == mgl::Keyboard::Escape) {
+ configure_hotkey_cancel();
+ return false;
+ }
+
+ if(mgl::Keyboard::key_is_modifier(event.key.code)) {
+ configure_config_hotkey.modifiers &= ~mgl_modifier_to_hotkey_modifier(event.key.code);
+ configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey));
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ Button* GlobalSettingsPage::configure_hotkey_get_button_by_active_type() {
+ switch(configure_hotkey_type) {
+ case ConfigureHotkeyType::NONE:
+ return nullptr;
+ case ConfigureHotkeyType::REPLAY_START_STOP:
+ return turn_replay_on_off_button_ptr;
+ case ConfigureHotkeyType::REPLAY_SAVE:
+ return save_replay_button_ptr;
+ case ConfigureHotkeyType::RECORD_START_STOP:
+ return start_stop_recording_button_ptr;
+ case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE:
+ return pause_unpause_recording_button_ptr;
+ case ConfigureHotkeyType::STREAM_START_STOP:
+ return start_stop_streaming_button_ptr;
+ case ConfigureHotkeyType::SHOW_HIDE:
+ return show_hide_button_ptr;
+ }
+ return nullptr;
+ }
+
+ ConfigHotkey* GlobalSettingsPage::configure_hotkey_get_config_by_active_type() {
+ switch(configure_hotkey_type) {
+ case ConfigureHotkeyType::NONE:
+ return nullptr;
+ case ConfigureHotkeyType::REPLAY_START_STOP:
+ return &config.replay_config.start_stop_hotkey;
+ case ConfigureHotkeyType::REPLAY_SAVE:
+ return &config.replay_config.save_hotkey;
+ case ConfigureHotkeyType::RECORD_START_STOP:
+ return &config.record_config.start_stop_hotkey;
+ case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE:
+ return &config.record_config.pause_unpause_hotkey;
+ case ConfigureHotkeyType::STREAM_START_STOP:
+ return &config.streaming_config.start_stop_hotkey;
+ case ConfigureHotkeyType::SHOW_HIDE:
+ return &config.main_config.show_hide_hotkey;
+ }
+ return nullptr;
+ }
+
+ void GlobalSettingsPage::for_each_config_hotkey(std::function<void(ConfigHotkey *config_hotkey)> callback) {
+ ConfigHotkey *config_hotkeys[] = {
+ &config.replay_config.start_stop_hotkey,
+ &config.replay_config.save_hotkey,
+ &config.record_config.start_stop_hotkey,
+ &config.record_config.pause_unpause_hotkey,
+ &config.streaming_config.start_stop_hotkey,
+ &config.main_config.show_hide_hotkey
+ };
+ for(ConfigHotkey *config_hotkey : config_hotkeys) {
+ callback(config_hotkey);
+ }
+ }
+
+ void GlobalSettingsPage::configure_hotkey_start(ConfigureHotkeyType hotkey_type) {
+ assert(hotkey_type != ConfigureHotkeyType::NONE);
+ configure_config_hotkey = {0, 0};
+ configure_hotkey_type = hotkey_type;
+
+ content_page_ptr->set_visible(false);
+ hotkey_overlay_ptr->set_visible(true);
+ overlay->unbind_all_keyboard_hotkeys();
+ }
+
+ void GlobalSettingsPage::configure_hotkey_cancel() {
+ Button *config_hotkey_button = configure_hotkey_get_button_by_active_type();
+ ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type();
+ if(config_hotkey_button && config_hotkey)
+ config_hotkey_button->set_text(config_hotkey_to_string(*config_hotkey));
+
+ configure_config_hotkey = {0, 0};
+ configure_hotkey_type = ConfigureHotkeyType::NONE;
+ content_page_ptr->set_visible(true);
+ hotkey_overlay_ptr->set_visible(false);
+ overlay->rebind_all_keyboard_hotkeys();
+ }
+
+ void GlobalSettingsPage::configure_hotkey_stop_and_save() {
+ Button *config_hotkey_button = configure_hotkey_get_button_by_active_type();
+ ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type();
+ if(config_hotkey_button && config_hotkey) {
+ bool hotkey_used_by_another_action = false;
+ for_each_config_hotkey([&](ConfigHotkey *config_hotkey_item) {
+ if(config_hotkey_item != config_hotkey && *config_hotkey_item == configure_config_hotkey)
+ hotkey_used_by_another_action = true;
+ });
+
+ if(hotkey_used_by_another_action) {
+ const std::string error_msg = "The hotkey \"" + config_hotkey_to_string(configure_config_hotkey) + " is already used for something else";
+ overlay->show_notification(error_msg.c_str(), 3.0, mgl::Color(255, 0, 0, 255), mgl::Color(255, 0, 0, 255), NotificationType::NONE);
+ config_hotkey_button->set_text(config_hotkey_to_string(*config_hotkey));
+ configure_config_hotkey = {0, 0};
+ return;
+ }
+
+ *config_hotkey = configure_config_hotkey;
+ }
+
+ configure_config_hotkey = {0, 0};
+ configure_hotkey_type = ConfigureHotkeyType::NONE;
+ content_page_ptr->set_visible(true);
+ hotkey_overlay_ptr->set_visible(false);
+ overlay->rebind_all_keyboard_hotkeys();
+ }
} \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index efb3583..17a73a5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,18 +1,14 @@
#include "../include/GsrInfo.hpp"
#include "../include/Overlay.hpp"
-#include "../include/GlobalHotkeysLinux.hpp"
-#include "../include/GlobalHotkeysJoystick.hpp"
#include "../include/gui/Utils.hpp"
#include "../include/Process.hpp"
#include "../include/Rpc.hpp"
#include <unistd.h>
#include <signal.h>
-#include <thread>
#include <string.h>
#include <limits.h>
-#include <X11/keysym.h>
#include <mglpp/mglpp.hpp>
#include <mglpp/system/Clock.hpp>
@@ -41,57 +37,6 @@ static void disable_prime_run() {
unsetenv("DRI_PRIME");
}
-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())
- fprintf(stderr, "error: failed to start global hotkeys\n");
-
- global_hotkeys->bind_action("show_hide", [overlay](const std::string &id) {
- fprintf(stderr, "pressed %s\n", id.c_str());
- overlay->toggle_show();
- });
-
- global_hotkeys->bind_action("record", [overlay](const std::string &id) {
- fprintf(stderr, "pressed %s\n", id.c_str());
- overlay->toggle_record();
- });
-
- global_hotkeys->bind_action("pause", [overlay](const std::string &id) {
- fprintf(stderr, "pressed %s\n", id.c_str());
- overlay->toggle_pause();
- });
-
- global_hotkeys->bind_action("stream", [overlay](const std::string &id) {
- fprintf(stderr, "pressed %s\n", id.c_str());
- overlay->toggle_stream();
- });
-
- global_hotkeys->bind_action("replay_start", [overlay](const std::string &id) {
- fprintf(stderr, "pressed %s\n", id.c_str());
- overlay->toggle_replay();
- });
-
- global_hotkeys->bind_action("replay_save", [overlay](const std::string &id) {
- fprintf(stderr, "pressed %s\n", id.c_str());
- overlay->save_replay();
- });
-
- 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());
@@ -320,34 +265,6 @@ int main(int argc, char **argv) {
rpc_add_commands(rpc.get(), overlay.get());
- std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr;
- if(overlay->get_config().main_config.hotkeys_enable_option == "enable_hotkeys")
- global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::ALL);
- 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;
@@ -358,24 +275,15 @@ int main(int argc, char **argv) {
gsr::set_frame_delta_seconds(frame_delta_seconds);
rpc->poll();
-
- if(global_hotkeys)
- global_hotkeys->poll_events();
-
- if(global_hotkeys_js)
- global_hotkeys_js->poll_events();
-
- overlay->handle_events(global_hotkeys.get());
+ overlay->handle_events();
if(!overlay->draw()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ usleep(100 * 1000); // 100ms
mgl_ping_display_server();
}
}
fprintf(stderr, "Info: shutting down!\n");
rpc.reset();
- global_hotkeys.reset();
- global_hotkeys_js.reset();
overlay.reset();
mgl_deinit();
diff --git a/tools/gsr-global-hotkeys/keyboard_event.c b/tools/gsr-global-hotkeys/keyboard_event.c
index 554c1a2..261dc57 100644
--- a/tools/gsr-global-hotkeys/keyboard_event.c
+++ b/tools/gsr-global-hotkeys/keyboard_event.c
@@ -25,24 +25,6 @@
#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);
@@ -135,36 +117,44 @@ 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);
+/* Return true if a global hotkey is assigned to the key combination */
+static bool keyboard_event_on_key_pressed(keyboard_event *self, const struct input_event *event, uint32_t modifiers) {
+ if(event->value != KEYBOARD_BUTTON_PRESSED)
+ return false;
+
+ bool global_hotkey_match = false;
+ for(int i = 0; i < self->num_global_hotkeys; ++i) {
+ if(event->code == self->global_hotkeys[i].key && modifiers == self->global_hotkeys[i].modifiers) {
+ puts(self->global_hotkeys[i].action);
+ fflush(stdout);
+ global_hotkey_match = true;
+ }
+ }
+ return global_hotkey_match;
+}
+
+static inline uint32_t set_bit(uint32_t value, uint32_t bit_flag, bool set) {
+ if(set)
+ return value | bit_flag;
else
- return 0;
+ return value & ~bit_flag;
}
-/* 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 uint32_t keycode_to_modifier_bit(uint32_t keycode) {
+ switch(keycode) {
+ case KEY_LEFTSHIFT: return KEYBOARD_MODKEY_LSHIFT;
+ case KEY_RIGHTSHIFT: return KEYBOARD_MODKEY_RSHIFT;
+ case KEY_LEFTCTRL: return KEYBOARD_MODKEY_LCTRL;
+ case KEY_RIGHTCTRL: return KEYBOARD_MODKEY_RCTRL;
+ case KEY_LEFTALT: return KEYBOARD_MODKEY_LALT;
+ case KEY_RIGHTALT: return KEYBOARD_MODKEY_RALT;
+ case KEY_LEFTMETA: return KEYBOARD_MODKEY_LSUPER;
+ case KEY_RIGHTMETA: return KEYBOARD_MODKEY_RSUPER;
}
+ 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) {
+static void keyboard_event_process_input_event_data(keyboard_event *self, event_extra_data *extra_data, int fd) {
struct input_event event;
if(read(fd, &event, sizeof(event)) != sizeof(event)) {
fprintf(stderr, "Error: failed to read input event data\n");
@@ -183,63 +173,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);
-
- /* We do this conversion from keycode to keysym back to keycode to support different keyboard layouts in the X server (which Wayland also uses to support Xwayland) */
- 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;
- case KEY_RIGHTSHIFT:
- self->rshift_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- case KEY_LEFTCTRL:
- self->lctrl_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- case KEY_RIGHTCTRL:
- self->rctrl_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- case KEY_LEFTALT:
- self->lalt_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- case KEY_RIGHTALT:
- self->ralt_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- case KEY_LEFTMETA:
- self->lmeta_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- case KEY_RIGHTMETA:
- self->rmeta_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
- break;
- default: {
- const bool shift_pressed = self->lshift_button_state == KEYBOARD_BUTTON_PRESSED || self->rshift_button_state == KEYBOARD_BUTTON_PRESSED;
- const bool ctrl_pressed = self->lctrl_button_state == KEYBOARD_BUTTON_PRESSED || self->rctrl_button_state == KEYBOARD_BUTTON_PRESSED;
- 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", 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)
- modifiers |= KEYBOARD_MODKEY_SHIFT;
- if(ctrl_pressed)
- modifiers |= KEYBOARD_MODKEY_CTRL;
- if(lalt_pressed)
- modifiers |= KEYBOARD_MODKEY_LALT;
- if(ralt_pressed)
- modifiers |= KEYBOARD_MODKEY_RALT;
- if(meta_pressed)
- modifiers |= KEYBOARD_MODKEY_SUPER;
-
- if(!callback(keycode, modifiers, event.value, userdata))
- return;
-
- break;
- }
+ const uint32_t modifier_bit = keycode_to_modifier_bit(event.code);
+ if(modifier_bit == 0) {
+ if(keyboard_event_on_key_pressed(self, &event, self->modifier_button_states))
+ return;
+ } else {
+ self->modifier_button_states = set_bit(self->modifier_button_states, modifier_bit, event.value >= 1);
}
}
@@ -250,6 +189,36 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
}
}
+/* Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only */
+static void* keyboard_event_close_fds_callback(void *userdata) {
+ keyboard_event *self = userdata;
+ while(self->running) {
+ pthread_mutex_lock(&self->close_dev_input_mutex);
+ for(int i = 0; i < self->num_close_fds; ++i) {
+ close(self->close_fds[i]);
+ }
+ self->num_close_fds = 0;
+ pthread_mutex_unlock(&self->close_dev_input_mutex);
+
+ usleep(100 * 1000); /* 100 milliseconds */
+ }
+ return NULL;
+}
+
+static bool keyboard_event_try_add_close_fd(keyboard_event *self, int fd) {
+ bool success = false;
+ pthread_mutex_lock(&self->close_dev_input_mutex);
+ if(self->num_close_fds < MAX_CLOSE_FDS) {
+ self->close_fds[self->num_close_fds] = fd;
+ ++self->num_close_fds;
+ success = true;
+ } else {
+ success = false;
+ }
+ pthread_mutex_unlock(&self->close_dev_input_mutex);
+ return success;
+}
+
/* Returns -1 if invalid format. Expected |dev_input_filepath| to be in format /dev/input/eventN */
static int get_dev_input_id_from_filepath(const char *dev_input_filepath) {
if(strncmp(dev_input_filepath, "/dev/input/event", 16) != 0)
@@ -357,7 +326,10 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
}
}
- close(fd);
+ if(!keyboard_event_try_add_close_fd(self, fd)) {
+ fprintf(stderr, "Error: failed to add immediately, closing now\n");
+ close(fd);
+ }
return false;
}
@@ -459,12 +431,19 @@ 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, x11_context x_context) {
+bool keyboard_event_init(keyboard_event *self, bool exclusive_grab, keyboard_grab_type grab_type) {
memset(self, 0, sizeof(*self));
- self->stdout_event_index = -1;
+ self->stdin_event_index = -1;
self->hotplug_event_index = -1;
self->grab_type = grab_type;
- self->x_context = x_context;
+ self->running = true;
+
+ pthread_mutex_init(&self->close_dev_input_mutex, NULL);
+ if(pthread_create(&self->close_dev_input_fds_thread, NULL, keyboard_event_close_fds_callback, self) != 0) {
+ self->close_dev_input_fds_thread = 0;
+ fprintf(stderr, "Error: failed to create close fds thread\n");
+ return false;
+ }
if(exclusive_grab) {
self->uinput_fd = setup_virtual_keyboard_input(GSR_UI_VIRTUAL_KEYBOARD_NAME);
@@ -472,23 +451,21 @@ bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool excl
fprintf(stderr, "Warning: failed to setup virtual keyboard input for exclusive grab. The focused application will receive keys used for global hotkeys\n");
}
- if(poll_stdout_error) {
- self->event_polls[self->num_event_polls] = (struct pollfd) {
- .fd = STDOUT_FILENO,
- .events = 0,
- .revents = 0
- };
+ self->event_polls[self->num_event_polls] = (struct pollfd) {
+ .fd = STDIN_FILENO,
+ .events = POLLIN,
+ .revents = 0
+ };
- self->event_extra_data[self->num_event_polls] = (event_extra_data) {
- .dev_input_id = -1,
- .grabbed = false,
- .key_states = NULL,
- .num_keys_pressed = 0
- };
+ self->event_extra_data[self->num_event_polls] = (event_extra_data) {
+ .dev_input_id = -1,
+ .grabbed = false,
+ .key_states = NULL,
+ .num_keys_pressed = 0
+ };
- self->stdout_event_index = self->num_event_polls;
- ++self->num_event_polls;
- }
+ self->stdin_event_index = self->num_event_polls;
+ ++self->num_event_polls;
if(hotplug_event_init(&self->hotplug_ev)) {
self->event_polls[self->num_event_polls] = (struct pollfd) {
@@ -522,6 +499,13 @@ bool keyboard_event_init(keyboard_event *self, bool poll_stdout_error, bool excl
}
void keyboard_event_deinit(keyboard_event *self) {
+ self->running = false;
+
+ for(int i = 0; i < self->num_global_hotkeys; ++i) {
+ free(self->global_hotkeys[i].action);
+ }
+ self->num_global_hotkeys = 0;
+
if(self->uinput_fd > 0) {
close(self->uinput_fd);
self->uinput_fd = -1;
@@ -535,40 +519,184 @@ void keyboard_event_deinit(keyboard_event *self) {
self->num_event_polls = 0;
hotplug_event_deinit(&self->hotplug_ev);
+
+ if(self->close_dev_input_fds_thread > 0) {
+ pthread_join(self->close_dev_input_fds_thread, NULL);
+ self->close_dev_input_fds_thread = 0;
+ }
+
+ pthread_mutex_destroy(&self->close_dev_input_mutex);
}
static void on_device_added_callback(const char *devname, void *userdata) {
keyboard_event *keyboard_ev = userdata;
- char dev_input_filepath[1024];
+ char dev_input_filepath[256];
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/%s", devname);
keyboard_event_try_add_device_if_keyboard(keyboard_ev, dev_input_filepath);
}
-#define MappingNotify 34
+/* Returns -1 on error */
+static int parse_u8(const char *str, int size) {
+ if(size <= 0)
+ return -1;
-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;
+ int result = 0;
+ for(int i = 0; i < size; ++i) {
+ char c = str[i];
+ if(c >= '0' && c <= '9') {
+ result = result * 10 + (c - '0');
+ if(result > 255)
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+ return result;
+}
+
+static bool keyboard_event_parse_bind_keys(const char *str, int size, uint8_t *key, uint32_t *modifiers) {
+ *key = 0;
+ *modifiers = 0;
+
+ const char *number_start = str;
+ const char *end = str + size;
+ for(;;) {
+ const char *next = strchr(number_start, '+');
+ if(!next)
+ next = end;
+
+ const int number_len = next - number_start;
+ const int number = parse_u8(number_start, number_len);
+ if(number == -1) {
+ fprintf(stderr, "Error: bind command keys \"%s\" is in invalid format\n", str);
+ return false;
+ }
+
+ const uint32_t modifier_bit = keycode_to_modifier_bit(number);
+ if(modifier_bit == 0) {
+ if(*key != 0) {
+ fprintf(stderr, "Error: can't bind hotkey with multiple non-modifier keys\n");
+ return false;
+ }
+ *key = number;
+ } else {
+ *modifiers = set_bit(*modifiers, modifier_bit, true);
+ }
+
+ number_start = next + 1;
+ if(next == end)
+ break;
+ }
+
+ if(key == 0) {
+ fprintf(stderr, "Error: can't bind hotkey without a non-modifier key\n");
+ return false;
+ }
+
+ if(modifiers == 0) {
+ fprintf(stderr, "Error: can't bind hotkey without a modifier\n");
+ return false;
+ }
+
+ return true;
+}
+
+/* |command| is null-terminated */
+static void keyboard_event_parse_stdin_command(keyboard_event *self, const char *command, int command_size) {
+ if(strncmp(command, "bind ", 5) == 0) {
+ /* Example: |bind show_hide 20+40| */
+ if(self->num_global_hotkeys >= MAX_GLOBAL_HOTKEYS) {
+ fprintf(stderr, "Error: can't add another hotkey. The maximum number of hotkeys (%d) has been reached\n", MAX_GLOBAL_HOTKEYS);
+ return;
+ }
+
+ const char *action_name_end = strchr(command + 5, ' ');
+ if(!action_name_end) {
+ fprintf(stderr, "Error: command \"%s\" is in invalid format\n", command);
+ return;
+ }
+
+ const char *action_name = command + 5;
+ const int action_name_size = action_name_end - action_name;
+
+ uint8_t key = 0;
+ uint32_t modifiers = 0;
+ const char *number_start = action_name_end + 1;
+ const char *end = command + command_size;
+ if(!keyboard_event_parse_bind_keys(number_start, end - number_start, &key, &modifiers))
+ 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);
+ char *action = strndup(action_name, action_name_size);
+ if(!action) {
+ fprintf(stderr, "Error: failed to duplicate %.*s\n", action_name_size, action_name);
+ return;
+ }
+
+ self->global_hotkeys[self->num_global_hotkeys] = (global_hotkey) {
+ .action = action,
+ .key = key,
+ .modifiers = modifiers
+ };
+ ++self->num_global_hotkeys;
+ fprintf(stderr, "Info: bound hotkey: %s\n", action);
+ } else if(strncmp(command, "unbind_all", 10) == 0) {
+ for(int i = 0; i < self->num_global_hotkeys; ++i) {
+ free(self->global_hotkeys[i].action);
+ }
+ self->num_global_hotkeys = 0;
+ fprintf(stderr, "Info: unbound all hotkeys\n");
+ } else {
+ fprintf(stderr, "Warning: got invalid command: \"%s\", expected command to start with either \"bind\" or \"unbind_all\"\n", command);
}
}
-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);
+static void keyboard_event_process_stdin_command_data(keyboard_event *self, int fd) {
+ const int num_bytes_to_read = sizeof(self->stdin_command_data) - self->stdin_command_data_size;
+ if(num_bytes_to_read == 0) {
+ fprintf(stderr, "Error: failed to read data from stdin, buffer is full. Clearing buffer\n");
+ self->stdin_command_data_size = 0;
+ return;
+ }
+
+ const ssize_t bytes_read = read(fd, self->stdin_command_data + self->stdin_command_data_size, num_bytes_to_read);
+ if(bytes_read <= 0)
+ return;
+
+ const char *command_start = self->stdin_command_data;
+ const char *search = self->stdin_command_data + self->stdin_command_data_size;
+ const char *end = search + bytes_read;
+ self->stdin_command_data_size += bytes_read;
+
+ for(;;) {
+ char *next = memchr(search, '\n', end - search);
+ if(!next)
+ break;
+
+ *next = '\0';
+ keyboard_event_parse_stdin_command(self, command_start, next - command_start);
+ search = next + 1;
+ command_start = search;
+ if(next == end)
+ break;
+ }
+
+ const int bytes_parsed = command_start - self->stdin_command_data;
+ if(bytes_parsed > 0) {
+ self->stdin_command_data_size -= bytes_parsed;
+ memmove(self->stdin_command_data, command_start, self->stdin_command_data_size);
+ }
+}
+void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds) {
if(poll(self->event_polls, self->num_event_polls, timeout_milliseconds) <= 0)
return;
+ if(self->stdin_failed)
+ return;
+
for(int i = 0; i < self->num_event_polls; ++i) {
- if(i == self->stdout_event_index && (self->event_polls[i].revents & (POLLHUP|POLLERR)))
- self->stdout_failed = true;
+ if(i == self->stdin_event_index && (self->event_polls[i].revents & (POLLHUP|POLLERR)))
+ self->stdin_failed = true;
if(self->event_polls[i].revents & POLLHUP) { /* TODO: What if this is the hotplug fd? */
keyboard_event_remove_event(self, i);
@@ -582,14 +710,14 @@ void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds,
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 if(i == self->stdout_event_index) {
- /* Do nothing, this shouldn't happen anyways since we dont poll for input */
+ } else if(i == self->stdin_event_index) {
+ keyboard_event_process_stdin_command_data(self, self->event_polls[i].fd);
} else {
- keyboard_event_process_input_event_data(self, &self->event_extra_data[i], self->event_polls[i].fd, callback, userdata);
+ keyboard_event_process_input_event_data(self, &self->event_extra_data[i], self->event_polls[i].fd);
}
}
}
-bool keyboard_event_stdout_has_failed(const keyboard_event *self) {
- return self->stdout_failed;
+bool keyboard_event_stdin_has_failed(const keyboard_event *self) {
+ return self->stdin_failed;
}
diff --git a/tools/gsr-global-hotkeys/keyboard_event.h b/tools/gsr-global-hotkeys/keyboard_event.h
index 79fac75..a86b3dd 100644
--- a/tools/gsr-global-hotkeys/keyboard_event.h
+++ b/tools/gsr-global-hotkeys/keyboard_event.h
@@ -11,38 +11,24 @@
/* POSIX */
#include <poll.h>
+#include <pthread.h>
/* LINUX */
#include <linux/input-event-codes.h>
#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;
+#define MAX_CLOSE_FDS 256
+#define MAX_GLOBAL_HOTKEYS 32
typedef enum {
KEYBOARD_MODKEY_LALT = 1 << 0,
- KEYBOARD_MODKEY_RALT = 1 << 2,
- KEYBOARD_MODKEY_SUPER = 1 << 3,
- KEYBOARD_MODKEY_CTRL = 1 << 4,
- KEYBOARD_MODKEY_SHIFT = 1 << 5
+ KEYBOARD_MODKEY_RALT = 1 << 1,
+ KEYBOARD_MODKEY_LSUPER = 1 << 2,
+ KEYBOARD_MODKEY_RSUPER = 1 << 3,
+ KEYBOARD_MODKEY_LCTRL = 1 << 4,
+ KEYBOARD_MODKEY_RCTRL = 1 << 5,
+ KEYBOARD_MODKEY_LSHIFT = 1 << 6,
+ KEYBOARD_MODKEY_RSHIFT = 1 << 7
} keyboard_modkeys;
typedef enum {
@@ -63,38 +49,44 @@ typedef enum {
} keyboard_grab_type;
typedef struct {
+ uint32_t key;
+ uint32_t modifiers; /* keyboard_modkeys bitmask */
+ char *action;
+} global_hotkey;
+
+typedef struct {
struct pollfd event_polls[MAX_EVENT_POLLS]; /* Current size is |num_event_polls| */
event_extra_data event_extra_data[MAX_EVENT_POLLS]; /* Current size is |num_event_polls| */
int num_event_polls;
- int stdout_event_index;
+ int stdin_event_index;
int hotplug_event_index;
int uinput_fd;
- bool stdout_failed;
+ bool stdin_failed;
keyboard_grab_type grab_type;
- x11_context x_context;
+
+ pthread_t close_dev_input_fds_thread;
+ pthread_mutex_t close_dev_input_mutex;
+ int close_fds[MAX_CLOSE_FDS];
+ int num_close_fds;
+ bool running;
+
+ char stdin_command_data[512];
+ int stdin_command_data_size;
+
+ global_hotkey global_hotkeys[MAX_GLOBAL_HOTKEYS];
+ int num_global_hotkeys;
hotplug_event hotplug_ev;
- keyboard_button_state lshift_button_state;
- keyboard_button_state rshift_button_state;
- keyboard_button_state lctrl_button_state;
- keyboard_button_state rctrl_button_state;
- keyboard_button_state lalt_button_state;
- keyboard_button_state ralt_button_state;
- keyboard_button_state lmeta_button_state;
- keyboard_button_state rmeta_button_state;
+ uint32_t modifier_button_states;
} keyboard_event;
-/* |key| is a KEY_ from linux/input-event-codes.h. |modifiers| is a bitmask of keyboard_modkeys. |press_status| is 0 for released, 1 for pressed and 2 for repeat */
-/* 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, x11_context x_context);
+bool keyboard_event_init(keyboard_event *self, bool exclusive_grab, keyboard_grab_type grab_type);
void keyboard_event_deinit(keyboard_event *self);
/* If |timeout_milliseconds| is -1 then wait until an event is received */
-void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds, key_callback callback, void *userdata);
-bool keyboard_event_stdout_has_failed(const keyboard_event *self);
+void keyboard_event_poll_events(keyboard_event *self, int timeout_milliseconds);
+bool keyboard_event_stdin_has_failed(const keyboard_event *self);
#endif /* KEYBOARD_EVENT_H */
diff --git a/tools/gsr-global-hotkeys/main.c b/tools/gsr-global-hotkeys/main.c
index d05ca5f..41e5ca5 100644
--- a/tools/gsr-global-hotkeys/main.c
+++ b/tools/gsr-global-hotkeys/main.c
@@ -2,42 +2,11 @@
/* C stdlib */
#include <stdio.h>
-#include <stdint.h>
#include <string.h>
+#include <locale.h>
/* POSIX */
#include <unistd.h>
-#include <dlfcn.h>
-
-typedef struct {
- uint32_t key;
- uint32_t modifiers; /* keyboard_modkeys bitmask */
- const char *action;
-} global_hotkey;
-
-#define NUM_GLOBAL_HOTKEYS 6
-static global_hotkey global_hotkeys[NUM_GLOBAL_HOTKEYS] = {
- { .key = KEY_Z, .modifiers = KEYBOARD_MODKEY_LALT, .action = "show_hide" },
- { .key = KEY_F9, .modifiers = KEYBOARD_MODKEY_LALT, .action = "record" },
- { .key = KEY_F7, .modifiers = KEYBOARD_MODKEY_LALT, .action = "pause" },
- { .key = KEY_F8, .modifiers = KEYBOARD_MODKEY_LALT, .action = "stream" },
- { .key = KEY_F10, .modifiers = KEYBOARD_MODKEY_LALT | KEYBOARD_MODKEY_SHIFT, .action = "replay_start" },
- { .key = KEY_F10, .modifiers = KEYBOARD_MODKEY_LALT, .action = "replay_save" }
-};
-
-static bool on_key_callback(uint32_t key, uint32_t modifiers, int press_status, void *userdata) {
- (void)userdata;
- for(int i = 0; i < NUM_GLOBAL_HOTKEYS; ++i) {
- if(key == global_hotkeys[i].key && modifiers == global_hotkeys[i].modifiers) {
- if(press_status == 1) { /* 1 == Pressed */
- puts(global_hotkeys[i].action);
- fflush(stdout);
- }
- return false;
- }
- }
- return true;
-}
static void usage(void) {
fprintf(stderr, "usage: gsr-global-hotkeys [--all|--virtual]\n");
@@ -46,76 +15,6 @@ 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;
-}
-
static bool is_gsr_global_hotkeys_already_running(void) {
FILE *f = fopen("/proc/bus/input/devices", "rb");
if(!f)
@@ -135,6 +34,8 @@ static bool is_gsr_global_hotkeys_already_running(void) {
}
int main(int argc, char **argv) {
+ setlocale(LC_ALL, "C"); /* Sigh... stupid C */
+
keyboard_grab_type grab_type = KEYBOARD_GRAB_TYPE_ALL;
if(argc == 2) {
const char *grab_type_arg = argv[1];
@@ -158,8 +59,6 @@ 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) {
@@ -169,7 +68,7 @@ int main(int argc, char **argv) {
}
keyboard_event keyboard_ev;
- if(!keyboard_event_init(&keyboard_ev, true, true, grab_type, x_context)) {
+ if(!keyboard_event_init(&keyboard_ev, true, grab_type)) {
fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n");
setuid(user_id);
return 1;
@@ -178,9 +77,9 @@ int main(int argc, char **argv) {
fprintf(stderr, "Info: global hotkeys setup, waiting for hotkeys to be pressed\n");
for(;;) {
- keyboard_event_poll_events(&keyboard_ev, -1, on_key_callback, NULL);
- if(keyboard_event_stdout_has_failed(&keyboard_ev)) {
- fprintf(stderr, "Info: stdout closed (parent process likely closed this process), exiting...\n");
+ keyboard_event_poll_events(&keyboard_ev, -1);
+ if(keyboard_event_stdin_has_failed(&keyboard_ev)) {
+ fprintf(stderr, "Info: stdin closed (parent process likely closed this process), exiting...\n");
break;
}
}