Skip to content
Snippets Groups Projects
window-state.ts 3.60 KiB
import fs from 'fs';
import path from 'path';

import electron, { BrowserWindow } from 'electron';

const backend = undefined;
const getBackend = async () =>
  // eslint-disable-next-line import/extensions
  backend || (await import('loot-core/lib-dist/bundle.desktop.js'));

type WindowState = Electron.Rectangle & {
  isMaximized?: boolean;
  isFullScreen?: boolean;
  displayBounds?: Electron.Rectangle;
};

async function loadState() {
  let state: WindowState | undefined = undefined;
  const backend = await getBackend();
  try {
    state = JSON.parse(
      fs.readFileSync(
        path.join(backend.lib.getDataDir(), 'window.json'),
        'utf8',
      ),
    );
  } catch (e) {
    console.log('Could not load window state');
  }

  return validateState(state);
}

function updateState(win: BrowserWindow, state: WindowState) {
  const screen = electron.screen;
  const bounds = win.getBounds();
  if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) {
    state.width = bounds.width;
    state.height = bounds.height;
  }

  state.x = bounds.x;
  state.y = bounds.y;
  state.isMaximized = win.isMaximized();
  state.isFullScreen = win.isFullScreen();
  state.displayBounds = screen.getDisplayMatching(bounds).bounds;
}

async function saveState(win: BrowserWindow, state: WindowState) {
  const backend = await getBackend();
  updateState(win, state);
  fs.writeFileSync(
    path.join(backend.lib.getDataDir(), 'window.json'),
    JSON.stringify(state),
    'utf8',
  );
}

export function listen(win: BrowserWindow, state: WindowState) {
  if (state.isMaximized) {
    win.maximize();
  }
  if (state.isFullScreen) {
    win.setFullScreen(true);
  }

  const saver = saveState.bind(null, win, state);

  win.on('close', saver);
  return () => {
    win.removeListener('close', saver);
  };
}

function hasBounds(state: WindowState) {
  return (
    Number.isInteger(state.x) &&
    Number.isInteger(state.y) &&
    Number.isInteger(state.width) &&
    state.width > 0 &&
    Number.isInteger(state.height) &&
    state.height > 0
  );
}

function validateState(state?: WindowState): Partial<WindowState> {
  if (
    !state ||
    !(hasBounds(state) || state.isMaximized || state.isFullScreen)
  ) {
    return {};
  }

  const newState = Object.assign({}, state);

  if (hasBounds(state) && state.displayBounds) {
    const screen = electron.screen;

    // Check if the display where the window was last open is still available
    const displayBounds = screen.getDisplayMatching(state).bounds;

    if (
      state.displayBounds.x !== displayBounds.x ||
      state.displayBounds.y !== displayBounds.y ||
      state.displayBounds.width !== displayBounds.width ||
      state.displayBounds.height !== displayBounds.height
    ) {
      if (displayBounds.width < state.displayBounds.width) {
        if (state.x > displayBounds.width) {
          newState.x = 0;
        }

        if (state.width > displayBounds.width) {
          newState.width = displayBounds.width;
        }
      }

      if (displayBounds.height < state.displayBounds.height) {
        if (state.y > displayBounds.height) {
          newState.y = 0;
        }

        if (state.height > displayBounds.height) {
          newState.height = displayBounds.height;
        }
      }
    }
  }

  return newState;
}

export async function get() {
  const screen = electron.screen;
  const displayBounds = screen.getPrimaryDisplay().bounds;

  const state: WindowState = Object.assign(
    {
      x: 100,
      y: 50,
      width: Math.min(1000, displayBounds.width - 100),
      height: Math.min(700, displayBounds.width - 50),
    },
    await loadState(),
  );

  return state;
}