aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Config.cpp118
-rw-r--r--src/GsrInfo.cpp14
-rw-r--r--src/Hotplug.cpp3
-rw-r--r--src/Overlay.cpp288
-rw-r--r--src/Theme.cpp3
-rw-r--r--src/Utils.cpp8
-rw-r--r--src/WindowUtils.cpp12
-rw-r--r--src/gui/Button.cpp67
-rw-r--r--src/gui/DropdownButton.cpp9
-rw-r--r--src/gui/GlobalSettingsPage.cpp126
-rw-r--r--src/gui/GsrPage.cpp13
-rw-r--r--src/gui/ScreenshotSettingsPage.cpp339
-rw-r--r--src/gui/SettingsPage.cpp26
-rw-r--r--src/main.cpp7
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");