fix(desktop): guard manual window drag coordinates

This commit is contained in:
Mohamed Boudra
2026-03-21 14:58:57 +07:00
parent b15159caeb
commit 6731879931
6 changed files with 360 additions and 142 deletions
+81 -59
View File
@@ -1,81 +1,103 @@
import { app, BrowserWindow, ipcMain } from "electron";
import { app, BrowserWindow, ipcMain } from 'electron'
import {
createWindowMoveState,
readWindowMovePayload,
resolveWindowMovePosition,
type WindowMoveState,
} from './window-drag-coordinates.js'
export function registerWindowManager(): void {
// ---------------------------------------------------------------------------
// Manual window dragging (replaces flaky CSS -webkit-app-region: drag).
// State is keyed per window id to support multiple windows.
// ---------------------------------------------------------------------------
const moveStates = new Map<number, { offsetX: number; offsetY: number }>();
const moveStates = new Map<number, WindowMoveState>()
ipcMain.on(
"paseo:window:startMove",
(event, payload: { screenX: number; screenY: number }) => {
if (typeof payload?.screenX !== "number" || typeof payload?.screenY !== "number") return;
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) return;
const [winX, winY] = win.getPosition();
moveStates.set(win.id, { offsetX: payload.screenX - winX, offsetY: payload.screenY - winY });
ipcMain.on('paseo:window:startMove', (event, payload: { screenX: number; screenY: number }) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (!win) return
const movePayload = readWindowMovePayload(payload)
if (!movePayload) {
moveStates.delete(win.id)
return
}
);
ipcMain.on(
"paseo:window:moving",
(event, payload: { screenX: number; screenY: number }) => {
if (typeof payload?.screenX !== "number" || typeof payload?.screenY !== "number") return;
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) return;
const state = moveStates.get(win.id);
if (!state) return;
win.setPosition(
Math.round(payload.screenX - state.offsetX),
Math.round(payload.screenY - state.offsetY)
);
const [winX, winY] = win.getPosition()
const moveState = createWindowMoveState({
payload: movePayload,
windowX: winX,
windowY: winY,
})
if (!moveState) {
moveStates.delete(win.id)
return
}
);
moveStates.set(win.id, moveState)
})
ipcMain.on("paseo:window:endMove", (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) moveStates.delete(win.id);
});
ipcMain.on('paseo:window:moving', (event, payload: { screenX: number; screenY: number }) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (!win) return
const state = moveStates.get(win.id)
if (!state) return
const movePayload = readWindowMovePayload(payload)
if (!movePayload) {
moveStates.delete(win.id)
return
}
const nextPosition = resolveWindowMovePosition({
payload: movePayload,
state,
})
if (!nextPosition) {
moveStates.delete(win.id)
return
}
win.setPosition(nextPosition.x, nextPosition.y)
})
app.on("browser-window-created", (_event, win) => {
win.on("closed", () => moveStates.delete(win.id));
});
ipcMain.on('paseo:window:endMove', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) moveStates.delete(win.id)
})
ipcMain.handle("paseo:window:toggleMaximize", (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) return;
app.on('browser-window-created', (_event, win) => {
win.on('closed', () => moveStates.delete(win.id))
})
ipcMain.handle('paseo:window:toggleMaximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (!win) return
if (win.isMaximized()) {
win.unmaximize();
win.unmaximize()
} else {
win.maximize();
win.maximize()
}
});
})
ipcMain.handle("paseo:window:isFullscreen", (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
return win?.isFullScreen() ?? false;
});
ipcMain.handle('paseo:window:isFullscreen', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
return win?.isFullScreen() ?? false
})
ipcMain.handle("paseo:window:setBadgeCount", (_event, count?: number) => {
if (process.platform === "darwin" || process.platform === "linux") {
app.setBadgeCount(count ?? 0);
ipcMain.handle('paseo:window:setBadgeCount', (_event, count?: number) => {
if (process.platform === 'darwin' || process.platform === 'linux') {
app.setBadgeCount(count ?? 0)
}
});
})
}
export function setupWindowResizeEvents(win: BrowserWindow): void {
win.on("resize", () => {
win.webContents.send("paseo:window:resized", {});
});
win.on('resize', () => {
win.webContents.send('paseo:window:resized', {})
})
win.on("enter-full-screen", () => {
win.webContents.send("paseo:window:resized", {});
});
win.on('enter-full-screen', () => {
win.webContents.send('paseo:window:resized', {})
})
win.on("leave-full-screen", () => {
win.webContents.send("paseo:window:resized", {});
});
win.on('leave-full-screen', () => {
win.webContents.send('paseo:window:resized', {})
})
}
/**
@@ -83,11 +105,11 @@ export function setupWindowResizeEvents(win: BrowserWindow): void {
* The renderer handles drag-drop via standard HTML5 APIs instead.
*/
export function setupDragDropPrevention(win: BrowserWindow): void {
win.webContents.on("will-navigate", (event, url) => {
win.webContents.on('will-navigate', (event, url) => {
// Allow normal navigation (e.g. dev server hot-reload) but block file:// URLs
// that result from dropping files onto the window.
if (url.startsWith("file://")) {
event.preventDefault();
if (url.startsWith('file://')) {
event.preventDefault()
}
});
})
}