diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Config.cpp | 118 | ||||
-rw-r--r-- | src/GsrInfo.cpp | 14 | ||||
-rw-r--r-- | src/Hotplug.cpp | 3 | ||||
-rw-r--r-- | src/Overlay.cpp | 288 | ||||
-rw-r--r-- | src/Theme.cpp | 3 | ||||
-rw-r--r-- | src/Utils.cpp | 8 | ||||
-rw-r--r-- | src/WindowUtils.cpp | 12 | ||||
-rw-r--r-- | src/gui/Button.cpp | 67 | ||||
-rw-r--r-- | src/gui/DropdownButton.cpp | 9 | ||||
-rw-r--r-- | src/gui/GlobalSettingsPage.cpp | 126 | ||||
-rw-r--r-- | src/gui/GsrPage.cpp | 13 | ||||
-rw-r--r-- | src/gui/ScreenshotSettingsPage.cpp | 339 | ||||
-rw-r--r-- | src/gui/SettingsPage.cpp | 26 | ||||
-rw-r--r-- | src/main.cpp | 7 |
14 files changed, 838 insertions, 195 deletions
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"); |