aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Config.cpp11
-rw-r--r--src/CursorTracker/CursorTrackerWayland.cpp (renamed from src/CursorTrackerWayland.cpp)106
-rw-r--r--src/CursorTracker/CursorTrackerX11.cpp (renamed from src/CursorTrackerX11.cpp)4
-rw-r--r--src/GlobalHotkeys/GlobalHotkeysJoystick.cpp (renamed from src/GlobalHotkeysJoystick.cpp)79
-rw-r--r--src/GlobalHotkeys/GlobalHotkeysLinux.cpp (renamed from src/GlobalHotkeysLinux.cpp)2
-rw-r--r--src/GlobalHotkeys/GlobalHotkeysX11.cpp (renamed from src/GlobalHotkeysX11.cpp)2
-rw-r--r--src/Overlay.cpp552
-rw-r--r--src/Process.cpp23
-rw-r--r--src/RegionSelector.cpp6
-rw-r--r--src/Rpc.cpp7
-rw-r--r--src/Theme.cpp27
-rw-r--r--src/Utils.cpp27
-rw-r--r--src/WindowSelector.cpp229
-rw-r--r--src/WindowUtils.cpp25
-rw-r--r--src/gui/ComboBox.cpp2
-rw-r--r--src/gui/GlobalSettingsPage.cpp5
-rw-r--r--src/gui/RadioButton.cpp11
-rw-r--r--src/gui/ScreenshotSettingsPage.cpp18
-rw-r--r--src/gui/SettingsPage.cpp137
-rw-r--r--src/main.cpp63
20 files changed, 1015 insertions, 321 deletions
diff --git a/src/Config.cpp b/src/Config.cpp
index e920bf0..313cd38 100644
--- a/src/Config.cpp
+++ b/src/Config.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>
@@ -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},
@@ -264,6 +266,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},
diff --git a/src/CursorTrackerWayland.cpp b/src/CursorTracker/CursorTrackerWayland.cpp
index 5f37d0a..7af86b4 100644
--- a/src/CursorTrackerWayland.cpp
+++ b/src/CursorTracker/CursorTrackerWayland.cpp
@@ -1,4 +1,4 @@
-#include "../include/CursorTrackerWayland.hpp"
+#include "../../include/CursorTracker/CursorTrackerWayland.hpp"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
@@ -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,
@@ -27,19 +21,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 +75,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,20 +91,12 @@ namespace gsr {
return false;
}
- 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];
+ 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);
}
// Note: this monitor name logic is kept in sync with gpu screen recorder
@@ -119,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;
}
@@ -325,7 +308,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 +318,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 +335,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 +413,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 +436,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 +448,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 +457,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);
}
diff --git a/src/CursorTrackerX11.cpp b/src/CursorTracker/CursorTrackerX11.cpp
index 7c40cea..7c98f4d 100644
--- a/src/CursorTrackerX11.cpp
+++ b/src/CursorTracker/CursorTrackerX11.cpp
@@ -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) {
diff --git a/src/GlobalHotkeysJoystick.cpp b/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp
index 822a73a..5969438 100644
--- a/src/GlobalHotkeysJoystick.cpp
+++ b/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp
@@ -1,4 +1,4 @@
-#include "../include/GlobalHotkeysJoystick.hpp"
+#include "../../include/GlobalHotkeys/GlobalHotkeysJoystick.hpp"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
@@ -11,9 +11,73 @@ namespace gsr {
static constexpr int triangle_button = 2;
static constexpr int options_button = 9;
static constexpr int playstation_button = 10;
+ static constexpr int l3_button = 11;
+ static constexpr int r3_button = 12;
static constexpr int axis_up_down = 7;
static constexpr int axis_left_right = 6;
+ struct DeviceId {
+ uint16_t vendor;
+ uint16_t product;
+ };
+
+ static bool read_file_hex_number(const char *path, unsigned int *value) {
+ *value = 0;
+ FILE *f = fopen(path, "rb");
+ if(!f)
+ return false;
+
+ fscanf(f, "%x", value);
+ fclose(f);
+ return true;
+ }
+
+ static DeviceId joystick_get_device_id(const char *path) {
+ DeviceId device_id;
+ device_id.vendor = 0;
+ device_id.product = 0;
+
+ const char *js_path_id = nullptr;
+ const int len = strlen(path);
+ for(int i = len - 1; i >= 0; --i) {
+ if(path[i] == '/') {
+ js_path_id = path + i + 1;
+ break;
+ }
+ }
+
+ if(!js_path_id)
+ return device_id;
+
+ unsigned int vendor = 0;
+ unsigned int product = 0;
+ char path_buf[1024];
+
+ snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/vendor", js_path_id);
+ if(!read_file_hex_number(path_buf, &vendor))
+ return device_id;
+
+ snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/product", js_path_id);
+ if(!read_file_hex_number(path_buf, &product))
+ return device_id;
+
+ device_id.vendor = vendor;
+ device_id.product = product;
+ return device_id;
+ }
+
+ static bool is_ps4_controller(DeviceId device_id) {
+ return device_id.vendor == 0x054C && (device_id.product == 0x09CC || device_id.product == 0x0BA0 || device_id.product == 0x05C4);
+ }
+
+ static bool is_ps5_controller(DeviceId device_id) {
+ return device_id.vendor == 0x054C && (device_id.product == 0x0DF2 || device_id.product == 0x0CE6);
+ }
+
+ static bool is_stadia_controller(DeviceId device_id) {
+ return device_id.vendor == 0x18D1 && (device_id.product == 0x9400);
+ }
+
// Returns -1 on error
static int get_js_dev_input_id_from_filepath(const char *dev_input_filepath) {
if(strncmp(dev_input_filepath, "/dev/input/js", 13) != 0)
@@ -204,7 +268,8 @@ namespace gsr {
if((event.type & JS_EVENT_BUTTON) == JS_EVENT_BUTTON) {
switch(event.number) {
case playstation_button: {
- playstation_button_pressed = event.value == button_pressed;
+ // Workaround weird steam input (in-game) behavior where steam triggers playstation button + options when pressing both l3 and r3 at the same time
+ playstation_button_pressed = (event.value == button_pressed) && !l3_button_pressed && !r3_button_pressed;
break;
}
case options_button: {
@@ -222,6 +287,14 @@ namespace gsr {
save_10_min_replay = true;
break;
}
+ case l3_button: {
+ l3_button_pressed = event.value == button_pressed;
+ break;
+ }
+ case r3_button: {
+ r3_button_pressed = event.value == button_pressed;
+ break;
+ }
}
} else if((event.type & JS_EVENT_AXIS) == JS_EVENT_AXIS && playstation_button_pressed) {
const int trigger_threshold = 16383;
@@ -276,6 +349,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;
diff --git a/src/GlobalHotkeysLinux.cpp b/src/GlobalHotkeys/GlobalHotkeysLinux.cpp
index d780916..a56bbc6 100644
--- a/src/GlobalHotkeysLinux.cpp
+++ b/src/GlobalHotkeys/GlobalHotkeysLinux.cpp
@@ -1,4 +1,4 @@
-#include "../include/GlobalHotkeysLinux.hpp"
+#include "../../include/GlobalHotkeys/GlobalHotkeysLinux.hpp"
#include <sys/wait.h>
#include <fcntl.h>
#include <limits.h>
diff --git a/src/GlobalHotkeysX11.cpp b/src/GlobalHotkeys/GlobalHotkeysX11.cpp
index 9af2607..bc79ce8 100644
--- a/src/GlobalHotkeysX11.cpp
+++ b/src/GlobalHotkeys/GlobalHotkeysX11.cpp
@@ -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>
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
index 91f20db..c698cff 100644
--- a/src/Overlay.cpp
+++ b/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>
@@ -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>
@@ -207,24 +209,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() {
@@ -275,6 +273,33 @@ namespace gsr {
return true;
}
+ static bool is_hyprland_waybar_running_as_dock() {
+ const char *args[] = { "hyprctl", "layers", nullptr };
+ std::string stdout_str;
+ if(exec_program_on_host_get_stdout(args, stdout_str) != 0)
+ return false;
+
+ int waybar_layer_level = -1;
+ int current_layer_level = 0;
+ string_split_char(stdout_str, '\n', [&](const std::string_view line) {
+ if(line.find("Layer level 0") != std::string_view::npos)
+ current_layer_level = 0;
+ else if(line.find("Layer level 1") != std::string_view::npos)
+ current_layer_level = 1;
+ else if(line.find("Layer level 2") != std::string_view::npos)
+ current_layer_level = 2;
+ else if(line.find("Layer level 3") != std::string_view::npos)
+ current_layer_level = 3;
+ else if(line.find("namespace: waybar") != std::string_view::npos) {
+ waybar_layer_level = current_layer_level;
+ return false;
+ }
+ return true;
+ });
+
+ return waybar_layer_level >= 0 && waybar_layer_level <= 1;
+ }
+
static Hotkey config_hotkey_to_hotkey(ConfigHotkey config_hotkey) {
return {
(uint32_t)mgl::Keyboard::key_to_x11_keysym((mgl::Keyboard::Key)config_hotkey.key),
@@ -681,6 +706,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;
@@ -717,6 +758,12 @@ namespace gsr {
update_gsr_screenshot_process_status();
replay_status_update_status();
+ if(hide_ui) {
+ hide_ui = false;
+ hide();
+ return false;
+ }
+
if(start_region_capture) {
start_region_capture = false;
hide();
@@ -726,7 +773,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;
}
@@ -860,7 +916,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;
@@ -881,6 +937,8 @@ namespace gsr {
const std::string wm_name = get_window_manager_name(display);
const bool is_kwin = wm_name == "KWin";
const bool is_wlroots = wm_name.find("wlroots") != std::string::npos;
+ const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos;
+ const bool hyprland_waybar_is_dock = is_hyprland && is_hyprland_waybar_running_as_dock();
std::optional<CursorInfo> cursor_info;
if(cursor_tracker) {
@@ -894,16 +952,21 @@ 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.
// If the focused window is a wayland application then don't use override redirect and instead create
// a fullscreen window for the ui.
- const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots;
+ // 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 || is_hyprland;
if(prevent_game_minimizing) {
window_pos = focused_monitor->position;
@@ -933,8 +996,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);
@@ -1004,7 +1070,7 @@ namespace gsr {
// Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events.
xi_grab_all_mouse_devices(xi_display);
- if(!is_wlroots)
+ if(!is_wlroots && !hyprland_waybar_is_dock)
window->set_fullscreen(true);
visible = true;
@@ -1087,7 +1153,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);
@@ -1121,7 +1187,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);
@@ -1146,7 +1212,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);
@@ -1278,6 +1344,8 @@ namespace gsr {
if(!visible)
return;
+ hide_ui = false;
+
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
@@ -1308,6 +1376,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);
@@ -1379,10 +1448,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);
@@ -1428,6 +1499,27 @@ 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;
+ }
+
+ if(byte_index < str.size()) {
+ str.erase(byte_index);
+ str += "...";
+ }
+ }
+
static bool is_hex_num(char c) {
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}
@@ -1455,8 +1547,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) {
@@ -1477,20 +1648,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;
@@ -1588,11 +1763,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;
@@ -1622,11 +1792,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;
}
@@ -1640,11 +1806,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;
}
@@ -1652,11 +1814,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;
}
@@ -1681,7 +1839,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);
@@ -1689,10 +1847,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());
}
}
@@ -1741,6 +1896,35 @@ namespace gsr {
}
}
+ void Overlay::on_gsr_process_error(int exit_code, NotificationType notification_type) {
+ fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
+ if(exit_code == 50) {
+ show_notification("Desktop portal capture failed.\nEither you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture\nor it's incorrectly setup on your system", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
+ } else if(exit_code == 60) {
+ show_notification("Stopped capture because the user canceled the desktop portal", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
+ } else {
+ const char *prefix = "";
+ switch(notification_type) {
+ case NotificationType::NONE:
+ case NotificationType::SCREENSHOT:
+ break;
+ case NotificationType::RECORD:
+ prefix = "Failed to start/save recording";
+ break;
+ case NotificationType::REPLAY:
+ prefix = "Replay stopped because of an error";
+ break;
+ case NotificationType::STREAM:
+ prefix = "Streaming stopped because of an error";
+ break;
+ }
+
+ char msg[256];
+ snprintf(msg, sizeof(msg), "%s. Verify if settings are correct", prefix);
+ show_notification(msg, notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
+ }
+ }
+
void Overlay::update_gsr_process_status() {
if(gpu_screen_recorder_process <= 0)
return;
@@ -1767,8 +1951,7 @@ namespace gsr {
if(config.replay_config.show_replay_stopped_notifications)
show_notification("Replay stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
} else {
- fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
- show_notification("Replay stopped because of an error. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
+ on_gsr_process_error(exit_code, NotificationType::REPLAY);
}
break;
}
@@ -1783,8 +1966,7 @@ namespace gsr {
if(config.streaming_config.show_streaming_stopped_notifications)
show_notification("Streaming has stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
} else {
- fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
- show_notification("Streaming stopped because of an error. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
+ on_gsr_process_error(exit_code, NotificationType::STREAM);
}
break;
}
@@ -1811,12 +1993,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 {
@@ -1913,17 +2092,13 @@ 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 {
- fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
- show_notification("Failed to start/save recording. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
+ on_gsr_process_error(exit_code, NotificationType::RECORD);
}
update_ui_recording_stopped();
replay_recording = false;
@@ -2047,6 +2222,8 @@ namespace gsr {
for(const AudioTrack &audio_track : audio_tracks) {
std::string audio_track_merged;
+ int num_app_audio = 0;
+
for(const std::string &audio_input_name : audio_track.audio_inputs) {
std::string new_audio_input_name = audio_input_name;
const bool is_app_audio = starts_with(new_audio_input_name, "app:");
@@ -2056,12 +2233,22 @@ namespace gsr {
if(is_app_audio && audio_track.application_audio_invert)
new_audio_input_name.replace(0, 4, "app-inverse:");
+ if(is_app_audio)
+ ++num_app_audio;
+
if(!audio_track_merged.empty())
audio_track_merged += "|";
audio_track_merged += new_audio_input_name;
}
+ if(num_app_audio == 0 && audio_track.application_audio_invert) {
+ if(!audio_track_merged.empty())
+ audio_track_merged += "|";
+
+ audio_track_merged += "app-inverse:";
+ }
+
if(!audio_track_merged.empty())
result.push_back(std::move(audio_track_merged));
}
@@ -2111,11 +2298,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") {
@@ -2129,16 +2317,45 @@ 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();
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
@@ -2189,8 +2406,50 @@ 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())
+ static const char* switch_video_codec_to_usable_hardware_encoder(const GsrInfo &gsr_info) {
+ if(gsr_info.supported_video_codecs.h264)
+ return "h264";
+ else if(gsr_info.supported_video_codecs.hevc)
+ return "hevc";
+ else if(gsr_info.supported_video_codecs.av1)
+ return "av1";
+ else if(gsr_info.supported_video_codecs.vp8)
+ return "vp8";
+ else if(gsr_info.supported_video_codecs.vp9)
+ return "vp9";
+ return nullptr;
+ }
+
+ static const char* change_container_if_codec_not_supported(const char *video_codec, const char *container) {
+ if(strcmp(video_codec, "vp8") == 0 || strcmp(video_codec, "vp9") == 0) {
+ if(strcmp(container, "webm") != 0 && strcmp(container, "matroska") != 0) {
+ fprintf(stderr, "Warning: container '%s' is not compatible with video codec '%s', using webm container instead\n", container, video_codec);
+ return "webm";
+ }
+ } else if(strcmp(container, "webm") == 0) {
+ fprintf(stderr, "Warning: container webm is not compatible with video codec '%s', using mp4 container instead\n", video_codec);
+ return "mp4";
+ }
+ return container;
+ }
+
+ static void choose_video_codec_and_container_with_fallback(const GsrInfo &gsr_info, const char **video_codec, const char **container, const char **encoder) {
+ *encoder = "gpu";
+ if(strcmp(*video_codec, "h264_software") == 0) {
+ *video_codec = "h264";
+ *encoder = "cpu";
+ } else if(strcmp(*video_codec, "auto") == 0) {
+ *video_codec = switch_video_codec_to_usable_hardware_encoder(gsr_info);
+ if(!*video_codec) {
+ *video_codec = "h264";
+ *encoder = "cpu";
+ }
+ }
+ *container = change_container_if_codec_not_supported(*video_codec, *container);
+ }
+
+ 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) {
@@ -2233,14 +2492,14 @@ namespace gsr {
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
- if(!validate_capture_target(recording_capture_target, capture_options)) {
+ if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) {
char err_msg[256];
- snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str());
+ snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
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);
@@ -2248,6 +2507,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);
@@ -2255,12 +2522,10 @@ namespace gsr {
const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.replay_config.record_options.audio_tracks_list, gsr_info);
const std::string framerate_mode = config.replay_config.record_options.framerate_mode == "auto" ? "vfr" : config.replay_config.record_options.framerate_mode;
const std::string replay_time = std::to_string(config.replay_config.replay_time);
+ const char *container = config.replay_config.container.c_str();
const char *video_codec = config.replay_config.record_options.video_codec.c_str();
const char *encoder = "gpu";
- if(strcmp(video_codec, "h264_software") == 0) {
- video_codec = "h264";
- encoder = "cpu";
- }
+ choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
char size[64];
size[0] = '\0';
@@ -2272,7 +2537,7 @@ namespace gsr {
std::vector<const char*> args = {
"gpu-screen-recorder", "-w", recording_capture_target.c_str(),
- "-c", config.replay_config.container.c_str(),
+ "-c", container,
"-ac", config.replay_config.record_options.audio_codec.c_str(),
"-cursor", config.replay_config.record_options.record_cursor ? "yes" : "no",
"-cr", config.replay_config.record_options.color_range.c_str(),
@@ -2290,6 +2555,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);
@@ -2322,18 +2592,18 @@ 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());
}
+ if(config.replay_config.record_options.record_area_option == "portal")
+ hide_ui = true;
+
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) {
@@ -2404,12 +2674,12 @@ namespace gsr {
recording_capture_target = get_capture_target(config.record_config.record_options.record_area_option, capture_options);
if(!validate_capture_target(config.record_config.record_options.record_area_option, capture_options)) {
char err_msg[256];
- snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str());
+ snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
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);
@@ -2417,6 +2687,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
@@ -2425,12 +2703,10 @@ namespace gsr {
const std::string output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str());
const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.record_config.record_options.audio_tracks_list, gsr_info);
const std::string framerate_mode = config.record_config.record_options.framerate_mode == "auto" ? "vfr" : config.record_config.record_options.framerate_mode;
+ const char *container = config.record_config.container.c_str();
const char *video_codec = config.record_config.record_options.video_codec.c_str();
const char *encoder = "gpu";
- if(strcmp(video_codec, "h264_software") == 0) {
- video_codec = "h264";
- encoder = "cpu";
- }
+ choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
char size[64];
size[0] = '\0';
@@ -2442,7 +2718,7 @@ namespace gsr {
std::vector<const char*> args = {
"gpu-screen-recorder", "-w", recording_capture_target.c_str(),
- "-c", config.record_config.container.c_str(),
+ "-c", container,
"-ac", config.record_config.record_options.audio_codec.c_str(),
"-cursor", config.record_config.record_options.record_cursor ? "yes" : "no",
"-cr", config.record_config.record_options.color_range.c_str(),
@@ -2479,12 +2755,12 @@ 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());
}
+
+ if(config.record_config.record_options.record_area_option == "portal")
+ hide_ui = true;
}
static std::string streaming_get_url(const Config &config) {
@@ -2495,6 +2771,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)
@@ -2519,8 +2798,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) {
@@ -2561,12 +2840,12 @@ namespace gsr {
recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options);
if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) {
char err_msg[256];
- snprintf(err_msg, sizeof(err_msg), "Failed to start streaming, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str());
+ snprintf(err_msg, sizeof(err_msg), "Failed to start streaming, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
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);
@@ -2574,6 +2853,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);
@@ -2583,16 +2870,12 @@ namespace gsr {
if(audio_tracks.size() > 1)
audio_tracks.resize(1);
const std::string framerate_mode = config.streaming_config.record_options.framerate_mode == "auto" ? "vfr" : config.streaming_config.record_options.framerate_mode;
+ const char *container = "flv";
+ if(config.streaming_config.streaming_service == "custom")
+ container = config.streaming_config.custom.container.c_str();
const char *video_codec = config.streaming_config.record_options.video_codec.c_str();
const char *encoder = "gpu";
- if(strcmp(video_codec, "h264_software") == 0) {
- video_codec = "h264";
- encoder = "cpu";
- }
-
- std::string container = "flv";
- if(config.streaming_config.streaming_service == "custom")
- container = config.streaming_config.custom.container;
+ choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
const std::string url = streaming_get_url(config);
@@ -2606,7 +2889,7 @@ namespace gsr {
std::vector<const char*> args = {
"gpu-screen-recorder", "-w", recording_capture_target.c_str(),
- "-c", container.c_str(),
+ "-c", container,
"-ac", config.streaming_config.record_options.audio_codec.c_str(),
"-cursor", config.streaming_config.record_options.record_cursor ? "yes" : "no",
"-cr", config.streaming_config.record_options.color_range.c_str(),
@@ -2649,16 +2932,16 @@ 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());
}
+
+ if(config.streaming_config.record_options.record_area_option == "portal")
+ hide_ui = true;
}
- 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) {
@@ -2672,12 +2955,12 @@ namespace gsr {
screenshot_capture_target = get_capture_target(record_area_option, capture_options);
if(!validate_capture_target(record_area_option, capture_options)) {
char err_msg[256];
- snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid. Please change capture target in settings", screenshot_capture_target.c_str());
+ snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid.\nPlease change capture target in settings", screenshot_capture_target.c_str());
show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT);
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
@@ -2686,6 +2969,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
@@ -2721,6 +3012,9 @@ namespace gsr {
if(gpu_screen_recorder_screenshot_process == -1) {
show_notification("Failed to launch gpu-screen-recorder to take a screenshot", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT);
}
+
+ if(config.screenshot_config.record_area_option == "portal")
+ hide_ui = true;
}
bool Overlay::update_compositor_texture(const Monitor &monitor) {
diff --git a/src/Process.cpp b/src/Process.cpp
index 0a62986..c02753a 100644
--- a/src/Process.cpp
+++ b/src/Process.cpp
@@ -130,8 +130,6 @@ namespace gsr {
exit_status = -1;
break;
}
-
- buffer[bytes_read] = '\0';
result.append(buffer, bytes_read);
}
@@ -178,11 +176,21 @@ namespace gsr {
}
}
+ static const char *get_basename(const char *path, int size) {
+ for(int i = size - 1; i >= 0; --i) {
+ if(path[i] == '/')
+ return path + i + 1;
+ }
+ return path;
+ }
+
// |output_buffer| should be at least PATH_MAX in size
bool read_cmdline_arg0(const char *filepath, char *output_buffer, int output_buffer_size) {
output_buffer[0] = '\0';
+ const char *arg0_start = NULL;
const char *arg0_end = NULL;
+ int arg0_size = 0;
int fd = open(filepath, O_RDONLY);
if(fd == -1)
return false;
@@ -192,13 +200,16 @@ namespace gsr {
if(bytes_read == -1)
goto err;
- arg0_end = (const char*)memchr(buffer, '\0', bytes_read);
+ arg0_start = buffer;
+ arg0_end = (const char*)memchr(arg0_start, '\0', bytes_read);
if(!arg0_end)
goto err;
- if((arg0_end - buffer) + 1 <= output_buffer_size) {
- memcpy(output_buffer, buffer, arg0_end - buffer);
- output_buffer[arg0_end - buffer] = '\0';
+ arg0_start = get_basename(arg0_start, arg0_end - arg0_start);
+ arg0_size = arg0_end - arg0_start;
+ if(arg0_size + 1 <= output_buffer_size) {
+ memcpy(output_buffer, arg0_start, arg0_size);
+ output_buffer[arg0_size] = '\0';
close(fd);
return true;
}
diff --git a/src/RegionSelector.cpp b/src/RegionSelector.cpp
index 5b7243b..89a0209 100644
--- a/src/RegionSelector.cpp
+++ b/src/RegionSelector.cpp
@@ -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;
diff --git a/src/Rpc.cpp b/src/Rpc.cpp
index 3eec98d..803a4dc 100644
--- a/src/Rpc.cpp
+++ b/src/Rpc.cpp
@@ -32,7 +32,7 @@ namespace gsr {
fclose(file);
if(!fifo_filepath.empty())
- remove(fifo_filepath.c_str());
+ unlink(fifo_filepath.c_str());
}
bool Rpc::create(const char *name) {
@@ -44,15 +44,16 @@ namespace gsr {
char fifo_filepath_tmp[PATH_MAX];
get_runtime_filepath(fifo_filepath_tmp, sizeof(fifo_filepath_tmp), name);
fifo_filepath = fifo_filepath_tmp;
- remove(fifo_filepath.c_str());
+ unlink(fifo_filepath.c_str());
if(mkfifo(fifo_filepath.c_str(), 0600) != 0) {
fprintf(stderr, "Error: mkfifo failed, error: %s, %s\n", strerror(errno), fifo_filepath.c_str());
+ fifo_filepath.clear();
return false;
}
if(!open_filepath(fifo_filepath.c_str())) {
- remove(fifo_filepath.c_str());
+ unlink(fifo_filepath.c_str());
fifo_filepath.clear();
return false;
}
diff --git a/src/Theme.cpp b/src/Theme.cpp
index 6c384e3..2bef3c8 100644
--- a/src/Theme.cpp
+++ b/src/Theme.cpp
@@ -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}))
diff --git a/src/Utils.cpp b/src/Utils.cpp
index bc7b1f2..c36a64a 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -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) {
diff --git a/src/WindowSelector.cpp b/src/WindowSelector.cpp
new file mode 100644
index 0000000..f04d600
--- /dev/null
+++ b/src/WindowSelector.cpp
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp
index eb6080f..c6b278b 100644
--- a/src/WindowUtils.cpp
+++ b/src/WindowUtils.cpp
@@ -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);
diff --git a/src/gui/ComboBox.cpp b/src/gui/ComboBox.cpp
index dbe9aa0..4287a53 100644
--- a/src/gui/ComboBox.cpp
+++ b/src/gui/ComboBox.cpp
@@ -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;
}
diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp
index ccebb92..6650c69 100644
--- a/src/gui/GlobalSettingsPage.cpp
+++ b/src/gui/GlobalSettingsPage.cpp
@@ -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;
}
diff --git a/src/gui/RadioButton.cpp b/src/gui/RadioButton.cpp
index a6ef96a..bbb958a 100644
--- a/src/gui/RadioButton.cpp
+++ b/src/gui/RadioButton.cpp
@@ -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();
+ }
+ }
} \ No newline at end of file
diff --git a/src/gui/ScreenshotSettingsPage.cpp b/src/gui/ScreenshotSettingsPage.cpp
index 5b8efbd..c3560ad 100644
--- a/src/gui/ScreenshotSettingsPage.cpp
+++ b/src/gui/ScreenshotSettingsPage.cpp
@@ -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,13 +115,12 @@ 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());
ll->add_widget(std::move(capture_target_list));
ll->add_widget(create_change_image_resolution_section());
- return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
+ return std::make_unique<Subsection>("Capture", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_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;
diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp
index 9890d17..405ba28 100644
--- a/src/gui/SettingsPage.cpp
+++ b/src/gui/SettingsPage.cpp
@@ -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,20 +177,29 @@ 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());
ll->add_widget(std::move(capture_target_list));
ll->add_widget(create_change_video_resolution_section());
- return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
+ return std::make_unique<Subsection>("Capture", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
+ }
+
+ 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() {
+ 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(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 +741,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 +792,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 +837,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 +873,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 +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));
@@ -919,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;
@@ -943,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;
}
@@ -1004,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");
@@ -1095,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));
@@ -1173,6 +1216,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 +1229,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 +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);
}
@@ -1206,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);
}
@@ -1322,6 +1368,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 +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();
}
@@ -1345,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();
}
diff --git a/src/main.cpp b/src/main.cpp
index 19a23c7..192e84e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,7 +4,6 @@
#include "../include/Process.hpp"
#include "../include/Rpc.hpp"
-#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <limits.h>
@@ -14,7 +13,6 @@
#include <mglpp/system/Clock.hpp>
// TODO: Make keyboard/controller controllable for steam deck (and other controllers).
-// TODO: Keep track of gpu screen recorder run by other programs to not allow recording at the same time, or something.
// TODO: Add systray by using org.kde.StatusNotifierWatcher/etc dbus directly.
// TODO: Make sure the overlay always stays on top. Test with starting the overlay and then opening youtube in fullscreen.
// This is done in Overlay::force_window_on_top, but it's not called right now. It cant be used because the overlay will be on top of
@@ -159,6 +157,21 @@ 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");
@@ -203,30 +216,19 @@ int main(int argc, char **argv) {
usage();
}
- // 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);
- }
+ set_display_server_environment_variables();
- const char *wayland_display = getenv("WAYLAND_DISPLAY");
- if(!wayland_display) {
- wayland_display = "wayland-1";
- setenv("WAYLAND_DISPLAY", wayland_display, true);
- }
+ auto rpc = std::make_unique<gsr::Rpc>();
+ const bool rpc_created = rpc->create("gsr-ui");
+ if(!rpc_created)
+ fprintf(stderr, "Error: Failed to create rpc\n");
- // 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.
- // TODO: This method doesn't work when disabling hotkeys and the method below with pidof gsr-ui doesn't work in flatpak.
- // What do? creating a pid file doesn't work in flatpak either.
- // TODO: This doesn't work in flatpak when disabling hotkeys.
- if(is_gsr_ui_virtual_keyboard_running() || gsr::pidof("gsr-ui", getpid()) != -1) {
+ if(is_gsr_ui_virtual_keyboard_running() || !rpc_created) {
if(launch_action == LaunchAction::LAUNCH_DAEMON)
return 1;
- gsr::Rpc rpc;
- if(rpc.open("gsr-ui") && rpc.write("show_ui\n", 8)) {
+ rpc = std::make_unique<gsr::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");
} else {
fprintf(stderr, "Error: failed to send command to running gsr-ui instance, user will have to open the UI manually with Alt+Z\n");
@@ -236,6 +238,16 @@ int main(int argc, char **argv) {
return 1;
}
+ if(gsr::pidof("gpu-screen-recorder", -1) != -1) {
+ const char *args[] = { "gsr-notify", "--text", "GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.", "--timeout", "5.0", "--icon-color", "ff0000", "--bg-color", "ff0000", nullptr };
+ gsr::exec_program_daemonized(args);
+ }
+
+ if(mgl_init(MGL_WINDOW_SYSTEM_X11) != 0) {
+ fprintf(stderr, "Error: failed to initialize mgl. Failed to either connect to the X11 server or setup opengl\n");
+ return 1;
+ }
+
if(is_flatpak())
install_flatpak_systemd_service();
else
@@ -279,11 +291,6 @@ int main(int argc, char **argv) {
disable_prime_run();
}
- if(mgl_init(MGL_WINDOW_SYSTEM_X11) != 0) {
- fprintf(stderr, "Error: failed to initialize mgl. Failed to either connect to the X11 server or setup opengl\n");
- exit(1);
- }
-
gsr::SupportedCaptureOptions capture_options = gsr::get_supported_capture_options(gsr_info);
std::string resources_path;
@@ -316,10 +323,6 @@ int main(int argc, char **argv) {
if(launch_action == LaunchAction::LAUNCH_SHOW)
overlay->show();
- auto rpc = std::make_unique<gsr::Rpc>();
- if(!rpc->create("gsr-ui"))
- fprintf(stderr, "Error: Failed to create rpc, commands won't be received\n");
-
rpc_add_commands(rpc.get(), overlay.get());
// TODO: Add hotkeys in Overlay when using x11 global hotkeys. The hotkeys in Overlay should duplicate each key that is used for x11 global hotkeys.