Mention that recording has to be restarted to apply changes. Fix stuck in repeat state if pressed while gsr-global-hotkey starts

This commit is contained in:
dec05eba
2024-12-26 15:22:57 +01:00
parent ec6d4090af
commit e5b745d696
11 changed files with 119 additions and 18 deletions

5
TODO
View File

@@ -109,4 +109,7 @@ Move ui hover code from ::draw to ::on_event, to properly handle widget event st
Save audio devices by name instead of id. This is more robust since audio id can change(?). Save audio devices by name instead of id. This is more robust since audio id can change(?).
Test gsr-global-hotkey on laptop keyboard (that has touchpad). Improve linux global hotkeys startup time by parsing /proc/bus/input/devices instead of ioctl.
Keyboard grabbing has some issues. If a key is grabbed while its being held down that it will be kept in held-down state (a hack exists to workaround this, but it may not work in all environments).
This also causes an issue where is a key is pressed before key is grabbed and then released while grabbed and then key is ungrabbed then the key will have to be pressed twice to register in the display server.

View File

@@ -12,6 +12,9 @@ namespace gsr {
struct ConfigHotkey { struct ConfigHotkey {
int64_t keysym = 0; int64_t keysym = 0;
uint32_t modifiers = 0; uint32_t modifiers = 0;
bool operator==(const ConfigHotkey &other) const;
bool operator!=(const ConfigHotkey &other) const;
}; };
struct RecordOptions { struct RecordOptions {
@@ -94,6 +97,8 @@ namespace gsr {
struct Config { struct Config {
Config(const SupportedCaptureOptions &capture_options); Config(const SupportedCaptureOptions &capture_options);
bool operator==(const Config &other);
bool operator!=(const Config &other);
MainConfig main_config; MainConfig main_config;
StreamingConfig streaming_config; StreamingConfig streaming_config;

View File

@@ -27,7 +27,6 @@ namespace gsr {
struct SupportedCaptureOptions { struct SupportedCaptureOptions {
bool window = false; bool window = false;
bool focused = false; bool focused = false;
bool screen = false;
bool portal = false; bool portal = false;
std::vector<GsrMonitor> monitors; std::vector<GsrMonitor> monitors;
}; };

View File

@@ -32,6 +32,8 @@ namespace gsr {
void load(); void load();
void save(); void save();
void on_navigate_away_from_page() override; void on_navigate_away_from_page() override;
std::function<void()> on_config_changed;
private: private:
std::unique_ptr<RadioButton> create_view_radio_button(); std::unique_ptr<RadioButton> create_view_radio_button();
std::unique_ptr<ComboBox> create_record_area_box(); std::unique_ptr<ComboBox> create_record_area_box();

View File

@@ -5,6 +5,7 @@
#include <limits.h> #include <limits.h>
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <iostream>
#define FORMAT_I32 "%" PRIi32 #define FORMAT_I32 "%" PRIi32
#define FORMAT_I64 "%" PRIi64 #define FORMAT_I64 "%" PRIi64
@@ -13,6 +14,14 @@
#define CONFIG_FILE_VERSION 1 #define CONFIG_FILE_VERSION 1
namespace gsr { namespace gsr {
bool ConfigHotkey::operator==(const ConfigHotkey &other) const {
return keysym == other.keysym && modifiers == other.modifiers;
}
bool ConfigHotkey::operator!=(const ConfigHotkey &other) const {
return !operator==(other);
}
Config::Config(const SupportedCaptureOptions &capture_options) { Config::Config(const SupportedCaptureOptions &capture_options) {
const std::string default_save_directory = get_videos_dir(); const std::string default_save_directory = get_videos_dir();
@@ -141,6 +150,38 @@ namespace gsr {
}; };
} }
bool Config::operator==(const Config &other) {
const auto config_options = get_config_options(*this);
const auto config_options_other = get_config_options(const_cast<Config&>(other));
for(auto it : config_options) {
auto it_other = config_options_other.find(it.first);
if(it_other == config_options_other.end() || it_other->second.index() != it.second.index())
return false;
if(std::holds_alternative<bool*>(it.second)) {
if(*std::get<bool*>(it.second) != *std::get<bool*>(it_other->second))
return false;
} else if(std::holds_alternative<std::string*>(it.second)) {
if(*std::get<std::string*>(it.second) != *std::get<std::string*>(it_other->second))
return false;
} else if(std::holds_alternative<int32_t*>(it.second)) {
if(*std::get<int32_t*>(it.second) != *std::get<int32_t*>(it_other->second))
return false;
} else if(std::holds_alternative<ConfigHotkey*>(it.second)) {
if(*std::get<ConfigHotkey*>(it.second) != *std::get<ConfigHotkey*>(it_other->second))
return false;
} else if(std::holds_alternative<std::vector<std::string>*>(it.second)) {
if(*std::get<std::vector<std::string>*>(it.second) != *std::get<std::vector<std::string>*>(it_other->second))
return false;
}
}
return true;
}
bool Config::operator!=(const Config &other) {
return !operator==(other);
}
std::optional<Config> read_config(const SupportedCaptureOptions &capture_options) { std::optional<Config> read_config(const SupportedCaptureOptions &capture_options) {
std::optional<Config> config; std::optional<Config> config;

View File

@@ -209,8 +209,6 @@ namespace gsr {
capture_options.window = true; capture_options.window = true;
else if(line == "focused") else if(line == "focused")
capture_options.focused = true; capture_options.focused = true;
else if(line == "screen")
capture_options.screen = true;
else if(line == "portal") else if(line == "portal")
capture_options.portal = true; capture_options.portal = true;
else { else {

View File

@@ -906,6 +906,10 @@ namespace gsr {
button->on_click = [this](const std::string &id) { button->on_click = [this](const std::string &id) {
if(id == "settings") { if(id == "settings") {
auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack); auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack);
replay_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::REPLAY)
show_notification("Replay settings have been modified.\nYou may need to restart replay to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
};
page_stack.push(std::move(replay_settings_page)); page_stack.push(std::move(replay_settings_page));
} else if(id == "save") { } else if(id == "save") {
on_press_save_replay(); on_press_save_replay();
@@ -927,6 +931,10 @@ namespace gsr {
button->on_click = [this](const std::string &id) { button->on_click = [this](const std::string &id) {
if(id == "settings") { if(id == "settings") {
auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack); auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack);
record_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::RECORD)
show_notification("Recording settings have been modified.\nYou may need to restart recording to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
};
page_stack.push(std::move(record_settings_page)); page_stack.push(std::move(record_settings_page));
} else if(id == "pause") { } else if(id == "pause") {
toggle_pause(); toggle_pause();
@@ -946,6 +954,10 @@ namespace gsr {
button->on_click = [this](const std::string &id) { button->on_click = [this](const std::string &id) {
if(id == "settings") { if(id == "settings") {
auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack); auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack);
stream_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::STREAM)
show_notification("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
};
page_stack.push(std::move(stream_settings_page)); page_stack.push(std::move(stream_settings_page));
} else if(id == "start") { } else if(id == "start") {
on_press_start_stream(); on_press_start_stream();
@@ -1318,7 +1330,7 @@ namespace gsr {
show_notification("Replay stopped", 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY); show_notification("Replay stopped", 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
} else { } else {
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
show_notification("Replay stopped because of an error", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY); show_notification("Replay stopped because of an error. Verify if settings are correct", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
} }
break; break;
} }
@@ -1334,7 +1346,7 @@ namespace gsr {
show_notification("Streaming has stopped", 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM); show_notification("Streaming has stopped", 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
} else { } else {
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
show_notification("Streaming stopped because of an error", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM); show_notification("Streaming stopped because of an error. Verify if settings are correct", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
} }
break; break;
} }
@@ -1399,7 +1411,7 @@ namespace gsr {
} }
} else { } else {
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
show_notification("Failed to start/save recording", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); show_notification("Failed to start/save recording. Verify if settings are correct", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
} }
} }
@@ -1574,8 +1586,6 @@ namespace gsr {
// TODO: Also check x11 window when enabled (check if capture_target is a decminal/hex number) // TODO: Also check x11 window when enabled (check if capture_target is a decminal/hex number)
if(capture_target == "focused") { if(capture_target == "focused") {
return capture_options.focused; return capture_options.focused;
} else if(capture_target == "screen") {
return capture_options.screen;
} else if(capture_target == "portal") { } else if(capture_target == "portal") {
return capture_options.portal; return capture_options.portal;
} else { } else {

View File

@@ -64,9 +64,6 @@ namespace gsr {
// record_area_box->add_item("Window", "window"); // record_area_box->add_item("Window", "window");
if(capture_options.focused) if(capture_options.focused)
record_area_box->add_item("Follow focused window", "focused"); record_area_box->add_item("Follow focused window", "focused");
// Do we really need this? it's only available on nvidia x11
//if(capture_options.screen)
// record_area_box->add_item("All monitors", "screen");
for(const auto &monitor : capture_options.monitors) { for(const auto &monitor : capture_options.monitors) {
char name[256]; char name[256];
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y); snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
@@ -896,6 +893,7 @@ namespace gsr {
} }
void SettingsPage::save() { void SettingsPage::save() {
Config prev_config = config;
switch(type) { switch(type) {
case Type::REPLAY: case Type::REPLAY:
save_replay(); save_replay();
@@ -908,6 +906,9 @@ namespace gsr {
break; break;
} }
save_config(config); save_config(config);
if(on_config_changed && config != prev_config)
on_config_changed();
} }
static const std::string* get_application_audio_by_name_case_insensitive(const std::vector<std::string> &application_audio, const std::string &name) { static const std::string* get_application_audio_by_name_case_insensitive(const std::vector<std::string> &application_audio, const std::string &name) {

View File

@@ -18,6 +18,10 @@
#define GSR_UI_VIRTUAL_KEYBOARD_NAME "gsr-ui virtual keyboard" #define GSR_UI_VIRTUAL_KEYBOARD_NAME "gsr-ui virtual keyboard"
#define KEY_RELEASE 0
#define KEY_PRESS 1
#define KEY_REPEAT 2
/* /*
We could get initial keyboard state with: We could get initial keyboard state with:
unsigned char key_states[KEY_MAX/8 + 1]; unsigned char key_states[KEY_MAX/8 + 1];
@@ -28,6 +32,22 @@ static bool keyboard_event_has_exclusive_grab(const keyboard_event *self) {
return self->uinput_fd > 0; return self->uinput_fd > 0;
} }
static void keyboard_event_send_virtual_keyboard_event(keyboard_event *self, uint16_t code, int32_t value) {
if(self->uinput_fd <= 0)
return;
struct input_event event = {0};
event.type = EV_KEY;
event.code = code;
event.value = value;
write(self->uinput_fd, &event, sizeof(event));
event.type = EV_SYN;
event.code = 0;
event.value = SYN_REPORT;
write(self->uinput_fd, &event, sizeof(event));
}
static void keyboard_event_process_input_event_data(keyboard_event *self, const event_extra_data *extra_data, int fd, key_callback callback, void *userdata) { static void keyboard_event_process_input_event_data(keyboard_event *self, const event_extra_data *extra_data, int fd, key_callback callback, void *userdata) {
struct input_event event; struct input_event event;
if(read(fd, &event, sizeof(event)) != sizeof(event)) { if(read(fd, &event, sizeof(event)) != sizeof(event)) {
@@ -35,12 +55,31 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, const
return; return;
} }
// value = 1 == key pressed //if(event.type == EV_KEY && event.code == KEY_A && event.value == KEY_PRESS) {
//if(event.type == EV_KEY && event.code == KEY_A && event.value == 1) {
//fprintf(stderr, "fd: %d, type: %d, pressed %d, value: %d\n", fd, event.type, event.code, event.value); //fprintf(stderr, "fd: %d, type: %d, pressed %d, value: %d\n", fd, event.type, event.code, event.value);
//} //}
if(event.type == EV_KEY) { if(event.type == EV_KEY) {
/*
TODO: This is a hack! if a keyboard is grabbed while a key is being repeated then the key release will not be registered properly.
To deal with this we ignore repeat key that is sent without without pressed first and when the key is released we send key press and key release.
Maybe this needs to be done for all keys? Find a better solution if there is one!
*/
const bool first_key_event = !self->has_received_key_event;
self->has_received_key_event = true;
if(first_key_event && event.value == KEY_REPEAT)
self->repeat_key_to_ignore = (int32_t)event.code;
if(self->repeat_key_to_ignore > 0 && event.code == self->repeat_key_to_ignore) {
if(event.value == KEY_RELEASE) {
self->repeat_key_to_ignore = 0;
keyboard_event_send_virtual_keyboard_event(self, event.code, KEY_PRESS);
keyboard_event_send_virtual_keyboard_event(self, event.code, KEY_RELEASE);
}
return;
}
switch(event.code) { switch(event.code) {
case KEY_LEFTSHIFT: case KEY_LEFTSHIFT:
self->lshift_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED; self->lshift_button_state = event.value >= 1 ? KEYBOARD_BUTTON_PRESSED : KEYBOARD_BUTTON_RELEASED;
@@ -141,10 +180,11 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
unsigned char key_bits[KEY_MAX/8 + 1] = {0}; unsigned char key_bits[KEY_MAX/8 + 1] = {0};
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits); ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits);
const bool supports_key_events = key_bits[KEY_A/8] & (1 << (KEY_A % 8)); const bool supports_key_events = key_bits[KEY_A/8] & (1 << (KEY_A % 8));
const bool supports_mouse_events = key_bits[BTN_MOUSE/8] & (1 << (BTN_MOUSE % 8)); const bool supports_mouse_events = key_bits[BTN_MOUSE/8] & (1 << (BTN_MOUSE % 8));
//const bool supports_touch_events = key_bits[BTN_TOUCH/8] & (1 << (BTN_TOUCH % 8));
const bool supports_joystick_events = key_bits[BTN_JOYSTICK/8] & (1 << (BTN_JOYSTICK % 8)); const bool supports_joystick_events = key_bits[BTN_JOYSTICK/8] & (1 << (BTN_JOYSTICK % 8));
const bool supports_wheel_events = key_bits[BTN_WHEEL/8] & (1 << (BTN_WHEEL % 8)); const bool supports_wheel_events = key_bits[BTN_WHEEL/8] & (1 << (BTN_WHEEL % 8));
if(supports_key_events && !supports_mouse_events && !supports_joystick_events && !supports_wheel_events) { if(supports_key_events && !supports_mouse_events && !supports_joystick_events && !supports_wheel_events) {
if(self->num_event_polls < MAX_EVENT_POLLS) { if(self->num_event_polls < MAX_EVENT_POLLS) {
bool grabbed = false; bool grabbed = false;

View File

@@ -43,6 +43,8 @@ typedef struct {
int hotplug_event_index; int hotplug_event_index;
int uinput_fd; int uinput_fd;
bool stdout_failed; bool stdout_failed;
int32_t repeat_key_to_ignore;
bool has_received_key_event;
hotplug_event hotplug_ev; hotplug_event hotplug_ev;