@@ -2,7 +2,7 @@ import { inject, injectable } from 'inversify';
22import { app , BrowserWindow , BrowserWindowConstructorOptions , ipcMain , screen } 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,7 @@ 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' ;
1920
2021app . commandLine . appendSwitch ( 'disable-http-cache' ) ;
2122
@@ -36,6 +37,7 @@ const WORKSPACES = 'workspaces';
3637export class ElectronMainApplication extends TheiaElectronMainApplication {
3738 protected _windows : BrowserWindow [ ] = [ ] ;
3839 protected startup = false ;
40+ protected openFilePromise = new Deferred ( ) ;
3941
4042 @inject ( SplashServiceImpl )
4143 protected readonly splashService : SplashServiceImpl ;
@@ -45,17 +47,53 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
4547 // See: https://github.com/electron-userland/electron-builder/issues/2468
4648 // Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701
4749 app . on ( 'ready' , ( ) => app . setName ( config . applicationName ) ) ;
50+ this . attachFileAssociations ( ) ;
4851 return super . start ( config ) ;
4952 }
5053
54+ attachFileAssociations ( ) {
55+ // OSX: register open-file event
56+ if ( process . platform === 'darwin' ) {
57+ app . on ( 'open-file' , async ( event , uri ) => {
58+ event . preventDefault ( ) ;
59+ if ( uri . endsWith ( '.ino' ) && await fs . pathExists ( uri ) ) {
60+ this . openFilePromise . reject ( ) ;
61+ await this . openSketch ( dirname ( uri ) ) ;
62+ }
63+ } ) ;
64+ setTimeout ( ( ) => this . openFilePromise . resolve ( ) , 500 ) ;
65+ } else {
66+ this . openFilePromise . resolve ( ) ;
67+ }
68+ }
69+
70+ protected async isValidSketchPath ( uri : string ) : Promise < boolean | undefined > {
71+ return typeof uri === 'string' && await fs . pathExists ( uri ) ;
72+ }
73+
5174 protected async launch ( params : ElectronMainExecutionParams ) : Promise < void > {
5275 this . startup = true ;
76+
77+ try {
78+ // When running on MacOS, we either have to wait until
79+ // 1. The `open-file` command has been received by the app, rejecting the promise
80+ // 2. A short timeout resolves the promise automatically, falling back to the usual app launch
81+ await this . openFilePromise . promise ;
82+ } catch {
83+ // Application has received the `open-file` event and will skip the default application launch
84+ return ;
85+ }
86+
87+ if ( process . platform === 'win32' && await this . launchWindowsOpen ( ) ) {
88+ // Application has received a file in its arguments and will skip the default application launch
89+ return ;
90+ }
91+
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,46 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
67105 }
68106 }
69107
70- protected async openSketch ( workspace : WorkspaceOptions ) : Promise < BrowserWindow > {
108+ protected async launchWindowsOpen ( ) : Promise < boolean > {
109+ // Copy to prevent manipulation of original array
110+ const argCopy = [ ...process . argv ] ;
111+ if ( app . isPackaged ) {
112+ // workaround for missing executable argument when app is packaged
113+ argCopy . unshift ( 'packaged' ) ;
114+ }
115+ const possibleUris = argCopy . slice ( 2 ) || null ;
116+ if ( possibleUris ) {
117+ let uri : string | undefined ;
118+ for ( const possibleUri of possibleUris ) {
119+ if ( possibleUri . endsWith ( '.ino' ) && await this . isValidSketchPath ( possibleUri ) ) {
120+ uri = possibleUri ;
121+ break ;
122+ }
123+ }
124+ if ( uri ) {
125+ await this . openSketch ( dirname ( uri ) ) ;
126+ return true ;
127+ }
128+ }
129+ return false ;
130+ }
131+
132+ protected async openSketch ( workspace : WorkspaceOptions | string ) : Promise < BrowserWindow > {
71133 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 ;
134+ let file : string ;
135+ if ( typeof workspace === 'object' ) {
136+ options . x = workspace . x ;
137+ options . y = workspace . y ;
138+ options . width = workspace . width ;
139+ options . height = workspace . height ;
140+ options . isMaximized = workspace . isMaximized ;
141+ options . isFullScreen = workspace . isFullScreen ;
142+ file = workspace . file ;
143+ } else {
144+ file = workspace ;
145+ }
78146 const [ uri , electronWindow ] = await Promise . all ( [ this . createWindowUri ( ) , this . createWindow ( options ) ] ) ;
79- electronWindow . loadURL ( uri . withFragment ( encodeURI ( workspace . file ) ) . toString ( true ) ) ;
147+ electronWindow . loadURL ( uri . withFragment ( encodeURI ( file ) ) . toString ( true ) ) ;
80148 return electronWindow ;
81149 }
82150
0 commit comments