Compare commits

..

16 Commits

Author SHA1 Message Date
dec05eba
8bf6e533c5 1.10.2 2026-01-20 18:45:56 +01:00
dec05eba
902fc7f6a9 Force h264 for rumble/kick 2026-01-20 18:45:32 +01:00
dec05eba
794064a8b8 Fix incorrect region captured on wayland when using monitor scaling and without letting x11 scale monitors 2026-01-20 18:31:29 +01:00
dec05eba
e44b2ec528 1.10.1 2026-01-19 22:26:40 +01:00
dec05eba
5f484bd82c Add hotkey for region/window recording 2026-01-19 22:26:03 +01:00
dec05eba
0269387b9a 1.10.0 2026-01-18 17:06:22 +01:00
dec05eba
5c4ebbab59 Add option to choose webcam resolution and fps 2026-01-18 17:05:55 +01:00
dec05eba
40b2af5668 Add kick streaming option 2026-01-18 16:22:49 +01:00
dec05eba
aa717a95ec Show warning when adding output device and application audio at the same time 2026-01-18 16:05:37 +01:00
dec05eba
86424607b7 Add tooltip for 'record in low-power mode' 2026-01-18 15:25:49 +01:00
dec05eba
74bb6f0070 Add low power mode for amd 2026-01-18 01:21:07 +01:00
dec05eba
61bbaf3728 Update to handle new gsr --info output, remove general section from streaming 2026-01-15 23:44:32 +01:00
dec05eba
fed47000ce 1.9.3 - Only use led indicator if it's enabled 2026-01-08 20:40:11 +01:00
dec05eba
7f43adfbd5 1.9.3 2026-01-08 20:23:39 +01:00
dec05eba
1f6251baf3 Fix high cpu usage when running global hotkeys without grab and then connecting a secondary keyboard 2026-01-08 20:23:21 +01:00
dec05eba
d1220b013e Update flatpak version reference 2026-01-08 01:25:08 +01:00
40 changed files with 1070 additions and 374 deletions

10
TODO
View File

@@ -250,6 +250,12 @@ Sometimes when opening gpu screen recorder ui gsr-global-hotkeys incorrectly det
When running replay for a long time and then stopping it it takes a while. Improve this. When running replay for a long time and then stopping it it takes a while. Improve this.
When adding webcamera make replay auto start wait for camera to be available (when /dev/video device exists and can be initialized), just like audio device. Make it possible to resize webcam box from top left, top right and bottom left as well.
Make it possible to resize webcam box from top left, top right and bottom left as well. The flatpak version can for some get stuck at shutdown when instant replay is running. It only happens in the flatpak version and only when instant replay is running and it happens always. Manual SIGINT on gsr-ui stops gsr-ui properly, so why does it fail when shutting down the computer when the systemd stop signal is SIGINT? Maybe its related to the flatpak version being launched through gsr-gtk. I cant personally reproduce it.
Redesign the UI to allow capturing multiple video sources. Move webcam to capture sources as well then. Maybe design the UI to work more like obs studio then, where you start recording and then add sources at capture time, with a preview.
Add option to choose video container (either flv or mpegts) for youtube livestreaming.
Get wayland cursor position for region selector, otherwise the start position before the cursor moves is off.

BIN
images/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/question_mark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -60,10 +60,14 @@ namespace gsr {
bool overclock = false; bool overclock = false;
bool record_cursor = true; bool record_cursor = true;
bool restore_portal_session = true; bool restore_portal_session = true;
bool low_power_mode = false;
std::string webcam_source = ""; std::string webcam_source = "";
bool webcam_flip_horizontally = false; bool webcam_flip_horizontally = false;
std::string webcam_video_format = "auto"; std::string webcam_video_format = "auto";
int32_t webcam_camera_width = 0;
int32_t webcam_camera_height = 0;
int32_t webcam_camera_fps = 0;
int32_t webcam_x = 0; // A value between 0 and 100 (percentage) int32_t webcam_x = 0; // A value between 0 and 100 (percentage)
int32_t webcam_y = 0; // A value between 0 and 100 (percentage) int32_t webcam_y = 0; // A value between 0 and 100 (percentage)
int32_t webcam_width = 30; // A value between 0 and 100 (percentage), 0 = Don't scale it int32_t webcam_width = 30; // A value between 0 and 100 (percentage), 0 = Don't scale it
@@ -96,6 +100,11 @@ namespace gsr {
std::string stream_key; std::string stream_key;
}; };
struct KickStreamConfig {
std::string stream_url;
std::string stream_key;
};
struct CustomStreamConfig { struct CustomStreamConfig {
std::string url; std::string url;
std::string key; std::string key;
@@ -108,6 +117,7 @@ namespace gsr {
YoutubeStreamConfig youtube; YoutubeStreamConfig youtube;
TwitchStreamConfig twitch; TwitchStreamConfig twitch;
RumbleStreamConfig rumble; RumbleStreamConfig rumble;
KickStreamConfig kick;
CustomStreamConfig custom; CustomStreamConfig custom;
ConfigHotkey start_stop_hotkey; ConfigHotkey start_stop_hotkey;
}; };
@@ -119,6 +129,8 @@ namespace gsr {
std::string container = "mp4"; std::string container = "mp4";
ConfigHotkey start_stop_hotkey; ConfigHotkey start_stop_hotkey;
ConfigHotkey pause_unpause_hotkey; ConfigHotkey pause_unpause_hotkey;
ConfigHotkey start_stop_region_hotkey;
ConfigHotkey start_stop_window_hotkey;
}; };
struct ReplayConfig { struct ReplayConfig {

View File

@@ -1,44 +1,23 @@
#pragma once #pragma once
#include "CursorTracker.hpp" #include "CursorTracker.hpp"
#include <stdint.h>
#include <vector>
struct wl_display; struct wl_display;
struct wl_registry;
struct wl_output;
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
namespace gsr { namespace gsr {
struct WaylandOutput {
uint32_t wl_name;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
mgl::vec2i pos;
mgl::vec2i size;
int32_t transform;
std::string name;
};
class CursorTrackerWayland : public CursorTracker { class CursorTrackerWayland : public CursorTracker {
public: public:
CursorTrackerWayland(const char *card_path); CursorTrackerWayland(const char *card_path, struct wl_display *wayland_dpy);
CursorTrackerWayland(const CursorTrackerWayland&) = delete; CursorTrackerWayland(const CursorTrackerWayland&) = delete;
CursorTrackerWayland& operator=(const CursorTrackerWayland&) = delete; CursorTrackerWayland& operator=(const CursorTrackerWayland&) = delete;
~CursorTrackerWayland(); ~CursorTrackerWayland();
void update() override; void update() override;
std::optional<CursorInfo> get_latest_cursor_info() override; std::optional<CursorInfo> get_latest_cursor_info() override;
std::vector<WaylandOutput> monitors;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
private:
void clear_monitors();
void set_monitor_outputs_from_xdg_output(struct wl_display *dpy);
private: private:
int drm_fd = -1; int drm_fd = -1;
mgl::vec2i latest_cursor_position; // Position of the cursor within the monitor mgl::vec2i latest_cursor_position; // Position of the cursor within the monitor
int latest_crtc_id = -1; int latest_crtc_id = -1;
struct wl_display *wayland_dpy = nullptr;
}; };
} }

View File

@@ -25,9 +25,9 @@ namespace gsr {
bool png = false; bool png = false;
}; };
struct SupportedCameraPixelFormats { enum GsrCameraPixelFormat {
bool yuyv = false; YUYV,
bool mjpeg = false; MJPEG
}; };
struct GsrMonitor { struct GsrMonitor {
@@ -35,10 +35,16 @@ namespace gsr {
mgl::vec2i size; mgl::vec2i size;
}; };
struct GsrCameraSetup {
mgl::vec2i resolution;
int fps;
//GsrCameraPixelFormat pixel_format;
};
struct GsrCamera { struct GsrCamera {
std::string path; std::string path;
mgl::vec2i size; std::vector<GsrCameraSetup> yuyv_setups;
SupportedCameraPixelFormats supported_pixel_formats; std::vector<GsrCameraSetup> mjpeg_setups;
}; };
struct GsrVersion { struct GsrVersion {

View File

@@ -24,6 +24,8 @@
#include <array> #include <array>
struct wl_display;
namespace gsr { namespace gsr {
class DropdownButton; class DropdownButton;
class GlobalHotkeys; class GlobalHotkeys;
@@ -49,6 +51,12 @@ namespace gsr {
ERROR, ERROR,
}; };
enum class RecordForceType {
NONE,
REGION,
WINDOW
};
enum class ScreenshotForceType { enum class ScreenshotForceType {
NONE, NONE,
REGION, REGION,
@@ -74,7 +82,7 @@ namespace gsr {
void show(); void show();
void hide_next_frame(); void hide_next_frame();
void toggle_show(); void toggle_show();
void toggle_record(); void toggle_record(RecordForceType force_type);
void toggle_pause(); void toggle_pause();
void toggle_stream(); void toggle_stream();
void toggle_replay(); void toggle_replay();
@@ -108,6 +116,7 @@ namespace gsr {
void on_event(mgl::Event &event); void on_event(mgl::Event &event);
void recreate_global_hotkeys(const char *hotkey_option); void recreate_global_hotkeys(const char *hotkey_option);
void update_led_indicator_after_settings_change();
void create_frontpage_ui_components(); void create_frontpage_ui_components();
void xi_setup(); void xi_setup();
void handle_xi_events(); void handle_xi_events();
@@ -150,11 +159,14 @@ namespace gsr {
void on_press_save_replay_1_min_replay(); void on_press_save_replay_1_min_replay();
void on_press_save_replay_10_min_replay(); void on_press_save_replay_10_min_replay();
bool on_press_start_replay(bool disable_notification, bool finished_selection); bool on_press_start_replay(bool disable_notification, bool finished_selection);
void on_press_start_record(bool finished_selection); void on_press_start_record(bool finished_selection, RecordForceType force_type);
void on_press_start_stream(bool finished_selection); void on_press_start_stream(bool finished_selection);
void on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type); void on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type);
bool update_compositor_texture(const Monitor &monitor); bool update_compositor_texture(const Monitor &monitor);
void add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size);
void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const std::string &region_area_option);
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options); std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
void force_window_on_top(); void force_window_on_top();
@@ -243,6 +255,8 @@ namespace gsr {
Display *x11_dpy = nullptr; Display *x11_dpy = nullptr;
XEvent x11_mapping_xev; XEvent x11_mapping_xev;
struct wl_display *wayland_dpy = nullptr;
mgl::Clock replay_save_clock; mgl::Clock replay_save_clock;
bool replay_save_show_notification = false; bool replay_save_show_notification = false;
ReplayStartupMode replay_startup_mode = ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP; ReplayStartupMode replay_startup_mode = ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;

View File

@@ -7,6 +7,8 @@
#include <X11/Xlib.h> #include <X11/Xlib.h>
struct wl_display;
namespace gsr { namespace gsr {
struct Region { struct Region {
mgl::vec2i pos; mgl::vec2i pos;
@@ -28,7 +30,7 @@ namespace gsr {
bool poll_events(); bool poll_events();
bool take_selection(); bool take_selection();
bool take_canceled(); bool take_canceled();
Region get_selection() const; Region get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const;
private: private:
void on_button_press(const void *de); void on_button_press(const void *de);
void on_button_release(const void *de); void on_button_release(const void *de);

View File

@@ -47,6 +47,9 @@ namespace gsr {
mgl::Texture trash_texture; mgl::Texture trash_texture;
mgl::Texture masked_texture; mgl::Texture masked_texture;
mgl::Texture unmasked_texture; mgl::Texture unmasked_texture;
mgl::Texture warning_texture;
mgl::Texture info_texture;
mgl::Texture question_mark_texture;
mgl::Texture ps4_home_texture; mgl::Texture ps4_home_texture;
mgl::Texture ps4_options_texture; mgl::Texture ps4_options_texture;

View File

@@ -6,6 +6,8 @@
#include <optional> #include <optional>
#include <X11/Xlib.h> #include <X11/Xlib.h>
struct wl_display;
namespace gsr { namespace gsr {
enum class WindowCaptureType { enum class WindowCaptureType {
FOCUSED, FOCUSED,
@@ -13,8 +15,8 @@ namespace gsr {
}; };
struct Monitor { struct Monitor {
mgl::vec2i position; mgl::vec2i position; // Logical position on Wayland
mgl::vec2i size; mgl::vec2i size; // Logical size on Wayland
std::string name; std::string name;
}; };
@@ -30,6 +32,7 @@ namespace gsr {
std::string get_window_manager_name(Display *display); std::string get_window_manager_name(Display *display);
bool is_compositor_running(Display *dpy, int screen); bool is_compositor_running(Display *dpy, int screen);
std::vector<Monitor> get_monitors(Display *dpy); std::vector<Monitor> get_monitors(Display *dpy);
std::vector<Monitor> get_monitors_wayland(struct wl_display *dpy);
void xi_grab_all_mouse_devices(Display *dpy); void xi_grab_all_mouse_devices(Display *dpy);
void xi_ungrab_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 xi_warp_all_mouse_devices(Display *dpy, mgl::vec2i position);

View File

@@ -18,7 +18,7 @@ namespace gsr {
bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override;
void draw(mgl::Window &window, mgl::vec2f offset) override; void draw(mgl::Window &window, mgl::vec2f offset) override;
void add_item(const std::string &text, const std::string &id); void add_item(const std::string &text, const std::string &id, bool allow_duplicate = true);
void clear_items(); void clear_items();
// The item can only be selected if it's enabled // The item can only be selected if it's enabled

View File

@@ -26,6 +26,8 @@ namespace gsr {
REPLAY_SAVE_10_MIN, REPLAY_SAVE_10_MIN,
RECORD_START_STOP, RECORD_START_STOP,
RECORD_PAUSE_UNPAUSE, RECORD_PAUSE_UNPAUSE,
RECORD_START_STOP_REGION,
RECORD_START_STOP_WINDOW,
STREAM_START_STOP, STREAM_START_STOP,
TAKE_SCREENSHOT, TAKE_SCREENSHOT,
TAKE_SCREENSHOT_REGION, TAKE_SCREENSHOT_REGION,
@@ -61,6 +63,7 @@ namespace gsr {
std::unique_ptr<List> create_replay_hotkey_options(); std::unique_ptr<List> create_replay_hotkey_options();
std::unique_ptr<List> create_replay_partial_save_hotkey_options(); std::unique_ptr<List> create_replay_partial_save_hotkey_options();
std::unique_ptr<List> create_record_hotkey_options(); std::unique_ptr<List> create_record_hotkey_options();
std::unique_ptr<List> create_record_hotkey_window_region_options();
std::unique_ptr<List> create_stream_hotkey_options(); std::unique_ptr<List> create_stream_hotkey_options();
std::unique_ptr<List> create_screenshot_hotkey_options(); std::unique_ptr<List> create_screenshot_hotkey_options();
std::unique_ptr<List> create_screenshot_region_hotkey_options(); std::unique_ptr<List> create_screenshot_region_hotkey_options();
@@ -100,6 +103,8 @@ namespace gsr {
Button *save_replay_10_min_button_ptr = nullptr; Button *save_replay_10_min_button_ptr = nullptr;
Button *start_stop_recording_button_ptr = nullptr; Button *start_stop_recording_button_ptr = nullptr;
Button *pause_unpause_recording_button_ptr = nullptr; Button *pause_unpause_recording_button_ptr = nullptr;
Button *start_stop_recording_region_button_ptr = nullptr;
Button *start_stop_recording_window_button_ptr = nullptr;
Button *start_stop_streaming_button_ptr = nullptr; Button *start_stop_streaming_button_ptr = nullptr;
Button *take_screenshot_button_ptr = nullptr; Button *take_screenshot_button_ptr = nullptr;
Button *take_screenshot_region_button_ptr = nullptr; Button *take_screenshot_region_button_ptr = nullptr;

View File

@@ -3,6 +3,7 @@
#include "Widget.hpp" #include "Widget.hpp"
#include <mglpp/graphics/Sprite.hpp> #include <mglpp/graphics/Sprite.hpp>
#include <functional>
namespace gsr { namespace gsr {
class Image : public Widget { class Image : public Widget {
@@ -21,6 +22,8 @@ namespace gsr {
void draw(mgl::Window &window, mgl::vec2f offset) override; void draw(mgl::Window &window, mgl::vec2f offset) override;
mgl::vec2f get_size() override; mgl::vec2f get_size() override;
std::function<void(bool inside)> on_mouse_move;
private: private:
mgl::Sprite sprite; mgl::Sprite sprite;
mgl::vec2f size; mgl::vec2f size;

View File

@@ -23,6 +23,8 @@ namespace gsr {
void load(); void load();
void save(); void save();
void on_navigate_away_from_page() override; void on_navigate_away_from_page() override;
std::function<void()> on_config_changed;
private: private:
std::unique_ptr<ComboBox> create_record_area_box(); std::unique_ptr<ComboBox> create_record_area_box();
std::unique_ptr<Widget> create_record_area(); std::unique_ptr<Widget> create_record_area();

View File

@@ -67,7 +67,9 @@ namespace gsr {
std::unique_ptr<Widget> create_change_video_resolution_section(); std::unique_ptr<Widget> create_change_video_resolution_section();
std::unique_ptr<Widget> create_capture_target_section(); std::unique_ptr<Widget> create_capture_target_section();
std::unique_ptr<List> create_webcam_sources(); std::unique_ptr<List> create_webcam_sources();
std::unique_ptr<List> create_webcam_video_setups();
std::unique_ptr<List> create_webcam_video_format(); std::unique_ptr<List> create_webcam_video_format();
std::unique_ptr<List> create_webcam_video_setup_list();
std::unique_ptr<Widget> create_webcam_location_widget(); std::unique_ptr<Widget> create_webcam_location_widget();
std::unique_ptr<CheckBox> create_flip_camera_checkbox(); std::unique_ptr<CheckBox> create_flip_camera_checkbox();
std::unique_ptr<List> create_webcam_body(); std::unique_ptr<List> create_webcam_body();
@@ -76,6 +78,7 @@ namespace gsr {
std::unique_ptr<Button> create_remove_audio_device_button(List *audio_input_list_ptr, List *audio_device_list_ptr); std::unique_ptr<Button> create_remove_audio_device_button(List *audio_input_list_ptr, List *audio_device_list_ptr);
std::unique_ptr<List> create_audio_device(AudioDeviceType device_type, List *audio_input_list_ptr); std::unique_ptr<List> create_audio_device(AudioDeviceType device_type, List *audio_input_list_ptr);
std::unique_ptr<Button> create_add_audio_track_button(); std::unique_ptr<Button> create_add_audio_track_button();
void update_application_audio_warning_visibility();
std::unique_ptr<Button> create_add_audio_output_device_button(List *audio_input_list_ptr); std::unique_ptr<Button> create_add_audio_output_device_button(List *audio_input_list_ptr);
std::unique_ptr<Button> create_add_audio_input_device_button(List *audio_input_list_ptr); std::unique_ptr<Button> create_add_audio_input_device_button(List *audio_input_list_ptr);
std::unique_ptr<ComboBox> create_application_audio_selection_combobox(List *application_audio_row); std::unique_ptr<ComboBox> create_application_audio_selection_combobox(List *application_audio_row);
@@ -85,6 +88,7 @@ namespace gsr {
std::unique_ptr<List> create_add_audio_buttons(List *audio_input_list_ptr); std::unique_ptr<List> create_add_audio_buttons(List *audio_input_list_ptr);
std::unique_ptr<List> create_audio_input_section(); std::unique_ptr<List> create_audio_input_section();
std::unique_ptr<CheckBox> create_application_audio_invert_checkbox(); std::unique_ptr<CheckBox> create_application_audio_invert_checkbox();
std::unique_ptr<Widget> create_application_audio_warning();
std::unique_ptr<List> create_audio_track_title_and_remove(Subsection *audio_track_subsection, const char *title); std::unique_ptr<List> create_audio_track_title_and_remove(Subsection *audio_track_subsection, const char *title);
std::unique_ptr<Subsection> create_audio_track_section(Widget *parent_widget); std::unique_ptr<Subsection> create_audio_track_section(Widget *parent_widget);
std::unique_ptr<List> create_audio_track_section_list(); std::unique_ptr<List> create_audio_track_section_list();
@@ -129,6 +133,7 @@ namespace gsr {
std::unique_ptr<CheckBox> create_led_indicator(const char *type); std::unique_ptr<CheckBox> create_led_indicator(const char *type);
std::unique_ptr<CheckBox> create_notifications(const char *type); std::unique_ptr<CheckBox> create_notifications(const char *type);
std::unique_ptr<List> create_indicator(const char *type); std::unique_ptr<List> create_indicator(const char *type);
std::unique_ptr<Widget> create_low_power_mode();
void add_replay_widgets(); void add_replay_widgets();
void add_record_widgets(); void add_record_widgets();
@@ -203,6 +208,8 @@ namespace gsr {
Entry *twitch_stream_key_entry_ptr = nullptr; Entry *twitch_stream_key_entry_ptr = nullptr;
Entry *youtube_stream_key_entry_ptr = nullptr; Entry *youtube_stream_key_entry_ptr = nullptr;
Entry *rumble_stream_key_entry_ptr = nullptr; Entry *rumble_stream_key_entry_ptr = nullptr;
Entry *kick_stream_url_entry_ptr = nullptr;
Entry *kick_stream_key_entry_ptr = nullptr;
Entry *stream_url_entry_ptr = nullptr; Entry *stream_url_entry_ptr = nullptr;
Entry *stream_key_entry_ptr = nullptr; Entry *stream_key_entry_ptr = nullptr;
Entry *replay_time_entry_ptr = nullptr; Entry *replay_time_entry_ptr = nullptr;
@@ -214,9 +221,11 @@ namespace gsr {
CheckBox *led_indicator_checkbox_ptr = nullptr; CheckBox *led_indicator_checkbox_ptr = nullptr;
CheckBox *show_notification_checkbox_ptr = nullptr; CheckBox *show_notification_checkbox_ptr = nullptr;
ComboBox *webcam_sources_box_ptr = nullptr; ComboBox *webcam_sources_box_ptr = nullptr;
ComboBox *webcam_video_setup_box_ptr = nullptr;
ComboBox *webcam_video_format_box_ptr = nullptr; ComboBox *webcam_video_format_box_ptr = nullptr;
List *webcam_body_list_ptr = nullptr; List *webcam_body_list_ptr = nullptr;
CheckBox *flip_camera_horizontally_checkbox_ptr = nullptr; CheckBox *flip_camera_horizontally_checkbox_ptr = nullptr;
CheckBox *low_power_mode_checkbox_ptr = nullptr;
PageStack *page_stack = nullptr; PageStack *page_stack = nullptr;
@@ -237,5 +246,6 @@ namespace gsr {
mgl::vec2f webcam_box_size_resize_start; mgl::vec2f webcam_box_size_resize_start;
std::optional<GsrCamera> selected_camera; std::optional<GsrCamera> selected_camera;
std::optional<GsrCameraSetup> selected_camera_setup;
}; };
} }

22
include/gui/Tooltip.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "Widget.hpp"
#include <mglpp/graphics/Text.hpp>
namespace gsr {
class Tooltip : public Widget {
public:
Tooltip(mgl::Font *font);
Tooltip(const Tooltip&) = delete;
Tooltip& operator=(const Tooltip&) = delete;
bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override;
void draw(mgl::Window &window, mgl::vec2f offset) override;
mgl::vec2f get_size() override;
void set_text(std::string text);
private:
mgl::Text label;
};
}

View File

@@ -2,6 +2,7 @@
#include <mglpp/system/vec.hpp> #include <mglpp/system/vec.hpp>
#include <memory> #include <memory>
#include <string>
namespace mgl { namespace mgl {
class Event; class Event;
@@ -44,9 +45,14 @@ namespace gsr {
Alignment get_vertical_alignment() const; Alignment get_vertical_alignment() const;
void set_visible(bool visible); void set_visible(bool visible);
bool is_visible() const;
Widget* get_parent_widget(); Widget* get_parent_widget();
void set_tooltip_text(std::string text);
const std::string& get_tooltip_text() const;
void handle_tooltip_event(mgl::Event &event, mgl::vec2f position, mgl::vec2f size);
void *userdata = nullptr; void *userdata = nullptr;
protected: protected:
void set_widget_as_selected_in_parent(); void set_widget_as_selected_in_parent();
@@ -61,8 +67,13 @@ namespace gsr {
Alignment vertical_aligment = Alignment::START; Alignment vertical_aligment = Alignment::START;
bool visible = true; bool visible = true;
std::string tooltip_text;
}; };
void add_widget_to_remove(std::unique_ptr<Widget> widget); void add_widget_to_remove(std::unique_ptr<Widget> widget);
void remove_widgets_to_be_removed(); void remove_widgets_to_be_removed();
void set_current_tooltip(Widget *widget);
void remove_as_current_tooltip(Widget *widget);
void draw_tooltip(mgl::Window &window);
} }

View File

@@ -1,4 +1,4 @@
project('gsr-ui', ['c', 'cpp'], version : '1.9.2', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends') project('gsr-ui', ['c', 'cpp'], version : '1.10.2', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp']) add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
@@ -34,6 +34,7 @@ src = [
'src/gui/GlobalSettingsPage.cpp', 'src/gui/GlobalSettingsPage.cpp',
'src/gui/GsrPage.cpp', 'src/gui/GsrPage.cpp',
'src/gui/Subsection.cpp', 'src/gui/Subsection.cpp',
'src/gui/Tooltip.cpp',
'src/GlobalHotkeys/GlobalHotkeysX11.cpp', 'src/GlobalHotkeys/GlobalHotkeysX11.cpp',
'src/GlobalHotkeys/GlobalHotkeysLinux.cpp', 'src/GlobalHotkeys/GlobalHotkeysLinux.cpp',
'src/GlobalHotkeys/GlobalHotkeysJoystick.cpp', 'src/GlobalHotkeys/GlobalHotkeysJoystick.cpp',
@@ -67,7 +68,7 @@ gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
icons_path = join_paths(prefix, datadir, 'icons') icons_path = join_paths(prefix, datadir, 'icons')
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp']) add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.11.3"', language: ['c', 'cpp']) add_project_arguments('-DGSR_FLATPAK_VERSION="5.12.0"', language: ['c', 'cpp'])
executable( executable(
meson.project_name(), meson.project_name(),

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "gsr-ui" name = "gsr-ui"
type = "executable" type = "executable"
version = "1.9.2" version = "1.10.2"
platforms = ["posix"] platforms = ["posix"]
[lang.cpp] [lang.cpp]

View File

@@ -272,8 +272,10 @@ namespace gsr {
} }
clipboard_copies.clear(); clipboard_copies.clear();
if(XGetSelectionOwner(dpy, clipboard_atom) == clipboard_window) if(XGetSelectionOwner(dpy, clipboard_atom) == clipboard_window) {
XSetSelectionOwner(dpy, clipboard_atom, None, CurrentTime); XSetSelectionOwner(dpy, clipboard_atom, None, CurrentTime);
XFlush(dpy);
}
if(filepath.empty()) { if(filepath.empty()) {
// TODO: Cancel transfer // TODO: Cancel transfer

View File

@@ -145,6 +145,8 @@ namespace gsr {
record_config.start_stop_hotkey = {mgl::Keyboard::F9, 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}; record_config.pause_unpause_hotkey = {mgl::Keyboard::F7, HOTKEY_MOD_LALT};
record_config.start_stop_region_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LCTRL};
record_config.start_stop_window_hotkey = {mgl::Keyboard::F9, HOTKEY_MOD_LSHIFT};
replay_config.start_stop_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT | HOTKEY_MOD_LSHIFT}; 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.save_hotkey = {mgl::Keyboard::F10, HOTKEY_MOD_LALT};
@@ -199,9 +201,13 @@ namespace gsr {
{"streaming.record_options.overclock", &config.streaming_config.record_options.overclock}, {"streaming.record_options.overclock", &config.streaming_config.record_options.overclock},
{"streaming.record_options.record_cursor", &config.streaming_config.record_options.record_cursor}, {"streaming.record_options.record_cursor", &config.streaming_config.record_options.record_cursor},
{"streaming.record_options.restore_portal_session", &config.streaming_config.record_options.restore_portal_session}, {"streaming.record_options.restore_portal_session", &config.streaming_config.record_options.restore_portal_session},
{"streaming.record_options.low_power_mode", &config.streaming_config.record_options.low_power_mode},
{"streaming.record_options.webcam_source", &config.streaming_config.record_options.webcam_source}, {"streaming.record_options.webcam_source", &config.streaming_config.record_options.webcam_source},
{"streaming.record_options.webcam_flip_horizontally", &config.streaming_config.record_options.webcam_flip_horizontally}, {"streaming.record_options.webcam_flip_horizontally", &config.streaming_config.record_options.webcam_flip_horizontally},
{"streaming.record_options.webcam_video_format", &config.streaming_config.record_options.webcam_video_format}, {"streaming.record_options.webcam_video_format", &config.streaming_config.record_options.webcam_video_format},
{"streaming.record_options.webcam_camera_width", &config.streaming_config.record_options.webcam_camera_width},
{"streaming.record_options.webcam_camera_height", &config.streaming_config.record_options.webcam_camera_height},
{"streaming.record_options.webcam_camera_fps", &config.streaming_config.record_options.webcam_camera_fps},
{"streaming.record_options.webcam_x", &config.streaming_config.record_options.webcam_x}, {"streaming.record_options.webcam_x", &config.streaming_config.record_options.webcam_x},
{"streaming.record_options.webcam_y", &config.streaming_config.record_options.webcam_y}, {"streaming.record_options.webcam_y", &config.streaming_config.record_options.webcam_y},
{"streaming.record_options.webcam_width", &config.streaming_config.record_options.webcam_width}, {"streaming.record_options.webcam_width", &config.streaming_config.record_options.webcam_width},
@@ -212,6 +218,8 @@ namespace gsr {
{"streaming.youtube.key", &config.streaming_config.youtube.stream_key}, {"streaming.youtube.key", &config.streaming_config.youtube.stream_key},
{"streaming.twitch.key", &config.streaming_config.twitch.stream_key}, {"streaming.twitch.key", &config.streaming_config.twitch.stream_key},
{"streaming.rumble.key", &config.streaming_config.rumble.stream_key}, {"streaming.rumble.key", &config.streaming_config.rumble.stream_key},
{"streaming.kick.url", &config.streaming_config.kick.stream_url},
{"streaming.kick.key", &config.streaming_config.kick.stream_key},
{"streaming.custom.url", &config.streaming_config.custom.url}, {"streaming.custom.url", &config.streaming_config.custom.url},
{"streaming.custom.key", &config.streaming_config.custom.key}, {"streaming.custom.key", &config.streaming_config.custom.key},
{"streaming.custom.container", &config.streaming_config.custom.container}, {"streaming.custom.container", &config.streaming_config.custom.container},
@@ -238,9 +246,13 @@ namespace gsr {
{"record.record_options.overclock", &config.record_config.record_options.overclock}, {"record.record_options.overclock", &config.record_config.record_options.overclock},
{"record.record_options.record_cursor", &config.record_config.record_options.record_cursor}, {"record.record_options.record_cursor", &config.record_config.record_options.record_cursor},
{"record.record_options.restore_portal_session", &config.record_config.record_options.restore_portal_session}, {"record.record_options.restore_portal_session", &config.record_config.record_options.restore_portal_session},
{"record.record_options.low_power_mode", &config.record_config.record_options.low_power_mode},
{"record.record_options.webcam_source", &config.record_config.record_options.webcam_source}, {"record.record_options.webcam_source", &config.record_config.record_options.webcam_source},
{"record.record_options.webcam_flip_horizontally", &config.record_config.record_options.webcam_flip_horizontally}, {"record.record_options.webcam_flip_horizontally", &config.record_config.record_options.webcam_flip_horizontally},
{"record.record_options.webcam_video_format", &config.record_config.record_options.webcam_video_format}, {"record.record_options.webcam_video_format", &config.record_config.record_options.webcam_video_format},
{"record.record_options.webcam_camera_width", &config.record_config.record_options.webcam_camera_width},
{"record.record_options.webcam_camera_height", &config.record_config.record_options.webcam_camera_height},
{"record.record_options.webcam_camera_fps", &config.record_config.record_options.webcam_camera_fps},
{"record.record_options.webcam_x", &config.record_config.record_options.webcam_x}, {"record.record_options.webcam_x", &config.record_config.record_options.webcam_x},
{"record.record_options.webcam_y", &config.record_config.record_options.webcam_y}, {"record.record_options.webcam_y", &config.record_config.record_options.webcam_y},
{"record.record_options.webcam_width", &config.record_config.record_options.webcam_width}, {"record.record_options.webcam_width", &config.record_config.record_options.webcam_width},
@@ -252,6 +264,8 @@ namespace gsr {
{"record.container", &config.record_config.container}, {"record.container", &config.record_config.container},
{"record.start_stop_hotkey", &config.record_config.start_stop_hotkey}, {"record.start_stop_hotkey", &config.record_config.start_stop_hotkey},
{"record.pause_unpause_hotkey", &config.record_config.pause_unpause_hotkey}, {"record.pause_unpause_hotkey", &config.record_config.pause_unpause_hotkey},
{"record.start_stop_region_hotkey", &config.record_config.start_stop_region_hotkey},
{"record.start_stop_window_hotkey", &config.record_config.start_stop_window_hotkey},
{"replay.record_options.record_area_option", &config.replay_config.record_options.record_area_option}, {"replay.record_options.record_area_option", &config.replay_config.record_options.record_area_option},
{"replay.record_options.record_area_width", &config.replay_config.record_options.record_area_width}, {"replay.record_options.record_area_width", &config.replay_config.record_options.record_area_width},
@@ -274,9 +288,13 @@ namespace gsr {
{"replay.record_options.overclock", &config.replay_config.record_options.overclock}, {"replay.record_options.overclock", &config.replay_config.record_options.overclock},
{"replay.record_options.record_cursor", &config.replay_config.record_options.record_cursor}, {"replay.record_options.record_cursor", &config.replay_config.record_options.record_cursor},
{"replay.record_options.restore_portal_session", &config.replay_config.record_options.restore_portal_session}, {"replay.record_options.restore_portal_session", &config.replay_config.record_options.restore_portal_session},
{"replay.record_options.low_power_mode", &config.replay_config.record_options.low_power_mode},
{"replay.record_options.webcam_source", &config.replay_config.record_options.webcam_source}, {"replay.record_options.webcam_source", &config.replay_config.record_options.webcam_source},
{"replay.record_options.webcam_flip_horizontally", &config.replay_config.record_options.webcam_flip_horizontally}, {"replay.record_options.webcam_flip_horizontally", &config.replay_config.record_options.webcam_flip_horizontally},
{"replay.record_options.webcam_video_format", &config.replay_config.record_options.webcam_video_format}, {"replay.record_options.webcam_video_format", &config.replay_config.record_options.webcam_video_format},
{"replay.record_options.webcam_camera_width", &config.replay_config.record_options.webcam_camera_width},
{"replay.record_options.webcam_camera_height", &config.replay_config.record_options.webcam_camera_height},
{"replay.record_options.webcam_camera_fps", &config.replay_config.record_options.webcam_camera_fps},
{"replay.record_options.webcam_x", &config.replay_config.record_options.webcam_x}, {"replay.record_options.webcam_x", &config.replay_config.record_options.webcam_x},
{"replay.record_options.webcam_y", &config.replay_config.record_options.webcam_y}, {"replay.record_options.webcam_y", &config.replay_config.record_options.webcam_y},
{"replay.record_options.webcam_width", &config.replay_config.record_options.webcam_width}, {"replay.record_options.webcam_width", &config.replay_config.record_options.webcam_width},

View File

@@ -1,11 +1,10 @@
#include "../../include/CursorTracker/CursorTrackerWayland.hpp" #include "../../include/CursorTracker/CursorTrackerWayland.hpp"
#include "../../include/WindowUtils.hpp"
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <xf86drm.h> #include <xf86drm.h>
#include <xf86drmMode.h> #include <xf86drmMode.h>
#include <wayland-client.h>
#include "xdg-output-unstable-v1-client-protocol.h"
namespace gsr { namespace gsr {
static const int MAX_CONNECTORS = 32; static const int MAX_CONNECTORS = 32;
@@ -136,177 +135,14 @@ namespace gsr {
} }
// Name is the crtc name. TODO: verify if this works on all wayland compositors // Name is the crtc name. TODO: verify if this works on all wayland compositors
static const WaylandOutput* get_wayland_monitor_by_name(const std::vector<WaylandOutput> &monitors, const std::string &name) { static const Monitor* get_wayland_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) {
for(const WaylandOutput &monitor : monitors) { for(const Monitor &monitor : monitors) {
if(monitor.name == name) if(monitor.name == name)
return &monitor; return &monitor;
} }
return nullptr; return nullptr;
} }
static WaylandOutput* get_wayland_monitor_by_output(CursorTrackerWayland &cursor_tracker_wayland, struct wl_output *output) {
for(WaylandOutput &monitor : cursor_tracker_wayland.monitors) {
if(monitor.output == output)
return &monitor;
}
return nullptr;
}
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
(void)wl_output;
(void)phys_width;
(void)phys_height;
(void)subpixel;
(void)make;
(void)model;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->pos.x = x;
monitor->pos.y = y;
monitor->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
(void)wl_output;
(void)flags;
(void)refresh;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->size.x = width;
monitor->size.y = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
(void)data;
(void)wl_output;
}
static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
(void)data;
(void)wl_output;
(void)factor;
}
static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
(void)wl_output;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->name = name;
}
static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
(void)data;
(void)wl_output;
(void)description;
}
static const struct wl_output_listener output_listener = {
output_handle_geometry,
output_handle_mode,
output_handle_done,
output_handle_scale,
output_handle_name,
output_handle_description,
};
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n");
return;
}
struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4);
cursor_tracker_wayland->monitors.push_back(
WaylandOutput{
name,
output,
nullptr,
mgl::vec2i{0, 0},
mgl::vec2i{0, 0},
0,
""
});
wl_output_add_listener(output, &output_listener, cursor_tracker_wayland);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n");
return;
}
if(cursor_tracker_wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(cursor_tracker_wayland->xdg_output_manager);
cursor_tracker_wayland->xdg_output_manager = NULL;
}
cursor_tracker_wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
registry_add_object,
registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->pos.x = x;
monitor->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)data;
(void)xdg_output;
(void)width;
(void)height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_logical_position,
xdg_output_handle_logical_size,
xdg_output_handle_done,
xdg_output_handle_name,
xdg_output_handle_description,
};
/* Returns nullptr if not found */ /* Returns nullptr if not found */
static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) { static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) {
for(int i = 0; i < connectors->num_connectors; ++i) { for(int i = 0; i < connectors->num_connectors; ++i) {
@@ -368,8 +204,7 @@ namespace gsr {
if(!properties) if(!properties)
goto next_crtc; goto next_crtc;
if(!get_drm_property_by_name(drm_fd, properties, "VRR_ENABLED", &vrr_enabled)) get_drm_property_by_name(drm_fd, properties, "VRR_ENABLED", &vrr_enabled);
goto next_crtc;
connector = get_drm_connector_by_crtc_id(drm_connectors, crtc->crtc_id); connector = get_drm_connector_by_crtc_id(drm_connectors, crtc->crtc_id);
if(!connector) if(!connector)
@@ -391,7 +226,7 @@ namespace gsr {
drmModeFreeResources(resources); drmModeFreeResources(resources);
} }
CursorTrackerWayland::CursorTrackerWayland(const char *card_path) { CursorTrackerWayland::CursorTrackerWayland(const char *card_path, struct wl_display *wayland_dpy) : wayland_dpy(wayland_dpy) {
drm_fd = open(card_path, O_RDONLY); drm_fd = open(card_path, O_RDONLY);
if(drm_fd <= 0) { if(drm_fd <= 0) {
fprintf(stderr, "Error: CursorTrackerWayland: failed to open %s\n", card_path); fprintf(stderr, "Error: CursorTrackerWayland: failed to open %s\n", card_path);
@@ -403,7 +238,6 @@ namespace gsr {
} }
CursorTrackerWayland::~CursorTrackerWayland() { CursorTrackerWayland::~CursorTrackerWayland() {
clear_monitors();
if(drm_fd > 0) if(drm_fd > 0)
close(drm_fd); close(drm_fd);
} }
@@ -466,80 +300,19 @@ namespace gsr {
drmModeFreePlaneResources(planes); drmModeFreePlaneResources(planes);
} }
void CursorTrackerWayland::set_monitor_outputs_from_xdg_output(struct wl_display *dpy) {
if(!xdg_output_manager) {
fprintf(stderr, "Warning: CursorTrackerWayland::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n");
return;
}
for(WaylandOutput &monitor : monitors) {
monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, monitor.output);
zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor);
}
// Fetch xdg_output
wl_display_roundtrip(dpy);
}
void CursorTrackerWayland::clear_monitors() {
for(WaylandOutput &monitor : monitors) {
if(monitor.output) {
wl_output_destroy(monitor.output);
monitor.output = nullptr;
}
if(monitor.xdg_output) {
zxdg_output_v1_destroy(monitor.xdg_output);
monitor.xdg_output = nullptr;
}
}
monitors.clear();
}
std::optional<CursorInfo> CursorTrackerWayland::get_latest_cursor_info() { std::optional<CursorInfo> CursorTrackerWayland::get_latest_cursor_info() {
if(drm_fd <= 0 || latest_crtc_id == -1) if(drm_fd <= 0 || latest_crtc_id == -1 || !wayland_dpy)
return std::nullopt; return std::nullopt;
std::string monitor_name = get_monitor_name_from_crtc_id(drm_fd, latest_crtc_id); std::string monitor_name = get_monitor_name_from_crtc_id(drm_fd, latest_crtc_id);
if(monitor_name.empty()) if(monitor_name.empty())
return std::nullopt; return std::nullopt;
struct wl_display *dpy = wl_display_connect(nullptr); const std::vector<Monitor> wayland_monitors = get_monitors_wayland(wayland_dpy);
if(!dpy) { const Monitor *wayland_monitor = get_wayland_monitor_by_name(wayland_monitors, monitor_name);
fprintf(stderr, "Error: CursorTrackerWayland::get_latest_cursor_info: failed to connect to the wayland server\n"); if(!wayland_monitor)
return std::nullopt; return std::nullopt;
}
clear_monitors(); return CursorInfo{ wayland_monitor->position + latest_cursor_position, std::move(monitor_name) };
struct wl_registry *registry = wl_display_get_registry(dpy);
wl_registry_add_listener(registry, &registry_listener, this);
// Fetch globals
wl_display_roundtrip(dpy);
// Fetch wl_output
wl_display_roundtrip(dpy);
set_monitor_outputs_from_xdg_output(dpy);
mgl::vec2i cursor_position = latest_cursor_position;
const WaylandOutput *wayland_monitor = get_wayland_monitor_by_name(monitors, monitor_name);
if(!wayland_monitor) {
clear_monitors();
return std::nullopt;
}
cursor_position = wayland_monitor->pos + latest_cursor_position;
clear_monitors();
if(xdg_output_manager) {
zxdg_output_manager_v1_destroy(xdg_output_manager);
xdg_output_manager = nullptr;
}
wl_registry_destroy(registry);
wl_display_disconnect(dpy);
return CursorInfo{ cursor_position, std::move(monitor_name) };
} }
} }

View File

@@ -310,32 +310,34 @@ namespace gsr {
}; };
} }
static SupportedCameraPixelFormats parse_supported_camera_pixel_formats(std::string_view line) { static bool parse_camera_pixel_format(std::string_view line, GsrCameraPixelFormat &pixel_format) {
SupportedCameraPixelFormats result; if(line == "yuyv") {
string_split_char(line, ',', [&](std::string_view column) { pixel_format = YUYV;
if(column == "yuyv")
result.yuyv = true;
else if(column == "mjpeg")
result.mjpeg = true;
return true; return true;
}); } else if(line == "mjpeg") {
return result; pixel_format = MJPEG;
return true;
} else {
return false;
}
} }
static std::optional<GsrCamera> capture_option_line_to_camera(std::string_view line) { static bool capture_option_line_to_camera(std::string_view line, std::string &path, GsrCameraSetup &camera_setup, GsrCameraPixelFormat &pixel_format) {
std::optional<GsrCamera> camera;
const std::optional<KeyValue3> key_value3 = parse_3(line); const std::optional<KeyValue3> key_value3 = parse_3(line);
if(!key_value3) if(!key_value3)
return camera; return false;
path = key_value3->value1;
mgl::vec2i size;
char value_buffer[256]; char value_buffer[256];
snprintf(value_buffer, sizeof(value_buffer), "%.*s", (int)key_value3->value2.size(), key_value3->value2.data()); snprintf(value_buffer, sizeof(value_buffer), "%.*s", (int)key_value3->value2.size(), key_value3->value2.data());
if(sscanf(value_buffer, "%dx%d", &size.x, &size.y) != 2) if(sscanf(value_buffer, "%dx%d@%dhz", &camera_setup.resolution.x, &camera_setup.resolution.y, &camera_setup.fps) != 3)
return camera; return false;
camera = GsrCamera{std::string(key_value3->value1), size, parse_supported_camera_pixel_formats(key_value3->value3)}; if(!parse_camera_pixel_format(key_value3->value3, pixel_format))
return camera; return false;
return true;
} }
static std::optional<GsrMonitor> capture_option_line_to_monitor(std::string_view line) { static std::optional<GsrMonitor> capture_option_line_to_monitor(std::string_view line) {
@@ -354,6 +356,37 @@ namespace gsr {
return monitor; return monitor;
} }
static GsrCamera* get_gsr_camera_by_path(std::vector<GsrCamera> &cameras, const std::string &path) {
for(GsrCamera &camera : cameras) {
if(camera.path == path)
return &camera;
}
return nullptr;
}
static void parse_camera_line(std::string_view line, std::vector<GsrCamera> &cameras) {
std::string camera_path;
GsrCameraSetup camera_setup;
GsrCameraPixelFormat pixel_format;
if(!capture_option_line_to_camera(line, camera_path, camera_setup, pixel_format))
return;
GsrCamera *existing_camera = get_gsr_camera_by_path(cameras, camera_path);
if(!existing_camera) {
cameras.push_back(GsrCamera{camera_path, std::vector<GsrCameraSetup>{}, std::vector<GsrCameraSetup>{}});
existing_camera = &cameras.back();
}
switch(pixel_format) {
case YUYV:
existing_camera->yuyv_setups.push_back(camera_setup);
break;
case MJPEG:
existing_camera->mjpeg_setups.push_back(camera_setup);
break;
}
}
static void parse_capture_options_line(SupportedCaptureOptions &capture_options, std::string_view line) { static void parse_capture_options_line(SupportedCaptureOptions &capture_options, std::string_view line) {
if(line == "window") { if(line == "window") {
capture_options.window = true; capture_options.window = true;
@@ -364,9 +397,7 @@ namespace gsr {
} else if(line == "portal") { } else if(line == "portal") {
capture_options.portal = true; capture_options.portal = true;
} else if(!line.empty() && line[0] == '/') { } else if(!line.empty() && line[0] == '/') {
std::optional<GsrCamera> camera = capture_option_line_to_camera(line); parse_camera_line(line, capture_options.cameras);
if(camera)
capture_options.cameras.push_back(std::move(camera.value()));
} else { } else {
std::optional<GsrMonitor> monitor = capture_option_line_to_monitor(line); std::optional<GsrMonitor> monitor = capture_option_line_to_monitor(line);
if(monitor) if(monitor)
@@ -414,9 +445,7 @@ namespace gsr {
} }
string_split_char(stdout_str, '\n', [&](std::string_view line) { string_split_char(stdout_str, '\n', [&](std::string_view line) {
std::optional<GsrCamera> camera = capture_option_line_to_camera(line); parse_camera_line(line, cameras);
if(camera)
cameras.push_back(std::move(camera.value()));
return true; return true;
}); });

View File

@@ -152,19 +152,24 @@ namespace gsr {
if(read_led_brightness_timer.get_elapsed_time_seconds() > 0.2) { if(read_led_brightness_timer.get_elapsed_time_seconds() > 0.2) {
read_led_brightness_timer.restart(); read_led_brightness_timer.restart();
bool led_status_outdated = false; bool any_keyboard_with_led_enabled = false;
bool any_keyboard_with_led_disabled = false;
char buffer[32]; char buffer[32];
for(int led_brightness_file_fd : led_brightness_files) { for(int led_brightness_file_fd : led_brightness_files) {
const ssize_t bytes_read = read(led_brightness_file_fd, buffer, sizeof(buffer)); const ssize_t bytes_read = read(led_brightness_file_fd, buffer, sizeof(buffer));
if(bytes_read > 0) { if(bytes_read > 0) {
if(buffer[0] == '0') if(buffer[0] == '0')
led_status_outdated = true; any_keyboard_with_led_disabled = true;
else
any_keyboard_with_led_enabled = true;
lseek(led_brightness_file_fd, 0, SEEK_SET); lseek(led_brightness_file_fd, 0, SEEK_SET);
} }
} }
if(led_status_outdated && led_enabled) if(led_enabled && any_keyboard_with_led_disabled)
run_gsr_global_hotkeys_set_leds(true); run_gsr_global_hotkeys_set_leds(true);
else if(!led_enabled && any_keyboard_with_led_enabled)
run_gsr_global_hotkeys_set_leds(false);
} }
} }
} }

View File

@@ -39,6 +39,9 @@
#include <X11/extensions/XInput2.h> #include <X11/extensions/XInput2.h>
#include <X11/extensions/shapeconst.h> #include <X11/extensions/shapeconst.h>
#include <X11/Xcursor/Xcursor.h> #include <X11/Xcursor/Xcursor.h>
#include <wayland-client.h>
#include <mglpp/system/Rect.hpp> #include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp> #include <mglpp/window/Event.hpp>
#include <mglpp/system/Utf8.hpp> #include <mglpp/system/Utf8.hpp>
@@ -323,7 +326,7 @@ namespace gsr {
config_hotkey_to_hotkey(overlay->get_config().record_config.start_stop_hotkey), config_hotkey_to_hotkey(overlay->get_config().record_config.start_stop_hotkey),
"record", [overlay](const std::string &id) { "record", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str()); fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_record(); overlay->toggle_record(RecordForceType::NONE);
}); });
global_hotkeys->bind_key_press( global_hotkeys->bind_key_press(
@@ -333,6 +336,20 @@ namespace gsr {
overlay->toggle_pause(); overlay->toggle_pause();
}); });
global_hotkeys->bind_key_press(
config_hotkey_to_hotkey(overlay->get_config().record_config.start_stop_region_hotkey),
"record_region", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_record(RecordForceType::REGION);
});
global_hotkeys->bind_key_press(
config_hotkey_to_hotkey(overlay->get_config().record_config.start_stop_window_hotkey),
"record_window", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_record(RecordForceType::WINDOW);
});
global_hotkeys->bind_key_press( global_hotkeys->bind_key_press(
config_hotkey_to_hotkey(overlay->get_config().streaming_config.start_stop_hotkey), config_hotkey_to_hotkey(overlay->get_config().streaming_config.start_stop_hotkey),
"stream", [overlay](const std::string &id) { "stream", [overlay](const std::string &id) {
@@ -441,7 +458,7 @@ namespace gsr {
global_hotkeys_js->bind_action("toggle_record", [overlay](const std::string &id) { global_hotkeys_js->bind_action("toggle_record", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str()); fprintf(stderr, "pressed %s\n", id.c_str());
overlay->toggle_record(); overlay->toggle_record(RecordForceType::NONE);
}); });
global_hotkeys_js->bind_action("toggle_replay", [overlay](const std::string &id) { global_hotkeys_js->bind_action("toggle_replay", [overlay](const std::string &id) {
@@ -473,6 +490,14 @@ namespace gsr {
top_bar_background({0.0f, 0.0f}), top_bar_background({0.0f, 0.0f}),
close_button_widget({0.0f, 0.0f}) close_button_widget({0.0f, 0.0f})
{ {
if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
wayland_dpy = wl_display_connect(nullptr);
if(!wayland_dpy)
fprintf(stderr, "Warning: failed to connect to the wayland server\n");
} else {
wayland_dpy = nullptr;
}
gsr_icon_path = this->resources_path + "images/gpu_screen_recorder_logo.png"; gsr_icon_path = this->resources_path + "images/gpu_screen_recorder_logo.png";
key_bindings[0].key_event.code = mgl::Keyboard::Escape; key_bindings[0].key_event.code = mgl::Keyboard::Escape;
@@ -516,7 +541,7 @@ namespace gsr {
cursor_tracker = std::make_unique<CursorTrackerX11>((Display*)mgl_get_context()->connection); cursor_tracker = std::make_unique<CursorTrackerX11>((Display*)mgl_get_context()->connection);
else if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) { else if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
if(!this->gsr_info.gpu_info.card_path.empty()) if(!this->gsr_info.gpu_info.card_path.empty())
cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str()); cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str(), wayland_dpy);
if(!config.main_config.wayland_warning_shown) { if(!config.main_config.wayland_warning_shown) {
config.main_config.wayland_warning_shown = true; config.main_config.wayland_warning_shown = true;
@@ -525,8 +550,7 @@ namespace gsr {
} }
} }
// TODO: Only do this if led indicator is enabled (at startup or when changing recording/screenshot settings to enabled it) update_led_indicator_after_settings_change();
led_indicator = std::make_unique<LedIndicator>();
} }
Overlay::~Overlay() { Overlay::~Overlay() {
@@ -569,6 +593,9 @@ namespace gsr {
if(x11_dpy) if(x11_dpy)
XCloseDisplay(x11_dpy); XCloseDisplay(x11_dpy);
if(wayland_dpy)
wl_display_disconnect(wayland_dpy);
} }
void Overlay::xi_setup() { void Overlay::xi_setup() {
@@ -880,6 +907,7 @@ namespace gsr {
close_button_widget.draw(*window, mgl::vec2f(0.0f, 0.0f)); close_button_widget.draw(*window, mgl::vec2f(0.0f, 0.0f));
page_stack.draw(*window, mgl::vec2f(0.0f, 0.0f)); page_stack.draw(*window, mgl::vec2f(0.0f, 0.0f));
draw_tooltip(*window);
if(cursor_texture.is_valid()) { if(cursor_texture.is_valid()) {
cursor_sprite.set_position((window->get_mouse_position() - cursor_hotspot).to_vec2f()); cursor_sprite.set_position((window->get_mouse_position() - cursor_hotspot).to_vec2f());
@@ -1179,6 +1207,15 @@ namespace gsr {
global_hotkeys.reset(); global_hotkeys.reset();
} }
void Overlay::update_led_indicator_after_settings_change() {
if(config.record_config.record_options.use_led_indicator || config.replay_config.record_options.use_led_indicator || config.streaming_config.record_options.use_led_indicator || config.screenshot_config.use_led_indicator) {
if(!led_indicator)
led_indicator = std::make_unique<LedIndicator>();
} else {
led_indicator.reset();
}
}
void Overlay::create_frontpage_ui_components() { void Overlay::create_frontpage_ui_components() {
bg_screenshot_overlay = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height)); bg_screenshot_overlay = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height));
top_bar_background = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height*0.06f).floor()); top_bar_background = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height*0.06f).floor());
@@ -1269,12 +1306,14 @@ namespace gsr {
record_settings_page->on_config_changed = [this]() { record_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::RECORD) if(recording_status == RecordingStatus::RECORD)
show_notification("Recording settings have been modified.\nYou may need to restart recording to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD); show_notification("Recording settings have been modified.\nYou may need to restart recording to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
update_led_indicator_after_settings_change();
}; };
page_stack.push(std::move(record_settings_page)); page_stack.push(std::move(record_settings_page));
} else if(id == "pause") { } else if(id == "pause") {
toggle_pause(); toggle_pause();
} else if(id == "start") { } else if(id == "start") {
on_press_start_record(false); on_press_start_record(false, RecordForceType::NONE);
} }
}; };
button->set_item_enabled("pause", false); button->set_item_enabled("pause", false);
@@ -1294,6 +1333,8 @@ namespace gsr {
stream_settings_page->on_config_changed = [this]() { stream_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::STREAM) if(recording_status == RecordingStatus::STREAM)
show_notification("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM); show_notification("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
update_led_indicator_after_settings_change();
}; };
page_stack.push(std::move(stream_settings_page)); page_stack.push(std::move(stream_settings_page));
} else if(id == "start") { } else if(id == "start") {
@@ -1376,6 +1417,9 @@ namespace gsr {
button->set_icon_padding_scale(1.2f); button->set_icon_padding_scale(1.2f);
button->on_click = [&]() { button->on_click = [&]() {
auto screenshot_settings_page = std::make_unique<ScreenshotSettingsPage>(&gsr_info, config, &page_stack); auto screenshot_settings_page = std::make_unique<ScreenshotSettingsPage>(&gsr_info, config, &page_stack);
screenshot_settings_page->on_config_changed = [this]() {
update_led_indicator_after_settings_change();
};
page_stack.push(std::move(screenshot_settings_page)); page_stack.push(std::move(screenshot_settings_page));
}; };
front_page_ptr->add_widget(std::move(button)); front_page_ptr->add_widget(std::move(button));
@@ -1513,8 +1557,8 @@ namespace gsr {
} }
} }
void Overlay::toggle_record() { void Overlay::toggle_record(RecordForceType force_type) {
on_press_start_record(false); on_press_start_record(false, force_type);
} }
void Overlay::toggle_pause() { void Overlay::toggle_pause() {
@@ -2477,8 +2521,8 @@ namespace gsr {
return result; return result;
} }
static void add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size, const RegionSelector &region_selector) { void Overlay::add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size) {
Region region = region_selector.get_selection(); Region region = region_selector.get_selection(x11_dpy, wayland_dpy);
if(region.size.x <= 32 && region.size.y <= 32) { if(region.size.x <= 32 && region.size.y <= 32) {
region.size.x = 0; region.size.x = 0;
region.size.y = 0; region.size.y = 0;
@@ -2488,7 +2532,7 @@ namespace gsr {
args.push_back(region_str); args.push_back(region_str);
} }
static void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const RegionSelector &region_selector) { void Overlay::add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const std::string &region_area_option) {
if(record_options.video_quality == "custom") { if(record_options.video_quality == "custom") {
args.push_back("-bm"); args.push_back("-bm");
args.push_back("cbr"); args.push_back("cbr");
@@ -2499,7 +2543,7 @@ namespace gsr {
args.push_back(record_options.video_quality.c_str()); args.push_back(record_options.video_quality.c_str());
} }
if(record_options.record_area_option == "focused" || record_options.change_video_resolution) { if(region_area_option == "focused" || record_options.change_video_resolution) {
args.push_back("-s"); args.push_back("-s");
args.push_back(region); args.push_back(region);
} }
@@ -2514,8 +2558,13 @@ namespace gsr {
args.push_back("yes"); args.push_back("yes");
} }
if(record_options.record_area_option == "region") if(record_options.low_power_mode) {
add_region_command(args, region_str, region_str_size, region_selector); args.push_back("-low-power");
args.push_back("yes");
}
if(region_area_option == "region")
add_region_command(args, region_str, region_str_size);
} }
static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) { static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
@@ -2742,6 +2791,9 @@ namespace gsr {
capture_source_arg += ";width=" + std::to_string(webcam_size.x) + "%"; capture_source_arg += ";width=" + std::to_string(webcam_size.x) + "%";
capture_source_arg += ";height=" + std::to_string(webcam_size.y) + "%"; capture_source_arg += ";height=" + std::to_string(webcam_size.y) + "%";
capture_source_arg += ";pixfmt=" + record_options.webcam_video_format; capture_source_arg += ";pixfmt=" + record_options.webcam_video_format;
capture_source_arg += ";camera_width=" + std::to_string(record_options.webcam_camera_width);
capture_source_arg += ";camera_height=" + std::to_string(record_options.webcam_camera_height);
capture_source_arg += ";camera_fps=" + std::to_string(record_options.webcam_camera_fps);
if(record_options.webcam_flip_horizontally) if(record_options.webcam_flip_horizontally)
capture_source_arg += ";hflip=true"; capture_source_arg += ";hflip=true";
} }
@@ -2868,7 +2920,7 @@ namespace gsr {
} }
char region_str[128]; char region_str[128];
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector); add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), config.replay_config.record_options.record_area_option);
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
args.push_back("-ro"); args.push_back("-ro");
@@ -2917,7 +2969,7 @@ namespace gsr {
return true; return true;
} }
void Overlay::on_press_start_record(bool finished_selection) { void Overlay::on_press_start_record(bool finished_selection, RecordForceType force_type) {
if(region_selector.is_started() || window_selector.is_started()) if(region_selector.is_started() || window_selector.is_started())
return; return;
@@ -3019,27 +3071,40 @@ namespace gsr {
update_upause_status(); update_upause_status();
std::string record_area_option;
switch(force_type) {
case RecordForceType::NONE:
record_area_option = config.record_config.record_options.record_area_option;
break;
case RecordForceType::REGION:
record_area_option = "region";
break;
case RecordForceType::WINDOW:
record_area_option = gsr_info.system_info.display_server == DisplayServer::X11 ? "window" : "portal";
break;
}
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
recording_capture_target = get_capture_target(config.record_config.record_options.record_area_option, capture_options); recording_capture_target = get_capture_target(record_area_option, capture_options);
if(!validate_capture_target(config.record_config.record_options.record_area_option, capture_options)) { if(!validate_capture_target(record_area_option, capture_options)) {
char err_msg[256]; char err_msg[256];
snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str()); snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD, nullptr, NotificationLevel::ERROR); show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD, nullptr, NotificationLevel::ERROR);
return; return;
} }
if(config.record_config.record_options.record_area_option == "region" && !finished_selection) { if(record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [this]() { on_region_selected = [this, force_type]() {
on_press_start_record(true); on_press_start_record(true, force_type);
}; };
return; return;
} }
if(config.record_config.record_options.record_area_option == "window" && !finished_selection) { if(record_area_option == "window" && !finished_selection) {
start_window_capture = true; start_window_capture = true;
on_window_selected = [this]() { on_window_selected = [this, force_type]() {
on_press_start_record(true); on_press_start_record(true, force_type);
}; };
return; return;
} }
@@ -3059,10 +3124,10 @@ namespace gsr {
char size[64]; char size[64];
size[0] = '\0'; size[0] = '\0';
if(config.record_config.record_options.record_area_option == "focused") if(record_area_option == "focused")
snprintf(size, sizeof(size), "%dx%d", (int)config.record_config.record_options.record_area_width, (int)config.record_config.record_options.record_area_height); snprintf(size, sizeof(size), "%dx%d", (int)config.record_config.record_options.record_area_width, (int)config.record_config.record_options.record_area_height);
if(config.record_config.record_options.record_area_option != "focused" && config.record_config.record_options.change_video_resolution) if(record_area_option != "focused" && config.record_config.record_options.change_video_resolution)
snprintf(size, sizeof(size), "%dx%d", (int)config.record_config.record_options.video_width, (int)config.record_config.record_options.video_height); snprintf(size, sizeof(size), "%dx%d", (int)config.record_config.record_options.video_width, (int)config.record_config.record_options.video_height);
const std::string capture_source_arg = compose_capture_source_arg(recording_capture_target, config.record_config.record_options); const std::string capture_source_arg = compose_capture_source_arg(recording_capture_target, config.record_config.record_options);
@@ -3082,7 +3147,7 @@ namespace gsr {
}; };
char region_str[128]; char region_str[128];
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector); add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), record_area_option);
args.push_back(nullptr); args.push_back(nullptr);
@@ -3115,7 +3180,7 @@ namespace gsr {
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
} }
if(config.record_config.record_options.record_area_option == "portal") if(record_area_option == "portal")
hide_ui = true; hide_ui = true;
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has // TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
@@ -3125,6 +3190,7 @@ namespace gsr {
static std::string streaming_get_url(const Config &config) { static std::string streaming_get_url(const Config &config) {
std::string url; std::string url;
fprintf(stderr, "streaming service: %s\n", config.streaming_config.streaming_service.c_str());
if(config.streaming_config.streaming_service == "twitch") { if(config.streaming_config.streaming_service == "twitch") {
url += "rtmp://live.twitch.tv/app/"; url += "rtmp://live.twitch.tv/app/";
url += config.streaming_config.twitch.stream_key; url += config.streaming_config.twitch.stream_key;
@@ -3134,6 +3200,12 @@ namespace gsr {
} else if(config.streaming_config.streaming_service == "rumble") { } else if(config.streaming_config.streaming_service == "rumble") {
url += "rtmp://rtmp.rumble.com/live/"; url += "rtmp://rtmp.rumble.com/live/";
url += config.streaming_config.rumble.stream_key; url += config.streaming_config.rumble.stream_key;
} else if(config.streaming_config.streaming_service == "kick") {
url += config.streaming_config.kick.stream_url;
if(!url.empty() && url.back() != '/')
url += "/";
url += "app/";
url += config.streaming_config.kick.stream_key;
} else if(config.streaming_config.streaming_service == "custom") { } else if(config.streaming_config.streaming_service == "custom") {
url = config.streaming_config.custom.url; url = config.streaming_config.custom.url;
if(url.size() >= 7 && strncmp(url.c_str(), "rtmp://", 7) == 0) if(url.size() >= 7 && strncmp(url.c_str(), "rtmp://", 7) == 0)
@@ -3246,6 +3318,10 @@ namespace gsr {
choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder); choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
const std::string url = streaming_get_url(config); const std::string url = streaming_get_url(config);
if(config.streaming_config.streaming_service == "rumble" || config.streaming_config.streaming_service == "kick") {
fprintf(stderr, "Info: forcing video codec to h264 as rumble/kick supports only h264\n");
video_codec = "h264";
}
char size[64]; char size[64];
size[0] = '\0'; size[0] = '\0';
@@ -3272,7 +3348,7 @@ namespace gsr {
}; };
char region_str[128]; char region_str[128];
add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector); add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), config.streaming_config.record_options.record_area_option);
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) { if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
args.push_back("-ro"); args.push_back("-ro");
@@ -3404,7 +3480,7 @@ namespace gsr {
char region_str[128]; char region_str[128];
if(record_area_option == "region") if(record_area_option == "region")
add_region_command(args, region_str, sizeof(region_str), region_selector); add_region_command(args, region_str, sizeof(region_str));
args.push_back(nullptr); args.push_back(nullptr);

View File

@@ -166,6 +166,62 @@ namespace gsr {
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); 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);
} }
static const Monitor* get_monitor_by_region_center(const std::vector<Monitor> &monitors, Region region) {
const mgl::vec2i center = {region.pos.x + region.size.x / 2, region.pos.y + region.size.y / 2};
for(const Monitor &monitor : monitors) {
if(center.x >= monitor.position.x && center.x <= monitor.position.x + monitor.size.x
&& center.y >= monitor.position.y && center.y <= monitor.position.y + monitor.size.y)
{
return &monitor;
}
}
return nullptr;
}
// Name is the x11 name. TODO: verify if this works on all wayland compositors
static const Monitor* get_wayland_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) {
for(const Monitor &monitor : monitors) {
if(monitor.name == name)
return &monitor;
}
return nullptr;
}
static mgl::vec2d to_vec2d(mgl::vec2i v) {
return { (double)v.x, (double)v.y };
}
static Region x11_region_to_wayland_region(Display *dpy, struct wl_display *wayland_dpy, Region x11_region) {
const std::vector<Monitor> x11_monitors = get_monitors(dpy);
const Monitor *x11_selected_monitor = get_monitor_by_region_center(x11_monitors, x11_region);
if(!x11_selected_monitor) {
fprintf(stderr, "Warning: RegionSelector: failed to get x11 monitor\n");
return x11_region;
}
const std::vector<Monitor> wayland_monitors = get_monitors_wayland(wayland_dpy);
const Monitor *wayland_monitor = get_wayland_monitor_by_name(wayland_monitors, x11_selected_monitor->name);
if(!wayland_monitor) {
fprintf(stderr, "Warning: RegionSelector: failed to get wayland monitor\n");
return x11_region;
}
const mgl::vec2d region_relative_pos = {
(double)(x11_region.pos.x - x11_selected_monitor->position.x) / (double)x11_selected_monitor->size.x,
(double)(x11_region.pos.y - x11_selected_monitor->position.y) / (double)x11_selected_monitor->size.y,
};
const mgl::vec2d region_relative_size = {
(double)x11_region.size.x / (double)x11_selected_monitor->size.x,
(double)x11_region.size.y / (double)x11_selected_monitor->size.y,
};
return Region {
wayland_monitor->position + (region_relative_pos * to_vec2d(wayland_monitor->size)).to_vec2i(),
(region_relative_size * to_vec2d(wayland_monitor->size)).to_vec2i(),
};
}
RegionSelector::RegionSelector() { RegionSelector::RegionSelector() {
} }
@@ -385,8 +441,11 @@ namespace gsr {
return result; return result;
} }
Region RegionSelector::get_selection() const { Region RegionSelector::get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const {
return region; Region returned_region = region;
if(is_wayland && x11_dpy && wayland_dpy)
returned_region = x11_region_to_wayland_region(x11_dpy, wayland_dpy, returned_region);
return returned_region;
} }
void RegionSelector::on_button_press(const void *de) { void RegionSelector::on_button_press(const void *de) {

View File

@@ -129,6 +129,15 @@ namespace gsr {
if(!theme->unmasked_texture.load_from_file((resources_path + "images/unmasked.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) if(!theme->unmasked_texture.load_from_file((resources_path + "images/unmasked.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error; goto error;
if(!theme->warning_texture.load_from_file((resources_path + "images/warning.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->question_mark_texture.load_from_file((resources_path + "images/question_mark.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->info_texture.load_from_file((resources_path + "images/info.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error; goto error;

View File

@@ -8,6 +8,9 @@
#include <X11/extensions/shapeconst.h> #include <X11/extensions/shapeconst.h>
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
#include <wayland-client.h>
#include "xdg-output-unstable-v1-client-protocol.h"
#include <mglpp/system/Utf8.hpp> #include <mglpp/system/Utf8.hpp>
extern "C" { extern "C" {
@@ -23,6 +26,209 @@ extern "C" {
#define MAX_PROPERTY_VALUE_LEN 4096 #define MAX_PROPERTY_VALUE_LEN 4096
namespace gsr { namespace gsr {
struct WaylandOutput {
uint32_t wl_name;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
mgl::vec2i pos;
mgl::vec2i size;
int32_t transform;
std::string name;
};
struct Wayland {
std::vector<WaylandOutput> outputs;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
};
static WaylandOutput* get_wayland_monitor_by_output(Wayland &wayland, struct wl_output *output) {
for(WaylandOutput &monitor : wayland.outputs) {
if(monitor.output == output)
return &monitor;
}
return nullptr;
}
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
(void)wl_output;
(void)phys_width;
(void)phys_height;
(void)subpixel;
(void)make;
(void)model;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->pos.x = x;
monitor->pos.y = y;
monitor->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
(void)wl_output;
(void)flags;
(void)refresh;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->size.x = width;
monitor->size.y = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
(void)data;
(void)wl_output;
}
static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
(void)data;
(void)wl_output;
(void)factor;
}
static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
(void)wl_output;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->name = name;
}
static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
(void)data;
(void)wl_output;
(void)description;
}
static const struct wl_output_listener output_listener = {
output_handle_geometry,
output_handle_mode,
output_handle_done,
output_handle_scale,
output_handle_name,
output_handle_description,
};
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
Wayland *wayland = (Wayland*)data;
if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n");
return;
}
struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4);
wayland->outputs.push_back(
WaylandOutput{
name,
output,
nullptr,
mgl::vec2i{0, 0},
mgl::vec2i{0, 0},
0,
""
});
wl_output_add_listener(output, &output_listener, wayland);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n");
return;
}
if(wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(wayland->xdg_output_manager);
wayland->xdg_output_manager = NULL;
}
wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
registry_add_object,
registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->pos.x = x;
monitor->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)xdg_output;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->size.x = width;
monitor->size.y = height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_logical_position,
xdg_output_handle_logical_size,
xdg_output_handle_done,
xdg_output_handle_name,
xdg_output_handle_description,
};
static const int transform_90 = 1;
static const int transform_270 = 3;
static void transform_monitors(Wayland &wayland) {
for(WaylandOutput &output : wayland.outputs) {
if(output.transform == transform_90 || output.transform == transform_270)
std::swap(output.size.x, output.size.y);
}
}
static void set_monitor_outputs_from_xdg_output(Wayland &wayland, struct wl_display *dpy) {
if(!wayland.xdg_output_manager) {
fprintf(stderr, "Warning: WindowUtils::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n");
return;
}
for(WaylandOutput &monitor : wayland.outputs) {
monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(wayland.xdg_output_manager, monitor.output);
zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor);
}
// Fetch xdg_output
wl_display_roundtrip(dpy);
}
static unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) { static unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) {
Atom ret_property_type = None; Atom ret_property_type = None;
int ret_format = 0; int ret_format = 0;
@@ -519,6 +725,47 @@ namespace gsr {
return monitors; return monitors;
} }
std::vector<Monitor> get_monitors_wayland(struct wl_display *dpy) {
Wayland wayland;
struct wl_registry *registry = wl_display_get_registry(dpy);
wl_registry_add_listener(registry, &registry_listener, &wayland);
// Fetch globals
wl_display_roundtrip(dpy);
// Fetch wl_output
wl_display_roundtrip(dpy);
transform_monitors(wayland);
set_monitor_outputs_from_xdg_output(wayland, dpy);
std::vector<Monitor> monitors;
for(WaylandOutput &output : wayland.outputs) {
monitors.push_back(Monitor{output.pos, output.size, std::move(output.name)});
if(output.output) {
wl_output_destroy(output.output);
output.output = nullptr;
}
if(output.xdg_output) {
zxdg_output_v1_destroy(output.xdg_output);
output.xdg_output = nullptr;
}
}
wayland.outputs.clear();
if(wayland.xdg_output_manager) {
zxdg_output_manager_v1_destroy(wayland.xdg_output_manager);
wayland.xdg_output_manager = nullptr;
}
wl_registry_destroy(registry);
return monitors;
}
static bool device_is_mouse(const XIDeviceInfo *dev) { static bool device_is_mouse(const XIDeviceInfo *dev) {
for(int i = 0; i < dev->num_classes; ++i) { for(int i = 0; i < dev->num_classes; ++i) {
if(dev->classes[i]->type == XIMasterPointer || dev->classes[i]->type == XISlavePointer) if(dev->classes[i]->type == XIMasterPointer || dev->classes[i]->type == XISlavePointer)

View File

@@ -40,6 +40,8 @@ namespace gsr {
if(!visible) if(!visible)
return true; return true;
handle_tooltip_event(event, position + offset, get_size());
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
const bool clicked_inside = mgl::FloatRect(position + offset, get_size()).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y }); const bool clicked_inside = mgl::FloatRect(position + offset, get_size()).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y });
if(clicked_inside) { if(clicked_inside) {

View File

@@ -83,7 +83,14 @@ namespace gsr {
draw_unselected(window, draw_pos); draw_unselected(window, draw_pos);
} }
void ComboBox::add_item(const std::string &text, const std::string &id) { void ComboBox::add_item(const std::string &text, const std::string &id, bool allow_duplicate) {
if(!allow_duplicate) {
for(const auto &item : items) {
if(item.id == id)
return;
}
}
items.push_back({mgl::Text(text, *font), id, {0.0f, 0.0f}}); items.push_back({mgl::Text(text, *font), id, {0.0f, 0.0f}});
items.back().text.set_max_width(font->get_character_size() * 20); // TODO: Make a proper solution items.back().text.set_max_width(font->get_character_size() * 20); // TODO: Make a proper solution
//items.back().text.set_max_rows(1); //items.back().text.set_max_rows(1);

View File

@@ -304,6 +304,36 @@ namespace gsr {
return list; return list;
} }
std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_window_region_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop recording a region:", get_color_theme().text_color));
auto start_stop_recording_region_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
start_stop_recording_region_button_ptr = start_stop_recording_region_button.get();
list->add_widget(std::move(start_stop_recording_region_button));
char str[128];
if(gsr_info->system_info.display_server == DisplayServer::X11)
snprintf(str, sizeof(str), "Start/stop recording a window:");
else
snprintf(str, sizeof(str), "Start/stop recording with desktop portal:");
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
auto start_stop_recording_window_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
start_stop_recording_window_button_ptr = start_stop_recording_window_button.get();
list->add_widget(std::move(start_stop_recording_window_button));
start_stop_recording_region_button_ptr->on_click = [this] {
configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP_REGION);
};
start_stop_recording_window_button_ptr->on_click = [this] {
configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP_WINDOW);
};
return list;
}
std::unique_ptr<List> GlobalSettingsPage::create_stream_hotkey_options() { std::unique_ptr<List> GlobalSettingsPage::create_stream_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
@@ -375,17 +405,9 @@ namespace gsr {
auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
clear_hotkeys_button->on_click = [this] { clear_hotkeys_button->on_click = [this] {
config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; for_each_config_hotkey([&](ConfigHotkey *config_hotkey_item) {
config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; *config_hotkey_item = {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.replay_config.save_1_min_hotkey = {mgl::Keyboard::Unknown, 0};
config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0};
config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0};
config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0};
config.screenshot_config.take_screenshot_window_hotkey = {mgl::Keyboard::Unknown, 0};
config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
load_hotkeys(); load_hotkeys();
overlay->rebind_all_keyboard_hotkeys(); overlay->rebind_all_keyboard_hotkeys();
}; };
@@ -424,6 +446,7 @@ namespace gsr {
list_ptr->add_widget(create_replay_hotkey_options()); list_ptr->add_widget(create_replay_hotkey_options());
list_ptr->add_widget(create_replay_partial_save_hotkey_options()); list_ptr->add_widget(create_replay_partial_save_hotkey_options());
list_ptr->add_widget(create_record_hotkey_options()); list_ptr->add_widget(create_record_hotkey_options());
list_ptr->add_widget(create_record_hotkey_window_region_options());
list_ptr->add_widget(create_stream_hotkey_options()); list_ptr->add_widget(create_stream_hotkey_options());
list_ptr->add_widget(create_screenshot_hotkey_options()); list_ptr->add_widget(create_screenshot_hotkey_options());
list_ptr->add_widget(create_screenshot_region_hotkey_options()); list_ptr->add_widget(create_screenshot_region_hotkey_options());
@@ -595,6 +618,8 @@ namespace gsr {
start_stop_recording_button_ptr->set_text(config.record_config.start_stop_hotkey.to_string()); 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()); pause_unpause_recording_button_ptr->set_text(config.record_config.pause_unpause_hotkey.to_string());
start_stop_recording_region_button_ptr->set_text(config.record_config.start_stop_region_hotkey.to_string());
start_stop_recording_window_button_ptr->set_text(config.record_config.start_stop_window_hotkey.to_string());
start_stop_streaming_button_ptr->set_text(config.streaming_config.start_stop_hotkey.to_string()); start_stop_streaming_button_ptr->set_text(config.streaming_config.start_stop_hotkey.to_string());
@@ -679,6 +704,10 @@ namespace gsr {
return start_stop_recording_button_ptr; return start_stop_recording_button_ptr;
case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE:
return pause_unpause_recording_button_ptr; return pause_unpause_recording_button_ptr;
case ConfigureHotkeyType::RECORD_START_STOP_REGION:
return start_stop_recording_region_button_ptr;
case ConfigureHotkeyType::RECORD_START_STOP_WINDOW:
return start_stop_recording_window_button_ptr;
case ConfigureHotkeyType::STREAM_START_STOP: case ConfigureHotkeyType::STREAM_START_STOP:
return start_stop_streaming_button_ptr; return start_stop_streaming_button_ptr;
case ConfigureHotkeyType::TAKE_SCREENSHOT: case ConfigureHotkeyType::TAKE_SCREENSHOT:
@@ -709,6 +738,10 @@ namespace gsr {
return &config.record_config.start_stop_hotkey; return &config.record_config.start_stop_hotkey;
case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE:
return &config.record_config.pause_unpause_hotkey; return &config.record_config.pause_unpause_hotkey;
case ConfigureHotkeyType::RECORD_START_STOP_REGION:
return &config.record_config.start_stop_region_hotkey;
case ConfigureHotkeyType::RECORD_START_STOP_WINDOW:
return &config.record_config.start_stop_window_hotkey;
case ConfigureHotkeyType::STREAM_START_STOP: case ConfigureHotkeyType::STREAM_START_STOP:
return &config.streaming_config.start_stop_hotkey; return &config.streaming_config.start_stop_hotkey;
case ConfigureHotkeyType::TAKE_SCREENSHOT: case ConfigureHotkeyType::TAKE_SCREENSHOT:
@@ -727,8 +760,12 @@ namespace gsr {
ConfigHotkey *config_hotkeys[] = { ConfigHotkey *config_hotkeys[] = {
&config.replay_config.start_stop_hotkey, &config.replay_config.start_stop_hotkey,
&config.replay_config.save_hotkey, &config.replay_config.save_hotkey,
&config.replay_config.save_1_min_hotkey,
&config.replay_config.save_10_min_hotkey,
&config.record_config.start_stop_hotkey, &config.record_config.start_stop_hotkey,
&config.record_config.pause_unpause_hotkey, &config.record_config.pause_unpause_hotkey,
&config.record_config.start_stop_region_hotkey,
&config.record_config.start_stop_window_hotkey,
&config.streaming_config.start_stop_hotkey, &config.streaming_config.start_stop_hotkey,
&config.screenshot_config.take_screenshot_hotkey, &config.screenshot_config.take_screenshot_hotkey,
&config.screenshot_config.take_screenshot_region_hotkey, &config.screenshot_config.take_screenshot_region_hotkey,
@@ -772,6 +809,15 @@ namespace gsr {
case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE:
hotkey_configure_action_name = "Pause/unpause recording"; hotkey_configure_action_name = "Pause/unpause recording";
break; break;
case ConfigureHotkeyType::RECORD_START_STOP_REGION:
hotkey_configure_action_name = "Start/stop recording a region";
break;
case ConfigureHotkeyType::RECORD_START_STOP_WINDOW:
if(gsr_info->system_info.display_server == DisplayServer::X11)
hotkey_configure_action_name = "Start/stop recording a window";
else
hotkey_configure_action_name = "Start/stop recording with desktop portal";
break;
case ConfigureHotkeyType::STREAM_START_STOP: case ConfigureHotkeyType::STREAM_START_STOP:
hotkey_configure_action_name = "Start/stop streaming"; hotkey_configure_action_name = "Start/stop streaming";
break; break;

View File

@@ -2,6 +2,8 @@
#include "../../include/gui/Utils.hpp" #include "../../include/gui/Utils.hpp"
#include <mglpp/window/Window.hpp> #include <mglpp/window/Window.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/system/FloatRect.hpp>
#include <mglpp/graphics/Texture.hpp> #include <mglpp/graphics/Texture.hpp>
namespace gsr { namespace gsr {
@@ -19,8 +21,15 @@ namespace gsr {
if(!visible) if(!visible)
return; return;
const mgl::vec2f draw_pos = (position + offset).floor();
if(on_mouse_move) {
const bool mouse_inside = mgl::FloatRect(draw_pos, get_size()).contains(window.get_mouse_position().to_vec2f());
on_mouse_move(mouse_inside);
}
sprite.set_size(get_size()); sprite.set_size(get_size());
sprite.set_position((position + offset).floor()); sprite.set_position(draw_pos);
window.draw(sprite); window.draw(sprite);
} }

View File

@@ -357,6 +357,8 @@ namespace gsr {
} }
void ScreenshotSettingsPage::save() { void ScreenshotSettingsPage::save() {
Config prev_config = config;
config.screenshot_config.record_area_option = record_area_box_ptr->get_selected_id(); 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_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.image_height = atoi(image_height_entry_ptr->get_text().c_str());
@@ -390,5 +392,8 @@ namespace gsr {
} }
save_config(config); save_config(config);
if(on_config_changed && config != prev_config)
on_config_changed();
} }
} }

View File

@@ -5,6 +5,7 @@
#include "../../include/gui/FileChooser.hpp" #include "../../include/gui/FileChooser.hpp"
#include "../../include/gui/Subsection.hpp" #include "../../include/gui/Subsection.hpp"
#include "../../include/gui/CustomRendererWidget.hpp" #include "../../include/gui/CustomRendererWidget.hpp"
#include "../../include/gui/Image.hpp"
#include "../../include/gui/Utils.hpp" #include "../../include/gui/Utils.hpp"
#include "../../include/Theme.hpp" #include "../../include/Theme.hpp"
#include "../../include/GsrInfo.hpp" #include "../../include/GsrInfo.hpp"
@@ -205,6 +206,7 @@ namespace gsr {
webcam_sources_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { webcam_sources_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
selected_camera = std::nullopt; selected_camera = std::nullopt;
selected_camera_setup = std::nullopt;
webcam_video_format_box_ptr->clear_items(); webcam_video_format_box_ptr->clear_items();
if(id == "") { if(id == "") {
webcam_body_list_ptr->set_visible(false); webcam_body_list_ptr->set_visible(false);
@@ -220,20 +222,54 @@ namespace gsr {
webcam_body_list_ptr->set_visible(true); webcam_body_list_ptr->set_visible(true);
webcam_video_format_box_ptr->add_item("Auto (recommended)", "auto"); webcam_video_format_box_ptr->add_item("Auto (recommended)", "auto");
if(it->supported_pixel_formats.yuyv) if(!it->yuyv_setups.empty())
webcam_video_format_box_ptr->add_item("YUYV", "yuyv"); webcam_video_format_box_ptr->add_item("YUYV", "yuyv");
if(it->supported_pixel_formats.mjpeg) if(!it->mjpeg_setups.empty())
webcam_video_format_box_ptr->add_item("Motion-JPEG", "mjpeg"); webcam_video_format_box_ptr->add_item("Motion-JPEG", "mjpeg");
webcam_video_format_box_ptr->set_selected_item("auto");
webcam_video_format_box_ptr->set_selected_item(get_current_record_options().webcam_video_format); webcam_video_format_box_ptr->set_selected_item(get_current_record_options().webcam_video_format);
selected_camera = *it; selected_camera = *it;
// TODO: Set from config
if(webcam_video_format_box_ptr->get_selected_id() == "yuyv" && !it->yuyv_setups.empty())
selected_camera_setup = selected_camera->yuyv_setups.front();
else if(webcam_video_format_box_ptr->get_selected_id() == "mjpeg" && !it->mjpeg_setups.empty())
selected_camera_setup = selected_camera->mjpeg_setups.front();
else if(webcam_video_format_box_ptr->get_selected_id() == "auto") {
if(!it->mjpeg_setups.empty())
selected_camera_setup = selected_camera->mjpeg_setups.front();
else if(!it->yuyv_setups.empty())
selected_camera_setup = selected_camera->yuyv_setups.front();
}
}; };
ll->add_widget(std::move(combobox)); ll->add_widget(std::move(combobox));
return ll; return ll;
} }
std::unique_ptr<List> SettingsPage::create_webcam_video_setups() {
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video setup:", get_color_theme().text_color));
auto combobox = std::make_unique<ComboBox>(&get_theme().body_font);
webcam_video_setup_box_ptr = combobox.get();
ll->add_widget(std::move(combobox));
return ll;
}
static std::vector<GsrCameraSetup> sort_camera_setup(const std::vector<GsrCameraSetup> &setups) {
auto result = setups;
std::sort(result.begin(), result.end(), [](const auto &a, const auto &b) {
const uint64_t score_a = (uint64_t)a.resolution.x * (uint64_t)a.resolution.y * (uint64_t)a.fps;
const uint64_t score_b = (uint64_t)b.resolution.x * (uint64_t)b.resolution.y * (uint64_t)b.fps;
return score_a > score_b;
});
return result;
}
std::unique_ptr<List> SettingsPage::create_webcam_video_format() { std::unique_ptr<List> SettingsPage::create_webcam_video_format() {
auto ll = std::make_unique<List>(List::Orientation::VERTICAL); auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video format:", get_color_theme().text_color)); ll->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video format:", get_color_theme().text_color));
@@ -243,12 +279,52 @@ namespace gsr {
webcam_video_format_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { webcam_video_format_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
get_current_record_options().webcam_video_format = id; get_current_record_options().webcam_video_format = id;
auto it = std::find_if(capture_options.cameras.begin(), capture_options.cameras.end(), [&](const GsrCamera &camera) {
return camera.path == webcam_sources_box_ptr->get_selected_id();
});
if(it == capture_options.cameras.end())
return;
webcam_video_setup_box_ptr->clear_items();
if(id == "yuyv") {
const auto setups = sort_camera_setup(it->yuyv_setups);
for(const auto &setup : setups) {
char setup_str[256];
snprintf(setup_str, sizeof(setup_str), "%dx%d@%dhz", setup.resolution.x, setup.resolution.y, setup.fps);
webcam_video_setup_box_ptr->add_item(setup_str, setup_str, false);
}
} else if(id == "mjpeg") {
const auto setups = sort_camera_setup(it->mjpeg_setups);
for(const auto &setup : setups) {
char setup_str[256];
snprintf(setup_str, sizeof(setup_str), "%dx%d@%dhz", setup.resolution.x, setup.resolution.y, setup.fps);
webcam_video_setup_box_ptr->add_item(setup_str, setup_str, false);
}
} else if(id == "auto") {
auto setups = it->yuyv_setups;
setups.insert(setups.end(), it->mjpeg_setups.begin(), it->mjpeg_setups.end());
setups = sort_camera_setup(setups);
for(const auto &setup : setups) {
char setup_str[256];
snprintf(setup_str, sizeof(setup_str), "%dx%d@%dhz", setup.resolution.x, setup.resolution.y, setup.fps);
webcam_video_setup_box_ptr->add_item(setup_str, setup_str, false);
}
}
}; };
ll->add_widget(std::move(combobox)); ll->add_widget(std::move(combobox));
return ll; return ll;
} }
std::unique_ptr<List> SettingsPage::create_webcam_video_setup_list() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL);
list->add_widget(create_webcam_video_format());
list->add_widget(create_webcam_video_setups());
return list;
}
std::unique_ptr<Widget> SettingsPage::create_webcam_location_widget() { std::unique_ptr<Widget> SettingsPage::create_webcam_location_widget() {
const float camera_screen_width = std::min(400.0f, (float)settings_scrollable_page_ptr->get_inner_size().x * 0.90f); const float camera_screen_width = std::min(400.0f, (float)settings_scrollable_page_ptr->get_inner_size().x * 0.90f);
camera_screen_size = mgl::vec2f(camera_screen_width, camera_screen_width * 0.5625); camera_screen_size = mgl::vec2f(camera_screen_width, camera_screen_width * 0.5625);
@@ -261,13 +337,13 @@ namespace gsr {
auto camera_location_widget = std::make_unique<CustomRendererWidget>(camera_screen_size); auto camera_location_widget = std::make_unique<CustomRendererWidget>(camera_screen_size);
camera_location_widget->draw_handler = [this, screen_border_size, screen_border](mgl::Window &window, mgl::vec2f pos, mgl::vec2f size) { camera_location_widget->draw_handler = [this, screen_border_size, screen_border](mgl::Window &window, mgl::vec2f pos, mgl::vec2f size) {
if(!selected_camera.has_value()) if(!selected_camera.has_value() || !selected_camera_setup.has_value())
return; return;
pos = pos.floor(); pos = pos.floor();
size = size.floor(); size = size.floor();
const mgl::vec2i mouse_pos = window.get_mouse_position(); const mgl::vec2i mouse_pos = window.get_mouse_position();
const mgl::vec2f webcam_box_min_size = clamp_keep_aspect_ratio(selected_camera->size.to_vec2f(), screen_inner_size * 0.2f); const mgl::vec2f webcam_box_min_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), screen_inner_size * 0.2f);
if(moving_webcam_box) { if(moving_webcam_box) {
webcam_box_pos = mouse_pos.to_vec2f() - screen_border_size - webcam_box_grab_offset - pos; webcam_box_pos = mouse_pos.to_vec2f() - screen_border_size - webcam_box_grab_offset - pos;
@@ -276,7 +352,7 @@ namespace gsr {
webcam_box_size = webcam_box_size_resize_start + mouse_diff; webcam_box_size = webcam_box_size_resize_start + mouse_diff;
} }
webcam_box_size = clamp_keep_aspect_ratio(selected_camera->size.to_vec2f(), webcam_box_size); webcam_box_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), webcam_box_size);
if(webcam_box_pos.x < 0.0f) if(webcam_box_pos.x < 0.0f)
webcam_box_pos.x = 0.0f; webcam_box_pos.x = 0.0f;
@@ -300,7 +376,7 @@ namespace gsr {
else if(webcam_box_pos.y + webcam_box_size.y > screen_inner_size.y) else if(webcam_box_pos.y + webcam_box_size.y > screen_inner_size.y)
webcam_box_size.y = screen_inner_size.y - webcam_box_pos.y; webcam_box_size.y = screen_inner_size.y - webcam_box_pos.y;
webcam_box_size = clamp_keep_aspect_ratio(selected_camera->size.to_vec2f(), webcam_box_size); webcam_box_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), webcam_box_size);
{ {
draw_rectangle_outline(window, pos, size, mgl::Color(255, 0, 0, 255), screen_border); draw_rectangle_outline(window, pos, size, mgl::Color(255, 0, 0, 255), screen_border);
@@ -310,7 +386,7 @@ namespace gsr {
} }
{ {
webcam_box_drawn_size = clamp_keep_aspect_ratio(selected_camera->size.to_vec2f(), webcam_box_size); webcam_box_drawn_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), webcam_box_size);
webcam_box_drawn_pos = (pos + screen_border_size + webcam_box_pos).floor(); webcam_box_drawn_pos = (pos + screen_border_size + webcam_box_pos).floor();
draw_rectangle_outline(window, webcam_box_drawn_pos, webcam_box_drawn_size, mgl::Color(0, 255, 0, 255), screen_border); draw_rectangle_outline(window, webcam_box_drawn_pos, webcam_box_drawn_size, mgl::Color(0, 255, 0, 255), screen_border);
@@ -391,7 +467,7 @@ namespace gsr {
body_list->add_widget(create_webcam_location_widget()); body_list->add_widget(create_webcam_location_widget());
body_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "* Right click in the bottom right corner to resize the webcam", get_color_theme().text_color)); body_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "* Right click in the bottom right corner to resize the webcam", get_color_theme().text_color));
body_list->add_widget(create_flip_camera_checkbox()); body_list->add_widget(create_flip_camera_checkbox());
body_list->add_widget(create_webcam_video_format()); body_list->add_widget(create_webcam_video_setup_list());
return body_list; return body_list;
} }
@@ -446,8 +522,9 @@ namespace gsr {
auto remove_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 0)); auto remove_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 0));
remove_audio_track_button->set_icon(&get_theme().trash_texture); remove_audio_track_button->set_icon(&get_theme().trash_texture);
remove_audio_track_button->set_icon_padding_scale(0.75f); remove_audio_track_button->set_icon_padding_scale(0.75f);
remove_audio_track_button->on_click = [audio_input_list_ptr, audio_device_list_ptr]() { remove_audio_track_button->on_click = [this, audio_input_list_ptr, audio_device_list_ptr]() {
audio_input_list_ptr->remove_widget(audio_device_list_ptr); audio_input_list_ptr->remove_widget(audio_device_list_ptr);
update_application_audio_warning_visibility();
}; };
return remove_audio_track_button; return remove_audio_track_button;
} }
@@ -470,11 +547,48 @@ namespace gsr {
return button; return button;
} }
void SettingsPage::update_application_audio_warning_visibility() {
audio_track_section_list_ptr->for_each_child_widget([](std::unique_ptr<Widget> &child_widget) {
Subsection *audio_subsection = dynamic_cast<Subsection*>(child_widget.get());
List *audio_track_section_items_list_ptr = dynamic_cast<List*>(audio_subsection->get_inner_widget());
List *audio_input_list_ptr = dynamic_cast<List*>(audio_track_section_items_list_ptr->get_child_widget_by_index(2));
CheckBox *application_audio_invert_checkbox_ptr = dynamic_cast<CheckBox*>(audio_track_section_items_list_ptr->get_child_widget_by_index(3));
List *application_audio_warning_list_ptr = dynamic_cast<List*>(audio_track_section_items_list_ptr->get_child_widget_by_index(4));
int num_output_devices = 0;
int num_application_audio = 0;
audio_input_list_ptr->for_each_child_widget([&num_output_devices, &num_application_audio](std::unique_ptr<Widget> &child_widget){
List *audio_track_line = dynamic_cast<List*>(child_widget.get());
const AudioTrackType audio_track_type = (AudioTrackType)(uintptr_t)audio_track_line->userdata;
switch(audio_track_type) {
case AudioTrackType::DEVICE: {
Label *label = dynamic_cast<Label*>(audio_track_line->get_child_widget_by_index(0));
const bool is_output_device = starts_with(label->get_text().c_str(), "Output device");
if(is_output_device)
num_output_devices++;
break;
}
case AudioTrackType::APPLICATION:
case AudioTrackType::APPLICATION_CUSTOM: {
num_application_audio++;
break;
}
}
return true;
});
application_audio_warning_list_ptr->set_visible(num_output_devices > 0 && (num_application_audio > 0 || application_audio_invert_checkbox_ptr->is_checked()));
return true;
});
}
std::unique_ptr<Button> SettingsPage::create_add_audio_output_device_button(List *audio_input_list_ptr) { std::unique_ptr<Button> SettingsPage::create_add_audio_output_device_button(List *audio_input_list_ptr) {
auto button = std::make_unique<Button>(&get_theme().body_font, "Add output device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); auto button = std::make_unique<Button>(&get_theme().body_font, "Add output device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
button->on_click = [this, audio_input_list_ptr]() { button->on_click = [this, audio_input_list_ptr]() {
audio_devices = get_audio_devices(); audio_devices = get_audio_devices();
audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::OUTPUT, audio_input_list_ptr)); audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::OUTPUT, audio_input_list_ptr));
update_application_audio_warning_visibility();
}; };
return button; return button;
} }
@@ -533,6 +647,8 @@ namespace gsr {
audio_input_list_ptr->add_widget(create_custom_application_audio(audio_input_list_ptr)); audio_input_list_ptr->add_widget(create_custom_application_audio(audio_input_list_ptr));
else else
audio_input_list_ptr->add_widget(create_application_audio(audio_input_list_ptr)); audio_input_list_ptr->add_widget(create_application_audio(audio_input_list_ptr));
update_application_audio_warning_visibility();
}; };
return add_audio_track_button; return add_audio_track_button;
} }
@@ -554,9 +670,24 @@ namespace gsr {
std::unique_ptr<CheckBox> SettingsPage::create_application_audio_invert_checkbox() { std::unique_ptr<CheckBox> SettingsPage::create_application_audio_invert_checkbox() {
auto application_audio_invert_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record audio from all applications except the selected ones"); auto application_audio_invert_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record audio from all applications except the selected ones");
application_audio_invert_checkbox->set_checked(false); application_audio_invert_checkbox->set_checked(false);
application_audio_invert_checkbox->on_changed = [this](bool) {
update_application_audio_warning_visibility();
};
return application_audio_invert_checkbox; return application_audio_invert_checkbox;
} }
std::unique_ptr<Widget> SettingsPage::create_application_audio_warning() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL);
list->set_spacing(0.003f);
list->set_visible(false);
const int font_character_size = get_theme().body_font.get_character_size();
list->add_widget(std::make_unique<Image>(&get_theme().warning_texture, mgl::vec2f(font_character_size, font_character_size), Image::ScaleBehavior::SCALE));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices.", get_color_theme().text_color));
return list;
}
static void update_audio_track_titles(List *audio_track_section_list_ptr) { static void update_audio_track_titles(List *audio_track_section_list_ptr) {
int index = 0; int index = 0;
audio_track_section_list_ptr->for_each_child_widget([&index](std::unique_ptr<Widget> &widget) { audio_track_section_list_ptr->for_each_child_widget([&index](std::unique_ptr<Widget> &widget) {
@@ -604,6 +735,7 @@ namespace gsr {
list_ptr->add_widget(create_add_audio_buttons(audio_input_section_ptr)); list_ptr->add_widget(create_add_audio_buttons(audio_input_section_ptr));
list_ptr->add_widget(std::move(audio_input_section)); list_ptr->add_widget(std::move(audio_input_section));
list_ptr->add_widget(create_application_audio_invert_checkbox()); list_ptr->add_widget(create_application_audio_invert_checkbox());
list_ptr->add_widget(create_application_audio_warning());
set_application_audio_options_visible(subsection.get(), view_radio_button_ptr->get_selected_id() == "advanced", *gsr_info); set_application_audio_options_visible(subsection.get(), view_radio_button_ptr->get_selected_id() == "advanced", *gsr_info);
return subsection; return subsection;
@@ -1082,6 +1214,33 @@ namespace gsr {
return list; return list;
} }
std::unique_ptr<Widget> SettingsPage::create_low_power_mode() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record in low-power mode");
checkbox->set_visible(gsr_info->gpu_info.vendor == GpuVendor::AMD);
low_power_mode_checkbox_ptr = checkbox.get();
list->add_widget(std::move(checkbox));
auto info = std::make_unique<Image>(&get_theme().question_mark_texture, low_power_mode_checkbox_ptr->get_size(), Image::ScaleBehavior::SCALE);
info->set_tooltip_text(
"Do not force the GPU to go into high performance mode when recording.\n"
"May affect recording performance, especially when playing a video at the same time.\n"
"If enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle."
);
Image *info_ptr = info.get();
info->on_mouse_move = [info_ptr](bool inside) {
if(inside)
set_current_tooltip(info_ptr);
else
remove_as_current_tooltip(info_ptr);
};
list->add_widget(std::move(info));
return list;
}
void SettingsPage::add_replay_widgets() { void SettingsPage::add_replay_widgets() {
auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL); auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL);
auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL); auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
@@ -1097,6 +1256,7 @@ namespace gsr {
general_list->add_widget(create_save_replay_in_game_folder()); general_list->add_widget(create_save_replay_in_game_folder());
if(gsr_info->system_info.gsr_version >= GsrVersion{5, 0, 3}) if(gsr_info->system_info.gsr_version >= GsrVersion{5, 0, 3})
general_list->add_widget(create_restart_replay_on_save()); general_list->add_widget(create_restart_replay_on_save());
general_list->add_widget(create_low_power_mode());
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", 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>("Replay indicator", create_indicator("replay"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); settings_list_ptr->add_widget(std::make_unique<Subsection>("Replay indicator", create_indicator("replay"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
@@ -1153,6 +1313,7 @@ namespace gsr {
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL); auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
general_list->add_widget(create_save_recording_in_game_folder()); general_list->add_widget(create_save_recording_in_game_folder());
general_list->add_widget(create_low_power_mode());
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", 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>("Recording indicator", create_indicator("recording"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); settings_list_ptr->add_widget(std::make_unique<Subsection>("Recording indicator", create_indicator("recording"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
@@ -1173,6 +1334,7 @@ namespace gsr {
streaming_service_box->add_item("Twitch", "twitch"); streaming_service_box->add_item("Twitch", "twitch");
streaming_service_box->add_item("YouTube", "youtube"); streaming_service_box->add_item("YouTube", "youtube");
streaming_service_box->add_item("Rumble", "rumble"); streaming_service_box->add_item("Rumble", "rumble");
streaming_service_box->add_item("Kick", "kick");
streaming_service_box->add_item("Custom", "custom"); streaming_service_box->add_item("Custom", "custom");
streaming_service_box_ptr = streaming_service_box.get(); streaming_service_box_ptr = streaming_service_box.get();
return streaming_service_box; return streaming_service_box;
@@ -1216,6 +1378,8 @@ namespace gsr {
twitch_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get()); twitch_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
youtube_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get()); youtube_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
rumble_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get()); rumble_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
kick_stream_url_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
kick_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
stream_key_list_ptr = stream_key_list.get(); stream_key_list_ptr = stream_key_list.get();
return stream_key_list; return stream_key_list;
@@ -1282,7 +1446,7 @@ namespace gsr {
settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL); auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
general_list->add_widget(create_save_recording_in_game_folder()); general_list->add_widget(create_low_power_mode());
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", 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>("Streaming indicator", create_indicator("streaming"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming indicator", create_indicator("streaming"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
@@ -1291,12 +1455,15 @@ namespace gsr {
const bool twitch_option = id == "twitch"; const bool twitch_option = id == "twitch";
const bool youtube_option = id == "youtube"; const bool youtube_option = id == "youtube";
const bool rumble_option = id == "rumble"; const bool rumble_option = id == "rumble";
const bool kick_option = id == "kick";
const bool custom_option = id == "custom"; const bool custom_option = id == "custom";
stream_key_list_ptr->set_visible(!custom_option); stream_key_list_ptr->set_visible(!custom_option);
custom_stream_list_ptr->set_visible(custom_option); custom_stream_list_ptr->set_visible(custom_option);
twitch_stream_key_entry_ptr->get_parent_widget()->set_visible(twitch_option); twitch_stream_key_entry_ptr->get_parent_widget()->set_visible(twitch_option);
youtube_stream_key_entry_ptr->get_parent_widget()->set_visible(youtube_option); youtube_stream_key_entry_ptr->get_parent_widget()->set_visible(youtube_option);
rumble_stream_key_entry_ptr->get_parent_widget()->set_visible(rumble_option); rumble_stream_key_entry_ptr->get_parent_widget()->set_visible(rumble_option);
kick_stream_url_entry_ptr->get_parent_widget()->set_visible(kick_option);
kick_stream_key_entry_ptr->get_parent_widget()->set_visible(kick_option);
return true; return true;
}; };
streaming_service_box_ptr->on_selection_changed("Twitch", "twitch"); streaming_service_box_ptr->on_selection_changed("Twitch", "twitch");
@@ -1407,6 +1574,8 @@ namespace gsr {
auto audio_track_section = create_audio_track_section(audio_section_ptr); auto audio_track_section = create_audio_track_section(audio_section_ptr);
audio_track_section_list_ptr->add_widget(std::move(audio_track_section)); audio_track_section_list_ptr->add_widget(std::move(audio_track_section));
} }
update_application_audio_warning_visibility();
} }
void SettingsPage::load_common(RecordOptions &record_options) { void SettingsPage::load_common(RecordOptions &record_options) {
@@ -1425,17 +1594,22 @@ namespace gsr {
restore_portal_session_checkbox_ptr->set_checked(record_options.restore_portal_session); restore_portal_session_checkbox_ptr->set_checked(record_options.restore_portal_session);
show_notification_checkbox_ptr->set_checked(record_options.show_notifications); show_notification_checkbox_ptr->set_checked(record_options.show_notifications);
led_indicator_checkbox_ptr->set_checked(record_options.use_led_indicator); led_indicator_checkbox_ptr->set_checked(record_options.use_led_indicator);
low_power_mode_checkbox_ptr->set_checked(record_options.low_power_mode);
char webcam_setup_str[256];
snprintf(webcam_setup_str, sizeof(webcam_setup_str), "%dx%d@%dhz", record_options.webcam_camera_width, record_options.webcam_camera_height, record_options.webcam_camera_fps);
webcam_sources_box_ptr->set_selected_item(record_options.webcam_source); webcam_sources_box_ptr->set_selected_item(record_options.webcam_source);
flip_camera_horizontally_checkbox_ptr->set_checked(record_options.webcam_flip_horizontally); flip_camera_horizontally_checkbox_ptr->set_checked(record_options.webcam_flip_horizontally);
webcam_video_format_box_ptr->set_selected_item(record_options.webcam_video_format); webcam_video_format_box_ptr->set_selected_item(record_options.webcam_video_format);
webcam_video_setup_box_ptr->set_selected_item(webcam_setup_str);
webcam_box_pos.x = ((float)record_options.webcam_x / 100.0f) * screen_inner_size.x; webcam_box_pos.x = ((float)record_options.webcam_x / 100.0f) * screen_inner_size.x;
webcam_box_pos.y = ((float)record_options.webcam_y / 100.0f) * screen_inner_size.y; webcam_box_pos.y = ((float)record_options.webcam_y / 100.0f) * screen_inner_size.y;
webcam_box_size.x = ((float)record_options.webcam_width / 100.0f * screen_inner_size.x); webcam_box_size.x = ((float)record_options.webcam_width / 100.0f * screen_inner_size.x);
webcam_box_size.y = ((float)record_options.webcam_height / 100.0f * screen_inner_size.y); webcam_box_size.y = ((float)record_options.webcam_height / 100.0f * screen_inner_size.y);
if(selected_camera.has_value()) if(selected_camera_setup.has_value())
webcam_box_size = clamp_keep_aspect_ratio(selected_camera->size.to_vec2f(), webcam_box_size); webcam_box_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), webcam_box_size);
if(record_options.record_area_width == 0) if(record_options.record_area_width == 0)
record_options.record_area_width = 1920; record_options.record_area_width = 1920;
@@ -1505,6 +1679,8 @@ namespace gsr {
youtube_stream_key_entry_ptr->set_text(config.streaming_config.youtube.stream_key); youtube_stream_key_entry_ptr->set_text(config.streaming_config.youtube.stream_key);
twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key); twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key);
rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key); rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key);
kick_stream_url_entry_ptr->set_text(config.streaming_config.kick.stream_url);
kick_stream_key_entry_ptr->set_text(config.streaming_config.kick.stream_key);
stream_url_entry_ptr->set_text(config.streaming_config.custom.url); stream_url_entry_ptr->set_text(config.streaming_config.custom.url);
stream_key_entry_ptr->set_text(config.streaming_config.custom.key); stream_key_entry_ptr->set_text(config.streaming_config.custom.key);
container_box_ptr->set_selected_item(config.streaming_config.custom.container); container_box_ptr->set_selected_item(config.streaming_config.custom.container);
@@ -1568,13 +1744,24 @@ namespace gsr {
record_options.restore_portal_session = restore_portal_session_checkbox_ptr->is_checked(); record_options.restore_portal_session = restore_portal_session_checkbox_ptr->is_checked();
record_options.show_notifications = show_notification_checkbox_ptr->is_checked(); record_options.show_notifications = show_notification_checkbox_ptr->is_checked();
record_options.use_led_indicator = led_indicator_checkbox_ptr->is_checked(); record_options.use_led_indicator = led_indicator_checkbox_ptr->is_checked();
record_options.low_power_mode = low_power_mode_checkbox_ptr->is_checked();
if(selected_camera.has_value()) // TODO: Set selected_camera_setup properly when updating and shit
webcam_box_size = clamp_keep_aspect_ratio(selected_camera->size.to_vec2f(), webcam_box_size);
if(selected_camera_setup.has_value())
webcam_box_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), webcam_box_size);
int camera_width = 0;
int camera_height = 0;
int camera_fps = 0;
sscanf(webcam_video_setup_box_ptr->get_selected_id().c_str(), "%dx%d@%dhz", &camera_width, &camera_height, &camera_fps);
record_options.webcam_source = webcam_sources_box_ptr->get_selected_id(); record_options.webcam_source = webcam_sources_box_ptr->get_selected_id();
record_options.webcam_flip_horizontally = flip_camera_horizontally_checkbox_ptr->is_checked(); record_options.webcam_flip_horizontally = flip_camera_horizontally_checkbox_ptr->is_checked();
record_options.webcam_video_format = webcam_video_format_box_ptr->get_selected_id(); record_options.webcam_video_format = webcam_video_format_box_ptr->get_selected_id();
record_options.webcam_camera_width = camera_width;
record_options.webcam_camera_height = camera_height;
record_options.webcam_camera_fps = camera_fps;
record_options.webcam_x = std::round((webcam_box_pos.x / screen_inner_size.x) * 100.0f); record_options.webcam_x = std::round((webcam_box_pos.x / screen_inner_size.x) * 100.0f);
record_options.webcam_y = std::round((webcam_box_pos.y / screen_inner_size.y) * 100.0f); record_options.webcam_y = std::round((webcam_box_pos.y / screen_inner_size.y) * 100.0f);
record_options.webcam_width = std::round((webcam_box_size.x / screen_inner_size.x) * 100.0f); record_options.webcam_width = std::round((webcam_box_size.x / screen_inner_size.x) * 100.0f);
@@ -1653,6 +1840,8 @@ namespace gsr {
config.streaming_config.youtube.stream_key = youtube_stream_key_entry_ptr->get_text(); config.streaming_config.youtube.stream_key = youtube_stream_key_entry_ptr->get_text();
config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text(); config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text();
config.streaming_config.rumble.stream_key = rumble_stream_key_entry_ptr->get_text(); config.streaming_config.rumble.stream_key = rumble_stream_key_entry_ptr->get_text();
config.streaming_config.kick.stream_url = kick_stream_url_entry_ptr->get_text();
config.streaming_config.kick.stream_key = kick_stream_key_entry_ptr->get_text();
config.streaming_config.custom.url = stream_url_entry_ptr->get_text(); config.streaming_config.custom.url = stream_url_entry_ptr->get_text();
config.streaming_config.custom.key = stream_key_entry_ptr->get_text(); config.streaming_config.custom.key = stream_key_entry_ptr->get_text();
config.streaming_config.custom.container = container_box_ptr->get_selected_id(); config.streaming_config.custom.container = container_box_ptr->get_selected_id();

67
src/gui/Tooltip.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "../../include/gui/Tooltip.hpp"
#include "../../include/Theme.hpp"
#include <mglpp/graphics/Rectangle.hpp>
#include <mglpp/graphics/Sprite.hpp>
#include <mglpp/window/Window.hpp>
namespace gsr {
static const float padding_top_scale = 0.008f;
static const float padding_bottom_scale = 0.008f;
static const float padding_left_scale = 0.008f;
static const float padding_right_scale = 0.008f;
static const float accent_scale = 0.0025f;
Tooltip::Tooltip(mgl::Font *font) : label("", *font) {}
bool Tooltip::on_event(mgl::Event&, mgl::Window&, mgl::vec2f) {
return true;
}
void Tooltip::draw(mgl::Window &window, mgl::vec2f offset) {
if(!visible)
return;
const mgl::vec2f draw_pos = (window.get_mouse_position().to_vec2f() + offset).floor();
const int padding_top = get_theme().window_height * padding_top_scale;
const int padding_left = get_theme().window_height * padding_left_scale;
const int accent_height = get_theme().window_height * accent_scale;
const int icon_height = label.get_font()->get_character_size();
mgl::Rectangle background(get_size());
background.set_position(draw_pos - mgl::vec2f(0.0f, background.get_size().y));
background.set_color(mgl::Color(0, 0, 0));
window.draw(background);
mgl::Rectangle accent(background.get_position(), mgl::vec2f(background.get_size().x, accent_height).floor());
accent.set_color(get_color_theme().tint_color);
window.draw(accent);
mgl::Sprite icon_sprite(&get_theme().info_texture, background.get_position() + mgl::vec2f(padding_left, accent_height + padding_top).floor());
icon_sprite.set_height(icon_height);
window.draw(icon_sprite);
label.set_position(background.get_position() + mgl::vec2f(padding_left, accent_height + padding_top + icon_sprite.get_size().y).floor());
window.draw(label);
}
mgl::vec2f Tooltip::get_size() {
if(!visible)
return {0.0f, 0.0f};
const int padding_top = get_theme().window_height * padding_top_scale;
const int padding_bottom = get_theme().window_height * padding_bottom_scale;
const int padding_left = get_theme().window_height * padding_left_scale;
const int padding_right = get_theme().window_height * padding_right_scale;
const int accent_height = get_theme().window_height * accent_scale;
const mgl::vec2f text_size = label.get_bounds().size.floor();
const int icon_height = label.get_font()->get_character_size();
return mgl::vec2f(padding_left + text_size.x + padding_right, accent_height + padding_top + icon_height + text_size.y + padding_bottom).floor();
}
void Tooltip::set_text(std::string text) {
label.set_string(std::move(text));
}
}

View File

@@ -1,8 +1,16 @@
#include "../../include/gui/Widget.hpp" #include "../../include/gui/Widget.hpp"
#include "../../include/gui/Tooltip.hpp"
#include "../../include/Theme.hpp"
#include <vector> #include <vector>
#include <mglpp/window/Event.hpp>
namespace gsr { namespace gsr {
static std::vector<std::unique_ptr<Widget>> widgets_to_remove; static std::vector<std::unique_ptr<Widget>> widgets_to_remove;
static Widget *current_tooltip_widget = nullptr;
static std::unique_ptr<Tooltip> tooltip;
static void set_current_tooltip_text(Widget *widget);
Widget::Widget() { Widget::Widget() {
@@ -10,6 +18,7 @@ namespace gsr {
Widget::~Widget() { Widget::~Widget() {
remove_widget_as_selected_in_parent(); remove_widget_as_selected_in_parent();
remove_as_current_tooltip(this);
} }
void Widget::set_position(mgl::vec2f position) { void Widget::set_position(mgl::vec2f position) {
@@ -64,10 +73,34 @@ namespace gsr {
this->visible = visible; this->visible = visible;
} }
bool Widget::is_visible() const {
return visible;
}
Widget* Widget::get_parent_widget() { Widget* Widget::get_parent_widget() {
return parent_widget; return parent_widget;
} }
void Widget::set_tooltip_text(std::string text) {
tooltip_text = std::move(text);
if(current_tooltip_widget == this)
set_current_tooltip_text(current_tooltip_widget);
}
const std::string& Widget::get_tooltip_text() const {
return tooltip_text;
}
void Widget::handle_tooltip_event(mgl::Event &event, mgl::vec2f position, mgl::vec2f size) {
if(event.type == mgl::Event::MouseMoved) {
if(mgl::FloatRect(position, size).contains(mgl::vec2f(event.mouse_move.x, event.mouse_move.y))) {
set_current_tooltip(this);
} else {
remove_as_current_tooltip(this);
}
}
}
void add_widget_to_remove(std::unique_ptr<Widget> widget) { void add_widget_to_remove(std::unique_ptr<Widget> widget) {
widgets_to_remove.push_back(std::move(widget)); widgets_to_remove.push_back(std::move(widget));
} }
@@ -78,4 +111,40 @@ namespace gsr {
} }
widgets_to_remove.clear(); widgets_to_remove.clear();
} }
void set_current_tooltip(Widget *widget) {
if(current_tooltip_widget == widget)
return;
set_current_tooltip_text(widget);
}
void remove_as_current_tooltip(Widget *widget) {
if(current_tooltip_widget == widget)
set_current_tooltip_text(nullptr);
}
void set_current_tooltip_text(Widget *widget) {
if(widget && !widget->get_tooltip_text().empty()) {
current_tooltip_widget = widget;
if(!tooltip)
tooltip = std::make_unique<Tooltip>(&get_theme().body_font);
tooltip->set_text(current_tooltip_widget->get_tooltip_text());
} else {
current_tooltip_widget = nullptr;
tooltip.reset();
}
}
void draw_tooltip(mgl::Window &window) {
if(!tooltip)
return;
if(!current_tooltip_widget->is_visible()) {
set_current_tooltip(nullptr);
return;
}
tooltip->draw(window, mgl::vec2f(0.0f, 0.0f));
}
} }

View File

@@ -56,7 +56,7 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
rpc->add_handler("toggle-record", [overlay](const std::string &name) { rpc->add_handler("toggle-record", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str()); fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_record(); overlay->toggle_record(gsr::RecordForceType::NONE);
}); });
rpc->add_handler("toggle-pause", [overlay](const std::string &name) { rpc->add_handler("toggle-pause", [overlay](const std::string &name) {
@@ -64,6 +64,16 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
overlay->toggle_pause(); overlay->toggle_pause();
}); });
rpc->add_handler("toggle-record-region", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_record(gsr::RecordForceType::REGION);
});
rpc->add_handler("toggle-record-window", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_record(gsr::RecordForceType::WINDOW);
});
rpc->add_handler("toggle-stream", [overlay](const std::string &name) { rpc->add_handler("toggle-stream", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str()); fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->toggle_stream(); overlay->toggle_stream();

View File

@@ -243,17 +243,6 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
fprintf(stderr, "Error: failed to write event data to virtual keyboard for exclusively grabbed device\n"); fprintf(stderr, "Error: failed to write event data to virtual keyboard for exclusively grabbed device\n");
} }
if(event.type == EV_LED) {
write(fd, &event, sizeof(event));
const struct input_event syn_event = {
.type = EV_SYN,
.code = 0,
.value = 0
};
write(fd, &syn_event, sizeof(syn_event));
}
if(!extra_data->is_possibly_non_keyboard_device) if(!extra_data->is_possibly_non_keyboard_device)
return; return;

View File

@@ -52,6 +52,10 @@ static void usage(void) {
printf(" Start/stop recording.\n"); printf(" Start/stop recording.\n");
printf(" toggle-pause\n"); printf(" toggle-pause\n");
printf(" Pause/unpause recording. Only applies to regular recording.\n"); printf(" Pause/unpause recording. Only applies to regular recording.\n");
printf(" toggle-record-region\n");
printf(" Start/stop recording a region.\n");
printf(" toggle-record-window\n");
printf(" Start/stop recording a window (or desktop portal on Wayland).\n");
printf(" toggle-stream\n"); printf(" toggle-stream\n");
printf(" Start/stop streaming.\n"); printf(" Start/stop streaming.\n");
printf(" toggle-replay\n"); printf(" toggle-replay\n");
@@ -80,6 +84,8 @@ static bool is_valid_command(const char *command) {
"toggle-show", "toggle-show",
"toggle-record", "toggle-record",
"toggle-pause", "toggle-pause",
"toggle-record-region",
"toggle-record-window",
"toggle-stream", "toggle-stream",
"toggle-replay", "toggle-replay",
"replay-save", "replay-save",