diff options
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | TODO | 4 | ||||
m--------- | depends/mglpp | 0 | ||||
-rw-r--r-- | images/settings_small.png | bin | 0 -> 1903 bytes | |||
-rw-r--r-- | include/Config.hpp | 7 | ||||
-rw-r--r-- | include/GsrInfo.hpp | 3 | ||||
-rw-r--r-- | include/Overlay.hpp | 2 | ||||
-rw-r--r-- | include/Process.hpp | 3 | ||||
-rw-r--r-- | include/Theme.hpp | 4 | ||||
-rw-r--r-- | include/gui/Button.hpp | 7 | ||||
-rw-r--r-- | include/gui/GlobalSettingsPage.hpp | 34 | ||||
-rw-r--r-- | include/gui/SettingsPage.hpp | 48 | ||||
-rw-r--r-- | include/gui/Utils.hpp | 1 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/Config.cpp | 15 | ||||
-rw-r--r-- | src/GsrInfo.cpp | 167 | ||||
-rw-r--r-- | src/Overlay.cpp | 105 | ||||
-rw-r--r-- | src/Process.cpp | 48 | ||||
-rw-r--r-- | src/Theme.cpp | 48 | ||||
-rw-r--r-- | src/gui/Button.cpp | 44 | ||||
-rw-r--r-- | src/gui/GlobalSettingsPage.cpp | 83 | ||||
-rw-r--r-- | src/gui/SettingsPage.cpp | 146 | ||||
-rw-r--r-- | src/gui/Utils.cpp | 17 | ||||
-rw-r--r-- | src/main.cpp | 11 |
24 files changed, 567 insertions, 236 deletions
@@ -27,6 +27,8 @@ These are the dependencies needed to build GPU Screen Recorder UI: * libxkbcommon ## Runtime dependencies +There are also additional dependencies needed at runtime: + * [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/) * [GPU Screen Recorder Notification](https://git.dec05eba.com/gpu-screen-recorder-notification/) @@ -47,4 +49,5 @@ If you want to donate you can donate via bitcoin or monero. # Known issues * Some games receive mouse input while the UI is open -* Global hotkeys on Wayland can clash with keys used by other applications. This is primarly because Wayland compositors are missing support for global hotkey so this software uses a global hotkey system that works on all Wayland compositors.
\ No newline at end of file +* Global hotkeys on Wayland can clash with keys used by other applications. This is primarly because Wayland compositors are missing support for global hotkey so this software uses a global hotkey system that works on all Wayland compositors. +* When the UI is open the wallpaper is shown instead of the game on Hyprland and Sway. This is an issue with Hyprland and Sway. It cant be fixed until the UI is redesigned to not be a fullscreen overlay. @@ -66,7 +66,7 @@ Make save-video-in-game-folder.sh and notify-saved-name.sh run ~/.config/gpu-scr if the profile is called 4chan. Create a directory of such example scripts, including 4chan webm one. -On nvidia check if suspend fix is applied. If not, show a popup asking the user to apply it (and apply it automatically). +On nvidia check if suspend fix is applied. If not, show a popup asking the user to apply it (and apply it automatically). This is a requirement before this package is made to a flatpak. Show warning when using steam deck or when trying to capture hevc/av1 on amd (the same warnings as gpu screen recorder gtk). @@ -103,4 +103,4 @@ Dont allow autostart of replay if capture option is window recording (when windo Use global shortcuts desktop portal protocol on wayland when available. -Use `gpu-screen-recorder --list-capture-options` instead of gsr_info.supported_capture_options.monitors and update it everytime when opening setting page and when starting recording.
\ No newline at end of file +When support for window capture is enabled on x11 then make sure to not save the window except temporary while the program is open.
\ No newline at end of file diff --git a/depends/mglpp b/depends/mglpp -Subproject 4dbee5ac57c10819ff8d06560be573ad3a3e4e0 +Subproject 0c8ccb86a55e9b5b98ab68ca538ad7374308bae diff --git a/images/settings_small.png b/images/settings_small.png Binary files differnew file mode 100644 index 0000000..30534e6 --- /dev/null +++ b/images/settings_small.png diff --git a/include/Config.hpp b/include/Config.hpp index 6044ab8..c61ca10 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -7,7 +7,7 @@ #include <optional> namespace gsr { - struct GsrInfo; + struct SupportedCaptureOptions; struct ConfigHotkey { int64_t keysym = 0; @@ -40,6 +40,7 @@ namespace gsr { struct MainConfig { int32_t config_file_version = 0; bool software_encoding_warning_shown = false; + std::string tint_color; }; struct YoutubeStreamConfig { @@ -92,7 +93,7 @@ namespace gsr { }; struct Config { - Config(const GsrInfo &gsr_info); + Config(const SupportedCaptureOptions &capture_options); MainConfig main_config; StreamingConfig streaming_config; @@ -100,6 +101,6 @@ namespace gsr { ReplayConfig replay_config; }; - std::optional<Config> read_config(const GsrInfo &gsr_info); + std::optional<Config> read_config(const SupportedCaptureOptions &capture_options); void save_config(Config &config); }
\ No newline at end of file diff --git a/include/GsrInfo.hpp b/include/GsrInfo.hpp index cd6292c..6ec8e23 100644 --- a/include/GsrInfo.hpp +++ b/include/GsrInfo.hpp @@ -52,13 +52,13 @@ namespace gsr { struct GpuInfo { GpuVendor vendor = GpuVendor::UNKNOWN; + std::string card_path; }; struct GsrInfo { SystemInfo system_info; GpuInfo gpu_info; SupportedVideoCodecs supported_video_codecs; - SupportedCaptureOptions supported_capture_options; }; enum class GsrInfoExitStatus { @@ -78,4 +78,5 @@ namespace gsr { std::vector<AudioDevice> get_audio_devices(); std::vector<std::string> get_application_audio(); + SupportedCaptureOptions get_supported_capture_options(const GsrInfo &gsr_info); }
\ No newline at end of file diff --git a/include/Overlay.hpp b/include/Overlay.hpp index 0a4a1e9..283f2b1 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -36,7 +36,7 @@ namespace gsr { class Overlay { public: - Overlay(std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs); + Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs); Overlay(const Overlay&) = delete; Overlay& operator=(const Overlay&) = delete; ~Overlay(); diff --git a/include/Process.hpp b/include/Process.hpp index 731062e..40373b5 100644 --- a/include/Process.hpp +++ b/include/Process.hpp @@ -1,6 +1,7 @@ #pragma once #include <sys/types.h> +#include <string> namespace gsr { enum class GsrMode { @@ -14,6 +15,8 @@ namespace gsr { bool exec_program_daemonized(const char **args); // Arguments ending with NULL. |read_fd| can be NULL pid_t exec_program(const char **args, int *read_fd); + // Arguments ending with NULL. Returns the exit status of the program or -1 on error + int exec_program_get_stdout(const char **args, std::string &result); // |output_buffer| should be at least PATH_MAX in size bool read_cmdline_arg0(const char *filepath, char *output_buffer); }
\ No newline at end of file diff --git a/include/Theme.hpp b/include/Theme.hpp index 23bcbb7..185bcdc 100644 --- a/include/Theme.hpp +++ b/include/Theme.hpp @@ -8,6 +8,7 @@ #include <string> namespace gsr { + struct Config; struct GsrInfo; struct Theme { @@ -26,6 +27,7 @@ namespace gsr { mgl::Texture combobox_arrow_texture; mgl::Texture settings_texture; + mgl::Texture settings_small_texture; mgl::Texture folder_texture; mgl::Texture up_arrow_texture; mgl::Texture replay_button_texture; @@ -56,7 +58,7 @@ namespace gsr { mgl::Color text_color = mgl::Color(255, 255, 255); }; - bool init_color_theme(const GsrInfo &gsr_info); + bool init_color_theme(const Config &config, const GsrInfo &gsr_info); void deinit_color_theme(); ColorTheme& get_color_theme(); }
\ No newline at end of file diff --git a/include/gui/Button.hpp b/include/gui/Button.hpp index bc1dd94..eb68e99 100644 --- a/include/gui/Button.hpp +++ b/include/gui/Button.hpp @@ -5,6 +5,7 @@ #include <mglpp/graphics/Color.hpp> #include <mglpp/graphics/Text.hpp> +#include <mglpp/graphics/Sprite.hpp> namespace gsr { class Button : public Widget { @@ -20,15 +21,21 @@ namespace gsr { mgl::vec2f get_size() override; void set_border_scale(float scale); + void set_bg_hover_color(mgl::Color color); + void set_icon(mgl::Texture *texture); const std::string& get_text() const; void set_text(std::string str); std::function<void()> on_click; private: + void scale_sprite_to_button_size(); + private: mgl::vec2f size; mgl::Color bg_color; + mgl::Color bg_hover_color; mgl::Text text; + mgl::Sprite sprite; float border_scale = 0.0015f; }; }
\ No newline at end of file diff --git a/include/gui/GlobalSettingsPage.hpp b/include/gui/GlobalSettingsPage.hpp new file mode 100644 index 0000000..cd4a50c --- /dev/null +++ b/include/gui/GlobalSettingsPage.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "StaticPage.hpp" +#include "../GsrInfo.hpp" +#include "../Config.hpp" + +namespace gsr { + class GsrPage; + class PageStack; + class ScrollablePage; + class Subsection; + class RadioButton; + + class GlobalSettingsPage : public StaticPage { + public: + GlobalSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack); + GlobalSettingsPage(const GlobalSettingsPage&) = delete; + GlobalSettingsPage& operator=(const GlobalSettingsPage&) = delete; + + void load(); + void save(); + void on_navigate_away_from_page() override; + private: + std::unique_ptr<Subsection> create_appearance_subsection(ScrollablePage *parent_page); + void add_widgets(); + private: + Config &config; + const GsrInfo *gsr_info = nullptr; + + GsrPage *content_page_ptr = nullptr; + PageStack *page_stack = nullptr; + RadioButton *tint_color_radio_button_ptr = nullptr; + }; +}
\ No newline at end of file diff --git a/include/gui/SettingsPage.hpp b/include/gui/SettingsPage.hpp index f18ff65..981c99a 100644 --- a/include/gui/SettingsPage.hpp +++ b/include/gui/SettingsPage.hpp @@ -25,17 +25,17 @@ namespace gsr { STREAM }; - SettingsPage(Type type, const GsrInfo &gsr_info, Config &config, PageStack *page_stack); + SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack); SettingsPage(const SettingsPage&) = delete; SettingsPage& operator=(const SettingsPage&) = delete; - void load(const GsrInfo &gsr_info); + void load(); void save(); void on_navigate_away_from_page() override; private: std::unique_ptr<RadioButton> create_view_radio_button(); - std::unique_ptr<ComboBox> create_record_area_box(const GsrInfo &gsr_info); - std::unique_ptr<Widget> create_record_area(const GsrInfo &gsr_info); + std::unique_ptr<ComboBox> create_record_area_box(); + std::unique_ptr<Widget> create_record_area(); std::unique_ptr<List> create_select_window(); std::unique_ptr<Entry> create_area_width_entry(); std::unique_ptr<Entry> create_area_height_entry(); @@ -48,7 +48,7 @@ namespace gsr { std::unique_ptr<CheckBox> create_restore_portal_session_checkbox(); std::unique_ptr<List> create_restore_portal_session_section(); std::unique_ptr<Widget> create_change_video_resolution_section(); - std::unique_ptr<Widget> create_capture_target(const GsrInfo &gsr_info); + std::unique_ptr<Widget> create_capture_target(); std::unique_ptr<ComboBox> create_audio_device_selection_combobox(); std::unique_ptr<Button> create_remove_audio_device_button(List *audio_device_list_ptr); std::unique_ptr<List> create_audio_device(); @@ -70,8 +70,8 @@ namespace gsr { std::unique_ptr<ComboBox> create_color_range_box(); std::unique_ptr<List> create_color_range(); std::unique_ptr<List> create_video_quality_section(); - std::unique_ptr<ComboBox> create_video_codec_box(const GsrInfo &gsr_info); - std::unique_ptr<List> create_video_codec(const GsrInfo &gsr_info); + std::unique_ptr<ComboBox> create_video_codec_box(); + std::unique_ptr<List> create_video_codec(); std::unique_ptr<ComboBox> create_audio_codec_box(); std::unique_ptr<List> create_audio_codec(); std::unique_ptr<Entry> create_framerate_entry(); @@ -80,24 +80,24 @@ namespace gsr { std::unique_ptr<List> create_framerate_mode(); std::unique_ptr<List> create_framerate_section(); std::unique_ptr<Widget> create_record_cursor_section(); - std::unique_ptr<Widget> create_video_section(const GsrInfo &gsr_info); - std::unique_ptr<Widget> create_settings(const GsrInfo &gsr_info); - void add_widgets(const GsrInfo &gsr_info); + std::unique_ptr<Widget> create_video_section(); + std::unique_ptr<Widget> create_settings(); + void add_widgets(); - void add_page_specific_widgets(const GsrInfo &gsr_info); + void add_page_specific_widgets(); std::unique_ptr<List> create_save_directory(const char *label); std::unique_ptr<ComboBox> create_container_box(); std::unique_ptr<List> create_container_section(); std::unique_ptr<Entry> create_replay_time_entry(); std::unique_ptr<List> create_replay_time(); - std::unique_ptr<RadioButton> create_start_replay_automatically(const GsrInfo &gsr_info); - std::unique_ptr<CheckBox> create_save_replay_in_game_folder(const GsrInfo &gsr_info); + std::unique_ptr<RadioButton> create_start_replay_automatically(); + std::unique_ptr<CheckBox> create_save_replay_in_game_folder(); std::unique_ptr<Label> create_estimated_file_size(); void update_estimated_file_size(); - std::unique_ptr<CheckBox> create_save_recording_in_game_folder(const GsrInfo &gsr_info); - void add_replay_widgets(const GsrInfo &gsr_info); - void add_record_widgets(const GsrInfo &gsr_info); + std::unique_ptr<CheckBox> create_save_recording_in_game_folder(); + void add_replay_widgets(); + void add_record_widgets(); std::unique_ptr<ComboBox> create_streaming_service_box(); std::unique_ptr<List> create_streaming_service_section(); @@ -105,13 +105,13 @@ namespace gsr { std::unique_ptr<List> create_stream_url_section(); std::unique_ptr<ComboBox> create_stream_container_box(); std::unique_ptr<List> create_stream_container_section(); - void add_stream_widgets(const GsrInfo &gsr_info); + void add_stream_widgets(); - void load_audio_tracks(const RecordOptions &record_options, const GsrInfo &gsr_info); - void load_common(RecordOptions &record_options, const GsrInfo &gsr_info); - void load_replay(const GsrInfo &gsr_info); - void load_record(const GsrInfo &gsr_info); - void load_stream(const GsrInfo &gsr_info); + void load_audio_tracks(const RecordOptions &record_options); + void load_common(RecordOptions &record_options); + void load_replay(); + void load_record(); + void load_stream(); void save_common(RecordOptions &record_options); void save_replay(); @@ -120,8 +120,10 @@ namespace gsr { private: Type type; Config &config; + const GsrInfo *gsr_info = nullptr; std::vector<AudioDevice> audio_devices; std::vector<std::string> application_audio; + SupportedCaptureOptions capture_options; GsrPage *content_page_ptr = nullptr; ScrollablePage *settings_scrollable_page_ptr = nullptr; @@ -179,7 +181,5 @@ namespace gsr { RadioButton *turn_on_replay_automatically_mode_ptr = nullptr; PageStack *page_stack = nullptr; - - mgl::Text settings_title_text; }; }
\ No newline at end of file diff --git a/include/gui/Utils.hpp b/include/gui/Utils.hpp index 6963bc5..35b2bb7 100644 --- a/include/gui/Utils.hpp +++ b/include/gui/Utils.hpp @@ -15,4 +15,5 @@ namespace gsr { void draw_rectangle_outline(mgl::Window &window, mgl::vec2f pos, mgl::vec2f size, mgl::Color color, float border_size); double get_frame_delta_seconds(); void set_frame_delta_seconds(double frame_delta); + mgl::vec2f scale_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to); }
\ No newline at end of file diff --git a/meson.build b/meson.build index 324295a..3b21a93 100644 --- a/meson.build +++ b/meson.build @@ -27,6 +27,7 @@ src = [ 'src/gui/CustomRendererWidget.cpp', 'src/gui/FileChooser.cpp', 'src/gui/SettingsPage.cpp', + 'src/gui/GlobalSettingsPage.cpp', 'src/gui/GsrPage.cpp', 'src/gui/Subsection.cpp', 'src/Utils.cpp', diff --git a/src/Config.cpp b/src/Config.cpp index 112688a..4deaaf4 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -13,7 +13,7 @@ #define CONFIG_FILE_VERSION 1 namespace gsr { - Config::Config(const GsrInfo &gsr_info) { + Config::Config(const SupportedCaptureOptions &capture_options) { const std::string default_save_directory = get_videos_dir(); streaming_config.record_options.video_quality = "custom"; @@ -29,10 +29,10 @@ namespace gsr { replay_config.record_options.audio_tracks.push_back("default_output"); replay_config.record_options.video_bitrate = 45000; - if(!gsr_info.supported_capture_options.monitors.empty()) { - streaming_config.record_options.record_area_option = gsr_info.supported_capture_options.monitors.front().name; - record_config.record_options.record_area_option = gsr_info.supported_capture_options.monitors.front().name; - replay_config.record_options.record_area_option = gsr_info.supported_capture_options.monitors.front().name; + if(!capture_options.monitors.empty()) { + streaming_config.record_options.record_area_option = capture_options.monitors.front().name; + record_config.record_options.record_area_option = capture_options.monitors.front().name; + replay_config.record_options.record_area_option = capture_options.monitors.front().name; } } @@ -49,6 +49,7 @@ namespace gsr { return { {"main.config_file_version", &config.main_config.config_file_version}, {"main.software_encoding_warning_shown", &config.main_config.software_encoding_warning_shown}, + {"main.tint_color", &config.main_config.tint_color}, {"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option}, {"streaming.record_options.record_area_width", &config.streaming_config.record_options.record_area_width}, @@ -140,7 +141,7 @@ namespace gsr { }; } - std::optional<Config> read_config(const GsrInfo &gsr_info) { + std::optional<Config> read_config(const SupportedCaptureOptions &capture_options) { std::optional<Config> config; const std::string config_path = get_config_dir() + "/config_ui"; @@ -150,7 +151,7 @@ namespace gsr { return config; } - config = Config(gsr_info); + 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(); diff --git a/src/GsrInfo.cpp b/src/GsrInfo.cpp index 276870b..c35ccfb 100644 --- a/src/GsrInfo.cpp +++ b/src/GsrInfo.cpp @@ -1,5 +1,7 @@ #include "../include/GsrInfo.hpp" #include "../include/Utils.hpp" +#include "../include/Process.hpp" + #include <optional> #include <string.h> @@ -38,6 +40,8 @@ namespace gsr { gsr_info->gpu_info.vendor = GpuVendor::INTEL; else if(key_value->value == "nvidia") gsr_info->gpu_info.vendor = GpuVendor::NVIDIA; + } else if(key_value->key == "card_path") { + gsr_info->gpu_info.card_path = key_value->value; } } @@ -64,38 +68,6 @@ namespace gsr { gsr_info->supported_video_codecs.vp9 = true; } - static std::optional<GsrMonitor> capture_option_line_to_monitor(std::string_view line) { - std::optional<GsrMonitor> monitor; - const std::optional<KeyValue> key_value = parse_key_value(line); - if(!key_value) - return monitor; - - char value_buffer[256]; - snprintf(value_buffer, sizeof(value_buffer), "%.*s", (int)key_value->value.size(), key_value->value.data()); - - monitor = GsrMonitor{std::string(key_value->key), mgl::vec2i{0, 0}}; - if(sscanf(value_buffer, "%dx%d", &monitor->size.x, &monitor->size.y) != 2) - monitor->size = {0, 0}; - - return monitor; - } - - static void parse_capture_options_line(GsrInfo *gsr_info, std::string_view line) { - if(line == "window") - gsr_info->supported_capture_options.window = true; - else if(line == "focused") - gsr_info->supported_capture_options.focused = true; - else if(line == "screen") - gsr_info->supported_capture_options.screen = true; - else if(line == "portal") - gsr_info->supported_capture_options.portal = true; - else { - std::optional<GsrMonitor> monitor = capture_option_line_to_monitor(line); - if(monitor) - gsr_info->supported_capture_options.monitors.push_back(std::move(monitor.value())); - } - } - enum class GsrInfoSection { UNKNOWN, SYSTEM_INFO, @@ -112,23 +84,19 @@ namespace gsr { GsrInfoExitStatus get_gpu_screen_recorder_info(GsrInfo *gsr_info) { *gsr_info = GsrInfo{}; - FILE *f = popen("gpu-screen-recorder --info", "r"); - if(!f) { - fprintf(stderr, "error: 'gpu-screen-recorder --info' failed\n"); - return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; - } - - char output[8192]; - ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f); - if(bytes_read < 0 || ferror(f)) { - fprintf(stderr, "error: failed to read 'gpu-screen-recorder --info' output\n"); - pclose(f); - return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; + std::string stdout_str; + const char *args[] = { "gpu-screen-recorder", "--info", nullptr }; + const int exit_status = exec_program_get_stdout(args, stdout_str); + switch(exit_status) { + case 0: break; + case 14: return GsrInfoExitStatus::BROKEN_DRIVERS; + case 22: return GsrInfoExitStatus::OPENGL_FAILED; + case 23: return GsrInfoExitStatus::NO_DRM_CARD; + default: return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; } - output[bytes_read] = '\0'; GsrInfoSection section = GsrInfoSection::UNKNOWN; - string_split_char({output, (size_t)bytes_read}, '\n', [&](std::string_view line) { + string_split_char(stdout_str, '\n', [&](std::string_view line) { if(starts_with(line, "section=")) { const std::string_view section_name = line.substr(8); if(section_name == "system_info") @@ -161,7 +129,7 @@ namespace gsr { break; } case GsrInfoSection::CAPTURE_OPTIONS: { - parse_capture_options_line(gsr_info, line); + // Intentionally ignore, get capture options with get_supported_capture_options instead break; } } @@ -169,18 +137,7 @@ namespace gsr { return true; }); - int status = pclose(f); - if(WIFEXITED(status)) { - switch(WEXITSTATUS(status)) { - case 0: return GsrInfoExitStatus::OK; - case 14: return GsrInfoExitStatus::BROKEN_DRIVERS; - case 22: return GsrInfoExitStatus::OPENGL_FAILED; - case 23: return GsrInfoExitStatus::NO_DRM_CARD; - default: return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; - } - } - - return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; + return GsrInfoExitStatus::OK; } static std::optional<AudioDevice> parse_audio_device_line(std::string_view line) { @@ -196,22 +153,14 @@ namespace gsr { std::vector<AudioDevice> get_audio_devices() { std::vector<AudioDevice> audio_devices; - FILE *f = popen("gpu-screen-recorder --list-audio-devices", "r"); - if(!f) { + std::string stdout_str; + const char *args[] = { "gpu-screen-recorder", "--list-audio-devices", nullptr }; + if(exec_program_get_stdout(args, stdout_str) != 0) { fprintf(stderr, "error: 'gpu-screen-recorder --list-audio-devices' failed\n"); return audio_devices; } - char output[16384]; - ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f); - if(bytes_read < 0 || ferror(f)) { - fprintf(stderr, "error: failed to read 'gpu-screen-recorder --list-audio-devices' output\n"); - pclose(f); - return audio_devices; - } - output[bytes_read] = '\0'; - - string_split_char({output, (size_t)bytes_read}, '\n', [&](std::string_view line) { + string_split_char(stdout_str, '\n', [&](std::string_view line) { std::optional<AudioDevice> audio_device = parse_audio_device_line(line); if(audio_device) audio_devices.push_back(std::move(audio_device.value())); @@ -224,26 +173,78 @@ namespace gsr { std::vector<std::string> get_application_audio() { std::vector<std::string> application_audio; - FILE *f = popen("gpu-screen-recorder --list-application-audio", "r"); - if(!f) { + std::string stdout_str; + const char *args[] = { "gpu-screen-recorder", "--list-application-audio", nullptr }; + if(exec_program_get_stdout(args, stdout_str) != 0) { fprintf(stderr, "error: 'gpu-screen-recorder --list-application-audio' failed\n"); return application_audio; } - char output[16384]; - ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f); - if(bytes_read < 0 || ferror(f)) { - fprintf(stderr, "error: failed to read 'gpu-screen-recorder --list-application-audio' output\n"); - pclose(f); - return application_audio; - } - output[bytes_read] = '\0'; - - string_split_char({output, (size_t)bytes_read}, '\n', [&](std::string_view line) { + string_split_char(stdout_str, '\n', [&](std::string_view line) { application_audio.emplace_back(line); return true; }); return application_audio; } + + static std::optional<GsrMonitor> capture_option_line_to_monitor(std::string_view line) { + std::optional<GsrMonitor> monitor; + const std::optional<KeyValue> key_value = parse_key_value(line); + if(!key_value) + return monitor; + + char value_buffer[256]; + snprintf(value_buffer, sizeof(value_buffer), "%.*s", (int)key_value->value.size(), key_value->value.data()); + + monitor = GsrMonitor{std::string(key_value->key), mgl::vec2i{0, 0}}; + if(sscanf(value_buffer, "%dx%d", &monitor->size.x, &monitor->size.y) != 2) + monitor->size = {0, 0}; + + return monitor; + } + + static void parse_capture_options_line(SupportedCaptureOptions &capture_options, std::string_view line) { + if(line == "window") + capture_options.window = true; + else if(line == "focused") + capture_options.focused = true; + else if(line == "screen") + capture_options.screen = true; + else if(line == "portal") + capture_options.portal = true; + else { + std::optional<GsrMonitor> monitor = capture_option_line_to_monitor(line); + if(monitor) + capture_options.monitors.push_back(std::move(monitor.value())); + } + } + + static const char* gpu_vendor_to_string(GpuVendor vendor) { + switch(vendor) { + case GpuVendor::UNKNOWN: return "unknown"; + case GpuVendor::AMD: return "amd"; + case GpuVendor::INTEL: return "intel"; + case GpuVendor::NVIDIA: return "nvidia"; + } + return "unknown"; + } + + SupportedCaptureOptions get_supported_capture_options(const GsrInfo &gsr_info) { + SupportedCaptureOptions capture_options; + + std::string stdout_str; + const char *args[] = { "gpu-screen-recorder", "--list-capture-options", gsr_info.gpu_info.card_path.c_str(), gpu_vendor_to_string(gsr_info.gpu_info.vendor), nullptr }; + if(exec_program_get_stdout(args, stdout_str) != 0) { + fprintf(stderr, "error: 'gpu-screen-recorder --list-capture-options' failed\n"); + return capture_options; + } + + string_split_char(stdout_str, '\n', [&](std::string_view line) { + parse_capture_options_line(capture_options, line); + return true; + }); + + return capture_options; + } } diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 5a2783b..88625d6 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -7,8 +7,10 @@ #include "../include/gui/DropdownButton.hpp" #include "../include/gui/CustomRendererWidget.hpp" #include "../include/gui/SettingsPage.hpp" +#include "../include/gui/GlobalSettingsPage.hpp" #include "../include/gui/Utils.hpp" #include "../include/gui/PageStack.hpp" +#include "../include/gui/GsrPage.hpp" #include "../include/WindowUtils.hpp" #include "../include/GlobalHotkeys.hpp" @@ -17,6 +19,7 @@ #include <sys/wait.h> #include <limits.h> #include <fcntl.h> +#include <poll.h> #include <stdexcept> #include <X11/Xlib.h> @@ -297,10 +300,11 @@ namespace gsr { } static mgl::vec2i create_window_get_center_position(Display *display) { - const int screen = DefaultScreen(display); + const int size = 16; XSetWindowAttributes window_attr; window_attr.event_mask = StructureNotifyMask; - const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 32, 32, 0, DefaultDepth(display, screen), InputOutput, DefaultVisual(display, screen), CWEventMask, &window_attr); + window_attr.background_pixel = 0; + const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr); if(!window) return {0, 0}; @@ -319,12 +323,36 @@ namespace gsr { const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha); XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); + XSizeHints *size_hints = XAllocSizeHints(); + size_hints->width = size; + size_hints->min_width = size; + size_hints->max_width = size; + size_hints->height = size; + size_hints->min_height = size; + size_hints->max_height = size; + size_hints->flags = PSize | PMinSize | PMaxSize; + XSetWMNormalHints(display, window, size_hints); + XFree(size_hints); + XMapWindow(display, window); XFlush(display); + const int x_fd = XConnectionNumber(display); mgl::vec2i window_pos; XEvent xev; while(true) { + struct pollfd poll_fd; + poll_fd.fd = x_fd; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + const int fds_ready = poll(&poll_fd, 1, 1000); + if(fds_ready == 0) { + fprintf(stderr, "Error: timed out waiting for ConfigureNotify after XCreateWindow\n"); + break; + } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) { + continue; + } + XNextEvent(display, &xev); if(xev.type == ConfigureNotify) { window_pos.x = xev.xconfigure.x + xev.xconfigure.width / 2; @@ -394,11 +422,11 @@ namespace gsr { return true; } - Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs) : + Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs) : resources_path(std::move(resources_path)), - gsr_info(gsr_info), + gsr_info(std::move(gsr_info)), egl_funcs(egl_funcs), - config(gsr_info), + config(capture_options), bg_screenshot_overlay({0.0f, 0.0f}), top_bar_background({0.0f, 0.0f}), close_button_widget({0.0f, 0.0f}) @@ -417,11 +445,11 @@ namespace gsr { memset(&window_texture, 0, sizeof(window_texture)); - std::optional<Config> new_config = read_config(gsr_info); + std::optional<Config> new_config = read_config(capture_options); if(new_config) config = std::move(new_config.value()); - init_color_theme(gsr_info); + init_color_theme(config, this->gsr_info); power_supply_online_filepath = get_power_supply_online_filepath(); @@ -864,6 +892,7 @@ namespace gsr { const int button_width = button_height; auto main_buttons_list = std::make_unique<List>(List::Orientation::HORIZONTAL); + List * main_buttons_list_ptr = main_buttons_list.get(); main_buttons_list->set_spacing(0.0f); { auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Instant Replay", "Off", &get_theme().replay_button_texture, @@ -876,7 +905,7 @@ namespace gsr { button->set_item_icon("save", &get_theme().save_texture); button->on_click = [this](const std::string &id) { if(id == "settings") { - auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, gsr_info, config, &page_stack); + auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack); page_stack.push(std::move(replay_settings_page)); } else if(id == "save") { on_press_save_replay(); @@ -897,7 +926,7 @@ namespace gsr { button->set_item_icon("pause", &get_theme().pause_texture); button->on_click = [this](const std::string &id) { if(id == "settings") { - auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, gsr_info, config, &page_stack); + auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack); page_stack.push(std::move(record_settings_page)); } else if(id == "pause") { toggle_pause(); @@ -916,7 +945,7 @@ namespace gsr { button->set_item_icon("start", &get_theme().play_texture); button->on_click = [this](const std::string &id) { if(id == "settings") { - auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, gsr_info, config, &page_stack); + auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack); page_stack.push(std::move(stream_settings_page)); } else if(id == "start") { on_press_start_stream(); @@ -929,6 +958,20 @@ namespace gsr { main_buttons_list->set_position((mgl::vec2f(window_size.x * 0.5f, window_size.y * 0.25f) - main_buttons_list_size * 0.5f).floor()); front_page_ptr->add_widget(std::move(main_buttons_list)); + { + const mgl::vec2f main_buttons_size = main_buttons_list_ptr->get_size(); + const int settings_button_size = main_buttons_size.y * 0.2f; + auto button = std::make_unique<Button>(&get_theme().title_font, "", mgl::vec2f(settings_button_size, settings_button_size), mgl::Color(0, 0, 0, 180)); + button->set_position((main_buttons_list_ptr->get_position() + main_buttons_size - mgl::vec2f(0.0f, settings_button_size) + mgl::vec2f(settings_button_size * 0.333f, 0.0f)).floor()); + button->set_bg_hover_color(mgl::Color(0, 0, 0, 255)); + button->set_icon(&get_theme().settings_small_texture); + button->on_click = [&]() { + auto settings_page = std::make_unique<GlobalSettingsPage>(&gsr_info, config, &page_stack); + page_stack.push(std::move(settings_page)); + }; + front_page_ptr->add_widget(std::move(button)); + } + close_button_widget.draw_handler = [&](mgl::Window &window, mgl::vec2f pos, mgl::vec2f size) { const int border_size = std::max(1.0f, 0.0015f * get_theme().window_height); const float padding_size = std::max(1.0f, 0.003f * get_theme().window_height); @@ -986,8 +1029,7 @@ namespace gsr { // XFlush(display); // } - if(!prevent_game_minimizing) - window->set_fullscreen(true); + window->set_fullscreen(true); visible = true; @@ -1526,6 +1568,24 @@ namespace gsr { } } + static bool validate_capture_target(const GsrInfo &gsr_info, const std::string &capture_target) { + const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); + // TODO: Also check x11 window when enabled (check if capture_target is a decminal/hex number) + if(capture_target == "focused" && !capture_options.focused) { + return false; + } else if(capture_target == "screen" && !capture_options.screen) { + return false; + } else if(capture_target == "portal" && !capture_options.portal) { + return false; + } else { + for(const GsrMonitor &monitor : capture_options.monitors) { + if(capture_target == monitor.name) + return true; + } + return false; + } + } + void Overlay::on_press_save_replay() { if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0) return; @@ -1571,6 +1631,13 @@ namespace gsr { return; } + if(!validate_capture_target(gsr_info, config.replay_config.record_options.record_area_option)) { + 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", config.replay_config.record_options.record_area_option.c_str()); + show_notification(err_msg, 3.0, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::REPLAY); + return; + } + // TODO: Validate input, fallback to valid values 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); @@ -1678,6 +1745,13 @@ namespace gsr { return; } + if(!validate_capture_target(gsr_info, config.record_config.record_options.record_area_option)) { + 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", config.record_config.record_options.record_area_option.c_str()); + show_notification(err_msg, 3.0, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::RECORD); + return; + } + record_filepath.clear(); // TODO: Validate input, fallback to valid values @@ -1807,6 +1881,13 @@ namespace gsr { return; } + if(!validate_capture_target(gsr_info, config.streaming_config.record_options.record_area_option)) { + 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", config.streaming_config.record_options.record_area_option.c_str()); + show_notification(err_msg, 3.0, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::STREAM); + return; + } + // 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); diff --git a/src/Process.cpp b/src/Process.cpp index e5886c0..a9c5103 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -29,7 +29,7 @@ namespace gsr { debug_print_args(args); - pid_t pid = vfork(); + const pid_t pid = vfork(); if(pid == -1) { perror("Failed to vfork"); return false; @@ -38,7 +38,7 @@ namespace gsr { signal(SIGHUP, SIG_IGN); // Daemonize child to make the parent the init process which will reap the zombie child - pid_t second_child = vfork(); + const pid_t second_child = vfork(); if(second_child == 0) { // child execvp(args[0], (char* const*)args); perror("execvp"); @@ -68,7 +68,7 @@ namespace gsr { debug_print_args(args); - pid_t pid = vfork(); + const pid_t pid = vfork(); if(pid == -1) { close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); @@ -92,6 +92,48 @@ namespace gsr { } } + int exec_program_get_stdout(const char **args, std::string &result) { + result.clear(); + int read_fd = -1; + const pid_t process_id = exec_program(args, &read_fd); + if(process_id == -1) + return -1; + + int exit_status = 0; + char buffer[8192]; + for(;;) { + ssize_t bytes_read = read(read_fd, buffer, sizeof(buffer)); + if(bytes_read == 0) { + break; + } else if(bytes_read == -1) { + fprintf(stderr, "Failed to read from pipe to program %s, error: %s\n", args[0], strerror(errno)); + exit_status = -1; + break; + } + + buffer[bytes_read] = '\0'; + result.append(buffer, bytes_read); + } + + if(exit_status != 0) + kill(process_id, SIGKILL); + + int status = 0; + if(waitpid(process_id, &status, 0) == -1) { + perror("waitpid failed"); + exit_status = -1; + } + + if(!WIFEXITED(status)) + exit_status = -1; + + if(exit_status == 0) + exit_status = WEXITSTATUS(status); + + close(read_fd); + return exit_status; + } + bool read_cmdline_arg0(const char *filepath, char *output_buffer) { output_buffer[0] = '\0'; diff --git a/src/Theme.cpp b/src/Theme.cpp index a88aa1e..a6d1050 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -1,4 +1,5 @@ #include "../include/Theme.hpp" +#include "../include/Config.hpp" #include "../include/GsrInfo.hpp" #include <assert.h> @@ -7,6 +8,27 @@ namespace gsr { static Theme *theme = nullptr; static ColorTheme *color_theme = nullptr; + static mgl::Color gpu_vendor_to_color(GpuVendor vendor) { + switch(vendor) { + case GpuVendor::UNKNOWN: return mgl::Color(221, 0, 49); + case GpuVendor::AMD: return mgl::Color(221, 0, 49); + case GpuVendor::INTEL: return mgl::Color(8, 109, 183); + case GpuVendor::NVIDIA: return mgl::Color(118, 185, 0); + } + return mgl::Color(221, 0, 49); + } + + static mgl::Color color_name_to_color(const std::string &color_name) { + GpuVendor vendor = GpuVendor::UNKNOWN; + if(color_name == "amd") + vendor = GpuVendor::AMD; + else if(color_name == "intel") + vendor = GpuVendor::INTEL; + else if(color_name == "nvidia") + vendor = GpuVendor::NVIDIA; + return gpu_vendor_to_color(vendor); + } + bool Theme::set_window_size(mgl::vec2i window_size) { if(std::abs(window_size.x - window_width) < 0.1f && std::abs(window_size.y - window_height) < 0.1f) return true; @@ -44,6 +66,9 @@ namespace gsr { if(!theme->settings_texture.load_from_file((resources_path + "images/settings.png").c_str())) goto error; + if(!theme->settings_small_texture.load_from_file((resources_path + "images/settings_small.png").c_str())) + goto error; + if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str())) goto error; @@ -102,29 +127,16 @@ namespace gsr { return *theme; } - bool init_color_theme(const GsrInfo &gsr_info) { + bool init_color_theme(const Config &config, const GsrInfo &gsr_info) { if(color_theme) return true; color_theme = new ColorTheme(); - switch(gsr_info.gpu_info.vendor) { - case GpuVendor::UNKNOWN: { - break; - } - case GpuVendor::AMD: { - color_theme->tint_color = mgl::Color(221, 0, 49); - break; - } - case GpuVendor::INTEL: { - color_theme->tint_color = mgl::Color(8, 109, 183); - break; - } - case GpuVendor::NVIDIA: { - color_theme->tint_color = mgl::Color(118, 185, 0); - break; - } - } + if(config.main_config.tint_color.empty()) + color_theme->tint_color = gpu_vendor_to_color(gsr_info.gpu_info.vendor); + else + color_theme->tint_color = color_name_to_color(config.main_config.tint_color); return true; } diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index fbf5cdd..54d1854 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -12,7 +12,15 @@ namespace gsr { static const float padding_left_scale = 0.007f; static const float padding_right_scale = 0.007f; - Button::Button(mgl::Font *font, const char *text, mgl::vec2f size, mgl::Color bg_color) : size(size), bg_color(bg_color), text(text, *font) { + // These are relative to the button size + static const float padding_top_icon_scale = 0.25f; + static const float padding_bottom_icon_scale = 0.25f; + static const float padding_left_icon_scale = 0.25f; + static const float padding_right_icon_scale = 0.25f; + + Button::Button(mgl::Font *font, const char *text, mgl::vec2f size, mgl::Color bg_color) : + size(size), bg_color(bg_color), bg_hover_color(bg_color), text(text, *font) + { } @@ -37,17 +45,23 @@ namespace gsr { return; const mgl::vec2f draw_pos = position + offset; - const mgl::vec2f item_size = get_size().floor(); + const bool mouse_inside = mgl::FloatRect(draw_pos, item_size).contains(window.get_mouse_position().to_vec2f()) && !has_parent_with_selected_child_widget(); + mgl::Rectangle background(item_size); background.set_position(draw_pos.floor()); - background.set_color(bg_color); + background.set_color(mouse_inside ? bg_hover_color : bg_color); window.draw(background); text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor()); window.draw(text); - const bool mouse_inside = mgl::FloatRect(draw_pos, item_size).contains(window.get_mouse_position().to_vec2f()) && !has_parent_with_selected_child_widget(); + if(sprite.get_texture() && sprite.get_texture()->is_valid()) { + scale_sprite_to_button_size(); + sprite.set_position((background.get_position() + background.get_size() * 0.5f - sprite.get_size() * 0.5f).floor()); + window.draw(sprite); + } + if(mouse_inside) { const mgl::Color outline_color = (bg_color == get_color_theme().tint_color) ? mgl::Color(255, 255, 255) : get_color_theme().tint_color; draw_rectangle_outline(window, draw_pos, item_size, outline_color, std::max(1.0f, border_scale * get_theme().window_height)); @@ -76,6 +90,14 @@ namespace gsr { border_scale = scale; } + void Button::set_bg_hover_color(mgl::Color color) { + bg_hover_color = color; + } + + void Button::set_icon(mgl::Texture *texture) { + sprite.set_texture(texture); + } + const std::string& Button::get_text() const { return text.get_string(); } @@ -83,4 +105,18 @@ namespace gsr { void Button::set_text(std::string str) { text.set_string(std::move(str)); } + + void Button::scale_sprite_to_button_size() { + if(!sprite.get_texture() || !sprite.get_texture()->is_valid()) + return; + + const mgl::vec2f button_size = get_size(); + const int padding_icon_top = padding_top_icon_scale * button_size.y; + const int padding_icon_bottom = padding_bottom_icon_scale * button_size.y; + const int padding_icon_left = padding_left_icon_scale * button_size.y; + const int padding_icon_right = padding_right_icon_scale * button_size.y; + + const mgl::vec2f desired_size = button_size - mgl::vec2f(padding_icon_left + padding_icon_right, padding_icon_top + padding_icon_bottom); + sprite.set_size(scale_keep_aspect_ratio(sprite.get_texture()->get_size().to_vec2f(), desired_size).floor()); + } }
\ No newline at end of file diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp new file mode 100644 index 0000000..6c25663 --- /dev/null +++ b/src/gui/GlobalSettingsPage.cpp @@ -0,0 +1,83 @@ +#include "../../include/gui/GlobalSettingsPage.hpp" + +#include "../../include/Theme.hpp" +#include "../../include/gui/GsrPage.hpp" +#include "../../include/gui/PageStack.hpp" +#include "../../include/gui/ScrollablePage.hpp" +#include "../../include/gui/Subsection.hpp" +#include "../../include/gui/List.hpp" +#include "../../include/gui/Label.hpp" +#include "../../include/gui/RadioButton.hpp" + +namespace gsr { + static const char* gpu_vendor_to_color_name(GpuVendor vendor) { + switch(vendor) { + case GpuVendor::UNKNOWN: return "amd"; + case GpuVendor::AMD: return "amd"; + case GpuVendor::INTEL: return "intel"; + case GpuVendor::NVIDIA: return "nvidia"; + } + return "amd"; + } + + GlobalSettingsPage::GlobalSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : + StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), + config(config), + gsr_info(gsr_info), + page_stack(page_stack) + { + auto content_page = std::make_unique<GsrPage>(); + content_page->add_button("Back", "back", get_color_theme().page_bg_color); + content_page->on_click = [page_stack](const std::string &id) { + if(id == "back") + page_stack->pop(); + }; + content_page_ptr = content_page.get(); + add_widget(std::move(content_page)); + + add_widgets(); + load(); + } + + std::unique_ptr<Subsection> GlobalSettingsPage::create_appearance_subsection(ScrollablePage *parent_page) { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Tint color", get_color_theme().text_color)); + auto tint_color_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + 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->on_selection_changed = [](const std::string&, const std::string &id) { + if(id == "amd") + get_color_theme().tint_color = mgl::Color(221, 0, 49); + else if(id == "nvidia") + get_color_theme().tint_color = mgl::Color(118, 185, 0); + else if(id == "intel") + get_color_theme().tint_color = mgl::Color(8, 109, 183); + }; + list->add_widget(std::move(tint_color_radio_button)); + return std::make_unique<Subsection>("Appearance", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + } + + void GlobalSettingsPage::add_widgets() { + auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size()); + scrollable_page->add_widget(create_appearance_subsection(scrollable_page.get())); + content_page_ptr->add_widget(std::move(scrollable_page)); + } + + void GlobalSettingsPage::on_navigate_away_from_page() { + save(); + } + + void GlobalSettingsPage::load() { + if(config.main_config.tint_color.empty()) + tint_color_radio_button_ptr->set_selected_item(gpu_vendor_to_color_name(gsr_info->gpu_info.vendor)); + else + tint_color_radio_button_ptr->set_selected_item(config.main_config.tint_color); + } + + void GlobalSettingsPage::save() { + config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id(); + save_config(config); + } +}
\ No newline at end of file diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 79f6c52..28821d1 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -22,15 +22,16 @@ namespace gsr { APPLICATION_CUSTOM }; - SettingsPage::SettingsPage(Type type, const GsrInfo &gsr_info, Config &config, PageStack *page_stack) : + SettingsPage::SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), type(type), config(config), - page_stack(page_stack), - settings_title_text("Settings", get_theme().title_font) + gsr_info(gsr_info), + page_stack(page_stack) { audio_devices = get_audio_devices(); application_audio = get_application_audio(); + capture_options = get_supported_capture_options(*gsr_info); auto content_page = std::make_unique<GsrPage>(); content_page->add_button("Back", "back", get_color_theme().page_bg_color); @@ -41,9 +42,9 @@ namespace gsr { content_page_ptr = content_page.get(); add_widget(std::move(content_page)); - add_widgets(gsr_info); - add_page_specific_widgets(gsr_info); - load(gsr_info); + add_widgets(); + add_page_specific_widgets(); + load(); } std::unique_ptr<RadioButton> SettingsPage::create_view_radio_button() { @@ -55,31 +56,32 @@ namespace gsr { return view_radio_button; } - std::unique_ptr<ComboBox> SettingsPage::create_record_area_box(const GsrInfo &gsr_info) { + std::unique_ptr<ComboBox> SettingsPage::create_record_area_box() { auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font); // TODO: Show options not supported but disable them // TODO: Enable this - //if(gsr_info.supported_capture_options.window) + //if(capture_options.window) // record_area_box->add_item("Window", "window"); - if(gsr_info.supported_capture_options.focused) + if(capture_options.focused) record_area_box->add_item("Follow focused window", "focused"); - if(gsr_info.supported_capture_options.screen) - record_area_box->add_item("All monitors", "screen"); - for(const auto &monitor : gsr_info.supported_capture_options.monitors) { + // Do we really need this? it's only available on nvidia x11 + //if(capture_options.screen) + // record_area_box->add_item("All monitors", "screen"); + for(const auto &monitor : capture_options.monitors) { char name[256]; snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y); record_area_box->add_item(name, monitor.name); } - if(gsr_info.supported_capture_options.portal) + if(capture_options.portal) record_area_box->add_item("Desktop portal", "portal"); record_area_box_ptr = record_area_box.get(); return record_area_box; } - std::unique_ptr<Widget> SettingsPage::create_record_area(const GsrInfo &gsr_info) { + std::unique_ptr<Widget> SettingsPage::create_record_area() { auto record_area_list = std::make_unique<List>(List::Orientation::VERTICAL); record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Capture target:", get_color_theme().text_color)); - record_area_list->add_widget(create_record_area_box(gsr_info)); + record_area_list->add_widget(create_record_area_box()); return record_area_list; } @@ -172,11 +174,11 @@ namespace gsr { return checkbox; } - std::unique_ptr<Widget> SettingsPage::create_capture_target(const GsrInfo &gsr_info) { + std::unique_ptr<Widget> SettingsPage::create_capture_target() { auto ll = std::make_unique<List>(List::Orientation::VERTICAL); auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); - capture_target_list->add_widget(create_record_area(gsr_info)); + capture_target_list->add_widget(create_record_area()); capture_target_list->add_widget(create_select_window()); capture_target_list->add_widget(create_area_size_section()); capture_target_list->add_widget(create_video_resolution_section()); @@ -378,40 +380,40 @@ namespace gsr { return quality_list; } - std::unique_ptr<ComboBox> SettingsPage::create_video_codec_box(const GsrInfo &gsr_info) { + std::unique_ptr<ComboBox> SettingsPage::create_video_codec_box() { auto video_codec_box = std::make_unique<ComboBox>(&get_theme().body_font); // TODO: Show options not supported but disable them. // TODO: Show error if no encoders are supported. // TODO: Show warning (once) if only software encoder is available. video_codec_box->add_item("Auto (Recommended)", "auto"); - if(gsr_info.supported_video_codecs.h264) + if(gsr_info->supported_video_codecs.h264) video_codec_box->add_item("H264", "h264"); - if(gsr_info.supported_video_codecs.hevc) + if(gsr_info->supported_video_codecs.hevc) video_codec_box->add_item("HEVC", "hevc"); - if(gsr_info.supported_video_codecs.av1) + if(gsr_info->supported_video_codecs.av1) video_codec_box->add_item("AV1", "av1"); - if(gsr_info.supported_video_codecs.vp8) + if(gsr_info->supported_video_codecs.vp8) video_codec_box->add_item("VP8", "vp8"); - if(gsr_info.supported_video_codecs.vp9) + if(gsr_info->supported_video_codecs.vp9) video_codec_box->add_item("VP9", "vp9"); - if(gsr_info.supported_video_codecs.hevc_hdr) + if(gsr_info->supported_video_codecs.hevc_hdr) video_codec_box->add_item("HEVC (HDR)", "hevc_hdr"); - if(gsr_info.supported_video_codecs.hevc_10bit) + if(gsr_info->supported_video_codecs.hevc_10bit) video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit"); - if(gsr_info.supported_video_codecs.av1_hdr) + if(gsr_info->supported_video_codecs.av1_hdr) video_codec_box->add_item("AV1 (HDR)", "av1_hdr"); - if(gsr_info.supported_video_codecs.av1_10bit) + if(gsr_info->supported_video_codecs.av1_10bit) video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit"); - if(gsr_info.supported_video_codecs.h264_software) + if(gsr_info->supported_video_codecs.h264_software) video_codec_box->add_item("H264 Software Encoder (Slow, not recommended)", "h264_software"); video_codec_box_ptr = video_codec_box.get(); return video_codec_box; } - std::unique_ptr<List> SettingsPage::create_video_codec(const GsrInfo &gsr_info) { + std::unique_ptr<List> SettingsPage::create_video_codec() { auto video_codec_list = std::make_unique<List>(List::Orientation::VERTICAL); video_codec_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video codec:", get_color_theme().text_color)); - video_codec_list->add_widget(create_video_codec_box(gsr_info)); + video_codec_list->add_widget(create_video_codec_box()); video_codec_ptr = video_codec_list.get(); return video_codec_list; } @@ -477,16 +479,16 @@ namespace gsr { return record_cursor_checkbox; } - std::unique_ptr<Widget> SettingsPage::create_video_section(const GsrInfo &gsr_info) { + std::unique_ptr<Widget> SettingsPage::create_video_section() { auto video_section_list = std::make_unique<List>(List::Orientation::VERTICAL); video_section_list->add_widget(create_video_quality_section()); - video_section_list->add_widget(create_video_codec(gsr_info)); + video_section_list->add_widget(create_video_codec()); video_section_list->add_widget(create_framerate_section()); video_section_list->add_widget(create_record_cursor_section()); return std::make_unique<Subsection>("Video", std::move(video_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); } - std::unique_ptr<Widget> SettingsPage::create_settings(const GsrInfo &gsr_info) { + std::unique_ptr<Widget> SettingsPage::create_settings() { auto page_list = std::make_unique<List>(List::Orientation::VERTICAL); page_list->set_spacing(0.018f); page_list->add_widget(create_view_radio_button()); @@ -496,16 +498,16 @@ namespace gsr { auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL); settings_list->set_spacing(0.018f); - settings_list->add_widget(create_capture_target(gsr_info)); + settings_list->add_widget(create_capture_target()); settings_list->add_widget(create_audio_section()); - settings_list->add_widget(create_video_section(gsr_info)); + settings_list->add_widget(create_video_section()); settings_list_ptr = settings_list.get(); settings_scrollable_page_ptr->add_widget(std::move(settings_list)); return page_list; } - void SettingsPage::add_widgets(const GsrInfo &gsr_info) { - content_page_ptr->add_widget(create_settings(gsr_info)); + 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; @@ -534,32 +536,32 @@ namespace gsr { }; video_quality_box_ptr->on_selection_changed("", video_quality_box_ptr->get_selected_id()); - if(!gsr_info.supported_capture_options.monitors.empty()) - record_area_box_ptr->set_selected_item(gsr_info.supported_capture_options.monitors.front().name); - else if(gsr_info.supported_capture_options.portal) + if(!capture_options.monitors.empty()) + record_area_box_ptr->set_selected_item(capture_options.monitors.front().name); + else if(capture_options.portal) record_area_box_ptr->set_selected_item("portal"); - else if(gsr_info.supported_capture_options.window) + else if(capture_options.window) record_area_box_ptr->set_selected_item("window"); else record_area_box_ptr->on_selection_changed("", ""); - if(!gsr_info.system_info.supports_app_audio) { + 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(const GsrInfo &gsr_info) { + void SettingsPage::add_page_specific_widgets() { switch(type) { case Type::REPLAY: - add_replay_widgets(gsr_info); + add_replay_widgets(); break; case Type::RECORD: - add_record_widgets(gsr_info); + add_record_widgets(); break; case Type::STREAM: - add_stream_widgets(gsr_info); + add_stream_widgets(); break; } } @@ -622,9 +624,9 @@ namespace gsr { return replay_time_list; } - std::unique_ptr<RadioButton> SettingsPage::create_start_replay_automatically(const GsrInfo &gsr_info) { + 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 only)"); + snprintf(fullscreen_text, sizeof(fullscreen_text), "Turn on replay when starting a fullscreen application%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 only)"); auto radiobutton = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL); radiobutton->add_item("Don't turn on replay automatically", "dont_turn_on_automatically"); @@ -635,9 +637,9 @@ namespace gsr { return radiobutton; } - std::unique_ptr<CheckBox> SettingsPage::create_save_replay_in_game_folder(const GsrInfo &gsr_info) { + std::unique_ptr<CheckBox> SettingsPage::create_save_replay_in_game_folder() { char text[256]; - snprintf(text, sizeof(text), "Save video in a folder with the name of the game%s", gsr_info.system_info.display_server == DisplayServer::X11 ? "" : " (X11 only)"); + snprintf(text, sizeof(text), "Save video in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 only)"); auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text); save_replay_in_game_folder_ptr = checkbox.get(); return checkbox; @@ -659,7 +661,7 @@ namespace gsr { estimated_file_size_ptr->set_text(buffer); } - void SettingsPage::add_replay_widgets(const GsrInfo &gsr_info) { + 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); file_info_data_list->add_widget(create_save_directory("Directory to save replays:")); @@ -670,8 +672,8 @@ 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(gsr_info)); - general_list->add_widget(create_save_replay_in_game_folder(gsr_info)); + general_list->add_widget(create_start_replay_automatically()); + general_list->add_widget(create_save_replay_in_game_folder()); 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))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); @@ -716,22 +718,22 @@ namespace gsr { }; } - std::unique_ptr<CheckBox> SettingsPage::create_save_recording_in_game_folder(const GsrInfo &gsr_info) { + std::unique_ptr<CheckBox> SettingsPage::create_save_recording_in_game_folder() { char text[256]; - snprintf(text, sizeof(text), "Save video in a folder with the name of the game%s", gsr_info.system_info.display_server == DisplayServer::X11 ? "" : " (X11 only)"); + snprintf(text, sizeof(text), "Save video in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 only)"); auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text); save_recording_in_game_folder_ptr = checkbox.get(); return checkbox; } - void SettingsPage::add_record_widgets(const GsrInfo &gsr_info) { + void SettingsPage::add_record_widgets() { auto file_list = std::make_unique<List>(List::Orientation::HORIZONTAL); file_list->add_widget(create_save_directory("Directory to save the video:")); file_list->add_widget(create_container_section()); settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_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_save_recording_in_game_folder(gsr_info)); + general_list->add_widget(create_save_recording_in_game_folder()); 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))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); @@ -825,7 +827,7 @@ namespace gsr { return container_list; } - void SettingsPage::add_stream_widgets(const GsrInfo&) { + void SettingsPage::add_stream_widgets() { auto streaming_info_list = std::make_unique<List>(List::Orientation::HORIZONTAL); streaming_info_list->add_widget(create_streaming_service_section()); streaming_info_list->add_widget(create_stream_key_section()); @@ -879,16 +881,16 @@ namespace gsr { save(); } - void SettingsPage::load(const GsrInfo &gsr_info) { + void SettingsPage::load() { switch(type) { case Type::REPLAY: - load_replay(gsr_info); + load_replay(); break; case Type::RECORD: - load_record(gsr_info); + load_record(); break; case Type::STREAM: - load_stream(gsr_info); + load_stream(); break; } } @@ -921,11 +923,11 @@ namespace gsr { return str.size() >= len && memcmp(str.data(), substr, len) == 0; } - void SettingsPage::load_audio_tracks(const RecordOptions &record_options, const GsrInfo &gsr_info) { + 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) + if(!gsr_info->system_info.supports_app_audio) continue; std::string audio_track_name = audio_track.substr(4); @@ -955,12 +957,12 @@ namespace gsr { } } - void SettingsPage::load_common(RecordOptions &record_options, const GsrInfo &gsr_info) { + void SettingsPage::load_common(RecordOptions &record_options) { record_area_box_ptr->set_selected_item(record_options.record_area_option); merge_audio_tracks_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, gsr_info); + load_audio_tracks(record_options); color_range_box_ptr->set_selected_item(record_options.color_range); video_quality_box_ptr->set_selected_item(record_options.video_quality); video_codec_box_ptr->set_selected_item(record_options.video_codec); @@ -1009,8 +1011,8 @@ namespace gsr { video_bitrate_entry_ptr->set_text(std::to_string(record_options.video_bitrate)); } - void SettingsPage::load_replay(const GsrInfo &gsr_info) { - load_common(config.replay_config.record_options, gsr_info); + void SettingsPage::load_replay() { + load_common(config.replay_config.record_options); 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); show_replay_started_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_started_notifications); @@ -1024,8 +1026,8 @@ namespace gsr { replay_time_entry_ptr->set_text(std::to_string(config.replay_config.replay_time)); } - void SettingsPage::load_record(const GsrInfo &gsr_info) { - load_common(config.record_config.record_options, gsr_info); + void SettingsPage::load_record() { + load_common(config.record_config.record_options); save_recording_in_game_folder_ptr->set_checked(config.record_config.save_video_in_game_folder); show_recording_started_notification_checkbox_ptr->set_checked(config.record_config.show_recording_started_notifications); show_video_saved_notification_checkbox_ptr->set_checked(config.record_config.show_video_saved_notifications); @@ -1033,8 +1035,8 @@ namespace gsr { container_box_ptr->set_selected_item(config.record_config.container); } - void SettingsPage::load_stream(const GsrInfo &gsr_info) { - load_common(config.streaming_config.record_options, gsr_info); + void SettingsPage::load_stream() { + load_common(config.streaming_config.record_options); show_streaming_started_notification_checkbox_ptr->set_checked(config.streaming_config.show_streaming_started_notifications); show_streaming_stopped_notification_checkbox_ptr->set_checked(config.streaming_config.show_streaming_stopped_notifications); streaming_service_box_ptr->set_selected_item(config.streaming_config.streaming_service); diff --git a/src/gui/Utils.cpp b/src/gui/Utils.cpp index e000b7a..d1643f2 100644 --- a/src/gui/Utils.cpp +++ b/src/gui/Utils.cpp @@ -50,4 +50,21 @@ namespace gsr { void set_frame_delta_seconds(double frame_delta) { frame_delta_seconds = frame_delta; } + + mgl::vec2f scale_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to) { + if(std::abs(from.x) <= 0.0001f || std::abs(from.y) <= 0.0001f) + return {0.0f, 0.0f}; + + const double height_to_width_ratio = (double)from.y / (double)from.x; + from.x = to.x; + from.y = from.x * height_to_width_ratio; + + if(from.y > to.y) { + const double width_height_ratio = (double)from.x / (double)from.y; + from.y = to.y; + from.x = from.y * width_height_ratio; + } + + return from; + } }
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 7002288..98b1ce3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -168,8 +168,11 @@ int main(void) { exit(1); } - if(gsr_info.system_info.display_server == gsr::DisplayServer::WAYLAND) - fprintf(stderr, "warning: Wayland support is experimental and requires XWayland. Things may not work as expected.\n"); + const gsr::DisplayServer display_server = gsr_info.system_info.display_server; + if(display_server == gsr::DisplayServer::WAYLAND) + fprintf(stderr, "warning: Wayland support is experimental and requires XWayland. Things may not work as expected.\n"); + + gsr::SupportedCaptureOptions capture_options = gsr::get_supported_capture_options(gsr_info); std::string resources_path; if(access("sibs-build", F_OK) == 0) { @@ -197,11 +200,11 @@ int main(void) { fprintf(stderr, "info: gsr ui is now ready, waiting for inputs. Press alt+z to show/hide the overlay\n"); - auto overlay = std::make_unique<gsr::Overlay>(resources_path, gsr_info, egl_funcs); + auto overlay = std::make_unique<gsr::Overlay>(resources_path, std::move(gsr_info), std::move(capture_options), egl_funcs); //overlay.show(); std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr; - if(gsr_info.system_info.display_server == gsr::DisplayServer::X11) { + if(display_server == gsr::DisplayServer::X11) { global_hotkeys = register_x11_hotkeys(overlay.get()); if(!global_hotkeys) { fprintf(stderr, "info: failed to register some x11 hotkeys because they are registered by another program. Will use linux hotkeys instead that can clash with keys used by other applications\n"); |