feat(electrobun): native GTK resize via button-press-event signal (tao pattern)

All previous approaches failed because they initiated resize from JS
(too late, wrong timestamp, WebKit steals grab). The correct approach
(proven by Tauri/tao) is to connect button-press-event DIRECTLY on the
GtkWindow at the GTK level, BEFORE WebKitGTK processes events.

New: gtk-resize.ts
- JSCallback for button-press-event + motion-notify-event
- Hit-test in 8px border zone (same as tao's 5*scale_factor)
- begin_resize_drag with REAL event timestamp (not GDK_CURRENT_TIME)
- Returns TRUE to STOP propagation (WebKit never sees the press)
- Cursor updates on motion-notify in border zone

Removed: all JS resize handles (divs, mousemove, mouseup, RPC calls)
This commit is contained in:
Hibryda 2026-03-25 15:25:25 +01:00
parent c4d06ca999
commit e9fcd8401e
3 changed files with 201 additions and 76 deletions

View file

@ -104,53 +104,9 @@
e.preventDefault();
}
// ── Window resize — JS-based with X11 direct frame set ────────
let resizeEdge: string | null = null;
let resizeStartX = 0;
let resizeStartY = 0;
let resizeFrame = { x: 0, y: 0, width: 0, height: 0 };
const CURSOR_MAP: Record<string, string> = {
n: 'n-resize', s: 's-resize', e: 'e-resize', w: 'w-resize',
ne: 'ne-resize', nw: 'nw-resize', se: 'se-resize', sw: 'sw-resize',
};
function onResizeStart(e: MouseEvent, edge: string) {
e.preventDefault();
e.stopPropagation();
resizeEdge = edge;
resizeStartX = e.screenX;
resizeStartY = e.screenY;
document.body.style.cursor = CURSOR_MAP[edge] || 'default';
document.body.style.userSelect = 'none';
// Capture frame synchronously from browser — no RPC delay
resizeFrame = {
x: window.screenX, y: window.screenY,
width: window.outerWidth, height: window.outerHeight,
};
}
function onResizeMove(e: MouseEvent) {
if (!resizeEdge) return;
const dx = e.screenX - resizeStartX;
const dy = e.screenY - resizeStartY;
let { x, y, width, height } = resizeFrame;
const MIN_W = 400, MIN_H = 300;
if (resizeEdge.includes('e')) width = Math.max(MIN_W, width + dx);
if (resizeEdge.includes('w')) { const nw = Math.max(MIN_W, width - dx); x += width - nw; width = nw; }
if (resizeEdge.includes('s')) height = Math.max(MIN_H, height + dy);
if (resizeEdge.includes('n')) { const nh = Math.max(MIN_H, height - dy); y += height - nh; height = nh; }
// Direct browser API — no RPC, no GTK, no FFI. Works in Chromium, test in WebKitGTK.
window.moveTo(Math.round(x), Math.round(y));
window.resizeTo(Math.round(width), Math.round(height));
}
function onResizeEnd() {
if (!resizeEdge) return;
resizeEdge = null;
document.body.style.cursor = '';
document.body.style.userSelect = '';
saveWindowFrame();
}
// ── Window resize handled natively by GTK (see gtk-resize.ts) ──
// No JS resize handlers needed — GTK button-press-event intercepts
// at the native level before WebKitGTK, matching Tauri/tao's approach.
// ── Window frame persistence (debounced 500ms) ──────────────
let frameSaveTimer: ReturnType<typeof setTimeout> | null = null;
@ -250,9 +206,7 @@
setAgentToastFn(showToast);
setupErrorBoundary();
// JS resize uses document-level listeners
document.addEventListener('mousemove', onResizeMove);
document.addEventListener('mouseup', onResizeEnd);
// Resize handled natively by GTK (gtk-resize.ts) — no JS listeners needed
// Blink + session timers — MUST be in onMount, NOT $effect
// $effect interacts with reactive graph and causes cycles
@ -348,8 +302,7 @@
clearInterval(sessionId);
document.removeEventListener("keydown", handleSearchShortcut);
window.removeEventListener("palette-command", handlePaletteCommand);
document.removeEventListener('mousemove', onResizeMove);
document.removeEventListener('mouseup', onResizeEnd);
// Resize handled natively — no JS listeners to clean up
};
});
</script>
@ -369,23 +322,7 @@
onClose={() => setNotifDrawerOpen(false)}
/>
<!-- Resize handles (all edges + corners) -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-n" onmousedown={(e) => onResizeStart(e, 'n')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-s" onmousedown={(e) => onResizeStart(e, 's')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-e" onmousedown={(e) => onResizeStart(e, 'e')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-w" onmousedown={(e) => onResizeStart(e, 'w')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-ne" onmousedown={(e) => onResizeStart(e, 'ne')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-nw" onmousedown={(e) => onResizeStart(e, 'nw')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-se" onmousedown={(e) => onResizeStart(e, 'se')}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rz rz-sw" onmousedown={(e) => onResizeStart(e, 'sw')}></div>
<!-- Resize handled natively by GTK (gtk-resize.ts) — 8px border zone on the GtkWindow -->
<div class="app-shell" role="presentation">
<!-- Left sidebar icon rail — draggable for window move -->