diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-02-22 13:31:51 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-02-22 13:31:51 +0100 |
commit | 189736c1a96a1ad0e571ad69f01039e96455011a (patch) | |
tree | 22e1a78f698209e2ce0ef462a5d281b5b3030897 | |
parent | 8003c209fea16cd164817306cb33d46ac61a44f0 (diff) |
Add option to take a screenshot (default hotkey: alt+f1)
31 files changed, 976 insertions, 202 deletions
@@ -78,8 +78,6 @@ Dont allow autostart of replay if capture option is window recording (when windo Use global shortcuts desktop portal protocol on wayland when available. -When support for window capture is enabled on x11 then make sure to not save the window except temporary while the program is open. - Support CJK. Move ui hover code from ::draw to ::on_event, to properly handle widget event stack. @@ -121,3 +119,10 @@ Add recording timer to see duration of recording/streaming. Make folder with window name work when using gamescope. Gamescope runs x11 itself so to get the window name inside that we have to connect to the gamescope X11 server (DISPLAY=:1 on x11 and DISPLAY=:2 on wayland, but not always). When clicking on current directory in file manager show a dropdown menu where you can select common directories (HOME, Videos, Downloads and mounted drives) for quick navigation. Maybe even button to search. + +Maybe change gsr-ui startup retry time in the systemd service, from 5 seconds to 2 seconds. + +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. diff --git a/depends/mglpp b/depends/mglpp -Subproject d875a5c2b9cd3b123e4253ba48f8738ff5b08f1 +Subproject 04a9fdec5a7248245b0c3ec874a5df56e039a8f diff --git a/images/screenshot.png b/images/screenshot.png Binary files differnew file mode 100644 index 0000000..9f04433 --- /dev/null +++ b/images/screenshot.png diff --git a/include/Config.hpp b/include/Config.hpp index 34c2010..1c82822 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -17,6 +17,8 @@ namespace gsr { bool operator==(const ConfigHotkey &other) const; bool operator!=(const ConfigHotkey &other) const; + + std::string to_string(bool spaces = true, bool modifier_side = true) const; }; struct RecordOptions { @@ -101,15 +103,34 @@ namespace gsr { ConfigHotkey save_hotkey; }; + struct ScreenshotConfig { + std::string record_area_option = "screen"; + int32_t image_width = 0; + int32_t image_height = 0; + bool change_image_resolution = false; + std::string image_quality = "very_high"; + std::string image_format = "jpg"; + bool record_cursor = true; + bool restore_portal_session = true; + + bool save_screenshot_in_game_folder = false; + bool show_screenshot_saved_notifications = true; + std::string save_directory; + ConfigHotkey take_screenshot_hotkey; + }; + struct Config { Config(const SupportedCaptureOptions &capture_options); bool operator==(const Config &other); bool operator!=(const Config &other); + void set_hotkeys_to_default(); + MainConfig main_config; StreamingConfig streaming_config; RecordConfig record_config; ReplayConfig replay_config; + ScreenshotConfig screenshot_config; }; std::optional<Config> read_config(const SupportedCaptureOptions &capture_options); diff --git a/include/GsrInfo.hpp b/include/GsrInfo.hpp index a8c0742..b027cc5 100644 --- a/include/GsrInfo.hpp +++ b/include/GsrInfo.hpp @@ -20,6 +20,11 @@ namespace gsr { bool vp9 = false; }; + struct SupportedImageFormats { + bool jpeg = false; + bool png = false; + }; + struct GsrMonitor { std::string name; mgl::vec2i size; @@ -75,6 +80,7 @@ namespace gsr { SystemInfo system_info; GpuInfo gpu_info; SupportedVideoCodecs supported_video_codecs; + SupportedImageFormats supported_image_formats; }; enum class GsrInfoExitStatus { diff --git a/include/Overlay.hpp b/include/Overlay.hpp index f3025b2..812954e 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -35,7 +35,8 @@ namespace gsr { NONE, RECORD, REPLAY, - STREAM + STREAM, + SCREENSHOT }; class Overlay { @@ -57,6 +58,7 @@ namespace gsr { void toggle_stream(); void toggle_replay(); void save_replay(); + void take_screenshot(); void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type); bool is_open() const; bool should_exit(std::string &reason) const; @@ -70,6 +72,7 @@ namespace gsr { void handle_keyboard_mapping_event(); void on_event(mgl::Event &event); + void create_frontpage_ui_components(); void xi_setup(); void handle_xi_events(); void process_key_bindings(mgl::Event &event); @@ -84,6 +87,7 @@ namespace gsr { void on_replay_saved(const char *replay_saved_filepath); void update_gsr_replay_save(); void update_gsr_process_status(); + void update_gsr_screenshot_process_status(); void replay_status_update_status(); void update_focused_fullscreen_status(); @@ -107,6 +111,7 @@ namespace gsr { void on_press_start_replay(bool disable_notification); void on_press_start_record(); void on_press_start_stream(); + void on_press_take_screenshot(); bool update_compositor_texture(const Monitor &monitor); void force_window_on_top(); @@ -149,6 +154,7 @@ namespace gsr { pid_t notification_process = -1; int gpu_screen_recorder_process_output_fd = -1; FILE *gpu_screen_recorder_process_output_file = nullptr; + pid_t gpu_screen_recorder_screenshot_process = -1; DropdownButton *replay_dropdown_button_ptr = nullptr; DropdownButton *record_dropdown_button_ptr = nullptr; @@ -165,6 +171,7 @@ namespace gsr { bool focused_window_is_fullscreen = false; std::string record_filepath; + std::string screenshot_filepath; Display *xi_display = nullptr; int xi_opcode = 0; diff --git a/include/Theme.hpp b/include/Theme.hpp index 185bcdc..90dd8cf 100644 --- a/include/Theme.hpp +++ b/include/Theme.hpp @@ -41,6 +41,7 @@ namespace gsr { mgl::Texture stop_texture; mgl::Texture pause_texture; mgl::Texture save_texture; + mgl::Texture screenshot_texture; double double_click_timeout_seconds = 0.4; diff --git a/include/Utils.hpp b/include/Utils.hpp index e7bb3bc..f7d8538 100644 --- a/include/Utils.hpp +++ b/include/Utils.hpp @@ -24,6 +24,8 @@ namespace gsr { std::map<std::string, std::string> get_xdg_variables(); std::string get_videos_dir(); + std::string get_pictures_dir(); + // Returns 0 on success int create_directory_recursive(char *path); bool file_get_content(const char *filepath, std::string &file_content); diff --git a/include/gui/Button.hpp b/include/gui/Button.hpp index eb68e99..7070457 100644 --- a/include/gui/Button.hpp +++ b/include/gui/Button.hpp @@ -30,6 +30,7 @@ namespace gsr { std::function<void()> on_click; private: void scale_sprite_to_button_size(); + float get_button_height(); private: mgl::vec2f size; mgl::Color bg_color; diff --git a/include/gui/DropdownButton.hpp b/include/gui/DropdownButton.hpp index cbbcda2..486e811 100644 --- a/include/gui/DropdownButton.hpp +++ b/include/gui/DropdownButton.hpp @@ -20,6 +20,7 @@ namespace gsr { void add_item(const std::string &text, const std::string &id, const std::string &description = ""); void set_item_label(const std::string &id, const std::string &new_label); void set_item_icon(const std::string &id, mgl::Texture *texture); + void set_item_description(const std::string &id, const std::string &new_description); void set_description(std::string description_text); void set_activated(bool activated); diff --git a/include/gui/GlobalSettingsPage.hpp b/include/gui/GlobalSettingsPage.hpp index 580e943..d0a0336 100644 --- a/include/gui/GlobalSettingsPage.hpp +++ b/include/gui/GlobalSettingsPage.hpp @@ -25,6 +25,7 @@ namespace gsr { RECORD_START_STOP, RECORD_PAUSE_UNPAUSE, STREAM_START_STOP, + TAKE_SCREENSHOT, SHOW_HIDE }; @@ -44,6 +45,7 @@ namespace gsr { std::function<void(const char *reason)> on_click_exit_program_button; std::function<void(const char *hotkey_option)> on_keyboard_hotkey_changed; std::function<void(const char *hotkey_option)> on_joystick_hotkey_changed; + std::function<void()> on_page_closed; private: void load_hotkeys(); @@ -55,6 +57,7 @@ namespace gsr { std::unique_ptr<List> create_replay_hotkey_options(); std::unique_ptr<List> create_record_hotkey_options(); std::unique_ptr<List> create_stream_hotkey_options(); + std::unique_ptr<List> create_screenshot_hotkey_options(); std::unique_ptr<List> create_hotkey_control_buttons(); std::unique_ptr<Subsection> create_hotkey_subsection(ScrollablePage *parent_page); std::unique_ptr<Button> create_exit_program_button(); @@ -86,6 +89,7 @@ namespace gsr { Button *start_stop_recording_button_ptr = nullptr; Button *pause_unpause_recording_button_ptr = nullptr; Button *start_stop_streaming_button_ptr = nullptr; + Button *take_screenshot_button_ptr = nullptr; Button *show_hide_button_ptr = nullptr; ConfigHotkey configure_config_hotkey; diff --git a/include/gui/GsrPage.hpp b/include/gui/GsrPage.hpp index 1d298f4..76c28a6 100644 --- a/include/gui/GsrPage.hpp +++ b/include/gui/GsrPage.hpp @@ -9,7 +9,7 @@ namespace gsr { class GsrPage : public Page { public: - GsrPage(); + GsrPage(const char *top_text, const char *bottom_text); GsrPage(const GsrPage&) = delete; GsrPage& operator=(const GsrPage&) = delete; @@ -42,7 +42,8 @@ namespace gsr { float margin_bottom_scale = 0.0f; float margin_left_scale = 0.0f; float margin_right_scale = 0.0f; - mgl::Text label_text; + mgl::Text top_text; + mgl::Text bottom_text; std::vector<ButtonItem> buttons; }; }
\ No newline at end of file diff --git a/include/gui/ScreenshotSettingsPage.hpp b/include/gui/ScreenshotSettingsPage.hpp new file mode 100644 index 0000000..1cfbf00 --- /dev/null +++ b/include/gui/ScreenshotSettingsPage.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "StaticPage.hpp" +#include "List.hpp" +#include "ComboBox.hpp" +#include "Entry.hpp" +#include "CheckBox.hpp" +#include "../GsrInfo.hpp" +#include "../Config.hpp" + +namespace gsr { + class PageStack; + class GsrPage; + class ScrollablePage; + class Button; + + class ScreenshotSettingsPage : public StaticPage { + public: + ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack); + ScreenshotSettingsPage(const ScreenshotSettingsPage&) = delete; + ScreenshotSettingsPage& operator=(const ScreenshotSettingsPage&) = delete; + + void load(); + void save(); + void on_navigate_away_from_page() override; + private: + 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_image_width_entry(); + std::unique_ptr<Entry> create_image_height_entry(); + std::unique_ptr<List> create_image_resolution(); + std::unique_ptr<List> create_image_resolution_section(); + std::unique_ptr<CheckBox> create_restore_portal_session_checkbox(); + std::unique_ptr<List> create_restore_portal_session_section(); + std::unique_ptr<Widget> create_change_image_resolution_section(); + std::unique_ptr<Widget> create_capture_target_section(); + std::unique_ptr<List> create_image_quality_section(); + std::unique_ptr<Widget> create_record_cursor_section(); + std::unique_ptr<Widget> create_image_section(); + std::unique_ptr<List> create_save_directory(const char *label); + std::unique_ptr<ComboBox> create_image_format_box(); + std::unique_ptr<List> create_image_format_section(); + std::unique_ptr<Widget> create_file_info_section(); + std::unique_ptr<CheckBox> create_save_screenshot_in_game_folder(); + std::unique_ptr<Widget> create_general_section(); + std::unique_ptr<Widget> create_notifications_section(); + std::unique_ptr<Widget> create_settings(); + void add_widgets(); + + void save(RecordOptions &record_options); + private: + Config &config; + const GsrInfo *gsr_info = nullptr; + SupportedCaptureOptions capture_options; + + GsrPage *content_page_ptr = nullptr; + ScrollablePage *settings_scrollable_page_ptr = nullptr; + List *select_window_list_ptr = nullptr; + List *image_resolution_list_ptr = nullptr; + List *restore_portal_session_list_ptr = nullptr; + List *color_range_list_ptr = nullptr; + Widget *image_format_ptr = nullptr; + ComboBox *record_area_box_ptr = nullptr; + Entry *image_width_entry_ptr = nullptr; + Entry *image_height_entry_ptr = nullptr; + CheckBox *record_cursor_checkbox_ptr = nullptr; + CheckBox *restore_portal_session_checkbox_ptr = nullptr; + CheckBox *change_image_resolution_checkbox_ptr = nullptr; + ComboBox *image_quality_box_ptr = nullptr; + ComboBox *image_format_box_ptr = nullptr; + Button *save_directory_button_ptr = nullptr; + CheckBox *save_screenshot_in_game_folder_checkbox_ptr = nullptr; + CheckBox *show_screenshot_saved_notification_checkbox_ptr = nullptr; + + PageStack *page_stack = nullptr; + }; +}
\ No newline at end of file diff --git a/include/gui/SettingsPage.hpp b/include/gui/SettingsPage.hpp index ad5f05a..69ecc5a 100644 --- a/include/gui/SettingsPage.hpp +++ b/include/gui/SettingsPage.hpp @@ -52,7 +52,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(); + 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(); diff --git a/meson.build b/meson.build index fc2f4f4..9a0f140 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/ScreenshotSettingsPage.cpp', 'src/gui/GlobalSettingsPage.cpp', 'src/gui/GsrPage.cpp', 'src/gui/Subsection.cpp', diff --git a/src/Config.cpp b/src/Config.cpp index b9e4cb7..ebdc0ca 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -6,6 +6,7 @@ #include <limits.h> #include <inttypes.h> #include <libgen.h> +#include <assert.h> #include <mglpp/window/Keyboard.hpp> #define FORMAT_I32 "%" PRIi32 @@ -13,6 +14,37 @@ #define FORMAT_U32 "%" PRIu32 namespace gsr { + 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) + result.push_back(mgl::Keyboard::LControl); + if(modifiers & HOTKEY_MOD_LSHIFT) + result.push_back(mgl::Keyboard::LShift); + if(modifiers & HOTKEY_MOD_LALT) + result.push_back(mgl::Keyboard::LAlt); + if(modifiers & HOTKEY_MOD_LSUPER) + result.push_back(mgl::Keyboard::LSystem); + if(modifiers & HOTKEY_MOD_RCTRL) + result.push_back(mgl::Keyboard::RControl); + if(modifiers & HOTKEY_MOD_RSHIFT) + result.push_back(mgl::Keyboard::RShift); + if(modifiers & HOTKEY_MOD_RALT) + result.push_back(mgl::Keyboard::RAlt); + if(modifiers & HOTKEY_MOD_RSUPER) + result.push_back(mgl::Keyboard::RSystem); + return result; + } + + static void string_remove_all(std::string &str, const std::string &substr) { + size_t index = 0; + while(true) { + index = str.find(substr, index); + if(index == std::string::npos) + break; + str.erase(index, substr.size()); + } + } + bool ConfigHotkey::operator==(const ConfigHotkey &other) const { return key == other.key && modifiers == other.modifiers; } @@ -21,36 +53,83 @@ namespace gsr { return !operator==(other); } + std::string ConfigHotkey::to_string(bool spaces, bool modifier_side) const { + std::string result; + + const std::vector<mgl::Keyboard::Key> modifier_keys = hotkey_modifiers_to_mgl_keys(modifiers); + std::string modifier_str; + for(const mgl::Keyboard::Key modifier_key : modifier_keys) { + if(!result.empty()) { + if(spaces) + result += " + "; + else + result += "+"; + } + + modifier_str = mgl::Keyboard::key_to_string(modifier_key); + if(!modifier_side) { + string_remove_all(modifier_str, "Left"); + string_remove_all(modifier_str, "Right"); + } + result += modifier_str; + } + + if(key != 0) { + if(!result.empty()) { + if(spaces) + result += " + "; + else + result += "+"; + } + result += mgl::Keyboard::key_to_string((mgl::Keyboard::Key)key); + } + + return result; + } + Config::Config(const SupportedCaptureOptions &capture_options) { - const std::string default_save_directory = get_videos_dir(); + const std::string default_videos_save_directory = get_videos_dir(); + const std::string default_pictures_save_directory = get_pictures_dir(); + + set_hotkeys_to_default(); - streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT}; streaming_config.record_options.video_quality = "custom"; streaming_config.record_options.audio_tracks.push_back("default_output"); streaming_config.record_options.video_bitrate = 15000; - record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT}; - record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT}; - record_config.save_directory = default_save_directory; + record_config.save_directory = default_videos_save_directory; record_config.record_options.audio_tracks.push_back("default_output"); record_config.record_options.video_bitrate = 45000; - replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT}; - replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT}; replay_config.record_options.video_quality = "custom"; - replay_config.save_directory = default_save_directory; + replay_config.save_directory = default_videos_save_directory; replay_config.record_options.audio_tracks.push_back("default_output"); replay_config.record_options.video_bitrate = 45000; - main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT}; + screenshot_config.save_directory = default_pictures_save_directory; 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; + screenshot_config.record_area_option = capture_options.monitors.front().name; } } + void Config::set_hotkeys_to_default() { + streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT}; + + record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT}; + record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT}; + + replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT}; + replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT}; + + screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::F1, HOTKEY_MOD_LALT}; + + main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT}; + } + static std::optional<KeyValue> parse_key_value(std::string_view line) { const size_t space_index = line.find(' '); if(space_index == std::string_view::npos) @@ -156,7 +235,20 @@ namespace gsr { {"replay.container", &config.replay_config.container}, {"replay.time", &config.replay_config.replay_time}, {"replay.start_stop_hotkey", &config.replay_config.start_stop_hotkey}, - {"replay.save_hotkey", &config.replay_config.save_hotkey} + {"replay.save_hotkey", &config.replay_config.save_hotkey}, + + {"screenshot.record_area_option", &config.screenshot_config.record_area_option}, + {"screenshot.image_width", &config.screenshot_config.image_width}, + {"screenshot.image_height", &config.screenshot_config.image_height}, + {"screenshot.change_image_resolution", &config.screenshot_config.change_image_resolution}, + {"screenshot.image_quality", &config.screenshot_config.image_quality}, + {"screenshot.image_format", &config.screenshot_config.image_format}, + {"screenshot.record_cursor", &config.screenshot_config.record_cursor}, + {"screenshot.restore_portal_session", &config.screenshot_config.restore_portal_session}, + {"screenshot.save_screenshot_in_game_folder", &config.screenshot_config.save_screenshot_in_game_folder}, + {"screenshot.show_screenshot_saved_notifications", &config.screenshot_config.show_screenshot_saved_notifications}, + {"screenshot.save_directory", &config.screenshot_config.save_directory}, + {"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey} }; } @@ -183,6 +275,8 @@ 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 { + assert(false); } } return true; @@ -245,6 +339,8 @@ 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 { + assert(false); } return true; @@ -294,6 +390,8 @@ namespace gsr { for(const std::string &value : *array) { fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), value.c_str()); } + } else { + assert(false); } } diff --git a/src/GsrInfo.cpp b/src/GsrInfo.cpp index 033757c..c9373ae 100644 --- a/src/GsrInfo.cpp +++ b/src/GsrInfo.cpp @@ -157,11 +157,19 @@ namespace gsr { gsr_info->supported_video_codecs.vp9 = true; } + static void parse_image_formats_line(GsrInfo *gsr_info, std::string_view line) { + if(line == "jpeg") + gsr_info->supported_image_formats.jpeg = true; + else if(line == "png") + gsr_info->supported_image_formats.png = true; + } + enum class GsrInfoSection { UNKNOWN, SYSTEM_INFO, GPU_INFO, VIDEO_CODECS, + IMAGE_FORMATS, CAPTURE_OPTIONS }; @@ -194,6 +202,8 @@ namespace gsr { section = GsrInfoSection::GPU_INFO; else if(section_name == "video_codecs") section = GsrInfoSection::VIDEO_CODECS; + else if(section_name == "image_formats") + section = GsrInfoSection::IMAGE_FORMATS; else if(section_name == "capture_options") section = GsrInfoSection::CAPTURE_OPTIONS; else @@ -217,6 +227,10 @@ namespace gsr { parse_video_codecs_line(gsr_info, line); break; } + case GsrInfoSection::IMAGE_FORMATS: { + parse_image_formats_line(gsr_info, line); + break; + } case GsrInfoSection::CAPTURE_OPTIONS: { // Intentionally ignore, get capture options with get_supported_capture_options instead break; diff --git a/src/Hotplug.cpp b/src/Hotplug.cpp index 84ed5bb..0f5155c 100644 --- a/src/Hotplug.cpp +++ b/src/Hotplug.cpp @@ -44,9 +44,10 @@ namespace gsr { } void Hotplug::process_event_data(int fd, const HotplugEventCallback &callback) { - const int bytes_read = read(fd, event_data, sizeof(event_data)); + const int bytes_read = read(fd, event_data, sizeof(event_data) - 1); if(bytes_read <= 0) return; + event_data[bytes_read] = '\0'; /* Hotplug data ends with a newline and a null terminator */ int data_index = 0; diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 919117d..ef4e630 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -7,10 +7,10 @@ #include "../include/gui/DropdownButton.hpp" #include "../include/gui/CustomRendererWidget.hpp" #include "../include/gui/SettingsPage.hpp" +#include "../include/gui/ScreenshotSettingsPage.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" @@ -20,6 +20,7 @@ #include <limits.h> #include <fcntl.h> #include <poll.h> +#include <malloc.h> #include <stdexcept> #include <X11/Xlib.h> @@ -377,6 +378,13 @@ namespace gsr { fprintf(stderr, "pressed %s\n", id.c_str()); overlay->save_replay(); }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().screenshot_config.take_screenshot_hotkey), + "take_screenshot", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->take_screenshot(); + }); } static std::unique_ptr<GlobalHotkeysLinux> register_linux_hotkeys(Overlay *overlay, GlobalHotkeysLinux::GrabType grab_type) { @@ -470,6 +478,16 @@ namespace gsr { gpu_screen_recorder_process = -1; } + if(gpu_screen_recorder_screenshot_process > 0) { + kill(gpu_screen_recorder_screenshot_process, SIGINT); + int status; + if(waitpid(gpu_screen_recorder_screenshot_process, &status, 0) == -1) { + perror("waitpid failed"); + /* Ignore... */ + } + gpu_screen_recorder_screenshot_process = -1; + } + close_gpu_screen_recorder_output(); deinit_color_theme(); @@ -674,6 +692,7 @@ namespace gsr { update_notification_process_status(); update_gsr_replay_save(); update_gsr_process_status(); + update_gsr_screenshot_process_status(); replay_status_update_status(); if(!visible) @@ -939,6 +958,71 @@ namespace gsr { update_compositor_texture(*focused_monitor); + create_frontpage_ui_components(); + + // The focused application can be an xwayland application but the cursor can hover over a wayland application. + // This is even the case when hovering over the titlebar of the xwayland application. + const bool fake_cursor = is_wlroots ? x11_cursor_window != None : prevent_game_minimizing; + if(fake_cursor) + xi_setup(); + + //window->set_fullscreen(true); + if(gsr_info.system_info.display_server == DisplayServer::X11) + make_window_click_through(display, window->get_system_handle()); + + window->set_visible(true); + + make_window_sticky(display, window->get_system_handle()); + hide_window_from_taskbar(display, window->get_system_handle()); + + if(default_cursor) { + XFreeCursor(display, default_cursor); + default_cursor = 0; + } + default_cursor = XCreateFontCursor(display, XC_left_ptr); + XFlush(display); + + grab_mouse_and_keyboard(); + + // The real cursor doesn't move when all devices are grabbed, so we create our own cursor and diplay that while grabbed + xi_setup_fake_cursor(); + + // We want to grab all devices to prevent any other application below the UI from receiving events. + // Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events. + xi_grab_all_mouse_devices(); + + if(!is_wlroots) + window->set_fullscreen(true); + + visible = true; + + if(gpu_screen_recorder_process > 0) { + switch(recording_status) { + case RecordingStatus::NONE: + break; + case RecordingStatus::REPLAY: + update_ui_replay_started(); + break; + case RecordingStatus::RECORD: + update_ui_recording_started(); + break; + case RecordingStatus::STREAM: + update_ui_streaming_started(); + break; + } + } + + if(paused) + update_ui_recording_paused(); + + // Wayland compositors have retarded fullscreen animations that we cant disable in a proper way + // without messing up window position. + show_overlay_timeout_seconds = prevent_game_minimizing ? 0.0 : 0.15; + show_overlay_clock.restart(); + draw(); + } + + void Overlay::create_frontpage_ui_components() { bg_screenshot_overlay = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height)); top_bar_background = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height*0.06f).floor()); top_bar_text = mgl::Text("GPU Screen Recorder", get_theme().top_bar_font); @@ -976,8 +1060,8 @@ namespace gsr { auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Instant Replay", "Off", &get_theme().replay_button_texture, mgl::vec2f(button_width, button_height)); replay_dropdown_button_ptr = button.get(); - button->add_item("Turn on", "start", "Alt+Shift+F10"); - button->add_item("Save", "save", "Alt+F10"); + button->add_item("Turn on", "start", config.replay_config.start_stop_hotkey.to_string(false, false)); + button->add_item("Save", "save", config.replay_config.save_hotkey.to_string(false, false)); button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); button->set_item_icon("save", &get_theme().save_texture); @@ -1002,8 +1086,8 @@ namespace gsr { auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Record", "Not recording", &get_theme().record_button_texture, mgl::vec2f(button_width, button_height)); record_dropdown_button_ptr = button.get(); - button->add_item("Start", "start", "Alt+F9"); - button->add_item("Pause", "pause", "Alt+F7"); + button->add_item("Start", "start", config.record_config.start_stop_hotkey.to_string(false, false)); + button->add_item("Pause", "pause", config.record_config.pause_unpause_hotkey.to_string(false, false)); button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); button->set_item_icon("pause", &get_theme().pause_texture); @@ -1028,7 +1112,7 @@ namespace gsr { auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Livestream", "Not streaming", &get_theme().stream_button_texture, mgl::vec2f(button_width, button_height)); stream_dropdown_button_ptr = button.get(); - button->add_item("Start", "start", "Alt+F8"); + button->add_item("Start", "start", config.streaming_config.start_stop_hotkey.to_string(false, false)); button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); button->set_item_icon("settings", &get_theme().settings_small_texture); @@ -1053,7 +1137,7 @@ namespace gsr { { const mgl::vec2f main_buttons_size = main_buttons_list_ptr->get_size(); - const int settings_button_size = main_buttons_size.y * 0.2f; + const int settings_button_size = main_buttons_size.y * 0.33f; 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)); @@ -1099,11 +1183,45 @@ namespace gsr { global_hotkeys_js.reset(); }; + settings_page->on_page_closed = [this]() { + if(global_hotkeys) { + replay_dropdown_button_ptr->set_item_description("start", config.replay_config.start_stop_hotkey.to_string(false, false)); + replay_dropdown_button_ptr->set_item_description("save", config.replay_config.save_hotkey.to_string(false, false)); + + record_dropdown_button_ptr->set_item_description("start", config.record_config.start_stop_hotkey.to_string(false, false)); + record_dropdown_button_ptr->set_item_description("pause", config.record_config.pause_unpause_hotkey.to_string(false, false)); + + stream_dropdown_button_ptr->set_item_description("start", config.streaming_config.start_stop_hotkey.to_string(false, false)); + } else { + replay_dropdown_button_ptr->set_item_description("start", ""); + replay_dropdown_button_ptr->set_item_description("save", ""); + + record_dropdown_button_ptr->set_item_description("start", ""); + record_dropdown_button_ptr->set_item_description("pause", ""); + + stream_dropdown_button_ptr->set_item_description("start", ""); + } + }; + page_stack.push(std::move(settings_page)); }; front_page_ptr->add_widget(std::move(button)); } + { + const mgl::vec2f main_buttons_size = main_buttons_list_ptr->get_size(); + const int settings_button_size = main_buttons_size.y * 0.33f; + 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*2) + 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().screenshot_texture); + button->on_click = [&]() { + auto screenshot_settings_page = std::make_unique<ScreenshotSettingsPage>(&gsr_info, config, &page_stack); + page_stack.push(std::move(screenshot_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); @@ -1130,67 +1248,6 @@ namespace gsr { } return true; }; - - // The focused application can be an xwayland application but the cursor can hover over a wayland application. - // This is even the case when hovering over the titlebar of the xwayland application. - const bool fake_cursor = is_wlroots ? x11_cursor_window != None : prevent_game_minimizing; - if(fake_cursor) - xi_setup(); - - //window->set_fullscreen(true); - if(gsr_info.system_info.display_server == DisplayServer::X11) - make_window_click_through(display, window->get_system_handle()); - - window->set_visible(true); - - make_window_sticky(display, window->get_system_handle()); - hide_window_from_taskbar(display, window->get_system_handle()); - - if(default_cursor) { - XFreeCursor(display, default_cursor); - default_cursor = 0; - } - default_cursor = XCreateFontCursor(display, XC_left_ptr); - XFlush(display); - - grab_mouse_and_keyboard(); - - // The real cursor doesn't move when all devices are grabbed, so we create our own cursor and diplay that while grabbed - xi_setup_fake_cursor(); - - // We want to grab all devices to prevent any other application below the UI from receiving events. - // Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events. - xi_grab_all_mouse_devices(); - - if(!is_wlroots) - window->set_fullscreen(true); - - visible = true; - - if(gpu_screen_recorder_process > 0) { - switch(recording_status) { - case RecordingStatus::NONE: - break; - case RecordingStatus::REPLAY: - update_ui_replay_started(); - break; - case RecordingStatus::RECORD: - update_ui_recording_started(); - break; - case RecordingStatus::STREAM: - update_ui_streaming_started(); - break; - } - } - - if(paused) - update_ui_recording_paused(); - - // Wayland compositors have retarded fullscreen animations that we cant disable in a proper way - // without messing up window position. - show_overlay_timeout_seconds = prevent_game_minimizing ? 0.0 : 0.15; - show_overlay_clock.restart(); - draw(); } void Overlay::hide() { @@ -1269,6 +1326,7 @@ namespace gsr { } deinit_theme(); + malloc_trim(0); } void Overlay::toggle_show() { @@ -1316,12 +1374,17 @@ namespace gsr { on_press_save_replay(); } + void Overlay::take_screenshot() { + on_press_take_screenshot(); + } + static const char* notification_type_to_string(NotificationType notification_type) { switch(notification_type) { - case NotificationType::NONE: return nullptr; - case NotificationType::RECORD: return "record"; - case NotificationType::REPLAY: return "replay"; - case NotificationType::STREAM: return "stream"; + case NotificationType::NONE: return nullptr; + case NotificationType::RECORD: return "record"; + case NotificationType::REPLAY: return "replay"; + case NotificationType::STREAM: return "stream"; + case NotificationType::SCREENSHOT: return "screenshot"; } return nullptr; } @@ -1466,6 +1529,12 @@ namespace gsr { text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'"; break; } + case NotificationType::SCREENSHOT: { + if(!config.screenshot_config.show_screenshot_saved_notifications) + return; + text = "Saved screenshot to '" + focused_window_name + "/" + video_filename + "'"; + break; + } case NotificationType::NONE: case NotificationType::STREAM: break; @@ -1558,6 +1627,35 @@ namespace gsr { recording_status = RecordingStatus::NONE; } + void Overlay::update_gsr_screenshot_process_status() { + if(gpu_screen_recorder_screenshot_process <= 0) + return; + + int status; + if(waitpid(gpu_screen_recorder_screenshot_process, &status, WNOHANG) == 0) { + // Still running + return; + } + + int exit_code = -1; + if(WIFEXITED(status)) + exit_code = WEXITSTATUS(status); + + if(exit_code == 0) { + if(config.screenshot_config.save_screenshot_in_game_folder) { + save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT); + } else { + const std::string text = "Saved screenshot to '" + filepath_get_filename(screenshot_filepath.c_str()) + "'"; + show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT); + } + } else { + fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); + show_notification("Failed to take a screenshot. Verify if settings are correct", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT); + } + + gpu_screen_recorder_screenshot_process = -1; + } + void Overlay::replay_status_update_status() { if(replay_status_update_clock.get_elapsed_time_seconds() < replay_status_update_check_timeout_seconds) return; @@ -2175,6 +2273,52 @@ namespace gsr { show_notification("Streaming has started", 3.0, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM); } + void Overlay::on_press_take_screenshot() { + if(gpu_screen_recorder_screenshot_process > 0) { + fprintf(stderr, "Error: failed to take screenshot, another screenshot is currently being saved\n"); + return; + } + + if(!validate_capture_target(gsr_info, config.screenshot_config.record_area_option)) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid. Please change capture target in settings", config.screenshot_config.record_area_option.c_str()); + show_notification(err_msg, 3.0, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::SCREENSHOT); + return; + } + + // TODO: Validate input, fallback to valid values + const std::string output_file = config.screenshot_config.save_directory + "/Screenshot_" + get_date_str() + "." + config.screenshot_config.image_format; // TODO: Validate image format + + std::vector<const char*> args = { + "gpu-screen-recorder", "-w", config.screenshot_config.record_area_option.c_str(), + "-cursor", config.screenshot_config.record_cursor ? "yes" : "no", + "-v", "no", + "-q", config.screenshot_config.image_quality.c_str(), + "-o", output_file.c_str() + }; + + char region[64]; + region[0] = '\0'; + if(config.screenshot_config.change_image_resolution) { + snprintf(region, sizeof(region), "%dx%d", (int)config.screenshot_config.image_width, (int)config.screenshot_config.image_height); + args.push_back("-s"); + args.push_back(region); + } + + if(config.screenshot_config.restore_portal_session) { + args.push_back("-restore-portal-session"); + args.push_back("yes"); + } + + args.push_back(nullptr); + + screenshot_filepath = output_file; + gpu_screen_recorder_screenshot_process = exec_program(args.data(), nullptr); + if(gpu_screen_recorder_screenshot_process == -1) { + // TODO: Show notification failed to start + } + } + bool Overlay::update_compositor_texture(const Monitor &monitor) { window_texture_deinit(&window_texture); window_texture_sprite.set_texture(nullptr); diff --git a/src/Theme.cpp b/src/Theme.cpp index a6d1050..e28ff51 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -108,6 +108,9 @@ namespace gsr { if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str())) goto error; + if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str())) + goto error; + return true; error: diff --git a/src/Utils.cpp b/src/Utils.cpp index 6d45196..df6db2f 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -114,6 +114,14 @@ namespace gsr { return xdg_videos_dir; } + std::string get_pictures_dir() { + auto xdg_vars = get_xdg_variables(); + std::string xdg_videos_dir = xdg_vars["XDG_PICTURES_DIR"]; + if(xdg_videos_dir.empty()) + xdg_videos_dir = get_home_dir() + "/Pictures"; + return xdg_videos_dir; + } + int create_directory_recursive(char *path) { int path_len = strlen(path); char *p = path; diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp index ec01e26..d588374 100644 --- a/src/WindowUtils.cpp +++ b/src/WindowUtils.cpp @@ -156,7 +156,7 @@ namespace gsr { std::string result; for(int i = 0; i < size;) { // Some games such as the finals has utf8-bom between each character, wtf? - if(i + 3 < size && memcmp(str + i, "\xEF\xBB\xBF", 3) == 0) { + if(i + 3 <= size && memcmp(str + i, "\xEF\xBB\xBF", 3) == 0) { i += 3; continue; } @@ -246,10 +246,14 @@ namespace gsr { XClassHint class_hint = {nullptr, nullptr}; XGetClassHint(dpy, focused_window, &class_hint); - if(class_hint.res_class) { + if(class_hint.res_class) result = strip(class_hint.res_class); - return result; - } + + if(class_hint.res_name) + XFree(class_hint.res_name); + + if(class_hint.res_class) + XFree(class_hint.res_class); return result; } diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index 54d1854..476e679 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -15,8 +15,8 @@ namespace gsr { // 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; + //static const float padding_left_icon_scale = 0.25f; + static const float padding_right_icon_scale = 0.15f; 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) @@ -53,13 +53,21 @@ namespace gsr { 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); - 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()); + const int padding_left = padding_left_scale * get_theme().window_height; + if(text.get_string().empty()) // Center + sprite.set_position((background.get_position() + background.get_size() * 0.5f - sprite.get_size() * 0.5f).floor()); + else // Left + sprite.set_position((draw_pos + mgl::vec2f(padding_left, background.get_size().y * 0.5f - sprite.get_size().y * 0.5f)).floor()); window.draw(sprite); + + const int padding_icon_right = padding_right_icon_scale * get_button_height(); + text.set_position((sprite.get_position() + mgl::vec2f(sprite.get_size().x + padding_icon_right, sprite.get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor()); + window.draw(text); + } else { + text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor()); + window.draw(text); } if(mouse_inside) { @@ -72,18 +80,25 @@ namespace gsr { if(!visible) return {0.0f, 0.0f}; - const int padding_top = padding_top_scale * get_theme().window_height; - const int padding_bottom = padding_bottom_scale * get_theme().window_height; const int padding_left = padding_left_scale * get_theme().window_height; const int padding_right = padding_right_scale * get_theme().window_height; const mgl::vec2f text_bounds = text.get_bounds().size; - mgl::vec2f s = size; - if(s.x < 0.0001f) - s.x = padding_left + text_bounds.x + padding_right; - if(s.y < 0.0001f) - s.y = padding_top + text_bounds.y + padding_bottom; - return s; + mgl::vec2f widget_size = size; + + if(widget_size.y < 0.0001f) + widget_size.y = get_button_height(); + + if(widget_size.x < 0.0001f) { + widget_size.x = padding_left + text_bounds.x + padding_right; + if(sprite.get_texture() && sprite.get_texture()->is_valid()) { + scale_sprite_to_button_size(); + const int padding_icon_right = text_bounds.x > 0.001f ? padding_right_icon_scale * widget_size.y : 0.0f; + widget_size.x += sprite.get_size().x + padding_icon_right; + } + } + + return widget_size; } void Button::set_border_scale(float scale) { @@ -110,13 +125,23 @@ namespace gsr { 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 float widget_height = get_button_height(); + + const int padding_icon_top = padding_top_icon_scale * widget_height; + const int padding_icon_bottom = padding_bottom_icon_scale * widget_height; + + const float desired_height = widget_height - (padding_icon_top + padding_icon_bottom); + sprite.set_height((int)desired_height); + } + + float Button::get_button_height() { + const int padding_top = padding_top_scale * get_theme().window_height; + const int padding_bottom = padding_bottom_scale * get_theme().window_height; + + float widget_height = size.y; + if(widget_height < 0.0001f) + widget_height = padding_top + text.get_bounds().size.y + padding_bottom; - 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()); + return widget_height; } }
\ No newline at end of file diff --git a/src/gui/DropdownButton.cpp b/src/gui/DropdownButton.cpp index 81bc015..bdc4027 100644 --- a/src/gui/DropdownButton.cpp +++ b/src/gui/DropdownButton.cpp @@ -201,6 +201,15 @@ namespace gsr { } } + void DropdownButton::set_item_description(const std::string &id, const std::string &new_description) { + for(auto &item : items) { + if(item.id == id) { + item.description_text.set_string(new_description); + return; + } + } + } + void DropdownButton::set_description(std::string description_text) { description.set_string(std::move(description_text)); } diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp index d00ad49..a65cf8f 100644 --- a/src/gui/GlobalSettingsPage.cpp +++ b/src/gui/GlobalSettingsPage.cpp @@ -67,46 +67,6 @@ namespace gsr { return 0; } - 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) - result.push_back(mgl::Keyboard::LControl); - if(modifiers & HOTKEY_MOD_LSHIFT) - result.push_back(mgl::Keyboard::LShift); - if(modifiers & HOTKEY_MOD_LALT) - result.push_back(mgl::Keyboard::LAlt); - if(modifiers & HOTKEY_MOD_LSUPER) - result.push_back(mgl::Keyboard::LSystem); - if(modifiers & HOTKEY_MOD_RCTRL) - result.push_back(mgl::Keyboard::RControl); - if(modifiers & HOTKEY_MOD_RSHIFT) - result.push_back(mgl::Keyboard::RShift); - if(modifiers & HOTKEY_MOD_RALT) - result.push_back(mgl::Keyboard::RAlt); - if(modifiers & HOTKEY_MOD_RSUPER) - result.push_back(mgl::Keyboard::RSystem); - return result; - } - - static std::string config_hotkey_to_string(ConfigHotkey config_hotkey) { - std::string result; - - const std::vector<mgl::Keyboard::Key> modifier_keys = hotkey_modifiers_to_mgl_keys(config_hotkey.modifiers); - for(const mgl::Keyboard::Key modifier_key : modifier_keys) { - if(!result.empty()) - result += " + "; - result += mgl::Keyboard::key_to_string(modifier_key); - } - - if(config_hotkey.key != 0) { - if(!result.empty()) - result += " + "; - result += mgl::Keyboard::key_to_string((mgl::Keyboard::Key)config_hotkey.key); - } - - return result; - } - GlobalSettingsPage::GlobalSettingsPage(Overlay *overlay, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), overlay(overlay), @@ -114,7 +74,7 @@ namespace gsr { gsr_info(gsr_info), page_stack(page_stack) { - auto content_page = std::make_unique<GsrPage>(); + auto content_page = std::make_unique<GsrPage>("Global", "Settings"); 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") @@ -322,30 +282,41 @@ namespace gsr { return list; } + std::unique_ptr<List> GlobalSettingsPage::create_screenshot_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Take a screenshot:", get_color_theme().text_color)); + auto take_screenshot_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + take_screenshot_button_ptr = take_screenshot_button.get(); + list->add_widget(std::move(take_screenshot_button)); + + take_screenshot_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::TAKE_SCREENSHOT); + }; + + return list; + } + std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() { auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); - // auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - // clear_hotkeys_button->on_click = [this] { - // config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; - // config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; - // config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0}; - // config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; - // config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0}; - // config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0}; - // load_hotkeys(); - // overlay->rebind_all_keyboard_hotkeys(); - // }; - // list->add_widget(std::move(clear_hotkeys_button)); + auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + clear_hotkeys_button->on_click = [this] { + config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; + config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; + config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0}; + config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0}; + config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0}; + load_hotkeys(); + overlay->rebind_all_keyboard_hotkeys(); + }; + list->add_widget(std::move(clear_hotkeys_button)); auto reset_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Reset hotkeys to default", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); reset_hotkeys_button->on_click = [this] { - config.streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT}; - config.record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT}; - config.record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT}; - config.replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT}; - config.replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT}; - config.main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT}; + config.set_hotkeys_to_default(); load_hotkeys(); overlay->rebind_all_keyboard_hotkeys(); }; @@ -368,6 +339,7 @@ namespace gsr { list_ptr->add_widget(create_replay_hotkey_options()); list_ptr->add_widget(create_record_hotkey_options()); list_ptr->add_widget(create_stream_hotkey_options()); + list_ptr->add_widget(create_screenshot_hotkey_options()); list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Double-click the controller share button to save a replay", get_color_theme().text_color)); list_ptr->add_widget(create_hotkey_control_buttons()); return subsection; @@ -440,6 +412,8 @@ namespace gsr { void GlobalSettingsPage::on_navigate_away_from_page() { save(); + if(on_page_closed) + on_page_closed(); } void GlobalSettingsPage::load() { @@ -460,15 +434,17 @@ namespace gsr { } void GlobalSettingsPage::load_hotkeys() { - turn_replay_on_off_button_ptr->set_text(config_hotkey_to_string(config.replay_config.start_stop_hotkey)); - save_replay_button_ptr->set_text(config_hotkey_to_string(config.replay_config.save_hotkey)); + turn_replay_on_off_button_ptr->set_text(config.replay_config.start_stop_hotkey.to_string()); + save_replay_button_ptr->set_text(config.replay_config.save_hotkey.to_string()); - start_stop_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.start_stop_hotkey)); - pause_unpause_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.pause_unpause_hotkey)); + start_stop_recording_button_ptr->set_text(config.record_config.start_stop_hotkey.to_string()); + pause_unpause_recording_button_ptr->set_text(config.record_config.pause_unpause_hotkey.to_string()); - start_stop_streaming_button_ptr->set_text(config_hotkey_to_string(config.streaming_config.start_stop_hotkey)); + start_stop_streaming_button_ptr->set_text(config.streaming_config.start_stop_hotkey.to_string()); - show_hide_button_ptr->set_text(config_hotkey_to_string(config.main_config.show_hide_hotkey)); + take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string()); + + show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string()); } void GlobalSettingsPage::save() { @@ -496,10 +472,10 @@ namespace gsr { if(mgl::Keyboard::key_is_modifier(event.key.code)) { configure_config_hotkey.modifiers |= mgl_modifier_to_hotkey_modifier(event.key.code); - configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey)); + configure_hotkey_button->set_text(configure_config_hotkey.to_string()); } else if(configure_config_hotkey.modifiers != 0) { configure_config_hotkey.key = event.key.code; - configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey)); + configure_hotkey_button->set_text(configure_config_hotkey.to_string()); configure_hotkey_stop_and_save(); } @@ -512,7 +488,7 @@ namespace gsr { if(mgl::Keyboard::key_is_modifier(event.key.code)) { configure_config_hotkey.modifiers &= ~mgl_modifier_to_hotkey_modifier(event.key.code); - configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey)); + configure_hotkey_button->set_text(configure_config_hotkey.to_string()); } return false; @@ -535,6 +511,8 @@ namespace gsr { return pause_unpause_recording_button_ptr; case ConfigureHotkeyType::STREAM_START_STOP: return start_stop_streaming_button_ptr; + case ConfigureHotkeyType::TAKE_SCREENSHOT: + return take_screenshot_button_ptr; case ConfigureHotkeyType::SHOW_HIDE: return show_hide_button_ptr; } @@ -555,6 +533,8 @@ namespace gsr { return &config.record_config.pause_unpause_hotkey; case ConfigureHotkeyType::STREAM_START_STOP: return &config.streaming_config.start_stop_hotkey; + case ConfigureHotkeyType::TAKE_SCREENSHOT: + return &config.screenshot_config.take_screenshot_hotkey; case ConfigureHotkeyType::SHOW_HIDE: return &config.main_config.show_hide_hotkey; } @@ -568,6 +548,7 @@ namespace gsr { &config.record_config.start_stop_hotkey, &config.record_config.pause_unpause_hotkey, &config.streaming_config.start_stop_hotkey, + &config.screenshot_config.take_screenshot_hotkey, &config.main_config.show_hide_hotkey }; for(ConfigHotkey *config_hotkey : config_hotkeys) { @@ -604,6 +585,9 @@ namespace gsr { case ConfigureHotkeyType::STREAM_START_STOP: hotkey_configure_action_name = "Start/stop streaming"; break; + case ConfigureHotkeyType::TAKE_SCREENSHOT: + hotkey_configure_action_name = "Take a screenshot"; + break; case ConfigureHotkeyType::SHOW_HIDE: hotkey_configure_action_name = "Show/hide UI"; break; @@ -614,7 +598,7 @@ namespace gsr { Button *config_hotkey_button = configure_hotkey_get_button_by_active_type(); ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type(); if(config_hotkey_button && config_hotkey) - config_hotkey_button->set_text(config_hotkey_to_string(*config_hotkey)); + config_hotkey_button->set_text(config_hotkey->to_string()); configure_config_hotkey = {0, 0}; configure_hotkey_type = ConfigureHotkeyType::NONE; @@ -634,9 +618,9 @@ namespace gsr { }); if(hotkey_used_by_another_action) { - const std::string error_msg = "The hotkey \"" + config_hotkey_to_string(configure_config_hotkey) + " is already used for something else"; + const std::string error_msg = "The hotkey \"" + configure_config_hotkey.to_string() + " is already used for something else"; overlay->show_notification(error_msg.c_str(), 3.0, mgl::Color(255, 0, 0, 255), mgl::Color(255, 0, 0, 255), NotificationType::NONE); - config_hotkey_button->set_text(config_hotkey_to_string(*config_hotkey)); + config_hotkey_button->set_text(config_hotkey->to_string()); configure_config_hotkey = {0, 0}; return; } diff --git a/src/gui/GsrPage.cpp b/src/gui/GsrPage.cpp index c5fa263..663187c 100644 --- a/src/gui/GsrPage.cpp +++ b/src/gui/GsrPage.cpp @@ -8,8 +8,9 @@ namespace gsr { static const float button_spacing_scale = 0.015f; - GsrPage::GsrPage() : - label_text("Settings", get_theme().title_font) + GsrPage::GsrPage(const char *top_text, const char *bottom_text) : + top_text(top_text, get_theme().title_font), + bottom_text(bottom_text, get_theme().title_font) { const float margin = 0.02f; set_margins(margin, margin, margin, margin); @@ -80,13 +81,17 @@ namespace gsr { window.draw(background); const int text_margin = background.get_size().y * 0.085; - label_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - label_text.get_bounds().size.x * 0.5f, text_margin)).floor()); - window.draw(label_text); + + top_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - top_text.get_bounds().size.x * 0.5f, text_margin)).floor()); + window.draw(top_text); mgl::Sprite icon(&get_theme().settings_texture); icon.set_height((int)(background.get_size().y * 0.5f)); icon.set_position((background.get_position() + background.get_size() * 0.5f - icon.get_size() * 0.5f).floor()); window.draw(icon); + + bottom_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - bottom_text.get_bounds().size.x * 0.5f, background.get_size().y - bottom_text.get_bounds().size.y - text_margin)).floor()); + window.draw(bottom_text); } void GsrPage::draw_buttons(mgl::Window &window, mgl::vec2f body_pos, mgl::vec2f body_size) { diff --git a/src/gui/ScreenshotSettingsPage.cpp b/src/gui/ScreenshotSettingsPage.cpp new file mode 100644 index 0000000..457ff89 --- /dev/null +++ b/src/gui/ScreenshotSettingsPage.cpp @@ -0,0 +1,339 @@ +#include "../../include/gui/ScreenshotSettingsPage.hpp" +#include "../../include/gui/GsrPage.hpp" +#include "../../include/gui/PageStack.hpp" +#include "../../include/Theme.hpp" +#include "../../include/GsrInfo.hpp" +#include "../../include/Utils.hpp" +#include "../../include/gui/List.hpp" +#include "../../include/gui/ScrollablePage.hpp" +#include "../../include/gui/Label.hpp" +#include "../../include/gui/Subsection.hpp" +#include "../../include/gui/FileChooser.hpp" + +namespace gsr { + ScreenshotSettingsPage::ScreenshotSettingsPage(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) + { + capture_options = get_supported_capture_options(*gsr_info); + + auto content_page = std::make_unique<GsrPage>("Screenshot", "Settings"); + 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<ComboBox> ScreenshotSettingsPage::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(capture_options.window) + // record_area_box->add_item("Window", "window"); + 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(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> ScreenshotSettingsPage::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()); + return record_area_list; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_select_window() { + auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL); + select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color)); + select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120))); + select_window_list_ptr = select_window_list.get(); + return select_window_list; + } + + std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_width_entry() { + auto image_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3); + image_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); + image_width_entry_ptr = image_width_entry.get(); + return image_width_entry; + } + + std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_height_entry() { + auto image_height_entry = std::make_unique<Entry>(&get_theme().body_font, "1080", get_theme().body_font.get_character_size() * 3); + image_height_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); + image_height_entry_ptr = image_height_entry.get(); + return image_height_entry; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution() { + auto area_size_params_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + area_size_params_list->add_widget(create_image_width_entry()); + area_size_params_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "x", get_color_theme().text_color)); + area_size_params_list->add_widget(create_image_height_entry()); + return area_size_params_list; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution_section() { + auto image_resolution_list = std::make_unique<List>(List::Orientation::VERTICAL); + image_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image resolution limit:", get_color_theme().text_color)); + image_resolution_list->add_widget(create_image_resolution()); + image_resolution_list_ptr = image_resolution_list.get(); + return image_resolution_list; + } + + std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_restore_portal_session_checkbox() { + auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restore portal session"); + restore_portal_session_checkbox->set_checked(true); + restore_portal_session_checkbox_ptr = restore_portal_session_checkbox.get(); + return restore_portal_session_checkbox; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_restore_portal_session_section() { + auto restore_portal_session_list = std::make_unique<List>(List::Orientation::VERTICAL); + restore_portal_session_list->add_widget(std::make_unique<Label>(&get_theme().body_font, " ", get_color_theme().text_color)); + restore_portal_session_list->add_widget(create_restore_portal_session_checkbox()); + restore_portal_session_list_ptr = restore_portal_session_list.get(); + return restore_portal_session_list; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_change_image_resolution_section() { + auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Change image resolution"); + change_image_resolution_checkbox_ptr = checkbox.get(); + return checkbox; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_capture_target_section() { + 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()); + capture_target_list->add_widget(create_select_window()); + capture_target_list->add_widget(create_image_resolution_section()); + capture_target_list->add_widget(create_restore_portal_session_section()); + + ll->add_widget(std::move(capture_target_list)); + ll->add_widget(create_change_image_resolution_section()); + return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_section() { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image quality:", get_color_theme().text_color)); + + auto image_quality_box = std::make_unique<ComboBox>(&get_theme().body_font); + image_quality_box->add_item("Medium", "medium"); + image_quality_box->add_item("High", "high"); + image_quality_box->add_item("Very high (Recommended)", "very_high"); + image_quality_box->add_item("Ultra", "ultra"); + image_quality_box->set_selected_item("very_high"); + + image_quality_box_ptr = image_quality_box.get(); + list->add_widget(std::move(image_quality_box)); + + return list; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_cursor_section() { + auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor"); + record_cursor_checkbox->set_checked(true); + record_cursor_checkbox_ptr = record_cursor_checkbox.get(); + return record_cursor_checkbox; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_image_section() { + auto image_section_list = std::make_unique<List>(List::Orientation::VERTICAL); + image_section_list->add_widget(create_image_quality_section()); + image_section_list->add_widget(create_record_cursor_section()); + return std::make_unique<Subsection>("Image", std::move(image_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_save_directory(const char *label) { + auto save_directory_list = std::make_unique<List>(List::Orientation::VERTICAL); + save_directory_list->add_widget(std::make_unique<Label>(&get_theme().body_font, label, get_color_theme().text_color)); + auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_pictures_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_directory_button_ptr = save_directory_button.get(); + save_directory_button->on_click = [this]() { + auto select_directory_page = std::make_unique<GsrPage>("File", "Settings"); + select_directory_page->add_button("Save", "save", get_color_theme().tint_color); + select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color); + + auto file_chooser = std::make_unique<FileChooser>(save_directory_button_ptr->get_text().c_str(), select_directory_page->get_inner_size()); + FileChooser *file_chooser_ptr = file_chooser.get(); + select_directory_page->add_widget(std::move(file_chooser)); + + select_directory_page->on_click = [this, file_chooser_ptr](const std::string &id) { + if(id == "save") { + save_directory_button_ptr->set_text(file_chooser_ptr->get_current_directory()); + page_stack->pop(); + } else if(id == "cancel") { + page_stack->pop(); + } + }; + + page_stack->push(std::move(select_directory_page)); + }; + save_directory_list->add_widget(std::move(save_directory_button)); + return save_directory_list; + } + + std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_image_format_box() { + auto box = std::make_unique<ComboBox>(&get_theme().body_font); + box->add_item("jpg", "jpg"); + box->add_item("png", "png"); + image_format_box_ptr = box.get(); + return box; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_format_section() { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image format:", get_color_theme().text_color)); + list->add_widget(create_image_format_box()); + return list; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_file_info_section() { + auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL); + file_info_data_list->add_widget(create_save_directory("Directory to save the screenshot:")); + file_info_data_list->add_widget(create_image_format_section()); + return std::make_unique<Subsection>("File info", std::move(file_info_data_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_in_game_folder() { + char text[256]; + snprintf(text, sizeof(text), "Save screenshot in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)"); + auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text); + save_screenshot_in_game_folder_checkbox_ptr = checkbox.get(); + return checkbox; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_general_section() { + return std::make_unique<Subsection>("General", create_save_screenshot_in_game_folder(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_notifications_section() { + auto show_screenshot_saved_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show screenshot saved notification"); + show_screenshot_saved_notification_checkbox->set_checked(true); + show_screenshot_saved_notification_checkbox_ptr = show_screenshot_saved_notification_checkbox.get(); + return std::make_unique<Subsection>("Notifications", std::move(show_screenshot_saved_notification_checkbox), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_settings() { + auto page_list = std::make_unique<List>(List::Orientation::VERTICAL); + page_list->set_spacing(0.018f); + auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size() - mgl::vec2f(0.0f, page_list->get_size().y + 0.018f * get_theme().window_height)); + settings_scrollable_page_ptr = scrollable_page.get(); + page_list->add_widget(std::move(scrollable_page)); + + auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL); + settings_list->set_spacing(0.018f); + settings_list->add_widget(create_capture_target_section()); + settings_list->add_widget(create_image_section()); + settings_list->add_widget(create_file_info_section()); + settings_list->add_widget(create_general_section()); + settings_list->add_widget(create_notifications_section()); + settings_scrollable_page_ptr->add_widget(std::move(settings_list)); + return page_list; + } + + 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; + const bool window_selected = id == "window"; + const bool portal_selected = id == "portal"; + select_window_list_ptr->set_visible(window_selected); + image_resolution_list_ptr->set_visible(change_image_resolution_checkbox_ptr->is_checked()); + restore_portal_session_list_ptr->set_visible(portal_selected); + return true; + }; + + change_image_resolution_checkbox_ptr->on_changed = [this](bool checked) { + image_resolution_list_ptr->set_visible(checked); + }; + + 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(capture_options.window) + record_area_box_ptr->set_selected_item("window"); + else + record_area_box_ptr->on_selection_changed("", ""); + } + + void ScreenshotSettingsPage::on_navigate_away_from_page() { + save(); + } + + void ScreenshotSettingsPage::load() { + record_area_box_ptr->set_selected_item(config.screenshot_config.record_area_option); + change_image_resolution_checkbox_ptr->set_checked(config.screenshot_config.change_image_resolution); + image_quality_box_ptr->set_selected_item(config.screenshot_config.image_quality); + image_format_box_ptr->set_selected_item(config.screenshot_config.image_format); + record_cursor_checkbox_ptr->set_checked(config.screenshot_config.record_cursor); + restore_portal_session_checkbox_ptr->set_checked(config.screenshot_config.restore_portal_session); + save_directory_button_ptr->set_text(config.screenshot_config.save_directory); + save_screenshot_in_game_folder_checkbox_ptr->set_checked(config.screenshot_config.save_screenshot_in_game_folder); + show_screenshot_saved_notification_checkbox_ptr->set_checked(config.screenshot_config.show_screenshot_saved_notifications); + + if(config.screenshot_config.image_width == 0) + config.screenshot_config.image_width = 1920; + + if(config.screenshot_config.image_height == 0) + config.screenshot_config.image_height = 1080; + + if(config.screenshot_config.image_width < 32) + config.screenshot_config.image_width = 32; + image_width_entry_ptr->set_text(std::to_string(config.screenshot_config.image_width)); + + if(config.screenshot_config.image_height < 32) + config.screenshot_config.image_height = 32; + image_height_entry_ptr->set_text(std::to_string(config.screenshot_config.image_height)); + } + + void ScreenshotSettingsPage::save() { + config.screenshot_config.record_area_option = record_area_box_ptr->get_selected_id(); + config.screenshot_config.image_width = atoi(image_width_entry_ptr->get_text().c_str()); + config.screenshot_config.image_height = atoi(image_height_entry_ptr->get_text().c_str()); + config.screenshot_config.change_image_resolution = change_image_resolution_checkbox_ptr->is_checked(); + config.screenshot_config.image_quality = image_quality_box_ptr->get_selected_id(); + config.screenshot_config.image_format = image_format_box_ptr->get_selected_id(); + config.screenshot_config.record_cursor = record_cursor_checkbox_ptr->is_checked(); + config.screenshot_config.restore_portal_session = restore_portal_session_checkbox_ptr->is_checked(); + config.screenshot_config.save_directory = save_directory_button_ptr->get_text(); + config.screenshot_config.save_screenshot_in_game_folder = save_screenshot_in_game_folder_checkbox_ptr->is_checked(); + config.screenshot_config.show_screenshot_saved_notifications = show_screenshot_saved_notification_checkbox_ptr->is_checked(); + + if(config.screenshot_config.image_width == 0) + config.screenshot_config.image_width = 1920; + + if(config.screenshot_config.image_height == 0) + config.screenshot_config.image_height = 1080; + + if(config.screenshot_config.image_width < 32) { + config.screenshot_config.image_width = 32; + image_width_entry_ptr->set_text("32"); + } + + if(config.screenshot_config.image_height < 32) { + config.screenshot_config.image_height = 32; + image_height_entry_ptr->set_text("32"); + } + + save_config(config); + } +}
\ No newline at end of file diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 4d1109a..9394104 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -8,11 +8,6 @@ #include "../../include/GsrInfo.hpp" #include "../../include/Utils.hpp" -#include <mglpp/graphics/Rectangle.hpp> -#include <mglpp/graphics/Sprite.hpp> -#include <mglpp/graphics/Text.hpp> -#include <mglpp/window/Window.hpp> - #include <string.h> namespace gsr { @@ -22,6 +17,15 @@ namespace gsr { APPLICATION_CUSTOM }; + static const char* settings_page_type_to_title_text(SettingsPage::Type type) { + switch(type) { + case SettingsPage::Type::REPLAY: return "Instant Replay"; + case SettingsPage::Type::RECORD: return "Record"; + case SettingsPage::Type::STREAM: return "Livestream"; + } + return ""; + } + 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), @@ -33,7 +37,7 @@ namespace gsr { application_audio = get_application_audio(); capture_options = get_supported_capture_options(*gsr_info); - auto content_page = std::make_unique<GsrPage>(); + auto content_page = std::make_unique<GsrPage>(settings_page_type_to_title_text(type), "Settings"); 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") @@ -171,7 +175,7 @@ namespace gsr { return checkbox; } - std::unique_ptr<Widget> SettingsPage::create_capture_target() { + std::unique_ptr<Widget> SettingsPage::create_capture_target_section() { auto ll = std::make_unique<List>(List::Orientation::VERTICAL); auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); @@ -512,7 +516,7 @@ 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()); + settings_list->add_widget(create_capture_target_section()); settings_list->add_widget(create_audio_section()); settings_list->add_widget(create_video_section()); settings_list_ptr = settings_list.get(); @@ -589,7 +593,7 @@ namespace gsr { auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_videos_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); save_directory_button_ptr = save_directory_button.get(); save_directory_button->on_click = [this]() { - auto select_directory_page = std::make_unique<GsrPage>(); + auto select_directory_page = std::make_unique<GsrPage>("File", "Settings"); select_directory_page->add_button("Save", "save", get_color_theme().tint_color); select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color); @@ -801,9 +805,7 @@ namespace gsr { file_info_list->add_widget(create_estimated_record_file_size()); 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_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))); + settings_list_ptr->add_widget(std::make_unique<Subsection>("General", create_save_recording_in_game_folder(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); diff --git a/src/main.cpp b/src/main.cpp index ffaf596..169721e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include <signal.h> #include <string.h> #include <limits.h> +#include <malloc.h> #include <mglpp/mglpp.hpp> #include <mglpp/system/Clock.hpp> @@ -72,6 +73,11 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) { fprintf(stderr, "rpc command executed: %s\n", name.c_str()); overlay->save_replay(); }); + + rpc->add_handler("take-screenshot", [overlay](const std::string &name) { + fprintf(stderr, "rpc command executed: %s\n", name.c_str()); + overlay->take_screenshot(); + }); } static bool is_gsr_ui_virtual_keyboard_running() { @@ -150,6 +156,7 @@ enum class LaunchAction { int main(int argc, char **argv) { setlocale(LC_ALL, "C"); // Sigh... stupid C + mallopt(M_MMAP_THRESHOLD, 65536); if(geteuid() == 0) { fprintf(stderr, "Error: don't run gsr-ui as the root user\n"); diff --git a/tools/gsr-global-hotkeys/hotplug.c b/tools/gsr-global-hotkeys/hotplug.c index 5ea2978..2e8ca9f 100644 --- a/tools/gsr-global-hotkeys/hotplug.c +++ b/tools/gsr-global-hotkeys/hotplug.c @@ -65,9 +65,10 @@ static void hotplug_event_parse_netlink_data(hotplug_event *self, const char *li /* Netlink uevent structure is documented here: https://web.archive.org/web/20160127215232/https://www.kernel.org/doc/pending/hotplug.txt */ void hotplug_event_process_event_data(hotplug_event *self, int fd, hotplug_device_added_callback callback, void *userdata) { - const int bytes_read = read(fd, self->event_data, sizeof(self->event_data)); + const int bytes_read = read(fd, self->event_data, sizeof(self->event_data) - 1); if(bytes_read <= 0) return; + self->event_data[bytes_read] = '\0'; /* Hotplug data ends with a newline and a null terminator */ int data_index = 0; diff --git a/tools/gsr-ui-cli/main.c b/tools/gsr-ui-cli/main.c index bcb5c81..9f1eb03 100644 --- a/tools/gsr-ui-cli/main.c +++ b/tools/gsr-ui-cli/main.c @@ -50,6 +50,7 @@ static void usage(void) { printf(" toggle-stream Start/stop streaming.\n"); printf(" toggle-replay Start/stop replay.\n"); printf(" replay-save Save replay.\n"); + printf(" take-screenshot Take a screenshot.\n"); printf("\n"); printf("EXAMPLES:\n"); printf(" gsr-ui-cli toggle-show\n"); @@ -65,6 +66,7 @@ static bool is_valid_command(const char *command) { "toggle-stream", "toggle-replay", "replay-save", + "take-screenshot", NULL }; |