mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-03-31 09:17:04 +09:00
Save recording status to file to reload it when gsr overlay restarts
This commit is contained in:
6
TODO
6
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.
|
||||
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.
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
222
src/Overlay.cpp
222
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 <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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user