Use X11 global hotkeys on X11 when possible to prevent clashing with keys used by other applications

This commit is contained in:
dec05eba
2024-11-30 22:25:58 +01:00
parent f885ae67f1
commit 6cde892148
8 changed files with 185 additions and 98 deletions

View File

@@ -48,4 +48,4 @@ If you want to donate you can donate via bitcoin or monero.
# Known issues # Known issues
* The UI always opens on the same (incorrect) monitor when using multiple monitors on Wayland * The UI always opens on the same (incorrect) monitor when using multiple monitors on Wayland
* Some games receive mouse input while the UI is open * Some games receive mouse input while the UI is open
* Global hotkeys can clash with other hotkeys. This is primarly because Wayland compositors are missing support for global hotkey so this software uses a global hotkey system that works on X11 and Wayland by bypassing X11 and Wayland. * Global hotkeys on Wayland can clash with keys used by other applications. This is primarly because Wayland compositors are missing support for global hotkey so this software uses a global hotkey system that works on all Wayland compositors.

6
TODO
View File

@@ -99,4 +99,8 @@ Remove all dependencies from tools/gsr-global-hotkeys and roll our own keyboard
Test global hotkeys with azerty instead of qwerty. Test global hotkeys with azerty instead of qwerty.
Fix cursor grab not working in owlboy, need to use xigrab. Fix cursor grab not working in owlboy, need to use xigrab.
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.

View File

@@ -4,6 +4,10 @@
#include <functional> #include <functional>
#include <string> #include <string>
namespace mgl {
class Event;
}
namespace gsr { namespace gsr {
struct Hotkey { struct Hotkey {
uint64_t key = 0; uint64_t key = 0;
@@ -24,5 +28,7 @@ namespace gsr {
virtual void unbind_all_keys() {} virtual void unbind_all_keys() {}
virtual bool bind_action(const std::string &id, GlobalHotkeyCallback callback) { (void)id; (void)callback; return false; }; virtual bool bind_action(const std::string &id, GlobalHotkeyCallback callback) { (void)id; (void)callback; return false; };
virtual void poll_events() = 0; virtual void poll_events() = 0;
// Returns true if the event wasn't consumed (if the event didn't match a key that has been bound)
virtual bool on_event(mgl::Event &event) { (void)event; return true; }
}; };
} }

View File

@@ -12,12 +12,15 @@ namespace gsr {
GlobalHotkeysX11& operator=(const GlobalHotkeysX11&) = delete; GlobalHotkeysX11& operator=(const GlobalHotkeysX11&) = delete;
~GlobalHotkeysX11() override; ~GlobalHotkeysX11() override;
// Hotkey key is a KeySym (XK_z for example) and modifiers is a bitmask of X11 modifier masks (for example ShiftMask | Mod1Mask)
bool bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) override; bool bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) override;
void unbind_key_press(const std::string &id) override; void unbind_key_press(const std::string &id) override;
void unbind_all_keys() override; void unbind_all_keys() override;
void poll_events() override; void poll_events() override;
bool on_event(mgl::Event &event) override;
private: private:
void call_hotkey_callback(Hotkey hotkey) const; // Returns true if a key bind has been registered for the hotkey
bool call_hotkey_callback(Hotkey hotkey) const;
private: private:
struct HotkeyData { struct HotkeyData {
Hotkey hotkey; Hotkey hotkey;

View File

@@ -18,6 +18,7 @@
namespace gsr { namespace gsr {
class DropdownButton; class DropdownButton;
class GlobalHotkeys;
enum class RecordingStatus { enum class RecordingStatus {
NONE, NONE,
@@ -40,7 +41,7 @@ namespace gsr {
Overlay& operator=(const Overlay&) = delete; Overlay& operator=(const Overlay&) = delete;
~Overlay(); ~Overlay();
void handle_events(); void handle_events(gsr::GlobalHotkeys *global_hotkeys);
void on_event(mgl::Event &event); void on_event(mgl::Event &event);
// Returns false if not visible // Returns false if not visible
bool draw(); bool draw();

View File

@@ -1,6 +1,7 @@
#include "../include/GlobalHotkeysX11.hpp" #include "../include/GlobalHotkeysX11.hpp"
#define XK_MISCELLANY #include <X11/keysym.h>
#include <X11/keysymdef.h> #include <mglpp/window/Event.hpp>
#include <assert.h>
namespace gsr { namespace gsr {
static bool x_failed = false; static bool x_failed = false;
@@ -25,6 +26,30 @@ namespace gsr {
return numlockmask; return numlockmask;
} }
static KeySym mgl_key_to_key_sym(mgl::Keyboard::Key key) {
switch(key) {
case mgl::Keyboard::Z: return XK_z;
case mgl::Keyboard::F7: return XK_F7;
case mgl::Keyboard::F8: return XK_F8;
case mgl::Keyboard::F9: return XK_F9;
case mgl::Keyboard::F10: return XK_F10;
default: return None;
}
}
static uint32_t mgl_key_modifiers_to_x11_modifier_mask(const mgl::Event::KeyEvent &key_event) {
uint32_t mask = 0;
if(key_event.shift)
mask |= ShiftMask;
if(key_event.control)
mask |= ControlMask;
if(key_event.alt)
mask |= Mod1Mask;
if(key_event.system)
mask |= Mod4Mask;
return mask;
}
GlobalHotkeysX11::GlobalHotkeysX11() { GlobalHotkeysX11::GlobalHotkeysX11() {
dpy = XOpenDisplay(NULL); dpy = XOpenDisplay(NULL);
if(!dpy) if(!dpy)
@@ -122,16 +147,27 @@ namespace gsr {
} }
} }
bool GlobalHotkeysX11::on_event(mgl::Event &event) {
if(event.type != mgl::Event::KeyPressed)
return true;
// 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});
}
static unsigned int key_state_without_locks(unsigned int key_state) { static unsigned int key_state_without_locks(unsigned int key_state) {
return key_state & ~(Mod2Mask|LockMask); return key_state & ~(Mod2Mask|LockMask);
} }
void GlobalHotkeysX11::call_hotkey_callback(Hotkey hotkey) const { bool GlobalHotkeysX11::call_hotkey_callback(Hotkey hotkey) const {
for(const auto &[key, val] : bound_keys_by_id) { 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(val.hotkey.modifiers) == key_state_without_locks(hotkey.modifiers)) {
val.callback(key); val.callback(key);
return; return true;
} }
} }
return false;
} }
} }

View File

@@ -10,6 +10,7 @@
#include "../include/gui/Utils.hpp" #include "../include/gui/Utils.hpp"
#include "../include/gui/PageStack.hpp" #include "../include/gui/PageStack.hpp"
#include "../include/WindowUtils.hpp" #include "../include/WindowUtils.hpp"
#include "../include/GlobalHotkeys.hpp"
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
@@ -555,13 +556,17 @@ namespace gsr {
} }
} }
void Overlay::handle_events() { void Overlay::handle_events(gsr::GlobalHotkeys *global_hotkeys) {
if(!visible || !window) if(!visible || !window)
return; return;
handle_xi_events(); handle_xi_events();
while(window->poll_event(event)) { while(window->poll_event(event)) {
if(global_hotkeys) {
if(!global_hotkeys->on_event(event))
continue;
}
on_event(event); on_event(event);
} }
} }
@@ -910,7 +915,7 @@ namespace gsr {
// The real cursor doesn't move when all devices are grabbed, so we create our own cursor and diplay that while grabbed // The real cursor doesn't move when all devices are grabbed, so we create our own cursor and diplay that while grabbed
xi_setup_fake_cursor(); xi_setup_fake_cursor();
// We want to grab all devices to prevent any other application below from receiving events. // We want to grab all devices to prevent any other application below the UI from receiving events.
// Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events. // Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events.
xi_grab_all_devices(); xi_grab_all_devices();
@@ -1005,10 +1010,16 @@ namespace gsr {
} }
void Overlay::toggle_show() { void Overlay::toggle_show() {
if(visible) if(visible) {
hide(); //hide();
else // We dont want to hide immediately because hide is called in event callback, in which it destroys the window.
// Instead remove all pages and wait until next iteration to close the UI (which happens when there are no pages to render).
while(!page_stack.empty()) {
page_stack.pop();
}
} else {
show(); show();
}
} }
void Overlay::toggle_record() { void Overlay::toggle_record() {
@@ -1555,6 +1566,8 @@ namespace gsr {
const int fdl = fcntl(gpu_screen_recorder_process_output_fd, F_GETFL); const int fdl = fcntl(gpu_screen_recorder_process_output_fd, F_GETFL);
fcntl(gpu_screen_recorder_process_output_fd, F_SETFL, fdl | O_NONBLOCK); fcntl(gpu_screen_recorder_process_output_fd, F_SETFL, fdl | O_NONBLOCK);
gpu_screen_recorder_process_output_file = fdopen(gpu_screen_recorder_process_output_fd, "r"); gpu_screen_recorder_process_output_file = fdopen(gpu_screen_recorder_process_output_fd, "r");
if(gpu_screen_recorder_process_output_file)
gpu_screen_recorder_process_output_fd = -1;
// TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video.
// Make clear to the user that the recording starts after the notification is gone. // Make clear to the user that the recording starts after the notification is gone.

View File

@@ -14,7 +14,7 @@
#include <mglpp/mglpp.hpp> #include <mglpp/mglpp.hpp>
#include <mglpp/system/Clock.hpp> #include <mglpp/system/Clock.hpp>
// TODO: Make keyboard controllable for steam deck (and other controllers). // TODO: Make keyboard/controller controllable for steam deck (and other controllers).
// TODO: Keep track of gpu screen recorder run by other programs to not allow recording at the same time, or something. // TODO: Keep track of gpu screen recorder run by other programs to not allow recording at the same time, or something.
// TODO: Add systray by using org.kde.StatusNotifierWatcher/etc dbus directly. // TODO: Add systray by using org.kde.StatusNotifierWatcher/etc dbus directly.
// TODO: Make sure the overlay always stays on top. Test with starting the overlay and then opening youtube in fullscreen. // TODO: Make sure the overlay always stays on top. Test with starting the overlay and then opening youtube in fullscreen.
@@ -38,6 +38,100 @@ static void disable_prime_run() {
unsetenv("__VK_LAYER_NV_optimus"); unsetenv("__VK_LAYER_NV_optimus");
} }
static std::unique_ptr<gsr::GlobalHotkeysX11> register_x11_hotkeys(gsr::Overlay *overlay) {
auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysX11>();
const bool show_hotkey_registered = global_hotkeys->bind_key_press({ XK_z, Mod1Mask }, "show_hide", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_show();
});
const bool record_hotkey_registered = global_hotkeys->bind_key_press({ XK_F9, Mod1Mask }, "record", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_record();
});
const bool pause_hotkey_registered = global_hotkeys->bind_key_press({ XK_F7, Mod1Mask }, "pause", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_pause();
});
const bool stream_hotkey_registered = global_hotkeys->bind_key_press({ XK_F8, Mod1Mask }, "stream", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_stream();
});
const bool replay_hotkey_registered = global_hotkeys->bind_key_press({ XK_F10, ShiftMask | Mod1Mask }, "replay_start", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_replay();
});
const bool replay_save_hotkey_registered = global_hotkeys->bind_key_press({ XK_F10, Mod1Mask }, "replay_save", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->save_replay();
});
if(!show_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+z for showing the overlay because the hotkey is registered by another program\n");
if(!record_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f9 for recording because the hotkey is registered by another program\n");
if(!pause_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f7 for pausing because the hotkey is registered by another program\n");
if(!stream_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f8 for streaming because the hotkey is registered by another program\n");
if(!replay_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+shift+f10 for starting replay because the hotkey is registered by another program\n");
if(!replay_save_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f10 for saving replay because the hotkey is registered by another program\n");
if(!show_hotkey_registered || !record_hotkey_registered || !pause_hotkey_registered || !stream_hotkey_registered || !replay_hotkey_registered || !replay_save_hotkey_registered)
return nullptr;
return global_hotkeys;
}
static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Overlay *overlay) {
auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysLinux>();
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;
}
int main(void) { int main(void) {
setlocale(LC_ALL, "C"); // Sigh... stupid C setlocale(LC_ALL, "C"); // Sigh... stupid C
@@ -89,7 +183,6 @@ int main(void) {
} }
mgl_context *context = mgl_get_context(); mgl_context *context = mgl_get_context();
const int x11_socket = XConnectionNumber((Display*)context->connection);
egl_functions egl_funcs; egl_functions egl_funcs;
egl_funcs.eglGetError = (decltype(egl_funcs.eglGetError))context->gl.eglGetProcAddress("eglGetError"); egl_funcs.eglGetError = (decltype(egl_funcs.eglGetError))context->gl.eglGetProcAddress("eglGetError");
@@ -107,96 +200,27 @@ int main(void) {
auto overlay = std::make_unique<gsr::Overlay>(resources_path, gsr_info, egl_funcs); auto overlay = std::make_unique<gsr::Overlay>(resources_path, gsr_info, egl_funcs);
//overlay.show(); //overlay.show();
// gsr::GlobalHotkeysX11 global_hotkeys; std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr;
// const bool show_hotkey_registered = global_hotkeys.bind_key_press({ XK_z, Mod1Mask }, "show_hide", [&](const std::string &id) { if(gsr_info.system_info.display_server == gsr::DisplayServer::X11) {
// fprintf(stderr, "pressed %s\n", id.c_str()); global_hotkeys = register_x11_hotkeys(overlay.get());
// overlay->toggle_show(); if(!global_hotkeys) {
// }); fprintf(stderr, "info: failed to register some x11 hotkeys because they are registered by another program. Will use linux hotkeys instead that can clash with keys used by other applications\n");
global_hotkeys = register_linux_hotkeys(overlay.get());
}
} else {
fprintf(stderr, "info: Global linux hotkeys are used which can clash with keys used by other applications. Use X11 instead if this is an issue for you\n");
global_hotkeys = register_linux_hotkeys(overlay.get());
}
// const bool record_hotkey_registered = global_hotkeys.bind_key_press({ XK_F9, Mod1Mask }, "record", [&](const std::string &id) { // 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.
// fprintf(stderr, "pressed %s\n", id.c_str());
// overlay->toggle_record();
// });
// const bool pause_hotkey_registered = global_hotkeys.bind_key_press({ XK_F7, Mod1Mask }, "pause", [&](const std::string &id) {
// fprintf(stderr, "pressed %s\n", id.c_str());
// overlay->toggle_pause();
// });
// const bool stream_hotkey_registered = global_hotkeys.bind_key_press({ XK_F8, Mod1Mask }, "stream", [&](const std::string &id) {
// fprintf(stderr, "pressed %s\n", id.c_str());
// overlay->toggle_stream();
// });
// const bool replay_hotkey_registered = global_hotkeys.bind_key_press({ XK_F10, ShiftMask | Mod1Mask }, "replay_start", [&](const std::string &id) {
// fprintf(stderr, "pressed %s\n", id.c_str());
// overlay->toggle_replay();
// });
// const bool replay_save_hotkey_registered = global_hotkeys.bind_key_press({ XK_F10, Mod1Mask }, "replay_save", [&](const std::string &id) {
// fprintf(stderr, "pressed %s\n", id.c_str());
// overlay->save_replay();
// });
gsr::GlobalHotkeysLinux global_hotkeys;
if(!global_hotkeys.start())
fprintf(stderr, "error: failed to start global hotkeys\n");
const bool show_hotkey_registered = global_hotkeys.bind_action("show_hide", [&](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_show();
});
const bool record_hotkey_registered = global_hotkeys.bind_action("record", [&](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_record();
});
const bool pause_hotkey_registered = global_hotkeys.bind_action("pause", [&](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_pause();
});
const bool stream_hotkey_registered = global_hotkeys.bind_action("stream", [&](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_stream();
});
const bool replay_hotkey_registered = global_hotkeys.bind_action("replay_start", [&](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_replay();
});
const bool replay_save_hotkey_registered = global_hotkeys.bind_action("replay_save", [&](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->save_replay();
});
if(!show_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+z for showing the overlay because the hotkey is registered by another program\n");
if(!record_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f9 for recording because the hotkey is registered by another program\n");
if(!pause_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f7 for pausing because the hotkey is registered by another program\n");
if(!stream_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f8 for streaming because the hotkey is registered by another program\n");
if(!replay_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+shift+f10 for starting replay because the hotkey is registered by another program\n");
if(!replay_save_hotkey_registered)
fprintf(stderr, "error: failed to register hotkey alt+f10 for saving replay because the hotkey is registered by another program\n");
mgl::Clock frame_delta_clock; mgl::Clock frame_delta_clock;
while(running && mgl_is_connected_to_display_server()) { while(running && mgl_is_connected_to_display_server()) {
const double frame_delta_seconds = frame_delta_clock.restart(); const double frame_delta_seconds = frame_delta_clock.restart();
gsr::set_frame_delta_seconds(frame_delta_seconds); gsr::set_frame_delta_seconds(frame_delta_seconds);
global_hotkeys.poll_events(); global_hotkeys->poll_events();
overlay->handle_events(); overlay->handle_events(global_hotkeys.get());
if(!overlay->draw()) { if(!overlay->draw()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
mgl_ping_display_server(); mgl_ping_display_server();