diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-04-14 11:38:52 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-04-14 11:38:52 +0200 |
commit | 0018788780d756dbf0d3a77f6b40b384183348f7 (patch) | |
tree | 63c8e5f5f0ffb60f85d500c35d4aa2d1d5c73e56 | |
parent | e3e6c3c3b9485f5cdb375eec77b3ae0f1f8a4135 (diff) |
Redesign audio to support multiple audio tracks explicitly
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | images/trash.png | bin | 0 -> 691 bytes | |||
-rw-r--r-- | include/Config.hpp | 17 | ||||
-rw-r--r-- | include/SafeVector.hpp | 137 | ||||
-rw-r--r-- | include/Theme.hpp | 1 | ||||
-rw-r--r-- | include/Utils.hpp | 1 | ||||
-rw-r--r-- | include/gui/List.hpp | 5 | ||||
-rw-r--r-- | include/gui/Page.hpp | 4 | ||||
-rw-r--r-- | include/gui/ScrollablePage.hpp | 1 | ||||
-rw-r--r-- | include/gui/SettingsPage.hpp | 35 | ||||
-rw-r--r-- | include/gui/Subsection.hpp | 5 | ||||
-rw-r--r-- | include/gui/Widget.hpp | 6 | ||||
-rw-r--r-- | src/Config.cpp | 82 | ||||
-rw-r--r-- | src/GlobalHotkeysLinux.cpp | 2 | ||||
-rw-r--r-- | src/GsrInfo.cpp | 5 | ||||
-rw-r--r-- | src/Overlay.cpp | 117 | ||||
-rw-r--r-- | src/Theme.cpp | 9 | ||||
-rw-r--r-- | src/Utils.cpp | 5 | ||||
-rw-r--r-- | src/gui/ComboBox.cpp | 2 | ||||
-rw-r--r-- | src/gui/GlobalSettingsPage.cpp | 2 | ||||
-rw-r--r-- | src/gui/GsrPage.cpp | 5 | ||||
-rw-r--r-- | src/gui/List.cpp | 30 | ||||
-rw-r--r-- | src/gui/Page.cpp | 15 | ||||
-rw-r--r-- | src/gui/ScreenshotSettingsPage.cpp | 3 | ||||
-rw-r--r-- | src/gui/ScrollablePage.cpp | 13 | ||||
-rw-r--r-- | src/gui/SettingsPage.cpp | 382 | ||||
-rw-r--r-- | src/gui/StaticPage.cpp | 5 | ||||
-rw-r--r-- | src/gui/Subsection.cpp | 17 | ||||
-rw-r--r-- | src/gui/Widget.cpp | 16 | ||||
-rw-r--r-- | src/main.cpp | 5 |
30 files changed, 596 insertions, 335 deletions
@@ -123,8 +123,6 @@ Maybe change gsr-ui startup retry time in the systemd service, from 5 seconds to Add support for window capture. This should not prompt for window selection directly but instead prompt for window selection when recording starts and hide the ui first. For screenshots window capture should exist but "follow focused" option should not exist. -Improve audio design. It should have a button to add/remove audio tracks and button to add audio into each audio track separately and "record audio from all applications except the selected ones" for each audio track. Then also remove the "merge audio tracks" option. - Make it possible to take a screenshot through a button in the ui instead of having to use hotkey. Handle failing to save a replay. gsr should output "failed to save replay, or something like that" to make it possible to detect that. @@ -161,4 +159,4 @@ If CursorTrackerWayland fails then fallback to getting focused monitor by window Maybe automatically switch to recording with the device that controls the monitor. In that case also add all monitors available to capture in the capture list and automatically choose the gpu that controls the monitor. -Support cjk font. Use fc-match to find the location of the font. This also works in flatpak, in which case the fonts are in /run/host/..., where it lists system fonts. +Support cjk font. Use fc-match to find the location of the font. This also works in flatpak, in which case the fonts are in /run/host/..., where it lists system fonts.
\ No newline at end of file diff --git a/images/trash.png b/images/trash.png Binary files differnew file mode 100644 index 0000000..cf1d9de --- /dev/null +++ b/images/trash.png diff --git a/include/Config.hpp b/include/Config.hpp index 0e8e4eb..bbb5381 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -6,7 +6,7 @@ #include <vector> #include <optional> -#define GSR_CONFIG_FILE_VERSION 1 +#define GSR_CONFIG_FILE_VERSION 2 namespace gsr { struct SupportedCaptureOptions; @@ -30,6 +30,14 @@ namespace gsr { std::string to_string(bool spaces = true, bool modifier_side = true) const; }; + struct AudioTrack { + std::vector<std::string> audio_inputs; // ids + bool application_audio_invert = false; + + bool operator==(const AudioTrack &other) const; + bool operator!=(const AudioTrack &other) const; + }; + struct RecordOptions { std::string record_area_option = "screen"; int32_t record_area_width = 0; @@ -38,10 +46,11 @@ namespace gsr { int32_t video_height = 0; int32_t fps = 60; int32_t video_bitrate = 15000; - bool merge_audio_tracks = true; // Currently unused for streaming because all known streaming sites only support 1 audio track - bool application_audio_invert = false; + bool merge_audio_tracks = true; // TODO: Remove in the future + bool application_audio_invert = false; // TODO: Remove in the future bool change_video_resolution = false; - std::vector<std::string> audio_tracks; + std::vector<std::string> audio_tracks; // ids, TODO: Remove in the future + std::vector<AudioTrack> audio_tracks_list; std::string color_range = "limited"; std::string video_quality = "very_high"; std::string video_codec = "auto"; diff --git a/include/SafeVector.hpp b/include/SafeVector.hpp index 6cb4725..63125ad 100644 --- a/include/SafeVector.hpp +++ b/include/SafeVector.hpp @@ -1,8 +1,15 @@ #pragma once #include <vector> -#include <memory> #include <functional> +#include <assert.h> +#include "gui/Widget.hpp" + +template <typename T> +struct SafeVectorItem { + T item; + bool alive = false; +}; // A vector that can be modified while iterating template <typename T> @@ -10,64 +17,84 @@ class SafeVector { public: using PointerType = typename std::pointer_traits<T>::element_type*; + SafeVector() = default; + SafeVector(const SafeVector&) = delete; + SafeVector& operator=(const SafeVector&) = delete; + ~SafeVector() { + clear(); + } + void push_back(T item) { - data.push_back(std::move(item)); + data.push_back({std::move(item), true}); + ++num_items_alive; } // Safe to call when vector is empty // TODO: Make this iterator safe void pop_back() { - if(!data.empty()) + if(!data.empty()) { + gsr::add_widget_to_remove(std::move(data.back().item)); data.pop_back(); + --num_items_alive; + } } // Might not remove the data immediately if inside for_each loop. // In that case the item is removed at the end of the loop. void remove(PointerType item_to_remove) { - if(for_each_depth == 0) + if(for_each_depth == 0) { remove_item(item_to_remove); - else - remove_queue.push_back(item_to_remove); + return; + } + + SafeVectorItem<T> *item = get_item(item_to_remove); + if(item && item->alive) { + item->alive = false; + --num_items_alive; + has_items_to_remove = true; + } } // Safe to call when vector is empty, in which case it returns nullptr T* back() { - if(data.empty()) - return nullptr; - else - return &data.back(); + for(auto it = data.rbegin(), end = data.rend(); it != end; ++it) { + if(it->alive) + return &it->item; + } + return nullptr; } // TODO: Make this iterator safe void clear() { + for(auto &item : data) { + gsr::add_widget_to_remove(std::move(item.item)); + } data.clear(); - remove_queue.clear(); + num_items_alive = 0; } // Return true from |callback| to continue. This function returns false if |callback| returned false - bool for_each(std::function<bool(T &t)> callback) { + bool for_each(std::function<bool(T &t)> callback, bool include_dead = false) { bool result = true; ++for_each_depth; for(size_t i = 0; i < data.size(); ++i) { - result = callback(data[i]); - if(!result) - break; + if(data[i].alive || include_dead) { + result = callback(data[i].item); + if(!result) + break; + } } --for_each_depth; - if(for_each_depth == 0) { - for(PointerType item_to_remove : remove_queue) { - remove_item(item_to_remove); - } - remove_queue.clear(); - } + if(for_each_depth == 0) + remove_dead_items(); return result; } // Return true from |callback| to continue. This function returns false if |callback| returned false - bool for_each_reverse(std::function<bool(T &t)> callback) { + bool for_each_reverse(std::function<bool(T &t)> callback, bool include_dead = false) { bool result = true; ++for_each_depth; @@ -80,50 +107,84 @@ public: if(i < 0) break; - result = callback(data[i]); - if(!result) - break; + if(data[i].alive || include_dead) { + result = callback(data[i].item); + if(!result) + break; + } --i; } --for_each_depth; - if(for_each_depth == 0) { - for(PointerType item_to_remove : remove_queue) { - remove_item(item_to_remove); - } - remove_queue.clear(); - } + if(for_each_depth == 0) + remove_dead_items(); return result; } T& operator[](size_t index) { - return data[index]; + assert(index < data.size()); + return data[index].item; } const T& operator[](size_t index) const { - return data[index]; + assert(index < data.size()); + return data[index].item; } size_t size() const { - return data.size(); + return (size_t)num_items_alive; } bool empty() const { - return data.empty(); + return num_items_alive == 0; + } + + void replace_item(PointerType item_to_replace, T new_item) { + SafeVectorItem<T> *item = get_item(item_to_replace); + if(item->alive) { + gsr::add_widget_to_remove(std::move(item->item)); + item->item = std::move(new_item); + } } private: void remove_item(PointerType item_to_remove) { for(auto it = data.begin(), end = data.end(); it != end; ++it) { - if(&*(*it) == item_to_remove) { + if(&*(it->item) == item_to_remove) { + gsr::add_widget_to_remove(std::move(it->item)); data.erase(it); + --num_items_alive; return; } } } + + SafeVectorItem<T>* get_item(PointerType item_to_remove) { + for(auto &item : data) { + if(&*(item.item) == item_to_remove) + return &item; + } + return nullptr; + } + + void remove_dead_items() { + if(!has_items_to_remove) + return; + + for(auto it = data.begin(); it != data.end();) { + if(it->alive) { + ++it; + } else { + gsr::add_widget_to_remove(std::move(it->item)); + it = data.erase(it); + } + } + has_items_to_remove = false; + } private: - std::vector<T> data; - std::vector<PointerType> remove_queue; + std::vector<SafeVectorItem<T>> data; int for_each_depth = 0; + int num_items_alive = 0; + bool has_items_to_remove = false; };
\ No newline at end of file diff --git a/include/Theme.hpp b/include/Theme.hpp index 4305072..670980f 100644 --- a/include/Theme.hpp +++ b/include/Theme.hpp @@ -42,6 +42,7 @@ namespace gsr { mgl::Texture pause_texture; mgl::Texture save_texture; mgl::Texture screenshot_texture; + mgl::Texture trash_texture; mgl::Texture ps4_home_texture; mgl::Texture ps4_options_texture; diff --git a/include/Utils.hpp b/include/Utils.hpp index 19700df..1415209 100644 --- a/include/Utils.hpp +++ b/include/Utils.hpp @@ -14,6 +14,7 @@ namespace gsr { using StringSplitCallback = std::function<bool(std::string_view line)>; void string_split_char(std::string_view str, char delimiter, StringSplitCallback callback_func); + bool starts_with(std::string_view str, const char *substr); std::string get_home_dir(); std::string get_config_dir(); diff --git a/include/gui/List.hpp b/include/gui/List.hpp index 72c5353..f79d165 100644 --- a/include/gui/List.hpp +++ b/include/gui/List.hpp @@ -21,19 +21,20 @@ namespace gsr { List(Orientation orientation, Alignment content_alignment = Alignment::START); List(const List&) = delete; List& operator=(const List&) = delete; + virtual ~List() override; bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; void draw(mgl::Window &window, mgl::vec2f offset) override; - //void remove_child_widget(Widget *widget) override; - void add_widget(std::unique_ptr<Widget> widget); void remove_widget(Widget *widget); + void replace_widget(Widget *widget, std::unique_ptr<Widget> new_widget); void clear(); // Return true from |callback| to continue void for_each_child_widget(std::function<bool(std::unique_ptr<Widget> &widget)> callback); // Returns nullptr if index is invalid Widget* get_child_widget_by_index(size_t index) const; + size_t get_num_children() const; void set_spacing(float spacing); diff --git a/include/gui/Page.hpp b/include/gui/Page.hpp index 0d8536a..00a53c6 100644 --- a/include/gui/Page.hpp +++ b/include/gui/Page.hpp @@ -10,13 +10,11 @@ namespace gsr { Page() = default; Page(const Page&) = delete; Page& operator=(const Page&) = delete; - virtual ~Page() = default; + virtual ~Page() override; virtual void on_navigate_to_page() {} virtual void on_navigate_away_from_page() {} - //void remove_child_widget(Widget *widget) override; - virtual void add_widget(std::unique_ptr<Widget> widget); protected: SafeVector<std::unique_ptr<Widget>> widgets; diff --git a/include/gui/ScrollablePage.hpp b/include/gui/ScrollablePage.hpp index 452d0e9..54ec2cb 100644 --- a/include/gui/ScrollablePage.hpp +++ b/include/gui/ScrollablePage.hpp @@ -12,6 +12,7 @@ namespace gsr { ScrollablePage(mgl::vec2f size); ScrollablePage(const ScrollablePage&) = delete; ScrollablePage& operator=(const ScrollablePage&) = delete; + virtual ~ScrollablePage() override; bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; void draw(mgl::Window &window, mgl::vec2f offset) override; diff --git a/include/gui/SettingsPage.hpp b/include/gui/SettingsPage.hpp index b9f5cde..fb705cc 100644 --- a/include/gui/SettingsPage.hpp +++ b/include/gui/SettingsPage.hpp @@ -18,6 +18,7 @@ namespace gsr { class ScrollablePage; class Label; class LineSeparator; + class Subsection; class SettingsPage : public StaticPage { public: @@ -54,19 +55,20 @@ namespace gsr { std::unique_ptr<Widget> create_change_video_resolution_section(); std::unique_ptr<Widget> create_capture_target_section(); 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(); - std::unique_ptr<Button> create_add_audio_device_button(); - std::unique_ptr<ComboBox> create_application_audio_selection_combobox(); - std::unique_ptr<List> create_application_audio(); - std::unique_ptr<List> create_custom_application_audio(); - std::unique_ptr<Button> create_add_application_audio_button(); - std::unique_ptr<Button> create_add_custom_application_audio_button(); - std::unique_ptr<List> create_add_audio_buttons(); - std::unique_ptr<List> create_audio_track_track_section(); - std::unique_ptr<CheckBox> create_split_audio_checkbox(); + std::unique_ptr<Button> create_remove_audio_device_button(List *audio_input_list_ptr, List *audio_device_list_ptr); + std::unique_ptr<List> create_audio_device(List *audio_input_list_ptr); + std::unique_ptr<Button> create_add_audio_track_button(); + std::unique_ptr<Button> create_add_audio_device_button(List *audio_input_list_ptr); + std::unique_ptr<ComboBox> create_application_audio_selection_combobox(List *application_audio_row); + std::unique_ptr<List> create_application_audio(List *audio_input_list_ptr); + std::unique_ptr<List> create_custom_application_audio(List *audio_input_list_ptr); + std::unique_ptr<Button> create_add_application_audio_button(List *audio_input_list_ptr); + std::unique_ptr<List> create_add_audio_buttons(List *audio_input_list_ptr); + std::unique_ptr<List> create_audio_input_section(); std::unique_ptr<CheckBox> create_application_audio_invert_checkbox(); - std::unique_ptr<Widget> create_audio_track_section(); + std::unique_ptr<List> create_audio_track_title_and_remove(Subsection *audio_track_subsection, const char *title); + std::unique_ptr<Subsection> create_audio_track_section(Widget *parent_widget); + std::unique_ptr<List> create_audio_track_section_list(); std::unique_ptr<Widget> create_audio_section(); std::unique_ptr<List> create_video_quality_box(); std::unique_ptr<List> create_video_bitrate_entry(); @@ -125,6 +127,8 @@ namespace gsr { void save_replay(); void save_record(); void save_stream(); + + void view_changed(bool advanced_view, Subsection *notifications_subsection_ptr); private: Type type; Config &config; @@ -152,11 +156,6 @@ namespace gsr { Entry *framerate_entry_ptr = nullptr; Entry *video_bitrate_entry_ptr = nullptr; List *video_bitrate_list_ptr = nullptr; - List *audio_track_list_ptr = nullptr; - Button *add_application_audio_button_ptr = nullptr; - Button *add_custom_application_audio_button_ptr = nullptr; - CheckBox *split_audio_checkbox_ptr = nullptr; - CheckBox *application_audio_invert_checkbox_ptr = nullptr; CheckBox *change_video_resolution_checkbox_ptr = nullptr; ComboBox *color_range_box_ptr = nullptr; ComboBox *video_quality_box_ptr = nullptr; @@ -189,6 +188,8 @@ namespace gsr { Entry *replay_time_entry_ptr = nullptr; Label *replay_time_label_ptr = nullptr; RadioButton *turn_on_replay_automatically_mode_ptr = nullptr; + Subsection *audio_section_ptr = nullptr; + List *audio_track_section_list_ptr = nullptr; PageStack *page_stack = nullptr; }; diff --git a/include/gui/Subsection.hpp b/include/gui/Subsection.hpp index 4da6baf..88953d2 100644 --- a/include/gui/Subsection.hpp +++ b/include/gui/Subsection.hpp @@ -11,15 +11,20 @@ namespace gsr { Subsection(const char *title, std::unique_ptr<Widget> inner_widget, mgl::vec2f size); Subsection(const Subsection&) = delete; Subsection& operator=(const Subsection&) = delete; + virtual ~Subsection() override; bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; void draw(mgl::Window &window, mgl::vec2f offset) override; mgl::vec2f get_size() override; mgl::vec2f get_inner_size() override; + + Widget* get_inner_widget(); + void set_bg_color(mgl::Color color); private: Label label; std::unique_ptr<Widget> inner_widget; mgl::vec2f size; + mgl::Color bg_color{25, 30, 34}; }; }
\ No newline at end of file diff --git a/include/gui/Widget.hpp b/include/gui/Widget.hpp index 57424cd..131e756 100644 --- a/include/gui/Widget.hpp +++ b/include/gui/Widget.hpp @@ -1,6 +1,7 @@ #pragma once #include <mglpp/system/vec.hpp> +#include <memory> namespace mgl { class Event; @@ -31,8 +32,6 @@ namespace gsr { virtual void draw(mgl::Window &window, mgl::vec2f offset) = 0; virtual void set_position(mgl::vec2f position); - //virtual void remove_child_widget(Widget *widget) { (void)widget; } - virtual mgl::vec2f get_position() const; virtual mgl::vec2f get_size() = 0; // This can be different from get_size, for example with ScrollablePage this excludes the margins @@ -61,4 +60,7 @@ namespace gsr { bool visible = true; }; + + void add_widget_to_remove(std::unique_ptr<Widget> widget); + void remove_widgets_to_be_removed(); }
\ No newline at end of file diff --git a/src/Config.cpp b/src/Config.cpp index 9847c45..3832c83 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -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) @@ -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,16 +118,16 @@ 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.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); record_config.record_options.video_bitrate = 45000; 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.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); replay_config.record_options.video_bitrate = 45000; screenshot_config.save_directory = default_pictures_save_directory; @@ -152,7 +162,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 +184,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 +214,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 +243,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}, @@ -291,6 +304,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 +318,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 +340,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 +387,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 +411,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 +456,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/GlobalHotkeysLinux.cpp b/src/GlobalHotkeysLinux.cpp index 20b089f..d780916 100644 --- a/src/GlobalHotkeysLinux.cpp +++ b/src/GlobalHotkeysLinux.cpp @@ -77,6 +77,8 @@ namespace gsr { 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) { 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 c342d37..fe7c88e 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -685,6 +685,8 @@ namespace gsr { } bool Overlay::draw() { + remove_widgets_to_be_removed(); + update_notification_process_status(); update_gsr_replay_save(); update_gsr_process_status(); @@ -1249,6 +1251,7 @@ namespace gsr { while(!page_stack.empty()) { page_stack.pop(); } + remove_widgets_to_be_removed(); if(default_cursor) { XFreeCursor(display, default_cursor); @@ -1742,28 +1745,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; @@ -1794,7 +1794,7 @@ namespace gsr { 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 +1811,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,7 +1823,7 @@ 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); } @@ -1947,29 +1947,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 +1986,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 +2002,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) { @@ -2133,8 +2128,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(); @@ -2173,7 +2167,7 @@ namespace gsr { } 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); args.push_back(nullptr); @@ -2276,8 +2270,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,7 +2302,7 @@ 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); @@ -2429,8 +2422,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"; @@ -2466,9 +2462,8 @@ 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); args.push_back(nullptr); diff --git a/src/Theme.cpp b/src/Theme.cpp index 2001f7d..c813834 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, true})) 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, true})) 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, true})) goto error; if(!theme->play_texture.load_from_file((resources_path + "images/play.png").c_str())) @@ -114,6 +114,9 @@ namespace gsr { if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str())) goto error; + if(!theme->trash_texture.load_from_file((resources_path + "images/trash.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) + goto error; + if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, true})) goto error; diff --git a/src/Utils.cpp b/src/Utils.cpp index df6db2f..bc7b1f2 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -22,6 +22,11 @@ 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; + } + std::string get_home_dir() { const char *home_dir = getenv("HOME"); if(!home_dir) { diff --git a/src/gui/ComboBox.cpp b/src/gui/ComboBox.cpp index dbe9aa0..08c4ec2 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() * 25); // TODO: Make a proper solution //items.back().text.set_max_rows(1); dirty = true; } diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp index 1e2a444..fd4b6b8 100644 --- a/src/gui/GlobalSettingsPage.cpp +++ b/src/gui/GlobalSettingsPage.cpp @@ -149,7 +149,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); 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/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..94eeb0d 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, @@ -202,121 +204,199 @@ namespace gsr { 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(1)); + 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(35, 40, 44)); + remove_audio_track_button->set_icon(&get_theme().trash_texture); + 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(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(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() { + 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_device_button(List *audio_input_list_ptr) { 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]() { + add_audio_track_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(audio_input_list_ptr)); }; return add_audio_track_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(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<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()); + 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<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()); - }; - 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_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(35, 40, 44)); + remove_track_button->set_icon(&get_theme().trash_texture); + 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->add_widget(create_add_audio_buttons()); - list->add_widget(create_audio_track_track_section()); + 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); + 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 +611,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 +628,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 +647,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() { @@ -716,6 +788,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); @@ -754,16 +836,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"); @@ -828,16 +902,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 +999,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 +1012,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 +1064,61 @@ 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:")) { + std::unique_ptr<List> audio_track_widget = create_audio_device(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.substr(7)); + 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)); + std::unique_ptr<List> audio_track_widget = create_audio_device(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); @@ -1139,28 +1208,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; - } - 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; + 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_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 +1252,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(); 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..395b69a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -303,7 +303,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; } |