From d6af771495cab327eb49c75acd7a73cafc883e35 Mon Sep 17 00:00:00 2001 From: Boof2015 <75185879+Boof2015@users.noreply.github.com> Date: Fri, 15 May 2026 15:44:39 -0400 Subject: [PATCH] fix position not saving when using send to buttons --- src/main/index.ts | 66 +++++++++++++++++++++++++++++++---- test/renderer-helpers.test.ts | 11 ++++++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index d0e67fe..09d4e22 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -444,23 +444,39 @@ async function syncNativeThemeAppearance(): Promise { applyNativeThemeSnapshot(await getThemeLibrary().getSnapshot()) } +function clearPendingMainWindowBoundsSave(): void { + if (!mainWindowBoundsTimer) return + + clearTimeout(mainWindowBoundsTimer) + mainWindowBoundsTimer = null +} + +function sendMainWindowBoundsChanged(window: BrowserWindow): void { + if (!isMainRendererWindow(window) || !mainRendererReady || !supportsGeometryPersistence()) return + if (window.isDestroyed() || window.webContents.isDestroyed()) return + + window.webContents.send('window:bounds-changed', toLogicalBounds(window)) +} + function scheduleMainWindowBoundsSave(window: BrowserWindow): void { if (!isMainRendererWindow(window) || !mainRendererReady || !supportsGeometryPersistence()) return - if (mainWindowBoundsTimer) { - clearTimeout(mainWindowBoundsTimer) - } + clearPendingMainWindowBoundsSave() mainWindowBoundsTimer = setTimeout(() => { mainWindowBoundsTimer = null - if (window.isDestroyed() || window.webContents.isDestroyed()) return if (isMainWindowSyncSuppressed()) { return } - window.webContents.send('window:bounds-changed', toLogicalBounds(window)) + sendMainWindowBoundsChanged(window) }, 80) } +function flushMainWindowBoundsChanged(window: BrowserWindow): void { + clearPendingMainWindowBoundsSave() + sendMainWindowBoundsChanged(window) +} + function normalizeIncomingProfile(raw: unknown, fallbackName = 'Profile'): Profile { return normalizeProfile(raw, fallbackName) } @@ -1055,6 +1071,18 @@ function createMainWindow(): void { loadRendererTarget(mainWindow, { window: 'main' }) } +function sendScopePopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void { + if ( + !mainWindow + || mainWindow.isDestroyed() + || window.isDestroyed() + || !mainRendererReady + || !supportsGeometryPersistence() + ) return + + mainWindow.webContents.send('scope-popout:bounds-changed', kind, toLogicalBounds(window)) +} + function emitPopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void { if ( !mainWindow @@ -1077,13 +1105,35 @@ function emitPopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void { suppressNextPopoutBoundsEvents.delete(kind) return } - const bounds = toLogicalBounds(window) - mainWindow.webContents.send('scope-popout:bounds-changed', kind, bounds) + sendScopePopoutBoundsChanged(kind, window) }, 80) popoutBoundsTimers.set(kind, timer) } +function flushScopePopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void { + const existingTimer = popoutBoundsTimers.get(kind) + if (existingTimer) { + clearTimeout(existingTimer) + popoutBoundsTimers.delete(kind) + } + + suppressNextPopoutBoundsEvents.delete(kind) + sendScopePopoutBoundsChanged(kind, window) +} + +function flushRepositionedWindowBounds(window: BrowserWindow): void { + if (isMainRendererWindow(window)) { + flushMainWindowBoundsChanged(window) + return + } + + const kind = getScopeKindForWindow(window) + if (kind) { + flushScopePopoutBoundsChanged(kind, window) + } +} + function destroyScopePopoutWindow(kind: ScopeKind): void { const window = scopePopoutWindows.get(kind) if (!window) return @@ -1773,6 +1823,7 @@ function setupIPC(): void { width: workArea.width, height: logicalBounds.height, }) + flushRepositionedWindowBounds(targetWindow) return } @@ -1784,6 +1835,7 @@ function setupIPC(): void { targetWindow.setPosition(workArea.x, workArea.y + workArea.height - height) } targetWindow.setSize(workArea.width, height) + flushRepositionedWindowBounds(targetWindow) }) ipcMain.on('window:expand-settings', (event, panelHeight: number) => { diff --git a/test/renderer-helpers.test.ts b/test/renderer-helpers.test.ts index f756d9e..2414878 100644 --- a/test/renderer-helpers.test.ts +++ b/test/renderer-helpers.test.ts @@ -2810,6 +2810,17 @@ test('Wayland window controls use native drag regions and omit unsupported repos assert.match(stylesSource, /\.scope-popout__header\.is-native-drag \{/) }) +test('programmatic top/bottom reposition flushes fresh bounds through persistence channels', async () => { + const mainSource = await readFile(join(process.cwd(), 'src', 'main', 'index.ts'), 'utf8') + + assert.match(mainSource, /function sendMainWindowBoundsChanged\(window: BrowserWindow\): void \{[\s\S]*?window\.webContents\.send\('window:bounds-changed', toLogicalBounds\(window\)\)[\s\S]*?\}/) + assert.match(mainSource, /function flushMainWindowBoundsChanged\(window: BrowserWindow\): void \{[\s\S]*?clearPendingMainWindowBoundsSave\(\)[\s\S]*?sendMainWindowBoundsChanged\(window\)[\s\S]*?\}/) + assert.match(mainSource, /function sendScopePopoutBoundsChanged\(kind: ScopeKind, window: BrowserWindow\): void \{[\s\S]*?mainWindow\.webContents\.send\('scope-popout:bounds-changed', kind, toLogicalBounds\(window\)\)[\s\S]*?\}/) + assert.match(mainSource, /function flushScopePopoutBoundsChanged\(kind: ScopeKind, window: BrowserWindow\): void \{[\s\S]*?popoutBoundsTimers\.delete\(kind\)[\s\S]*?suppressNextPopoutBoundsEvents\.delete\(kind\)[\s\S]*?sendScopePopoutBoundsChanged\(kind, window\)[\s\S]*?\}/) + assert.match(mainSource, /applyLogicalBounds\(targetWindow, \{[\s\S]*?height: logicalBounds\.height,[\s\S]*?\}\)\s*flushRepositionedWindowBounds\(targetWindow\)\s*return/) + assert.match(mainSource, /targetWindow\.setSize\(workArea\.width, height\)\s*flushRepositionedWindowBounds\(targetWindow\)/) +}) + test('toggleScope appends now playing to the scope order when it is enabled from an opt-in profile', () => { const previousSettingsState = useSettingsStore.getState() const fakeWindow = installFakeElectronWindow()