"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.createWindow = createWindow;
exports.initElectron = initElectron;
exports.getCommand = void 0;

var _debug = _interopRequireDefault(require("debug"));

var _electron = require("electron");

var _defaults = _interopRequireWildcard(require("../webapp/defaults"));

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/*
 * Copyright 2017-18 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};

const debug = (0, _debug.default)('main/spawn-electron');
debug('loading');

/**
 * Keep a global reference of the window object, if you don't, the window will
 * be closed automatically when the JavaScript object is garbage collected.
 *
 */
let nWindows = 0;
/**
 * Keep refs to the electron app around
 *
 */

let app;

function createWindow(noHeadless = false, executeThisArgvPlease, subwindowPlease, subwindowPrefs) {
  debug('createWindow', executeThisArgvPlease);

  if (subwindowPrefs && subwindowPrefs.bringYourOwnWindow) {
    subwindowPrefs.bringYourOwnWindow();
    return;
  } // Create the browser window.


  let width = subwindowPrefs && subwindowPrefs.width || 1280;
  let height = subwindowPrefs && subwindowPrefs.height || 960;

  if (process.env.WINDOW_WIDTH) {
    width = parseInt(process.env.WINDOW_WIDTH, 10);

    if (isNaN(width)) {
      console.error('Cannot parse WINDOW_WIDTH ' + process.env.WINDOW_WIDTH);
      width = 1280;
    }
  }

  if (process.env.WINDOW_HEIGHT) {
    height = parseInt(process.env.WINDOW_HEIGHT, 10);

    if (isNaN(height)) {
      console.error('Cannot parse WINDOW_HEIGHT ' + process.env.WINDOW_HEIGHT);
      height = 960;
    }
  }

  let promise = Promise.resolve();

  if (!app) {
    debug('we need to spawn electron', subwindowPlease, subwindowPrefs);
    delete subwindowPrefs.synonymFor; // circular JSON
    // eslint-disable-next-line @typescript-eslint/no-use-before-define

    promise = initElectron(['--'].concat(executeThisArgvPlease), {}, subwindowPlease, subwindowPrefs).then(() => __awaiter(this, void 0, void 0, function* () {
      app = (yield Promise.resolve().then(() => require('electron'))).app;
    })).catch(err => {
      // headless
      debug('not ready for graphics', err);
    });
  } // note: titleBarStyle on macOS needs to be customButtonsOnHover if we want to support cursor:pointer
  // but this doesn't render the inset window buttons
  // see https://github.com/electron/electron/issues/10243


  promise.then(() => __awaiter(this, void 0, void 0, function* () {
    const {
      productName
    } = yield Promise.resolve().then(() => require('@kui-shell/client/config.d/name.json'));
    const {
      filesystem
    } = yield Promise.resolve().then(() => require('@kui-shell/client/config.d/icons.json'));
    const position = subwindowPrefs && subwindowPrefs.position ? yield subwindowPrefs.position() : {};
    const opts = Object.assign({
      title: productName,
      width,
      height,
      webPreferences: {
        backgroundThrottling: false,
        nodeIntegration: true // prior to electron 5, this was the default

      } // titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default'

    }, position); // if user ups zoom level, reloads, we're stuck at a higher zoom
    // see https://github.com/electron/electron/issues/10572
    // note that this requires show: false above

    opts.webPreferences.zoomFactor = 1;
    const {
      dirname,
      join
    } = yield Promise.resolve().then(() => require('path'));
    const root = dirname(require.resolve('@kui-shell/prescan.json'));

    if (process.platform === 'linux') {
      const icon = join(root, 'build', filesystem.linux);
      opts.icon = icon;
    } else if (process.platform === 'win32') {
      const icon = join(root, 'build', filesystem.win32);
      opts.icon = icon;
    }

    if (process.platform === 'linux' || process.platform === 'win32') {
      opts.autoHideMenuBar = true;
    }

    if (subwindowPlease) {
      // this tells electron to size content to the given width and height,
      // (i.e. NOT vice versa, to size the window to the content!)
      opts.useContentSize = true;
    }

    if (process.env.KUI_POSITION_X) {
      opts.x = parseInt(process.env.KUI_POSITION_X, 10);
    }

    if (process.env.KUI_POSITION_Y) {
      opts.y = parseInt(process.env.KUI_POSITION_Y, 10);
    }

    debug('createWindow::new BrowserWindow');
    const mainWindow = new _electron.BrowserWindow(opts);
    nWindows++;
    debug('createWindow::new BrowserWindow success');
    mainWindow.once('ready-to-show', () => {
      mainWindow.setVisibleOnAllWorkspaces(true);
      mainWindow.show();
      mainWindow.setVisibleOnAllWorkspaces(false);
    });
    const fixedWindows = {};

    const openFixedWindow = opts => {
      const {
        type,
        event,
        url,
        size = mainWindow.getBounds(),
        position = mainWindow.getBounds()
      } = opts;
      const existing = fixedWindows[type] || {};
      const {
        window: existingWindow,
        url: currentURL
      } = existing;

      if (!existingWindow || existingWindow.isDestroyed()) {
        const window = new _electron.BrowserWindow({
          width: size.width,
          height: size.height,
          frame: true
        });
        fixedWindows[type] = {
          window,
          url
        };
        window.setPosition(position.x + 62, position.y + 62); // window.on('closed', () => { docsWindow = null })

        window.loadURL(url);
      } else {
        if (currentURL !== url) {
          existingWindow.loadURL(url);
          existing.url = url;
        }

        existingWindow.focus();
      }

      event.preventDefault();
    };
    /** this event handler will be called when the window's content finishes loading */


    mainWindow.webContents.on('did-finish-load', () => __awaiter(this, void 0, void 0, function* () {
      if (mainWindow) {
        try {// const { switchToPersistedThemeChoice } = await import('../webapp/themes/persistence')
          // await switchToPersistedThemeChoice(mainWindow.webContents /*, Electron.nativeTheme.shouldUseDarkColors */)
        } catch (err) {
          console.error('error initializing themes', err);
        }
      }
    }));
    /** jump in and manage the way popups create new windows */

    mainWindow.webContents.on('new-window', // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (event, url, frameName, disposition, options) => __awaiter(this, void 0, void 0, function* () {
      if (url.startsWith('https://youtu.be')) {
        // special handling of youtube links
        openFixedWindow({
          type: 'videos',
          event,
          url,
          options,
          size: {
            width: 800,
            height: 600
          }
        });
      } else {
        event.preventDefault();
        (yield Promise.resolve().then(() => require('electron'))).shell.openExternal(url);
      }
    }));
    let commandContext = executeThisArgvPlease && executeThisArgvPlease.find(_ => /--command-context/.test(_));

    if (commandContext) {
      executeThisArgvPlease = executeThisArgvPlease.filter(_ => !_.match(/--command-context/)); // strip off the leading --, to help with URL window.location.search

      commandContext = commandContext.replace(/^--/, '');
    }

    if (noHeadless === true && executeThisArgvPlease) {
      debug('setting argv', executeThisArgvPlease);
      mainWindow.executeThisArgvPlease = executeThisArgvPlease;
    }

    debug('subwindowPrefs', subwindowPrefs);

    if (subwindowPrefs && Object.keys(subwindowPrefs).length > 0) {
      mainWindow.subwindow = subwindowPrefs;
    } // and load the index.html of the app.


    const urlSpec = {
      pathname: join(root, `build/index${process.env.KUI_TEST_PARALLEL && process.env.PORT_OFFSET ? process.env.PORT_OFFSET : ''}.html`),
      protocol: 'file:',
      search: commandContext ? `?${commandContext}` : '',
      slashes: true
    };
    debug('mainWindow::loadURL', urlSpec);

    try {
      mainWindow.loadURL(require('url').format(urlSpec));
    } catch (err) {
      const errorIsNavigatedError = err.message.includes('Inspected target navigated or closed') || err.message.includes('cannot determine loading status') || err.message.includes('Inspected target navigated or closed');

      if (!process.env.TRAVIS_JOB_ID || !errorIsNavigatedError) {
        throw err;
      }
    }

    debug('install menus');

    require('./menu').install(createWindow); // Open the DevTools.
    // mainWindow.webContents.openDevTools()
    // Emitted when the window is closed.


    mainWindow.once('closed', function () {
      // Dereference the window object, usually you would store windows
      // in an array if your app supports multi windows, this is the time
      // when you should delete the corresponding element.
      nWindows--;
    }); //
    // set up ipc from renderer
    //

    const {
      ipcMain
    } = yield Promise.resolve().then(() => require('electron')); //
    // take a screenshot; note that this has to be done in the main
    // process, due to the way clipboard.writeImage is implemented on
    // Linux. on macOS, this could be done entirely in the renderer
    // process. on Linux, however, the nativeImages aren't
    // translatable between the renderer and main processes as fluidly
    // as they are on macOS. oh well! this is why the screenshot
    // plugin has to pollute main.js
    //

    debug('ipc registration');
    ipcMain.on('capture-page-to-clipboard', (event, contentsId, rect) => __awaiter(this, void 0, void 0, function* () {
      try {
        const {
          clipboard,
          nativeImage,
          webContents
        } = yield Promise.resolve().then(() => require('electron'));
        const image = yield webContents.fromId(parseInt(contentsId, 10)).capturePage(rect);

        try {
          const buf = image.toPNG();
          clipboard.writeImage(nativeImage.createFromBuffer(buf));
          event.sender.send('capture-page-to-clipboard-done', buf);
        } catch (err) {
          console.log(err);
          event.sender.send('capture-page-to-clipboard-done');
        }
      } catch (err) {
        console.log(err);
        event.sender.send('capture-page-to-clipboard-done');
      }
    })); // end of screenshot logic

    ipcMain.on('synchronous-message', (event, arg) => {
      const message = JSON.parse(arg);

      switch (message.operation) {
        case 'quit':
          app.quit();
          break;

        case 'open-graphical-shell':
          createWindow(true);
          break;

        case 'enlarge-window':
          mainWindow.setContentSize(1400, 1050, true);
          break;

        case 'reduce-window':
          mainWindow.setContentSize(1024, 768, true);
          break;

        case 'maximize-window':
          mainWindow.maximize();
          break;

        case 'unmaximize-window':
          mainWindow.unmaximize();
          break;
      }

      event.returnValue = 'ok';
    });
    ipcMain.on('/exec/invoke', (event, arg) => __awaiter(this, void 0, void 0, function* () {
      const message = JSON.parse(arg);
      const channel = `/exec/response/${message.hash}`;
      debug('invoke', message);

      try {
        const mod = yield Promise.resolve().then(() => require(`${message.module}`));
        debug('invoke got module');
        const returnValue = yield mod[message.main || 'main'](message.args);
        debug('invoke got returnValue', returnValue);
        event.sender.send(channel, JSON.stringify({
          success: true,
          returnValue
        }));
      } catch (error) {
        debug('error in exec', error);
        event.sender.send(channel, JSON.stringify({
          success: false,
          error
        }));
      }
    }));
    debug('createWindow done');
  }));
}
/**
 * Screen coordinates for popup window
 *
 */


function getPositionForPopup(screen) {
  if (screen) {
    const nWindows = _electron.BrowserWindow.getAllWindows().length;

    const {
      bounds
    } = screen.getPrimaryDisplay();
    return {
      x: bounds.width - _defaults.popupWindowDefaults.width - 50,
      y: Math.round((bounds.height - _defaults.popupWindowDefaults.height) / 4 + (nWindows - 1) * 40)
    };
  }
}

const getCommand = (argv, screen) => {
  debug('getCommand', argv);
  const dashDash = argv.lastIndexOf('--');
  argv = dashDash === -1 ? argv.slice(1) : argv.slice(dashDash + 1); // re: the -psn bit, opening Kui from macOS Finder adds additional argv -psn; see: https://github.com/IBM/kui/issues/382

  argv = argv.filter(_ => _ !== '--ui' && _ !== '--no-color' && !_.match(/^-psn/)); // re: argv.length === 0, this should happen for double-click launches

  const isShell = !process.env.KUI_POPUP && (argv.length === 0 || argv.find(_ => _ === 'shell') || process.env.RUNNING_SHELL_TEST && !process.env.KUI_TEE_TO_FILE);
  debug('isShell', argv, isShell);
  let subwindowPlease = true;
  let subwindowPrefs = {
    fullscreen: true,
    width: _defaults.default.width,
    height: _defaults.default.height
  };

  if (isShell) {
    // use a full window for 'shell'
    argv = ['shell'];
    subwindowPlease = false;
    subwindowPrefs = {};
  } else if (process.env.KUI_POPUP) {
    argv = JSON.parse(process.env.KUI_POPUP);
  }

  if (process.env.KUI_POPUP_WINDOW_RESIZE) {
    subwindowPrefs = {
      fullscreen: true,
      position: () => __awaiter(void 0, void 0, void 0, function* () {
        return getPositionForPopup(yield screen());
      }),
      width: _defaults.popupWindowDefaults.width,
      height: _defaults.popupWindowDefaults.height
    };
  }

  debug('using args', argv, subwindowPrefs);
  return {
    argv,
    subwindowPlease,
    subwindowPrefs
  };
};
/**
 * Spawn electron
 *
 */


exports.getCommand = getCommand;

function initElectron(command = [], {
  isRunningHeadless = false
} = {}, subwindowPlease, subwindowPrefs) {
  return __awaiter(this, void 0, void 0, function* () {
    debug('initElectron', command, subwindowPlease, subwindowPrefs);

    if (!app) {
      debug('loading electron');
      const Electron = yield Promise.resolve().then(() => require('electron'));
      app = Electron.app;

      if (!app) {
        // then we're still in pure headless mode; we'll need to fork ourselves to spawn electron
        const path = yield Promise.resolve().then(() => require('path'));
        const {
          spawn
        } = yield Promise.resolve().then(() => require('child_process'));
        const appHome = path.resolve(path.join(__dirname, 'main'));
        const args = [appHome, '--', ...command];
        debug('spawning electron', appHome, args); // pass through any window options, originating from the command's usage model, on to the subprocess

        const windowOptions = {};

        if (subwindowPlease) {
          debug('passing through subwindowPlease', subwindowPlease);
          windowOptions['subwindowPlease'] = subwindowPlease.toString();
        }

        if (subwindowPrefs && Object.keys(subwindowPrefs).length > 0) {
          debug('passing through subwindowPrefs', subwindowPrefs);
          windowOptions['subwindowPrefs'] = JSON.stringify(subwindowPrefs);
        } // note how we ignore the subprocess's stdio if debug mode
        // is not enabled this allows you (as a developer) to
        // debug issues with spawning the subprocess by passing
        // DEBUG=* or DEBUG=main


        const env = Object.assign({}, process.env, windowOptions);
        delete env.KUI_HEADLESS;
        const child = spawn(Electron.toString(), args, {
          stdio: debug.enabled ? 'inherit' : 'ignore',
          env,
          detached: true // needed on windows to separate this process into its own process group

        });

        if (!debug.enabled) {
          // as with the "ignore stdio" comment immediately
          // above: unless we're in DEBUG mode, let's disown
          // ("unref" in nodejs terms) the subprocess
          child.unref();
        }

        debug('spawning electron done, this process will soon exit');
        process.exit(0);
      } else {
        debug('loading electron done');
      }
    } // deal with multiple processes


    if (!process.env.RUNNING_SHELL_TEST) {
      app.on('second-instance', (event, commandLine) => {
        // Someone tried to run a second instance, open a new window
        // to handle it
        const {
          argv,
          subwindowPlease,
          subwindowPrefs
        } = getCommand(commandLine, () => __awaiter(this, void 0, void 0, function* () {
          return (yield Promise.resolve().then(() => require('electron'))).screen;
        }));
        debug('opening window for second instance', commandLine, subwindowPlease, subwindowPrefs);
        createWindow(true, argv, subwindowPlease, subwindowPrefs);
      });

      if (!app.requestSingleInstanceLock()) {
        // The primary instance of app failed to optain the lock, which means another instance of app is already running with the lock
        debug('exiting, since we are not the first instance');
        return app.exit(0);
      }
    } // This method will be called when Electron has finished
    // initialization and is ready to create browser windows.
    // Some APIs can only be used after this event occurs.


    app.once('ready', () => {
      debug('opening primary window', command);
      createWindow(true, command.length > 0 && command, subwindowPlease, subwindowPrefs);
    }); // Quit when all windows are closed.

    app.on('window-all-closed', function () {
      // On OS X it is common for applications and their menu bar
      // to stay active until the user quits explicitly with Cmd + Q
      if (process.platform !== 'darwin' || isRunningHeadless) {
        // if we're running headless, then quit on window closed, no matter which platform we're on
        app.quit();
      } else {
        app.hide();
      }
    });
    app.on('activate', function () {
      // On OS X it's common to re-create a window in the app when the
      // dock icon is clicked and there are no other windows open.
      if (nWindows === 0) {
        createWindow();
      }
    });
  });
}
/* initElectron */