Replay on startup: wait until audio devices are available before turning replay on

This commit is contained in:
dec05eba
2025-02-25 17:33:21 +01:00
parent d9a1e5c2eb
commit f0bbbbe4a9
8 changed files with 108 additions and 39 deletions

View File

@@ -11,6 +11,15 @@
namespace gsr { namespace gsr {
struct SupportedCaptureOptions; struct SupportedCaptureOptions;
enum class ReplayStartupMode {
DONT_TURN_ON_AUTOMATICALLY,
TURN_ON_AT_SYSTEM_STARTUP,
TURN_ON_AT_FULLSCREEN,
TURN_ON_AT_POWER_SUPPLY_CONNECTED
};
ReplayStartupMode replay_startup_string_to_type(const char *startup_mode_str);
struct ConfigHotkey { struct ConfigHotkey {
int64_t key = 0; // Mgl key int64_t key = 0; // Mgl key
uint32_t modifiers = 0; // HotkeyModifier uint32_t modifiers = 0; // HotkeyModifier

View File

@@ -92,6 +92,7 @@ namespace gsr {
void replay_status_update_status(); void replay_status_update_status();
void update_focused_fullscreen_status(); void update_focused_fullscreen_status();
void update_power_supply_status(); void update_power_supply_status();
void update_system_startup_status();
void on_stop_recording(int exit_code); void on_stop_recording(int exit_code);
@@ -108,7 +109,7 @@ namespace gsr {
void update_ui_replay_stopped(); void update_ui_replay_stopped();
void on_press_save_replay(); void on_press_save_replay();
void on_press_start_replay(bool disable_notification); bool on_press_start_replay(bool disable_notification);
void on_press_start_record(); void on_press_start_record();
void on_press_start_stream(); void on_press_start_stream();
void on_press_take_screenshot(); void on_press_take_screenshot();
@@ -197,6 +198,8 @@ namespace gsr {
mgl::Clock replay_save_clock; mgl::Clock replay_save_clock;
bool replay_save_show_notification = false; bool replay_save_show_notification = false;
ReplayStartupMode replay_startup_mode = ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;
bool try_replay_startup = true;
AudioPlayer audio_player; AudioPlayer audio_player;
}; };

View File

@@ -12,14 +12,14 @@ namespace gsr {
}; };
// Arguments ending with NULL // Arguments ending with NULL
bool exec_program_daemonized(const char **args); bool exec_program_daemonized(const char **args, bool debug = true);
// Arguments ending with NULL. |read_fd| can be NULL // Arguments ending with NULL. |read_fd| can be NULL
pid_t exec_program(const char **args, int *read_fd); pid_t exec_program(const char **args, int *read_fd, bool debug = true);
// Arguments ending with NULL. Returns the exit status of the program or -1 on error // Arguments ending with NULL. Returns the exit status of the program or -1 on error
int exec_program_get_stdout(const char **args, std::string &result); int exec_program_get_stdout(const char **args, std::string &result, bool debug = true);
// Arguments ending with NULL. Returns the exit status of the program or -1 on error. // Arguments ending with NULL. Returns the exit status of the program or -1 on error.
// This works the same as |exec_program_get_stdout|, except on flatpak where this runs the program on the // This works the same as |exec_program_get_stdout|, except on flatpak where this runs the program on the
// host machine with flatpak-spawn --host // host machine with flatpak-spawn --host
int exec_program_on_host_get_stdout(const char **args, std::string &result); int exec_program_on_host_get_stdout(const char **args, std::string &result, bool debug = true);
pid_t pidof(const char *process_name, pid_t ignore_pid); pid_t pidof(const char *process_name, pid_t ignore_pid);
} }

View File

@@ -6,6 +6,7 @@
#include <limits.h> #include <limits.h>
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <string.h>
#include <assert.h> #include <assert.h>
#include <mglpp/window/Keyboard.hpp> #include <mglpp/window/Keyboard.hpp>
@@ -45,6 +46,19 @@ namespace gsr {
} }
} }
ReplayStartupMode replay_startup_string_to_type(const char *startup_mode_str) {
if(strcmp(startup_mode_str, "dont_turn_on_automatically") == 0)
return ReplayStartupMode::DONT_TURN_ON_AUTOMATICALLY;
else if(strcmp(startup_mode_str, "turn_on_at_system_startup") == 0)
return ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;
else if(strcmp(startup_mode_str, "turn_on_at_fullscreen") == 0)
return ReplayStartupMode::TURN_ON_AT_FULLSCREEN;
else if(strcmp(startup_mode_str, "turn_on_at_power_supply_connected") == 0)
return ReplayStartupMode::TURN_ON_AT_POWER_SUPPLY_CONNECTED;
else
return ReplayStartupMode::DONT_TURN_ON_AUTOMATICALLY;
}
bool ConfigHotkey::operator==(const ConfigHotkey &other) const { bool ConfigHotkey::operator==(const ConfigHotkey &other) const {
return key == other.key && modifiers == other.modifiers; return key == other.key && modifiers == other.modifiers;
} }

View File

@@ -258,7 +258,7 @@ namespace gsr {
std::string stdout_str; std::string stdout_str;
const char *args[] = { "gpu-screen-recorder", "--list-audio-devices", nullptr }; const char *args[] = { "gpu-screen-recorder", "--list-audio-devices", nullptr };
if(exec_program_get_stdout(args, stdout_str) != 0) { if(exec_program_get_stdout(args, stdout_str, false) != 0) {
fprintf(stderr, "error: 'gpu-screen-recorder --list-audio-devices' failed\n"); fprintf(stderr, "error: 'gpu-screen-recorder --list-audio-devices' failed\n");
return audio_devices; return audio_devices;
} }

View File

@@ -22,6 +22,7 @@
#include <poll.h> #include <poll.h>
#include <malloc.h> #include <malloc.h>
#include <stdexcept> #include <stdexcept>
#include <algorithm>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
@@ -41,7 +42,7 @@ extern "C" {
namespace gsr { namespace gsr {
static const mgl::Color bg_color(0, 0, 0, 100); static const mgl::Color bg_color(0, 0, 0, 100);
static const double force_window_on_top_timeout_seconds = 1.0; static const double force_window_on_top_timeout_seconds = 1.0;
static const double replay_status_update_check_timeout_seconds = 1.0; static const double replay_status_update_check_timeout_seconds = 1.5;
static const double replay_saving_notification_timeout_seconds = 0.5; static const double replay_saving_notification_timeout_seconds = 0.5;
static mgl::Texture texture_from_ximage(XImage *img) { static mgl::Texture texture_from_ximage(XImage *img) {
@@ -436,9 +437,7 @@ namespace gsr {
init_color_theme(config, this->gsr_info); init_color_theme(config, this->gsr_info);
power_supply_online_filepath = get_power_supply_online_filepath(); power_supply_online_filepath = get_power_supply_online_filepath();
replay_startup_mode = replay_startup_string_to_type(config.replay_config.turn_on_replay_automatically_mode.c_str());
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") if(config.main_config.hotkeys_enable_option == "enable_hotkeys")
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL); global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
@@ -1070,6 +1069,7 @@ namespace gsr {
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]() { replay_settings_page->on_config_changed = [this]() {
replay_startup_mode = replay_startup_string_to_type(config.replay_config.turn_on_replay_automatically_mode.c_str());
if(recording_status == RecordingStatus::REPLAY) 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); 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);
}; };
@@ -1656,6 +1656,33 @@ namespace gsr {
gpu_screen_recorder_screenshot_process = -1; gpu_screen_recorder_screenshot_process = -1;
} }
static bool starts_with(std::string_view str, const char *substr) {
size_t len = strlen(substr);
return str.size() >= len && memcmp(str.data(), substr, len) == 0;
}
static bool are_all_audio_tracks_available_to_capture(const std::vector<std::string> &audio_tracks) {
const auto audio_devices = get_audio_devices();
for(const std::string &audio_track : audio_tracks) {
std::string_view audio_track_name(audio_track.c_str());
const bool is_app_audio = starts_with(audio_track_name, "app:");
if(is_app_audio)
continue;
if(starts_with(audio_track_name, "device:"))
audio_track_name.remove_prefix(7);
auto it = std::find_if(audio_devices.begin(), audio_devices.end(), [&](const auto &audio_device) {
return audio_device.name == audio_track_name;
});
if(it == audio_devices.end()) {
//fprintf(stderr, "Audio not ready\n");
return false;
}
}
return true;
}
void Overlay::replay_status_update_status() { void Overlay::replay_status_update_status() {
if(replay_status_update_clock.get_elapsed_time_seconds() < replay_status_update_check_timeout_seconds) if(replay_status_update_clock.get_elapsed_time_seconds() < replay_status_update_check_timeout_seconds)
return; return;
@@ -1663,10 +1690,11 @@ namespace gsr {
replay_status_update_clock.restart(); replay_status_update_clock.restart();
update_focused_fullscreen_status(); update_focused_fullscreen_status();
update_power_supply_status(); update_power_supply_status();
update_system_startup_status();
} }
void Overlay::update_focused_fullscreen_status() { void Overlay::update_focused_fullscreen_status() {
if(config.replay_config.turn_on_replay_automatically_mode != "turn_on_at_fullscreen") if(replay_startup_mode != ReplayStartupMode::TURN_ON_AT_FULLSCREEN)
return; return;
mgl_context *context = mgl_get_context(); mgl_context *context = mgl_get_context();
@@ -1679,28 +1707,40 @@ namespace gsr {
const bool prev_focused_window_is_fullscreen = focused_window_is_fullscreen; const bool prev_focused_window_is_fullscreen = focused_window_is_fullscreen;
focused_window_is_fullscreen = focused_window != 0 && window_is_fullscreen(display, focused_window); focused_window_is_fullscreen = focused_window != 0 && window_is_fullscreen(display, focused_window);
if(focused_window_is_fullscreen != prev_focused_window_is_fullscreen) { if(focused_window_is_fullscreen != prev_focused_window_is_fullscreen) {
if(recording_status == RecordingStatus::NONE && focused_window_is_fullscreen) if(recording_status == RecordingStatus::NONE && focused_window_is_fullscreen) {
on_press_start_replay(false); if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks))
else if(recording_status == RecordingStatus::REPLAY && !focused_window_is_fullscreen) on_press_start_replay(false);
} else if(recording_status == RecordingStatus::REPLAY && !focused_window_is_fullscreen) {
on_press_start_replay(true); on_press_start_replay(true);
}
} }
} }
// TODO: Instead of checking power supply status periodically listen to power supply event // TODO: Instead of checking power supply status periodically listen to power supply event
void Overlay::update_power_supply_status() { void Overlay::update_power_supply_status() {
if(config.replay_config.turn_on_replay_automatically_mode != "turn_on_at_power_supply_connected") if(replay_startup_mode != ReplayStartupMode::TURN_ON_AT_POWER_SUPPLY_CONNECTED)
return; return;
const bool prev_power_supply_status = power_supply_connected; const bool prev_power_supply_status = power_supply_connected;
power_supply_connected = power_supply_online_filepath.empty() || power_supply_is_connected(power_supply_online_filepath.c_str()); power_supply_connected = power_supply_online_filepath.empty() || power_supply_is_connected(power_supply_online_filepath.c_str());
if(power_supply_connected != prev_power_supply_status) { if(power_supply_connected != prev_power_supply_status) {
if(recording_status == RecordingStatus::NONE && power_supply_connected) if(recording_status == RecordingStatus::NONE && power_supply_connected) {
on_press_start_replay(false); if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks))
else if(recording_status == RecordingStatus::REPLAY && !power_supply_connected) on_press_start_replay(false);
} else if(recording_status == RecordingStatus::REPLAY && !power_supply_connected) {
on_press_start_replay(false); on_press_start_replay(false);
}
} }
} }
void Overlay::update_system_startup_status() {
if(replay_startup_mode != ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP || recording_status != RecordingStatus::NONE || !try_replay_startup)
return;
if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks))
on_press_start_replay(true);
}
void Overlay::on_stop_recording(int exit_code) { void Overlay::on_stop_recording(int exit_code) {
if(exit_code == 0) { if(exit_code == 0) {
if(config.record_config.save_video_in_game_folder) { if(config.record_config.save_video_in_game_folder) {
@@ -1816,11 +1856,6 @@ namespace gsr {
return container; return container;
} }
static bool starts_with(std::string_view str, const char *substr) {
size_t len = strlen(substr);
return str.size() >= len && memcmp(str.data(), substr, len) == 0;
}
static std::vector<std::string> create_audio_tracks_real_names(const std::vector<std::string> &audio_tracks, bool application_audio_invert, const GsrInfo &gsr_info) { static std::vector<std::string> create_audio_tracks_real_names(const std::vector<std::string> &audio_tracks, bool application_audio_invert, const GsrInfo &gsr_info) {
std::vector<std::string> result; std::vector<std::string> result;
for(const std::string &audio_track : audio_tracks) { for(const std::string &audio_track : audio_tracks) {
@@ -1906,21 +1941,22 @@ namespace gsr {
kill(gpu_screen_recorder_process, SIGUSR1); kill(gpu_screen_recorder_process, SIGUSR1);
} }
void Overlay::on_press_start_replay(bool disable_notification) { bool Overlay::on_press_start_replay(bool disable_notification) {
switch(recording_status) { switch(recording_status) {
case RecordingStatus::NONE: case RecordingStatus::NONE:
case RecordingStatus::REPLAY: case RecordingStatus::REPLAY:
break; break;
case RecordingStatus::RECORD: case RecordingStatus::RECORD:
show_notification("Unable to start replay when recording.\nStop recording before starting replay.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD); show_notification("Unable to start replay when recording.\nStop recording before starting replay.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
return; return false;
case RecordingStatus::STREAM: case RecordingStatus::STREAM:
show_notification("Unable to start replay when streaming.\nStop streaming before starting replay.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM); show_notification("Unable to start replay when streaming.\nStop streaming before starting replay.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
return; return false;
} }
paused = false; paused = false;
replay_save_show_notification = false; replay_save_show_notification = false;
try_replay_startup = false;
// window->close(); // window->close();
// usleep(1000 * 50); // 50 milliseconds // usleep(1000 * 50); // 50 milliseconds
@@ -1942,14 +1978,15 @@ namespace gsr {
// TODO: Show this with a slight delay to make sure it doesn't show up in the video // TODO: Show this with a slight delay to make sure it doesn't show up in the video
if(!disable_notification && config.replay_config.show_replay_stopped_notifications) if(!disable_notification && config.replay_config.show_replay_stopped_notifications)
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);
return;
return true;
} }
if(!validate_capture_target(gsr_info, config.replay_config.record_options.record_area_option)) { if(!validate_capture_target(gsr_info, config.replay_config.record_options.record_area_option)) {
char err_msg[256]; char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", config.replay_config.record_options.record_area_option.c_str()); snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", config.replay_config.record_options.record_area_option.c_str());
show_notification(err_msg, 3.0, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::REPLAY); show_notification(err_msg, 3.0, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::REPLAY);
return; return false;
} }
// TODO: Validate input, fallback to valid values // TODO: Validate input, fallback to valid values
@@ -2024,6 +2061,8 @@ namespace gsr {
// to see when the program has exit. // to see when the program has exit.
if(!disable_notification && config.replay_config.show_replay_started_notifications) if(!disable_notification && config.replay_config.show_replay_started_notifications)
show_notification("Replay has started", 3.0, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY); show_notification("Replay has started", 3.0, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY);
return true;
} }
void Overlay::on_press_start_record() { void Overlay::on_press_start_record() {

View File

@@ -40,12 +40,13 @@ namespace gsr {
return num_args; return num_args;
} }
bool exec_program_daemonized(const char **args) { bool exec_program_daemonized(const char **args, bool debug) {
/* 1 argument */ /* 1 argument */
if(args[0] == nullptr) if(args[0] == nullptr)
return false; return false;
debug_print_args(args); if(debug)
debug_print_args(args);
const pid_t pid = vfork(); const pid_t pid = vfork();
if(pid == -1) { if(pid == -1) {
@@ -72,7 +73,7 @@ namespace gsr {
return true; return true;
} }
pid_t exec_program(const char **args, int *read_fd) { pid_t exec_program(const char **args, int *read_fd, bool debug) {
if(read_fd) if(read_fd)
*read_fd = -1; *read_fd = -1;
@@ -84,7 +85,8 @@ namespace gsr {
if(pipe(fds) == -1) if(pipe(fds) == -1)
return -1; return -1;
debug_print_args(args); if(debug)
debug_print_args(args);
const pid_t pid = vfork(); const pid_t pid = vfork();
if(pid == -1) { if(pid == -1) {
@@ -110,10 +112,10 @@ namespace gsr {
} }
} }
int exec_program_get_stdout(const char **args, std::string &result) { int exec_program_get_stdout(const char **args, std::string &result, bool debug) {
result.clear(); result.clear();
int read_fd = -1; int read_fd = -1;
const pid_t process_id = exec_program(args, &read_fd); const pid_t process_id = exec_program(args, &read_fd, debug);
if(process_id == -1) if(process_id == -1)
return -1; return -1;
@@ -152,7 +154,7 @@ namespace gsr {
return exit_status; return exit_status;
} }
int exec_program_on_host_get_stdout(const char **args, std::string &result) { int exec_program_on_host_get_stdout(const char **args, std::string &result, bool debug) {
if(count_num_args(args) > 64 - 3) { if(count_num_args(args) > 64 - 3) {
fprintf(stderr, "Error: too many arguments when trying to launch \"%s\"\n", args[0]); fprintf(stderr, "Error: too many arguments when trying to launch \"%s\"\n", args[0]);
return -1; return -1;
@@ -170,9 +172,9 @@ namespace gsr {
} }
modified_args[i] = arg; modified_args[i] = arg;
} }
return exec_program_get_stdout(modified_args, result); return exec_program_get_stdout(modified_args, result, debug);
} else { } else {
return exec_program_get_stdout(args, result); return exec_program_get_stdout(args, result, debug);
} }
} }

View File

@@ -191,8 +191,10 @@ namespace gsr {
std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_image_format_box() { std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_image_format_box() {
auto box = std::make_unique<ComboBox>(&get_theme().body_font); auto box = std::make_unique<ComboBox>(&get_theme().body_font);
box->add_item("jpg", "jpg"); if(gsr_info->supported_image_formats.jpeg)
box->add_item("png", "png"); box->add_item("jpg", "jpg");
if(gsr_info->supported_image_formats.png)
box->add_item("png", "png");
image_format_box_ptr = box.get(); image_format_box_ptr = box.get();
return box; return box;
} }