mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-08 12:24:52 +09:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f8b7061f | ||
|
|
3a4f03ce27 | ||
|
|
e8dc3859fe | ||
|
|
5fe5830056 | ||
|
|
9ac14c963e | ||
|
|
cae1c47643 | ||
|
|
de1ed58f8d | ||
|
|
ff564fcb52 | ||
|
|
aabe190bf1 | ||
|
|
320d368699 | ||
|
|
af4fc84ef7 | ||
|
|
c7bfaf90ec | ||
|
|
61f8c666fe | ||
|
|
28be9d1c6f | ||
|
|
305c9df7ac | ||
|
|
d08ea69277 | ||
|
|
180a3b73db | ||
|
|
ac1d57e8ba | ||
|
|
5a32c469d3 | ||
|
|
5a17aae0ab |
@@ -61,7 +61,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
37
TODO
@@ -147,8 +147,6 @@ 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.
|
||||
@@ -161,4 +159,37 @@ Support cjk font. Use fc-match to find the location of the font. This also works
|
||||
|
||||
Keyboard layout is incorrect on wayland when using kde plasma keyboard settings to setup multiple keyboards, for example when changing to french.
|
||||
Text input is correct, but hotkey is incorrect.
|
||||
Need to use "setxkbmap fr" as well.
|
||||
Need to use "setxkbmap fr" as well.
|
||||
This happens only when grabbing keyboard (gsr-global-hotkeys). Same thing is seen with xev.
|
||||
|
||||
Getting focused monitor on wayland doesn't work when vrr is enabled. This is because it uses software cursor instead (at least on kde plasma wayland).
|
||||
Right now it falls back to create window & getting window position trick if there is no cursor visible (or a software cursor) and one monitor has vrr enabled.
|
||||
Remove this when linux & wayland supports vrr with hardware cursor plane.
|
||||
Find out another way to get cursor position on wayland.
|
||||
This was fixed in linux 6.11 and in kde plasma in this commit: https://invent.kde.org/plasma/kwin/-/merge_requests/7582/diffs.
|
||||
|
||||
Add option to start recording/replay/stream after the notification has disappeared. Show "Starting recording on this monitor in 3 seconds".
|
||||
See if we can use hardware overlay plane instead somehow.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Description=GPU Screen Recorder UI Service
|
||||
|
||||
[Service]
|
||||
ExecStart=gsr-ui
|
||||
ExecStart=gsr-ui launch-daemon
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Description=GPU Screen Recorder UI Service
|
||||
|
||||
[Service]
|
||||
ExecStart=flatpak run com.dec05eba.gpu_screen_recorder gsr-ui
|
||||
ExecStart=flatpak run com.dec05eba.gpu_screen_recorder gsr-ui launch-daemon
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
images/settings_extra_small.png
Normal file
BIN
images/settings_extra_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 959 B |
@@ -100,6 +100,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;
|
||||
@@ -117,6 +118,7 @@ namespace gsr {
|
||||
std::string save_directory;
|
||||
std::string container = "mp4";
|
||||
int32_t replay_time = 60;
|
||||
std::string replay_storage = "ram";
|
||||
ConfigHotkey start_stop_hotkey;
|
||||
ConfigHotkey save_hotkey;
|
||||
ConfigHotkey save_1_min_hotkey;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "GlobalHotkeys.hpp"
|
||||
#include "Hotplug.hpp"
|
||||
#include "../Hotplug.hpp"
|
||||
#include <unordered_map>
|
||||
#include <thread>
|
||||
#include <poll.h>
|
||||
@@ -6,10 +6,10 @@
|
||||
#include "Config.hpp"
|
||||
#include "window_texture.h"
|
||||
#include "WindowUtils.hpp"
|
||||
#include "GlobalHotkeysJoystick.hpp"
|
||||
#include "GlobalHotkeys/GlobalHotkeysJoystick.hpp"
|
||||
#include "AudioPlayer.hpp"
|
||||
#include "RegionSelector.hpp"
|
||||
#include "CursorTracker.hpp"
|
||||
#include "CursorTracker/CursorTracker.hpp"
|
||||
|
||||
#include <mglpp/window/Window.hpp>
|
||||
#include <mglpp/window/Event.hpp>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,6 +15,7 @@ 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 get_home_dir();
|
||||
std::string get_config_dir();
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace gsr {
|
||||
|
||||
void add_item(const std::string &text, const std::string &id);
|
||||
void set_selected_item(const std::string &id, bool trigger_event = true, bool trigger_event_even_if_selection_not_changed = true);
|
||||
const std::string get_selected_id() const;
|
||||
const std::string& get_selected_id() const;
|
||||
const std::string& get_selected_text() const;
|
||||
|
||||
mgl::vec2f get_size() override;
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace gsr {
|
||||
class LineSeparator;
|
||||
class Subsection;
|
||||
|
||||
enum class AudioDeviceType {
|
||||
OUTPUT,
|
||||
INPUT
|
||||
};
|
||||
|
||||
class SettingsPage : public StaticPage {
|
||||
public:
|
||||
enum class Type {
|
||||
@@ -54,11 +59,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);
|
||||
@@ -97,11 +103,12 @@ namespace gsr {
|
||||
std::unique_ptr<List> create_container_section();
|
||||
std::unique_ptr<List> create_replay_time_entry();
|
||||
std::unique_ptr<List> create_replay_time();
|
||||
std::unique_ptr<List> create_replay_storage();
|
||||
std::unique_ptr<RadioButton> create_start_replay_automatically();
|
||||
std::unique_ptr<CheckBox> create_save_replay_in_game_folder();
|
||||
std::unique_ptr<CheckBox> create_restart_replay_on_save();
|
||||
std::unique_ptr<Label> create_estimated_replay_file_size();
|
||||
void update_estimated_replay_file_size();
|
||||
void update_estimated_replay_file_size(const std::string &replay_storage_type);
|
||||
void update_replay_time_text();
|
||||
std::unique_ptr<CheckBox> create_save_recording_in_game_folder();
|
||||
std::unique_ptr<Label> create_estimated_record_file_size();
|
||||
@@ -179,6 +186,7 @@ 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;
|
||||
@@ -186,6 +194,7 @@ namespace gsr {
|
||||
Entry *youtube_stream_key_entry_ptr = nullptr;
|
||||
Entry *stream_url_entry_ptr = nullptr;
|
||||
Entry *replay_time_entry_ptr = nullptr;
|
||||
RadioButton *replay_storage_button_ptr = nullptr;
|
||||
Label *replay_time_label_ptr = nullptr;
|
||||
RadioButton *turn_on_replay_automatically_mode_ptr = nullptr;
|
||||
Subsection *audio_section_ptr = nullptr;
|
||||
|
||||
14
meson.build
14
meson.build
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.5.0', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.6.4', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
|
||||
if get_option('buildtype') == 'debug'
|
||||
add_project_arguments('-g3', language : ['c', 'cpp'])
|
||||
@@ -32,6 +32,11 @@ src = [
|
||||
'src/gui/GlobalSettingsPage.cpp',
|
||||
'src/gui/GsrPage.cpp',
|
||||
'src/gui/Subsection.cpp',
|
||||
'src/GlobalHotkeys/GlobalHotkeysX11.cpp',
|
||||
'src/GlobalHotkeys/GlobalHotkeysLinux.cpp',
|
||||
'src/GlobalHotkeys/GlobalHotkeysJoystick.cpp',
|
||||
'src/CursorTracker/CursorTrackerX11.cpp',
|
||||
'src/CursorTracker/CursorTrackerWayland.cpp',
|
||||
'src/Utils.cpp',
|
||||
'src/WindowUtils.cpp',
|
||||
'src/RegionSelector.cpp',
|
||||
@@ -39,11 +44,6 @@ src = [
|
||||
'src/GsrInfo.cpp',
|
||||
'src/Process.cpp',
|
||||
'src/Overlay.cpp',
|
||||
'src/GlobalHotkeysX11.cpp',
|
||||
'src/GlobalHotkeysLinux.cpp',
|
||||
'src/GlobalHotkeysJoystick.cpp',
|
||||
'src/CursorTrackerX11.cpp',
|
||||
'src/CursorTrackerWayland.cpp',
|
||||
'src/AudioPlayer.cpp',
|
||||
'src/Hotplug.cpp',
|
||||
'src/Rpc.cpp',
|
||||
@@ -61,7 +61,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.4.0"', language: ['c', 'cpp'])
|
||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.6.0"', language: ['c', 'cpp'])
|
||||
|
||||
executable(
|
||||
meson.project_name(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.5.0"
|
||||
version = "1.6.4"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "../include/Config.hpp"
|
||||
#include "../include/Utils.hpp"
|
||||
#include "../include/GsrInfo.hpp"
|
||||
#include "../include/GlobalHotkeys.hpp"
|
||||
#include "../include/GlobalHotkeys/GlobalHotkeys.hpp"
|
||||
#include <variant>
|
||||
#include <limits.h>
|
||||
#include <inttypes.h>
|
||||
@@ -123,12 +123,12 @@ namespace gsr {
|
||||
|
||||
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;
|
||||
|
||||
@@ -229,6 +229,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},
|
||||
@@ -264,6 +265,7 @@ namespace gsr {
|
||||
{"replay.save_directory", &config.replay_config.save_directory},
|
||||
{"replay.container", &config.replay_config.container},
|
||||
{"replay.time", &config.replay_config.replay_time},
|
||||
{"replay.replay_storage", &config.replay_config.replay_storage},
|
||||
{"replay.start_stop_hotkey", &config.replay_config.start_stop_hotkey},
|
||||
{"replay.save_hotkey", &config.replay_config.save_hotkey},
|
||||
{"replay.save_1_min_hotkey", &config.replay_config.save_1_min_hotkey},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../include/CursorTrackerWayland.hpp"
|
||||
#include "../../include/CursorTracker/CursorTrackerWayland.hpp"
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
@@ -27,19 +27,20 @@ namespace gsr {
|
||||
typedef struct {
|
||||
uint64_t crtc_id;
|
||||
mgl::vec2i size;
|
||||
bool vrr_enabled;
|
||||
} drm_connector;
|
||||
|
||||
typedef struct {
|
||||
drm_connector connectors[MAX_CONNECTORS];
|
||||
int num_connectors;
|
||||
bool has_any_crtc_with_vrr_enabled;
|
||||
} drm_connectors;
|
||||
|
||||
/* Returns plane_property_mask */
|
||||
static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id, bool *is_cursor) {
|
||||
static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id) {
|
||||
*crtc_x = 0;
|
||||
*crtc_y = 0;
|
||||
*crtc_id = 0;
|
||||
*is_cursor = false;
|
||||
|
||||
uint32_t property_mask = 0;
|
||||
|
||||
@@ -80,8 +81,8 @@ namespace gsr {
|
||||
return property_mask;
|
||||
}
|
||||
|
||||
static bool connector_get_property_by_name(int drm_fd, drmModeConnectorPtr props, const char *name, uint64_t *result) {
|
||||
for(int i = 0; i < props->count_props; ++i) {
|
||||
static bool get_drm_property_by_name(int drm_fd, drmModeObjectPropertiesPtr props, const char *name, uint64_t *result) {
|
||||
for(uint32_t i = 0; i < props->count_props; ++i) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[i]);
|
||||
if(!prop)
|
||||
continue;
|
||||
@@ -96,6 +97,14 @@ namespace gsr {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool connector_get_property_by_name(int drm_fd, drmModeConnectorPtr props, const char *name, uint64_t *result) {
|
||||
drmModeObjectProperties properties;
|
||||
properties.count_props = (uint32_t)props->count_props;
|
||||
properties.props = props->props;
|
||||
properties.prop_values = props->prop_values;
|
||||
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)
|
||||
@@ -325,7 +334,7 @@ namespace gsr {
|
||||
};
|
||||
|
||||
/* Returns nullptr if not found */
|
||||
static const drm_connector* get_drm_connector_by_crtc_id(const drm_connectors *connectors, uint32_t crtc_id) {
|
||||
static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) {
|
||||
for(int i = 0; i < connectors->num_connectors; ++i) {
|
||||
if(connectors->connectors[i].crtc_id == crtc_id)
|
||||
return &connectors->connectors[i];
|
||||
@@ -335,6 +344,8 @@ namespace gsr {
|
||||
|
||||
static void get_drm_connectors(int drm_fd, drm_connectors *drm_connectors) {
|
||||
drm_connectors->num_connectors = 0;
|
||||
drm_connectors->has_any_crtc_with_vrr_enabled = false;
|
||||
|
||||
drmModeResPtr resources = drmModeGetResources(drm_fd);
|
||||
if(!resources)
|
||||
return;
|
||||
@@ -350,23 +361,59 @@ namespace gsr {
|
||||
uint64_t crtc_id = 0;
|
||||
connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &crtc_id);
|
||||
if(crtc_id == 0)
|
||||
goto next;
|
||||
goto next_connector;
|
||||
|
||||
crtc = drmModeGetCrtc(drm_fd, crtc_id);
|
||||
if(!crtc)
|
||||
goto next;
|
||||
goto next_connector;
|
||||
|
||||
drm_connectors->connectors[drm_connectors->num_connectors].crtc_id = crtc_id;
|
||||
drm_connectors->connectors[drm_connectors->num_connectors].size = mgl::vec2i{(int)crtc->width, (int)crtc->height};
|
||||
drm_connectors->connectors[drm_connectors->num_connectors].vrr_enabled = false;
|
||||
++drm_connectors->num_connectors;
|
||||
|
||||
next:
|
||||
next_connector:
|
||||
if(crtc)
|
||||
drmModeFreeCrtc(crtc);
|
||||
|
||||
if(connector)
|
||||
drmModeFreeConnector(connector);
|
||||
}
|
||||
|
||||
for(int i = 0; i < resources->count_crtcs; ++i) {
|
||||
drmModeCrtcPtr crtc = nullptr;
|
||||
drmModeObjectPropertiesPtr properties = nullptr;
|
||||
uint64_t vrr_enabled = 0;
|
||||
drm_connector *connector = nullptr;
|
||||
|
||||
crtc = drmModeGetCrtc(drm_fd, resources->crtcs[i]);
|
||||
if(!crtc)
|
||||
continue;
|
||||
|
||||
properties = drmModeObjectGetProperties(drm_fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC);
|
||||
if(!properties)
|
||||
goto next_crtc;
|
||||
|
||||
if(!get_drm_property_by_name(drm_fd, properties, "VRR_ENABLED", &vrr_enabled))
|
||||
goto next_crtc;
|
||||
|
||||
connector = get_drm_connector_by_crtc_id(drm_connectors, crtc->crtc_id);
|
||||
if(!connector)
|
||||
goto next_crtc;
|
||||
|
||||
if(vrr_enabled) {
|
||||
connector->vrr_enabled = true;
|
||||
drm_connectors->has_any_crtc_with_vrr_enabled = true;
|
||||
}
|
||||
|
||||
next_crtc:
|
||||
if(properties)
|
||||
drmModeFreeObjectProperties(properties);
|
||||
|
||||
if(crtc)
|
||||
drmModeFreeCrtc(crtc);
|
||||
}
|
||||
|
||||
drmModeFreeResources(resources);
|
||||
}
|
||||
|
||||
@@ -392,19 +439,20 @@ namespace gsr {
|
||||
|
||||
drm_connectors connectors;
|
||||
connectors.num_connectors = 0;
|
||||
connectors.has_any_crtc_with_vrr_enabled = false;
|
||||
get_drm_connectors(drm_fd, &connectors);
|
||||
|
||||
drmModePlaneResPtr planes = drmModeGetPlaneResources(drm_fd);
|
||||
if(!planes)
|
||||
return;
|
||||
|
||||
bool found_cursor = false;
|
||||
for(uint32_t i = 0; i < planes->count_planes; ++i) {
|
||||
drmModePlanePtr plane = nullptr;
|
||||
const drm_connector *connector = nullptr;
|
||||
int crtc_x = 0;
|
||||
int crtc_y = 0;
|
||||
int crtc_id = 0;
|
||||
bool is_cursor = false;
|
||||
uint32_t property_mask = 0;
|
||||
|
||||
plane = drmModeGetPlane(drm_fd, planes->planes[i]);
|
||||
@@ -414,7 +462,7 @@ namespace gsr {
|
||||
if(!plane->fb_id)
|
||||
goto next;
|
||||
|
||||
property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id, &is_cursor);
|
||||
property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id);
|
||||
if(property_mask != plane_property_all || crtc_id <= 0)
|
||||
goto next;
|
||||
|
||||
@@ -426,6 +474,7 @@ namespace gsr {
|
||||
latest_cursor_position.x = crtc_x;
|
||||
latest_cursor_position.y = crtc_y;
|
||||
latest_crtc_id = crtc_id;
|
||||
found_cursor = true;
|
||||
drmModeFreePlane(plane);
|
||||
break;
|
||||
}
|
||||
@@ -434,6 +483,11 @@ namespace gsr {
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
|
||||
// On kde plasma wayland (and possibly other wayland compositors) it uses a software cursor only for the monitors with vrr enabled.
|
||||
// In that case we cant know the cursor location and we instead want to fallback to getting focused monitor by using the hack of creating a window and getting the position.
|
||||
if(!found_cursor && latest_crtc_id > 0 && connectors.has_any_crtc_with_vrr_enabled)
|
||||
latest_crtc_id = -1;
|
||||
|
||||
drmModeFreePlaneResources(planes);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../include/CursorTrackerX11.hpp"
|
||||
#include "../include/WindowUtils.hpp"
|
||||
#include "../../include/CursorTracker/CursorTrackerX11.hpp"
|
||||
#include "../../include/WindowUtils.hpp"
|
||||
|
||||
namespace gsr {
|
||||
CursorTrackerX11::CursorTrackerX11(Display *dpy) : dpy(dpy) {
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../include/GlobalHotkeysJoystick.hpp"
|
||||
#include "../../include/GlobalHotkeys/GlobalHotkeysJoystick.hpp"
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../include/GlobalHotkeysLinux.hpp"
|
||||
#include "../../include/GlobalHotkeys/GlobalHotkeysLinux.hpp"
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../include/GlobalHotkeysX11.hpp"
|
||||
#include "../../include/GlobalHotkeys/GlobalHotkeysX11.hpp"
|
||||
#include <X11/keysym.h>
|
||||
#include <mglpp/window/Event.hpp>
|
||||
#include <assert.h>
|
||||
168
src/Overlay.cpp
168
src/Overlay.cpp
@@ -12,10 +12,10 @@
|
||||
#include "../include/gui/Utils.hpp"
|
||||
#include "../include/gui/PageStack.hpp"
|
||||
#include "../include/WindowUtils.hpp"
|
||||
#include "../include/GlobalHotkeys.hpp"
|
||||
#include "../include/GlobalHotkeysLinux.hpp"
|
||||
#include "../include/CursorTrackerX11.hpp"
|
||||
#include "../include/CursorTrackerWayland.hpp"
|
||||
#include "../include/GlobalHotkeys/GlobalHotkeys.hpp"
|
||||
#include "../include/GlobalHotkeys/GlobalHotkeysLinux.hpp"
|
||||
#include "../include/CursorTracker/CursorTrackerX11.hpp"
|
||||
#include "../include/CursorTracker/CursorTrackerWayland.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
@@ -207,24 +207,20 @@ namespace gsr {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
// Returns the first monitor if not found. Assumes there is at least one monitor connected.
|
||||
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;
|
||||
}
|
||||
return &monitors.front();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Returns the first monitor if not found. Assumes there is at least one monitor connected.
|
||||
static const Monitor* find_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) {
|
||||
assert(!monitors.empty());
|
||||
for(const Monitor &monitor : monitors) {
|
||||
if(monitor.name == name)
|
||||
return &monitor;
|
||||
}
|
||||
return &monitors.front();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::string get_power_supply_online_filepath() {
|
||||
@@ -894,10 +890,14 @@ namespace gsr {
|
||||
const Monitor *focused_monitor = nullptr;
|
||||
if(cursor_info) {
|
||||
focused_monitor = find_monitor_by_name(monitors, cursor_info->monitor_name);
|
||||
if(!focused_monitor)
|
||||
focused_monitor = &monitors.front();
|
||||
cursor_position = cursor_info->position;
|
||||
} else {
|
||||
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);
|
||||
focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value);
|
||||
if(!focused_monitor)
|
||||
focused_monitor = &monitors.front();
|
||||
}
|
||||
|
||||
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
|
||||
@@ -933,8 +933,11 @@ namespace gsr {
|
||||
// when a compositor isn't running.
|
||||
window_create_params.graphics_api = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? MGL_GRAPHICS_API_GLX : MGL_GRAPHICS_API_EGL;
|
||||
|
||||
if(!window->create("gsr ui", window_create_params))
|
||||
if(!window->create("gsr ui", window_create_params)) {
|
||||
fprintf(stderr, "error: failed to create window\n");
|
||||
window.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
//window->set_low_latency(true);
|
||||
|
||||
@@ -1028,6 +1031,9 @@ namespace gsr {
|
||||
if(paused)
|
||||
update_ui_recording_paused();
|
||||
|
||||
if(replay_recording)
|
||||
update_ui_recording_started();
|
||||
|
||||
// Wayland compositors have retarded fullscreen animations that we cant disable in a proper way
|
||||
// without messing up window position.
|
||||
show_overlay_timeout_seconds = prevent_game_minimizing ? 0.0 : 0.15;
|
||||
@@ -1084,7 +1090,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);
|
||||
@@ -1118,7 +1124,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);
|
||||
@@ -1143,7 +1149,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);
|
||||
@@ -1376,10 +1382,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);
|
||||
@@ -1456,6 +1464,49 @@ namespace gsr {
|
||||
return strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
|
||||
}
|
||||
|
||||
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) {
|
||||
char timeout_seconds_str[32];
|
||||
snprintf(timeout_seconds_str, sizeof(timeout_seconds_str), "%f", timeout_seconds);
|
||||
@@ -1474,20 +1525,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;
|
||||
@@ -1620,9 +1675,9 @@ namespace gsr {
|
||||
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());
|
||||
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\"", recording_capture_target.c_str(), focused_window_name.c_str());
|
||||
|
||||
capture_target = recording_capture_target.c_str();
|
||||
break;
|
||||
@@ -1638,9 +1693,9 @@ namespace gsr {
|
||||
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());
|
||||
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, recording_capture_target.c_str(), focused_window_name.c_str());
|
||||
|
||||
capture_target = recording_capture_target.c_str();
|
||||
break;
|
||||
@@ -1650,9 +1705,9 @@ namespace gsr {
|
||||
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());
|
||||
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\"", screenshot_capture_target.c_str(), focused_window_name.c_str());
|
||||
|
||||
capture_target = screenshot_capture_target.c_str();
|
||||
break;
|
||||
@@ -1678,7 +1733,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);
|
||||
@@ -1808,7 +1863,7 @@ 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");
|
||||
@@ -1910,7 +1965,7 @@ 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");
|
||||
@@ -1968,6 +2023,7 @@ namespace gsr {
|
||||
record_dropdown_button_ptr->set_item_icon("pause", &get_theme().pause_texture);
|
||||
record_dropdown_button_ptr->set_item_enabled("pause", false);
|
||||
paused = false;
|
||||
replay_recording = false;
|
||||
}
|
||||
|
||||
void Overlay::update_ui_streaming_started() {
|
||||
@@ -2125,6 +2181,23 @@ 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") {
|
||||
std::optional<CursorInfo> cursor_info;
|
||||
@@ -2133,8 +2206,18 @@ namespace gsr {
|
||||
cursor_info = cursor_tracker->get_latest_cursor_info();
|
||||
}
|
||||
|
||||
if(cursor_info)
|
||||
return cursor_info->monitor_name;
|
||||
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;
|
||||
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())
|
||||
return capture_options.monitors.front().name;
|
||||
else
|
||||
@@ -2286,6 +2369,11 @@ namespace gsr {
|
||||
args.push_back("yes");
|
||||
}
|
||||
|
||||
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) {
|
||||
args.push_back("-replay-storage");
|
||||
args.push_back(config.replay_config.replay_storage.c_str());
|
||||
}
|
||||
|
||||
char region_str[128];
|
||||
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector);
|
||||
|
||||
|
||||
@@ -130,8 +130,6 @@ namespace gsr {
|
||||
exit_status = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer[bytes_read] = '\0';
|
||||
result.append(buffer, bytes_read);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}))
|
||||
|
||||
@@ -27,6 +27,11 @@ 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 get_home_dir() {
|
||||
const char *home_dir = getenv("HOME");
|
||||
if(!home_dir) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "../../include/gui/GlobalSettingsPage.hpp"
|
||||
|
||||
#include "../../include/Overlay.hpp"
|
||||
#include "../../include/GlobalHotkeys.hpp"
|
||||
#include "../../include/Theme.hpp"
|
||||
#include "../../include/Process.hpp"
|
||||
#include "../../include/gui/GsrPage.hpp"
|
||||
@@ -420,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace gsr {
|
||||
}
|
||||
}
|
||||
|
||||
const std::string RadioButton::get_selected_id() const {
|
||||
const std::string& RadioButton::get_selected_id() const {
|
||||
if(items.empty()) {
|
||||
static std::string dummy;
|
||||
return dummy;
|
||||
@@ -177,4 +177,13 @@ namespace gsr {
|
||||
return items[selected_item].id;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& RadioButton::get_selected_text() const {
|
||||
if(items.empty()) {
|
||||
static std::string dummy;
|
||||
return dummy;
|
||||
} else {
|
||||
return items[selected_item].text.get_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,10 +196,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 +221,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 +246,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 +264,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 +304,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 +313,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 +333,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;
|
||||
}
|
||||
@@ -715,7 +735,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(1, 10800);
|
||||
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));
|
||||
|
||||
@@ -733,6 +753,24 @@ namespace gsr {
|
||||
return replay_time_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_replay_storage() {
|
||||
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Where should temporary replay data be stored?", get_color_theme().text_color));
|
||||
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->on_selection_changed = [this](const std::string&, const std::string &id) {
|
||||
update_estimated_replay_file_size(id);
|
||||
return true;
|
||||
};
|
||||
|
||||
list->add_widget(std::move(replay_storage_button));
|
||||
list->set_visible(gsr_info->system_info.gsr_version >= GsrVersion{5, 5, 0});
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<RadioButton> SettingsPage::create_start_replay_automatically() {
|
||||
char fullscreen_text[256];
|
||||
snprintf(fullscreen_text, sizeof(fullscreen_text), "Turn on replay when starting a fullscreen application%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)");
|
||||
@@ -766,13 +804,13 @@ namespace gsr {
|
||||
return label;
|
||||
}
|
||||
|
||||
void SettingsPage::update_estimated_replay_file_size() {
|
||||
void SettingsPage::update_estimated_replay_file_size(const std::string &replay_storage_type) {
|
||||
const int64_t replay_time_seconds = atoi(replay_time_entry_ptr->get_text().c_str());
|
||||
const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL;
|
||||
const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024;
|
||||
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "Estimated video max file size in RAM: %.2fMB.\nChange video bitrate or replay duration to change file size.", video_filesize_mb);
|
||||
snprintf(buffer, sizeof(buffer), "Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.", replay_storage_type == "ram" ? "in RAM" : "on disk", video_filesize_mb);
|
||||
estimated_file_size_ptr->set_text(buffer);
|
||||
}
|
||||
|
||||
@@ -811,12 +849,14 @@ namespace gsr {
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
|
||||
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
general_list->add_widget(create_start_replay_automatically());
|
||||
general_list->add_widget(create_replay_storage());
|
||||
general_list->add_widget(create_save_replay_in_game_folder());
|
||||
if(gsr_info->system_info.gsr_version >= GsrVersion{5, 0, 3})
|
||||
general_list->add_widget(create_restart_replay_on_save());
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("Autostart", create_start_replay_automatically(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
|
||||
auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
|
||||
auto show_replay_started_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show replay started notification");
|
||||
@@ -845,12 +885,12 @@ namespace gsr {
|
||||
view_radio_button_ptr->on_selection_changed("Simple", "simple");
|
||||
|
||||
replay_time_entry_ptr->on_changed = [this](const std::string&) {
|
||||
update_estimated_replay_file_size();
|
||||
update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id());
|
||||
update_replay_time_text();
|
||||
};
|
||||
|
||||
video_bitrate_entry_ptr->on_changed = [this](const std::string&) {
|
||||
update_estimated_replay_file_size();
|
||||
update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -900,6 +940,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));
|
||||
@@ -1095,12 +1140,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));
|
||||
@@ -1173,6 +1221,7 @@ namespace gsr {
|
||||
|
||||
void SettingsPage::load_replay() {
|
||||
load_common(config.replay_config.record_options);
|
||||
replay_storage_button_ptr->set_selected_item(config.replay_config.replay_storage);
|
||||
turn_on_replay_automatically_mode_ptr->set_selected_item(config.replay_config.turn_on_replay_automatically_mode);
|
||||
save_replay_in_game_folder_ptr->set_checked(config.replay_config.save_video_in_game_folder);
|
||||
if(restart_replay_on_save)
|
||||
@@ -1185,8 +1234,8 @@ namespace gsr {
|
||||
|
||||
if(config.replay_config.replay_time < 2)
|
||||
config.replay_config.replay_time = 2;
|
||||
if(config.replay_config.replay_time > 10800)
|
||||
config.replay_config.replay_time = 10800;
|
||||
if(config.replay_config.replay_time > 86400)
|
||||
config.replay_config.replay_time = 86400;
|
||||
replay_time_entry_ptr->set_text(std::to_string(config.replay_config.replay_time));
|
||||
}
|
||||
|
||||
@@ -1195,6 +1244,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);
|
||||
}
|
||||
@@ -1322,6 +1372,7 @@ namespace gsr {
|
||||
config.replay_config.save_directory = save_directory_button_ptr->get_text();
|
||||
config.replay_config.container = container_box_ptr->get_selected_id();
|
||||
config.replay_config.replay_time = atoi(replay_time_entry_ptr->get_text().c_str());
|
||||
config.replay_config.replay_storage = replay_storage_button_ptr->get_selected_id();
|
||||
|
||||
if(config.replay_config.replay_time < 5) {
|
||||
config.replay_config.replay_time = 5;
|
||||
@@ -1334,6 +1385,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();
|
||||
}
|
||||
|
||||
40
src/main.cpp
40
src/main.cpp
@@ -159,18 +159,35 @@ static bool is_flatpak() {
|
||||
return getenv("FLATPAK_ID") != nullptr;
|
||||
}
|
||||
|
||||
static void set_display_server_environment_variables() {
|
||||
// Some users dont have properly setup environments (no display manager that does systemctl --user import-environment DISPLAY WAYLAND_DISPLAY)
|
||||
const char *display = getenv("DISPLAY");
|
||||
if(!display) {
|
||||
display = ":0";
|
||||
setenv("DISPLAY", display, true);
|
||||
}
|
||||
|
||||
const char *wayland_display = getenv("WAYLAND_DISPLAY");
|
||||
if(!wayland_display) {
|
||||
wayland_display = "wayland-1";
|
||||
setenv("WAYLAND_DISPLAY", wayland_display, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
printf("usage: gsr-ui [action]\n");
|
||||
printf("OPTIONS:\n");
|
||||
printf(" action The launch action. Should be either \"launch-show\" or \"launch-hide\". Optional, defaults to \"launch-hide\".\n");
|
||||
printf(" action The launch action. Should be either \"launch-show\", \"launch-hide\" or \"launch-daemon\". Optional, defaults to \"launch-hide\".\n");
|
||||
printf(" If \"launch-show\" is used then the program starts and the UI is immediately opened and can be shown/hidden with Alt+Z.\n");
|
||||
printf(" If \"launch-hide\" is used then the program starts but the UI is not opened until Alt+Z is pressed.\n");
|
||||
printf(" If \"launch-hide\" is used then the program starts but the UI is not opened until Alt+Z is pressed. The UI will be opened if the program is already running in another process.\n");
|
||||
printf(" If \"launch-daemon\" is used then the program starts but the UI is not opened until Alt+Z is pressed. The UI will not be opened if the program is already running in another process.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
enum class LaunchAction {
|
||||
LAUNCH_SHOW,
|
||||
LAUNCH_HIDE
|
||||
LAUNCH_HIDE,
|
||||
LAUNCH_DAEMON
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
@@ -191,18 +208,17 @@ int main(int argc, char **argv) {
|
||||
launch_action = LaunchAction::LAUNCH_SHOW;
|
||||
} else if(strcmp(launch_action_opt, "launch-hide") == 0) {
|
||||
launch_action = LaunchAction::LAUNCH_HIDE;
|
||||
} else if(strcmp(launch_action_opt, "launch-daemon") == 0) {
|
||||
launch_action = LaunchAction::LAUNCH_DAEMON;
|
||||
} else {
|
||||
printf("error: invalid action \"%s\", expected \"launch-show\" or \"launch-hide\".\n", launch_action_opt);
|
||||
printf("error: invalid action \"%s\", expected \"launch-show\", \"launch-hide\" or \"launch-daemon\".\n", launch_action_opt);
|
||||
usage();
|
||||
}
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
|
||||
if(is_flatpak())
|
||||
install_flatpak_systemd_service();
|
||||
else
|
||||
remove_flatpak_systemd_service();
|
||||
set_display_server_environment_variables();
|
||||
|
||||
// TODO: This is a shitty method to detect if multiple instances of gsr-ui is running but this will work properly even in flatpak
|
||||
// that uses pid sandboxing. Replace this with a better method once we no longer rely on linux global hotkeys on some platform.
|
||||
@@ -210,6 +226,9 @@ int main(int argc, char **argv) {
|
||||
// What do? creating a pid file doesn't work in flatpak either.
|
||||
// TODO: This doesn't work in flatpak when disabling hotkeys.
|
||||
if(is_gsr_ui_virtual_keyboard_running() || gsr::pidof("gsr-ui", getpid()) != -1) {
|
||||
if(launch_action == LaunchAction::LAUNCH_DAEMON)
|
||||
return 1;
|
||||
|
||||
gsr::Rpc rpc;
|
||||
if(rpc.open("gsr-ui") && rpc.write("show_ui\n", 8)) {
|
||||
fprintf(stderr, "Error: another instance of gsr-ui is already running, opening that one instead\n");
|
||||
@@ -221,6 +240,11 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(is_flatpak())
|
||||
install_flatpak_systemd_service();
|
||||
else
|
||||
remove_flatpak_systemd_service();
|
||||
|
||||
// Stop nvidia driver from buffering frames
|
||||
setenv("__GL_MaxFramesAllowed", "1", true);
|
||||
// If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context,
|
||||
|
||||
@@ -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)
|
||||
@@ -404,8 +456,10 @@ static void keyboard_event_remove_event(keyboard_event *self, int index) {
|
||||
if(index < 0 || index >= self->num_event_polls)
|
||||
return;
|
||||
|
||||
ioctl(self->event_polls[index].fd, EVIOCGRAB, 0);
|
||||
close(self->event_polls[index].fd);
|
||||
if(self->event_polls[index].fd > 0) {
|
||||
ioctl(self->event_polls[index].fd, EVIOCGRAB, 0);
|
||||
close(self->event_polls[index].fd);
|
||||
}
|
||||
free(self->event_extra_data[index].key_states);
|
||||
free(self->event_extra_data[index].key_presses_grabbed);
|
||||
|
||||
@@ -435,19 +489,20 @@ static int setup_virtual_keyboard_input(const char *name) {
|
||||
success &= (ioctl(fd, UI_SET_EVBIT, EV_KEY) != -1);
|
||||
success &= (ioctl(fd, UI_SET_EVBIT, EV_REP) != -1);
|
||||
success &= (ioctl(fd, UI_SET_EVBIT, EV_REL) != -1);
|
||||
success &= (ioctl(fd, UI_SET_EVBIT, EV_LED) != -1);
|
||||
//success &= (ioctl(fd, UI_SET_EVBIT, EV_LED) != -1);
|
||||
|
||||
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);
|
||||
}
|
||||
for(int i = 0; i < REL_MAX; ++i) {
|
||||
success &= (ioctl(fd, UI_SET_RELBIT, i) != -1);
|
||||
}
|
||||
for(int i = 0; i < LED_MAX; ++i) {
|
||||
success &= (ioctl(fd, UI_SET_LEDBIT, i) != -1);
|
||||
}
|
||||
// for(int i = 0; i < LED_MAX; ++i) {
|
||||
// success &= (ioctl(fd, UI_SET_LEDBIT, i) != -1);
|
||||
// }
|
||||
|
||||
// success &= (ioctl(fd, UI_SET_EVBIT, EV_ABS) != -1);
|
||||
// success &= (ioctl(fd, UI_SET_ABSBIT, ABS_X) != -1);
|
||||
@@ -566,8 +621,10 @@ void keyboard_event_deinit(keyboard_event *self) {
|
||||
}
|
||||
|
||||
for(int i = 0; i < self->num_event_polls; ++i) {
|
||||
ioctl(self->event_polls[i].fd, EVIOCGRAB, 0);
|
||||
close(self->event_polls[i].fd);
|
||||
if(self->event_polls[i].fd > 0) {
|
||||
ioctl(self->event_polls[i].fd, EVIOCGRAB, 0);
|
||||
close(self->event_polls[i].fd);
|
||||
}
|
||||
free(self->event_extra_data[i].key_states);
|
||||
free(self->event_extra_data[i].key_presses_grabbed);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user