From 9bc0270a13523cdec3597a122d5dcb097ce2f496 Mon Sep 17 00:00:00 2001 From: Devin Binnie <52460000+devinbinnie@users.noreply.github.com> Date: Wed, 29 May 2024 10:08:11 -0400 Subject: [PATCH] [MM-58357] Account for monitor scale factor when creating the window from saved bounds (#3045) --- src/common/Validator.ts | 2 +- src/main/windows/mainWindow.test.js | 16 +++++++++++++++- src/main/windows/mainWindow.ts | 23 +++++++++++++++++------ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/common/Validator.ts b/src/common/Validator.ts index 68b6ce7f..7075d321 100644 --- a/src/common/Validator.ts +++ b/src/common/Validator.ts @@ -179,7 +179,7 @@ export function validateArgs(data: Args) { } // validate bounds_info.json -export function validateBoundsInfo(data: SavedWindowState) { +export function validateBoundsInfo(data: SavedWindowState | null) { return validateAgainstSchema(data, boundsInfoSchema); } diff --git a/src/main/windows/mainWindow.test.js b/src/main/windows/mainWindow.test.js index 4377a2eb..3763e60d 100644 --- a/src/main/windows/mainWindow.test.js +++ b/src/main/windows/mainWindow.test.js @@ -104,7 +104,7 @@ describe('main/windows/mainWindow', () => { BrowserWindow.mockImplementation(() => baseWindow); fs.readFileSync.mockImplementation(() => '{"x":400,"y":300,"width":1280,"height":700,"maximized":false,"fullscreen":false}'); path.join.mockImplementation(() => 'anyfile.txt'); - screen.getDisplayMatching.mockImplementation(() => ({bounds: {x: 0, y: 0, width: 1920, height: 1080}})); + screen.getDisplayMatching.mockImplementation(() => ({scaleFactor: 1, bounds: {x: 0, y: 0, width: 1920, height: 1080}})); isInsideRectangle.mockReturnValue(true); Validator.validateBoundsInfo.mockImplementation((data) => data); ContextMenu.mockImplementation(() => ({ @@ -129,6 +129,20 @@ describe('main/windows/mainWindow', () => { })); }); + it('should set scaled window size using bounds read from file', () => { + screen.getDisplayMatching.mockImplementation(() => ({scaleFactor: 2, bounds: {x: 0, y: 0, width: 1920, height: 1080}})); + const mainWindow = new MainWindow(); + mainWindow.init(); + expect(BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({ + x: 400, + y: 300, + width: 640, + height: 350, + maximized: false, + fullscreen: false, + })); + }); + it('should set default window size when failing to read bounds from file', () => { fs.readFileSync.mockImplementation(() => 'just a bunch of garbage'); const mainWindow = new MainWindow(); diff --git a/src/main/windows/mainWindow.ts b/src/main/windows/mainWindow.ts index 0d8c0b6f..81d3f1e0 100644 --- a/src/main/windows/mainWindow.ts +++ b/src/main/windows/mainWindow.ts @@ -45,7 +45,7 @@ const ALT_MENU_KEYS = ['Alt+F', 'Alt+E', 'Alt+V', 'Alt+H', 'Alt+W', 'Alt+P']; export class MainWindow extends EventEmitter { private win?: BrowserWindow; - private savedWindowState?: SavedWindowState; + private savedWindowState?: Partial; private ready: boolean; private isResizing: boolean; private lastEmittedBounds?: Electron.Rectangle; @@ -89,6 +89,7 @@ export class MainWindow extends EventEmitter { spellcheck: typeof Config.useSpellChecker === 'undefined' ? true : Config.useSpellChecker, }, }); + log.debug('main window options', windowOptions); if (process.platform === 'linux') { windowOptions.icon = path.join(path.resolve(app.getAppPath(), 'assets'), 'linux', 'app_icon.png'); @@ -245,25 +246,34 @@ export class MainWindow extends EventEmitter { return os.platform() === 'darwin' || (os.platform() === 'win32' && Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.2')); }; - private getSavedWindowState = () => { - let savedWindowState: any; + private getSavedWindowState = (): Partial => { try { - savedWindowState = JSON.parse(fs.readFileSync(boundsInfoPath, 'utf-8')); + let savedWindowState: SavedWindowState | null = JSON.parse(fs.readFileSync(boundsInfoPath, 'utf-8')); savedWindowState = Validator.validateBoundsInfo(savedWindowState); if (!savedWindowState) { throw new Error('Provided bounds info file does not validate, using defaults instead.'); } const matchingScreen = screen.getDisplayMatching(savedWindowState); + log.debug('matching screen for main window', matchingScreen); if (!(matchingScreen && (isInsideRectangle(matchingScreen.bounds, savedWindowState) || savedWindowState.maximized))) { throw new Error('Provided bounds info are outside the bounds of your screen, using defaults instead.'); } + // We check for the monitor's scale factor when we want to set these bounds + // This is due to a long running Electron issue: https://github.com/electron/electron/issues/10862 + return { + ...savedWindowState, + width: Math.floor(savedWindowState.width / matchingScreen.scaleFactor), + height: Math.floor(savedWindowState.height / matchingScreen.scaleFactor), + }; } catch (e) { log.error(e); // Follow Electron's defaults, except for window dimensions which targets 1024x768 screen resolution. - savedWindowState = {width: DEFAULT_WINDOW_WIDTH, height: DEFAULT_WINDOW_HEIGHT}; + return { + width: DEFAULT_WINDOW_WIDTH, + height: DEFAULT_WINDOW_HEIGHT, + }; } - return savedWindowState; }; private saveWindowState = (file: string, window: BrowserWindow) => { @@ -273,6 +283,7 @@ export class MainWindow extends EventEmitter { fullscreen: window.isFullScreen(), }; try { + log.debug('saving window state', windowState); fs.writeFileSync(file, JSON.stringify(windowState)); } catch (e) { // [Linux] error happens only when the window state is changed before the config dir is created.