mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-15 23:36:33 +00:00
Aligned the electron app to the latest Theia APIs.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
def93ea32f
commit
e755a1cd7e
@ -113,6 +113,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frontend": "lib/browser/boards/quick-open/boards-quick-open-module"
|
"frontend": "lib/browser/boards/quick-open/boards-quick-open-module"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"electronMain": "lib/electron-main/arduino-electron-main-module"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
// The version to use.
|
||||||
|
const version = '1.9.0';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
@ -10,14 +13,21 @@
|
|||||||
const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
|
const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
|
||||||
if (shell.mkdir('-p', repository).code !== 0) {
|
if (shell.mkdir('-p', repository).code !== 0) {
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shell.exec(`git clone https://github.com/arduino/arduino.git --depth 1 ${repository}`).code !== 0) {
|
if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) {
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const destination = path.join(__dirname, '..', 'Examples');
|
const destination = path.join(__dirname, '..', 'Examples');
|
||||||
shell.mkdir('-p', destination);
|
shell.mkdir('-p', destination);
|
||||||
shell.cp('-fR', path.join(repository, 'build', 'shared', 'examples', '*'), destination);
|
shell.cp('-fR', path.join(repository, 'examples', '*'), destination);
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { inject, injectable, interfaces } from 'inversify';
|
import { inject, injectable, interfaces } from 'inversify';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { ILogger } from '@theia/core/lib/common/logger';
|
import { ILogger } from '@theia/core/lib/common/logger';
|
||||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
@ -59,8 +59,8 @@ export abstract class Contribution implements CommandContribution, MenuContribut
|
|||||||
@injectable()
|
@injectable()
|
||||||
export abstract class SketchContribution extends Contribution {
|
export abstract class SketchContribution extends Contribution {
|
||||||
|
|
||||||
@inject(FileSystem)
|
@inject(FileService)
|
||||||
protected readonly fileSystem: FileSystem;
|
protected readonly fileService: FileService;
|
||||||
|
|
||||||
@inject(FileSystemExt)
|
@inject(FileSystemExt)
|
||||||
protected readonly fileSystemExt: FileSystemExt;
|
protected readonly fileSystemExt: FileSystemExt;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry } from './contribution';
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry } from './contribution';
|
||||||
|
|
||||||
@ -30,9 +31,9 @@ export class OpenSketchExternal extends SketchContribution {
|
|||||||
protected async openExternal(): Promise<void> {
|
protected async openExternal(): Promise<void> {
|
||||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
const uri = await this.sketchServiceClient.currentSketchFile();
|
||||||
if (uri) {
|
if (uri) {
|
||||||
const exists = this.fileSystem.exists(uri);
|
const exists = this.fileService.exists(new URI(uri));
|
||||||
if (exists) {
|
if (exists) {
|
||||||
const fsPath = await this.fileSystem.getFsPath(uri);
|
const fsPath = await this.fileService.fsPath(new URI(uri));
|
||||||
if (fsPath) {
|
if (fsPath) {
|
||||||
remote.shell.showItemInFolder(fsPath);
|
remote.shell.showItemInFolder(fsPath);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ export class OpenSketch extends SketchContribution {
|
|||||||
|
|
||||||
protected async selectSketch(): Promise<Sketch | undefined> {
|
protected async selectSketch(): Promise<Sketch | undefined> {
|
||||||
const config = await this.configService.getConfiguration();
|
const config = await this.configService.getConfiguration();
|
||||||
const defaultPath = await this.fileSystem.getFsPath(config.sketchDirUri);
|
const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri));
|
||||||
const { filePaths } = await remote.dialog.showOpenDialog({
|
const { filePaths } = await remote.dialog.showOpenDialog({
|
||||||
defaultPath,
|
defaultPath,
|
||||||
properties: ['createDirectory', 'openFile'],
|
properties: ['createDirectory', 'openFile'],
|
||||||
@ -149,7 +149,7 @@ export class OpenSketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
if (response === 1) { // OK
|
if (response === 1) { // OK
|
||||||
const newSketchUri = new URI(sketchFileUri).parent.resolve(name);
|
const newSketchUri = new URI(sketchFileUri).parent.resolve(name);
|
||||||
const exists = await this.fileSystem.exists(newSketchUri.toString());
|
const exists = await this.fileService.exists(newSketchUri);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
await remote.dialog.showMessageBox({
|
await remote.dialog.showMessageBox({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
@ -158,8 +158,8 @@ export class OpenSketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
await this.fileSystem.createFolder(newSketchUri.toString());
|
await this.fileService.createFolder(newSketchUri);
|
||||||
await this.fileSystem.move(sketchFileUri, newSketchUri.resolve(nameWithExt).toString());
|
await this.fileService.move(new URI(sketchFileUri), new URI(newSketchUri.resolve(nameWithExt).toString()));
|
||||||
return this.sketchService.getSketchFolder(newSketchUri.toString());
|
return this.sketchService.getSketchFolder(newSketchUri.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
// If target does not exist, propose a `directories.user`/${sketch.name} path
|
// If target does not exist, propose a `directories.user`/${sketch.name} path
|
||||||
// If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss}
|
// If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss}
|
||||||
const sketchDirUri = new URI((await this.configService.getConfiguration()).sketchDirUri);
|
const sketchDirUri = new URI((await this.configService.getConfiguration()).sketchDirUri);
|
||||||
const exists = await this.fileSystem.exists(sketchDirUri.resolve(sketch.name).toString());
|
const exists = await this.fileService.exists(sketchDirUri.resolve(sketch.name));
|
||||||
const defaultUri = exists
|
const defaultUri = exists
|
||||||
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
|
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
|
||||||
: sketchDirUri.resolve(sketch.name);
|
: sketchDirUri.resolve(sketch.name);
|
||||||
const defaultPath = await this.fileSystem.getFsPath(defaultUri.toString())!;
|
const defaultPath = await this.fileService.fsPath(defaultUri);
|
||||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||||
if (!filePath || canceled) {
|
if (!filePath || canceled) {
|
||||||
return false;
|
return false;
|
||||||
@ -61,7 +61,7 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
||||||
if (workspaceUri && openAfterMove) {
|
if (workspaceUri && openAfterMove) {
|
||||||
if (wipeOriginal) {
|
if (wipeOriginal) {
|
||||||
await this.fileSystem.delete(sketch.uri);
|
await this.fileService.delete(new URI(sketch.uri));
|
||||||
}
|
}
|
||||||
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { ContainerModule } from 'inversify';
|
||||||
|
import { ElectronMainApplication as TheiaElectronMainApplication } from '@theia/core/lib/electron-main/electron-main-application';
|
||||||
|
import { ElectronMainApplication } from './theia/electron-main-application';
|
||||||
|
|
||||||
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
|
bind(ElectronMainApplication).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaElectronMainApplication).toService(ElectronMainApplication);
|
||||||
|
});
|
@ -0,0 +1,76 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { app } from 'electron';
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
import { AddressInfo } from 'net';
|
||||||
|
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
|
||||||
|
import { ElectronMainApplication as TheiaElectronMainApplication, TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/electron-main-application';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||||
|
|
||||||
|
protected async getDefaultBrowserWindowOptions(): Promise<TheiaBrowserWindowOptions> {
|
||||||
|
const options = await super.getDefaultBrowserWindowOptions();
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
// Set and use a custom minimum window size: https://github.com/arduino/arduino-pro-ide/issues/337#issuecomment-687017281
|
||||||
|
minWidth: 900,
|
||||||
|
minHeight: 800
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async startBackend(): Promise<number> {
|
||||||
|
// Check if we should run everything as one process.
|
||||||
|
const noBackendFork = process.argv.indexOf('--no-cluster') !== -1;
|
||||||
|
// 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 = this.globals.THEIA_APP_PROJECT_PATH;
|
||||||
|
// 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.
|
||||||
|
process.env.THEIA_ELECTRON_VERSION = process.versions.electron;
|
||||||
|
if (noBackendFork) {
|
||||||
|
process.env[ElectronSecurityToken] = JSON.stringify(this.electronSecurityToken);
|
||||||
|
// The backend server main file is supposed to export a promise resolving with the port used by the http(s) server.
|
||||||
|
const address: AddressInfo = await require(this.globals.THEIA_BACKEND_MAIN_PATH);
|
||||||
|
return address.port;
|
||||||
|
} else {
|
||||||
|
let args = this.processArgv.getProcessArgvWithoutBin();
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/8227
|
||||||
|
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 backendProcess = fork(
|
||||||
|
this.globals.THEIA_BACKEND_MAIN_PATH,
|
||||||
|
args,
|
||||||
|
await this.getForkOptions(),
|
||||||
|
);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// The backend server main file is also supposed to send the resolved http(s) server port via IPC.
|
||||||
|
backendProcess.on('message', (address: AddressInfo) => {
|
||||||
|
resolve(address.port);
|
||||||
|
});
|
||||||
|
backendProcess.on('error', error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
app.on('quit', () => {
|
||||||
|
try {
|
||||||
|
// 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(backendProcess.pid);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ESRCH') {
|
||||||
|
console.log('Could not terminate the backend process. It was not running.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,8 +6,8 @@ import { ncp } from 'ncp';
|
|||||||
import { Stats } from 'fs';
|
import { Stats } from 'fs';
|
||||||
import * as fs from './fs-extra';
|
import * as fs from './fs-extra';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { FileUri } from '@theia/core/lib/node';
|
||||||
import { isWindows } from '@theia/core/lib/common/os';
|
import { isWindows } from '@theia/core/lib/common/os';
|
||||||
import { FileUri, BackendApplicationContribution } from '@theia/core/lib/node';
|
|
||||||
import { ConfigService } from '../common/protocol/config-service';
|
import { ConfigService } from '../common/protocol/config-service';
|
||||||
import { SketchesService, Sketch } from '../common/protocol/sketches-service';
|
import { SketchesService, Sketch } from '../common/protocol/sketches-service';
|
||||||
import { firstToLowerCase } from '../common/utils';
|
import { firstToLowerCase } from '../common/utils';
|
||||||
@ -23,17 +23,11 @@ const prefix = '.arduinoProIDE-unsaved';
|
|||||||
|
|
||||||
// TODO: `fs`: use async API
|
// TODO: `fs`: use async API
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SketchesServiceImpl implements SketchesService, BackendApplicationContribution {
|
export class SketchesServiceImpl implements SketchesService {
|
||||||
|
|
||||||
protected readonly temp = temp.track();
|
|
||||||
|
|
||||||
@inject(ConfigService)
|
@inject(ConfigService)
|
||||||
protected readonly configService: ConfigService;
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
onStop(): void {
|
|
||||||
this.temp.cleanupSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSketches(uri?: string): Promise<Sketch[]> {
|
async getSketches(uri?: string): Promise<Sketch[]> {
|
||||||
const sketches: Array<Sketch & { mtimeMs: number }> = [];
|
const sketches: Array<Sketch & { mtimeMs: number }> = [];
|
||||||
let fsPath: undefined | string;
|
let fsPath: undefined | string;
|
||||||
@ -210,7 +204,7 @@ export class SketchesServiceImpl implements SketchesService, BackendApplicationC
|
|||||||
async cloneExample(uri: string): Promise<Sketch> {
|
async cloneExample(uri: string): Promise<Sketch> {
|
||||||
const sketch = await this.loadSketch(uri);
|
const sketch = await this.loadSketch(uri);
|
||||||
const parentPath = await new Promise<string>((resolve, reject) => {
|
const parentPath = await new Promise<string>((resolve, reject) => {
|
||||||
this.temp.mkdir({ prefix }, (err, dirPath) => {
|
temp.mkdir({ prefix }, (err, dirPath) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
@ -277,7 +271,7 @@ export class SketchesServiceImpl implements SketchesService, BackendApplicationC
|
|||||||
const monthNames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
const monthNames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const parentPath = await new Promise<string>((resolve, reject) => {
|
const parentPath = await new Promise<string>((resolve, reject) => {
|
||||||
this.temp.mkdir({ prefix }, (err, dirPath) => {
|
temp.mkdir({ prefix }, (err, dirPath) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
@ -373,6 +367,11 @@ void loop() {
|
|||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new Error(`Sketch does not exist: ${sketch}`);
|
throw new Error(`Sketch does not exist: ${sketch}`);
|
||||||
}
|
}
|
||||||
|
// Nothing to do when source and destination are the same.
|
||||||
|
if (sketch.uri === destinationUri) {
|
||||||
|
await this.loadSketch(sketch.uri); // Sanity check.
|
||||||
|
return sketch.uri;
|
||||||
|
}
|
||||||
const destination = FileUri.fsPath(destinationUri);
|
const destination = FileUri.fsPath(destinationUri);
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
ncp.ncp(source, destination, async error => {
|
ncp.ncp(source, destination, async error => {
|
||||||
@ -384,9 +383,7 @@ void loop() {
|
|||||||
try {
|
try {
|
||||||
const oldPath = path.join(destination, new URI(sketch.mainFileUri).path.base);
|
const oldPath = path.join(destination, new URI(sketch.mainFileUri).path.base);
|
||||||
const newPath = path.join(destination, `${newName}.ino`);
|
const newPath = path.join(destination, `${newName}.ino`);
|
||||||
if (oldPath !== newPath) {
|
await fs.rename(oldPath, newPath);
|
||||||
await fs.rename(oldPath, newPath);
|
|
||||||
}
|
|
||||||
await this.loadSketch(destinationUri); // Sanity check.
|
await this.loadSketch(destinationUri); // Sanity check.
|
||||||
resolve();
|
resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1,301 +0,0 @@
|
|||||||
// @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');
|
|
||||||
|
|
||||||
// Fix the window reloading issue, see: https://github.com/electron/electron/issues/22119
|
|
||||||
app.allowRendererProcessReuse = false;
|
|
||||||
|
|
||||||
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,
|
|
||||||
// Set and use a custom minimum window size: https://github.com/arduino/arduino-pro-ide/issues/337#issuecomment-687017281
|
|
||||||
minWidth: 900,
|
|
||||||
minHeight: 800,
|
|
||||||
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
|
|
||||||
try {
|
|
||||||
process.kill(cp.pid);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'ESRCH') {
|
|
||||||
console.log('Could not terminate the backend process. It was not running.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -22,7 +22,7 @@
|
|||||||
"package": "cross-env DEBUG=* && electron-builder --publish=never",
|
"package": "cross-env DEBUG=* && electron-builder --publish=never",
|
||||||
"package:publish": "cross-env DEBUG=* && electron-builder --publish=always",
|
"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 && ncp ./patch/main.js ./src-gen/backend/main.js"
|
"patch": "ncp ./patch/main.js ./src-gen/backend/main.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.14.1 <13"
|
"node": ">=12.14.1 <13"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user