From e26d5ac79feefaa3286afe8c057f6d03e6f87d52 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 12 Jun 2019 15:07:01 -0700 Subject: [PATCH] browser keybinding service --- .../workbench/browser/web.simpleservices.ts | 23 +-- src/vs/workbench/electron-browser/main.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 2 +- .../browser/browserKeymapService.ts | 48 ++++++ .../keybindingService.ts | 162 ++---------------- .../keybinding/common/dispatchConfig.ts | 17 ++ .../keybinding/common/keymapService.ts | 97 +++++++++++ .../keybinding.contribution.ts | 36 ++++ .../electron-browser/nativeKeymapService.ts | 160 +++++++++++++++++ src/vs/workbench/workbench.main.ts | 4 +- src/vs/workbench/workbench.web.main.ts | 3 +- 11 files changed, 381 insertions(+), 173 deletions(-) create mode 100644 src/vs/workbench/services/keybinding/browser/browserKeymapService.ts rename src/vs/workbench/services/keybinding/{electron-browser => browser}/keybindingService.ts (79%) create mode 100644 src/vs/workbench/services/keybinding/common/dispatchConfig.ts create mode 100644 src/vs/workbench/services/keybinding/common/keymapService.ts create mode 100644 src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts create mode 100644 src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index b69b8e72f45..568651178d8 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -13,7 +13,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // tslint:disable-next-line: import-patterns no-standalone-editor -import { StandaloneKeybindingService, SimpleResourcePropertiesService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleResourcePropertiesService } from 'vs/editor/standalone/browser/simpleServices'; import { IDownloadService } from 'vs/platform/download/common/download'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment/common/environment'; @@ -21,10 +21,7 @@ import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOper import { IPager } from 'vs/base/common/paging'; import { IExtensionManifest, ExtensionType, ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { ITelemetryService, ITelemetryData, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; import { ILogService, LogLevel, ConsoleLogService } from 'vs/platform/log/common/log'; import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -40,7 +37,6 @@ import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IReloadSessionEvent, IExtensionHostDebugService, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug'; @@ -638,23 +634,6 @@ registerSingleton(IExtensionUrlHandler, SimpleExtensionURLHandler, true); //#endregion -//#region Keybinding - -export class SimpleKeybindingService extends StandaloneKeybindingService { - constructor( - @IContextKeyService contextKeyService: IContextKeyService, - @ICommandService commandService: ICommandService, - @ITelemetryService telemetryService: ITelemetryService, - @INotificationService notificationService: INotificationService, - ) { - super(contextKeyService, commandService, telemetryService, notificationService, window.document.body); - } -} - -registerSingleton(IKeybindingService, SimpleKeybindingService); - -//#endregion - //#region Lifecycle export class SimpleLifecycleService extends AbstractLifecycleService { diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 39a00efc7eb..921b9642456 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -19,7 +19,7 @@ import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/n import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; -import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 6b58a8de754..7d9aafccda7 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -21,7 +21,7 @@ import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/theme import * as browser from 'vs/base/browser/browser'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction } from 'vs/platform/actions/common/actions'; diff --git a/src/vs/workbench/services/keybinding/browser/browserKeymapService.ts b/src/vs/workbench/services/keybinding/browser/browserKeymapService.ts new file mode 100644 index 00000000000..d6d6d4147bd --- /dev/null +++ b/src/vs/workbench/services/keybinding/browser/browserKeymapService.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; +import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { WindowsKeyboardMapper } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; +import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper'; + +class BrowserKeymapService extends Disposable implements IKeymapService { + public _serviceBrand: any; + + private readonly _onDidChangeKeyboardMapper = new Emitter(); + public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; + + constructor() { + super(); + } + + getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { + return this._createKeyboardMapper(dispatchConfig); + } + + private _createKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { + if (OS === OperatingSystem.Windows) { + return new WindowsKeyboardMapper(true, {}); + } + + // Looks like reading the mappings failed (most likely Mac + Japanese/Chinese keyboard layouts) + return new MacLinuxFallbackKeyboardMapper(OS); + } + + public getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null { + return null; + } + + public getRawKeyboardMapping(): IKeyboardMapping | null { + return null; + } +} + +registerSingleton(IKeymapService, BrowserKeymapService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts similarity index 79% rename from src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts rename to src/vs/workbench/services/keybinding/browser/keybindingService.ts index 99d5f9cad60..1ab23b603cc 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as nativeKeymap from 'native-keymap'; -import { release } from 'os'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter, Event } from 'vs/base/common/event'; @@ -30,14 +28,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { keybindingsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from 'vs/workbench/services/keybinding/common/keybindingIO'; -import { CachedKeyboardMapper, IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; -import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper'; -import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; -import { IWindowsKeyboardMapping, WindowsKeyboardMapper, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; +import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +// tslint:disable-next-line: import-patterns import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -46,121 +42,8 @@ import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/file import { dirname, isEqual } from 'vs/base/common/resources'; import { parse } from 'vs/base/common/json'; import * as objects from 'vs/base/common/objects'; - -export class KeyboardMapperFactory { - public static readonly INSTANCE = new KeyboardMapperFactory(); - - private _layoutInfo: nativeKeymap.IKeyboardLayoutInfo | null; - private _rawMapping: nativeKeymap.IKeyboardMapping | null; - private _keyboardMapper: IKeyboardMapper | null; - private _initialized: boolean; - - private readonly _onDidChangeKeyboardMapper = new Emitter(); - public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; - - private constructor() { - this._layoutInfo = null; - this._rawMapping = null; - this._keyboardMapper = null; - this._initialized = false; - } - - public _onKeyboardLayoutChanged(): void { - if (this._initialized) { - this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); - } - } - - public getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { - if (!this._initialized) { - this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); - } - if (dispatchConfig === DispatchConfig.KeyCode) { - // Forcefully set to use keyCode - return new MacLinuxFallbackKeyboardMapper(OS); - } - return this._keyboardMapper!; - } - - public getCurrentKeyboardLayout(): nativeKeymap.IKeyboardLayoutInfo | null { - if (!this._initialized) { - this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); - } - return this._layoutInfo; - } - - private static _isUSStandard(_kbInfo: nativeKeymap.IKeyboardLayoutInfo): boolean { - if (OS === OperatingSystem.Linux) { - const kbInfo = _kbInfo; - return (kbInfo && kbInfo.layout === 'us'); - } - - if (OS === OperatingSystem.Macintosh) { - const kbInfo = _kbInfo; - return (kbInfo && kbInfo.id === 'com.apple.keylayout.US'); - } - - if (OS === OperatingSystem.Windows) { - const kbInfo = _kbInfo; - return (kbInfo && kbInfo.name === '00000409'); - } - - return false; - } - - public getRawKeyboardMapping(): nativeKeymap.IKeyboardMapping | null { - if (!this._initialized) { - this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); - } - return this._rawMapping; - } - - private _setKeyboardData(layoutInfo: nativeKeymap.IKeyboardLayoutInfo, rawMapping: nativeKeymap.IKeyboardMapping): void { - this._layoutInfo = layoutInfo; - - if (this._initialized && KeyboardMapperFactory._equals(this._rawMapping, rawMapping)) { - // nothing to do... - return; - } - - this._initialized = true; - this._rawMapping = rawMapping; - this._keyboardMapper = new CachedKeyboardMapper( - KeyboardMapperFactory._createKeyboardMapper(this._layoutInfo, this._rawMapping) - ); - this._onDidChangeKeyboardMapper.fire(); - } - - private static _createKeyboardMapper(layoutInfo: nativeKeymap.IKeyboardLayoutInfo, rawMapping: nativeKeymap.IKeyboardMapping): IKeyboardMapper { - const isUSStandard = KeyboardMapperFactory._isUSStandard(layoutInfo); - if (OS === OperatingSystem.Windows) { - return new WindowsKeyboardMapper(isUSStandard, rawMapping); - } - - if (Object.keys(rawMapping).length === 0) { - // Looks like reading the mappings failed (most likely Mac + Japanese/Chinese keyboard layouts) - return new MacLinuxFallbackKeyboardMapper(OS); - } - - if (OS === OperatingSystem.Macintosh) { - const kbInfo = layoutInfo; - if (kbInfo.id === 'com.apple.keylayout.DVORAK-QWERTYCMD') { - // Use keyCode based dispatching for DVORAK - QWERTY ⌘ - return new MacLinuxFallbackKeyboardMapper(OS); - } - } - - return new MacLinuxKeyboardMapper(isUSStandard, rawMapping, OS); - } - - private static _equals(a: nativeKeymap.IKeyboardMapping | null, b: nativeKeymap.IKeyboardMapping | null): boolean { - if (OS === OperatingSystem.Windows) { - return windowsKeyboardMappingEquals(a, b); - } - - return macLinuxKeyboardMappingEquals(a, b); - } -} +import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapService'; +import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; interface ContributedKeyBinding { command: string; @@ -257,17 +140,6 @@ const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPointkeyboard).dispatch : null); - return (r === 'keyCode' ? DispatchConfig.KeyCode : DispatchConfig.Code); -} - export class WorkbenchKeybindingService extends AbstractKeybindingService { private _keyboardMapper: IKeyboardMapper; @@ -283,7 +155,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { @IConfigurationService configurationService: IConfigurationService, @IWindowService private readonly windowService: IWindowService, @IExtensionService extensionService: IExtensionService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @IKeymapService private readonly keymapService: IKeymapService ) { super(contextKeyService, commandService, telemetryService, notificationService); @@ -297,13 +170,13 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } dispatchConfig = newDispatchConfig; - this._keyboardMapper = KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig); + this._keyboardMapper = this.keymapService.getKeyboardMapper(dispatchConfig); this.updateResolver({ source: KeybindingSource.Default }); }); - this._keyboardMapper = KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig); - KeyboardMapperFactory.INSTANCE.onDidChangeKeyboardMapper(() => { - this._keyboardMapper = KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig); + this._keyboardMapper = this.keymapService.getKeyboardMapper(dispatchConfig); + this.keymapService.onDidChangeKeyboardMapper(() => { + this._keyboardMapper = this.keymapService.getKeyboardMapper(dispatchConfig); this.updateResolver({ source: KeybindingSource.Default }); }); @@ -353,7 +226,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); keybindingsTelemetry(telemetryService, this); - let data = KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(); + let data = this.keymapService.getCurrentKeyboardLayout(); /* __GDPR__ "keyboardLayout" : { "currentKeyboardLayout": { "${inline}": [ "${IKeyboardLayoutInfo}" ] } @@ -365,9 +238,9 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } public _dumpDebugInfo(): string { - const layoutInfo = JSON.stringify(KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(), null, '\t'); + const layoutInfo = JSON.stringify(this.keymapService.getCurrentKeyboardLayout(), null, '\t'); const mapperInfo = this._keyboardMapper.dumpDebugInfo(); - const rawMapping = JSON.stringify(KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(), null, '\t'); + const rawMapping = JSON.stringify(this.keymapService.getRawKeyboardMapping(), null, '\t'); return `Layout info:\n${layoutInfo}\n${mapperInfo}\n\nRaw mapping:\n${rawMapping}`; } @@ -559,7 +432,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } // consult the KeyboardMapperFactory to check the given event for // a printable value. - const mapping = KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(); + const mapping = this.keymapService.getRawKeyboardMapping(); if (!mapping) { return false; } @@ -799,13 +672,8 @@ const keyboardConfiguration: IConfigurationNode = { 'default': 'code', 'markdownDescription': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), 'included': OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux - }, - 'keyboard.touchbar.enabled': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('touchbar.enabled', "Enables the macOS touchbar buttons on the keyboard if available."), - 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) } + // no touch bar support } }; diff --git a/src/vs/workbench/services/keybinding/common/dispatchConfig.ts b/src/vs/workbench/services/keybinding/common/dispatchConfig.ts new file mode 100644 index 00000000000..a92871c7e96 --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/dispatchConfig.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export const enum DispatchConfig { + Code, + KeyCode +} + +export function getDispatchConfig(configurationService: IConfigurationService): DispatchConfig { + const keyboard = configurationService.getValue('keyboard'); + const r = (keyboard ? (keyboard).dispatch : null); + return (r === 'keyCode' ? DispatchConfig.KeyCode : DispatchConfig.Code); +} \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/common/keymapService.ts b/src/vs/workbench/services/keybinding/common/keymapService.ts new file mode 100644 index 00000000000..260a961c511 --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/keymapService.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; +import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; + +export interface IWindowsKeyMapping { + vkey: string; + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; +} +export interface IWindowsKeyboardMapping { + [code: string]: IWindowsKeyMapping; +} +export interface ILinuxKeyMapping { + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; +} +export interface ILinuxKeyboardMapping { + [code: string]: ILinuxKeyMapping; +} +export interface IMacKeyMapping { + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; + valueIsDeadKey: boolean; + withShiftIsDeadKey: boolean; + withAltGrIsDeadKey: boolean; + withShiftAltGrIsDeadKey: boolean; +} +export interface IMacKeyboardMapping { + [code: string]: IMacKeyMapping; +} + +export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping; + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface IWindowsKeyboardLayoutInfo { + name: string; + id: string; + text: string; +} + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface ILinuxKeyboardLayoutInfo { + model: string; + layout: string; + variant: string; + options: string; + rules: string; +} + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface IMacKeyboardLayoutInfo { + id: string; + lang: string; +} + +export type IKeyboardLayoutInfo = IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo; + +export const IKeymapService = createDecorator('keymapService'); + +export interface IKeymapService { + _serviceBrand: ServiceIdentifier; + onDidChangeKeyboardMapper: Event; + getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper; + getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null; + getRawKeyboardMapping(): IKeyboardMapping | null; +} diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts new file mode 100644 index 00000000000..1264211e1e3 --- /dev/null +++ b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.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. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { release } from 'os'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; + +const configurationRegistry = Registry.as(ConfigExtensions.Configuration); +const keyboardConfiguration: IConfigurationNode = { + 'id': 'keyboard', + 'order': 15, + 'type': 'object', + 'title': nls.localize('keyboardConfigurationTitle', "Keyboard"), + 'overridable': true, + 'properties': { + 'keyboard.dispatch': { + 'type': 'string', + 'enum': ['code', 'keyCode'], + 'default': 'code', + 'markdownDescription': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), + 'included': OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux + }, + 'keyboard.touchbar.enabled': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('touchbar.enabled', "Enables the macOS touchbar buttons on the keyboard if available."), + 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) + } + } +}; + +configurationRegistry.registerConfiguration(keyboardConfiguration); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts new file mode 100644 index 00000000000..6870722e2a6 --- /dev/null +++ b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nativeKeymap from 'native-keymap'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IKeyboardMapper, CachedKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; +import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { WindowsKeyboardMapper, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; +import { MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals, IMacLinuxKeyboardMapping } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; + +export class KeyboardMapperFactory { + public static readonly INSTANCE = new KeyboardMapperFactory(); + + private _layoutInfo: nativeKeymap.IKeyboardLayoutInfo | null; + private _rawMapping: nativeKeymap.IKeyboardMapping | null; + private _keyboardMapper: IKeyboardMapper | null; + private _initialized: boolean; + + private readonly _onDidChangeKeyboardMapper = new Emitter(); + public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; + + private constructor() { + this._layoutInfo = null; + this._rawMapping = null; + this._keyboardMapper = null; + this._initialized = false; + } + + public _onKeyboardLayoutChanged(): void { + if (this._initialized) { + this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); + } + } + + public getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { + if (!this._initialized) { + this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); + } + if (dispatchConfig === DispatchConfig.KeyCode) { + // Forcefully set to use keyCode + return new MacLinuxFallbackKeyboardMapper(OS); + } + return this._keyboardMapper!; + } + + public getCurrentKeyboardLayout(): nativeKeymap.IKeyboardLayoutInfo | null { + if (!this._initialized) { + this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); + } + return this._layoutInfo; + } + + private static _isUSStandard(_kbInfo: nativeKeymap.IKeyboardLayoutInfo): boolean { + if (OS === OperatingSystem.Linux) { + const kbInfo = _kbInfo; + return (kbInfo && kbInfo.layout === 'us'); + } + + if (OS === OperatingSystem.Macintosh) { + const kbInfo = _kbInfo; + return (kbInfo && kbInfo.id === 'com.apple.keylayout.US'); + } + + if (OS === OperatingSystem.Windows) { + const kbInfo = _kbInfo; + return (kbInfo && kbInfo.name === '00000409'); + } + + return false; + } + + public getRawKeyboardMapping(): nativeKeymap.IKeyboardMapping | null { + if (!this._initialized) { + this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); + } + return this._rawMapping; + } + + private _setKeyboardData(layoutInfo: nativeKeymap.IKeyboardLayoutInfo, rawMapping: nativeKeymap.IKeyboardMapping): void { + this._layoutInfo = layoutInfo; + + if (this._initialized && KeyboardMapperFactory._equals(this._rawMapping, rawMapping)) { + // nothing to do... + return; + } + + this._initialized = true; + this._rawMapping = rawMapping; + this._keyboardMapper = new CachedKeyboardMapper( + KeyboardMapperFactory._createKeyboardMapper(this._layoutInfo, this._rawMapping) + ); + this._onDidChangeKeyboardMapper.fire(); + } + + private static _createKeyboardMapper(layoutInfo: nativeKeymap.IKeyboardLayoutInfo, rawMapping: nativeKeymap.IKeyboardMapping): IKeyboardMapper { + const isUSStandard = KeyboardMapperFactory._isUSStandard(layoutInfo); + if (OS === OperatingSystem.Windows) { + return new WindowsKeyboardMapper(isUSStandard, rawMapping); + } + + if (Object.keys(rawMapping).length === 0) { + // Looks like reading the mappings failed (most likely Mac + Japanese/Chinese keyboard layouts) + return new MacLinuxFallbackKeyboardMapper(OS); + } + + if (OS === OperatingSystem.Macintosh) { + const kbInfo = layoutInfo; + if (kbInfo.id === 'com.apple.keylayout.DVORAK-QWERTYCMD') { + // Use keyCode based dispatching for DVORAK - QWERTY ⌘ + return new MacLinuxFallbackKeyboardMapper(OS); + } + } + + return new MacLinuxKeyboardMapper(isUSStandard, rawMapping, OS); + } + + private static _equals(a: nativeKeymap.IKeyboardMapping | null, b: nativeKeymap.IKeyboardMapping | null): boolean { + if (OS === OperatingSystem.Windows) { + return windowsKeyboardMappingEquals(a, b); + } + + return macLinuxKeyboardMappingEquals(a, b); + } +} + +class NativeKeymapService extends Disposable implements IKeymapService { + public _serviceBrand: any; + + private readonly _onDidChangeKeyboardMapper = new Emitter(); + public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; + + constructor() { + super(); + + this._register(KeyboardMapperFactory.INSTANCE.onDidChangeKeyboardMapper(() => { + this._onDidChangeKeyboardMapper.fire(); + })); + } + + getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { + return KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig); + } + + public getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null { + return KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(); + } + + public getRawKeyboardMapping(): IKeyboardMapping | null { + return KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(); + } +} + +registerSingleton(IKeymapService, NativeKeymapService, true); diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 1863b0c8277..1ac2dcb301e 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -117,7 +117,9 @@ import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/browser/parts/views/views'; -import 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; +import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; +import 'vs/workbench/services/keybinding/browser/keybindingService'; import 'vs/workbench/services/untitled/common/untitledEditorService'; import 'vs/workbench/services/textfile/node/textResourcePropertiesService'; import 'vs/workbench/services/mode/common/workbenchModeService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 392a1fb655b..751c13c4ff2 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -117,7 +117,8 @@ import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/browser/parts/views/views'; -// import 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import 'vs/workbench/services/keybinding/browser/browserKeymapService'; +import 'vs/workbench/services/keybinding/browser/keybindingService'; import 'vs/workbench/services/untitled/common/untitledEditorService'; // import 'vs/workbench/services/textfile/node/textResourcePropertiesService'; import 'vs/workbench/services/mode/common/workbenchModeService';