From 92d724f7364427e44d38b3568202241148703ad0 Mon Sep 17 00:00:00 2001 From: Andras Laszlo Date: Fri, 16 Oct 2026 14:34:09 +0200 Subject: [PATCH] Fix GNOME Wayland fullscreen top bar and mouse confinement in cocotron --- patches/cocotron.patch | 91 ++++++++++++++++++++++++++++++---------- tools/run-riot-client.sh | 2 +- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/patches/cocotron.patch b/patches/cocotron.patch index db7736b..dd95907 100644 --- a/patches/cocotron.patch +++ b/patches/cocotron.patch @@ -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; incrtc; ++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; diff --git a/tools/run-riot-client.sh b/tools/run-riot-client.sh index fe99494..c408bd5 100755 --- a/tools/run-riot-client.sh +++ b/tools/run-riot-client.sh @@ -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