diff options
Diffstat (limited to 'src')
27 files changed, 1134 insertions, 473 deletions
diff --git a/src/Config.cpp b/src/Config.cpp index 9847c45..292c7b3 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -1,7 +1,7 @@ #include "../include/Config.hpp" #include "../include/Utils.hpp" #include "../include/GsrInfo.hpp" -#include "../include/GlobalHotkeys.hpp" +#include "../include/GlobalHotkeys/GlobalHotkeys.hpp" #include <variant> #include <limits.h> #include <inttypes.h> @@ -15,6 +15,8 @@ #define FORMAT_U32 "%" PRIu32 namespace gsr { + static const std::string_view add_audio_track_tag = "[add_audio_track]"; + static std::vector<mgl::Keyboard::Key> hotkey_modifiers_to_mgl_keys(uint32_t modifiers) { std::vector<mgl::Keyboard::Key> result; if(modifiers & HOTKEY_MOD_LCTRL) @@ -82,8 +84,8 @@ namespace gsr { modifier_str = mgl::Keyboard::key_to_string(modifier_key); if(!modifier_side) { - string_remove_all(modifier_str, "Left"); - string_remove_all(modifier_str, "Right"); + string_remove_all(modifier_str, "Left "); + string_remove_all(modifier_str, "Right "); } result += modifier_str; } @@ -101,6 +103,14 @@ namespace gsr { return result; } + bool AudioTrack::operator==(const AudioTrack &other) const { + return audio_inputs == other.audio_inputs && application_audio_invert == other.application_audio_invert; + } + + bool AudioTrack::operator!=(const AudioTrack &other) const { + return !operator==(other); + } + Config::Config(const SupportedCaptureOptions &capture_options) { const std::string default_videos_save_directory = get_videos_dir(); const std::string default_pictures_save_directory = get_pictures_dir(); @@ -108,17 +118,17 @@ namespace gsr { set_hotkeys_to_default(); streaming_config.record_options.video_quality = "custom"; - streaming_config.record_options.audio_tracks.push_back("default_output"); + streaming_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); streaming_config.record_options.video_bitrate = 15000; record_config.save_directory = default_videos_save_directory; - record_config.record_options.audio_tracks.push_back("default_output"); - record_config.record_options.video_bitrate = 45000; + record_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); + record_config.record_options.video_bitrate = 40000; replay_config.record_options.video_quality = "custom"; replay_config.save_directory = default_videos_save_directory; - replay_config.record_options.audio_tracks.push_back("default_output"); - replay_config.record_options.video_bitrate = 45000; + replay_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); + replay_config.record_options.video_bitrate = 40000; screenshot_config.save_directory = default_pictures_save_directory; @@ -138,6 +148,8 @@ namespace gsr { replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT}; replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT}; + replay_config.save_1_min_hotkey = {mgl::Keyboard::F11, HOTKEY_MOD_LALT}; + replay_config.save_10_min_hotkey = {mgl::Keyboard::F12, HOTKEY_MOD_LALT}; screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Printscreen, 0}; screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LCTRL}; @@ -152,7 +164,7 @@ namespace gsr { return KeyValue{line.substr(0, space_index), line.substr(space_index + 1)}; } - using ConfigValue = std::variant<bool*, std::string*, int32_t*, ConfigHotkey*, std::vector<std::string>*>; + using ConfigValue = std::variant<bool*, std::string*, int32_t*, ConfigHotkey*, std::vector<std::string>*, std::vector<AudioTrack>*>; static std::map<std::string_view, ConfigValue> get_config_options(Config &config) { return { @@ -174,6 +186,7 @@ namespace gsr { {"streaming.record_options.application_audio_invert", &config.streaming_config.record_options.application_audio_invert}, {"streaming.record_options.change_video_resolution", &config.streaming_config.record_options.change_video_resolution}, {"streaming.record_options.audio_track", &config.streaming_config.record_options.audio_tracks}, + {"streaming.record_options.audio_track_item", &config.streaming_config.record_options.audio_tracks_list}, {"streaming.record_options.color_range", &config.streaming_config.record_options.color_range}, {"streaming.record_options.video_quality", &config.streaming_config.record_options.video_quality}, {"streaming.record_options.codec", &config.streaming_config.record_options.video_codec}, @@ -203,6 +216,7 @@ namespace gsr { {"record.record_options.application_audio_invert", &config.record_config.record_options.application_audio_invert}, {"record.record_options.change_video_resolution", &config.record_config.record_options.change_video_resolution}, {"record.record_options.audio_track", &config.record_config.record_options.audio_tracks}, + {"record.record_options.audio_track_item", &config.record_config.record_options.audio_tracks_list}, {"record.record_options.color_range", &config.record_config.record_options.color_range}, {"record.record_options.video_quality", &config.record_config.record_options.video_quality}, {"record.record_options.codec", &config.record_config.record_options.video_codec}, @@ -231,6 +245,7 @@ namespace gsr { {"replay.record_options.application_audio_invert", &config.replay_config.record_options.application_audio_invert}, {"replay.record_options.change_video_resolution", &config.replay_config.record_options.change_video_resolution}, {"replay.record_options.audio_track", &config.replay_config.record_options.audio_tracks}, + {"replay.record_options.audio_track_item", &config.replay_config.record_options.audio_tracks_list}, {"replay.record_options.color_range", &config.replay_config.record_options.color_range}, {"replay.record_options.video_quality", &config.replay_config.record_options.video_quality}, {"replay.record_options.codec", &config.replay_config.record_options.video_codec}, @@ -249,8 +264,11 @@ namespace gsr { {"replay.save_directory", &config.replay_config.save_directory}, {"replay.container", &config.replay_config.container}, {"replay.time", &config.replay_config.replay_time}, + {"replay.replay_storage", &config.replay_config.replay_storage}, {"replay.start_stop_hotkey", &config.replay_config.start_stop_hotkey}, {"replay.save_hotkey", &config.replay_config.save_hotkey}, + {"replay.save_1_min_hotkey", &config.replay_config.save_1_min_hotkey}, + {"replay.save_10_min_hotkey", &config.replay_config.save_10_min_hotkey}, {"screenshot.record_area_option", &config.screenshot_config.record_area_option}, {"screenshot.image_width", &config.screenshot_config.image_width}, @@ -291,6 +309,9 @@ namespace gsr { } else if(std::holds_alternative<std::vector<std::string>*>(it.second)) { if(*std::get<std::vector<std::string>*>(it.second) != *std::get<std::vector<std::string>*>(it_other->second)) return false; + } else if(std::holds_alternative<std::vector<AudioTrack>*>(it.second)) { + if(*std::get<std::vector<AudioTrack>*>(it.second) != *std::get<std::vector<AudioTrack>*>(it_other->second)) + return false; } else { assert(false); } @@ -302,6 +323,17 @@ namespace gsr { return !operator==(other); } + static void populate_new_audio_track_from_old(RecordOptions &record_options) { + if(record_options.merge_audio_tracks) { + record_options.audio_tracks_list.push_back({std::move(record_options.audio_tracks), record_options.application_audio_invert}); + } else { + for(const std::string &audio_input : record_options.audio_tracks) { + record_options.audio_tracks_list.push_back({std::vector<std::string>{audio_input}, record_options.application_audio_invert}); + } + } + record_options.audio_tracks.clear(); + } + std::optional<Config> read_config(const SupportedCaptureOptions &capture_options) { std::optional<Config> config; @@ -313,10 +345,15 @@ namespace gsr { } config = Config(capture_options); + config->streaming_config.record_options.audio_tracks.clear(); config->record_config.record_options.audio_tracks.clear(); config->replay_config.record_options.audio_tracks.clear(); + config->streaming_config.record_options.audio_tracks_list.clear(); + config->record_config.record_options.audio_tracks_list.clear(); + config->replay_config.record_options.audio_tracks_list.clear(); + auto config_options = get_config_options(config.value()); string_split_char(file_content, '\n', [&](std::string_view line) { @@ -355,6 +392,23 @@ namespace gsr { } else if(std::holds_alternative<std::vector<std::string>*>(it->second)) { std::string array_value(key_value->value); std::get<std::vector<std::string>*>(it->second)->push_back(std::move(array_value)); + } else if(std::holds_alternative<std::vector<AudioTrack>*>(it->second)) { + const size_t space_index = key_value->value.find(' '); + if(space_index == std::string_view::npos) { + fprintf(stderr, "Warning: Invalid config option value for %.*s\n", (int)key_value->key.size(), key_value->key.data()); + return true; + } + + const bool application_audio_invert = key_value->value.substr(0, space_index) == "true"; + const std::string_view audio_input = key_value->value.substr(space_index + 1); + std::vector<AudioTrack> &audio_tracks = *std::get<std::vector<AudioTrack>*>(it->second); + + if(audio_input == add_audio_track_tag) { + audio_tracks.push_back({std::vector<std::string>{}, application_audio_invert}); + } else if(!audio_tracks.empty()) { + audio_tracks.back().application_audio_invert = application_audio_invert; + audio_tracks.back().audio_inputs.emplace_back(audio_input); + } } else { assert(false); } @@ -362,11 +416,16 @@ namespace gsr { return true; }); - if(config->main_config.config_file_version != GSR_CONFIG_FILE_VERSION) { - fprintf(stderr, "Info: the config file is outdated, resetting it\n"); - config = std::nullopt; + if(config->main_config.config_file_version == 1) { + populate_new_audio_track_from_old(config->streaming_config.record_options); + populate_new_audio_track_from_old(config->record_config.record_options); + populate_new_audio_track_from_old(config->replay_config.record_options); } + config->streaming_config.record_options.audio_tracks.clear(); + config->record_config.record_options.audio_tracks.clear(); + config->replay_config.record_options.audio_tracks.clear(); + return config; } @@ -402,9 +461,17 @@ namespace gsr { const ConfigHotkey *config_hotkey = std::get<ConfigHotkey*>(it.second); fprintf(file, "%.*s " FORMAT_I64 " " FORMAT_U32 "\n", (int)it.first.size(), it.first.data(), config_hotkey->key, config_hotkey->modifiers); } else if(std::holds_alternative<std::vector<std::string>*>(it.second)) { - std::vector<std::string> *array = std::get<std::vector<std::string>*>(it.second); - for(const std::string &value : *array) { - fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), value.c_str()); + std::vector<std::string> *audio_inputs = std::get<std::vector<std::string>*>(it.second); + for(const std::string &audio_input : *audio_inputs) { + fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), audio_input.c_str()); + } + } else if(std::holds_alternative<std::vector<AudioTrack>*>(it.second)) { + std::vector<AudioTrack> *audio_tracks = std::get<std::vector<AudioTrack>*>(it.second); + for(const AudioTrack &audio_track : *audio_tracks) { + fprintf(file, "%.*s %s %.*s\n", (int)it.first.size(), it.first.data(), audio_track.application_audio_invert ? "true" : "false", (int)add_audio_track_tag.size(), add_audio_track_tag.data()); + for(const std::string &audio_input : audio_track.audio_inputs) { + fprintf(file, "%.*s %s %s\n", (int)it.first.size(), it.first.data(), audio_track.application_audio_invert ? "true" : "false", audio_input.c_str()); + } } } else { assert(false); diff --git a/src/CursorTrackerWayland.cpp b/src/CursorTracker/CursorTrackerWayland.cpp index 5f37d0a..b28b978 100644 --- a/src/CursorTrackerWayland.cpp +++ b/src/CursorTracker/CursorTrackerWayland.cpp @@ -1,4 +1,4 @@ -#include "../include/CursorTrackerWayland.hpp" +#include "../../include/CursorTracker/CursorTrackerWayland.hpp" #include <string.h> #include <unistd.h> #include <fcntl.h> @@ -27,19 +27,20 @@ namespace gsr { typedef struct { uint64_t crtc_id; mgl::vec2i size; + bool vrr_enabled; } drm_connector; typedef struct { drm_connector connectors[MAX_CONNECTORS]; int num_connectors; + bool has_any_crtc_with_vrr_enabled; } drm_connectors; /* Returns plane_property_mask */ - static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id, bool *is_cursor) { + static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id) { *crtc_x = 0; *crtc_y = 0; *crtc_id = 0; - *is_cursor = false; uint32_t property_mask = 0; @@ -80,8 +81,8 @@ namespace gsr { return property_mask; } - static bool connector_get_property_by_name(int drm_fd, drmModeConnectorPtr props, const char *name, uint64_t *result) { - for(int i = 0; i < props->count_props; ++i) { + static bool get_drm_property_by_name(int drm_fd, drmModeObjectPropertiesPtr props, const char *name, uint64_t *result) { + for(uint32_t i = 0; i < props->count_props; ++i) { drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[i]); if(!prop) continue; @@ -96,6 +97,14 @@ namespace gsr { return false; } + static bool connector_get_property_by_name(int drm_fd, drmModeConnectorPtr props, const char *name, uint64_t *result) { + drmModeObjectProperties properties; + properties.count_props = (uint32_t)props->count_props; + properties.props = props->props; + properties.prop_values = props->prop_values; + return get_drm_property_by_name(drm_fd, &properties, name, result); + } + static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) { for(int i = 0; i < *num_type_counts; ++i) { if(type_counts[i].type == connector_type) @@ -325,7 +334,7 @@ namespace gsr { }; /* Returns nullptr if not found */ - static const drm_connector* get_drm_connector_by_crtc_id(const drm_connectors *connectors, uint32_t crtc_id) { + static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) { for(int i = 0; i < connectors->num_connectors; ++i) { if(connectors->connectors[i].crtc_id == crtc_id) return &connectors->connectors[i]; @@ -335,6 +344,8 @@ namespace gsr { static void get_drm_connectors(int drm_fd, drm_connectors *drm_connectors) { drm_connectors->num_connectors = 0; + drm_connectors->has_any_crtc_with_vrr_enabled = false; + drmModeResPtr resources = drmModeGetResources(drm_fd); if(!resources) return; @@ -350,23 +361,59 @@ namespace gsr { uint64_t crtc_id = 0; connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &crtc_id); if(crtc_id == 0) - goto next; + goto next_connector; crtc = drmModeGetCrtc(drm_fd, crtc_id); if(!crtc) - goto next; + goto next_connector; drm_connectors->connectors[drm_connectors->num_connectors].crtc_id = crtc_id; drm_connectors->connectors[drm_connectors->num_connectors].size = mgl::vec2i{(int)crtc->width, (int)crtc->height}; + drm_connectors->connectors[drm_connectors->num_connectors].vrr_enabled = false; ++drm_connectors->num_connectors; - next: + next_connector: if(crtc) drmModeFreeCrtc(crtc); if(connector) drmModeFreeConnector(connector); } + + for(int i = 0; i < resources->count_crtcs; ++i) { + drmModeCrtcPtr crtc = nullptr; + drmModeObjectPropertiesPtr properties = nullptr; + uint64_t vrr_enabled = 0; + drm_connector *connector = nullptr; + + crtc = drmModeGetCrtc(drm_fd, resources->crtcs[i]); + if(!crtc) + continue; + + properties = drmModeObjectGetProperties(drm_fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC); + if(!properties) + goto next_crtc; + + if(!get_drm_property_by_name(drm_fd, properties, "VRR_ENABLED", &vrr_enabled)) + goto next_crtc; + + connector = get_drm_connector_by_crtc_id(drm_connectors, crtc->crtc_id); + if(!connector) + goto next_crtc; + + if(vrr_enabled) { + connector->vrr_enabled = true; + drm_connectors->has_any_crtc_with_vrr_enabled = true; + } + + next_crtc: + if(properties) + drmModeFreeObjectProperties(properties); + + if(crtc) + drmModeFreeCrtc(crtc); + } + drmModeFreeResources(resources); } @@ -392,19 +439,20 @@ namespace gsr { drm_connectors connectors; connectors.num_connectors = 0; + connectors.has_any_crtc_with_vrr_enabled = false; get_drm_connectors(drm_fd, &connectors); drmModePlaneResPtr planes = drmModeGetPlaneResources(drm_fd); if(!planes) return; + bool found_cursor = false; for(uint32_t i = 0; i < planes->count_planes; ++i) { drmModePlanePtr plane = nullptr; const drm_connector *connector = nullptr; int crtc_x = 0; int crtc_y = 0; int crtc_id = 0; - bool is_cursor = false; uint32_t property_mask = 0; plane = drmModeGetPlane(drm_fd, planes->planes[i]); @@ -414,7 +462,7 @@ namespace gsr { if(!plane->fb_id) goto next; - property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id, &is_cursor); + property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id); if(property_mask != plane_property_all || crtc_id <= 0) goto next; @@ -426,6 +474,7 @@ namespace gsr { latest_cursor_position.x = crtc_x; latest_cursor_position.y = crtc_y; latest_crtc_id = crtc_id; + found_cursor = true; drmModeFreePlane(plane); break; } @@ -434,6 +483,11 @@ namespace gsr { drmModeFreePlane(plane); } + // On kde plasma wayland (and possibly other wayland compositors) it uses a software cursor only for the monitors with vrr enabled. + // In that case we cant know the cursor location and we instead want to fallback to getting focused monitor by using the hack of creating a window and getting the position. + if(!found_cursor && latest_crtc_id > 0 && connectors.has_any_crtc_with_vrr_enabled) + latest_crtc_id = -1; + drmModeFreePlaneResources(planes); } diff --git a/src/CursorTrackerX11.cpp b/src/CursorTracker/CursorTrackerX11.cpp index 7c40cea..7c98f4d 100644 --- a/src/CursorTrackerX11.cpp +++ b/src/CursorTracker/CursorTrackerX11.cpp @@ -1,5 +1,5 @@ -#include "../include/CursorTrackerX11.hpp" -#include "../include/WindowUtils.hpp" +#include "../../include/CursorTracker/CursorTrackerX11.hpp" +#include "../../include/WindowUtils.hpp" namespace gsr { CursorTrackerX11::CursorTrackerX11(Display *dpy) : dpy(dpy) { diff --git a/src/GlobalHotkeysJoystick.cpp b/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp index d005aa9..b3b21c8 100644 --- a/src/GlobalHotkeysJoystick.cpp +++ b/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp @@ -1,4 +1,4 @@ -#include "../include/GlobalHotkeysJoystick.hpp" +#include "../../include/GlobalHotkeys/GlobalHotkeysJoystick.hpp" #include <string.h> #include <errno.h> #include <fcntl.h> @@ -7,6 +7,8 @@ namespace gsr { static constexpr int button_pressed = 1; + static constexpr int cross_button = 0; + static constexpr int triangle_button = 2; static constexpr int options_button = 9; static constexpr int playstation_button = 10; static constexpr int axis_up_down = 7; @@ -104,6 +106,20 @@ namespace gsr { it->second("save_replay"); } + if(save_1_min_replay) { + save_1_min_replay = false; + auto it = bound_actions_by_id.find("save_1_min_replay"); + if(it != bound_actions_by_id.end()) + it->second("save_1_min_replay"); + } + + if(save_10_min_replay) { + save_10_min_replay = false; + auto it = bound_actions_by_id.find("save_10_min_replay"); + if(it != bound_actions_by_id.end()) + it->second("save_10_min_replay"); + } + if(take_screenshot) { take_screenshot = false; auto it = bound_actions_by_id.find("take_screenshot"); @@ -186,10 +202,27 @@ namespace gsr { return; if((event.type & JS_EVENT_BUTTON) == JS_EVENT_BUTTON) { - if(event.number == playstation_button) - playstation_button_pressed = event.value == button_pressed; - else if(playstation_button_pressed && event.number == options_button && event.value == button_pressed) - toggle_show = true; + switch(event.number) { + case playstation_button: { + playstation_button_pressed = event.value == button_pressed; + break; + } + case options_button: { + if(playstation_button_pressed && event.value == button_pressed) + toggle_show = true; + break; + } + case cross_button: { + if(playstation_button_pressed && event.value == button_pressed) + save_1_min_replay = true; + break; + } + case triangle_button: { + if(playstation_button_pressed && event.value == button_pressed) + save_10_min_replay = true; + break; + } + } } else if((event.type & JS_EVENT_AXIS) == JS_EVENT_AXIS && playstation_button_pressed) { const int trigger_threshold = 16383; const bool prev_up_pressed = up_pressed; diff --git a/src/GlobalHotkeysLinux.cpp b/src/GlobalHotkeys/GlobalHotkeysLinux.cpp index fbba0ea..a56bbc6 100644 --- a/src/GlobalHotkeysLinux.cpp +++ b/src/GlobalHotkeys/GlobalHotkeysLinux.cpp @@ -1,5 +1,4 @@ -#include "../include/GlobalHotkeysLinux.hpp" -#include <signal.h> +#include "../../include/GlobalHotkeys/GlobalHotkeysLinux.hpp" #include <sys/wait.h> #include <fcntl.h> #include <limits.h> @@ -71,21 +70,41 @@ namespace gsr { } GlobalHotkeysLinux::~GlobalHotkeysLinux() { + if(write_pipes[PIPE_WRITE] > 0) { + char command[32]; + const int command_size = snprintf(command, sizeof(command), "exit\n"); + if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { + fprintf(stderr, "Error: GlobalHotkeysLinux::~GlobalHotkeysLinux: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); + close_fds(); + } + } else { + close_fds(); + } + + if(process_id > 0) { + int status; + waitpid(process_id, &status, 0); + } + + close_fds(); + } + + void GlobalHotkeysLinux::close_fds() { for(int i = 0; i < 2; ++i) { - if(read_pipes[i] > 0) + if(read_pipes[i] > 0) { close(read_pipes[i]); + read_pipes[i] = -1; + } - if(write_pipes[i] > 0) + if(write_pipes[i] > 0) { close(write_pipes[i]); + write_pipes[i] = -1; + } } - if(read_file) + if(read_file) { fclose(read_file); - - if(process_id > 0) { - kill(process_id, SIGKILL); - int status; - waitpid(process_id, &status, 0); + read_file = nullptr; } } diff --git a/src/GlobalHotkeysX11.cpp b/src/GlobalHotkeys/GlobalHotkeysX11.cpp index 9af2607..bc79ce8 100644 --- a/src/GlobalHotkeysX11.cpp +++ b/src/GlobalHotkeys/GlobalHotkeysX11.cpp @@ -1,4 +1,4 @@ -#include "../include/GlobalHotkeysX11.hpp" +#include "../../include/GlobalHotkeys/GlobalHotkeysX11.hpp" #include <X11/keysym.h> #include <mglpp/window/Event.hpp> #include <assert.h> diff --git a/src/GsrInfo.cpp b/src/GsrInfo.cpp index 5af6397..d7212d7 100644 --- a/src/GsrInfo.cpp +++ b/src/GsrInfo.cpp @@ -175,11 +175,6 @@ namespace gsr { CAPTURE_OPTIONS }; - static bool starts_with(std::string_view str, const char *substr) { - size_t len = strlen(substr); - return str.size() >= len && memcmp(str.data(), substr, len) == 0; - } - GsrInfoExitStatus get_gpu_screen_recorder_info(GsrInfo *gsr_info) { *gsr_info = GsrInfo{}; diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 2a4ca81..70278ee 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -12,10 +12,10 @@ #include "../include/gui/Utils.hpp" #include "../include/gui/PageStack.hpp" #include "../include/WindowUtils.hpp" -#include "../include/GlobalHotkeys.hpp" -#include "../include/GlobalHotkeysLinux.hpp" -#include "../include/CursorTrackerX11.hpp" -#include "../include/CursorTrackerWayland.hpp" +#include "../include/GlobalHotkeys/GlobalHotkeys.hpp" +#include "../include/GlobalHotkeys/GlobalHotkeysLinux.hpp" +#include "../include/CursorTracker/CursorTrackerX11.hpp" +#include "../include/CursorTracker/CursorTrackerWayland.hpp" #include <string.h> #include <assert.h> @@ -47,7 +47,7 @@ namespace gsr { static const double force_window_on_top_timeout_seconds = 1.0; static const double replay_status_update_check_timeout_seconds = 1.5; static const double replay_saving_notification_timeout_seconds = 0.5; - static const double notification_timeout_seconds = 2.0; + static const double notification_timeout_seconds = 2.5; static const double notification_error_timeout_seconds = 5.0; static const double cursor_tracker_update_timeout_sec = 0.1; @@ -207,24 +207,21 @@ namespace gsr { return false; }*/ - // Returns the first monitor if not found. Assumes there is at least one monitor connected. static const Monitor* find_monitor_at_position(const std::vector<Monitor> &monitors, mgl::vec2i pos) { assert(!monitors.empty()); for(const Monitor &monitor : monitors) { if(mgl::IntRect(monitor.position, monitor.size).contains(pos)) return &monitor; } - return &monitors.front(); + return nullptr; } - // Returns the first monitor if not found. Assumes there is at least one monitor connected. static const Monitor* find_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) { - assert(!monitors.empty()); for(const Monitor &monitor : monitors) { if(monitor.name == name) return &monitor; } - return &monitors.front(); + return nullptr; } static std::string get_power_supply_online_filepath() { @@ -326,6 +323,20 @@ namespace gsr { }); global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().replay_config.save_1_min_hotkey), + "replay_save_1_min", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay_1_min(); + }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().replay_config.save_10_min_hotkey), + "replay_save_10_min", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay_10_min(); + }); + + global_hotkeys->bind_key_press( config_hotkey_to_hotkey(overlay->get_config().screenshot_config.take_screenshot_hotkey), "take_screenshot", [overlay](const std::string &id) { fprintf(stderr, "pressed %s\n", id.c_str()); @@ -371,6 +382,16 @@ namespace gsr { overlay->save_replay(); }); + global_hotkeys_js->bind_action("save_1_min_replay", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay_1_min(); + }); + + global_hotkeys_js->bind_action("save_10_min_replay", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay_10_min(); + }); + global_hotkeys_js->bind_action("take_screenshot", [overlay](const std::string &id) { fprintf(stderr, "pressed %s\n", id.c_str()); overlay->take_screenshot(); @@ -556,8 +577,8 @@ namespace gsr { memset(xi_output_xev, 0, sizeof(*xi_output_xev)); xi_output_xev->type = MotionNotify; xi_output_xev->xmotion.display = display; - xi_output_xev->xmotion.window = window->get_system_handle(); - xi_output_xev->xmotion.subwindow = window->get_system_handle(); + xi_output_xev->xmotion.window = (Window)window->get_system_handle(); + xi_output_xev->xmotion.subwindow = (Window)window->get_system_handle(); xi_output_xev->xmotion.x = de->root_x - window_pos.x; xi_output_xev->xmotion.y = de->root_y - window_pos.y; xi_output_xev->xmotion.x_root = de->root_x; @@ -569,8 +590,8 @@ namespace gsr { memset(xi_output_xev, 0, sizeof(*xi_output_xev)); xi_output_xev->type = cookie->evtype == XI_ButtonPress ? ButtonPress : ButtonRelease; xi_output_xev->xbutton.display = display; - xi_output_xev->xbutton.window = window->get_system_handle(); - xi_output_xev->xbutton.subwindow = window->get_system_handle(); + xi_output_xev->xbutton.window = (Window)window->get_system_handle(); + xi_output_xev->xbutton.subwindow = (Window)window->get_system_handle(); xi_output_xev->xbutton.x = de->root_x - window_pos.x; xi_output_xev->xbutton.y = de->root_y - window_pos.y; xi_output_xev->xbutton.x_root = de->root_x; @@ -583,8 +604,8 @@ namespace gsr { memset(xi_output_xev, 0, sizeof(*xi_output_xev)); xi_output_xev->type = cookie->evtype == XI_KeyPress ? KeyPress : KeyRelease; xi_output_xev->xkey.display = display; - xi_output_xev->xkey.window = window->get_system_handle(); - xi_output_xev->xkey.subwindow = window->get_system_handle(); + xi_output_xev->xkey.window = (Window)window->get_system_handle(); + xi_output_xev->xkey.subwindow = (Window)window->get_system_handle(); xi_output_xev->xkey.x = de->root_x - window_pos.x; xi_output_xev->xkey.y = de->root_y - window_pos.y; xi_output_xev->xkey.x_root = de->root_x; @@ -685,8 +706,10 @@ namespace gsr { } bool Overlay::draw() { + remove_widgets_to_be_removed(); + update_notification_process_status(); - update_gsr_replay_save(); + process_gsr_output(); update_gsr_process_status(); update_gsr_screenshot_process_status(); replay_status_update_status(); @@ -695,7 +718,7 @@ namespace gsr { start_region_capture = false; hide(); if(!region_selector.start(get_color_theme().tint_color)) { - show_notification("Failed to start region capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::NONE); + show_notification("Failed to start region capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE); on_region_selected = nullptr; } } @@ -765,13 +788,13 @@ namespace gsr { // There should be a debug mode to not use these mgl_context *context = mgl_get_context(); Display *display = (Display*)context->connection; - XGrabPointer(display, window->get_system_handle(), True, + XGrabPointer(display, (Window)window->get_system_handle(), True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); // TODO: This breaks global hotkeys (when using x11 global hotkeys) - XGrabKeyboard(display, window->get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); + XGrabKeyboard(display, (Window)window->get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); XFlush(display); } @@ -868,10 +891,14 @@ namespace gsr { const Monitor *focused_monitor = nullptr; if(cursor_info) { focused_monitor = find_monitor_by_name(monitors, cursor_info->monitor_name); + if(!focused_monitor) + focused_monitor = &monitors.front(); cursor_position = cursor_info->position; } else { const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display); focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value); + if(!focused_monitor) + focused_monitor = &monitors.front(); } // Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused. @@ -905,18 +932,21 @@ namespace gsr { // Nvidia + Wayland + Egl doesn't work on some systems properly and it instead falls back to software rendering. // Use Glx on Wayland to workaround this issue. This is fine since Egl is only needed for x11 to reliably get the texture of the fullscreen window on Nvidia // when a compositor isn't running. - window_create_params.render_api = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? MGL_RENDER_API_GLX : MGL_RENDER_API_EGL; + window_create_params.graphics_api = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? MGL_GRAPHICS_API_GLX : MGL_GRAPHICS_API_EGL; - if(!window->create("gsr ui", window_create_params)) + if(!window->create("gsr ui", window_create_params)) { fprintf(stderr, "error: failed to create window\n"); + window.reset(); + return; + } //window->set_low_latency(true); unsigned char data = 2; // Prefer being composed to allow transparency - XChangeProperty(display, window->get_system_handle(), XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); + XChangeProperty(display, (Window)window->get_system_handle(), XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); data = 1; - XChangeProperty(display, window->get_system_handle(), XInternAtom(display, "GAMESCOPE_EXTERNAL_OVERLAY", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); + XChangeProperty(display, (Window)window->get_system_handle(), XInternAtom(display, "GAMESCOPE_EXTERNAL_OVERLAY", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); const auto original_window_size = window_size; window_pos = focused_monitor->position; @@ -950,12 +980,12 @@ namespace gsr { //window->set_fullscreen(true); if(gsr_info.system_info.display_server == DisplayServer::X11) - make_window_click_through(display, window->get_system_handle()); + make_window_click_through(display, (Window)window->get_system_handle()); window->set_visible(true); - make_window_sticky(display, window->get_system_handle()); - hide_window_from_taskbar(display, window->get_system_handle()); + make_window_sticky(display, (Window)window->get_system_handle()); + hide_window_from_taskbar(display, (Window)window->get_system_handle()); if(default_cursor) { XFreeCursor(display, default_cursor); @@ -1002,6 +1032,9 @@ namespace gsr { if(paused) update_ui_recording_paused(); + if(replay_recording) + update_ui_recording_started(); + // Wayland compositors have retarded fullscreen animations that we cant disable in a proper way // without messing up window position. show_overlay_timeout_seconds = prevent_game_minimizing ? 0.0 : 0.15; @@ -1049,9 +1082,15 @@ namespace gsr { replay_dropdown_button_ptr = button.get(); button->add_item("Turn on", "start", config.replay_config.start_stop_hotkey.to_string(false, false)); button->add_item("Save", "save", config.replay_config.save_hotkey.to_string(false, false)); + if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { + button->add_item("Save 1 min", "save_1_min", config.replay_config.save_1_min_hotkey.to_string(false, false)); + button->add_item("Save 10 min", "save_10_min", config.replay_config.save_10_min_hotkey.to_string(false, false)); + } button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); button->set_item_icon("save", &get_theme().save_texture); + button->set_item_icon("save_1_min", &get_theme().save_texture); + button->set_item_icon("save_10_min", &get_theme().save_texture); button->set_item_icon("settings", &get_theme().settings_small_texture); button->on_click = [this](const std::string &id) { if(id == "settings") { @@ -1064,10 +1103,17 @@ namespace gsr { page_stack.push(std::move(replay_settings_page)); } else if(id == "save") { on_press_save_replay(); + } else if(id == "save_1_min") { + on_press_save_replay_1_min_replay(); + } else if(id == "save_10_min") { + on_press_save_replay_10_min_replay(); } else if(id == "start") { on_press_start_replay(false, false); } }; + button->set_item_enabled("save", false); + button->set_item_enabled("save_1_min", false); + button->set_item_enabled("save_10_min", false); main_buttons_list->add_widget(std::move(button)); } { @@ -1094,6 +1140,7 @@ namespace gsr { on_press_start_record(false); } }; + button->set_item_enabled("pause", false); main_buttons_list->add_widget(std::move(button)); } { @@ -1172,23 +1219,15 @@ namespace gsr { }; settings_page->on_page_closed = [this]() { - if(global_hotkeys) { - replay_dropdown_button_ptr->set_item_description("start", config.replay_config.start_stop_hotkey.to_string(false, false)); - replay_dropdown_button_ptr->set_item_description("save", config.replay_config.save_hotkey.to_string(false, false)); + replay_dropdown_button_ptr->set_item_description("start", config.replay_config.start_stop_hotkey.to_string(false, false)); + replay_dropdown_button_ptr->set_item_description("save", config.replay_config.save_hotkey.to_string(false, false)); + replay_dropdown_button_ptr->set_item_description("save_1_min", config.replay_config.save_1_min_hotkey.to_string(false, false)); + replay_dropdown_button_ptr->set_item_description("save_10_min", config.replay_config.save_10_min_hotkey.to_string(false, false)); - record_dropdown_button_ptr->set_item_description("start", config.record_config.start_stop_hotkey.to_string(false, false)); - record_dropdown_button_ptr->set_item_description("pause", config.record_config.pause_unpause_hotkey.to_string(false, false)); + record_dropdown_button_ptr->set_item_description("start", config.record_config.start_stop_hotkey.to_string(false, false)); + record_dropdown_button_ptr->set_item_description("pause", config.record_config.pause_unpause_hotkey.to_string(false, false)); - stream_dropdown_button_ptr->set_item_description("start", config.streaming_config.start_stop_hotkey.to_string(false, false)); - } else { - replay_dropdown_button_ptr->set_item_description("start", ""); - replay_dropdown_button_ptr->set_item_description("save", ""); - - record_dropdown_button_ptr->set_item_description("start", ""); - record_dropdown_button_ptr->set_item_description("pause", ""); - - stream_dropdown_button_ptr->set_item_description("start", ""); - } + stream_dropdown_button_ptr->set_item_description("start", config.streaming_config.start_stop_hotkey.to_string(false, false)); }; page_stack.push(std::move(settings_page)); @@ -1249,6 +1288,7 @@ namespace gsr { while(!page_stack.empty()) { page_stack.pop(); } + remove_widgets_to_be_removed(); if(default_cursor) { XFreeCursor(display, default_cursor); @@ -1365,6 +1405,14 @@ namespace gsr { on_press_save_replay(); } + void Overlay::save_replay_1_min() { + on_press_save_replay_1_min_replay(); + } + + void Overlay::save_replay_10_min() { + on_press_save_replay_10_min_replay(); + } + void Overlay::take_screenshot() { on_press_take_screenshot(false, false); } @@ -1554,7 +1602,7 @@ namespace gsr { Display *display = (Display*)context->connection; const std::string video_filename = filepath_get_filename(video_filepath); - const Window gsr_ui_window = window ? window->get_system_handle() : None; + 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); if(focused_window_name.empty()) focused_window_name = get_focused_window_name(display, WindowCaptureType::FOCUSED); @@ -1572,7 +1620,6 @@ namespace gsr { truncate_string(focused_window_name, 20); const char *capture_target = nullptr; char msg[512]; - const std::string filename = focused_window_name + "/" + video_filename; switch(notification_type) { case NotificationType::RECORD: { @@ -1580,9 +1627,9 @@ namespace gsr { return; if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved recording of this monitor to '%s'", filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a recording of this monitor to \"%s\"", focused_window_name.c_str()); else - snprintf(msg, sizeof(msg), "Saved recording of %s to '%s'", recording_capture_target.c_str(), filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", recording_capture_target.c_str(), focused_window_name.c_str()); capture_target = recording_capture_target.c_str(); break; @@ -1591,12 +1638,18 @@ namespace gsr { if(!config.replay_config.show_replay_saved_notifications) return; - if(is_capture_target_monitor(replay_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved replay of this monitor to '%s'", filename.c_str()); + char duration[32]; + if(replay_save_duration_min > 0) + snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min); else - snprintf(msg, sizeof(msg), "Saved replay of %s to '%s'", replay_capture_target.c_str(), filename.c_str()); + snprintf(duration, sizeof(duration), " "); - capture_target = replay_capture_target.c_str(); + if(is_capture_target_monitor(recording_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved a%sreplay of this monitor to \"%s\"", duration, focused_window_name.c_str()); + else + snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, recording_capture_target.c_str(), focused_window_name.c_str()); + + capture_target = recording_capture_target.c_str(); break; } case NotificationType::SCREENSHOT: { @@ -1604,9 +1657,9 @@ namespace gsr { return; if(is_capture_target_monitor(screenshot_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved screenshot of this monitor to '%s'", filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a screenshot of this monitor to \"%s\"", focused_window_name.c_str()); else - snprintf(msg, sizeof(msg), "Saved screenshot of %s to '%s'", screenshot_capture_target.c_str(), filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", screenshot_capture_target.c_str(), focused_window_name.c_str()); capture_target = screenshot_capture_target.c_str(); break; @@ -1618,22 +1671,37 @@ namespace gsr { show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, notification_type, capture_target); } + static NotificationType recording_status_to_notification_type(RecordingStatus recording_status) { + switch(recording_status) { + case RecordingStatus::NONE: return NotificationType::NONE; + case RecordingStatus::REPLAY: return NotificationType::REPLAY; + case RecordingStatus::RECORD: return NotificationType::RECORD; + case RecordingStatus::STREAM: return NotificationType::STREAM; + } + return NotificationType::NONE; + } + 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); } else { - const std::string filename = filepath_get_filename(replay_saved_filepath); + char duration[32]; + if(replay_save_duration_min > 0) + snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min); + else + snprintf(duration, sizeof(duration), " "); + char msg[512]; - if(is_capture_target_monitor(replay_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved replay of this monitor to '%s'", filename.c_str()); + if(is_capture_target_monitor(recording_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved a%sreplay of this monitor", duration); else - snprintf(msg, sizeof(msg), "Saved replay of %s to '%s'", replay_capture_target.c_str(), filename.c_str()); - show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, replay_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, recording_capture_target.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()); } } - void Overlay::update_gsr_replay_save() { + void Overlay::process_gsr_output() { if(replay_save_show_notification && replay_save_clock.get_elapsed_time_seconds() >= replay_saving_notification_timeout_seconds) { replay_save_show_notification = false; show_notification("Saving replay, this might take some time", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY); @@ -1641,15 +1709,36 @@ namespace gsr { if(gpu_screen_recorder_process_output_file) { char buffer[1024]; - char *replay_saved_filepath = fgets(buffer, sizeof(buffer), gpu_screen_recorder_process_output_file); - if(!replay_saved_filepath || replay_saved_filepath[0] == '\0') + char *line = fgets(buffer, sizeof(buffer), gpu_screen_recorder_process_output_file); + if(!line || line[0] == '\0') return; - const int line_len = strlen(replay_saved_filepath); - if(replay_saved_filepath[line_len - 1] == '\n') - replay_saved_filepath[line_len - 1] = '\0'; + const int line_len = strlen(line); + if(line[line_len - 1] == '\n') + line[line_len - 1] = '\0'; - on_replay_saved(replay_saved_filepath); + if(starts_with({line, (size_t)line_len}, "Error: ")) { + show_notification(line + 7, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), recording_status_to_notification_type(recording_status)); + return; + } + + const std::string video_filepath = filepath_get_filename(line); + if(starts_with(video_filepath, "Video_")) { + on_stop_recording(0, line); + return; + } + + switch(recording_status) { + case RecordingStatus::NONE: + break; + case RecordingStatus::REPLAY: + on_replay_saved(line); + break; + case RecordingStatus::RECORD: + break; + case RecordingStatus::STREAM: + break; + } } else if(gpu_screen_recorder_process_output_fd > 0) { char buffer[1024]; read(gpu_screen_recorder_process_output_fd, buffer, sizeof(buffer)); @@ -1676,6 +1765,7 @@ namespace gsr { case RecordingStatus::NONE: break; case RecordingStatus::REPLAY: { + replay_save_duration_min = 0; update_ui_replay_stopped(); if(exit_code == 0) { if(config.replay_config.show_replay_stopped_notifications) @@ -1688,7 +1778,7 @@ namespace gsr { } case RecordingStatus::RECORD: { update_ui_recording_stopped(); - on_stop_recording(exit_code); + on_stop_recording(exit_code, record_filepath); break; } case RecordingStatus::STREAM: { @@ -1726,12 +1816,11 @@ namespace gsr { if(config.screenshot_config.save_screenshot_in_game_folder) { save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT); } else { - const std::string filename = filepath_get_filename(screenshot_filepath.c_str()); char msg[512]; if(is_capture_target_monitor(screenshot_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved screenshot of this monitor to '%s'", filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a screenshot of this monitor"); else - snprintf(msg, sizeof(msg), "Saved screenshot of %s to '%s'", screenshot_capture_target.c_str(), filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a screenshot of %s", screenshot_capture_target.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()); } } else { @@ -1742,28 +1831,25 @@ namespace gsr { gpu_screen_recorder_screenshot_process = -1; } - static bool starts_with(std::string_view str, const char *substr) { - size_t len = strlen(substr); - return str.size() >= len && memcmp(str.data(), substr, len) == 0; - } - - static bool are_all_audio_tracks_available_to_capture(const std::vector<std::string> &audio_tracks) { + static bool are_all_audio_tracks_available_to_capture(const std::vector<AudioTrack> &audio_tracks) { const auto audio_devices = get_audio_devices(); - for(const std::string &audio_track : audio_tracks) { - std::string_view audio_track_name(audio_track.c_str()); - const bool is_app_audio = starts_with(audio_track_name, "app:"); - if(is_app_audio) - continue; + for(const AudioTrack &audio_track : audio_tracks) { + for(const std::string &audio_input : audio_track.audio_inputs) { + std::string_view audio_track_name(audio_input.c_str()); + const bool is_app_audio = starts_with(audio_track_name, "app:"); + if(is_app_audio) + continue; - if(starts_with(audio_track_name, "device:")) - audio_track_name.remove_prefix(7); + if(starts_with(audio_track_name, "device:")) + audio_track_name.remove_prefix(7); - auto it = std::find_if(audio_devices.begin(), audio_devices.end(), [&](const auto &audio_device) { - return audio_device.name == audio_track_name; - }); - if(it == audio_devices.end()) { - //fprintf(stderr, "Audio not ready\n"); - return false; + auto it = std::find_if(audio_devices.begin(), audio_devices.end(), [&](const auto &audio_device) { + return audio_device.name == audio_track_name; + }); + if(it == audio_devices.end()) { + //fprintf(stderr, "Audio not ready\n"); + return false; + } } } return true; @@ -1787,14 +1873,14 @@ namespace gsr { Display *display = (Display*)context->connection; const Window focused_window = get_focused_window(display, WindowCaptureType::FOCUSED); - if(window && focused_window == window->get_system_handle()) + if(window && focused_window == (Window)window->get_system_handle()) return; const bool prev_focused_window_is_fullscreen = focused_window_is_fullscreen; focused_window_is_fullscreen = focused_window != 0 && window_is_fullscreen(display, focused_window); if(focused_window_is_fullscreen != prev_focused_window_is_fullscreen) { if(recording_status == RecordingStatus::NONE && focused_window_is_fullscreen) { - if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks)) + if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks_list)) on_press_start_replay(false, false); } else if(recording_status == RecordingStatus::REPLAY && !focused_window_is_fullscreen) { on_press_start_replay(true, false); @@ -1811,7 +1897,7 @@ namespace gsr { power_supply_connected = power_supply_online_filepath.empty() || power_supply_is_connected(power_supply_online_filepath.c_str()); if(power_supply_connected != prev_power_supply_status) { if(recording_status == RecordingStatus::NONE && power_supply_connected) { - if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks)) + if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks_list)) on_press_start_replay(false, false); } else if(recording_status == RecordingStatus::REPLAY && !power_supply_connected) { on_press_start_replay(false, false); @@ -1823,27 +1909,28 @@ namespace gsr { if(replay_startup_mode != ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP || recording_status != RecordingStatus::NONE || !try_replay_startup) return; - if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks)) + if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks_list)) on_press_start_replay(true, false); } - void Overlay::on_stop_recording(int exit_code) { + void Overlay::on_stop_recording(int exit_code, const std::string &video_filepath) { if(exit_code == 0) { if(config.record_config.save_video_in_game_folder) { - save_video_in_current_game_directory(record_filepath.c_str(), NotificationType::RECORD); + save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD); } else { - const std::string filename = filepath_get_filename(record_filepath.c_str()); char msg[512]; if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved recording of this monitor to '%s'", filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a recording of this monitor"); else - snprintf(msg, sizeof(msg), "Saved recording of %s to '%s'", recording_capture_target.c_str(), filename.c_str()); + snprintf(msg, sizeof(msg), "Saved a recording of %s", recording_capture_target.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()); } } else { fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); show_notification("Failed to start/save recording. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); } + update_ui_recording_stopped(); + replay_recording = false; } void Overlay::update_ui_recording_paused() { @@ -1872,6 +1959,7 @@ namespace gsr { record_dropdown_button_ptr->set_activated(true); record_dropdown_button_ptr->set_description("Recording"); record_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture); + record_dropdown_button_ptr->set_item_enabled("pause", recording_status == RecordingStatus::RECORD); } void Overlay::update_ui_recording_stopped() { @@ -1885,7 +1973,9 @@ namespace gsr { record_dropdown_button_ptr->set_item_label("pause", "Pause"); record_dropdown_button_ptr->set_item_icon("pause", &get_theme().pause_texture); + record_dropdown_button_ptr->set_item_enabled("pause", false); paused = false; + replay_recording = false; } void Overlay::update_ui_streaming_started() { @@ -1906,6 +1996,7 @@ namespace gsr { stream_dropdown_button_ptr->set_activated(false); stream_dropdown_button_ptr->set_description("Not streaming"); stream_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); + update_ui_recording_stopped(); } void Overlay::update_ui_replay_started() { @@ -1916,6 +2007,9 @@ namespace gsr { replay_dropdown_button_ptr->set_activated(true); replay_dropdown_button_ptr->set_description("On"); replay_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture); + replay_dropdown_button_ptr->set_item_enabled("save", true); + replay_dropdown_button_ptr->set_item_enabled("save_1_min", true); + replay_dropdown_button_ptr->set_item_enabled("save_10_min", true); } void Overlay::update_ui_replay_stopped() { @@ -1926,6 +2020,10 @@ namespace gsr { replay_dropdown_button_ptr->set_activated(false); replay_dropdown_button_ptr->set_description("Off"); replay_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); + replay_dropdown_button_ptr->set_item_enabled("save", false); + replay_dropdown_button_ptr->set_item_enabled("save_1_min", false); + replay_dropdown_button_ptr->set_item_enabled("save_10_min", false); + update_ui_recording_stopped(); } static std::string get_date_str() { @@ -1947,29 +2045,31 @@ namespace gsr { return container; } - static std::vector<std::string> create_audio_tracks_real_names(const std::vector<std::string> &audio_tracks, bool application_audio_invert, const GsrInfo &gsr_info) { + static std::vector<std::string> create_audio_tracks_cli_args(const std::vector<AudioTrack> &audio_tracks, const GsrInfo &gsr_info) { std::vector<std::string> result; - for(const std::string &audio_track : audio_tracks) { - std::string audio_track_name = audio_track; - const bool is_app_audio = starts_with(audio_track_name, "app:"); - if(is_app_audio && !gsr_info.system_info.supports_app_audio) - continue; + result.reserve(audio_tracks.size()); + + for(const AudioTrack &audio_track : audio_tracks) { + std::string audio_track_merged; + for(const std::string &audio_input_name : audio_track.audio_inputs) { + std::string new_audio_input_name = audio_input_name; + const bool is_app_audio = starts_with(new_audio_input_name, "app:"); + if(is_app_audio && !gsr_info.system_info.supports_app_audio) + continue; - if(is_app_audio && application_audio_invert) - audio_track_name.replace(0, 4, "app-inverse:"); + if(is_app_audio && audio_track.application_audio_invert) + new_audio_input_name.replace(0, 4, "app-inverse:"); - result.push_back(std::move(audio_track_name)); - } - return result; - } + if(!audio_track_merged.empty()) + audio_track_merged += "|"; - static std::string merge_audio_tracks(const std::vector<std::string> &audio_tracks) { - std::string result; - for(size_t i = 0; i < audio_tracks.size(); ++i) { - if(i > 0) - result += "|"; - result += audio_tracks[i]; + audio_track_merged += new_audio_input_name; + } + + if(!audio_track_merged.empty()) + result.push_back(std::move(audio_track_merged)); } + return result; } @@ -1984,7 +2084,7 @@ namespace gsr { args.push_back(region_str); } - static void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, const std::string &audio_devices_merged, char *region_str, int region_str_size, const RegionSelector ®ion_selector) { + static void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const RegionSelector ®ion_selector) { if(record_options.video_quality == "custom") { args.push_back("-bm"); args.push_back("cbr"); @@ -2000,16 +2100,9 @@ namespace gsr { args.push_back(region); } - if(record_options.merge_audio_tracks) { - if(!audio_devices_merged.empty()) { - args.push_back("-a"); - args.push_back(audio_devices_merged.c_str()); - } - } else { - for(const std::string &audio_track : audio_tracks) { - args.push_back("-a"); - args.push_back(audio_track.c_str()); - } + for(const std::string &audio_track : audio_tracks) { + args.push_back("-a"); + args.push_back(audio_track.c_str()); } if(record_options.restore_portal_session) { @@ -2048,8 +2141,25 @@ namespace gsr { cursor_info = cursor_tracker->get_latest_cursor_info(); } - if(cursor_info) - return cursor_info->monitor_name; + std::string focused_monitor_name; + if(cursor_info) { + focused_monitor_name = std::move(cursor_info->monitor_name); + } else { + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + + Window x11_cursor_window = None; + mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window); + + const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display); + auto monitors = get_monitors(display); + const Monitor *focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value); + if(focused_monitor) + focused_monitor_name = focused_monitor->name; + } + + if(!focused_monitor_name.empty()) + return focused_monitor_name; else if(!capture_options.monitors.empty()) return capture_options.monitors.front().name; else @@ -2059,15 +2169,47 @@ namespace gsr { } } + void Overlay::prepare_gsr_output_for_reading() { + if(gpu_screen_recorder_process_output_fd <= 0) + return; + + const int fdl = fcntl(gpu_screen_recorder_process_output_fd, F_GETFL); + fcntl(gpu_screen_recorder_process_output_fd, F_SETFL, fdl | O_NONBLOCK); + gpu_screen_recorder_process_output_file = fdopen(gpu_screen_recorder_process_output_fd, "r"); + if(gpu_screen_recorder_process_output_file) + gpu_screen_recorder_process_output_fd = -1; + } + void Overlay::on_press_save_replay() { if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0) return; + replay_save_duration_min = 0; replay_save_show_notification = true; replay_save_clock.restart(); kill(gpu_screen_recorder_process, SIGUSR1); } + void Overlay::on_press_save_replay_1_min_replay() { + if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0) + return; + + replay_save_duration_min = 1; + replay_save_show_notification = true; + replay_save_clock.restart(); + kill(gpu_screen_recorder_process, SIGRTMIN+3); + } + + void Overlay::on_press_save_replay_10_min_replay() { + if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0) + return; + + replay_save_duration_min = 10; + replay_save_show_notification = true; + replay_save_clock.restart(); + kill(gpu_screen_recorder_process, SIGRTMIN+5); + } + bool Overlay::on_press_start_replay(bool disable_notification, bool finished_region_selection) { if(region_selector.is_started()) return false; @@ -2077,10 +2219,10 @@ namespace gsr { case RecordingStatus::REPLAY: break; case RecordingStatus::RECORD: - show_notification("Unable to start replay when recording.\nStop recording before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD); + show_notification("Unable to start replay when recording.\nStop recording before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); return false; case RecordingStatus::STREAM: - show_notification("Unable to start replay when streaming.\nStop streaming before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM); + show_notification("Unable to start replay when streaming.\nStop streaming before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM); return false; } @@ -2088,9 +2230,6 @@ namespace gsr { replay_save_show_notification = false; try_replay_startup = false; - // window->close(); - // usleep(1000 * 50); // 50 milliseconds - close_gpu_screen_recorder_output(); if(gpu_screen_recorder_process > 0) { @@ -2103,6 +2242,7 @@ namespace gsr { gpu_screen_recorder_process = -1; recording_status = RecordingStatus::NONE; + replay_save_duration_min = 0; update_ui_replay_stopped(); // TODO: Show this with a slight delay to make sure it doesn't show up in the video @@ -2113,11 +2253,11 @@ namespace gsr { } const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - replay_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options); - if(!validate_capture_target(replay_capture_target, capture_options)) { + recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options); + if(!validate_capture_target(recording_capture_target, capture_options)) { char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", replay_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::REPLAY); + snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str()); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY); return false; } @@ -2133,8 +2273,7 @@ namespace gsr { const std::string fps = std::to_string(config.replay_config.record_options.fps); const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate); const std::string output_directory = config.replay_config.save_directory; - const std::vector<std::string> audio_tracks = create_audio_tracks_real_names(config.replay_config.record_options.audio_tracks, config.replay_config.record_options.application_audio_invert, gsr_info); - const std::string audio_tracks_merged = merge_audio_tracks(audio_tracks); + const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.replay_config.record_options.audio_tracks_list, gsr_info); const std::string framerate_mode = config.replay_config.record_options.framerate_mode == "auto" ? "vfr" : config.replay_config.record_options.framerate_mode; const std::string replay_time = std::to_string(config.replay_config.replay_time); const char *video_codec = config.replay_config.record_options.video_codec.c_str(); @@ -2153,7 +2292,7 @@ namespace gsr { snprintf(size, sizeof(size), "%dx%d", (int)config.replay_config.record_options.video_width, (int)config.replay_config.record_options.video_height); std::vector<const char*> args = { - "gpu-screen-recorder", "-w", replay_capture_target.c_str(), + "gpu-screen-recorder", "-w", recording_capture_target.c_str(), "-c", config.replay_config.container.c_str(), "-ac", config.replay_config.record_options.audio_codec.c_str(), "-cursor", config.replay_config.record_options.record_cursor ? "yes" : "no", @@ -2172,24 +2311,31 @@ namespace gsr { args.push_back("yes"); } + if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) { + args.push_back("-replay-storage"); + args.push_back(config.replay_config.replay_storage.c_str()); + } + char region_str[128]; - add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, audio_tracks_merged, region_str, sizeof(region_str), region_selector); + add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector); + + if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { + args.push_back("-ro"); + args.push_back(config.record_config.save_directory.c_str()); + } args.push_back(nullptr); gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd); if(gpu_screen_recorder_process == -1) { - // TODO: Show notification failed to start + show_notification("Failed to launch gpu-screen-recorder to start replay", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY); + return false; } else { recording_status = RecordingStatus::REPLAY; update_ui_replay_started(); } - const int fdl = fcntl(gpu_screen_recorder_process_output_fd, F_GETFL); - fcntl(gpu_screen_recorder_process_output_fd, F_SETFL, fdl | O_NONBLOCK); - gpu_screen_recorder_process_output_file = fdopen(gpu_screen_recorder_process_output_fd, "r"); - if(gpu_screen_recorder_process_output_file) - gpu_screen_recorder_process_output_fd = -1; + prepare_gsr_output_for_reading(); // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. // Make clear to the user that the recording starts after the notification is gone. @@ -2202,11 +2348,11 @@ namespace gsr { // to see when the program has exit. if(!disable_notification && config.replay_config.show_replay_started_notifications) { char msg[256]; - if(is_capture_target_monitor(replay_capture_target.c_str())) + if(is_capture_target_monitor(recording_capture_target.c_str())) snprintf(msg, sizeof(msg), "Started replaying this monitor"); else - snprintf(msg, sizeof(msg), "Started replaying %s", replay_capture_target.c_str()); - show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, replay_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Started replaying %s", recording_capture_target.c_str()); + show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str()); } return true; @@ -2220,18 +2366,45 @@ namespace gsr { case RecordingStatus::NONE: case RecordingStatus::RECORD: break; - case RecordingStatus::REPLAY: - show_notification("Unable to start recording when replay is turned on.\nTurn off replay before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY); + case RecordingStatus::REPLAY: { + if(gpu_screen_recorder_process <= 0) + return; + + if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { + if(!replay_recording) { + if(config.record_config.show_recording_started_notifications) + show_notification("Started recording in the replay session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD); + update_ui_recording_started(); + } + replay_recording = true; + kill(gpu_screen_recorder_process, SIGRTMIN); + } else { + show_notification("Unable to start recording when replay is turned on.\nTurn off replay before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), get_color_theme().tint_color, NotificationType::REPLAY); + } return; - case RecordingStatus::STREAM: - show_notification("Unable to start recording when streaming.\nStop streaming before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM); + } + case RecordingStatus::STREAM: { + if(gpu_screen_recorder_process <= 0) + return; + + if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { + if(!replay_recording) { + if(config.record_config.show_recording_started_notifications) + show_notification("Started recording in the streaming session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD); + update_ui_recording_started(); + } + replay_recording = true; + kill(gpu_screen_recorder_process, SIGRTMIN); + } else { + show_notification("Unable to start recording when streaming.\nStop streaming before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), get_color_theme().tint_color, NotificationType::STREAM); + } return; + } } paused = false; - // window->close(); - // usleep(1000 * 50); // 50 milliseconds + close_gpu_screen_recorder_output(); if(gpu_screen_recorder_process > 0) { kill(gpu_screen_recorder_process, SIGINT); @@ -2243,7 +2416,7 @@ namespace gsr { int exit_code = -1; if(WIFEXITED(status)) exit_code = WEXITSTATUS(status); - on_stop_recording(exit_code); + on_stop_recording(exit_code, record_filepath); } gpu_screen_recorder_process = -1; @@ -2258,7 +2431,7 @@ namespace gsr { if(!validate_capture_target(config.record_config.record_options.record_area_option, capture_options)) { char err_msg[256]; snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::RECORD); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); return; } @@ -2276,8 +2449,7 @@ namespace gsr { const std::string fps = std::to_string(config.record_config.record_options.fps); const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate); const std::string output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str()); - const std::vector<std::string> audio_tracks = create_audio_tracks_real_names(config.record_config.record_options.audio_tracks, config.record_config.record_options.application_audio_invert, gsr_info); - const std::string audio_tracks_merged = merge_audio_tracks(audio_tracks); + const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.record_config.record_options.audio_tracks_list, gsr_info); const std::string framerate_mode = config.record_config.record_options.framerate_mode == "auto" ? "vfr" : config.record_config.record_options.framerate_mode; const char *video_codec = config.record_config.record_options.video_codec.c_str(); const char *encoder = "gpu"; @@ -2309,19 +2481,22 @@ namespace gsr { }; char region_str[128]; - add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, audio_tracks_merged, region_str, sizeof(region_str), region_selector); + add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector); args.push_back(nullptr); record_filepath = output_file; - gpu_screen_recorder_process = exec_program(args.data(), nullptr); + gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd); if(gpu_screen_recorder_process == -1) { - // TODO: Show notification failed to start + show_notification("Failed to launch gpu-screen-recorder to start recording", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); + return; } else { recording_status = RecordingStatus::RECORD; update_ui_recording_started(); } + prepare_gsr_output_for_reading(); + // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. // Make clear to the user that the recording starts after the notification is gone. // Maybe have the option in notification to show timer until its getting hidden, then the notification can say: @@ -2379,17 +2554,16 @@ namespace gsr { case RecordingStatus::STREAM: break; case RecordingStatus::REPLAY: - show_notification("Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY); + show_notification("Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY); return; case RecordingStatus::RECORD: - show_notification("Unable to start streaming when recording.\nStop recording before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD); + show_notification("Unable to start streaming when recording.\nStop recording before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); return; } paused = false; - // window->close(); - // usleep(1000 * 50); // 50 milliseconds + close_gpu_screen_recorder_output(); if(gpu_screen_recorder_process > 0) { kill(gpu_screen_recorder_process, SIGINT); @@ -2410,11 +2584,11 @@ namespace gsr { } const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - const std::string capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options); + recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options); if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) { char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Failed to start streaming, capture target \"%s\" is invalid. Please change capture target in settings", capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::STREAM); + snprintf(err_msg, sizeof(err_msg), "Failed to start streaming, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str()); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM); return; } @@ -2429,8 +2603,11 @@ namespace gsr { // TODO: Validate input, fallback to valid values const std::string fps = std::to_string(config.streaming_config.record_options.fps); const std::string video_bitrate = std::to_string(config.streaming_config.record_options.video_bitrate); - const std::vector<std::string> audio_tracks = create_audio_tracks_real_names(config.streaming_config.record_options.audio_tracks, config.streaming_config.record_options.application_audio_invert, gsr_info); - const std::string audio_tracks_merged = merge_audio_tracks(audio_tracks); + std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.streaming_config.record_options.audio_tracks_list, gsr_info); + // This isn't possible unless the user modified the config file manually, + // But we check it anyways as streaming on some sites can fail if there is more than one audio track + if(audio_tracks.size() > 1) + audio_tracks.resize(1); const std::string framerate_mode = config.streaming_config.record_options.framerate_mode == "auto" ? "vfr" : config.streaming_config.record_options.framerate_mode; const char *video_codec = config.streaming_config.record_options.video_codec.c_str(); const char *encoder = "gpu"; @@ -2454,7 +2631,7 @@ namespace gsr { snprintf(size, sizeof(size), "%dx%d", (int)config.streaming_config.record_options.video_width, (int)config.streaming_config.record_options.video_height); std::vector<const char*> args = { - "gpu-screen-recorder", "-w", capture_target.c_str(), + "gpu-screen-recorder", "-w", recording_capture_target.c_str(), "-c", container.c_str(), "-ac", config.streaming_config.record_options.audio_codec.c_str(), "-cursor", config.streaming_config.record_options.record_cursor ? "yes" : "no", @@ -2466,20 +2643,27 @@ namespace gsr { "-o", url.c_str() }; - config.streaming_config.record_options.merge_audio_tracks = true; char region_str[128]; - add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, audio_tracks_merged, region_str, sizeof(region_str), region_selector); + add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector); + + if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { + args.push_back("-ro"); + args.push_back(config.record_config.save_directory.c_str()); + } args.push_back(nullptr); - gpu_screen_recorder_process = exec_program(args.data(), nullptr); + gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd); if(gpu_screen_recorder_process == -1) { - // TODO: Show notification failed to start + show_notification("Failed to launch gpu-screen-recorder to start streaming", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM); + return; } else { recording_status = RecordingStatus::STREAM; update_ui_streaming_started(); } + prepare_gsr_output_for_reading(); + // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. // Make clear to the user that the recording starts after the notification is gone. // Maybe have the option in notification to show timer until its getting hidden, then the notification can say: @@ -2491,11 +2675,11 @@ namespace gsr { // to see when the program has exit. if(config.streaming_config.show_streaming_started_notifications) { char msg[256]; - if(is_capture_target_monitor(capture_target.c_str())) + if(is_capture_target_monitor(recording_capture_target.c_str())) snprintf(msg, sizeof(msg), "Started streaming this monitor"); else - snprintf(msg, sizeof(msg), "Started streaming %s", capture_target.c_str()); - show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, capture_target.c_str()); + snprintf(msg, sizeof(msg), "Started streaming %s", recording_capture_target.c_str()); + show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str()); } } @@ -2515,7 +2699,7 @@ namespace gsr { if(!validate_capture_target(record_area_option, capture_options)) { char err_msg[256]; snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid. Please change capture target in settings", screenshot_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::SCREENSHOT); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT); return; } @@ -2561,7 +2745,7 @@ namespace gsr { screenshot_filepath = output_file; gpu_screen_recorder_screenshot_process = exec_program(args.data(), nullptr); if(gpu_screen_recorder_screenshot_process == -1) { - // TODO: Show notification failed to start + show_notification("Failed to launch gpu-screen-recorder to take a screenshot", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT); } } @@ -2610,7 +2794,7 @@ namespace gsr { mgl_context *context = mgl_get_context(); Display *display = (Display*)context->connection; - XRaiseWindow(display, window->get_system_handle()); + XRaiseWindow(display, (Window)window->get_system_handle()); XFlush(display); } } diff --git a/src/Process.cpp b/src/Process.cpp index 0a62986..45be208 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -130,8 +130,6 @@ namespace gsr { exit_status = -1; break; } - - buffer[bytes_read] = '\0'; result.append(buffer, bytes_read); } diff --git a/src/Theme.cpp b/src/Theme.cpp index 2001f7d..6c384e3 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -75,7 +75,7 @@ namespace gsr { if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str())) goto error; - if(!theme->up_arrow_texture.load_from_file((resources_path + "images/up_arrow.png").c_str())) + if(!theme->up_arrow_texture.load_from_file((resources_path + "images/up_arrow.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; if(!theme->replay_button_texture.load_from_file((resources_path + "images/replay.png").c_str())) @@ -93,10 +93,10 @@ namespace gsr { if(!theme->logo_texture.load_from_file((resources_path + "images/gpu_screen_recorder_logo.png").c_str())) goto error; - if(!theme->checkbox_circle_texture.load_from_file((resources_path + "images/checkbox_circle.png").c_str())) + if(!theme->checkbox_circle_texture.load_from_file((resources_path + "images/checkbox_circle.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; - if(!theme->checkbox_background_texture.load_from_file((resources_path + "images/checkbox_background.png").c_str())) + if(!theme->checkbox_background_texture.load_from_file((resources_path + "images/checkbox_background.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; if(!theme->play_texture.load_from_file((resources_path + "images/play.png").c_str())) @@ -114,22 +114,31 @@ namespace gsr { if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str())) goto error; - if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + if(!theme->trash_texture.load_from_file((resources_path + "images/trash.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; - if(!theme->ps4_options_texture.load_from_file((resources_path + "images/ps4_options.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; - if(!theme->ps4_dpad_up_texture.load_from_file((resources_path + "images/ps4_dpad_up.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + if(!theme->ps4_options_texture.load_from_file((resources_path + "images/ps4_options.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; - if(!theme->ps4_dpad_down_texture.load_from_file((resources_path + "images/ps4_dpad_down.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + if(!theme->ps4_dpad_up_texture.load_from_file((resources_path + "images/ps4_dpad_up.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) + goto error; + + if(!theme->ps4_dpad_down_texture.load_from_file((resources_path + "images/ps4_dpad_down.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; - if(!theme->ps4_dpad_left_texture.load_from_file((resources_path + "images/ps4_dpad_left.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + if(!theme->ps4_dpad_left_texture.load_from_file((resources_path + "images/ps4_dpad_left.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) + goto error; + + if(!theme->ps4_dpad_right_texture.load_from_file((resources_path + "images/ps4_dpad_right.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) + goto error; + + if(!theme->ps4_cross_texture.load_from_file((resources_path + "images/ps4_cross.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; - if(!theme->ps4_dpad_right_texture.load_from_file((resources_path + "images/ps4_dpad_right.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + if(!theme->ps4_triangle_texture.load_from_file((resources_path + "images/ps4_triangle.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) goto error; return true; diff --git a/src/Utils.cpp b/src/Utils.cpp index df6db2f..f23a330 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -22,6 +22,16 @@ namespace gsr { } } + bool starts_with(std::string_view str, const char *substr) { + size_t len = strlen(substr); + return str.size() >= len && memcmp(str.data(), substr, len) == 0; + } + + bool ends_with(std::string_view str, const char *substr) { + size_t len = strlen(substr); + return str.size() >= len && memcmp(str.data() + str.size() - len, substr, len) == 0; + } + std::string get_home_dir() { const char *home_dir = getenv("HOME"); if(!home_dir) { diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp index cf983db..eb6080f 100644 --- a/src/WindowUtils.cpp +++ b/src/WindowUtils.cpp @@ -5,6 +5,7 @@ #include <X11/extensions/XInput2.h> #include <X11/extensions/Xfixes.h> #include <X11/extensions/shapeconst.h> +#include <X11/extensions/Xrandr.h> #include <mglpp/system/Utf8.hpp> @@ -518,14 +519,21 @@ namespace gsr { return XGetSelectionOwner(dpy, prop_atom) != None; } - static void get_monitors_callback(const mgl_monitor *monitor, void *userdata) { - std::vector<Monitor> *monitors = (std::vector<Monitor>*)userdata; - monitors->push_back({mgl::vec2i(monitor->pos.x, monitor->pos.y), mgl::vec2i(monitor->size.x, monitor->size.y), std::string(monitor->name)}); - } - std::vector<Monitor> get_monitors(Display *dpy) { std::vector<Monitor> monitors; - mgl_for_each_active_monitor_output(dpy, get_monitors_callback, &monitors); + int nmonitors = 0; + XRRMonitorInfo *monitor_info = XRRGetMonitors(dpy, DefaultRootWindow(dpy), True, &nmonitors); + if(monitor_info) { + for(int i = 0; i < nmonitors; ++i) { + char *monitor_name = XGetAtomName(dpy, monitor_info[i].name); + if(!monitor_name) + continue; + + monitors.push_back({mgl::vec2i(monitor_info[i].x, monitor_info[i].y), mgl::vec2i(monitor_info[i].width, monitor_info[i].height), std::string(monitor_name)}); + XFree(monitor_name); + } + XRRFreeMonitors(monitor_info); + } return monitors; } diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index d8cb85b..6e343c4 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -63,7 +63,7 @@ namespace gsr { window.draw(sprite); const int padding_icon_right = padding_right_icon_scale * get_button_height(); - text.set_position((sprite.get_position() + mgl::vec2f(sprite.get_size().x + padding_icon_right, sprite.get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor()); + text.set_position((sprite.get_position() + mgl::vec2f(sprite.get_size().x + padding_icon_right, sprite.get_size().y * 0.5f - text.get_bounds().size.y * 0.52f)).floor()); window.draw(text); } else { text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor()); diff --git a/src/gui/ComboBox.cpp b/src/gui/ComboBox.cpp index dbe9aa0..4287a53 100644 --- a/src/gui/ComboBox.cpp +++ b/src/gui/ComboBox.cpp @@ -85,7 +85,7 @@ namespace gsr { void ComboBox::add_item(const std::string &text, const std::string &id) { items.push_back({mgl::Text(text, *font), id, {0.0f, 0.0f}}); - items.back().text.set_max_width(font->get_character_size() * 22); // TODO: Make a proper solution + items.back().text.set_max_width(font->get_character_size() * 20); // TODO: Make a proper solution //items.back().text.set_max_rows(1); dirty = true; } diff --git a/src/gui/DropdownButton.cpp b/src/gui/DropdownButton.cpp index bdc4027..5d1cc38 100644 --- a/src/gui/DropdownButton.cpp +++ b/src/gui/DropdownButton.cpp @@ -110,6 +110,14 @@ namespace gsr { window.draw(rect); } + if(activated) { + description.set_color(get_color_theme().tint_color); + icon_sprite.set_color(get_color_theme().tint_color); + } else { + description.set_color(mgl::Color(150, 150, 150)); + icon_sprite.set_color(mgl::Color(255, 255, 255)); + } + const int text_margin = size.y * 0.085; const auto title_bounds = title.get_bounds(); @@ -148,7 +156,7 @@ namespace gsr { window.draw(separator); } - if(mouse_inside_item == -1) { + if(mouse_inside_item == -1 && item.enabled) { const bool inside = mgl::FloatRect(item_position, item_size).contains({ (float)mouse_pos.x, (float)mouse_pos.y }); if(inside) { draw_rectangle_outline(window, item_position, item_size, get_color_theme().tint_color, border_size); @@ -161,16 +169,18 @@ namespace gsr { mgl::Sprite icon(item.icon_texture); icon.set_height((int)(item_size.y * 0.4f)); icon.set_position((item_position + mgl::vec2f(padding_left, item_size.y * 0.5f - icon.get_size().y * 0.5f)).floor()); + icon.set_color(item.enabled ? mgl::Color(255, 255, 255, 255) : mgl::Color(255, 255, 255, 80)); window.draw(icon); icon_offset = icon.get_size().x + icon_spacing; } item.text.set_position((item_position + mgl::vec2f(padding_left + icon_offset, item_size.y * 0.5f - text_bounds.size.y * 0.5f)).floor()); + item.text.set_color(item.enabled ? mgl::Color(255, 255, 255, 255) : mgl::Color(255, 255, 255, 80)); window.draw(item.text); const auto description_bounds = item.description_text.get_bounds(); item.description_text.set_position((item_position + mgl::vec2f(item_size.x - description_bounds.size.x - padding_right, item_size.y * 0.5f - description_bounds.size.y * 0.5f)).floor()); - item.description_text.set_color(mgl::Color(255, 255, 255, 120)); + item.description_text.set_color(item.enabled ? mgl::Color(255, 255, 255, 120) : mgl::Color(255, 255, 255, 40)); window.draw(item.description_text); item_position.y += item_size.y; @@ -179,6 +189,10 @@ namespace gsr { } void DropdownButton::add_item(const std::string &text, const std::string &id, const std::string &description) { + for(auto &item : items) { + if(item.id == id) + return; + } items.push_back({mgl::Text(text, *title_font), mgl::Text(description, *description_font), nullptr, id}); dirty = true; } @@ -210,6 +224,15 @@ namespace gsr { } } + void DropdownButton::set_item_enabled(const std::string &id, bool enabled) { + for(auto &item : items) { + if(item.id == id) { + item.enabled = enabled; + return; + } + } + } + void DropdownButton::set_description(std::string description_text) { description.set_string(std::move(description_text)); } @@ -219,14 +242,6 @@ namespace gsr { return; this->activated = activated; - - if(activated) { - description.set_color(get_color_theme().tint_color); - icon_sprite.set_color(get_color_theme().tint_color); - } else { - description.set_color(mgl::Color(150, 150, 150)); - icon_sprite.set_color(mgl::Color(255, 255, 255)); - } } void DropdownButton::update_if_dirty() { diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp index 1e2a444..5444ae5 100644 --- a/src/gui/GlobalSettingsPage.cpp +++ b/src/gui/GlobalSettingsPage.cpp @@ -1,7 +1,6 @@ #include "../../include/gui/GlobalSettingsPage.hpp" #include "../../include/Overlay.hpp" -#include "../../include/GlobalHotkeys.hpp" #include "../../include/Theme.hpp" #include "../../include/Process.hpp" #include "../../include/gui/GsrPage.hpp" @@ -149,7 +148,7 @@ namespace gsr { tint_color_radio_button_ptr = tint_color_radio_button.get(); tint_color_radio_button->add_item("Red", "amd"); tint_color_radio_button->add_item("Green", "nvidia"); - tint_color_radio_button->add_item("blue", "intel"); + tint_color_radio_button->add_item("Blue", "intel"); tint_color_radio_button->on_selection_changed = [](const std::string&, const std::string &id) { if(id == "amd") get_color_theme().tint_color = mgl::Color(221, 0, 49); @@ -256,6 +255,30 @@ namespace gsr { return list; } + std::unique_ptr<List> GlobalSettingsPage::create_replay_partial_save_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save 1 minute replay:", get_color_theme().text_color)); + auto save_replay_1_min_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_replay_1_min_button_ptr = save_replay_1_min_button.get(); + list->add_widget(std::move(save_replay_1_min_button)); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save 10 minute replay:", get_color_theme().text_color)); + auto save_replay_10_min_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_replay_10_min_button_ptr = save_replay_10_min_button.get(); + list->add_widget(std::move(save_replay_10_min_button)); + + save_replay_1_min_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::REPLAY_SAVE_1_MIN); + }; + + save_replay_10_min_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::REPLAY_SAVE_10_MIN); + }; + + return list; + } + std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_options() { auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); @@ -335,6 +358,8 @@ namespace gsr { config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0}; config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.save_1_min_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0}; config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0}; config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0}; config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0}; @@ -374,6 +399,7 @@ namespace gsr { list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x)); list_ptr->add_widget(create_show_hide_hotkey_options()); list_ptr->add_widget(create_replay_hotkey_options()); + list_ptr->add_widget(create_replay_partial_save_hotkey_options()); list_ptr->add_widget(create_record_hotkey_options()); list_ptr->add_widget(create_stream_hotkey_options()); list_ptr->add_widget(create_screenshot_hotkey_options()); @@ -393,6 +419,8 @@ namespace gsr { list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_options_texture, get_theme().body_font.get_character_size(), "to show/hide the UI")); list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_up_texture, get_theme().body_font.get_character_size(), "to take a screenshot")); list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_down_texture, get_theme().body_font.get_character_size(), "to save a replay")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_cross_texture, get_theme().body_font.get_character_size(), "to save a 1 minute replay")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_triangle_texture, get_theme().body_font.get_character_size(), "to save a 10 minute replay")); list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_left_texture, get_theme().body_font.get_character_size(), "to start/stop recording")); list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_right_texture, get_theme().body_font.get_character_size(), "to turn replay on/off")); return subsection; @@ -490,6 +518,8 @@ namespace gsr { void GlobalSettingsPage::load_hotkeys() { turn_replay_on_off_button_ptr->set_text(config.replay_config.start_stop_hotkey.to_string()); save_replay_button_ptr->set_text(config.replay_config.save_hotkey.to_string()); + save_replay_1_min_button_ptr->set_text(config.replay_config.save_1_min_hotkey.to_string()); + save_replay_10_min_button_ptr->set_text(config.replay_config.save_10_min_hotkey.to_string()); start_stop_recording_button_ptr->set_text(config.record_config.start_stop_hotkey.to_string()); pause_unpause_recording_button_ptr->set_text(config.record_config.pause_unpause_hotkey.to_string()); @@ -567,6 +597,10 @@ namespace gsr { return turn_replay_on_off_button_ptr; case ConfigureHotkeyType::REPLAY_SAVE: return save_replay_button_ptr; + case ConfigureHotkeyType::REPLAY_SAVE_1_MIN: + return save_replay_1_min_button_ptr; + case ConfigureHotkeyType::REPLAY_SAVE_10_MIN: + return save_replay_10_min_button_ptr; case ConfigureHotkeyType::RECORD_START_STOP: return start_stop_recording_button_ptr; case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: @@ -591,6 +625,10 @@ namespace gsr { return &config.replay_config.start_stop_hotkey; case ConfigureHotkeyType::REPLAY_SAVE: return &config.replay_config.save_hotkey; + case ConfigureHotkeyType::REPLAY_SAVE_1_MIN: + return &config.replay_config.save_1_min_hotkey; + case ConfigureHotkeyType::REPLAY_SAVE_10_MIN: + return &config.replay_config.save_10_min_hotkey; case ConfigureHotkeyType::RECORD_START_STOP: return &config.record_config.start_stop_hotkey; case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: @@ -643,6 +681,12 @@ namespace gsr { case ConfigureHotkeyType::REPLAY_SAVE: hotkey_configure_action_name = "Save replay"; break; + case ConfigureHotkeyType::REPLAY_SAVE_1_MIN: + hotkey_configure_action_name = "Save 1 minute replay"; + break; + case ConfigureHotkeyType::REPLAY_SAVE_10_MIN: + hotkey_configure_action_name = "Save 10 minute replay"; + break; case ConfigureHotkeyType::RECORD_START_STOP: hotkey_configure_action_name = "Start/stop recording"; break; diff --git a/src/gui/GsrPage.cpp b/src/gui/GsrPage.cpp index 663187c..b4005f5 100644 --- a/src/gui/GsrPage.cpp +++ b/src/gui/GsrPage.cpp @@ -39,8 +39,9 @@ namespace gsr { // Process widgets by visibility (backwards) return widgets.for_each_reverse([selected_widget, &window, &event, content_page_position](std::unique_ptr<Widget> &widget) { - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, content_page_position)) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, content_page_position)) return false; } return true; diff --git a/src/gui/List.cpp b/src/gui/List.cpp index 5294e36..57a6045 100644 --- a/src/gui/List.cpp +++ b/src/gui/List.cpp @@ -24,14 +24,23 @@ namespace gsr { // Process widgets by visibility (backwards) return widgets.for_each_reverse([selected_widget, &event, &window](std::unique_ptr<Widget> &widget) { // Ignore offset because widgets are positioned with offset in ::draw, this solution is simpler - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) return false; } return true; }); } + List::~List() { + widgets.for_each([this](std::unique_ptr<Widget> &widget) { + if(widget->parent_widget == this) + widget->parent_widget = nullptr; + return true; + }, true); + } + void List::draw(mgl::Window &window, mgl::vec2f offset) { if(!visible) return; @@ -104,15 +113,6 @@ namespace gsr { selected_widget->draw(window, mgl::vec2f(0.0f, 0.0f)); } - // void List::remove_child_widget(Widget *widget) { - // for(auto it = widgets.begin(), end = widgets.end(); it != end; ++it) { - // if(it->get() == widget) { - // widgets.erase(it); - // return; - // } - // } - // } - void List::add_widget(std::unique_ptr<Widget> widget) { widget->parent_widget = this; widgets.push_back(std::move(widget)); @@ -122,6 +122,10 @@ namespace gsr { widgets.remove(widget); } + void List::replace_widget(Widget *widget, std::unique_ptr<Widget> new_widget) { + widgets.replace_item(widget, std::move(new_widget)); + } + void List::clear() { widgets.clear(); } @@ -137,6 +141,10 @@ namespace gsr { return nullptr; } + size_t List::get_num_children() const { + return widgets.size(); + } + void List::set_spacing(float spacing) { spacing_scale = spacing; } diff --git a/src/gui/Page.cpp b/src/gui/Page.cpp index ae13d82..5f21b71 100644 --- a/src/gui/Page.cpp +++ b/src/gui/Page.cpp @@ -1,14 +1,13 @@ #include "../../include/gui/Page.hpp" namespace gsr { - // void Page::remove_child_widget(Widget *widget) { - // for(auto it = widgets.begin(), end = widgets.end(); it != end; ++it) { - // if(it->get() == widget) { - // widgets.erase(it); - // return; - // } - // } - // } + Page::~Page() { + widgets.for_each([this](std::unique_ptr<Widget> &widget) { + if(widget->parent_widget == this) + widget->parent_widget = nullptr; + return true; + }, true); + } void Page::add_widget(std::unique_ptr<Widget> widget) { widget->parent_widget = this; diff --git a/src/gui/RadioButton.cpp b/src/gui/RadioButton.cpp index a6ef96a..bbb958a 100644 --- a/src/gui/RadioButton.cpp +++ b/src/gui/RadioButton.cpp @@ -169,7 +169,7 @@ namespace gsr { } } - const std::string RadioButton::get_selected_id() const { + const std::string& RadioButton::get_selected_id() const { if(items.empty()) { static std::string dummy; return dummy; @@ -177,4 +177,13 @@ namespace gsr { return items[selected_item].id; } } + + const std::string& RadioButton::get_selected_text() const { + if(items.empty()) { + static std::string dummy; + return dummy; + } else { + return items[selected_item].text.get_string(); + } + } }
\ No newline at end of file diff --git a/src/gui/ScreenshotSettingsPage.cpp b/src/gui/ScreenshotSettingsPage.cpp index 2dedd86..5b8efbd 100644 --- a/src/gui/ScreenshotSettingsPage.cpp +++ b/src/gui/ScreenshotSettingsPage.cpp @@ -257,8 +257,7 @@ namespace gsr { void ScreenshotSettingsPage::add_widgets() { content_page_ptr->add_widget(create_settings()); - record_area_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; + record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool window_selected = id == "window"; const bool portal_selected = id == "portal"; select_window_list_ptr->set_visible(window_selected); diff --git a/src/gui/ScrollablePage.cpp b/src/gui/ScrollablePage.cpp index d5e92d0..cec20d3 100644 --- a/src/gui/ScrollablePage.cpp +++ b/src/gui/ScrollablePage.cpp @@ -15,6 +15,14 @@ namespace gsr { ScrollablePage::ScrollablePage(mgl::vec2f size) : size(size) {} + ScrollablePage::~ScrollablePage() { + widgets.for_each([this](std::unique_ptr<Widget> &widget) { + if(widget->parent_widget == this) + widget->parent_widget = nullptr; + return true; + }, true); + } + bool ScrollablePage::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) { if(!visible) return true; @@ -57,8 +65,9 @@ namespace gsr { // Process widgets by visibility (backwards) const bool continue_events = widgets.for_each_reverse([selected_widget, &window, &event, offset](std::unique_ptr<Widget> &widget) { - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, offset)) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, offset)) return false; } return true; diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 663e941..363b48c 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -11,6 +11,8 @@ #include <string.h> namespace gsr { + static const char *custom_app_audio_tag = "[custom]"; + enum class AudioTrackType { DEVICE, APPLICATION, @@ -194,129 +196,229 @@ namespace gsr { return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); } - std::unique_ptr<ComboBox> SettingsPage::create_audio_device_selection_combobox() { + static bool audio_device_is_output(const std::string &audio_device_id) { + return audio_device_id == "default_output" || ends_with(audio_device_id, ".monitor"); + } + + std::unique_ptr<ComboBox> SettingsPage::create_audio_device_selection_combobox(AudioDeviceType device_type) { auto audio_device_box = std::make_unique<ComboBox>(&get_theme().body_font); for(const auto &audio_device : audio_devices) { - audio_device_box->add_item(audio_device.description, audio_device.name); + const bool device_is_output = audio_device_is_output(audio_device.name); + if((device_type == AudioDeviceType::OUTPUT && device_is_output) || (device_type == AudioDeviceType::INPUT && !device_is_output)) { + std::string description = audio_device.description; + if(starts_with(description, "Monitor of ")) + description.erase(0, 11); + audio_device_box->add_item(description, audio_device.name); + } } return audio_device_box; } - std::unique_ptr<Button> SettingsPage::create_remove_audio_device_button(List *audio_device_list_ptr) { - auto remove_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Remove", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - remove_audio_track_button->on_click = [this, audio_device_list_ptr]() { - audio_track_list_ptr->remove_widget(audio_device_list_ptr); + static void set_application_audio_options_visible(Subsection *audio_track_subsection, bool visible, const GsrInfo &gsr_info) { + if(!gsr_info.system_info.supports_app_audio) + visible = false; + + List *audio_track_items_list = dynamic_cast<List*>(audio_track_subsection->get_inner_widget()); + + List *buttons_list = dynamic_cast<List*>(audio_track_items_list->get_child_widget_by_index(1)); + Button *add_application_audio_button = dynamic_cast<Button*>(buttons_list->get_child_widget_by_index(2)); + add_application_audio_button->set_visible(visible); + + CheckBox *invert_app_audio_checkbox = dynamic_cast<CheckBox*>(audio_track_items_list->get_child_widget_by_index(3)); + invert_app_audio_checkbox->set_visible(visible); + } + + static void set_application_audio_options_visible(List *audio_track_section_list_ptr, bool visible, const GsrInfo &gsr_info) { + audio_track_section_list_ptr->for_each_child_widget([visible, &gsr_info](std::unique_ptr<Widget> &widget) { + Subsection *audio_track_subsection = dynamic_cast<Subsection*>(widget.get()); + set_application_audio_options_visible(audio_track_subsection, visible, gsr_info); + return true; + }); + } + + std::unique_ptr<Button> SettingsPage::create_remove_audio_device_button(List *audio_input_list_ptr, List *audio_device_list_ptr) { + auto remove_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 0)); + remove_audio_track_button->set_icon(&get_theme().trash_texture); + remove_audio_track_button->set_icon_padding_scale(0.75f); + remove_audio_track_button->on_click = [audio_input_list_ptr, audio_device_list_ptr]() { + audio_input_list_ptr->remove_widget(audio_device_list_ptr); }; return remove_audio_track_button; } - std::unique_ptr<List> SettingsPage::create_audio_device() { + std::unique_ptr<List> SettingsPage::create_audio_device(AudioDeviceType device_type, List *audio_input_list_ptr) { auto audio_device_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); audio_device_list->userdata = (void*)(uintptr_t)AudioTrackType::DEVICE; - audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Device:", get_color_theme().text_color)); - audio_device_list->add_widget(create_audio_device_selection_combobox()); - audio_device_list->add_widget(create_remove_audio_device_button(audio_device_list.get())); + audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, device_type == AudioDeviceType::OUTPUT ? "Output device:" : "Input device: ", get_color_theme().text_color)); + audio_device_list->add_widget(create_audio_device_selection_combobox(device_type)); + audio_device_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, audio_device_list.get())); return audio_device_list; } - std::unique_ptr<Button> SettingsPage::create_add_audio_device_button() { - auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add audio device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - add_audio_track_button->on_click = [this]() { + std::unique_ptr<Button> SettingsPage::create_add_audio_track_button() { + auto button = std::make_unique<Button>(&get_theme().body_font, "Add audio track", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + button->on_click = [this]() { + audio_track_section_list_ptr->add_widget(create_audio_track_section(audio_section_ptr)); + }; + button->set_visible(type != Type::STREAM); + return button; + } + + std::unique_ptr<Button> SettingsPage::create_add_audio_output_device_button(List *audio_input_list_ptr) { + auto button = std::make_unique<Button>(&get_theme().body_font, "Add output device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + button->on_click = [this, audio_input_list_ptr]() { audio_devices = get_audio_devices(); - audio_track_list_ptr->add_widget(create_audio_device()); + audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::OUTPUT, audio_input_list_ptr)); }; - return add_audio_track_button; + return button; + } + + std::unique_ptr<Button> SettingsPage::create_add_audio_input_device_button(List *audio_input_list_ptr) { + auto button = std::make_unique<Button>(&get_theme().body_font, "Add input device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + button->on_click = [this, audio_input_list_ptr]() { + audio_devices = get_audio_devices(); + audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::INPUT, audio_input_list_ptr)); + }; + return button; } - std::unique_ptr<ComboBox> SettingsPage::create_application_audio_selection_combobox() { + std::unique_ptr<ComboBox> SettingsPage::create_application_audio_selection_combobox(List *application_audio_row) { auto audio_device_box = std::make_unique<ComboBox>(&get_theme().body_font); + ComboBox *audio_device_box_ptr = audio_device_box.get(); for(const auto &app_audio : application_audio) { audio_device_box->add_item(app_audio, app_audio); } + audio_device_box->add_item("Custom...", custom_app_audio_tag); + + audio_device_box->on_selection_changed = [application_audio_row, audio_device_box_ptr](const std::string&, const std::string &id) { + if(id == custom_app_audio_tag) { + application_audio_row->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION_CUSTOM; + auto custom_app_audio_entry = std::make_unique<Entry>(&get_theme().body_font, "", (int)(get_theme().body_font.get_character_size() * 10.0f)); + application_audio_row->replace_widget(audio_device_box_ptr, std::move(custom_app_audio_entry)); + } + }; + return audio_device_box; } - std::unique_ptr<List> SettingsPage::create_application_audio() { + std::unique_ptr<List> SettingsPage::create_application_audio(List *audio_input_list_ptr) { auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION; - application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "App: ", get_color_theme().text_color)); - application_audio_list->add_widget(create_application_audio_selection_combobox()); - application_audio_list->add_widget(create_remove_audio_device_button(application_audio_list.get())); + application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color)); + application_audio_list->add_widget(create_application_audio_selection_combobox(application_audio_list.get())); + application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get())); return application_audio_list; } - std::unique_ptr<List> SettingsPage::create_custom_application_audio() { + std::unique_ptr<List> SettingsPage::create_custom_application_audio(List *audio_input_list_ptr) { auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION_CUSTOM; - application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "App: ", get_color_theme().text_color)); + application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color)); application_audio_list->add_widget(std::make_unique<Entry>(&get_theme().body_font, "", (int)(get_theme().body_font.get_character_size() * 10.0f))); - application_audio_list->add_widget(create_remove_audio_device_button(application_audio_list.get())); + application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get())); return application_audio_list; } - std::unique_ptr<Button> SettingsPage::create_add_application_audio_button() { + std::unique_ptr<Button> SettingsPage::create_add_application_audio_button(List *audio_input_list_ptr) { auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add application audio", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - add_application_audio_button_ptr = add_audio_track_button.get(); - add_audio_track_button->on_click = [this]() { + add_audio_track_button->on_click = [this, audio_input_list_ptr]() { application_audio = get_application_audio(); - audio_track_list_ptr->add_widget(create_application_audio()); - }; - return add_audio_track_button; - } - - std::unique_ptr<Button> SettingsPage::create_add_custom_application_audio_button() { - auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add custom application audio", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - add_custom_application_audio_button_ptr = add_audio_track_button.get(); - add_audio_track_button->on_click = [this]() { - audio_track_list_ptr->add_widget(create_custom_application_audio()); + if(application_audio.empty()) + audio_input_list_ptr->add_widget(create_custom_application_audio(audio_input_list_ptr)); + else + audio_input_list_ptr->add_widget(create_application_audio(audio_input_list_ptr)); }; return add_audio_track_button; } - std::unique_ptr<List> SettingsPage::create_add_audio_buttons() { + std::unique_ptr<List> SettingsPage::create_add_audio_buttons(List *audio_input_list_ptr) { auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); - list->add_widget(create_add_audio_device_button()); - list->add_widget(create_add_application_audio_button()); - list->add_widget(create_add_custom_application_audio_button()); + list->add_widget(create_add_audio_output_device_button(audio_input_list_ptr)); + list->add_widget(create_add_audio_input_device_button(audio_input_list_ptr)); + list->add_widget(create_add_application_audio_button(audio_input_list_ptr)); return list; } - std::unique_ptr<List> SettingsPage::create_audio_track_track_section() { + std::unique_ptr<List> SettingsPage::create_audio_input_section() { auto list = std::make_unique<List>(List::Orientation::VERTICAL); - audio_track_list_ptr = list.get(); - audio_track_list_ptr->add_widget(create_audio_device()); // Add default_output by default + //list->add_widget(create_audio_device(list.get())); // Add default_output by default return list; } - std::unique_ptr<CheckBox> SettingsPage::create_split_audio_checkbox() { - auto split_audio_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Split each device/app audio into separate audio tracks"); - split_audio_checkbox->set_checked(false); - split_audio_checkbox_ptr = split_audio_checkbox.get(); - return split_audio_checkbox; - } - std::unique_ptr<CheckBox> SettingsPage::create_application_audio_invert_checkbox() { auto application_audio_invert_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record audio from all applications except the selected ones"); application_audio_invert_checkbox->set_checked(false); - application_audio_invert_checkbox_ptr = application_audio_invert_checkbox.get(); return application_audio_invert_checkbox; } - std::unique_ptr<Widget> SettingsPage::create_audio_track_section() { + static void update_audio_track_titles(List *audio_track_section_list_ptr) { + int index = 0; + audio_track_section_list_ptr->for_each_child_widget([&index](std::unique_ptr<Widget> &widget) { + char audio_track_name[32]; + snprintf(audio_track_name, sizeof(audio_track_name), "Audio track #%d", 1 + index); + ++index; + + Subsection *subsection = dynamic_cast<Subsection*>(widget.get()); + List *subesection_items = dynamic_cast<List*>(subsection->get_inner_widget()); + Label *audio_track_title = dynamic_cast<Label*>(dynamic_cast<List*>(subesection_items->get_child_widget_by_index(0))->get_child_widget_by_index(0)); + audio_track_title->set_text(audio_track_name); + return true; + }); + } + + std::unique_ptr<List> SettingsPage::create_audio_track_title_and_remove(Subsection *audio_track_subsection, const char *title) { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + list->add_widget(std::make_unique<Label>(&get_theme().title_font, title, get_color_theme().text_color)); + + auto remove_track_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 0)); + remove_track_button->set_icon(&get_theme().trash_texture); + remove_track_button->set_icon_padding_scale(0.75f); + remove_track_button->on_click = [this, audio_track_subsection]() { + audio_track_section_list_ptr->remove_widget(audio_track_subsection); + update_audio_track_titles(audio_track_section_list_ptr); + }; + list->add_widget(std::move(remove_track_button)); + list->set_visible(type != Type::STREAM); + return list; + } + + std::unique_ptr<Subsection> SettingsPage::create_audio_track_section(Widget *parent_widget) { + char audio_track_name[32]; + snprintf(audio_track_name, sizeof(audio_track_name), "Audio track #%d", 1 + (int)audio_track_section_list_ptr->get_num_children()); + + auto audio_input_section = create_audio_input_section(); + List *audio_input_section_ptr = audio_input_section.get(); + + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + List *list_ptr = list.get(); + auto subsection = std::make_unique<Subsection>("", std::move(std::move(list)), mgl::vec2f(parent_widget->get_inner_size().x, 0.0f)); + subsection->set_bg_color(mgl::Color(35, 40, 44)); + + list_ptr->add_widget(create_audio_track_title_and_remove(subsection.get(), audio_track_name)); + list_ptr->add_widget(create_add_audio_buttons(audio_input_section_ptr)); + list_ptr->add_widget(std::move(audio_input_section)); + list_ptr->add_widget(create_application_audio_invert_checkbox()); + + set_application_audio_options_visible(subsection.get(), view_radio_button_ptr->get_selected_id() == "advanced", *gsr_info); + return subsection; + } + + std::unique_ptr<List> SettingsPage::create_audio_track_section_list() { auto list = std::make_unique<List>(List::Orientation::VERTICAL); - list->add_widget(create_add_audio_buttons()); - list->add_widget(create_audio_track_track_section()); + audio_track_section_list_ptr = list.get(); return list; } std::unique_ptr<Widget> SettingsPage::create_audio_section() { auto audio_device_section_list = std::make_unique<List>(List::Orientation::VERTICAL); - audio_device_section_list->add_widget(create_audio_track_section()); - if(type != Type::STREAM) - audio_device_section_list->add_widget(create_split_audio_checkbox()); - audio_device_section_list->add_widget(create_application_audio_invert_checkbox()); - audio_device_section_list->add_widget(create_audio_codec()); - return std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + List *audio_device_section_list_ptr = audio_device_section_list.get(); + + auto subsection = std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + audio_section_ptr = subsection.get(); + audio_device_section_list_ptr->add_widget(create_add_audio_track_button()); + audio_device_section_list_ptr->add_widget(create_audio_track_section_list()); + audio_device_section_list_ptr->add_widget(create_audio_codec()); + return subsection; } std::unique_ptr<List> SettingsPage::create_video_quality_box() { @@ -531,8 +633,7 @@ namespace gsr { void SettingsPage::add_widgets() { content_page_ptr->add_widget(create_settings()); - record_area_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; + record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool window_selected = id == "window"; const bool focused_selected = id == "focused"; const bool portal_selected = id == "portal"; @@ -549,8 +650,7 @@ namespace gsr { video_resolution_list_ptr->set_visible(!focused_selected && checked); }; - video_quality_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; + video_quality_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool custom_selected = id == "custom"; video_bitrate_list_ptr->set_visible(custom_selected); @@ -569,12 +669,6 @@ namespace gsr { record_area_box_ptr->set_selected_item("window"); else record_area_box_ptr->on_selection_changed("", ""); - - if(!gsr_info->system_info.supports_app_audio) { - add_application_audio_button_ptr->set_visible(false); - add_custom_application_audio_button_ptr->set_visible(false); - application_audio_invert_checkbox_ptr->set_visible(false); - } } void SettingsPage::add_page_specific_widgets() { @@ -641,7 +735,7 @@ namespace gsr { auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); auto replay_time_entry = std::make_unique<Entry>(&get_theme().body_font, "60", get_theme().body_font.get_character_size() * 3); - replay_time_entry->validate_handler = create_entry_validator_integer_in_range(1, 10800); + replay_time_entry->validate_handler = create_entry_validator_integer_in_range(2, 86400); replay_time_entry_ptr = replay_time_entry.get(); list->add_widget(std::move(replay_time_entry)); @@ -659,6 +753,24 @@ namespace gsr { return replay_time_list; } + std::unique_ptr<List> SettingsPage::create_replay_storage() { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Where should temporary replay data be stored?", get_color_theme().text_color)); + auto replay_storage_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + replay_storage_button_ptr = replay_storage_button.get(); + replay_storage_button->add_item("RAM", "ram"); + replay_storage_button->add_item("Disk (Not recommended on SSDs)", "disk"); + + replay_storage_button->on_selection_changed = [this](const std::string&, const std::string &id) { + update_estimated_replay_file_size(id); + return true; + }; + + list->add_widget(std::move(replay_storage_button)); + list->set_visible(gsr_info->system_info.gsr_version >= GsrVersion{5, 5, 0}); + return list; + } + std::unique_ptr<RadioButton> SettingsPage::create_start_replay_automatically() { char fullscreen_text[256]; snprintf(fullscreen_text, sizeof(fullscreen_text), "Turn on replay when starting a fullscreen application%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)"); @@ -692,13 +804,13 @@ namespace gsr { return label; } - void SettingsPage::update_estimated_replay_file_size() { + void SettingsPage::update_estimated_replay_file_size(const std::string &replay_storage_type) { const int64_t replay_time_seconds = atoi(replay_time_entry_ptr->get_text().c_str()); const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL; const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024; char buffer[256]; - snprintf(buffer, sizeof(buffer), "Estimated video max file size in RAM: %.2fMB.\nChange video bitrate or replay duration to change file size.", video_filesize_mb); + snprintf(buffer, sizeof(buffer), "Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.", replay_storage_type == "ram" ? "in RAM" : "on disk", video_filesize_mb); estimated_file_size_ptr->set_text(buffer); } @@ -716,6 +828,16 @@ namespace gsr { replay_time_label_ptr->set_text(buffer); } + void SettingsPage::view_changed(bool advanced_view, Subsection *notifications_subsection_ptr) { + color_range_list_ptr->set_visible(advanced_view); + audio_codec_ptr->set_visible(advanced_view); + video_codec_ptr->set_visible(advanced_view); + framerate_mode_list_ptr->set_visible(advanced_view); + notifications_subsection_ptr->set_visible(advanced_view); + set_application_audio_options_visible(audio_track_section_list_ptr, advanced_view, *gsr_info); + settings_scrollable_page_ptr->reset_scroll(); + } + void SettingsPage::add_replay_widgets() { auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL); auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL); @@ -727,12 +849,14 @@ namespace gsr { settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); auto general_list = std::make_unique<List>(List::Orientation::VERTICAL); - general_list->add_widget(create_start_replay_automatically()); + general_list->add_widget(create_replay_storage()); general_list->add_widget(create_save_replay_in_game_folder()); if(gsr_info->system_info.gsr_version >= GsrVersion{5, 0, 3}) general_list->add_widget(create_restart_replay_on_save()); settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); + settings_list_ptr->add_widget(std::make_unique<Subsection>("Autostart", create_start_replay_automatically(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); + auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); auto show_replay_started_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show replay started notification"); @@ -754,27 +878,19 @@ namespace gsr { Subsection *notifications_subsection_ptr = notifications_subsection.get(); settings_list_ptr->add_widget(std::move(notifications_subsection)); - view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { - (void)text; - const bool advanced_view = id == "advanced"; - color_range_list_ptr->set_visible(advanced_view); - audio_codec_ptr->set_visible(advanced_view); - video_codec_ptr->set_visible(advanced_view); - framerate_mode_list_ptr->set_visible(advanced_view); - notifications_subsection_ptr->set_visible(advanced_view); - split_audio_checkbox_ptr->set_visible(advanced_view); - settings_scrollable_page_ptr->reset_scroll(); + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string&, const std::string &id) { + view_changed(id == "advanced", notifications_subsection_ptr); return true; }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); replay_time_entry_ptr->on_changed = [this](const std::string&) { - update_estimated_replay_file_size(); + update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id()); update_replay_time_text(); }; video_bitrate_entry_ptr->on_changed = [this](const std::string&) { - update_estimated_replay_file_size(); + update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id()); }; } @@ -828,16 +944,8 @@ namespace gsr { Subsection *notifications_subsection_ptr = notifications_subsection.get(); settings_list_ptr->add_widget(std::move(notifications_subsection)); - view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { - (void)text; - const bool advanced_view = id == "advanced"; - color_range_list_ptr->set_visible(advanced_view); - audio_codec_ptr->set_visible(advanced_view); - video_codec_ptr->set_visible(advanced_view); - framerate_mode_list_ptr->set_visible(advanced_view); - notifications_subsection_ptr->set_visible(advanced_view); - split_audio_checkbox_ptr->set_visible(advanced_view); - settings_scrollable_page_ptr->reset_scroll(); + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string&, const std::string &id) { + view_changed(id == "advanced", notifications_subsection_ptr); return true; }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); @@ -933,8 +1041,7 @@ namespace gsr { Subsection *notifications_subsection_ptr = notifications_subsection.get(); settings_list_ptr->add_widget(std::move(notifications_subsection)); - streaming_service_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; + streaming_service_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool twitch_option = id == "twitch"; const bool youtube_option = id == "youtube"; const bool custom_option = id == "custom"; @@ -947,15 +1054,8 @@ namespace gsr { }; streaming_service_box_ptr->on_selection_changed("Twitch", "twitch"); - view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { - (void)text; - const bool advanced_view = id == "advanced"; - color_range_list_ptr->set_visible(advanced_view); - audio_codec_ptr->set_visible(advanced_view); - video_codec_ptr->set_visible(advanced_view); - framerate_mode_list_ptr->set_visible(advanced_view); - notifications_subsection_ptr->set_visible(advanced_view); - settings_scrollable_page_ptr->reset_scroll(); + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string&, const std::string &id) { + view_changed(id == "advanced", notifications_subsection_ptr); return true; }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); @@ -1006,50 +1106,64 @@ namespace gsr { return nullptr; } - static bool starts_with(std::string_view str, const char *substr) { - size_t len = strlen(substr); - return str.size() >= len && memcmp(str.data(), substr, len) == 0; - } - void SettingsPage::load_audio_tracks(const RecordOptions &record_options) { - audio_track_list_ptr->clear(); - for(const std::string &audio_track : record_options.audio_tracks) { - if(starts_with(audio_track, "app:")) { - if(!gsr_info->system_info.supports_app_audio) - continue; - - std::string audio_track_name = audio_track.substr(4); - const std::string *app_audio = get_application_audio_by_name_case_insensitive(application_audio, audio_track_name); - if(app_audio) { - std::unique_ptr<List> application_audio_widget = create_application_audio(); - ComboBox *application_audio_box = static_cast<ComboBox*>(application_audio_widget->get_child_widget_by_index(1)); - application_audio_box->set_selected_item(*app_audio); - audio_track_list_ptr->add_widget(std::move(application_audio_widget)); + audio_track_section_list_ptr->clear(); + for(const AudioTrack &audio_track : record_options.audio_tracks_list) { + auto audio_track_section = create_audio_track_section(audio_section_ptr); + List *audio_track_section_items_list_ptr = dynamic_cast<List*>(audio_track_section->get_inner_widget()); + List *audio_input_list_ptr = dynamic_cast<List*>(audio_track_section_items_list_ptr->get_child_widget_by_index(2)); + CheckBox *application_audio_invert_checkbox_ptr = dynamic_cast<CheckBox*>(audio_track_section_items_list_ptr->get_child_widget_by_index(3)); + application_audio_invert_checkbox_ptr->set_checked(audio_track.application_audio_invert); + + audio_input_list_ptr->clear(); + for(const std::string &audio_input : audio_track.audio_inputs) { + if(starts_with(audio_input, "app:")) { + if(!gsr_info->system_info.supports_app_audio) + continue; + + std::string audio_track_name = audio_input.substr(4); + const std::string *app_audio = get_application_audio_by_name_case_insensitive(application_audio, audio_track_name); + if(app_audio) { + std::unique_ptr<List> application_audio_widget = create_application_audio(audio_input_list_ptr); + ComboBox *application_audio_box = dynamic_cast<ComboBox*>(application_audio_widget->get_child_widget_by_index(1)); + application_audio_box->set_selected_item(*app_audio); + audio_input_list_ptr->add_widget(std::move(application_audio_widget)); + } else { + std::unique_ptr<List> application_audio_widget = create_custom_application_audio(audio_input_list_ptr); + Entry *application_audio_entry = dynamic_cast<Entry*>(application_audio_widget->get_child_widget_by_index(1)); + application_audio_entry->set_text(std::move(audio_track_name)); + audio_input_list_ptr->add_widget(std::move(application_audio_widget)); + } + } else if(starts_with(audio_input, "device:")) { + const std::string device_name = audio_input.substr(7); + const AudioDeviceType audio_device_type = audio_device_is_output(device_name) ? AudioDeviceType::OUTPUT : AudioDeviceType::INPUT; + std::unique_ptr<List> audio_track_widget = create_audio_device(audio_device_type, audio_input_list_ptr); + ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); + audio_device_box->set_selected_item(device_name); + audio_input_list_ptr->add_widget(std::move(audio_track_widget)); } else { - std::unique_ptr<List> application_audio_widget = create_custom_application_audio(); - Entry *application_audio_entry = static_cast<Entry*>(application_audio_widget->get_child_widget_by_index(1)); - application_audio_entry->set_text(std::move(audio_track_name)); - audio_track_list_ptr->add_widget(std::move(application_audio_widget)); + const AudioDeviceType audio_device_type = audio_device_is_output(audio_input) ? AudioDeviceType::OUTPUT : AudioDeviceType::INPUT; + std::unique_ptr<List> audio_track_widget = create_audio_device(audio_device_type, audio_input_list_ptr); + ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); + audio_device_box->set_selected_item(audio_input); + audio_input_list_ptr->add_widget(std::move(audio_track_widget)); } - } else if(starts_with(audio_track, "device:")) { - std::unique_ptr<List> audio_track_widget = create_audio_device(); - ComboBox *audio_device_box = static_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); - audio_device_box->set_selected_item(audio_track.substr(7)); - audio_track_list_ptr->add_widget(std::move(audio_track_widget)); - } else { - std::unique_ptr<List> audio_track_widget = create_audio_device(); - ComboBox *audio_device_box = static_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); - audio_device_box->set_selected_item(audio_track); - audio_track_list_ptr->add_widget(std::move(audio_track_widget)); } + + audio_track_section_list_ptr->add_widget(std::move(audio_track_section)); + + if(type == Type::STREAM) + break; + } + + if(type == Type::STREAM && audio_track_section_list_ptr->get_num_children() == 0) { + auto audio_track_section = create_audio_track_section(audio_section_ptr); + audio_track_section_list_ptr->add_widget(std::move(audio_track_section)); } } void SettingsPage::load_common(RecordOptions &record_options) { record_area_box_ptr->set_selected_item(record_options.record_area_option); - if(split_audio_checkbox_ptr) - split_audio_checkbox_ptr->set_checked(!record_options.merge_audio_tracks); - application_audio_invert_checkbox_ptr->set_checked(record_options.application_audio_invert); change_video_resolution_checkbox_ptr->set_checked(record_options.change_video_resolution); load_audio_tracks(record_options); color_range_box_ptr->set_selected_item(record_options.color_range); @@ -1102,6 +1216,7 @@ namespace gsr { void SettingsPage::load_replay() { load_common(config.replay_config.record_options); + replay_storage_button_ptr->set_selected_item(config.replay_config.replay_storage); turn_on_replay_automatically_mode_ptr->set_selected_item(config.replay_config.turn_on_replay_automatically_mode); save_replay_in_game_folder_ptr->set_checked(config.replay_config.save_video_in_game_folder); if(restart_replay_on_save) @@ -1114,8 +1229,8 @@ namespace gsr { if(config.replay_config.replay_time < 2) config.replay_config.replay_time = 2; - if(config.replay_config.replay_time > 10800) - config.replay_config.replay_time = 10800; + if(config.replay_config.replay_time > 86400) + config.replay_config.replay_time = 86400; replay_time_entry_ptr->set_text(std::to_string(config.replay_config.replay_time)); } @@ -1139,28 +1254,38 @@ namespace gsr { container_box_ptr->set_selected_item(config.streaming_config.custom.container); } - static void save_audio_tracks(std::vector<std::string> &audio_devices, List *audio_devices_list_ptr) { - audio_devices.clear(); - audio_devices_list_ptr->for_each_child_widget([&audio_devices](std::unique_ptr<Widget> &child_widget) { - List *audio_track_line = static_cast<List*>(child_widget.get()); - const AudioTrackType audio_track_type = (AudioTrackType)(uintptr_t)audio_track_line->userdata; - switch(audio_track_type) { - case AudioTrackType::DEVICE: { - ComboBox *audio_device_box = static_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); - audio_devices.push_back("device:" + audio_device_box->get_selected_id()); - break; + static void save_audio_tracks(std::vector<AudioTrack> &audio_tracks, List *audio_track_section_list_ptr) { + audio_tracks.clear(); + audio_track_section_list_ptr->for_each_child_widget([&audio_tracks](std::unique_ptr<Widget> &child_widget) { + Subsection *audio_subsection = dynamic_cast<Subsection*>(child_widget.get()); + List *audio_track_section_items_list_ptr = dynamic_cast<List*>(audio_subsection->get_inner_widget()); + List *audio_input_list_ptr = dynamic_cast<List*>(audio_track_section_items_list_ptr->get_child_widget_by_index(2)); + CheckBox *application_audio_invert_checkbox_ptr = dynamic_cast<CheckBox*>(audio_track_section_items_list_ptr->get_child_widget_by_index(3)); + + audio_tracks.push_back({std::vector<std::string>{}, application_audio_invert_checkbox_ptr->is_checked()}); + audio_input_list_ptr->for_each_child_widget([&audio_tracks](std::unique_ptr<Widget> &child_widget){ + List *audio_track_line = dynamic_cast<List*>(child_widget.get()); + const AudioTrackType audio_track_type = (AudioTrackType)(uintptr_t)audio_track_line->userdata; + switch(audio_track_type) { + case AudioTrackType::DEVICE: { + ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); + audio_tracks.back().audio_inputs.push_back("device:" + audio_device_box->get_selected_id()); + break; + } + case AudioTrackType::APPLICATION: { + ComboBox *application_audio_box = dynamic_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); + audio_tracks.back().audio_inputs.push_back("app:" + application_audio_box->get_selected_id()); + break; + } + case AudioTrackType::APPLICATION_CUSTOM: { + Entry *application_audio_entry = dynamic_cast<Entry*>(audio_track_line->get_child_widget_by_index(1)); + audio_tracks.back().audio_inputs.push_back("app:" + application_audio_entry->get_text()); + break; + } } - case AudioTrackType::APPLICATION: { - ComboBox *application_audio_box = static_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); - audio_devices.push_back("app:" + application_audio_box->get_selected_id()); - break; - } - case AudioTrackType::APPLICATION_CUSTOM: { - Entry *application_audio_entry = static_cast<Entry*>(audio_track_line->get_child_widget_by_index(1)); - audio_devices.push_back("app:" + application_audio_entry->get_text()); - break; - } - } + return true; + }); + return true; }); } @@ -1173,11 +1298,8 @@ namespace gsr { record_options.video_height = atoi(video_height_entry_ptr->get_text().c_str()); record_options.fps = atoi(framerate_entry_ptr->get_text().c_str()); record_options.video_bitrate = atoi(video_bitrate_entry_ptr->get_text().c_str()); - if(split_audio_checkbox_ptr) - record_options.merge_audio_tracks = !split_audio_checkbox_ptr->is_checked(); - record_options.application_audio_invert = application_audio_invert_checkbox_ptr->is_checked(); record_options.change_video_resolution = change_video_resolution_checkbox_ptr->is_checked(); - save_audio_tracks(record_options.audio_tracks, audio_track_list_ptr); + save_audio_tracks(record_options.audio_tracks_list, audio_track_section_list_ptr); record_options.color_range = color_range_box_ptr->get_selected_id(); record_options.video_quality = video_quality_box_ptr->get_selected_id(); record_options.video_codec = video_codec_box_ptr->get_selected_id(); @@ -1244,6 +1366,7 @@ namespace gsr { config.replay_config.save_directory = save_directory_button_ptr->get_text(); config.replay_config.container = container_box_ptr->get_selected_id(); config.replay_config.replay_time = atoi(replay_time_entry_ptr->get_text().c_str()); + config.replay_config.replay_storage = replay_storage_button_ptr->get_selected_id(); if(config.replay_config.replay_time < 5) { config.replay_config.replay_time = 5; diff --git a/src/gui/StaticPage.cpp b/src/gui/StaticPage.cpp index 182464c..5147819 100644 --- a/src/gui/StaticPage.cpp +++ b/src/gui/StaticPage.cpp @@ -20,8 +20,9 @@ namespace gsr { // Process widgets by visibility (backwards) return widgets.for_each_reverse([selected_widget, &window, &event, offset](std::unique_ptr<Widget> &widget) { - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, offset)) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, offset)) return false; } return true; diff --git a/src/gui/Subsection.cpp b/src/gui/Subsection.cpp index c97460e..bc75a9c 100644 --- a/src/gui/Subsection.cpp +++ b/src/gui/Subsection.cpp @@ -12,12 +12,17 @@ namespace gsr { static const float title_spacing_scale = 0.010f; Subsection::Subsection(const char *title, std::unique_ptr<Widget> inner_widget, mgl::vec2f size) : - label(&get_theme().title_font, title, get_color_theme().text_color), + label(&get_theme().title_font, title ? title : "", get_color_theme().text_color), inner_widget(std::move(inner_widget)), size(size) { this->inner_widget->parent_widget = this; } + + Subsection::~Subsection() { + if(inner_widget->parent_widget == this) + inner_widget->parent_widget = nullptr; + } bool Subsection::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f) { if(!visible) @@ -32,7 +37,7 @@ namespace gsr { mgl::vec2f draw_pos = position + offset; mgl::Rectangle background(draw_pos.floor(), get_size().floor()); - background.set_color(mgl::Color(25, 30, 34)); + background.set_color(bg_color); window.draw(background); draw_pos += mgl::vec2f(margin_left_scale, margin_top_scale) * mgl::vec2f(get_theme().window_height, get_theme().window_height); @@ -69,4 +74,12 @@ namespace gsr { const mgl::vec2f margin_size = mgl::vec2f(margin_left_scale + margin_right_scale, margin_top_scale + margin_bottom_scale) * mgl::vec2f(get_theme().window_height, get_theme().window_height); return get_size() - margin_size; } + + Widget* Subsection::get_inner_widget() { + return inner_widget.get(); + } + + void Subsection::set_bg_color(mgl::Color color) { + bg_color = color; + } }
\ No newline at end of file diff --git a/src/gui/Widget.cpp b/src/gui/Widget.cpp index 8732bd7..66cf193 100644 --- a/src/gui/Widget.cpp +++ b/src/gui/Widget.cpp @@ -1,14 +1,15 @@ #include "../../include/gui/Widget.hpp" +#include <vector> namespace gsr { + static std::vector<std::unique_ptr<Widget>> widgets_to_remove; + Widget::Widget() { } Widget::~Widget() { remove_widget_as_selected_in_parent(); - // if(parent_widget) - // parent_widget->remove_child_widget(this); } void Widget::set_position(mgl::vec2f position) { @@ -62,4 +63,15 @@ namespace gsr { void Widget::set_visible(bool visible) { this->visible = visible; } + + void add_widget_to_remove(std::unique_ptr<Widget> widget) { + widgets_to_remove.push_back(std::move(widget)); + } + + void remove_widgets_to_be_removed() { + for(size_t i = 0; i < widgets_to_remove.size(); ++i) { + widgets_to_remove[i].reset(); + } + widgets_to_remove.clear(); + } }
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index be2f6d0..31ec8ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,10 @@ static void sigint_handler(int signal) { running = 0; } +static void signal_ignore(int) { + +} + static void disable_prime_run() { unsetenv("__NV_PRIME_RENDER_OFFLOAD"); unsetenv("__NV_PRIME_RENDER_OFFLOAD_PROVIDER"); @@ -74,6 +78,16 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) { overlay->save_replay(); }); + rpc->add_handler("replay-save-1-min", [overlay](const std::string &name) { + fprintf(stderr, "rpc command executed: %s\n", name.c_str()); + overlay->save_replay_1_min(); + }); + + rpc->add_handler("replay-save-10-min", [overlay](const std::string &name) { + fprintf(stderr, "rpc command executed: %s\n", name.c_str()); + overlay->save_replay_10_min(); + }); + rpc->add_handler("take-screenshot", [overlay](const std::string &name) { fprintf(stderr, "rpc command executed: %s\n", name.c_str()); overlay->take_screenshot(); @@ -145,18 +159,35 @@ static bool is_flatpak() { return getenv("FLATPAK_ID") != nullptr; } +static void set_display_server_environment_variables() { + // Some users dont have properly setup environments (no display manager that does systemctl --user import-environment DISPLAY WAYLAND_DISPLAY) + const char *display = getenv("DISPLAY"); + if(!display) { + display = ":0"; + setenv("DISPLAY", display, true); + } + + const char *wayland_display = getenv("WAYLAND_DISPLAY"); + if(!wayland_display) { + wayland_display = "wayland-1"; + setenv("WAYLAND_DISPLAY", wayland_display, true); + } +} + static void usage() { printf("usage: gsr-ui [action]\n"); printf("OPTIONS:\n"); - printf(" action The launch action. Should be either \"launch-show\" or \"launch-hide\". Optional, defaults to \"launch-hide\".\n"); + printf(" action The launch action. Should be either \"launch-show\", \"launch-hide\" or \"launch-daemon\". Optional, defaults to \"launch-hide\".\n"); printf(" If \"launch-show\" is used then the program starts and the UI is immediately opened and can be shown/hidden with Alt+Z.\n"); - printf(" If \"launch-hide\" is used then the program starts but the UI is not opened until Alt+Z is pressed.\n"); + printf(" If \"launch-hide\" is used then the program starts but the UI is not opened until Alt+Z is pressed. The UI will be opened if the program is already running in another process.\n"); + printf(" If \"launch-daemon\" is used then the program starts but the UI is not opened until Alt+Z is pressed. The UI will not be opened if the program is already running in another process.\n"); exit(1); } enum class LaunchAction { LAUNCH_SHOW, - LAUNCH_HIDE + LAUNCH_HIDE, + LAUNCH_DAEMON }; int main(int argc, char **argv) { @@ -177,18 +208,17 @@ int main(int argc, char **argv) { launch_action = LaunchAction::LAUNCH_SHOW; } else if(strcmp(launch_action_opt, "launch-hide") == 0) { launch_action = LaunchAction::LAUNCH_HIDE; + } else if(strcmp(launch_action_opt, "launch-daemon") == 0) { + launch_action = LaunchAction::LAUNCH_DAEMON; } else { - printf("error: invalid action \"%s\", expected \"launch-show\" or \"launch-hide\".\n", launch_action_opt); + printf("error: invalid action \"%s\", expected \"launch-show\", \"launch-hide\" or \"launch-daemon\".\n", launch_action_opt); usage(); } } else { usage(); } - if(is_flatpak()) - install_flatpak_systemd_service(); - else - remove_flatpak_systemd_service(); + set_display_server_environment_variables(); // TODO: This is a shitty method to detect if multiple instances of gsr-ui is running but this will work properly even in flatpak // that uses pid sandboxing. Replace this with a better method once we no longer rely on linux global hotkeys on some platform. @@ -196,6 +226,9 @@ int main(int argc, char **argv) { // What do? creating a pid file doesn't work in flatpak either. // TODO: This doesn't work in flatpak when disabling hotkeys. if(is_gsr_ui_virtual_keyboard_running() || gsr::pidof("gsr-ui", getpid()) != -1) { + if(launch_action == LaunchAction::LAUNCH_DAEMON) + return 1; + gsr::Rpc rpc; if(rpc.open("gsr-ui") && rpc.write("show_ui\n", 8)) { fprintf(stderr, "Error: another instance of gsr-ui is already running, opening that one instead\n"); @@ -207,6 +240,11 @@ int main(int argc, char **argv) { return 1; } + if(is_flatpak()) + install_flatpak_systemd_service(); + else + remove_flatpak_systemd_service(); + // Stop nvidia driver from buffering frames setenv("__GL_MaxFramesAllowed", "1", true); // If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context, @@ -218,6 +256,16 @@ int main(int argc, char **argv) { unsetenv("vblank_mode"); signal(SIGINT, sigint_handler); + signal(SIGTERM, sigint_handler); + signal(SIGUSR1, signal_ignore); + signal(SIGUSR2, signal_ignore); + signal(SIGRTMIN, signal_ignore); + signal(SIGRTMIN+1, signal_ignore); + signal(SIGRTMIN+2, signal_ignore); + signal(SIGRTMIN+3, signal_ignore); + signal(SIGRTMIN+4, signal_ignore); + signal(SIGRTMIN+5, signal_ignore); + signal(SIGRTMIN+6, signal_ignore); gsr::GsrInfo gsr_info; // TODO: Show the error in ui @@ -235,7 +283,7 @@ int main(int argc, char **argv) { disable_prime_run(); } - if(mgl_init() != 0) { + if(mgl_init(MGL_WINDOW_SYSTEM_X11) != 0) { fprintf(stderr, "Error: failed to initialize mgl. Failed to either connect to the X11 server or setup opengl\n"); exit(1); } @@ -303,7 +351,10 @@ int main(int argc, char **argv) { if(exit_reason == "back-to-old-ui") { const char *args[] = { "gpu-screen-recorder-gtk", "use-old-ui", nullptr }; execvp(args[0], (char* const*)args); + return 0; + } else if(exit_reason == "exit") { + return 0; } - return 0; + return mgl_is_connected_to_display_server() ? 0 : 1; } |