diff --git a/packages/core/src/browser/http-open-handler.ts b/packages/core/src/browser/http-open-handler.ts index e31b1f9bdc89d..d8f467a549372 100644 --- a/packages/core/src/browser/http-open-handler.ts +++ b/packages/core/src/browser/http-open-handler.ts @@ -27,6 +27,8 @@ export interface HttpOpenHandlerOptions { @injectable() export class HttpOpenHandler implements OpenHandler { + static readonly PRIORITY: number = 500; + readonly id = 'http'; @inject(WindowService) @@ -36,7 +38,7 @@ export class HttpOpenHandler implements OpenHandler { protected readonly externalUriService: ExternalUriService; canHandle(uri: URI, options?: HttpOpenHandlerOptions): number { - return ((options && options.openExternal) || uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? 500 : 0; + return ((options && options.openExternal) || uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? HttpOpenHandler.PRIORITY : 0; } async open(uri: URI): Promise { diff --git a/packages/core/src/electron-browser/preload.ts b/packages/core/src/electron-browser/preload.ts index b0903d1a764af..78d0d29a93f3c 100644 --- a/packages/core/src/electron-browser/preload.ts +++ b/packages/core/src/electron-browser/preload.ts @@ -79,8 +79,8 @@ const api: TheiaCoreAPI = { showItemInFolder: fsPath => { ipcRenderer.send(CHANNEL_SHOW_ITEM_IN_FOLDER, fsPath); }, - openWithSystemApp: fsPath => { - ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, fsPath); + openWithSystemApp: location => { + ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, location); }, attachSecurityToken: (endpoint: string) => ipcRenderer.invoke(CHANNEL_ATTACH_SECURITY_TOKEN, endpoint), diff --git a/packages/core/src/electron-browser/window/electron-window-module.ts b/packages/core/src/electron-browser/window/electron-window-module.ts index b50e21267b4cd..78f490c1c98db 100644 --- a/packages/core/src/electron-browser/window/electron-window-module.ts +++ b/packages/core/src/electron-browser/window/electron-window-module.ts @@ -15,18 +15,20 @@ // ***************************************************************************** import { ContainerModule } from 'inversify'; -import { WindowService } from '../../browser/window/window-service'; -import { ElectronWindowService } from './electron-window-service'; -import { FrontendApplicationContribution } from '../../browser/frontend-application-contribution'; -import { ElectronClipboardService } from '../electron-clipboard-service'; +import { OpenHandler } from '../../browser'; import { ClipboardService } from '../../browser/clipboard-service'; +import { FrontendApplicationContribution } from '../../browser/frontend-application-contribution'; +import { FrontendApplicationStateService } from '../../browser/frontend-application-state'; +import { SecondaryWindowService } from '../../browser/window/secondary-window-service'; +import { WindowService } from '../../browser/window/window-service'; import { ElectronMainWindowService, electronMainWindowServicePath } from '../../electron-common/electron-main-window-service'; +import { ElectronClipboardService } from '../electron-clipboard-service'; import { ElectronIpcConnectionProvider } from '../messaging/electron-ipc-connection-source'; -import { bindWindowPreferences } from './electron-window-preferences'; -import { FrontendApplicationStateService } from '../../browser/frontend-application-state'; import { ElectronFrontendApplicationStateService } from './electron-frontend-application-state'; import { ElectronSecondaryWindowService } from './electron-secondary-window-service'; -import { SecondaryWindowService } from '../../browser/window/secondary-window-service'; +import { bindWindowPreferences } from './electron-window-preferences'; +import { ElectronWindowService } from './electron-window-service'; +import { ExternalAppOpenHandler } from './external-app-open-handler'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ElectronMainWindowService).toDynamicValue(context => @@ -38,4 +40,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ClipboardService).to(ElectronClipboardService).inSingletonScope(); rebind(FrontendApplicationStateService).to(ElectronFrontendApplicationStateService).inSingletonScope(); bind(SecondaryWindowService).to(ElectronSecondaryWindowService).inSingletonScope(); + bind(ExternalAppOpenHandler).toSelf().inSingletonScope(); + bind(OpenHandler).toService(ExternalAppOpenHandler); }); diff --git a/packages/core/src/electron-browser/window/external-app-open-handler.ts b/packages/core/src/electron-browser/window/external-app-open-handler.ts new file mode 100644 index 0000000000000..d74c3a2378e1d --- /dev/null +++ b/packages/core/src/electron-browser/window/external-app-open-handler.ts @@ -0,0 +1,42 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { injectable } from 'inversify'; +import { OpenHandler } from '../../browser/opener-service'; +import URI from '../../common/uri'; +import { HttpOpenHandler } from '../../browser/http-open-handler'; + +export interface ExternalAppOpenHandlerOptions { + openExternalApp?: boolean +} + +@injectable() +export class ExternalAppOpenHandler implements OpenHandler { + + static readonly PRIORITY: number = HttpOpenHandler.PRIORITY + 100; + readonly id = 'external-app'; + + canHandle(uri: URI, options?: ExternalAppOpenHandlerOptions): number { + return (options && options.openExternalApp) ? ExternalAppOpenHandler.PRIORITY : -1; + } + + async open(uri: URI): Promise { + // For files 'file:' scheme, system accepts only the path. + // For other protocols e.g. 'vscode:' we use the full URI to propagate target app information. + window.electronTheiaCore.openWithSystemApp(uri.scheme === 'file' ? uri.path.fsPath() : uri.toString(true)); + return undefined; + } +} diff --git a/packages/core/src/electron-common/electron-api.ts b/packages/core/src/electron-common/electron-api.ts index 6bcde6a4fb333..833b44d6070e2 100644 --- a/packages/core/src/electron-common/electron-api.ts +++ b/packages/core/src/electron-common/electron-api.ts @@ -56,7 +56,11 @@ export interface TheiaCoreAPI { focusWindow(name?: string): void; showItemInFolder(fsPath: string): void; - openWithSystemApp(fsPath: string): void; + + /** + * @param location The location to open with the system app. This can be a file path or a URL. + */ + openWithSystemApp(location: string): void; getTitleBarStyleAtStartup(): Promise; setTitleBarStyle(style: string): void; diff --git a/packages/plugin-ext/src/main/browser/window-state-main.ts b/packages/plugin-ext/src/main/browser/window-state-main.ts index 1a48ea1f50e56..64e9439cf2e37 100644 --- a/packages/plugin-ext/src/main/browser/window-state-main.ts +++ b/packages/plugin-ext/src/main/browser/window-state-main.ts @@ -69,7 +69,7 @@ export class WindowStateMain implements WindowMain, Disposable { const uri = URI.revive(uriComponent); const url = new CoreURI(encodeURI(uri.toString(true))); try { - await open(this.openerService, url); + await open(this.openerService, url, { openExternalApp: true }); return true; } catch (e) { return false;