aboutsummaryrefslogtreecommitdiff
path: root/src/Overlay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Overlay.cpp')
-rw-r--r--src/Overlay.cpp288
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);