Make hotkeys reconfigurable, faster hotkey startup time, fix some keyboard locale issues

This commit is contained in:
dec05eba
2025-01-23 21:23:19 +01:00
parent 47ada4d798
commit 1d9d4d6398
20 changed files with 1187 additions and 567 deletions

View File

@@ -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) {

View File

@@ -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_action(const std::string &id, GlobalHotkeyCallback callback) {
return bound_actions_by_id.insert(std::make_pair(id, std::move(callback))).second;
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;
}
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() {

View File

@@ -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;
}

View File

@@ -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) {
window_pos = focused_monitor->position;
window_size = focused_monitor->size;
} else {
window_pos = {0, 0};
window_size = {32, 32};
}
// 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;
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 = focused_monitor->size / 2;
}
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;

View File

@@ -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);
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)
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;
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);

View File

@@ -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;
}
@@ -167,33 +390,31 @@ 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));
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));
}
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->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
snprintf(str, sizeof(str), "GPU vendor: %s", gpu_vendor_to_string(gsr_info->gpu_info.vendor));
list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
}
return subsection;
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();
}
}

View File

@@ -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();