feat(electrobun): native GTK drag/resize via gtk_window_begin_resize_drag
- gtk-window.ts: FFI wrapper calling libgtk-3.so.0 directly via bun:ffi - begin_resize_drag: delegates resize to window manager (zero CPU, smooth) - begin_move_drag: delegates move to window manager (replaces JS drag) - Removed all JavaScript-based drag/resize logic (no mousemove/mouseup) - RPC: window.beginResize + window.beginMove - Resize handles: 4px edges + 8px corners with proper cursors
This commit is contained in:
parent
48d32f6f28
commit
9da9d96ebd
4 changed files with 149 additions and 72 deletions
105
ui-electrobun/src/bun/gtk-window.ts
Normal file
105
ui-electrobun/src/bun/gtk-window.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* GTK3 FFI — direct calls to libgtk-3.so.0 for window management.
|
||||
*
|
||||
* Used for begin_resize_drag and begin_move_drag which Electrobun
|
||||
* doesn't expose natively. These delegate to the window manager
|
||||
* for smooth, zero-CPU resize/move behavior.
|
||||
*/
|
||||
|
||||
import { dlopen, FFIType, ptr } from "bun:ffi";
|
||||
|
||||
// GdkWindowEdge values
|
||||
export const GDK_EDGE = {
|
||||
NW: 0, N: 1, NE: 2,
|
||||
W: 3, E: 4,
|
||||
SW: 5, S: 6, SE: 7,
|
||||
} as const;
|
||||
|
||||
type GdkEdge = (typeof GDK_EDGE)[keyof typeof GDK_EDGE];
|
||||
|
||||
let gtk3: ReturnType<typeof dlopen> | null = null;
|
||||
|
||||
function getGtk() {
|
||||
if (gtk3) return gtk3;
|
||||
try {
|
||||
gtk3 = dlopen("libgtk-3.so.0", {
|
||||
gtk_window_begin_resize_drag: {
|
||||
args: [FFIType.ptr, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.u32],
|
||||
returns: FFIType.void,
|
||||
},
|
||||
gtk_window_begin_move_drag: {
|
||||
args: [FFIType.ptr, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.u32],
|
||||
returns: FFIType.void,
|
||||
},
|
||||
});
|
||||
return gtk3;
|
||||
} catch (err) {
|
||||
console.error("[gtk-window] Failed to dlopen libgtk-3.so.0:", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate resize to the window manager.
|
||||
* The WM handles cursor, animation, constraints — zero CPU from us.
|
||||
*/
|
||||
export function beginResizeDrag(
|
||||
windowPtr: number | bigint,
|
||||
edge: GdkEdge,
|
||||
button: number,
|
||||
rootX: number,
|
||||
rootY: number,
|
||||
) {
|
||||
const lib = getGtk();
|
||||
if (!lib) return false;
|
||||
try {
|
||||
lib.symbols.gtk_window_begin_resize_drag(
|
||||
windowPtr as any,
|
||||
edge,
|
||||
button,
|
||||
Math.round(rootX),
|
||||
Math.round(rootY),
|
||||
0, // GDK_CURRENT_TIME
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("[gtk-window] begin_resize_drag failed:", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate move to the window manager.
|
||||
*/
|
||||
export function beginMoveDrag(
|
||||
windowPtr: number | bigint,
|
||||
button: number,
|
||||
rootX: number,
|
||||
rootY: number,
|
||||
) {
|
||||
const lib = getGtk();
|
||||
if (!lib) return false;
|
||||
try {
|
||||
lib.symbols.gtk_window_begin_move_drag(
|
||||
windowPtr as any,
|
||||
button,
|
||||
Math.round(rootX),
|
||||
Math.round(rootY),
|
||||
0,
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("[gtk-window] begin_move_drag failed:", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Edge string → GDK_EDGE mapping
|
||||
const EDGE_MAP: Record<string, GdkEdge> = {
|
||||
n: GDK_EDGE.N, s: GDK_EDGE.S, e: GDK_EDGE.E, w: GDK_EDGE.W,
|
||||
ne: GDK_EDGE.NE, nw: GDK_EDGE.NW, se: GDK_EDGE.SE, sw: GDK_EDGE.SW,
|
||||
};
|
||||
|
||||
export function edgeStringToGdk(edge: string): GdkEdge | null {
|
||||
return EDGE_MAP[edge] ?? null;
|
||||
}
|
||||
|
|
@ -108,6 +108,24 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
...providerHandlers,
|
||||
...miscHandlers,
|
||||
|
||||
// GTK native drag/resize — delegates to window manager (zero CPU)
|
||||
"window.beginResize": ({ edge, button, rootX, rootY }: { edge: string; button: number; rootX: number; rootY: number }) => {
|
||||
try {
|
||||
const { beginResizeDrag, edgeStringToGdk } = require("./gtk-window.ts");
|
||||
const gdkEdge = edgeStringToGdk(edge);
|
||||
if (gdkEdge === null) return { ok: false, error: `Unknown edge: ${edge}` };
|
||||
const ok = beginResizeDrag((mainWindow as any).ptr, gdkEdge, button, rootX, rootY);
|
||||
return { ok };
|
||||
} catch (err) { console.error("[window.beginResize]", err); return { ok: false }; }
|
||||
},
|
||||
"window.beginMove": ({ button, rootX, rootY }: { button: number; rootX: number; rootY: number }) => {
|
||||
try {
|
||||
const { beginMoveDrag } = require("./gtk-window.ts");
|
||||
const ok = beginMoveDrag((mainWindow as any).ptr, button, rootX, rootY);
|
||||
return { ok };
|
||||
} catch (err) { console.error("[window.beginMove]", err); return { ok: false }; }
|
||||
},
|
||||
|
||||
// Window controls — need mainWindow closure, stay inline
|
||||
"window.minimize": () => { try { mainWindow.minimize(); return { ok: true }; } catch (err) { console.error("[window.minimize]", err); return { ok: false }; } },
|
||||
"window.maximize": () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue