Add option to take a screenshot (default hotkey: alt+f1)

This commit is contained in:
dec05eba
2025-02-22 13:31:51 +01:00
parent 8003c209fe
commit 189736c1a9
31 changed files with 1102 additions and 328 deletions

9
TODO
View File

@@ -78,8 +78,6 @@ Dont allow autostart of replay if capture option is window recording (when windo
Use global shortcuts desktop portal protocol on wayland when available. Use global shortcuts desktop portal protocol on wayland when available.
When support for window capture is enabled on x11 then make sure to not save the window except temporary while the program is open.
Support CJK. Support CJK.
Move ui hover code from ::draw to ::on_event, to properly handle widget event stack. Move ui hover code from ::draw to ::on_event, to properly handle widget event stack.
@@ -121,3 +119,10 @@ Add recording timer to see duration of recording/streaming.
Make folder with window name work when using gamescope. Gamescope runs x11 itself so to get the window name inside that we have to connect to the gamescope X11 server (DISPLAY=:1 on x11 and DISPLAY=:2 on wayland, but not always). Make folder with window name work when using gamescope. Gamescope runs x11 itself so to get the window name inside that we have to connect to the gamescope X11 server (DISPLAY=:1 on x11 and DISPLAY=:2 on wayland, but not always).
When clicking on current directory in file manager show a dropdown menu where you can select common directories (HOME, Videos, Downloads and mounted drives) for quick navigation. Maybe even button to search. When clicking on current directory in file manager show a dropdown menu where you can select common directories (HOME, Videos, Downloads and mounted drives) for quick navigation. Maybe even button to search.
Maybe change gsr-ui startup retry time in the systemd service, from 5 seconds to 2 seconds.
Add support for window capture. This should not prompt for window selection directly but instead prompt for window selection when recording starts and hide the ui first.
For screenshots window capture should exist but "follow focused" option should not exist.
Improve audio design. It should have a button to add/remove audio tracks and button to add audio into each audio track separately and "record audio from all applications except the selected ones" for each audio track. Then also remove the "merge audio tracks" option.

BIN
images/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -17,6 +17,8 @@ namespace gsr {
bool operator==(const ConfigHotkey &other) const; bool operator==(const ConfigHotkey &other) const;
bool operator!=(const ConfigHotkey &other) const; bool operator!=(const ConfigHotkey &other) const;
std::string to_string(bool spaces = true, bool modifier_side = true) const;
}; };
struct RecordOptions { struct RecordOptions {
@@ -101,15 +103,34 @@ namespace gsr {
ConfigHotkey save_hotkey; ConfigHotkey save_hotkey;
}; };
struct ScreenshotConfig {
std::string record_area_option = "screen";
int32_t image_width = 0;
int32_t image_height = 0;
bool change_image_resolution = false;
std::string image_quality = "very_high";
std::string image_format = "jpg";
bool record_cursor = true;
bool restore_portal_session = true;
bool save_screenshot_in_game_folder = false;
bool show_screenshot_saved_notifications = true;
std::string save_directory;
ConfigHotkey take_screenshot_hotkey;
};
struct Config { struct Config {
Config(const SupportedCaptureOptions &capture_options); Config(const SupportedCaptureOptions &capture_options);
bool operator==(const Config &other); bool operator==(const Config &other);
bool operator!=(const Config &other); bool operator!=(const Config &other);
void set_hotkeys_to_default();
MainConfig main_config; MainConfig main_config;
StreamingConfig streaming_config; StreamingConfig streaming_config;
RecordConfig record_config; RecordConfig record_config;
ReplayConfig replay_config; ReplayConfig replay_config;
ScreenshotConfig screenshot_config;
}; };
std::optional<Config> read_config(const SupportedCaptureOptions &capture_options); std::optional<Config> read_config(const SupportedCaptureOptions &capture_options);

View File

@@ -20,6 +20,11 @@ namespace gsr {
bool vp9 = false; bool vp9 = false;
}; };
struct SupportedImageFormats {
bool jpeg = false;
bool png = false;
};
struct GsrMonitor { struct GsrMonitor {
std::string name; std::string name;
mgl::vec2i size; mgl::vec2i size;
@@ -75,6 +80,7 @@ namespace gsr {
SystemInfo system_info; SystemInfo system_info;
GpuInfo gpu_info; GpuInfo gpu_info;
SupportedVideoCodecs supported_video_codecs; SupportedVideoCodecs supported_video_codecs;
SupportedImageFormats supported_image_formats;
}; };
enum class GsrInfoExitStatus { enum class GsrInfoExitStatus {

View File

@@ -35,7 +35,8 @@ namespace gsr {
NONE, NONE,
RECORD, RECORD,
REPLAY, REPLAY,
STREAM STREAM,
SCREENSHOT
}; };
class Overlay { class Overlay {
@@ -57,6 +58,7 @@ namespace gsr {
void toggle_stream(); void toggle_stream();
void toggle_replay(); void toggle_replay();
void save_replay(); void save_replay();
void take_screenshot();
void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type); void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type);
bool is_open() const; bool is_open() const;
bool should_exit(std::string &reason) const; bool should_exit(std::string &reason) const;
@@ -70,6 +72,7 @@ namespace gsr {
void handle_keyboard_mapping_event(); void handle_keyboard_mapping_event();
void on_event(mgl::Event &event); void on_event(mgl::Event &event);
void create_frontpage_ui_components();
void xi_setup(); void xi_setup();
void handle_xi_events(); void handle_xi_events();
void process_key_bindings(mgl::Event &event); void process_key_bindings(mgl::Event &event);
@@ -84,6 +87,7 @@ namespace gsr {
void on_replay_saved(const char *replay_saved_filepath); void on_replay_saved(const char *replay_saved_filepath);
void update_gsr_replay_save(); void update_gsr_replay_save();
void update_gsr_process_status(); void update_gsr_process_status();
void update_gsr_screenshot_process_status();
void replay_status_update_status(); void replay_status_update_status();
void update_focused_fullscreen_status(); void update_focused_fullscreen_status();
@@ -107,6 +111,7 @@ namespace gsr {
void on_press_start_replay(bool disable_notification); void on_press_start_replay(bool disable_notification);
void on_press_start_record(); void on_press_start_record();
void on_press_start_stream(); void on_press_start_stream();
void on_press_take_screenshot();
bool update_compositor_texture(const Monitor &monitor); bool update_compositor_texture(const Monitor &monitor);
void force_window_on_top(); void force_window_on_top();
@@ -149,6 +154,7 @@ namespace gsr {
pid_t notification_process = -1; pid_t notification_process = -1;
int gpu_screen_recorder_process_output_fd = -1; int gpu_screen_recorder_process_output_fd = -1;
FILE *gpu_screen_recorder_process_output_file = nullptr; FILE *gpu_screen_recorder_process_output_file = nullptr;
pid_t gpu_screen_recorder_screenshot_process = -1;
DropdownButton *replay_dropdown_button_ptr = nullptr; DropdownButton *replay_dropdown_button_ptr = nullptr;
DropdownButton *record_dropdown_button_ptr = nullptr; DropdownButton *record_dropdown_button_ptr = nullptr;
@@ -165,6 +171,7 @@ namespace gsr {
bool focused_window_is_fullscreen = false; bool focused_window_is_fullscreen = false;
std::string record_filepath; std::string record_filepath;
std::string screenshot_filepath;
Display *xi_display = nullptr; Display *xi_display = nullptr;
int xi_opcode = 0; int xi_opcode = 0;

View File

@@ -41,6 +41,7 @@ namespace gsr {
mgl::Texture stop_texture; mgl::Texture stop_texture;
mgl::Texture pause_texture; mgl::Texture pause_texture;
mgl::Texture save_texture; mgl::Texture save_texture;
mgl::Texture screenshot_texture;
double double_click_timeout_seconds = 0.4; double double_click_timeout_seconds = 0.4;

View File

@@ -24,6 +24,8 @@ namespace gsr {
std::map<std::string, std::string> get_xdg_variables(); std::map<std::string, std::string> get_xdg_variables();
std::string get_videos_dir(); std::string get_videos_dir();
std::string get_pictures_dir();
// Returns 0 on success // Returns 0 on success
int create_directory_recursive(char *path); int create_directory_recursive(char *path);
bool file_get_content(const char *filepath, std::string &file_content); bool file_get_content(const char *filepath, std::string &file_content);

View File

@@ -30,6 +30,7 @@ namespace gsr {
std::function<void()> on_click; std::function<void()> on_click;
private: private:
void scale_sprite_to_button_size(); void scale_sprite_to_button_size();
float get_button_height();
private: private:
mgl::vec2f size; mgl::vec2f size;
mgl::Color bg_color; mgl::Color bg_color;

View File

@@ -20,6 +20,7 @@ namespace gsr {
void add_item(const std::string &text, const std::string &id, const std::string &description = ""); void add_item(const std::string &text, const std::string &id, const std::string &description = "");
void set_item_label(const std::string &id, const std::string &new_label); void set_item_label(const std::string &id, const std::string &new_label);
void set_item_icon(const std::string &id, mgl::Texture *texture); void set_item_icon(const std::string &id, mgl::Texture *texture);
void set_item_description(const std::string &id, const std::string &new_description);
void set_description(std::string description_text); void set_description(std::string description_text);
void set_activated(bool activated); void set_activated(bool activated);

View File

@@ -25,6 +25,7 @@ namespace gsr {
RECORD_START_STOP, RECORD_START_STOP,
RECORD_PAUSE_UNPAUSE, RECORD_PAUSE_UNPAUSE,
STREAM_START_STOP, STREAM_START_STOP,
TAKE_SCREENSHOT,
SHOW_HIDE SHOW_HIDE
}; };
@@ -44,6 +45,7 @@ namespace gsr {
std::function<void(const char *reason)> on_click_exit_program_button; std::function<void(const char *reason)> on_click_exit_program_button;
std::function<void(const char *hotkey_option)> on_keyboard_hotkey_changed; std::function<void(const char *hotkey_option)> on_keyboard_hotkey_changed;
std::function<void(const char *hotkey_option)> on_joystick_hotkey_changed; std::function<void(const char *hotkey_option)> on_joystick_hotkey_changed;
std::function<void()> on_page_closed;
private: private:
void load_hotkeys(); void load_hotkeys();
@@ -55,6 +57,7 @@ namespace gsr {
std::unique_ptr<List> create_replay_hotkey_options(); std::unique_ptr<List> create_replay_hotkey_options();
std::unique_ptr<List> create_record_hotkey_options(); std::unique_ptr<List> create_record_hotkey_options();
std::unique_ptr<List> create_stream_hotkey_options(); std::unique_ptr<List> create_stream_hotkey_options();
std::unique_ptr<List> create_screenshot_hotkey_options();
std::unique_ptr<List> create_hotkey_control_buttons(); std::unique_ptr<List> create_hotkey_control_buttons();
std::unique_ptr<Subsection> create_hotkey_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_hotkey_subsection(ScrollablePage *parent_page);
std::unique_ptr<Button> create_exit_program_button(); std::unique_ptr<Button> create_exit_program_button();
@@ -86,6 +89,7 @@ namespace gsr {
Button *start_stop_recording_button_ptr = nullptr; Button *start_stop_recording_button_ptr = nullptr;
Button *pause_unpause_recording_button_ptr = nullptr; Button *pause_unpause_recording_button_ptr = nullptr;
Button *start_stop_streaming_button_ptr = nullptr; Button *start_stop_streaming_button_ptr = nullptr;
Button *take_screenshot_button_ptr = nullptr;
Button *show_hide_button_ptr = nullptr; Button *show_hide_button_ptr = nullptr;
ConfigHotkey configure_config_hotkey; ConfigHotkey configure_config_hotkey;

View File

@@ -9,7 +9,7 @@
namespace gsr { namespace gsr {
class GsrPage : public Page { class GsrPage : public Page {
public: public:
GsrPage(); GsrPage(const char *top_text, const char *bottom_text);
GsrPage(const GsrPage&) = delete; GsrPage(const GsrPage&) = delete;
GsrPage& operator=(const GsrPage&) = delete; GsrPage& operator=(const GsrPage&) = delete;
@@ -42,7 +42,8 @@ namespace gsr {
float margin_bottom_scale = 0.0f; float margin_bottom_scale = 0.0f;
float margin_left_scale = 0.0f; float margin_left_scale = 0.0f;
float margin_right_scale = 0.0f; float margin_right_scale = 0.0f;
mgl::Text label_text; mgl::Text top_text;
mgl::Text bottom_text;
std::vector<ButtonItem> buttons; std::vector<ButtonItem> buttons;
}; };
} }

View File

@@ -0,0 +1,78 @@
#pragma once
#include "StaticPage.hpp"
#include "List.hpp"
#include "ComboBox.hpp"
#include "Entry.hpp"
#include "CheckBox.hpp"
#include "../GsrInfo.hpp"
#include "../Config.hpp"
namespace gsr {
class PageStack;
class GsrPage;
class ScrollablePage;
class Button;
class ScreenshotSettingsPage : public StaticPage {
public:
ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack);
ScreenshotSettingsPage(const ScreenshotSettingsPage&) = delete;
ScreenshotSettingsPage& operator=(const ScreenshotSettingsPage&) = delete;
void load();
void save();
void on_navigate_away_from_page() override;
private:
std::unique_ptr<ComboBox> create_record_area_box();
std::unique_ptr<Widget> create_record_area();
std::unique_ptr<List> create_select_window();
std::unique_ptr<Entry> create_image_width_entry();
std::unique_ptr<Entry> create_image_height_entry();
std::unique_ptr<List> create_image_resolution();
std::unique_ptr<List> create_image_resolution_section();
std::unique_ptr<CheckBox> create_restore_portal_session_checkbox();
std::unique_ptr<List> create_restore_portal_session_section();
std::unique_ptr<Widget> create_change_image_resolution_section();
std::unique_ptr<Widget> create_capture_target_section();
std::unique_ptr<List> create_image_quality_section();
std::unique_ptr<Widget> create_record_cursor_section();
std::unique_ptr<Widget> create_image_section();
std::unique_ptr<List> create_save_directory(const char *label);
std::unique_ptr<ComboBox> create_image_format_box();
std::unique_ptr<List> create_image_format_section();
std::unique_ptr<Widget> create_file_info_section();
std::unique_ptr<CheckBox> create_save_screenshot_in_game_folder();
std::unique_ptr<Widget> create_general_section();
std::unique_ptr<Widget> create_notifications_section();
std::unique_ptr<Widget> create_settings();
void add_widgets();
void save(RecordOptions &record_options);
private:
Config &config;
const GsrInfo *gsr_info = nullptr;
SupportedCaptureOptions capture_options;
GsrPage *content_page_ptr = nullptr;
ScrollablePage *settings_scrollable_page_ptr = nullptr;
List *select_window_list_ptr = nullptr;
List *image_resolution_list_ptr = nullptr;
List *restore_portal_session_list_ptr = nullptr;
List *color_range_list_ptr = nullptr;
Widget *image_format_ptr = nullptr;
ComboBox *record_area_box_ptr = nullptr;
Entry *image_width_entry_ptr = nullptr;
Entry *image_height_entry_ptr = nullptr;
CheckBox *record_cursor_checkbox_ptr = nullptr;
CheckBox *restore_portal_session_checkbox_ptr = nullptr;
CheckBox *change_image_resolution_checkbox_ptr = nullptr;
ComboBox *image_quality_box_ptr = nullptr;
ComboBox *image_format_box_ptr = nullptr;
Button *save_directory_button_ptr = nullptr;
CheckBox *save_screenshot_in_game_folder_checkbox_ptr = nullptr;
CheckBox *show_screenshot_saved_notification_checkbox_ptr = nullptr;
PageStack *page_stack = nullptr;
};
}

View File

@@ -52,7 +52,7 @@ namespace gsr {
std::unique_ptr<CheckBox> create_restore_portal_session_checkbox(); std::unique_ptr<CheckBox> create_restore_portal_session_checkbox();
std::unique_ptr<List> create_restore_portal_session_section(); std::unique_ptr<List> create_restore_portal_session_section();
std::unique_ptr<Widget> create_change_video_resolution_section(); std::unique_ptr<Widget> create_change_video_resolution_section();
std::unique_ptr<Widget> create_capture_target(); std::unique_ptr<Widget> create_capture_target_section();
std::unique_ptr<ComboBox> create_audio_device_selection_combobox(); std::unique_ptr<ComboBox> create_audio_device_selection_combobox();
std::unique_ptr<Button> create_remove_audio_device_button(List *audio_device_list_ptr); std::unique_ptr<Button> create_remove_audio_device_button(List *audio_device_list_ptr);
std::unique_ptr<List> create_audio_device(); std::unique_ptr<List> create_audio_device();

View File

@@ -27,6 +27,7 @@ src = [
'src/gui/CustomRendererWidget.cpp', 'src/gui/CustomRendererWidget.cpp',
'src/gui/FileChooser.cpp', 'src/gui/FileChooser.cpp',
'src/gui/SettingsPage.cpp', 'src/gui/SettingsPage.cpp',
'src/gui/ScreenshotSettingsPage.cpp',
'src/gui/GlobalSettingsPage.cpp', 'src/gui/GlobalSettingsPage.cpp',
'src/gui/GsrPage.cpp', 'src/gui/GsrPage.cpp',
'src/gui/Subsection.cpp', 'src/gui/Subsection.cpp',

View File

@@ -6,6 +6,7 @@
#include <limits.h> #include <limits.h>
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <assert.h>
#include <mglpp/window/Keyboard.hpp> #include <mglpp/window/Keyboard.hpp>
#define FORMAT_I32 "%" PRIi32 #define FORMAT_I32 "%" PRIi32
@@ -13,6 +14,37 @@
#define FORMAT_U32 "%" PRIu32 #define FORMAT_U32 "%" PRIu32
namespace gsr { 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 { bool ConfigHotkey::operator==(const ConfigHotkey &other) const {
return key == other.key && modifiers == other.modifiers; return key == other.key && modifiers == other.modifiers;
} }
@@ -21,36 +53,83 @@ namespace gsr {
return !operator==(other); return !operator==(other);
} }
Config::Config(const SupportedCaptureOptions &capture_options) { std::string ConfigHotkey::to_string(bool spaces, bool modifier_side) const {
const std::string default_save_directory = get_videos_dir(); 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_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.video_quality = "custom";
streaming_config.record_options.audio_tracks.push_back("default_output"); streaming_config.record_options.audio_tracks.push_back("default_output");
streaming_config.record_options.video_bitrate = 15000; streaming_config.record_options.video_bitrate = 15000;
record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT}; record_config.save_directory = default_videos_save_directory;
record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
record_config.save_directory = default_save_directory;
record_config.record_options.audio_tracks.push_back("default_output"); record_config.record_options.audio_tracks.push_back("default_output");
record_config.record_options.video_bitrate = 45000; 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.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.audio_tracks.push_back("default_output");
replay_config.record_options.video_bitrate = 45000; 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()) { if(!capture_options.monitors.empty()) {
streaming_config.record_options.record_area_option = capture_options.monitors.front().name; streaming_config.record_options.record_area_option = capture_options.monitors.front().name;
record_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; 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) { static std::optional<KeyValue> parse_key_value(std::string_view line) {
const size_t space_index = line.find(' '); const size_t space_index = line.find(' ');
if(space_index == std::string_view::npos) if(space_index == std::string_view::npos)
@@ -156,7 +235,20 @@ namespace gsr {
{"replay.container", &config.replay_config.container}, {"replay.container", &config.replay_config.container},
{"replay.time", &config.replay_config.replay_time}, {"replay.time", &config.replay_config.replay_time},
{"replay.start_stop_hotkey", &config.replay_config.start_stop_hotkey}, {"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)) { } 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)) if(*std::get<std::vector<std::string>*>(it.second) != *std::get<std::vector<std::string>*>(it_other->second))
return false; return false;
} else {
assert(false);
} }
} }
return true; return true;
@@ -245,6 +339,8 @@ namespace gsr {
} else if(std::holds_alternative<std::vector<std::string>*>(it->second)) { } else if(std::holds_alternative<std::vector<std::string>*>(it->second)) {
std::string array_value(key_value->value); std::string array_value(key_value->value);
std::get<std::vector<std::string>*>(it->second)->push_back(std::move(array_value)); std::get<std::vector<std::string>*>(it->second)->push_back(std::move(array_value));
} else {
assert(false);
} }
return true; return true;
@@ -294,6 +390,8 @@ namespace gsr {
for(const std::string &value : *array) { for(const std::string &value : *array) {
fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), value.c_str()); fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), value.c_str());
} }
} else {
assert(false);
} }
} }

View File

@@ -157,11 +157,19 @@ namespace gsr {
gsr_info->supported_video_codecs.vp9 = true; 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 { enum class GsrInfoSection {
UNKNOWN, UNKNOWN,
SYSTEM_INFO, SYSTEM_INFO,
GPU_INFO, GPU_INFO,
VIDEO_CODECS, VIDEO_CODECS,
IMAGE_FORMATS,
CAPTURE_OPTIONS CAPTURE_OPTIONS
}; };
@@ -194,6 +202,8 @@ namespace gsr {
section = GsrInfoSection::GPU_INFO; section = GsrInfoSection::GPU_INFO;
else if(section_name == "video_codecs") else if(section_name == "video_codecs")
section = GsrInfoSection::VIDEO_CODECS; section = GsrInfoSection::VIDEO_CODECS;
else if(section_name == "image_formats")
section = GsrInfoSection::IMAGE_FORMATS;
else if(section_name == "capture_options") else if(section_name == "capture_options")
section = GsrInfoSection::CAPTURE_OPTIONS; section = GsrInfoSection::CAPTURE_OPTIONS;
else else
@@ -217,6 +227,10 @@ namespace gsr {
parse_video_codecs_line(gsr_info, line); parse_video_codecs_line(gsr_info, line);
break; break;
} }
case GsrInfoSection::IMAGE_FORMATS: {
parse_image_formats_line(gsr_info, line);
break;
}
case GsrInfoSection::CAPTURE_OPTIONS: { case GsrInfoSection::CAPTURE_OPTIONS: {
// Intentionally ignore, get capture options with get_supported_capture_options instead // Intentionally ignore, get capture options with get_supported_capture_options instead
break; break;

View File

@@ -44,9 +44,10 @@ namespace gsr {
} }
void Hotplug::process_event_data(int fd, const HotplugEventCallback &callback) { 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) if(bytes_read <= 0)
return; return;
event_data[bytes_read] = '\0';
/* Hotplug data ends with a newline and a null terminator */ /* Hotplug data ends with a newline and a null terminator */
int data_index = 0; int data_index = 0;

View File

@@ -7,10 +7,10 @@
#include "../include/gui/DropdownButton.hpp" #include "../include/gui/DropdownButton.hpp"
#include "../include/gui/CustomRendererWidget.hpp" #include "../include/gui/CustomRendererWidget.hpp"
#include "../include/gui/SettingsPage.hpp" #include "../include/gui/SettingsPage.hpp"
#include "../include/gui/ScreenshotSettingsPage.hpp"
#include "../include/gui/GlobalSettingsPage.hpp" #include "../include/gui/GlobalSettingsPage.hpp"
#include "../include/gui/Utils.hpp" #include "../include/gui/Utils.hpp"
#include "../include/gui/PageStack.hpp" #include "../include/gui/PageStack.hpp"
#include "../include/gui/GsrPage.hpp"
#include "../include/WindowUtils.hpp" #include "../include/WindowUtils.hpp"
#include "../include/GlobalHotkeys.hpp" #include "../include/GlobalHotkeys.hpp"
@@ -20,6 +20,7 @@
#include <limits.h> #include <limits.h>
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <malloc.h>
#include <stdexcept> #include <stdexcept>
#include <X11/Xlib.h> #include <X11/Xlib.h>
@@ -377,6 +378,13 @@ namespace gsr {
fprintf(stderr, "pressed %s\n", id.c_str()); fprintf(stderr, "pressed %s\n", id.c_str());
overlay->save_replay(); 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) { 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; 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(); close_gpu_screen_recorder_output();
deinit_color_theme(); deinit_color_theme();
@@ -674,6 +692,7 @@ namespace gsr {
update_notification_process_status(); update_notification_process_status();
update_gsr_replay_save(); update_gsr_replay_save();
update_gsr_process_status(); update_gsr_process_status();
update_gsr_screenshot_process_status();
replay_status_update_status(); replay_status_update_status();
if(!visible) if(!visible)
@@ -939,197 +958,7 @@ namespace gsr {
update_compositor_texture(*focused_monitor); update_compositor_texture(*focused_monitor);
bg_screenshot_overlay = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height)); create_frontpage_ui_components();
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);
logo_sprite = mgl::Sprite(&get_theme().logo_texture);
close_button_widget.set_size(mgl::vec2f(top_bar_background.get_size().y * 0.35f, top_bar_background.get_size().y * 0.35f).floor());
bg_screenshot_overlay.set_color(bg_color);
top_bar_background.set_color(mgl::Color(0, 0, 0, 180));
//top_bar_text.set_color(get_color_theme().tint_color);
top_bar_text.set_position((top_bar_background.get_position() + top_bar_background.get_size()*0.5f - top_bar_text.get_bounds().size*0.5f).floor());
logo_sprite.set_height((int)(top_bar_background.get_size().y * 0.65f));
logo_sprite.set_position(mgl::vec2f(
(top_bar_background.get_size().y - logo_sprite.get_size().y) * 0.5f,
top_bar_background.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f
).floor());
close_button_widget.set_position(mgl::vec2f(get_theme().window_width - close_button_widget.get_size().x - logo_sprite.get_position().x, top_bar_background.get_size().y * 0.5f - close_button_widget.get_size().y * 0.5f).floor());
while(!page_stack.empty()) {
page_stack.pop();
}
auto front_page = std::make_unique<StaticPage>(window_size.to_vec2f());
StaticPage *front_page_ptr = front_page.get();
page_stack.push(std::move(front_page));
const int button_height = window_size.y / 5.0f;
const int button_width = button_height;
auto main_buttons_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
List * main_buttons_list_ptr = main_buttons_list.get();
main_buttons_list->set_spacing(0.0f);
{
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("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("save", &get_theme().save_texture);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack);
replay_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::REPLAY)
show_notification("Replay settings have been modified.\nYou may need to restart replay to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
};
page_stack.push(std::move(replay_settings_page));
} else if(id == "save") {
on_press_save_replay();
} else if(id == "start") {
on_press_start_replay(false);
}
};
main_buttons_list->add_widget(std::move(button));
}
{
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("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("pause", &get_theme().pause_texture);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack);
record_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::RECORD)
show_notification("Recording settings have been modified.\nYou may need to restart recording to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
};
page_stack.push(std::move(record_settings_page));
} else if(id == "pause") {
toggle_pause();
} else if(id == "start") {
on_press_start_record();
}
};
main_buttons_list->add_widget(std::move(button));
}
{
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("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack);
stream_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::STREAM)
show_notification("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
};
page_stack.push(std::move(stream_settings_page));
} else if(id == "start") {
on_press_start_stream();
}
};
main_buttons_list->add_widget(std::move(button));
}
const mgl::vec2f main_buttons_list_size = main_buttons_list->get_size();
main_buttons_list->set_position((mgl::vec2f(window_size.x * 0.5f, window_size.y * 0.25f) - main_buttons_list_size * 0.5f).floor());
front_page_ptr->add_widget(std::move(main_buttons_list));
{
const mgl::vec2f main_buttons_size = main_buttons_list_ptr->get_size();
const int settings_button_size = main_buttons_size.y * 0.2f;
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));
button->set_icon(&get_theme().settings_small_texture);
button->on_click = [&]() {
auto settings_page = std::make_unique<GlobalSettingsPage>(this, &gsr_info, config, &page_stack);
settings_page->on_startup_changed = [&](bool enable, int exit_status) {
if(exit_status == 0)
return;
if(exit_status == 127) {
if(enable)
show_notification("Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add \"gsr-ui\" to system startup on systems that uses another init system.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
} else {
if(enable)
show_notification("Failed to add GPU Screen Recorder to system startup", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
else
show_notification("Failed to remove GPU Screen Recorder from system startup", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
}
};
settings_page->on_click_exit_program_button = [this](const char *reason) {
do_exit = true;
exit_reason = reason;
};
settings_page->on_keyboard_hotkey_changed = [this](const char *hotkey_option) {
global_hotkeys.reset();
if(strcmp(hotkey_option, "enable_hotkeys") == 0)
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
else if(strcmp(hotkey_option, "enable_hotkeys_virtual_devices") == 0)
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::VIRTUAL);
else if(strcmp(hotkey_option, "disable_hotkeys") == 0)
global_hotkeys.reset();
};
settings_page->on_joystick_hotkey_changed = [this](const char *hotkey_option) {
global_hotkeys_js.reset();
if(strcmp(hotkey_option, "enable_hotkeys") == 0)
global_hotkeys_js = register_joystick_hotkeys(this);
else if(strcmp(hotkey_option, "disable_hotkeys") == 0)
global_hotkeys_js.reset();
};
page_stack.push(std::move(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);
const mgl::vec2f padding(padding_size, padding_size);
if(mgl::FloatRect(pos, size).contains(window.get_mouse_position().to_vec2f()))
draw_rectangle_outline(window, pos.floor(), size.floor(), get_color_theme().tint_color, border_size);
mgl::Sprite close_sprite(&get_theme().close_texture);
close_sprite.set_position(pos + padding);
close_sprite.set_size(size - padding * 2.0f);
window.draw(close_sprite);
};
close_button_widget.event_handler = [&](mgl::Event &event, mgl::Window&, mgl::vec2f pos, mgl::vec2f size) {
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
close_button_pressed_inside = mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y));
} else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left && close_button_pressed_inside) {
if(mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y))) {
while(!page_stack.empty()) {
page_stack.pop();
}
return false;
}
}
return true;
};
// The focused application can be an xwayland application but the cursor can hover over a wayland application. // 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. // This is even the case when hovering over the titlebar of the xwayland application.
@@ -1193,6 +1022,234 @@ namespace gsr {
draw(); 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);
logo_sprite = mgl::Sprite(&get_theme().logo_texture);
close_button_widget.set_size(mgl::vec2f(top_bar_background.get_size().y * 0.35f, top_bar_background.get_size().y * 0.35f).floor());
bg_screenshot_overlay.set_color(bg_color);
top_bar_background.set_color(mgl::Color(0, 0, 0, 180));
//top_bar_text.set_color(get_color_theme().tint_color);
top_bar_text.set_position((top_bar_background.get_position() + top_bar_background.get_size()*0.5f - top_bar_text.get_bounds().size*0.5f).floor());
logo_sprite.set_height((int)(top_bar_background.get_size().y * 0.65f));
logo_sprite.set_position(mgl::vec2f(
(top_bar_background.get_size().y - logo_sprite.get_size().y) * 0.5f,
top_bar_background.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f
).floor());
close_button_widget.set_position(mgl::vec2f(get_theme().window_width - close_button_widget.get_size().x - logo_sprite.get_position().x, top_bar_background.get_size().y * 0.5f - close_button_widget.get_size().y * 0.5f).floor());
while(!page_stack.empty()) {
page_stack.pop();
}
auto front_page = std::make_unique<StaticPage>(window_size.to_vec2f());
StaticPage *front_page_ptr = front_page.get();
page_stack.push(std::move(front_page));
const int button_height = window_size.y / 5.0f;
const int button_width = button_height;
auto main_buttons_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
List * main_buttons_list_ptr = main_buttons_list.get();
main_buttons_list->set_spacing(0.0f);
{
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", 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);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack);
replay_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::REPLAY)
show_notification("Replay settings have been modified.\nYou may need to restart replay to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
};
page_stack.push(std::move(replay_settings_page));
} else if(id == "save") {
on_press_save_replay();
} else if(id == "start") {
on_press_start_replay(false);
}
};
main_buttons_list->add_widget(std::move(button));
}
{
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", 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);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack);
record_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::RECORD)
show_notification("Recording settings have been modified.\nYou may need to restart recording to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
};
page_stack.push(std::move(record_settings_page));
} else if(id == "pause") {
toggle_pause();
} else if(id == "start") {
on_press_start_record();
}
};
main_buttons_list->add_widget(std::move(button));
}
{
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", 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);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack);
stream_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::STREAM)
show_notification("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.", 5.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
};
page_stack.push(std::move(stream_settings_page));
} else if(id == "start") {
on_press_start_stream();
}
};
main_buttons_list->add_widget(std::move(button));
}
const mgl::vec2f main_buttons_list_size = main_buttons_list->get_size();
main_buttons_list->set_position((mgl::vec2f(window_size.x * 0.5f, window_size.y * 0.25f) - main_buttons_list_size * 0.5f).floor());
front_page_ptr->add_widget(std::move(main_buttons_list));
{
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) + 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().settings_small_texture);
button->on_click = [&]() {
auto settings_page = std::make_unique<GlobalSettingsPage>(this, &gsr_info, config, &page_stack);
settings_page->on_startup_changed = [&](bool enable, int exit_status) {
if(exit_status == 0)
return;
if(exit_status == 127) {
if(enable)
show_notification("Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add \"gsr-ui\" to system startup on systems that uses another init system.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
} else {
if(enable)
show_notification("Failed to add GPU Screen Recorder to system startup", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
else
show_notification("Failed to remove GPU Screen Recorder from system startup", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
}
};
settings_page->on_click_exit_program_button = [this](const char *reason) {
do_exit = true;
exit_reason = reason;
};
settings_page->on_keyboard_hotkey_changed = [this](const char *hotkey_option) {
global_hotkeys.reset();
if(strcmp(hotkey_option, "enable_hotkeys") == 0)
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
else if(strcmp(hotkey_option, "enable_hotkeys_virtual_devices") == 0)
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::VIRTUAL);
else if(strcmp(hotkey_option, "disable_hotkeys") == 0)
global_hotkeys.reset();
};
settings_page->on_joystick_hotkey_changed = [this](const char *hotkey_option) {
global_hotkeys_js.reset();
if(strcmp(hotkey_option, "enable_hotkeys") == 0)
global_hotkeys_js = register_joystick_hotkeys(this);
else if(strcmp(hotkey_option, "disable_hotkeys") == 0)
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);
const mgl::vec2f padding(padding_size, padding_size);
if(mgl::FloatRect(pos, size).contains(window.get_mouse_position().to_vec2f()))
draw_rectangle_outline(window, pos.floor(), size.floor(), get_color_theme().tint_color, border_size);
mgl::Sprite close_sprite(&get_theme().close_texture);
close_sprite.set_position(pos + padding);
close_sprite.set_size(size - padding * 2.0f);
window.draw(close_sprite);
};
close_button_widget.event_handler = [&](mgl::Event &event, mgl::Window&, mgl::vec2f pos, mgl::vec2f size) {
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
close_button_pressed_inside = mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y));
} else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left && close_button_pressed_inside) {
if(mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y))) {
while(!page_stack.empty()) {
page_stack.pop();
}
return false;
}
}
return true;
};
}
void Overlay::hide() { void Overlay::hide() {
if(!visible) if(!visible)
return; return;
@@ -1269,6 +1326,7 @@ namespace gsr {
} }
deinit_theme(); deinit_theme();
malloc_trim(0);
} }
void Overlay::toggle_show() { void Overlay::toggle_show() {
@@ -1316,12 +1374,17 @@ namespace gsr {
on_press_save_replay(); on_press_save_replay();
} }
void Overlay::take_screenshot() {
on_press_take_screenshot();
}
static const char* notification_type_to_string(NotificationType notification_type) { static const char* notification_type_to_string(NotificationType notification_type) {
switch(notification_type) { switch(notification_type) {
case NotificationType::NONE: return nullptr; case NotificationType::NONE: return nullptr;
case NotificationType::RECORD: return "record"; case NotificationType::RECORD: return "record";
case NotificationType::REPLAY: return "replay"; case NotificationType::REPLAY: return "replay";
case NotificationType::STREAM: return "stream"; case NotificationType::STREAM: return "stream";
case NotificationType::SCREENSHOT: return "screenshot";
} }
return nullptr; return nullptr;
} }
@@ -1466,6 +1529,12 @@ namespace gsr {
text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'"; text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'";
break; 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::NONE:
case NotificationType::STREAM: case NotificationType::STREAM:
break; break;
@@ -1558,6 +1627,35 @@ namespace gsr {
recording_status = RecordingStatus::NONE; 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() { void Overlay::replay_status_update_status() {
if(replay_status_update_clock.get_elapsed_time_seconds() < replay_status_update_check_timeout_seconds) if(replay_status_update_clock.get_elapsed_time_seconds() < replay_status_update_check_timeout_seconds)
return; 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); 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) { bool Overlay::update_compositor_texture(const Monitor &monitor) {
window_texture_deinit(&window_texture); window_texture_deinit(&window_texture);
window_texture_sprite.set_texture(nullptr); window_texture_sprite.set_texture(nullptr);

View File

@@ -108,6 +108,9 @@ namespace gsr {
if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str())) if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str()))
goto error; goto error;
if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str()))
goto error;
return true; return true;
error: error:

View File

@@ -114,6 +114,14 @@ namespace gsr {
return xdg_videos_dir; 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 create_directory_recursive(char *path) {
int path_len = strlen(path); int path_len = strlen(path);
char *p = path; char *p = path;

View File

@@ -156,7 +156,7 @@ namespace gsr {
std::string result; std::string result;
for(int i = 0; i < size;) { for(int i = 0; i < size;) {
// Some games such as the finals has utf8-bom between each character, wtf? // 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; i += 3;
continue; continue;
} }
@@ -246,10 +246,14 @@ namespace gsr {
XClassHint class_hint = {nullptr, nullptr}; XClassHint class_hint = {nullptr, nullptr};
XGetClassHint(dpy, focused_window, &class_hint); XGetClassHint(dpy, focused_window, &class_hint);
if(class_hint.res_class) { if(class_hint.res_class)
result = strip(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; return result;
} }

View File

@@ -15,8 +15,8 @@ namespace gsr {
// These are relative to the button size // These are relative to the button size
static const float padding_top_icon_scale = 0.25f; static const float padding_top_icon_scale = 0.25f;
static const float padding_bottom_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_left_icon_scale = 0.25f;
static const float padding_right_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) : 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) 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); background.set_color(mouse_inside ? bg_hover_color : bg_color);
window.draw(background); 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()) { if(sprite.get_texture() && sprite.get_texture()->is_valid()) {
scale_sprite_to_button_size(); 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); 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) { if(mouse_inside) {
@@ -72,18 +80,25 @@ namespace gsr {
if(!visible) if(!visible)
return {0.0f, 0.0f}; 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_left = padding_left_scale * get_theme().window_height;
const int padding_right = padding_right_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; const mgl::vec2f text_bounds = text.get_bounds().size;
mgl::vec2f s = size; mgl::vec2f widget_size = size;
if(s.x < 0.0001f)
s.x = padding_left + text_bounds.x + padding_right; if(widget_size.y < 0.0001f)
if(s.y < 0.0001f) widget_size.y = get_button_height();
s.y = padding_top + text_bounds.y + padding_bottom;
return s; 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) { void Button::set_border_scale(float scale) {
@@ -110,13 +125,23 @@ namespace gsr {
if(!sprite.get_texture() || !sprite.get_texture()->is_valid()) if(!sprite.get_texture() || !sprite.get_texture()->is_valid())
return; return;
const mgl::vec2f button_size = get_size(); const float widget_height = get_button_height();
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 mgl::vec2f desired_size = button_size - mgl::vec2f(padding_icon_left + padding_icon_right, padding_icon_top + padding_icon_bottom); const int padding_icon_top = padding_top_icon_scale * widget_height;
sprite.set_size(scale_keep_aspect_ratio(sprite.get_texture()->get_size().to_vec2f(), desired_size).floor()); 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;
return widget_height;
} }
} }

View File

@@ -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) { void DropdownButton::set_description(std::string description_text) {
description.set_string(std::move(description_text)); description.set_string(std::move(description_text));
} }

View File

@@ -67,46 +67,6 @@ namespace gsr {
return 0; 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) : 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()), StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
overlay(overlay), overlay(overlay),
@@ -114,7 +74,7 @@ namespace gsr {
gsr_info(gsr_info), gsr_info(gsr_info),
page_stack(page_stack) 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->add_button("Back", "back", get_color_theme().page_bg_color);
content_page->on_click = [page_stack](const std::string &id) { content_page->on_click = [page_stack](const std::string &id) {
if(id == "back") if(id == "back")
@@ -322,30 +282,41 @@ namespace gsr {
return list; 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() { std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); 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)); 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] { clear_hotkeys_button->on_click = [this] {
// config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
// config.record_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.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0};
// config.replay_config.start_stop_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.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0};
// config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0}; config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0};
// load_hotkeys(); config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
// overlay->rebind_all_keyboard_hotkeys(); load_hotkeys();
// }; overlay->rebind_all_keyboard_hotkeys();
// list->add_widget(std::move(clear_hotkeys_button)); };
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)); 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] { reset_hotkeys_button->on_click = [this] {
config.streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT}; config.set_hotkeys_to_default();
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};
load_hotkeys(); load_hotkeys();
overlay->rebind_all_keyboard_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_replay_hotkey_options());
list_ptr->add_widget(create_record_hotkey_options()); list_ptr->add_widget(create_record_hotkey_options());
list_ptr->add_widget(create_stream_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(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()); list_ptr->add_widget(create_hotkey_control_buttons());
return subsection; return subsection;
@@ -440,6 +412,8 @@ namespace gsr {
void GlobalSettingsPage::on_navigate_away_from_page() { void GlobalSettingsPage::on_navigate_away_from_page() {
save(); save();
if(on_page_closed)
on_page_closed();
} }
void GlobalSettingsPage::load() { void GlobalSettingsPage::load() {
@@ -460,15 +434,17 @@ namespace gsr {
} }
void GlobalSettingsPage::load_hotkeys() { void GlobalSettingsPage::load_hotkeys() {
turn_replay_on_off_button_ptr->set_text(config_hotkey_to_string(config.replay_config.start_stop_hotkey)); turn_replay_on_off_button_ptr->set_text(config.replay_config.start_stop_hotkey.to_string());
save_replay_button_ptr->set_text(config_hotkey_to_string(config.replay_config.save_hotkey)); 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)); start_stop_recording_button_ptr->set_text(config.record_config.start_stop_hotkey.to_string());
pause_unpause_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.pause_unpause_hotkey)); 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() { void GlobalSettingsPage::save() {
@@ -496,10 +472,10 @@ namespace gsr {
if(mgl::Keyboard::key_is_modifier(event.key.code)) { if(mgl::Keyboard::key_is_modifier(event.key.code)) {
configure_config_hotkey.modifiers |= mgl_modifier_to_hotkey_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) { } else if(configure_config_hotkey.modifiers != 0) {
configure_config_hotkey.key = event.key.code; 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(); configure_hotkey_stop_and_save();
} }
@@ -512,7 +488,7 @@ namespace gsr {
if(mgl::Keyboard::key_is_modifier(event.key.code)) { if(mgl::Keyboard::key_is_modifier(event.key.code)) {
configure_config_hotkey.modifiers &= ~mgl_modifier_to_hotkey_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; return false;
@@ -535,6 +511,8 @@ namespace gsr {
return pause_unpause_recording_button_ptr; return pause_unpause_recording_button_ptr;
case ConfigureHotkeyType::STREAM_START_STOP: case ConfigureHotkeyType::STREAM_START_STOP:
return start_stop_streaming_button_ptr; return start_stop_streaming_button_ptr;
case ConfigureHotkeyType::TAKE_SCREENSHOT:
return take_screenshot_button_ptr;
case ConfigureHotkeyType::SHOW_HIDE: case ConfigureHotkeyType::SHOW_HIDE:
return show_hide_button_ptr; return show_hide_button_ptr;
} }
@@ -555,6 +533,8 @@ namespace gsr {
return &config.record_config.pause_unpause_hotkey; return &config.record_config.pause_unpause_hotkey;
case ConfigureHotkeyType::STREAM_START_STOP: case ConfigureHotkeyType::STREAM_START_STOP:
return &config.streaming_config.start_stop_hotkey; return &config.streaming_config.start_stop_hotkey;
case ConfigureHotkeyType::TAKE_SCREENSHOT:
return &config.screenshot_config.take_screenshot_hotkey;
case ConfigureHotkeyType::SHOW_HIDE: case ConfigureHotkeyType::SHOW_HIDE:
return &config.main_config.show_hide_hotkey; return &config.main_config.show_hide_hotkey;
} }
@@ -568,6 +548,7 @@ namespace gsr {
&config.record_config.start_stop_hotkey, &config.record_config.start_stop_hotkey,
&config.record_config.pause_unpause_hotkey, &config.record_config.pause_unpause_hotkey,
&config.streaming_config.start_stop_hotkey, &config.streaming_config.start_stop_hotkey,
&config.screenshot_config.take_screenshot_hotkey,
&config.main_config.show_hide_hotkey &config.main_config.show_hide_hotkey
}; };
for(ConfigHotkey *config_hotkey : config_hotkeys) { for(ConfigHotkey *config_hotkey : config_hotkeys) {
@@ -604,6 +585,9 @@ namespace gsr {
case ConfigureHotkeyType::STREAM_START_STOP: case ConfigureHotkeyType::STREAM_START_STOP:
hotkey_configure_action_name = "Start/stop streaming"; hotkey_configure_action_name = "Start/stop streaming";
break; break;
case ConfigureHotkeyType::TAKE_SCREENSHOT:
hotkey_configure_action_name = "Take a screenshot";
break;
case ConfigureHotkeyType::SHOW_HIDE: case ConfigureHotkeyType::SHOW_HIDE:
hotkey_configure_action_name = "Show/hide UI"; hotkey_configure_action_name = "Show/hide UI";
break; break;
@@ -614,7 +598,7 @@ namespace gsr {
Button *config_hotkey_button = configure_hotkey_get_button_by_active_type(); Button *config_hotkey_button = configure_hotkey_get_button_by_active_type();
ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type(); ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type();
if(config_hotkey_button && config_hotkey) 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_config_hotkey = {0, 0};
configure_hotkey_type = ConfigureHotkeyType::NONE; configure_hotkey_type = ConfigureHotkeyType::NONE;
@@ -634,9 +618,9 @@ namespace gsr {
}); });
if(hotkey_used_by_another_action) { 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); 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}; configure_config_hotkey = {0, 0};
return; return;
} }

View File

@@ -8,8 +8,9 @@
namespace gsr { namespace gsr {
static const float button_spacing_scale = 0.015f; static const float button_spacing_scale = 0.015f;
GsrPage::GsrPage() : GsrPage::GsrPage(const char *top_text, const char *bottom_text) :
label_text("Settings", get_theme().title_font) top_text(top_text, get_theme().title_font),
bottom_text(bottom_text, get_theme().title_font)
{ {
const float margin = 0.02f; const float margin = 0.02f;
set_margins(margin, margin, margin, margin); set_margins(margin, margin, margin, margin);
@@ -80,13 +81,17 @@ namespace gsr {
window.draw(background); window.draw(background);
const int text_margin = background.get_size().y * 0.085; 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); mgl::Sprite icon(&get_theme().settings_texture);
icon.set_height((int)(background.get_size().y * 0.5f)); 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()); icon.set_position((background.get_position() + background.get_size() * 0.5f - icon.get_size() * 0.5f).floor());
window.draw(icon); 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) { void GsrPage::draw_buttons(mgl::Window &window, mgl::vec2f body_pos, mgl::vec2f body_size) {

View File

@@ -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);
}
}

View File

@@ -8,11 +8,6 @@
#include "../../include/GsrInfo.hpp" #include "../../include/GsrInfo.hpp"
#include "../../include/Utils.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> #include <string.h>
namespace gsr { namespace gsr {
@@ -22,6 +17,15 @@ namespace gsr {
APPLICATION_CUSTOM 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) : 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()), StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
type(type), type(type),
@@ -33,7 +37,7 @@ namespace gsr {
application_audio = get_application_audio(); application_audio = get_application_audio();
capture_options = get_supported_capture_options(*gsr_info); 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->add_button("Back", "back", get_color_theme().page_bg_color);
content_page->on_click = [page_stack](const std::string &id) { content_page->on_click = [page_stack](const std::string &id) {
if(id == "back") if(id == "back")
@@ -171,7 +175,7 @@ namespace gsr {
return checkbox; 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 ll = std::make_unique<List>(List::Orientation::VERTICAL);
auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); 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); auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL);
settings_list->set_spacing(0.018f); 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_audio_section());
settings_list->add_widget(create_video_section()); settings_list->add_widget(create_video_section());
settings_list_ptr = settings_list.get(); 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)); 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_ptr = save_directory_button.get();
save_directory_button->on_click = [this]() { 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("Save", "save", get_color_theme().tint_color);
select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_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()); 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))); 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); 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)));
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)));
auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL);

View File

@@ -8,6 +8,7 @@
#include <signal.h> #include <signal.h>
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
#include <malloc.h>
#include <mglpp/mglpp.hpp> #include <mglpp/mglpp.hpp>
#include <mglpp/system/Clock.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()); fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->save_replay(); 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() { static bool is_gsr_ui_virtual_keyboard_running() {
@@ -150,6 +156,7 @@ enum class LaunchAction {
int main(int argc, char **argv) { int main(int argc, char **argv) {
setlocale(LC_ALL, "C"); // Sigh... stupid C setlocale(LC_ALL, "C"); // Sigh... stupid C
mallopt(M_MMAP_THRESHOLD, 65536);
if(geteuid() == 0) { if(geteuid() == 0) {
fprintf(stderr, "Error: don't run gsr-ui as the root user\n"); fprintf(stderr, "Error: don't run gsr-ui as the root user\n");

View File

@@ -65,9 +65,10 @@ static void hotplug_event_parse_netlink_data(hotplug_event *self, const char *li
/* Netlink uevent structure is documented here: https://web.archive.org/web/20160127215232/https://www.kernel.org/doc/pending/hotplug.txt */ /* Netlink uevent structure is documented here: https://web.archive.org/web/20160127215232/https://www.kernel.org/doc/pending/hotplug.txt */
void hotplug_event_process_event_data(hotplug_event *self, int fd, hotplug_device_added_callback callback, void *userdata) { void hotplug_event_process_event_data(hotplug_event *self, int fd, hotplug_device_added_callback callback, void *userdata) {
const int bytes_read = read(fd, self->event_data, sizeof(self->event_data)); const int bytes_read = read(fd, self->event_data, sizeof(self->event_data) - 1);
if(bytes_read <= 0) if(bytes_read <= 0)
return; return;
self->event_data[bytes_read] = '\0';
/* Hotplug data ends with a newline and a null terminator */ /* Hotplug data ends with a newline and a null terminator */
int data_index = 0; int data_index = 0;

View File

@@ -50,6 +50,7 @@ static void usage(void) {
printf(" toggle-stream Start/stop streaming.\n"); printf(" toggle-stream Start/stop streaming.\n");
printf(" toggle-replay Start/stop replay.\n"); printf(" toggle-replay Start/stop replay.\n");
printf(" replay-save Save replay.\n"); printf(" replay-save Save replay.\n");
printf(" take-screenshot Take a screenshot.\n");
printf("\n"); printf("\n");
printf("EXAMPLES:\n"); printf("EXAMPLES:\n");
printf(" gsr-ui-cli toggle-show\n"); printf(" gsr-ui-cli toggle-show\n");
@@ -65,6 +66,7 @@ static bool is_valid_command(const char *command) {
"toggle-stream", "toggle-stream",
"toggle-replay", "toggle-replay",
"replay-save", "replay-save",
"take-screenshot",
NULL NULL
}; };