From 2865949f016fd5b891de36cfa43e1cef77ed8ddb Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 28 Apr 2017 16:19:38 +0200 Subject: [PATCH] Fixes #24153: ISO Keyboards: Backslash and IntlBackslash "swapped" --- npm-shrinkwrap.json | 6 +- package.json | 2 +- src/typings/native-keymap.d.ts | 2 + src/vs/code/electron-main/windows.ts | 43 ++++++++-- src/vs/workbench/electron-browser/window.ts | 4 +- .../common/macLinuxKeyboardMapper.ts | 25 +++++- .../electron-browser/keybindingService.ts | 11 ++- .../test/macLinuxKeyboardMapper.test.ts | 79 ++++++++++++++++++- 8 files changed, 150 insertions(+), 22 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e997f8ba10a..cc268be6f54 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -293,9 +293,9 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.4.0.tgz" }, "native-keymap": { - "version": "1.2.2", - "from": "native-keymap@1.2.2", - "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-1.2.2.tgz" + "version": "1.2.3", + "from": "native-keymap@1.2.3", + "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-1.2.3.tgz" }, "normalize-path": { "version": "2.0.1", diff --git a/package.json b/package.json index 09aaf0585e3..718eb01e19e 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "iconv-lite": "0.4.15", "jschardet": "^1.4.2", "minimist": "1.2.0", - "native-keymap": "1.2.2", + "native-keymap": "1.2.3", "node-pty": "0.6.4", "semver": "4.3.6", "v8-profiler": "jrieken/v8-profiler#vscode", diff --git a/src/typings/native-keymap.d.ts b/src/typings/native-keymap.d.ts index 807935bbb49..65acdb584fa 100644 --- a/src/typings/native-keymap.d.ts +++ b/src/typings/native-keymap.d.ts @@ -66,4 +66,6 @@ declare module 'native-keymap' { export function getCurrentKeyboardLayout(): IKeyboardLayoutInfo; export function onDidChangeKeyboardLayout(callback: () => void); + + export function isISOKeyboard(): boolean; } \ No newline at end of file diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 1308d9e82aa..5b8428bf2e2 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -253,9 +253,9 @@ export class WindowsManager implements IWindowsMainService { this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win)); this.lifecycleService.onBeforeQuit(() => this.onBeforeQuit()); - KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => { + KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout((isISOKeyboard: boolean) => { WindowsManager.WINDOWS.forEach((window) => { - window.sendWhenReady('vscode:keyboardLayoutChanged'); + window.sendWhenReady('vscode:keyboardLayoutChanged', isISOKeyboard); }); }); } @@ -1329,21 +1329,52 @@ class KeyboardLayoutMonitor { public static INSTANCE = new KeyboardLayoutMonitor(); - private _emitter: Emitter; + private _emitter: Emitter; private _registered: boolean; private constructor() { - this._emitter = new Emitter(); + this._emitter = new Emitter(); this._registered = false; } - public onDidChangeKeyboardLayout(callback: () => void): IDisposable { + public onDidChangeKeyboardLayout(callback: (isISOKeyboard: boolean) => void): IDisposable { if (!this._registered) { this._registered = true; + nativeKeymap.onDidChangeKeyboardLayout(() => { - this._emitter.fire(); + this._emitter.fire(this._isISOKeyboard()); }); + + if (platform.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. + let prevValue: boolean = null; + setInterval(() => { + let newValue = this._isISOKeyboard(); + if (prevValue === newValue) { + // no change + return; + } + + prevValue = newValue; + this._emitter.fire(this._isISOKeyboard()); + + }, 3000); + } } return this._emitter.event(callback); } + + private _isISOKeyboard(): boolean { + if (platform.isMacintosh) { + return nativeKeymap.isISOKeyboard(); + } + return false; + } } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index b7dca196d79..f779df1a858 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -297,8 +297,8 @@ export class ElectronWindow extends Themable { }); // keyboard layout changed event - ipc.on('vscode:keyboardLayoutChanged', () => { - KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged(); + ipc.on('vscode:keyboardLayoutChanged', (event, isISOKeyboard: boolean) => { + KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged(isISOKeyboard); }); // Configuration changes diff --git a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts index 77fb9036225..b884e80add2 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts @@ -510,7 +510,11 @@ class ScanCodeKeyCodeMapper { export class MacLinuxKeyboardMapper implements IKeyboardMapper { /** - * OS (can be Linux or Macintosh) + * Is the keyboard type ISO (on Mac) + */ + private readonly _isISOKeyboard: boolean; + /** + * Is this the standard US keyboard layout? */ private readonly _isUSStandard: boolean; /** @@ -534,7 +538,8 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { */ private readonly _scanCodeToDispatch: string[] = []; - constructor(isUSStandard: boolean, rawMappings: IMacLinuxKeyboardMapping, OS: OperatingSystem) { + constructor(isISOKeyboard: boolean, isUSStandard: boolean, rawMappings: IMacLinuxKeyboardMapping, OS: OperatingSystem) { + this._isISOKeyboard = isISOKeyboard; this._isUSStandard = isUSStandard; this._OS = OS; this._codeInfo = []; @@ -1051,11 +1056,27 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): NativeResolvedKeybinding { let code = ScanCodeUtils.toEnum(keyboardEvent.code); + // Treat NumpadEnter as Enter if (code === ScanCode.NumpadEnter) { code = ScanCode.Enter; } + if (this._OS === OperatingSystem.Macintosh && this._isISOKeyboard) { + // See https://github.com/Microsoft/vscode/issues/24153 + // On OSX, on ISO keyboards, Chromium swaps the scan codes + // of IntlBackslash and Backquote. + + switch (code) { + case ScanCode.IntlBackslash: + code = ScanCode.Backquote; + break; + case ScanCode.Backquote: + code = ScanCode.IntlBackslash; + break; + } + } + const keyCode = keyboardEvent.keyCode; if ( diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 7b1edd55412..5b6e0095d14 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -44,6 +44,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur export class KeyboardMapperFactory { public static INSTANCE = new KeyboardMapperFactory(); + private _isISOKeyboard: boolean; private _layoutInfo: nativeKeymap.IKeyboardLayoutInfo; private _rawMapping: nativeKeymap.IKeyboardMapping; private _keyboardMapper: IKeyboardMapper; @@ -53,13 +54,15 @@ export class KeyboardMapperFactory { public onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; private constructor() { + this._isISOKeyboard = false; this._layoutInfo = null; this._rawMapping = null; this._keyboardMapper = null; this._initialized = false; } - public _onKeyboardLayoutChanged(): void { + public _onKeyboardLayoutChanged(isISOKeyboard: boolean): void { + this._isISOKeyboard = isISOKeyboard; if (this._initialized) { this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap()); } @@ -124,11 +127,11 @@ export class KeyboardMapperFactory { this._initialized = true; this._rawMapping = rawMapping; - this._keyboardMapper = KeyboardMapperFactory._createKeyboardMapper(KeyboardMapperFactory._isUSStandard(this._layoutInfo), this._rawMapping); + this._keyboardMapper = KeyboardMapperFactory._createKeyboardMapper(this._isISOKeyboard, KeyboardMapperFactory._isUSStandard(this._layoutInfo), this._rawMapping); this._onDidChangeKeyboardMapper.fire(); } - private static _createKeyboardMapper(isUSStandard: boolean, rawMapping: nativeKeymap.IKeyboardMapping): IKeyboardMapper { + private static _createKeyboardMapper(isISOKeyboard: boolean, isUSStandard: boolean, rawMapping: nativeKeymap.IKeyboardMapping): IKeyboardMapper { if (OS === OperatingSystem.Windows) { return new WindowsKeyboardMapper(rawMapping); } @@ -138,7 +141,7 @@ export class KeyboardMapperFactory { return new MacLinuxFallbackKeyboardMapper(OS); } - return new MacLinuxKeyboardMapper(isUSStandard, rawMapping, OS); + return new MacLinuxKeyboardMapper(isISOKeyboard, isUSStandard, rawMapping, OS); } private static _equals(a: nativeKeymap.IKeyboardMapping, b: nativeKeymap.IKeyboardMapping): boolean { diff --git a/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts index 616e098a388..d47b0dccf77 100644 --- a/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts @@ -19,7 +19,7 @@ const WRITE_FILE_IF_DIFFERENT = false; function createKeyboardMapper(isUSStandard: boolean, file: string, OS: OperatingSystem): TPromise { return readRawMapping(file).then((rawMappings) => { - return new MacLinuxKeyboardMapper(isUSStandard, rawMappings, OS); + return new MacLinuxKeyboardMapper(false, isUSStandard, rawMappings, OS); }); } @@ -1562,7 +1562,7 @@ suite('keyboardMapper - LINUX en_us', () => { suite('keyboardMapper', () => { test('issue #23706: Linux UK layout: Ctrl + Apostrophe also toggles terminal', () => { - let mapper = new MacLinuxKeyboardMapper(false, { + let mapper = new MacLinuxKeyboardMapper(false, false, { 'Backquote': { 'value': '`', 'withShift': '¬', @@ -1600,7 +1600,7 @@ suite('keyboardMapper', () => { }); test('issue #24064: NumLock/NumPad keys stopped working in 1.11 on Linux', () => { - let mapper = new MacLinuxKeyboardMapper(false, {}, OperatingSystem.Linux); + let mapper = new MacLinuxKeyboardMapper(false, false, {}, OperatingSystem.Linux); function assertNumpadKeyboardEvent(keyCode: KeyCode, code: string, label: string, electronAccelerator: string, userSettingsLabel: string, dispatch: string): void { assertResolveKeyboardEvent( @@ -1645,7 +1645,7 @@ suite('keyboardMapper', () => { }); test('issue #24107: Delete, Insert, Home, End, PgUp, PgDn, and arrow keys no longer work editor in 1.11', () => { - let mapper = new MacLinuxKeyboardMapper(false, {}, OperatingSystem.Linux); + let mapper = new MacLinuxKeyboardMapper(false, false, {}, OperatingSystem.Linux); function assertKeyboardEvent(keyCode: KeyCode, code: string, label: string, electronAccelerator: string, userSettingsLabel: string, dispatch: string): void { assertResolveKeyboardEvent( @@ -1701,6 +1701,77 @@ suite('keyboardMapper', () => { assertKeyboardEvent(KeyCode.UpArrow, 'Lang3', 'UpArrow', 'Up', 'up', '[ArrowUp]'); }); + test('issue #24153: ISO Keyboards: Backslash and IntlBackslash "swapped"', () => { + let mapper = new MacLinuxKeyboardMapper(true, false, { + 'Backquote': { + 'value': '`', + 'withShift': '~', + 'withAltGr': '`', + 'withShiftAltGr': '`' + }, + 'IntlBackslash': { + 'value': '§', + 'withShift': '°', + 'withAltGr': '§', + 'withShiftAltGr': '°' + } + }, OperatingSystem.Macintosh); + + assertResolveKeyboardEvent( + mapper, + { + ctrlKey: true, + shiftKey: false, + altKey: false, + metaKey: false, + keyCode: -1, + code: 'Backquote' + }, + { + label: '⌃§', + ariaLabel: 'Control+§', + labelWithoutModifiers: '§', + ariaLabelWithoutModifiers: '§', + electronAccelerator: null, + userSettingsLabel: 'ctrl+[IntlBackslash]', + isWYSIWYG: false, + isChord: false, + hasCtrlModifier: true, + hasShiftModifier: false, + hasAltModifier: false, + hasMetaModifier: false, + dispatchParts: ['ctrl+[IntlBackslash]', null], + } + ); + + assertResolveKeyboardEvent( + mapper, + { + ctrlKey: true, + shiftKey: false, + altKey: false, + metaKey: false, + keyCode: -1, + code: 'IntlBackslash' + }, + { + label: '⌃`', + ariaLabel: 'Control+`', + labelWithoutModifiers: '`', + ariaLabelWithoutModifiers: '`', + electronAccelerator: null, + userSettingsLabel: 'ctrl+`', + isWYSIWYG: true, + isChord: false, + hasCtrlModifier: true, + hasShiftModifier: false, + hasAltModifier: false, + hasMetaModifier: false, + dispatchParts: ['ctrl+[Backquote]', null], + } + ); + }); + }); suite('keyboardMapper - LINUX ru', () => { -- GitLab