Compare commits

..

26 Commits
1.7.4 ... 1.7.9

Author SHA1 Message Date
dec05eba
5bd600fad6 1.7.9 2025-10-29 18:16:35 +01:00
dec05eba
5144994575 Fix screenshot clipboard paste not working in browsers: ignore image/png request, send as jpg anyways 2025-10-29 18:15:56 +01:00
dec05eba
1c24616388 Support more controllers than real ps4 controllers 2025-10-26 14:26:46 +01:00
dec05eba
ecd9a1f13f Replace / and \ with space in application name 2025-10-26 01:04:44 +02:00
dec05eba
4181d80405 Fix for strict aliasing build 2025-10-18 15:52:13 +02:00
dec05eba
085f4d8bad Add donation link in settings 2025-10-16 19:30:02 +02:00
dec05eba
bb320e97ed Update flatpak version reference 2025-10-05 13:09:37 +02:00
dec05eba
ccf96030da Force no cursor in capture when using region/window screenshot hotkey 2025-10-03 18:00:16 +02:00
dec05eba
ca4061f171 1.7.8 2025-10-03 13:09:16 +02:00
dec05eba
0b4af1e6bb Fix rpc file getting deleted when launching gsr-ui twice. Use unix domain socket instead 2025-10-03 13:08:57 +02:00
dec05eba
9e03cd0354 Test fix for alpha egl 2025-10-03 01:56:50 +02:00
dec05eba
3d4badf5cd Fix build for old meson version 2025-09-30 11:14:10 +02:00
dec05eba
071ecf46de Update TODO 2025-09-30 11:07:09 +02:00
dec05eba
5ee2b95384 Workaround amd driver bug: kill notifications with SIGINT instead of SIGKILL 2025-09-24 18:38:22 +02:00
dec05eba
d610a980f8 Update flatpak version reference 2025-09-23 19:43:43 +02:00
dec05eba
70780ae14e 1.7.6 2025-09-21 03:32:19 +02:00
dec05eba
5f7cb94f4e Only do override-redirect on wayland if the focused x11 application is fullscreen (fixes input focus issue on cosmic when clicking on a window behind the overlay 2025-09-19 14:14:46 +02:00
dec05eba
748c51e2b6 Reorder README.md 2025-09-17 18:02:30 +02:00
dec05eba
3ba9ce771b Update flatpak version reference 2025-09-10 21:36:19 +02:00
dec05eba
c18b062180 README 2025-09-09 16:45:59 +02:00
dec05eba
705da21363 README 2025-09-09 16:43:25 +02:00
dec05eba
609a3e54fd 1.7.5 2025-09-06 19:16:09 +02:00
dec05eba
4e62d12e8c Allow 'sync to content' framerate mode option on wayland (only desktop portal) 2025-09-06 01:58:28 +02:00
dec05eba
b4e003c8f7 Only use mallopt M_MMAP_THRESHOLD if glibc 2025-09-03 18:21:58 +02:00
dec05eba
9efe9d3c91 Add content framerate mode (for x11) 2025-09-01 18:11:16 +02:00
dec05eba
ef4a0fe7cb Update flatpak version reference 2025-08-25 22:30:10 +02:00
22 changed files with 479 additions and 262 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ compile_commands.json
**/xdg-output-unstable-v1-client-protocol.h
**/xdg-output-unstable-v1-protocol.c
depends/.wraplock

View File

@@ -4,18 +4,18 @@
A fullscreen overlay UI for [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/) in the style of ShadowPlay.\
The application is currently primarly designed for X11 but it can run on Wayland as well through XWayland, with some caveats because of Wayland limitations.
# Usage
You can start the overlay UI and make it start automatically on system startup by running `systemctl enable --now --user gpu-screen-recorder-ui`.
Alternatively you can run `gsr-ui` and go into settings and enable start on system startup setting.\
Press `Left Alt+Z` to show/hide the UI. Go into settings to view all of the different hotkeys configured.\
If you use a non-systemd distro and want to start the UI on system startup then you have to manually add `gsr-ui` to your system startup script.\
A program called `gsr-ui-cli` is also installed when installing this software. This can be used to remotely control the UI. Run `gsr-ui-cli --help` to list the available commands.
# Installation
If you are using an Arch Linux based distro then you can find gpu screen recorder ui on aur under the name gpu-screen-recorder-ui (`yay -S gpu-screen-recorder-ui`).\
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which includes this UI.
# Usage
Press `Left Alt+Z` to show/hide the UI. Go into settings (the icon on the right) to view all of the different hotkeys configured.\
You can start the overlay UI and make it start automatically on system startup by running `systemctl enable --now --user gpu-screen-recorder-ui`.
Alternatively you can run `gsr-ui` and go into settings and enable start on system startup setting.\
If you use a non-systemd distro and want to start the UI on system startup then you have to manually add `gsr-ui launch-daemon` to your system startup script.\
A program called `gsr-ui-cli` is also installed when installing this software. This can be used to remotely control the UI. Run `gsr-ui-cli --help` to list the available commands.
# Dependencies
GPU Screen Recorder UI uses meson build system so you need to install `meson` to build GPU Screen Recorder UI.

26
TODO
View File

@@ -206,3 +206,29 @@ Support localization.
Add option to not capture cursor in screenshot when doing region/window capture.
Window selection doesn't work when a window is fullscreen on x11.
Make it possible to change replay duration of the "save 1 min" and "save 10 min" by adding them to the replay settings as options.
If replay duration is set below the "save 1 min" or "save 10 min" then gray them out and when hovering over those buttons
show a tooltip that says that those buttons cant be used because the replay duration in replay settings is set to a lower value than that (and display the replay duration there).
The UI is unusable on a vertical monitor.
Steam overlay interfers with controller input in gsr ui. Maybe move controller handling the gsr-global-hotkeys to do a grab on the controller, to only give the key input to gsr ui.
Add option to show recording status with scroll lock led (use x11 xkb). Blink when starting/stopping recording and set led on when recording is running and set led off when not recording.
For joysticks (gamepads) create a virtual device for each one (/dev/uinput) that has the same vendor, product and name. This is to make sure that it behaves the same way in applications since applications
access joysticks directly through /dev/input/eventN or /dev/input/jsN. It needs the same number of buttons and pretend to be a controller of the same time, for example a ps4 controller
so that games automatically display ps4 buttons if supported.
This also allows us to copy event bits and other data from the device instead of saying we support everything.
This should fix the issue of not being able to write to /dev/uinput with ABS_X event bit set.
Maybe do this for regular keyboard inputs as well?
Use generic icons for controller input in settings and allow configuring them.
Add option to include game name in file name (video and screenshot). Replace / with space.
Check if the focused window is on top on x11 when choosing to take screenshot or show the window as the background of the overlay.
Convert clipboard image to requested type (from jpg to png for example).

View File

@@ -4,8 +4,10 @@
#include "../Hotplug.hpp"
#include <unordered_map>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <poll.h>
#include <linux/joystick.h>
#include <linux/input.h>
namespace gsr {
static constexpr int max_js_poll_fd = 16;
@@ -30,8 +32,10 @@ namespace gsr {
bool bind_action(const std::string &id, GlobalHotkeyCallback callback) override;
void poll_events() override;
private:
void close_fds();
void read_events();
void process_js_event(int fd, js_event &event);
void process_input_event(int fd, input_event &event);
void add_all_joystick_devices();
bool add_device(const char *dev_input_filepath, bool print_error = true);
bool remove_device(const char *dev_input_filepath);
bool remove_poll_fd(int index);
@@ -45,6 +49,11 @@ namespace gsr {
std::unordered_map<std::string, GlobalHotkeyCallback> bound_actions_by_id;
std::thread read_thread;
std::thread close_fd_thread;
std::vector<int> fds_to_close;
std::mutex close_fd_mutex;
std::condition_variable close_fd_cv;
pollfd poll_fd[max_js_poll_fd];
ExtraData extra_data[max_js_poll_fd];
int num_poll_fd = 0;
@@ -56,8 +65,6 @@ namespace gsr {
bool down_pressed = false;
bool left_pressed = false;
bool right_pressed = false;
bool l3_button_pressed = false;
bool r3_button_pressed = false;
bool save_replay = false;
bool save_1_min_replay = false;

View File

@@ -65,7 +65,7 @@ namespace gsr {
bool draw();
void show();
void hide();
void hide_next_frame();
void toggle_show();
void toggle_record();
void toggle_pause();
@@ -90,6 +90,8 @@ namespace gsr {
void set_notification_speed(NotificationSpeed notification_speed);
private:
void hide();
void handle_keyboard_mapping_event();
void on_event(mgl::Event &event);

View File

@@ -4,31 +4,47 @@
#include <functional>
#include <unordered_map>
#include <string>
#include <poll.h>
typedef struct _IO_FILE FILE;
#define GSR_RPC_MAX_CONNECTIONS 8
#define GSR_RPC_MAX_POLLS (1 + GSR_RPC_MAX_CONNECTIONS) /* +1 to include the socket_fd itself for accept */
#define GSR_RPC_MAX_MESSAGE_SIZE 128
namespace gsr {
using RpcCallback = std::function<void(const std::string &name)>;
enum class RpcOpenResult {
OK,
CONNECTION_REFUSED,
ERROR
};
class Rpc {
public:
Rpc() = default;
struct PollData {
char buffer[GSR_RPC_MAX_MESSAGE_SIZE];
int buffer_size = 0;
};
Rpc();
Rpc(const Rpc&) = delete;
Rpc& operator=(const Rpc&) = delete;
~Rpc();
bool create(const char *name);
bool open(const char *name);
RpcOpenResult open(const char *name);
bool write(const char *str, size_t size);
void poll();
bool add_handler(const std::string &name, RpcCallback callback);
private:
bool open_filepath(const char *filepath);
void handle_client_data(int client_fd, PollData &poll_data);
private:
int fd = 0;
FILE *file = nullptr;
std::string fifo_filepath;
int socket_fd = 0;
std::string socket_filepath;
struct pollfd polls[GSR_RPC_MAX_POLLS];
PollData polls_data[GSR_RPC_MAX_POLLS];
int num_polls = 0;
std::unordered_map<std::string, RpcCallback> handlers_by_name;
};
}

View File

@@ -19,7 +19,9 @@ namespace gsr {
void draw(mgl::Window &window, mgl::vec2f offset) override;
void add_item(const std::string &text, const std::string &id);
// The item can only be selected if it's enabled
void set_selected_item(const std::string &id, bool trigger_event = true, bool trigger_event_even_if_selection_not_changed = true);
void set_item_enabled(const std::string &id, bool enabled);
const std::string& get_selected_id() const;
mgl::vec2f get_size() override;
@@ -36,6 +38,7 @@ namespace gsr {
mgl::Text text;
std::string id;
mgl::vec2f position;
bool enabled = true;
};
mgl::vec2f max_size;

View File

@@ -73,6 +73,7 @@ namespace gsr {
std::unique_ptr<List> create_notification_speed();
std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_application_info_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_donate_subsection(ScrollablePage *parent_page);
void add_widgets();
Button* configure_hotkey_get_button_by_active_type();

View File

@@ -1,4 +1,4 @@
project('gsr-ui', ['c', 'cpp'], version : '1.7.4', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
project('gsr-ui', ['c', 'cpp'], version : '1.7.9', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
@@ -65,7 +65,7 @@ datadir = get_option('datadir')
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.7.6"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.8.3"', language: ['c', 'cpp'])
executable(
meson.project_name(),

View File

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

View File

@@ -149,26 +149,31 @@ namespace gsr {
if(xselectionrequest->target == targets_atom) {
int num_targets = 1;
Atom targets[3];
Atom targets[4];
targets[0] = targets_atom;
switch(file_type) {
case FileType::JPG:
num_targets = 3;
num_targets = 4;
targets[1] = image_jpg_atom;
targets[2] = image_jpeg_atom;
targets[3] = image_png_atom;
break;
case FileType::PNG:
num_targets = 2;
targets[1] = image_png_atom;
targets[2] = image_jpg_atom;
targets[3] = image_jpeg_atom;
break;
}
XChangeProperty(dpy, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets);
} else if(xselectionrequest->target == image_jpg_atom || xselectionrequest->target == image_jpeg_atom || xselectionrequest->target == image_png_atom) {
// TODO: Convert image to requested image type. Right now sending a jpg file when a png file is requested works ok in browsers (discord and element)
if(!file_type_matches_request_atom(file_type, xselectionrequest->target)) {
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to request clipboard of type %s, but %s was expected\n", (int64_t)xselectionrequest->requestor, file_type_clipboard_get_name(xselectionrequest->target), file_type_get_name(file_type));
return;
const char *expected_file_type = file_type_get_name(file_type);
fprintf(stderr, "gsr ui: warning: ClipboardFile::send_clipboard: requestor window " FORMAT_I64 " tried to request clipboard of type %s, but %s was expected. Ignoring requestor and sending as %s\n", (int64_t)xselectionrequest->requestor, file_type_clipboard_get_name(xselectionrequest->target), expected_file_type, expected_file_type);
//return;
}
ClipboardCopy *clipboard_copy = get_clipboard_copy_by_requestor(xselectionrequest->requestor);

View File

@@ -3,92 +3,48 @@
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/eventfd.h>
namespace gsr {
static constexpr int button_pressed = 1;
static constexpr int cross_button = 0;
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)
static int get_dev_input_event_id_from_filepath(const char *dev_input_filepath) {
if(strncmp(dev_input_filepath, "/dev/input/event", 16) != 0)
return -1;
int dev_input_id = -1;
if(sscanf(dev_input_filepath + 13, "%d", &dev_input_id) == 1)
if(sscanf(dev_input_filepath + 16, "%d", &dev_input_id) == 1)
return dev_input_id;
return -1;
}
static inline bool supports_key(unsigned char *key_bits, unsigned int key) {
return key_bits[key/8] & (1 << (key % 8));
}
static bool supports_joystick_keys(unsigned char *key_bits) {
const int keys[7] = { BTN_A, BTN_B, BTN_X, BTN_Y, BTN_SELECT, BTN_START, BTN_SELECT };
for(int i = 0; i < 7; ++i) {
if(supports_key(key_bits, keys[i]))
return true;
}
return false;
}
static bool is_input_device_joystick(int input_fd) {
unsigned long evbit = 0;
ioctl(input_fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
if((evbit & (1 << EV_SYN)) && (evbit & (1 << EV_KEY))) {
unsigned char key_bits[KEY_MAX/8 + 1];
memset(key_bits, 0, sizeof(key_bits));
ioctl(input_fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), &key_bits);
return supports_joystick_keys(key_bits);
}
return false;
}
GlobalHotkeysJoystick::~GlobalHotkeysJoystick() {
if(event_fd > 0) {
const uint64_t exit = 1;
@@ -98,8 +54,18 @@ namespace gsr {
if(read_thread.joinable())
read_thread.join();
if(event_fd > 0)
if(event_fd > 0) {
close(event_fd);
event_fd = 0;
}
close_fd_cv.notify_one();
if(close_fd_thread.joinable())
close_fd_thread.join();
for(int fd : fds_to_close) {
close(fd);
}
for(int i = 0; i < num_poll_fd; ++i) {
if(poll_fd[i].fd > 0)
@@ -141,16 +107,10 @@ namespace gsr {
++num_poll_fd;
}
char dev_input_path[128];
for(int i = 0; i < 8; ++i) {
snprintf(dev_input_path, sizeof(dev_input_path), "/dev/input/js%d", i);
add_device(dev_input_path, false);
}
if(num_poll_fd == 0)
fprintf(stderr, "Info: no joysticks found, assuming they might be connected later\n");
add_all_joystick_devices();
read_thread = std::thread(&GlobalHotkeysJoystick::read_events, this);
close_fd_thread = std::thread(&GlobalHotkeysJoystick::close_fds, this);
return true;
}
@@ -214,8 +174,30 @@ namespace gsr {
}
}
// Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only
void GlobalHotkeysJoystick::close_fds() {
std::vector<int> current_fds_to_close;
while(event_fd > 0) {
{
std::unique_lock<std::mutex> lock(close_fd_mutex);
close_fd_cv.wait(lock, [this]{ return !fds_to_close.empty() || event_fd <= 0; });
}
{
std::lock_guard<std::mutex> lock(close_fd_mutex);
current_fds_to_close = std::move(fds_to_close);
fds_to_close.clear();
}
for(int fd : current_fds_to_close) {
close(fd);
}
current_fds_to_close.clear();
}
}
void GlobalHotkeysJoystick::read_events() {
js_event event;
input_event event;
while(poll(poll_fd, num_poll_fd, -1) > 0) {
for(int i = 0; i < num_poll_fd; ++i) {
if(poll_fd[i].revents & (POLLHUP|POLLERR|POLLNVAL)) {
@@ -223,7 +205,7 @@ namespace gsr {
goto done;
char dev_input_filepath[256];
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/js%d", extra_data[i].dev_input_id);
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/event%d", extra_data[i].dev_input_id);
fprintf(stderr, "Info: removed joystick: %s\n", dev_input_filepath);
if(remove_poll_fd(i))
--i; // This item was removed so we want to repeat the same index to continue to the next item
@@ -240,7 +222,7 @@ namespace gsr {
hotplug.process_event_data(poll_fd[i].fd, [&](HotplugAction hotplug_action, const char *devname) {
switch(hotplug_action) {
case HotplugAction::ADD: {
add_device(devname);
add_device(devname, false);
break;
}
case HotplugAction::REMOVE: {
@@ -251,7 +233,7 @@ namespace gsr {
}
});
} else {
process_js_event(poll_fd[i].fd, event);
process_input_event(poll_fd[i].fd, event);
}
}
}
@@ -260,54 +242,44 @@ namespace gsr {
;
}
void GlobalHotkeysJoystick::process_js_event(int fd, js_event &event) {
void GlobalHotkeysJoystick::process_input_event(int fd, input_event &event) {
if(read(fd, &event, sizeof(event)) != sizeof(event))
return;
if((event.type & JS_EVENT_BUTTON) == JS_EVENT_BUTTON) {
switch(event.number) {
case playstation_button: {
// 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;
if(event.type == EV_KEY) {
switch(event.code) {
case BTN_MODE: {
playstation_button_pressed = (event.value == button_pressed);
break;
}
case options_button: {
case BTN_START: {
if(playstation_button_pressed && event.value == button_pressed)
toggle_show = true;
break;
}
case cross_button: {
case BTN_SOUTH: {
if(playstation_button_pressed && event.value == button_pressed)
save_1_min_replay = true;
break;
}
case triangle_button: {
case BTN_NORTH: {
if(playstation_button_pressed && event.value == button_pressed)
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;
} else if(event.type == EV_ABS && playstation_button_pressed) {
const bool prev_up_pressed = up_pressed;
const bool prev_down_pressed = down_pressed;
const bool prev_left_pressed = left_pressed;
const bool prev_right_pressed = right_pressed;
if(event.number == axis_up_down) {
up_pressed = event.value <= -trigger_threshold;
down_pressed = event.value >= trigger_threshold;
} else if(event.number == axis_left_right) {
left_pressed = event.value <= -trigger_threshold;
right_pressed = event.value >= trigger_threshold;
if(event.code == ABS_HAT0Y) {
up_pressed = event.value == -1;
down_pressed = event.value == 1;
} else if(event.code == ABS_HAT0X) {
left_pressed = event.value == -1;
right_pressed = event.value == 1;
}
if(up_pressed && !prev_up_pressed)
@@ -321,13 +293,36 @@ namespace gsr {
}
}
void GlobalHotkeysJoystick::add_all_joystick_devices() {
DIR *dir = opendir("/dev/input");
if(!dir) {
fprintf(stderr, "Error: failed to open /dev/input, error: %s\n", strerror(errno));
return;
}
char dev_input_filepath[1024];
for(;;) {
struct dirent *entry = readdir(dir);
if(!entry)
break;
if(strncmp(entry->d_name, "event", 5) != 0)
continue;
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/%s", entry->d_name);
add_device(dev_input_filepath, false);
}
closedir(dir);
}
bool GlobalHotkeysJoystick::add_device(const char *dev_input_filepath, bool print_error) {
if(num_poll_fd >= max_js_poll_fd) {
fprintf(stderr, "Warning: failed to add joystick device %s, too many joysticks have been added\n", dev_input_filepath);
return false;
}
const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath);
const int dev_input_id = get_dev_input_event_id_from_filepath(dev_input_filepath);
if(dev_input_id == -1)
return false;
@@ -338,6 +333,15 @@ namespace gsr {
return false;
}
if(!is_input_device_joystick(fd)) {
{
std::lock_guard<std::mutex> lock(close_fd_mutex);
fds_to_close.push_back(fd);
}
close_fd_cv.notify_one();
return false;
}
poll_fd[num_poll_fd] = {
fd,
POLLIN,
@@ -356,7 +360,7 @@ namespace gsr {
}
bool GlobalHotkeysJoystick::remove_device(const char *dev_input_filepath) {
const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath);
const int dev_input_id = get_dev_input_event_id_from_filepath(dev_input_filepath);
if(dev_input_id == -1)
return false;
@@ -372,8 +376,13 @@ namespace gsr {
if(index < 0 || index >= num_poll_fd)
return false;
if(poll_fd[index].fd > 0)
close(poll_fd[index].fd);
if(poll_fd[index].fd > 0) {
{
std::lock_guard<std::mutex> lock(close_fd_mutex);
fds_to_close.push_back(poll_fd[index].fd);
}
close_fd_cv.notify_one();
}
for(int i = index + 1; i < num_poll_fd; ++i) {
poll_fd[i - 1] = poll_fd[i];

View File

@@ -512,7 +512,7 @@ namespace gsr {
hide();
if(notification_process > 0) {
kill(notification_process, SIGKILL);
kill(notification_process, SIGINT);
int status;
if(waitpid(notification_process, &status, 0) == -1) {
perror("waitpid failed");
@@ -959,6 +959,7 @@ namespace gsr {
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 is_smithay = wm_name.find("Smithay") != std::string::npos;
const bool hyprland_waybar_is_dock = is_hyprland && is_hyprland_waybar_running_as_dock();
std::optional<CursorInfo> cursor_info;
@@ -986,8 +987,7 @@ namespace gsr {
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
// If the focused window is a wayland application then don't use override redirect and instead create
// a fullscreen window for the ui.
// TODO: (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor))
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots || is_hyprland;
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor)) || is_wlroots || is_hyprland;
if(prevent_game_minimizing) {
window_pos = focused_monitor->position;
@@ -1446,6 +1446,10 @@ namespace gsr {
malloc_trim(0);
}
void Overlay::hide_next_frame() {
hide_ui = true;
}
void Overlay::toggle_show() {
if(visible) {
//hide();
@@ -1700,7 +1704,7 @@ namespace gsr {
notification_args[arg_index++] = nullptr;
if(notification_process > 0) {
kill(notification_process, SIGKILL);
kill(notification_process, SIGINT);
int status = 0;
waitpid(notification_process, &status, 0);
}
@@ -1828,8 +1832,6 @@ namespace gsr {
result += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
}
fprintf(stderr, "to duration string: %f, %d, %d, %d\n", duration_sec, seconds, minutes, hours);
return result;
}
@@ -1863,7 +1865,7 @@ namespace gsr {
if(focused_window_name.empty())
focused_window_name = "Game";
string_replace_characters(focused_window_name.data(), "/\\", '_');
string_replace_characters(focused_window_name.data(), "/\\", ' ');
std::string video_directory = filepath_get_directory(video_filepath) + "/" + focused_window_name;
create_directory_recursive(video_directory.data());
@@ -2560,6 +2562,14 @@ namespace gsr {
*container = change_container_if_codec_not_supported(*video_codec, *container);
}
static std::string get_framerate_mode_validate(const RecordOptions &record_options, const GsrInfo &gsr_info) {
(void)gsr_info;
std::string framerate_mode = record_options.framerate_mode;
if(framerate_mode == "auto")
framerate_mode = "vfr";
return framerate_mode;
}
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) {
if(region_selector.is_started() || window_selector.is_started())
return false;
@@ -2632,7 +2642,7 @@ namespace gsr {
const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate);
const std::string output_directory = config.replay_config.save_directory;
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 framerate_mode = get_framerate_mode_validate(config.replay_config.record_options, gsr_info);
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();
@@ -2829,7 +2839,7 @@ namespace gsr {
const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate);
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 std::string framerate_mode = get_framerate_mode_validate(config.record_config.record_options, gsr_info);
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";
@@ -3005,7 +3015,7 @@ namespace gsr {
// But we check it anyways as streaming on some sites can fail if there is more than one audio track
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 std::string framerate_mode = get_framerate_mode_validate(config.streaming_config.record_options, gsr_info);
const char *container = "flv";
if(config.streaming_config.streaming_service == "custom")
container = config.streaming_config.custom.container.c_str();
@@ -3085,7 +3095,6 @@ namespace gsr {
return;
}
bool hotkey_window_capture = false;
std::string record_area_option;
switch(force_type) {
case ScreenshotForceType::NONE:
@@ -3096,7 +3105,6 @@ namespace gsr {
break;
case ScreenshotForceType::WINDOW:
record_area_option = gsr_info.system_info.display_server == DisplayServer::X11 ? "window" : "portal";
hotkey_window_capture = true;
break;
}
@@ -3127,10 +3135,11 @@ namespace gsr {
// 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
const bool capture_cursor = force_type == ScreenshotForceType::NONE && config.screenshot_config.record_cursor;
std::vector<const char*> args = {
"gpu-screen-recorder", "-w", screenshot_capture_target.c_str(),
"-cursor", config.screenshot_config.record_cursor ? "yes" : "no",
"-cursor", capture_cursor ? "yes" : "no",
"-v", "no",
"-q", config.screenshot_config.image_quality.c_str(),
"-o", output_file.c_str()
@@ -3144,7 +3153,7 @@ namespace gsr {
args.push_back(size);
}
if(config.screenshot_config.restore_portal_session && !hotkey_window_capture) {
if(config.screenshot_config.restore_portal_session && force_type != ScreenshotForceType::WINDOW) {
args.push_back("-restore-portal-session");
args.push_back("yes");
}
@@ -3152,7 +3161,7 @@ namespace gsr {
const std::string hotkey_window_capture_portal_session_token_filepath = get_config_dir() + "/gpu-screen-recorder/gsr-ui-window-capture-token";
if(record_area_option == "portal") {
hide_ui = true;
if(hotkey_window_capture) {
if(force_type == ScreenshotForceType::WINDOW) {
args.push_back("-portal-session-token-filepath");
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
}

View File

@@ -5,11 +5,12 @@
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/un.h>
namespace gsr {
static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *filename) {
static void get_socket_filepath(char *buffer, size_t buffer_size, const char *filename) {
char dir[PATH_MAX];
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
@@ -24,79 +25,117 @@ namespace gsr {
snprintf(buffer, buffer_size, "%s/%s", dir, filename);
}
static int create_socket(const char *name, struct sockaddr_un *addr, std::string &socket_filepath) {
char socket_filepath_tmp[PATH_MAX];
get_socket_filepath(socket_filepath_tmp, sizeof(socket_filepath_tmp), name);
socket_filepath = socket_filepath_tmp;
memset(addr, 0, sizeof(*addr));
if(strlen(name) > sizeof(addr->sun_path))
return false;
addr->sun_family = AF_UNIX;
snprintf(addr->sun_path, sizeof(addr->sun_path), "%s", socket_filepath.c_str());
return socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
}
Rpc::Rpc() {
num_polls = 0;
}
Rpc::~Rpc() {
if(fd > 0)
close(fd);
if(socket_fd > 0)
close(socket_fd);
if(file)
fclose(file);
if(!fifo_filepath.empty())
unlink(fifo_filepath.c_str());
if(!socket_filepath.empty())
unlink(socket_filepath.c_str());
}
bool Rpc::create(const char *name) {
if(file) {
if(socket_fd > 0) {
fprintf(stderr, "Error: Rpc::create: already created/opened\n");
return false;
}
char fifo_filepath_tmp[PATH_MAX];
get_runtime_filepath(fifo_filepath_tmp, sizeof(fifo_filepath_tmp), name);
fifo_filepath = fifo_filepath_tmp;
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();
struct sockaddr_un addr;
socket_fd = create_socket(name, &addr, socket_filepath);
if(socket_fd <= 0) {
fprintf(stderr, "Error: Rpc::create: failed to create socket, error: %s\n", strerror(errno));
return false;
}
if(!open_filepath(fifo_filepath.c_str())) {
unlink(fifo_filepath.c_str());
fifo_filepath.clear();
unlink(socket_filepath.c_str());
if(bind(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
const int err = errno;
close(socket_fd);
socket_fd = 0;
fprintf(stderr, "Error: Rpc::create: failed to bind, error: %s\n", strerror(err));
return false;
}
if(listen(socket_fd, GSR_RPC_MAX_CONNECTIONS) == -1) {
const int err = errno;
close(socket_fd);
socket_fd = 0;
fprintf(stderr, "Error: Rpc::create: failed to listen, error: %s\n", strerror(err));
return false;
}
polls[0].fd = socket_fd;
polls[0].events = POLLIN;
polls[0].revents = 0;
++num_polls;
return true;
}
bool Rpc::open(const char *name) {
if(file) {
RpcOpenResult Rpc::open(const char *name) {
if(socket_fd > 0) {
fprintf(stderr, "Error: Rpc::open: already created/opened\n");
return false;
return RpcOpenResult::ERROR;
}
char fifo_filepath_tmp[PATH_MAX];
get_runtime_filepath(fifo_filepath_tmp, sizeof(fifo_filepath_tmp), name);
return open_filepath(fifo_filepath_tmp);
}
bool Rpc::open_filepath(const char *filepath) {
fd = ::open(filepath, O_RDWR | O_NONBLOCK);
if(fd <= 0)
return false;
file = fdopen(fd, "r+");
if(!file) {
close(fd);
fd = 0;
return false;
struct sockaddr_un addr;
socket_fd = create_socket(name, &addr, socket_filepath);
socket_filepath.clear(); /* We dont want to delete the socket on exit as the client */
if(socket_fd <= 0) {
fprintf(stderr, "Error: Rpc::open: failed to create socket, error: %s\n", strerror(errno));
return RpcOpenResult::ERROR;
}
fd = 0;
return true;
while(true) {
if(connect(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
const int err = errno;
if(err == EWOULDBLOCK) {
usleep(10 * 1000);
} else {
close(socket_fd);
socket_fd = 0;
if(err != ENOENT && err != ECONNREFUSED)
fprintf(stderr, "Error: Rpc::create: failed to connect, error: %s\n", strerror(err));
return RpcOpenResult::ERROR;
}
} else {
break;
}
}
return RpcOpenResult::OK;
}
bool Rpc::write(const char *str, size_t size) {
if(!file) {
fprintf(stderr, "Error: Rpc::write: fifo not created/opened yet\n");
if(socket_fd <= 0) {
fprintf(stderr, "Error: Rpc::write: unix domain socket not created/opened yet\n");
return false;
}
ssize_t offset = 0;
while(offset < (ssize_t)size) {
const ssize_t bytes_written = fwrite(str + offset, 1, size - offset, file);
fflush(file);
const ssize_t bytes_written = ::write(socket_fd, str + offset, size - offset);
if(bytes_written > 0)
offset += bytes_written;
}
@@ -104,30 +143,73 @@ namespace gsr {
}
void Rpc::poll() {
if(!file) {
//fprintf(stderr, "Error: Rpc::poll: fifo not created/opened yet\n");
if(socket_fd <= 0) {
//fprintf(stderr, "Error: Rpc::poll: unix domain socket not created/opened yet\n");
return;
}
std::string name;
char line[1024];
while(fgets(line, sizeof(line), file)) {
int line_len = strlen(line);
if(line_len == 0)
continue;
while(::poll(polls, num_polls, 0) > 0) {
for(int i = 0; i < num_polls; ++i) {
if(polls[i].fd == socket_fd) {
if(polls[i].revents & (POLLERR|POLLHUP)) {
close(socket_fd);
socket_fd = 0;
return;
}
if(line[line_len - 1] == '\n') {
line[line_len - 1] = '\0';
--line_len;
const int client_fd = accept(socket_fd, NULL, NULL);
if(num_polls >= GSR_RPC_MAX_POLLS) {
if(errno != EWOULDBLOCK)
fprintf(stderr, "Error: Rpc::poll: unable to accept more clients, error: %s\n", strerror(errno));
} else {
polls[num_polls].fd = client_fd;
polls[num_polls].events = POLLIN;
polls[num_polls].revents = 0;
++num_polls;
}
continue;
}
if(polls[i].revents & POLLIN)
handle_client_data(polls[i].fd, polls_data[i]);
if(polls[i].revents & (POLLERR|POLLHUP)) {
close(polls[i].fd);
polls[i] = polls[num_polls - 1];
memcpy(polls_data[i].buffer, polls_data[num_polls - 1].buffer, polls_data[num_polls - 1].buffer_size);
polls_data[i].buffer_size = polls_data[num_polls - 1].buffer_size;
--num_polls;
--i;
}
}
name = line;
auto it = handlers_by_name.find(name);
if(it != handlers_by_name.end())
it->second(name);
}
}
void Rpc::handle_client_data(int client_fd, PollData &poll_data) {
char *write_buffer = poll_data.buffer + poll_data.buffer_size;
const ssize_t num_bytes_read = read(client_fd, write_buffer, sizeof(poll_data.buffer) - poll_data.buffer_size);
if(num_bytes_read <= 0)
return;
poll_data.buffer_size += num_bytes_read;
const char *newline_p = (const char*)memchr(write_buffer, '\n', num_bytes_read);
if(!newline_p)
return;
const size_t command_size = newline_p - poll_data.buffer;
std::string name;
name.assign(poll_data.buffer, command_size);
memmove(poll_data.buffer, newline_p + 1, poll_data.buffer_size - (command_size + 1));
poll_data.buffer_size -= (command_size + 1);
auto it = handlers_by_name.find(name);
if(it != handlers_by_name.end())
it->second(name);
}
bool Rpc::add_handler(const std::string &name, RpcCallback callback) {
return handlers_by_name.insert(std::make_pair(name, std::move(callback))).second;
}

View File

@@ -36,7 +36,7 @@ namespace gsr {
for(size_t i = 0; i < items.size(); ++i) {
Item &item = items[i];
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
if(mgl::FloatRect(item.position, item_size).contains(mouse_pos)) {
if(mgl::FloatRect(item.position, item_size).contains(mouse_pos) && item.enabled) {
const size_t prev_selected_item = selected_item;
selected_item = i;
show_dropdown = false;
@@ -93,7 +93,7 @@ namespace gsr {
void ComboBox::set_selected_item(const std::string &id, bool trigger_event, bool trigger_event_even_if_selection_not_changed) {
for(size_t i = 0; i < items.size(); ++i) {
auto &item = items[i];
if(item.id == id) {
if(item.id == id && item.enabled) {
const size_t prev_selected_item = selected_item;
selected_item = i;
dirty = true;
@@ -106,6 +106,22 @@ namespace gsr {
}
}
void ComboBox::set_item_enabled(const std::string &id, bool enabled) {
for(size_t i = 0; i < items.size(); ++i) {
auto &item = items[i];
if(item.id == id) {
item.enabled = enabled;
item.text.set_color(item.enabled ? mgl::Color(255, 255, 255, 255) : mgl::Color(255, 255, 255, 80));
if(selected_item == i) {
selected_item = 0;
show_dropdown = false;
dirty = true;
}
return;
}
}
}
const std::string& ComboBox::get_selected_id() const {
if(items.empty()) {
static std::string dummy;
@@ -150,7 +166,7 @@ namespace gsr {
Item &item = items[i];
item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom;
if(!cursor_inside) {
if(!cursor_inside && item.enabled) {
cursor_inside = mgl::FloatRect(items_draw_pos, item_size).contains(mouse_pos);
if(cursor_inside) {
mgl::Rectangle item_background(items_draw_pos.floor(), item_size.floor());

View File

@@ -528,6 +528,22 @@ namespace gsr {
return std::make_unique<Subsection>("Application info", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
std::unique_ptr<Subsection> GlobalSettingsPage::create_donate_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:", get_color_theme().text_color));
auto donate_button = std::make_unique<Button>(&get_theme().body_font, "Donate", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
donate_button->on_click = [this] {
const char *args[] = { "xdg-open", "https://buymeacoffee.com/dec05eba", nullptr };
exec_program_daemonized(args);
overlay->hide_next_frame();
};
list->add_widget(std::move(donate_button));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.", get_color_theme().text_color));
return std::make_unique<Subsection>("Donate", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
void GlobalSettingsPage::add_widgets() {
auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size());
@@ -539,6 +555,7 @@ namespace gsr {
settings_list->add_widget(create_controller_hotkey_subsection(scrollable_page.get()));
settings_list->add_widget(create_application_options_subsection(scrollable_page.get()));
settings_list->add_widget(create_application_info_subsection(scrollable_page.get()));
settings_list->add_widget(create_donate_subsection(scrollable_page.get()));
scrollable_page->add_widget(std::move(settings_list));
content_page_ptr->add_widget(std::move(scrollable_page));

View File

@@ -40,7 +40,7 @@ namespace gsr {
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");
record_area_box->add_item("Focused monitor", "focused_monitor");
for(const auto &monitor : capture_options.monitors) {
char name[256];
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);

View File

@@ -73,7 +73,7 @@ namespace gsr {
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");
record_area_box->add_item("Focused monitor", "focused_monitor");
for(const auto &monitor : capture_options.monitors) {
char name[256];
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
@@ -568,6 +568,10 @@ namespace gsr {
framerate_mode_box->add_item("Auto (Recommended)", "auto");
framerate_mode_box->add_item("Constant", "cfr");
framerate_mode_box->add_item("Variable", "vfr");
if(gsr_info->system_info.display_server == DisplayServer::X11)
framerate_mode_box->add_item("Sync to content", "content");
else
framerate_mode_box->add_item("Sync to content (Only X11 or desktop portal capture)", "content");
framerate_mode_box_ptr = framerate_mode_box.get();
return framerate_mode_box;
}

View File

@@ -102,24 +102,6 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
});
}
static bool is_gsr_ui_virtual_keyboard_running() {
FILE *f = fopen("/proc/bus/input/devices", "rb");
if(!f)
return false;
bool virtual_keyboard_running = false;
char line[1024];
while(fgets(line, sizeof(line), f)) {
if(strstr(line, "gsr-ui virtual keyboard")) {
virtual_keyboard_running = true;
break;
}
}
fclose(f);
return virtual_keyboard_running;
}
static void install_flatpak_systemd_service() {
const bool systemd_service_exists = system(
"data_home=$(flatpak-spawn --host -- /bin/sh -c 'echo \"${XDG_DATA_HOME:-$HOME/.local/share}\"') && "
@@ -195,7 +177,9 @@ enum class LaunchAction {
int main(int argc, char **argv) {
setlocale(LC_ALL, "C"); // Sigh... stupid C
#ifdef __GLIBC__
mallopt(M_MMAP_THRESHOLD, 65536);
#endif
if(geteuid() == 0) {
fprintf(stderr, "Error: don't run gsr-ui as the root user\n");
@@ -224,16 +208,13 @@ int main(int argc, char **argv) {
set_display_server_environment_variables();
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");
const gsr::RpcOpenResult rpc_open_result = rpc->open("gsr-ui");
if(is_gsr_ui_virtual_keyboard_running() || !rpc_created) {
if(rpc_open_result == gsr::RpcOpenResult::OK) {
if(launch_action == LaunchAction::LAUNCH_DAEMON)
return 1;
rpc = std::make_unique<gsr::Rpc>();
if(rpc->open("gsr-ui") && rpc->write("show_ui\n", 8)) {
if(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");
@@ -243,6 +224,9 @@ int main(int argc, char **argv) {
return 1;
}
if(!rpc->create("gsr-ui"))
fprintf(stderr, "Error: Failed to create rpc\n");
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);

View File

@@ -234,14 +234,23 @@ static void keyboard_event_process_input_event_data(keyboard_event *self, event_
/* Retarded linux takes very long time to close /dev/input/eventN files, even though they are virtual and opened read-only */
static void* keyboard_event_close_fds_callback(void *userdata) {
keyboard_event *self = userdata;
int fds_to_close_now[MAX_CLOSE_FDS];
int num_fds_to_close_now = 0;
while(self->running) {
pthread_mutex_lock(&self->close_dev_input_mutex);
for(int i = 0; i < self->num_close_fds; ++i) {
close(self->close_fds[i]);
fds_to_close_now[i] = self->close_fds[i];
}
num_fds_to_close_now = self->num_close_fds;
self->num_close_fds = 0;
pthread_mutex_unlock(&self->close_dev_input_mutex);
for(int i = 0; i < num_fds_to_close_now; ++i) {
close(fds_to_close_now[i]);
}
num_fds_to_close_now = 0;
usleep(100 * 1000); /* 100 milliseconds */
}
return NULL;
@@ -456,9 +465,13 @@ static void keyboard_event_remove_event(keyboard_event *self, int index) {
if(index < 0 || index >= self->num_event_polls)
return;
if(self->event_polls[index].fd > 0) {
ioctl(self->event_polls[index].fd, EVIOCGRAB, 0);
close(self->event_polls[index].fd);
const int poll_fd = self->event_polls[index].fd;
if(poll_fd > 0) {
ioctl(poll_fd, EVIOCGRAB, 0);
if(!keyboard_event_try_add_close_fd(self, poll_fd)) {
fprintf(stderr, "Error: failed to add immediately, closing now\n");
close(poll_fd);
}
}
free(self->event_extra_data[index].key_states);
free(self->event_extra_data[index].key_presses_grabbed);

View File

@@ -5,9 +5,11 @@
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *filename) {
static void get_socket_filepath(char *buffer, size_t buffer_size, const char *filename) {
char dir[PATH_MAX];
const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
@@ -23,7 +25,7 @@ static void get_runtime_filepath(char *buffer, size_t buffer_size, const char *f
}
/* Assumes |str| size is less than 256 */
static void fifo_write_all(int file_fd, const char *str) {
static void file_write_all(int file_fd, const char *str) {
char command[256];
const ssize_t command_size = snprintf(command, sizeof(command), "%s\n", str);
if(command_size >= (ssize_t)sizeof(command)) {
@@ -33,7 +35,7 @@ static void fifo_write_all(int file_fd, const char *str) {
ssize_t offset = 0;
while(offset < (ssize_t)command_size) {
const ssize_t bytes_written = write(file_fd, str + offset, command_size - offset);
const ssize_t bytes_written = write(file_fd, command + offset, command_size - offset);
if(bytes_written > 0)
offset += bytes_written;
}
@@ -112,15 +114,34 @@ int main(int argc, char **argv) {
usage();
}
char fifo_filepath[PATH_MAX];
get_runtime_filepath(fifo_filepath, sizeof(fifo_filepath), "gsr-ui");
const int fifo_fd = open(fifo_filepath, O_RDWR | O_NONBLOCK);
if(fifo_fd <= 0) {
fprintf(stderr, "Error: failed to open fifo file %s. Maybe gsr-ui is not running?\n", fifo_filepath);
char socket_filepath[PATH_MAX];
get_socket_filepath(socket_filepath, sizeof(socket_filepath), "gsr-ui");
const int socket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if(socket_fd <= 0) {
fprintf(stderr, "Error: failed to create socket\n");
exit(2);
}
fifo_write_all(fifo_fd, command);
close(fifo_fd);
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_filepath);
for(;;) {
if(connect(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
const int err = errno;
if(err == EWOULDBLOCK) {
usleep(10 * 1000);
} else {
fprintf(stderr, "Error: failed to connect, error: %s. Maybe gsr-ui is not running?\n", strerror(err));
exit(2);
}
} else {
break;
}
}
file_write_all(socket_fd, command);
close(socket_fd);
return 0;
}