aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Config.cpp1
-rw-r--r--src/GlobalHotkeysLinux.cpp17
-rw-r--r--src/Overlay.cpp307
-rw-r--r--src/Process.cpp13
-rw-r--r--src/WindowUtils.cpp334
-rw-r--r--src/gui/GlobalSettingsPage.cpp41
-rw-r--r--src/gui/RadioButton.cpp20
-rw-r--r--src/gui/SettingsPage.cpp96
-rw-r--r--src/main.cpp86
9 files changed, 654 insertions, 261 deletions
diff --git a/src/Config.cpp b/src/Config.cpp
index a9c8843..4ad1107 100644
--- a/src/Config.cpp
+++ b/src/Config.cpp
@@ -58,6 +58,7 @@ namespace gsr {
return {
{"main.config_file_version", &config.main_config.config_file_version},
{"main.software_encoding_warning_shown", &config.main_config.software_encoding_warning_shown},
+ {"main.hotkeys_enable_option", &config.main_config.hotkeys_enable_option},
{"main.tint_color", &config.main_config.tint_color},
{"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option},
diff --git a/src/GlobalHotkeysLinux.cpp b/src/GlobalHotkeysLinux.cpp
index d16cc06..3d1813d 100644
--- a/src/GlobalHotkeysLinux.cpp
+++ b/src/GlobalHotkeysLinux.cpp
@@ -9,7 +9,15 @@
#define PIPE_WRITE 1
namespace gsr {
- GlobalHotkeysLinux::GlobalHotkeysLinux() {
+ static const char* grab_type_to_arg(GlobalHotkeysLinux::GrabType grab_type) {
+ switch(grab_type) {
+ case GlobalHotkeysLinux::GrabType::ALL: return "--all";
+ case GlobalHotkeysLinux::GrabType::VIRTUAL: return "--virtual";
+ }
+ return "--all";
+ }
+
+ GlobalHotkeysLinux::GlobalHotkeysLinux(GrabType grab_type) : grab_type(grab_type) {
for(int i = 0; i < 2; ++i) {
pipes[i] = -1;
}
@@ -32,6 +40,7 @@ namespace gsr {
}
bool GlobalHotkeysLinux::start() {
+ const char *grab_type_arg = grab_type_to_arg(grab_type);
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *user_homepath = getenv("HOME");
if(!user_homepath)
@@ -61,14 +70,14 @@ namespace gsr {
}
if(inside_flatpak) {
- const char *args[] = { "flatpak-spawn", "--host", "--", gsr_global_hotkeys_flatpak, NULL };
+ const char *args[] = { "flatpak-spawn", "--host", "--", gsr_global_hotkeys_flatpak, grab_type_arg, nullptr };
execvp(args[0], (char* const*)args);
} else {
- const char *args[] = { "gsr-global-hotkeys", NULL };
+ const char *args[] = { "gsr-global-hotkeys", grab_type_arg, nullptr };
execvp(args[0], (char* const*)args);
}
- perror("execvp");
+ perror("gsr-global-hotkeys");
_exit(127);
} else { /* parent */
process_id = pid;
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
index 9a795ee..aa14e3b 100644
--- a/src/Overlay.cpp
+++ b/src/Overlay.cpp
@@ -29,6 +29,7 @@
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/shape.h>
+#include <X11/Xcursor/Xcursor.h>
#include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp>
@@ -41,10 +42,6 @@ namespace gsr {
static const double force_window_on_top_timeout_seconds = 1.0;
static const double replay_status_update_check_timeout_seconds = 1.0;
- static bool is_focused_application_wayland(Display *dpy) {
- return get_focused_window(dpy, WindowCaptureType::FOCUSED) == 0;
- }
-
static mgl::Texture texture_from_ximage(XImage *img) {
uint8_t *texture_data = (uint8_t*)malloc(img->width * img->height * 3);
// TODO:
@@ -70,17 +67,17 @@ namespace gsr {
return texture;
}
- static bool texture_from_x11_cursor(XFixesCursorImage *x11_cursor_image, bool *visible, mgl::vec2i *hotspot, mgl::Texture &texture) {
+ static bool texture_from_x11_cursor(XcursorImage *x11_cursor_image, bool *visible, mgl::vec2i *hotspot, mgl::Texture &texture) {
uint8_t *cursor_data = NULL;
uint8_t *out = NULL;
- const unsigned long *pixels = NULL;
+ const unsigned int *pixels = NULL;
*visible = false;
if(!x11_cursor_image)
- goto err;
+ return false;
if(!x11_cursor_image->pixels)
- goto err;
+ return false;
hotspot->x = x11_cursor_image->xhot;
hotspot->y = x11_cursor_image->yhot;
@@ -88,12 +85,12 @@ namespace gsr {
pixels = x11_cursor_image->pixels;
cursor_data = (uint8_t*)malloc((int)x11_cursor_image->width * (int)x11_cursor_image->height * 4);
if(!cursor_data)
- goto err;
+ return false;
out = cursor_data;
/* Un-premultiply alpha */
- for(int y = 0; y < x11_cursor_image->height; ++y) {
- for(int x = 0; x < x11_cursor_image->width; ++x) {
+ for(uint32_t y = 0; y < x11_cursor_image->height; ++y) {
+ for(uint32_t x = 0; x < x11_cursor_image->width; ++x) {
uint32_t pixel = *pixels++;
uint8_t *in = (uint8_t*)&pixel;
uint8_t alpha = in[3];
@@ -114,13 +111,7 @@ namespace gsr {
texture.load_from_memory(cursor_data, x11_cursor_image->width, x11_cursor_image->height, MGL_IMAGE_FORMAT_RGBA);
free(cursor_data);
- XFree(x11_cursor_image);
return true;
-
- err:
- if(x11_cursor_image)
- XFree(x11_cursor_image);
- return false;
}
static char hex_value_to_str(uint8_t v) {
@@ -299,81 +290,6 @@ namespace gsr {
return &win->monitors[0];
}
- static mgl::vec2i create_window_get_center_position(Display *display) {
- const int size = 16;
- XSetWindowAttributes window_attr;
- window_attr.event_mask = StructureNotifyMask;
- window_attr.background_pixel = 0;
- const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr);
- if(!window)
- return {0, 0};
-
- const Atom net_wm_window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
- const Atom net_wm_window_type_notification_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
- const Atom net_wm_window_type_utility = XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
- const Atom net_wm_window_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
-
- const Atom window_type_atoms[2] = {
- net_wm_window_type_notification_atom,
- net_wm_window_type_utility
- };
- XChangeProperty(display, window, net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)window_type_atoms, 2L);
-
- const double alpha = 0.0;
- const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha);
- XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
-
- XSizeHints *size_hints = XAllocSizeHints();
- size_hints->width = size;
- size_hints->min_width = size;
- size_hints->max_width = size;
- size_hints->height = size;
- size_hints->min_height = size;
- size_hints->max_height = size;
- size_hints->flags = PSize | PMinSize | PMaxSize;
- XSetWMNormalHints(display, window, size_hints);
- XFree(size_hints);
-
- XMapWindow(display, window);
- XFlush(display);
-
- const int x_fd = XConnectionNumber(display);
- mgl::vec2i window_pos;
- XEvent xev;
- while(true) {
- struct pollfd poll_fd;
- poll_fd.fd = x_fd;
- poll_fd.events = POLLIN;
- poll_fd.revents = 0;
- const int fds_ready = poll(&poll_fd, 1, 1000);
- if(fds_ready == 0) {
- fprintf(stderr, "Error: timed out waiting for ConfigureNotify after XCreateWindow\n");
- break;
- } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) {
- continue;
- }
-
- XNextEvent(display, &xev);
- if(xev.type == ConfigureNotify) {
- window_pos.x = xev.xconfigure.x + xev.xconfigure.width / 2;
- window_pos.y = xev.xconfigure.y + xev.xconfigure.height / 2;
- break;
- }
- }
-
- XDestroyWindow(display, window);
- XFlush(display);
-
- return window_pos;
- }
-
- static bool is_compositor_running(Display *dpy, int screen) {
- char prop_name[20];
- snprintf(prop_name, sizeof(prop_name), "_NET_WM_CM_S%d", screen);
- Atom prop_atom = XInternAtom(dpy, prop_name, False);
- return XGetSelectionOwner(dpy, prop_atom) != None;
- }
-
static std::string get_power_supply_online_filepath() {
std::string result;
const char *paths[] = {
@@ -431,9 +347,6 @@ namespace gsr {
top_bar_background({0.0f, 0.0f}),
close_button_widget({0.0f, 0.0f})
{
- // TODO:
- //xi_setup();
-
key_bindings[0].key_event.code = mgl::Keyboard::Escape;
key_bindings[0].key_event.alt = false;
key_bindings[0].key_event.control = false;
@@ -470,8 +383,6 @@ namespace gsr {
notification_process = -1;
}
- close_gpu_screen_recorder_output();
-
if(gpu_screen_recorder_process > 0) {
kill(gpu_screen_recorder_process, SIGINT);
int status;
@@ -482,10 +393,8 @@ namespace gsr {
gpu_screen_recorder_process = -1;
}
- free(xi_input_xev);
- free(xi_output_xev);
- if(xi_display)
- XCloseDisplay(xi_display);
+ close_gpu_screen_recorder_output();
+ deinit_color_theme();
}
void Overlay::xi_setup() {
@@ -509,6 +418,7 @@ namespace gsr {
unsigned char mask[XIMaskLen(XI_LASTEVENT)];
memset(mask, 0, sizeof(mask));
XISetMask(mask, XI_Motion);
+ //XISetMask(mask, XI_RawMotion);
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_ButtonRelease);
XISetMask(mask, XI_KeyPress);
@@ -567,8 +477,8 @@ namespace gsr {
xi_output_xev->xmotion.display = display;
xi_output_xev->xmotion.window = window->get_system_handle();
xi_output_xev->xmotion.subwindow = window->get_system_handle();
- xi_output_xev->xmotion.x = de->event_x;
- xi_output_xev->xmotion.y = de->event_y;
+ xi_output_xev->xmotion.x = de->root_x - window_pos.x;
+ xi_output_xev->xmotion.y = de->root_y - window_pos.y;
xi_output_xev->xmotion.x_root = de->root_x;
xi_output_xev->xmotion.y_root = de->root_y;
//xi_output_xev->xmotion.state = // modifiers // TODO:
@@ -580,8 +490,8 @@ namespace gsr {
xi_output_xev->xbutton.display = display;
xi_output_xev->xbutton.window = window->get_system_handle();
xi_output_xev->xbutton.subwindow = window->get_system_handle();
- xi_output_xev->xbutton.x = de->event_x;
- xi_output_xev->xbutton.y = de->event_y;
+ xi_output_xev->xbutton.x = de->root_x - window_pos.x;
+ xi_output_xev->xbutton.y = de->root_y - window_pos.y;
xi_output_xev->xbutton.x_root = de->root_x;
xi_output_xev->xbutton.y_root = de->root_y;
//xi_output_xev->xbutton.state = // modifiers // TODO:
@@ -594,8 +504,8 @@ namespace gsr {
xi_output_xev->xkey.display = display;
xi_output_xev->xkey.window = window->get_system_handle();
xi_output_xev->xkey.subwindow = window->get_system_handle();
- xi_output_xev->xkey.x = de->event_x;
- xi_output_xev->xkey.y = de->event_y;
+ xi_output_xev->xkey.x = de->root_x - window_pos.x;
+ xi_output_xev->xkey.y = de->root_y - window_pos.y;
xi_output_xev->xkey.x_root = de->root_x;
xi_output_xev->xkey.y_root = de->root_y;
xi_output_xev->xkey.state = de->mods.effective;
@@ -694,12 +604,6 @@ namespace gsr {
page_stack.draw(*window, mgl::vec2f(0.0f, 0.0f));
if(cursor_texture.is_valid()) {
- if(!cursor_drawn) {
- cursor_drawn = true;
- XFixesHideCursor(xi_display, DefaultRootWindow(xi_display));
- XFlush(xi_display);
- }
-
cursor_sprite.set_position((window->get_mouse_position() - cursor_hotspot).to_vec2f());
window->draw(cursor_sprite);
}
@@ -737,45 +641,35 @@ namespace gsr {
if(!xi_display)
return;
- XFixesShowCursor(xi_display, DefaultRootWindow(xi_display));
+ XFixesHideCursor(xi_display, DefaultRootWindow(xi_display));
XFlush(xi_display);
- bool cursor_visible = false;
- texture_from_x11_cursor(XFixesGetCursorImage(xi_display), &cursor_visible, &cursor_hotspot, cursor_texture);
- if(cursor_texture.is_valid())
- cursor_sprite.set_texture(&cursor_texture);
- }
+ // TODO: XCURSOR_SIZE and XCURSOR_THEME environment variables
+ const char *cursor_theme = XcursorGetTheme(xi_display);
+ if(!cursor_theme) {
+ //fprintf(stderr, "Warning: failed to get cursor theme, using \"default\" theme instead\n");
+ cursor_theme = "default";
+ }
- void Overlay::xi_grab_all_devices() {
- if(!xi_display)
- return;
+ int cursor_size = XcursorGetDefaultSize(xi_display);
+ if(cursor_size <= 1)
+ cursor_size = 24;
- int num_devices = 0;
- XIDeviceInfo *info = XIQueryDevice(xi_display, XIAllDevices, &num_devices);
- if(!info)
+ XcursorImage *cursor_image = XcursorShapeLoadImage(XC_left_ptr, cursor_theme, cursor_size);
+ if(!cursor_image) {
+ fprintf(stderr, "Error: failed to get cursor\n");
return;
-
- for (int i = 0; i < num_devices; ++i) {
- const XIDeviceInfo *dev = &info[i];
- XIEventMask masks[1];
- unsigned char mask0[XIMaskLen(XI_LASTEVENT)];
- memset(mask0, 0, sizeof(mask0));
- XISetMask(mask0, XI_Motion);
- XISetMask(mask0, XI_ButtonPress);
- XISetMask(mask0, XI_ButtonRelease);
- XISetMask(mask0, XI_KeyPress);
- XISetMask(mask0, XI_KeyRelease);
- masks[0].deviceid = dev->deviceid;
- masks[0].mask_len = sizeof(mask0);
- masks[0].mask = mask0;
- XIGrabDevice(xi_display, dev->deviceid, window->get_system_handle(), CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XIOwnerEvents, masks);
}
- XFlush(xi_display);
- XIFreeDeviceInfo(info);
+ bool cursor_visible = false;
+ texture_from_x11_cursor(cursor_image, &cursor_visible, &cursor_hotspot, cursor_texture);
+ if(cursor_texture.is_valid())
+ cursor_sprite.set_texture(&cursor_texture);
+
+ XcursorImageDestroy(cursor_image);
}
- void Overlay::xi_warp_pointer(mgl::vec2i position) {
+ void Overlay::xi_grab_all_devices() {
if(!xi_display)
return;
@@ -784,9 +678,22 @@ namespace gsr {
if(!info)
return;
+ unsigned char mask[XIMaskLen(XI_LASTEVENT)];
+ memset(mask, 0, sizeof(mask));
+ XISetMask(mask, XI_Motion);
+ //XISetMask(mask, XI_RawMotion);
+ XISetMask(mask, XI_ButtonPress);
+ XISetMask(mask, XI_ButtonRelease);
+ XISetMask(mask, XI_KeyPress);
+ XISetMask(mask, XI_KeyRelease);
+
for (int i = 0; i < num_devices; ++i) {
const XIDeviceInfo *dev = &info[i];
- XIWarpPointer(xi_display, dev->deviceid, DefaultRootWindow(xi_display), DefaultRootWindow(xi_display), 0, 0, 0, 0, position.x, position.y);
+ XIEventMask xi_masks;
+ xi_masks.deviceid = dev->deviceid;
+ xi_masks.mask_len = sizeof(mask);
+ xi_masks.mask = mask;
+ XIGrabDevice(xi_display, dev->deviceid, window->get_system_handle(), CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XIOwnerEvents, &xi_masks);
}
XFlush(xi_display);
@@ -805,24 +712,37 @@ namespace gsr {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
+ const std::string wm_name = get_window_manager_name(display);
+ const bool is_kwin = wm_name == "KWin";
+
+ // The cursor position is wrong on wayland if an x11 window is not focused. On wayland we instead create a window and get the position where the wayland compositor puts it
+ Window x11_cursor_window = None;
+ const mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window);
+ const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display);
+
// Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused.
// If the focused window is a wayland application then don't use override redirect and instead create
// a fullscreen window for the ui.
- const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || !is_focused_application_wayland(display);
+ const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window;
- mgl::vec2i window_size = { 1280, 720 };
- mgl::vec2i window_pos = { 0, 0 };
+ window_size = { 32, 32 };
+ window_pos = { 0, 0 };
mgl::Window::CreateParams window_create_params;
window_create_params.size = window_size;
- window_create_params.min_size = window_size;
- window_create_params.max_size = window_size;
+ if(prevent_game_minimizing) {
+ window_create_params.min_size = window_size;
+ window_create_params.max_size = window_size;
+ }
window_create_params.position = window_pos;
- window_create_params.hidden = true;
+ window_create_params.hidden = prevent_game_minimizing;
window_create_params.override_redirect = prevent_game_minimizing;
window_create_params.background_color = bg_color;
window_create_params.support_alpha = true;
- window_create_params.window_type = MGL_WINDOW_TYPE_NORMAL;
+ window_create_params.hide_decorations = true;
+ // MGL_WINDOW_TYPE_DIALOG is needed for kde plasma wayland in some cases, otherwise the window will pop up on another activity
+ // or may not be visible at all
+ window_create_params.window_type = (is_kwin && gsr_info.system_info.display_server == DisplayServer::WAYLAND) ? MGL_WINDOW_TYPE_DIALOG : MGL_WINDOW_TYPE_NORMAL;
window_create_params.render_api = MGL_RENDER_API_EGL;
if(!window->create("gsr ui", window_create_params))
@@ -842,19 +762,23 @@ namespace gsr {
mgl_window *win = window->internal_window();
if(win->num_monitors == 0) {
fprintf(stderr, "gsr warning: no monitors found, not showing overlay\n");
+ window.reset();
return;
}
- // The cursor position is wrong on wayland if an x11 window is not focused. On wayland we instead create a window and get the position where the wayland compositor puts it
- const mgl::vec2i monitor_position_query_value = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? create_window_get_center_position(display) : mgl::vec2i(win->cursor_position.x, win->cursor_position.y);
const mgl_monitor *focused_monitor = find_monitor_at_position(*window, monitor_position_query_value);
window_pos = {focused_monitor->pos.x, focused_monitor->pos.y};
window_size = {focused_monitor->size.x, focused_monitor->size.y};
get_theme().set_window_size(window_size);
- window->set_size(window_size);
- window->set_size_limits(window_size, window_size);
- window->set_position(window_pos);
+ if(prevent_game_minimizing) {
+ window->set_size(window_size);
+ window->set_size_limits(window_size, window_size);
+ window->set_position(window_pos);
+ }
+
+ win->cursor_position.x = cursor_position.x - window_pos.x;
+ win->cursor_position.y = cursor_position.y - window_pos.y;
update_compositor_texture(focused_monitor);
@@ -1032,6 +956,11 @@ namespace gsr {
return true;
};
+ // The focused application can be an xwayland application but the cursor can hover over a wayland application.
+ // This is even the case when hovering over the titlebar of the xwayland application.
+ if(prevent_game_minimizing)
+ xi_setup();
+
//window->set_fullscreen(true);
if(gsr_info.system_info.display_server == DisplayServer::X11)
make_window_click_through(display, window->get_system_handle());
@@ -1045,7 +974,7 @@ namespace gsr {
XFreeCursor(display, default_cursor);
default_cursor = 0;
}
- default_cursor = XCreateFontCursor(display, XC_arrow);
+ default_cursor = XCreateFontCursor(display, XC_left_ptr);
XFlush(display);
grab_mouse_and_keyboard();
@@ -1107,10 +1036,6 @@ namespace gsr {
XFlush(display);
if(xi_display) {
- // TODO: Only show cursor if it wasn't hidden before the ui was shown
- cursor_drawn = false;
- //XFixesShowCursor(xi_display, DefaultRootWindow(xi_display));
- //XFlush(xi_display);
cursor_texture.clear();
cursor_sprite.set_texture(nullptr);
}
@@ -1123,29 +1048,45 @@ namespace gsr {
visible = false;
drawn_first_frame = false;
- if(window) {
- const mgl::vec2i new_cursor_position = mgl::vec2i(window->internal_window()->pos.x, window->internal_window()->pos.y) + window->get_mouse_position();
- window->set_visible(false);
- window.reset();
+ if(xi_input_xev) {
+ free(xi_input_xev);
+ xi_input_xev = nullptr;
+ }
- if(xi_display) {
- XFlush(display);
+ if(xi_output_xev) {
+ free(xi_output_xev);
+ xi_output_xev = nullptr;
+ }
+
+ if(xi_display) {
+ XCloseDisplay(xi_display);
+ xi_display = nullptr;
+
+ if(window) {
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+
+ const mgl::vec2i new_cursor_position = mgl::vec2i(window->internal_window()->pos.x, window->internal_window()->pos.y) + window->get_mouse_position();
XWarpPointer(display, DefaultRootWindow(display), DefaultRootWindow(display), 0, 0, 0, 0, new_cursor_position.x, new_cursor_position.y);
XFlush(display);
- //xi_warp_pointer(new_cursor_position);
- XFixesShowCursor(xi_display, DefaultRootWindow(xi_display));
- XFlush(xi_display);
+ XFixesShowCursor(display, DefaultRootWindow(display));
+ XFlush(display);
}
}
+ if(window) {
+ window->set_visible(false);
+ window.reset();
+ }
+
deinit_theme();
}
void Overlay::toggle_show() {
if(visible) {
//hide();
- // We dont want to hide immediately because hide is called in event callback, in which it destroys the window.
+ // We dont want to hide immediately because hide is called in mgl event callback, in which it destroys the mgl window.
// Instead remove all pages and wait until next iteration to close the UI (which happens when there are no pages to render).
while(!page_stack.empty()) {
page_stack.pop();
@@ -1211,7 +1152,7 @@ namespace gsr {
const char *notification_type_str = notification_type_to_string(notification_type);
if(notification_type_str) {
notification_args[9] = "--icon";
- notification_args[10] = "record",
+ notification_args[10] = notification_type_str;
notification_args[11] = nullptr;
} else {
notification_args[9] = nullptr;
@@ -1241,6 +1182,10 @@ namespace gsr {
do_exit = true;
}
+ const Config& Overlay::get_config() const {
+ return config;
+ }
+
void Overlay::update_notification_process_status() {
if(notification_process <= 0)
return;
@@ -1306,12 +1251,18 @@ namespace gsr {
truncate_string(focused_window_name, 20);
std::string text;
switch(notification_type) {
- case NotificationType::RECORD:
+ case NotificationType::RECORD: {
+ if(!config.record_config.show_video_saved_notifications)
+ return;
text = "Saved recording to '" + focused_window_name + "/" + video_filename + "'";
break;
- case NotificationType::REPLAY:
+ }
+ case NotificationType::REPLAY: {
+ if(!config.replay_config.show_replay_saved_notifications)
+ return;
text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'";
break;
+ }
case NotificationType::NONE:
case NotificationType::STREAM:
break;
@@ -1973,11 +1924,11 @@ namespace gsr {
"-fm", framerate_mode.c_str(),
"-encoder", encoder,
"-f", fps.c_str(),
- "-f", fps.c_str(),
"-v", "no",
"-o", url.c_str()
};
+ config.streaming_config.record_options.merge_audio_tracks = true;
add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged);
args.push_back(nullptr);
@@ -2016,8 +1967,10 @@ namespace gsr {
return false;
bool window_texture_loaded = false;
- const Window focused_window = get_focused_window(display, WindowCaptureType::FOCUSED);
- if(is_window_fullscreen_on_monitor(display, focused_window, monitor) && focused_window)
+ Window focused_window = get_focused_window(display, WindowCaptureType::CURSOR);
+ if(!focused_window)
+ focused_window = get_focused_window(display, WindowCaptureType::FOCUSED);
+ if(focused_window && is_window_fullscreen_on_monitor(display, focused_window, monitor))
window_texture_loaded = window_texture_init(&window_texture, display, mgl_window_get_egl_display(window->internal_window()), focused_window, egl_funcs) == 0;
if(window_texture_loaded && window_texture.texture_id) {
diff --git a/src/Process.cpp b/src/Process.cpp
index 347c485..c5fcf0f 100644
--- a/src/Process.cpp
+++ b/src/Process.cpp
@@ -59,7 +59,7 @@ namespace gsr {
const pid_t second_child = vfork();
if(second_child == 0) { // child
execvp(args[0], (char* const*)args);
- perror("execvp");
+ perror(args[0]);
_exit(127);
} else if(second_child != -1) {
// TODO:
@@ -98,7 +98,7 @@ namespace gsr {
close(fds[PIPE_WRITE]);
execvp(args[0], (char* const*)args);
- perror("execvp");
+ perror(args[0]);
_exit(127);
} else { /* parent */
close(fds[PIPE_WRITE]);
@@ -206,7 +206,7 @@ namespace gsr {
return false;
}
- pid_t pidof(const char *process_name) {
+ pid_t pidof(const char *process_name, pid_t ignore_pid) {
pid_t result = -1;
DIR *dir = opendir("/proc");
if(!dir)
@@ -222,8 +222,11 @@ namespace gsr {
snprintf(cmdline_filepath, sizeof(cmdline_filepath), "/proc/%s/cmdline", entry->d_name);
if(read_cmdline_arg0(cmdline_filepath, arg0, sizeof(arg0)) && strcmp(process_name, arg0) == 0) {
- result = atoi(entry->d_name);
- break;
+ const pid_t pid = atoi(entry->d_name);
+ if(pid != ignore_pid) {
+ result = pid;
+ break;
+ }
}
}
diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp
index c033058..7631e4d 100644
--- a/src/WindowUtils.cpp
+++ b/src/WindowUtils.cpp
@@ -7,8 +7,31 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include <poll.h>
+
+#define MAX_PROPERTY_VALUE_LEN 4096
namespace gsr {
+ static unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) {
+ Atom ret_property_type = None;
+ int ret_format = 0;
+ unsigned long num_items = 0;
+ unsigned long num_remaining_bytes = 0;
+ unsigned char *data = nullptr;
+ const Atom atom = XInternAtom(dpy, property_name, False);
+ if(XGetWindowProperty(dpy, window, atom, 0, MAX_PROPERTY_VALUE_LEN / 4, False, property_type, &ret_property_type, &ret_format, &num_items, &num_remaining_bytes, &data) != Success || !data) {
+ return nullptr;
+ }
+
+ if(ret_property_type != property_type) {
+ XFree(data);
+ return nullptr;
+ }
+
+ *property_size = (ret_format / (32 / sizeof(long))) * num_items;
+ return data;
+ }
+
static bool window_has_atom(Display *dpy, Window window, Atom atom) {
Atom type;
unsigned long len, bytes_left;
@@ -29,19 +52,56 @@ namespace gsr {
return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom);
}
+ static Window window_get_target_window_child(Display *display, Window window) {
+ if(window == None)
+ return None;
+
+ if(window_is_user_program(display, window))
+ return window;
+
+ Window root;
+ Window parent;
+ Window *children = nullptr;
+ unsigned int num_children = 0;
+ if(!XQueryTree(display, window, &root, &parent, &children, &num_children) || !children)
+ return None;
+
+ Window found_window = None;
+ for(int i = num_children - 1; i >= 0; --i) {
+ if(children[i] && window_is_user_program(display, children[i])) {
+ found_window = children[i];
+ goto finished;
+ }
+ }
+
+ for(int i = num_children - 1; i >= 0; --i) {
+ if(children[i]) {
+ Window win = window_get_target_window_child(display, children[i]);
+ if(win) {
+ found_window = win;
+ goto finished;
+ }
+ }
+ }
+
+ finished:
+ XFree(children);
+ return found_window;
+ }
+
static Window get_window_at_cursor_position(Display *dpy) {
Window root_window = None;
Window window = None;
int dummy_i;
unsigned int dummy_u;
- int cursor_pos_x = 0;
- int cursor_pos_y = 0;
- XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u);
+ XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &dummy_i, &dummy_i, &dummy_u);
+ if(window)
+ window = window_get_target_window_child(dpy, window);
return window;
}
Window get_focused_window(Display *dpy, WindowCaptureType cap_type) {
- const Atom net_active_window_atom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+ //const Atom net_active_window_atom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
Window focused_window = None;
if(cap_type == WindowCaptureType::FOCUSED) {
@@ -52,10 +112,14 @@ namespace gsr {
// unsigned char *data = NULL;
// XGetWindowProperty(dpy, DefaultRootWindow(dpy), net_active_window_atom, 0, 1, False, XA_WINDOW, &type, &format, &num_items, &bytes_left, &data);
- // fprintf(stderr, "focused window: %p\n", (void*)data);
-
// if(type == XA_WINDOW && num_items == 1 && data)
- // return *(Window*)data;
+ // focused_window = *(Window*)data;
+
+ // if(data)
+ // XFree(data);
+
+ // if(focused_window)
+ // return focused_window;
int revert_to = 0;
XGetInputFocus(dpy, &focused_window, &revert_to);
@@ -64,7 +128,7 @@ namespace gsr {
}
focused_window = get_window_at_cursor_position(dpy);
- if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window))
+ if(focused_window && focused_window != DefaultRootWindow(dpy))
return focused_window;
return None;
@@ -72,7 +136,7 @@ namespace gsr {
static char* get_window_title(Display *dpy, Window window) {
const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
- const Atom wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
+ const Atom wm_name_atom = XInternAtom(dpy, "WM_NAME", False);
const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False);
Atom type = None;
@@ -119,7 +183,7 @@ namespace gsr {
return str;
}
- static std::string string_string(const char *str) {
+ static std::string strip_strip(const char *str) {
int len = 0;
str = strip(str, &len);
return std::string(str, len);
@@ -134,17 +198,263 @@ namespace gsr {
// Window title is not always ideal (for example for a browser), but for games its pretty much required
char *window_title = get_window_title(dpy, focused_window);
if(window_title) {
- result = string_string(window_title);
+ result = strip_strip(window_title);
+ XFree(window_title);
return result;
}
XClassHint class_hint = {nullptr, nullptr};
XGetClassHint(dpy, focused_window, &class_hint);
if(class_hint.res_class) {
- result = string_string(class_hint.res_class);
+ result = strip_strip(class_hint.res_class);
return result;
}
return result;
}
+
+ mgl::vec2i get_cursor_position(Display *dpy, Window *window) {
+ Window root_window = None;
+ *window = None;
+ int dummy_i;
+ unsigned int dummy_u;
+ mgl::vec2i root_pos;
+ XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
+
+ // This dumb shit is done to satisfy gnome wayland. Only set |window| if a valid x11 window is focused
+ if(window) {
+ XWindowAttributes attr;
+ if(XGetWindowAttributes(dpy, *window, &attr) && attr.override_redirect)
+ *window = None;
+
+ int revert_to = 0;
+ Window input_focus_window = None;
+ if(XGetInputFocus(dpy, &input_focus_window, &revert_to)) {
+ if(input_focus_window) {
+ if(XGetWindowAttributes(dpy, input_focus_window, &attr) && attr.override_redirect)
+ *window = None;
+ } else {
+ *window = None;
+ }
+ }
+ }
+
+ return root_pos;
+ }
+
+ typedef struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long input_mode;
+ unsigned long status;
+ } MotifHints;
+
+ #define MWM_HINTS_DECORATIONS 2
+
+ #define MWM_DECOR_NONE 0
+ #define MWM_DECOR_ALL 1
+
+ static void window_set_decorations_visible(Display *display, Window window, bool visible) {
+ const Atom motif_wm_hints_atom = XInternAtom(display, "_MOTIF_WM_HINTS", False);
+ MotifHints motif_hints;
+ memset(&motif_hints, 0, sizeof(motif_hints));
+ motif_hints.flags = MWM_HINTS_DECORATIONS;
+ motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE;
+ XChangeProperty(display, window, motif_wm_hints_atom, motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long));
+ }
+
+ static bool create_window_get_center_position_kde(Display *display, mgl::vec2i &position) {
+ const int size = 1;
+ XSetWindowAttributes window_attr;
+ window_attr.event_mask = StructureNotifyMask;
+ window_attr.background_pixel = 0;
+ const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr);
+ if(!window)
+ return false;
+
+ const Atom net_wm_window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
+ const Atom net_wm_window_type_notification_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
+ const Atom net_wm_window_type_utility = XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ const Atom net_wm_window_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
+
+ const Atom window_type_atoms[2] = {
+ net_wm_window_type_notification_atom,
+ net_wm_window_type_utility
+ };
+ XChangeProperty(display, window, net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)window_type_atoms, 2L);
+
+ const double alpha = 0.0;
+ const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha);
+ XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
+
+ window_set_decorations_visible(display, window, false);
+
+ XSizeHints *size_hints = XAllocSizeHints();
+ size_hints->width = size;
+ size_hints->height = size;
+ size_hints->min_width = size;
+ size_hints->min_height = size;
+ size_hints->max_width = size;
+ size_hints->max_height = size;
+ size_hints->flags = PSize | PMinSize | PMaxSize;
+ XSetWMNormalHints(display, window, size_hints);
+ XFree(size_hints);
+
+ XMapWindow(display, window);
+ XFlush(display);
+
+ bool got_data = false;
+ const int x_fd = XConnectionNumber(display);
+ XEvent xev;
+ while(true) {
+ struct pollfd poll_fd;
+ poll_fd.fd = x_fd;
+ poll_fd.events = POLLIN;
+ poll_fd.revents = 0;
+ const int fds_ready = poll(&poll_fd, 1, 1000);
+ if(fds_ready == 0) {
+ fprintf(stderr, "Error: timed out waiting for ConfigureNotify after XCreateWindow\n");
+ break;
+ } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) {
+ continue;
+ }
+
+ XNextEvent(display, &xev);
+ if(xev.type == ConfigureNotify && xev.xconfigure.window == window) {
+ got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0;
+ position.x = xev.xconfigure.x + xev.xconfigure.width / 2;
+ position.y = xev.xconfigure.y + xev.xconfigure.height / 2;
+ break;
+ }
+ }
+
+ XDestroyWindow(display, window);
+ XFlush(display);
+
+ return got_data;
+ }
+
+ static bool create_window_get_center_position_gnome(Display *display, mgl::vec2i &position) {
+ const int size = 32;
+ XSetWindowAttributes window_attr;
+ window_attr.event_mask = StructureNotifyMask | ExposureMask;
+ window_attr.background_pixel = 0;
+ const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr);
+ if(!window)
+ return false;
+
+ const Atom net_wm_window_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
+ const double alpha = 0.0;
+ const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha);
+ XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
+
+ window_set_decorations_visible(display, window, false);
+
+ XSizeHints *size_hints = XAllocSizeHints();
+ size_hints->width = size;
+ size_hints->height = size;
+ size_hints->min_width = size;
+ size_hints->min_height = size;
+ size_hints->max_width = size;
+ size_hints->max_height = size;
+ size_hints->flags = PSize | PMinSize | PMaxSize;
+ XSetWMNormalHints(display, window, size_hints);
+ XFree(size_hints);
+
+ XMapWindow(display, window);
+ XFlush(display);
+
+ bool got_data = false;
+ const int x_fd = XConnectionNumber(display);
+ XEvent xev;
+ while(true) {
+ struct pollfd poll_fd;
+ poll_fd.fd = x_fd;
+ poll_fd.events = POLLIN;
+ poll_fd.revents = 0;
+ const int fds_ready = poll(&poll_fd, 1, 1000);
+ if(fds_ready == 0) {
+ fprintf(stderr, "Error: timed out waiting for MapNotify/ConfigureNotify after XCreateWindow\n");
+ break;
+ } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) {
+ continue;
+ }
+
+ XNextEvent(display, &xev);
+ if(xev.type == MapNotify && xev.xmap.window == window) {
+ int x = 0;
+ int y = 0;
+ Window w = None;
+ XTranslateCoordinates(display, window, DefaultRootWindow(display), 0, 0, &x, &y, &w);
+
+ got_data = x > 0 && y > 0;
+ position.x = x + size / 2;
+ position.y = y + size / 2;
+ if(got_data)
+ break;
+ } else if(xev.type == ConfigureNotify && xev.xconfigure.window == window) {
+ got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0;
+ position.x = xev.xconfigure.x + xev.xconfigure.width / 2;
+ position.y = xev.xconfigure.y + xev.xconfigure.height / 2;
+ if(got_data)
+ break;
+ }
+ }
+
+ XDestroyWindow(display, window);
+ XFlush(display);
+
+ return got_data;
+ }
+
+ mgl::vec2i create_window_get_center_position(Display *display) {
+ mgl::vec2i pos;
+ if(!create_window_get_center_position_kde(display, pos)) {
+ pos.x = 0;
+ pos.y = 0;
+ create_window_get_center_position_gnome(display, pos);
+ }
+ return pos;
+ }
+
+ std::string get_window_manager_name(Display *display) {
+ std::string wm_name;
+ unsigned int property_size = 0;
+ Window window = None;
+
+ unsigned char *net_supporting_wm_check = window_get_property(display, DefaultRootWindow(display), XA_WINDOW, "_NET_SUPPORTING_WM_CHECK", &property_size);
+ if(net_supporting_wm_check) {
+ if(property_size == 8)
+ window = *(Window*)net_supporting_wm_check;
+ XFree(net_supporting_wm_check);
+ }
+
+ if(!window) {
+ unsigned char *win_supporting_wm_check = window_get_property(display, DefaultRootWindow(display), XA_WINDOW, "_WIN_SUPPORTING_WM_CHECK", &property_size);
+ if(win_supporting_wm_check) {
+ if(property_size == 8)
+ window = *(Window*)win_supporting_wm_check;
+ XFree(win_supporting_wm_check);
+ }
+ }
+
+ if(!window)
+ return wm_name;
+
+ char *window_title = get_window_title(display, window);
+ if(window_title) {
+ wm_name = strip_strip(window_title);
+ XFree(window_title);
+ }
+
+ return wm_name;
+ }
+
+ bool is_compositor_running(Display *dpy, int screen) {
+ char prop_name[20];
+ snprintf(prop_name, sizeof(prop_name), "_NET_WM_CM_S%d", screen);
+ Atom prop_atom = XInternAtom(dpy, prop_name, False);
+ return XGetSelectionOwner(dpy, prop_atom) != None;
+ }
} \ No newline at end of file
diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp
index ca4c4ea..d3d440d 100644
--- a/src/gui/GlobalSettingsPage.cpp
+++ b/src/gui/GlobalSettingsPage.cpp
@@ -55,6 +55,7 @@ namespace gsr {
get_color_theme().tint_color = mgl::Color(118, 185, 0);
else if(id == "intel")
get_color_theme().tint_color = mgl::Color(8, 109, 183);
+ return true;
};
list->add_widget(std::move(tint_color_radio_button));
return std::make_unique<Subsection>("Appearance", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
@@ -62,10 +63,11 @@ namespace gsr {
std::unique_ptr<Subsection> GlobalSettingsPage::create_startup_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
- auto startup_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL);
+ list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start program on system startup?", get_color_theme().text_color));
+ auto startup_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
startup_radio_button_ptr = startup_radio_button.get();
- startup_radio_button->add_item("Don't start this program on system startup", "dont_start_on_system_startup");
- startup_radio_button->add_item("Start this program on system startup", "start_on_system_startup");
+ startup_radio_button->add_item("Yes", "start_on_system_startup");
+ startup_radio_button->add_item("No", "dont_start_on_system_startup");
startup_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
bool enable = false;
if(id == "dont_start_on_system_startup")
@@ -73,18 +75,44 @@ namespace gsr {
else if(id == "start_on_system_startup")
enable = true;
else
- return;
+ return false;
const char *args[] = { "systemctl", enable ? "enable" : "disable", "--user", "gpu-screen-recorder-ui", nullptr };
std::string stdout_str;
const int exit_status = exec_program_on_host_get_stdout(args, stdout_str);
if(on_startup_changed)
on_startup_changed(enable, exit_status);
+ return exit_status == 0;
};
list->add_widget(std::move(startup_radio_button));
return std::make_unique<Subsection>("Startup", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
+ std::unique_ptr<Subsection> GlobalSettingsPage::create_hotkey_subsection(ScrollablePage *parent_page) {
+ const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
+
+ auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL);
+ enable_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get();
+ enable_hotkeys_radio_button->add_item("Enable hotkeys", "enable_hotkeys");
+ if(!inside_flatpak)
+ enable_hotkeys_radio_button->add_item("Disable hotkeys", "disable_hotkeys");
+ enable_hotkeys_radio_button->add_item("Only grab virtual devices (supports input remapping software)", "enable_hotkeys_virtual_devices");
+ enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
+ if(!on_click_exit_program_button)
+ return true;
+
+ if(id == "enable_hotkeys")
+ on_click_exit_program_button("restart");
+ else if(id == "disable_hotkeys")
+ on_click_exit_program_button("restart");
+ else if(id == "enable_hotkeys_virtual_devices")
+ on_click_exit_program_button("restart");
+
+ return true;
+ };
+ return std::make_unique<Subsection>("Hotkeys", std::move(enable_hotkeys_radio_button), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
+ }
+
std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() {
auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, "Exit program", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
exit_program_button->on_click = [&]() {
@@ -105,7 +133,6 @@ namespace gsr {
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
-
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL);
list->add_widget(create_exit_program_button());
if(inside_flatpak)
@@ -120,6 +147,7 @@ namespace gsr {
settings_list->set_spacing(0.018f);
settings_list->add_widget(create_appearance_subsection(scrollable_page.get()));
settings_list->add_widget(create_startup_subsection(scrollable_page.get()));
+ settings_list->add_widget(create_hotkey_subsection(scrollable_page.get()));
settings_list->add_widget(create_application_options_subsection(scrollable_page.get()));
scrollable_page->add_widget(std::move(settings_list));
@@ -140,10 +168,13 @@ namespace gsr {
std::string stdout_str;
const int exit_status = exec_program_on_host_get_stdout(args, stdout_str);
startup_radio_button_ptr->set_selected_item(exit_status == 0 ? "start_on_system_startup" : "dont_start_on_system_startup", false, false);
+
+ enable_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false);
}
void GlobalSettingsPage::save() {
config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id();
+ config.main_config.hotkeys_enable_option = enable_hotkeys_radio_button_ptr->get_selected_id();
save_config(config);
}
} \ No newline at end of file
diff --git a/src/gui/RadioButton.cpp b/src/gui/RadioButton.cpp
index 061d811..a6ef96a 100644
--- a/src/gui/RadioButton.cpp
+++ b/src/gui/RadioButton.cpp
@@ -35,12 +35,12 @@ namespace gsr {
const bool mouse_inside = mgl::FloatRect(draw_pos, item_size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y));
if(mouse_inside) {
- const size_t prev_selected_item = selected_item;
- selected_item = i;
-
- if(selected_item != prev_selected_item && on_selection_changed)
- on_selection_changed(item.text.get_string(), item.id);
+ if(selected_item != i && on_selection_changed) {
+ if(!on_selection_changed(item.text.get_string(), item.id))
+ return false;
+ }
+ selected_item = i;
return false;
}
@@ -158,12 +158,12 @@ namespace gsr {
for(size_t i = 0; i < items.size(); ++i) {
auto &item = items[i];
if(item.id == id) {
- const size_t prev_selected_item = selected_item;
- selected_item = i;
-
- if(trigger_event && (trigger_event_even_if_selection_not_changed || selected_item != prev_selected_item) && on_selection_changed)
- on_selection_changed(item.text.get_string(), item.id);
+ if(trigger_event && (trigger_event_even_if_selection_not_changed || selected_item != i) && on_selection_changed) {
+ if(!on_selection_changed(item.text.get_string(), item.id))
+ break;
+ }
+ selected_item = i;
break;
}
}
diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp
index 2e6fa08..5fdcc91 100644
--- a/src/gui/SettingsPage.cpp
+++ b/src/gui/SettingsPage.cpp
@@ -304,7 +304,8 @@ namespace gsr {
std::unique_ptr<Widget> SettingsPage::create_audio_section() {
auto audio_device_section_list = std::make_unique<List>(List::Orientation::VERTICAL);
audio_device_section_list->add_widget(create_audio_track_section());
- audio_device_section_list->add_widget(create_merge_audio_tracks_checkbox());
+ if(type != Type::STREAM)
+ audio_device_section_list->add_widget(create_merge_audio_tracks_checkbox());
audio_device_section_list->add_widget(create_application_audio_invert_checkbox());
audio_device_section_list->add_widget(create_audio_codec());
return std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
@@ -338,11 +339,27 @@ namespace gsr {
return list;
}
- std::unique_ptr<Entry> SettingsPage::create_video_bitrate_entry() {
+ std::unique_ptr<List> SettingsPage::create_video_bitrate_entry() {
+ auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "15000", (int)(get_theme().body_font.get_character_size() * 4.0f));
video_bitrate_entry->validate_handler = create_entry_validator_integer_in_range(1, 500000);
video_bitrate_entry_ptr = video_bitrate_entry.get();
- return video_bitrate_entry;
+ list->add_widget(std::move(video_bitrate_entry));
+
+ if(type == Type::STREAM) {
+ auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "1.92MB", get_color_theme().text_color);
+ Label *size_mb_label_ptr = size_mb_label.get();
+ list->add_widget(std::move(size_mb_label));
+
+ video_bitrate_entry_ptr->on_changed = [size_mb_label_ptr](const std::string &text) {
+ const double video_bitrate_mb_per_seconds = (double)atoi(text.c_str()) / 1000LL / 8LL * 1.024;
+ char buffer[32];
+ snprintf(buffer, sizeof(buffer), "%.2fMB", video_bitrate_mb_per_seconds);
+ size_mb_label_ptr->set_text(buffer);
+ };
+ }
+
+ return list;
}
std::unique_ptr<List> SettingsPage::create_video_bitrate() {
@@ -387,20 +404,20 @@ namespace gsr {
video_codec_box->add_item("H264", "h264");
if(gsr_info->supported_video_codecs.hevc)
video_codec_box->add_item("HEVC", "hevc");
+ if(gsr_info->supported_video_codecs.hevc_10bit)
+ video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit");
+ if(gsr_info->supported_video_codecs.hevc_hdr)
+ video_codec_box->add_item("HEVC (HDR)", "hevc_hdr");
if(gsr_info->supported_video_codecs.av1)
video_codec_box->add_item("AV1", "av1");
+ if(gsr_info->supported_video_codecs.av1_10bit)
+ video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit");
+ if(gsr_info->supported_video_codecs.av1_hdr)
+ video_codec_box->add_item("AV1 (HDR)", "av1_hdr");
if(gsr_info->supported_video_codecs.vp8)
video_codec_box->add_item("VP8", "vp8");
if(gsr_info->supported_video_codecs.vp9)
video_codec_box->add_item("VP9", "vp9");
- if(gsr_info->supported_video_codecs.hevc_hdr)
- video_codec_box->add_item("HEVC (HDR)", "hevc_hdr");
- if(gsr_info->supported_video_codecs.hevc_10bit)
- video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit");
- if(gsr_info->supported_video_codecs.av1_hdr)
- video_codec_box->add_item("AV1 (HDR)", "av1_hdr");
- if(gsr_info->supported_video_codecs.av1_10bit)
- video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit");
if(gsr_info->supported_video_codecs.h264_software)
video_codec_box->add_item("H264 Software Encoder (Slow, not recommended)", "h264_software");
video_codec_box_ptr = video_codec_box.get();
@@ -516,6 +533,7 @@ namespace gsr {
video_resolution_list_ptr->set_visible(!focused_selected && change_video_resolution_checkbox_ptr->is_checked());
change_video_resolution_checkbox_ptr->set_visible(!focused_selected);
restore_portal_session_list_ptr->set_visible(portal_selected);
+ return true;
};
change_video_resolution_checkbox_ptr->on_changed = [this](bool checked) {
@@ -530,6 +548,8 @@ namespace gsr {
if(estimated_file_size_ptr)
estimated_file_size_ptr->set_visible(custom_selected);
+
+ return true;
};
video_quality_box_ptr->on_selection_changed("", video_quality_box_ptr->get_selected_id());
@@ -644,16 +664,16 @@ namespace gsr {
return checkbox;
}
- std::unique_ptr<Label> SettingsPage::create_estimated_file_size() {
- auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video max file size in RAM: 5.23MB", get_color_theme().text_color);
+ std::unique_ptr<Label> SettingsPage::create_estimated_replay_file_size() {
+ auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video max file size in RAM: 57.60MB", get_color_theme().text_color);
estimated_file_size_ptr = label.get();
return label;
}
- void SettingsPage::update_estimated_file_size() {
+ void SettingsPage::update_estimated_replay_file_size() {
const int64_t replay_time_seconds = atoi(replay_time_entry_ptr->get_text().c_str());
const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL;
- const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1024.0 / 1024.0;
+ const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024;
char buffer[512];
snprintf(buffer, sizeof(buffer), "Estimated video max file size in RAM: %.2fMB", video_filesize_mb);
@@ -667,7 +687,7 @@ namespace gsr {
file_info_data_list->add_widget(create_container_section());
file_info_data_list->add_widget(create_replay_time());
file_info_list->add_widget(std::move(file_info_data_list));
- file_info_list->add_widget(create_estimated_file_size());
+ file_info_list->add_widget(create_estimated_replay_file_size());
settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
@@ -705,15 +725,16 @@ namespace gsr {
framerate_mode_list_ptr->set_visible(advanced_view);
notifications_subsection_ptr->set_visible(advanced_view);
settings_scrollable_page_ptr->reset_scroll();
+ return true;
};
view_radio_button_ptr->on_selection_changed("Simple", "simple");
replay_time_entry_ptr->on_changed = [this](const std::string&) {
- update_estimated_file_size();
+ update_estimated_replay_file_size();
};
video_bitrate_entry_ptr->on_changed = [this](const std::string&) {
- update_estimated_file_size();
+ update_estimated_replay_file_size();
};
}
@@ -725,11 +746,29 @@ namespace gsr {
return checkbox;
}
+ std::unique_ptr<Label> SettingsPage::create_estimated_record_file_size() {
+ auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video file size per minute (excluding audio): 345.60MB", get_color_theme().text_color);
+ estimated_file_size_ptr = label.get();
+ return label;
+ }
+
+ void SettingsPage::update_estimated_record_file_size() {
+ const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL;
+ const double video_filesize_mb_per_minute = (60.0 * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024;
+
+ char buffer[512];
+ snprintf(buffer, sizeof(buffer), "Estimated video file size per minute (excluding audio): %.2fMB", video_filesize_mb_per_minute);
+ estimated_file_size_ptr->set_text(buffer);
+ }
+
void SettingsPage::add_record_widgets() {
- auto file_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
- file_list->add_widget(create_save_directory("Directory to save the video:"));
- file_list->add_widget(create_container_section());
- settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
+ auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL);
+ auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
+ file_info_data_list->add_widget(create_save_directory("Directory to save the video:"));
+ file_info_data_list->add_widget(create_container_section());
+ file_info_list->add_widget(std::move(file_info_data_list));
+ file_info_list->add_widget(create_estimated_record_file_size());
+ settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
general_list->add_widget(create_save_recording_in_game_folder());
@@ -760,8 +799,13 @@ namespace gsr {
framerate_mode_list_ptr->set_visible(advanced_view);
notifications_subsection_ptr->set_visible(advanced_view);
settings_scrollable_page_ptr->reset_scroll();
+ return true;
};
view_radio_button_ptr->on_selection_changed("Simple", "simple");
+
+ video_bitrate_entry_ptr->on_changed = [this](const std::string&) {
+ update_estimated_record_file_size();
+ };
}
std::unique_ptr<ComboBox> SettingsPage::create_streaming_service_box() {
@@ -860,6 +904,7 @@ namespace gsr {
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);
+ return true;
};
streaming_service_box_ptr->on_selection_changed("Twitch", "twitch");
@@ -872,6 +917,7 @@ namespace gsr {
framerate_mode_list_ptr->set_visible(advanced_view);
notifications_subsection_ptr->set_visible(advanced_view);
settings_scrollable_page_ptr->reset_scroll();
+ return true;
};
view_radio_button_ptr->on_selection_changed("Simple", "simple");
}
@@ -962,7 +1008,8 @@ namespace gsr {
void SettingsPage::load_common(RecordOptions &record_options) {
record_area_box_ptr->set_selected_item(record_options.record_area_option);
- merge_audio_tracks_checkbox_ptr->set_checked(record_options.merge_audio_tracks);
+ if(merge_audio_tracks_checkbox_ptr)
+ merge_audio_tracks_checkbox_ptr->set_checked(record_options.merge_audio_tracks);
application_audio_invert_checkbox_ptr->set_checked(record_options.application_audio_invert);
change_video_resolution_checkbox_ptr->set_checked(record_options.change_video_resolution);
load_audio_tracks(record_options);
@@ -1083,7 +1130,8 @@ namespace gsr {
record_options.video_height = atoi(video_height_entry_ptr->get_text().c_str());
record_options.fps = atoi(framerate_entry_ptr->get_text().c_str());
record_options.video_bitrate = atoi(video_bitrate_entry_ptr->get_text().c_str());
- record_options.merge_audio_tracks = merge_audio_tracks_checkbox_ptr->is_checked();
+ if(merge_audio_tracks_checkbox_ptr)
+ record_options.merge_audio_tracks = merge_audio_tracks_checkbox_ptr->is_checked();
record_options.application_audio_invert = application_audio_invert_checkbox_ptr->is_checked();
record_options.change_video_resolution = change_video_resolution_checkbox_ptr->is_checked();
save_audio_tracks(record_options.audio_tracks, audio_track_list_ptr);
diff --git a/src/main.cpp b/src/main.cpp
index ffee855..9c20a81 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,4 @@
#include "../include/GsrInfo.hpp"
-#include "../include/Theme.hpp"
#include "../include/Overlay.hpp"
#include "../include/GlobalHotkeysX11.hpp"
#include "../include/GlobalHotkeysLinux.hpp"
@@ -96,8 +95,8 @@ static std::unique_ptr<gsr::GlobalHotkeysX11> register_x11_hotkeys(gsr::Overlay
return global_hotkeys;
}
-static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Overlay *overlay) {
- auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysLinux>();
+static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Overlay *overlay, gsr::GlobalHotkeysLinux::GrabType grab_type) {
+ auto global_hotkeys = std::make_unique<gsr::GlobalHotkeysLinux>(grab_type);
if(!global_hotkeys->start())
fprintf(stderr, "error: failed to start global hotkeys\n");
@@ -134,6 +133,43 @@ static std::unique_ptr<gsr::GlobalHotkeysLinux> register_linux_hotkeys(gsr::Over
return global_hotkeys;
}
+static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
+ rpc->add_handler("show_ui", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->show();
+ });
+
+ rpc->add_handler("toggle-show", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->toggle_show();
+ });
+
+ rpc->add_handler("toggle-record", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->toggle_record();
+ });
+
+ rpc->add_handler("toggle-pause", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->toggle_pause();
+ });
+
+ rpc->add_handler("toggle-stream", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->toggle_stream();
+ });
+
+ rpc->add_handler("toggle-replay", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->toggle_replay();
+ });
+
+ rpc->add_handler("replay-save", [overlay](const std::string &name) {
+ fprintf(stderr, "rpc command executed: %s\n", name.c_str());
+ overlay->save_replay();
+ });
+}
+
static bool is_gsr_ui_virtual_keyboard_running() {
FILE *f = fopen("/proc/bus/input/devices", "rb");
if(!f)
@@ -193,9 +229,11 @@ int main(int argc, char **argv) {
// TODO: This is a shitty method to detect if multiple instances of gsr-ui is running but this will work properly even in flatpak
// that uses pid sandboxing. Replace this with a better method once we no longer rely on linux global hotkeys on some platform.
- if(is_gsr_ui_virtual_keyboard_running()) {
+ // TODO: This method doesn't work when disabling hotkeys and the method below with pidof gsr-ui doesn't work in flatpak.
+ // What do? creating a pid file doesn't work in flatpak either.
+ if(is_gsr_ui_virtual_keyboard_running() || gsr::pidof("gsr-ui", getpid()) != -1) {
gsr::Rpc rpc;
- if(rpc.open("gsr-ui") && rpc.write("show_ui", 7)) {
+ if(rpc.open("gsr-ui") && rpc.write("show_ui\n", 8)) {
fprintf(stderr, "Error: another instance of gsr-ui is already running, opening that one instead\n");
} else {
fprintf(stderr, "Error: failed to send command to running gsr-ui instance, user will have to open the UI manually with Alt+Z\n");
@@ -204,12 +242,6 @@ int main(int argc, char **argv) {
}
return 1;
}
- // const pid_t gsr_ui_pid = gsr::pidof("gsr-ui");
- // if(gsr_ui_pid != -1) {
- // const char *args[] = { "gsr-notify", "--text", "Another instance of GPU Screen Recorder UI is already running", "--timeout", "5.0", "--icon-color", "ff0000", "--bg-color", "ff0000", nullptr };
- // gsr::exec_program_daemonized(args);
- // return 1;
- // }
// Cant get window texture when prime-run is used
disable_prime_run();
@@ -271,20 +303,21 @@ int main(int argc, char **argv) {
fprintf(stderr, "Info: gsr ui is now ready, waiting for inputs. Press alt+z to show/hide the overlay\n");
+ auto overlay = std::make_unique<gsr::Overlay>(resources_path, std::move(gsr_info), std::move(capture_options), egl_funcs);
+ if(launch_action == LaunchAction::LAUNCH_SHOW)
+ overlay->show();
+
auto rpc = std::make_unique<gsr::Rpc>();
if(!rpc->create("gsr-ui"))
fprintf(stderr, "Error: Failed to create rpc, commands won't be received\n");
- auto overlay = std::make_unique<gsr::Overlay>(resources_path, std::move(gsr_info), std::move(capture_options), egl_funcs);
-
- rpc->add_handler("show_ui", [&](const std::string&) {
- overlay->show();
- });
+ rpc_add_commands(rpc.get(), overlay.get());
- std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = register_linux_hotkeys(overlay.get());
-
- if(launch_action == LaunchAction::LAUNCH_SHOW)
- overlay->show();
+ std::unique_ptr<gsr::GlobalHotkeys> global_hotkeys = nullptr;
+ if(overlay->get_config().main_config.hotkeys_enable_option == "enable_hotkeys")
+ global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::ALL);
+ else if(overlay->get_config().main_config.hotkeys_enable_option == "enable_hotkeys_virtual_devices")
+ global_hotkeys = register_linux_hotkeys(overlay.get(), gsr::GlobalHotkeysLinux::GrabType::VIRTUAL);
// TODO: Add hotkeys in Overlay when using x11 global hotkeys. The hotkeys in Overlay should duplicate each key that is used for x11 global hotkeys.
@@ -296,7 +329,10 @@ int main(int argc, char **argv) {
gsr::set_frame_delta_seconds(frame_delta_seconds);
rpc->poll();
- global_hotkeys->poll_events();
+
+ if(global_hotkeys)
+ global_hotkeys->poll_events();
+
overlay->handle_events(global_hotkeys.get());
if(!overlay->draw()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -306,15 +342,17 @@ int main(int argc, char **argv) {
fprintf(stderr, "Info: shutting down!\n");
rpc.reset();
- global_hotkeys.reset();
+ if(global_hotkeys)
+ global_hotkeys.reset();
overlay.reset();
- gsr::deinit_theme();
- gsr::deinit_color_theme();
mgl_deinit();
if(exit_reason == "back-to-old-ui") {
const char *args[] = { "gpu-screen-recorder-gtk", "use-old-ui", nullptr };
execvp(args[0], (char* const*)args);
+ } else if(exit_reason == "restart") {
+ const char *args[] = { "gsr-ui", "launch-show", nullptr };
+ execvp(args[0], (char* const*)args);
}
return 0;