fix(electrobun): XUngrabPointer before begin_resize_drag (SDL2 pattern)
Root cause confirmed via X11 research: WebKitGTK holds an implicit X11 pointer grab from button-press. GTK's gdk_seat_ungrab targets the wrong device, leaving the grab active. Mutter gets AlreadyGrabbed and gives up. Fix: call XUngrabPointer(xdisplay, CurrentTime) + XFlush directly via libX11.so.6 FFI before begin_resize_drag. This releases ALL grabs on the display connection, matching SDL2's proven approach. Removed failed approaches: WebView sensitivity toggle, findWebView cache.
This commit is contained in:
parent
7bb08697d6
commit
058ae563d5
1 changed files with 57 additions and 46 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
* for smooth, zero-CPU resize/move behavior.
|
* for smooth, zero-CPU resize/move behavior.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { dlopen, FFIType, ptr } from "bun:ffi";
|
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
|
||||||
|
|
||||||
// GdkWindowEdge values
|
// GdkWindowEdge values
|
||||||
export const GDK_EDGE = {
|
export const GDK_EDGE = {
|
||||||
|
|
@ -18,6 +18,53 @@ export const GDK_EDGE = {
|
||||||
type GdkEdge = (typeof GDK_EDGE)[keyof typeof GDK_EDGE];
|
type GdkEdge = (typeof GDK_EDGE)[keyof typeof GDK_EDGE];
|
||||||
|
|
||||||
let gtk3: ReturnType<typeof dlopen> | null = null;
|
let gtk3: ReturnType<typeof dlopen> | null = null;
|
||||||
|
let gdk3: ReturnType<typeof dlopen> | null = null;
|
||||||
|
let x11: ReturnType<typeof dlopen> | null = null;
|
||||||
|
|
||||||
|
function getX11() {
|
||||||
|
if (x11) return x11;
|
||||||
|
try {
|
||||||
|
x11 = dlopen("libX11.so.6", {
|
||||||
|
XUngrabPointer: { args: [FFIType.ptr, FFIType.u64], returns: FFIType.i32 },
|
||||||
|
XFlush: { args: [FFIType.ptr], returns: FFIType.i32 },
|
||||||
|
});
|
||||||
|
return x11;
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGdk() {
|
||||||
|
if (gdk3) return gdk3;
|
||||||
|
try {
|
||||||
|
gdk3 = dlopen("libgdk-3.so.0", {
|
||||||
|
gdk_display_get_default: { args: [], returns: FFIType.ptr },
|
||||||
|
gdk_x11_display_get_xdisplay: { args: [FFIType.ptr], returns: FFIType.ptr },
|
||||||
|
});
|
||||||
|
return gdk3;
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release ALL X11 pointer grabs on the default display.
|
||||||
|
* This is the SDL2 pattern: XUngrabPointer(dpy, CurrentTime=0) + XFlush.
|
||||||
|
* Must be called BEFORE _NET_WM_MOVERESIZE so the WM can grab the pointer.
|
||||||
|
*/
|
||||||
|
function releaseX11Grab() {
|
||||||
|
const gdkLib = getGdk();
|
||||||
|
const x11Lib = getX11();
|
||||||
|
if (!gdkLib || !x11Lib) return false;
|
||||||
|
try {
|
||||||
|
const gdkDisplay = gdkLib.symbols.gdk_display_get_default();
|
||||||
|
if (!gdkDisplay) return false;
|
||||||
|
const xDisplay = gdkLib.symbols.gdk_x11_display_get_xdisplay(gdkDisplay);
|
||||||
|
if (!xDisplay) return false;
|
||||||
|
x11Lib.symbols.XUngrabPointer(xDisplay, BigInt(0)); // CurrentTime = 0
|
||||||
|
x11Lib.symbols.XFlush(xDisplay);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[gtk-window] releaseX11Grab failed:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getGtk() {
|
function getGtk() {
|
||||||
if (gtk3) return gtk3;
|
if (gtk3) return gtk3;
|
||||||
|
|
@ -219,41 +266,9 @@ function forceSmallMinSize(lib: NonNullable<typeof gtk3>, windowPtr: any) {
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cache the WebView widget pointer for sensitivity toggling during resize */
|
|
||||||
let cachedWebView: any = null;
|
|
||||||
|
|
||||||
function findWebView(lib: NonNullable<typeof gtk3>, windowPtr: any): any {
|
|
||||||
if (cachedWebView) return cachedWebView;
|
|
||||||
try {
|
|
||||||
// Window → container → scrolledwindow/webview
|
|
||||||
let widget = lib.symbols.gtk_bin_get_child(windowPtr);
|
|
||||||
// Walk down the bin chain to find the deepest child (the WebView)
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
const child = lib.symbols.gtk_bin_get_child(widget);
|
|
||||||
if (!child) break;
|
|
||||||
widget = child;
|
|
||||||
}
|
|
||||||
cachedWebView = widget;
|
|
||||||
return widget;
|
|
||||||
} catch { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporarily disable WebView input during resize to prevent grab interference.
|
|
||||||
* Re-enable after resize completes (WM sends configure-event).
|
|
||||||
*/
|
|
||||||
export function setWebViewSensitive(windowPtr: number | bigint, sensitive: boolean) {
|
|
||||||
const lib = getGtk();
|
|
||||||
if (!lib) return;
|
|
||||||
const wv = findWebView(lib, windowPtr as any);
|
|
||||||
if (wv) {
|
|
||||||
lib.symbols.gtk_widget_set_sensitive(wv, sensitive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegate resize to the window manager.
|
* Delegate resize to the window manager.
|
||||||
* Disables WebView input first to prevent grab interference.
|
* Releases X11 grabs first (SDL2 pattern) so WM can take the pointer.
|
||||||
*/
|
*/
|
||||||
export function beginResizeDrag(
|
export function beginResizeDrag(
|
||||||
windowPtr: number | bigint,
|
windowPtr: number | bigint,
|
||||||
|
|
@ -265,26 +280,22 @@ export function beginResizeDrag(
|
||||||
const lib = getGtk();
|
const lib = getGtk();
|
||||||
if (!lib) return false;
|
if (!lib) return false;
|
||||||
try {
|
try {
|
||||||
// 1. Force small min-size
|
// 1. Force small min-size so WM allows shrink
|
||||||
forceSmallMinSize(lib, windowPtr as any);
|
forceSmallMinSize(lib, windowPtr as any);
|
||||||
// 2. Disable WebView input to prevent grab interference
|
// 2. Release ALL X11 pointer grabs (SDL2 pattern)
|
||||||
const wv = findWebView(lib, windowPtr as any);
|
// WebKitGTK holds an implicit grab from the button-press event.
|
||||||
if (wv) lib.symbols.gtk_widget_set_sensitive(wv, false);
|
// gdk_seat_ungrab inside begin_resize_drag targets the wrong device.
|
||||||
// 3. Start WM resize
|
// Direct XUngrabPointer releases ALL grabs on this display connection.
|
||||||
|
releaseX11Grab();
|
||||||
|
// 3. Start WM resize — WM can now grab the pointer successfully
|
||||||
lib.symbols.gtk_window_begin_resize_drag(
|
lib.symbols.gtk_window_begin_resize_drag(
|
||||||
windowPtr as any,
|
windowPtr as any,
|
||||||
edge,
|
edge,
|
||||||
button,
|
button,
|
||||||
Math.round(rootX),
|
Math.round(rootX),
|
||||||
Math.round(rootY),
|
Math.round(rootY),
|
||||||
0, // GDK_CURRENT_TIME
|
0, // GDK_CURRENT_TIME — WM generates its own timestamp
|
||||||
);
|
);
|
||||||
// 4. Re-enable WebView after resize drag likely ends
|
|
||||||
// The WM holds the grab until mouse-up. 5s covers long drags.
|
|
||||||
// Shorter would risk re-enabling mid-drag.
|
|
||||||
setTimeout(() => {
|
|
||||||
if (wv) lib.symbols.gtk_widget_set_sensitive(wv, true);
|
|
||||||
}, 5000);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[gtk-window] begin_resize_drag failed:", err);
|
console.error("[gtk-window] begin_resize_drag failed:", err);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue