/*--------------------------------------------------------------------------------------------- * 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 { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { IWindowsService, OpenContext, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { shell, crashReporter, app, Menu } from 'electron'; import Event, { chain } from 'vs/base/common/event'; import { fromEventEmitter } from 'vs/base/node/event'; import { IURLService } from 'vs/platform/url/common/url'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IWindowsMainService, ISharedProcess } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export class WindowsService implements IWindowsService, IDisposable { _serviceBrand: any; private disposables: IDisposable[] = []; readonly onWindowOpen: Event = fromEventEmitter(app, 'browser-window-created', (_, w: Electron.BrowserWindow) => w.id); readonly onWindowFocus: Event = fromEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id); readonly onWindowBlur: Event = fromEventEmitter(app, 'browser-window-blur', (_, w: Electron.BrowserWindow) => w.id); constructor( private sharedProcess: ISharedProcess, @IWindowsMainService private windowsMainService: IWindowsMainService, @IEnvironmentService private environmentService: IEnvironmentService, @IURLService urlService: IURLService, @ILifecycleService private lifecycleService: ILifecycleService, @IHistoryMainService private historyService: IHistoryMainService ) { // Catch file URLs chain(urlService.onOpenURL) .filter(uri => uri.authority === 'file' && !!uri.path) .map(uri => URI.file(uri.fsPath)) .on(this.openFileForURI, this, this.disposables); // Catch extension URLs when there are no windows open chain(urlService.onOpenURL) .filter(uri => /^extension/.test(uri.path)) .filter(() => this.windowsMainService.getWindowCount() === 0) .on(this.openExtensionForURI, this, this.disposables); } pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise { this.windowsMainService.pickFileFolderAndOpen(options); return TPromise.as(null); } pickFileAndOpen(options: INativeOpenDialogOptions): TPromise { this.windowsMainService.pickFileAndOpen(options); return TPromise.as(null); } pickFolderAndOpen(options: INativeOpenDialogOptions): TPromise { this.windowsMainService.pickFolderAndOpen(options); return TPromise.as(null); } reloadWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { this.windowsMainService.reload(codeWindow); } return TPromise.as(null); } openDevTools(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.win.webContents.openDevTools(); } return TPromise.as(null); } toggleDevTools(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { const contents = codeWindow.win.webContents; if (codeWindow.hasHiddenTitleBarStyle() && !codeWindow.win.isFullScreen() && !contents.isDevToolsOpened()) { contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647 } else { contents.toggleDevTools(); } } return TPromise.as(null); } closeWorkspace(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { this.windowsMainService.closeWorkspace(codeWindow); } return TPromise.as(null); } openWorkspace(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { this.windowsMainService.openWorkspace(codeWindow); } return TPromise.as(null); } createAndOpenWorkspace(windowId: number, folders?: string[], path?: string): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { this.windowsMainService.createAndOpenWorkspace(codeWindow, folders, path); } return TPromise.as(null); } saveAndOpenWorkspace(windowId: number, path: string): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { this.windowsMainService.saveAndOpenWorkspace(codeWindow, path); } return TPromise.as(null); } toggleFullScreen(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.toggleFullScreen(); } return TPromise.as(null); } setRepresentedFilename(windowId: number, fileName: string): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.setRepresentedFilename(fileName); } return TPromise.as(null); } addRecentlyOpened(files: string[]): TPromise { this.historyService.addRecentlyOpened(void 0, files); return TPromise.as(null); } removeFromRecentlyOpened(paths: string[]): TPromise { this.historyService.removeFromRecentlyOpened(paths); return TPromise.as(null); } clearRecentlyOpened(): TPromise { this.historyService.clearRecentlyOpened(); return TPromise.as(null); } getRecentlyOpened(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { return TPromise.as(this.historyService.getRecentlyOpened(codeWindow.config.workspace || codeWindow.config.folderPath, codeWindow.config.filesToOpen)); } return TPromise.as(this.historyService.getRecentlyOpened()); } showPreviousWindowTab(): TPromise { Menu.sendActionToFirstResponder('selectPreviousTab:'); return TPromise.as(void 0); } showNextWindowTab(): TPromise { Menu.sendActionToFirstResponder('selectNextTab:'); return TPromise.as(void 0); } moveWindowTabToNewWindow(): TPromise { Menu.sendActionToFirstResponder('moveTabToNewWindow:'); return TPromise.as(void 0); } mergeAllWindowTabs(): TPromise { Menu.sendActionToFirstResponder('mergeAllWindows:'); return TPromise.as(void 0); } toggleWindowTabsBar(): TPromise { Menu.sendActionToFirstResponder('toggleTabBar:'); return TPromise.as(void 0); } focusWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.win.focus(); } return TPromise.as(null); } closeWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.win.close(); } return TPromise.as(null); } isFocused(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { return TPromise.as(codeWindow.win.isFocused()); } return TPromise.as(null); } isMaximized(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { return TPromise.as(codeWindow.win.isMaximized()); } return TPromise.as(null); } maximizeWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.win.maximize(); } return TPromise.as(null); } unmaximizeWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.win.unmaximize(); } return TPromise.as(null); } onWindowTitleDoubleClick(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.onWindowTitleDoubleClick(); } return TPromise.as(null); } setDocumentEdited(windowId: number, flag: boolean): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow && codeWindow.win.isDocumentEdited() !== flag) { codeWindow.win.setDocumentEdited(flag); } return TPromise.as(null); } openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { if (!paths || !paths.length) { return TPromise.as(null); } this.windowsMainService.open({ context: OpenContext.API, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options && options.forceNewWindow, forceReuseWindow: options && options.forceReuseWindow, forceOpenWorkspaceAsFile: options && options.forceOpenWorkspaceAsFile }); return TPromise.as(null); } openNewWindow(): TPromise { this.windowsMainService.openNewWindow(OpenContext.API); return TPromise.as(null); } showWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); if (codeWindow) { codeWindow.win.show(); } return TPromise.as(null); } getWindows(): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]> { const windows = this.windowsMainService.getWindows(); const result = windows.map(w => ({ id: w.id, workspace: w.openedWorkspace, openedFolderPath: w.openedFolderPath, title: w.win.getTitle(), filename: w.getRepresentedFilename() })); return TPromise.as(result); } getWindowCount(): TPromise { return TPromise.as(this.windowsMainService.getWindows().length); } log(severity: string, ...messages: string[]): TPromise { console[severity].apply(console, ...messages); return TPromise.as(null); } showItemInFolder(path: string): TPromise { shell.showItemInFolder(path); return TPromise.as(null); } openExternal(url: string): TPromise { return TPromise.as(shell.openExternal(url)); } startCrashReporter(config: Electron.CrashReporterStartOptions): TPromise { crashReporter.start(config); return TPromise.as(null); } quit(): TPromise { this.windowsMainService.quit(); return TPromise.as(null); } relaunch(options: { addArgs?: string[], removeArgs?: string[] }): TPromise { this.lifecycleService.relaunch(options); return TPromise.as(null); } whenSharedProcessReady(): TPromise { return this.sharedProcess.whenReady(); } toggleSharedProcess(): TPromise { this.sharedProcess.toggle(); return TPromise.as(null); } private openFileForURI(uri: URI): TPromise { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const pathsToOpen = [uri.fsPath]; this.windowsMainService.open({ context: OpenContext.API, cli, pathsToOpen }); return TPromise.as(null); } /** * This should only fire whenever an extension URL is open * and there are no windows to handle it. */ private async openExtensionForURI(uri: URI): TPromise { const cli = assign(Object.create(null), this.environmentService.args); const window = await this.windowsMainService.open({ context: OpenContext.API, cli })[0]; if (!window) { return; } window.win.show(); } dispose(): void { this.disposables = dispose(this.disposables); } }