Compare commits

..

23 Commits
1.6.0 ... 1.6.6

Author SHA1 Message Date
dec05eba
57448f6579 Fix meson build 2025-05-31 23:41:34 +02:00
dec05eba
4d7526d21e Add x11 window capture (video and screenshot) 2025-05-31 23:00:42 +02:00
dec05eba
fded9b8d57 Match gsr monitor name with wayland monitor name. Thanks info@leocodes 2025-05-25 19:08:57 +02:00
dec05eba
b80e3f8beb Fix crash when opening settings page because of recent change 2025-05-24 18:24:18 +02:00
dec05eba
b807712d79 Mention setcap dependency 2025-05-24 16:38:36 +02:00
dec05eba
2df417f23f gsr-global-hotkeys: better error messages 2025-05-24 14:00:23 +02:00
dec05eba
a82d1a2dfc Only show replay storage option in advanced view 2025-05-21 23:41:52 +02:00
dec05eba
043b6df255 Add livestream url for rumble 2025-05-21 23:00:42 +02:00
dec05eba
831f583f89 Add support for rumble streaming by default 2025-05-21 22:57:55 +02:00
dec05eba
d0f8b7061f 1.6.4 2025-05-18 01:32:18 +02:00
dec05eba
3a4f03ce27 Use mipmap for almost all icons 2025-05-18 01:30:56 +02:00
dec05eba
e8dc3859fe Improve quality of screenshot and settings icons, especially for smaller resolutions 2025-05-18 01:23:42 +02:00
dec05eba
5fe5830056 Better monitor tracking for capture/notification on wayland 2025-05-17 23:59:59 +02:00
dec05eba
9ac14c963e Properly honor notification settings (when not saving video in game folder). Add pause/unpause notification option 2025-05-16 18:09:39 +02:00
dec05eba
cae1c47643 Update README and TODO 2025-05-16 17:57:44 +02:00
dec05eba
de1ed58f8d 1.6.3 2025-05-15 12:37:12 +02:00
dec05eba
ff564fcb52 Fix replay duration range 2025-05-15 12:36:30 +02:00
dec05eba
aabe190bf1 1.6.2 2025-05-15 09:45:22 +02:00
dec05eba
320d368699 minor reorder 2025-05-15 09:44:21 +02:00
dec05eba
af4fc84ef7 Fix some mice and controllers being grabbed when they shouldn't 2025-05-14 21:00:24 +02:00
dec05eba
c7bfaf90ec M 2025-05-05 13:59:47 +02:00
dec05eba
61f8c666fe Separate audio into output and input 2025-05-04 23:23:36 +02:00
dec05eba
28be9d1c6f Update flatpak version 2025-05-04 22:41:13 +02:00
31 changed files with 822 additions and 275 deletions

View File

@@ -14,7 +14,7 @@ A program called `gsr-ui-cli` is also installed when installing this software. T
# Installation
If you are using an Arch Linux based distro then you can find gpu screen recorder ui on aur under the name gpu-screen-recorder-ui (`yay -S gpu-screen-recorder-ui`).\
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder). This flatpak includes both this UI and gpu-screen-recorder so no need to install that first.
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which includes this UI.
# Dependencies
GPU Screen Recorder UI uses meson build system so you need to install `meson` to build GPU Screen Recorder UI.
@@ -28,6 +28,7 @@ These are the dependencies needed to build GPU Screen Recorder UI:
* libpulse (libpulse-simple)
* libdrm
* wayland (wayland-client, wayland-egl, wayland-scanner)
* setcap (libcap)
## Runtime dependencies
There are also additional dependencies needed at runtime:
@@ -61,7 +62,9 @@ I'm looking for somebody that can create sound effects for the notifications.
# Known issues
* When the UI is open the wallpaper is shown instead of the game on Hyprland. This is an issue with Hyprland. It cant be fixed until the UI is redesigned to not be a fullscreen overlay.
* Opening the UI when a game is fullscreened can mess up the game window a bit on Hyprland. I believe this is an issue with Hyprland.
* Opening the UI when a game is fullscreen can mess up the game window a bit on Hyprland. This is an issue with Hyprland.
* The background of the UI is black when opening the UI while a Wayland application is focused on COSMIC. This is an issue with COSMIC.
* Unable to close the region selection with escape key while a Wayland application is focused on COSMIC. This is an issue with COSMIC.
# FAQ
## I get an error when trying to start the gpu-screen-recorder-ui.service systemd service

37
TODO
View File

@@ -12,8 +12,6 @@ Handle events in draw function because the render position of elements is availa
Add nvidia overclock option.
Add support for window selection in capture.
Filechooser should have the option to select list view, search bar and common folders/mounted drives on the left side for quick navigation. Also a button to create a new directory.
Restart replay on system start if monitor resolution changes.
@@ -37,8 +35,6 @@ Fix first frame being black when running without a compositor.
Add support for systray.
Add option to take screenshot.
Move event callbacks to a global list instead of std::function object in each widget. This reduces the size of widgets,
since most widgets wont have the event callback set.
This event callback would pass the widget as an argument.
@@ -72,8 +68,6 @@ Run `systemctl status --user gpu-screen-recorder` when starting recording and gi
Add option to select which gpu to record with, or list all monitors and automatically use the gpu associated with the monitor. Do the same in gtk application.
Dont allow autostart of replay if capture option is window recording (when window recording is added).
Use global shortcuts desktop portal protocol on wayland when available.
Support CJK.
@@ -98,9 +92,6 @@ Make gsr-ui flatpak systemd work nicely with non-flatpak gsr-ui. Maybe change Ex
When enabling X11 global hotkey again only grab lalt, not ralt.
When adding window capture only add it to recording and streaming and do the window selection when recording starts, to make it more ergonomic with hotkeys.
If hotkey for recording/streaming start is pressed on the button for start is clicked then hide the ui if it's visible and show the window selection option (cursor).
Show an error that prime run will be disabled when using desktop portal capture option. This can cause issues as the user may have selected a video codec option that isn't available on their iGPU but is available on the prime-run dGPU.
For keyboards that report supporting mice the keyboard grab will be delayed until any key has been pressed (and then released), see: https://github.com/dec05eba/gpu-screen-recorder-issues/issues/97
@@ -118,9 +109,6 @@ When clicking on current directory in file manager show a dropdown menu where yo
Maybe change gsr-ui startup retry time in the systemd service, from 5 seconds to 2 seconds.
Add support for window capture. This should not prompt for window selection directly but instead prompt for window selection when recording starts and hide the ui first.
For screenshots window capture should exist but "follow focused" option should not exist.
Make it possible to take a screenshot through a button in the ui instead of having to use hotkey.
Handle failing to save a replay. gsr should output "failed to save replay, or something like that" to make it possible to detect that.
@@ -141,14 +129,10 @@ Make inactive buttons gray (in dropdown boxes and in the front page with save, e
Add option to do screen-direct recording. But make it clear that it should not be used, except for gsync on x11 nvidia.
Add window capture option (for x11).
Add systray for recording status.
Add a desktop icon when gsr-ui has a window mode option (which should be the default launch option).
Use /dev/input/eventN (or /dev/hidrawN) instead of /dev/input/jsN for joystick input.
Verify if cursor tracker monitor name is always correct. It uses the wayland monitor name for recording, but gpu screen recorder uses a custom name created from the drm connector name.
Notification with the focused monitor (with CursorTrackerWayland) assumes that the x11 monitor name is the same as the drm monitor name. Same for find_monitor_by_name.
@@ -175,4 +159,23 @@ Add option to start recording/replay/stream after the notification has disappear
When using wayland for mgl try using wlr-layer-shell and set layer to overlay and keyboard interactivity to exclusive. Do something similar for notifications.
When starting gsr-ui remove any temporary replay disk data that has possibly remained from a crash, by looking for all folders that starts with gsr-replay and end with .gsr, in the replay directory.
When starting gsr-ui remove any temporary replay disk data that has possibly remained from a crash, by looking for all folders that starts with gsr-replay and end with .gsr, in the replay directory.
Add restart program button, in global settings. It should do almost the same thing as exit program, execept execv gsr-ui.
When gpu screen recorder ui can run as a regular window (and supports tray icon and global shortcut portal) remove gpu screen recorder gtk. Then all error checking needs to be moved from that project to this project.
May need support for multi windows, or create a small project to display dialogs.
Add a bug report page that automatically includes system info (make this clear to the user).
Do this by sending the report to a custom server that stores that data.
The server should limit reports per IP to prevent spam.
Make it possible to change controller hotkeys. Also read from /dev/input/eventN instead of /dev/input/jsN. This is readable for controllers.
Add option to copy screenshot to clipboard. Does it work properly on Wayland compositors? Maybe need to wait until the application becomes Wayland native instead of XWayland.
Show message that replay/streaming has to be restarted if recording settings are changed while replay/streaming is ongoing.
Support vector graphics. Maybe support svg, rendering it to a texture for better performance.
Support freetype for text rendering. Maybe load freetype as runtime (with dlopen) and use that when available and fallback to stb_freetype if not available.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

View File

@@ -45,7 +45,7 @@ namespace gsr {
int32_t video_width = 0;
int32_t video_height = 0;
int32_t fps = 60;
int32_t video_bitrate = 15000;
int32_t video_bitrate = 8000;
bool merge_audio_tracks = true; // TODO: Remove in the future
bool application_audio_invert = false; // TODO: Remove in the future
bool change_video_resolution = false;
@@ -55,7 +55,7 @@ namespace gsr {
std::string video_quality = "very_high";
std::string video_codec = "auto";
std::string audio_codec = "opus";
std::string framerate_mode = "vfr";
std::string framerate_mode = "auto";
bool advanced_view = false;
bool overclock = false;
bool record_cursor = true;
@@ -79,6 +79,10 @@ namespace gsr {
std::string stream_key;
};
struct RumbleStreamConfig {
std::string stream_key;
};
struct CustomStreamConfig {
std::string url;
std::string container = "flv";
@@ -91,6 +95,7 @@ namespace gsr {
std::string streaming_service = "twitch";
YoutubeStreamConfig youtube;
TwitchStreamConfig twitch;
RumbleStreamConfig rumble;
CustomStreamConfig custom;
ConfigHotkey start_stop_hotkey;
};
@@ -100,6 +105,7 @@ namespace gsr {
bool save_video_in_game_folder = false;
bool show_recording_started_notifications = true;
bool show_video_saved_notifications = true;
bool show_video_paused_notifications = true;
std::string save_directory;
std::string container = "mp4";
ConfigHotkey start_stop_hotkey;

View File

@@ -9,6 +9,7 @@
#include "GlobalHotkeys/GlobalHotkeysJoystick.hpp"
#include "AudioPlayer.hpp"
#include "RegionSelector.hpp"
#include "WindowSelector.hpp"
#include "CursorTracker/CursorTracker.hpp"
#include <mglpp/window/Window.hpp>
@@ -116,10 +117,10 @@ namespace gsr {
void on_press_save_replay();
void on_press_save_replay_1_min_replay();
void on_press_save_replay_10_min_replay();
bool on_press_start_replay(bool disable_notification, bool finished_region_selection);
void on_press_start_record(bool finished_region_selection);
void on_press_start_stream(bool finished_region_selection);
void on_press_take_screenshot(bool finished_region_selection, bool force_region_capture);
bool on_press_start_replay(bool disable_notification, bool finished_selection);
void on_press_start_record(bool finished_selection);
void on_press_start_stream(bool finished_selection);
void on_press_take_screenshot(bool finished_selection, bool force_region_capture);
bool update_compositor_texture(const Monitor &monitor);
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
@@ -213,10 +214,15 @@ namespace gsr {
int replay_save_duration_min = 0;
AudioPlayer audio_player;
RegionSelector region_selector;
bool start_region_capture = false;
std::function<void()> on_region_selected;
WindowSelector window_selector;
bool start_window_capture = false;
std::function<void()> on_window_selected;
std::string recording_capture_target;
std::string screenshot_capture_target;

View File

@@ -26,7 +26,6 @@ namespace gsr {
bool failed() const;
bool poll_events();
bool is_selected() const;
bool take_selection();
bool take_canceled();
Region get_selection() const;

View File

@@ -28,6 +28,7 @@ namespace gsr {
mgl::Texture combobox_arrow_texture;
mgl::Texture settings_texture;
mgl::Texture settings_small_texture;
mgl::Texture settings_extra_small_texture;
mgl::Texture folder_texture;
mgl::Texture up_arrow_texture;
mgl::Texture replay_button_texture;

View File

@@ -15,6 +15,8 @@ namespace gsr {
void string_split_char(std::string_view str, char delimiter, StringSplitCallback callback_func);
bool starts_with(std::string_view str, const char *substr);
bool ends_with(std::string_view str, const char *substr);
std::string strip(const std::string &str);
std::string get_home_dir();
std::string get_config_dir();

View File

@@ -0,0 +1,33 @@
#pragma once
#include <X11/Xlib.h>
#include <mglpp/graphics/Color.hpp>
namespace gsr {
class WindowSelector {
public:
WindowSelector();
WindowSelector(const WindowSelector&) = delete;
WindowSelector& operator=(const WindowSelector&) = delete;
~WindowSelector();
bool start(mgl::Color border_color);
void stop();
bool is_started() const;
bool failed() const;
bool poll_events();
bool take_selection();
bool take_canceled();
Window get_selection() const;
private:
Display *dpy = nullptr;
Cursor crosshair_cursor = None;
Colormap border_window_colormap = None;
Window border_window = None;
Window selected_window = None;
bool selected = false;
bool canceled = false;
};
}

View File

@@ -24,6 +24,7 @@ namespace gsr {
std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window);
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window);
void set_window_size_not_resizable(Display *dpy, Window window, int width, int height);
Window window_get_target_window_child(Display *display, Window window);
mgl::vec2i get_cursor_position(Display *dpy, Window *window);
mgl::vec2i create_window_get_center_position(Display *display);
std::string get_window_manager_name(Display *display);

View File

@@ -26,7 +26,6 @@ namespace gsr {
private:
std::unique_ptr<ComboBox> create_record_area_box();
std::unique_ptr<Widget> create_record_area();
std::unique_ptr<List> create_select_window();
std::unique_ptr<Entry> create_image_width_entry();
std::unique_ptr<Entry> create_image_height_entry();
std::unique_ptr<List> create_image_resolution();
@@ -56,7 +55,6 @@ namespace gsr {
GsrPage *content_page_ptr = nullptr;
ScrollablePage *settings_scrollable_page_ptr = nullptr;
List *select_window_list_ptr = nullptr;
List *image_resolution_list_ptr = nullptr;
List *restore_portal_session_list_ptr = nullptr;
List *color_range_list_ptr = nullptr;

View File

@@ -20,6 +20,11 @@ namespace gsr {
class LineSeparator;
class Subsection;
enum class AudioDeviceType {
OUTPUT,
INPUT
};
class SettingsPage : public StaticPage {
public:
enum class Type {
@@ -41,7 +46,6 @@ namespace gsr {
std::unique_ptr<RadioButton> create_view_radio_button();
std::unique_ptr<ComboBox> create_record_area_box();
std::unique_ptr<Widget> create_record_area();
std::unique_ptr<List> create_select_window();
std::unique_ptr<Entry> create_area_width_entry();
std::unique_ptr<Entry> create_area_height_entry();
std::unique_ptr<List> create_area_size();
@@ -54,11 +58,12 @@ namespace gsr {
std::unique_ptr<List> create_restore_portal_session_section();
std::unique_ptr<Widget> create_change_video_resolution_section();
std::unique_ptr<Widget> create_capture_target_section();
std::unique_ptr<ComboBox> create_audio_device_selection_combobox();
std::unique_ptr<ComboBox> create_audio_device_selection_combobox(AudioDeviceType device_type);
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(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_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<ComboBox> create_application_audio_selection_combobox(List *application_audio_row);
std::unique_ptr<List> create_application_audio(List *audio_input_list_ptr);
std::unique_ptr<List> create_custom_application_audio(List *audio_input_list_ptr);
@@ -141,7 +146,6 @@ namespace gsr {
GsrPage *content_page_ptr = nullptr;
ScrollablePage *settings_scrollable_page_ptr = nullptr;
List *settings_list_ptr = nullptr;
List *select_window_list_ptr = nullptr;
List *area_size_list_ptr = nullptr;
List *video_resolution_list_ptr = nullptr;
List *restore_portal_session_list_ptr = nullptr;
@@ -180,11 +184,13 @@ namespace gsr {
CheckBox *save_recording_in_game_folder_ptr = nullptr;
CheckBox *show_recording_started_notification_checkbox_ptr = nullptr;
CheckBox *show_video_saved_notification_checkbox_ptr = nullptr;
CheckBox *show_video_paused_notification_checkbox_ptr = nullptr;
CheckBox *show_streaming_started_notification_checkbox_ptr = nullptr;
CheckBox *show_streaming_stopped_notification_checkbox_ptr = nullptr;
Button *save_directory_button_ptr = nullptr;
Entry *twitch_stream_key_entry_ptr = nullptr;
Entry *youtube_stream_key_entry_ptr = nullptr;
Entry *rumble_stream_key_entry_ptr = nullptr;
Entry *stream_url_entry_ptr = nullptr;
Entry *replay_time_entry_ptr = nullptr;
RadioButton *replay_storage_button_ptr = nullptr;

View File

@@ -1,4 +1,4 @@
project('gsr-ui', ['c', 'cpp'], version : '1.6.0', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
project('gsr-ui', ['c', 'cpp'], version : '1.6.6', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
if get_option('buildtype') == 'debug'
add_project_arguments('-g3', language : ['c', 'cpp'])
@@ -40,6 +40,7 @@ src = [
'src/Utils.cpp',
'src/WindowUtils.cpp',
'src/RegionSelector.cpp',
'src/WindowSelector.cpp',
'src/Config.cpp',
'src/GsrInfo.cpp',
'src/Process.cpp',
@@ -61,7 +62,7 @@ datadir = get_option('datadir')
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.5.0"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.6.0"', language: ['c', 'cpp'])
executable(
meson.project_name(),

View File

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

View File

@@ -119,16 +119,16 @@ namespace gsr {
streaming_config.record_options.video_quality = "custom";
streaming_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false});
streaming_config.record_options.video_bitrate = 15000;
streaming_config.record_options.video_bitrate = 8000;
record_config.save_directory = default_videos_save_directory;
record_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false});
record_config.record_options.video_bitrate = 45000;
record_config.record_options.video_bitrate = 40000;
replay_config.record_options.video_quality = "custom";
replay_config.save_directory = default_videos_save_directory;
replay_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false});
replay_config.record_options.video_bitrate = 45000;
replay_config.record_options.video_bitrate = 40000;
screenshot_config.save_directory = default_pictures_save_directory;
@@ -201,6 +201,7 @@ namespace gsr {
{"streaming.service", &config.streaming_config.streaming_service},
{"streaming.youtube.key", &config.streaming_config.youtube.stream_key},
{"streaming.twitch.key", &config.streaming_config.twitch.stream_key},
{"streaming.rumble.key", &config.streaming_config.rumble.stream_key},
{"streaming.custom.url", &config.streaming_config.custom.url},
{"streaming.custom.container", &config.streaming_config.custom.container},
{"streaming.start_stop_hotkey", &config.streaming_config.start_stop_hotkey},
@@ -229,6 +230,7 @@ namespace gsr {
{"record.save_video_in_game_folder", &config.record_config.save_video_in_game_folder},
{"record.show_recording_started_notifications", &config.record_config.show_recording_started_notifications},
{"record.show_video_saved_notifications", &config.record_config.show_video_saved_notifications},
{"record.show_video_paused_notifications", &config.record_config.show_video_paused_notifications},
{"record.save_directory", &config.record_config.save_directory},
{"record.container", &config.record_config.container},
{"record.start_stop_hotkey", &config.record_config.start_stop_hotkey},

View File

@@ -9,14 +9,8 @@
namespace gsr {
static const int MAX_CONNECTORS = 32;
static const int CONNECTOR_TYPE_COUNTS = 32;
static const uint32_t plane_property_all = 0xF;
typedef struct {
int type;
int count;
} drm_connector_type_count;
typedef enum {
PLANE_PROPERTY_CRTC_X = 1 << 0,
PLANE_PROPERTY_CRTC_Y = 1 << 1,
@@ -105,22 +99,6 @@ namespace gsr {
return get_drm_property_by_name(drm_fd, &properties, name, result);
}
static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) {
for(int i = 0; i < *num_type_counts; ++i) {
if(type_counts[i].type == connector_type)
return &type_counts[i];
}
if(*num_type_counts == CONNECTOR_TYPE_COUNTS)
return NULL;
const int index = *num_type_counts;
type_counts[index].type = connector_type;
type_counts[index].count = 0;
++*num_type_counts;
return &type_counts[index];
}
// Note: this monitor name logic is kept in sync with gpu screen recorder
static std::string get_monitor_name_from_crtc_id(int drm_fd, uint32_t crtc_id) {
std::string result;
@@ -128,27 +106,23 @@ namespace gsr {
if(!resources)
return result;
drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS];
int num_type_counts = 0;
for(int i = 0; i < resources->count_connectors; ++i) {
uint64_t connector_crtc_id = 0;
drmModeConnectorPtr connector = drmModeGetConnectorCurrent(drm_fd, resources->connectors[i]);
if(!connector)
continue;
drm_connector_type_count *connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector->connector_type);
const char *connection_name = drmModeGetConnectorTypeName(connector->connector_type);
if(connector_type)
++connector_type->count;
if(!connection_name)
goto next;
if(connector->connection != DRM_MODE_CONNECTED)
goto next;
if(connector_type && connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &connector_crtc_id) && connector_crtc_id == crtc_id) {
if(connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &connector_crtc_id) && connector_crtc_id == crtc_id) {
result = connection_name;
result += "-";
result += std::to_string(connector_type->count);
result += std::to_string(connector->connector_type_id);
drmModeFreeConnector(connector);
break;
}

View File

@@ -14,6 +14,68 @@ namespace gsr {
static constexpr int axis_up_down = 7;
static constexpr int axis_left_right = 6;
struct DeviceId {
uint16_t vendor;
uint16_t product;
};
static bool read_file_hex_number(const char *path, unsigned int *value) {
*value = 0;
FILE *f = fopen(path, "rb");
if(!f)
return false;
fscanf(f, "%x", value);
fclose(f);
return true;
}
static DeviceId joystick_get_device_id(const char *path) {
DeviceId device_id;
device_id.vendor = 0;
device_id.product = 0;
const char *js_path_id = nullptr;
const int len = strlen(path);
for(int i = len - 1; i >= 0; --i) {
if(path[i] == '/') {
js_path_id = path + i + 1;
break;
}
}
if(!js_path_id)
return device_id;
unsigned int vendor = 0;
unsigned int product = 0;
char path_buf[1024];
snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/vendor", js_path_id);
if(!read_file_hex_number(path_buf, &vendor))
return device_id;
snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/product", js_path_id);
if(!read_file_hex_number(path_buf, &product))
return device_id;
device_id.vendor = vendor;
device_id.product = product;
return device_id;
}
static bool is_ps4_controller(DeviceId device_id) {
return device_id.vendor == 0x054C && (device_id.product == 0x09CC || device_id.product == 0x0BA0 || device_id.product == 0x05C4);
}
static bool is_ps5_controller(DeviceId device_id) {
return device_id.vendor == 0x054C && (device_id.product == 0x0DF2 || device_id.product == 0x0CE6);
}
static bool is_stadia_controller(DeviceId device_id) {
return device_id.vendor == 0x18D1 && (device_id.product == 0x9400);
}
// Returns -1 on error
static int get_js_dev_input_id_from_filepath(const char *dev_input_filepath) {
if(strncmp(dev_input_filepath, "/dev/input/js", 13) != 0)
@@ -276,6 +338,8 @@ namespace gsr {
dev_input_id
};
//const DeviceId device_id = joystick_get_device_id(dev_input_filepath);
++num_poll_fd;
fprintf(stderr, "Info: added joystick: %s\n", dev_input_filepath);
return true;

View File

@@ -26,6 +26,7 @@
#include <malloc.h>
#include <stdexcept>
#include <algorithm>
#include <inttypes.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
@@ -37,6 +38,7 @@
#include <X11/Xcursor/Xcursor.h>
#include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/system/Utf8.hpp>
extern "C" {
#include <mgl/mgl.h>
@@ -208,7 +210,6 @@ namespace gsr {
}*/
static const Monitor* find_monitor_at_position(const std::vector<Monitor> &monitors, mgl::vec2i pos) {
assert(!monitors.empty());
for(const Monitor &monitor : monitors) {
if(mgl::IntRect(monitor.position, monitor.size).contains(pos))
return &monitor;
@@ -678,6 +679,22 @@ namespace gsr {
on_region_selected = nullptr;
}
window_selector.poll_events();
if(window_selector.take_canceled()) {
on_window_selected = nullptr;
} else if(window_selector.take_selection() && on_window_selected) {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
const Window selected_window = window_selector.get_selection();
if(selected_window && selected_window != DefaultRootWindow(display)) {
on_window_selected();
} else {
show_notification("No window selected", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
}
on_window_selected = nullptr;
}
if(!visible || !window)
return;
@@ -723,7 +740,16 @@ namespace gsr {
}
}
if(region_selector.is_started()) {
if(start_window_capture) {
start_window_capture = false;
hide();
if(!window_selector.start(get_color_theme().tint_color)) {
show_notification("Failed to start window capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
on_window_selected = nullptr;
}
}
if(region_selector.is_started() || window_selector.is_started()) {
usleep(5 * 1000); // 5 ms
return true;
}
@@ -857,7 +883,7 @@ namespace gsr {
if(visible)
return;
if(region_selector.is_started())
if(region_selector.is_started() || window_selector.is_started())
return;
drawn_first_frame = false;
@@ -904,6 +930,7 @@ namespace gsr {
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
// If the focused window is a wayland application then don't use override redirect and instead create
// a fullscreen window for the ui.
// TODO: (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor))
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots;
if(prevent_game_minimizing) {
@@ -1091,7 +1118,7 @@ namespace gsr {
button->set_item_icon("save", &get_theme().save_texture);
button->set_item_icon("save_1_min", &get_theme().save_texture);
button->set_item_icon("save_10_min", &get_theme().save_texture);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->set_item_icon("settings", &get_theme().settings_extra_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack);
@@ -1125,7 +1152,7 @@ namespace gsr {
button->add_item("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("pause", &get_theme().pause_texture);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->set_item_icon("settings", &get_theme().settings_extra_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack);
@@ -1150,7 +1177,7 @@ namespace gsr {
button->add_item("Start", "start", config.streaming_config.start_stop_hotkey.to_string(false, false));
button->add_item("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("settings", &get_theme().settings_small_texture);
button->set_item_icon("settings", &get_theme().settings_extra_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack);
@@ -1312,6 +1339,7 @@ namespace gsr {
visible = false;
drawn_first_frame = false;
start_region_capture = false;
start_window_capture = false;
if(xi_input_xev) {
free(xi_input_xev);
@@ -1383,10 +1411,12 @@ namespace gsr {
if(paused) {
update_ui_recording_unpaused();
show_notification("Recording has been unpaused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
if(config.record_config.show_video_paused_notifications)
show_notification("Recording has been unpaused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
} else {
update_ui_recording_paused();
show_notification("Recording has been paused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
if(config.record_config.show_video_paused_notifications)
show_notification("Recording has been paused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
}
kill(gpu_screen_recorder_process, SIGUSR2);
@@ -1432,6 +1462,24 @@ namespace gsr {
return nullptr;
}
static void truncate_string(std::string &str, int max_length) {
int index = 0;
size_t byte_index = 0;
while(index < max_length && byte_index < str.size()) {
uint32_t codepoint = 0;
size_t codepoint_length = 0;
mgl::utf8_decode((const unsigned char*)str.c_str() + byte_index, str.size() - byte_index, &codepoint, &codepoint_length);
if(codepoint_length == 0)
codepoint_length = 1;
index += 1;
byte_index += codepoint_length;
}
str.erase(byte_index);
}
static bool is_hex_num(char c) {
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}
@@ -1459,8 +1507,87 @@ namespace gsr {
return is_hex && !hex_start;
}
static bool is_number(const char *str) {
const char *p = str;
while(*p) {
char c = *p;
if(c < '0' || c > '9')
return false;
++p;
}
return true;
}
static bool is_capture_target_monitor(const char *capture_target) {
return strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
}
static std::string capture_target_get_notification_name(const char *capture_target) {
std::string result;
if(is_capture_target_monitor(capture_target)) {
result = "this monitor";
} else if(is_number(capture_target)) {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
int64_t window_id = None;
sscanf(capture_target, "%" PRIi64, &window_id);
const std::optional<std::string> window_title = get_window_title(display, window_id);
if(window_title) {
result = strip(window_title.value());
truncate_string(result, 20);
result = "window \"" + result + "\"";
} else {
result = std::string("window ") + capture_target;
}
} else {
result = capture_target;
}
return result;
}
static std::string get_valid_monitor_x11(const std::string &target_monitor_name, const std::vector<Monitor> &monitors) {
std::string target_monitor_name_clean = target_monitor_name;
if(starts_with(target_monitor_name_clean, "HDMI-A"))
target_monitor_name_clean.replace(0, 6, "HDMI");
for(const Monitor &monitor : monitors) {
std::string monitor_name_clean = monitor.name;
if(starts_with(monitor_name_clean, "HDMI-A"))
monitor_name_clean.replace(0, 6, "HDMI");
if(target_monitor_name_clean == monitor_name_clean)
return monitor.name;
}
return "";
}
static std::string get_focused_monitor_by_cursor(CursorTracker *cursor_tracker, const GsrInfo &gsr_info, const std::vector<Monitor> &x11_monitors) {
std::optional<CursorInfo> cursor_info;
if(cursor_tracker) {
cursor_tracker->update();
cursor_info = cursor_tracker->get_latest_cursor_info();
}
std::string focused_monitor_name;
if(cursor_info) {
focused_monitor_name = std::move(cursor_info->monitor_name);
} else {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
Window x11_cursor_window = None;
mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window);
const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display);
const Monitor *focused_monitor = find_monitor_at_position(x11_monitors, monitor_position_query_value);
if(focused_monitor)
focused_monitor_name = focused_monitor->name;
}
return focused_monitor_name;
}
void Overlay::show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target) {
@@ -1481,20 +1608,24 @@ namespace gsr {
notification_args[arg_index++] = notification_type_str;
}
if(capture_target && is_capture_target_monitor(capture_target)) {
notification_args[arg_index++] = "--monitor";
notification_args[arg_index++] = capture_target;
} else {
std::optional<CursorInfo> cursor_info;
if(cursor_tracker) {
cursor_tracker->update();
cursor_info = cursor_tracker->get_latest_cursor_info();
}
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
if(cursor_info) {
notification_args[arg_index++] = "--monitor";
notification_args[arg_index++] = cursor_info->monitor_name.c_str();
}
std::string monitor_name;
const auto monitors = get_monitors(display);
if(capture_target && is_capture_target_monitor(capture_target))
monitor_name = capture_target;
else
monitor_name = get_focused_monitor_by_cursor(cursor_tracker.get(), gsr_info, monitors);
monitor_name = get_valid_monitor_x11(monitor_name, monitors);
if(!monitor_name.empty()) {
notification_args[arg_index++] = "--monitor";
notification_args[arg_index++] = monitor_name.c_str();
} else if(!monitors.empty()) {
notification_args[arg_index++] = "--monitor";
notification_args[arg_index++] = monitors.front().name.c_str();
}
notification_args[arg_index++] = nullptr;
@@ -1592,11 +1723,6 @@ namespace gsr {
return result;
}
static void truncate_string(std::string &str, int max_length) {
if((int)str.size() > max_length)
str.replace(str.begin() + max_length, str.end(), "...");
}
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
@@ -1626,11 +1752,7 @@ namespace gsr {
if(!config.record_config.show_video_saved_notifications)
return;
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Saved a recording of this monitor to \"%s\"", focused_window_name.c_str());
else
snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", recording_capture_target.c_str(), focused_window_name.c_str());
snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
capture_target = recording_capture_target.c_str();
break;
}
@@ -1644,11 +1766,7 @@ namespace gsr {
else
snprintf(duration, sizeof(duration), " ");
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Saved a%sreplay of this monitor to \"%s\"", duration, focused_window_name.c_str());
else
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, recording_capture_target.c_str(), focused_window_name.c_str());
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
capture_target = recording_capture_target.c_str();
break;
}
@@ -1656,11 +1774,7 @@ namespace gsr {
if(!config.screenshot_config.show_screenshot_saved_notifications)
return;
if(is_capture_target_monitor(screenshot_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Saved a screenshot of this monitor to \"%s\"", focused_window_name.c_str());
else
snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", screenshot_capture_target.c_str(), focused_window_name.c_str());
snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str(), focused_window_name.c_str());
capture_target = screenshot_capture_target.c_str();
break;
}
@@ -1685,7 +1799,7 @@ namespace gsr {
replay_save_show_notification = false;
if(config.replay_config.save_video_in_game_folder) {
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
} else {
} else if(config.replay_config.show_replay_saved_notifications) {
char duration[32];
if(replay_save_duration_min > 0)
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
@@ -1693,10 +1807,7 @@ namespace gsr {
snprintf(duration, sizeof(duration), " ");
char msg[512];
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Saved a%sreplay of this monitor", duration);
else
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, recording_capture_target.c_str());
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
}
}
@@ -1815,12 +1926,9 @@ namespace gsr {
if(exit_code == 0) {
if(config.screenshot_config.save_screenshot_in_game_folder) {
save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT);
} else {
} else if(config.screenshot_config.show_screenshot_saved_notifications) {
char msg[512];
if(is_capture_target_monitor(screenshot_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Saved a screenshot of this monitor");
else
snprintf(msg, sizeof(msg), "Saved a screenshot of %s", screenshot_capture_target.c_str());
snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str());
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str());
}
} else {
@@ -1917,12 +2025,9 @@ namespace gsr {
if(exit_code == 0) {
if(config.record_config.save_video_in_game_folder) {
save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
} else {
} else if(config.record_config.show_video_saved_notifications) {
char msg[512];
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Saved a recording of this monitor");
else
snprintf(msg, sizeof(msg), "Saved a recording of %s", recording_capture_target.c_str());
snprintf(msg, sizeof(msg), "Saved a recording of %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
}
} else {
@@ -2115,11 +2220,12 @@ namespace gsr {
}
static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
// TODO: Also check x11 window when enabled (check if capture_target is a decminal/hex number)
if(capture_target == "region") {
return capture_options.region;
if(capture_target == "window") {
return capture_options.window;
} else if(capture_target == "focused") {
return capture_options.focused;
} else if(capture_target == "region") {
return capture_options.region;
} else if(capture_target == "portal") {
return capture_options.portal;
} else if(capture_target == "focused_monitor") {
@@ -2133,8 +2239,27 @@ namespace gsr {
}
}
static std::string get_valid_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
std::string capture_target_clean = capture_target;
if(starts_with(capture_target_clean, "HDMI-A"))
capture_target_clean.replace(0, 6, "HDMI");
for(const GsrMonitor &monitor : capture_options.monitors) {
std::string monitor_name_clean = monitor.name;
if(starts_with(monitor_name_clean, "HDMI-A"))
monitor_name_clean.replace(0, 6, "HDMI");
if(capture_target_clean == monitor_name_clean)
return monitor.name;
}
return "";
}
std::string Overlay::get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
if(capture_target == "focused_monitor") {
if(capture_target == "window") {
return std::to_string(window_selector.get_selection());
} else if(capture_target == "focused_monitor") {
std::optional<CursorInfo> cursor_info;
if(cursor_tracker) {
cursor_tracker->update();
@@ -2147,17 +2272,10 @@ namespace gsr {
} else {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
Window x11_cursor_window = None;
mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window);
const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display);
auto monitors = get_monitors(display);
const Monitor *focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value);
if(focused_monitor)
focused_monitor_name = focused_monitor->name;
focused_monitor_name = get_focused_monitor_by_cursor(cursor_tracker.get(), gsr_info, get_monitors(display));
}
focused_monitor_name = get_valid_capture_target(focused_monitor_name, capture_options);
if(!focused_monitor_name.empty())
return focused_monitor_name;
else if(!capture_options.monitors.empty())
@@ -2210,8 +2328,8 @@ namespace gsr {
kill(gpu_screen_recorder_process, SIGRTMIN+5);
}
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_region_selection) {
if(region_selector.is_started())
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) {
if(region_selector.is_started() || window_selector.is_started())
return false;
switch(recording_status) {
@@ -2261,7 +2379,7 @@ namespace gsr {
return false;
}
if(config.replay_config.record_options.record_area_option == "region" && !finished_region_selection) {
if(config.replay_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true;
on_region_selected = [disable_notification, this]() {
on_press_start_replay(disable_notification, true);
@@ -2269,6 +2387,14 @@ namespace gsr {
return false;
}
if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) {
start_window_capture = true;
on_window_selected = [disable_notification, this]() {
on_press_start_replay(disable_notification, true);
};
return false;
}
// TODO: Validate input, fallback to valid values
const std::string fps = std::to_string(config.replay_config.record_options.fps);
const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate);
@@ -2348,18 +2474,15 @@ namespace gsr {
// to see when the program has exit.
if(!disable_notification && config.replay_config.show_replay_started_notifications) {
char msg[256];
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Started replaying this monitor");
else
snprintf(msg, sizeof(msg), "Started replaying %s", recording_capture_target.c_str());
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
}
return true;
}
void Overlay::on_press_start_record(bool finished_region_selection) {
if(region_selector.is_started())
void Overlay::on_press_start_record(bool finished_selection) {
if(region_selector.is_started() || window_selector.is_started())
return;
switch(recording_status) {
@@ -2435,7 +2558,7 @@ namespace gsr {
return;
}
if(config.record_config.record_options.record_area_option == "region" && !finished_region_selection) {
if(config.record_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true;
on_region_selected = [this]() {
on_press_start_record(true);
@@ -2443,6 +2566,14 @@ namespace gsr {
return;
}
if(config.record_config.record_options.record_area_option == "window" && !finished_selection) {
start_window_capture = true;
on_window_selected = [this]() {
on_press_start_record(true);
};
return;
}
record_filepath.clear();
// TODO: Validate input, fallback to valid values
@@ -2505,10 +2636,7 @@ namespace gsr {
// 1...
if(config.record_config.show_recording_started_notifications) {
char msg[256];
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Started recording this monitor");
else
snprintf(msg, sizeof(msg), "Started recording %s", recording_capture_target.c_str());
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
}
}
@@ -2521,6 +2649,9 @@ namespace gsr {
} else if(config.streaming_config.streaming_service == "youtube") {
url += "rtmp://a.rtmp.youtube.com/live2/";
url += config.streaming_config.youtube.stream_key;
} else if(config.streaming_config.streaming_service == "rumble") {
url += "rtmp://rtmp.rumble.com/live/";
url += config.streaming_config.rumble.stream_key;
} else if(config.streaming_config.streaming_service == "custom") {
url = config.streaming_config.custom.url;
if(url.size() >= 7 && strncmp(url.c_str(), "rtmp://", 7) == 0)
@@ -2545,8 +2676,8 @@ namespace gsr {
return url;
}
void Overlay::on_press_start_stream(bool finished_region_selection) {
if(region_selector.is_started())
void Overlay::on_press_start_stream(bool finished_selection) {
if(region_selector.is_started() || window_selector.is_started())
return;
switch(recording_status) {
@@ -2592,7 +2723,7 @@ namespace gsr {
return;
}
if(config.streaming_config.record_options.record_area_option == "region" && !finished_region_selection) {
if(config.streaming_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true;
on_region_selected = [this]() {
on_press_start_stream(true);
@@ -2600,6 +2731,14 @@ namespace gsr {
return;
}
if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) {
start_window_capture = true;
on_window_selected = [this]() {
on_press_start_stream(true);
};
return;
}
// TODO: Validate input, fallback to valid values
const std::string fps = std::to_string(config.streaming_config.record_options.fps);
const std::string video_bitrate = std::to_string(config.streaming_config.record_options.video_bitrate);
@@ -2675,16 +2814,13 @@ namespace gsr {
// to see when the program has exit.
if(config.streaming_config.show_streaming_started_notifications) {
char msg[256];
if(is_capture_target_monitor(recording_capture_target.c_str()))
snprintf(msg, sizeof(msg), "Started streaming this monitor");
else
snprintf(msg, sizeof(msg), "Started streaming %s", recording_capture_target.c_str());
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
}
}
void Overlay::on_press_take_screenshot(bool finished_region_selection, bool force_region_capture) {
if(region_selector.is_started())
void Overlay::on_press_take_screenshot(bool finished_selection, bool force_region_capture) {
if(region_selector.is_started() || window_selector.is_started())
return;
if(gpu_screen_recorder_screenshot_process > 0) {
@@ -2703,7 +2839,7 @@ namespace gsr {
return;
}
if(region_capture && !finished_region_selection) {
if(region_capture && !finished_selection) {
start_region_capture = true;
on_region_selected = [this, force_region_capture]() {
usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this
@@ -2712,6 +2848,14 @@ namespace gsr {
return;
}
if(config.screenshot_config.record_area_option == "window" && !finished_selection) {
start_window_capture = true;
on_window_selected = [this, force_region_capture]() {
on_press_take_screenshot(true, force_region_capture);
};
return;
}
// TODO: Validate input, fallback to valid values
const std::string output_file = config.screenshot_config.save_directory + "/Screenshot_" + get_date_str() + "." + config.screenshot_config.image_format; // TODO: Validate image format

View File

@@ -208,7 +208,7 @@ namespace gsr {
window_attr.background_pixel = is_wayland ? 0 : border_color_x11;
window_attr.border_pixel = 0;
window_attr.override_redirect = true;
window_attr.event_mask = StructureNotifyMask | PointerMotionMask;
window_attr.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
window_attr.colormap = region_window_colormap;
Screen *screen = XDefaultScreenOfDisplay(dpy);
@@ -366,10 +366,6 @@ namespace gsr {
return true;
}
bool RegionSelector::is_selected() const {
return selected;
}
bool RegionSelector::take_selection() {
const bool result = selected;
selected = false;

View File

@@ -63,31 +63,34 @@ namespace gsr {
if(!theme->title_font_file.load((resources_path + "fonts/NotoSans-Bold.ttf").c_str(), mgl::MemoryMappedFile::LoadOptions{true, false}))
goto error;
if(!theme->combobox_arrow_texture.load_from_file((resources_path + "images/combobox_arrow.png").c_str()))
if(!theme->combobox_arrow_texture.load_from_file((resources_path + "images/combobox_arrow.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->settings_texture.load_from_file((resources_path + "images/settings.png").c_str()))
goto error;
if(!theme->settings_small_texture.load_from_file((resources_path + "images/settings_small.png").c_str()))
if(!theme->settings_small_texture.load_from_file((resources_path + "images/settings_small.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str()))
if(!theme->settings_extra_small_texture.load_from_file((resources_path + "images/settings_extra_small.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->up_arrow_texture.load_from_file((resources_path + "images/up_arrow.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->replay_button_texture.load_from_file((resources_path + "images/replay.png").c_str()))
if(!theme->replay_button_texture.load_from_file((resources_path + "images/replay.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->record_button_texture.load_from_file((resources_path + "images/record.png").c_str()))
if(!theme->record_button_texture.load_from_file((resources_path + "images/record.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->stream_button_texture.load_from_file((resources_path + "images/stream.png").c_str()))
if(!theme->stream_button_texture.load_from_file((resources_path + "images/stream.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->close_texture.load_from_file((resources_path + "images/cross.png").c_str()))
if(!theme->close_texture.load_from_file((resources_path + "images/cross.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->logo_texture.load_from_file((resources_path + "images/gpu_screen_recorder_logo.png").c_str()))
@@ -99,19 +102,19 @@ namespace gsr {
if(!theme->checkbox_background_texture.load_from_file((resources_path + "images/checkbox_background.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->play_texture.load_from_file((resources_path + "images/play.png").c_str()))
if(!theme->play_texture.load_from_file((resources_path + "images/play.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->stop_texture.load_from_file((resources_path + "images/stop.png").c_str()))
if(!theme->stop_texture.load_from_file((resources_path + "images/stop.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->pause_texture.load_from_file((resources_path + "images/pause.png").c_str()))
if(!theme->pause_texture.load_from_file((resources_path + "images/pause.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str()))
if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str()))
if(!theme->screenshot_texture.load_from_file((resources_path + "images/screenshot.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
goto error;
if(!theme->trash_texture.load_from_file((resources_path + "images/trash.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))

View File

@@ -27,6 +27,33 @@ namespace gsr {
return str.size() >= len && memcmp(str.data(), substr, len) == 0;
}
bool ends_with(std::string_view str, const char *substr) {
size_t len = strlen(substr);
return str.size() >= len && memcmp(str.data() + str.size() - len, substr, len) == 0;
}
std::string strip(const std::string &str) {
int start_index = 0;
int str_len = str.size();
for(int i = 0; i < str_len; ++i) {
if(str[i] != ' ') {
start_index += i;
str_len -= i;
break;
}
}
for(int i = str_len - 1; i >= 0; --i) {
if(str[i] != ' ') {
str_len = i + 1;
break;
}
}
return str.substr(start_index, str_len);
}
std::string get_home_dir() {
const char *home_dir = getenv("HOME");
if(!home_dir) {

229
src/WindowSelector.cpp Normal file
View File

@@ -0,0 +1,229 @@
#include "../include/WindowSelector.hpp"
#include "../include/WindowUtils.hpp"
#include <stdio.h>
#include <string.h>
#include <X11/extensions/shape.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
namespace gsr {
static const int rectangle_border_size = 2;
static int max_int(int a, int b) {
return a >= b ? a : b;
}
static void set_region_rectangle(Display *dpy, Window window, int x, int y, int width, int height, int border_size) {
if(width < 0) {
x += width;
width = abs(width);
}
if(height < 0) {
y += height;
height = abs(height);
}
XRectangle rectangles[] = {
{
(short)max_int(0, x), (short)max_int(0, y),
(unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height)
}, // Left
{
(short)max_int(0, x + width - border_size), (short)max_int(0, y),
(unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height)
}, // Right
{
(short)max_int(0, x + border_size), (short)max_int(0, y),
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
}, // Top
{
(short)max_int(0, x + border_size), (short)max_int(0, y + height - border_size),
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
}, // Bottom
};
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted);
XFlush(dpy);
}
static unsigned long mgl_color_to_x11_color(mgl::Color color) {
if(color.a == 0)
return 0;
return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
}
static Window get_cursor_window(Display *dpy) {
Window root_window = None;
Window window = None;
int dummy_i;
unsigned int dummy_u;
mgl::vec2i root_pos;
XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
return window;
}
static void get_window_geometry(Display *dpy, Window window, mgl::vec2i &pos, mgl::vec2i &size) {
Window root_window;
int x = 0;
int y = 0;
unsigned int w = 0;
unsigned int h = 0;
unsigned int dummy_border, dummy_depth;
XGetGeometry(dpy, window, &root_window, &x, &y, &w, &h, &dummy_border, &dummy_depth);
pos.x = x;
pos.y = y;
size.x = w;
size.y = h;
}
WindowSelector::WindowSelector() {
}
WindowSelector::~WindowSelector() {
stop();
}
bool WindowSelector::start(mgl::Color border_color) {
if(dpy)
return false;
const unsigned long border_color_x11 = mgl_color_to_x11_color(border_color);
dpy = XOpenDisplay(nullptr);
if(!dpy) {
fprintf(stderr, "Error: WindowSelector::start: failed to connect to the X11 server\n");
return false;
}
const Window cursor_window = get_cursor_window(dpy);
mgl::vec2i cursor_window_pos, cursor_window_size;
get_window_geometry(dpy, cursor_window, cursor_window_pos, cursor_window_size);
XVisualInfo vinfo;
memset(&vinfo, 0, sizeof(vinfo));
XMatchVisualInfo(dpy, DefaultScreen(dpy), 32, TrueColor, &vinfo);
border_window_colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo.visual, AllocNone);
XSetWindowAttributes window_attr;
window_attr.background_pixel = border_color_x11;
window_attr.border_pixel = 0;
window_attr.override_redirect = true;
window_attr.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
window_attr.colormap = border_window_colormap;
Screen *screen = XDefaultScreenOfDisplay(dpy);
border_window = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, XWidthOfScreen(screen), XHeightOfScreen(screen), 0,
vinfo.depth, InputOutput, vinfo.visual, CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWEventMask | CWColormap, &window_attr);
if(!border_window) {
fprintf(stderr, "Error: WindowSelector::start: failed to create region window\n");
stop();
return false;
}
set_window_size_not_resizable(dpy, border_window, XWidthOfScreen(screen), XHeightOfScreen(screen));
if(cursor_window && cursor_window != DefaultRootWindow(dpy))
set_region_rectangle(dpy, border_window, cursor_window_pos.x, cursor_window_pos.y, cursor_window_size.x, cursor_window_size.y, rectangle_border_size);
else
set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0);
make_window_click_through(dpy, border_window);
XMapWindow(dpy, border_window);
crosshair_cursor = XCreateFontCursor(dpy, XC_crosshair);
XGrabPointer(dpy, DefaultRootWindow(dpy), True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, crosshair_cursor, CurrentTime);
XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime);
XFlush(dpy);
selected = false;
canceled = false;
selected_window = None;
return true;
}
void WindowSelector::stop() {
if(!dpy)
return;
XUngrabPointer(dpy, CurrentTime);
XUngrabKeyboard(dpy, CurrentTime);
if(border_window_colormap) {
XFreeColormap(dpy, border_window_colormap);
border_window_colormap = 0;
}
if(border_window) {
XDestroyWindow(dpy, border_window);
border_window = 0;
}
if(crosshair_cursor) {
XFreeCursor(dpy, crosshair_cursor);
crosshair_cursor = None;
}
XFlush(dpy);
XCloseDisplay(dpy);
dpy = nullptr;
}
bool WindowSelector::is_started() const {
return dpy != nullptr;
}
bool WindowSelector::failed() const {
return !dpy;
}
bool WindowSelector::poll_events() {
if(!dpy || selected)
return false;
XEvent xev;
while(XPending(dpy)) {
XNextEvent(dpy, &xev);
if(xev.type == MotionNotify) {
const Window motion_window = xev.xmotion.subwindow;
mgl::vec2i motion_window_pos, motion_window_size;
get_window_geometry(dpy, motion_window, motion_window_pos, motion_window_size);
if(motion_window && motion_window != DefaultRootWindow(dpy))
set_region_rectangle(dpy, border_window, motion_window_pos.x, motion_window_pos.y, motion_window_size.x, motion_window_size.y, rectangle_border_size);
else
set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0);
XFlush(dpy);
} else if(xev.type == ButtonRelease && xev.xbutton.button == Button1) {
selected_window = xev.xbutton.subwindow;
const Window clicked_window_real = window_get_target_window_child(dpy, selected_window);
if(clicked_window_real)
selected_window = clicked_window_real;
selected = true;
stop();
break;
} else if(xev.type == KeyRelease && XKeycodeToKeysym(dpy, xev.xkey.keycode, 0) == XK_Escape) {
canceled = true;
selected = false;
stop();
break;
}
}
return true;
}
bool WindowSelector::take_selection() {
const bool result = selected;
selected = false;
return result;
}
bool WindowSelector::take_canceled() {
const bool result = canceled;
canceled = false;
return result;
}
Window WindowSelector::get_selection() const {
return selected_window;
}
}

View File

@@ -1,4 +1,5 @@
#include "../include/WindowUtils.hpp"
#include "../include/Utils.hpp"
#include <X11/Xatom.h>
#include <X11/Xutil.h>
@@ -62,7 +63,7 @@ namespace gsr {
return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom);
}
static Window window_get_target_window_child(Display *display, Window window) {
Window window_get_target_window_child(Display *display, Window window) {
if(window == None)
return None;
@@ -212,28 +213,6 @@ namespace gsr {
return result;
}
static std::string strip(const std::string &str) {
int start_index = 0;
int str_len = str.size();
for(int i = 0; i < str_len; ++i) {
if(str[i] != ' ') {
start_index += i;
str_len -= i;
break;
}
}
for(int i = str_len - 1; i >= 0; --i) {
if(str[i] != ' ') {
str_len = i + 1;
break;
}
}
return str.substr(start_index, str_len);
}
std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type) {
std::string result;
const Window focused_window = get_focused_window(dpy, window_capture_type);

View File

@@ -85,7 +85,7 @@ namespace gsr {
void ComboBox::add_item(const std::string &text, const std::string &id) {
items.push_back({mgl::Text(text, *font), id, {0.0f, 0.0f}});
items.back().text.set_max_width(font->get_character_size() * 22); // TODO: Make a proper solution
items.back().text.set_max_width(font->get_character_size() * 20); // TODO: Make a proper solution
//items.back().text.set_max_rows(1);
dirty = true;
}

View File

@@ -419,10 +419,10 @@ namespace gsr {
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_options_texture, get_theme().body_font.get_character_size(), "to show/hide the UI"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_up_texture, get_theme().body_font.get_character_size(), "to take a screenshot"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_down_texture, get_theme().body_font.get_character_size(), "to save a replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_cross_texture, get_theme().body_font.get_character_size(), "to save a 1 minute replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_triangle_texture, get_theme().body_font.get_character_size(), "to save a 10 minute replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_left_texture, get_theme().body_font.get_character_size(), "to start/stop recording"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_right_texture, get_theme().body_font.get_character_size(), "to turn replay on/off"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_cross_texture, get_theme().body_font.get_character_size(), "to save a 1 minute replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_triangle_texture, get_theme().body_font.get_character_size(), "to save a 10 minute replay"));
return subsection;
}

View File

@@ -35,9 +35,8 @@ namespace gsr {
std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_record_area_box() {
auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font);
// TODO: Show options not supported but disable them
// TODO: Enable this
//if(capture_options.window)
// record_area_box->add_item("Window", "window");
if(capture_options.window)
record_area_box->add_item("Window", "window");
if(capture_options.region)
record_area_box->add_item("Region", "region");
if(!capture_options.monitors.empty())
@@ -60,14 +59,6 @@ namespace gsr {
return record_area_list;
}
std::unique_ptr<List> ScreenshotSettingsPage::create_select_window() {
auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL);
select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color));
select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)));
select_window_list_ptr = select_window_list.get();
return select_window_list;
}
std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_width_entry() {
auto image_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3);
image_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15);
@@ -124,7 +115,6 @@ namespace gsr {
auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
capture_target_list->add_widget(create_record_area());
capture_target_list->add_widget(create_select_window());
capture_target_list->add_widget(create_image_resolution_section());
capture_target_list->add_widget(create_restore_portal_session_section());
@@ -258,9 +248,7 @@ namespace gsr {
content_page_ptr->add_widget(create_settings());
record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
const bool window_selected = id == "window";
const bool portal_selected = id == "portal";
select_window_list_ptr->set_visible(window_selected);
image_resolution_list_ptr->set_visible(change_image_resolution_checkbox_ptr->is_checked());
restore_portal_session_list_ptr->set_visible(portal_selected);
return true;

View File

@@ -65,13 +65,12 @@ namespace gsr {
std::unique_ptr<ComboBox> SettingsPage::create_record_area_box() {
auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font);
// TODO: Show options not supported but disable them
// TODO: Enable this
//if(capture_options.window)
// record_area_box->add_item("Window", "window");
if(capture_options.region)
record_area_box->add_item("Region", "region");
if(capture_options.window)
record_area_box->add_item("Window", "window");
if(capture_options.focused)
record_area_box->add_item("Follow focused window", "focused");
if(capture_options.region)
record_area_box->add_item("Region", "region");
if(!capture_options.monitors.empty())
record_area_box->add_item(gsr_info->system_info.display_server == DisplayServer::WAYLAND ? "Focused monitor (Experimental on Wayland)" : "Focused monitor", "focused_monitor");
for(const auto &monitor : capture_options.monitors) {
@@ -92,14 +91,6 @@ namespace gsr {
return record_area_list;
}
std::unique_ptr<List> SettingsPage::create_select_window() {
auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL);
select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color));
select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)));
select_window_list_ptr = select_window_list.get();
return select_window_list;
}
std::unique_ptr<Entry> SettingsPage::create_area_width_entry() {
auto area_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3);
area_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15);
@@ -186,7 +177,6 @@ namespace gsr {
auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
capture_target_list->add_widget(create_record_area());
capture_target_list->add_widget(create_select_window());
capture_target_list->add_widget(create_area_size_section());
capture_target_list->add_widget(create_video_resolution_section());
capture_target_list->add_widget(create_restore_portal_session_section());
@@ -196,10 +186,20 @@ namespace gsr {
return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<ComboBox> SettingsPage::create_audio_device_selection_combobox() {
static bool audio_device_is_output(const std::string &audio_device_id) {
return audio_device_id == "default_output" || ends_with(audio_device_id, ".monitor");
}
std::unique_ptr<ComboBox> SettingsPage::create_audio_device_selection_combobox(AudioDeviceType device_type) {
auto audio_device_box = std::make_unique<ComboBox>(&get_theme().body_font);
for(const auto &audio_device : audio_devices) {
audio_device_box->add_item(audio_device.description, audio_device.name);
const bool device_is_output = audio_device_is_output(audio_device.name);
if((device_type == AudioDeviceType::OUTPUT && device_is_output) || (device_type == AudioDeviceType::INPUT && !device_is_output)) {
std::string description = audio_device.description;
if(starts_with(description, "Monitor of "))
description.erase(0, 11);
audio_device_box->add_item(description, audio_device.name);
}
}
return audio_device_box;
}
@@ -211,7 +211,7 @@ namespace gsr {
List *audio_track_items_list = dynamic_cast<List*>(audio_track_subsection->get_inner_widget());
List *buttons_list = dynamic_cast<List*>(audio_track_items_list->get_child_widget_by_index(1));
Button *add_application_audio_button = dynamic_cast<Button*>(buttons_list->get_child_widget_by_index(1));
Button *add_application_audio_button = dynamic_cast<Button*>(buttons_list->get_child_widget_by_index(2));
add_application_audio_button->set_visible(visible);
CheckBox *invert_app_audio_checkbox = dynamic_cast<CheckBox*>(audio_track_items_list->get_child_widget_by_index(3));
@@ -236,11 +236,11 @@ namespace gsr {
return remove_audio_track_button;
}
std::unique_ptr<List> SettingsPage::create_audio_device(List *audio_input_list_ptr) {
std::unique_ptr<List> SettingsPage::create_audio_device(AudioDeviceType device_type, List *audio_input_list_ptr) {
auto audio_device_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
audio_device_list->userdata = (void*)(uintptr_t)AudioTrackType::DEVICE;
audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Device:", get_color_theme().text_color));
audio_device_list->add_widget(create_audio_device_selection_combobox());
audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, device_type == AudioDeviceType::OUTPUT ? "Output device:" : "Input device: ", get_color_theme().text_color));
audio_device_list->add_widget(create_audio_device_selection_combobox(device_type));
audio_device_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, audio_device_list.get()));
return audio_device_list;
}
@@ -254,13 +254,22 @@ namespace gsr {
return button;
}
std::unique_ptr<Button> SettingsPage::create_add_audio_device_button(List *audio_input_list_ptr) {
auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add audio device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
add_audio_track_button->on_click = [this, 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));
button->on_click = [this, audio_input_list_ptr]() {
audio_devices = get_audio_devices();
audio_input_list_ptr->add_widget(create_audio_device(audio_input_list_ptr));
audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::OUTPUT, audio_input_list_ptr));
};
return add_audio_track_button;
return button;
}
std::unique_ptr<Button> SettingsPage::create_add_audio_input_device_button(List *audio_input_list_ptr) {
auto button = std::make_unique<Button>(&get_theme().body_font, "Add input device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
button->on_click = [this, audio_input_list_ptr]() {
audio_devices = get_audio_devices();
audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::INPUT, audio_input_list_ptr));
};
return button;
}
std::unique_ptr<ComboBox> SettingsPage::create_application_audio_selection_combobox(List *application_audio_row) {
@@ -285,7 +294,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_application_audio(List *audio_input_list_ptr) {
auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION;
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "App: ", get_color_theme().text_color));
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color));
application_audio_list->add_widget(create_application_audio_selection_combobox(application_audio_list.get()));
application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get()));
return application_audio_list;
@@ -294,7 +303,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_custom_application_audio(List *audio_input_list_ptr) {
auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION_CUSTOM;
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "App: ", get_color_theme().text_color));
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color));
application_audio_list->add_widget(std::make_unique<Entry>(&get_theme().body_font, "", (int)(get_theme().body_font.get_character_size() * 10.0f)));
application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get()));
return application_audio_list;
@@ -314,7 +323,8 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_add_audio_buttons(List *audio_input_list_ptr) {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(create_add_audio_device_button(audio_input_list_ptr));
list->add_widget(create_add_audio_output_device_button(audio_input_list_ptr));
list->add_widget(create_add_audio_input_device_button(audio_input_list_ptr));
list->add_widget(create_add_application_audio_button(audio_input_list_ptr));
return list;
}
@@ -431,13 +441,13 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_video_bitrate_entry() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "15000", (int)(get_theme().body_font.get_character_size() * 4.0f));
auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "8000", (int)(get_theme().body_font.get_character_size() * 4.0f));
video_bitrate_entry->validate_handler = create_entry_validator_integer_in_range(1, 500000);
video_bitrate_entry_ptr = video_bitrate_entry.get();
list->add_widget(std::move(video_bitrate_entry));
if(type == Type::STREAM) {
auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "1.64MB", get_color_theme().text_color);
auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "", get_color_theme().text_color);
Label *size_mb_label_ptr = size_mb_label.get();
list->add_widget(std::move(size_mb_label));
@@ -614,10 +624,8 @@ namespace gsr {
content_page_ptr->add_widget(create_settings());
record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
const bool window_selected = id == "window";
const bool focused_selected = id == "focused";
const bool portal_selected = id == "portal";
select_window_list_ptr->set_visible(window_selected);
area_size_list_ptr->set_visible(focused_selected);
video_resolution_list_ptr->set_visible(!focused_selected && change_video_resolution_checkbox_ptr->is_checked());
change_video_resolution_checkbox_ptr->set_visible(!focused_selected);
@@ -715,7 +723,7 @@ namespace gsr {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
auto replay_time_entry = std::make_unique<Entry>(&get_theme().body_font, "60", get_theme().body_font.get_character_size() * 3);
replay_time_entry->validate_handler = create_entry_validator_integer_in_range(2, 86400);
replay_time_entry->validate_handler = create_entry_validator_integer_in_range(1, 86400);
replay_time_entry_ptr = replay_time_entry.get();
list->add_widget(std::move(replay_time_entry));
@@ -739,7 +747,7 @@ namespace gsr {
auto replay_storage_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
replay_storage_button_ptr = replay_storage_button.get();
replay_storage_button->add_item("RAM", "ram");
replay_storage_button->add_item("Disk (not recommended on SSDs)", "disk");
replay_storage_button->add_item("Disk (Not recommended on SSDs)", "disk");
replay_storage_button->on_selection_changed = [this](const std::string&, const std::string &id) {
update_estimated_replay_file_size(id);
@@ -920,6 +928,11 @@ namespace gsr {
show_video_saved_notification_checkbox_ptr = show_video_saved_notification_checkbox.get();
checkboxes_list->add_widget(std::move(show_video_saved_notification_checkbox));
auto show_video_paused_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show video paused/unpaused notification");
show_video_paused_notification_checkbox->set_checked(true);
show_video_paused_notification_checkbox_ptr = show_video_paused_notification_checkbox.get();
checkboxes_list->add_widget(std::move(show_video_paused_notification_checkbox));
auto notifications_subsection = std::make_unique<Subsection>("Notifications", std::move(checkboxes_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
Subsection *notifications_subsection_ptr = notifications_subsection.get();
settings_list_ptr->add_widget(std::move(notifications_subsection));
@@ -939,6 +952,7 @@ namespace gsr {
auto streaming_service_box = std::make_unique<ComboBox>(&get_theme().body_font);
streaming_service_box->add_item("Twitch", "twitch");
streaming_service_box->add_item("YouTube", "youtube");
streaming_service_box->add_item("Rumble", "rumble");
streaming_service_box->add_item("Custom", "custom");
streaming_service_box_ptr = streaming_service_box.get();
return streaming_service_box;
@@ -963,6 +977,10 @@ namespace gsr {
youtube_stream_key_entry_ptr = youtube_stream_key_entry.get();
stream_key_list->add_widget(std::move(youtube_stream_key_entry));
auto rumble_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
rumble_stream_key_entry_ptr = rumble_stream_key_entry.get();
stream_key_list->add_widget(std::move(rumble_stream_key_entry));
stream_key_list_ptr = stream_key_list.get();
return stream_key_list;
}
@@ -1024,12 +1042,14 @@ namespace gsr {
streaming_service_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
const bool twitch_option = id == "twitch";
const bool youtube_option = id == "youtube";
const bool rumble_option = id == "rumble";
const bool custom_option = id == "custom";
stream_key_list_ptr->set_visible(!custom_option);
stream_url_list_ptr->set_visible(custom_option);
container_list_ptr->set_visible(custom_option);
twitch_stream_key_entry_ptr->set_visible(twitch_option);
youtube_stream_key_entry_ptr->set_visible(youtube_option);
rumble_stream_key_entry_ptr->set_visible(rumble_option);
return true;
};
streaming_service_box_ptr->on_selection_changed("Twitch", "twitch");
@@ -1115,12 +1135,15 @@ namespace gsr {
audio_input_list_ptr->add_widget(std::move(application_audio_widget));
}
} else if(starts_with(audio_input, "device:")) {
std::unique_ptr<List> audio_track_widget = create_audio_device(audio_input_list_ptr);
const std::string device_name = audio_input.substr(7);
const AudioDeviceType audio_device_type = audio_device_is_output(device_name) ? AudioDeviceType::OUTPUT : AudioDeviceType::INPUT;
std::unique_ptr<List> audio_track_widget = create_audio_device(audio_device_type, audio_input_list_ptr);
ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1));
audio_device_box->set_selected_item(audio_input.substr(7));
audio_device_box->set_selected_item(device_name);
audio_input_list_ptr->add_widget(std::move(audio_track_widget));
} else {
std::unique_ptr<List> audio_track_widget = create_audio_device(audio_input_list_ptr);
const AudioDeviceType audio_device_type = audio_device_is_output(audio_input) ? AudioDeviceType::OUTPUT : AudioDeviceType::INPUT;
std::unique_ptr<List> audio_track_widget = create_audio_device(audio_device_type, audio_input_list_ptr);
ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1));
audio_device_box->set_selected_item(audio_input);
audio_input_list_ptr->add_widget(std::move(audio_track_widget));
@@ -1216,6 +1239,7 @@ namespace gsr {
save_recording_in_game_folder_ptr->set_checked(config.record_config.save_video_in_game_folder);
show_recording_started_notification_checkbox_ptr->set_checked(config.record_config.show_recording_started_notifications);
show_video_saved_notification_checkbox_ptr->set_checked(config.record_config.show_video_saved_notifications);
show_video_paused_notification_checkbox_ptr->set_checked(config.record_config.show_video_paused_notifications);
save_directory_button_ptr->set_text(config.record_config.save_directory);
container_box_ptr->set_selected_item(config.record_config.container);
}
@@ -1227,6 +1251,7 @@ namespace gsr {
streaming_service_box_ptr->set_selected_item(config.streaming_config.streaming_service);
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);
rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key);
stream_url_entry_ptr->set_text(config.streaming_config.custom.url);
container_box_ptr->set_selected_item(config.streaming_config.custom.container);
}
@@ -1356,6 +1381,7 @@ namespace gsr {
config.record_config.save_video_in_game_folder = save_recording_in_game_folder_ptr->is_checked();
config.record_config.show_recording_started_notifications = show_recording_started_notification_checkbox_ptr->is_checked();
config.record_config.show_video_saved_notifications = show_video_saved_notification_checkbox_ptr->is_checked();
config.record_config.show_video_paused_notifications = show_video_paused_notification_checkbox_ptr->is_checked();
config.record_config.save_directory = save_directory_button_ptr->get_text();
config.record_config.container = container_box_ptr->get_selected_id();
}
@@ -1367,6 +1393,7 @@ namespace gsr {
config.streaming_config.streaming_service = streaming_service_box_ptr->get_selected_id();
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.rumble.stream_key = rumble_stream_key_entry_ptr->get_text();
config.streaming_config.custom.url = stream_url_entry_ptr->get_text();
config.streaming_config.custom.container = container_box_ptr->get_selected_id();
}

View File

@@ -69,7 +69,7 @@ static void keyboard_event_fetch_update_key_states(keyboard_event *self, event_e
if(ioctl(fd, EVIOCGKEY(KEY_STATES_SIZE), extra_data->key_states) == -1)
fprintf(stderr, "Warning: failed to fetch key states for device: /dev/input/event%d\n", extra_data->dev_input_id);
if(!keyboard_event_has_exclusive_grab(self) || extra_data->grabbed)
if(!keyboard_event_has_exclusive_grab(self) || extra_data->grabbed || extra_data->is_non_keyboard_device)
return;
extra_data->num_keys_pressed = keyboard_event_get_num_keys_pressed(extra_data->key_states);
@@ -106,7 +106,7 @@ static void keyboard_event_process_key_state_change(keyboard_event *self, const
extra_data->key_states[byte_index] = key_byte_state;
if(!keyboard_event_has_exclusive_grab(self) || extra_data->grabbed)
if(!keyboard_event_has_exclusive_grab(self) || extra_data->grabbed || extra_data->is_non_keyboard_device)
return;
if(extra_data->num_keys_pressed == 0) {
@@ -193,7 +193,8 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
//fprintf(stderr, "fd: %d, type: %d, pressed %d, value: %d\n", fd, event.type, event.code, event.value);
//}
if(event.type == EV_KEY && is_keyboard_key(event.code)) {
const bool keyboard_key = is_keyboard_key(event.code);
if(event.type == EV_KEY && keyboard_key) {
keyboard_event_process_key_state_change(self, &event, extra_data, fd);
const uint32_t modifier_bit = keycode_to_modifier_bit(event.code);
if(modifier_bit == 0) {
@@ -214,6 +215,20 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
if(write(self->uinput_fd, &event, sizeof(event)) != sizeof(event))
fprintf(stderr, "Error: failed to write event data to virtual keyboard for exclusively grabbed device\n");
}
if(!extra_data->is_possibly_non_keyboard_device)
return;
/* TODO: What if some key is being pressed down while this is done? will it remain pressed down? */
if(!extra_data->is_non_keyboard_device && (event.type == EV_REL || event.type == EV_ABS || (event.type == EV_KEY && !keyboard_key))) {
fprintf(stderr, "Info: device /dev/input/event%d is likely a non-keyboard device as it received a non-keyboard event. This device will be ignored\n", extra_data->dev_input_id);
extra_data->is_non_keyboard_device = true;
if(extra_data->grabbed) {
extra_data->grabbed = false;
ioctl(fd, EVIOCGRAB, 0);
fprintf(stderr, "Info: ungrabbed device: /dev/input/event%d\n", extra_data->dev_input_id);
}
}
}
/* Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only */
@@ -292,6 +307,46 @@ static bool dev_input_is_virtual(int dev_input_id) {
return is_virtual;
}
static inline bool supports_key(unsigned char *key_bits, unsigned int key) {
return key_bits[key/8] & (1 << (key % 8));
}
static bool supports_keyboard_keys(unsigned char *key_bits) {
const int keys[2] = { KEY_A, KEY_ESC };
for(int i = 0; i < 2; ++i) {
if(supports_key(key_bits, keys[i]))
return true;
}
return false;
}
static bool supports_mouse_keys(unsigned char *key_bits) {
const int keys[2] = { BTN_MOUSE, BTN_LEFT };
for(int i = 0; i < 2; ++i) {
if(supports_key(key_bits, keys[i]))
return true;
}
return false;
}
static bool supports_joystick_keys(unsigned char *key_bits) {
const int keys[9] = { BTN_JOYSTICK, BTN_A, BTN_B, BTN_X, BTN_Y, BTN_SELECT, BTN_START, BTN_SELECT, BTN_TRIGGER_HAPPY1 };
for(int i = 0; i < 9; ++i) {
if(supports_key(key_bits, keys[i]))
return true;
}
return false;
}
static bool supports_wheel_keys(unsigned char *key_bits) {
const int keys[2] = { BTN_WHEEL, BTN_GEAR_DOWN };
for(int i = 0; i < 2; ++i) {
if(supports_key(key_bits, keys[i]))
return true;
}
return false;
}
static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, const char *dev_input_filepath) {
const int dev_input_id = get_dev_input_id_from_filepath(dev_input_filepath);
if(dev_input_id == -1)
@@ -320,15 +375,11 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
unsigned char key_bits[KEY_MAX/8 + 1] = {0};
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits);
const bool supports_key_a = key_bits[KEY_A/8] & (1 << (KEY_A % 8));
const bool supports_key_esc = key_bits[KEY_ESC/8] & (1 << (KEY_ESC % 8));
const bool supports_key_volume_up = key_bits[KEY_VOLUMEUP/8] & (1 << (KEY_VOLUMEUP % 8));
const bool supports_key_events = supports_key_a || supports_key_esc || supports_key_volume_up;
const bool supports_key_events = supports_keyboard_keys(key_bits);
const bool supports_mouse_events = supports_mouse_keys(key_bits);
const bool supports_joystick_events = supports_joystick_keys(key_bits);
const bool supports_wheel_events = supports_wheel_keys(key_bits);
const bool supports_mouse_events = key_bits[BTN_MOUSE/8] & (1 << (BTN_MOUSE % 8));
//const bool supports_touch_events = key_bits[BTN_TOUCH/8] & (1 << (BTN_TOUCH % 8));
const bool supports_joystick_events = key_bits[BTN_JOYSTICK/8] & (1 << (BTN_JOYSTICK % 8));
const bool supports_wheel_events = key_bits[BTN_WHEEL/8] & (1 << (BTN_WHEEL % 8));
if(supports_key_events && (is_virtual_device || (!supports_joystick_events && !supports_wheel_events))) {
unsigned char *key_states = calloc(1, KEY_STATES_SIZE);
unsigned char *key_presses_grabbed = calloc(1, KEY_STATES_SIZE);
@@ -349,6 +400,7 @@ static bool keyboard_event_try_add_device_if_keyboard(keyboard_event *self, cons
};
if(supports_mouse_events || supports_joystick_events || supports_wheel_events) {
self->event_extra_data[self->num_event_polls].is_possibly_non_keyboard_device = true;
fprintf(stderr, "Info: device not grabbed yet because it might be a mouse: /dev/input/event%d\n", dev_input_id);
fsync(fd);
if(ioctl(fd, EVIOCGKEY(KEY_STATES_SIZE), self->event_extra_data[self->num_event_polls].key_states) == -1)
@@ -441,6 +493,7 @@ static int setup_virtual_keyboard_input(const char *name) {
success &= (ioctl(fd, UI_SET_MSCBIT, MSC_SCAN) != -1);
for(int i = 1; i < KEY_MAX; ++i) {
// TODO: Check for joystick button? if we accidentally grab joystick
if(is_keyboard_key(i) || is_mouse_button(i))
success &= (ioctl(fd, UI_SET_KEYBIT, i) != -1);
}

View File

@@ -39,6 +39,8 @@ typedef enum {
typedef struct {
int dev_input_id;
bool grabbed;
bool is_non_keyboard_device;
bool is_possibly_non_keyboard_device;
unsigned char *key_states;
unsigned char *key_presses_grabbed;
int num_keys_pressed;

View File

@@ -44,42 +44,42 @@ int main(int argc, char **argv) {
} else if(strcmp(grab_type_arg, "--virtual") == 0) {
grab_type = KEYBOARD_GRAB_TYPE_VIRTUAL;
} else {
fprintf(stderr, "Error: expected --all or --virtual, got %s\n", grab_type_arg);
fprintf(stderr, "gsr-global-hotkeys error: expected --all or --virtual, got %s\n", grab_type_arg);
usage();
return 1;
}
} else if(argc != 1) {
fprintf(stderr, "Error: expected 0 or 1 arguments, got %d argument(s)\n", argc);
fprintf(stderr, "gsr-global-hotkeys error: expected 0 or 1 arguments, got %d argument(s)\n", argc);
usage();
return 1;
}
if(is_gsr_global_hotkeys_already_running()) {
fprintf(stderr, "Error: gsr-global-hotkeys is already running\n");
fprintf(stderr, "gsr-global-hotkeys error: gsr-global-hotkeys is already running\n");
return 1;
}
const uid_t user_id = getuid();
if(geteuid() != 0) {
if(setuid(0) == -1) {
fprintf(stderr, "Error: failed to change user to root\n");
fprintf(stderr, "gsr-global-hotkeys error: failed to change user to root, global hotkeys will not work. Make sure to set the correct capability on gsr-global-hotkeys\n");
return 1;
}
}
keyboard_event keyboard_ev;
if(!keyboard_event_init(&keyboard_ev, true, grab_type)) {
fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n");
fprintf(stderr, "gsr-global-hotkeys error: failed to setup hotplugging and no keyboard input devices were found\n");
setuid(user_id);
return 1;
}
fprintf(stderr, "Info: global hotkeys setup, waiting for hotkeys to be pressed\n");
fprintf(stderr, "gsr-global-hotkeys info: global hotkeys setup, waiting for hotkeys to be pressed\n");
for(;;) {
keyboard_event_poll_events(&keyboard_ev, -1);
if(keyboard_event_stdin_has_failed(&keyboard_ev)) {
fprintf(stderr, "Info: stdin closed (parent process likely closed this process), exiting...\n");
fprintf(stderr, "gsr-global-hotkeys info: stdin closed (parent process likely closed this process), exiting...\n");
break;
}
}