From 5d8fbcd43770b159824639b044a20eb3d2f03782 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Thu, 23 Jul 2020 08:55:13 +0200 Subject: [PATCH] patched the electron-maindropped -psn arg on macOS.Signed-off-by: Akos Kitta --- electron/build/patch/electron-main.js | 289 ++++++++++++++++++++++++++ electron/build/template-package.json | 10 +- electron/packager/package.json | 2 +- 3 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 electron/build/patch/electron-main.js diff --git a/electron/build/patch/electron-main.js b/electron/build/patch/electron-main.js new file mode 100644 index 00000000..7d98fb0d --- /dev/null +++ b/electron/build/patch/electron-main.js @@ -0,0 +1,289 @@ +// @ts-check + +// Useful for Electron/NW.js apps as GUI apps on macOS doesn't inherit the `$PATH` define +// in your dotfiles (.bashrc/.bash_profile/.zshrc/etc). +// https://github.com/electron/electron/issues/550#issuecomment-162037357 +// https://github.com/eclipse-theia/theia/pull/3534#issuecomment-439689082 +require('fix-path')(); + +// Workaround for https://github.com/electron/electron/issues/9225. Chrome has an issue where +// in certain locales (e.g. PL), image metrics are wrongly computed. We explicitly set the +// LC_NUMERIC to prevent this from happening (selects the numeric formatting category of the +// C locale, http://en.cppreference.com/w/cpp/locale/LC_categories). +if (process.env.LC_ALL) { + process.env.LC_ALL = 'C'; +} +process.env.LC_NUMERIC = 'C'; + +const { v4 } = require('uuid'); +const electron = require('electron'); +const { join, resolve } = require('path'); +const { fork } = require('child_process'); +const { app, dialog, shell, BrowserWindow, ipcMain, Menu, globalShortcut } = electron; +const { ElectronSecurityToken } = require('@theia/core/lib/electron-common/electron-token'); + +const applicationName = `Arduino Pro IDE`; +const isSingleInstance = false; +const disallowReloadKeybinding = false; +const defaultWindowOptionsAdditions = {}; + + +if (isSingleInstance && !app.requestSingleInstanceLock()) { + // There is another instance running, exit now. The other instance will request focus. + app.quit(); + return; +} + +const nativeKeymap = require('native-keymap'); +const Storage = require('electron-store'); +const electronStore = new Storage(); + +const electronSecurityToken = { + value: v4(), +}; + +// Make it easy for renderer process to fetch the ElectronSecurityToken: +global[ElectronSecurityToken] = electronSecurityToken; + +app.on('ready', () => { + + // Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit") + // See: https://github.com/electron-userland/electron-builder/issues/2468 + app.setName(applicationName); + + const { screen } = electron; + + // Remove the default electron menus, waiting for the application to set its own. + Menu.setApplicationMenu(Menu.buildFromTemplate([{ + role: 'help', submenu: [{ role: 'toggleDevTools' }] + }])); + + function createNewWindow(theUrl) { + + // We must center by hand because `browserWindow.center()` fails on multi-screen setups + // See: https://github.com/electron/electron/issues/3490 + const { bounds } = screen.getDisplayNearestPoint(screen.getCursorScreenPoint()); + const height = Math.floor(bounds.height * (2 / 3)); + const width = Math.floor(bounds.width * (2 / 3)); + + const y = Math.floor(bounds.y + (bounds.height - height) / 2); + const x = Math.floor(bounds.x + (bounds.width - width) / 2); + + const WINDOW_STATE = 'windowstate'; + const windowState = electronStore.get(WINDOW_STATE, { + width, height, x, y + }); + + const persistedWindowOptionsAdditions = electronStore.get('windowOptions', {}); + + const windowOptionsAdditions = { + ...defaultWindowOptionsAdditions, + ...persistedWindowOptionsAdditions + }; + + let windowOptions = { + show: false, + title: applicationName, + width: windowState.width, + height: windowState.height, + minWidth: 200, + minHeight: 120, + x: windowState.x, + y: windowState.y, + isMaximized: windowState.isMaximized, + ...windowOptionsAdditions, + webPreferences: { + nodeIntegration: true + } + }; + + // Always hide the window, we will show the window when it is ready to be shown in any case. + const newWindow = new BrowserWindow(windowOptions); + if (windowOptions.isMaximized) { + newWindow.maximize(); + } + newWindow.on('ready-to-show', () => newWindow.show()); + if (disallowReloadKeybinding) { + newWindow.on('focus', event => { + for (const accelerator of ['CmdOrCtrl+R', 'F5']) { + globalShortcut.register(accelerator, () => { }); + } + }); + newWindow.on('blur', event => globalShortcut.unregisterAll()); + } + + // Prevent calls to "window.open" from opening an ElectronBrowser window, + // and rather open in the OS default web browser. + newWindow.webContents.on('new-window', (event, url) => { + event.preventDefault(); + shell.openExternal(url); + }); + + // Save the window geometry state on every change + const saveWindowState = () => { + try { + let bounds; + if (newWindow.isMaximized()) { + bounds = electronStore.get(WINDOW_STATE, {}); + } else { + bounds = newWindow.getBounds(); + } + electronStore.set(WINDOW_STATE, { + isMaximized: newWindow.isMaximized(), + width: bounds.width, + height: bounds.height, + x: bounds.x, + y: bounds.y + }); + } catch (e) { + console.error("Error while saving window state.", e); + } + }; + let delayedSaveTimeout; + const saveWindowStateDelayed = () => { + if (delayedSaveTimeout) { + clearTimeout(delayedSaveTimeout); + } + delayedSaveTimeout = setTimeout(saveWindowState, 1000); + }; + newWindow.on('close', saveWindowState); + newWindow.on('resize', saveWindowStateDelayed); + newWindow.on('move', saveWindowStateDelayed); + + // Fired when a beforeunload handler tries to prevent the page unloading + newWindow.webContents.on('will-prevent-unload', async event => { + const { response } = await dialog.showMessageBox(newWindow, { + type: 'question', + buttons: ['Yes', 'No'], + title: 'Confirm', + message: 'Are you sure you want to quit?', + detail: 'Any unsaved changes will not be saved.' + }); + if (response === 0) { // 'Yes' + // This ignores the beforeunload callback, allowing the page to unload + event.preventDefault(); + } + }); + + // Notify the renderer process on keyboard layout change + nativeKeymap.onDidChangeKeyboardLayout(() => { + if (!newWindow.isDestroyed()) { + const newLayout = { + info: nativeKeymap.getCurrentKeyboardLayout(), + mapping: nativeKeymap.getKeyMap() + }; + newWindow.webContents.send('keyboardLayoutChanged', newLayout); + } + }); + + if (!!theUrl) { + newWindow.loadURL(theUrl); + } + return newWindow; + } + + app.on('window-all-closed', () => { + app.quit(); + }); + ipcMain.on('create-new-window', (event, url) => { + createNewWindow(url); + }); + ipcMain.on('open-external', (event, url) => { + shell.openExternal(url); + }); + ipcMain.on('set-window-options', (event, options) => { + electronStore.set('windowOptions', options); + }); + ipcMain.on('get-persisted-window-options-additions', event => { + event.returnValue = electronStore.get('windowOptions', {}); + }); + + // Check whether we are in bundled application or development mode. + // @ts-ignore + const devMode = process.defaultApp || /node_modules[/]electron[/]/.test(process.execPath); + // Check if we should run everything as one process. + const noBackendFork = process.argv.includes('--no-cluster'); + const mainWindow = createNewWindow(); + + if (isSingleInstance) { + app.on('second-instance', (event, commandLine, workingDirectory) => { + // Someone tried to run a second instance, we should focus our window. + if (mainWindow && !mainWindow.isDestroyed()) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.focus() + } + }) + } + + const setElectronSecurityToken = async port => { + await electron.session.defaultSession.cookies.set({ + url: `http://localhost:${port}/`, + name: ElectronSecurityToken, + value: JSON.stringify(electronSecurityToken), + httpOnly: true + }); + }; + + const loadMainWindow = port => { + if (!mainWindow.isDestroyed()) { + mainWindow.loadURL('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port); + } + }; + + // We cannot use the `process.cwd()` as the application project path (the location of the `package.json` in other words) + // in a bundled electron application because it depends on the way we start it. For instance, on OS X, these are a differences: + // https://github.com/eclipse-theia/theia/issues/3297#issuecomment-439172274 + process.env.THEIA_APP_PROJECT_PATH = resolve(__dirname, '..', '..'); + + // Set the electron version for both the dev and the production mode. (https://github.com/eclipse-theia/theia/issues/3254) + // Otherwise, the forked backend processes will not know that they're serving the electron frontend. + // The forked backend should patch its `process.versions.electron` with this value if it is missing. + process.env.THEIA_ELECTRON_VERSION = process.versions.electron; + + const mainPath = join(__dirname, '..', 'backend', 'main'); + // We spawn a separate process for the backend for Express to not run in the Electron main process. + // See: https://github.com/eclipse-theia/theia/pull/7361#issuecomment-601272212 + // But when in debugging we want to run everything in the same process to make things easier. + if (noBackendFork) { + process.env[ElectronSecurityToken] = JSON.stringify(electronSecurityToken); + require(mainPath).then(async (address) => { + await setElectronSecurityToken(address.port); + loadMainWindow(address.port); + }).catch((error) => { + console.error(error); + app.exit(1); + }); + } else { + // We want to pass flags passed to the Electron app to the backend process. + // Quirk: When developing from sources, we execute Electron as `electron.exe electron-main.js ...args`, but when bundled, + // the command looks like `bundled-application.exe ...args`. + let args = process.argv.slice(devMode ? 2 : 1); + if (process.platform === 'darwin') { + // https://github.com/electron/electron/issues/3657 + // https://stackoverflow.com/questions/10242115/os-x-strange-psn-command-line-parameter-when-launched-from-finder#comment102377986_10242200 + // macOS appends an extra `-psn_0_someNumber` arg if a file is opened from Finder after downloading from the Internet. + // "AppName" is an app downloaded from the Internet. Are you sure you want to open it? + args = args.filter(arg => !arg.startsWith('-psn')); + } + const cp = fork(mainPath, args, { + env: Object.assign({ + [ElectronSecurityToken]: JSON.stringify(electronSecurityToken), + }, process.env) + }); + cp.on('message', async (address) => { + await setElectronSecurityToken(address.port); + loadMainWindow(address.port); + }); + cp.on('error', (error) => { + console.error(error); + app.exit(1); + }); + app.on('quit', () => { + // If we forked the process for the clusters, we need to manually terminate it. + // See: https://github.com/eclipse-theia/theia/issues/835 + process.kill(cp.pid); + }); + } +}); diff --git a/electron/build/template-package.json b/electron/build/template-package.json index c4a8b0cc..98831e9a 100644 --- a/electron/build/template-package.json +++ b/electron/build/template-package.json @@ -10,17 +10,19 @@ "electron-builder": "^22.4.1", "electron-notarize": "^0.3.0", "is-ci": "^2.0.0", + "ncp": "^2.0.0", "shelljs": "^0.8.3" }, "scripts": { - "build": "yarn download:plugins && theia build --mode development", - "build:publish": "yarn download:plugins && theia build --mode production", + "build": "yarn download:plugins && theia build --mode development && yarn patch", + "build:publish": "yarn download:plugins && theia build --mode production && yarn patch", "package": "cross-env DEBUG=* && electron-builder --publish=never", "package:publish": "cross-env DEBUG=* && electron-builder --publish=always", - "download:plugins": "theia download:plugins" + "download:plugins": "theia download:plugins", + "patch": "ncp ./patch/electron-main.js ./src-gen/frontend/electron-main.js" }, "engines": { - "node": ">=10.11.0 <12" + "node": ">=10.11.0 <13" }, "repository": { "type": "git", diff --git a/electron/packager/package.json b/electron/packager/package.json index 0193ee50..c2637786 100644 --- a/electron/packager/package.json +++ b/electron/packager/package.json @@ -31,7 +31,7 @@ "yargs": "^12.0.5" }, "engines": { - "node": ">=10.11.0 <12" + "node": ">=10.11.0 <13" }, "mocha": { "reporter": "spec",