diff --git a/src/vs/base/parts/ipc/common/ipc.electron.ts b/src/vs/base/parts/ipc/common/ipc.electron.ts index 5712b4082e6..4d27c87d1d3 100644 --- a/src/vs/base/parts/ipc/common/ipc.electron.ts +++ b/src/vs/base/parts/ipc/common/ipc.electron.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { Server as IPCServer, Client as IPCClient, IServer, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -12,24 +11,26 @@ const Hello = 'ipc:hello'; const Goodbye = 'ipc:goodbye'; const Message = 'ipc:message'; -export interface IPC extends NodeJS.EventEmitter { +export interface Sender { send(channel: string, ...args: any[]): void; } +export interface IPC extends Sender, NodeJS.EventEmitter {} + class Protocol implements IMessagePassingProtocol { private listener: IDisposable; - constructor(private ipc: IPC) {} + constructor(private sender: Sender, private receiver: NodeJS.EventEmitter) {} send(message: any): void { - this.ipc.send(Message, message); + this.sender.send(Message, message); } onMessage(callback: (message: any) => void): void { const cb = (_, m) => callback(m); - this.ipc.on(Message, cb); - this.listener = toDisposable(() => this.ipc.removeListener(Message, cb)); + this.receiver.on(Message, cb); + this.listener = toDisposable(() => this.receiver.removeListener(Message, cb)); } dispose(): void { @@ -41,11 +42,11 @@ export class Server implements IServer, IDisposable { private channels: { [name: string]: IChannel }; - constructor(ipc: IPC) { + constructor(ipc: NodeJS.EventEmitter) { this.channels = Object.create(null); ipc.on(Hello, ({ sender }) => { - const protocol = new Protocol(sender); + const protocol = new Protocol(sender, ipc); const ipcServer = new IPCServer(protocol); Object.keys(this.channels) @@ -55,8 +56,6 @@ export class Server implements IServer, IDisposable { ipcServer.dispose(); protocol.dispose(); }); - - sender.send(Hello); }); } @@ -75,7 +74,8 @@ export class Client implements IClient, IDisposable { private ipcClient: IPCClient; constructor(private ipc: IPC) { - this.protocol = new Protocol(ipc); + ipc.send(Hello); + this.protocol = new Protocol(ipc, ipc); this.ipcClient = new IPCClient(this.protocol); } @@ -87,16 +87,4 @@ export class Client implements IClient, IDisposable { this.ipc.send(Goodbye); this.protocol = dispose(this.protocol); } -} - -export function connect(ipc: IPC): TPromise { - return new TPromise((c, e) => { - ipc.once(Hello, () => { - ipc.removeListener('error', e); - c(new Client(ipc)); - }); - - ipc.once('error', e); - ipc.send(Hello); - }); } \ No newline at end of file diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index e23a8478ba2..c837d853050 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -16,6 +16,7 @@ import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifec import { VSCodeMenu } from 'vs/code/electron-main/menus'; import { ISettingsService, SettingsManager } from 'vs/code/electron-main/settings'; import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager'; +import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/common/ipc.electron'; import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { TPromise } from 'vs/base/common/winjs.base'; import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc'; @@ -31,6 +32,8 @@ import { ILogService, MainLogService } from 'vs/code/electron-main/log'; import { IStorageService, StorageService } from 'vs/code/electron-main/storage'; import * as cp from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; +import { URLChannel } from 'vs/platform/url/common/urlIpc'; +import { URLService } from 'vs/platform/url/electron-main/urlService'; function quit(accessor: ServicesAccessor, error?: Error); function quit(accessor: ServicesAccessor, message?: string); @@ -52,7 +55,7 @@ function quit(accessor: ServicesAccessor, arg?: any) { process.exit(exitCode); // in main, process.exit === app.exit } -function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEnvironment): void { +function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProcessEnvironment): void { const instantiationService = accessor.get(IInstantiationService); const logService = accessor.get(ILogService); const envService = accessor.get(IEnvironmentService); @@ -93,14 +96,22 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn // noop } - // Register IPC services + // Register Main IPC services const launchService = instantiationService.createInstance(LaunchService); const launchChannel = new LaunchChannel(launchService); - ipcServer.registerChannel('launch', launchChannel); + mainIpcServer.registerChannel('launch', launchChannel); const askpassService = new GitAskpassService(); const askpassChannel = new AskpassChannel(askpassService); - ipcServer.registerChannel('askpass', askpassChannel); + mainIpcServer.registerChannel('askpass', askpassChannel); + + // Create Electron IPC Server + const electronIpcServer = new ElectronIPCServer(ipc); + + // Register Electron IPC services + const urlService = instantiationService.createInstance(URLService); + const urlChannel = new URLChannel(urlService); + electronIpcServer.registerChannel('url', urlChannel); // Used by sub processes to communicate back to the main instance process.env['VSCODE_PID'] = '' + process.pid; @@ -125,9 +136,9 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn global.programStart = envService.cliArgs.programStart; function dispose() { - if (ipcServer) { - ipcServer.dispose(); - ipcServer = null; + if (mainIpcServer) { + mainIpcServer.dispose(); + mainIpcServer = null; } sharedProcess.dispose(); @@ -144,12 +155,6 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn dispose(); }); - app.setAsDefaultProtocolClient('vscode'); - - app.on('open-url', url => { - console.log(url); - }); - // Dispose on vscode:exit ipc.on('vscode:exit', (event, code: number) => { logService.log('IPC#vscode:exit', code); @@ -356,6 +361,6 @@ getUserEnvironment() return instantiationService.invokeFunction(a => a.get(IEnvironmentService).createPaths()) .then(() => instantiationService.invokeFunction(setupIPC)) - .then(ipcServer => instantiationService.invokeFunction(main, ipcServer, userEnv)); + .then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, userEnv)); }) .done(null, err => instantiationService.invokeFunction(quit, err)); \ No newline at end of file diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts index a3ef9cf051a..7caa5dbda95 100644 --- a/src/vs/platform/url/common/url.ts +++ b/src/vs/platform/url/common/url.ts @@ -12,5 +12,6 @@ export const ID = 'urlService'; export const IURLService = createDecorator(ID); export interface IURLService { - onOpenUrl: Event; + _serviceBrand: any; + onOpenURL: Event; } diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 2000711f430..a00c7c1c5ec 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -3,4 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; \ No newline at end of file +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IURLService } from './url'; +import Event from 'vs/base/common/event'; + +export interface IURLChannel extends IChannel { + call(command: 'event:onOpenURL'): TPromise; + call(command: string, arg: any): TPromise; +} + +export class URLChannel implements IURLChannel { + + constructor(private service: IURLService) { } + + call(command: string, arg: any): TPromise { + switch (command) { + case 'event:onOpenURL': return eventToCall(this.service.onOpenURL); + } + } +} + +export class URLChannelClient implements IURLService { + + _serviceBrand: any; + + constructor(private channel: IChannel) { } + + private _onOpenURL = eventFromCall(this.channel, 'event:onOpenURL'); + get onOpenURL(): Event { return this._onOpenURL; } +} \ No newline at end of file diff --git a/src/vs/platform/url/electron-main/urlService.ts b/src/vs/platform/url/electron-main/urlService.ts new file mode 100644 index 00000000000..6db88e185a1 --- /dev/null +++ b/src/vs/platform/url/electron-main/urlService.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import Event, {Emitter} from 'vs/base/common/event'; +import {IDisposable, dispose, toDisposable} from 'vs/base/common/lifecycle'; +import {IURLService} from 'vs/platform/url/common/url'; +import {app} from 'electron'; + +export class URLService implements IURLService, IDisposable { + + _serviceBrand: any; + + private _onOpenURL = new Emitter(); + onOpenURL: Event = this._onOpenURL.event; + private disposables: IDisposable[] = []; + + constructor() { + const handler = (e: Electron.Event, url: string) => { + e.preventDefault(); + this._onOpenURL.fire(url); + }; + + app.on('open-url', handler); + this.disposables.push(toDisposable(() => app.removeListener('open-url', handler))); + + // app.setAsDefaultProtocolClient('vscode'); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/platform/url/node/urlService.ts b/src/vs/platform/url/node/urlService.ts deleted file mode 100644 index ce3dce08007..00000000000 --- a/src/vs/platform/url/node/urlService.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import Event, {Emitter} from 'vs/base/common/event'; -import {IURLService} from 'vs/platform/url/common/url'; - -export class URLService implements IURLService { - - private _onOpenUrl = new Emitter(); - onOpenUrl: Event = this._onOpenUrl.event; - - constructor() { - - } -} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 9897ba3accb..7601de4a0b9 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -70,9 +70,13 @@ import {CrashReporter} from 'vs/workbench/electron-browser/crashReporter'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; import {ThemeService} from 'vs/workbench/services/themes/electron-browser/themeService'; import {getDelayedChannel} from 'vs/base/parts/ipc/common/ipc'; -import {connect} from 'vs/base/parts/ipc/node/ipc.net'; +import {connect as connectNet} from 'vs/base/parts/ipc/node/ipc.net'; +import {Client as ElectronIPCClient} from 'vs/base/parts/ipc/common/ipc.electron'; +import {ipcRenderer} from 'electron'; import {IExtensionManagementChannel, ExtensionManagementChannelClient} from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import {IExtensionManagementService} from 'vs/platform/extensionManagement/common/extensionManagement'; +import {URLChannelClient} from 'vs/platform/url/common/urlIpc'; +import {IURLService} from 'vs/platform/url/common/url'; import {ReloadWindowAction} from 'vs/workbench/electron-browser/actions'; // self registering services @@ -202,7 +206,7 @@ export class WorkbenchShell { } private initServiceCollection(): [InstantiationService, ServiceCollection] { - const sharedProcess = connect(process.env['VSCODE_SHARED_IPC_HOOK']); + const sharedProcess = connectNet(process.env['VSCODE_SHARED_IPC_HOOK']); sharedProcess.done(service => { service.onClose(() => { this.messageService.show(Severity.Error, { @@ -212,6 +216,8 @@ export class WorkbenchShell { }); }, errors.onUnexpectedError); + const mainProcessClient = new ElectronIPCClient(ipcRenderer); + const serviceCollection = new ServiceCollection(); serviceCollection.set(IEventService, this.eventService); serviceCollection.set(IWorkspaceContextService, this.contextService); @@ -314,6 +320,10 @@ export class WorkbenchShell { const extensionManagementChannelClient = instantiationService.createInstance(ExtensionManagementChannelClient, extensionManagementChannel); serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient); + const urlChannel = mainProcessClient.getChannel('url'); + const urlChannelClient = instantiationService.createInstance(URLChannelClient, urlChannel); + serviceCollection.set(IURLService, urlChannelClient); + return [instantiationService, serviceCollection]; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts index 8233041d58e..1422643cd06 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts @@ -17,6 +17,7 @@ import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import { ipcRenderer as ipc } from 'electron'; +import { IURLService } from 'vs/platform/url/common/url'; interface IInstallExtensionsRequest { extensionsToInstall: string[]; @@ -31,7 +32,8 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { @IMessageService private messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IExtensionTipsService extenstionTips: IExtensionTipsService, // this is to eagerly start the service - @IExtensionGalleryService galleryService: IExtensionGalleryService + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IURLService urlService: IURLService ) { this.registerListeners(); @@ -40,6 +42,9 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { if (options.extensionsToInstall && options.extensionsToInstall.length) { this.install(options.extensionsToInstall).done(null, onUnexpectedError); } + + urlService.onOpenURL(url => console.log(url)); + //actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallExtensionAction, InstallExtensionAction.ID, InstallExtensionAction.LABEL), 'Extensions: Install Extension', ExtensionsLabel); }