11import { inject , injectable } from 'inversify' ;
2- import { app , BrowserWindow , BrowserWindowConstructorOptions , ipcMain , screen } from '@theia/core/electron-shared/electron' ;
2+ import { app , BrowserWindow , BrowserWindowConstructorOptions , ipcMain , screen , Event as ElectronEvent } from '@theia/core/electron-shared/electron' ;
33import { fork } from 'child_process' ;
44import { AddressInfo } from 'net' ;
5- import { join } from 'path' ;
5+ import { join , dirname } from 'path' ;
66import * as fs from 'fs-extra' ;
77import { initSplashScreen } from '../splash/splash-screen' ;
88import { MaybePromise } from '@theia/core/lib/common/types' ;
@@ -16,6 +16,8 @@ import {
1616import { SplashServiceImpl } from '../splash/splash-service-impl' ;
1717import { URI } from '@theia/core/shared/vscode-uri' ;
1818import * as electronRemoteMain from '@theia/core/electron-shared/@electron/remote/main' ;
19+ import { Deferred } from '@theia/core/lib/common/promise-util' ;
20+ import * as os from '@theia/core/lib/common/os' ;
1921
2022app . commandLine . appendSwitch ( 'disable-http-cache' ) ;
2123
@@ -36,6 +38,7 @@ const WORKSPACES = 'workspaces';
3638export class ElectronMainApplication extends TheiaElectronMainApplication {
3739 protected _windows : BrowserWindow [ ] = [ ] ;
3840 protected startup = false ;
41+ protected openFilePromise = new Deferred ( ) ;
3942
4043 @inject ( SplashServiceImpl )
4144 protected readonly splashService : SplashServiceImpl ;
@@ -45,17 +48,52 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
4548 // See: https://github.com/electron-userland/electron-builder/issues/2468
4649 // Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701
4750 app . on ( 'ready' , ( ) => app . setName ( config . applicationName ) ) ;
51+ this . attachFileAssociations ( ) ;
4852 return super . start ( config ) ;
4953 }
5054
55+ attachFileAssociations ( ) {
56+ // OSX: register open-file event
57+ if ( os . isOSX ) {
58+ app . on ( 'open-file' , async ( event , uri ) => {
59+ event . preventDefault ( ) ;
60+ if ( uri . endsWith ( '.ino' ) && await fs . pathExists ( uri ) ) {
61+ this . openFilePromise . reject ( ) ;
62+ await this . openSketch ( dirname ( uri ) ) ;
63+ }
64+ } ) ;
65+ setTimeout ( ( ) => this . openFilePromise . resolve ( ) , 500 ) ;
66+ } else {
67+ this . openFilePromise . resolve ( ) ;
68+ }
69+ }
70+
71+ protected async isValidSketchPath ( uri : string ) : Promise < boolean | undefined > {
72+ return typeof uri === 'string' && await fs . pathExists ( uri ) ;
73+ }
74+
5175 protected async launch ( params : ElectronMainExecutionParams ) : Promise < void > {
76+ try {
77+ // When running on MacOS, we either have to wait until
78+ // 1. The `open-file` command has been received by the app, rejecting the promise
79+ // 2. A short timeout resolves the promise automatically, falling back to the usual app launch
80+ await this . openFilePromise . promise ;
81+ } catch {
82+ // Application has received the `open-file` event and will skip the default application launch
83+ return ;
84+ }
85+
86+ if ( ! os . isOSX && await this . launchFromArgs ( params ) ) {
87+ // Application has received a file in its arguments and will skip the default application launch
88+ return ;
89+ }
90+
5291 this . startup = true ;
5392 const workspaces : WorkspaceOptions [ ] | undefined = this . electronStore . get ( WORKSPACES ) ;
5493 let useDefault = true ;
5594 if ( workspaces && workspaces . length > 0 ) {
5695 for ( const workspace of workspaces ) {
57- const file = workspace . file ;
58- if ( typeof file === 'string' && await fs . pathExists ( file ) ) {
96+ if ( await this . isValidSketchPath ( workspace . file ) ) {
5997 useDefault = false ;
6098 await this . openSketch ( workspace ) ;
6199 }
@@ -67,16 +105,39 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
67105 }
68106 }
69107
70- protected async openSketch ( workspace : WorkspaceOptions ) : Promise < BrowserWindow > {
108+ protected async launchFromArgs ( params : ElectronMainExecutionParams ) : Promise < boolean > {
109+ // Copy to prevent manipulation of original array
110+ const argCopy = [ ...params . argv ] ;
111+ let uri : string | undefined ;
112+ for ( const possibleUri of argCopy ) {
113+ if ( possibleUri . endsWith ( '.ino' ) && await this . isValidSketchPath ( possibleUri ) ) {
114+ uri = possibleUri ;
115+ break ;
116+ }
117+ }
118+ if ( uri ) {
119+ await this . openSketch ( dirname ( uri ) ) ;
120+ return true ;
121+ }
122+ return false ;
123+ }
124+
125+ protected async openSketch ( workspace : WorkspaceOptions | string ) : Promise < BrowserWindow > {
71126 const options = await this . getLastWindowOptions ( ) ;
72- options . x = workspace . x ;
73- options . y = workspace . y ;
74- options . width = workspace . width ;
75- options . height = workspace . height ;
76- options . isMaximized = workspace . isMaximized ;
77- options . isFullScreen = workspace . isFullScreen ;
127+ let file : string ;
128+ if ( typeof workspace === 'object' ) {
129+ options . x = workspace . x ;
130+ options . y = workspace . y ;
131+ options . width = workspace . width ;
132+ options . height = workspace . height ;
133+ options . isMaximized = workspace . isMaximized ;
134+ options . isFullScreen = workspace . isFullScreen ;
135+ file = workspace . file ;
136+ } else {
137+ file = workspace ;
138+ }
78139 const [ uri , electronWindow ] = await Promise . all ( [ this . createWindowUri ( ) , this . createWindow ( options ) ] ) ;
79- electronWindow . loadURL ( uri . withFragment ( encodeURI ( workspace . file ) ) . toString ( true ) ) ;
140+ electronWindow . loadURL ( uri . withFragment ( encodeURI ( file ) ) . toString ( true ) ) ;
80141 return electronWindow ;
81142 }
82143
@@ -101,6 +162,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
101162 } ) ;
102163 }
103164
165+ protected async onSecondInstance ( event : ElectronEvent , argv : string [ ] , cwd : string ) : Promise < void > {
166+ if ( ! os . isOSX && await this . launchFromArgs ( { cwd, argv, secondInstance : true } ) ) {
167+ // Application has received a file in its arguments
168+ return ;
169+ }
170+ super . onSecondInstance ( event , argv , cwd ) ;
171+ }
172+
104173 /**
105174 * Use this rather than creating `BrowserWindow` instances from scratch, since some security parameters need to be set, this method will do it.
106175 *
0 commit comments