Fix window not being fullscreen on multi monitor systems on cinnamon. Fix some applications getting minimized when opening the ui

This commit is contained in:
dec05eba
2024-11-28 12:16:49 +01:00
parent 5cfca3e55a
commit ece3d02e0a
7 changed files with 371 additions and 50 deletions

View File

@@ -19,7 +19,7 @@ GPU Screen Recorder UI uses meson build system so you need to install `meson` to
## Build dependencies ## Build dependencies
These are the dependencies needed to build GPU Screen Recorder UI: These are the dependencies needed to build GPU Screen Recorder UI:
* x11 (libx11, libxrandr, libxrender, libxfixes, libxcomposite) * x11 (libx11, libxrandr, libxrender, libxcomposite, libxfixes, libxi)
* libglvnd (which provides libgl, libglx and libegl) * libglvnd (which provides libgl, libglx and libegl)
* libevdev * libevdev
* libudev (systemd-libs) * libudev (systemd-libs)

4
TODO
View File

@@ -97,4 +97,6 @@ Add option to select which gpu to record with, or list all monitors and automati
Remove all dependencies from tools/gsr-global-hotkeys and roll our own keyboard events code. Remove all dependencies from tools/gsr-global-hotkeys and roll our own keyboard events code.
Test global hotkeys with azerty instead of qwerty. Test global hotkeys with azerty instead of qwerty.
Fix cursor grab not working in owlboy, need to use xigrab.

View File

@@ -54,6 +54,12 @@ namespace gsr {
void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type); void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type);
bool is_open() const; bool is_open() const;
private: private:
void xi_setup();
void handle_xi_events();
void xi_setup_fake_cursor();
void xi_grab_all_devices();
void xi_warp_pointer(mgl::vec2i position);
void process_key_bindings(mgl::Event &event); void process_key_bindings(mgl::Event &event);
void update_notification_process_status(); void update_notification_process_status();
@@ -99,6 +105,10 @@ namespace gsr {
mgl::Texture screenshot_texture; mgl::Texture screenshot_texture;
mgl::Sprite screenshot_sprite; mgl::Sprite screenshot_sprite;
mgl::Rectangle bg_screenshot_overlay; mgl::Rectangle bg_screenshot_overlay;
mgl::Texture cursor_texture;
mgl::Sprite cursor_sprite;
mgl::vec2i cursor_hotspot;
bool cursor_drawn = false;
WindowTexture window_texture; WindowTexture window_texture;
PageStack page_stack; PageStack page_stack;
mgl::Rectangle top_bar_background; mgl::Rectangle top_bar_background;
@@ -125,5 +135,10 @@ namespace gsr {
bool focused_window_is_fullscreen = false; bool focused_window_is_fullscreen = false;
std::array<KeyBinding, 1> key_bindings; std::array<KeyBinding, 1> key_bindings;
Display *xi_display = nullptr;
int xi_opcode = 0;
XEvent *xi_input_xev = nullptr;
XEvent *xi_output_xev = nullptr;
}; };
} }

View File

@@ -42,11 +42,6 @@ src = [
mglpp_proj = subproject('mglpp') mglpp_proj = subproject('mglpp')
mglpp_dep = mglpp_proj.get_variable('mglpp_dep') mglpp_dep = mglpp_proj.get_variable('mglpp_dep')
dep = [
mglpp_dep,
dependency('xcomposite'),
]
prefix = get_option('prefix') prefix = get_option('prefix')
datadir = get_option('datadir') datadir = get_option('datadir')
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui') gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
@@ -55,7 +50,12 @@ executable(
meson.project_name(), meson.project_name(),
src, src,
install : true, install : true,
dependencies : dep, dependencies : [
mglpp_dep,
dependency('xcomposite'),
dependency('xfixes'),
dependency('xi'),
],
cpp_args : '-DGSR_UI_RESOURCES_PATH="' + gsr_ui_resources_path + '"', cpp_args : '-DGSR_UI_RESOURCES_PATH="' + gsr_ui_resources_path + '"',
) )

View File

@@ -11,4 +11,6 @@ version = "c++17"
ignore_dirs = ["build", "tools"] ignore_dirs = ["build", "tools"]
[dependencies] [dependencies]
xcomposite = ">=0" xcomposite = ">=0"
xfixes = ">=0"
xi = ">=0"

View File

@@ -21,6 +21,9 @@
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/cursorfont.h> #include <X11/cursorfont.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/shape.h>
#include <mglpp/system/Rect.hpp> #include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp> #include <mglpp/window/Event.hpp>
@@ -117,6 +120,59 @@ namespace gsr {
return texture; return texture;
} }
static bool texture_from_x11_cursor(XFixesCursorImage *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;
*visible = false;
if(!x11_cursor_image)
goto err;
if(!x11_cursor_image->pixels)
goto err;
hotspot->x = x11_cursor_image->xhot;
hotspot->y = x11_cursor_image->yhot;
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;
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) {
uint32_t pixel = *pixels++;
uint8_t *in = (uint8_t*)&pixel;
uint8_t alpha = in[3];
if(alpha == 0) {
alpha = 1;
} else {
*visible = true;
}
out[0] = (float)in[2] * 255.0/(float)alpha;
out[1] = (float)in[1] * 255.0/(float)alpha;
out[2] = (float)in[0] * 255.0/(float)alpha;
out[3] = in[3];
out += 4;
in += 4;
}
}
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) { static char hex_value_to_str(uint8_t v) {
if(v <= 9) if(v <= 9)
return '0' + v; return '0' + v;
@@ -265,6 +321,14 @@ namespace gsr {
return True; return True;
} }
static void make_window_click_through(Display *display, Window window) {
XRectangle rect;
memset(&rect, 0, sizeof(rect));
XserverRegion region = XFixesCreateRegion(display, &rect, 1);
XFixesSetWindowShapeRegion(display, window, ShapeInput, 0, 0, region);
XFixesDestroyRegion(display, region);
}
static Bool make_window_sticky(Display *dpy, Window window) { static Bool make_window_sticky(Display *dpy, Window window) {
return set_window_wm_state(dpy, window, XInternAtom(dpy, "_NET_WM_STATE_STICKY", False)); return set_window_wm_state(dpy, window, XInternAtom(dpy, "_NET_WM_STATE_STICKY", False));
} }
@@ -320,6 +384,26 @@ namespace gsr {
return is_connected; return is_connected;
} }
static bool xinput_is_supported(Display *dpy, int *xi_opcode) {
*xi_opcode = 0;
int query_event = 0;
int query_error = 0;
if(!XQueryExtension(dpy, "XInputExtension", xi_opcode, &query_event, &query_error)) {
fprintf(stderr, "gsr-ui error: X Input extension not available\n");
return false;
}
int major = 2;
int minor = 1;
int retval = XIQueryVersion(dpy, &major, &minor);
if (retval != Success) {
fprintf(stderr, "gsr-ui error: XInput 2.1 is not supported\n");
return false;
}
return true;
}
Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs) : Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs) :
resources_path(std::move(resources_path)), resources_path(std::move(resources_path)),
gsr_info(gsr_info), gsr_info(gsr_info),
@@ -329,6 +413,9 @@ namespace gsr {
close_button_widget({0.0f, 0.0f}), close_button_widget({0.0f, 0.0f}),
config(gsr_info) config(gsr_info)
{ {
// TODO:
//xi_setup();
memset(&window_texture, 0, sizeof(window_texture)); memset(&window_texture, 0, sizeof(window_texture));
key_bindings[0].key_event.code = mgl::Keyboard::Escape; key_bindings[0].key_event.code = mgl::Keyboard::Escape;
@@ -377,6 +464,60 @@ namespace gsr {
} }
gpu_screen_recorder_process = -1; gpu_screen_recorder_process = -1;
} }
free(xi_input_xev);
free(xi_output_xev);
if(xi_display)
XCloseDisplay(xi_display);
}
void Overlay::xi_setup() {
xi_display = XOpenDisplay(nullptr);
if(!xi_display) {
fprintf(stderr, "gsr-ui error: failed to setup XI connection\n");
return;
}
if(!xinput_is_supported(xi_display, &xi_opcode))
goto error;
xi_input_xev = (XEvent*)calloc(1, sizeof(XEvent));
if(!xi_input_xev)
throw std::runtime_error("gsr-ui error: failed to allocate XEvent data");
xi_output_xev = (XEvent*)calloc(1, sizeof(XEvent));
if(!xi_output_xev)
throw std::runtime_error("gsr-ui error: failed to allocate XEvent data");
unsigned char mask[XIMaskLen(XI_LASTEVENT)];
memset(mask, 0, sizeof(mask));
XISetMask(mask, XI_Motion);
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_ButtonRelease);
XISetMask(mask, XI_KeyPress);
XISetMask(mask, XI_KeyRelease);
XIEventMask xi_masks;
xi_masks.deviceid = XIAllMasterDevices;
xi_masks.mask_len = sizeof(mask);
xi_masks.mask = mask;
if(XISelectEvents(xi_display, DefaultRootWindow(xi_display), &xi_masks, 1) != Success) {
fprintf(stderr, "gsr-ui error: XISelectEvents failed\n");
goto error;
}
XFlush(xi_display);
return;
error:
free(xi_input_xev);
xi_input_xev = nullptr;
free(xi_output_xev);
xi_output_xev = nullptr;
if(xi_display) {
XCloseDisplay(xi_display);
xi_display = nullptr;
}
} }
static uint32_t key_event_to_bitmask(mgl::Event::KeyEvent key_event) { static uint32_t key_event_to_bitmask(mgl::Event::KeyEvent key_event) {
@@ -397,10 +538,72 @@ namespace gsr {
} }
} }
void Overlay::handle_xi_events() {
if(!xi_display)
return;
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
while(XPending(xi_display)) {
XNextEvent(xi_display, xi_input_xev);
XGenericEventCookie *cookie = &xi_input_xev->xcookie;
if(cookie->type == GenericEvent && cookie->extension == xi_opcode && XGetEventData(xi_display, cookie)) {
const XIDeviceEvent *de = (XIDeviceEvent*)cookie->data;
if(cookie->evtype == XI_Motion) {
memset(xi_output_xev, 0, sizeof(*xi_output_xev));
xi_output_xev->type = MotionNotify;
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_root = de->root_x;
xi_output_xev->xmotion.y_root = de->root_y;
//xi_output_xev->xmotion.state = // modifiers // TODO:
if(window->inject_x11_event(xi_output_xev, event))
on_event(event);
} else if(cookie->evtype == XI_ButtonPress || cookie->evtype == XI_ButtonRelease) {
memset(xi_output_xev, 0, sizeof(*xi_output_xev));
xi_output_xev->type = cookie->evtype == XI_ButtonPress ? ButtonPress : ButtonRelease;
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_root = de->root_x;
xi_output_xev->xbutton.y_root = de->root_y;
//xi_output_xev->xbutton.state = // modifiers // TODO:
xi_output_xev->xbutton.button = de->detail;
if(window->inject_x11_event(xi_output_xev, event))
on_event(event);
} else if(cookie->evtype == XI_KeyPress || cookie->evtype == XI_KeyRelease) {
memset(xi_output_xev, 0, sizeof(*xi_output_xev));
xi_output_xev->type = cookie->evtype == XI_KeyPress ? KeyPress : KeyRelease;
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_root = de->root_x;
xi_output_xev->xkey.y_root = de->root_y;
xi_output_xev->xkey.state = de->mods.effective;
xi_output_xev->xkey.keycode = de->detail;
if(window->inject_x11_event(xi_output_xev, event))
on_event(event);
}
//fprintf(stderr, "got xi event: %d\n", cookie->evtype);
XFreeEventData(xi_display, cookie);
}
}
}
void Overlay::handle_events() { void Overlay::handle_events() {
if(!visible || !window) if(!visible || !window)
return; return;
handle_xi_events();
while(window->poll_event(event)) { while(window->poll_event(event)) {
on_event(event); on_event(event);
} }
@@ -452,12 +655,86 @@ namespace gsr {
close_button_widget.draw(*window, mgl::vec2f(0.0f, 0.0f)); close_button_widget.draw(*window, mgl::vec2f(0.0f, 0.0f));
page_stack.draw(*window, mgl::vec2f(0.0f, 0.0f)); 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);
}
window->display(); window->display();
return true; return true;
} }
void Overlay::xi_setup_fake_cursor() {
if(!xi_display)
return;
XFixesShowCursor(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);
}
void Overlay::xi_grab_all_devices() {
if(!xi_display)
return;
int num_devices = 0;
XIDeviceInfo *info = XIQueryDevice(xi_display, XIAllDevices, &num_devices);
if(!info)
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);
}
void Overlay::xi_warp_pointer(mgl::vec2i position) {
if(!xi_display)
return;
int num_devices = 0;
XIDeviceInfo *info = XIQueryDevice(xi_display, XIAllDevices, &num_devices);
if(!info)
return;
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);
}
XFlush(xi_display);
XIFreeDeviceInfo(info);
}
void Overlay::show() { void Overlay::show() {
if(visible)
return;
window.reset(); window.reset();
window = std::make_unique<mgl::Window>(); window = std::make_unique<mgl::Window>();
deinit_theme(); deinit_theme();
@@ -471,7 +748,7 @@ namespace gsr {
window_create_params.max_size = window_size; window_create_params.max_size = window_size;
window_create_params.position = window_pos; window_create_params.position = window_pos;
window_create_params.hidden = true; window_create_params.hidden = true;
window_create_params.override_redirect = false; window_create_params.override_redirect = true;
window_create_params.background_color = bg_color; window_create_params.background_color = bg_color;
window_create_params.support_alpha = true; window_create_params.support_alpha = true;
window_create_params.window_type = MGL_WINDOW_TYPE_NORMAL; window_create_params.window_type = MGL_WINDOW_TYPE_NORMAL;
@@ -637,33 +914,47 @@ namespace gsr {
return true; return true;
}; };
window->set_fullscreen(true); //window->set_fullscreen(true);
if(gsr_info.system_info.display_server == DisplayServer::X11)
make_window_click_through(display, window->get_system_handle());
window->set_visible(true); window->set_visible(true);
make_window_sticky(display, window->get_system_handle()); make_window_sticky(display, window->get_system_handle());
hide_window_from_taskbar(display, window->get_system_handle()); hide_window_from_taskbar(display, window->get_system_handle());
// if(default_cursor) { if(default_cursor) {
// XFreeCursor(display, default_cursor); XFreeCursor(display, default_cursor);
// default_cursor = 0; default_cursor = 0;
// } }
// default_cursor = XCreateFontCursor(display, XC_arrow); default_cursor = XCreateFontCursor(display, XC_arrow);
// TODO: Retry if these fail. // TODO: Remove these grabs when debugging with a debugger, or your X11 session will appear frozen.
// TODO: Hmm, these dont work in owlboy. Maybe owlboy uses xi2 and that breaks this (does it?). // There should be a debug mode to not use these
// Remove these grabs when debugging with a debugger, or your X11 session will appear frozen
// XGrabPointer(display, window->get_system_handle(), True, XGrabPointer(display, window->get_system_handle(), True,
// ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
// Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask |
// ButtonMotionMask, ButtonMotionMask,
// GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime);
// TODO: This breaks global hotkeys // TODO: This breaks global hotkeys (when using x11 global hotkeys)
//XGrabKeyboard(display, window->get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); XGrabKeyboard(display, window->get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime);
set_focused_window(display, window->get_system_handle());
XFlush(display); XFlush(display);
window->set_fullscreen(true); // The real cursor doesn't move when all devices are grabbed, so we create our own cursor and diplay that while grabbed
xi_setup_fake_cursor();
// We want to grab all devices to prevent any other application below from receiving events.
// Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events.
xi_grab_all_devices();
// if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
// set_focused_window(display, window->get_system_handle());
// XFlush(display);
// }
//window->set_fullscreen(true);
visible = true; visible = true;
@@ -694,6 +985,9 @@ namespace gsr {
} }
void Overlay::hide() { void Overlay::hide() {
if(!visible)
return;
mgl_context *context = mgl_get_context(); mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection; Display *display = (Display*)context->connection;
@@ -701,14 +995,23 @@ namespace gsr {
page_stack.pop(); page_stack.pop();
} }
// if(default_cursor) { if(default_cursor) {
// XFreeCursor(display, default_cursor); XFreeCursor(display, default_cursor);
// default_cursor = 0; default_cursor = 0;
// } }
// XUngrabKeyboard(display, CurrentTime); XUngrabKeyboard(display, CurrentTime);
// XUngrabPointer(display, CurrentTime); XUngrabPointer(display, CurrentTime);
// XFlush(display); 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);
}
window_texture_deinit(&window_texture); window_texture_deinit(&window_texture);
window_texture_sprite.set_texture(nullptr); window_texture_sprite.set_texture(nullptr);
@@ -717,8 +1020,19 @@ namespace gsr {
visible = false; visible = false;
if(window) { 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->set_visible(false);
window.reset(); window.reset();
if(xi_display) {
XFlush(display);
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);
}
} }
deinit_theme(); deinit_theme();
@@ -831,20 +1145,8 @@ namespace gsr {
} }
int exit_code = -1; int exit_code = -1;
// The process is no longer a child process since gsr ui has restarted if(WIFEXITED(status))
if(errno == ECHILD) { exit_code = WEXITSTATUS(status);
errno = 0;
kill(gpu_screen_recorder_process, 0);
if(errno != ESRCH) {
// Still running
return;
}
// We cant know the exit status, so we assume it succeeded
exit_code = 0;
} else {
if(WIFEXITED(status))
exit_code = WEXITSTATUS(status);
}
switch(recording_status) { switch(recording_status) {
case RecordingStatus::NONE: case RecordingStatus::NONE: