mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-20 08:55:50 +09:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70780ae14e | ||
|
|
5f7cb94f4e | ||
|
|
748c51e2b6 | ||
|
|
3ba9ce771b | ||
|
|
c18b062180 | ||
|
|
705da21363 | ||
|
|
609a3e54fd | ||
|
|
4e62d12e8c | ||
|
|
b4e003c8f7 | ||
|
|
9efe9d3c91 | ||
|
|
ef4a0fe7cb | ||
|
|
dacf6126bf | ||
|
|
9bbec944de | ||
|
|
6a55338b12 | ||
|
|
2d3abace0e | ||
|
|
47c02fc6c8 | ||
|
|
5f8c366b43 | ||
|
|
f4ed622510 | ||
|
|
f1ee19d014 | ||
|
|
67a8040e57 | ||
|
|
ff00be30df | ||
|
|
cf282bc225 | ||
|
|
c05a8290b7 | ||
|
|
a9e118ea8f | ||
|
|
8ed1fe4799 | ||
|
|
c1d76b5169 | ||
|
|
b1e650c7ec | ||
|
|
2e0dc48f3e | ||
|
|
d64e698eb1 | ||
|
|
315fab99a8 | ||
|
|
8ffd8de74a | ||
|
|
ad94cff59e | ||
|
|
b64cb6a3fd | ||
|
|
182c96d8e9 | ||
|
|
9192b3eba1 | ||
|
|
dd7aae3191 |
14
README.md
14
README.md
@@ -4,18 +4,18 @@
|
|||||||
A fullscreen overlay UI for [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/) in the style of ShadowPlay.\
|
A fullscreen overlay UI for [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/) in the style of ShadowPlay.\
|
||||||
The application is currently primarly designed for X11 but it can run on Wayland as well through XWayland, with some caveats because of Wayland limitations.
|
The application is currently primarly designed for X11 but it can run on Wayland as well through XWayland, with some caveats because of Wayland limitations.
|
||||||
|
|
||||||
# Usage
|
|
||||||
You can start the overlay UI and make it start automatically on system startup by running `systemctl enable --now --user gpu-screen-recorder-ui`.
|
|
||||||
Alternatively you can run `gsr-ui` and go into settings and enable start on system startup setting.\
|
|
||||||
Press `Left Alt+Z` to show/hide the UI. Go into settings to view all of the different hotkeys configured.\
|
|
||||||
If you use a non-systemd distro and want to start the UI on system startup then you have to manually add `gsr-ui` to your system startup script.\
|
|
||||||
A program called `gsr-ui-cli` is also installed when installing this software. This can be used to remotely control the UI. Run `gsr-ui-cli --help` to list the available commands.
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
If you are using an Arch Linux based distro then you can find gpu screen recorder ui on aur under the name gpu-screen-recorder-ui (`yay -S gpu-screen-recorder-ui`).\
|
If you are using an Arch Linux based distro then you can find gpu screen recorder ui on aur under the name gpu-screen-recorder-ui (`yay -S gpu-screen-recorder-ui`).\
|
||||||
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
|
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
|
||||||
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which includes this UI.
|
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which includes this UI.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
Press `Left Alt+Z` to show/hide the UI. Go into settings (the icon on the right) to view all of the different hotkeys configured.\
|
||||||
|
You can start the overlay UI and make it start automatically on system startup by running `systemctl enable --now --user gpu-screen-recorder-ui`.
|
||||||
|
Alternatively you can run `gsr-ui` and go into settings and enable start on system startup setting.\
|
||||||
|
If you use a non-systemd distro and want to start the UI on system startup then you have to manually add `gsr-ui launch-daemon` to your system startup script.\
|
||||||
|
A program called `gsr-ui-cli` is also installed when installing this software. This can be used to remotely control the UI. Run `gsr-ui-cli --help` to list the available commands.
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
GPU Screen Recorder UI uses meson build system so you need to install `meson` to build GPU Screen Recorder UI.
|
GPU Screen Recorder UI uses meson build system so you need to install `meson` to build GPU Screen Recorder UI.
|
||||||
|
|
||||||
|
|||||||
18
TODO
18
TODO
@@ -25,8 +25,6 @@ Have different modes. Overlay, window and side menu. Overlay can be used on x11,
|
|||||||
|
|
||||||
Show navigation breadcrumbs for settings and deeper navigation (such as selecting a directory to save videos).
|
Show navigation breadcrumbs for settings and deeper navigation (such as selecting a directory to save videos).
|
||||||
|
|
||||||
Add option to hide stream key like a password input.
|
|
||||||
|
|
||||||
Add global setting. In that setting there should be an option to enable/disable gsr-ui from system startup (the systemd service).
|
Add global setting. In that setting there should be an option to enable/disable gsr-ui from system startup (the systemd service).
|
||||||
|
|
||||||
Add profiles and hotkey to switch between profiles (show notification when switching profile).
|
Add profiles and hotkey to switch between profiles (show notification when switching profile).
|
||||||
@@ -87,6 +85,7 @@ Dont put widget position to int position when scrolling. This makes the UI jitte
|
|||||||
Show warning if another instance of gpu screen recorder is already running when starting recording?
|
Show warning if another instance of gpu screen recorder is already running when starting recording?
|
||||||
|
|
||||||
Keyboard leds get turned off when stopping gsr-global-hotkeys (for example numlock). The numlock key has to be pressed twice again to make it look correct to match its state.
|
Keyboard leds get turned off when stopping gsr-global-hotkeys (for example numlock). The numlock key has to be pressed twice again to make it look correct to match its state.
|
||||||
|
Fix this by writing 0 or 1 to /sys/class/leds/input2::numlock/brightness.
|
||||||
|
|
||||||
Make gsr-ui flatpak systemd work nicely with non-flatpak gsr-ui. Maybe change ExecStart to do flatpak run ... || gsr-ui, but make it run as a shell command first with /bin/sh -c "".
|
Make gsr-ui flatpak systemd work nicely with non-flatpak gsr-ui. Maybe change ExecStart to do flatpak run ... || gsr-ui, but make it run as a shell command first with /bin/sh -c "".
|
||||||
|
|
||||||
@@ -172,8 +171,6 @@ Add a bug report page that automatically includes system info (make this clear t
|
|||||||
|
|
||||||
Make it possible to change controller hotkeys. Also read from /dev/input/eventN instead of /dev/input/jsN. This is readable for controllers.
|
Make it possible to change controller hotkeys. Also read from /dev/input/eventN instead of /dev/input/jsN. This is readable for controllers.
|
||||||
|
|
||||||
Add option to copy screenshot to clipboard. Does it work properly on Wayland compositors? Maybe need to wait until the application becomes Wayland native instead of XWayland.
|
|
||||||
|
|
||||||
Show message that replay/streaming has to be restarted if recording settings are changed while replay/streaming is ongoing.
|
Show message that replay/streaming has to be restarted if recording settings are changed while replay/streaming is ongoing.
|
||||||
|
|
||||||
Support vector graphics. Maybe support svg, rendering it to a texture for better performance.
|
Support vector graphics. Maybe support svg, rendering it to a texture for better performance.
|
||||||
@@ -196,8 +193,6 @@ Add a window that shows a warning if gpu video encoding isn't supported.
|
|||||||
|
|
||||||
Disable system notifications when recording. Does the notification dbus interface support pausing notifications?
|
Disable system notifications when recording. Does the notification dbus interface support pausing notifications?
|
||||||
|
|
||||||
Automatically mark window region in window capture for screenshot on x11.
|
|
||||||
|
|
||||||
Disable hotkeys if virtual keyboard is found (either at startup or after running), if grab type if not virtual. Show a notification if that happens that hotkeys have been disabled.
|
Disable hotkeys if virtual keyboard is found (either at startup or after running), if grab type if not virtual. Show a notification if that happens that hotkeys have been disabled.
|
||||||
Detect if keyboard is locked by listening to gsr-ui virtual keyboard events and if no event is received after pressing a key (when writing to it after receiving input from another keyboard)
|
Detect if keyboard is locked by listening to gsr-ui virtual keyboard events and if no event is received after pressing a key (when writing to it after receiving input from another keyboard)
|
||||||
then remove the keyboard grab and show a message or something.
|
then remove the keyboard grab and show a message or something.
|
||||||
@@ -205,3 +200,14 @@ Disable hotkeys if virtual keyboard is found (either at startup or after running
|
|||||||
Maybe this can be fixed automatically by grabbing gsr-ui virtual keyboard and releasing it just before we write to it and then release it again.
|
Maybe this can be fixed automatically by grabbing gsr-ui virtual keyboard and releasing it just before we write to it and then release it again.
|
||||||
But wont keyboard remapping software grab the keyboard first if they detect it quickly?
|
But wont keyboard remapping software grab the keyboard first if they detect it quickly?
|
||||||
If we fail to grab it because some other software did then dont grab any keyboards nor gsr-ui virtual keyboards, just listen to them.
|
If we fail to grab it because some other software did then dont grab any keyboards nor gsr-ui virtual keyboards, just listen to them.
|
||||||
|
|
||||||
|
Support localization.
|
||||||
|
|
||||||
|
Add option to not capture cursor in screenshot when doing region/window capture.
|
||||||
|
|
||||||
|
Window selection doesn't work when a window is fullscreen on x11.
|
||||||
|
|
||||||
|
Make it possible to change replay duration of the "save 1 min" and "save 10 min" by adding them to the replay settings as options.
|
||||||
|
|
||||||
|
If replay duration is set below the "save 1 min" or "save 10 min" then gray them out and when hovering over those buttons
|
||||||
|
show a tooltip that says that those buttons cant be used because the replay duration in replay settings is set to a lower value than that (and display the replay duration there).
|
||||||
Submodule depends/mglpp updated: a784fdb62b...6e720fe411
BIN
images/masked.png
Normal file
BIN
images/masked.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 930 B |
BIN
images/unmasked.png
Normal file
BIN
images/unmasked.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
60
include/ClipboardFile.hpp
Normal file
60
include/ClipboardFile.hpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
namespace gsr {
|
||||||
|
struct ClipboardCopy {
|
||||||
|
Window requestor = None;
|
||||||
|
uint64_t file_offset = 0;
|
||||||
|
Atom property = None;
|
||||||
|
Atom requestor_target = None;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClipboardFile {
|
||||||
|
public:
|
||||||
|
enum class FileType {
|
||||||
|
JPG,
|
||||||
|
PNG
|
||||||
|
};
|
||||||
|
|
||||||
|
ClipboardFile();
|
||||||
|
~ClipboardFile();
|
||||||
|
ClipboardFile(const ClipboardFile&) = delete;
|
||||||
|
ClipboardFile& operator=(const ClipboardFile&) = delete;
|
||||||
|
|
||||||
|
// Set this to an empty string to unset clipboard
|
||||||
|
void set_current_file(const std::string &filepath, FileType file_type);
|
||||||
|
private:
|
||||||
|
bool file_type_matches_request_atom(FileType file_type, Atom request_atom);
|
||||||
|
const char* file_type_clipboard_get_name(Atom request_atom);
|
||||||
|
const char* file_type_get_name(FileType file_type);
|
||||||
|
void send_clipboard_start(XSelectionRequestEvent *xselectionrequest);
|
||||||
|
void transfer_clipboard_data(XSelectionRequestEvent *xselectionrequest, ClipboardCopy *clipboard_copy);
|
||||||
|
ClipboardCopy* get_clipboard_copy_by_requestor(Window requestor);
|
||||||
|
void remove_clipboard_copy(Window requestor);
|
||||||
|
private:
|
||||||
|
Display *dpy = nullptr;
|
||||||
|
Window clipboard_window = None;
|
||||||
|
int file_fd = -1;
|
||||||
|
uint64_t file_size = 0;
|
||||||
|
FileType file_type = FileType::JPG;
|
||||||
|
|
||||||
|
Atom incr_atom = None;
|
||||||
|
Atom targets_atom = None;
|
||||||
|
Atom clipboard_atom = None;
|
||||||
|
Atom image_jpg_atom = None;
|
||||||
|
Atom image_jpeg_atom = None;
|
||||||
|
Atom image_png_atom = None;
|
||||||
|
|
||||||
|
std::thread event_thread;
|
||||||
|
std::mutex mutex;
|
||||||
|
bool running = true;
|
||||||
|
|
||||||
|
std::vector<ClipboardCopy> clipboard_copies;
|
||||||
|
bool should_clear_selection = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ namespace gsr {
|
|||||||
std::string hotkeys_enable_option = "enable_hotkeys";
|
std::string hotkeys_enable_option = "enable_hotkeys";
|
||||||
std::string joystick_hotkeys_enable_option = "disable_hotkeys";
|
std::string joystick_hotkeys_enable_option = "disable_hotkeys";
|
||||||
std::string tint_color;
|
std::string tint_color;
|
||||||
|
std::string notification_speed = "normal";
|
||||||
ConfigHotkey show_hide_hotkey;
|
ConfigHotkey show_hide_hotkey;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ namespace gsr {
|
|||||||
|
|
||||||
struct CustomStreamConfig {
|
struct CustomStreamConfig {
|
||||||
std::string url;
|
std::string url;
|
||||||
|
std::string key;
|
||||||
std::string container = "flv";
|
std::string container = "flv";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,10 +143,12 @@ namespace gsr {
|
|||||||
bool restore_portal_session = true;
|
bool restore_portal_session = true;
|
||||||
|
|
||||||
bool save_screenshot_in_game_folder = false;
|
bool save_screenshot_in_game_folder = false;
|
||||||
|
bool save_screenshot_to_clipboard = false;
|
||||||
bool show_screenshot_saved_notifications = true;
|
bool show_screenshot_saved_notifications = true;
|
||||||
std::string save_directory;
|
std::string save_directory;
|
||||||
ConfigHotkey take_screenshot_hotkey;
|
ConfigHotkey take_screenshot_hotkey;
|
||||||
ConfigHotkey take_screenshot_region_hotkey;
|
ConfigHotkey take_screenshot_region_hotkey;
|
||||||
|
ConfigHotkey take_screenshot_window_hotkey; // Or desktop portal, on wayland
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "AudioPlayer.hpp"
|
#include "AudioPlayer.hpp"
|
||||||
#include "RegionSelector.hpp"
|
#include "RegionSelector.hpp"
|
||||||
#include "WindowSelector.hpp"
|
#include "WindowSelector.hpp"
|
||||||
|
#include "ClipboardFile.hpp"
|
||||||
#include "CursorTracker/CursorTracker.hpp"
|
#include "CursorTracker/CursorTracker.hpp"
|
||||||
|
|
||||||
#include <mglpp/window/Window.hpp>
|
#include <mglpp/window/Window.hpp>
|
||||||
@@ -41,6 +42,17 @@ namespace gsr {
|
|||||||
SCREENSHOT
|
SCREENSHOT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ScreenshotForceType {
|
||||||
|
NONE,
|
||||||
|
REGION,
|
||||||
|
WINDOW
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NotificationSpeed {
|
||||||
|
NORMAL,
|
||||||
|
FAST
|
||||||
|
};
|
||||||
|
|
||||||
class Overlay {
|
class Overlay {
|
||||||
public:
|
public:
|
||||||
Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs);
|
Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs);
|
||||||
@@ -64,6 +76,7 @@ namespace gsr {
|
|||||||
void save_replay_10_min();
|
void save_replay_10_min();
|
||||||
void take_screenshot();
|
void take_screenshot();
|
||||||
void take_screenshot_region();
|
void take_screenshot_region();
|
||||||
|
void take_screenshot_window();
|
||||||
void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target = nullptr);
|
void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target = nullptr);
|
||||||
bool is_open() const;
|
bool is_open() const;
|
||||||
bool should_exit(std::string &reason) const;
|
bool should_exit(std::string &reason) const;
|
||||||
@@ -74,6 +87,8 @@ namespace gsr {
|
|||||||
|
|
||||||
void unbind_all_keyboard_hotkeys();
|
void unbind_all_keyboard_hotkeys();
|
||||||
void rebind_all_keyboard_hotkeys();
|
void rebind_all_keyboard_hotkeys();
|
||||||
|
|
||||||
|
void set_notification_speed(NotificationSpeed notification_speed);
|
||||||
private:
|
private:
|
||||||
void handle_keyboard_mapping_event();
|
void handle_keyboard_mapping_event();
|
||||||
void on_event(mgl::Event &event);
|
void on_event(mgl::Event &event);
|
||||||
@@ -87,6 +102,7 @@ namespace gsr {
|
|||||||
|
|
||||||
void close_gpu_screen_recorder_output();
|
void close_gpu_screen_recorder_output();
|
||||||
|
|
||||||
|
double get_time_passed_in_replay_buffer_seconds();
|
||||||
void update_notification_process_status();
|
void update_notification_process_status();
|
||||||
void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type);
|
void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type);
|
||||||
void on_replay_saved(const char *replay_saved_filepath);
|
void on_replay_saved(const char *replay_saved_filepath);
|
||||||
@@ -121,7 +137,7 @@ namespace gsr {
|
|||||||
bool on_press_start_replay(bool disable_notification, bool finished_selection);
|
bool on_press_start_replay(bool disable_notification, bool finished_selection);
|
||||||
void on_press_start_record(bool finished_selection);
|
void on_press_start_record(bool finished_selection);
|
||||||
void on_press_start_stream(bool finished_selection);
|
void on_press_start_stream(bool finished_selection);
|
||||||
void on_press_take_screenshot(bool finished_selection, bool force_region_capture);
|
void on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type);
|
||||||
bool update_compositor_texture(const Monitor &monitor);
|
bool update_compositor_texture(const Monitor &monitor);
|
||||||
|
|
||||||
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
|
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
|
||||||
@@ -213,6 +229,12 @@ namespace gsr {
|
|||||||
bool try_replay_startup = true;
|
bool try_replay_startup = true;
|
||||||
bool replay_recording = false;
|
bool replay_recording = false;
|
||||||
int replay_save_duration_min = 0;
|
int replay_save_duration_min = 0;
|
||||||
|
double replay_buffer_save_duration_sec = 0.0;
|
||||||
|
mgl::Clock replay_duration_clock;
|
||||||
|
double replay_saved_duration_sec = 0.0;
|
||||||
|
bool replay_restart_on_save = false;
|
||||||
|
|
||||||
|
mgl::Clock recording_duration_clock;
|
||||||
|
|
||||||
AudioPlayer audio_player;
|
AudioPlayer audio_player;
|
||||||
|
|
||||||
@@ -229,5 +251,9 @@ namespace gsr {
|
|||||||
|
|
||||||
std::unique_ptr<CursorTracker> cursor_tracker;
|
std::unique_ptr<CursorTracker> cursor_tracker;
|
||||||
mgl::Clock cursor_tracker_update_clock;
|
mgl::Clock cursor_tracker_update_clock;
|
||||||
|
|
||||||
|
bool hide_ui = false;
|
||||||
|
double notification_duration_multiplier = 1.0;
|
||||||
|
ClipboardFile clipboard_file;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,8 @@ namespace gsr {
|
|||||||
mgl::Texture save_texture;
|
mgl::Texture save_texture;
|
||||||
mgl::Texture screenshot_texture;
|
mgl::Texture screenshot_texture;
|
||||||
mgl::Texture trash_texture;
|
mgl::Texture trash_texture;
|
||||||
|
mgl::Texture masked_texture;
|
||||||
|
mgl::Texture unmasked_texture;
|
||||||
|
|
||||||
mgl::Texture ps4_home_texture;
|
mgl::Texture ps4_home_texture;
|
||||||
mgl::Texture ps4_options_texture;
|
mgl::Texture ps4_options_texture;
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ namespace gsr {
|
|||||||
void draw(mgl::Window &window, mgl::vec2f offset) override;
|
void draw(mgl::Window &window, mgl::vec2f offset) override;
|
||||||
|
|
||||||
void add_item(const std::string &text, const std::string &id);
|
void add_item(const std::string &text, const std::string &id);
|
||||||
|
// The item can only be selected if it's enabled
|
||||||
void set_selected_item(const std::string &id, bool trigger_event = true, bool trigger_event_even_if_selection_not_changed = true);
|
void set_selected_item(const std::string &id, bool trigger_event = true, bool trigger_event_even_if_selection_not_changed = true);
|
||||||
|
void set_item_enabled(const std::string &id, bool enabled);
|
||||||
const std::string& get_selected_id() const;
|
const std::string& get_selected_id() const;
|
||||||
|
|
||||||
mgl::vec2f get_size() override;
|
mgl::vec2f get_size() override;
|
||||||
@@ -36,6 +38,7 @@ namespace gsr {
|
|||||||
mgl::Text text;
|
mgl::Text text;
|
||||||
std::string id;
|
std::string id;
|
||||||
mgl::vec2f position;
|
mgl::vec2f position;
|
||||||
|
bool enabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
mgl::vec2f max_size;
|
mgl::vec2f max_size;
|
||||||
|
|||||||
@@ -4,13 +4,31 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <mglpp/graphics/Color.hpp>
|
#include <mglpp/graphics/Color.hpp>
|
||||||
#include <mglpp/graphics/Text.hpp>
|
#include <mglpp/graphics/Text32.hpp>
|
||||||
|
#include <mglpp/graphics/Rectangle.hpp>
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
using EntryValidateHandler = std::function<bool(std::string &str)>;
|
class Entry;
|
||||||
|
|
||||||
|
enum class EntryValidateHandlerResult {
|
||||||
|
DENY,
|
||||||
|
ALLOW,
|
||||||
|
REPLACED
|
||||||
|
};
|
||||||
|
using EntryValidateHandler = std::function<EntryValidateHandlerResult(Entry &entry, const std::u32string &str)>;
|
||||||
|
|
||||||
|
struct CaretIndexPos {
|
||||||
|
int index;
|
||||||
|
mgl::vec2f pos;
|
||||||
|
};
|
||||||
|
|
||||||
class Entry : public Widget {
|
class Entry : public Widget {
|
||||||
public:
|
public:
|
||||||
|
enum class Direction {
|
||||||
|
LEFT,
|
||||||
|
RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
Entry(mgl::Font *font, const char *text, float max_width);
|
Entry(mgl::Font *font, const char *text, float max_width);
|
||||||
Entry(const Entry&) = delete;
|
Entry(const Entry&) = delete;
|
||||||
Entry& operator=(const Entry&) = delete;
|
Entry& operator=(const Entry&) = delete;
|
||||||
@@ -20,8 +38,11 @@ namespace gsr {
|
|||||||
|
|
||||||
mgl::vec2f get_size() override;
|
mgl::vec2f get_size() override;
|
||||||
|
|
||||||
void set_text(std::string str);
|
EntryValidateHandlerResult set_text(const std::string &str);
|
||||||
const std::string& get_text() const;
|
std::string get_text() const;
|
||||||
|
|
||||||
|
void set_masked(bool masked);
|
||||||
|
bool is_masked() const;
|
||||||
|
|
||||||
// Return false to specify that the string should not be accepted. This reverts the string back to its previous value.
|
// Return false to specify that the string should not be accepted. This reverts the string back to its previous value.
|
||||||
// The input can be changed by changing the input parameter and returning true.
|
// The input can be changed by changing the input parameter and returning true.
|
||||||
@@ -29,10 +50,31 @@ namespace gsr {
|
|||||||
|
|
||||||
std::function<void(const std::string &text)> on_changed;
|
std::function<void(const std::string &text)> on_changed;
|
||||||
private:
|
private:
|
||||||
mgl::Text text;
|
// Also updates the cursor position
|
||||||
|
void replace_text(size_t index, size_t size, const std::u32string &replacement);
|
||||||
|
void move_caret_word(Direction direction, size_t max_codepoints);
|
||||||
|
EntryValidateHandlerResult set_text_internal(std::u32string str);
|
||||||
|
void draw_caret(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size);
|
||||||
|
void draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size);
|
||||||
|
CaretIndexPos find_closest_caret_index_by_position(mgl::vec2f position);
|
||||||
|
private:
|
||||||
|
struct Caret {
|
||||||
|
float offset_x = 0.0f;
|
||||||
|
int index = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
mgl::Rectangle background;
|
||||||
|
mgl::Text32 text;
|
||||||
|
mgl::Text32 masked_text;
|
||||||
float max_width;
|
float max_width;
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
float caret_offset_x = 0.0f;
|
bool selecting_text = false;
|
||||||
|
bool selecting_with_keyboard = false;
|
||||||
|
bool show_selection = false;
|
||||||
|
bool masked = false;
|
||||||
|
Caret caret;
|
||||||
|
Caret selection_start_caret;
|
||||||
|
float text_overflow = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max);
|
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace gsr {
|
|||||||
STREAM_START_STOP,
|
STREAM_START_STOP,
|
||||||
TAKE_SCREENSHOT,
|
TAKE_SCREENSHOT,
|
||||||
TAKE_SCREENSHOT_REGION,
|
TAKE_SCREENSHOT_REGION,
|
||||||
|
TAKE_SCREENSHOT_WINDOW,
|
||||||
SHOW_HIDE
|
SHOW_HIDE
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,11 +64,13 @@ namespace gsr {
|
|||||||
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_screenshot_hotkey_options();
|
||||||
std::unique_ptr<List> create_screenshot_region_hotkey_options();
|
std::unique_ptr<List> create_screenshot_region_hotkey_options();
|
||||||
|
std::unique_ptr<List> create_screenshot_window_hotkey_options();
|
||||||
std::unique_ptr<List> create_hotkey_control_buttons();
|
std::unique_ptr<List> create_hotkey_control_buttons();
|
||||||
std::unique_ptr<Subsection> create_keyboard_hotkey_subsection(ScrollablePage *parent_page);
|
std::unique_ptr<Subsection> create_keyboard_hotkey_subsection(ScrollablePage *parent_page);
|
||||||
std::unique_ptr<Subsection> create_controller_hotkey_subsection(ScrollablePage *parent_page);
|
std::unique_ptr<Subsection> create_controller_hotkey_subsection(ScrollablePage *parent_page);
|
||||||
std::unique_ptr<Button> create_exit_program_button();
|
std::unique_ptr<Button> create_exit_program_button();
|
||||||
std::unique_ptr<Button> create_go_back_to_old_ui_button();
|
std::unique_ptr<Button> create_go_back_to_old_ui_button();
|
||||||
|
std::unique_ptr<List> create_notification_speed();
|
||||||
std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page);
|
std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page);
|
||||||
std::unique_ptr<Subsection> create_application_info_subsection(ScrollablePage *parent_page);
|
std::unique_ptr<Subsection> create_application_info_subsection(ScrollablePage *parent_page);
|
||||||
void add_widgets();
|
void add_widgets();
|
||||||
@@ -99,7 +102,9 @@ namespace gsr {
|
|||||||
Button *start_stop_streaming_button_ptr = nullptr;
|
Button *start_stop_streaming_button_ptr = nullptr;
|
||||||
Button *take_screenshot_button_ptr = nullptr;
|
Button *take_screenshot_button_ptr = nullptr;
|
||||||
Button *take_screenshot_region_button_ptr = nullptr;
|
Button *take_screenshot_region_button_ptr = nullptr;
|
||||||
|
Button *take_screenshot_window_button_ptr = nullptr;
|
||||||
Button *show_hide_button_ptr = nullptr;
|
Button *show_hide_button_ptr = nullptr;
|
||||||
|
RadioButton *notification_speed_button_ptr = nullptr;
|
||||||
|
|
||||||
ConfigHotkey configure_config_hotkey;
|
ConfigHotkey configure_config_hotkey;
|
||||||
ConfigureHotkeyType configure_hotkey_type = ConfigureHotkeyType::NONE;
|
ConfigureHotkeyType configure_hotkey_type = ConfigureHotkeyType::NONE;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace gsr {
|
|||||||
std::unique_ptr<List> create_image_format_section();
|
std::unique_ptr<List> create_image_format_section();
|
||||||
std::unique_ptr<Widget> create_file_info_section();
|
std::unique_ptr<Widget> create_file_info_section();
|
||||||
std::unique_ptr<CheckBox> create_save_screenshot_in_game_folder();
|
std::unique_ptr<CheckBox> create_save_screenshot_in_game_folder();
|
||||||
|
std::unique_ptr<CheckBox> create_save_screenshot_to_clipboard();
|
||||||
std::unique_ptr<Widget> create_general_section();
|
std::unique_ptr<Widget> create_general_section();
|
||||||
std::unique_ptr<Widget> create_notifications_section();
|
std::unique_ptr<Widget> create_notifications_section();
|
||||||
std::unique_ptr<Widget> create_settings();
|
std::unique_ptr<Widget> create_settings();
|
||||||
@@ -69,6 +70,7 @@ namespace gsr {
|
|||||||
ComboBox *image_format_box_ptr = nullptr;
|
ComboBox *image_format_box_ptr = nullptr;
|
||||||
Button *save_directory_button_ptr = nullptr;
|
Button *save_directory_button_ptr = nullptr;
|
||||||
CheckBox *save_screenshot_in_game_folder_checkbox_ptr = nullptr;
|
CheckBox *save_screenshot_in_game_folder_checkbox_ptr = nullptr;
|
||||||
|
CheckBox *save_screenshot_to_clipboard_checkbox_ptr = nullptr;
|
||||||
CheckBox *show_screenshot_saved_notification_checkbox_ptr = nullptr;
|
CheckBox *show_screenshot_saved_notification_checkbox_ptr = nullptr;
|
||||||
|
|
||||||
PageStack *page_stack = nullptr;
|
PageStack *page_stack = nullptr;
|
||||||
|
|||||||
@@ -118,9 +118,11 @@ namespace gsr {
|
|||||||
std::unique_ptr<ComboBox> create_streaming_service_box();
|
std::unique_ptr<ComboBox> create_streaming_service_box();
|
||||||
std::unique_ptr<List> create_streaming_service_section();
|
std::unique_ptr<List> create_streaming_service_section();
|
||||||
std::unique_ptr<List> create_stream_key_section();
|
std::unique_ptr<List> create_stream_key_section();
|
||||||
std::unique_ptr<List> create_stream_url_section();
|
std::unique_ptr<List> create_stream_custom_url();
|
||||||
|
std::unique_ptr<List> create_stream_custom_key();
|
||||||
|
std::unique_ptr<List> create_stream_custom_section();
|
||||||
std::unique_ptr<ComboBox> create_stream_container_box();
|
std::unique_ptr<ComboBox> create_stream_container_box();
|
||||||
std::unique_ptr<List> create_stream_container_section();
|
std::unique_ptr<List> create_stream_container();
|
||||||
void add_stream_widgets();
|
void add_stream_widgets();
|
||||||
|
|
||||||
void load_audio_tracks(const RecordOptions &record_options);
|
void load_audio_tracks(const RecordOptions &record_options);
|
||||||
@@ -173,8 +175,7 @@ namespace gsr {
|
|||||||
ComboBox *container_box_ptr = nullptr;
|
ComboBox *container_box_ptr = nullptr;
|
||||||
ComboBox *streaming_service_box_ptr = nullptr;
|
ComboBox *streaming_service_box_ptr = nullptr;
|
||||||
List *stream_key_list_ptr = nullptr;
|
List *stream_key_list_ptr = nullptr;
|
||||||
List *stream_url_list_ptr = nullptr;
|
List *custom_stream_list_ptr = nullptr;
|
||||||
List *container_list_ptr = nullptr;
|
|
||||||
CheckBox *save_replay_in_game_folder_ptr = nullptr;
|
CheckBox *save_replay_in_game_folder_ptr = nullptr;
|
||||||
CheckBox *restart_replay_on_save = nullptr;
|
CheckBox *restart_replay_on_save = nullptr;
|
||||||
Label *estimated_file_size_ptr = nullptr;
|
Label *estimated_file_size_ptr = nullptr;
|
||||||
@@ -192,6 +193,7 @@ namespace gsr {
|
|||||||
Entry *youtube_stream_key_entry_ptr = nullptr;
|
Entry *youtube_stream_key_entry_ptr = nullptr;
|
||||||
Entry *rumble_stream_key_entry_ptr = nullptr;
|
Entry *rumble_stream_key_entry_ptr = nullptr;
|
||||||
Entry *stream_url_entry_ptr = nullptr;
|
Entry *stream_url_entry_ptr = nullptr;
|
||||||
|
Entry *stream_key_entry_ptr = nullptr;
|
||||||
Entry *replay_time_entry_ptr = nullptr;
|
Entry *replay_time_entry_ptr = nullptr;
|
||||||
RadioButton *replay_storage_button_ptr = nullptr;
|
RadioButton *replay_storage_button_ptr = nullptr;
|
||||||
Label *replay_time_label_ptr = nullptr;
|
Label *replay_time_label_ptr = nullptr;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <mglpp/system/vec.hpp>
|
#include <mglpp/system/vec.hpp>
|
||||||
#include <mglpp/graphics/Color.hpp>
|
#include <mglpp/graphics/Color.hpp>
|
||||||
|
#include <mglpp/window/Window.hpp>
|
||||||
|
|
||||||
namespace mgl {
|
namespace mgl {
|
||||||
class Window;
|
class Window;
|
||||||
@@ -14,4 +15,5 @@ namespace gsr {
|
|||||||
void set_frame_delta_seconds(double frame_delta);
|
void set_frame_delta_seconds(double frame_delta);
|
||||||
mgl::vec2f scale_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to);
|
mgl::vec2f scale_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to);
|
||||||
mgl::vec2f clamp_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to);
|
mgl::vec2f clamp_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to);
|
||||||
|
mgl::Scissor scissor_get_sub_area(mgl::Scissor parent, mgl::Scissor child);
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,8 @@ namespace gsr {
|
|||||||
|
|
||||||
void set_visible(bool visible);
|
void set_visible(bool visible);
|
||||||
|
|
||||||
|
Widget* get_parent_widget();
|
||||||
|
|
||||||
void *userdata = nullptr;
|
void *userdata = nullptr;
|
||||||
protected:
|
protected:
|
||||||
void set_widget_as_selected_in_parent();
|
void set_widget_as_selected_in_parent();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
project('gsr-ui', ['c', 'cpp'], version : '1.6.8', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
project('gsr-ui', ['c', 'cpp'], version : '1.7.6', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||||
|
|
||||||
|
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
|
||||||
|
|
||||||
if get_option('buildtype') == 'debug'
|
if get_option('buildtype') == 'debug'
|
||||||
add_project_arguments('-g3', language : ['c', 'cpp'])
|
add_project_arguments('-g3', language : ['c', 'cpp'])
|
||||||
@@ -47,6 +49,7 @@ src = [
|
|||||||
'src/Overlay.cpp',
|
'src/Overlay.cpp',
|
||||||
'src/AudioPlayer.cpp',
|
'src/AudioPlayer.cpp',
|
||||||
'src/Hotplug.cpp',
|
'src/Hotplug.cpp',
|
||||||
|
'src/ClipboardFile.cpp',
|
||||||
'src/Rpc.cpp',
|
'src/Rpc.cpp',
|
||||||
'src/main.cpp',
|
'src/main.cpp',
|
||||||
]
|
]
|
||||||
@@ -62,7 +65,7 @@ datadir = get_option('datadir')
|
|||||||
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
|
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
|
||||||
|
|
||||||
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
|
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
|
||||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.7.3"', language: ['c', 'cpp'])
|
add_project_arguments('-DGSR_FLATPAK_VERSION="5.8.0"', language: ['c', 'cpp'])
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
meson.project_name(),
|
meson.project_name(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gsr-ui"
|
name = "gsr-ui"
|
||||||
type = "executable"
|
type = "executable"
|
||||||
version = "1.6.8"
|
version = "1.7.6"
|
||||||
platforms = ["posix"]
|
platforms = ["posix"]
|
||||||
|
|
||||||
[lang.cpp]
|
[lang.cpp]
|
||||||
@@ -10,6 +10,9 @@ version = "c++17"
|
|||||||
[config]
|
[config]
|
||||||
ignore_dirs = ["build", "tools"]
|
ignore_dirs = ["build", "tools"]
|
||||||
|
|
||||||
|
[define]
|
||||||
|
_FILE_OFFSET_BITS = "64"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
xcomposite = ">=0"
|
xcomposite = ">=0"
|
||||||
xfixes = ">=0"
|
xfixes = ">=0"
|
||||||
|
|||||||
294
src/ClipboardFile.cpp
Normal file
294
src/ClipboardFile.cpp
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
#include "../include/ClipboardFile.hpp"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
|
||||||
|
#define FORMAT_I64 "%" PRIi64
|
||||||
|
#define FORMAT_U64 "%" PRIu64
|
||||||
|
|
||||||
|
namespace gsr {
|
||||||
|
ClipboardFile::ClipboardFile() {
|
||||||
|
dpy = XOpenDisplay(nullptr);
|
||||||
|
if(!dpy) {
|
||||||
|
fprintf(stderr, "gsr ui: error: ClipboardFile: failed to connect to the X11 server\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clipboard_window = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 8, 8, 0, 0, 0);
|
||||||
|
if(!clipboard_window) {
|
||||||
|
fprintf(stderr, "gsr ui: error: ClipboardFile: failed to create clipboard window\n");
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
dpy = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
incr_atom = XInternAtom(dpy, "INCR", False);
|
||||||
|
targets_atom = XInternAtom(dpy, "TARGETS", False);
|
||||||
|
clipboard_atom = XInternAtom(dpy, "CLIPBOARD", False);
|
||||||
|
image_jpg_atom = XInternAtom(dpy, "image/jpg", False);
|
||||||
|
image_jpeg_atom = XInternAtom(dpy, "image/jpeg", False);
|
||||||
|
image_png_atom = XInternAtom(dpy, "image/png", False);
|
||||||
|
|
||||||
|
event_thread = std::thread([&]() {
|
||||||
|
pollfd poll_fds[1];
|
||||||
|
poll_fds[0].fd = ConnectionNumber(dpy);
|
||||||
|
poll_fds[0].events = POLLIN;
|
||||||
|
poll_fds[0].revents = 0;
|
||||||
|
|
||||||
|
XEvent xev;
|
||||||
|
while(running) {
|
||||||
|
poll(poll_fds, 1, 100);
|
||||||
|
while(XPending(dpy)) {
|
||||||
|
XNextEvent(dpy, &xev);
|
||||||
|
switch(xev.type) {
|
||||||
|
case SelectionClear: {
|
||||||
|
should_clear_selection = true;
|
||||||
|
if(clipboard_copies.empty()) {
|
||||||
|
should_clear_selection = false;
|
||||||
|
set_current_file("", file_type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SelectionRequest:
|
||||||
|
send_clipboard_start(&xev.xselectionrequest);
|
||||||
|
break;
|
||||||
|
case PropertyNotify: {
|
||||||
|
if(xev.xproperty.state == PropertyDelete) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
ClipboardCopy *clipboard_copy = get_clipboard_copy_by_requestor(xev.xproperty.window);
|
||||||
|
if(!clipboard_copy || xev.xproperty.atom != clipboard_copy->property)
|
||||||
|
return;
|
||||||
|
|
||||||
|
XSelectionRequestEvent xselectionrequest;
|
||||||
|
xselectionrequest.display = xev.xproperty.display;;
|
||||||
|
xselectionrequest.requestor = xev.xproperty.window;
|
||||||
|
xselectionrequest.selection = clipboard_atom;
|
||||||
|
xselectionrequest.target = clipboard_copy->requestor_target;
|
||||||
|
xselectionrequest.property = clipboard_copy->property;
|
||||||
|
xselectionrequest.time = xev.xproperty.time;
|
||||||
|
transfer_clipboard_data(&xselectionrequest, clipboard_copy);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardFile::~ClipboardFile() {
|
||||||
|
running = false;
|
||||||
|
if(event_thread.joinable())
|
||||||
|
event_thread.join();
|
||||||
|
|
||||||
|
if(file_fd > 0)
|
||||||
|
close(file_fd);
|
||||||
|
|
||||||
|
if(dpy) {
|
||||||
|
XDestroyWindow(dpy, clipboard_window);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClipboardFile::file_type_matches_request_atom(FileType file_type, Atom request_atom) {
|
||||||
|
switch(file_type) {
|
||||||
|
case FileType::JPG:
|
||||||
|
return request_atom == image_jpg_atom || request_atom == image_jpeg_atom;
|
||||||
|
case FileType::PNG:
|
||||||
|
return request_atom == image_png_atom;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ClipboardFile::file_type_clipboard_get_name(Atom request_atom) {
|
||||||
|
if(request_atom == image_jpg_atom)
|
||||||
|
return "image/jpg";
|
||||||
|
else if(request_atom == image_jpeg_atom)
|
||||||
|
return "image/jpeg";
|
||||||
|
else if(request_atom == image_png_atom)
|
||||||
|
return "image/png";
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ClipboardFile::file_type_get_name(FileType file_type) {
|
||||||
|
switch(file_type) {
|
||||||
|
case FileType::JPG:
|
||||||
|
return "image/jpeg";
|
||||||
|
case FileType::PNG:
|
||||||
|
return "image/png";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardFile::send_clipboard_start(XSelectionRequestEvent *xselectionrequest) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
if(file_fd <= 0) {
|
||||||
|
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to get clipboard from us but we don't have any clipboard file open\n", (int64_t)xselectionrequest->requestor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(xselectionrequest->selection != clipboard_atom) {
|
||||||
|
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to non-clipboard selection from us\n", (int64_t)xselectionrequest->requestor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XSelectionEvent selection_event;
|
||||||
|
selection_event.type = SelectionNotify;
|
||||||
|
selection_event.display = xselectionrequest->display;
|
||||||
|
selection_event.requestor = xselectionrequest->requestor;
|
||||||
|
selection_event.selection = xselectionrequest->selection;
|
||||||
|
selection_event.property = xselectionrequest->property;
|
||||||
|
selection_event.time = xselectionrequest->time;
|
||||||
|
selection_event.target = xselectionrequest->target;
|
||||||
|
|
||||||
|
if(xselectionrequest->target == targets_atom) {
|
||||||
|
int num_targets = 1;
|
||||||
|
Atom targets[3];
|
||||||
|
targets[0] = targets_atom;
|
||||||
|
|
||||||
|
switch(file_type) {
|
||||||
|
case FileType::JPG:
|
||||||
|
num_targets = 3;
|
||||||
|
targets[1] = image_jpg_atom;
|
||||||
|
targets[2] = image_jpeg_atom;
|
||||||
|
break;
|
||||||
|
case FileType::PNG:
|
||||||
|
num_targets = 2;
|
||||||
|
targets[1] = image_png_atom;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
XChangeProperty(dpy, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets);
|
||||||
|
} else if(xselectionrequest->target == image_jpg_atom || xselectionrequest->target == image_jpeg_atom || xselectionrequest->target == image_png_atom) {
|
||||||
|
if(!file_type_matches_request_atom(file_type, xselectionrequest->target)) {
|
||||||
|
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to request clipboard of type %s, but %s was expected\n", (int64_t)xselectionrequest->requestor, file_type_clipboard_get_name(xselectionrequest->target), file_type_get_name(file_type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardCopy *clipboard_copy = get_clipboard_copy_by_requestor(xselectionrequest->requestor);
|
||||||
|
if(!clipboard_copy) {
|
||||||
|
clipboard_copies.push_back({ xselectionrequest->requestor, 0 });
|
||||||
|
clipboard_copy = &clipboard_copies.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
*clipboard_copy = { xselectionrequest->requestor, 0 };
|
||||||
|
clipboard_copy->property = selection_event.property;
|
||||||
|
clipboard_copy->requestor_target = selection_event.target;
|
||||||
|
XSelectInput(dpy, selection_event.requestor, PropertyChangeMask);
|
||||||
|
|
||||||
|
const long lower_bound = std::min((uint64_t)1<<16, file_size);
|
||||||
|
XChangeProperty(dpy, selection_event.requestor, selection_event.property, incr_atom, 32, PropModeReplace, (const unsigned char*)&lower_bound, 1);
|
||||||
|
} else {
|
||||||
|
char *target_clipboard_name = XGetAtomName(dpy, xselectionrequest->target);
|
||||||
|
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to request clipboard of type %s, expected TARGETS, image/jpg, image/jpeg or image/png\n", (int64_t)xselectionrequest->requestor, target_clipboard_name ? target_clipboard_name : "Unknown");
|
||||||
|
if(target_clipboard_name)
|
||||||
|
XFree(target_clipboard_name);
|
||||||
|
selection_event.property = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
XSendEvent(dpy, selection_event.requestor, False, NoEventMask, (XEvent*)&selection_event);
|
||||||
|
XFlush(dpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardFile::transfer_clipboard_data(XSelectionRequestEvent *xselectionrequest, ClipboardCopy *clipboard_copy) {
|
||||||
|
uint8_t file_buffer[1<<16];
|
||||||
|
ssize_t file_bytes_read = 0;
|
||||||
|
|
||||||
|
if(lseek(file_fd, clipboard_copy->file_offset, SEEK_SET) == -1) {
|
||||||
|
fprintf(stderr, "gsr ui: error: ClipboardFile::send_clipboard: failed to seek in clipboard file to offset " FORMAT_U64 " for requestor window " FORMAT_I64 ", error: %s\n", (uint64_t)clipboard_copy->file_offset, (int64_t)xselectionrequest->requestor, strerror(errno));
|
||||||
|
clipboard_copy->file_offset = 0;
|
||||||
|
// TODO: Cancel transfer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_bytes_read = read(file_fd, file_buffer, sizeof(file_buffer));
|
||||||
|
if(file_bytes_read < 0) {
|
||||||
|
fprintf(stderr, "gsr ui: error: ClipbaordFile::send_clipboard: failed to read data from offset " FORMAT_U64 " for requestor window " FORMAT_I64 ", error: %s\n", (uint64_t)clipboard_copy->file_offset, (int64_t)xselectionrequest->requestor, strerror(errno));
|
||||||
|
clipboard_copy->file_offset = 0;
|
||||||
|
// TODO: Cancel transfer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XChangeProperty(dpy, xselectionrequest->requestor, xselectionrequest->property, xselectionrequest->target, 8, PropModeReplace, (const unsigned char*)file_buffer, file_bytes_read);
|
||||||
|
XSendEvent(dpy, xselectionrequest->requestor, False, NoEventMask, (XEvent*)xselectionrequest);
|
||||||
|
XFlush(dpy);
|
||||||
|
|
||||||
|
clipboard_copy->file_offset += file_bytes_read;
|
||||||
|
if(file_bytes_read == 0)
|
||||||
|
remove_clipboard_copy(clipboard_copy->requestor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardCopy* ClipboardFile::get_clipboard_copy_by_requestor(Window requestor) {
|
||||||
|
for(ClipboardCopy &clipboard_copy : clipboard_copies) {
|
||||||
|
if(clipboard_copy.requestor == requestor)
|
||||||
|
return &clipboard_copy;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardFile::remove_clipboard_copy(Window requestor) {
|
||||||
|
for(auto it = clipboard_copies.begin(), end = clipboard_copies.end(); it != end; ++it) {
|
||||||
|
if(it->requestor == requestor) {
|
||||||
|
clipboard_copies.erase(it);
|
||||||
|
XSelectInput(dpy, requestor, 0);
|
||||||
|
|
||||||
|
if(clipboard_copies.empty() && should_clear_selection) {
|
||||||
|
should_clear_selection = false;
|
||||||
|
set_current_file("", file_type);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardFile::set_current_file(const std::string &filepath, FileType file_type) {
|
||||||
|
if(!dpy)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
for(ClipboardCopy &clipboard_copy : clipboard_copies) {
|
||||||
|
XSelectInput(dpy, clipboard_copy.requestor, 0);
|
||||||
|
}
|
||||||
|
clipboard_copies.clear();
|
||||||
|
|
||||||
|
if(filepath.empty()) {
|
||||||
|
if(file_fd > 0) {
|
||||||
|
close(file_fd);
|
||||||
|
file_fd = -1;
|
||||||
|
}
|
||||||
|
file_size = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(file_fd > 0) {
|
||||||
|
close(file_fd);
|
||||||
|
file_fd = -1;
|
||||||
|
file_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_fd = open(filepath.c_str(), O_RDONLY);
|
||||||
|
if(file_fd <= 0) {
|
||||||
|
fprintf(stderr, "gsr ui: error: ClipboardFile::set_current_file: failed to open file %s, error: %s\n", filepath.c_str(), strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat64 stat;
|
||||||
|
if(fstat64(file_fd, &stat) == -1) {
|
||||||
|
fprintf(stderr, "gsr ui: error: ClipboardFile::set_current_file: failed to get file size for file %s, error: %s\n", filepath.c_str(), strerror(errno));
|
||||||
|
close(file_fd);
|
||||||
|
file_fd = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file_size = stat.st_size;
|
||||||
|
this->file_type = file_type;
|
||||||
|
|
||||||
|
XSetSelectionOwner(dpy, clipboard_atom, clipboard_window, CurrentTime);
|
||||||
|
XFlush(dpy);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,6 +153,7 @@ namespace gsr {
|
|||||||
|
|
||||||
screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Printscreen, 0};
|
screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Printscreen, 0};
|
||||||
screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LCTRL};
|
screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LCTRL};
|
||||||
|
screenshot_config.take_screenshot_window_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LSHIFT};
|
||||||
|
|
||||||
main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
|
main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
|
||||||
}
|
}
|
||||||
@@ -173,6 +174,7 @@ namespace gsr {
|
|||||||
{"main.hotkeys_enable_option", &config.main_config.hotkeys_enable_option},
|
{"main.hotkeys_enable_option", &config.main_config.hotkeys_enable_option},
|
||||||
{"main.joystick_hotkeys_enable_option", &config.main_config.joystick_hotkeys_enable_option},
|
{"main.joystick_hotkeys_enable_option", &config.main_config.joystick_hotkeys_enable_option},
|
||||||
{"main.tint_color", &config.main_config.tint_color},
|
{"main.tint_color", &config.main_config.tint_color},
|
||||||
|
{"main.notification_speed", &config.main_config.notification_speed},
|
||||||
{"main.show_hide_hotkey", &config.main_config.show_hide_hotkey},
|
{"main.show_hide_hotkey", &config.main_config.show_hide_hotkey},
|
||||||
|
|
||||||
{"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option},
|
{"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option},
|
||||||
@@ -203,6 +205,7 @@ namespace gsr {
|
|||||||
{"streaming.twitch.key", &config.streaming_config.twitch.stream_key},
|
{"streaming.twitch.key", &config.streaming_config.twitch.stream_key},
|
||||||
{"streaming.rumble.key", &config.streaming_config.rumble.stream_key},
|
{"streaming.rumble.key", &config.streaming_config.rumble.stream_key},
|
||||||
{"streaming.custom.url", &config.streaming_config.custom.url},
|
{"streaming.custom.url", &config.streaming_config.custom.url},
|
||||||
|
{"streaming.custom.key", &config.streaming_config.custom.key},
|
||||||
{"streaming.custom.container", &config.streaming_config.custom.container},
|
{"streaming.custom.container", &config.streaming_config.custom.container},
|
||||||
{"streaming.start_stop_hotkey", &config.streaming_config.start_stop_hotkey},
|
{"streaming.start_stop_hotkey", &config.streaming_config.start_stop_hotkey},
|
||||||
|
|
||||||
@@ -281,10 +284,12 @@ namespace gsr {
|
|||||||
{"screenshot.record_cursor", &config.screenshot_config.record_cursor},
|
{"screenshot.record_cursor", &config.screenshot_config.record_cursor},
|
||||||
{"screenshot.restore_portal_session", &config.screenshot_config.restore_portal_session},
|
{"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.save_screenshot_in_game_folder", &config.screenshot_config.save_screenshot_in_game_folder},
|
||||||
|
{"screenshot.save_screenshot_to_clipboard", &config.screenshot_config.save_screenshot_to_clipboard},
|
||||||
{"screenshot.show_screenshot_saved_notifications", &config.screenshot_config.show_screenshot_saved_notifications},
|
{"screenshot.show_screenshot_saved_notifications", &config.screenshot_config.show_screenshot_saved_notifications},
|
||||||
{"screenshot.save_directory", &config.screenshot_config.save_directory},
|
{"screenshot.save_directory", &config.screenshot_config.save_directory},
|
||||||
{"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey},
|
{"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey},
|
||||||
{"screenshot.take_screenshot_region_hotkey", &config.screenshot_config.take_screenshot_region_hotkey}
|
{"screenshot.take_screenshot_region_hotkey", &config.screenshot_config.take_screenshot_region_hotkey},
|
||||||
|
{"screenshot.take_screenshot_window_hotkey", &config.screenshot_config.take_screenshot_window_hotkey}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ namespace gsr {
|
|||||||
close(event_fd);
|
close(event_fd);
|
||||||
|
|
||||||
for(int i = 0; i < num_poll_fd; ++i) {
|
for(int i = 0; i < num_poll_fd; ++i) {
|
||||||
close(poll_fd[i].fd);
|
if(poll_fd[i].fd > 0)
|
||||||
|
close(poll_fd[i].fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +222,9 @@ namespace gsr {
|
|||||||
if(i == event_index)
|
if(i == event_index)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
|
char dev_input_filepath[256];
|
||||||
|
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/js%d", extra_data[i].dev_input_id);
|
||||||
|
fprintf(stderr, "Info: removed joystick: %s\n", dev_input_filepath);
|
||||||
if(remove_poll_fd(i))
|
if(remove_poll_fd(i))
|
||||||
--i; // This item was removed so we want to repeat the same index to continue to the next item
|
--i; // This item was removed so we want to repeat the same index to continue to the next item
|
||||||
|
|
||||||
@@ -234,18 +238,13 @@ namespace gsr {
|
|||||||
goto done;
|
goto done;
|
||||||
} else if(i == hotplug_poll_index) {
|
} else if(i == hotplug_poll_index) {
|
||||||
hotplug.process_event_data(poll_fd[i].fd, [&](HotplugAction hotplug_action, const char *devname) {
|
hotplug.process_event_data(poll_fd[i].fd, [&](HotplugAction hotplug_action, const char *devname) {
|
||||||
char dev_input_filepath[1024];
|
|
||||||
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/%s", devname);
|
|
||||||
switch(hotplug_action) {
|
switch(hotplug_action) {
|
||||||
case HotplugAction::ADD: {
|
case HotplugAction::ADD: {
|
||||||
// Cant open the /dev/input device immediately or it fails.
|
add_device(devname);
|
||||||
// TODO: Remove this hack when a better solution is found.
|
|
||||||
usleep(50 * 1000);
|
|
||||||
add_device(dev_input_filepath);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HotplugAction::REMOVE: {
|
case HotplugAction::REMOVE: {
|
||||||
if(remove_device(dev_input_filepath))
|
if(remove_device(devname))
|
||||||
--i; // This item was removed so we want to repeat the same index to continue to the next item
|
--i; // This item was removed so we want to repeat the same index to continue to the next item
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -373,7 +372,9 @@ namespace gsr {
|
|||||||
if(index < 0 || index >= num_poll_fd)
|
if(index < 0 || index >= num_poll_fd)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
close(poll_fd[index].fd);
|
if(poll_fd[index].fd > 0)
|
||||||
|
close(poll_fd[index].fd);
|
||||||
|
|
||||||
for(int i = index + 1; i < num_poll_fd; ++i) {
|
for(int i = index + 1; i < num_poll_fd; ++i) {
|
||||||
poll_fd[i - 1] = poll_fd[i];
|
poll_fd[i - 1] = poll_fd[i];
|
||||||
extra_data[i - 1] = extra_data[i];
|
extra_data[i - 1] = extra_data[i];
|
||||||
|
|||||||
@@ -59,10 +59,9 @@ namespace gsr {
|
|||||||
|
|
||||||
/* TODO: This assumes SUBSYSTEM= is output before DEVNAME=, is that always true? */
|
/* TODO: This assumes SUBSYSTEM= is output before DEVNAME=, is that always true? */
|
||||||
void Hotplug::parse_netlink_data(const char *line, const HotplugEventCallback &callback) {
|
void Hotplug::parse_netlink_data(const char *line, const HotplugEventCallback &callback) {
|
||||||
const char *at_symbol = strchr(line, '@');
|
if(strncmp(line, "ACTION=", 7) == 0) {
|
||||||
if(at_symbol) {
|
event_is_add = strncmp(line+7, "add", 3) == 0;
|
||||||
event_is_add = strncmp(line, "add@", 4) == 0;
|
event_is_remove = strncmp(line+7, "remove", 6) == 0;
|
||||||
event_is_remove = strncmp(line, "remove@", 7) == 0;
|
|
||||||
subsystem_is_input = false;
|
subsystem_is_input = false;
|
||||||
} else if(event_is_add || event_is_remove) {
|
} else if(event_is_add || event_is_remove) {
|
||||||
if(strcmp(line, "SUBSYSTEM=input") == 0)
|
if(strcmp(line, "SUBSYSTEM=input") == 0)
|
||||||
|
|||||||
294
src/Overlay.cpp
294
src/Overlay.cpp
@@ -27,6 +27,7 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/Xutil.h>
|
#include <X11/Xutil.h>
|
||||||
@@ -49,7 +50,8 @@ namespace gsr {
|
|||||||
static const double force_window_on_top_timeout_seconds = 1.0;
|
static const double force_window_on_top_timeout_seconds = 1.0;
|
||||||
static const double replay_status_update_check_timeout_seconds = 1.5;
|
static const double replay_status_update_check_timeout_seconds = 1.5;
|
||||||
static const double replay_saving_notification_timeout_seconds = 0.5;
|
static const double replay_saving_notification_timeout_seconds = 0.5;
|
||||||
static const double notification_timeout_seconds = 2.5;
|
static const double short_notification_timeout_seconds = 2.0;
|
||||||
|
static const double notification_timeout_seconds = 3.0;
|
||||||
static const double notification_error_timeout_seconds = 5.0;
|
static const double notification_error_timeout_seconds = 5.0;
|
||||||
static const double cursor_tracker_update_timeout_sec = 0.1;
|
static const double cursor_tracker_update_timeout_sec = 0.1;
|
||||||
|
|
||||||
@@ -378,6 +380,13 @@ namespace gsr {
|
|||||||
overlay->take_screenshot_region();
|
overlay->take_screenshot_region();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
global_hotkeys->bind_key_press(
|
||||||
|
config_hotkey_to_hotkey(overlay->get_config().screenshot_config.take_screenshot_window_hotkey),
|
||||||
|
"take_screenshot_window", [overlay](const std::string &id) {
|
||||||
|
fprintf(stderr, "pressed %s\n", id.c_str());
|
||||||
|
overlay->take_screenshot_window();
|
||||||
|
});
|
||||||
|
|
||||||
global_hotkeys->bind_key_press(
|
global_hotkeys->bind_key_press(
|
||||||
config_hotkey_to_hotkey(ConfigHotkey{ mgl::Keyboard::Key::Escape, HOTKEY_MOD_LCTRL | HOTKEY_MOD_LSHIFT | HOTKEY_MOD_LALT }),
|
config_hotkey_to_hotkey(ConfigHotkey{ mgl::Keyboard::Key::Escape, HOTKEY_MOD_LCTRL | HOTKEY_MOD_LSHIFT | HOTKEY_MOD_LALT }),
|
||||||
"exit", [overlay](const std::string &id) {
|
"exit", [overlay](const std::string &id) {
|
||||||
@@ -438,6 +447,17 @@ namespace gsr {
|
|||||||
return global_hotkeys_js;
|
return global_hotkeys_js;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NotificationSpeed to_notification_speed(const std::string ¬ification_speed_str) {
|
||||||
|
if(notification_speed_str == "normal")
|
||||||
|
return NotificationSpeed::NORMAL;
|
||||||
|
else if(notification_speed_str == "fast")
|
||||||
|
return NotificationSpeed::FAST;
|
||||||
|
else {
|
||||||
|
assert(false);
|
||||||
|
return NotificationSpeed::NORMAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs) :
|
Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs) :
|
||||||
resources_path(std::move(resources_path)),
|
resources_path(std::move(resources_path)),
|
||||||
gsr_info(std::move(gsr_info)),
|
gsr_info(std::move(gsr_info)),
|
||||||
@@ -466,6 +486,7 @@ namespace gsr {
|
|||||||
|
|
||||||
power_supply_online_filepath = get_power_supply_online_filepath();
|
power_supply_online_filepath = get_power_supply_online_filepath();
|
||||||
replay_startup_mode = replay_startup_string_to_type(config.replay_config.turn_on_replay_automatically_mode.c_str());
|
replay_startup_mode = replay_startup_string_to_type(config.replay_config.turn_on_replay_automatically_mode.c_str());
|
||||||
|
set_notification_speed(to_notification_speed(config.main_config.notification_speed));
|
||||||
|
|
||||||
if(config.main_config.hotkeys_enable_option == "enable_hotkeys")
|
if(config.main_config.hotkeys_enable_option == "enable_hotkeys")
|
||||||
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
|
global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL);
|
||||||
@@ -758,6 +779,12 @@ namespace gsr {
|
|||||||
update_gsr_screenshot_process_status();
|
update_gsr_screenshot_process_status();
|
||||||
replay_status_update_status();
|
replay_status_update_status();
|
||||||
|
|
||||||
|
if(hide_ui) {
|
||||||
|
hide_ui = false;
|
||||||
|
hide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(start_region_capture) {
|
if(start_region_capture) {
|
||||||
start_region_capture = false;
|
start_region_capture = false;
|
||||||
hide();
|
hide();
|
||||||
@@ -932,6 +959,7 @@ namespace gsr {
|
|||||||
const bool is_kwin = wm_name == "KWin";
|
const bool is_kwin = wm_name == "KWin";
|
||||||
const bool is_wlroots = wm_name.find("wlroots") != std::string::npos;
|
const bool is_wlroots = wm_name.find("wlroots") != std::string::npos;
|
||||||
const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos;
|
const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos;
|
||||||
|
//const bool is_smithay = wm_name.find("Smithay") != std::string::npos;
|
||||||
const bool hyprland_waybar_is_dock = is_hyprland && is_hyprland_waybar_running_as_dock();
|
const bool hyprland_waybar_is_dock = is_hyprland && is_hyprland_waybar_running_as_dock();
|
||||||
|
|
||||||
std::optional<CursorInfo> cursor_info;
|
std::optional<CursorInfo> cursor_info;
|
||||||
@@ -959,8 +987,7 @@ namespace gsr {
|
|||||||
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
|
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
|
||||||
// If the focused window is a wayland application then don't use override redirect and instead create
|
// If the focused window is a wayland application then don't use override redirect and instead create
|
||||||
// a fullscreen window for the ui.
|
// a fullscreen window for the ui.
|
||||||
// TODO: (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor))
|
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor)) || is_wlroots || is_hyprland;
|
||||||
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots || is_hyprland;
|
|
||||||
|
|
||||||
if(prevent_game_minimizing) {
|
if(prevent_game_minimizing) {
|
||||||
window_pos = focused_monitor->position;
|
window_pos = focused_monitor->position;
|
||||||
@@ -1338,6 +1365,8 @@ namespace gsr {
|
|||||||
if(!visible)
|
if(!visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
hide_ui = false;
|
||||||
|
|
||||||
mgl_context *context = mgl_get_context();
|
mgl_context *context = mgl_get_context();
|
||||||
Display *display = (Display*)context->connection;
|
Display *display = (Display*)context->connection;
|
||||||
|
|
||||||
@@ -1473,11 +1502,15 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Overlay::take_screenshot() {
|
void Overlay::take_screenshot() {
|
||||||
on_press_take_screenshot(false, false);
|
on_press_take_screenshot(false, ScreenshotForceType::NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Overlay::take_screenshot_region() {
|
void Overlay::take_screenshot_region() {
|
||||||
on_press_take_screenshot(false, true);
|
on_press_take_screenshot(false, ScreenshotForceType::REGION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlay::take_screenshot_window() {
|
||||||
|
on_press_take_screenshot(false, ScreenshotForceType::WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* notification_type_to_string(NotificationType notification_type) {
|
static const char* notification_type_to_string(NotificationType notification_type) {
|
||||||
@@ -1554,7 +1587,7 @@ namespace gsr {
|
|||||||
return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
|
return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string capture_target_get_notification_name(const char *capture_target) {
|
static std::string capture_target_get_notification_name(const char *capture_target, bool save) {
|
||||||
std::string result;
|
std::string result;
|
||||||
if(is_capture_target_monitor(capture_target)) {
|
if(is_capture_target_monitor(capture_target)) {
|
||||||
result = "this monitor";
|
result = "this monitor";
|
||||||
@@ -1566,9 +1599,11 @@ namespace gsr {
|
|||||||
sscanf(capture_target, "%" PRIi64, &window_id);
|
sscanf(capture_target, "%" PRIi64, &window_id);
|
||||||
|
|
||||||
const std::optional<std::string> window_title = get_window_title(display, window_id);
|
const std::optional<std::string> window_title = get_window_title(display, window_id);
|
||||||
if(window_title) {
|
if(save) {
|
||||||
|
result = "window";
|
||||||
|
} else if(window_title) {
|
||||||
result = strip(window_title.value());
|
result = strip(window_title.value());
|
||||||
truncate_string(result, 20);
|
truncate_string(result, 30);
|
||||||
result = "window \"" + result + "\"";
|
result = "window \"" + result + "\"";
|
||||||
} else {
|
} else {
|
||||||
result = std::string("window ") + capture_target;
|
result = std::string("window ") + capture_target;
|
||||||
@@ -1623,6 +1658,8 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Overlay::show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target) {
|
void Overlay::show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target) {
|
||||||
|
timeout_seconds *= notification_duration_multiplier;
|
||||||
|
|
||||||
char timeout_seconds_str[32];
|
char timeout_seconds_str[32];
|
||||||
snprintf(timeout_seconds_str, sizeof(timeout_seconds_str), "%f", timeout_seconds);
|
snprintf(timeout_seconds_str, sizeof(timeout_seconds_str), "%f", timeout_seconds);
|
||||||
|
|
||||||
@@ -1715,6 +1752,17 @@ namespace gsr {
|
|||||||
bind_linux_hotkeys(static_cast<GlobalHotkeysLinux*>(global_hotkeys.get()), this);
|
bind_linux_hotkeys(static_cast<GlobalHotkeysLinux*>(global_hotkeys.get()), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Overlay::set_notification_speed(NotificationSpeed notification_speed) {
|
||||||
|
switch(notification_speed) {
|
||||||
|
case NotificationSpeed::NORMAL:
|
||||||
|
notification_duration_multiplier = 1.0;
|
||||||
|
break;
|
||||||
|
case NotificationSpeed::FAST:
|
||||||
|
notification_duration_multiplier = 0.3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Overlay::update_notification_process_status() {
|
void Overlay::update_notification_process_status() {
|
||||||
if(notification_process <= 0)
|
if(notification_process <= 0)
|
||||||
return;
|
return;
|
||||||
@@ -1755,6 +1803,54 @@ namespace gsr {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string to_duration_string(double duration_sec) {
|
||||||
|
int seconds = ceil(duration_sec);
|
||||||
|
|
||||||
|
const int hours = seconds / 60 / 60;
|
||||||
|
seconds -= (hours * 60 * 60);
|
||||||
|
|
||||||
|
const int minutes = seconds / 60;
|
||||||
|
seconds -= (minutes * 60);
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
if(hours > 0)
|
||||||
|
result += std::to_string(hours) + " hour" + (hours == 1 ? "" : "s");
|
||||||
|
|
||||||
|
if(minutes > 0) {
|
||||||
|
if(!result.empty())
|
||||||
|
result += " ";
|
||||||
|
result += std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(seconds > 0 || (hours == 0 && minutes == 0)) {
|
||||||
|
if(!result.empty())
|
||||||
|
result += " ";
|
||||||
|
result += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "to duration string: %f, %d, %d, %d\n", duration_sec, seconds, minutes, hours);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Overlay::get_time_passed_in_replay_buffer_seconds() {
|
||||||
|
double replay_duration_sec = replay_saved_duration_sec;
|
||||||
|
if(replay_duration_sec > replay_buffer_save_duration_sec)
|
||||||
|
replay_duration_sec = replay_buffer_save_duration_sec;
|
||||||
|
if(replay_save_duration_min > 0 && replay_duration_sec > replay_save_duration_min * 60)
|
||||||
|
replay_duration_sec = replay_save_duration_min * 60;
|
||||||
|
return replay_duration_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClipboardFile::FileType filename_to_clipboard_file_type(const std::string &filename) {
|
||||||
|
if(ends_with(filename, ".jpg") || ends_with(filename, ".jpeg"))
|
||||||
|
return ClipboardFile::FileType::JPG;
|
||||||
|
else if(ends_with(filename, ".png"))
|
||||||
|
return ClipboardFile::FileType::PNG;
|
||||||
|
assert(false);
|
||||||
|
return ClipboardFile::FileType::PNG;
|
||||||
|
}
|
||||||
|
|
||||||
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
|
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
|
||||||
mgl_context *context = mgl_get_context();
|
mgl_context *context = mgl_get_context();
|
||||||
Display *display = (Display*)context->connection;
|
Display *display = (Display*)context->connection;
|
||||||
@@ -1775,7 +1871,7 @@ namespace gsr {
|
|||||||
const std::string new_video_filepath = video_directory + "/" + video_filename;
|
const std::string new_video_filepath = video_directory + "/" + video_filename;
|
||||||
rename(video_filepath, new_video_filepath.c_str());
|
rename(video_filepath, new_video_filepath.c_str());
|
||||||
|
|
||||||
truncate_string(focused_window_name, 20);
|
truncate_string(focused_window_name, 40);
|
||||||
const char *capture_target = nullptr;
|
const char *capture_target = nullptr;
|
||||||
char msg[512];
|
char msg[512];
|
||||||
|
|
||||||
@@ -1784,7 +1880,10 @@ namespace gsr {
|
|||||||
if(!config.record_config.show_video_saved_notifications)
|
if(!config.record_config.show_video_saved_notifications)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
|
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds());
|
||||||
|
snprintf(msg, sizeof(msg), "Saved a %s recording of %s\nto \"%s\"",
|
||||||
|
duration_str.c_str(),
|
||||||
|
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
|
||||||
capture_target = recording_capture_target.c_str();
|
capture_target = recording_capture_target.c_str();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1792,13 +1891,10 @@ namespace gsr {
|
|||||||
if(!config.replay_config.show_replay_saved_notifications)
|
if(!config.replay_config.show_replay_saved_notifications)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char duration[32];
|
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
|
||||||
if(replay_save_duration_min > 0)
|
snprintf(msg, sizeof(msg), "Saved a %s replay of %s\nto \"%s\"",
|
||||||
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
|
duration_str.c_str(),
|
||||||
else
|
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
|
||||||
snprintf(duration, sizeof(duration), " ");
|
|
||||||
|
|
||||||
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
|
|
||||||
capture_target = recording_capture_target.c_str();
|
capture_target = recording_capture_target.c_str();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1806,8 +1902,12 @@ namespace gsr {
|
|||||||
if(!config.screenshot_config.show_screenshot_saved_notifications)
|
if(!config.screenshot_config.show_screenshot_saved_notifications)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str(), focused_window_name.c_str());
|
snprintf(msg, sizeof(msg), "Saved a screenshot of %s\nto \"%s\"",
|
||||||
|
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
|
||||||
capture_target = screenshot_capture_target.c_str();
|
capture_target = screenshot_capture_target.c_str();
|
||||||
|
|
||||||
|
if(config.screenshot_config.save_screenshot_to_clipboard)
|
||||||
|
clipboard_file.set_current_file(new_video_filepath, filename_to_clipboard_file_type(new_video_filepath));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NotificationType::NONE:
|
case NotificationType::NONE:
|
||||||
@@ -1832,14 +1932,12 @@ namespace gsr {
|
|||||||
if(config.replay_config.save_video_in_game_folder) {
|
if(config.replay_config.save_video_in_game_folder) {
|
||||||
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
|
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
|
||||||
} else if(config.replay_config.show_replay_saved_notifications) {
|
} else if(config.replay_config.show_replay_saved_notifications) {
|
||||||
char duration[32];
|
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
|
||||||
if(replay_save_duration_min > 0)
|
|
||||||
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
|
|
||||||
else
|
|
||||||
snprintf(duration, sizeof(duration), " ");
|
|
||||||
|
|
||||||
char msg[512];
|
char msg[512];
|
||||||
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
snprintf(msg, sizeof(msg), "Saved a %s replay of %s",
|
||||||
|
duration_str.c_str(),
|
||||||
|
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str());
|
||||||
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
|
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1891,14 +1989,24 @@ namespace gsr {
|
|||||||
void Overlay::on_gsr_process_error(int exit_code, NotificationType notification_type) {
|
void Overlay::on_gsr_process_error(int exit_code, NotificationType notification_type) {
|
||||||
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
|
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
|
||||||
if(exit_code == 50) {
|
if(exit_code == 50) {
|
||||||
show_notification("Desktop portal capture failed.\nEither you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture\nor it's incorrectly setup on your system", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
show_notification("Desktop portal capture failed.\nEither you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture\nor it's incorrectly setup on your system.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||||
|
} else if(exit_code == 51) {
|
||||||
|
show_notification("Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||||
|
} else if(exit_code == 52) {
|
||||||
|
show_notification("Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||||
|
} else if(exit_code == 53) {
|
||||||
|
show_notification("Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||||
|
} else if(exit_code == 54) {
|
||||||
|
show_notification("Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||||
} else if(exit_code == 60) {
|
} else if(exit_code == 60) {
|
||||||
show_notification("Stopped capture because the user canceled the desktop portal", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
show_notification("Stopped capture because the user canceled the desktop portal", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||||
} else {
|
} else {
|
||||||
const char *prefix = "";
|
const char *prefix = "";
|
||||||
switch(notification_type) {
|
switch(notification_type) {
|
||||||
case NotificationType::NONE:
|
case NotificationType::NONE:
|
||||||
|
break;
|
||||||
case NotificationType::SCREENSHOT:
|
case NotificationType::SCREENSHOT:
|
||||||
|
prefix = "Failed to take a screenshot";
|
||||||
break;
|
break;
|
||||||
case NotificationType::RECORD:
|
case NotificationType::RECORD:
|
||||||
prefix = "Failed to start/save recording";
|
prefix = "Failed to start/save recording";
|
||||||
@@ -1941,7 +2049,7 @@ namespace gsr {
|
|||||||
update_ui_replay_stopped();
|
update_ui_replay_stopped();
|
||||||
if(exit_code == 0) {
|
if(exit_code == 0) {
|
||||||
if(config.replay_config.show_replay_stopped_notifications)
|
if(config.replay_config.show_replay_stopped_notifications)
|
||||||
show_notification("Replay stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
|
show_notification("Replay stopped", short_notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
|
||||||
} else {
|
} else {
|
||||||
on_gsr_process_error(exit_code, NotificationType::REPLAY);
|
on_gsr_process_error(exit_code, NotificationType::REPLAY);
|
||||||
}
|
}
|
||||||
@@ -1956,7 +2064,7 @@ namespace gsr {
|
|||||||
update_ui_streaming_stopped();
|
update_ui_streaming_stopped();
|
||||||
if(exit_code == 0) {
|
if(exit_code == 0) {
|
||||||
if(config.streaming_config.show_streaming_stopped_notifications)
|
if(config.streaming_config.show_streaming_stopped_notifications)
|
||||||
show_notification("Streaming has stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
|
show_notification("Streaming has stopped", short_notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
|
||||||
} else {
|
} else {
|
||||||
on_gsr_process_error(exit_code, NotificationType::STREAM);
|
on_gsr_process_error(exit_code, NotificationType::STREAM);
|
||||||
}
|
}
|
||||||
@@ -1987,8 +2095,12 @@ namespace gsr {
|
|||||||
save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT);
|
save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT);
|
||||||
} else if(config.screenshot_config.show_screenshot_saved_notifications) {
|
} else if(config.screenshot_config.show_screenshot_saved_notifications) {
|
||||||
char msg[512];
|
char msg[512];
|
||||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str());
|
snprintf(msg, sizeof(msg), "Saved a screenshot of %s",
|
||||||
|
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str());
|
||||||
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str());
|
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str());
|
||||||
|
|
||||||
|
if(config.screenshot_config.save_screenshot_to_clipboard)
|
||||||
|
clipboard_file.set_current_file(screenshot_filepath, filename_to_clipboard_file_type(screenshot_filepath));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_screenshot_process, exit_code);
|
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_screenshot_process, exit_code);
|
||||||
@@ -2085,8 +2197,12 @@ namespace gsr {
|
|||||||
if(config.record_config.save_video_in_game_folder) {
|
if(config.record_config.save_video_in_game_folder) {
|
||||||
save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
|
save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
|
||||||
} else if(config.record_config.show_video_saved_notifications) {
|
} else if(config.record_config.show_video_saved_notifications) {
|
||||||
|
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds());
|
||||||
|
|
||||||
char msg[512];
|
char msg[512];
|
||||||
snprintf(msg, sizeof(msg), "Saved a recording of %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
snprintf(msg, sizeof(msg), "Saved a %s recording of %s",
|
||||||
|
duration_str.c_str(),
|
||||||
|
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str());
|
||||||
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
|
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2375,6 +2491,9 @@ namespace gsr {
|
|||||||
replay_save_duration_min = 0;
|
replay_save_duration_min = 0;
|
||||||
replay_save_show_notification = true;
|
replay_save_show_notification = true;
|
||||||
replay_save_clock.restart();
|
replay_save_clock.restart();
|
||||||
|
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
|
||||||
|
if(replay_restart_on_save)
|
||||||
|
replay_duration_clock.restart();
|
||||||
kill(gpu_screen_recorder_process, SIGUSR1);
|
kill(gpu_screen_recorder_process, SIGUSR1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2385,6 +2504,7 @@ namespace gsr {
|
|||||||
replay_save_duration_min = 1;
|
replay_save_duration_min = 1;
|
||||||
replay_save_show_notification = true;
|
replay_save_show_notification = true;
|
||||||
replay_save_clock.restart();
|
replay_save_clock.restart();
|
||||||
|
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
|
||||||
kill(gpu_screen_recorder_process, SIGRTMIN+3);
|
kill(gpu_screen_recorder_process, SIGRTMIN+3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2395,10 +2515,11 @@ namespace gsr {
|
|||||||
replay_save_duration_min = 10;
|
replay_save_duration_min = 10;
|
||||||
replay_save_show_notification = true;
|
replay_save_show_notification = true;
|
||||||
replay_save_clock.restart();
|
replay_save_clock.restart();
|
||||||
|
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
|
||||||
kill(gpu_screen_recorder_process, SIGRTMIN+5);
|
kill(gpu_screen_recorder_process, SIGRTMIN+5);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* switch_video_codec_to_usable_hardware_encoder(const GsrInfo &gsr_info) {
|
static const char* get_first_usable_hardware_video_codec_name(const GsrInfo &gsr_info) {
|
||||||
if(gsr_info.supported_video_codecs.h264)
|
if(gsr_info.supported_video_codecs.h264)
|
||||||
return "h264";
|
return "h264";
|
||||||
else if(gsr_info.supported_video_codecs.hevc)
|
else if(gsr_info.supported_video_codecs.hevc)
|
||||||
@@ -2431,8 +2552,7 @@ namespace gsr {
|
|||||||
*video_codec = "h264";
|
*video_codec = "h264";
|
||||||
*encoder = "cpu";
|
*encoder = "cpu";
|
||||||
} else if(strcmp(*video_codec, "auto") == 0) {
|
} else if(strcmp(*video_codec, "auto") == 0) {
|
||||||
*video_codec = switch_video_codec_to_usable_hardware_encoder(gsr_info);
|
if(!get_first_usable_hardware_video_codec_name(gsr_info)) {
|
||||||
if(!*video_codec) {
|
|
||||||
*video_codec = "h264";
|
*video_codec = "h264";
|
||||||
*encoder = "cpu";
|
*encoder = "cpu";
|
||||||
}
|
}
|
||||||
@@ -2440,6 +2560,14 @@ namespace gsr {
|
|||||||
*container = change_container_if_codec_not_supported(*video_codec, *container);
|
*container = change_container_if_codec_not_supported(*video_codec, *container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string get_framerate_mode_validate(const RecordOptions &record_options, const GsrInfo &gsr_info) {
|
||||||
|
(void)gsr_info;
|
||||||
|
std::string framerate_mode = record_options.framerate_mode;
|
||||||
|
if(framerate_mode == "auto")
|
||||||
|
framerate_mode = "vfr";
|
||||||
|
return framerate_mode;
|
||||||
|
}
|
||||||
|
|
||||||
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) {
|
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) {
|
||||||
if(region_selector.is_started() || window_selector.is_started())
|
if(region_selector.is_started() || window_selector.is_started())
|
||||||
return false;
|
return false;
|
||||||
@@ -2484,7 +2612,7 @@ namespace gsr {
|
|||||||
|
|
||||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||||
recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
|
recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
|
||||||
if(!validate_capture_target(recording_capture_target, capture_options)) {
|
if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) {
|
||||||
char err_msg[256];
|
char err_msg[256];
|
||||||
snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
|
snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
|
||||||
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
|
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
|
||||||
@@ -2512,7 +2640,7 @@ namespace gsr {
|
|||||||
const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate);
|
const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate);
|
||||||
const std::string output_directory = config.replay_config.save_directory;
|
const std::string output_directory = config.replay_config.save_directory;
|
||||||
const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.replay_config.record_options.audio_tracks_list, gsr_info);
|
const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.replay_config.record_options.audio_tracks_list, gsr_info);
|
||||||
const std::string framerate_mode = config.replay_config.record_options.framerate_mode == "auto" ? "vfr" : config.replay_config.record_options.framerate_mode;
|
const std::string framerate_mode = get_framerate_mode_validate(config.replay_config.record_options, gsr_info);
|
||||||
const std::string replay_time = std::to_string(config.replay_config.replay_time);
|
const std::string replay_time = std::to_string(config.replay_config.replay_time);
|
||||||
const char *container = config.replay_config.container.c_str();
|
const char *container = config.replay_config.container.c_str();
|
||||||
const char *video_codec = config.replay_config.record_options.video_codec.c_str();
|
const char *video_codec = config.replay_config.record_options.video_codec.c_str();
|
||||||
@@ -2545,6 +2673,9 @@ namespace gsr {
|
|||||||
if(config.replay_config.restart_replay_on_save && gsr_info.system_info.gsr_version >= GsrVersion{5, 0, 3}) {
|
if(config.replay_config.restart_replay_on_save && gsr_info.system_info.gsr_version >= GsrVersion{5, 0, 3}) {
|
||||||
args.push_back("-restart-replay-on-save");
|
args.push_back("-restart-replay-on-save");
|
||||||
args.push_back("yes");
|
args.push_back("yes");
|
||||||
|
replay_restart_on_save = true;
|
||||||
|
} else {
|
||||||
|
replay_restart_on_save = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) {
|
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) {
|
||||||
@@ -2584,10 +2715,17 @@ namespace gsr {
|
|||||||
// to see when the program has exit.
|
// to see when the program has exit.
|
||||||
if(!disable_notification && config.replay_config.show_replay_started_notifications) {
|
if(!disable_notification && config.replay_config.show_replay_started_notifications) {
|
||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
|
||||||
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
|
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(config.replay_config.record_options.record_area_option == "portal")
|
||||||
|
hide_ui = true;
|
||||||
|
|
||||||
|
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||||
|
// selected what to capture and accepted it.
|
||||||
|
replay_duration_clock.restart();
|
||||||
|
replay_buffer_save_duration_sec = config.replay_config.replay_time;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2606,8 +2744,12 @@ namespace gsr {
|
|||||||
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
|
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
|
||||||
if(!replay_recording) {
|
if(!replay_recording) {
|
||||||
if(config.record_config.show_recording_started_notifications)
|
if(config.record_config.show_recording_started_notifications)
|
||||||
show_notification("Started recording in the replay session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
|
show_notification("Started recording in the replay session", short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
|
||||||
update_ui_recording_started();
|
update_ui_recording_started();
|
||||||
|
|
||||||
|
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||||
|
// selected what to capture and accepted it.
|
||||||
|
recording_duration_clock.restart();
|
||||||
}
|
}
|
||||||
replay_recording = true;
|
replay_recording = true;
|
||||||
kill(gpu_screen_recorder_process, SIGRTMIN);
|
kill(gpu_screen_recorder_process, SIGRTMIN);
|
||||||
@@ -2623,8 +2765,12 @@ namespace gsr {
|
|||||||
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
|
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
|
||||||
if(!replay_recording) {
|
if(!replay_recording) {
|
||||||
if(config.record_config.show_recording_started_notifications)
|
if(config.record_config.show_recording_started_notifications)
|
||||||
show_notification("Started recording in the streaming session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
|
show_notification("Started recording in the streaming session", short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
|
||||||
update_ui_recording_started();
|
update_ui_recording_started();
|
||||||
|
|
||||||
|
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||||
|
// selected what to capture and accepted it.
|
||||||
|
recording_duration_clock.restart();
|
||||||
}
|
}
|
||||||
replay_recording = true;
|
replay_recording = true;
|
||||||
kill(gpu_screen_recorder_process, SIGRTMIN);
|
kill(gpu_screen_recorder_process, SIGRTMIN);
|
||||||
@@ -2691,7 +2837,7 @@ namespace gsr {
|
|||||||
const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate);
|
const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate);
|
||||||
const std::string output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str());
|
const std::string output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str());
|
||||||
const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.record_config.record_options.audio_tracks_list, gsr_info);
|
const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.record_config.record_options.audio_tracks_list, gsr_info);
|
||||||
const std::string framerate_mode = config.record_config.record_options.framerate_mode == "auto" ? "vfr" : config.record_config.record_options.framerate_mode;
|
const std::string framerate_mode = get_framerate_mode_validate(config.record_config.record_options, gsr_info);
|
||||||
const char *container = config.record_config.container.c_str();
|
const char *container = config.record_config.container.c_str();
|
||||||
const char *video_codec = config.record_config.record_options.video_codec.c_str();
|
const char *video_codec = config.record_config.record_options.video_codec.c_str();
|
||||||
const char *encoder = "gpu";
|
const char *encoder = "gpu";
|
||||||
@@ -2744,9 +2890,16 @@ namespace gsr {
|
|||||||
// 1...
|
// 1...
|
||||||
if(config.record_config.show_recording_started_notifications) {
|
if(config.record_config.show_recording_started_notifications) {
|
||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
|
||||||
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
|
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(config.record_config.record_options.record_area_option == "portal")
|
||||||
|
hide_ui = true;
|
||||||
|
|
||||||
|
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||||
|
// selected what to capture and accepted it.
|
||||||
|
recording_duration_clock.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string streaming_get_url(const Config &config) {
|
static std::string streaming_get_url(const Config &config) {
|
||||||
@@ -2780,6 +2933,11 @@ namespace gsr {
|
|||||||
{}
|
{}
|
||||||
else
|
else
|
||||||
url = "rtmp://" + url;
|
url = "rtmp://" + url;
|
||||||
|
|
||||||
|
if(!url.empty() && url.back() != '/' && url.back() != '=' && !config.streaming_config.custom.key.empty())
|
||||||
|
url += "/";
|
||||||
|
|
||||||
|
url += config.streaming_config.custom.key;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -2855,7 +3013,7 @@ namespace gsr {
|
|||||||
// But we check it anyways as streaming on some sites can fail if there is more than one audio track
|
// But we check it anyways as streaming on some sites can fail if there is more than one audio track
|
||||||
if(audio_tracks.size() > 1)
|
if(audio_tracks.size() > 1)
|
||||||
audio_tracks.resize(1);
|
audio_tracks.resize(1);
|
||||||
const std::string framerate_mode = config.streaming_config.record_options.framerate_mode == "auto" ? "vfr" : config.streaming_config.record_options.framerate_mode;
|
const std::string framerate_mode = get_framerate_mode_validate(config.streaming_config.record_options, gsr_info);
|
||||||
const char *container = "flv";
|
const char *container = "flv";
|
||||||
if(config.streaming_config.streaming_service == "custom")
|
if(config.streaming_config.streaming_service == "custom")
|
||||||
container = config.streaming_config.custom.container.c_str();
|
container = config.streaming_config.custom.container.c_str();
|
||||||
@@ -2918,12 +3076,15 @@ namespace gsr {
|
|||||||
// to see when the program has exit.
|
// to see when the program has exit.
|
||||||
if(config.streaming_config.show_streaming_started_notifications) {
|
if(config.streaming_config.show_streaming_started_notifications) {
|
||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
|
||||||
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
|
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(config.streaming_config.record_options.record_area_option == "portal")
|
||||||
|
hide_ui = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Overlay::on_press_take_screenshot(bool finished_selection, bool force_region_capture) {
|
void Overlay::on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type) {
|
||||||
if(region_selector.is_started() || window_selector.is_started())
|
if(region_selector.is_started() || window_selector.is_started())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -2932,8 +3093,21 @@ namespace gsr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool region_capture = config.screenshot_config.record_area_option == "region" || force_region_capture;
|
bool hotkey_window_capture = false;
|
||||||
const char *record_area_option = region_capture ? "region" : config.screenshot_config.record_area_option.c_str();
|
std::string record_area_option;
|
||||||
|
switch(force_type) {
|
||||||
|
case ScreenshotForceType::NONE:
|
||||||
|
record_area_option = config.screenshot_config.record_area_option;
|
||||||
|
break;
|
||||||
|
case ScreenshotForceType::REGION:
|
||||||
|
record_area_option = "region";
|
||||||
|
break;
|
||||||
|
case ScreenshotForceType::WINDOW:
|
||||||
|
record_area_option = gsr_info.system_info.display_server == DisplayServer::X11 ? "window" : "portal";
|
||||||
|
hotkey_window_capture = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||||
screenshot_capture_target = get_capture_target(record_area_option, capture_options);
|
screenshot_capture_target = get_capture_target(record_area_option, capture_options);
|
||||||
if(!validate_capture_target(record_area_option, capture_options)) {
|
if(!validate_capture_target(record_area_option, capture_options)) {
|
||||||
@@ -2943,19 +3117,18 @@ namespace gsr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(region_capture && !finished_selection) {
|
if(record_area_option == "region" && !finished_selection) {
|
||||||
start_region_capture = true;
|
start_region_capture = true;
|
||||||
on_region_selected = [this, force_region_capture]() {
|
on_region_selected = [this, force_type]() {
|
||||||
usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this
|
on_press_take_screenshot(true, force_type);
|
||||||
on_press_take_screenshot(true, force_region_capture);
|
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config.screenshot_config.record_area_option == "window" && !finished_selection) {
|
if(record_area_option == "window" && !finished_selection) {
|
||||||
start_window_capture = true;
|
start_window_capture = true;
|
||||||
on_window_selected = [this, force_region_capture]() {
|
on_window_selected = [this, force_type]() {
|
||||||
on_press_take_screenshot(true, force_region_capture);
|
on_press_take_screenshot(true, force_type);
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2979,13 +3152,22 @@ namespace gsr {
|
|||||||
args.push_back(size);
|
args.push_back(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config.screenshot_config.restore_portal_session) {
|
if(config.screenshot_config.restore_portal_session && !hotkey_window_capture) {
|
||||||
args.push_back("-restore-portal-session");
|
args.push_back("-restore-portal-session");
|
||||||
args.push_back("yes");
|
args.push_back("yes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string hotkey_window_capture_portal_session_token_filepath = get_config_dir() + "/gpu-screen-recorder/gsr-ui-window-capture-token";
|
||||||
|
if(record_area_option == "portal") {
|
||||||
|
hide_ui = true;
|
||||||
|
if(hotkey_window_capture) {
|
||||||
|
args.push_back("-portal-session-token-filepath");
|
||||||
|
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char region_str[128];
|
char region_str[128];
|
||||||
if(region_capture)
|
if(record_area_option == "region")
|
||||||
add_region_command(args, region_str, sizeof(region_str), region_selector);
|
add_region_command(args, region_str, sizeof(region_str), region_selector);
|
||||||
|
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|||||||
@@ -120,6 +120,12 @@ namespace gsr {
|
|||||||
if(!theme->trash_texture.load_from_file((resources_path + "images/trash.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
if(!theme->trash_texture.load_from_file((resources_path + "images/trash.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
if(!theme->masked_texture.load_from_file((resources_path + "images/masked.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if(!theme->unmasked_texture.load_from_file((resources_path + "images/unmasked.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||||
|
goto error;
|
||||||
|
|
||||||
if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace gsr {
|
|||||||
for(size_t i = 0; i < items.size(); ++i) {
|
for(size_t i = 0; i < items.size(); ++i) {
|
||||||
Item &item = items[i];
|
Item &item = items[i];
|
||||||
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
|
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
|
||||||
if(mgl::FloatRect(item.position, item_size).contains(mouse_pos)) {
|
if(mgl::FloatRect(item.position, item_size).contains(mouse_pos) && item.enabled) {
|
||||||
const size_t prev_selected_item = selected_item;
|
const size_t prev_selected_item = selected_item;
|
||||||
selected_item = i;
|
selected_item = i;
|
||||||
show_dropdown = false;
|
show_dropdown = false;
|
||||||
@@ -93,7 +93,7 @@ namespace gsr {
|
|||||||
void ComboBox::set_selected_item(const std::string &id, bool trigger_event, bool trigger_event_even_if_selection_not_changed) {
|
void ComboBox::set_selected_item(const std::string &id, bool trigger_event, bool trigger_event_even_if_selection_not_changed) {
|
||||||
for(size_t i = 0; i < items.size(); ++i) {
|
for(size_t i = 0; i < items.size(); ++i) {
|
||||||
auto &item = items[i];
|
auto &item = items[i];
|
||||||
if(item.id == id) {
|
if(item.id == id && item.enabled) {
|
||||||
const size_t prev_selected_item = selected_item;
|
const size_t prev_selected_item = selected_item;
|
||||||
selected_item = i;
|
selected_item = i;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
@@ -106,6 +106,22 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComboBox::set_item_enabled(const std::string &id, bool enabled) {
|
||||||
|
for(size_t i = 0; i < items.size(); ++i) {
|
||||||
|
auto &item = items[i];
|
||||||
|
if(item.id == id) {
|
||||||
|
item.enabled = enabled;
|
||||||
|
item.text.set_color(item.enabled ? mgl::Color(255, 255, 255, 255) : mgl::Color(255, 255, 255, 80));
|
||||||
|
if(selected_item == i) {
|
||||||
|
selected_item = 0;
|
||||||
|
show_dropdown = false;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const std::string& ComboBox::get_selected_id() const {
|
const std::string& ComboBox::get_selected_id() const {
|
||||||
if(items.empty()) {
|
if(items.empty()) {
|
||||||
static std::string dummy;
|
static std::string dummy;
|
||||||
@@ -150,7 +166,7 @@ namespace gsr {
|
|||||||
Item &item = items[i];
|
Item &item = items[i];
|
||||||
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
|
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
|
||||||
|
|
||||||
if(!cursor_inside) {
|
if(!cursor_inside && item.enabled) {
|
||||||
cursor_inside = mgl::FloatRect(items_draw_pos, item_size).contains(mouse_pos);
|
cursor_inside = mgl::FloatRect(items_draw_pos, item_size).contains(mouse_pos);
|
||||||
if(cursor_inside) {
|
if(cursor_inside) {
|
||||||
mgl::Rectangle item_background(items_draw_pos.floor(), item_size.floor());
|
mgl::Rectangle item_background(items_draw_pos.floor(), item_size.floor());
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#include "../../include/gui/Entry.hpp"
|
#include "../../include/gui/Entry.hpp"
|
||||||
#include "../../include/gui/Utils.hpp"
|
#include "../../include/gui/Utils.hpp"
|
||||||
#include "../../include/Theme.hpp"
|
#include "../../include/Theme.hpp"
|
||||||
#include <mglpp/graphics/Rectangle.hpp>
|
|
||||||
#include <mglpp/window/Window.hpp>
|
#include <mglpp/window/Window.hpp>
|
||||||
#include <mglpp/window/Event.hpp>
|
#include <mglpp/window/Event.hpp>
|
||||||
#include <mglpp/system/FloatRect.hpp>
|
#include <mglpp/system/FloatRect.hpp>
|
||||||
#include <mglpp/system/Utf8.hpp>
|
#include <mglpp/system/Utf8.hpp>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
static const float padding_top_scale = 0.004629f;
|
static const float padding_top_scale = 0.004629f;
|
||||||
@@ -16,8 +16,20 @@ namespace gsr {
|
|||||||
static const float border_scale = 0.0015f;
|
static const float border_scale = 0.0015f;
|
||||||
static const float caret_width_scale = 0.001f;
|
static const float caret_width_scale = 0.001f;
|
||||||
|
|
||||||
Entry::Entry(mgl::Font *font, const char *text, float max_width) : text("", *font), max_width(max_width) {
|
static void string_replace_all(std::string &str, char old_char, char new_char) {
|
||||||
|
for(char &c : str) {
|
||||||
|
if(c == old_char)
|
||||||
|
c = new_char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry::Entry(mgl::Font *font, const char *text, float max_width) :
|
||||||
|
text(std::u32string(), *font),
|
||||||
|
masked_text(std::u32string(), *font),
|
||||||
|
max_width(max_width)
|
||||||
|
{
|
||||||
this->text.set_color(get_color_theme().text_color);
|
this->text.set_color(get_color_theme().text_color);
|
||||||
|
this->masked_text.set_color(get_color_theme().text_color);
|
||||||
set_text(text);
|
set_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,25 +37,133 @@ namespace gsr {
|
|||||||
if(!visible)
|
if(!visible)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
|
||||||
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
|
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
|
||||||
selected = mgl::FloatRect(position + offset, get_size()).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y });
|
const mgl::vec2f mouse_pos = { (float)event.mouse_button.x, (float)event.mouse_button.y };
|
||||||
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
selected = mgl::FloatRect(position + offset, get_size()).contains(mouse_pos);
|
||||||
if(event.key.code == mgl::Keyboard::Backspace && !text.get_string().empty()) {
|
if(selected) {
|
||||||
std::string str = text.get_string();
|
selecting_text = true;
|
||||||
const size_t prev_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)str.c_str(), str.size(), str.size());
|
|
||||||
str.erase(prev_index, std::string::npos);
|
const auto caret_index_mouse = find_closest_caret_index_by_position(mouse_pos);
|
||||||
set_text(std::move(str));
|
caret.index = caret_index_mouse.index;
|
||||||
} else if(event.key.code == mgl::Keyboard::V && event.key.control) {
|
caret.offset_x = caret_index_mouse.pos.x - active_text.get_position().x;
|
||||||
std::string clipboard_text = window.get_clipboard_string();
|
selection_start_caret = caret;
|
||||||
std::string str = text.get_string();
|
show_selection = true;
|
||||||
str += clipboard_text;
|
} else {
|
||||||
set_text(std::move(str));
|
selecting_text = false;
|
||||||
|
selecting_with_keyboard = false;
|
||||||
|
show_selection = false;
|
||||||
}
|
}
|
||||||
} else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32) {
|
} else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left) {
|
||||||
std::string str = text.get_string();
|
selecting_text = false;
|
||||||
str.append(event.text.str, event.text.size);
|
if(caret.index == selection_start_caret.index)
|
||||||
set_text(std::move(str));
|
show_selection = false;
|
||||||
|
} else if(event.type == mgl::Event::MouseMoved && selected) {
|
||||||
|
if(selecting_text) {
|
||||||
|
const auto caret_index_mouse = find_closest_caret_index_by_position(mgl::vec2f(event.mouse_move.x, event.mouse_move.y));
|
||||||
|
caret.index = caret_index_mouse.index;
|
||||||
|
caret.offset_x = caret_index_mouse.pos.x - active_text.get_position().x;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
||||||
|
int selection_start_byte = caret.index;
|
||||||
|
int selection_end_byte = caret.index;
|
||||||
|
if(show_selection) {
|
||||||
|
selection_start_byte = std::min(caret.index, selection_start_caret.index);
|
||||||
|
selection_end_byte = std::max(caret.index, selection_start_caret.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event.key.code == mgl::Keyboard::Backspace) {
|
||||||
|
if(selection_start_byte == selection_end_byte && caret.index > 0)
|
||||||
|
selection_start_byte -= 1;
|
||||||
|
|
||||||
|
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::u32string());
|
||||||
|
} else if(event.key.code == mgl::Keyboard::Delete) {
|
||||||
|
if(selection_start_byte == selection_end_byte && caret.index < (int)active_text.get_string().size())
|
||||||
|
selection_end_byte += 1;
|
||||||
|
|
||||||
|
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::u32string());
|
||||||
|
} else if(event.key.code == mgl::Keyboard::C && event.key.control) {
|
||||||
|
const size_t selection_num_bytes = selection_end_byte - selection_start_byte;
|
||||||
|
if(selection_num_bytes > 0)
|
||||||
|
window.set_clipboard(mgl::utf32_to_utf8(text.get_string().substr(selection_start_byte, selection_num_bytes)));
|
||||||
|
} else if(event.key.code == mgl::Keyboard::V && event.key.control) {
|
||||||
|
std::string clipboard_string = window.get_clipboard_string();
|
||||||
|
string_replace_all(clipboard_string, '\n', ' ');
|
||||||
|
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, mgl::utf8_to_utf32(clipboard_string));
|
||||||
|
} else if(event.key.code == mgl::Keyboard::A && event.key.control) {
|
||||||
|
selection_start_caret.index = 0;
|
||||||
|
selection_start_caret.offset_x = 0.0f;
|
||||||
|
|
||||||
|
caret.index = active_text.get_string().size();
|
||||||
|
// TODO: Optimize
|
||||||
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
|
||||||
|
show_selection = true;
|
||||||
|
} else if(event.key.code == mgl::Keyboard::Left) {
|
||||||
|
if(!selecting_with_keyboard && show_selection)
|
||||||
|
show_selection = false;
|
||||||
|
else
|
||||||
|
move_caret_word(Direction::LEFT, event.key.control ? 999999 : 1);
|
||||||
|
|
||||||
|
if(!selecting_with_keyboard) {
|
||||||
|
selection_start_caret = caret;
|
||||||
|
show_selection = false;
|
||||||
|
}
|
||||||
|
} else if(event.key.code == mgl::Keyboard::Right) {
|
||||||
|
if(!selecting_with_keyboard && show_selection)
|
||||||
|
show_selection = false;
|
||||||
|
else
|
||||||
|
move_caret_word(Direction::RIGHT, event.key.control ? 999999 : 1);
|
||||||
|
|
||||||
|
if(!selecting_with_keyboard) {
|
||||||
|
selection_start_caret = caret;
|
||||||
|
show_selection = false;
|
||||||
|
}
|
||||||
|
} else if(event.key.code == mgl::Keyboard::Home) {
|
||||||
|
caret.index = 0;
|
||||||
|
caret.offset_x = 0.0f;
|
||||||
|
|
||||||
|
if(!selecting_with_keyboard) {
|
||||||
|
selection_start_caret = caret;
|
||||||
|
show_selection = false;
|
||||||
|
}
|
||||||
|
} else if(event.key.code == mgl::Keyboard::End) {
|
||||||
|
caret.index = active_text.get_string().size();
|
||||||
|
// TODO: Optimize
|
||||||
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
|
||||||
|
if(!selecting_with_keyboard) {
|
||||||
|
selection_start_caret = caret;
|
||||||
|
show_selection = false;
|
||||||
|
}
|
||||||
|
} else if(event.key.code == mgl::Keyboard::LShift || event.key.code == mgl::Keyboard::RShift) {
|
||||||
|
if(!show_selection)
|
||||||
|
selection_start_caret = caret;
|
||||||
|
selecting_with_keyboard = true;
|
||||||
|
show_selection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else if(event.type == mgl::Event::KeyReleased && selected) {
|
||||||
|
if(event.key.code == mgl::Keyboard::LShift || event.key.code == mgl::Keyboard::RShift) {
|
||||||
|
selecting_with_keyboard = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32 && event.text.codepoint != 127) {
|
||||||
|
int selection_start_byte = caret.index;
|
||||||
|
int selection_end_byte = caret.index;
|
||||||
|
if(show_selection) {
|
||||||
|
selection_start_byte = std::min(caret.index, selection_start_caret.index);
|
||||||
|
selection_end_byte = std::max(caret.index, selection_start_caret.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, mgl::utf8_to_utf32((const unsigned char*)event.text.str, event.text.size));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,26 +174,91 @@ namespace gsr {
|
|||||||
const mgl::vec2f draw_pos = position + offset;
|
const mgl::vec2f draw_pos = position + offset;
|
||||||
|
|
||||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
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;
|
||||||
|
|
||||||
mgl::Rectangle background(get_size());
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
|
||||||
|
background.set_size(get_size());
|
||||||
background.set_position(draw_pos.floor());
|
background.set_position(draw_pos.floor());
|
||||||
background.set_color(selected ? mgl::Color(0, 0, 0, 255) : mgl::Color(0, 0, 0, 120));
|
background.set_color(selected ? mgl::Color(0, 0, 0, 255) : mgl::Color(0, 0, 0, 120));
|
||||||
window.draw(background);
|
window.draw(background);
|
||||||
|
|
||||||
|
const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
||||||
|
const mgl::vec2f caret_size = mgl::vec2f(caret_width, active_text.get_bounds().size.y).floor();
|
||||||
|
|
||||||
|
const float overflow_left = (caret.offset_x + padding_left) - (padding_left + text_overflow);
|
||||||
|
if(overflow_left < 0.0f)
|
||||||
|
text_overflow += overflow_left;
|
||||||
|
|
||||||
|
const float overflow_right = (caret.offset_x + padding_left) - (background.get_size().x - padding_right);
|
||||||
|
if(overflow_right - text_overflow > 0.0f)
|
||||||
|
text_overflow = overflow_right;
|
||||||
|
|
||||||
|
active_text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - active_text.get_bounds().size.y * 0.5f) - mgl::vec2f(text_overflow, 0.0f)).floor());
|
||||||
|
|
||||||
|
const auto text_bounds = active_text.get_bounds();
|
||||||
|
const bool text_larger_than_background = text_bounds.size.x > (background.get_size().x - padding_left - padding_right);
|
||||||
|
const float text_overflow_right = (text_bounds.position.x + text_bounds.size.x) - (background.get_position().x + background.get_size().x - padding_right);
|
||||||
|
if(text_larger_than_background) {
|
||||||
|
if(text_overflow_right < 0.0f) {
|
||||||
|
text_overflow += text_overflow_right;
|
||||||
|
active_text.set_position(active_text.get_position() + mgl::vec2f(-text_overflow_right, 0.0f));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
active_text.set_position(active_text.get_position() + mgl::vec2f(-text_overflow, 0.0f));
|
||||||
|
text_overflow = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
if(selected) {
|
if(selected) {
|
||||||
const int border_size = std::max(1.0f, border_scale * get_theme().window_height);
|
const int border_size = std::max(1.0f, border_scale * get_theme().window_height);
|
||||||
draw_rectangle_outline(window, draw_pos.floor(), get_size().floor(), get_color_theme().tint_color, border_size);
|
draw_rectangle_outline(window, draw_pos.floor(), get_size().floor(), get_color_theme().tint_color, border_size);
|
||||||
|
draw_caret(window, draw_pos, caret_size);
|
||||||
const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
|
||||||
mgl::Rectangle caret({(float)caret_width, text.get_bounds().size.y});
|
|
||||||
caret.set_position((draw_pos + mgl::vec2f(padding_left + caret_offset_x, padding_top)).floor());
|
|
||||||
caret.set_color(mgl::Color(255, 255, 255));
|
|
||||||
window.draw(caret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor());
|
const mgl::Scissor parent_scissor = window.get_scissor();
|
||||||
window.draw(text);
|
const mgl::Scissor scissor = scissor_get_sub_area(parent_scissor,
|
||||||
|
mgl::Scissor{
|
||||||
|
(background.get_position() + mgl::vec2f(padding_left, padding_top)).to_vec2i(),
|
||||||
|
(background.get_size() - mgl::vec2f(padding_left + padding_right, padding_top + padding_bottom)).to_vec2i()
|
||||||
|
});
|
||||||
|
window.set_scissor(scissor);
|
||||||
|
|
||||||
|
window.draw(active_text);
|
||||||
|
|
||||||
|
if(show_selection)
|
||||||
|
draw_caret_selection(window, draw_pos, caret_size);
|
||||||
|
|
||||||
|
window.set_scissor(parent_scissor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entry::draw_caret(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size) {
|
||||||
|
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||||
|
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||||
|
|
||||||
|
mgl::Rectangle caret_rect(caret_size);
|
||||||
|
mgl::vec2f caret_draw_pos = draw_pos + mgl::vec2f(padding_left + caret.offset_x - text_overflow, padding_top);
|
||||||
|
caret_rect.set_position(caret_draw_pos.floor());
|
||||||
|
caret_rect.set_color(mgl::Color(255, 255, 255));
|
||||||
|
window.draw(caret_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entry::draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size) {
|
||||||
|
if(selection_start_caret.index == caret.index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||||
|
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||||
|
const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
||||||
|
const int offset = caret.index < selection_start_caret.index ? caret_width : 0;
|
||||||
|
|
||||||
|
mgl::Rectangle caret_selection_rect(mgl::vec2f(std::abs(selection_start_caret.offset_x - caret.offset_x) - offset, caret_size.y).floor());
|
||||||
|
caret_selection_rect.set_position((draw_pos + mgl::vec2f(padding_left + std::min(caret.offset_x, selection_start_caret.offset_x) - text_overflow + offset, padding_top)).floor());
|
||||||
|
mgl::Color caret_select_color = get_color_theme().tint_color;
|
||||||
|
caret_select_color.a = 100;
|
||||||
|
caret_selection_rect.set_color(caret_select_color);
|
||||||
|
window.draw(caret_selection_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
mgl::vec2f Entry::get_size() {
|
mgl::vec2f Entry::get_size() {
|
||||||
@@ -85,24 +270,157 @@ namespace gsr {
|
|||||||
return { max_width, text.get_bounds().size.y + padding_top + padding_bottom };
|
return { max_width, text.get_bounds().size.y + padding_top + padding_bottom };
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entry::set_text(std::string str) {
|
void Entry::move_caret_word(Direction direction, size_t max_codepoints) {
|
||||||
if(!validate_handler || validate_handler(str)) {
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
text.set_string(std::move(str));
|
const int dir_step = direction == Direction::LEFT ? -1 : 1;
|
||||||
caret_offset_x = text.find_character_pos(99999).x - this->text.get_position().x;
|
const int num_delimiter_chars = 15;
|
||||||
if(on_changed)
|
const char delimiter_chars[num_delimiter_chars + 1] = " \t\n/.,:;\\[](){}";
|
||||||
on_changed(text.get_string());
|
const char32_t *text_str = active_text.get_string().data();
|
||||||
|
|
||||||
|
int num_non_delimiter_chars_found = 0;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < max_codepoints; ++i) {
|
||||||
|
const uint32_t codepoint = text_str[caret.index];
|
||||||
|
|
||||||
|
const bool is_delimiter_char = codepoint < 127 && !!memchr(delimiter_chars, codepoint, num_delimiter_chars);
|
||||||
|
if(is_delimiter_char) {
|
||||||
|
if(num_non_delimiter_chars_found > 0)
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
++num_non_delimiter_chars_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(caret.index + dir_step < 0 || caret.index + dir_step > (int)active_text.get_string().size())
|
||||||
|
break;
|
||||||
|
|
||||||
|
caret.index += dir_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move right by some characters instead of calculating every character to caret index
|
||||||
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& Entry::get_text() const {
|
EntryValidateHandlerResult Entry::set_text(const std::string &str) {
|
||||||
return text.get_string();
|
EntryValidateHandlerResult validate_result = set_text_internal(mgl::utf8_to_utf32(str));
|
||||||
|
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
caret.index = active_text.get_string().size();
|
||||||
|
// TODO: Optimize
|
||||||
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
selection_start_caret = caret;
|
||||||
|
|
||||||
|
selecting_text = false;
|
||||||
|
selecting_with_keyboard = false;
|
||||||
|
show_selection = false;
|
||||||
|
}
|
||||||
|
return validate_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryValidateHandlerResult Entry::set_text_internal(std::u32string str) {
|
||||||
|
EntryValidateHandlerResult validate_result = EntryValidateHandlerResult::ALLOW;
|
||||||
|
if(validate_handler)
|
||||||
|
validate_result = validate_handler(*this, str);
|
||||||
|
|
||||||
|
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
||||||
|
text.set_string(std::move(str));
|
||||||
|
if(masked)
|
||||||
|
masked_text.set_string(std::u32string(text.get_string().size(), '*'));
|
||||||
|
// TODO: Call callback with utf32 instead?
|
||||||
|
if(on_changed)
|
||||||
|
on_changed(mgl::utf32_to_utf8(text.get_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return validate_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Entry::get_text() const {
|
||||||
|
return mgl::utf32_to_utf8(text.get_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entry::set_masked(bool masked) {
|
||||||
|
if(masked == this->masked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->masked = masked;
|
||||||
|
|
||||||
|
if(masked)
|
||||||
|
masked_text.set_string(std::u32string(text.get_string().size(), '*'));
|
||||||
|
else
|
||||||
|
masked_text.set_string(std::u32string());
|
||||||
|
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
selection_start_caret.offset_x = active_text.find_character_pos(selection_start_caret.index).x - active_text.get_position().x;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Entry::is_masked() const {
|
||||||
|
return masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entry::replace_text(size_t index, size_t size, const std::u32string &replacement) {
|
||||||
|
if(index + size > text.get_string().size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto prev_caret = caret;
|
||||||
|
|
||||||
|
if((int)index >= caret.index)
|
||||||
|
caret.index += replacement.size();
|
||||||
|
else
|
||||||
|
caret.index = caret.index - size + replacement.size();
|
||||||
|
|
||||||
|
std::u32string str = text.get_string();
|
||||||
|
str.replace(index, size, replacement);
|
||||||
|
const EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
||||||
|
if(validate_result == EntryValidateHandlerResult::DENY) {
|
||||||
|
caret = prev_caret;
|
||||||
|
return;
|
||||||
|
} else if(validate_result == EntryValidateHandlerResult::REPLACED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
// TODO: Optimize
|
||||||
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
selection_start_caret = caret;
|
||||||
|
|
||||||
|
selecting_text = false;
|
||||||
|
selecting_with_keyboard = false;
|
||||||
|
show_selection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CaretIndexPos Entry::find_closest_caret_index_by_position(mgl::vec2f position) {
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
const std::u32string &str = active_text.get_string();
|
||||||
|
mgl::Font *font = active_text.get_font();
|
||||||
|
|
||||||
|
CaretIndexPos result = {0, {active_text.get_position().x, active_text.get_position().y}};
|
||||||
|
for(result.index = 0; result.index < (int)str.size(); ++result.index) {
|
||||||
|
const uint32_t codepoint = str[result.index];
|
||||||
|
|
||||||
|
float glyph_width = 0.0f;
|
||||||
|
if(codepoint == '\t') {
|
||||||
|
const auto glyph = font->get_glyph(' ');
|
||||||
|
const int tab_width = 4;
|
||||||
|
glyph_width = glyph.advance * tab_width;
|
||||||
|
} else {
|
||||||
|
const auto glyph = font->get_glyph(codepoint);
|
||||||
|
glyph_width = glyph.advance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result.pos.x + glyph_width * 0.5f >= position.x)
|
||||||
|
break;
|
||||||
|
|
||||||
|
result.pos.x += glyph_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_number(uint8_t c) {
|
static bool is_number(uint8_t c) {
|
||||||
return c >= '0' && c <= '9';
|
return c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<int> to_integer(const std::string &str) {
|
static std::optional<int> to_integer(const std::u32string &str) {
|
||||||
if(str.empty())
|
if(str.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
@@ -114,7 +432,7 @@ namespace gsr {
|
|||||||
int number = 0;
|
int number = 0;
|
||||||
for(; i < str.size(); ++i) {
|
for(; i < str.size(); ++i) {
|
||||||
if(!is_number(str[i]))
|
if(!is_number(str[i]))
|
||||||
return false;
|
return std::nullopt;
|
||||||
|
|
||||||
const int new_number = number * 10 + (str[i] - '0');
|
const int new_number = number * 10 + (str[i] - '0');
|
||||||
if(new_number < number)
|
if(new_number < number)
|
||||||
@@ -129,19 +447,23 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max) {
|
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max) {
|
||||||
return [min, max](std::string &str) {
|
return [min, max](Entry &entry, const std::u32string &str) {
|
||||||
if(str.empty())
|
if(str.empty())
|
||||||
return true;
|
return EntryValidateHandlerResult::ALLOW;
|
||||||
|
|
||||||
std::optional<int> number = to_integer(str);
|
const std::optional<int> number = to_integer(str);
|
||||||
if(!number)
|
if(!number)
|
||||||
return false;
|
return EntryValidateHandlerResult::DENY;
|
||||||
|
|
||||||
if(number.value() < min)
|
if(number.value() < min) {
|
||||||
str = std::to_string(min);
|
entry.set_text(std::to_string(min));
|
||||||
else if(number.value() > max)
|
return EntryValidateHandlerResult::REPLACED;
|
||||||
str = std::to_string(max);
|
} else if(number.value() > max) {
|
||||||
return true;
|
entry.set_text(std::to_string(max));
|
||||||
|
return EntryValidateHandlerResult::REPLACED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EntryValidateHandlerResult::ALLOW;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,6 +348,27 @@ namespace gsr {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<List> GlobalSettingsPage::create_screenshot_window_hotkey_options() {
|
||||||
|
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||||
|
|
||||||
|
char str[128];
|
||||||
|
if(gsr_info->system_info.display_server == DisplayServer::X11)
|
||||||
|
snprintf(str, sizeof(str), "Take a screenshot of a window:");
|
||||||
|
else
|
||||||
|
snprintf(str, sizeof(str), "Take a screenshot with desktop portal:");
|
||||||
|
|
||||||
|
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
|
||||||
|
auto take_screenshot_window_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||||
|
take_screenshot_window_button_ptr = take_screenshot_window_button.get();
|
||||||
|
list->add_widget(std::move(take_screenshot_window_button));
|
||||||
|
|
||||||
|
take_screenshot_window_button_ptr->on_click = [this] {
|
||||||
|
configure_hotkey_start(ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
@@ -362,6 +383,7 @@ namespace gsr {
|
|||||||
config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0};
|
config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||||
config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0};
|
config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||||
config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0};
|
config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||||
|
config.screenshot_config.take_screenshot_window_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||||
config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
|
config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||||
load_hotkeys();
|
load_hotkeys();
|
||||||
overlay->rebind_all_keyboard_hotkeys();
|
overlay->rebind_all_keyboard_hotkeys();
|
||||||
@@ -404,6 +426,7 @@ namespace gsr {
|
|||||||
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(create_screenshot_hotkey_options());
|
||||||
list_ptr->add_widget(create_screenshot_region_hotkey_options());
|
list_ptr->add_widget(create_screenshot_region_hotkey_options());
|
||||||
|
list_ptr->add_widget(create_screenshot_window_hotkey_options());
|
||||||
list_ptr->add_widget(create_hotkey_control_buttons());
|
list_ptr->add_widget(create_hotkey_control_buttons());
|
||||||
return subsection;
|
return subsection;
|
||||||
}
|
}
|
||||||
@@ -444,13 +467,42 @@ namespace gsr {
|
|||||||
return exit_program_button;
|
return exit_program_button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<List> GlobalSettingsPage::create_notification_speed() {
|
||||||
|
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
|
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Notification speed", get_color_theme().text_color));
|
||||||
|
|
||||||
|
auto radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
|
||||||
|
notification_speed_button_ptr = radio_button.get();
|
||||||
|
radio_button->add_item("Normal", "normal");
|
||||||
|
radio_button->add_item("Fast", "fast");
|
||||||
|
radio_button->on_selection_changed = [this](const std::string&, const std::string &id) {
|
||||||
|
if(id == "normal")
|
||||||
|
overlay->set_notification_speed(NotificationSpeed::NORMAL);
|
||||||
|
else if(id == "fast")
|
||||||
|
overlay->set_notification_speed(NotificationSpeed::FAST);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
list->add_widget(std::move(radio_button));
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) {
|
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) {
|
||||||
|
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
|
List *list_ptr = list.get();
|
||||||
|
auto subsection = std::make_unique<Subsection>("Application options", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
|
||||||
|
|
||||||
|
list_ptr->add_widget(create_notification_speed());
|
||||||
|
list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x));
|
||||||
|
|
||||||
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
|
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
|
||||||
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
auto navigate_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||||
list->add_widget(create_exit_program_button());
|
navigate_list->add_widget(create_exit_program_button());
|
||||||
if(inside_flatpak)
|
if(inside_flatpak)
|
||||||
list->add_widget(create_go_back_to_old_ui_button());
|
navigate_list->add_widget(create_go_back_to_old_ui_button());
|
||||||
return std::make_unique<Subsection>("Application options", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
|
list_ptr->add_widget(std::move(navigate_list));
|
||||||
|
|
||||||
|
return subsection;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_info_subsection(ScrollablePage *parent_page) {
|
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_info_subsection(ScrollablePage *parent_page) {
|
||||||
@@ -512,6 +564,8 @@ namespace gsr {
|
|||||||
enable_keyboard_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false);
|
enable_keyboard_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false);
|
||||||
enable_joystick_hotkeys_radio_button_ptr->set_selected_item(config.main_config.joystick_hotkeys_enable_option, false, false);
|
enable_joystick_hotkeys_radio_button_ptr->set_selected_item(config.main_config.joystick_hotkeys_enable_option, false, false);
|
||||||
|
|
||||||
|
notification_speed_button_ptr->set_selected_item(config.main_config.notification_speed);
|
||||||
|
|
||||||
load_hotkeys();
|
load_hotkeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +582,7 @@ namespace gsr {
|
|||||||
|
|
||||||
take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string());
|
take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string());
|
||||||
take_screenshot_region_button_ptr->set_text(config.screenshot_config.take_screenshot_region_hotkey.to_string());
|
take_screenshot_region_button_ptr->set_text(config.screenshot_config.take_screenshot_region_hotkey.to_string());
|
||||||
|
take_screenshot_window_button_ptr->set_text(config.screenshot_config.take_screenshot_window_hotkey.to_string());
|
||||||
|
|
||||||
show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string());
|
show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string());
|
||||||
}
|
}
|
||||||
@@ -537,6 +592,7 @@ namespace gsr {
|
|||||||
config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id();
|
config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id();
|
||||||
config.main_config.hotkeys_enable_option = enable_keyboard_hotkeys_radio_button_ptr->get_selected_id();
|
config.main_config.hotkeys_enable_option = enable_keyboard_hotkeys_radio_button_ptr->get_selected_id();
|
||||||
config.main_config.joystick_hotkeys_enable_option = enable_joystick_hotkeys_radio_button_ptr->get_selected_id();
|
config.main_config.joystick_hotkeys_enable_option = enable_joystick_hotkeys_radio_button_ptr->get_selected_id();
|
||||||
|
config.main_config.notification_speed = notification_speed_button_ptr->get_selected_id();
|
||||||
save_config(config);
|
save_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,6 +667,8 @@ namespace gsr {
|
|||||||
return take_screenshot_button_ptr;
|
return take_screenshot_button_ptr;
|
||||||
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
|
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
|
||||||
return take_screenshot_region_button_ptr;
|
return take_screenshot_region_button_ptr;
|
||||||
|
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW:
|
||||||
|
return take_screenshot_window_button_ptr;
|
||||||
case ConfigureHotkeyType::SHOW_HIDE:
|
case ConfigureHotkeyType::SHOW_HIDE:
|
||||||
return show_hide_button_ptr;
|
return show_hide_button_ptr;
|
||||||
}
|
}
|
||||||
@@ -639,6 +697,8 @@ namespace gsr {
|
|||||||
return &config.screenshot_config.take_screenshot_hotkey;
|
return &config.screenshot_config.take_screenshot_hotkey;
|
||||||
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
|
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
|
||||||
return &config.screenshot_config.take_screenshot_region_hotkey;
|
return &config.screenshot_config.take_screenshot_region_hotkey;
|
||||||
|
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW:
|
||||||
|
return &config.screenshot_config.take_screenshot_window_hotkey;
|
||||||
case ConfigureHotkeyType::SHOW_HIDE:
|
case ConfigureHotkeyType::SHOW_HIDE:
|
||||||
return &config.main_config.show_hide_hotkey;
|
return &config.main_config.show_hide_hotkey;
|
||||||
}
|
}
|
||||||
@@ -654,6 +714,7 @@ namespace gsr {
|
|||||||
&config.streaming_config.start_stop_hotkey,
|
&config.streaming_config.start_stop_hotkey,
|
||||||
&config.screenshot_config.take_screenshot_hotkey,
|
&config.screenshot_config.take_screenshot_hotkey,
|
||||||
&config.screenshot_config.take_screenshot_region_hotkey,
|
&config.screenshot_config.take_screenshot_region_hotkey,
|
||||||
|
&config.screenshot_config.take_screenshot_window_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) {
|
||||||
@@ -702,6 +763,13 @@ namespace gsr {
|
|||||||
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
|
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
|
||||||
hotkey_configure_action_name = "Take a screenshot of a region";
|
hotkey_configure_action_name = "Take a screenshot of a region";
|
||||||
break;
|
break;
|
||||||
|
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW: {
|
||||||
|
if(gsr_info->system_info.display_server == DisplayServer::X11)
|
||||||
|
hotkey_configure_action_name = "Take a screenshot of a window";
|
||||||
|
else
|
||||||
|
hotkey_configure_action_name = "Take a screenshot with desktop portal";
|
||||||
|
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;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace gsr {
|
|||||||
|
|
||||||
ll->add_widget(std::move(capture_target_list));
|
ll->add_widget(std::move(capture_target_list));
|
||||||
ll->add_widget(create_change_image_resolution_section());
|
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));
|
return std::make_unique<Subsection>("Capture", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_section() {
|
std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_section() {
|
||||||
@@ -215,8 +215,17 @@ namespace gsr {
|
|||||||
return checkbox;
|
return checkbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_to_clipboard() {
|
||||||
|
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Save screenshot to clipboard");
|
||||||
|
save_screenshot_to_clipboard_checkbox_ptr = checkbox.get();
|
||||||
|
return checkbox;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_general_section() {
|
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));
|
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
|
list->add_widget(create_save_screenshot_in_game_folder());
|
||||||
|
list->add_widget(create_save_screenshot_to_clipboard());
|
||||||
|
return std::make_unique<Subsection>("General", std::move(list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_notifications_section() {
|
std::unique_ptr<Widget> ScreenshotSettingsPage::create_notifications_section() {
|
||||||
@@ -281,6 +290,7 @@ namespace gsr {
|
|||||||
restore_portal_session_checkbox_ptr->set_checked(config.screenshot_config.restore_portal_session);
|
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_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);
|
save_screenshot_in_game_folder_checkbox_ptr->set_checked(config.screenshot_config.save_screenshot_in_game_folder);
|
||||||
|
save_screenshot_to_clipboard_checkbox_ptr->set_checked(config.screenshot_config.save_screenshot_to_clipboard);
|
||||||
show_screenshot_saved_notification_checkbox_ptr->set_checked(config.screenshot_config.show_screenshot_saved_notifications);
|
show_screenshot_saved_notification_checkbox_ptr->set_checked(config.screenshot_config.show_screenshot_saved_notifications);
|
||||||
|
|
||||||
if(config.screenshot_config.image_width == 0)
|
if(config.screenshot_config.image_width == 0)
|
||||||
@@ -309,6 +319,7 @@ namespace gsr {
|
|||||||
config.screenshot_config.restore_portal_session = restore_portal_session_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_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.save_screenshot_in_game_folder = save_screenshot_in_game_folder_checkbox_ptr->is_checked();
|
||||||
|
config.screenshot_config.save_screenshot_to_clipboard = save_screenshot_to_clipboard_checkbox_ptr->is_checked();
|
||||||
config.screenshot_config.show_screenshot_saved_notifications = show_screenshot_saved_notification_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)
|
if(config.screenshot_config.image_width == 0)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "../../include/gui/PageStack.hpp"
|
#include "../../include/gui/PageStack.hpp"
|
||||||
#include "../../include/gui/FileChooser.hpp"
|
#include "../../include/gui/FileChooser.hpp"
|
||||||
#include "../../include/gui/Subsection.hpp"
|
#include "../../include/gui/Subsection.hpp"
|
||||||
|
#include "../../include/gui/Image.hpp"
|
||||||
#include "../../include/Theme.hpp"
|
#include "../../include/Theme.hpp"
|
||||||
#include "../../include/GsrInfo.hpp"
|
#include "../../include/GsrInfo.hpp"
|
||||||
#include "../../include/Utils.hpp"
|
#include "../../include/Utils.hpp"
|
||||||
@@ -183,7 +184,7 @@ namespace gsr {
|
|||||||
|
|
||||||
ll->add_widget(std::move(capture_target_list));
|
ll->add_widget(std::move(capture_target_list));
|
||||||
ll->add_widget(create_change_video_resolution_section());
|
ll->add_widget(create_change_video_resolution_section());
|
||||||
return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
return std::make_unique<Subsection>("Capture", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool audio_device_is_output(const std::string &audio_device_id) {
|
static bool audio_device_is_output(const std::string &audio_device_id) {
|
||||||
@@ -567,6 +568,10 @@ namespace gsr {
|
|||||||
framerate_mode_box->add_item("Auto (Recommended)", "auto");
|
framerate_mode_box->add_item("Auto (Recommended)", "auto");
|
||||||
framerate_mode_box->add_item("Constant", "cfr");
|
framerate_mode_box->add_item("Constant", "cfr");
|
||||||
framerate_mode_box->add_item("Variable", "vfr");
|
framerate_mode_box->add_item("Variable", "vfr");
|
||||||
|
if(gsr_info->system_info.display_server == DisplayServer::X11)
|
||||||
|
framerate_mode_box->add_item("Sync to content", "content");
|
||||||
|
else
|
||||||
|
framerate_mode_box->add_item("Sync to content (Only X11 or desktop portal capture)", "content");
|
||||||
framerate_mode_box_ptr = framerate_mode_box.get();
|
framerate_mode_box_ptr = framerate_mode_box.get();
|
||||||
return framerate_mode_box;
|
return framerate_mode_box;
|
||||||
}
|
}
|
||||||
@@ -965,36 +970,75 @@ namespace gsr {
|
|||||||
return streaming_service_list;
|
return streaming_service_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<Button> create_mask_toggle_button(Entry *entry_to_toggle, mgl::vec2f size) {
|
||||||
|
auto button = std::make_unique<Button>(&get_theme().body_font, "", size, mgl::Color(0, 0, 0, 0));
|
||||||
|
Button *button_ptr = button.get();
|
||||||
|
button->set_icon(&get_theme().masked_texture);
|
||||||
|
button->on_click = [entry_to_toggle, button_ptr]() {
|
||||||
|
const bool is_masked = entry_to_toggle->is_masked();
|
||||||
|
button_ptr->set_icon(is_masked ? &get_theme().unmasked_texture : &get_theme().masked_texture);
|
||||||
|
entry_to_toggle->set_masked(!is_masked);
|
||||||
|
};
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Entry* add_stream_key_entry_to_list(List *stream_key_list) {
|
||||||
|
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||||
|
auto key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||||
|
key_entry->set_masked(true);
|
||||||
|
Entry *key_entry_ptr = key_entry.get();
|
||||||
|
const float mask_icon_size = key_entry_ptr->get_size().y * 0.9f;
|
||||||
|
list->add_widget(std::move(key_entry));
|
||||||
|
list->add_widget(create_mask_toggle_button(key_entry_ptr, mgl::vec2f(mask_icon_size, mask_icon_size)));
|
||||||
|
stream_key_list->add_widget(std::move(list));
|
||||||
|
return key_entry_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<List> SettingsPage::create_stream_key_section() {
|
std::unique_ptr<List> SettingsPage::create_stream_key_section() {
|
||||||
auto stream_key_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
auto stream_key_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
stream_key_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
stream_key_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
||||||
|
|
||||||
auto twitch_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
twitch_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
|
||||||
twitch_stream_key_entry_ptr = twitch_stream_key_entry.get();
|
youtube_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
|
||||||
stream_key_list->add_widget(std::move(twitch_stream_key_entry));
|
rumble_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
|
||||||
|
|
||||||
auto youtube_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
|
||||||
youtube_stream_key_entry_ptr = youtube_stream_key_entry.get();
|
|
||||||
stream_key_list->add_widget(std::move(youtube_stream_key_entry));
|
|
||||||
|
|
||||||
auto rumble_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
|
||||||
rumble_stream_key_entry_ptr = rumble_stream_key_entry.get();
|
|
||||||
stream_key_list->add_widget(std::move(rumble_stream_key_entry));
|
|
||||||
|
|
||||||
stream_key_list_ptr = stream_key_list.get();
|
stream_key_list_ptr = stream_key_list.get();
|
||||||
return stream_key_list;
|
return stream_key_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<List> SettingsPage::create_stream_url_section() {
|
std::unique_ptr<List> SettingsPage::create_stream_custom_url() {
|
||||||
auto stream_url_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
stream_url_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "URL:", get_color_theme().text_color));
|
|
||||||
|
|
||||||
auto stream_url_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
auto stream_url_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||||
stream_url_entry_ptr = stream_url_entry.get();
|
stream_url_entry_ptr = stream_url_entry.get();
|
||||||
stream_url_list->add_widget(std::move(stream_url_entry));
|
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream URL:", get_color_theme().text_color));
|
||||||
|
list->add_widget(std::move(stream_url_entry));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
stream_url_list_ptr = stream_url_list.get();
|
std::unique_ptr<List> SettingsPage::create_stream_custom_key() {
|
||||||
return stream_url_list;
|
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||||
|
auto stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||||
|
stream_key_entry->set_masked(true);
|
||||||
|
stream_key_entry_ptr = stream_key_entry.get();
|
||||||
|
const float mask_icon_size = stream_key_entry_ptr->get_size().y * 0.9f;
|
||||||
|
list->add_widget(std::move(stream_key_entry));
|
||||||
|
list->add_widget(create_mask_toggle_button(stream_key_entry_ptr, mgl::vec2f(mask_icon_size, mask_icon_size)));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<List> SettingsPage::create_stream_custom_section() {
|
||||||
|
auto custom_stream_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
|
|
||||||
|
auto stream_url_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||||
|
stream_url_list->add_widget(create_stream_custom_url());
|
||||||
|
stream_url_list->add_widget(create_stream_container());
|
||||||
|
|
||||||
|
custom_stream_list->add_widget(std::move(stream_url_list));
|
||||||
|
custom_stream_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
||||||
|
custom_stream_list->add_widget(create_stream_custom_key());
|
||||||
|
|
||||||
|
custom_stream_list_ptr = custom_stream_list.get();
|
||||||
|
return custom_stream_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ComboBox> SettingsPage::create_stream_container_box() {
|
std::unique_ptr<ComboBox> SettingsPage::create_stream_container_box() {
|
||||||
@@ -1007,11 +1051,10 @@ namespace gsr {
|
|||||||
return container_box;
|
return container_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<List> SettingsPage::create_stream_container_section() {
|
std::unique_ptr<List> SettingsPage::create_stream_container() {
|
||||||
auto container_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
auto container_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Container:", get_color_theme().text_color));
|
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Container:", get_color_theme().text_color));
|
||||||
container_list->add_widget(create_stream_container_box());
|
container_list->add_widget(create_stream_container_box());
|
||||||
container_list_ptr = container_list.get();
|
|
||||||
return container_list;
|
return container_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1019,8 +1062,7 @@ namespace gsr {
|
|||||||
auto streaming_info_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
auto streaming_info_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||||
streaming_info_list->add_widget(create_streaming_service_section());
|
streaming_info_list->add_widget(create_streaming_service_section());
|
||||||
streaming_info_list->add_widget(create_stream_key_section());
|
streaming_info_list->add_widget(create_stream_key_section());
|
||||||
streaming_info_list->add_widget(create_stream_url_section());
|
streaming_info_list->add_widget(create_stream_custom_section());
|
||||||
streaming_info_list->add_widget(create_stream_container_section());
|
|
||||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_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);
|
||||||
@@ -1045,11 +1087,10 @@ namespace gsr {
|
|||||||
const bool rumble_option = id == "rumble";
|
const bool rumble_option = id == "rumble";
|
||||||
const bool custom_option = id == "custom";
|
const bool custom_option = id == "custom";
|
||||||
stream_key_list_ptr->set_visible(!custom_option);
|
stream_key_list_ptr->set_visible(!custom_option);
|
||||||
stream_url_list_ptr->set_visible(custom_option);
|
custom_stream_list_ptr->set_visible(custom_option);
|
||||||
container_list_ptr->set_visible(custom_option);
|
twitch_stream_key_entry_ptr->get_parent_widget()->set_visible(twitch_option);
|
||||||
twitch_stream_key_entry_ptr->set_visible(twitch_option);
|
youtube_stream_key_entry_ptr->get_parent_widget()->set_visible(youtube_option);
|
||||||
youtube_stream_key_entry_ptr->set_visible(youtube_option);
|
rumble_stream_key_entry_ptr->get_parent_widget()->set_visible(rumble_option);
|
||||||
rumble_stream_key_entry_ptr->set_visible(rumble_option);
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
streaming_service_box_ptr->on_selection_changed("Twitch", "twitch");
|
streaming_service_box_ptr->on_selection_changed("Twitch", "twitch");
|
||||||
@@ -1253,6 +1294,7 @@ namespace gsr {
|
|||||||
twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key);
|
twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key);
|
||||||
rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key);
|
rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key);
|
||||||
stream_url_entry_ptr->set_text(config.streaming_config.custom.url);
|
stream_url_entry_ptr->set_text(config.streaming_config.custom.url);
|
||||||
|
stream_key_entry_ptr->set_text(config.streaming_config.custom.key);
|
||||||
container_box_ptr->set_selected_item(config.streaming_config.custom.container);
|
container_box_ptr->set_selected_item(config.streaming_config.custom.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1395,6 +1437,7 @@ namespace gsr {
|
|||||||
config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text();
|
config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text();
|
||||||
config.streaming_config.rumble.stream_key = rumble_stream_key_entry_ptr->get_text();
|
config.streaming_config.rumble.stream_key = rumble_stream_key_entry_ptr->get_text();
|
||||||
config.streaming_config.custom.url = stream_url_entry_ptr->get_text();
|
config.streaming_config.custom.url = stream_url_entry_ptr->get_text();
|
||||||
|
config.streaming_config.custom.key = stream_key_entry_ptr->get_text();
|
||||||
config.streaming_config.custom.container = container_box_ptr->get_selected_id();
|
config.streaming_config.custom.container = container_box_ptr->get_selected_id();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,18 @@
|
|||||||
namespace gsr {
|
namespace gsr {
|
||||||
static double frame_delta_seconds = 1.0;
|
static double frame_delta_seconds = 1.0;
|
||||||
|
|
||||||
|
static mgl::vec2i min_vec2i(mgl::vec2i a, mgl::vec2i b) {
|
||||||
|
return { std::min(a.x, b.x), std::min(a.y, b.y) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static mgl::vec2i max_vec2i(mgl::vec2i a, mgl::vec2i b) {
|
||||||
|
return { std::max(a.x, b.x), std::max(a.y, b.y) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static mgl::vec2i clamp_vec2i(mgl::vec2i value, mgl::vec2i min, mgl::vec2i max) {
|
||||||
|
return min_vec2i(max, max_vec2i(value, min));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Use vertices to make it one draw call
|
// TODO: Use vertices to make it one draw call
|
||||||
void draw_rectangle_outline(mgl::Window &window, mgl::vec2f pos, mgl::vec2f size, mgl::Color color, float border_size) {
|
void draw_rectangle_outline(mgl::Window &window, mgl::vec2f pos, mgl::vec2f size, mgl::Color color, float border_size) {
|
||||||
pos = pos.floor();
|
pos = pos.floor();
|
||||||
@@ -74,4 +86,12 @@ namespace gsr {
|
|||||||
else
|
else
|
||||||
return from;
|
return from;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mgl::Scissor scissor_get_sub_area(mgl::Scissor parent, mgl::Scissor child) {
|
||||||
|
const mgl::vec2i pos = clamp_vec2i(child.position, parent.position, parent.position + parent.size);
|
||||||
|
return mgl::Scissor{
|
||||||
|
pos,
|
||||||
|
min_vec2i(child.size, parent.position + parent.size - pos)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,10 @@ namespace gsr {
|
|||||||
this->visible = visible;
|
this->visible = visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget* Widget::get_parent_widget() {
|
||||||
|
return parent_widget;
|
||||||
|
}
|
||||||
|
|
||||||
void add_widget_to_remove(std::unique_ptr<Widget> widget) {
|
void add_widget_to_remove(std::unique_ptr<Widget> widget) {
|
||||||
widgets_to_remove.push_back(std::move(widget));
|
widgets_to_remove.push_back(std::move(widget));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,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->take_screenshot_region();
|
overlay->take_screenshot_region();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rpc->add_handler("take-screenshot-window", [overlay](const std::string &name) {
|
||||||
|
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
|
||||||
|
overlay->take_screenshot_window();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_gsr_ui_virtual_keyboard_running() {
|
static bool is_gsr_ui_virtual_keyboard_running() {
|
||||||
@@ -190,7 +195,9 @@ 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
|
||||||
|
#ifdef __GLIBC__
|
||||||
mallopt(M_MMAP_THRESHOLD, 65536);
|
mallopt(M_MMAP_THRESHOLD, 65536);
|
||||||
|
#endif
|
||||||
|
|
||||||
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");
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ static void usage(void) {
|
|||||||
printf(" Take a screenshot.\n");
|
printf(" Take a screenshot.\n");
|
||||||
printf(" take-screenshot-region\n");
|
printf(" take-screenshot-region\n");
|
||||||
printf(" Take a screenshot of a region.\n");
|
printf(" Take a screenshot of a region.\n");
|
||||||
|
printf(" take-screenshot-window\n");
|
||||||
|
printf(" Take a screenshot of a window (or desktop portal on Wayland).\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");
|
||||||
@@ -83,6 +85,7 @@ static bool is_valid_command(const char *command) {
|
|||||||
"replay-save-10-min",
|
"replay-save-10-min",
|
||||||
"take-screenshot",
|
"take-screenshot",
|
||||||
"take-screenshot-region",
|
"take-screenshot-region",
|
||||||
|
"take-screenshot-window",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user