From 63cdab315235be34ba13e7eca4033f538ea63f36 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 8 Jun 2017 13:17:17 +0200 Subject: [PATCH] debt - extract IWindowsMainService --- src/vs/code/electron-main/app.ts | 4 +- src/vs/code/electron-main/keyboard.ts | 175 ++++++++++++++++++ src/vs/code/electron-main/launch.ts | 4 +- src/vs/code/electron-main/menus.ts | 107 +---------- src/vs/code/electron-main/window.ts | 69 +------ src/vs/code/electron-main/windows.ts | 68 +------ src/vs/code/node/keyboard.ts | 70 ------- .../lifecycle/electron-main/lifecycleMain.ts | 5 +- src/vs/platform/windows/common/windows.ts | 14 -- .../windows/electron-main/windowsService.ts | 135 +++++++++++++- 10 files changed, 324 insertions(+), 327 deletions(-) create mode 100644 src/vs/code/electron-main/keyboard.ts delete mode 100644 src/vs/code/node/keyboard.ts diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 6ebb6c1db95..9cc2c217653 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -7,10 +7,10 @@ import { app, ipcMain as ipc, BrowserWindow } from 'electron'; import * as platform from 'vs/base/common/platform'; -import { IWindowsMainService, WindowsManager } from 'vs/code/electron-main/windows'; +import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc'; -import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; +import { WindowsService, IWindowsMainService } from 'vs/platform/windows/electron-main/windowsService'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { CodeMenu } from 'vs/code/electron-main/menus'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; diff --git a/src/vs/code/electron-main/keyboard.ts b/src/vs/code/electron-main/keyboard.ts new file mode 100644 index 00000000000..84608f5507d --- /dev/null +++ b/src/vs/code/electron-main/keyboard.ts @@ -0,0 +1,175 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as nativeKeymap from 'native-keymap'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { isMacintosh } from 'vs/base/common/platform'; +import { IStorageService } from 'vs/platform/storage/node/storage'; +import Event, { Emitter, once } from 'vs/base/common/event'; +import { ConfigWatcher } from 'vs/base/node/config'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { IEnvironmentService } from "vs/platform/environment/common/environment"; +import { IWindowsMainService } from "vs/platform/windows/electron-main/windowsService"; +import { ipcMain as ipc } from 'electron'; + +export class KeyboardLayoutMonitor { + + public static readonly INSTANCE = new KeyboardLayoutMonitor(); + + private _emitter: Emitter; + private _registered: boolean; + private _isISOKeyboard: boolean; + + private constructor() { + this._emitter = new Emitter(); + this._registered = false; + this._isISOKeyboard = this._readIsISOKeyboard(); + } + + public onDidChangeKeyboardLayout(callback: (isISOKeyboard: boolean) => void): IDisposable { + if (!this._registered) { + this._registered = true; + + nativeKeymap.onDidChangeKeyboardLayout(() => { + this._emitter.fire(this._isISOKeyboard); + }); + + if (isMacintosh) { + // See https://github.com/Microsoft/vscode/issues/24153 + // On OSX, on ISO keyboards, Chromium swaps the scan codes + // of IntlBackslash and Backquote. + // + // The C++ methods can give the current keyboard type (ISO or not) + // only after a NSEvent was handled. + // + // We therefore poll. + setInterval(() => { + let newValue = this._readIsISOKeyboard(); + if (this._isISOKeyboard === newValue) { + // no change + return; + } + + this._isISOKeyboard = newValue; + this._emitter.fire(this._isISOKeyboard); + + }, 3000); + } + } + return this._emitter.event(callback); + } + + private _readIsISOKeyboard(): boolean { + if (isMacintosh) { + return nativeKeymap.isISOKeyboard(); + } + return false; + } + + public isISOKeyboard(): boolean { + return this._isISOKeyboard; + } +} + +export interface IKeybinding { + id: string; + label: string; + isNative: boolean; +} + +export class KeybindingsResolver { + + private static lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings'; + + private commandIds: Set; + private keybindings: { [commandId: string]: IKeybinding }; + private keybindingsWatcher: ConfigWatcher; + + private _onKeybindingsChanged = new Emitter(); + onKeybindingsChanged: Event = this._onKeybindingsChanged.event; + + constructor( + @IStorageService private storageService: IStorageService, + @IEnvironmentService environmentService: IEnvironmentService, + @IWindowsMainService private windowsService: IWindowsMainService + ) { + this.commandIds = new Set(); + this.keybindings = this.storageService.getItem<{ [id: string]: string; }>(KeybindingsResolver.lastKnownKeybindingsMapStorageKey) || Object.create(null); + this.keybindingsWatcher = new ConfigWatcher(environmentService.appKeybindingsPath, { changeBufferDelay: 100 }); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Resolve keybindings when any first window is loaded + const onceOnWindowReady = once(this.windowsService.onWindowReady); + onceOnWindowReady(win => this.resolveKeybindings(win)); + + // Listen to resolved keybindings from window + ipc.on('vscode:keybindingsResolved', (event, rawKeybindings: string) => { + let keybindings: IKeybinding[] = []; + try { + keybindings = JSON.parse(rawKeybindings); + } catch (error) { + // Should not happen + } + + // Fill hash map of resolved keybindings and check for changes + let keybindingsChanged = false; + let keybindingsCount = 0; + const resolvedKeybindings: { [commandId: string]: IKeybinding } = Object.create(null); + keybindings.forEach(keybinding => { + keybindingsCount++; + + resolvedKeybindings[keybinding.id] = keybinding; + + if (!this.keybindings[keybinding.id] || keybinding.label !== this.keybindings[keybinding.id].label) { + keybindingsChanged = true; + } + }); + + // A keybinding might have been unassigned, so we have to account for that too + if (Object.keys(this.keybindings).length !== keybindingsCount) { + keybindingsChanged = true; + } + + if (keybindingsChanged) { + this.keybindings = resolvedKeybindings; + this.storageService.setItem(KeybindingsResolver.lastKnownKeybindingsMapStorageKey, this.keybindings); // keep to restore instantly after restart + + this._onKeybindingsChanged.fire(); + } + }); + + // Resolve keybindings again when keybindings.json changes + this.keybindingsWatcher.onDidUpdateConfiguration(() => this.resolveKeybindings()); + + // Resolve keybindings when window reloads because an installed extension could have an impact + this.windowsService.onWindowReload(() => this.resolveKeybindings()); + } + + private resolveKeybindings(win = this.windowsService.getLastActiveWindow()): void { + if (this.commandIds.size && win) { + const commandIds = []; + this.commandIds.forEach(id => commandIds.push(id)); + win.sendWhenReady('vscode:resolveKeybindings', JSON.stringify(commandIds)); + } + } + + public getKeybinding(commandId: string): IKeybinding { + if (!commandId) { + return void 0; + } + + if (!this.commandIds.has(commandId)) { + this.commandIds.add(commandId); + } + + return this.keybindings[commandId]; + } +} \ No newline at end of file diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index 99103a7b5b4..17c9dd01223 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -5,7 +5,6 @@ 'use strict'; -import { IWindowsMainService } from 'vs/code/electron-main/windows'; import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILogService } from 'vs/platform/log/common/log'; @@ -14,7 +13,8 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { once } from 'vs/base/common/event'; -import { ICodeWindow, OpenContext } from "vs/platform/windows/common/windows"; +import { OpenContext } from "vs/platform/windows/common/windows"; +import { IWindowsMainService, ICodeWindow } from "vs/platform/windows/electron-main/windowsService"; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index a7b91e3360a..f4a6e1b79a6 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -11,26 +11,16 @@ import * as arrays from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem, BrowserWindow } from 'electron'; import { OpenContext } from "vs/platform/windows/common/windows"; -import { IWindowsMainService } from 'vs/code/electron-main/windows'; -import { CodeWindow } from 'vs/code/electron-main/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStorageService } from 'vs/platform/storage/node/storage'; import { IFilesConfiguration, AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update'; import product from 'vs/platform/node/product'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import Event, { Emitter, once } from 'vs/base/common/event'; -import { ConfigWatcher } from 'vs/base/node/config'; -import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { tildify } from "vs/base/common/labels"; - -interface IKeybinding { - id: string; - label: string; - isNative: boolean; -} +import { IWindowsMainService } from "vs/platform/windows/electron-main/windowsService"; +import { KeybindingsResolver } from "vs/code/electron-main/keyboard"; interface IExtensionViewlet { id: string; @@ -57,99 +47,6 @@ interface IConfiguration extends IFilesConfiguration { }; } -class KeybindingsResolver { - - private static lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings'; - - private commandIds: Set; - private keybindings: { [commandId: string]: IKeybinding }; - private keybindingsWatcher: ConfigWatcher; - - private _onKeybindingsChanged = new Emitter(); - onKeybindingsChanged: Event = this._onKeybindingsChanged.event; - - constructor( - @IStorageService private storageService: IStorageService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWindowsMainService private windowsService: IWindowsMainService - ) { - this.commandIds = new Set(); - this.keybindings = this.storageService.getItem<{ [id: string]: string; }>(KeybindingsResolver.lastKnownKeybindingsMapStorageKey) || Object.create(null); - this.keybindingsWatcher = new ConfigWatcher(environmentService.appKeybindingsPath, { changeBufferDelay: 100 }); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Resolve keybindings when any first window is loaded - const onceOnWindowReady = once(this.windowsService.onWindowReady); - onceOnWindowReady(win => this.resolveKeybindings(win)); - - // Listen to resolved keybindings from window - ipc.on('vscode:keybindingsResolved', (event, rawKeybindings: string) => { - let keybindings: IKeybinding[] = []; - try { - keybindings = JSON.parse(rawKeybindings); - } catch (error) { - // Should not happen - } - - // Fill hash map of resolved keybindings and check for changes - let keybindingsChanged = false; - let keybindingsCount = 0; - const resolvedKeybindings: { [commandId: string]: IKeybinding } = Object.create(null); - keybindings.forEach(keybinding => { - keybindingsCount++; - - resolvedKeybindings[keybinding.id] = keybinding; - - if (!this.keybindings[keybinding.id] || keybinding.label !== this.keybindings[keybinding.id].label) { - keybindingsChanged = true; - } - }); - - // A keybinding might have been unassigned, so we have to account for that too - if (Object.keys(this.keybindings).length !== keybindingsCount) { - keybindingsChanged = true; - } - - if (keybindingsChanged) { - this.keybindings = resolvedKeybindings; - this.storageService.setItem(KeybindingsResolver.lastKnownKeybindingsMapStorageKey, this.keybindings); // keep to restore instantly after restart - - this._onKeybindingsChanged.fire(); - } - }); - - // Resolve keybindings again when keybindings.json changes - this.keybindingsWatcher.onDidUpdateConfiguration(() => this.resolveKeybindings()); - - // Resolve keybindings when window reloads because an installed extension could have an impact - this.windowsService.onWindowReload(() => this.resolveKeybindings()); - } - - private resolveKeybindings(win: CodeWindow = this.windowsService.getLastActiveWindow()): void { - if (this.commandIds.size && win) { - const commandIds = []; - this.commandIds.forEach(id => commandIds.push(id)); - win.sendWhenReady('vscode:resolveKeybindings', JSON.stringify(commandIds)); - } - } - - public getKeybinding(commandId: string): IKeybinding { - if (!commandId) { - return void 0; - } - - if (!this.commandIds.has(commandId)) { - this.commandIds.add(commandId); - } - - return this.keybindings[commandId]; - } -} - const telemetryFrom = 'menu'; export class CodeMenu { diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 1073d3db129..dc0979736d6 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -19,11 +19,11 @@ import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { parseArgs } from 'vs/platform/environment/node/argv'; import product from 'vs/platform/node/product'; import { getCommonHTTPHeaders } from 'vs/platform/environment/node/http'; -import { IWindowSettings, MenuBarVisibility, ICodeWindow, ReadyState, IWindowCloseEvent } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, MenuBarVisibility, ReadyState } from 'vs/platform/windows/common/windows'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { KeyboardLayoutMonitor } from 'vs/code/node/keyboard'; -import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from "vs/base/common/platform"; -import CommonEvent, { Emitter } from "vs/base/common/event"; +import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard'; +import { isLinux, isMacintosh, isWindows } from "vs/base/common/platform"; +import { ICodeWindow, IWindowConfiguration } from "vs/platform/windows/electron-main/windowsService"; export interface IWindowState { width?: number; @@ -55,54 +55,6 @@ export const defaultWindowState = function (mode = WindowMode.Normal): IWindowSt }; }; -export interface IPath { - - // the workspace spath for a Code instance which can be null - workspacePath?: string; - - // the file path to open within a Code instance - filePath?: string; - - // the line number in the file path to open - lineNumber?: number; - - // the column number in the file path to open - columnNumber?: number; - - // indicator to create the file path in the Code instance - createFilePath?: boolean; -} - -export interface IWindowConfiguration extends ParsedArgs { - appRoot: string; - execPath: string; - - userEnv: IProcessEnvironment; - - isISOKeyboard?: boolean; - - zoomLevel?: number; - fullscreen?: boolean; - highContrast?: boolean; - baseTheme?: string; - backgroundColor?: string; - accessibilitySupport?: boolean; - - isInitialStartup?: boolean; - - perfStartTime?: number; - perfAppReady?: number; - perfWindowLoadTime?: number; - - workspacePath?: string; - - filesToOpen?: IPath[]; - filesToCreate?: IPath[]; - filesToDiff?: IPath[]; - - nodeCachedDataDir: string; -} - export class CodeWindow implements ICodeWindow { public static themeStorageKey = 'theme'; @@ -111,7 +63,6 @@ export class CodeWindow implements ICodeWindow { private static MIN_WIDTH = 200; private static MIN_HEIGHT = 120; - private _onClose: Emitter; private options: IWindowCreationOptions; private hiddenTitleBarStyle: boolean; private showTimeoutHandle: any; @@ -146,9 +97,6 @@ export class CodeWindow implements ICodeWindow { this.whenReadyCallbacks = []; this.toDispose = []; - this._onClose = new Emitter(); - this.toDispose.push(this._onClose); - // create browser window this.createBrowserWindow(config); @@ -163,10 +111,6 @@ export class CodeWindow implements ICodeWindow { this.registerListeners(); } - public get onClose(): CommonEvent { - return this._onClose.event; - } - private createBrowserWindow(config: IWindowCreationOptions): void { // Load window state @@ -360,11 +304,6 @@ export class CodeWindow implements ICodeWindow { private registerListeners(): void { - // Re-emit close event - this._win.on('close', e => { - this._onClose.fire(e); - }); - // Remember that we loaded this._win.webContents.on('did-finish-load', () => { this._readyState = ReadyState.LOADING; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index e31775df667..58ad622a738 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -16,41 +16,27 @@ import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { trim } from 'vs/base/common/strings'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStorageService } from 'vs/platform/storage/node/storage'; -import { IPath, CodeWindow, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } from 'vs/code/electron-main/window'; +import { CodeWindow, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } from 'vs/code/electron-main/window'; import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron'; import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleService, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { getPathLabel } from 'vs/base/common/labels'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowSettings, OpenContext } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolder } from 'vs/code/node/windowsUtils'; import CommonEvent, { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/node/product'; import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { isParent, isEqual, isEqualOrParent } from 'vs/platform/files/common/files'; -import { KeyboardLayoutMonitor } from 'vs/code/node/keyboard'; +import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard'; +import { IPath, IWindowsMainService, IOpenConfiguration, IRecentPathsList, IWindowConfiguration } from "vs/platform/windows/electron-main/windowsService"; enum WindowError { UNRESPONSIVE, CRASHED } -export interface IOpenConfiguration { - context: OpenContext; - cli: ParsedArgs; - userEnv?: platform.IProcessEnvironment; - pathsToOpen?: string[]; - preferNewWindow?: boolean; - forceNewWindow?: boolean; - forceReuseWindow?: boolean; - forceEmpty?: boolean; - windowToUse?: CodeWindow; - diffMode?: boolean; - initialStartup?: boolean; -} - interface INewWindowState extends ISingleWindowState { hasDefaultState?: boolean; } @@ -66,11 +52,6 @@ interface IWindowsState { openedFolders: IWindowState[]; } -export interface IRecentPathsList { - folders: string[]; - files: string[]; -} - interface INativeOpenDialogOptions { pickFolders?: boolean; pickFiles?: boolean; @@ -85,45 +66,6 @@ const ReopenFoldersSetting = { NONE: 'none' }; -export const IWindowsMainService = createDecorator('windowsMainService'); - -export interface IWindowsMainService { - _serviceBrand: any; - - // events - onWindowReady: CommonEvent; - onWindowClose: CommonEvent; - onWindowReload: CommonEvent; - onPathsOpen: CommonEvent; - onRecentPathsChange: CommonEvent; - - // methods - ready(initialUserEnv: platform.IProcessEnvironment): void; - reload(win: CodeWindow, cli?: ParsedArgs): void; - open(openConfig: IOpenConfiguration): CodeWindow[]; - openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void; - openFileFolderPicker(forceNewWindow?: boolean, data?: ITelemetryData): void; - openFilePicker(forceNewWindow?: boolean, path?: string, window?: CodeWindow, data?: ITelemetryData): void; - openFolderPicker(forceNewWindow?: boolean, window?: CodeWindow, data?: ITelemetryData): void; - focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow; - getLastActiveWindow(): CodeWindow; - findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): CodeWindow; - openNewWindow(context: OpenContext): void; - sendToFocused(channel: string, ...args: any[]): void; - sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; - getFocusedWindow(): CodeWindow; - getWindowById(windowId: number): CodeWindow; - getWindows(): CodeWindow[]; - getWindowCount(): number; - addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void; - getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList; - removeFromRecentPathsList(path: string): void; - removeFromRecentPathsList(paths: string[]): void; - clearRecentPathsList(): void; - updateWindowsJumpList(): void; - quit(): void; -} - export class WindowsManager implements IWindowsMainService { _serviceBrand: any; @@ -527,7 +469,7 @@ export class WindowsManager implements IWindowsMainService { } const configuration = this.toConfiguration(openConfig, folderToOpen, filesToOpen, filesToCreate, filesToDiff); - const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse); + const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse as CodeWindow); usedWindows.push(browserWindow); // Reset these because we handled them @@ -559,7 +501,7 @@ export class WindowsManager implements IWindowsMainService { else if (emptyToOpen.length > 0) { emptyToOpen.forEach(() => { const configuration = this.toConfiguration(openConfig); - const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse); + const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse as CodeWindow); usedWindows.push(browserWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then diff --git a/src/vs/code/node/keyboard.ts b/src/vs/code/node/keyboard.ts deleted file mode 100644 index 16979fc90c0..00000000000 --- a/src/vs/code/node/keyboard.ts +++ /dev/null @@ -1,70 +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 * as nativeKeymap from 'native-keymap'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { isMacintosh } from 'vs/base/common/platform'; -import { Emitter } from 'vs/base/common/event'; - -export class KeyboardLayoutMonitor { - - public static readonly INSTANCE = new KeyboardLayoutMonitor(); - - private _emitter: Emitter; - private _registered: boolean; - private _isISOKeyboard: boolean; - - private constructor() { - this._emitter = new Emitter(); - this._registered = false; - this._isISOKeyboard = this._readIsISOKeyboard(); - } - - public onDidChangeKeyboardLayout(callback: (isISOKeyboard: boolean) => void): IDisposable { - if (!this._registered) { - this._registered = true; - - nativeKeymap.onDidChangeKeyboardLayout(() => { - this._emitter.fire(this._isISOKeyboard); - }); - - if (isMacintosh) { - // See https://github.com/Microsoft/vscode/issues/24153 - // On OSX, on ISO keyboards, Chromium swaps the scan codes - // of IntlBackslash and Backquote. - // - // The C++ methods can give the current keyboard type (ISO or not) - // only after a NSEvent was handled. - // - // We therefore poll. - setInterval(() => { - let newValue = this._readIsISOKeyboard(); - if (this._isISOKeyboard === newValue) { - // no change - return; - } - - this._isISOKeyboard = newValue; - this._emitter.fire(this._isISOKeyboard); - - }, 3000); - } - } - return this._emitter.event(callback); - } - - private _readIsISOKeyboard(): boolean { - if (isMacintosh) { - return nativeKeymap.isISOKeyboard(); - } - return false; - } - - public isISOKeyboard(): boolean { - return this._isISOKeyboard; - } -} diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index 2dc0242f8cf..8c65a896d3d 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -12,7 +12,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/node/storage'; import Event, { Emitter } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ICodeWindow, ReadyState } from "vs/platform/windows/common/windows"; +import { ReadyState } from "vs/platform/windows/common/windows"; +import { ICodeWindow } from "vs/platform/windows/electron-main/windowsService"; export const ILifecycleService = createDecorator('lifecycleService'); @@ -135,7 +136,7 @@ export class LifecycleService implements ILifecycleService { public registerWindow(codeWindow: ICodeWindow): void { // Window Before Closing: Main -> Renderer - codeWindow.onClose(e => { + codeWindow.win.on('close', e => { const windowId = codeWindow.id; this.logService.log('Lifecycle#window-before-close', windowId); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 3ca41ea3f3b..844ce3a9aa0 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -109,20 +109,6 @@ export interface IWindowSettings { enableMenuBarMnemonics: boolean; } -export interface IWindowCloseEvent { - preventDefault: Function; -} - -export interface ICodeWindow { - id: number; - readyState: ReadyState; - - onClose: Event; - - close(): void; - send(channel: string, ...args: any[]): void; -} - export enum ReadyState { /** diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 492ae30fdc0..4850b5a9a22 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -9,21 +9,148 @@ 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 } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWindowsService, OpenContext, ReadyState } from 'vs/platform/windows/common/windows'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { shell, crashReporter, app } 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 { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowsMainService } from 'vs/code/electron-main/windows'; import { ILifecycleService } from "vs/platform/lifecycle/electron-main/lifecycleMain"; +import { createDecorator } from "vs/platform/instantiation/common/instantiation"; +import { IProcessEnvironment } from "vs/base/common/platform"; + +export interface ICodeWindow { + id: number; + win: Electron.BrowserWindow; + config: IWindowConfiguration; + openedWorkspacePath: string; + + readyState: ReadyState; + + close(): void; + + send(channel: string, ...args: any[]): void; + sendWhenReady(channel: string, ...args: any[]): void; + + toggleFullScreen(): void; + hasHiddenTitleBarStyle(): boolean; + setRepresentedFilename(name: string): void; + getRepresentedFilename(): string; + onWindowTitleDoubleClick(): void; +} + +export interface IWindowConfiguration extends ParsedArgs { + appRoot: string; + execPath: string; + + userEnv: IProcessEnvironment; + + isISOKeyboard?: boolean; + + zoomLevel?: number; + fullscreen?: boolean; + highContrast?: boolean; + baseTheme?: string; + backgroundColor?: string; + accessibilitySupport?: boolean; + + isInitialStartup?: boolean; + + perfStartTime?: number; + perfAppReady?: number; + perfWindowLoadTime?: number; + + workspacePath?: string; + + filesToOpen?: IPath[]; + filesToCreate?: IPath[]; + filesToDiff?: IPath[]; + + nodeCachedDataDir: string; +} export interface ISharedProcess { whenReady(): TPromise; toggle(): void; } +export const IWindowsMainService = createDecorator('windowsMainService'); + +export interface IWindowsMainService { + _serviceBrand: any; + + // events + onWindowReady: Event; + onWindowClose: Event; + onWindowReload: Event; + onPathsOpen: Event; + onRecentPathsChange: Event; + + // methods + ready(initialUserEnv: IProcessEnvironment): void; + reload(win: ICodeWindow, cli?: ParsedArgs): void; + open(openConfig: IOpenConfiguration): ICodeWindow[]; + openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void; + openFileFolderPicker(forceNewWindow?: boolean, data?: ITelemetryData): void; + openFilePicker(forceNewWindow?: boolean, path?: string, window?: ICodeWindow, data?: ITelemetryData): void; + openFolderPicker(forceNewWindow?: boolean, window?: ICodeWindow, data?: ITelemetryData): void; + focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow; + getLastActiveWindow(): ICodeWindow; + findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): ICodeWindow; + openNewWindow(context: OpenContext): void; + sendToFocused(channel: string, ...args: any[]): void; + sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; + getFocusedWindow(): ICodeWindow; + getWindowById(windowId: number): ICodeWindow; + getWindows(): ICodeWindow[]; + getWindowCount(): number; + addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void; + getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList; + removeFromRecentPathsList(path: string): void; + removeFromRecentPathsList(paths: string[]): void; + clearRecentPathsList(): void; + updateWindowsJumpList(): void; + quit(): void; +} + +export interface IPath { + + // the workspace spath for a Code instance which can be null + workspacePath?: string; + + // the file path to open within a Code instance + filePath?: string; + + // the line number in the file path to open + lineNumber?: number; + + // the column number in the file path to open + columnNumber?: number; + + // indicator to create the file path in the Code instance + createFilePath?: boolean; +} + +export interface IOpenConfiguration { + context: OpenContext; + cli: ParsedArgs; + userEnv?: IProcessEnvironment; + pathsToOpen?: string[]; + preferNewWindow?: boolean; + forceNewWindow?: boolean; + forceReuseWindow?: boolean; + forceEmpty?: boolean; + windowToUse?: ICodeWindow; + diffMode?: boolean; + initialStartup?: boolean; +} + +export interface IRecentPathsList { + folders: string[]; + files: string[]; +} + export class WindowsService implements IWindowsService, IDisposable { _serviceBrand: any; @@ -321,4 +448,4 @@ export class WindowsService implements IWindowsService, IDisposable { dispose(): void { this.disposables = dispose(this.disposables); } -} +} \ No newline at end of file