diff options
Diffstat (limited to 'src/Overlay.cpp')
-rw-r--r-- | src/Overlay.cpp | 288 |
1 files changed, 216 insertions, 72 deletions
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); |