mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-07 20:08:07 +09:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a0b49bc2 | ||
|
|
34b9aad24b | ||
|
|
63b2b6cbc3 | ||
|
|
6c7158c06d | ||
|
|
7d1f6f9a25 | ||
|
|
3c8dd9c4db | ||
|
|
c7fcf251e3 | ||
|
|
6d58b2495d | ||
|
|
347eced060 | ||
|
|
6449133c57 | ||
|
|
1168e68278 | ||
|
|
4836c661ce | ||
|
|
f0bbbbe4a9 | ||
|
|
d9a1e5c2eb | ||
|
|
b6c59e1049 | ||
|
|
af6984cd7e | ||
|
|
51a47193d7 | ||
|
|
189736c1a9 | ||
|
|
8003c209fe | ||
|
|
1734d48af6 | ||
|
|
fc2f6f4c50 | ||
|
|
f4e44cbef5 | ||
|
|
3d6354c642 | ||
|
|
efb5fc53c1 | ||
|
|
51367ac078 | ||
|
|
b0ab2099fd | ||
|
|
4a0612ae8f | ||
|
|
c650974a11 | ||
|
|
6fe9f1a8d5 | ||
|
|
8c148aceda | ||
|
|
b4c85910ce | ||
|
|
fd63ac3626 | ||
|
|
2a0782eb02 | ||
|
|
f505323d56 | ||
|
|
309cc3425b | ||
|
|
81cb8f539f | ||
|
|
5214fb1d7f | ||
|
|
9aebe81ec4 | ||
|
|
d73bd68a70 | ||
|
|
3cb156aecb | ||
|
|
dea4393588 |
21
README.md
21
README.md
@@ -2,9 +2,7 @@
|
||||
|
||||
# GPU Screen Recorder UI
|
||||
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.\
|
||||
Note: This software is still in early alpha. Expect bugs, and please report any if you experience them. Some are already known, but it doesn't hurt to report them anyways.\
|
||||
You can report an issue by emailing the issue to dec05eba@protonmail.com.
|
||||
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
|
||||
Run `gsr-ui` and press `Left Alt+Z` to show/hide the UI. You can start the overlay UI at system startup by running `systemctl enable --now --user gpu-screen-recorder-ui`.
|
||||
@@ -15,7 +13,7 @@ A program called `gsr-ui-cli` is also installed when installing this software. T
|
||||
# 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 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 (the gtk gui version) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder). This flatpak includes both this UI and gpu-screen-recorder so no need to install that first.
|
||||
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder). This flatpak includes both this UI and gpu-screen-recorder so no need to install that first.
|
||||
|
||||
# Dependencies
|
||||
GPU Screen Recorder UI uses meson build system so you need to install `meson` to build GPU Screen Recorder UI.
|
||||
@@ -23,10 +21,11 @@ GPU Screen Recorder UI uses meson build system so you need to install `meson` to
|
||||
## Build dependencies
|
||||
These are the dependencies needed to build GPU Screen Recorder UI:
|
||||
|
||||
* x11 (libx11, libxrandr, libxrender, libxcomposite, libxfixes, libxi)
|
||||
* x11 (libx11, libxrandr, libxrender, libxcomposite, libxfixes, libxext, libxi)
|
||||
* libxcursor
|
||||
* libglvnd (which provides libgl, libglx and libegl)
|
||||
* linux-api-headers
|
||||
* libpulse (libpulse-simple)
|
||||
|
||||
## Runtime dependencies
|
||||
There are also additional dependencies needed at runtime:
|
||||
@@ -39,7 +38,10 @@ This program has to grab all keyboards and create a virtual keyboard (`gsr-ui vi
|
||||
This might cause issues for you if you use input remapping software. To workaround this you can go into settings and select "Only grab virtual devices"
|
||||
|
||||
# License
|
||||
This software is licensed under GPL3.0-only. Files under `fonts/` directory belong to the Noto Sans Google fonts project and they are licensed under `SIL Open Font License`.
|
||||
This software is licensed under GPL3.0-only. Files under `fonts/` directory belong to the Noto Sans Google fonts project and they are licensed under `SIL Open Font License`. `images/default.cur` it part of the [Adwaita icon theme](https://gitlab.gnome.org/GNOME/adwaita-icon-theme/-/tree/master) which is licensed under `Creative Commons Attribution-Share Alike 3.0`.
|
||||
|
||||
# Reporting bugs, contributing patches, questions or donation
|
||||
See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about).
|
||||
|
||||
# Demo
|
||||
[](https://www.youtube.com/watch?v=SOqXusCTXXA)
|
||||
@@ -48,15 +50,10 @@ This software is licensed under GPL3.0-only. Files under `fonts/` directory belo
|
||||

|
||||

|
||||
|
||||
# Donations
|
||||
If you want to donate you can donate via bitcoin or monero.
|
||||
* Bitcoin: bc1qqvuqnwrdyppf707ge27fqz2n9y9gu7lf5ypyuf
|
||||
* Monero: 4An9kp2qW1C9Gah7ewv4JzcNFQ5TAX7ineGCqXWK6vQnhsGGcRpNgcn8r9EC3tMcgY7vqCKs3nSRXhejMHBaGvFdN2egYet
|
||||
|
||||
# Known issues
|
||||
* When the UI is open the wallpaper is shown instead of the game on Hyprland. This is an issue with Hyprland. It cant be fixed until the UI is redesigned to not be a fullscreen overlay.
|
||||
* Opening the UI when a game is fullscreened can mess up the game window a bit on Hyprland. I believe this is an issue with Hyprland.
|
||||
|
||||
# FAQ
|
||||
## I get an error when trying to start the gpu-screen-recorder-ui.service systemd service
|
||||
If you have previously used the flatpak version of GPU Screen Recorder with the new UI then non-flatpak version of the systemd service will conflict with that. Run `gsr-ui` to fix that.
|
||||
If you have previously used the flatpak version of GPU Screen Recorder with the new UI then the non-flatpak version of the systemd service will conflict with that. Run `gsr-ui` to fix that.
|
||||
|
||||
40
TODO
40
TODO
@@ -78,8 +78,6 @@ Dont allow autostart of replay if capture option is window recording (when windo
|
||||
|
||||
Use global shortcuts desktop portal protocol on wayland when available.
|
||||
|
||||
When support for window capture is enabled on x11 then make sure to not save the window except temporary while the program is open.
|
||||
|
||||
Support CJK.
|
||||
|
||||
Move ui hover code from ::draw to ::on_event, to properly handle widget event stack.
|
||||
@@ -107,4 +105,40 @@ When adding window capture only add it to recording and streaming and do the win
|
||||
|
||||
Show an error that prime run will be disabled when using desktop portal capture option. This can cause issues as the user may have selected a video codec option that isn't available on their iGPU but is available on the prime-run dGPU.
|
||||
|
||||
Is it possible to configure hotkey and the new hotkey to get triggered immediately?
|
||||
Is it possible to configure hotkey and the new hotkey to get triggered immediately?
|
||||
|
||||
For keyboards that report supporting mice the keyboard grab will be delayed until any key has been pressed (and then released), see: https://github.com/dec05eba/gpu-screen-recorder-issues/issues/97
|
||||
See if there is any way around this.
|
||||
|
||||
Instead of installing gsr-global-hotkeys in flatpak use kms-server-proxy to launch gsr-global-hotkeys inside the flatpak with root, just like gsr-kms-server. This removes the need to update gsr-global-hotkeys everytime there is an update.
|
||||
|
||||
Check if "modprobe uinput" is needed on some systems (old fedora?).
|
||||
|
||||
Add recording timer to see duration of recording/streaming.
|
||||
|
||||
Make folder with window name work when using gamescope. Gamescope runs x11 itself so to get the window name inside that we have to connect to the gamescope X11 server (DISPLAY=:1 on x11 and DISPLAY=:2 on wayland, but not always).
|
||||
|
||||
When clicking on current directory in file manager show a dropdown menu where you can select common directories (HOME, Videos, Downloads and mounted drives) for quick navigation. Maybe even button to search.
|
||||
|
||||
Maybe change gsr-ui startup retry time in the systemd service, from 5 seconds to 2 seconds.
|
||||
|
||||
Add support for window capture. This should not prompt for window selection directly but instead prompt for window selection when recording starts and hide the ui first.
|
||||
For screenshots window capture should exist but "follow focused" option should not exist.
|
||||
|
||||
Improve audio design. It should have a button to add/remove audio tracks and button to add audio into each audio track separately and "record audio from all applications except the selected ones" for each audio track. Then also remove the "merge audio tracks" option.
|
||||
|
||||
Make it possible to take a screenshot through a button in the ui instead of having to use hotkey.
|
||||
|
||||
Handle failing to save a replay. gsr should output "failed to save replay, or something like that" to make it possible to detect that.
|
||||
|
||||
Dont allow saving replay while a replay save is in progress.
|
||||
|
||||
Make input work with cjk input systems (such as fcitx).
|
||||
|
||||
System startup option should also support runit and some other init systems, not only soystemd.
|
||||
|
||||
Allow using a hotkey such as printscreen or any other non-alphanumeric key without a modifier. Allow that in gsr-ui and gsr-global-hotkeys. Update the ui to match that.
|
||||
|
||||
Use x11 shm instead of XGetImage (https://stackoverflow.com/questions/43442675/how-to-use-xshmgetimage-and-xshmputimage).
|
||||
|
||||
Add a hotkey to record/stream/replay/screenshot region.
|
||||
|
||||
Submodule depends/mglpp updated: cd258b5f2c...241ebb9d03
BIN
images/default.cur
Normal file
BIN
images/default.cur
Normal file
Binary file not shown.
BIN
images/screenshot.png
Normal file
BIN
images/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
22
include/AudioPlayer.hpp
Normal file
22
include/AudioPlayer.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace gsr {
|
||||
// Only plays raw stereo PCM audio in 48000hz in s16le format.
|
||||
// Use this command to convert an audio file (input.wav) to a format playable by this class (output.pcm):
|
||||
// ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ar 48000 output.pcm
|
||||
class AudioPlayer {
|
||||
public:
|
||||
AudioPlayer() = default;
|
||||
~AudioPlayer();
|
||||
AudioPlayer(const AudioPlayer&) = delete;
|
||||
AudioPlayer& operator=(const AudioPlayer&) = delete;
|
||||
|
||||
bool play(const char *filepath);
|
||||
private:
|
||||
std::thread thread;
|
||||
bool stop_playing_audio = false;
|
||||
int audio_file_fd = -1;
|
||||
};
|
||||
}
|
||||
@@ -11,12 +11,23 @@
|
||||
namespace gsr {
|
||||
struct SupportedCaptureOptions;
|
||||
|
||||
enum class ReplayStartupMode {
|
||||
DONT_TURN_ON_AUTOMATICALLY,
|
||||
TURN_ON_AT_SYSTEM_STARTUP,
|
||||
TURN_ON_AT_FULLSCREEN,
|
||||
TURN_ON_AT_POWER_SUPPLY_CONNECTED
|
||||
};
|
||||
|
||||
ReplayStartupMode replay_startup_string_to_type(const char *startup_mode_str);
|
||||
|
||||
struct ConfigHotkey {
|
||||
int64_t key = 0; // Mgl key
|
||||
uint32_t modifiers = 0; // HotkeyModifier
|
||||
|
||||
bool operator==(const ConfigHotkey &other) const;
|
||||
bool operator!=(const ConfigHotkey &other) const;
|
||||
|
||||
std::string to_string(bool spaces = true, bool modifier_side = true) const;
|
||||
};
|
||||
|
||||
struct RecordOptions {
|
||||
@@ -101,15 +112,34 @@ namespace gsr {
|
||||
ConfigHotkey save_hotkey;
|
||||
};
|
||||
|
||||
struct ScreenshotConfig {
|
||||
std::string record_area_option = "screen";
|
||||
int32_t image_width = 0;
|
||||
int32_t image_height = 0;
|
||||
bool change_image_resolution = false;
|
||||
std::string image_quality = "very_high";
|
||||
std::string image_format = "jpg";
|
||||
bool record_cursor = true;
|
||||
bool restore_portal_session = true;
|
||||
|
||||
bool save_screenshot_in_game_folder = false;
|
||||
bool show_screenshot_saved_notifications = true;
|
||||
std::string save_directory;
|
||||
ConfigHotkey take_screenshot_hotkey;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
Config(const SupportedCaptureOptions &capture_options);
|
||||
bool operator==(const Config &other);
|
||||
bool operator!=(const Config &other);
|
||||
|
||||
void set_hotkeys_to_default();
|
||||
|
||||
MainConfig main_config;
|
||||
StreamingConfig streaming_config;
|
||||
RecordConfig record_config;
|
||||
ReplayConfig replay_config;
|
||||
ScreenshotConfig screenshot_config;
|
||||
};
|
||||
|
||||
std::optional<Config> read_config(const SupportedCaptureOptions &capture_options);
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace gsr {
|
||||
bool vp9 = false;
|
||||
};
|
||||
|
||||
struct SupportedImageFormats {
|
||||
bool jpeg = false;
|
||||
bool png = false;
|
||||
};
|
||||
|
||||
struct GsrMonitor {
|
||||
std::string name;
|
||||
mgl::vec2i size;
|
||||
@@ -42,6 +47,7 @@ namespace gsr {
|
||||
|
||||
struct SupportedCaptureOptions {
|
||||
bool window = false;
|
||||
bool region = false;
|
||||
bool focused = false;
|
||||
bool portal = false;
|
||||
std::vector<GsrMonitor> monitors;
|
||||
@@ -75,6 +81,7 @@ namespace gsr {
|
||||
SystemInfo system_info;
|
||||
GpuInfo gpu_info;
|
||||
SupportedVideoCodecs supported_video_codecs;
|
||||
SupportedImageFormats supported_image_formats;
|
||||
};
|
||||
|
||||
enum class GsrInfoExitStatus {
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#include "Config.hpp"
|
||||
#include "window_texture.h"
|
||||
#include "WindowUtils.hpp"
|
||||
#include "GlobalHotkeysLinux.hpp"
|
||||
#include "GlobalHotkeysJoystick.hpp"
|
||||
#include "AudioPlayer.hpp"
|
||||
#include "RegionSelector.hpp"
|
||||
|
||||
#include <mglpp/window/Window.hpp>
|
||||
#include <mglpp/window/Event.hpp>
|
||||
@@ -34,7 +35,8 @@ namespace gsr {
|
||||
NONE,
|
||||
RECORD,
|
||||
REPLAY,
|
||||
STREAM
|
||||
STREAM,
|
||||
SCREENSHOT
|
||||
};
|
||||
|
||||
class Overlay {
|
||||
@@ -56,6 +58,7 @@ namespace gsr {
|
||||
void toggle_stream();
|
||||
void toggle_replay();
|
||||
void save_replay();
|
||||
void take_screenshot();
|
||||
void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type);
|
||||
bool is_open() const;
|
||||
bool should_exit(std::string &reason) const;
|
||||
@@ -69,23 +72,26 @@ namespace gsr {
|
||||
void handle_keyboard_mapping_event();
|
||||
void on_event(mgl::Event &event);
|
||||
|
||||
void create_frontpage_ui_components();
|
||||
void xi_setup();
|
||||
void handle_xi_events();
|
||||
void process_key_bindings(mgl::Event &event);
|
||||
void grab_mouse_and_keyboard();
|
||||
void xi_setup_fake_cursor();
|
||||
void xi_grab_all_mouse_devices();
|
||||
|
||||
void close_gpu_screen_recorder_output();
|
||||
|
||||
void update_notification_process_status();
|
||||
void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type);
|
||||
void on_replay_saved(const char *replay_saved_filepath);
|
||||
void update_gsr_replay_save();
|
||||
void update_gsr_process_status();
|
||||
void update_gsr_screenshot_process_status();
|
||||
|
||||
void replay_status_update_status();
|
||||
void update_focused_fullscreen_status();
|
||||
void update_power_supply_status();
|
||||
void update_system_startup_status();
|
||||
|
||||
void on_stop_recording(int exit_code);
|
||||
|
||||
@@ -102,9 +108,10 @@ namespace gsr {
|
||||
void update_ui_replay_stopped();
|
||||
|
||||
void on_press_save_replay();
|
||||
void on_press_start_replay(bool disable_notification);
|
||||
void on_press_start_record();
|
||||
void on_press_start_stream();
|
||||
bool on_press_start_replay(bool disable_notification, bool finished_region_selection);
|
||||
void on_press_start_record(bool finished_region_selection);
|
||||
void on_press_start_stream(bool finished_region_selection);
|
||||
void on_press_take_screenshot(bool finished_region_selection);
|
||||
bool update_compositor_texture(const Monitor &monitor);
|
||||
|
||||
void force_window_on_top();
|
||||
@@ -147,6 +154,7 @@ namespace gsr {
|
||||
pid_t notification_process = -1;
|
||||
int gpu_screen_recorder_process_output_fd = -1;
|
||||
FILE *gpu_screen_recorder_process_output_file = nullptr;
|
||||
pid_t gpu_screen_recorder_screenshot_process = -1;
|
||||
|
||||
DropdownButton *replay_dropdown_button_ptr = nullptr;
|
||||
DropdownButton *record_dropdown_button_ptr = nullptr;
|
||||
@@ -163,6 +171,7 @@ namespace gsr {
|
||||
bool focused_window_is_fullscreen = false;
|
||||
|
||||
std::string record_filepath;
|
||||
std::string screenshot_filepath;
|
||||
|
||||
Display *xi_display = nullptr;
|
||||
int xi_opcode = 0;
|
||||
@@ -185,5 +194,15 @@ namespace gsr {
|
||||
std::unique_ptr<GlobalHotkeysJoystick> global_hotkeys_js = nullptr;
|
||||
Display *x11_mapping_display = nullptr;
|
||||
XEvent x11_mapping_xev;
|
||||
|
||||
mgl::Clock replay_save_clock;
|
||||
bool replay_save_show_notification = false;
|
||||
ReplayStartupMode replay_startup_mode = ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;
|
||||
bool try_replay_startup = true;
|
||||
|
||||
AudioPlayer audio_player;
|
||||
RegionSelector region_selector;
|
||||
bool start_region_capture = false;
|
||||
std::function<void()> on_region_selected;
|
||||
};
|
||||
}
|
||||
@@ -12,14 +12,14 @@ namespace gsr {
|
||||
};
|
||||
|
||||
// Arguments ending with NULL
|
||||
bool exec_program_daemonized(const char **args);
|
||||
bool exec_program_daemonized(const char **args, bool debug = true);
|
||||
// Arguments ending with NULL. |read_fd| can be NULL
|
||||
pid_t exec_program(const char **args, int *read_fd);
|
||||
pid_t exec_program(const char **args, int *read_fd, bool debug = true);
|
||||
// Arguments ending with NULL. Returns the exit status of the program or -1 on error
|
||||
int exec_program_get_stdout(const char **args, std::string &result);
|
||||
int exec_program_get_stdout(const char **args, std::string &result, bool debug = true);
|
||||
// Arguments ending with NULL. Returns the exit status of the program or -1 on error.
|
||||
// This works the same as |exec_program_get_stdout|, except on flatpak where this runs the program on the
|
||||
// host machine with flatpak-spawn --host
|
||||
int exec_program_on_host_get_stdout(const char **args, std::string &result);
|
||||
int exec_program_on_host_get_stdout(const char **args, std::string &result, bool debug = true);
|
||||
pid_t pidof(const char *process_name, pid_t ignore_pid);
|
||||
}
|
||||
52
include/RegionSelector.hpp
Normal file
52
include/RegionSelector.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "WindowUtils.hpp"
|
||||
#include <mglpp/system/vec.hpp>
|
||||
#include <mglpp/graphics/Color.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
namespace gsr {
|
||||
struct Region {
|
||||
mgl::vec2i pos;
|
||||
mgl::vec2i size;
|
||||
};
|
||||
|
||||
class RegionSelector {
|
||||
public:
|
||||
RegionSelector();
|
||||
RegionSelector(const RegionSelector&) = delete;
|
||||
RegionSelector& operator=(const RegionSelector&) = delete;
|
||||
~RegionSelector();
|
||||
|
||||
bool start(mgl::Color border_color);
|
||||
void stop();
|
||||
bool is_started() const;
|
||||
|
||||
bool failed() const;
|
||||
bool poll_events();
|
||||
bool is_selected() const;
|
||||
bool take_selection();
|
||||
Region get_selection() const;
|
||||
private:
|
||||
void on_button_press(const void *de);
|
||||
void on_button_release(const void *de);
|
||||
void on_mouse_motion(const void *de);
|
||||
private:
|
||||
Display *dpy = nullptr;
|
||||
unsigned long region_window = 0;
|
||||
unsigned long cursor_window = 0;
|
||||
unsigned long region_window_colormap = 0;
|
||||
int xi_opcode = 0;
|
||||
GC region_gc = nullptr;
|
||||
GC cursor_gc = nullptr;
|
||||
|
||||
Region region;
|
||||
bool selecting_region = false;
|
||||
bool selected = false;
|
||||
bool is_wayland = false;
|
||||
std::vector<Monitor> monitors;
|
||||
mgl::vec2i cursor_pos;
|
||||
};
|
||||
}
|
||||
@@ -41,6 +41,7 @@ namespace gsr {
|
||||
mgl::Texture stop_texture;
|
||||
mgl::Texture pause_texture;
|
||||
mgl::Texture save_texture;
|
||||
mgl::Texture screenshot_texture;
|
||||
|
||||
double double_click_timeout_seconds = 0.4;
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace gsr {
|
||||
std::map<std::string, std::string> get_xdg_variables();
|
||||
|
||||
std::string get_videos_dir();
|
||||
std::string get_pictures_dir();
|
||||
|
||||
// Returns 0 on success
|
||||
int create_directory_recursive(char *path);
|
||||
bool file_get_content(const char *filepath, std::string &file_content);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <mglpp/system/vec.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
namespace gsr {
|
||||
@@ -16,11 +17,24 @@ namespace gsr {
|
||||
mgl::vec2i size;
|
||||
};
|
||||
|
||||
std::optional<std::string> get_window_title(Display *dpy, Window window);
|
||||
Window get_focused_window(Display *dpy, WindowCaptureType cap_type);
|
||||
std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type);
|
||||
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window);
|
||||
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window);
|
||||
void set_window_size_not_resizable(Display *dpy, Window window, int width, int height);
|
||||
mgl::vec2i get_cursor_position(Display *dpy, Window *window);
|
||||
mgl::vec2i create_window_get_center_position(Display *display);
|
||||
std::string get_window_manager_name(Display *display);
|
||||
bool is_compositor_running(Display *dpy, int screen);
|
||||
std::vector<Monitor> get_monitors(Display *dpy);
|
||||
void xi_grab_all_mouse_devices(Display *dpy);
|
||||
void xi_ungrab_all_mouse_devices(Display *dpy);
|
||||
void xi_warp_all_mouse_devices(Display *dpy, mgl::vec2i position);
|
||||
void window_set_fullscreen(Display *dpy, Window window, bool fullscreen);
|
||||
bool window_is_fullscreen(Display *display, Window window);
|
||||
bool set_window_wm_state(Display *dpy, Window window, Atom atom);
|
||||
void make_window_click_through(Display *display, Window window);
|
||||
bool make_window_sticky(Display *dpy, Window window);
|
||||
bool hide_window_from_taskbar(Display *dpy, Window window);
|
||||
}
|
||||
@@ -30,6 +30,7 @@ namespace gsr {
|
||||
std::function<void()> on_click;
|
||||
private:
|
||||
void scale_sprite_to_button_size();
|
||||
float get_button_height();
|
||||
private:
|
||||
mgl::vec2f size;
|
||||
mgl::Color bg_color;
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace gsr {
|
||||
void add_item(const std::string &text, const std::string &id, const std::string &description = "");
|
||||
void set_item_label(const std::string &id, const std::string &new_label);
|
||||
void set_item_icon(const std::string &id, mgl::Texture *texture);
|
||||
void set_item_description(const std::string &id, const std::string &new_description);
|
||||
|
||||
void set_description(std::string description_text);
|
||||
void set_activated(bool activated);
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace gsr {
|
||||
RECORD_START_STOP,
|
||||
RECORD_PAUSE_UNPAUSE,
|
||||
STREAM_START_STOP,
|
||||
TAKE_SCREENSHOT,
|
||||
SHOW_HIDE
|
||||
};
|
||||
|
||||
@@ -44,6 +45,7 @@ namespace gsr {
|
||||
std::function<void(const char *reason)> on_click_exit_program_button;
|
||||
std::function<void(const char *hotkey_option)> on_keyboard_hotkey_changed;
|
||||
std::function<void(const char *hotkey_option)> on_joystick_hotkey_changed;
|
||||
std::function<void()> on_page_closed;
|
||||
private:
|
||||
void load_hotkeys();
|
||||
|
||||
@@ -55,6 +57,7 @@ namespace gsr {
|
||||
std::unique_ptr<List> create_replay_hotkey_options();
|
||||
std::unique_ptr<List> create_record_hotkey_options();
|
||||
std::unique_ptr<List> create_stream_hotkey_options();
|
||||
std::unique_ptr<List> create_screenshot_hotkey_options();
|
||||
std::unique_ptr<List> create_hotkey_control_buttons();
|
||||
std::unique_ptr<Subsection> create_hotkey_subsection(ScrollablePage *parent_page);
|
||||
std::unique_ptr<Button> create_exit_program_button();
|
||||
@@ -86,6 +89,7 @@ namespace gsr {
|
||||
Button *start_stop_recording_button_ptr = nullptr;
|
||||
Button *pause_unpause_recording_button_ptr = nullptr;
|
||||
Button *start_stop_streaming_button_ptr = nullptr;
|
||||
Button *take_screenshot_button_ptr = nullptr;
|
||||
Button *show_hide_button_ptr = nullptr;
|
||||
|
||||
ConfigHotkey configure_config_hotkey;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace gsr {
|
||||
class GsrPage : public Page {
|
||||
public:
|
||||
GsrPage();
|
||||
GsrPage(const char *top_text, const char *bottom_text);
|
||||
GsrPage(const GsrPage&) = delete;
|
||||
GsrPage& operator=(const GsrPage&) = delete;
|
||||
|
||||
@@ -42,7 +42,8 @@ namespace gsr {
|
||||
float margin_bottom_scale = 0.0f;
|
||||
float margin_left_scale = 0.0f;
|
||||
float margin_right_scale = 0.0f;
|
||||
mgl::Text label_text;
|
||||
mgl::Text top_text;
|
||||
mgl::Text bottom_text;
|
||||
std::vector<ButtonItem> buttons;
|
||||
};
|
||||
}
|
||||
78
include/gui/ScreenshotSettingsPage.hpp
Normal file
78
include/gui/ScreenshotSettingsPage.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "StaticPage.hpp"
|
||||
#include "List.hpp"
|
||||
#include "ComboBox.hpp"
|
||||
#include "Entry.hpp"
|
||||
#include "CheckBox.hpp"
|
||||
#include "../GsrInfo.hpp"
|
||||
#include "../Config.hpp"
|
||||
|
||||
namespace gsr {
|
||||
class PageStack;
|
||||
class GsrPage;
|
||||
class ScrollablePage;
|
||||
class Button;
|
||||
|
||||
class ScreenshotSettingsPage : public StaticPage {
|
||||
public:
|
||||
ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack);
|
||||
ScreenshotSettingsPage(const ScreenshotSettingsPage&) = delete;
|
||||
ScreenshotSettingsPage& operator=(const ScreenshotSettingsPage&) = delete;
|
||||
|
||||
void load();
|
||||
void save();
|
||||
void on_navigate_away_from_page() override;
|
||||
private:
|
||||
std::unique_ptr<ComboBox> create_record_area_box();
|
||||
std::unique_ptr<Widget> create_record_area();
|
||||
std::unique_ptr<List> create_select_window();
|
||||
std::unique_ptr<Entry> create_image_width_entry();
|
||||
std::unique_ptr<Entry> create_image_height_entry();
|
||||
std::unique_ptr<List> create_image_resolution();
|
||||
std::unique_ptr<List> create_image_resolution_section();
|
||||
std::unique_ptr<CheckBox> create_restore_portal_session_checkbox();
|
||||
std::unique_ptr<List> create_restore_portal_session_section();
|
||||
std::unique_ptr<Widget> create_change_image_resolution_section();
|
||||
std::unique_ptr<Widget> create_capture_target_section();
|
||||
std::unique_ptr<List> create_image_quality_section();
|
||||
std::unique_ptr<Widget> create_record_cursor_section();
|
||||
std::unique_ptr<Widget> create_image_section();
|
||||
std::unique_ptr<List> create_save_directory(const char *label);
|
||||
std::unique_ptr<ComboBox> create_image_format_box();
|
||||
std::unique_ptr<List> create_image_format_section();
|
||||
std::unique_ptr<Widget> create_file_info_section();
|
||||
std::unique_ptr<CheckBox> create_save_screenshot_in_game_folder();
|
||||
std::unique_ptr<Widget> create_general_section();
|
||||
std::unique_ptr<Widget> create_notifications_section();
|
||||
std::unique_ptr<Widget> create_settings();
|
||||
void add_widgets();
|
||||
|
||||
void save(RecordOptions &record_options);
|
||||
private:
|
||||
Config &config;
|
||||
const GsrInfo *gsr_info = nullptr;
|
||||
SupportedCaptureOptions capture_options;
|
||||
|
||||
GsrPage *content_page_ptr = nullptr;
|
||||
ScrollablePage *settings_scrollable_page_ptr = nullptr;
|
||||
List *select_window_list_ptr = nullptr;
|
||||
List *image_resolution_list_ptr = nullptr;
|
||||
List *restore_portal_session_list_ptr = nullptr;
|
||||
List *color_range_list_ptr = nullptr;
|
||||
Widget *image_format_ptr = nullptr;
|
||||
ComboBox *record_area_box_ptr = nullptr;
|
||||
Entry *image_width_entry_ptr = nullptr;
|
||||
Entry *image_height_entry_ptr = nullptr;
|
||||
CheckBox *record_cursor_checkbox_ptr = nullptr;
|
||||
CheckBox *restore_portal_session_checkbox_ptr = nullptr;
|
||||
CheckBox *change_image_resolution_checkbox_ptr = nullptr;
|
||||
ComboBox *image_quality_box_ptr = nullptr;
|
||||
ComboBox *image_format_box_ptr = nullptr;
|
||||
Button *save_directory_button_ptr = nullptr;
|
||||
CheckBox *save_screenshot_in_game_folder_checkbox_ptr = nullptr;
|
||||
CheckBox *show_screenshot_saved_notification_checkbox_ptr = nullptr;
|
||||
|
||||
PageStack *page_stack = nullptr;
|
||||
};
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace gsr {
|
||||
std::unique_ptr<CheckBox> create_restore_portal_session_checkbox();
|
||||
std::unique_ptr<List> create_restore_portal_session_section();
|
||||
std::unique_ptr<Widget> create_change_video_resolution_section();
|
||||
std::unique_ptr<Widget> create_capture_target();
|
||||
std::unique_ptr<Widget> create_capture_target_section();
|
||||
std::unique_ptr<ComboBox> create_audio_device_selection_combobox();
|
||||
std::unique_ptr<Button> create_remove_audio_device_button(List *audio_device_list_ptr);
|
||||
std::unique_ptr<List> create_audio_device();
|
||||
@@ -93,13 +93,14 @@ namespace gsr {
|
||||
std::unique_ptr<List> create_save_directory(const char *label);
|
||||
std::unique_ptr<ComboBox> create_container_box();
|
||||
std::unique_ptr<List> create_container_section();
|
||||
std::unique_ptr<Entry> create_replay_time_entry();
|
||||
std::unique_ptr<List> create_replay_time_entry();
|
||||
std::unique_ptr<List> create_replay_time();
|
||||
std::unique_ptr<RadioButton> create_start_replay_automatically();
|
||||
std::unique_ptr<CheckBox> create_save_replay_in_game_folder();
|
||||
std::unique_ptr<CheckBox> create_restart_replay_on_save();
|
||||
std::unique_ptr<Label> create_estimated_replay_file_size();
|
||||
void update_estimated_replay_file_size();
|
||||
void update_replay_time_text();
|
||||
std::unique_ptr<CheckBox> create_save_recording_in_game_folder();
|
||||
std::unique_ptr<Label> create_estimated_record_file_size();
|
||||
void update_estimated_record_file_size();
|
||||
@@ -186,6 +187,7 @@ namespace gsr {
|
||||
Entry *youtube_stream_key_entry_ptr = nullptr;
|
||||
Entry *stream_url_entry_ptr = nullptr;
|
||||
Entry *replay_time_entry_ptr = nullptr;
|
||||
Label *replay_time_label_ptr = nullptr;
|
||||
RadioButton *turn_on_replay_automatically_mode_ptr = nullptr;
|
||||
|
||||
PageStack *page_stack = nullptr;
|
||||
|
||||
10
meson.build
10
meson.build
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.1.3', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.3.0', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
|
||||
if get_option('buildtype') == 'debug'
|
||||
add_project_arguments('-g3', language : ['c', 'cpp'])
|
||||
@@ -27,11 +27,13 @@ src = [
|
||||
'src/gui/CustomRendererWidget.cpp',
|
||||
'src/gui/FileChooser.cpp',
|
||||
'src/gui/SettingsPage.cpp',
|
||||
'src/gui/ScreenshotSettingsPage.cpp',
|
||||
'src/gui/GlobalSettingsPage.cpp',
|
||||
'src/gui/GsrPage.cpp',
|
||||
'src/gui/Subsection.cpp',
|
||||
'src/Utils.cpp',
|
||||
'src/WindowUtils.cpp',
|
||||
'src/RegionSelector.cpp',
|
||||
'src/Config.cpp',
|
||||
'src/GsrInfo.cpp',
|
||||
'src/Process.cpp',
|
||||
@@ -39,6 +41,7 @@ src = [
|
||||
'src/GlobalHotkeysX11.cpp',
|
||||
'src/GlobalHotkeysLinux.cpp',
|
||||
'src/GlobalHotkeysJoystick.cpp',
|
||||
'src/AudioPlayer.cpp',
|
||||
'src/Hotplug.cpp',
|
||||
'src/Rpc.cpp',
|
||||
'src/main.cpp',
|
||||
@@ -52,7 +55,7 @@ datadir = get_option('datadir')
|
||||
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_FLATPAK_VERSION="5.1.2"', language: ['c', 'cpp'])
|
||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.2.0"', language: ['c', 'cpp'])
|
||||
|
||||
executable(
|
||||
meson.project_name(),
|
||||
@@ -63,8 +66,10 @@ executable(
|
||||
dependency('threads'),
|
||||
dependency('xcomposite'),
|
||||
dependency('xfixes'),
|
||||
dependency('xext'),
|
||||
dependency('xi'),
|
||||
dependency('xcursor'),
|
||||
dependency('libpulse-simple'),
|
||||
],
|
||||
cpp_args : '-DGSR_UI_RESOURCES_PATH="' + gsr_ui_resources_path + '"',
|
||||
)
|
||||
@@ -74,6 +79,7 @@ executable(
|
||||
[
|
||||
'tools/gsr-global-hotkeys/hotplug.c',
|
||||
'tools/gsr-global-hotkeys/keyboard_event.c',
|
||||
'tools/gsr-global-hotkeys/keys.c',
|
||||
'tools/gsr-global-hotkeys/main.c'
|
||||
],
|
||||
c_args : '-fstack-protector-all',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.1.3"
|
||||
version = "1.3.0"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
@@ -13,5 +13,7 @@ ignore_dirs = ["build", "tools"]
|
||||
[dependencies]
|
||||
xcomposite = ">=0"
|
||||
xfixes = ">=0"
|
||||
xext = ">=0"
|
||||
xi = ">=0"
|
||||
xcursor = ">=1"
|
||||
libpulse-simple = ">=0"
|
||||
|
||||
86
src/AudioPlayer.cpp
Normal file
86
src/AudioPlayer.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "../include/AudioPlayer.hpp"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
|
||||
#define BUFSIZE 4096
|
||||
|
||||
namespace gsr {
|
||||
AudioPlayer::~AudioPlayer() {
|
||||
if(thread.joinable()) {
|
||||
stop_playing_audio = true;
|
||||
thread.join();
|
||||
}
|
||||
|
||||
if(audio_file_fd > 0)
|
||||
close(audio_file_fd);
|
||||
}
|
||||
|
||||
bool AudioPlayer::play(const char *filepath) {
|
||||
if(thread.joinable()) {
|
||||
stop_playing_audio = true;
|
||||
thread.join();
|
||||
}
|
||||
|
||||
stop_playing_audio = false;
|
||||
audio_file_fd = open(filepath, O_RDONLY);
|
||||
if(audio_file_fd == -1)
|
||||
return false;
|
||||
|
||||
thread = std::thread([this]() {
|
||||
pa_sample_spec ss;
|
||||
ss.format = PA_SAMPLE_S16LE;
|
||||
ss.rate = 48000;
|
||||
ss.channels = 2;
|
||||
|
||||
pa_simple *s = NULL;
|
||||
int error;
|
||||
|
||||
/* Create a new playback stream */
|
||||
if(!(s = pa_simple_new(NULL, "gsr-ui-audio-playback", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) {
|
||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
uint8_t buf[BUFSIZE];
|
||||
for(;;) {
|
||||
ssize_t r;
|
||||
|
||||
if(stop_playing_audio)
|
||||
goto finish;
|
||||
|
||||
if((r = read(audio_file_fd, buf, sizeof(buf))) <= 0) {
|
||||
if(r == 0) /* EOF */
|
||||
break;
|
||||
|
||||
fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if(pa_simple_write(s, buf, (size_t) r, &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
if(pa_simple_drain(s, &error) < 0) {
|
||||
fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
if(s)
|
||||
pa_simple_free(s);
|
||||
|
||||
close(audio_file_fd);
|
||||
audio_file_fd = -1;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
134
src/Config.cpp
134
src/Config.cpp
@@ -6,6 +6,8 @@
|
||||
#include <limits.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <mglpp/window/Keyboard.hpp>
|
||||
|
||||
#define FORMAT_I32 "%" PRIi32
|
||||
@@ -13,6 +15,50 @@
|
||||
#define FORMAT_U32 "%" PRIu32
|
||||
|
||||
namespace gsr {
|
||||
static std::vector<mgl::Keyboard::Key> hotkey_modifiers_to_mgl_keys(uint32_t modifiers) {
|
||||
std::vector<mgl::Keyboard::Key> result;
|
||||
if(modifiers & HOTKEY_MOD_LCTRL)
|
||||
result.push_back(mgl::Keyboard::LControl);
|
||||
if(modifiers & HOTKEY_MOD_LSHIFT)
|
||||
result.push_back(mgl::Keyboard::LShift);
|
||||
if(modifiers & HOTKEY_MOD_LALT)
|
||||
result.push_back(mgl::Keyboard::LAlt);
|
||||
if(modifiers & HOTKEY_MOD_LSUPER)
|
||||
result.push_back(mgl::Keyboard::LSystem);
|
||||
if(modifiers & HOTKEY_MOD_RCTRL)
|
||||
result.push_back(mgl::Keyboard::RControl);
|
||||
if(modifiers & HOTKEY_MOD_RSHIFT)
|
||||
result.push_back(mgl::Keyboard::RShift);
|
||||
if(modifiers & HOTKEY_MOD_RALT)
|
||||
result.push_back(mgl::Keyboard::RAlt);
|
||||
if(modifiers & HOTKEY_MOD_RSUPER)
|
||||
result.push_back(mgl::Keyboard::RSystem);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void string_remove_all(std::string &str, const std::string &substr) {
|
||||
size_t index = 0;
|
||||
while(true) {
|
||||
index = str.find(substr, index);
|
||||
if(index == std::string::npos)
|
||||
break;
|
||||
str.erase(index, substr.size());
|
||||
}
|
||||
}
|
||||
|
||||
ReplayStartupMode replay_startup_string_to_type(const char *startup_mode_str) {
|
||||
if(strcmp(startup_mode_str, "dont_turn_on_automatically") == 0)
|
||||
return ReplayStartupMode::DONT_TURN_ON_AUTOMATICALLY;
|
||||
else if(strcmp(startup_mode_str, "turn_on_at_system_startup") == 0)
|
||||
return ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;
|
||||
else if(strcmp(startup_mode_str, "turn_on_at_fullscreen") == 0)
|
||||
return ReplayStartupMode::TURN_ON_AT_FULLSCREEN;
|
||||
else if(strcmp(startup_mode_str, "turn_on_at_power_supply_connected") == 0)
|
||||
return ReplayStartupMode::TURN_ON_AT_POWER_SUPPLY_CONNECTED;
|
||||
else
|
||||
return ReplayStartupMode::DONT_TURN_ON_AUTOMATICALLY;
|
||||
}
|
||||
|
||||
bool ConfigHotkey::operator==(const ConfigHotkey &other) const {
|
||||
return key == other.key && modifiers == other.modifiers;
|
||||
}
|
||||
@@ -21,36 +67,83 @@ namespace gsr {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
Config::Config(const SupportedCaptureOptions &capture_options) {
|
||||
const std::string default_save_directory = get_videos_dir();
|
||||
std::string ConfigHotkey::to_string(bool spaces, bool modifier_side) const {
|
||||
std::string result;
|
||||
|
||||
const std::vector<mgl::Keyboard::Key> modifier_keys = hotkey_modifiers_to_mgl_keys(modifiers);
|
||||
std::string modifier_str;
|
||||
for(const mgl::Keyboard::Key modifier_key : modifier_keys) {
|
||||
if(!result.empty()) {
|
||||
if(spaces)
|
||||
result += " + ";
|
||||
else
|
||||
result += "+";
|
||||
}
|
||||
|
||||
modifier_str = mgl::Keyboard::key_to_string(modifier_key);
|
||||
if(!modifier_side) {
|
||||
string_remove_all(modifier_str, "Left");
|
||||
string_remove_all(modifier_str, "Right");
|
||||
}
|
||||
result += modifier_str;
|
||||
}
|
||||
|
||||
if(key != 0) {
|
||||
if(!result.empty()) {
|
||||
if(spaces)
|
||||
result += " + ";
|
||||
else
|
||||
result += "+";
|
||||
}
|
||||
result += mgl::Keyboard::key_to_string((mgl::Keyboard::Key)key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Config::Config(const SupportedCaptureOptions &capture_options) {
|
||||
const std::string default_videos_save_directory = get_videos_dir();
|
||||
const std::string default_pictures_save_directory = get_pictures_dir();
|
||||
|
||||
set_hotkeys_to_default();
|
||||
|
||||
streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT};
|
||||
streaming_config.record_options.video_quality = "custom";
|
||||
streaming_config.record_options.audio_tracks.push_back("default_output");
|
||||
streaming_config.record_options.video_bitrate = 15000;
|
||||
|
||||
record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT};
|
||||
record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
|
||||
record_config.save_directory = default_save_directory;
|
||||
record_config.save_directory = default_videos_save_directory;
|
||||
record_config.record_options.audio_tracks.push_back("default_output");
|
||||
record_config.record_options.video_bitrate = 45000;
|
||||
|
||||
replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT};
|
||||
replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT};
|
||||
replay_config.record_options.video_quality = "custom";
|
||||
replay_config.save_directory = default_save_directory;
|
||||
replay_config.save_directory = default_videos_save_directory;
|
||||
replay_config.record_options.audio_tracks.push_back("default_output");
|
||||
replay_config.record_options.video_bitrate = 45000;
|
||||
|
||||
main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
|
||||
screenshot_config.save_directory = default_pictures_save_directory;
|
||||
|
||||
if(!capture_options.monitors.empty()) {
|
||||
streaming_config.record_options.record_area_option = capture_options.monitors.front().name;
|
||||
record_config.record_options.record_area_option = capture_options.monitors.front().name;
|
||||
replay_config.record_options.record_area_option = capture_options.monitors.front().name;
|
||||
screenshot_config.record_area_option = capture_options.monitors.front().name;
|
||||
}
|
||||
}
|
||||
|
||||
void Config::set_hotkeys_to_default() {
|
||||
streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT};
|
||||
|
||||
record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT};
|
||||
record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
|
||||
|
||||
replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT};
|
||||
replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT};
|
||||
|
||||
screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::F1, HOTKEY_MOD_LALT};
|
||||
|
||||
main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
|
||||
}
|
||||
|
||||
static std::optional<KeyValue> parse_key_value(std::string_view line) {
|
||||
const size_t space_index = line.find(' ');
|
||||
if(space_index == std::string_view::npos)
|
||||
@@ -156,7 +249,20 @@ namespace gsr {
|
||||
{"replay.container", &config.replay_config.container},
|
||||
{"replay.time", &config.replay_config.replay_time},
|
||||
{"replay.start_stop_hotkey", &config.replay_config.start_stop_hotkey},
|
||||
{"replay.save_hotkey", &config.replay_config.save_hotkey}
|
||||
{"replay.save_hotkey", &config.replay_config.save_hotkey},
|
||||
|
||||
{"screenshot.record_area_option", &config.screenshot_config.record_area_option},
|
||||
{"screenshot.image_width", &config.screenshot_config.image_width},
|
||||
{"screenshot.image_height", &config.screenshot_config.image_height},
|
||||
{"screenshot.change_image_resolution", &config.screenshot_config.change_image_resolution},
|
||||
{"screenshot.image_quality", &config.screenshot_config.image_quality},
|
||||
{"screenshot.image_format", &config.screenshot_config.image_format},
|
||||
{"screenshot.record_cursor", &config.screenshot_config.record_cursor},
|
||||
{"screenshot.restore_portal_session", &config.screenshot_config.restore_portal_session},
|
||||
{"screenshot.save_screenshot_in_game_folder", &config.screenshot_config.save_screenshot_in_game_folder},
|
||||
{"screenshot.show_screenshot_saved_notifications", &config.screenshot_config.show_screenshot_saved_notifications},
|
||||
{"screenshot.save_directory", &config.screenshot_config.save_directory},
|
||||
{"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,6 +289,8 @@ namespace gsr {
|
||||
} else if(std::holds_alternative<std::vector<std::string>*>(it.second)) {
|
||||
if(*std::get<std::vector<std::string>*>(it.second) != *std::get<std::vector<std::string>*>(it_other->second))
|
||||
return false;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -245,6 +353,8 @@ namespace gsr {
|
||||
} else if(std::holds_alternative<std::vector<std::string>*>(it->second)) {
|
||||
std::string array_value(key_value->value);
|
||||
std::get<std::vector<std::string>*>(it->second)->push_back(std::move(array_value));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -294,6 +404,8 @@ namespace gsr {
|
||||
for(const std::string &value : *array) {
|
||||
fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), value.c_str());
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,15 +91,6 @@ namespace gsr {
|
||||
if(!user_homepath)
|
||||
user_homepath = "/tmp";
|
||||
|
||||
char gsr_global_hotkeys_flatpak[PATH_MAX];
|
||||
snprintf(gsr_global_hotkeys_flatpak, sizeof(gsr_global_hotkeys_flatpak), "%s/.local/share/gpu-screen-recorder/gsr-global-hotkeys", user_homepath);
|
||||
|
||||
const char *display = getenv("DISPLAY");
|
||||
if(!display)
|
||||
display = ":0";
|
||||
char env_arg[256];
|
||||
snprintf(env_arg, sizeof(env_arg), "--env=DISPLAY=%s", display);
|
||||
|
||||
if(process_id > 0)
|
||||
return false;
|
||||
|
||||
@@ -136,7 +127,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
if(inside_flatpak) {
|
||||
const char *args[] = { "flatpak-spawn", "--host", env_arg, "--", gsr_global_hotkeys_flatpak, grab_type_arg, nullptr };
|
||||
const char *args[] = { "flatpak-spawn", "--host", "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/kms-server-proxy", "launch-gsr-global-hotkeys", user_homepath, grab_type_arg, nullptr };
|
||||
execvp(args[0], (char* const*)args);
|
||||
} else {
|
||||
const char *args[] = { "gsr-global-hotkeys", grab_type_arg, nullptr };
|
||||
|
||||
@@ -157,11 +157,19 @@ namespace gsr {
|
||||
gsr_info->supported_video_codecs.vp9 = true;
|
||||
}
|
||||
|
||||
static void parse_image_formats_line(GsrInfo *gsr_info, std::string_view line) {
|
||||
if(line == "jpeg")
|
||||
gsr_info->supported_image_formats.jpeg = true;
|
||||
else if(line == "png")
|
||||
gsr_info->supported_image_formats.png = true;
|
||||
}
|
||||
|
||||
enum class GsrInfoSection {
|
||||
UNKNOWN,
|
||||
SYSTEM_INFO,
|
||||
GPU_INFO,
|
||||
VIDEO_CODECS,
|
||||
IMAGE_FORMATS,
|
||||
CAPTURE_OPTIONS
|
||||
};
|
||||
|
||||
@@ -194,6 +202,8 @@ namespace gsr {
|
||||
section = GsrInfoSection::GPU_INFO;
|
||||
else if(section_name == "video_codecs")
|
||||
section = GsrInfoSection::VIDEO_CODECS;
|
||||
else if(section_name == "image_formats")
|
||||
section = GsrInfoSection::IMAGE_FORMATS;
|
||||
else if(section_name == "capture_options")
|
||||
section = GsrInfoSection::CAPTURE_OPTIONS;
|
||||
else
|
||||
@@ -217,6 +227,10 @@ namespace gsr {
|
||||
parse_video_codecs_line(gsr_info, line);
|
||||
break;
|
||||
}
|
||||
case GsrInfoSection::IMAGE_FORMATS: {
|
||||
parse_image_formats_line(gsr_info, line);
|
||||
break;
|
||||
}
|
||||
case GsrInfoSection::CAPTURE_OPTIONS: {
|
||||
// Intentionally ignore, get capture options with get_supported_capture_options instead
|
||||
break;
|
||||
@@ -244,7 +258,7 @@ namespace gsr {
|
||||
|
||||
std::string stdout_str;
|
||||
const char *args[] = { "gpu-screen-recorder", "--list-audio-devices", nullptr };
|
||||
if(exec_program_get_stdout(args, stdout_str) != 0) {
|
||||
if(exec_program_get_stdout(args, stdout_str, false) != 0) {
|
||||
fprintf(stderr, "error: 'gpu-screen-recorder --list-audio-devices' failed\n");
|
||||
return audio_devices;
|
||||
}
|
||||
@@ -296,6 +310,8 @@ namespace gsr {
|
||||
static void parse_capture_options_line(SupportedCaptureOptions &capture_options, std::string_view line) {
|
||||
if(line == "window")
|
||||
capture_options.window = true;
|
||||
else if(line == "region")
|
||||
capture_options.region = true;
|
||||
else if(line == "focused")
|
||||
capture_options.focused = true;
|
||||
else if(line == "portal")
|
||||
|
||||
@@ -44,9 +44,10 @@ namespace gsr {
|
||||
}
|
||||
|
||||
void Hotplug::process_event_data(int fd, const HotplugEventCallback &callback) {
|
||||
const int bytes_read = read(fd, event_data, sizeof(event_data));
|
||||
const int bytes_read = read(fd, event_data, sizeof(event_data) - 1);
|
||||
if(bytes_read <= 0)
|
||||
return;
|
||||
event_data[bytes_read] = '\0';
|
||||
|
||||
/* Hotplug data ends with a newline and a null terminator */
|
||||
int data_index = 0;
|
||||
|
||||
769
src/Overlay.cpp
769
src/Overlay.cpp
File diff suppressed because it is too large
Load Diff
@@ -40,12 +40,13 @@ namespace gsr {
|
||||
return num_args;
|
||||
}
|
||||
|
||||
bool exec_program_daemonized(const char **args) {
|
||||
bool exec_program_daemonized(const char **args, bool debug) {
|
||||
/* 1 argument */
|
||||
if(args[0] == nullptr)
|
||||
return false;
|
||||
|
||||
debug_print_args(args);
|
||||
if(debug)
|
||||
debug_print_args(args);
|
||||
|
||||
const pid_t pid = vfork();
|
||||
if(pid == -1) {
|
||||
@@ -72,7 +73,7 @@ namespace gsr {
|
||||
return true;
|
||||
}
|
||||
|
||||
pid_t exec_program(const char **args, int *read_fd) {
|
||||
pid_t exec_program(const char **args, int *read_fd, bool debug) {
|
||||
if(read_fd)
|
||||
*read_fd = -1;
|
||||
|
||||
@@ -84,7 +85,8 @@ namespace gsr {
|
||||
if(pipe(fds) == -1)
|
||||
return -1;
|
||||
|
||||
debug_print_args(args);
|
||||
if(debug)
|
||||
debug_print_args(args);
|
||||
|
||||
const pid_t pid = vfork();
|
||||
if(pid == -1) {
|
||||
@@ -110,10 +112,10 @@ namespace gsr {
|
||||
}
|
||||
}
|
||||
|
||||
int exec_program_get_stdout(const char **args, std::string &result) {
|
||||
int exec_program_get_stdout(const char **args, std::string &result, bool debug) {
|
||||
result.clear();
|
||||
int read_fd = -1;
|
||||
const pid_t process_id = exec_program(args, &read_fd);
|
||||
const pid_t process_id = exec_program(args, &read_fd, debug);
|
||||
if(process_id == -1)
|
||||
return -1;
|
||||
|
||||
@@ -152,7 +154,7 @@ namespace gsr {
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
int exec_program_on_host_get_stdout(const char **args, std::string &result) {
|
||||
int exec_program_on_host_get_stdout(const char **args, std::string &result, bool debug) {
|
||||
if(count_num_args(args) > 64 - 3) {
|
||||
fprintf(stderr, "Error: too many arguments when trying to launch \"%s\"\n", args[0]);
|
||||
return -1;
|
||||
@@ -170,9 +172,9 @@ namespace gsr {
|
||||
}
|
||||
modified_args[i] = arg;
|
||||
}
|
||||
return exec_program_get_stdout(modified_args, result);
|
||||
return exec_program_get_stdout(modified_args, result, debug);
|
||||
} else {
|
||||
return exec_program_get_stdout(args, result);
|
||||
return exec_program_get_stdout(args, result, debug);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
437
src/RegionSelector.cpp
Normal file
437
src/RegionSelector.cpp
Normal file
@@ -0,0 +1,437 @@
|
||||
#include "../include/RegionSelector.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/extensions/shape.h>
|
||||
|
||||
namespace gsr {
|
||||
static const int cursor_window_size = 32;
|
||||
static const int cursor_thickness = 5;
|
||||
static const int region_border_size = 2;
|
||||
|
||||
static bool xinput_is_supported(Display *dpy, int *xi_opcode) {
|
||||
*xi_opcode = 0;
|
||||
int query_event = 0;
|
||||
int query_error = 0;
|
||||
if(!XQueryExtension(dpy, "XInputExtension", xi_opcode, &query_event, &query_error)) {
|
||||
fprintf(stderr, "error: RegionSelector: X Input extension not available\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int major = 2;
|
||||
int minor = 1;
|
||||
int retval = XIQueryVersion(dpy, &major, &minor);
|
||||
if(retval != Success) {
|
||||
fprintf(stderr, "error: RegionSelector: XInput 2.1 is not supported\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int max_int(int a, int b) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
static void set_region_rectangle(Display *dpy, Window window, int x, int y, int width, int height, int border_size) {
|
||||
if(width < 0) {
|
||||
x += width;
|
||||
width = abs(width);
|
||||
}
|
||||
|
||||
if(height < 0) {
|
||||
y += height;
|
||||
height = abs(height);
|
||||
}
|
||||
|
||||
XRectangle rectangles[] = {
|
||||
{
|
||||
(short)max_int(0, x), (short)max_int(0, y),
|
||||
(unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height)
|
||||
}, // Left
|
||||
{
|
||||
(short)max_int(0, x + width - border_size), (short)max_int(0, y),
|
||||
(unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height)
|
||||
}, // Right
|
||||
{
|
||||
(short)max_int(0, x + border_size), (short)max_int(0, y),
|
||||
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
|
||||
}, // Top
|
||||
{
|
||||
(short)max_int(0, x + border_size), (short)max_int(0, y + height - border_size),
|
||||
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
|
||||
}, // Bottom
|
||||
};
|
||||
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted);
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
static void set_window_shape_cross(Display *dpy, Window window, int window_width, int window_height, int thickness) {
|
||||
XRectangle rectangles[] = {
|
||||
{
|
||||
(short)(window_width / 2 - thickness / 2), (short)0,
|
||||
(unsigned short)thickness, (unsigned short)window_height
|
||||
}, // Vertical
|
||||
{
|
||||
(short)(0), (short)(window_height / 2 - thickness / 2),
|
||||
(unsigned short)window_width, (unsigned short)thickness
|
||||
}, // Horizontal
|
||||
};
|
||||
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 2, ShapeSet, Unsorted);
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
static void draw_rectangle(Display *dpy, Window window, GC gc, int x, int y, int width, int height) {
|
||||
if(width < 0) {
|
||||
x += width;
|
||||
width = abs(width);
|
||||
}
|
||||
|
||||
if(height < 0) {
|
||||
y += height;
|
||||
height = abs(height);
|
||||
}
|
||||
|
||||
XDrawRectangle(dpy, window, gc, x, y, width, height);
|
||||
}
|
||||
|
||||
static Window create_cursor_window(Display *dpy, int width, int height, XVisualInfo *vinfo, unsigned long background_pixel) {
|
||||
XSetWindowAttributes window_attr;
|
||||
window_attr.background_pixel = background_pixel;
|
||||
window_attr.border_pixel = 0;
|
||||
window_attr.override_redirect = true;
|
||||
window_attr.event_mask = StructureNotifyMask | PointerMotionMask;
|
||||
window_attr.colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo->visual, AllocNone);
|
||||
const Window window = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, width, height, 0, vinfo->depth, InputOutput, vinfo->visual, CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWEventMask | CWColormap, &window_attr);
|
||||
if(window) {
|
||||
set_window_size_not_resizable(dpy, window, width, height);
|
||||
set_window_shape_cross(dpy, window, width, height, 5);
|
||||
make_window_click_through(dpy, window);
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
static void draw_rectangle_around_selected_monitor(Display *dpy, Window window, GC region_gc, int region_border_size, bool is_wayland, const std::vector<Monitor> &monitors, mgl::vec2i cursor_pos) {
|
||||
const Monitor *focused_monitor = nullptr;
|
||||
for(const Monitor &monitor : monitors) {
|
||||
if(cursor_pos.x >= monitor.position.x && cursor_pos.x <= monitor.position.x + monitor.size.x
|
||||
&& cursor_pos.y >= monitor.position.y && cursor_pos.y <= monitor.position.y + monitor.size.y)
|
||||
{
|
||||
focused_monitor = &monitor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
if(focused_monitor) {
|
||||
x = focused_monitor->position.x;
|
||||
y = focused_monitor->position.y;
|
||||
width = focused_monitor->size.x;
|
||||
height = focused_monitor->size.y;
|
||||
}
|
||||
|
||||
if(is_wayland)
|
||||
draw_rectangle(dpy, window, region_gc, x, y, width, height);
|
||||
else
|
||||
set_region_rectangle(dpy, window, x, y, width, height, region_border_size);
|
||||
}
|
||||
|
||||
static void update_cursor_window(Display *dpy, Window window, Window cursor_window, bool is_wayland, int cursor_x, int cursor_y, int cursor_window_size, int thickness, GC cursor_gc) {
|
||||
if(is_wayland) {
|
||||
const int x = cursor_x - cursor_window_size / 2;
|
||||
const int y = cursor_y - cursor_window_size / 2;
|
||||
XFillRectangle(dpy, window, cursor_gc, x + cursor_window_size / 2 - thickness / 2 , y, thickness, cursor_window_size);
|
||||
XFillRectangle(dpy, window, cursor_gc, x, y + cursor_window_size / 2 - thickness / 2, cursor_window_size, thickness);
|
||||
} else {
|
||||
XMoveWindow(dpy, cursor_window, cursor_x - cursor_window_size / 2, cursor_y - cursor_window_size / 2);
|
||||
}
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
static bool is_xwayland(Display *dpy) {
|
||||
int opcode, event, error;
|
||||
return XQueryExtension(dpy, "XWAYLAND", &opcode, &event, &error);
|
||||
}
|
||||
|
||||
static unsigned long mgl_color_to_x11_color(mgl::Color color) {
|
||||
if(color.a == 0)
|
||||
return 0;
|
||||
return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
|
||||
}
|
||||
|
||||
RegionSelector::RegionSelector() {
|
||||
|
||||
}
|
||||
|
||||
RegionSelector::~RegionSelector() {
|
||||
stop();
|
||||
}
|
||||
|
||||
bool RegionSelector::start(mgl::Color border_color) {
|
||||
if(dpy)
|
||||
return false;
|
||||
|
||||
const unsigned long border_color_x11 = mgl_color_to_x11_color(border_color);
|
||||
dpy = XOpenDisplay(nullptr);
|
||||
if(!dpy) {
|
||||
fprintf(stderr, "Error: RegionSelector::start: failed to connect to the X11 server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xi_opcode = 0;
|
||||
if(!xinput_is_supported(dpy, &xi_opcode)) {
|
||||
fprintf(stderr, "Error: RegionSelector::start: xinput not supported on your system\n");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
is_wayland = is_xwayland(dpy);
|
||||
monitors = get_monitors(dpy);
|
||||
|
||||
Window x11_cursor_window = None;
|
||||
cursor_pos = get_cursor_position(dpy, &x11_cursor_window);
|
||||
region.pos = {0, 0};
|
||||
region.size = {0, 0};
|
||||
|
||||
XVisualInfo vinfo;
|
||||
memset(&vinfo, 0, sizeof(vinfo));
|
||||
XMatchVisualInfo(dpy, DefaultScreen(dpy), 32, TrueColor, &vinfo);
|
||||
region_window_colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo.visual, AllocNone);
|
||||
|
||||
XSetWindowAttributes window_attr;
|
||||
window_attr.background_pixel = is_wayland ? 0 : border_color_x11;
|
||||
window_attr.border_pixel = 0;
|
||||
window_attr.override_redirect = true;
|
||||
window_attr.event_mask = StructureNotifyMask | PointerMotionMask;
|
||||
window_attr.colormap = region_window_colormap;
|
||||
|
||||
Screen *screen = XDefaultScreenOfDisplay(dpy);
|
||||
region_window = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, XWidthOfScreen(screen), XHeightOfScreen(screen), 0,
|
||||
vinfo.depth, InputOutput, vinfo.visual, CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWEventMask | CWColormap, &window_attr);
|
||||
if(!region_window) {
|
||||
fprintf(stderr, "Error: RegionSelector::start: failed to create region window\n");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
set_window_size_not_resizable(dpy, region_window, XWidthOfScreen(screen), XHeightOfScreen(screen));
|
||||
|
||||
if(!is_wayland) {
|
||||
cursor_window = create_cursor_window(dpy, cursor_window_size, cursor_window_size, &vinfo, border_color_x11);
|
||||
if(!cursor_window)
|
||||
fprintf(stderr, "Warning: RegionSelector::start: failed to create cursor window\n");
|
||||
set_region_rectangle(dpy, region_window, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
XGCValues region_gc_values;
|
||||
memset(®ion_gc_values, 0, sizeof(region_gc_values));
|
||||
region_gc_values.foreground = border_color_x11;
|
||||
region_gc_values.line_width = region_border_size;
|
||||
region_gc_values.line_style = LineSolid;
|
||||
region_gc = XCreateGC(dpy, region_window, GCForeground | GCLineWidth | GCLineStyle, ®ion_gc_values);
|
||||
|
||||
XGCValues cursor_gc_values;
|
||||
memset(&cursor_gc_values, 0, sizeof(cursor_gc_values));
|
||||
cursor_gc_values.foreground = border_color_x11;
|
||||
cursor_gc_values.line_width = cursor_thickness;
|
||||
cursor_gc_values.line_style = LineSolid;
|
||||
cursor_gc = XCreateGC(dpy, region_window, GCForeground | GCLineWidth | GCLineStyle, &cursor_gc_values);
|
||||
|
||||
if(!region_gc || !cursor_gc) {
|
||||
fprintf(stderr, "Error: RegionSelector::start: failed to create gc\n");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
XMapWindow(dpy, region_window);
|
||||
make_window_sticky(dpy, region_window);
|
||||
hide_window_from_taskbar(dpy, region_window);
|
||||
XFixesHideCursor(dpy, region_window);
|
||||
XGrabPointer(dpy, DefaultRootWindow(dpy), True, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
xi_grab_all_mouse_devices(dpy);
|
||||
XFlush(dpy);
|
||||
|
||||
window_set_fullscreen(dpy, region_window, true);
|
||||
|
||||
if(!is_wayland || x11_cursor_window)
|
||||
update_cursor_window(dpy, region_window, cursor_window, is_wayland, cursor_pos.x, cursor_pos.y, cursor_window_size, cursor_thickness, cursor_gc);
|
||||
|
||||
if(cursor_window) {
|
||||
XMapWindow(dpy, cursor_window);
|
||||
make_window_sticky(dpy, cursor_window);
|
||||
hide_window_from_taskbar(dpy, cursor_window);
|
||||
}
|
||||
|
||||
draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos);
|
||||
|
||||
XFlush(dpy);
|
||||
selected = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegionSelector::stop() {
|
||||
if(!dpy)
|
||||
return;
|
||||
|
||||
XWarpPointer(dpy, DefaultRootWindow(dpy), DefaultRootWindow(dpy), 0, 0, 0, 0, cursor_pos.x, cursor_pos.y);
|
||||
xi_warp_all_mouse_devices(dpy, cursor_pos);
|
||||
XFixesShowCursor(dpy, region_window);
|
||||
|
||||
XUngrabPointer(dpy, CurrentTime);
|
||||
xi_ungrab_all_mouse_devices(dpy);
|
||||
XFlush(dpy);
|
||||
|
||||
if(region_gc) {
|
||||
XFreeGC(dpy, region_gc);
|
||||
region_gc = nullptr;
|
||||
}
|
||||
|
||||
if(cursor_gc) {
|
||||
XFreeGC(dpy, cursor_gc);
|
||||
cursor_gc = nullptr;
|
||||
}
|
||||
|
||||
if(region_window_colormap) {
|
||||
XFreeColormap(dpy, region_window_colormap);
|
||||
region_window_colormap = 0;
|
||||
}
|
||||
|
||||
if(region_window) {
|
||||
XDestroyWindow(dpy, region_window);
|
||||
region_window = 0;
|
||||
}
|
||||
|
||||
XCloseDisplay(dpy);
|
||||
dpy = nullptr;
|
||||
selecting_region = false;
|
||||
}
|
||||
|
||||
bool RegionSelector::is_started() const {
|
||||
return dpy != nullptr;
|
||||
}
|
||||
|
||||
bool RegionSelector::failed() const {
|
||||
return !dpy;
|
||||
}
|
||||
|
||||
bool RegionSelector::poll_events() {
|
||||
if(!dpy || selected)
|
||||
return false;
|
||||
|
||||
XEvent xev;
|
||||
while(XPending(dpy)) {
|
||||
XNextEvent(dpy, &xev);
|
||||
XGenericEventCookie *cookie = &xev.xcookie;
|
||||
if(cookie->type != GenericEvent || cookie->extension != xi_opcode || !XGetEventData(dpy, cookie))
|
||||
continue;
|
||||
|
||||
const XIDeviceEvent *de = (XIDeviceEvent*)cookie->data;
|
||||
switch(cookie->evtype) {
|
||||
case XI_ButtonPress: {
|
||||
on_button_press(de);
|
||||
break;
|
||||
}
|
||||
case XI_ButtonRelease: {
|
||||
on_button_release(de);
|
||||
break;
|
||||
}
|
||||
case XI_Motion: {
|
||||
on_mouse_motion(de);
|
||||
break;
|
||||
}
|
||||
}
|
||||
XFreeEventData(dpy, cookie);
|
||||
|
||||
if(selected) {
|
||||
stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegionSelector::is_selected() const {
|
||||
return selected;
|
||||
}
|
||||
|
||||
bool RegionSelector::take_selection() {
|
||||
const bool result = selected;
|
||||
selected = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
Region RegionSelector::get_selection() const {
|
||||
return region;
|
||||
}
|
||||
|
||||
void RegionSelector::on_button_press(const void *de) {
|
||||
const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
|
||||
if(device_event->detail != Button1)
|
||||
return;
|
||||
|
||||
region.pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
selecting_region = true;
|
||||
}
|
||||
|
||||
void RegionSelector::on_button_release(const void *de) {
|
||||
const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
|
||||
if(device_event->detail != Button1)
|
||||
return;
|
||||
|
||||
if(!selecting_region)
|
||||
return;
|
||||
|
||||
if(is_wayland) {
|
||||
XClearWindow(dpy, region_window);
|
||||
XFlush(dpy);
|
||||
} else {
|
||||
set_region_rectangle(dpy, region_window, 0, 0, 0, 0, 0);
|
||||
}
|
||||
selecting_region = false;
|
||||
|
||||
cursor_pos = region.pos + region.size;
|
||||
|
||||
if(region.size.x < 0) {
|
||||
region.pos.x += region.size.x;
|
||||
region.size.x = abs(region.size.x);
|
||||
}
|
||||
|
||||
if(region.size.y < 0) {
|
||||
region.pos.y += region.size.y;
|
||||
region.size.y = abs(region.size.y);
|
||||
}
|
||||
|
||||
if(region.size.x > 0)
|
||||
region.size.x += 1;
|
||||
|
||||
if(region.size.y > 0)
|
||||
region.size.y += 1;
|
||||
|
||||
selected = true;
|
||||
}
|
||||
|
||||
void RegionSelector::on_mouse_motion(const void *de) {
|
||||
const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
|
||||
XClearWindow(dpy, region_window);
|
||||
if(selecting_region) {
|
||||
region.size.x = device_event->root_x - region.pos.x;
|
||||
region.size.y = device_event->root_y - region.pos.y;
|
||||
cursor_pos = region.pos + region.size;
|
||||
|
||||
if(is_wayland)
|
||||
draw_rectangle(dpy, region_window, region_gc, region.pos.x, region.pos.y, region.size.x, region.size.y);
|
||||
else
|
||||
set_region_rectangle(dpy, region_window, region.pos.x, region.pos.y, region.size.x, region.size.y, region_border_size);
|
||||
} else {
|
||||
cursor_pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos);
|
||||
}
|
||||
update_cursor_window(dpy, region_window, cursor_window, is_wayland, cursor_pos.x, cursor_pos.y, cursor_window_size, cursor_thickness, cursor_gc);
|
||||
XFlush(dpy);
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,9 @@ namespace gsr {
|
||||
if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str()))
|
||||
goto error;
|
||||
|
||||
if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str()))
|
||||
goto error;
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
|
||||
@@ -114,6 +114,14 @@ namespace gsr {
|
||||
return xdg_videos_dir;
|
||||
}
|
||||
|
||||
std::string get_pictures_dir() {
|
||||
auto xdg_vars = get_xdg_variables();
|
||||
std::string xdg_videos_dir = xdg_vars["XDG_PICTURES_DIR"];
|
||||
if(xdg_videos_dir.empty())
|
||||
xdg_videos_dir = get_home_dir() + "/Pictures";
|
||||
return xdg_videos_dir;
|
||||
}
|
||||
|
||||
int create_directory_recursive(char *path) {
|
||||
int path_len = strlen(path);
|
||||
char *p = path;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "../include/WindowUtils.hpp"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/shapeconst.h>
|
||||
|
||||
#include <mglpp/system/Utf8.hpp>
|
||||
|
||||
@@ -16,8 +18,6 @@ extern "C" {
|
||||
#include <string.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#define MAX_PROPERTY_VALUE_LEN 4096
|
||||
|
||||
namespace gsr {
|
||||
@@ -105,8 +105,17 @@ namespace gsr {
|
||||
unsigned int dummy_u;
|
||||
mgl::vec2i root_pos;
|
||||
XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
|
||||
if(window)
|
||||
*window = window_get_target_window_child(dpy, *window);
|
||||
|
||||
const Window direct_window = *window;
|
||||
*window = window_get_target_window_child(dpy, *window);
|
||||
// HACK: Count some other x11 windows as having an x11 window focused. Some games seem to create an Input window and that gets focused.
|
||||
if(!*window) {
|
||||
XWindowAttributes attr;
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
XGetWindowAttributes(dpy, direct_window, &attr);
|
||||
if(attr.c_class == InputOnly && !get_window_title(dpy, direct_window))
|
||||
*window = direct_window;
|
||||
}
|
||||
return root_pos;
|
||||
}
|
||||
|
||||
@@ -149,7 +158,7 @@ namespace gsr {
|
||||
std::string result;
|
||||
for(int i = 0; i < size;) {
|
||||
// Some games such as the finals has utf8-bom between each character, wtf?
|
||||
if(i + 3 < size && memcmp(str + i, "\xEF\xBB\xBF", 3) == 0) {
|
||||
if(i + 3 <= size && memcmp(str + i, "\xEF\xBB\xBF", 3) == 0) {
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
@@ -163,7 +172,8 @@ namespace gsr {
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<std::string> get_window_title(Display *dpy, Window window) {
|
||||
std::optional<std::string> get_window_title(Display *dpy, Window window) {
|
||||
std::optional<std::string> result;
|
||||
const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
|
||||
const Atom wm_name_atom = XInternAtom(dpy, "WM_NAME", False);
|
||||
const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False);
|
||||
@@ -175,8 +185,13 @@ namespace gsr {
|
||||
unsigned char *data = NULL;
|
||||
XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data);
|
||||
|
||||
if(type == utf8_string_atom && format == 8 && data)
|
||||
return utf8_sanitize(data, num_items);
|
||||
if(type == utf8_string_atom && format == 8 && data) {
|
||||
result = utf8_sanitize(data, num_items);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if(data)
|
||||
XFree(data);
|
||||
|
||||
type = None;
|
||||
format = 0;
|
||||
@@ -185,10 +200,15 @@ namespace gsr {
|
||||
data = NULL;
|
||||
XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data);
|
||||
|
||||
if((type == XA_STRING || type == utf8_string_atom) && data)
|
||||
return utf8_sanitize(data, num_items);
|
||||
if((type == XA_STRING || type == utf8_string_atom) && data) {
|
||||
result = utf8_sanitize(data, num_items);
|
||||
goto done;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
done:
|
||||
if(data)
|
||||
XFree(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string strip(const std::string &str) {
|
||||
@@ -228,14 +248,76 @@ namespace gsr {
|
||||
|
||||
XClassHint class_hint = {nullptr, nullptr};
|
||||
XGetClassHint(dpy, focused_window, &class_hint);
|
||||
if(class_hint.res_class) {
|
||||
if(class_hint.res_class)
|
||||
result = strip(class_hint.res_class);
|
||||
return result;
|
||||
}
|
||||
|
||||
if(class_hint.res_name)
|
||||
XFree(class_hint.res_name);
|
||||
|
||||
if(class_hint.res_class)
|
||||
XFree(class_hint.res_class);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window) {
|
||||
std::string result;
|
||||
|
||||
Window root;
|
||||
Window parent;
|
||||
Window *children = nullptr;
|
||||
unsigned int num_children = 0;
|
||||
if(!XQueryTree(dpy, DefaultRootWindow(dpy), &root, &parent, &children, &num_children) || !children)
|
||||
return result;
|
||||
|
||||
for(int i = (int)num_children - 1; i >= 0; --i) {
|
||||
if(children[i] == ignore_window)
|
||||
continue;
|
||||
|
||||
XWindowAttributes attr;
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
XGetWindowAttributes(dpy, children[i], &attr);
|
||||
if(attr.override_redirect || attr.c_class != InputOutput || attr.map_state != IsViewable)
|
||||
continue;
|
||||
|
||||
if(position.x >= attr.x && position.x <= attr.x + attr.width && position.y >= attr.y && position.y <= attr.y + attr.height) {
|
||||
const Window real_window = window_get_target_window_child(dpy, children[i]);
|
||||
if(!real_window || real_window == ignore_window)
|
||||
continue;
|
||||
|
||||
const std::optional<std::string> window_title = get_window_title(dpy, real_window);
|
||||
if(window_title)
|
||||
result = strip(window_title.value());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XFree(children);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window) {
|
||||
Window cursor_window;
|
||||
const mgl::vec2i cursor_position = get_cursor_position(dpy, &cursor_window);
|
||||
return get_window_name_at_position(dpy, cursor_position, ignore_window);
|
||||
}
|
||||
|
||||
void set_window_size_not_resizable(Display *dpy, Window window, int width, int height) {
|
||||
XSizeHints *size_hints = XAllocSizeHints();
|
||||
if(size_hints) {
|
||||
size_hints->width = width;
|
||||
size_hints->height = height;
|
||||
size_hints->min_width = width;
|
||||
size_hints->min_height = height;
|
||||
size_hints->max_width = width;
|
||||
size_hints->max_height = height;
|
||||
size_hints->flags = PSize | PMinSize | PMaxSize;
|
||||
XSetWMNormalHints(dpy, window, size_hints);
|
||||
XFree(size_hints);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned long flags;
|
||||
unsigned long functions;
|
||||
@@ -283,17 +365,7 @@ namespace gsr {
|
||||
XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
|
||||
|
||||
window_set_decorations_visible(display, window, false);
|
||||
|
||||
XSizeHints *size_hints = XAllocSizeHints();
|
||||
size_hints->width = size;
|
||||
size_hints->height = size;
|
||||
size_hints->min_width = size;
|
||||
size_hints->min_height = size;
|
||||
size_hints->max_width = size;
|
||||
size_hints->max_height = size;
|
||||
size_hints->flags = PSize | PMinSize | PMaxSize;
|
||||
XSetWMNormalHints(display, window, size_hints);
|
||||
XFree(size_hints);
|
||||
set_window_size_not_resizable(display, window, size, size);
|
||||
|
||||
XMapWindow(display, window);
|
||||
XFlush(display);
|
||||
@@ -347,17 +419,7 @@ namespace gsr {
|
||||
XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
|
||||
|
||||
window_set_decorations_visible(display, window, false);
|
||||
|
||||
XSizeHints *size_hints = XAllocSizeHints();
|
||||
size_hints->width = size;
|
||||
size_hints->height = size;
|
||||
size_hints->min_width = size;
|
||||
size_hints->min_height = size;
|
||||
size_hints->max_width = size;
|
||||
size_hints->max_height = size;
|
||||
size_hints->flags = PSize | PMinSize | PMaxSize;
|
||||
XSetWMNormalHints(display, window, size_hints);
|
||||
XFree(size_hints);
|
||||
set_window_size_not_resizable(display, window, size, size);
|
||||
|
||||
XMapWindow(display, window);
|
||||
XFlush(display);
|
||||
@@ -466,4 +528,172 @@ namespace gsr {
|
||||
mgl_for_each_active_monitor_output(dpy, get_monitors_callback, &monitors);
|
||||
return monitors;
|
||||
}
|
||||
|
||||
static bool device_is_mouse(const XIDeviceInfo *dev) {
|
||||
for(int i = 0; i < dev->num_classes; ++i) {
|
||||
if(dev->classes[i]->type == XIMasterPointer || dev->classes[i]->type == XISlavePointer)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void xi_grab_all_mouse_devices(Display *dpy, bool grab) {
|
||||
if(!dpy)
|
||||
return;
|
||||
|
||||
int num_devices = 0;
|
||||
XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &num_devices);
|
||||
if(!info)
|
||||
return;
|
||||
|
||||
unsigned char mask[XIMaskLen(XI_LASTEVENT)];
|
||||
memset(mask, 0, sizeof(mask));
|
||||
XISetMask(mask, XI_Motion);
|
||||
//XISetMask(mask, XI_RawMotion);
|
||||
XISetMask(mask, XI_ButtonPress);
|
||||
XISetMask(mask, XI_ButtonRelease);
|
||||
XISetMask(mask, XI_KeyPress);
|
||||
XISetMask(mask, XI_KeyRelease);
|
||||
|
||||
for (int i = 0; i < num_devices; ++i) {
|
||||
const XIDeviceInfo *dev = &info[i];
|
||||
if(!device_is_mouse(dev))
|
||||
continue;
|
||||
|
||||
XIEventMask xi_masks;
|
||||
xi_masks.deviceid = dev->deviceid;
|
||||
xi_masks.mask_len = sizeof(mask);
|
||||
xi_masks.mask = mask;
|
||||
if(grab)
|
||||
XIGrabDevice(dpy, dev->deviceid, DefaultRootWindow(dpy), CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XIOwnerEvents, &xi_masks);
|
||||
else
|
||||
XIUngrabDevice(dpy, dev->deviceid, CurrentTime);
|
||||
}
|
||||
|
||||
XFlush(dpy);
|
||||
XIFreeDeviceInfo(info);
|
||||
}
|
||||
|
||||
void xi_grab_all_mouse_devices(Display *dpy) {
|
||||
xi_grab_all_mouse_devices(dpy, true);
|
||||
}
|
||||
|
||||
void xi_ungrab_all_mouse_devices(Display *dpy) {
|
||||
xi_grab_all_mouse_devices(dpy, false);
|
||||
}
|
||||
|
||||
void xi_warp_all_mouse_devices(Display *dpy, mgl::vec2i position) {
|
||||
if(!dpy)
|
||||
return;
|
||||
|
||||
int num_devices = 0;
|
||||
XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &num_devices);
|
||||
if(!info)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < num_devices; ++i) {
|
||||
const XIDeviceInfo *dev = &info[i];
|
||||
if(!device_is_mouse(dev))
|
||||
continue;
|
||||
|
||||
XIWarpPointer(dpy, dev->deviceid, DefaultRootWindow(dpy), DefaultRootWindow(dpy), 0, 0, 0, 0, position.x, position.y);
|
||||
}
|
||||
|
||||
XFlush(dpy);
|
||||
XIFreeDeviceInfo(info);
|
||||
}
|
||||
|
||||
void window_set_fullscreen(Display *dpy, Window window, bool fullscreen) {
|
||||
const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False);
|
||||
const Atom net_wm_state_fullscreen_atom = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
|
||||
|
||||
XEvent xev;
|
||||
xev.type = ClientMessage;
|
||||
xev.xclient.window = window;
|
||||
xev.xclient.message_type = net_wm_state_atom;
|
||||
xev.xclient.format = 32;
|
||||
xev.xclient.data.l[0] = fullscreen ? 1 : 0;
|
||||
xev.xclient.data.l[1] = net_wm_state_fullscreen_atom;
|
||||
xev.xclient.data.l[2] = 0;
|
||||
xev.xclient.data.l[3] = 1;
|
||||
xev.xclient.data.l[4] = 0;
|
||||
|
||||
if(!XSendEvent(dpy, DefaultRootWindow(dpy), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) {
|
||||
fprintf(stderr, "mgl warning: failed to change window fullscreen state\n");
|
||||
return;
|
||||
}
|
||||
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
bool window_is_fullscreen(Display *display, Window window) {
|
||||
const Atom wm_state_atom = XInternAtom(display, "_NET_WM_STATE", False);
|
||||
const Atom wm_state_fullscreen_atom = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);
|
||||
|
||||
Atom type = None;
|
||||
int format = 0;
|
||||
unsigned long num_items = 0;
|
||||
unsigned long bytes_after = 0;
|
||||
unsigned char *properties = nullptr;
|
||||
if(XGetWindowProperty(display, window, wm_state_atom, 0, 1024, False, XA_ATOM, &type, &format, &num_items, &bytes_after, &properties) < Success) {
|
||||
fprintf(stderr, "Failed to get window wm state property\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!properties)
|
||||
return false;
|
||||
|
||||
bool is_fullscreen = false;
|
||||
Atom *atoms = (Atom*)properties;
|
||||
for(unsigned long i = 0; i < num_items; ++i) {
|
||||
if(atoms[i] == wm_state_fullscreen_atom) {
|
||||
is_fullscreen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XFree(properties);
|
||||
return is_fullscreen;
|
||||
}
|
||||
|
||||
#define _NET_WM_STATE_REMOVE 0
|
||||
#define _NET_WM_STATE_ADD 1
|
||||
#define _NET_WM_STATE_TOGGLE 2
|
||||
|
||||
bool set_window_wm_state(Display *dpy, Window window, Atom atom) {
|
||||
const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False);
|
||||
|
||||
XClientMessageEvent xclient;
|
||||
memset(&xclient, 0, sizeof(xclient));
|
||||
|
||||
xclient.type = ClientMessage;
|
||||
xclient.window = window;
|
||||
xclient.message_type = net_wm_state_atom;
|
||||
xclient.format = 32;
|
||||
xclient.data.l[0] = _NET_WM_STATE_ADD;
|
||||
xclient.data.l[1] = atom;
|
||||
xclient.data.l[2] = 0;
|
||||
xclient.data.l[3] = 0;
|
||||
xclient.data.l[4] = 0;
|
||||
|
||||
XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&xclient);
|
||||
XFlush(dpy);
|
||||
return true;
|
||||
}
|
||||
|
||||
void make_window_click_through(Display *display, Window window) {
|
||||
XRectangle rect;
|
||||
memset(&rect, 0, sizeof(rect));
|
||||
XserverRegion region = XFixesCreateRegion(display, &rect, 1);
|
||||
XFixesSetWindowShapeRegion(display, window, ShapeInput, 0, 0, region);
|
||||
XFixesDestroyRegion(display, region);
|
||||
}
|
||||
|
||||
bool make_window_sticky(Display *dpy, Window window) {
|
||||
return set_window_wm_state(dpy, window, XInternAtom(dpy, "_NET_WM_STATE_STICKY", False));
|
||||
}
|
||||
|
||||
bool hide_window_from_taskbar(Display *dpy, Window window) {
|
||||
return set_window_wm_state(dpy, window, XInternAtom(dpy, "_NET_WM_STATE_SKIP_TASKBAR", False));
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ namespace gsr {
|
||||
// These are relative to the button size
|
||||
static const float padding_top_icon_scale = 0.25f;
|
||||
static const float padding_bottom_icon_scale = 0.25f;
|
||||
static const float padding_left_icon_scale = 0.25f;
|
||||
static const float padding_right_icon_scale = 0.25f;
|
||||
//static const float padding_left_icon_scale = 0.25f;
|
||||
static const float padding_right_icon_scale = 0.15f;
|
||||
|
||||
Button::Button(mgl::Font *font, const char *text, mgl::vec2f size, mgl::Color bg_color) :
|
||||
size(size), bg_color(bg_color), bg_hover_color(bg_color), text(text, *font)
|
||||
@@ -53,13 +53,21 @@ namespace gsr {
|
||||
background.set_color(mouse_inside ? bg_hover_color : bg_color);
|
||||
window.draw(background);
|
||||
|
||||
text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor());
|
||||
window.draw(text);
|
||||
|
||||
if(sprite.get_texture() && sprite.get_texture()->is_valid()) {
|
||||
scale_sprite_to_button_size();
|
||||
sprite.set_position((background.get_position() + background.get_size() * 0.5f - sprite.get_size() * 0.5f).floor());
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
if(text.get_string().empty()) // Center
|
||||
sprite.set_position((background.get_position() + background.get_size() * 0.5f - sprite.get_size() * 0.5f).floor());
|
||||
else // Left
|
||||
sprite.set_position((draw_pos + mgl::vec2f(padding_left, background.get_size().y * 0.5f - sprite.get_size().y * 0.5f)).floor());
|
||||
window.draw(sprite);
|
||||
|
||||
const int padding_icon_right = padding_right_icon_scale * get_button_height();
|
||||
text.set_position((sprite.get_position() + mgl::vec2f(sprite.get_size().x + padding_icon_right, sprite.get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor());
|
||||
window.draw(text);
|
||||
} else {
|
||||
text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor());
|
||||
window.draw(text);
|
||||
}
|
||||
|
||||
if(mouse_inside) {
|
||||
@@ -72,18 +80,25 @@ namespace gsr {
|
||||
if(!visible)
|
||||
return {0.0f, 0.0f};
|
||||
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_bottom = padding_bottom_scale * get_theme().window_height;
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
const int padding_right = padding_right_scale * get_theme().window_height;
|
||||
|
||||
const mgl::vec2f text_bounds = text.get_bounds().size;
|
||||
mgl::vec2f s = size;
|
||||
if(s.x < 0.0001f)
|
||||
s.x = padding_left + text_bounds.x + padding_right;
|
||||
if(s.y < 0.0001f)
|
||||
s.y = padding_top + text_bounds.y + padding_bottom;
|
||||
return s;
|
||||
mgl::vec2f widget_size = size;
|
||||
|
||||
if(widget_size.y < 0.0001f)
|
||||
widget_size.y = get_button_height();
|
||||
|
||||
if(widget_size.x < 0.0001f) {
|
||||
widget_size.x = padding_left + text_bounds.x + padding_right;
|
||||
if(sprite.get_texture() && sprite.get_texture()->is_valid()) {
|
||||
scale_sprite_to_button_size();
|
||||
const int padding_icon_right = text_bounds.x > 0.001f ? padding_right_icon_scale * widget_size.y : 0.0f;
|
||||
widget_size.x += sprite.get_size().x + padding_icon_right;
|
||||
}
|
||||
}
|
||||
|
||||
return widget_size;
|
||||
}
|
||||
|
||||
void Button::set_border_scale(float scale) {
|
||||
@@ -110,13 +125,23 @@ namespace gsr {
|
||||
if(!sprite.get_texture() || !sprite.get_texture()->is_valid())
|
||||
return;
|
||||
|
||||
const mgl::vec2f button_size = get_size();
|
||||
const int padding_icon_top = padding_top_icon_scale * button_size.y;
|
||||
const int padding_icon_bottom = padding_bottom_icon_scale * button_size.y;
|
||||
const int padding_icon_left = padding_left_icon_scale * button_size.y;
|
||||
const int padding_icon_right = padding_right_icon_scale * button_size.y;
|
||||
const float widget_height = get_button_height();
|
||||
|
||||
const mgl::vec2f desired_size = button_size - mgl::vec2f(padding_icon_left + padding_icon_right, padding_icon_top + padding_icon_bottom);
|
||||
sprite.set_size(scale_keep_aspect_ratio(sprite.get_texture()->get_size().to_vec2f(), desired_size).floor());
|
||||
const int padding_icon_top = padding_top_icon_scale * widget_height;
|
||||
const int padding_icon_bottom = padding_bottom_icon_scale * widget_height;
|
||||
|
||||
const float desired_height = widget_height - (padding_icon_top + padding_icon_bottom);
|
||||
sprite.set_height((int)desired_height);
|
||||
}
|
||||
|
||||
float Button::get_button_height() {
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_bottom = padding_bottom_scale * get_theme().window_height;
|
||||
|
||||
float widget_height = size.y;
|
||||
if(widget_height < 0.0001f)
|
||||
widget_height = padding_top + text.get_bounds().size.y + padding_bottom;
|
||||
|
||||
return widget_height;
|
||||
}
|
||||
}
|
||||
@@ -26,16 +26,21 @@ namespace gsr {
|
||||
return true;
|
||||
|
||||
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_bottom = padding_bottom_scale * get_theme().window_height;
|
||||
|
||||
const mgl::vec2f mouse_pos = { (float)event.mouse_button.x, (float)event.mouse_button.y };
|
||||
const mgl::vec2f item_size = get_size();
|
||||
mgl::vec2f item_size = get_size();
|
||||
|
||||
if(show_dropdown) {
|
||||
for(size_t i = 0; i < items.size(); ++i) {
|
||||
Item &item = items[i];
|
||||
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
|
||||
if(mgl::FloatRect(item.position, item_size).contains(mouse_pos)) {
|
||||
const size_t prev_selected_item = selected_item;
|
||||
selected_item = i;
|
||||
show_dropdown = false;
|
||||
dirty = true;
|
||||
remove_widget_as_selected_in_parent();
|
||||
|
||||
if(selected_item != prev_selected_item && on_selection_changed)
|
||||
@@ -47,6 +52,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
const mgl::vec2f draw_pos = position + offset;
|
||||
item_size = get_size();
|
||||
if(mgl::FloatRect(draw_pos, item_size).contains(mouse_pos)) {
|
||||
show_dropdown = !show_dropdown;
|
||||
if(show_dropdown)
|
||||
@@ -66,9 +72,10 @@ namespace gsr {
|
||||
if(!visible)
|
||||
return;
|
||||
|
||||
//const mgl::Scissor scissor = window.get_scissor();
|
||||
update_if_dirty();
|
||||
|
||||
const mgl::vec2f draw_pos = (position + offset).floor();
|
||||
//max_size.x = std::min((scissor.position.x + scissor.size.x) - draw_pos.x, max_size.x);
|
||||
|
||||
if(show_dropdown)
|
||||
draw_selected(window, draw_pos);
|
||||
@@ -78,6 +85,8 @@ namespace gsr {
|
||||
|
||||
void ComboBox::add_item(const std::string &text, const std::string &id) {
|
||||
items.push_back({mgl::Text(text, *font), id, {0.0f, 0.0f}});
|
||||
items.back().text.set_max_width(font->get_character_size() * 22); // TODO: Make a proper solution
|
||||
//items.back().text.set_max_rows(1);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@@ -87,6 +96,7 @@ namespace gsr {
|
||||
if(item.id == id) {
|
||||
const size_t prev_selected_item = selected_item;
|
||||
selected_item = i;
|
||||
dirty = true;
|
||||
|
||||
if(trigger_event && (trigger_event_even_if_selection_not_changed || selected_item != prev_selected_item) && on_selection_changed)
|
||||
on_selection_changed(item.text.get_string(), item.id);
|
||||
@@ -107,13 +117,13 @@ namespace gsr {
|
||||
|
||||
void ComboBox::draw_selected(mgl::Window &window, mgl::vec2f draw_pos) {
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_bottom = padding_bottom_scale * get_theme().window_height;
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
|
||||
mgl_scissor scissor;
|
||||
mgl_window_get_scissor(window.internal_window(), &scissor);
|
||||
const mgl::Scissor scissor = window.get_scissor();
|
||||
const bool bottom_is_outside_scissor = draw_pos.y + max_size.y > scissor.position.y + scissor.size.y;
|
||||
|
||||
const mgl::vec2f item_size = get_size();
|
||||
mgl::vec2f item_size = get_size();
|
||||
mgl::vec2f items_draw_pos = draw_pos + mgl::vec2f(0.0f, item_size.y);
|
||||
|
||||
mgl::Rectangle background(draw_pos, item_size.floor());
|
||||
@@ -137,6 +147,9 @@ namespace gsr {
|
||||
const mgl::vec2f mouse_pos = window.get_mouse_position().to_vec2f();
|
||||
|
||||
for(size_t i = 0; i < items.size(); ++i) {
|
||||
Item &item = items[i];
|
||||
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
|
||||
|
||||
if(!cursor_inside) {
|
||||
cursor_inside = mgl::FloatRect(items_draw_pos, item_size).contains(mouse_pos);
|
||||
if(cursor_inside) {
|
||||
@@ -146,7 +159,6 @@ namespace gsr {
|
||||
}
|
||||
}
|
||||
|
||||
Item &item = items[i];
|
||||
item.text.set_position((items_draw_pos + mgl::vec2f(padding_left, padding_top)).floor());
|
||||
window.draw(item.text);
|
||||
|
||||
@@ -160,7 +172,7 @@ namespace gsr {
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
const int padding_right = padding_right_scale * get_theme().window_height;
|
||||
|
||||
const mgl::vec2f item_size = get_size();
|
||||
mgl::vec2f item_size = get_size();
|
||||
mgl::Rectangle background(draw_pos.floor(), item_size.floor());
|
||||
background.set_color(mgl::Color(0, 0, 0, 120));
|
||||
window.draw(background);
|
||||
@@ -197,11 +209,12 @@ namespace gsr {
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
const int padding_right = padding_right_scale * get_theme().window_height;
|
||||
|
||||
max_size = { 0.0f, font->get_character_size() + (float)padding_top + (float)padding_bottom };
|
||||
Item *selected_item_ptr = (selected_item < items.size()) ? &items[selected_item] : nullptr;
|
||||
max_size = { 0.0f, padding_top + padding_bottom + (selected_item_ptr ? selected_item_ptr->text.get_bounds().size.y : 0.0f) };
|
||||
for(Item &item : items) {
|
||||
const mgl::vec2f bounds = item.text.get_bounds().size;
|
||||
max_size.x = std::max(max_size.x, bounds.x + padding_left + padding_right);
|
||||
max_size.y += bounds.y + padding_top + padding_bottom;
|
||||
max_size.y += padding_top + bounds.y + padding_bottom;
|
||||
}
|
||||
|
||||
if(max_size.x <= 0.001f)
|
||||
@@ -219,7 +232,8 @@ namespace gsr {
|
||||
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_bottom = padding_bottom_scale * get_theme().window_height;
|
||||
return { max_size.x, font->get_character_size() + (float)padding_top + (float)padding_bottom };
|
||||
Item *selected_item_ptr = (selected_item < items.size()) ? &items[selected_item] : nullptr;
|
||||
return { max_size.x, padding_top + padding_bottom + (selected_item_ptr ? selected_item_ptr->text.get_bounds().size.y : 0.0f) };
|
||||
}
|
||||
|
||||
float ComboBox::get_dropdown_arrow_height() const {
|
||||
|
||||
@@ -17,19 +17,11 @@ namespace gsr {
|
||||
|
||||
const mgl::vec2f draw_pos = position + offset;
|
||||
|
||||
mgl_scissor prev_scissor;
|
||||
mgl_window_get_scissor(window.internal_window(), &prev_scissor);
|
||||
|
||||
const mgl_scissor new_scissor = {
|
||||
mgl_vec2i{(int)draw_pos.x, (int)draw_pos.y},
|
||||
mgl_vec2i{(int)size.x, (int)size.y}
|
||||
};
|
||||
mgl_window_set_scissor(window.internal_window(), &new_scissor);
|
||||
|
||||
const mgl::Scissor prev_scissor = window.get_scissor();
|
||||
window.set_scissor({draw_pos.to_vec2i(), size.to_vec2i()});
|
||||
if(draw_handler)
|
||||
draw_handler(window, draw_pos, size);
|
||||
|
||||
mgl_window_set_scissor(window.internal_window(), &prev_scissor);
|
||||
window.set_scissor(prev_scissor);
|
||||
}
|
||||
|
||||
mgl::vec2f CustomRendererWidget::get_size() {
|
||||
|
||||
@@ -201,6 +201,15 @@ namespace gsr {
|
||||
}
|
||||
}
|
||||
|
||||
void DropdownButton::set_item_description(const std::string &id, const std::string &new_description) {
|
||||
for(auto &item : items) {
|
||||
if(item.id == id) {
|
||||
item.description_text.set_string(new_description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DropdownButton::set_description(std::string description_text) {
|
||||
description.set_string(std::move(description_text));
|
||||
}
|
||||
|
||||
@@ -65,8 +65,7 @@ namespace gsr {
|
||||
if(!visible)
|
||||
return;
|
||||
|
||||
mgl_scissor scissor;
|
||||
mgl_window_get_scissor(window.internal_window(), &scissor);
|
||||
const mgl::Scissor scissor = window.get_scissor();
|
||||
|
||||
const mgl::vec2f draw_pos = position + offset;
|
||||
const mgl::vec2f mouse_pos = window.get_mouse_position().to_vec2f();
|
||||
|
||||
@@ -67,46 +67,6 @@ namespace gsr {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::vector<mgl::Keyboard::Key> hotkey_modifiers_to_mgl_keys(uint32_t modifiers) {
|
||||
std::vector<mgl::Keyboard::Key> result;
|
||||
if(modifiers & HOTKEY_MOD_LCTRL)
|
||||
result.push_back(mgl::Keyboard::LControl);
|
||||
if(modifiers & HOTKEY_MOD_LSHIFT)
|
||||
result.push_back(mgl::Keyboard::LShift);
|
||||
if(modifiers & HOTKEY_MOD_LALT)
|
||||
result.push_back(mgl::Keyboard::LAlt);
|
||||
if(modifiers & HOTKEY_MOD_LSUPER)
|
||||
result.push_back(mgl::Keyboard::LSystem);
|
||||
if(modifiers & HOTKEY_MOD_RCTRL)
|
||||
result.push_back(mgl::Keyboard::RControl);
|
||||
if(modifiers & HOTKEY_MOD_RSHIFT)
|
||||
result.push_back(mgl::Keyboard::RShift);
|
||||
if(modifiers & HOTKEY_MOD_RALT)
|
||||
result.push_back(mgl::Keyboard::RAlt);
|
||||
if(modifiers & HOTKEY_MOD_RSUPER)
|
||||
result.push_back(mgl::Keyboard::RSystem);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string config_hotkey_to_string(ConfigHotkey config_hotkey) {
|
||||
std::string result;
|
||||
|
||||
const std::vector<mgl::Keyboard::Key> modifier_keys = hotkey_modifiers_to_mgl_keys(config_hotkey.modifiers);
|
||||
for(const mgl::Keyboard::Key modifier_key : modifier_keys) {
|
||||
if(!result.empty())
|
||||
result += " + ";
|
||||
result += mgl::Keyboard::key_to_string(modifier_key);
|
||||
}
|
||||
|
||||
if(config_hotkey.key != 0) {
|
||||
if(!result.empty())
|
||||
result += " + ";
|
||||
result += mgl::Keyboard::key_to_string((mgl::Keyboard::Key)config_hotkey.key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GlobalSettingsPage::GlobalSettingsPage(Overlay *overlay, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
|
||||
StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
|
||||
overlay(overlay),
|
||||
@@ -114,7 +74,7 @@ namespace gsr {
|
||||
gsr_info(gsr_info),
|
||||
page_stack(page_stack)
|
||||
{
|
||||
auto content_page = std::make_unique<GsrPage>();
|
||||
auto content_page = std::make_unique<GsrPage>("Global", "Settings");
|
||||
content_page->add_button("Back", "back", get_color_theme().page_bg_color);
|
||||
content_page->on_click = [page_stack](const std::string &id) {
|
||||
if(id == "back")
|
||||
@@ -134,7 +94,7 @@ namespace gsr {
|
||||
|
||||
mgl::Text title_text("Press a key combination to use for the hotkey \"" + hotkey_configure_action_name + "\":", get_theme().title_font);
|
||||
mgl::Text hotkey_text(configure_hotkey_button->get_text(), get_theme().top_bar_font);
|
||||
mgl::Text description_text("The hotkey has to contain one or more of these keys: Alt, Ctrl, Shift and Super. Press Esc to cancel.", get_theme().body_font);
|
||||
mgl::Text description_text("The hotkey has to contain one or more of these keys: Alt, Ctrl, Shift and Super. Press Esc to cancel or Backspace to remove the hotkey.", get_theme().body_font);
|
||||
const float text_max_width = std::max(title_text.get_bounds().size.x, std::max(hotkey_text.get_bounds().size.x, description_text.get_bounds().size.x));
|
||||
|
||||
const float padding_horizontal = int(get_theme().window_height * 0.01f);
|
||||
@@ -171,7 +131,7 @@ namespace gsr {
|
||||
|
||||
std::unique_ptr<Subsection> GlobalSettingsPage::create_appearance_subsection(ScrollablePage *parent_page) {
|
||||
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Tint color", get_color_theme().text_color));
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Accent color", get_color_theme().text_color));
|
||||
auto tint_color_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
|
||||
tint_color_radio_button_ptr = tint_color_radio_button.get();
|
||||
tint_color_radio_button->add_item("Red", "amd");
|
||||
@@ -322,30 +282,41 @@ namespace gsr {
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> GlobalSettingsPage::create_screenshot_hotkey_options() {
|
||||
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Take a screenshot:", get_color_theme().text_color));
|
||||
auto take_screenshot_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||
take_screenshot_button_ptr = take_screenshot_button.get();
|
||||
list->add_widget(std::move(take_screenshot_button));
|
||||
|
||||
take_screenshot_button_ptr->on_click = [this] {
|
||||
configure_hotkey_start(ConfigureHotkeyType::TAKE_SCREENSHOT);
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() {
|
||||
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
|
||||
// auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||
// clear_hotkeys_button->on_click = [this] {
|
||||
// config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
// config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
// config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
// config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
// config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
// config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
// load_hotkeys();
|
||||
// overlay->rebind_all_keyboard_hotkeys();
|
||||
// };
|
||||
// list->add_widget(std::move(clear_hotkeys_button));
|
||||
auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||
clear_hotkeys_button->on_click = [this] {
|
||||
config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
load_hotkeys();
|
||||
overlay->rebind_all_keyboard_hotkeys();
|
||||
};
|
||||
list->add_widget(std::move(clear_hotkeys_button));
|
||||
|
||||
auto reset_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Reset hotkeys to default", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||
reset_hotkeys_button->on_click = [this] {
|
||||
config.streaming_config.start_stop_hotkey = {mgl::Keyboard::F8, HOTKEY_MOD_LALT};
|
||||
config.record_config.start_stop_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LALT};
|
||||
config.record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
|
||||
config.replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT};
|
||||
config.replay_config.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT};
|
||||
config.main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
|
||||
config.set_hotkeys_to_default();
|
||||
load_hotkeys();
|
||||
overlay->rebind_all_keyboard_hotkeys();
|
||||
};
|
||||
@@ -368,6 +339,7 @@ namespace gsr {
|
||||
list_ptr->add_widget(create_replay_hotkey_options());
|
||||
list_ptr->add_widget(create_record_hotkey_options());
|
||||
list_ptr->add_widget(create_stream_hotkey_options());
|
||||
list_ptr->add_widget(create_screenshot_hotkey_options());
|
||||
list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Double-click the controller share button to save a replay", get_color_theme().text_color));
|
||||
list_ptr->add_widget(create_hotkey_control_buttons());
|
||||
return subsection;
|
||||
@@ -440,6 +412,8 @@ namespace gsr {
|
||||
|
||||
void GlobalSettingsPage::on_navigate_away_from_page() {
|
||||
save();
|
||||
if(on_page_closed)
|
||||
on_page_closed();
|
||||
}
|
||||
|
||||
void GlobalSettingsPage::load() {
|
||||
@@ -460,15 +434,17 @@ namespace gsr {
|
||||
}
|
||||
|
||||
void GlobalSettingsPage::load_hotkeys() {
|
||||
turn_replay_on_off_button_ptr->set_text(config_hotkey_to_string(config.replay_config.start_stop_hotkey));
|
||||
save_replay_button_ptr->set_text(config_hotkey_to_string(config.replay_config.save_hotkey));
|
||||
turn_replay_on_off_button_ptr->set_text(config.replay_config.start_stop_hotkey.to_string());
|
||||
save_replay_button_ptr->set_text(config.replay_config.save_hotkey.to_string());
|
||||
|
||||
start_stop_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.start_stop_hotkey));
|
||||
pause_unpause_recording_button_ptr->set_text(config_hotkey_to_string(config.record_config.pause_unpause_hotkey));
|
||||
start_stop_recording_button_ptr->set_text(config.record_config.start_stop_hotkey.to_string());
|
||||
pause_unpause_recording_button_ptr->set_text(config.record_config.pause_unpause_hotkey.to_string());
|
||||
|
||||
start_stop_streaming_button_ptr->set_text(config_hotkey_to_string(config.streaming_config.start_stop_hotkey));
|
||||
start_stop_streaming_button_ptr->set_text(config.streaming_config.start_stop_hotkey.to_string());
|
||||
|
||||
show_hide_button_ptr->set_text(config_hotkey_to_string(config.main_config.show_hide_hotkey));
|
||||
take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string());
|
||||
|
||||
show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string());
|
||||
}
|
||||
|
||||
void GlobalSettingsPage::save() {
|
||||
@@ -494,12 +470,19 @@ namespace gsr {
|
||||
if(event.key.code == mgl::Keyboard::Escape)
|
||||
return false;
|
||||
|
||||
if(event.key.code == mgl::Keyboard::Backspace) {
|
||||
configure_config_hotkey = {mgl::Keyboard::Unknown, 0};
|
||||
configure_hotkey_button->set_text("");
|
||||
configure_hotkey_stop_and_save();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mgl::Keyboard::key_is_modifier(event.key.code)) {
|
||||
configure_config_hotkey.modifiers |= mgl_modifier_to_hotkey_modifier(event.key.code);
|
||||
configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey));
|
||||
configure_hotkey_button->set_text(configure_config_hotkey.to_string());
|
||||
} else if(configure_config_hotkey.modifiers != 0) {
|
||||
configure_config_hotkey.key = event.key.code;
|
||||
configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey));
|
||||
configure_hotkey_button->set_text(configure_config_hotkey.to_string());
|
||||
configure_hotkey_stop_and_save();
|
||||
}
|
||||
|
||||
@@ -512,7 +495,7 @@ namespace gsr {
|
||||
|
||||
if(mgl::Keyboard::key_is_modifier(event.key.code)) {
|
||||
configure_config_hotkey.modifiers &= ~mgl_modifier_to_hotkey_modifier(event.key.code);
|
||||
configure_hotkey_button->set_text(config_hotkey_to_string(configure_config_hotkey));
|
||||
configure_hotkey_button->set_text(configure_config_hotkey.to_string());
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -535,6 +518,8 @@ namespace gsr {
|
||||
return pause_unpause_recording_button_ptr;
|
||||
case ConfigureHotkeyType::STREAM_START_STOP:
|
||||
return start_stop_streaming_button_ptr;
|
||||
case ConfigureHotkeyType::TAKE_SCREENSHOT:
|
||||
return take_screenshot_button_ptr;
|
||||
case ConfigureHotkeyType::SHOW_HIDE:
|
||||
return show_hide_button_ptr;
|
||||
}
|
||||
@@ -555,6 +540,8 @@ namespace gsr {
|
||||
return &config.record_config.pause_unpause_hotkey;
|
||||
case ConfigureHotkeyType::STREAM_START_STOP:
|
||||
return &config.streaming_config.start_stop_hotkey;
|
||||
case ConfigureHotkeyType::TAKE_SCREENSHOT:
|
||||
return &config.screenshot_config.take_screenshot_hotkey;
|
||||
case ConfigureHotkeyType::SHOW_HIDE:
|
||||
return &config.main_config.show_hide_hotkey;
|
||||
}
|
||||
@@ -568,6 +555,7 @@ namespace gsr {
|
||||
&config.record_config.start_stop_hotkey,
|
||||
&config.record_config.pause_unpause_hotkey,
|
||||
&config.streaming_config.start_stop_hotkey,
|
||||
&config.screenshot_config.take_screenshot_hotkey,
|
||||
&config.main_config.show_hide_hotkey
|
||||
};
|
||||
for(ConfigHotkey *config_hotkey : config_hotkeys) {
|
||||
@@ -604,6 +592,9 @@ namespace gsr {
|
||||
case ConfigureHotkeyType::STREAM_START_STOP:
|
||||
hotkey_configure_action_name = "Start/stop streaming";
|
||||
break;
|
||||
case ConfigureHotkeyType::TAKE_SCREENSHOT:
|
||||
hotkey_configure_action_name = "Take a screenshot";
|
||||
break;
|
||||
case ConfigureHotkeyType::SHOW_HIDE:
|
||||
hotkey_configure_action_name = "Show/hide UI";
|
||||
break;
|
||||
@@ -614,7 +605,7 @@ namespace gsr {
|
||||
Button *config_hotkey_button = configure_hotkey_get_button_by_active_type();
|
||||
ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type();
|
||||
if(config_hotkey_button && config_hotkey)
|
||||
config_hotkey_button->set_text(config_hotkey_to_string(*config_hotkey));
|
||||
config_hotkey_button->set_text(config_hotkey->to_string());
|
||||
|
||||
configure_config_hotkey = {0, 0};
|
||||
configure_hotkey_type = ConfigureHotkeyType::NONE;
|
||||
@@ -628,15 +619,17 @@ namespace gsr {
|
||||
ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type();
|
||||
if(config_hotkey_button && config_hotkey) {
|
||||
bool hotkey_used_by_another_action = false;
|
||||
for_each_config_hotkey([&](ConfigHotkey *config_hotkey_item) {
|
||||
if(config_hotkey_item != config_hotkey && *config_hotkey_item == configure_config_hotkey)
|
||||
hotkey_used_by_another_action = true;
|
||||
});
|
||||
if(configure_config_hotkey.key != mgl::Keyboard::Unknown) {
|
||||
for_each_config_hotkey([&](ConfigHotkey *config_hotkey_item) {
|
||||
if(config_hotkey_item != config_hotkey && *config_hotkey_item == configure_config_hotkey)
|
||||
hotkey_used_by_another_action = true;
|
||||
});
|
||||
}
|
||||
|
||||
if(hotkey_used_by_another_action) {
|
||||
const std::string error_msg = "The hotkey \"" + config_hotkey_to_string(configure_config_hotkey) + " is already used for something else";
|
||||
const std::string error_msg = "The hotkey \"" + configure_config_hotkey.to_string() + " is already used for something else";
|
||||
overlay->show_notification(error_msg.c_str(), 3.0, mgl::Color(255, 0, 0, 255), mgl::Color(255, 0, 0, 255), NotificationType::NONE);
|
||||
config_hotkey_button->set_text(config_hotkey_to_string(*config_hotkey));
|
||||
config_hotkey_button->set_text(config_hotkey->to_string());
|
||||
configure_config_hotkey = {0, 0};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
namespace gsr {
|
||||
static const float button_spacing_scale = 0.015f;
|
||||
|
||||
GsrPage::GsrPage() :
|
||||
label_text("Settings", get_theme().title_font)
|
||||
GsrPage::GsrPage(const char *top_text, const char *bottom_text) :
|
||||
top_text(top_text, get_theme().title_font),
|
||||
bottom_text(bottom_text, get_theme().title_font)
|
||||
{
|
||||
const float margin = 0.02f;
|
||||
set_margins(margin, margin, margin, margin);
|
||||
@@ -80,13 +81,17 @@ namespace gsr {
|
||||
window.draw(background);
|
||||
|
||||
const int text_margin = background.get_size().y * 0.085;
|
||||
label_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - label_text.get_bounds().size.x * 0.5f, text_margin)).floor());
|
||||
window.draw(label_text);
|
||||
|
||||
top_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - top_text.get_bounds().size.x * 0.5f, text_margin)).floor());
|
||||
window.draw(top_text);
|
||||
|
||||
mgl::Sprite icon(&get_theme().settings_texture);
|
||||
icon.set_height((int)(background.get_size().y * 0.5f));
|
||||
icon.set_position((background.get_position() + background.get_size() * 0.5f - icon.get_size() * 0.5f).floor());
|
||||
window.draw(icon);
|
||||
|
||||
bottom_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - bottom_text.get_bounds().size.x * 0.5f, background.get_size().y - bottom_text.get_bounds().size.y - text_margin)).floor());
|
||||
window.draw(bottom_text);
|
||||
}
|
||||
|
||||
void GsrPage::draw_buttons(mgl::Window &window, mgl::vec2f body_pos, mgl::vec2f body_size) {
|
||||
@@ -102,15 +107,8 @@ namespace gsr {
|
||||
void GsrPage::draw_children(mgl::Window &window, mgl::vec2f position) {
|
||||
Widget *selected_widget = selected_child_widget;
|
||||
|
||||
mgl_scissor prev_scissor;
|
||||
mgl_window_get_scissor(window.internal_window(), &prev_scissor);
|
||||
|
||||
const mgl::vec2f inner_size = get_inner_size();
|
||||
const mgl_scissor new_scissor = {
|
||||
mgl_vec2i{(int)position.x, (int)position.y},
|
||||
mgl_vec2i{(int)inner_size.x, (int)inner_size.y}
|
||||
};
|
||||
mgl_window_set_scissor(window.internal_window(), &new_scissor);
|
||||
const mgl::Scissor prev_scissor = window.get_scissor();
|
||||
window.set_scissor({position.to_vec2i(), get_inner_size().to_vec2i()});
|
||||
|
||||
for(size_t i = 0; i < widgets.size(); ++i) {
|
||||
auto &widget = widgets[i];
|
||||
@@ -121,7 +119,7 @@ namespace gsr {
|
||||
if(selected_widget)
|
||||
selected_widget->draw(window, position);
|
||||
|
||||
mgl_window_set_scissor(window.internal_window(), &prev_scissor);
|
||||
window.set_scissor(prev_scissor);
|
||||
}
|
||||
|
||||
mgl::vec2f GsrPage::get_size() {
|
||||
|
||||
343
src/gui/ScreenshotSettingsPage.cpp
Normal file
343
src/gui/ScreenshotSettingsPage.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
#include "../../include/gui/ScreenshotSettingsPage.hpp"
|
||||
#include "../../include/gui/GsrPage.hpp"
|
||||
#include "../../include/gui/PageStack.hpp"
|
||||
#include "../../include/Theme.hpp"
|
||||
#include "../../include/GsrInfo.hpp"
|
||||
#include "../../include/Utils.hpp"
|
||||
#include "../../include/gui/List.hpp"
|
||||
#include "../../include/gui/ScrollablePage.hpp"
|
||||
#include "../../include/gui/Label.hpp"
|
||||
#include "../../include/gui/Subsection.hpp"
|
||||
#include "../../include/gui/FileChooser.hpp"
|
||||
|
||||
namespace gsr {
|
||||
ScreenshotSettingsPage::ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
|
||||
StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
|
||||
config(config),
|
||||
gsr_info(gsr_info),
|
||||
page_stack(page_stack)
|
||||
{
|
||||
capture_options = get_supported_capture_options(*gsr_info);
|
||||
|
||||
auto content_page = std::make_unique<GsrPage>("Screenshot", "Settings");
|
||||
content_page->add_button("Back", "back", get_color_theme().page_bg_color);
|
||||
content_page->on_click = [page_stack](const std::string &id) {
|
||||
if(id == "back")
|
||||
page_stack->pop();
|
||||
};
|
||||
content_page_ptr = content_page.get();
|
||||
add_widget(std::move(content_page));
|
||||
|
||||
add_widgets();
|
||||
load();
|
||||
}
|
||||
|
||||
std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_record_area_box() {
|
||||
auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font);
|
||||
// TODO: Show options not supported but disable them
|
||||
// TODO: Enable this
|
||||
//if(capture_options.window)
|
||||
// record_area_box->add_item("Window", "window");
|
||||
if(capture_options.region)
|
||||
record_area_box->add_item("Region", "region");
|
||||
for(const auto &monitor : capture_options.monitors) {
|
||||
char name[256];
|
||||
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
|
||||
record_area_box->add_item(name, monitor.name);
|
||||
}
|
||||
if(capture_options.portal)
|
||||
record_area_box->add_item("Desktop portal", "portal");
|
||||
record_area_box_ptr = record_area_box.get();
|
||||
return record_area_box;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_area() {
|
||||
auto record_area_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Capture target:", get_color_theme().text_color));
|
||||
record_area_list->add_widget(create_record_area_box());
|
||||
return record_area_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_select_window() {
|
||||
auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color));
|
||||
select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)));
|
||||
select_window_list_ptr = select_window_list.get();
|
||||
return select_window_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_width_entry() {
|
||||
auto image_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3);
|
||||
image_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15);
|
||||
image_width_entry_ptr = image_width_entry.get();
|
||||
return image_width_entry;
|
||||
}
|
||||
|
||||
std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_height_entry() {
|
||||
auto image_height_entry = std::make_unique<Entry>(&get_theme().body_font, "1080", get_theme().body_font.get_character_size() * 3);
|
||||
image_height_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15);
|
||||
image_height_entry_ptr = image_height_entry.get();
|
||||
return image_height_entry;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution() {
|
||||
auto area_size_params_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
area_size_params_list->add_widget(create_image_width_entry());
|
||||
area_size_params_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "x", get_color_theme().text_color));
|
||||
area_size_params_list->add_widget(create_image_height_entry());
|
||||
return area_size_params_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution_section() {
|
||||
auto image_resolution_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
image_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image resolution limit:", get_color_theme().text_color));
|
||||
image_resolution_list->add_widget(create_image_resolution());
|
||||
image_resolution_list_ptr = image_resolution_list.get();
|
||||
return image_resolution_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_restore_portal_session_checkbox() {
|
||||
auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restore portal session");
|
||||
restore_portal_session_checkbox->set_checked(true);
|
||||
restore_portal_session_checkbox_ptr = restore_portal_session_checkbox.get();
|
||||
return restore_portal_session_checkbox;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_restore_portal_session_section() {
|
||||
auto restore_portal_session_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
restore_portal_session_list->add_widget(std::make_unique<Label>(&get_theme().body_font, " ", get_color_theme().text_color));
|
||||
restore_portal_session_list->add_widget(create_restore_portal_session_checkbox());
|
||||
restore_portal_session_list_ptr = restore_portal_session_list.get();
|
||||
return restore_portal_session_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_change_image_resolution_section() {
|
||||
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Change image resolution");
|
||||
change_image_resolution_checkbox_ptr = checkbox.get();
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_capture_target_section() {
|
||||
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
|
||||
auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
capture_target_list->add_widget(create_record_area());
|
||||
capture_target_list->add_widget(create_select_window());
|
||||
capture_target_list->add_widget(create_image_resolution_section());
|
||||
capture_target_list->add_widget(create_restore_portal_session_section());
|
||||
|
||||
ll->add_widget(std::move(capture_target_list));
|
||||
ll->add_widget(create_change_image_resolution_section());
|
||||
return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_section() {
|
||||
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image quality:", get_color_theme().text_color));
|
||||
|
||||
auto image_quality_box = std::make_unique<ComboBox>(&get_theme().body_font);
|
||||
image_quality_box->add_item("Medium", "medium");
|
||||
image_quality_box->add_item("High", "high");
|
||||
image_quality_box->add_item("Very high (Recommended)", "very_high");
|
||||
image_quality_box->add_item("Ultra", "ultra");
|
||||
image_quality_box->set_selected_item("very_high");
|
||||
|
||||
image_quality_box_ptr = image_quality_box.get();
|
||||
list->add_widget(std::move(image_quality_box));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_cursor_section() {
|
||||
auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor");
|
||||
record_cursor_checkbox->set_checked(true);
|
||||
record_cursor_checkbox_ptr = record_cursor_checkbox.get();
|
||||
return record_cursor_checkbox;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_image_section() {
|
||||
auto image_section_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
image_section_list->add_widget(create_image_quality_section());
|
||||
image_section_list->add_widget(create_record_cursor_section());
|
||||
return std::make_unique<Subsection>("Image", std::move(image_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_save_directory(const char *label) {
|
||||
auto save_directory_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
save_directory_list->add_widget(std::make_unique<Label>(&get_theme().body_font, label, get_color_theme().text_color));
|
||||
auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_pictures_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||
save_directory_button_ptr = save_directory_button.get();
|
||||
save_directory_button->on_click = [this]() {
|
||||
auto select_directory_page = std::make_unique<GsrPage>("File", "Settings");
|
||||
select_directory_page->add_button("Save", "save", get_color_theme().tint_color);
|
||||
select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color);
|
||||
|
||||
auto file_chooser = std::make_unique<FileChooser>(save_directory_button_ptr->get_text().c_str(), select_directory_page->get_inner_size());
|
||||
FileChooser *file_chooser_ptr = file_chooser.get();
|
||||
select_directory_page->add_widget(std::move(file_chooser));
|
||||
|
||||
select_directory_page->on_click = [this, file_chooser_ptr](const std::string &id) {
|
||||
if(id == "save") {
|
||||
save_directory_button_ptr->set_text(file_chooser_ptr->get_current_directory());
|
||||
page_stack->pop();
|
||||
} else if(id == "cancel") {
|
||||
page_stack->pop();
|
||||
}
|
||||
};
|
||||
|
||||
page_stack->push(std::move(select_directory_page));
|
||||
};
|
||||
save_directory_list->add_widget(std::move(save_directory_button));
|
||||
return save_directory_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_image_format_box() {
|
||||
auto box = std::make_unique<ComboBox>(&get_theme().body_font);
|
||||
if(gsr_info->supported_image_formats.jpeg)
|
||||
box->add_item("jpg", "jpg");
|
||||
if(gsr_info->supported_image_formats.png)
|
||||
box->add_item("png", "png");
|
||||
image_format_box_ptr = box.get();
|
||||
return box;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> ScreenshotSettingsPage::create_image_format_section() {
|
||||
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image format:", get_color_theme().text_color));
|
||||
list->add_widget(create_image_format_box());
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_file_info_section() {
|
||||
auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||
file_info_data_list->add_widget(create_save_directory("Directory to save the screenshot:"));
|
||||
file_info_data_list->add_widget(create_image_format_section());
|
||||
return std::make_unique<Subsection>("File info", std::move(file_info_data_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||
}
|
||||
|
||||
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_in_game_folder() {
|
||||
char text[256];
|
||||
snprintf(text, sizeof(text), "Save screenshot in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)");
|
||||
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text);
|
||||
save_screenshot_in_game_folder_checkbox_ptr = checkbox.get();
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_general_section() {
|
||||
return std::make_unique<Subsection>("General", create_save_screenshot_in_game_folder(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_notifications_section() {
|
||||
auto show_screenshot_saved_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show screenshot saved notification");
|
||||
show_screenshot_saved_notification_checkbox->set_checked(true);
|
||||
show_screenshot_saved_notification_checkbox_ptr = show_screenshot_saved_notification_checkbox.get();
|
||||
return std::make_unique<Subsection>("Notifications", std::move(show_screenshot_saved_notification_checkbox), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> ScreenshotSettingsPage::create_settings() {
|
||||
auto page_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
page_list->set_spacing(0.018f);
|
||||
auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size() - mgl::vec2f(0.0f, page_list->get_size().y + 0.018f * get_theme().window_height));
|
||||
settings_scrollable_page_ptr = scrollable_page.get();
|
||||
page_list->add_widget(std::move(scrollable_page));
|
||||
|
||||
auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
settings_list->set_spacing(0.018f);
|
||||
settings_list->add_widget(create_capture_target_section());
|
||||
settings_list->add_widget(create_image_section());
|
||||
settings_list->add_widget(create_file_info_section());
|
||||
settings_list->add_widget(create_general_section());
|
||||
settings_list->add_widget(create_notifications_section());
|
||||
settings_scrollable_page_ptr->add_widget(std::move(settings_list));
|
||||
return page_list;
|
||||
}
|
||||
|
||||
void ScreenshotSettingsPage::add_widgets() {
|
||||
content_page_ptr->add_widget(create_settings());
|
||||
|
||||
record_area_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) {
|
||||
(void)text;
|
||||
const bool window_selected = id == "window";
|
||||
const bool portal_selected = id == "portal";
|
||||
select_window_list_ptr->set_visible(window_selected);
|
||||
image_resolution_list_ptr->set_visible(change_image_resolution_checkbox_ptr->is_checked());
|
||||
restore_portal_session_list_ptr->set_visible(portal_selected);
|
||||
return true;
|
||||
};
|
||||
|
||||
change_image_resolution_checkbox_ptr->on_changed = [this](bool checked) {
|
||||
image_resolution_list_ptr->set_visible(checked);
|
||||
};
|
||||
|
||||
if(!capture_options.monitors.empty())
|
||||
record_area_box_ptr->set_selected_item(capture_options.monitors.front().name);
|
||||
else if(capture_options.portal)
|
||||
record_area_box_ptr->set_selected_item("portal");
|
||||
else if(capture_options.window)
|
||||
record_area_box_ptr->set_selected_item("window");
|
||||
else
|
||||
record_area_box_ptr->on_selection_changed("", "");
|
||||
}
|
||||
|
||||
void ScreenshotSettingsPage::on_navigate_away_from_page() {
|
||||
save();
|
||||
}
|
||||
|
||||
void ScreenshotSettingsPage::load() {
|
||||
record_area_box_ptr->set_selected_item(config.screenshot_config.record_area_option);
|
||||
change_image_resolution_checkbox_ptr->set_checked(config.screenshot_config.change_image_resolution);
|
||||
image_quality_box_ptr->set_selected_item(config.screenshot_config.image_quality);
|
||||
image_format_box_ptr->set_selected_item(config.screenshot_config.image_format);
|
||||
record_cursor_checkbox_ptr->set_checked(config.screenshot_config.record_cursor);
|
||||
restore_portal_session_checkbox_ptr->set_checked(config.screenshot_config.restore_portal_session);
|
||||
save_directory_button_ptr->set_text(config.screenshot_config.save_directory);
|
||||
save_screenshot_in_game_folder_checkbox_ptr->set_checked(config.screenshot_config.save_screenshot_in_game_folder);
|
||||
show_screenshot_saved_notification_checkbox_ptr->set_checked(config.screenshot_config.show_screenshot_saved_notifications);
|
||||
|
||||
if(config.screenshot_config.image_width == 0)
|
||||
config.screenshot_config.image_width = 1920;
|
||||
|
||||
if(config.screenshot_config.image_height == 0)
|
||||
config.screenshot_config.image_height = 1080;
|
||||
|
||||
if(config.screenshot_config.image_width < 32)
|
||||
config.screenshot_config.image_width = 32;
|
||||
image_width_entry_ptr->set_text(std::to_string(config.screenshot_config.image_width));
|
||||
|
||||
if(config.screenshot_config.image_height < 32)
|
||||
config.screenshot_config.image_height = 32;
|
||||
image_height_entry_ptr->set_text(std::to_string(config.screenshot_config.image_height));
|
||||
}
|
||||
|
||||
void ScreenshotSettingsPage::save() {
|
||||
config.screenshot_config.record_area_option = record_area_box_ptr->get_selected_id();
|
||||
config.screenshot_config.image_width = atoi(image_width_entry_ptr->get_text().c_str());
|
||||
config.screenshot_config.image_height = atoi(image_height_entry_ptr->get_text().c_str());
|
||||
config.screenshot_config.change_image_resolution = change_image_resolution_checkbox_ptr->is_checked();
|
||||
config.screenshot_config.image_quality = image_quality_box_ptr->get_selected_id();
|
||||
config.screenshot_config.image_format = image_format_box_ptr->get_selected_id();
|
||||
config.screenshot_config.record_cursor = record_cursor_checkbox_ptr->is_checked();
|
||||
config.screenshot_config.restore_portal_session = restore_portal_session_checkbox_ptr->is_checked();
|
||||
config.screenshot_config.save_directory = save_directory_button_ptr->get_text();
|
||||
config.screenshot_config.save_screenshot_in_game_folder = save_screenshot_in_game_folder_checkbox_ptr->is_checked();
|
||||
config.screenshot_config.show_screenshot_saved_notifications = show_screenshot_saved_notification_checkbox_ptr->is_checked();
|
||||
|
||||
if(config.screenshot_config.image_width == 0)
|
||||
config.screenshot_config.image_width = 1920;
|
||||
|
||||
if(config.screenshot_config.image_height == 0)
|
||||
config.screenshot_config.image_height = 1080;
|
||||
|
||||
if(config.screenshot_config.image_width < 32) {
|
||||
config.screenshot_config.image_width = 32;
|
||||
image_width_entry_ptr->set_text("32");
|
||||
}
|
||||
|
||||
if(config.screenshot_config.image_height < 32) {
|
||||
config.screenshot_config.image_height = 32;
|
||||
image_height_entry_ptr->set_text("32");
|
||||
}
|
||||
|
||||
save_config(config);
|
||||
}
|
||||
}
|
||||
@@ -89,8 +89,7 @@ namespace gsr {
|
||||
|
||||
offset = position + offset;
|
||||
|
||||
mgl_scissor prev_scissor;
|
||||
mgl_window_get_scissor(window.internal_window(), &prev_scissor);
|
||||
const mgl::Scissor prev_scissor = window.get_scissor();
|
||||
|
||||
const mgl::vec2f content_size = get_inner_size();
|
||||
const mgl_scissor new_scissor = {
|
||||
@@ -150,7 +149,7 @@ namespace gsr {
|
||||
apply_animation();
|
||||
limit_scroll(child_height);
|
||||
|
||||
mgl_window_set_scissor(window.internal_window(), &prev_scissor);
|
||||
window.set_scissor(prev_scissor);
|
||||
|
||||
double scrollbar_height = 1.0;
|
||||
if(child_height > 0.001)
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
#include "../../include/GsrInfo.hpp"
|
||||
#include "../../include/Utils.hpp"
|
||||
|
||||
#include <mglpp/graphics/Rectangle.hpp>
|
||||
#include <mglpp/graphics/Sprite.hpp>
|
||||
#include <mglpp/graphics/Text.hpp>
|
||||
#include <mglpp/window/Window.hpp>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace gsr {
|
||||
@@ -22,6 +17,15 @@ namespace gsr {
|
||||
APPLICATION_CUSTOM
|
||||
};
|
||||
|
||||
static const char* settings_page_type_to_title_text(SettingsPage::Type type) {
|
||||
switch(type) {
|
||||
case SettingsPage::Type::REPLAY: return "Instant Replay";
|
||||
case SettingsPage::Type::RECORD: return "Record";
|
||||
case SettingsPage::Type::STREAM: return "Livestream";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
SettingsPage::SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
|
||||
StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
|
||||
type(type),
|
||||
@@ -33,7 +37,7 @@ namespace gsr {
|
||||
application_audio = get_application_audio();
|
||||
capture_options = get_supported_capture_options(*gsr_info);
|
||||
|
||||
auto content_page = std::make_unique<GsrPage>();
|
||||
auto content_page = std::make_unique<GsrPage>(settings_page_type_to_title_text(type), "Settings");
|
||||
content_page->add_button("Back", "back", get_color_theme().page_bg_color);
|
||||
content_page->on_click = [page_stack](const std::string &id) {
|
||||
if(id == "back")
|
||||
@@ -62,6 +66,8 @@ namespace gsr {
|
||||
// TODO: Enable this
|
||||
//if(capture_options.window)
|
||||
// record_area_box->add_item("Window", "window");
|
||||
if(capture_options.region)
|
||||
record_area_box->add_item("Region", "region");
|
||||
if(capture_options.focused)
|
||||
record_area_box->add_item("Follow focused window", "focused");
|
||||
for(const auto &monitor : capture_options.monitors) {
|
||||
@@ -171,7 +177,7 @@ namespace gsr {
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> SettingsPage::create_capture_target() {
|
||||
std::unique_ptr<Widget> SettingsPage::create_capture_target_section() {
|
||||
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
|
||||
auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
@@ -347,14 +353,14 @@ namespace gsr {
|
||||
list->add_widget(std::move(video_bitrate_entry));
|
||||
|
||||
if(type == Type::STREAM) {
|
||||
auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "1.92MB", get_color_theme().text_color);
|
||||
auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "1.64MB", get_color_theme().text_color);
|
||||
Label *size_mb_label_ptr = size_mb_label.get();
|
||||
list->add_widget(std::move(size_mb_label));
|
||||
|
||||
video_bitrate_entry_ptr->on_changed = [size_mb_label_ptr](const std::string &text) {
|
||||
const double video_bitrate_mb_per_seconds = (double)atoi(text.c_str()) / 1000LL / 8LL * 1.024;
|
||||
const double video_bitrate_mbits_per_seconds = (double)atoi(text.c_str()) / 1024.0;
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%.2fMB", video_bitrate_mb_per_seconds);
|
||||
snprintf(buffer, sizeof(buffer), "%.2fMbps", video_bitrate_mbits_per_seconds);
|
||||
size_mb_label_ptr->set_text(buffer);
|
||||
};
|
||||
}
|
||||
@@ -512,7 +518,7 @@ namespace gsr {
|
||||
|
||||
auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
settings_list->set_spacing(0.018f);
|
||||
settings_list->add_widget(create_capture_target());
|
||||
settings_list->add_widget(create_capture_target_section());
|
||||
settings_list->add_widget(create_audio_section());
|
||||
settings_list->add_widget(create_video_section());
|
||||
settings_list_ptr = settings_list.get();
|
||||
@@ -589,7 +595,7 @@ namespace gsr {
|
||||
auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_videos_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
|
||||
save_directory_button_ptr = save_directory_button.get();
|
||||
save_directory_button->on_click = [this]() {
|
||||
auto select_directory_page = std::make_unique<GsrPage>();
|
||||
auto select_directory_page = std::make_unique<GsrPage>("File", "Settings");
|
||||
select_directory_page->add_button("Save", "save", get_color_theme().tint_color);
|
||||
select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color);
|
||||
|
||||
@@ -629,16 +635,24 @@ namespace gsr {
|
||||
return container_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<Entry> SettingsPage::create_replay_time_entry() {
|
||||
std::unique_ptr<List> SettingsPage::create_replay_time_entry() {
|
||||
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
|
||||
auto replay_time_entry = std::make_unique<Entry>(&get_theme().body_font, "60", get_theme().body_font.get_character_size() * 3);
|
||||
replay_time_entry->validate_handler = create_entry_validator_integer_in_range(1, 1200);
|
||||
replay_time_entry->validate_handler = create_entry_validator_integer_in_range(1, 10800);
|
||||
replay_time_entry_ptr = replay_time_entry.get();
|
||||
return replay_time_entry;
|
||||
list->add_widget(std::move(replay_time_entry));
|
||||
|
||||
auto replay_time_label = std::make_unique<Label>(&get_theme().body_font, "00h:00m:00s", get_color_theme().text_color);
|
||||
replay_time_label_ptr = replay_time_label.get();
|
||||
list->add_widget(std::move(replay_time_label));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_replay_time() {
|
||||
auto replay_time_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
replay_time_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Replay time in seconds:", get_color_theme().text_color));
|
||||
replay_time_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Replay duration in seconds:", get_color_theme().text_color));
|
||||
replay_time_list->add_widget(create_replay_time_entry());
|
||||
return replay_time_list;
|
||||
}
|
||||
@@ -681,11 +695,25 @@ namespace gsr {
|
||||
const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL;
|
||||
const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024;
|
||||
|
||||
char buffer[512];
|
||||
snprintf(buffer, sizeof(buffer), "Estimated video max file size in RAM: %.2fMB", video_filesize_mb);
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "Estimated video max file size in RAM: %.2fMB.\nChange video bitrate or replay duration to change file size.", video_filesize_mb);
|
||||
estimated_file_size_ptr->set_text(buffer);
|
||||
}
|
||||
|
||||
void SettingsPage::update_replay_time_text() {
|
||||
int seconds = atoi(replay_time_entry_ptr->get_text().c_str());
|
||||
|
||||
const int hours = seconds / 60 / 60;
|
||||
seconds -= (hours * 60 * 60);
|
||||
|
||||
const int minutes = seconds / 60;
|
||||
seconds -= (minutes * 60);
|
||||
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "%02dh:%02dm:%02ds", hours, minutes, seconds);
|
||||
replay_time_label_ptr->set_text(buffer);
|
||||
}
|
||||
|
||||
void SettingsPage::add_replay_widgets() {
|
||||
auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||
@@ -739,6 +767,7 @@ namespace gsr {
|
||||
|
||||
replay_time_entry_ptr->on_changed = [this](const std::string&) {
|
||||
update_estimated_replay_file_size();
|
||||
update_replay_time_text();
|
||||
};
|
||||
|
||||
video_bitrate_entry_ptr->on_changed = [this](const std::string&) {
|
||||
@@ -778,9 +807,7 @@ namespace gsr {
|
||||
file_info_list->add_widget(create_estimated_record_file_size());
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
|
||||
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
general_list->add_widget(create_save_recording_in_game_folder());
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("General", create_save_recording_in_game_folder(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
|
||||
auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
|
||||
@@ -1081,8 +1108,10 @@ namespace gsr {
|
||||
save_directory_button_ptr->set_text(config.replay_config.save_directory);
|
||||
container_box_ptr->set_selected_item(config.replay_config.container);
|
||||
|
||||
if(config.replay_config.replay_time < 5)
|
||||
config.replay_config.replay_time = 5;
|
||||
if(config.replay_config.replay_time < 2)
|
||||
config.replay_config.replay_time = 2;
|
||||
if(config.replay_config.replay_time > 10800)
|
||||
config.replay_config.replay_time = 10800;
|
||||
replay_time_entry_ptr->set_text(std::to_string(config.replay_config.replay_time));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,14 +36,8 @@ namespace gsr {
|
||||
offset = draw_pos;
|
||||
Widget *selected_widget = selected_child_widget;
|
||||
|
||||
mgl_scissor prev_scissor;
|
||||
mgl_window_get_scissor(window.internal_window(), &prev_scissor);
|
||||
|
||||
const mgl_scissor new_scissor = {
|
||||
mgl_vec2i{(int)draw_pos.x, (int)draw_pos.y},
|
||||
mgl_vec2i{(int)size.x, (int)size.y}
|
||||
};
|
||||
mgl_window_set_scissor(window.internal_window(), &new_scissor);
|
||||
const mgl::Scissor prev_scissor = window.get_scissor();
|
||||
window.set_scissor({draw_pos.to_vec2i(), size.to_vec2i()});
|
||||
|
||||
for(size_t i = 0; i < widgets.size(); ++i) {
|
||||
auto &widget = widgets[i];
|
||||
@@ -54,7 +48,7 @@ namespace gsr {
|
||||
if(selected_widget)
|
||||
selected_widget->draw(window, offset);
|
||||
|
||||
mgl_window_set_scissor(window.internal_window(), &prev_scissor);
|
||||
window.set_scissor(prev_scissor);
|
||||
}
|
||||
|
||||
mgl::vec2f StaticPage::get_size() {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <mglpp/mglpp.hpp>
|
||||
#include <mglpp/system/Clock.hpp>
|
||||
@@ -72,6 +73,11 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
|
||||
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
|
||||
overlay->save_replay();
|
||||
});
|
||||
|
||||
rpc->add_handler("take-screenshot", [overlay](const std::string &name) {
|
||||
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
|
||||
overlay->take_screenshot();
|
||||
});
|
||||
}
|
||||
|
||||
static bool is_gsr_ui_virtual_keyboard_running() {
|
||||
@@ -150,6 +156,7 @@ enum class LaunchAction {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
setlocale(LC_ALL, "C"); // Sigh... stupid C
|
||||
mallopt(M_MMAP_THRESHOLD, 65536);
|
||||
|
||||
if(geteuid() == 0) {
|
||||
fprintf(stderr, "Error: don't run gsr-ui as the root user\n");
|
||||
@@ -182,6 +189,7 @@ int main(int argc, char **argv) {
|
||||
// that uses pid sandboxing. Replace this with a better method once we no longer rely on linux global hotkeys on some platform.
|
||||
// TODO: This method doesn't work when disabling hotkeys and the method below with pidof gsr-ui doesn't work in flatpak.
|
||||
// What do? creating a pid file doesn't work in flatpak either.
|
||||
// TODO: This doesn't work in flatpak when disabling hotkeys.
|
||||
if(is_gsr_ui_virtual_keyboard_running() || gsr::pidof("gsr-ui", getpid()) != -1) {
|
||||
gsr::Rpc rpc;
|
||||
if(rpc.open("gsr-ui") && rpc.write("show_ui\n", 8)) {
|
||||
|
||||
21
tools/gsr-global-hotkeys/README.md
Normal file
21
tools/gsr-global-hotkeys/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# About
|
||||
Global hotkeys for X11 and all Wayland compositors by using linux device api. Keyboards are grabbed and only the non-hotkey keys are passed through to the system.
|
||||
The program accepts text commands as input. Run the program with the option `--virtual` to only grab virtual devices. This is useful when using keyboard input mapping software such as
|
||||
kanata, otherwise kanata may fail to launch or this program may fail to launch.
|
||||
# Commands
|
||||
## Bind
|
||||
To add a key send `bind <action> <keycode+keycode+...><newline>` to the programs stdin, for example:
|
||||
```
|
||||
bind show_hide 56+44
|
||||
|
||||
```
|
||||
which will bind alt+z. When alt+z is pressed the program will output `show_hide` (and a newline) to stdout.
|
||||
The program only accepts one key for each keybind command but accepts a multiple modifier keys.
|
||||
The keybinding requires at least one modifier key (ctrl, alt, super or shift) and a key to be used.
|
||||
The keycodes are values from `<linux/input-event-codes.h>` linux api header (which is the same as X11 keycode value minus 8).
|
||||
## Unbind
|
||||
To unbind all keys send `unbind_all<newline>` to the programs stdin, for example:
|
||||
```
|
||||
unbind_all
|
||||
|
||||
```
|
||||
@@ -65,9 +65,10 @@ static void hotplug_event_parse_netlink_data(hotplug_event *self, const char *li
|
||||
|
||||
/* Netlink uevent structure is documented here: https://web.archive.org/web/20160127215232/https://www.kernel.org/doc/pending/hotplug.txt */
|
||||
void hotplug_event_process_event_data(hotplug_event *self, int fd, hotplug_device_added_callback callback, void *userdata) {
|
||||
const int bytes_read = read(fd, self->event_data, sizeof(self->event_data));
|
||||
const int bytes_read = read(fd, self->event_data, sizeof(self->event_data) - 1);
|
||||
if(bytes_read <= 0)
|
||||
return;
|
||||
self->event_data[bytes_read] = '\0';
|
||||
|
||||
/* Hotplug data ends with a newline and a null terminator */
|
||||
int data_index = 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "keyboard_event.h"
|
||||
#include "keys.h"
|
||||
|
||||
/* C stdlib */
|
||||
#include <stdio.h>
|
||||
@@ -81,19 +82,19 @@ static void keyboard_event_fetch_update_key_states(keyboard_event *self, event_e
|
||||
}
|
||||
}
|
||||
|
||||
static void keyboard_event_process_key_state_change(keyboard_event *self, struct input_event event, event_extra_data *extra_data, int fd) {
|
||||
if(event.type != EV_KEY)
|
||||
static void keyboard_event_process_key_state_change(keyboard_event *self, const struct input_event *event, event_extra_data *extra_data, int fd) {
|
||||
if(event->type != EV_KEY)
|
||||
return;
|
||||
|
||||
if(!extra_data->key_states || event.code >= KEY_STATES_SIZE * 8)
|
||||
if(!extra_data->key_states || event->code >= KEY_STATES_SIZE * 8)
|
||||
return;
|
||||
|
||||
const unsigned int byte_index = event.code / 8;
|
||||
const unsigned char bit_index = event.code % 8;
|
||||
const unsigned int byte_index = event->code / 8;
|
||||
const unsigned char bit_index = event->code % 8;
|
||||
unsigned char key_byte_state = extra_data->key_states[byte_index];
|
||||
const bool prev_key_pressed = (key_byte_state & (1 << bit_index)) != KEY_RELEASE;
|
||||
|
||||
if(event.value == KEY_RELEASE) {
|
||||
if(event->value == KEY_RELEASE) {
|
||||
key_byte_state &= ~(1 << bit_index);
|
||||
if(prev_key_pressed)
|
||||
--extra_data->num_keys_pressed;
|
||||
@@ -171,8 +172,8 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
|
||||
//fprintf(stderr, "fd: %d, type: %d, pressed %d, value: %d\n", fd, event.type, event.code, event.value);
|
||||
//}
|
||||
|
||||
if(event.type == EV_KEY) {
|
||||
keyboard_event_process_key_state_change(self, event, extra_data, fd);
|
||||
if(event.type == EV_KEY && is_keyboard_key(event.code)) {
|
||||
keyboard_event_process_key_state_change(self, &event, extra_data, fd);
|
||||
const uint32_t modifier_bit = keycode_to_modifier_bit(event.code);
|
||||
if(modifier_bit == 0) {
|
||||
if(keyboard_event_on_key_pressed(self, &event, self->modifier_button_states))
|
||||
@@ -270,7 +271,8 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
|
||||
if(dev_input_id == -1)
|
||||
return false;
|
||||
|
||||
if(self->grab_type == KEYBOARD_GRAB_TYPE_VIRTUAL && !dev_input_is_virtual(dev_input_id))
|
||||
const bool is_virtual_device = dev_input_is_virtual(dev_input_id);
|
||||
if(self->grab_type == KEYBOARD_GRAB_TYPE_VIRTUAL && !is_virtual_device)
|
||||
return false;
|
||||
|
||||
if(keyboard_event_has_event_with_dev_input_fd(self, dev_input_id))
|
||||
@@ -286,18 +288,22 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
|
||||
|
||||
unsigned long evbit = 0;
|
||||
ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
|
||||
const bool is_keyboard = (evbit & (1 << EV_SYN)) && (evbit & (1 << EV_KEY)) && (evbit & (1 << EV_MSC)) && (evbit & (1 << EV_REP));
|
||||
const bool is_keyboard = (evbit & (1 << EV_SYN)) && (evbit & (1 << EV_KEY));
|
||||
|
||||
if(is_keyboard && strcmp(device_name, GSR_UI_VIRTUAL_KEYBOARD_NAME) != 0) {
|
||||
unsigned char key_bits[KEY_MAX/8 + 1] = {0};
|
||||
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits);
|
||||
|
||||
const bool supports_key_events = key_bits[KEY_A/8] & (1 << (KEY_A % 8));
|
||||
//const bool supports_mouse_events = key_bits[BTN_MOUSE/8] & (1 << (BTN_MOUSE % 8));
|
||||
const bool supports_key_a = key_bits[KEY_A/8] & (1 << (KEY_A % 8));
|
||||
const bool supports_key_esc = key_bits[KEY_ESC/8] & (1 << (KEY_ESC % 8));
|
||||
const bool supports_key_volume_up = key_bits[KEY_VOLUMEUP/8] & (1 << (KEY_VOLUMEUP % 8));
|
||||
const bool supports_key_events = supports_key_a || supports_key_esc || supports_key_volume_up;
|
||||
|
||||
const bool supports_mouse_events = key_bits[BTN_MOUSE/8] & (1 << (BTN_MOUSE % 8));
|
||||
//const bool supports_touch_events = key_bits[BTN_TOUCH/8] & (1 << (BTN_TOUCH % 8));
|
||||
const bool supports_joystick_events = key_bits[BTN_JOYSTICK/8] & (1 << (BTN_JOYSTICK % 8));
|
||||
const bool supports_wheel_events = key_bits[BTN_WHEEL/8] & (1 << (BTN_WHEEL % 8));
|
||||
if(supports_key_events && !supports_joystick_events && !supports_wheel_events) {
|
||||
if(supports_key_events && (is_virtual_device || (!supports_joystick_events && !supports_wheel_events))) {
|
||||
unsigned char *key_states = calloc(1, KEY_STATES_SIZE);
|
||||
if(key_states && self->num_event_polls < MAX_EVENT_POLLS) {
|
||||
//fprintf(stderr, "%s (%s) supports key inputs\n", dev_input_filepath, device_name);
|
||||
@@ -314,9 +320,16 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
|
||||
.num_keys_pressed = 0
|
||||
};
|
||||
|
||||
keyboard_event_fetch_update_key_states(self, &self->event_extra_data[self->num_event_polls], fd);
|
||||
if(self->event_extra_data[self->num_event_polls].num_keys_pressed > 0)
|
||||
fprintf(stderr, "Info: device not grabbed yet because some keys are still being pressed: /dev/input/event%d\n", dev_input_id);
|
||||
if(supports_mouse_events || supports_joystick_events || supports_wheel_events) {
|
||||
fprintf(stderr, "Info: device not grabbed yet because it might be a mouse: /dev/input/event%d\n", dev_input_id);
|
||||
fsync(fd);
|
||||
if(ioctl(fd, EVIOCGKEY(KEY_STATES_SIZE), self->event_extra_data[self->num_event_polls].key_states) == -1)
|
||||
fprintf(stderr, "Warning: failed to fetch key states for device: /dev/input/event%d\n", dev_input_id);
|
||||
} else {
|
||||
keyboard_event_fetch_update_key_states(self, &self->event_extra_data[self->num_event_polls], fd);
|
||||
if(self->event_extra_data[self->num_event_polls].num_keys_pressed > 0)
|
||||
fprintf(stderr, "Info: device not grabbed yet because some keys are still being pressed: /dev/input/event%d\n", dev_input_id);
|
||||
}
|
||||
|
||||
++self->num_event_polls;
|
||||
return true;
|
||||
@@ -395,7 +408,8 @@ static int setup_virtual_keyboard_input(const char *name) {
|
||||
|
||||
success &= (ioctl(fd, UI_SET_MSCBIT, MSC_SCAN) != -1);
|
||||
for(int i = 1; i < KEY_MAX; ++i) {
|
||||
success &= (ioctl(fd, UI_SET_KEYBIT, i) != -1);
|
||||
if(is_keyboard_key(i) || is_mouse_button(i))
|
||||
success &= (ioctl(fd, UI_SET_KEYBIT, i) != -1);
|
||||
}
|
||||
for(int i = 0; i < REL_MAX; ++i) {
|
||||
success &= (ioctl(fd, UI_SET_RELBIT, i) != -1);
|
||||
@@ -513,6 +527,7 @@ void keyboard_event_deinit(keyboard_event *self) {
|
||||
self->num_global_hotkeys = 0;
|
||||
|
||||
if(self->uinput_fd > 0) {
|
||||
ioctl(self->uinput_fd, UI_DEV_DESTROY);
|
||||
close(self->uinput_fd);
|
||||
self->uinput_fd = -1;
|
||||
}
|
||||
|
||||
21
tools/gsr-global-hotkeys/keys.c
Normal file
21
tools/gsr-global-hotkeys/keys.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "keys.h"
|
||||
#include <linux/input-event-codes.h>
|
||||
|
||||
bool is_keyboard_key(uint32_t keycode) {
|
||||
return (keycode >= KEY_ESC && keycode <= KEY_KPDOT)
|
||||
|| (keycode >= KEY_ZENKAKUHANKAKU && keycode <= KEY_F24)
|
||||
|| (keycode >= KEY_PLAYCD && keycode <= KEY_MICMUTE)
|
||||
|| (keycode >= KEY_OK && keycode <= KEY_IMAGES)
|
||||
|| (keycode >= KEY_DEL_EOL && keycode <= KEY_DEL_LINE)
|
||||
|| (keycode >= KEY_FN && keycode <= KEY_FN_B)
|
||||
|| (keycode >= KEY_BRL_DOT1 && keycode <= KEY_BRL_DOT10)
|
||||
|| (keycode >= KEY_NUMERIC_0 && keycode <= KEY_LIGHTS_TOGGLE)
|
||||
|| (keycode == KEY_ALS_TOGGLE)
|
||||
|| (keycode >= KEY_BUTTONCONFIG && keycode <= KEY_VOICECOMMAND)
|
||||
|| (keycode >= KEY_BRIGHTNESS_MIN && keycode <= KEY_BRIGHTNESS_MAX)
|
||||
|| (keycode >= KEY_KBDINPUTASSIST_PREV && keycode <= KEY_ONSCREEN_KEYBOARD);
|
||||
}
|
||||
|
||||
bool is_mouse_button(uint32_t keycode) {
|
||||
return (keycode >= BTN_MOUSE && keycode <= BTN_TASK);
|
||||
}
|
||||
10
tools/gsr-global-hotkeys/keys.h
Normal file
10
tools/gsr-global-hotkeys/keys.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef KEYS_H
|
||||
#define KEYS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool is_keyboard_key(uint32_t keycode);
|
||||
bool is_mouse_button(uint32_t keycode);
|
||||
|
||||
#endif /* KEYS_H */
|
||||
@@ -50,6 +50,7 @@ static void usage(void) {
|
||||
printf(" toggle-stream Start/stop streaming.\n");
|
||||
printf(" toggle-replay Start/stop replay.\n");
|
||||
printf(" replay-save Save replay.\n");
|
||||
printf(" take-screenshot Take a screenshot.\n");
|
||||
printf("\n");
|
||||
printf("EXAMPLES:\n");
|
||||
printf(" gsr-ui-cli toggle-show\n");
|
||||
@@ -65,6 +66,7 @@ static bool is_valid_command(const char *command) {
|
||||
"toggle-stream",
|
||||
"toggle-replay",
|
||||
"replay-save",
|
||||
"take-screenshot",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user