mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-19 16:35:49 +09:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd600fad6 | ||
|
|
5144994575 | ||
|
|
1c24616388 | ||
|
|
ecd9a1f13f | ||
|
|
4181d80405 | ||
|
|
085f4d8bad | ||
|
|
bb320e97ed | ||
|
|
ccf96030da | ||
|
|
ca4061f171 | ||
|
|
0b4af1e6bb | ||
|
|
9e03cd0354 | ||
|
|
3d4badf5cd | ||
|
|
071ecf46de | ||
|
|
5ee2b95384 | ||
|
|
d610a980f8 | ||
|
|
70780ae14e | ||
|
|
5f7cb94f4e | ||
|
|
748c51e2b6 | ||
|
|
3ba9ce771b | ||
|
|
c18b062180 | ||
|
|
705da21363 | ||
|
|
609a3e54fd | ||
|
|
4e62d12e8c | ||
|
|
b4e003c8f7 | ||
|
|
9efe9d3c91 | ||
|
|
ef4a0fe7cb | ||
|
|
dacf6126bf | ||
|
|
9bbec944de | ||
|
|
6a55338b12 | ||
|
|
2d3abace0e | ||
|
|
47c02fc6c8 | ||
|
|
5f8c366b43 | ||
|
|
f4ed622510 | ||
|
|
f1ee19d014 | ||
|
|
67a8040e57 | ||
|
|
ff00be30df |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ compile_commands.json
|
|||||||
|
|
||||||
**/xdg-output-unstable-v1-client-protocol.h
|
**/xdg-output-unstable-v1-client-protocol.h
|
||||||
**/xdg-output-unstable-v1-protocol.c
|
**/xdg-output-unstable-v1-protocol.c
|
||||||
|
|
||||||
|
depends/.wraplock
|
||||||
|
|||||||
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.
|
||||||
|
|
||||||
|
|||||||
30
TODO
30
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).
|
||||||
@@ -173,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.
|
||||||
@@ -210,3 +206,29 @@ Support localization.
|
|||||||
Add option to not capture cursor in screenshot when doing region/window capture.
|
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.
|
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).
|
||||||
|
|
||||||
|
The UI is unusable on a vertical monitor.
|
||||||
|
|
||||||
|
Steam overlay interfers with controller input in gsr ui. Maybe move controller handling the gsr-global-hotkeys to do a grab on the controller, to only give the key input to gsr ui.
|
||||||
|
|
||||||
|
Add option to show recording status with scroll lock led (use x11 xkb). Blink when starting/stopping recording and set led on when recording is running and set led off when not recording.
|
||||||
|
|
||||||
|
For joysticks (gamepads) create a virtual device for each one (/dev/uinput) that has the same vendor, product and name. This is to make sure that it behaves the same way in applications since applications
|
||||||
|
access joysticks directly through /dev/input/eventN or /dev/input/jsN. It needs the same number of buttons and pretend to be a controller of the same time, for example a ps4 controller
|
||||||
|
so that games automatically display ps4 buttons if supported.
|
||||||
|
This also allows us to copy event bits and other data from the device instead of saying we support everything.
|
||||||
|
This should fix the issue of not being able to write to /dev/uinput with ABS_X event bit set.
|
||||||
|
Maybe do this for regular keyboard inputs as well?
|
||||||
|
|
||||||
|
Use generic icons for controller input in settings and allow configuring them.
|
||||||
|
|
||||||
|
Add option to include game name in file name (video and screenshot). Replace / with space.
|
||||||
|
|
||||||
|
Check if the focused window is on top on x11 when choosing to take screenshot or show the window as the background of the overlay.
|
||||||
|
|
||||||
|
Convert clipboard image to requested type (from jpg to png for example).
|
||||||
Submodule depends/mglpp updated: 0a3fe76641...b569045831
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,6 +143,7 @@ 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;
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
#include "../Hotplug.hpp"
|
#include "../Hotplug.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <linux/joystick.h>
|
#include <linux/input.h>
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
static constexpr int max_js_poll_fd = 16;
|
static constexpr int max_js_poll_fd = 16;
|
||||||
@@ -30,8 +32,10 @@ namespace gsr {
|
|||||||
bool bind_action(const std::string &id, GlobalHotkeyCallback callback) override;
|
bool bind_action(const std::string &id, GlobalHotkeyCallback callback) override;
|
||||||
void poll_events() override;
|
void poll_events() override;
|
||||||
private:
|
private:
|
||||||
|
void close_fds();
|
||||||
void read_events();
|
void read_events();
|
||||||
void process_js_event(int fd, js_event &event);
|
void process_input_event(int fd, input_event &event);
|
||||||
|
void add_all_joystick_devices();
|
||||||
bool add_device(const char *dev_input_filepath, bool print_error = true);
|
bool add_device(const char *dev_input_filepath, bool print_error = true);
|
||||||
bool remove_device(const char *dev_input_filepath);
|
bool remove_device(const char *dev_input_filepath);
|
||||||
bool remove_poll_fd(int index);
|
bool remove_poll_fd(int index);
|
||||||
@@ -45,6 +49,11 @@ namespace gsr {
|
|||||||
std::unordered_map<std::string, GlobalHotkeyCallback> bound_actions_by_id;
|
std::unordered_map<std::string, GlobalHotkeyCallback> bound_actions_by_id;
|
||||||
std::thread read_thread;
|
std::thread read_thread;
|
||||||
|
|
||||||
|
std::thread close_fd_thread;
|
||||||
|
std::vector<int> fds_to_close;
|
||||||
|
std::mutex close_fd_mutex;
|
||||||
|
std::condition_variable close_fd_cv;
|
||||||
|
|
||||||
pollfd poll_fd[max_js_poll_fd];
|
pollfd poll_fd[max_js_poll_fd];
|
||||||
ExtraData extra_data[max_js_poll_fd];
|
ExtraData extra_data[max_js_poll_fd];
|
||||||
int num_poll_fd = 0;
|
int num_poll_fd = 0;
|
||||||
@@ -56,8 +65,6 @@ namespace gsr {
|
|||||||
bool down_pressed = false;
|
bool down_pressed = false;
|
||||||
bool left_pressed = false;
|
bool left_pressed = false;
|
||||||
bool right_pressed = false;
|
bool right_pressed = false;
|
||||||
bool l3_button_pressed = false;
|
|
||||||
bool r3_button_pressed = false;
|
|
||||||
|
|
||||||
bool save_replay = false;
|
bool save_replay = false;
|
||||||
bool save_1_min_replay = false;
|
bool save_1_min_replay = false;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -47,6 +48,11 @@ namespace gsr {
|
|||||||
WINDOW
|
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);
|
||||||
@@ -59,7 +65,7 @@ namespace gsr {
|
|||||||
bool draw();
|
bool draw();
|
||||||
|
|
||||||
void show();
|
void show();
|
||||||
void hide();
|
void hide_next_frame();
|
||||||
void toggle_show();
|
void toggle_show();
|
||||||
void toggle_record();
|
void toggle_record();
|
||||||
void toggle_pause();
|
void toggle_pause();
|
||||||
@@ -81,7 +87,11 @@ 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 hide();
|
||||||
|
|
||||||
void handle_keyboard_mapping_event();
|
void handle_keyboard_mapping_event();
|
||||||
void on_event(mgl::Event &event);
|
void on_event(mgl::Event &event);
|
||||||
|
|
||||||
@@ -245,5 +255,7 @@ namespace gsr {
|
|||||||
mgl::Clock cursor_tracker_update_clock;
|
mgl::Clock cursor_tracker_update_clock;
|
||||||
|
|
||||||
bool hide_ui = false;
|
bool hide_ui = false;
|
||||||
|
double notification_duration_multiplier = 1.0;
|
||||||
|
ClipboardFile clipboard_file;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -4,31 +4,47 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
typedef struct _IO_FILE FILE;
|
#define GSR_RPC_MAX_CONNECTIONS 8
|
||||||
|
#define GSR_RPC_MAX_POLLS (1 + GSR_RPC_MAX_CONNECTIONS) /* +1 to include the socket_fd itself for accept */
|
||||||
|
#define GSR_RPC_MAX_MESSAGE_SIZE 128
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
using RpcCallback = std::function<void(const std::string &name)>;
|
using RpcCallback = std::function<void(const std::string &name)>;
|
||||||
|
|
||||||
|
enum class RpcOpenResult {
|
||||||
|
OK,
|
||||||
|
CONNECTION_REFUSED,
|
||||||
|
ERROR
|
||||||
|
};
|
||||||
|
|
||||||
class Rpc {
|
class Rpc {
|
||||||
public:
|
public:
|
||||||
Rpc() = default;
|
struct PollData {
|
||||||
|
char buffer[GSR_RPC_MAX_MESSAGE_SIZE];
|
||||||
|
int buffer_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Rpc();
|
||||||
Rpc(const Rpc&) = delete;
|
Rpc(const Rpc&) = delete;
|
||||||
Rpc& operator=(const Rpc&) = delete;
|
Rpc& operator=(const Rpc&) = delete;
|
||||||
~Rpc();
|
~Rpc();
|
||||||
|
|
||||||
bool create(const char *name);
|
bool create(const char *name);
|
||||||
bool open(const char *name);
|
RpcOpenResult open(const char *name);
|
||||||
bool write(const char *str, size_t size);
|
bool write(const char *str, size_t size);
|
||||||
void poll();
|
void poll();
|
||||||
|
|
||||||
bool add_handler(const std::string &name, RpcCallback callback);
|
bool add_handler(const std::string &name, RpcCallback callback);
|
||||||
private:
|
private:
|
||||||
bool open_filepath(const char *filepath);
|
void handle_client_data(int client_fd, PollData &poll_data);
|
||||||
private:
|
private:
|
||||||
int fd = 0;
|
int socket_fd = 0;
|
||||||
FILE *file = nullptr;
|
std::string socket_filepath;
|
||||||
std::string fifo_filepath;
|
struct pollfd polls[GSR_RPC_MAX_POLLS];
|
||||||
|
PollData polls_data[GSR_RPC_MAX_POLLS];
|
||||||
|
int num_polls = 0;
|
||||||
std::unordered_map<std::string, RpcCallback> handlers_by_name;
|
std::unordered_map<std::string, RpcCallback> handlers_by_name;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -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,7 +4,7 @@
|
|||||||
#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>
|
#include <mglpp/graphics/Rectangle.hpp>
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
@@ -15,10 +15,20 @@ namespace gsr {
|
|||||||
ALLOW,
|
ALLOW,
|
||||||
REPLACED
|
REPLACED
|
||||||
};
|
};
|
||||||
using EntryValidateHandler = std::function<EntryValidateHandlerResult(Entry &entry, const std::string &str)>;
|
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;
|
||||||
@@ -28,11 +38,11 @@ namespace gsr {
|
|||||||
|
|
||||||
mgl::vec2f get_size() override;
|
mgl::vec2f get_size() override;
|
||||||
|
|
||||||
EntryValidateHandlerResult set_text(std::string str);
|
EntryValidateHandlerResult set_text(const std::string &str);
|
||||||
const std::string& get_text() const;
|
std::string get_text() const;
|
||||||
|
|
||||||
// Also updates the cursor position
|
void set_masked(bool masked);
|
||||||
void replace_text(size_t index, size_t size, const std::string &replacement);
|
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.
|
||||||
@@ -40,24 +50,28 @@ namespace gsr {
|
|||||||
|
|
||||||
std::function<void(const std::string &text)> on_changed;
|
std::function<void(const std::string &text)> on_changed;
|
||||||
private:
|
private:
|
||||||
EntryValidateHandlerResult set_text_internal(std::string str);
|
// 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(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);
|
void draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size);
|
||||||
mgl_index_codepoint_pair find_closest_caret_index_by_position(mgl::vec2f position);
|
CaretIndexPos find_closest_caret_index_by_position(mgl::vec2f position);
|
||||||
private:
|
private:
|
||||||
struct Caret {
|
struct Caret {
|
||||||
float offset_x = 0.0f;
|
float offset_x = 0.0f;
|
||||||
int utf8_index = 0;
|
int index = 0;
|
||||||
int byte_index = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mgl::Rectangle background;
|
mgl::Rectangle background;
|
||||||
mgl::Text text;
|
mgl::Text32 text;
|
||||||
|
mgl::Text32 masked_text;
|
||||||
float max_width;
|
float max_width;
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
bool selecting_text = false;
|
bool selecting_text = false;
|
||||||
bool selecting_with_keyboard = false;
|
bool selecting_with_keyboard = false;
|
||||||
bool show_selection = false;
|
bool show_selection = false;
|
||||||
|
bool masked = false;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
Caret selection_start_caret;
|
Caret selection_start_caret;
|
||||||
float text_overflow = 0.0f;
|
float text_overflow = 0.0f;
|
||||||
|
|||||||
@@ -70,8 +70,10 @@ namespace gsr {
|
|||||||
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);
|
||||||
|
std::unique_ptr<Subsection> create_donate_subsection(ScrollablePage *parent_page);
|
||||||
void add_widgets();
|
void add_widgets();
|
||||||
|
|
||||||
Button* configure_hotkey_get_button_by_active_type();
|
Button* configure_hotkey_get_button_by_active_type();
|
||||||
@@ -103,6 +105,7 @@ namespace gsr {
|
|||||||
Button *take_screenshot_region_button_ptr = nullptr;
|
Button *take_screenshot_region_button_ptr = nullptr;
|
||||||
Button *take_screenshot_window_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_custom_url();
|
||||||
|
std::unique_ptr<List> create_stream_custom_key();
|
||||||
std::unique_ptr<List> create_stream_custom_section();
|
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;
|
||||||
|
|||||||
@@ -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.7.2', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
project('gsr-ui', ['c', 'cpp'], version : '1.7.9', 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.5"', language: ['c', 'cpp'])
|
add_project_arguments('-DGSR_FLATPAK_VERSION="5.8.3"', 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.7.2"
|
version = "1.7.9"
|
||||||
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"
|
||||||
|
|||||||
299
src/ClipboardFile.cpp
Normal file
299
src/ClipboardFile.cpp
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
#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[4];
|
||||||
|
targets[0] = targets_atom;
|
||||||
|
|
||||||
|
switch(file_type) {
|
||||||
|
case FileType::JPG:
|
||||||
|
num_targets = 4;
|
||||||
|
targets[1] = image_jpg_atom;
|
||||||
|
targets[2] = image_jpeg_atom;
|
||||||
|
targets[3] = image_png_atom;
|
||||||
|
break;
|
||||||
|
case FileType::PNG:
|
||||||
|
num_targets = 2;
|
||||||
|
targets[1] = image_png_atom;
|
||||||
|
targets[2] = image_jpg_atom;
|
||||||
|
targets[3] = image_jpeg_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) {
|
||||||
|
// TODO: Convert image to requested image type. Right now sending a jpg file when a png file is requested works ok in browsers (discord and element)
|
||||||
|
if(!file_type_matches_request_atom(file_type, xselectionrequest->target)) {
|
||||||
|
const char *expected_file_type = file_type_get_name(file_type);
|
||||||
|
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to request clipboard of type %s, but %s was expected. Ignoring requestor and sending as %s\n", (int64_t)xselectionrequest->requestor, file_type_clipboard_get_name(xselectionrequest->target), expected_file_type, expected_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -174,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},
|
||||||
@@ -283,6 +284,7 @@ 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},
|
||||||
|
|||||||
@@ -3,92 +3,48 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
#include <sys/eventfd.h>
|
#include <sys/eventfd.h>
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
static constexpr int button_pressed = 1;
|
static constexpr int button_pressed = 1;
|
||||||
static constexpr int cross_button = 0;
|
|
||||||
static constexpr int triangle_button = 2;
|
|
||||||
static constexpr int options_button = 9;
|
|
||||||
static constexpr int playstation_button = 10;
|
|
||||||
static constexpr int l3_button = 11;
|
|
||||||
static constexpr int r3_button = 12;
|
|
||||||
static constexpr int axis_up_down = 7;
|
|
||||||
static constexpr int axis_left_right = 6;
|
|
||||||
|
|
||||||
struct DeviceId {
|
|
||||||
uint16_t vendor;
|
|
||||||
uint16_t product;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool read_file_hex_number(const char *path, unsigned int *value) {
|
|
||||||
*value = 0;
|
|
||||||
FILE *f = fopen(path, "rb");
|
|
||||||
if(!f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
fscanf(f, "%x", value);
|
|
||||||
fclose(f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DeviceId joystick_get_device_id(const char *path) {
|
|
||||||
DeviceId device_id;
|
|
||||||
device_id.vendor = 0;
|
|
||||||
device_id.product = 0;
|
|
||||||
|
|
||||||
const char *js_path_id = nullptr;
|
|
||||||
const int len = strlen(path);
|
|
||||||
for(int i = len - 1; i >= 0; --i) {
|
|
||||||
if(path[i] == '/') {
|
|
||||||
js_path_id = path + i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!js_path_id)
|
|
||||||
return device_id;
|
|
||||||
|
|
||||||
unsigned int vendor = 0;
|
|
||||||
unsigned int product = 0;
|
|
||||||
char path_buf[1024];
|
|
||||||
|
|
||||||
snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/vendor", js_path_id);
|
|
||||||
if(!read_file_hex_number(path_buf, &vendor))
|
|
||||||
return device_id;
|
|
||||||
|
|
||||||
snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/product", js_path_id);
|
|
||||||
if(!read_file_hex_number(path_buf, &product))
|
|
||||||
return device_id;
|
|
||||||
|
|
||||||
device_id.vendor = vendor;
|
|
||||||
device_id.product = product;
|
|
||||||
return device_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_ps4_controller(DeviceId device_id) {
|
|
||||||
return device_id.vendor == 0x054C && (device_id.product == 0x09CC || device_id.product == 0x0BA0 || device_id.product == 0x05C4);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_ps5_controller(DeviceId device_id) {
|
|
||||||
return device_id.vendor == 0x054C && (device_id.product == 0x0DF2 || device_id.product == 0x0CE6);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_stadia_controller(DeviceId device_id) {
|
|
||||||
return device_id.vendor == 0x18D1 && (device_id.product == 0x9400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns -1 on error
|
// Returns -1 on error
|
||||||
static int get_js_dev_input_id_from_filepath(const char *dev_input_filepath) {
|
static int get_dev_input_event_id_from_filepath(const char *dev_input_filepath) {
|
||||||
if(strncmp(dev_input_filepath, "/dev/input/js", 13) != 0)
|
if(strncmp(dev_input_filepath, "/dev/input/event", 16) != 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int dev_input_id = -1;
|
int dev_input_id = -1;
|
||||||
if(sscanf(dev_input_filepath + 13, "%d", &dev_input_id) == 1)
|
if(sscanf(dev_input_filepath + 16, "%d", &dev_input_id) == 1)
|
||||||
return dev_input_id;
|
return dev_input_id;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool supports_key(unsigned char *key_bits, unsigned int key) {
|
||||||
|
return key_bits[key/8] & (1 << (key % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool supports_joystick_keys(unsigned char *key_bits) {
|
||||||
|
const int keys[7] = { BTN_A, BTN_B, BTN_X, BTN_Y, BTN_SELECT, BTN_START, BTN_SELECT };
|
||||||
|
for(int i = 0; i < 7; ++i) {
|
||||||
|
if(supports_key(key_bits, keys[i]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_input_device_joystick(int input_fd) {
|
||||||
|
unsigned long evbit = 0;
|
||||||
|
ioctl(input_fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
|
||||||
|
if((evbit & (1 << EV_SYN)) && (evbit & (1 << EV_KEY))) {
|
||||||
|
unsigned char key_bits[KEY_MAX/8 + 1];
|
||||||
|
memset(key_bits, 0, sizeof(key_bits));
|
||||||
|
ioctl(input_fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits);
|
||||||
|
return supports_joystick_keys(key_bits);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GlobalHotkeysJoystick::~GlobalHotkeysJoystick() {
|
GlobalHotkeysJoystick::~GlobalHotkeysJoystick() {
|
||||||
if(event_fd > 0) {
|
if(event_fd > 0) {
|
||||||
const uint64_t exit = 1;
|
const uint64_t exit = 1;
|
||||||
@@ -98,8 +54,18 @@ namespace gsr {
|
|||||||
if(read_thread.joinable())
|
if(read_thread.joinable())
|
||||||
read_thread.join();
|
read_thread.join();
|
||||||
|
|
||||||
if(event_fd > 0)
|
if(event_fd > 0) {
|
||||||
close(event_fd);
|
close(event_fd);
|
||||||
|
event_fd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
close_fd_cv.notify_one();
|
||||||
|
if(close_fd_thread.joinable())
|
||||||
|
close_fd_thread.join();
|
||||||
|
|
||||||
|
for(int fd : fds_to_close) {
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
for(int i = 0; i < num_poll_fd; ++i) {
|
for(int i = 0; i < num_poll_fd; ++i) {
|
||||||
if(poll_fd[i].fd > 0)
|
if(poll_fd[i].fd > 0)
|
||||||
@@ -141,16 +107,10 @@ namespace gsr {
|
|||||||
++num_poll_fd;
|
++num_poll_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
char dev_input_path[128];
|
add_all_joystick_devices();
|
||||||
for(int i = 0; i < 8; ++i) {
|
|
||||||
snprintf(dev_input_path, sizeof(dev_input_path), "/dev/input/js%d", i);
|
|
||||||
add_device(dev_input_path, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(num_poll_fd == 0)
|
|
||||||
fprintf(stderr, "Info: no joysticks found, assuming they might be connected later\n");
|
|
||||||
|
|
||||||
read_thread = std::thread(&GlobalHotkeysJoystick::read_events, this);
|
read_thread = std::thread(&GlobalHotkeysJoystick::read_events, this);
|
||||||
|
close_fd_thread = std::thread(&GlobalHotkeysJoystick::close_fds, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +174,30 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only
|
||||||
|
void GlobalHotkeysJoystick::close_fds() {
|
||||||
|
std::vector<int> current_fds_to_close;
|
||||||
|
while(event_fd > 0) {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(close_fd_mutex);
|
||||||
|
close_fd_cv.wait(lock, [this]{ return !fds_to_close.empty() || event_fd <= 0; });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(close_fd_mutex);
|
||||||
|
current_fds_to_close = std::move(fds_to_close);
|
||||||
|
fds_to_close.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int fd : current_fds_to_close) {
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
current_fds_to_close.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GlobalHotkeysJoystick::read_events() {
|
void GlobalHotkeysJoystick::read_events() {
|
||||||
js_event event;
|
input_event event;
|
||||||
while(poll(poll_fd, num_poll_fd, -1) > 0) {
|
while(poll(poll_fd, num_poll_fd, -1) > 0) {
|
||||||
for(int i = 0; i < num_poll_fd; ++i) {
|
for(int i = 0; i < num_poll_fd; ++i) {
|
||||||
if(poll_fd[i].revents & (POLLHUP|POLLERR|POLLNVAL)) {
|
if(poll_fd[i].revents & (POLLHUP|POLLERR|POLLNVAL)) {
|
||||||
@@ -223,7 +205,7 @@ namespace gsr {
|
|||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
char dev_input_filepath[256];
|
char dev_input_filepath[256];
|
||||||
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/js%d", extra_data[i].dev_input_id);
|
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/event%d", extra_data[i].dev_input_id);
|
||||||
fprintf(stderr, "Info: removed joystick: %s\n", dev_input_filepath);
|
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
|
||||||
@@ -240,7 +222,7 @@ namespace gsr {
|
|||||||
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) {
|
||||||
switch(hotplug_action) {
|
switch(hotplug_action) {
|
||||||
case HotplugAction::ADD: {
|
case HotplugAction::ADD: {
|
||||||
add_device(devname);
|
add_device(devname, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HotplugAction::REMOVE: {
|
case HotplugAction::REMOVE: {
|
||||||
@@ -251,7 +233,7 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
process_js_event(poll_fd[i].fd, event);
|
process_input_event(poll_fd[i].fd, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,54 +242,44 @@ namespace gsr {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalHotkeysJoystick::process_js_event(int fd, js_event &event) {
|
void GlobalHotkeysJoystick::process_input_event(int fd, input_event &event) {
|
||||||
if(read(fd, &event, sizeof(event)) != sizeof(event))
|
if(read(fd, &event, sizeof(event)) != sizeof(event))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if((event.type & JS_EVENT_BUTTON) == JS_EVENT_BUTTON) {
|
if(event.type == EV_KEY) {
|
||||||
switch(event.number) {
|
switch(event.code) {
|
||||||
case playstation_button: {
|
case BTN_MODE: {
|
||||||
// Workaround weird steam input (in-game) behavior where steam triggers playstation button + options when pressing both l3 and r3 at the same time
|
playstation_button_pressed = (event.value == button_pressed);
|
||||||
playstation_button_pressed = (event.value == button_pressed) && !l3_button_pressed && !r3_button_pressed;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case options_button: {
|
case BTN_START: {
|
||||||
if(playstation_button_pressed && event.value == button_pressed)
|
if(playstation_button_pressed && event.value == button_pressed)
|
||||||
toggle_show = true;
|
toggle_show = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case cross_button: {
|
case BTN_SOUTH: {
|
||||||
if(playstation_button_pressed && event.value == button_pressed)
|
if(playstation_button_pressed && event.value == button_pressed)
|
||||||
save_1_min_replay = true;
|
save_1_min_replay = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case triangle_button: {
|
case BTN_NORTH: {
|
||||||
if(playstation_button_pressed && event.value == button_pressed)
|
if(playstation_button_pressed && event.value == button_pressed)
|
||||||
save_10_min_replay = true;
|
save_10_min_replay = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case l3_button: {
|
|
||||||
l3_button_pressed = event.value == button_pressed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case r3_button: {
|
|
||||||
r3_button_pressed = event.value == button_pressed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if((event.type & JS_EVENT_AXIS) == JS_EVENT_AXIS && playstation_button_pressed) {
|
} else if(event.type == EV_ABS && playstation_button_pressed) {
|
||||||
const int trigger_threshold = 16383;
|
|
||||||
const bool prev_up_pressed = up_pressed;
|
const bool prev_up_pressed = up_pressed;
|
||||||
const bool prev_down_pressed = down_pressed;
|
const bool prev_down_pressed = down_pressed;
|
||||||
const bool prev_left_pressed = left_pressed;
|
const bool prev_left_pressed = left_pressed;
|
||||||
const bool prev_right_pressed = right_pressed;
|
const bool prev_right_pressed = right_pressed;
|
||||||
|
|
||||||
if(event.number == axis_up_down) {
|
if(event.code == ABS_HAT0Y) {
|
||||||
up_pressed = event.value <= -trigger_threshold;
|
up_pressed = event.value == -1;
|
||||||
down_pressed = event.value >= trigger_threshold;
|
down_pressed = event.value == 1;
|
||||||
} else if(event.number == axis_left_right) {
|
} else if(event.code == ABS_HAT0X) {
|
||||||
left_pressed = event.value <= -trigger_threshold;
|
left_pressed = event.value == -1;
|
||||||
right_pressed = event.value >= trigger_threshold;
|
right_pressed = event.value == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(up_pressed && !prev_up_pressed)
|
if(up_pressed && !prev_up_pressed)
|
||||||
@@ -321,13 +293,36 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GlobalHotkeysJoystick::add_all_joystick_devices() {
|
||||||
|
DIR *dir = opendir("/dev/input");
|
||||||
|
if(!dir) {
|
||||||
|
fprintf(stderr, "Error: failed to open /dev/input, error: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char dev_input_filepath[1024];
|
||||||
|
for(;;) {
|
||||||
|
struct dirent *entry = readdir(dir);
|
||||||
|
if(!entry)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(strncmp(entry->d_name, "event", 5) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/%s", entry->d_name);
|
||||||
|
add_device(dev_input_filepath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
bool GlobalHotkeysJoystick::add_device(const char *dev_input_filepath, bool print_error) {
|
bool GlobalHotkeysJoystick::add_device(const char *dev_input_filepath, bool print_error) {
|
||||||
if(num_poll_fd >= max_js_poll_fd) {
|
if(num_poll_fd >= max_js_poll_fd) {
|
||||||
fprintf(stderr, "Warning: failed to add joystick device %s, too many joysticks have been added\n", dev_input_filepath);
|
fprintf(stderr, "Warning: failed to add joystick device %s, too many joysticks have been added\n", dev_input_filepath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath);
|
const int dev_input_id = get_dev_input_event_id_from_filepath(dev_input_filepath);
|
||||||
if(dev_input_id == -1)
|
if(dev_input_id == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -338,6 +333,15 @@ namespace gsr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!is_input_device_joystick(fd)) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(close_fd_mutex);
|
||||||
|
fds_to_close.push_back(fd);
|
||||||
|
}
|
||||||
|
close_fd_cv.notify_one();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
poll_fd[num_poll_fd] = {
|
poll_fd[num_poll_fd] = {
|
||||||
fd,
|
fd,
|
||||||
POLLIN,
|
POLLIN,
|
||||||
@@ -356,7 +360,7 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalHotkeysJoystick::remove_device(const char *dev_input_filepath) {
|
bool GlobalHotkeysJoystick::remove_device(const char *dev_input_filepath) {
|
||||||
const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath);
|
const int dev_input_id = get_dev_input_event_id_from_filepath(dev_input_filepath);
|
||||||
if(dev_input_id == -1)
|
if(dev_input_id == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -372,8 +376,13 @@ namespace gsr {
|
|||||||
if(index < 0 || index >= num_poll_fd)
|
if(index < 0 || index >= num_poll_fd)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(poll_fd[index].fd > 0)
|
if(poll_fd[index].fd > 0) {
|
||||||
close(poll_fd[index].fd);
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(close_fd_mutex);
|
||||||
|
fds_to_close.push_back(poll_fd[index].fd);
|
||||||
|
}
|
||||||
|
close_fd_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
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];
|
||||||
|
|||||||
111
src/Overlay.cpp
111
src/Overlay.cpp
@@ -50,6 +50,7 @@ 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 short_notification_timeout_seconds = 2.0;
|
||||||
static const double notification_timeout_seconds = 3.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;
|
||||||
@@ -446,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)),
|
||||||
@@ -474,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);
|
||||||
@@ -499,7 +512,7 @@ namespace gsr {
|
|||||||
hide();
|
hide();
|
||||||
|
|
||||||
if(notification_process > 0) {
|
if(notification_process > 0) {
|
||||||
kill(notification_process, SIGKILL);
|
kill(notification_process, SIGINT);
|
||||||
int status;
|
int status;
|
||||||
if(waitpid(notification_process, &status, 0) == -1) {
|
if(waitpid(notification_process, &status, 0) == -1) {
|
||||||
perror("waitpid failed");
|
perror("waitpid failed");
|
||||||
@@ -946,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;
|
||||||
@@ -973,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;
|
||||||
@@ -1433,6 +1446,10 @@ namespace gsr {
|
|||||||
malloc_trim(0);
|
malloc_trim(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Overlay::hide_next_frame() {
|
||||||
|
hide_ui = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Overlay::toggle_show() {
|
void Overlay::toggle_show() {
|
||||||
if(visible) {
|
if(visible) {
|
||||||
//hide();
|
//hide();
|
||||||
@@ -1645,6 +1662,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);
|
||||||
|
|
||||||
@@ -1685,7 +1704,7 @@ namespace gsr {
|
|||||||
notification_args[arg_index++] = nullptr;
|
notification_args[arg_index++] = nullptr;
|
||||||
|
|
||||||
if(notification_process > 0) {
|
if(notification_process > 0) {
|
||||||
kill(notification_process, SIGKILL);
|
kill(notification_process, SIGINT);
|
||||||
int status = 0;
|
int status = 0;
|
||||||
waitpid(notification_process, &status, 0);
|
waitpid(notification_process, &status, 0);
|
||||||
}
|
}
|
||||||
@@ -1737,6 +1756,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;
|
||||||
@@ -1802,8 +1832,6 @@ namespace gsr {
|
|||||||
result += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1816,6 +1844,15 @@ namespace gsr {
|
|||||||
return replay_duration_sec;
|
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;
|
||||||
@@ -1828,7 +1865,7 @@ namespace gsr {
|
|||||||
if(focused_window_name.empty())
|
if(focused_window_name.empty())
|
||||||
focused_window_name = "Game";
|
focused_window_name = "Game";
|
||||||
|
|
||||||
string_replace_characters(focused_window_name.data(), "/\\", '_');
|
string_replace_characters(focused_window_name.data(), "/\\", ' ');
|
||||||
|
|
||||||
std::string video_directory = filepath_get_directory(video_filepath) + "/" + focused_window_name;
|
std::string video_directory = filepath_get_directory(video_filepath) + "/" + focused_window_name;
|
||||||
create_directory_recursive(video_directory.data());
|
create_directory_recursive(video_directory.data());
|
||||||
@@ -1870,6 +1907,9 @@ namespace gsr {
|
|||||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s\nto \"%s\"",
|
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_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:
|
||||||
@@ -1951,14 +1991,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";
|
||||||
@@ -2001,7 +2051,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);
|
||||||
}
|
}
|
||||||
@@ -2016,7 +2066,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);
|
||||||
}
|
}
|
||||||
@@ -2050,6 +2100,9 @@ namespace gsr {
|
|||||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s",
|
snprintf(msg, sizeof(msg), "Saved a screenshot of %s",
|
||||||
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str());
|
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);
|
||||||
@@ -2468,7 +2521,7 @@ namespace gsr {
|
|||||||
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)
|
||||||
@@ -2501,8 +2554,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";
|
||||||
}
|
}
|
||||||
@@ -2510,6 +2562,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;
|
||||||
@@ -2582,7 +2642,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();
|
||||||
@@ -2658,7 +2718,7 @@ namespace gsr {
|
|||||||
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(), false).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")
|
if(config.replay_config.record_options.record_area_option == "portal")
|
||||||
@@ -2686,7 +2746,7 @@ 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
|
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||||
@@ -2707,7 +2767,7 @@ 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
|
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||||
@@ -2779,7 +2839,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";
|
||||||
@@ -2833,7 +2893,7 @@ namespace gsr {
|
|||||||
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(), false).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")
|
if(config.record_config.record_options.record_area_option == "portal")
|
||||||
@@ -2955,7 +3015,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();
|
||||||
@@ -3019,7 +3079,7 @@ namespace gsr {
|
|||||||
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(), false).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")
|
if(config.streaming_config.record_options.record_area_option == "portal")
|
||||||
@@ -3035,7 +3095,6 @@ namespace gsr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hotkey_window_capture = false;
|
|
||||||
std::string record_area_option;
|
std::string record_area_option;
|
||||||
switch(force_type) {
|
switch(force_type) {
|
||||||
case ScreenshotForceType::NONE:
|
case ScreenshotForceType::NONE:
|
||||||
@@ -3046,7 +3105,6 @@ namespace gsr {
|
|||||||
break;
|
break;
|
||||||
case ScreenshotForceType::WINDOW:
|
case ScreenshotForceType::WINDOW:
|
||||||
record_area_option = gsr_info.system_info.display_server == DisplayServer::X11 ? "window" : "portal";
|
record_area_option = gsr_info.system_info.display_server == DisplayServer::X11 ? "window" : "portal";
|
||||||
hotkey_window_capture = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3077,10 +3135,11 @@ namespace gsr {
|
|||||||
|
|
||||||
// TODO: Validate input, fallback to valid values
|
// TODO: Validate input, fallback to valid values
|
||||||
const std::string output_file = config.screenshot_config.save_directory + "/Screenshot_" + get_date_str() + "." + config.screenshot_config.image_format; // TODO: Validate image format
|
const std::string output_file = config.screenshot_config.save_directory + "/Screenshot_" + get_date_str() + "." + config.screenshot_config.image_format; // TODO: Validate image format
|
||||||
|
const bool capture_cursor = force_type == ScreenshotForceType::NONE && config.screenshot_config.record_cursor;
|
||||||
|
|
||||||
std::vector<const char*> args = {
|
std::vector<const char*> args = {
|
||||||
"gpu-screen-recorder", "-w", screenshot_capture_target.c_str(),
|
"gpu-screen-recorder", "-w", screenshot_capture_target.c_str(),
|
||||||
"-cursor", config.screenshot_config.record_cursor ? "yes" : "no",
|
"-cursor", capture_cursor ? "yes" : "no",
|
||||||
"-v", "no",
|
"-v", "no",
|
||||||
"-q", config.screenshot_config.image_quality.c_str(),
|
"-q", config.screenshot_config.image_quality.c_str(),
|
||||||
"-o", output_file.c_str()
|
"-o", output_file.c_str()
|
||||||
@@ -3094,7 +3153,7 @@ namespace gsr {
|
|||||||
args.push_back(size);
|
args.push_back(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config.screenshot_config.restore_portal_session && !hotkey_window_capture) {
|
if(config.screenshot_config.restore_portal_session && force_type != ScreenshotForceType::WINDOW) {
|
||||||
args.push_back("-restore-portal-session");
|
args.push_back("-restore-portal-session");
|
||||||
args.push_back("yes");
|
args.push_back("yes");
|
||||||
}
|
}
|
||||||
@@ -3102,7 +3161,7 @@ namespace gsr {
|
|||||||
const std::string hotkey_window_capture_portal_session_token_filepath = get_config_dir() + "/gpu-screen-recorder/gsr-ui-window-capture-token";
|
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") {
|
if(record_area_option == "portal") {
|
||||||
hide_ui = true;
|
hide_ui = true;
|
||||||
if(hotkey_window_capture) {
|
if(force_type == ScreenshotForceType::WINDOW) {
|
||||||
args.push_back("-portal-session-token-filepath");
|
args.push_back("-portal-session-token-filepath");
|
||||||
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
|
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
204
src/Rpc.cpp
204
src/Rpc.cpp
@@ -5,11 +5,12 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <sys/stat.h>
|
#include <poll.h>
|
||||||
#include <fcntl.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *filename) {
|
static void get_socket_filepath(char *buffer, size_t buffer_size, const char *filename) {
|
||||||
char dir[PATH_MAX];
|
char dir[PATH_MAX];
|
||||||
|
|
||||||
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
|
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
|
||||||
@@ -24,79 +25,117 @@ namespace gsr {
|
|||||||
snprintf(buffer, buffer_size, "%s/%s", dir, filename);
|
snprintf(buffer, buffer_size, "%s/%s", dir, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int create_socket(const char *name, struct sockaddr_un *addr, std::string &socket_filepath) {
|
||||||
|
char socket_filepath_tmp[PATH_MAX];
|
||||||
|
get_socket_filepath(socket_filepath_tmp, sizeof(socket_filepath_tmp), name);
|
||||||
|
socket_filepath = socket_filepath_tmp;
|
||||||
|
|
||||||
|
memset(addr, 0, sizeof(*addr));
|
||||||
|
if(strlen(name) > sizeof(addr->sun_path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
addr->sun_family = AF_UNIX;
|
||||||
|
snprintf(addr->sun_path, sizeof(addr->sun_path), "%s", socket_filepath.c_str());
|
||||||
|
|
||||||
|
return socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rpc::Rpc() {
|
||||||
|
num_polls = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Rpc::~Rpc() {
|
Rpc::~Rpc() {
|
||||||
if(fd > 0)
|
if(socket_fd > 0)
|
||||||
close(fd);
|
close(socket_fd);
|
||||||
|
|
||||||
if(file)
|
if(!socket_filepath.empty())
|
||||||
fclose(file);
|
unlink(socket_filepath.c_str());
|
||||||
|
|
||||||
if(!fifo_filepath.empty())
|
|
||||||
unlink(fifo_filepath.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Rpc::create(const char *name) {
|
bool Rpc::create(const char *name) {
|
||||||
if(file) {
|
if(socket_fd > 0) {
|
||||||
fprintf(stderr, "Error: Rpc::create: already created/opened\n");
|
fprintf(stderr, "Error: Rpc::create: already created/opened\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char fifo_filepath_tmp[PATH_MAX];
|
struct sockaddr_un addr;
|
||||||
get_runtime_filepath(fifo_filepath_tmp, sizeof(fifo_filepath_tmp), name);
|
socket_fd = create_socket(name, &addr, socket_filepath);
|
||||||
fifo_filepath = fifo_filepath_tmp;
|
if(socket_fd <= 0) {
|
||||||
unlink(fifo_filepath.c_str());
|
fprintf(stderr, "Error: Rpc::create: failed to create socket, error: %s\n", strerror(errno));
|
||||||
|
|
||||||
if(mkfifo(fifo_filepath.c_str(), 0600) != 0) {
|
|
||||||
fprintf(stderr, "Error: mkfifo failed, error: %s, %s\n", strerror(errno), fifo_filepath.c_str());
|
|
||||||
fifo_filepath.clear();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!open_filepath(fifo_filepath.c_str())) {
|
unlink(socket_filepath.c_str());
|
||||||
unlink(fifo_filepath.c_str());
|
|
||||||
fifo_filepath.clear();
|
if(bind(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
const int err = errno;
|
||||||
|
close(socket_fd);
|
||||||
|
socket_fd = 0;
|
||||||
|
|
||||||
|
fprintf(stderr, "Error: Rpc::create: failed to bind, error: %s\n", strerror(err));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(listen(socket_fd, GSR_RPC_MAX_CONNECTIONS) == -1) {
|
||||||
|
const int err = errno;
|
||||||
|
close(socket_fd);
|
||||||
|
socket_fd = 0;
|
||||||
|
|
||||||
|
fprintf(stderr, "Error: Rpc::create: failed to listen, error: %s\n", strerror(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
polls[0].fd = socket_fd;
|
||||||
|
polls[0].events = POLLIN;
|
||||||
|
polls[0].revents = 0;
|
||||||
|
++num_polls;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Rpc::open(const char *name) {
|
RpcOpenResult Rpc::open(const char *name) {
|
||||||
if(file) {
|
if(socket_fd > 0) {
|
||||||
fprintf(stderr, "Error: Rpc::open: already created/opened\n");
|
fprintf(stderr, "Error: Rpc::open: already created/opened\n");
|
||||||
return false;
|
return RpcOpenResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
char fifo_filepath_tmp[PATH_MAX];
|
struct sockaddr_un addr;
|
||||||
get_runtime_filepath(fifo_filepath_tmp, sizeof(fifo_filepath_tmp), name);
|
socket_fd = create_socket(name, &addr, socket_filepath);
|
||||||
return open_filepath(fifo_filepath_tmp);
|
socket_filepath.clear(); /* We dont want to delete the socket on exit as the client */
|
||||||
}
|
if(socket_fd <= 0) {
|
||||||
|
fprintf(stderr, "Error: Rpc::open: failed to create socket, error: %s\n", strerror(errno));
|
||||||
bool Rpc::open_filepath(const char *filepath) {
|
return RpcOpenResult::ERROR;
|
||||||
fd = ::open(filepath, O_RDWR | O_NONBLOCK);
|
|
||||||
if(fd <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
file = fdopen(fd, "r+");
|
|
||||||
if(!file) {
|
|
||||||
close(fd);
|
|
||||||
fd = 0;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
fd = 0;
|
|
||||||
return true;
|
while(true) {
|
||||||
|
if(connect(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
const int err = errno;
|
||||||
|
if(err == EWOULDBLOCK) {
|
||||||
|
usleep(10 * 1000);
|
||||||
|
} else {
|
||||||
|
close(socket_fd);
|
||||||
|
socket_fd = 0;
|
||||||
|
if(err != ENOENT && err != ECONNREFUSED)
|
||||||
|
fprintf(stderr, "Error: Rpc::create: failed to connect, error: %s\n", strerror(err));
|
||||||
|
return RpcOpenResult::ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RpcOpenResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Rpc::write(const char *str, size_t size) {
|
bool Rpc::write(const char *str, size_t size) {
|
||||||
if(!file) {
|
if(socket_fd <= 0) {
|
||||||
fprintf(stderr, "Error: Rpc::write: fifo not created/opened yet\n");
|
fprintf(stderr, "Error: Rpc::write: unix domain socket not created/opened yet\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t offset = 0;
|
ssize_t offset = 0;
|
||||||
while(offset < (ssize_t)size) {
|
while(offset < (ssize_t)size) {
|
||||||
const ssize_t bytes_written = fwrite(str + offset, 1, size - offset, file);
|
const ssize_t bytes_written = ::write(socket_fd, str + offset, size - offset);
|
||||||
fflush(file);
|
|
||||||
if(bytes_written > 0)
|
if(bytes_written > 0)
|
||||||
offset += bytes_written;
|
offset += bytes_written;
|
||||||
}
|
}
|
||||||
@@ -104,30 +143,73 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Rpc::poll() {
|
void Rpc::poll() {
|
||||||
if(!file) {
|
if(socket_fd <= 0) {
|
||||||
//fprintf(stderr, "Error: Rpc::poll: fifo not created/opened yet\n");
|
//fprintf(stderr, "Error: Rpc::poll: unix domain socket not created/opened yet\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
char line[1024];
|
while(::poll(polls, num_polls, 0) > 0) {
|
||||||
while(fgets(line, sizeof(line), file)) {
|
for(int i = 0; i < num_polls; ++i) {
|
||||||
int line_len = strlen(line);
|
if(polls[i].fd == socket_fd) {
|
||||||
if(line_len == 0)
|
if(polls[i].revents & (POLLERR|POLLHUP)) {
|
||||||
continue;
|
close(socket_fd);
|
||||||
|
socket_fd = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(line[line_len - 1] == '\n') {
|
const int client_fd = accept(socket_fd, NULL, NULL);
|
||||||
line[line_len - 1] = '\0';
|
if(num_polls >= GSR_RPC_MAX_POLLS) {
|
||||||
--line_len;
|
if(errno != EWOULDBLOCK)
|
||||||
|
fprintf(stderr, "Error: Rpc::poll: unable to accept more clients, error: %s\n", strerror(errno));
|
||||||
|
} else {
|
||||||
|
polls[num_polls].fd = client_fd;
|
||||||
|
polls[num_polls].events = POLLIN;
|
||||||
|
polls[num_polls].revents = 0;
|
||||||
|
++num_polls;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(polls[i].revents & POLLIN)
|
||||||
|
handle_client_data(polls[i].fd, polls_data[i]);
|
||||||
|
|
||||||
|
if(polls[i].revents & (POLLERR|POLLHUP)) {
|
||||||
|
close(polls[i].fd);
|
||||||
|
polls[i] = polls[num_polls - 1];
|
||||||
|
|
||||||
|
memcpy(polls_data[i].buffer, polls_data[num_polls - 1].buffer, polls_data[num_polls - 1].buffer_size);
|
||||||
|
polls_data[i].buffer_size = polls_data[num_polls - 1].buffer_size;
|
||||||
|
|
||||||
|
--num_polls;
|
||||||
|
--i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
name = line;
|
|
||||||
auto it = handlers_by_name.find(name);
|
|
||||||
if(it != handlers_by_name.end())
|
|
||||||
it->second(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Rpc::handle_client_data(int client_fd, PollData &poll_data) {
|
||||||
|
char *write_buffer = poll_data.buffer + poll_data.buffer_size;
|
||||||
|
const ssize_t num_bytes_read = read(client_fd, write_buffer, sizeof(poll_data.buffer) - poll_data.buffer_size);
|
||||||
|
if(num_bytes_read <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
poll_data.buffer_size += num_bytes_read;
|
||||||
|
const char *newline_p = (const char*)memchr(write_buffer, '\n', num_bytes_read);
|
||||||
|
if(!newline_p)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const size_t command_size = newline_p - poll_data.buffer;
|
||||||
|
std::string name;
|
||||||
|
name.assign(poll_data.buffer, command_size);
|
||||||
|
memmove(poll_data.buffer, newline_p + 1, poll_data.buffer_size - (command_size + 1));
|
||||||
|
poll_data.buffer_size -= (command_size + 1);
|
||||||
|
|
||||||
|
auto it = handlers_by_name.find(name);
|
||||||
|
if(it != handlers_by_name.end())
|
||||||
|
it->second(name);
|
||||||
|
}
|
||||||
|
|
||||||
bool Rpc::add_handler(const std::string &name, RpcCallback callback) {
|
bool Rpc::add_handler(const std::string &name, RpcCallback callback) {
|
||||||
return handlers_by_name.insert(std::make_pair(name, std::move(callback))).second;
|
return handlers_by_name.insert(std::make_pair(name, std::move(callback))).second;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#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;
|
||||||
@@ -22,8 +23,13 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry::Entry(mgl::Font *font, const char *text, float max_width) : text("", *font), max_width(max_width) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +37,8 @@ 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) {
|
||||||
const mgl::vec2f mouse_pos = { (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 };
|
||||||
selected = mgl::FloatRect(position + offset, get_size()).contains(mouse_pos);
|
selected = mgl::FloatRect(position + offset, get_size()).contains(mouse_pos);
|
||||||
@@ -38,9 +46,8 @@ namespace gsr {
|
|||||||
selecting_text = true;
|
selecting_text = true;
|
||||||
|
|
||||||
const auto caret_index_mouse = find_closest_caret_index_by_position(mouse_pos);
|
const auto caret_index_mouse = find_closest_caret_index_by_position(mouse_pos);
|
||||||
caret.byte_index = caret_index_mouse.byte_index;
|
caret.index = caret_index_mouse.index;
|
||||||
caret.utf8_index = caret_index_mouse.utf8_index;
|
caret.offset_x = caret_index_mouse.pos.x - active_text.get_position().x;
|
||||||
caret.offset_x = caret_index_mouse.pos.x - this->text.get_position().x;
|
|
||||||
selection_start_caret = caret;
|
selection_start_caret = caret;
|
||||||
show_selection = true;
|
show_selection = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -50,89 +57,72 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
} else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left) {
|
} else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left) {
|
||||||
selecting_text = false;
|
selecting_text = false;
|
||||||
if(caret.byte_index == selection_start_caret.byte_index)
|
if(caret.index == selection_start_caret.index)
|
||||||
show_selection = false;
|
show_selection = false;
|
||||||
} else if(event.type == mgl::Event::MouseMoved && selected) {
|
} else if(event.type == mgl::Event::MouseMoved && selected) {
|
||||||
if(selecting_text) {
|
if(selecting_text) {
|
||||||
const auto caret_index_mouse = find_closest_caret_index_by_position(mgl::vec2f(event.mouse_move.x, event.mouse_move.y));
|
const auto caret_index_mouse = find_closest_caret_index_by_position(mgl::vec2f(event.mouse_move.x, event.mouse_move.y));
|
||||||
caret.byte_index = caret_index_mouse.byte_index;
|
caret.index = caret_index_mouse.index;
|
||||||
caret.utf8_index = caret_index_mouse.utf8_index;
|
caret.offset_x = caret_index_mouse.pos.x - active_text.get_position().x;
|
||||||
caret.offset_x = caret_index_mouse.pos.x - this->text.get_position().x;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
||||||
int selection_start_byte = caret.byte_index;
|
int selection_start_byte = caret.index;
|
||||||
int selection_end_byte = caret.byte_index;
|
int selection_end_byte = caret.index;
|
||||||
if(show_selection) {
|
if(show_selection) {
|
||||||
selection_start_byte = std::min(caret.byte_index, selection_start_caret.byte_index);
|
selection_start_byte = std::min(caret.index, selection_start_caret.index);
|
||||||
selection_end_byte = std::max(caret.byte_index, selection_start_caret.byte_index);
|
selection_end_byte = std::max(caret.index, selection_start_caret.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.key.code == mgl::Keyboard::Backspace) {
|
if(event.key.code == mgl::Keyboard::Backspace) {
|
||||||
if(selection_start_byte == selection_end_byte && caret.byte_index > 0)
|
if(selection_start_byte == selection_end_byte && caret.index > 0)
|
||||||
selection_start_byte = mgl::utf8_get_start_of_codepoint((const unsigned char*)text.get_string().c_str(), text.get_string().size(), caret.byte_index - 1);
|
selection_start_byte -= 1;
|
||||||
|
|
||||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, "");
|
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::u32string());
|
||||||
} else if(event.key.code == mgl::Keyboard::Delete) {
|
} else if(event.key.code == mgl::Keyboard::Delete) {
|
||||||
if(selection_start_byte == selection_end_byte && caret.byte_index < (int)text.get_string().size()) {
|
if(selection_start_byte == selection_end_byte && caret.index < (int)active_text.get_string().size())
|
||||||
size_t codepoint_length = 1;
|
selection_end_byte += 1;
|
||||||
mgl::utf8_get_codepoint_length(((const unsigned char*)text.get_string().c_str())[caret.byte_index], &codepoint_length);
|
|
||||||
selection_end_byte = selection_start_byte + codepoint_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, "");
|
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::u32string());
|
||||||
} else if(event.key.code == mgl::Keyboard::C && event.key.control) {
|
} else if(event.key.code == mgl::Keyboard::C && event.key.control) {
|
||||||
const size_t selection_num_bytes = selection_end_byte - selection_start_byte;
|
const size_t selection_num_bytes = selection_end_byte - selection_start_byte;
|
||||||
if(selection_num_bytes > 0)
|
if(selection_num_bytes > 0)
|
||||||
window.set_clipboard(text.get_string().substr(selection_start_byte, selection_num_bytes));
|
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) {
|
} else if(event.key.code == mgl::Keyboard::V && event.key.control) {
|
||||||
std::string clipboard_string = window.get_clipboard_string();
|
std::string clipboard_string = window.get_clipboard_string();
|
||||||
string_replace_all(clipboard_string, '\n', ' ');
|
string_replace_all(clipboard_string, '\n', ' ');
|
||||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::move(clipboard_string));
|
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) {
|
} else if(event.key.code == mgl::Keyboard::A && event.key.control) {
|
||||||
selection_start_caret.byte_index = 0;
|
selection_start_caret.index = 0;
|
||||||
selection_start_caret.utf8_index = 0;
|
|
||||||
selection_start_caret.offset_x = 0.0f;
|
selection_start_caret.offset_x = 0.0f;
|
||||||
|
|
||||||
caret.byte_index = text.get_string().size();
|
caret.index = active_text.get_string().size();
|
||||||
caret.utf8_index = mgl::utf8_get_character_count((const unsigned char*)text.get_string().data(), text.get_string().size());
|
|
||||||
// TODO: Optimize
|
// TODO: Optimize
|
||||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
|
||||||
show_selection = true;
|
show_selection = true;
|
||||||
} else if(event.key.code == mgl::Keyboard::Left && caret.byte_index > 0) {
|
} else if(event.key.code == mgl::Keyboard::Left) {
|
||||||
if(!selecting_with_keyboard && show_selection) {
|
if(!selecting_with_keyboard && show_selection)
|
||||||
show_selection = false;
|
show_selection = false;
|
||||||
} else {
|
else
|
||||||
caret.byte_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)text.get_string().data(), text.get_string().size(), caret.byte_index - 1);
|
move_caret_word(Direction::LEFT, event.key.control ? 999999 : 1);
|
||||||
caret.utf8_index -= 1;
|
|
||||||
// TODO: Move left by one character instead of calculating every character to caret index
|
|
||||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!selecting_with_keyboard) {
|
if(!selecting_with_keyboard) {
|
||||||
selection_start_caret = caret;
|
selection_start_caret = caret;
|
||||||
show_selection = false;
|
show_selection = false;
|
||||||
}
|
}
|
||||||
} else if(event.key.code == mgl::Keyboard::Right) {
|
} else if(event.key.code == mgl::Keyboard::Right) {
|
||||||
if(!selecting_with_keyboard && show_selection) {
|
if(!selecting_with_keyboard && show_selection)
|
||||||
show_selection = false;
|
show_selection = false;
|
||||||
} else {
|
else
|
||||||
const int caret_byte_index_before = caret.byte_index;
|
move_caret_word(Direction::RIGHT, event.key.control ? 999999 : 1);
|
||||||
caret.byte_index = mgl::utf8_index_to_byte_index((const unsigned char*)text.get_string().data(), text.get_string().size(), caret.utf8_index + 1);
|
|
||||||
if(caret.byte_index != caret_byte_index_before)
|
|
||||||
caret.utf8_index += 1;
|
|
||||||
// TODO: Move right by one character instead of calculating every character to caret index
|
|
||||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!selecting_with_keyboard) {
|
if(!selecting_with_keyboard) {
|
||||||
selection_start_caret = caret;
|
selection_start_caret = caret;
|
||||||
show_selection = false;
|
show_selection = false;
|
||||||
}
|
}
|
||||||
} else if(event.key.code == mgl::Keyboard::Home) {
|
} else if(event.key.code == mgl::Keyboard::Home) {
|
||||||
caret.byte_index = 0;
|
caret.index = 0;
|
||||||
caret.utf8_index = 0;
|
|
||||||
caret.offset_x = 0.0f;
|
caret.offset_x = 0.0f;
|
||||||
|
|
||||||
if(!selecting_with_keyboard) {
|
if(!selecting_with_keyboard) {
|
||||||
@@ -140,10 +130,9 @@ namespace gsr {
|
|||||||
show_selection = false;
|
show_selection = false;
|
||||||
}
|
}
|
||||||
} else if(event.key.code == mgl::Keyboard::End) {
|
} else if(event.key.code == mgl::Keyboard::End) {
|
||||||
caret.byte_index = text.get_string().size();
|
caret.index = active_text.get_string().size();
|
||||||
caret.utf8_index = mgl::utf8_get_character_count((const unsigned char*)text.get_string().data(), text.get_string().size());
|
|
||||||
// TODO: Optimize
|
// TODO: Optimize
|
||||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
|
|
||||||
if(!selecting_with_keyboard) {
|
if(!selecting_with_keyboard) {
|
||||||
selection_start_caret = caret;
|
selection_start_caret = caret;
|
||||||
@@ -164,14 +153,14 @@ namespace gsr {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32 && event.text.codepoint != 127) {
|
} else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32 && event.text.codepoint != 127) {
|
||||||
int selection_start_byte = caret.byte_index;
|
int selection_start_byte = caret.index;
|
||||||
int selection_end_byte = caret.byte_index;
|
int selection_end_byte = caret.index;
|
||||||
if(show_selection) {
|
if(show_selection) {
|
||||||
selection_start_byte = std::min(caret.byte_index, selection_start_caret.byte_index);
|
selection_start_byte = std::min(caret.index, selection_start_caret.index);
|
||||||
selection_end_byte = std::max(caret.byte_index, selection_start_caret.byte_index);
|
selection_end_byte = std::max(caret.index, selection_start_caret.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::string(event.text.str, event.text.size));
|
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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,13 +178,15 @@ namespace gsr {
|
|||||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||||
const int padding_right = padding_right_scale * get_theme().window_height;
|
const int padding_right = padding_right_scale * get_theme().window_height;
|
||||||
|
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
|
||||||
background.set_size(get_size());
|
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 int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
||||||
const mgl::vec2f caret_size = mgl::vec2f(caret_width, text.get_bounds().size.y).floor();
|
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);
|
const float overflow_left = (caret.offset_x + padding_left) - (padding_left + text_overflow);
|
||||||
if(overflow_left < 0.0f)
|
if(overflow_left < 0.0f)
|
||||||
@@ -205,18 +196,18 @@ namespace gsr {
|
|||||||
if(overflow_right - text_overflow > 0.0f)
|
if(overflow_right - text_overflow > 0.0f)
|
||||||
text_overflow = overflow_right;
|
text_overflow = overflow_right;
|
||||||
|
|
||||||
text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - text.get_bounds().size.y * 0.5f) - mgl::vec2f(text_overflow, 0.0f)).floor());
|
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 = text.get_bounds();
|
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 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);
|
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_larger_than_background) {
|
||||||
if(text_overflow_right < 0.0f) {
|
if(text_overflow_right < 0.0f) {
|
||||||
text_overflow += text_overflow_right;
|
text_overflow += text_overflow_right;
|
||||||
text.set_position(text.get_position() + mgl::vec2f(-text_overflow_right, 0.0f));
|
active_text.set_position(active_text.get_position() + mgl::vec2f(-text_overflow_right, 0.0f));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text.set_position(text.get_position() + mgl::vec2f(-text_overflow, 0.0f));
|
active_text.set_position(active_text.get_position() + mgl::vec2f(-text_overflow, 0.0f));
|
||||||
text_overflow = 0.0f;
|
text_overflow = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +225,7 @@ namespace gsr {
|
|||||||
});
|
});
|
||||||
window.set_scissor(scissor);
|
window.set_scissor(scissor);
|
||||||
|
|
||||||
window.draw(text);
|
window.draw(active_text);
|
||||||
|
|
||||||
if(show_selection)
|
if(show_selection)
|
||||||
draw_caret_selection(window, draw_pos, caret_size);
|
draw_caret_selection(window, draw_pos, caret_size);
|
||||||
@@ -254,11 +245,16 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Entry::draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size) {
|
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_top = padding_top_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 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), caret_size.y).floor());
|
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, padding_top)).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;
|
mgl::Color caret_select_color = get_color_theme().tint_color;
|
||||||
caret_select_color.a = 100;
|
caret_select_color.a = 100;
|
||||||
caret_selection_rect.set_color(caret_select_color);
|
caret_selection_rect.set_color(caret_select_color);
|
||||||
@@ -274,13 +270,43 @@ 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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
EntryValidateHandlerResult Entry::set_text(std::string str) {
|
void Entry::move_caret_word(Direction direction, size_t max_codepoints) {
|
||||||
EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
|
const int dir_step = direction == Direction::LEFT ? -1 : 1;
|
||||||
|
const int num_delimiter_chars = 15;
|
||||||
|
const char delimiter_chars[num_delimiter_chars + 1] = " \t\n/.,:;\\[](){}";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryValidateHandlerResult Entry::set_text(const std::string &str) {
|
||||||
|
EntryValidateHandlerResult validate_result = set_text_internal(mgl::utf8_to_utf32(str));
|
||||||
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
||||||
caret.byte_index = text.get_string().size();
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
caret.utf8_index = mgl::utf8_get_character_count((const unsigned char*)text.get_string().data(), text.get_string().size());
|
caret.index = active_text.get_string().size();
|
||||||
// TODO: Optimize
|
// TODO: Optimize
|
||||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
selection_start_caret = caret;
|
selection_start_caret = caret;
|
||||||
|
|
||||||
selecting_text = false;
|
selecting_text = false;
|
||||||
@@ -290,40 +316,59 @@ namespace gsr {
|
|||||||
return validate_result;
|
return validate_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntryValidateHandlerResult Entry::set_text_internal(std::string str) {
|
EntryValidateHandlerResult Entry::set_text_internal(std::u32string str) {
|
||||||
EntryValidateHandlerResult validate_result = EntryValidateHandlerResult::ALLOW;
|
EntryValidateHandlerResult validate_result = EntryValidateHandlerResult::ALLOW;
|
||||||
if(validate_handler)
|
if(validate_handler)
|
||||||
validate_result = validate_handler(*this, str);
|
validate_result = validate_handler(*this, str);
|
||||||
|
|
||||||
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
||||||
text.set_string(std::move(str));
|
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)
|
if(on_changed)
|
||||||
on_changed(text.get_string());
|
on_changed(mgl::utf32_to_utf8(text.get_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return validate_result;
|
return validate_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& Entry::get_text() const {
|
std::string Entry::get_text() const {
|
||||||
return text.get_string();
|
return mgl::utf32_to_utf8(text.get_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entry::replace_text(size_t index, size_t size, const std::string &replacement) {
|
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())
|
if(index + size > text.get_string().size())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto prev_caret = caret;
|
const auto prev_caret = caret;
|
||||||
|
|
||||||
if((int)index >= caret.byte_index) {
|
if((int)index >= caret.index)
|
||||||
caret.utf8_index += mgl::utf8_get_character_count((const unsigned char*)replacement.c_str(), replacement.size());
|
caret.index += replacement.size();
|
||||||
caret.byte_index += replacement.size();
|
else
|
||||||
} else {
|
caret.index = caret.index - size + replacement.size();
|
||||||
caret.utf8_index -= mgl::utf8_get_character_count((const unsigned char*)(text.get_string().c_str() + caret.byte_index - size), size);
|
|
||||||
caret.utf8_index += mgl::utf8_get_character_count((const unsigned char*)replacement.c_str(), replacement.size());
|
|
||||||
caret.byte_index = caret.byte_index - size + replacement.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string str = text.get_string();
|
std::u32string str = text.get_string();
|
||||||
str.replace(index, size, replacement);
|
str.replace(index, size, replacement);
|
||||||
const EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
const EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
||||||
if(validate_result == EntryValidateHandlerResult::DENY) {
|
if(validate_result == EntryValidateHandlerResult::DENY) {
|
||||||
@@ -333,8 +378,9 @@ namespace gsr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
// TODO: Optimize
|
// TODO: Optimize
|
||||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||||
selection_start_caret = caret;
|
selection_start_caret = caret;
|
||||||
|
|
||||||
selecting_text = false;
|
selecting_text = false;
|
||||||
@@ -342,17 +388,14 @@ namespace gsr {
|
|||||||
show_selection = false;
|
show_selection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mgl_index_codepoint_pair Entry::find_closest_caret_index_by_position(mgl::vec2f position) {
|
CaretIndexPos Entry::find_closest_caret_index_by_position(mgl::vec2f position) {
|
||||||
const std::string &str = text.get_string();
|
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||||
mgl::Font *font = text.get_font();
|
const std::u32string &str = active_text.get_string();
|
||||||
|
mgl::Font *font = active_text.get_font();
|
||||||
|
|
||||||
mgl_index_codepoint_pair result = {0, 0, {text.get_position().x, text.get_position().y}};
|
CaretIndexPos result = {0, {active_text.get_position().x, active_text.get_position().y}};
|
||||||
|
for(result.index = 0; result.index < (int)str.size(); ++result.index) {
|
||||||
for(; result.byte_index < str.size();) {
|
const uint32_t codepoint = str[result.index];
|
||||||
uint32_t codepoint = ' ';
|
|
||||||
size_t clen = 1;
|
|
||||||
if(!mgl::utf8_decode((const unsigned char*)&str[result.byte_index], str.size() - result.byte_index, &codepoint, &clen))
|
|
||||||
clen = 1;
|
|
||||||
|
|
||||||
float glyph_width = 0.0f;
|
float glyph_width = 0.0f;
|
||||||
if(codepoint == '\t') {
|
if(codepoint == '\t') {
|
||||||
@@ -368,8 +411,6 @@ namespace gsr {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
result.pos.x += glyph_width;
|
result.pos.x += glyph_width;
|
||||||
result.byte_index += clen;
|
|
||||||
result.utf8_index += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -379,7 +420,7 @@ namespace gsr {
|
|||||||
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;
|
||||||
|
|
||||||
@@ -406,7 +447,7 @@ 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](Entry &entry, const std::string &str) {
|
return [min, max](Entry &entry, const std::u32string &str) {
|
||||||
if(str.empty())
|
if(str.empty())
|
||||||
return EntryValidateHandlerResult::ALLOW;
|
return EntryValidateHandlerResult::ALLOW;
|
||||||
|
|
||||||
|
|||||||
@@ -467,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) {
|
||||||
@@ -499,6 +528,22 @@ namespace gsr {
|
|||||||
return std::make_unique<Subsection>("Application info", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
|
return std::make_unique<Subsection>("Application info", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Subsection> GlobalSettingsPage::create_donate_subsection(ScrollablePage *parent_page) {
|
||||||
|
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||||
|
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:", get_color_theme().text_color));
|
||||||
|
|
||||||
|
auto donate_button = std::make_unique<Button>(&get_theme().body_font, "Donate", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||||
|
donate_button->on_click = [this] {
|
||||||
|
const char *args[] = { "xdg-open", "https://buymeacoffee.com/dec05eba", nullptr };
|
||||||
|
exec_program_daemonized(args);
|
||||||
|
overlay->hide_next_frame();
|
||||||
|
};
|
||||||
|
list->add_widget(std::move(donate_button));
|
||||||
|
|
||||||
|
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.", get_color_theme().text_color));
|
||||||
|
return std::make_unique<Subsection>("Donate", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
void GlobalSettingsPage::add_widgets() {
|
void GlobalSettingsPage::add_widgets() {
|
||||||
auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size());
|
auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size());
|
||||||
|
|
||||||
@@ -510,6 +555,7 @@ namespace gsr {
|
|||||||
settings_list->add_widget(create_controller_hotkey_subsection(scrollable_page.get()));
|
settings_list->add_widget(create_controller_hotkey_subsection(scrollable_page.get()));
|
||||||
settings_list->add_widget(create_application_options_subsection(scrollable_page.get()));
|
settings_list->add_widget(create_application_options_subsection(scrollable_page.get()));
|
||||||
settings_list->add_widget(create_application_info_subsection(scrollable_page.get()));
|
settings_list->add_widget(create_application_info_subsection(scrollable_page.get()));
|
||||||
|
settings_list->add_widget(create_donate_subsection(scrollable_page.get()));
|
||||||
scrollable_page->add_widget(std::move(settings_list));
|
scrollable_page->add_widget(std::move(settings_list));
|
||||||
|
|
||||||
content_page_ptr->add_widget(std::move(scrollable_page));
|
content_page_ptr->add_widget(std::move(scrollable_page));
|
||||||
@@ -535,6 +581,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,6 +609,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace gsr {
|
|||||||
if(capture_options.region)
|
if(capture_options.region)
|
||||||
record_area_box->add_item("Region", "region");
|
record_area_box->add_item("Region", "region");
|
||||||
if(!capture_options.monitors.empty())
|
if(!capture_options.monitors.empty())
|
||||||
record_area_box->add_item(gsr_info->system_info.display_server == DisplayServer::WAYLAND ? "Focused monitor (Experimental on Wayland)" : "Focused monitor", "focused_monitor");
|
record_area_box->add_item("Focused monitor", "focused_monitor");
|
||||||
for(const auto &monitor : capture_options.monitors) {
|
for(const auto &monitor : capture_options.monitors) {
|
||||||
char name[256];
|
char name[256];
|
||||||
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
|
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
|
||||||
@@ -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"
|
||||||
@@ -72,7 +73,7 @@ namespace gsr {
|
|||||||
if(capture_options.region)
|
if(capture_options.region)
|
||||||
record_area_box->add_item("Region", "region");
|
record_area_box->add_item("Region", "region");
|
||||||
if(!capture_options.monitors.empty())
|
if(!capture_options.monitors.empty())
|
||||||
record_area_box->add_item(gsr_info->system_info.display_server == DisplayServer::WAYLAND ? "Focused monitor (Experimental on Wayland)" : "Focused monitor", "focused_monitor");
|
record_area_box->add_item("Focused monitor", "focused_monitor");
|
||||||
for(const auto &monitor : capture_options.monitors) {
|
for(const auto &monitor : capture_options.monitors) {
|
||||||
char name[256];
|
char name[256];
|
||||||
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
|
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
|
||||||
@@ -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,42 +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_custom_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, "Stream 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));
|
||||||
stream_url_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<List> SettingsPage::create_stream_custom_key() {
|
||||||
|
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);
|
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();
|
stream_key_entry_ptr = stream_key_entry.get();
|
||||||
stream_url_list->add_widget(std::move(stream_key_entry));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
stream_url_list_ptr = stream_url_list.get();
|
std::unique_ptr<List> SettingsPage::create_stream_custom_section() {
|
||||||
return stream_url_list;
|
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() {
|
||||||
@@ -1013,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,7 +1063,6 @@ namespace gsr {
|
|||||||
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_custom_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);
|
||||||
@@ -1051,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");
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/main.cpp
32
src/main.cpp
@@ -102,24 +102,6 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_gsr_ui_virtual_keyboard_running() {
|
|
||||||
FILE *f = fopen("/proc/bus/input/devices", "rb");
|
|
||||||
if(!f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool virtual_keyboard_running = false;
|
|
||||||
char line[1024];
|
|
||||||
while(fgets(line, sizeof(line), f)) {
|
|
||||||
if(strstr(line, "gsr-ui virtual keyboard")) {
|
|
||||||
virtual_keyboard_running = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
return virtual_keyboard_running;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void install_flatpak_systemd_service() {
|
static void install_flatpak_systemd_service() {
|
||||||
const bool systemd_service_exists = system(
|
const bool systemd_service_exists = system(
|
||||||
"data_home=$(flatpak-spawn --host -- /bin/sh -c 'echo \"${XDG_DATA_HOME:-$HOME/.local/share}\"') && "
|
"data_home=$(flatpak-spawn --host -- /bin/sh -c 'echo \"${XDG_DATA_HOME:-$HOME/.local/share}\"') && "
|
||||||
@@ -195,7 +177,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");
|
||||||
@@ -224,16 +208,13 @@ int main(int argc, char **argv) {
|
|||||||
set_display_server_environment_variables();
|
set_display_server_environment_variables();
|
||||||
|
|
||||||
auto rpc = std::make_unique<gsr::Rpc>();
|
auto rpc = std::make_unique<gsr::Rpc>();
|
||||||
const bool rpc_created = rpc->create("gsr-ui");
|
const gsr::RpcOpenResult rpc_open_result = rpc->open("gsr-ui");
|
||||||
if(!rpc_created)
|
|
||||||
fprintf(stderr, "Error: Failed to create rpc\n");
|
|
||||||
|
|
||||||
if(is_gsr_ui_virtual_keyboard_running() || !rpc_created) {
|
if(rpc_open_result == gsr::RpcOpenResult::OK) {
|
||||||
if(launch_action == LaunchAction::LAUNCH_DAEMON)
|
if(launch_action == LaunchAction::LAUNCH_DAEMON)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
rpc = std::make_unique<gsr::Rpc>();
|
if(rpc->write("show_ui\n", 8)) {
|
||||||
if(rpc->open("gsr-ui") && rpc->write("show_ui\n", 8)) {
|
|
||||||
fprintf(stderr, "Error: another instance of gsr-ui is already running, opening that one instead\n");
|
fprintf(stderr, "Error: another instance of gsr-ui is already running, opening that one instead\n");
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Error: failed to send command to running gsr-ui instance, user will have to open the UI manually with Alt+Z\n");
|
fprintf(stderr, "Error: failed to send command to running gsr-ui instance, user will have to open the UI manually with Alt+Z\n");
|
||||||
@@ -243,6 +224,9 @@ int main(int argc, char **argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!rpc->create("gsr-ui"))
|
||||||
|
fprintf(stderr, "Error: Failed to create rpc\n");
|
||||||
|
|
||||||
if(gsr::pidof("gpu-screen-recorder", -1) != -1) {
|
if(gsr::pidof("gpu-screen-recorder", -1) != -1) {
|
||||||
const char *args[] = { "gsr-notify", "--text", "GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.", "--timeout", "5.0", "--icon-color", "ff0000", "--bg-color", "ff0000", nullptr };
|
const char *args[] = { "gsr-notify", "--text", "GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.", "--timeout", "5.0", "--icon-color", "ff0000", "--bg-color", "ff0000", nullptr };
|
||||||
gsr::exec_program_daemonized(args);
|
gsr::exec_program_daemonized(args);
|
||||||
|
|||||||
@@ -234,14 +234,23 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
|
|||||||
/* Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only */
|
/* Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only */
|
||||||
static void* keyboard_event_close_fds_callback(void *userdata) {
|
static void* keyboard_event_close_fds_callback(void *userdata) {
|
||||||
keyboard_event *self = userdata;
|
keyboard_event *self = userdata;
|
||||||
|
int fds_to_close_now[MAX_CLOSE_FDS];
|
||||||
|
int num_fds_to_close_now = 0;
|
||||||
|
|
||||||
while(self->running) {
|
while(self->running) {
|
||||||
pthread_mutex_lock(&self->close_dev_input_mutex);
|
pthread_mutex_lock(&self->close_dev_input_mutex);
|
||||||
for(int i = 0; i < self->num_close_fds; ++i) {
|
for(int i = 0; i < self->num_close_fds; ++i) {
|
||||||
close(self->close_fds[i]);
|
fds_to_close_now[i] = self->close_fds[i];
|
||||||
}
|
}
|
||||||
|
num_fds_to_close_now = self->num_close_fds;
|
||||||
self->num_close_fds = 0;
|
self->num_close_fds = 0;
|
||||||
pthread_mutex_unlock(&self->close_dev_input_mutex);
|
pthread_mutex_unlock(&self->close_dev_input_mutex);
|
||||||
|
|
||||||
|
for(int i = 0; i < num_fds_to_close_now; ++i) {
|
||||||
|
close(fds_to_close_now[i]);
|
||||||
|
}
|
||||||
|
num_fds_to_close_now = 0;
|
||||||
|
|
||||||
usleep(100 * 1000); /* 100 milliseconds */
|
usleep(100 * 1000); /* 100 milliseconds */
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -456,9 +465,13 @@ static void keyboard_event_remove_event(keyboard_event *self, int index) {
|
|||||||
if(index < 0 || index >= self->num_event_polls)
|
if(index < 0 || index >= self->num_event_polls)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(self->event_polls[index].fd > 0) {
|
const int poll_fd = self->event_polls[index].fd;
|
||||||
ioctl(self->event_polls[index].fd, EVIOCGRAB, 0);
|
if(poll_fd > 0) {
|
||||||
close(self->event_polls[index].fd);
|
ioctl(poll_fd, EVIOCGRAB, 0);
|
||||||
|
if(!keyboard_event_try_add_close_fd(self, poll_fd)) {
|
||||||
|
fprintf(stderr, "Error: failed to add immediately, closing now\n");
|
||||||
|
close(poll_fd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
free(self->event_extra_data[index].key_states);
|
free(self->event_extra_data[index].key_states);
|
||||||
free(self->event_extra_data[index].key_presses_grabbed);
|
free(self->event_extra_data[index].key_presses_grabbed);
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <errno.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *filename) {
|
static void get_socket_filepath(char *buffer, size_t buffer_size, const char *filename) {
|
||||||
char dir[PATH_MAX];
|
char dir[PATH_MAX];
|
||||||
|
|
||||||
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
|
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
|
||||||
@@ -23,7 +25,7 @@ static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *f
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Assumes |str| size is less than 256 */
|
/* Assumes |str| size is less than 256 */
|
||||||
static void fifo_write_all(int file_fd, const char *str) {
|
static void file_write_all(int file_fd, const char *str) {
|
||||||
char command[256];
|
char command[256];
|
||||||
const ssize_t command_size = snprintf(command, sizeof(command), "%s\n", str);
|
const ssize_t command_size = snprintf(command, sizeof(command), "%s\n", str);
|
||||||
if(command_size >= (ssize_t)sizeof(command)) {
|
if(command_size >= (ssize_t)sizeof(command)) {
|
||||||
@@ -33,7 +35,7 @@ static void fifo_write_all(int file_fd, const char *str) {
|
|||||||
|
|
||||||
ssize_t offset = 0;
|
ssize_t offset = 0;
|
||||||
while(offset < (ssize_t)command_size) {
|
while(offset < (ssize_t)command_size) {
|
||||||
const ssize_t bytes_written = write(file_fd, str + offset, command_size - offset);
|
const ssize_t bytes_written = write(file_fd, command + offset, command_size - offset);
|
||||||
if(bytes_written > 0)
|
if(bytes_written > 0)
|
||||||
offset += bytes_written;
|
offset += bytes_written;
|
||||||
}
|
}
|
||||||
@@ -112,15 +114,34 @@ int main(int argc, char **argv) {
|
|||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
char fifo_filepath[PATH_MAX];
|
char socket_filepath[PATH_MAX];
|
||||||
get_runtime_filepath(fifo_filepath, sizeof(fifo_filepath), "gsr-ui");
|
get_socket_filepath(socket_filepath, sizeof(socket_filepath), "gsr-ui");
|
||||||
const int fifo_fd = open(fifo_filepath, O_RDWR | O_NONBLOCK);
|
|
||||||
if(fifo_fd <= 0) {
|
const int socket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||||
fprintf(stderr, "Error: failed to open fifo file %s. Maybe gsr-ui is not running?\n", fifo_filepath);
|
if(socket_fd <= 0) {
|
||||||
|
fprintf(stderr, "Error: failed to create socket\n");
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
fifo_write_all(fifo_fd, command);
|
struct sockaddr_un addr = {0};
|
||||||
close(fifo_fd);
|
addr.sun_family = AF_UNIX;
|
||||||
|
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_filepath);
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
if(connect(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
const int err = errno;
|
||||||
|
if(err == EWOULDBLOCK) {
|
||||||
|
usleep(10 * 1000);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Error: failed to connect, error: %s. Maybe gsr-ui is not running?\n", strerror(err));
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_write_all(socket_fd, command);
|
||||||
|
close(socket_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user