mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-08 20:34:52 +09:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fded9b8d57 | ||
|
|
b80e3f8beb | ||
|
|
b807712d79 | ||
|
|
2df417f23f | ||
|
|
a82d1a2dfc | ||
|
|
043b6df255 | ||
|
|
831f583f89 |
@@ -14,7 +14,7 @@ A program called `gsr-ui-cli` is also installed when installing this software. T
|
||||
# Installation
|
||||
If you are using an Arch Linux based distro then you can find gpu screen recorder ui on aur under the name gpu-screen-recorder-ui (`yay -S gpu-screen-recorder-ui`).\
|
||||
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
|
||||
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder). This flatpak includes both this UI and gpu-screen-recorder so no need to install that first.
|
||||
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which includes this UI.
|
||||
|
||||
# Dependencies
|
||||
GPU Screen Recorder UI uses meson build system so you need to install `meson` to build GPU Screen Recorder UI.
|
||||
@@ -28,6 +28,7 @@ These are the dependencies needed to build GPU Screen Recorder UI:
|
||||
* libpulse (libpulse-simple)
|
||||
* libdrm
|
||||
* wayland (wayland-client, wayland-egl, wayland-scanner)
|
||||
* setcap (libcap)
|
||||
|
||||
## Runtime dependencies
|
||||
There are also additional dependencies needed at runtime:
|
||||
|
||||
2
TODO
2
TODO
@@ -37,8 +37,6 @@ Fix first frame being black when running without a compositor.
|
||||
|
||||
Add support for systray.
|
||||
|
||||
Add option to take screenshot.
|
||||
|
||||
Move event callbacks to a global list instead of std::function object in each widget. This reduces the size of widgets,
|
||||
since most widgets wont have the event callback set.
|
||||
This event callback would pass the widget as an argument.
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace gsr {
|
||||
std::string video_quality = "very_high";
|
||||
std::string video_codec = "auto";
|
||||
std::string audio_codec = "opus";
|
||||
std::string framerate_mode = "vfr";
|
||||
std::string framerate_mode = "auto";
|
||||
bool advanced_view = false;
|
||||
bool overclock = false;
|
||||
bool record_cursor = true;
|
||||
@@ -79,6 +79,10 @@ namespace gsr {
|
||||
std::string stream_key;
|
||||
};
|
||||
|
||||
struct RumbleStreamConfig {
|
||||
std::string stream_key;
|
||||
};
|
||||
|
||||
struct CustomStreamConfig {
|
||||
std::string url;
|
||||
std::string container = "flv";
|
||||
@@ -91,6 +95,7 @@ namespace gsr {
|
||||
std::string streaming_service = "twitch";
|
||||
YoutubeStreamConfig youtube;
|
||||
TwitchStreamConfig twitch;
|
||||
RumbleStreamConfig rumble;
|
||||
CustomStreamConfig custom;
|
||||
ConfigHotkey start_stop_hotkey;
|
||||
};
|
||||
|
||||
@@ -192,6 +192,7 @@ namespace gsr {
|
||||
Button *save_directory_button_ptr = nullptr;
|
||||
Entry *twitch_stream_key_entry_ptr = nullptr;
|
||||
Entry *youtube_stream_key_entry_ptr = nullptr;
|
||||
Entry *rumble_stream_key_entry_ptr = nullptr;
|
||||
Entry *stream_url_entry_ptr = nullptr;
|
||||
Entry *replay_time_entry_ptr = nullptr;
|
||||
RadioButton *replay_storage_button_ptr = nullptr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.6.4', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.6.5', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
|
||||
if get_option('buildtype') == 'debug'
|
||||
add_project_arguments('-g3', language : ['c', 'cpp'])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.6.4"
|
||||
version = "1.6.5"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -9,14 +9,8 @@
|
||||
|
||||
namespace gsr {
|
||||
static const int MAX_CONNECTORS = 32;
|
||||
static const int CONNECTOR_TYPE_COUNTS = 32;
|
||||
static const uint32_t plane_property_all = 0xF;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
int count;
|
||||
} drm_connector_type_count;
|
||||
|
||||
typedef enum {
|
||||
PLANE_PROPERTY_CRTC_X = 1 << 0,
|
||||
PLANE_PROPERTY_CRTC_Y = 1 << 1,
|
||||
@@ -105,22 +99,6 @@ namespace gsr {
|
||||
return get_drm_property_by_name(drm_fd, &properties, name, result);
|
||||
}
|
||||
|
||||
static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) {
|
||||
for(int i = 0; i < *num_type_counts; ++i) {
|
||||
if(type_counts[i].type == connector_type)
|
||||
return &type_counts[i];
|
||||
}
|
||||
|
||||
if(*num_type_counts == CONNECTOR_TYPE_COUNTS)
|
||||
return NULL;
|
||||
|
||||
const int index = *num_type_counts;
|
||||
type_counts[index].type = connector_type;
|
||||
type_counts[index].count = 0;
|
||||
++*num_type_counts;
|
||||
return &type_counts[index];
|
||||
}
|
||||
|
||||
// Note: this monitor name logic is kept in sync with gpu screen recorder
|
||||
static std::string get_monitor_name_from_crtc_id(int drm_fd, uint32_t crtc_id) {
|
||||
std::string result;
|
||||
@@ -128,27 +106,23 @@ namespace gsr {
|
||||
if(!resources)
|
||||
return result;
|
||||
|
||||
drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS];
|
||||
int num_type_counts = 0;
|
||||
|
||||
for(int i = 0; i < resources->count_connectors; ++i) {
|
||||
uint64_t connector_crtc_id = 0;
|
||||
drmModeConnectorPtr connector = drmModeGetConnectorCurrent(drm_fd, resources->connectors[i]);
|
||||
if(!connector)
|
||||
continue;
|
||||
|
||||
drm_connector_type_count *connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector->connector_type);
|
||||
const char *connection_name = drmModeGetConnectorTypeName(connector->connector_type);
|
||||
if(connector_type)
|
||||
++connector_type->count;
|
||||
if(!connection_name)
|
||||
goto next;
|
||||
|
||||
if(connector->connection != DRM_MODE_CONNECTED)
|
||||
goto next;
|
||||
|
||||
if(connector_type && connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &connector_crtc_id) && connector_crtc_id == crtc_id) {
|
||||
if(connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &connector_crtc_id) && connector_crtc_id == crtc_id) {
|
||||
result = connection_name;
|
||||
result += "-";
|
||||
result += std::to_string(connector_type->count);
|
||||
result += std::to_string(connector->connector_type_id);
|
||||
drmModeFreeConnector(connector);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,68 @@ namespace gsr {
|
||||
static constexpr int axis_up_down = 7;
|
||||
static constexpr int axis_left_right = 6;
|
||||
|
||||
struct DeviceId {
|
||||
uint16_t vendor;
|
||||
uint16_t product;
|
||||
};
|
||||
|
||||
static bool read_file_hex_number(const char *path, unsigned int *value) {
|
||||
*value = 0;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if(!f)
|
||||
return false;
|
||||
|
||||
fscanf(f, "%x", value);
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static DeviceId joystick_get_device_id(const char *path) {
|
||||
DeviceId device_id;
|
||||
device_id.vendor = 0;
|
||||
device_id.product = 0;
|
||||
|
||||
const char *js_path_id = nullptr;
|
||||
const int len = strlen(path);
|
||||
for(int i = len - 1; i >= 0; --i) {
|
||||
if(path[i] == '/') {
|
||||
js_path_id = path + i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!js_path_id)
|
||||
return device_id;
|
||||
|
||||
unsigned int vendor = 0;
|
||||
unsigned int product = 0;
|
||||
char path_buf[1024];
|
||||
|
||||
snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/vendor", js_path_id);
|
||||
if(!read_file_hex_number(path_buf, &vendor))
|
||||
return device_id;
|
||||
|
||||
snprintf(path_buf, sizeof(path_buf), "/sys/class/input/%s/device/id/product", js_path_id);
|
||||
if(!read_file_hex_number(path_buf, &product))
|
||||
return device_id;
|
||||
|
||||
device_id.vendor = vendor;
|
||||
device_id.product = product;
|
||||
return device_id;
|
||||
}
|
||||
|
||||
static bool is_ps4_controller(DeviceId device_id) {
|
||||
return device_id.vendor == 0x054C && (device_id.product == 0x09CC || device_id.product == 0x0BA0 || device_id.product == 0x05C4);
|
||||
}
|
||||
|
||||
static bool is_ps5_controller(DeviceId device_id) {
|
||||
return device_id.vendor == 0x054C && (device_id.product == 0x0DF2 || device_id.product == 0x0CE6);
|
||||
}
|
||||
|
||||
static bool is_stadia_controller(DeviceId device_id) {
|
||||
return device_id.vendor == 0x18D1 && (device_id.product == 0x9400);
|
||||
}
|
||||
|
||||
// Returns -1 on error
|
||||
static int get_js_dev_input_id_from_filepath(const char *dev_input_filepath) {
|
||||
if(strncmp(dev_input_filepath, "/dev/input/js", 13) != 0)
|
||||
@@ -276,6 +338,8 @@ namespace gsr {
|
||||
dev_input_id
|
||||
};
|
||||
|
||||
//const DeviceId device_id = joystick_get_device_id(dev_input_filepath);
|
||||
|
||||
++num_poll_fd;
|
||||
fprintf(stderr, "Info: added joystick: %s\n", dev_input_filepath);
|
||||
return true;
|
||||
|
||||
@@ -37,6 +37,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>
|
||||
@@ -903,6 +904,7 @@ namespace gsr {
|
||||
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
|
||||
// If the focused window is a wayland application then don't use override redirect and instead create
|
||||
// a fullscreen window for the ui.
|
||||
// TODO: (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor))
|
||||
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots;
|
||||
|
||||
if(prevent_game_minimizing) {
|
||||
@@ -1641,8 +1643,21 @@ namespace gsr {
|
||||
}
|
||||
|
||||
static void truncate_string(std::string &str, int max_length) {
|
||||
if((int)str.size() > max_length)
|
||||
str.replace(str.begin() + max_length, str.end(), "...");
|
||||
int index = 0;
|
||||
size_t byte_index = 0;
|
||||
|
||||
while(index < max_length && byte_index < str.size()) {
|
||||
uint32_t codepoint = 0;
|
||||
size_t codepoint_length = 0;
|
||||
mgl::utf8_decode((const unsigned char*)str.c_str() + byte_index, str.size() - byte_index, &codepoint, &codepoint_length);
|
||||
if(codepoint_length == 0)
|
||||
codepoint_length = 1;
|
||||
|
||||
index += 1;
|
||||
byte_index += codepoint_length;
|
||||
}
|
||||
|
||||
str.erase(byte_index);
|
||||
}
|
||||
|
||||
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
|
||||
@@ -2579,6 +2594,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)
|
||||
|
||||
@@ -964,6 +964,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;
|
||||
@@ -988,6 +989,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;
|
||||
}
|
||||
@@ -1049,12 +1054,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");
|
||||
@@ -1256,6 +1263,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);
|
||||
}
|
||||
@@ -1397,6 +1405,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();
|
||||
}
|
||||
|
||||
@@ -44,42 +44,42 @@ int main(int argc, char **argv) {
|
||||
} else if(strcmp(grab_type_arg, "--virtual") == 0) {
|
||||
grab_type = KEYBOARD_GRAB_TYPE_VIRTUAL;
|
||||
} else {
|
||||
fprintf(stderr, "Error: expected --all or --virtual, got %s\n", grab_type_arg);
|
||||
fprintf(stderr, "gsr-global-hotkeys error: expected --all or --virtual, got %s\n", grab_type_arg);
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
} else if(argc != 1) {
|
||||
fprintf(stderr, "Error: expected 0 or 1 arguments, got %d argument(s)\n", argc);
|
||||
fprintf(stderr, "gsr-global-hotkeys error: expected 0 or 1 arguments, got %d argument(s)\n", argc);
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(is_gsr_global_hotkeys_already_running()) {
|
||||
fprintf(stderr, "Error: gsr-global-hotkeys is already running\n");
|
||||
fprintf(stderr, "gsr-global-hotkeys error: gsr-global-hotkeys is already running\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const uid_t user_id = getuid();
|
||||
if(geteuid() != 0) {
|
||||
if(setuid(0) == -1) {
|
||||
fprintf(stderr, "Error: failed to change user to root\n");
|
||||
fprintf(stderr, "gsr-global-hotkeys error: failed to change user to root, global hotkeys will not work. Make sure to set the correct capability on gsr-global-hotkeys\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
keyboard_event keyboard_ev;
|
||||
if(!keyboard_event_init(&keyboard_ev, true, grab_type)) {
|
||||
fprintf(stderr, "Error: failed to setup hotplugging and no keyboard input devices were found\n");
|
||||
fprintf(stderr, "gsr-global-hotkeys error: failed to setup hotplugging and no keyboard input devices were found\n");
|
||||
setuid(user_id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Info: global hotkeys setup, waiting for hotkeys to be pressed\n");
|
||||
fprintf(stderr, "gsr-global-hotkeys info: global hotkeys setup, waiting for hotkeys to be pressed\n");
|
||||
|
||||
for(;;) {
|
||||
keyboard_event_poll_events(&keyboard_ev, -1);
|
||||
if(keyboard_event_stdin_has_failed(&keyboard_ev)) {
|
||||
fprintf(stderr, "Info: stdin closed (parent process likely closed this process), exiting...\n");
|
||||
fprintf(stderr, "gsr-global-hotkeys info: stdin closed (parent process likely closed this process), exiting...\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user