mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-07 20:08:07 +09:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca4061f171 | ||
|
|
0b4af1e6bb | ||
|
|
9e03cd0354 | ||
|
|
3d4badf5cd | ||
|
|
071ecf46de | ||
|
|
5ee2b95384 | ||
|
|
d610a980f8 | ||
|
|
70780ae14e | ||
|
|
5f7cb94f4e | ||
|
|
748c51e2b6 | ||
|
|
3ba9ce771b | ||
|
|
c18b062180 | ||
|
|
705da21363 | ||
|
|
609a3e54fd | ||
|
|
4e62d12e8c | ||
|
|
b4e003c8f7 | ||
|
|
9efe9d3c91 | ||
|
|
ef4a0fe7cb |
14
README.md
14
README.md
@@ -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.
|
||||
|
||||
|
||||
9
TODO
9
TODO
@@ -206,3 +206,12 @@ 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.
|
||||
|
||||
Submodule depends/mglpp updated: 8489234dcb...50b2b007cc
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.8', 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.2"', language: ['c', 'cpp'])
|
||||
|
||||
executable(
|
||||
meson.project_name(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.7.4"
|
||||
version = "1.7.8"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
@@ -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;
|
||||
@@ -1700,7 +1700,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 +1828,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;
|
||||
}
|
||||
|
||||
@@ -2560,6 +2558,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 +2638,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 +2835,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 +3011,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();
|
||||
|
||||
204
src/Rpc.cpp
204
src/Rpc.cpp
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
14
src/main.cpp
14
src/main.cpp
@@ -195,7 +195,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 +226,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(is_gsr_ui_virtual_keyboard_running() || 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 +242,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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user