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

6
TODO
View File

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