Save recording status to file to reload it when gsr overlay restarts

This commit is contained in:
dec05eba
2024-09-22 18:17:46 +02:00
parent 5d6d57b881
commit 61c9b4918e
7 changed files with 270 additions and 66 deletions

4
TODO
View File

@@ -42,3 +42,7 @@ 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.
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.

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
#include <string_view>
#include <map>
#include <string>
#include <optional>
namespace gsr {
struct KeyValue {
@@ -23,10 +24,14 @@ namespace gsr {
std::map<std::string, std::string> 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<std::string> get_gsr_runtime_dir();
}

View File

@@ -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 <string.h>
#include <assert.h>
// TODO: Remove
#include <signal.h>
#include <sys/wait.h>
#include <limits.h>
#include <stdexcept>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
@@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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>(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.

View File

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

View File

@@ -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<std::string> get_gsr_runtime_dir() {
std::optional<std::string> 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;
}
}