fix(desktop): persist window geometry on Linux
`moved` and `resized` are macOS/Windows only — Electron tags them `@platform darwin,win32` — so on Linux neither the main window nor the HUD ever heard that it had been dragged or resized, and both reopened at their default placement every launch. The main window had a `close` flush to fall back on; the HUD had nothing, so its position was lost outright. Bind `move`/`resize` instead. Those carry no platform tag and fire everywhere, and the trailing debounce already collapses the mid-drag stream a settled event would have saved us from.
This commit is contained in:
parent
da3a0a852f
commit
d73bc7f17a
|
|
@ -212,6 +212,7 @@ import { createWakeIndicatorWindowController } from './wake-indicator-window'
|
|||
import { readWindowBelow } from './window-below'
|
||||
import { createWindowRevealController } from './window-reveal'
|
||||
import {
|
||||
bindGeometryPersistence,
|
||||
computeWindowOptions,
|
||||
debounce,
|
||||
sanitizeWindowState,
|
||||
|
|
@ -2347,7 +2348,7 @@ function persistWindowState() {
|
|||
}
|
||||
}
|
||||
|
||||
// resized/moved fire many times mid-drag on Linux; debounce to one write.
|
||||
// move/resize fire many times mid-drag; debounce to one write.
|
||||
const schedulePersistWindowState = debounce(persistWindowState, 250)
|
||||
|
||||
// Zoom's primary store is a main-process JSON file. The renderer localStorage
|
||||
|
|
@ -9220,8 +9221,7 @@ function spawnHudWindow(sessionId) {
|
|||
|
||||
// Remember where the user parks and sizes it (debounced — these fire many
|
||||
// times mid-drag).
|
||||
win.on('moved', schedulePersistHudState)
|
||||
win.on('resized', schedulePersistHudState)
|
||||
bindGeometryPersistence(win, schedulePersistHudState)
|
||||
|
||||
wireWindowReveal(win, {
|
||||
show: () => {
|
||||
|
|
@ -9619,10 +9619,9 @@ function createWindow() {
|
|||
mainWindow.on('hide', () => sendWindowStateChanged())
|
||||
mainWindow.on('show', () => sendWindowStateChanged())
|
||||
|
||||
// Reopen where the user left off. resized/moved settle once per drag; close is
|
||||
// the cross-platform backstop, flushed synchronously before the window is gone.
|
||||
mainWindow.on('resized', schedulePersistWindowState)
|
||||
mainWindow.on('moved', schedulePersistWindowState)
|
||||
// Reopen where the user left off. close is the backstop, flushed
|
||||
// synchronously before the window is gone.
|
||||
bindGeometryPersistence(mainWindow, schedulePersistWindowState)
|
||||
mainWindow.on('maximize', schedulePersistWindowState)
|
||||
mainWindow.on('unmaximize', schedulePersistWindowState)
|
||||
mainWindow.on('close', () => schedulePersistWindowState.flush())
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
import assert from 'node:assert/strict'
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
import { test, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
bindGeometryPersistence,
|
||||
computeWindowOptions,
|
||||
debounce,
|
||||
DEFAULT_HEIGHT,
|
||||
|
|
@ -155,3 +157,33 @@ test('debounce.flush runs now and cancels the pending timer', () => {
|
|||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
// ─── bindGeometryPersistence ───────────────────────────────────────────────
|
||||
|
||||
test('bindGeometryPersistence saves on drag and on resize', () => {
|
||||
const win = new EventEmitter()
|
||||
let saves = 0
|
||||
|
||||
bindGeometryPersistence(win, () => {
|
||||
saves += 1
|
||||
})
|
||||
|
||||
win.emit('move')
|
||||
win.emit('resize')
|
||||
assert.equal(saves, 2)
|
||||
})
|
||||
|
||||
// The regression this exists for: `moved`/`resized` never fire on Linux, so a
|
||||
// window bound only to those pretends to persist and silently forgets its place
|
||||
// every launch. A window that emits nothing else must still save.
|
||||
test('a window that never emits moved/resized still saves its geometry', () => {
|
||||
const win = new EventEmitter()
|
||||
let saves = 0
|
||||
|
||||
bindGeometryPersistence(win, () => {
|
||||
saves += 1
|
||||
})
|
||||
|
||||
win.emit('move')
|
||||
assert.ok(saves > 0)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -135,9 +135,30 @@ function debounce(fn, delayMs) {
|
|||
return debounced
|
||||
}
|
||||
|
||||
// The geometry events worth persisting from. `moved` and `resized` — the
|
||||
// settled-once-per-drag pair — are macOS/Windows only (Electron tags them
|
||||
// `@platform darwin,win32`), so a window bound to those alone never saves its
|
||||
// place on Linux: the events simply never arrive. `move` and `resize` carry no
|
||||
// platform tag and fire everywhere. They also fire continuously mid-drag, which
|
||||
// is what the trailing debounce above is for — and once a burst collapses to a
|
||||
// single trailing run, the settled events add nothing the debounce hasn't
|
||||
// already given us.
|
||||
const GEOMETRY_EVENTS = ['move', 'resize']
|
||||
|
||||
// Bind `schedule` to every geometry event, on a BrowserWindow or any emitter
|
||||
// with `.on`. One call site per window so the platform reasoning above can't be
|
||||
// half-applied to one window and not the other.
|
||||
function bindGeometryPersistence(win, schedule) {
|
||||
for (const event of GEOMETRY_EVENTS) {
|
||||
win.on(event, schedule)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
bindGeometryPersistence,
|
||||
computeWindowOptions,
|
||||
debounce,
|
||||
GEOMETRY_EVENTS,
|
||||
DEFAULT_HEIGHT,
|
||||
DEFAULT_WIDTH,
|
||||
MIN_HEIGHT,
|
||||
|
|
|
|||
Loading…
Reference in New Issue