Show recording/replay duration in notification

This commit is contained in:
dec05eba
2025-07-22 01:10:58 +02:00
parent 315fab99a8
commit d64e698eb1
3 changed files with 105 additions and 27 deletions

2
TODO
View File

@@ -207,3 +207,5 @@ Disable hotkeys if virtual keyboard is found (either at startup or after running
Support localization. Support localization.
Add option to not capture cursor in screenshot when doing region/window capture. Add option to not capture cursor in screenshot when doing region/window capture.
Window selection doesn't work when a window is fullscreen on x11.

View File

@@ -94,6 +94,7 @@ namespace gsr {
void close_gpu_screen_recorder_output(); void close_gpu_screen_recorder_output();
double get_time_passed_in_replay_buffer_seconds();
void update_notification_process_status(); void update_notification_process_status();
void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type); void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type);
void on_replay_saved(const char *replay_saved_filepath); void on_replay_saved(const char *replay_saved_filepath);
@@ -220,6 +221,12 @@ namespace gsr {
bool try_replay_startup = true; bool try_replay_startup = true;
bool replay_recording = false; bool replay_recording = false;
int replay_save_duration_min = 0; int replay_save_duration_min = 0;
double replay_buffer_save_duration_sec = 0.0;
mgl::Clock replay_duration_clock;
double replay_saved_duration_sec = 0.0;
bool replay_restart_on_save = false;
mgl::Clock recording_duration_clock;
AudioPlayer audio_player; AudioPlayer audio_player;

View File

@@ -27,6 +27,7 @@
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <inttypes.h> #include <inttypes.h>
#include <math.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
@@ -49,7 +50,7 @@ namespace gsr {
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.5; 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 const double notification_timeout_seconds = 2.5; static const double notification_timeout_seconds = 3.0;
static const double notification_error_timeout_seconds = 5.0; static const double notification_error_timeout_seconds = 5.0;
static const double cursor_tracker_update_timeout_sec = 0.1; static const double cursor_tracker_update_timeout_sec = 0.1;
@@ -1573,7 +1574,7 @@ namespace gsr {
return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target); return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
} }
static std::string capture_target_get_notification_name(const char *capture_target) { static std::string capture_target_get_notification_name(const char *capture_target, bool save) {
std::string result; std::string result;
if(is_capture_target_monitor(capture_target)) { if(is_capture_target_monitor(capture_target)) {
result = "this monitor"; result = "this monitor";
@@ -1585,9 +1586,11 @@ namespace gsr {
sscanf(capture_target, "%" PRIi64, &window_id); sscanf(capture_target, "%" PRIi64, &window_id);
const std::optional<std::string> window_title = get_window_title(display, window_id); const std::optional<std::string> window_title = get_window_title(display, window_id);
if(window_title) { if(save) {
result = "window";
} else if(window_title) {
result = strip(window_title.value()); result = strip(window_title.value());
truncate_string(result, 20); truncate_string(result, 30);
result = "window \"" + result + "\""; result = "window \"" + result + "\"";
} else { } else {
result = std::string("window ") + capture_target; result = std::string("window ") + capture_target;
@@ -1774,6 +1777,45 @@ namespace gsr {
return result; return result;
} }
static std::string to_duration_string(double duration_sec) {
int seconds = ceil(duration_sec);
const int hours = seconds / 60 / 60;
seconds -= (hours * 60 * 60);
const int minutes = seconds / 60;
seconds -= (minutes * 60);
std::string result;
if(hours > 0)
result += std::to_string(hours) + " hour" + (hours == 1 ? "" : "s");
if(minutes > 0) {
if(!result.empty())
result += " ";
result += std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s");
}
if(seconds > 0 || (hours == 0 && minutes == 0)) {
if(!result.empty())
result += " ";
result += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
}
fprintf(stderr, "to duration string: %f, %d, %d, %d\n", duration_sec, seconds, minutes, hours);
return result;
}
double Overlay::get_time_passed_in_replay_buffer_seconds() {
double replay_duration_sec = replay_saved_duration_sec;
if(replay_duration_sec > replay_buffer_save_duration_sec)
replay_duration_sec = replay_buffer_save_duration_sec;
if(replay_save_duration_min > 0 && replay_duration_sec > replay_save_duration_min * 60)
replay_duration_sec = replay_save_duration_min * 60;
return replay_duration_sec;
}
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) { void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
mgl_context *context = mgl_get_context(); mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection; Display *display = (Display*)context->connection;
@@ -1794,7 +1836,7 @@ namespace gsr {
const std::string new_video_filepath = video_directory + "/" + video_filename; const std::string new_video_filepath = video_directory + "/" + video_filename;
rename(video_filepath, new_video_filepath.c_str()); rename(video_filepath, new_video_filepath.c_str());
truncate_string(focused_window_name, 20); truncate_string(focused_window_name, 40);
const char *capture_target = nullptr; const char *capture_target = nullptr;
char msg[512]; char msg[512];
@@ -1803,7 +1845,10 @@ namespace gsr {
if(!config.record_config.show_video_saved_notifications) if(!config.record_config.show_video_saved_notifications)
return; return;
snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str()); const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds());
snprintf(msg, sizeof(msg), "Saved a %s recording of %s\nto \"%s\"",
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
capture_target = recording_capture_target.c_str(); capture_target = recording_capture_target.c_str();
break; break;
} }
@@ -1811,13 +1856,10 @@ namespace gsr {
if(!config.replay_config.show_replay_saved_notifications) if(!config.replay_config.show_replay_saved_notifications)
return; return;
char duration[32]; const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
if(replay_save_duration_min > 0) snprintf(msg, sizeof(msg), "Saved a %s replay of %s\nto \"%s\"",
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min); duration_str.c_str(),
else capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
snprintf(duration, sizeof(duration), " ");
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
capture_target = recording_capture_target.c_str(); capture_target = recording_capture_target.c_str();
break; break;
} }
@@ -1825,7 +1867,8 @@ namespace gsr {
if(!config.screenshot_config.show_screenshot_saved_notifications) if(!config.screenshot_config.show_screenshot_saved_notifications)
return; return;
snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str(), focused_window_name.c_str()); snprintf(msg, sizeof(msg), "Saved a screenshot of %s\nto \"%s\"",
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
capture_target = screenshot_capture_target.c_str(); capture_target = screenshot_capture_target.c_str();
break; break;
} }
@@ -1851,14 +1894,12 @@ namespace gsr {
if(config.replay_config.save_video_in_game_folder) { if(config.replay_config.save_video_in_game_folder) {
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY); save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
} else if(config.replay_config.show_replay_saved_notifications) { } else if(config.replay_config.show_replay_saved_notifications) {
char duration[32]; const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
if(replay_save_duration_min > 0)
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
else
snprintf(duration, sizeof(duration), " ");
char msg[512]; char msg[512];
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); snprintf(msg, sizeof(msg), "Saved a %s replay of %s",
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str());
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str()); show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
} }
} }
@@ -2006,7 +2047,8 @@ namespace gsr {
save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT); save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT);
} else if(config.screenshot_config.show_screenshot_saved_notifications) { } else if(config.screenshot_config.show_screenshot_saved_notifications) {
char msg[512]; char msg[512];
snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str()); snprintf(msg, sizeof(msg), "Saved a screenshot of %s",
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str());
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str()); show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str());
} }
} else { } else {
@@ -2104,8 +2146,12 @@ namespace gsr {
if(config.record_config.save_video_in_game_folder) { if(config.record_config.save_video_in_game_folder) {
save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD); save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
} else if(config.record_config.show_video_saved_notifications) { } else if(config.record_config.show_video_saved_notifications) {
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds());
char msg[512]; char msg[512];
snprintf(msg, sizeof(msg), "Saved a recording of %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); snprintf(msg, sizeof(msg), "Saved a %s recording of %s",
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str());
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
} }
} else { } else {
@@ -2394,6 +2440,9 @@ namespace gsr {
replay_save_duration_min = 0; replay_save_duration_min = 0;
replay_save_show_notification = true; replay_save_show_notification = true;
replay_save_clock.restart(); replay_save_clock.restart();
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
if(replay_restart_on_save)
replay_duration_clock.restart();
kill(gpu_screen_recorder_process, SIGUSR1); kill(gpu_screen_recorder_process, SIGUSR1);
} }
@@ -2404,6 +2453,7 @@ namespace gsr {
replay_save_duration_min = 1; replay_save_duration_min = 1;
replay_save_show_notification = true; replay_save_show_notification = true;
replay_save_clock.restart(); replay_save_clock.restart();
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
kill(gpu_screen_recorder_process, SIGRTMIN+3); kill(gpu_screen_recorder_process, SIGRTMIN+3);
} }
@@ -2414,6 +2464,7 @@ namespace gsr {
replay_save_duration_min = 10; replay_save_duration_min = 10;
replay_save_show_notification = true; replay_save_show_notification = true;
replay_save_clock.restart(); replay_save_clock.restart();
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
kill(gpu_screen_recorder_process, SIGRTMIN+5); kill(gpu_screen_recorder_process, SIGRTMIN+5);
} }
@@ -2564,6 +2615,9 @@ namespace gsr {
if(config.replay_config.restart_replay_on_save && gsr_info.system_info.gsr_version >= GsrVersion{5, 0, 3}) { if(config.replay_config.restart_replay_on_save && gsr_info.system_info.gsr_version >= GsrVersion{5, 0, 3}) {
args.push_back("-restart-replay-on-save"); args.push_back("-restart-replay-on-save");
args.push_back("yes"); args.push_back("yes");
replay_restart_on_save = true;
} else {
replay_restart_on_save = false;
} }
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) { if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) {
@@ -2603,13 +2657,17 @@ 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) {
char msg[256]; char msg[256];
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str()); show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
} }
if(config.replay_config.record_options.record_area_option == "portal") if(config.replay_config.record_options.record_area_option == "portal")
hide_ui = true; hide_ui = true;
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
// selected what to capture and accepted it.
replay_duration_clock.restart();
replay_buffer_save_duration_sec = config.replay_config.replay_time;
return true; return true;
} }
@@ -2630,6 +2688,10 @@ namespace gsr {
if(config.record_config.show_recording_started_notifications) if(config.record_config.show_recording_started_notifications)
show_notification("Started recording in the replay session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD); show_notification("Started recording in the replay session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
update_ui_recording_started(); update_ui_recording_started();
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
// selected what to capture and accepted it.
recording_duration_clock.restart();
} }
replay_recording = true; replay_recording = true;
kill(gpu_screen_recorder_process, SIGRTMIN); kill(gpu_screen_recorder_process, SIGRTMIN);
@@ -2647,6 +2709,10 @@ namespace gsr {
if(config.record_config.show_recording_started_notifications) if(config.record_config.show_recording_started_notifications)
show_notification("Started recording in the streaming session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD); show_notification("Started recording in the streaming session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
update_ui_recording_started(); update_ui_recording_started();
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
// selected what to capture and accepted it.
recording_duration_clock.restart();
} }
replay_recording = true; replay_recording = true;
kill(gpu_screen_recorder_process, SIGRTMIN); kill(gpu_screen_recorder_process, SIGRTMIN);
@@ -2766,12 +2832,16 @@ namespace gsr {
// 1... // 1...
if(config.record_config.show_recording_started_notifications) { if(config.record_config.show_recording_started_notifications) {
char msg[256]; char msg[256];
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
} }
if(config.record_config.record_options.record_area_option == "portal") if(config.record_config.record_options.record_area_option == "portal")
hide_ui = true; hide_ui = true;
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
// selected what to capture and accepted it.
recording_duration_clock.restart();
} }
static std::string streaming_get_url(const Config &config) { static std::string streaming_get_url(const Config &config) {
@@ -2943,7 +3013,7 @@ namespace gsr {
// to see when the program has exit. // to see when the program has exit.
if(config.streaming_config.show_streaming_started_notifications) { if(config.streaming_config.show_streaming_started_notifications) {
char msg[256]; char msg[256];
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str()); show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
} }
@@ -2987,7 +3057,6 @@ namespace gsr {
if(record_area_option == "region" && !finished_selection) { if(record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [this, force_type]() { on_region_selected = [this, force_type]() {
usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this
on_press_take_screenshot(true, force_type); on_press_take_screenshot(true, force_type);
}; };
return; return;