Compare commits

...

6 Commits
1.8.2 ... 1.8.3

Author SHA1 Message Date
dec05eba
245dcf5730 1.8.3 2025-12-07 18:15:47 +01:00
dec05eba
e68a342b81 Default empty text not kolourpaint 2025-12-04 23:23:57 +01:00
Giovane Perlin
11aa237821 feat: adds an option to run a script after a screenshot 2025-12-04 23:18:01 +01:00
dec05eba
d7be9b38b1 Screenshot: fix image not saved to clipboard if notifications are disabled 2025-12-04 22:43:54 +01:00
dec05eba
4717c64b03 Update flatpak version reference 2025-12-04 20:16:53 +01:00
dec05eba
2c2633ec58 Add exec_program_on_host_daemonized 2025-12-01 19:57:56 +01:00
11 changed files with 124 additions and 86 deletions

2
TODO
View File

@@ -246,4 +246,4 @@ Remove all mgl::Clock usage in Overlay. We only need to get the time once per up
Handle stopping replay/stream when recording is running (show notification that the video is saved and move the video to folder with game name).
Support translations.
Support translations.

View File

@@ -146,6 +146,8 @@ namespace gsr {
ConfigHotkey take_screenshot_hotkey;
ConfigHotkey take_screenshot_region_hotkey;
ConfigHotkey take_screenshot_window_hotkey; // Or desktop portal, on wayland
std::string custom_script;
};
struct Config {
@@ -164,4 +166,4 @@ namespace gsr {
std::optional<Config> read_config(const SupportedCaptureOptions &capture_options);
void save_config(Config &config);
}
}

View File

@@ -117,7 +117,7 @@ namespace gsr {
double get_time_passed_in_replay_buffer_seconds();
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(std::string &video_filepath, NotificationType notification_type);
void on_replay_saved(const char *replay_saved_filepath);
void process_gsr_output();
void on_gsr_process_error(int exit_code, NotificationType notification_type);
@@ -129,7 +129,7 @@ namespace gsr {
void update_power_supply_status();
void update_system_startup_status();
void on_stop_recording(int exit_code, const std::string &video_filepath);
void on_stop_recording(int exit_code, std::string &video_filepath);
void update_ui_recording_paused();
void update_ui_recording_unpaused();

View File

@@ -13,13 +13,17 @@ namespace gsr {
// Arguments ending with NULL
bool exec_program_daemonized(const char **args, bool debug = true);
// Arguments ending with NULL.
// This works the same as |exec_program_get_stdout|, except on flatpak where this runs the program on the
// host machine with flatpak-spawn --host.
bool exec_program_on_host_daemonized(const char **args, bool debug = true);
// Arguments ending with NULL. |read_fd| can be NULL
pid_t exec_program(const char **args, int *read_fd, bool debug = true);
// Arguments ending with NULL. Returns the exit status of the program or -1 on error
int exec_program_get_stdout(const char **args, std::string &result, bool debug = true);
// Arguments ending with NULL. Returns the exit status of the program or -1 on error.
// This works the same as |exec_program_get_stdout|, except on flatpak where this runs the program on the
// host machine with flatpak-spawn --host
// host machine with flatpak-spawn --host.
int exec_program_on_host_get_stdout(const char **args, std::string &result, bool debug = true);
pid_t pidof(const char *process_name, pid_t ignore_pid);
}

View File

@@ -47,6 +47,9 @@ namespace gsr {
std::unique_ptr<Widget> create_led_indicator();
std::unique_ptr<Widget> create_general_section();
std::unique_ptr<Widget> create_screenshot_indicator_section();
std::unique_ptr<Widget> create_custom_script_screenshot_section();
std::unique_ptr<List> create_custom_script_screenshot_entry();
std::unique_ptr<List> create_custom_script_screenshot();
std::unique_ptr<Widget> create_settings();
void add_widgets();
@@ -75,7 +78,8 @@ namespace gsr {
CheckBox *save_screenshot_to_clipboard_checkbox_ptr = nullptr;
CheckBox *show_notification_checkbox_ptr = nullptr;
CheckBox *led_indicator_checkbox_ptr = nullptr;
Entry *create_custom_script_screenshot_entry_ptr = nullptr;
PageStack *page_stack = nullptr;
};
}
}

View File

@@ -1,4 +1,4 @@
project('gsr-ui', ['c', 'cpp'], version : '1.8.2', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
project('gsr-ui', ['c', 'cpp'], version : '1.8.3', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
@@ -66,7 +66,7 @@ datadir = get_option('datadir')
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.9.0"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.10.0"', language: ['c', 'cpp'])
executable(
meson.project_name(),

View File

@@ -1,7 +1,7 @@
[package]
name = "gsr-ui"
type = "executable"
version = "1.8.2"
version = "1.8.3"
platforms = ["posix"]
[lang.cpp]

View File

@@ -289,7 +289,8 @@ namespace gsr {
{"screenshot.save_directory", &config.screenshot_config.save_directory},
{"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey},
{"screenshot.take_screenshot_region_hotkey", &config.screenshot_config.take_screenshot_region_hotkey},
{"screenshot.take_screenshot_window_hotkey", &config.screenshot_config.take_screenshot_window_hotkey}
{"screenshot.take_screenshot_window_hotkey", &config.screenshot_config.take_screenshot_window_hotkey},
{"screenshot.custom_script", &config.screenshot_config.custom_script},
};
}
@@ -487,4 +488,4 @@ namespace gsr {
fclose(file);
}
}
}

View File

@@ -1909,10 +1909,10 @@ namespace gsr {
return ClipboardFile::FileType::PNG;
}
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
void Overlay::save_video_in_current_game_directory(std::string &video_filepath, NotificationType notification_type) {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
const std::string video_filename = filepath_get_filename(video_filepath);
const std::string video_filename = filepath_get_filename(video_filepath.c_str());
const Window gsr_ui_window = window ? (Window)window->get_system_handle() : None;
std::string focused_window_name = get_window_name_at_cursor_position(display, gsr_ui_window);
@@ -1923,11 +1923,12 @@ namespace gsr {
string_replace_characters(focused_window_name.data(), "/\\", ' ');
std::string video_directory = filepath_get_directory(video_filepath) + "/" + focused_window_name;
std::string video_directory = filepath_get_directory(video_filepath.c_str()) + "/" + focused_window_name;
create_directory_recursive(video_directory.data());
const std::string new_video_filepath = video_directory + "/" + video_filename;
rename(video_filepath, new_video_filepath.c_str());
rename(video_filepath.c_str(), new_video_filepath.c_str());
video_filepath = new_video_filepath;
truncate_string(focused_window_name, 40);
const char *capture_target = nullptr;
@@ -1935,15 +1936,6 @@ namespace gsr {
switch(notification_type) {
case NotificationType::RECORD: {
if(led_indicator) {
if(recording_status == RecordingStatus::REPLAY && !current_recording_config.replay_config.record_options.use_led_indicator)
led_indicator->set_led(false);
else if(recording_status == RecordingStatus::STREAM && !current_recording_config.streaming_config.record_options.use_led_indicator)
led_indicator->set_led(false);
else if(config.record_config.record_options.use_led_indicator)
led_indicator->blink();
}
if(!config.record_config.record_options.show_notifications)
return;
@@ -1955,9 +1947,6 @@ namespace gsr {
break;
}
case NotificationType::REPLAY: {
if(led_indicator && config.replay_config.record_options.use_led_indicator)
led_indicator->blink();
if(!config.replay_config.record_options.show_notifications)
return;
@@ -1969,18 +1958,12 @@ namespace gsr {
break;
}
case NotificationType::SCREENSHOT: {
if(led_indicator && config.screenshot_config.use_led_indicator)
led_indicator->blink();
if(!config.screenshot_config.show_notifications)
return;
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();
if(config.screenshot_config.save_screenshot_to_clipboard)
clipboard_file.set_current_file(new_video_filepath, filename_to_clipboard_file_type(new_video_filepath));
break;
}
case NotificationType::NONE:
@@ -2003,22 +1986,20 @@ namespace gsr {
void Overlay::on_replay_saved(const char *replay_saved_filepath) {
replay_save_show_notification = false;
if(config.replay_config.save_video_in_game_folder) {
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
return;
} else {
if(config.replay_config.record_options.show_notifications) {
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
std::string filepath = replay_saved_filepath;
save_video_in_current_game_directory(filepath, NotificationType::REPLAY);
} else if(config.replay_config.record_options.show_notifications) {
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
char msg[512];
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());
}
if(led_indicator && config.replay_config.record_options.use_led_indicator)
led_indicator->blink();
char msg[512];
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());
}
if(led_indicator && config.replay_config.record_options.use_led_indicator)
led_indicator->blink();
}
void Overlay::process_gsr_output() {
@@ -2045,7 +2026,8 @@ namespace gsr {
const std::string video_filepath = filepath_get_filename(line);
if(starts_with(video_filepath, "Video_")) {
on_stop_recording(0, line);
record_filepath = line;
on_stop_recording(0, record_filepath);
return;
}
@@ -2182,20 +2164,22 @@ namespace gsr {
if(exit_code == 0) {
if(config.screenshot_config.save_screenshot_in_game_folder) {
save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT);
} else {
if(config.screenshot_config.show_notifications) {
char msg[512];
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());
save_video_in_current_game_directory(screenshot_filepath, NotificationType::SCREENSHOT);
} else if(config.screenshot_config.show_notifications) {
char msg[512];
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());
}
if(config.screenshot_config.save_screenshot_to_clipboard)
clipboard_file.set_current_file(screenshot_filepath, filename_to_clipboard_file_type(screenshot_filepath));
}
if(config.screenshot_config.save_screenshot_to_clipboard)
clipboard_file.set_current_file(screenshot_filepath, filename_to_clipboard_file_type(screenshot_filepath));
if(led_indicator && config.screenshot_config.use_led_indicator)
led_indicator->blink();
if(led_indicator && config.screenshot_config.use_led_indicator)
led_indicator->blink();
if(!strip(config.screenshot_config.custom_script).empty()) {
const char *args[] = { config.screenshot_config.custom_script.c_str(), screenshot_filepath.c_str(), nullptr };
exec_program_on_host_daemonized(args);
}
} else {
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_screenshot_process, exit_code);
@@ -2287,29 +2271,27 @@ namespace gsr {
on_press_start_replay(true, false);
}
void Overlay::on_stop_recording(int exit_code, const std::string &video_filepath) {
void Overlay::on_stop_recording(int exit_code, std::string &video_filepath) {
if(exit_code == 0) {
if(config.record_config.save_video_in_game_folder) {
save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
} else {
if(config.record_config.record_options.show_notifications) {
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds() - paused_total_time_seconds - (paused ? paused_clock.get_elapsed_time_seconds() : 0.0));
save_video_in_current_game_directory(video_filepath, NotificationType::RECORD);
} else if(config.record_config.record_options.show_notifications) {
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds() - paused_total_time_seconds - (paused ? paused_clock.get_elapsed_time_seconds() : 0.0));
char msg[512];
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());
}
char msg[512];
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());
}
if(led_indicator) {
if(recording_status == RecordingStatus::REPLAY && !current_recording_config.replay_config.record_options.use_led_indicator)
led_indicator->set_led(false);
else if(recording_status == RecordingStatus::STREAM && !current_recording_config.streaming_config.record_options.use_led_indicator)
led_indicator->set_led(false);
else if(config.record_config.record_options.use_led_indicator)
led_indicator->blink();
}
if(led_indicator) {
if(recording_status == RecordingStatus::REPLAY && !current_recording_config.replay_config.record_options.use_led_indicator)
led_indicator->set_led(false);
else if(recording_status == RecordingStatus::STREAM && !current_recording_config.streaming_config.record_options.use_led_indicator)
led_indicator->set_led(false);
else if(config.record_config.record_options.use_led_indicator)
led_indicator->blink();
}
} else {
on_gsr_process_error(exit_code, NotificationType::RECORD);

View File

@@ -73,6 +73,28 @@ namespace gsr {
return true;
}
bool exec_program_on_host_daemonized(const char **args, bool debug) {
if(count_num_args(args) > 64 - 3) {
fprintf(stderr, "Error: too many arguments when trying to launch \"%s\"\n", args[0]);
return -1;
}
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
if(inside_flatpak) {
// Assumes programs wont need more than 64 - 3 args
const char *modified_args[64] = { "flatpak-spawn", "--host", "--" };
for(int i = 3; i < 64; ++i) {
const char *arg = args[i - 3];
modified_args[i] = arg;
if(!arg)
break;
}
return exec_program_daemonized(modified_args, debug);
} else {
return exec_program_daemonized(args, debug);
}
}
pid_t exec_program(const char **args, int *read_fd, bool debug) {
if(read_fd)
*read_fd = -1;
@@ -164,11 +186,9 @@ namespace gsr {
const char *modified_args[64] = { "flatpak-spawn", "--host", "--" };
for(int i = 3; i < 64; ++i) {
const char *arg = args[i - 3];
if(!arg) {
modified_args[i] = nullptr;
break;
}
modified_args[i] = arg;
if(!arg)
break;
}
return exec_program_get_stdout(modified_args, result, debug);
} else {

View File

@@ -139,7 +139,7 @@ namespace gsr {
return list;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_cursor_section() {
auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor");
record_cursor_checkbox->set_checked(true);
@@ -163,7 +163,7 @@ namespace gsr {
auto select_directory_page = std::make_unique<GsrPage>("File", "Settings");
select_directory_page->add_button("Save", "save", get_color_theme().tint_color);
select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color);
auto file_chooser = std::make_unique<FileChooser>(save_directory_button_ptr->get_text().c_str(), select_directory_page->get_inner_size());
FileChooser *file_chooser_ptr = file_chooser.get();
select_directory_page->add_widget(std::move(file_chooser));
@@ -249,6 +249,27 @@ namespace gsr {
return std::make_unique<Subsection>("Screenshot indicator", std::move(list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<List> ScreenshotSettingsPage::create_custom_script_screenshot_entry() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL, List::Alignment::CENTER);
auto create_custom_script_screenshot_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
create_custom_script_screenshot_entry_ptr = create_custom_script_screenshot_entry.get();
list->add_widget(std::move(create_custom_script_screenshot_entry));
return list;
}
std::unique_ptr<List> ScreenshotSettingsPage::create_custom_script_screenshot() {
auto custom_script_screenshot_list = std::make_unique<List>(List::Orientation::VERTICAL);
custom_script_screenshot_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Program to open the screenshot with:", get_color_theme().text_color));
custom_script_screenshot_list->add_widget(create_custom_script_screenshot_entry());
return custom_script_screenshot_list;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_custom_script_screenshot_section() {
return std::make_unique<Subsection>("Script", create_custom_script_screenshot(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_settings() {
auto page_list = std::make_unique<List>(List::Orientation::VERTICAL);
page_list->set_spacing(0.018f);
@@ -263,6 +284,7 @@ namespace gsr {
settings_list->add_widget(create_file_info_section());
settings_list->add_widget(create_general_section());
settings_list->add_widget(create_screenshot_indicator_section());
settings_list->add_widget(create_custom_script_screenshot_section());
settings_scrollable_page_ptr->add_widget(std::move(settings_list));
return page_list;
}
@@ -321,6 +343,8 @@ namespace gsr {
if(config.screenshot_config.image_height < 32)
config.screenshot_config.image_height = 32;
image_height_entry_ptr->set_text(std::to_string(config.screenshot_config.image_height));
create_custom_script_screenshot_entry_ptr->set_text(config.screenshot_config.custom_script);
}
void ScreenshotSettingsPage::save() {
@@ -337,6 +361,7 @@ namespace gsr {
config.screenshot_config.save_screenshot_to_clipboard = save_screenshot_to_clipboard_checkbox_ptr->is_checked();
config.screenshot_config.show_notifications = show_notification_checkbox_ptr->is_checked();
config.screenshot_config.use_led_indicator = led_indicator_checkbox_ptr->is_checked();
config.screenshot_config.custom_script = create_custom_script_screenshot_entry_ptr->get_text();
if(config.screenshot_config.image_width == 0)
config.screenshot_config.image_width = 1920;
@@ -356,4 +381,4 @@ namespace gsr {
save_config(config);
}
}
}