From f43dbe4e7c03d249a6fed22b974c5e4aa9f31177 Mon Sep 17 00:00:00 2001 From: Edoardo Spadoni Date: Wed, 18 Feb 2026 12:20:54 +0100 Subject: [PATCH] fix: window position drift on Windows multi-monitor setups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows with mixed DPI monitors (e.g. laptop at 125% + external at 100%), the PhoneIsland window drifted progressively on each call cycle, eventually disappearing off-screen. Root cause confirmed via diagnostic logging: calling setBounds() with zero dimensions ({width:0, height:0}) causes Chromium on Windows to multiply the window position by the primary display's scaleFactor. Each resize(0,0) at call end shifted coordinates by exactly 1.25x: x=150 → 187 → 233 → 291 → 363 → 453 (×1.25 each cycle) Additionally, showPhoneIsland() called resize() first (showing the window at the wrong position) then repositioned with a second setBounds(), causing a visible "trail" artifact. Changes: - PhoneIslandController.resize(): skip setBounds entirely when dimensions are 0x0, just hide directly. Always pass full rect {x, y, width, height} for non-zero resizes. - PhoneIslandController.showPhoneIsland(): set position and size in a single setBounds() call BEFORE show(), eliminating the visual trail. Only fall back to resize()+center() when no saved position exists. - ipcEvents.ts drag handler: use getBounds() instead of getContentSize() to get correct outer window dimensions. --- .../controllers/PhoneIslandController.ts | 42 +++++++++++-------- src/main/lib/ipcEvents.ts | 6 +-- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/main/classes/controllers/PhoneIslandController.ts b/src/main/classes/controllers/PhoneIslandController.ts index a1ca938f..86571782 100644 --- a/src/main/classes/controllers/PhoneIslandController.ts +++ b/src/main/classes/controllers/PhoneIslandController.ts @@ -22,15 +22,19 @@ export class PhoneIslandController { const { w, h } = size const window = this.window.getWindow() if (window) { - const bounds = window.getBounds() - if (bounds.height !== h || bounds.width !== w) { - window.setBounds({ width: w, height: h }) - PhoneIslandWindow.currentSize = { width: w, height: h } - } - //make sure the size is equal to [0,0] when you want to close the phone island, otherwise the size will not close and will generate slowness problems. if (h === 0 && w === 0) { + // Skip setBounds for 0x0: on Windows with mixed DPI monitors, setBounds with + // zero dimensions causes Chromium to multiply the position by the primary + // display's scaleFactor (e.g. 1.25), drifting the window on every call cycle. + // Just hide directly — the next show will set the correct size. window.hide() + PhoneIslandWindow.currentSize = { width: 0, height: 0 } } else { + const bounds = window.getBounds() + if (bounds.height !== h || bounds.width !== w) { + window.setBounds({ x: bounds.x, y: bounds.y, width: w, height: h }) + PhoneIslandWindow.currentSize = { width: w, height: h } + } // Don't show window during warm-up if (!window.isVisible() && !this.isWarmingUp) { window.show() @@ -48,8 +52,8 @@ export class PhoneIslandController { try { const window = this.window.getWindow() if (window) { + const { w, h } = size - this.resize(size) if (process.platform !== 'linux') { const phoneIslandPosition = AccountController.instance.getAccountPhoneIslandPosition() if (phoneIslandPosition) { @@ -59,22 +63,26 @@ export class PhoneIslandController { result || (phoneIslandPosition.x >= area.x && phoneIslandPosition.y >= area.y && - (phoneIslandPosition.x + size.w) < (area.x + area.width) && - (phoneIslandPosition.y + size.h) < (area.y + area.height)) + (phoneIslandPosition.x + w) < (area.x + area.width) && + (phoneIslandPosition.y + h) < (area.y + area.height)) ) }, false) if (isPhoneIslandOnDisplay) { - window?.setBounds({ x: phoneIslandPosition.x, y: phoneIslandPosition.y }, false) - } else { - window?.center() + // Set position and size in a single call BEFORE showing, to avoid + // the window briefly appearing at the wrong position (visual trail) + window.setBounds({ x: phoneIslandPosition.x, y: phoneIslandPosition.y, width: w, height: h }, false) + PhoneIslandWindow.currentSize = { width: w, height: h } + if (!window.isVisible() && !this.isWarmingUp) { + window.show() + window.setAlwaysOnTop(true, 'screen-saver') + } + return } } - else { - window?.center() - } - } else { - window?.center() } + // Fallback: no saved position, off-screen, or Linux + this.resize(size) + window.center() } } catch (e) { Log.warning('error during showing PhoneIslandWindow:', e) diff --git a/src/main/lib/ipcEvents.ts b/src/main/lib/ipcEvents.ts index 46dcccfa..ffeb670f 100644 --- a/src/main/lib/ipcEvents.ts +++ b/src/main/lib/ipcEvents.ts @@ -213,12 +213,12 @@ export function registerIpcEvents() { height }, false) } else { - const [w, h] = window.getContentSize() + const bounds = window.getBounds() window.setBounds({ x: newX, y: newY, - width: w, - height: h + width: bounds.width, + height: bounds.height }, false) } }