From 61c9b4918ed81a6ad439748f8bcb1c6f9b0cf65e Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 22 Sep 2024 18:17:46 +0200 Subject: Save recording status to file to reload it when gsr overlay restarts --- TODO | 6 +- include/Overlay.hpp | 20 +++++ include/Process.hpp | 3 +- include/Utils.hpp | 5 ++ src/Overlay.cpp | 222 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/Process.cpp | 49 +----------- src/Utils.cpp | 31 ++++++++ 7 files changed, 270 insertions(+), 66 deletions(-) diff --git a/TODO b/TODO index eeda731..6c9a5b0 100644 --- a/TODO +++ b/TODO @@ -41,4 +41,8 @@ Save gsr info data to file to quick re-open. Support wayland (excluding gnome, or force xwayland on gnome). -Restart replay on system start if monitor resolution changes. \ No newline at end of file +Restart replay on system start if monitor resolution changes. + +Show warning when selecting hevc/av1 on amd because of amd driver/ffmpeg bug. + +Update gsr info and validate saved config when monitors update. The selected monitor/audio may no longer be available. \ No newline at end of file diff --git a/include/Overlay.hpp b/include/Overlay.hpp index bb366e7..bf3600f 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -15,6 +15,13 @@ namespace gsr { class DropdownButton; + enum class RecordingStatus { + NONE, + REPLAY, + RECORD, + STREAM + }; + class Overlay { public: Overlay(mgl::Window &window, std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs, mgl::Color bg_color); @@ -30,6 +37,17 @@ namespace gsr { void toggle_show(); bool is_open() const; private: + void update_gsr_process_status(); + + void load_program_status(); + void save_program_status(); + void load_program_pid(); + void save_program_pid(); + void recording_stopped_remove_runtime_files(); + + void update_ui_recording_started(); + void update_ui_recording_stopped(); + void on_press_start_replay(const std::string &id); void on_press_start_record(const std::string &id); void on_press_start_stream(const std::string &id); @@ -60,5 +78,7 @@ namespace gsr { DropdownButton *replay_dropdown_button_ptr = nullptr; DropdownButton *record_dropdown_button_ptr = nullptr; DropdownButton *stream_dropdown_button_ptr = nullptr; + + RecordingStatus recording_status = RecordingStatus::NONE; }; } \ No newline at end of file diff --git a/include/Process.hpp b/include/Process.hpp index bd76306..125a880 100644 --- a/include/Process.hpp +++ b/include/Process.hpp @@ -14,5 +14,6 @@ namespace gsr { bool exec_program_daemonized(const char **args); // Arguments ending with NULL pid_t exec_program(const char **args); - bool is_gpu_screen_recorder_running(pid_t &gsr_pid, GsrMode &mode); + // |output_buffer| should be at least PATH_MAX in size + bool read_cmdline_arg0(const char *filepath, char *output_buffer); } \ No newline at end of file diff --git a/include/Utils.hpp b/include/Utils.hpp index b18ab8e..8ca38b5 100644 --- a/include/Utils.hpp +++ b/include/Utils.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace gsr { struct KeyValue { @@ -23,10 +24,14 @@ namespace gsr { std::map get_xdg_variables(); std::string get_videos_dir(); + // Returns 0 on success int create_directory_recursive(char *path); bool file_get_content(const char *filepath, std::string &file_content); + bool file_overwrite(const char *filepath, const std::string &data); // Returns the path to the parent directory (ignoring trailing /) // of "." if there is no parent directory and the directory path is relative std::string get_parent_directory(std::string_view directory); + + std::optional get_gsr_runtime_dir(); } \ No newline at end of file diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 7445bc2..dcd18cf 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -2,6 +2,7 @@ #include "../include/Theme.hpp" #include "../include/Config.hpp" #include "../include/Process.hpp" +#include "../include/Utils.hpp" #include "../include/gui/StaticPage.hpp" #include "../include/gui/DropdownButton.hpp" #include "../include/gui/CustomRendererWidget.hpp" @@ -11,9 +12,9 @@ #include #include -// TODO: Remove -#include #include +#include +#include #include #include @@ -196,19 +197,21 @@ namespace gsr { close_button_widget({0.0f, 0.0f}) { memset(&window_texture, 0, sizeof(window_texture)); + load_program_status(); + load_program_pid(); } Overlay::~Overlay() { hide(); - if(gpu_screen_recorder_process > 0) { - kill(gpu_screen_recorder_process, SIGINT); - int status; - if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { - perror("waitpid failed"); - /* Ignore... */ - } - gpu_screen_recorder_process = -1; - } + // if(gpu_screen_recorder_process > 0) { + // kill(gpu_screen_recorder_process, SIGINT); + // int status; + // if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { + // perror("waitpid failed"); + // /* Ignore... */ + // } + // gpu_screen_recorder_process = -1; + // } } void Overlay::on_event(mgl::Event &event, mgl::Window &window) { @@ -224,6 +227,8 @@ namespace gsr { } void Overlay::draw(mgl::Window &window) { + update_gsr_process_status(); + if(!visible) return; @@ -400,6 +405,9 @@ namespace gsr { event.mouse_move.x = window.get_mouse_position().x; event.mouse_move.y = window.get_mouse_position().y; on_event(event, window); + + if(gpu_screen_recorder_process > 0 && recording_status == RecordingStatus::RECORD) + update_ui_recording_started(); } void Overlay::hide() { @@ -431,6 +439,185 @@ namespace gsr { return visible; } + void Overlay::update_gsr_process_status() { + if(gpu_screen_recorder_process <= 0) + return; + + errno = 0; + int status; + if(waitpid(gpu_screen_recorder_process, &status, WNOHANG) == 0) { + // Still running + return; + } + + int exit_code = -1; + // The process is no longer a child process since gsr overlay has restarted + if(errno == ECHILD) { + errno = 0; + kill(gpu_screen_recorder_process, 0); + if(errno != ESRCH) { + // Still running + return; + } + // We cant know the exit status, so we assume it succeeded + exit_code = 0; + } else { + if(WIFEXITED(status)) + exit_code = WEXITSTATUS(status); + } + + gpu_screen_recorder_process = -1; + recording_status = RecordingStatus::NONE; + recording_stopped_remove_runtime_files(); + update_ui_recording_stopped(); + + if(exit_code == 0) { + if(config->record_config.show_video_saved_notifications) { + const std::string tint_color_as_hex = color_to_hex_str(get_theme().tint_color); + const char *notification_args[] = { + "gsr-notify", "--text", "Recording has been saved", "--timeout", "3.0", + "--icon", "record", + "--icon-color", "ffffff", "--bg-color", tint_color_as_hex.c_str(), + nullptr + }; + exec_program_daemonized(notification_args); + } + } else { + fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); + const char *notification_args[] = { + "gsr-notify", "--text", "Failed to start/save recording", "--timeout", "3.0", + "--icon", "record", + "--icon-color", "ff0000", "--bg-color", "ff0000", + nullptr + }; + exec_program_daemonized(notification_args); + } + } + + static RecordingStatus recording_status_from_string(const char *status) { + RecordingStatus recording_status = RecordingStatus::NONE; + if(strcmp(status, "none") == 0) + recording_status = RecordingStatus::NONE; + else if(strcmp(status, "replay") == 0) + recording_status = RecordingStatus::REPLAY; + else if(strcmp(status, "record") == 0) + recording_status = RecordingStatus::RECORD; + else if(strcmp(status, "stream") == 0) + recording_status = RecordingStatus::STREAM; + return recording_status; + } + + static const char* recording_status_to_string(RecordingStatus status) { + switch(status) { + case RecordingStatus::NONE: return "none"; + case RecordingStatus::REPLAY: return "replay"; + case RecordingStatus::RECORD: return "record"; + case RecordingStatus::STREAM: return "stream"; + } + return "none"; + } + + void Overlay::load_program_status() { + recording_status = RecordingStatus::NONE; + + std::optional status_filepath = get_gsr_runtime_dir(); + if(!status_filepath) + throw std::runtime_error("Failed to find/create runtime directory /run/user/.../gsr-overlay or /tmp/gsr-overlay"); + + status_filepath.value() += "/status"; + + std::string file_content; + if(!file_get_content(status_filepath.value().c_str(), file_content)) + return; + + recording_status = recording_status_from_string(file_content.c_str()); + } + + void Overlay::save_program_status() { + std::optional status_filepath = get_gsr_runtime_dir(); + if(!status_filepath) + throw std::runtime_error("Failed to find/create runtime directory /run/user/.../gsr-overlay or /tmp/gsr-overlay"); + + status_filepath.value() += "/status"; + if(!file_overwrite(status_filepath.value().c_str(), recording_status_to_string(recording_status))) + fprintf(stderr, "Error: failed to update status to file %s\n", status_filepath.value().c_str()); + } + + void Overlay::load_program_pid() { + gpu_screen_recorder_process = -1; + + std::optional status_filepath = get_gsr_runtime_dir(); + if(!status_filepath) + throw std::runtime_error("Failed to find/create runtime directory /run/user/.../gsr-overlay or /tmp/gsr-overlay"); + + status_filepath.value() += "/pid"; + + std::string file_content; + if(!file_get_content(status_filepath.value().c_str(), file_content)) + return; + + int pid = -1; + if(sscanf(file_content.c_str(), "%d", &pid) != 1) { + fprintf(stderr, "Error: failed to read pid from file %s, content: %s\n", status_filepath.value().c_str(), file_content.c_str()); + return; + } + + char cmdline_path[256]; + snprintf(cmdline_path, sizeof(cmdline_path), "/proc/%d/cmdline", pid); + + char program_arg0[PATH_MAX]; + program_arg0[0] = '\0'; + if(!read_cmdline_arg0(cmdline_path, program_arg0)) { + fprintf(stderr, "Error: failed to parse arg0 from file %s. Was the gpu-screen-recorder process that was started by gsr-overlay closed by another program or the user?\n", cmdline_path); + return; + } + + if(strcmp(program_arg0, "gpu-screen-recorder") != 0) { + fprintf(stderr, "Warning: process %d exists but doesn't belong to gpu-screen-recorder (is instead %s). Was the gpu-screen-recorder process that was started by gsr-overlay closed by another program or the user?\n", pid, program_arg0); + return; + } + + gpu_screen_recorder_process = pid; + } + + void Overlay::save_program_pid() { + std::optional status_filepath = get_gsr_runtime_dir(); + if(!status_filepath) + throw std::runtime_error("Failed to find/create runtime directory /run/user/.../gsr-overlay or /tmp/gsr-overlay"); + + status_filepath.value() += "/pid"; + + char str[32]; + snprintf(str, sizeof(str), "%d", (int)gpu_screen_recorder_process); + if(!file_overwrite(status_filepath.value().c_str(), str)) + fprintf(stderr, "Error: failed to update pid to file %s\n", status_filepath.value().c_str()); + } + + void Overlay::recording_stopped_remove_runtime_files() { + std::optional status_filepath = get_gsr_runtime_dir(); + if(!status_filepath) { + fprintf(stderr, "Error: Failed to find/create runtime directory /run/user/.../gsr-overlay or /tmp/gsr-overlay"); + return; + } + + const std::string status_file = status_filepath.value() + "/status"; + const std::string pid_file = status_filepath.value() + "/pid"; + remove(status_file.c_str()); + remove(pid_file.c_str()); + } + + void Overlay::update_ui_recording_started() { + record_dropdown_button_ptr->set_item_label("start", "Stop and save"); + record_dropdown_button_ptr->set_activated(true); + record_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture); + } + + void Overlay::update_ui_recording_stopped() { + record_dropdown_button_ptr->set_item_label("start", "Start"); + record_dropdown_button_ptr->set_activated(false); + record_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); + } + void Overlay::on_press_start_replay(const std::string &id) { if(id == "settings") { auto replay_settings_page = std::make_unique(SettingsPage::Type::REPLAY, gsr_info, audio_devices, config, &page_stack); @@ -511,9 +698,9 @@ namespace gsr { // return; //exit(0); gpu_screen_recorder_process = -1; - record_dropdown_button_ptr->set_item_label(id, "Start"); - record_dropdown_button_ptr->set_activated(false); - record_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); + recording_status = RecordingStatus::NONE; + recording_stopped_remove_runtime_files(); + update_ui_recording_stopped(); // TODO: Show this with a slight delay to make sure it doesn't show up in the video if(config->record_config.show_video_saved_notifications) { @@ -570,9 +757,10 @@ namespace gsr { if(gpu_screen_recorder_process == -1) { // TODO: Show notification failed to start } else { - record_dropdown_button_ptr->set_item_label(id, "Stop and save"); - record_dropdown_button_ptr->set_activated(true); - record_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture); + recording_status = RecordingStatus::RECORD; + save_program_status(); + save_program_pid(); + update_ui_recording_started(); } // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. diff --git a/src/Process.cpp b/src/Process.cpp index a1e1e6c..822e82e 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -58,17 +58,9 @@ namespace gsr { } } - static bool is_number(const char *str) { - while(*str) { - char c = *str; - if(c < '0' || c > '9') - return false; - ++str; - } - return true; - } + bool read_cmdline_arg0(const char *filepath, char *output_buffer) { + output_buffer[0] = '\0'; - static bool read_cmdline(const char *filepath, char *output_buffer) { const char *arg0_end = NULL; int fd = open(filepath, O_RDONLY); if(fd == -1) @@ -92,41 +84,4 @@ namespace gsr { close(fd); return false; } - - static pid_t pidof(const char *process_name) { - pid_t result = -1; - DIR *dir = opendir("/proc"); - if(!dir) - return -1; - - char cmdline_filepath[PATH_MAX]; - char arg0[PATH_MAX]; - - struct dirent *entry; - while((entry = readdir(dir)) != NULL) { - if(!is_number(entry->d_name)) - continue; - - snprintf(cmdline_filepath, sizeof(cmdline_filepath), "/proc/%s/cmdline", entry->d_name); - if(read_cmdline(cmdline_filepath, arg0) && strcmp(process_name, arg0) == 0) { - result = atoi(entry->d_name); - break; - } - } - - closedir(dir); - return result; - } - - bool is_gpu_screen_recorder_running(pid_t &gsr_pid, GsrMode &mode) { - // TODO: Set |mode| by checking cmdline - gsr_pid = pidof("gpu-screen-recorder"); - if(gsr_pid == -1) { - mode = GsrMode::Unknown; - return false; - } else { - mode = GsrMode::Record; - return true; - } - } } \ No newline at end of file diff --git a/src/Utils.cpp b/src/Utils.cpp index ae24f7b..c3da908 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -167,6 +167,20 @@ namespace gsr { return success; } + bool file_overwrite(const char *filepath, const std::string &data) { + bool success = false; + + FILE *file = fopen(filepath, "wb"); + if(!file) + return success; + + if(fwrite(data.data(), 1, data.size(), file) == data.size()) + success = true; + + fclose(file); + return success; + } + std::string get_parent_directory(std::string_view directory) { std::string result; @@ -184,4 +198,21 @@ namespace gsr { } return result; } + + std::optional get_gsr_runtime_dir() { + std::optional result; + char runtime_dir_path[256]; + snprintf(runtime_dir_path, sizeof(runtime_dir_path), "/run/user/%u", (unsigned int)getuid()); + + struct stat st; + if(stat(runtime_dir_path, &st) == -1 || !S_ISDIR(st.st_mode)) + snprintf(runtime_dir_path, sizeof(runtime_dir_path), "/tmp"); + + strcat(runtime_dir_path, "/gsr-overlay"); + if(create_directory_recursive(runtime_dir_path) != 0) + return result; + + result = runtime_dir_path; + return result; + } } \ No newline at end of file -- cgit v1.2.3