Fix GNOME Wayland fullscreen top bar and mouse confinement in cocotron

This commit is contained in:
Andras Laszlo committed 2026-10-16 14:34:09 +02:00
1 parent 4ae26c8fdc
commit 92d724f736
2 files changed
+71 -22

No files matched your search

+70 -21
View File
@@ -2826,29 +2826,20 @@ index 5ae29bf4..358e606b 100644
if ([window respondsToSelector: @selector(propertyNotify:)]) {
[window propertyNotify: &ev->xproperty];
}
@@ -1483,6 +1823,64 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
@@ -1483,6 +1823,113 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
XSync(_display, False);
}
+/* Confinement preserves absolute motion and edge panning. Relative-mode
+ * capture remains separate: it deliberately warps to the center. */
+- (void) updateMouseConfinementForWindow:(X11Window *)window {
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT check window=%p delegate=%p focused=%p relative=%d\n",window,[window delegate],lastFocusedWindow,_cursorGrabbed);
+ if (!window || _cursorGrabbed) return;
+ if (getenv("RCLOL_MOUSE_CONFINEMENT") && strcmp(getenv("RCLOL_MOUSE_CONFINEMENT"), "0") == 0) return;
+ Window win = [window windowHandle];
+ Window focus; int revert;
+ XGetInputFocus(_display, &focus, &revert);
+ Window ancestor = focus;
+ while (ancestor != win && ancestor != None && ancestor != PointerRoot && ancestor != DefaultRootWindow(_display)) {
+ Window root, parent, *children=NULL; unsigned nchildren;
+ if (!XQueryTree(_display, ancestor, &root, &parent, &children, &nchildren)) break;
+ if (children) XFree(children);
+ ancestor=parent;
+ }
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT focus=%lu target=%lu ancestor=%lu\n",focus,win,ancestor);
+ if (ancestor != win) return;
+ XWindowAttributes attrs;
+ if (!XGetWindowAttributes(_display, win, &attrs)) return;
+
+ BOOL fullscreen = NO;
+ Atom actual; int format; unsigned long count, remaining; unsigned char *data = NULL;
+ Atom state = XInternAtom(_display, "_NET_WM_STATE", False);
@@ -2858,25 +2849,83 @@ index 5ae29bf4..358e606b 100644
+ for (unsigned long i=0; i<count; ++i) if (((Atom *)data)[i] == fs) fullscreen = YES;
+ }
+ if (data) XFree(data);
+
+ /* League's borderless fullscreen does not set the EWMH fullscreen bit.
+ * Recognize a borderless window covering one monitor, including monitor 2. */
+ if (!fullscreen && !([[window delegate] styleMask] & NSTitledWindowMask)) {
+ Window child; int x, y;
+ * Recognize a borderless window covering a monitor, including multi-monitor
+ * setups and Wayland/GNOME top panel offsets. */
+ NSWindow *delegate = [window delegate];
+ BOOL isBorderless = delegate ? !([delegate styleMask] & NSTitledWindowMask) : YES;
+ BOOL isAppWindow = delegate ? !([delegate isKindOfClass:[NSMenuWindow class]] ||
+ [delegate isKindOfClass:[NSPopUpWindow class]] ||
+ ![delegate canBecomeKeyWindow]) : YES;
+
+ if (isBorderless && isAppWindow) {
+ Window child; int x = 0, y = 0;
+ XTranslateCoordinates(_display, win, DefaultRootWindow(_display), 0, 0, &x, &y, &child);
+ NSRect delegateFrame = delegate ? [delegate frame] : NSZeroRect;
+ XRRScreenResources *resources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display));
+ if (resources) {
+ for (int i=0; i<resources->ncrtc; ++i) {
+ XRRCrtcInfo *crtc = XRRGetCrtcInfo(_display, resources, resources->crtcs[i]);
+ if (crtc) {
+ if (crtc->mode && abs(x-crtc->x)<=2 && abs(y-crtc->y)<=2 &&
+ abs(attrs.width-(int)crtc->width)<=2 && abs(attrs.height-(int)crtc->height)<=2) fullscreen=YES;
+ if (crtc->mode) {
+ BOOL sizeMatchesCrtc =
+ (abs(attrs.width - (int)crtc->width) <= 4 &&
+ (abs(attrs.height - (int)crtc->height) <= 4 ||
+ attrs.height >= (int)crtc->height - 60)) ||
+ (delegateFrame.size.width > 0 &&
+ abs((int)delegateFrame.size.width - (int)crtc->width) <= 4 &&
+ abs((int)delegateFrame.size.height - (int)crtc->height) <= 4);
+
+ BOOL posMatchesCrtc =
+ abs(x - crtc->x) <= 4 &&
+ (y >= crtc->y - 4 && y <= crtc->y + 60);
+
+ if (sizeMatchesCrtc && (posMatchesCrtc || delegateFrame.size.width > 0)) {
+ if (!fullscreen) {
+ fullscreen = YES;
+ /* Inform GNOME Shell / Mutter / EWMH to make this window fullscreen.
+ * This hides the GNOME top bar, sizes the window to fill the entire monitor,
+ * and enables Wayland pointer confinement (zwp_confined_pointer_v1). */
+ XClientMessageEvent cme = {0};
+ cme.type = ClientMessage;
+ cme.window = win;
+ cme.message_type = state;
+ cme.format = 32;
+ cme.data.l[0] = 1; /* _NET_WM_STATE_ADD */
+ cme.data.l[1] = (long)fs;
+ cme.data.l[2] = 0;
+ cme.data.l[3] = 1; /* normal application */
+ cme.data.l[4] = 0;
+ XSendEvent(_display, DefaultRootWindow(_display), False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ (XEvent *)&cme);
+ XFlush(_display);
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT promoted to EWMH FULLSCREEN for window %lu\n",win);
+ }
+ }
+ }
+ XRRFreeCrtcInfo(crtc);
+ if (fullscreen) break;
+ }
+ }
+ XRRFreeScreenResources(resources);
+ }
+ }
+ if (!fullscreen || attrs.map_state != IsViewable) {
+
+ /* Check focus to determine whether mouse confinement should be active. */
+ Window focus; int revert;
+ XGetInputFocus(_display, &focus, &revert);
+ Window ancestor = focus;
+ while (ancestor != win && ancestor != None && ancestor != PointerRoot && ancestor != DefaultRootWindow(_display)) {
+ Window root, parent, *children=NULL; unsigned nchildren;
+ if (!XQueryTree(_display, ancestor, &root, &parent, &children, &nchildren)) break;
+ if (children) XFree(children);
+ ancestor=parent;
+ }
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT focus=%lu target=%lu ancestor=%lu fullscreen=%d\n",focus,win,ancestor,fullscreen);
+
+ if (!fullscreen || attrs.map_state != IsViewable || ancestor != win) {
+ if (_confinedWindow == win) { XUngrabPointer(_display, CurrentTime); _confinedWindow=None; }
+ return;
+ }
@@ -2891,7 +2940,7 @@ index 5ae29bf4..358e606b 100644
- (void) grabMouse: (BOOL) doGrab {
if (doGrab) {
NSWindow *nswin = [NSApp keyWindow];
@@ -1495,9 +1893,10 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
@@ -1495,9 +1942,10 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
const unsigned int mask = ButtonPressMask | ButtonReleaseMask |
PointerMotionMask | FocusChangeMask;
int result = XGrabPointer(_display, win, False, mask, GrabModeAsync,
@@ -2903,7 +2952,7 @@ index 5ae29bf4..358e606b 100644
NSLog(@"XGrabPointer() succeeded for window %lu\n", win);
NSRect frame = [xwin transformFrame: nswin.frame];
@@ -1522,6 +1921,9 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
@@ -1522,6 +1970,9 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
} else {
XUngrabPointer(_display, CurrentTime);
_cursorGrabbed = NO;
+1 -1
View File
@@ -17,7 +17,7 @@ unset RCLOL_APP_ARG3 RCLOL_APP_ARG3_SLOT RCLOL_UX_ARG1 RCLOL_UX_ARG1_SLOT
unset RCLOL_UX_ARG2 RCLOL_UX_ARG2_SLOT
# The paired runtime presents completed GPU textures without CPU readback.
# Explicit 0 or an empty value selects the CPU fallback for diagnostics.
for option in RCLOL_GPU_DIRECT_PRESENT RCLOL_GPU_DIRECT_ASYNC DARLING_CA_PRESENT_AT_COMMIT DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER RCLOL_FORWARD_MOUSE_MOVED; do
for option in RCLOL_GPU_DIRECT_PRESENT RCLOL_GPU_DIRECT_ASYNC DARLING_CA_PRESENT_AT_COMMIT DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER RCLOL_FORWARD_MOUSE_MOVED RCLOL_MOUSE_CONFINEMENT; do
eval 'value=${'"$option"'-1}'
case "$value" in 0|'') unset "$option";; *) export "$option=$value";; esac
done