From 08f2f7f07ddc867f5a6316c3b4309d444367252e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 1 Apr 2019 16:02:06 +0200 Subject: [PATCH] #4079: New approach for handling keyboard layouts and keyboard events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miro Spönemann --- .travis.yml | 3 + configs/base.tsconfig.json | 1 + .../src/generator/frontend-generator.ts | 12 + .../application-manager/src/rebuild.ts | 2 +- packages/core/package.json | 11 +- packages/core/scripts/generate-layout.js | 53 ++ packages/core/src/browser/dialogs.ts | 2 +- .../browser/frontend-application-module.ts | 2 + .../core/src/browser/frontend-application.ts | 2 +- packages/core/src/browser/index.ts | 2 +- packages/core/src/browser/keybinding.spec.ts | 244 +---- packages/core/src/browser/keybinding.ts | 210 +++-- .../keyboard/browser-keyboard-module.ts | 25 + packages/core/src/browser/keyboard/index.ts | 19 + .../src/browser/keyboard/keyboard-browser.ts | 179 ++++ .../keyboard/keyboard-layout-service.spec.ts | 121 +++ .../keyboard/keyboard-layout-service.ts | 425 +++++++++ .../core/src/browser/keyboard/keys.spec.ts | 230 +++++ packages/core/src/browser/keyboard/keys.ts | 649 +++++++++++++ packages/core/src/browser/keys.ts | 891 +----------------- .../src/browser/menu/browser-menu-plugin.ts | 15 +- packages/core/src/browser/saveable.ts | 2 +- packages/core/src/browser/tree/search-box.ts | 2 +- .../core/src/browser/tree/tree-widget.tsx | 2 +- packages/core/src/browser/widgets/widget.ts | 2 +- .../src/common/keyboard/layout-provider.ts | 37 + .../keyboard/layouts/linux-de-German.json | 1 + .../keyboard/layouts/linux-fr-French.json | 1 + .../keyboard/layouts/mac-de-German.json | 1 + .../common/keyboard/layouts/mac-en-US.json | 1 + .../keyboard/layouts/mac-fr-French.json | 1 + .../keyboard/layouts/win-de-German.json | 1 + .../common/keyboard/layouts/win-en-US.json | 1 + .../keyboard/layouts/win-fr-French.json | 1 + .../keyboard/change-notifier.ts | 40 + .../keyboard/electron-keyboard-module.ts | 28 + .../menu/electron-main-menu-factory.ts | 68 +- .../src/node/backend-application-module.ts | 10 + .../core/src/node/keyboard/native-layout.ts | 35 + packages/core/src/typings/native-keymap.d.ts | 93 ++ .../src/browser/editor/debug-hover-widget.ts | 2 +- .../src/browser/quick-file-open.ts | 5 +- .../src/browser/mini-browser-content.ts | 3 +- .../monaco/src/browser/monaco-keybinding.ts | 39 +- packages/monaco/src/browser/monaco-loader.ts | 5 +- .../src/browser/monaco-quick-open-service.ts | 187 +++- packages/monaco/src/typings/monaco/index.d.ts | 69 +- .../keybindings-contribution-handler.ts | 4 +- .../browser/terminal-frontend-contribution.ts | 13 +- yarn.lock | 5 + 50 files changed, 2441 insertions(+), 1316 deletions(-) create mode 100644 packages/core/scripts/generate-layout.js create mode 100644 packages/core/src/browser/keyboard/browser-keyboard-module.ts create mode 100644 packages/core/src/browser/keyboard/index.ts create mode 100644 packages/core/src/browser/keyboard/keyboard-browser.ts create mode 100644 packages/core/src/browser/keyboard/keyboard-layout-service.spec.ts create mode 100644 packages/core/src/browser/keyboard/keyboard-layout-service.ts create mode 100644 packages/core/src/browser/keyboard/keys.spec.ts create mode 100644 packages/core/src/browser/keyboard/keys.ts create mode 100644 packages/core/src/common/keyboard/layout-provider.ts create mode 100644 packages/core/src/common/keyboard/layouts/linux-de-German.json create mode 100644 packages/core/src/common/keyboard/layouts/linux-fr-French.json create mode 100644 packages/core/src/common/keyboard/layouts/mac-de-German.json create mode 100644 packages/core/src/common/keyboard/layouts/mac-en-US.json create mode 100644 packages/core/src/common/keyboard/layouts/mac-fr-French.json create mode 100644 packages/core/src/common/keyboard/layouts/win-de-German.json create mode 100644 packages/core/src/common/keyboard/layouts/win-en-US.json create mode 100644 packages/core/src/common/keyboard/layouts/win-fr-French.json create mode 100644 packages/core/src/electron-browser/keyboard/change-notifier.ts create mode 100644 packages/core/src/electron-browser/keyboard/electron-keyboard-module.ts create mode 100644 packages/core/src/node/keyboard/native-layout.ts create mode 100644 packages/core/src/typings/native-keymap.d.ts diff --git a/.travis.yml b/.travis.yml index 46fba9a9add6a..deebff4c865f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,12 +72,15 @@ env: - NODE_OPTIONS="--max_old_space_size=4096" addons: apt: + update: true sources: - ubuntu-toolchain-r-test packages: - g++-4.8 - oracle-java9-set-default - libsecret-1-dev + - libx11-dev + - libxkbfile-dev chrome: stable before_script: - export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start ; diff --git a/configs/base.tsconfig.json b/configs/base.tsconfig.json index 29aa11d314cc9..e0f72aaadf52b 100644 --- a/configs/base.tsconfig.json +++ b/configs/base.tsconfig.json @@ -11,6 +11,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "downlevelIteration": true, + "resolveJsonModule": true, "module": "commonjs", "moduleResolution": "node", "target": "es5", diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index 72da4f5a2e3a7..1cbeb41298e62 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -132,6 +132,7 @@ const applicationName = \`${this.pck.props.frontend.config.applicationName}\`; if (isMaster) { + const nativeKeymap = require('native-keymap'); const Storage = require('electron-store'); const electronStore = new Storage(); @@ -216,6 +217,17 @@ if (isMaster) { newWindow.on('resize', saveWindowStateDelayed); newWindow.on('move', saveWindowStateDelayed); + // Notify the renderer process on keyboard layout change + nativeKeymap.onDidChangeKeyboardLayout(() => { + if (!newWindow.isDestroyed()) { + const newLayout = { + info: nativeKeymap.getCurrentKeyboardLayout(), + mapping: nativeKeymap.getKeyMap() + }; + newWindow.webContents.send('keyboardLayoutChanged', newLayout); + } + }); + if (!!theUrl) { newWindow.loadURL(theUrl); } diff --git a/dev-packages/application-manager/src/rebuild.ts b/dev-packages/application-manager/src/rebuild.ts index 953d1972e6ed9..f983fbf0bf6f9 100644 --- a/dev-packages/application-manager/src/rebuild.ts +++ b/dev-packages/application-manager/src/rebuild.ts @@ -21,7 +21,7 @@ import cp = require('child_process'); export function rebuild(target: 'electron' | 'browser', modules: string[]) { const nodeModulesPath = path.join(process.cwd(), 'node_modules'); const browserModulesPath = path.join(process.cwd(), '.browser_modules'); - const modulesToProcess = modules || ['@theia/node-pty', 'nsfw', 'find-git-repositories']; + const modulesToProcess = modules || ['@theia/node-pty', 'nsfw', 'native-keymap', 'find-git-repositories']; if (target === 'electron' && !fs.existsSync(browserModulesPath)) { const dependencies: { diff --git a/packages/core/package.json b/packages/core/package.json index d11578ded63af..78d31d92ba7af 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,6 +31,7 @@ "inversify": "^4.14.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", + "native-keymap": "^1.2.5", "nsfw": "^1.2.2", "perfect-scrollbar": "^1.3.0", "react": "^16.4.1", @@ -56,6 +57,10 @@ { "frontend": "lib/browser/window/browser-window-module", "frontendElectron": "lib/electron-browser/window/electron-window-module" + }, + { + "frontend": "lib/browser/keyboard/browser-keyboard-module", + "frontendElectron": "lib/electron-browser/keyboard/electron-keyboard-module" } ], "keywords": [ @@ -79,10 +84,12 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test" + "test": "theiaext test", + "generate-layout": "electron ./scripts/generate-layout" }, "devDependencies": { - "@theia/ext-scripts": "^0.5.0" + "@theia/ext-scripts": "^0.5.0", + "minimist": "^1.2.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/core/scripts/generate-layout.js b/packages/core/scripts/generate-layout.js new file mode 100644 index 0000000000000..7ead4e5f4d406 --- /dev/null +++ b/packages/core/scripts/generate-layout.js @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +const parseArgs = require('minimist'); +const nativeKeymap = require('native-keymap'); +const fs = require('fs'); +const electron = require('electron'); + +/* + * Usage: + * yarn generate-layout [--info] [--pretty] [--output file] + * + * --info Print the keyboard layout information; if omitted, the full + * keyboard layout with info and mapping is printed. + * --pretty Pretty-print the JSON output. + * --output file Write the output to the given file instead of stdout. + */ +const args = parseArgs(process.argv); +const printInfo = args.info; +const prettyPrint = args.pretty; +const outFile = args.output; + +let output; +if (printInfo) { + output = nativeKeymap.getCurrentKeyboardLayout(); +} else { + output = { + info: nativeKeymap.getCurrentKeyboardLayout(), + mapping: nativeKeymap.getKeyMap() + }; +} + +const stringOutput = JSON.stringify(output, undefined, prettyPrint ? 2 : undefined); +if (outFile) { + fs.writeFileSync(outFile, stringOutput); +} else { + console.log(stringOutput); +} + +electron.app.quit(); diff --git a/packages/core/src/browser/dialogs.ts b/packages/core/src/browser/dialogs.ts index 8f25d23995f26..fe7c25f875881 100644 --- a/packages/core/src/browser/dialogs.ts +++ b/packages/core/src/browser/dialogs.ts @@ -16,7 +16,7 @@ import { injectable, inject } from 'inversify'; import { Disposable, MaybePromise, CancellationTokenSource } from '../common'; -import { Key } from './keys'; +import { Key } from './keyboard/keys'; import { Widget, BaseWidget, Message } from './widgets'; @injectable() diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index eeff9e95a0a4e..4368107cd719a 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -71,6 +71,7 @@ import { QuickPickServiceImpl } from './quick-open/quick-pick-service-impl'; import { QuickPickService, quickPickServicePath } from '../common/quick-pick-service'; import { ContextKeyService } from './context-key-service'; import { ResourceContextKey } from './resource-context-key'; +import { KeyboardLayoutService } from './keyboard/keyboard-layout-service'; export const frontendApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => { const themeService = ThemeService.get(); @@ -144,6 +145,7 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo bind(MenuModelRegistry).toSelf().inSingletonScope(); bindContributionProvider(bind, MenuContribution); + bind(KeyboardLayoutService).toSelf().inSingletonScope(); bind(KeybindingRegistry).toSelf().inSingletonScope(); bindContributionProvider(bind, KeybindingContext); bindContributionProvider(bind, KeybindingContribution); diff --git a/packages/core/src/browser/frontend-application.ts b/packages/core/src/browser/frontend-application.ts index 4ee111a39809d..d7ce79c129d78 100644 --- a/packages/core/src/browser/frontend-application.ts +++ b/packages/core/src/browser/frontend-application.ts @@ -296,7 +296,7 @@ export class FrontendApplication { * - consider treat commands, keybindings and menus as frontend application contributions */ this.commands.onStart(); - this.keybindings.onStart(); + await this.keybindings.onStart(); this.menus.onStart(); for (const contribution of this.contributions.getContributions()) { if (contribution.onStart) { diff --git a/packages/core/src/browser/index.ts b/packages/core/src/browser/index.ts index 455f7e14bc16d..06dc208c8dfcf 100644 --- a/packages/core/src/browser/index.ts +++ b/packages/core/src/browser/index.ts @@ -16,6 +16,7 @@ export * from './shell'; export * from './frontend-application'; +export * from './keyboard'; export * from './opener-service'; export * from './browser'; export * from './context-menu-renderer'; @@ -31,7 +32,6 @@ export * from './saveable'; export * from './storage-service'; export * from './preferences'; export * from './keybinding'; -export * from './keys'; export * from './status-bar'; export * from './label-provider'; export * from './widget-open-handler'; diff --git a/packages/core/src/browser/keybinding.spec.ts b/packages/core/src/browser/keybinding.spec.ts index bd5966c31d2b9..44fbbb91f8d24 100644 --- a/packages/core/src/browser/keybinding.spec.ts +++ b/packages/core/src/browser/keybinding.spec.ts @@ -13,24 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { enableJSDOM } from '../browser/test/jsdom'; +import { enableJSDOM } from '../browser/test/jsdom'; let disableJSDOM = enableJSDOM(); import { Container, injectable, ContainerModule } from 'inversify'; import { bindContributionProvider } from '../common/contribution-provider'; +import { KeyboardLayoutProvider, NativeKeyboardLayout, KeyboardLayoutChangeNotifier } from '../common/keyboard/layout-provider'; import { ILogger } from '../common/logger'; import { KeybindingRegistry, KeybindingContext, Keybinding, KeybindingContribution, KeybindingScope } from './keybinding'; -import { KeyCode, Key, KeyModifier, KeySequence, EasyKey } from './keys'; +import { KeyCode, Key, KeyModifier, KeySequence } from './keyboard/keys'; +import { KeyboardLayoutService } from './keyboard/keyboard-layout-service'; import { CommandRegistry, CommandService, CommandContribution, Command } from '../common/command'; import { LabelParser } from './label-parser'; import { MockLogger } from '../common/test/mock-logger'; import { StatusBar, StatusBarImpl } from './status-bar/status-bar'; import { FrontendApplicationStateService } from './frontend-application-state'; +import { ContextKeyService } from './context-key-service'; import * as os from '../common/os'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { ContextKeyService } from './context-key-service'; +import { Emitter } from '../common/event'; disableJSDOM(); @@ -49,6 +52,12 @@ before(async () => { /* Mock logger binding*/ bind(ILogger).to(MockLogger); + bind(KeyboardLayoutService).toSelf().inSingletonScope(); + bind(MockKeyboardLayoutProvider).toSelf().inSingletonScope(); + bind(KeyboardLayoutProvider).toService(MockKeyboardLayoutProvider); + bind(MockKeyboardLayoutChangeNotifier).toSelf().inSingletonScope(); + bind(KeyboardLayoutChangeNotifier).toService(MockKeyboardLayoutChangeNotifier); + bindContributionProvider(bind, KeybindingContext); bind(CommandRegistry).toSelf().inSingletonScope(); @@ -96,10 +105,10 @@ describe('keybindings', () => { disableJSDOM(); }); - beforeEach(() => { + beforeEach(async () => { stub = sinon.stub(os, 'isOSX').value(false); keybindingRegistry = testContainer.get(KeybindingRegistry); - keybindingRegistry.onStart(); + await keybindingRegistry.onStart(); }); afterEach(() => { @@ -321,211 +330,6 @@ describe('keybindings', () => { }); }); -describe('keys api', () => { - before(() => { - disableJSDOM = enableJSDOM(); - }); - - after(() => { - disableJSDOM(); - }); - - it('should parse a string to a KeyCode correctly', () => { - - const keycode = KeyCode.parse('ctrl+b'); - expect(keycode.ctrl).to.be.true; - expect(keycode.key).is.equal(Key.KEY_B); - - // Invalid keystroke string - expect(() => KeyCode.parse('ctl+b')).to.throw(Error); - - }); - - it('should parse a string containing special modifiers to a KeyCode correctly', () => { - const stub = sinon.stub(os, 'isOSX').value(false); - const keycode = KeyCode.parse('ctrl+b'); - expect(keycode.ctrl).to.be.true; - expect(keycode.key).is.equal(Key.KEY_B); - - const keycodeOption = KeyCode.parse('option+b'); - expect(keycodeOption.alt).to.be.true; - expect(keycodeOption.key).is.equal(Key.KEY_B); - - expect(() => KeyCode.parse('cmd+b')).to.throw(/OSX only/); - - const keycodeCtrlOrCommand = KeyCode.parse('ctrlcmd+b'); - expect(keycodeCtrlOrCommand.meta).to.be.false; - expect(keycodeCtrlOrCommand.ctrl).to.be.true; - expect(keycodeCtrlOrCommand.key).is.equal(Key.KEY_B); - stub.restore(); - }); - - it('should parse a string containing special modifiers to a KeyCode correctly (macOS)', () => { - KeyCode.resetKeyBindings(); - const stub = sinon.stub(os, 'isOSX').value(true); - const keycode = KeyCode.parse('ctrl+b'); - expect(keycode.ctrl).to.be.true; - expect(keycode.key).is.equal(Key.KEY_B); - - const keycodeOption = KeyCode.parse('option+b'); - expect(keycodeOption.alt).to.be.true; - expect(keycodeOption.key).is.equal(Key.KEY_B); - - const keycodeCommand = KeyCode.parse('cmd+b'); - expect(keycodeCommand.meta).to.be.true; - expect(keycodeCommand.key).is.equal(Key.KEY_B); - - const keycodeCtrlOrCommand = KeyCode.parse('ctrlcmd+b'); - expect(keycodeCtrlOrCommand.meta).to.be.true; - expect(keycodeCtrlOrCommand.ctrl).to.be.false; - expect(keycodeCtrlOrCommand.key).is.equal(Key.KEY_B); - - stub.restore(); - }); - - it('it should serialize a keycode properly with BACKQUOTE + M1', () => { - const stub = sinon.stub(os, 'isOSX').value(true); - let keyCode = KeyCode.createKeyCode({ first: Key.BACKQUOTE, modifiers: [KeyModifier.CtrlCmd] }); - let keyCodeString = keyCode.toString(); - expect(keyCodeString).to.be.equal('meta+`'); - let parsedKeyCode = KeyCode.parse(keyCodeString); - expect(KeyCode.equals(parsedKeyCode, keyCode)).to.be.true; - - sinon.stub(os, 'isOSX').value(false); - keyCode = KeyCode.createKeyCode({ first: Key.BACKQUOTE, modifiers: [KeyModifier.CtrlCmd] }); - keyCodeString = keyCode.toString(); - expect(keyCodeString).to.be.equal('ctrl+`'); - parsedKeyCode = KeyCode.parse(keyCodeString); - expect(KeyCode.equals(parsedKeyCode, keyCode)).to.be.true; - - stub.restore(); - }); - - it('it should serialize a keycode properly with a + M2 + M3', () => { - const keyCode = KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift, KeyModifier.Alt] }); - const keyCodeString = keyCode.toString(); - expect(keyCodeString).to.be.equal('shift+alt+a'); - const parsedKeyCode = KeyCode.parse(keyCodeString); - expect(KeyCode.equals(parsedKeyCode, keyCode)).to.be.true; - }); - - it('the order of the modifiers should not matter when parsing the key code', () => { - const left = KeySequence.parse('shift+alt+a'); - const right = KeySequence.parse('alt+shift+a'); - expect(KeySequence.compare(left, right)).to.be.equal(KeySequence.CompareResult.FULL); - - expect(KeySequence.compare( - [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Alt, KeyModifier.Shift] })], right)).to.be.equal( - KeySequence.CompareResult.FULL); - expect(KeySequence.compare( - left, [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Alt, KeyModifier.Shift] })])).to.be.equal( - KeySequence.CompareResult.FULL); - - expect(KeySequence.compare( - [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift, KeyModifier.Alt] })], right)).to.be.equal( - KeySequence.CompareResult.FULL); - expect(KeySequence.compare( - left, [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift, KeyModifier.Alt] })])).to.be.equal( - KeySequence.CompareResult.FULL); - }); - - it('it should parse ctrl key properly on both OS X and other platforms', () => { - const event = new KeyboardEvent('keydown', { - key: EasyKey.BACKQUOTE.easyString, - code: Key.BACKQUOTE.code, - ctrlKey: true, - }); - const stub = sinon.stub(os, 'isOSX').value(true); - expect(KeyCode.createKeyCode(event).keystroke).to.be.equal('Backquote+M4'); - sinon.stub(os, 'isOSX').value(false); - expect(KeyCode.createKeyCode(event).keystroke).to.be.equal('Backquote+M1'); - stub.restore(); - }); - - it('it should serialize a keycode properly with a + M4', () => { - const stub = sinon.stub(os, 'isOSX').value(true); - const keyCode = KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.MacCtrl] }); - const keyCodeString = keyCode.toString(); - expect(keyCodeString).to.be.equal('ctrl+a'); - const parsedKeyCode = KeyCode.parse(keyCodeString); - expect(KeyCode.equals(parsedKeyCode, keyCode)).to.be.true; - stub.restore(); - }); - - it('it should parse a multi keycode keybinding', () => { - const validKeyCodes = []; - validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })); - validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_C, modifiers: [KeyModifier.CtrlCmd, KeyModifier.Shift] })); - - const parsedKeyCodes = KeySequence.parse('ctrlcmd+a ctrlcmd+shift+c'); - expect(parsedKeyCodes).to.deep.equal(validKeyCodes); - }); - - it('it should parse a multi keycode keybinding with no modifiers', () => { - const validKeyCodes = []; - validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })); - validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_C })); - - const parsedKeyCodes = KeySequence.parse('ctrlcmd+a c'); - expect(parsedKeyCodes).to.deep.equal(validKeyCodes); - }); - - it('it should compare keysequences properly', () => { - let a = KeySequence.parse('ctrlcmd+a'); - let b = KeySequence.parse('ctrlcmd+a t'); - - expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.PARTIAL); - - a = KeySequence.parse('ctrlcmd+a t'); - b = KeySequence.parse('ctrlcmd+a'); - - expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.SHADOW); - - a = KeySequence.parse('ctrlcmd+a t'); - b = KeySequence.parse('ctrlcmd+a b c'); - expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.NONE); - - a = KeySequence.parse('ctrlcmd+a t'); - b = KeySequence.parse('ctrlcmd+a a'); - expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.NONE); - - a = KeySequence.parse('ctrlcmd+a t'); - b = KeySequence.parse('ctrlcmd+a t'); - expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.FULL); - - a = KeySequence.parse('ctrlcmd+a t b'); - b = KeySequence.parse('ctrlcmd+a t b'); - expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.FULL); - }); - - it('it should be a modifier only', () => { - const keyCode = KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd] }); - expect(keyCode).to.be.deep.equal(KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd] })); - expect(keyCode.isModifierOnly()).to.be.true; - }); - - it('it should be multiple modifiers only', () => { - const keyCode = KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd, KeyModifier.Alt] }); - expect(keyCode).to.be.deep.equal(KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd, KeyModifier.Alt] })); - expect(keyCode.isModifierOnly()).to.be.true; - }); - - it('it should translate non US layout chords properly', () => { - // mimic a german layout, i.e. the '/' is on the 'Shift+7'. - const keyCode = new KeyCode(KeyCode.parse('ctrlcmd+shift+7').keystroke, '/'); - const normalized = keyCode.normalizeToUsLayout(); - expect(normalized).to.be.deep.equal(KeyCode.parse('ctrlcmd+/')); - }); - - it('parse bogus keybinding', () => { - const [first, second] = KeySequence.parse(' Ctrl+sHiFt+F10 b '); - expect(first.ctrl).to.be.true; - expect(first.shift).to.be.true; - expect(first.key).is.equal(Key.F10); - expect(second.key).is.equal(Key.KEY_B); - }); -}); - const TEST_COMMAND: Command = { id: 'test.command' }; @@ -539,7 +343,25 @@ const TEST_COMMAND_SHADOW: Command = { }; @injectable() -export class TestContribution implements CommandContribution, KeybindingContribution { +class MockKeyboardLayoutProvider implements KeyboardLayoutProvider { + getNativeLayout(): Promise { + return Promise.resolve({ + info: { id: 'mock', lang: 'en' }, + mapping: {} + }); + } +} + +@injectable() +class MockKeyboardLayoutChangeNotifier implements KeyboardLayoutChangeNotifier { + private emitter = new Emitter(); + get onNativeLayoutChanged() { + return this.emitter.event; + } +} + +@injectable() +class TestContribution implements CommandContribution, KeybindingContribution { registerCommands(commands: CommandRegistry): void { commands.registerCommand(TEST_COMMAND); diff --git a/packages/core/src/browser/keybinding.ts b/packages/core/src/browser/keybinding.ts index 946ff6605b178..5187426925455 100644 --- a/packages/core/src/browser/keybinding.ts +++ b/packages/core/src/browser/keybinding.ts @@ -15,12 +15,14 @@ ********************************************************************************/ import { injectable, inject, named } from 'inversify'; +import { isOSX } from '../common/os'; +import { Emitter } from '../common/event'; import { CommandRegistry } from '../common/command'; -import { KeyCode, KeySequence } from './keys'; +import { KeyCode, KeySequence, Key } from './keyboard/keys'; +import { KeyboardLayoutService } from './keyboard/keyboard-layout-service'; import { ContributionProvider } from '../common/contribution-provider'; import { ILogger } from '../common/logger'; import { StatusBarAlignment, StatusBar } from './status-bar/status-bar'; -import { isOSX } from '../common/os'; import { ContextKeyService } from './context-key-service'; export enum KeybindingScope { @@ -51,12 +53,6 @@ export namespace Keybinding { return JSON.stringify(copy); } - /* Return a user visible representation of a keybinding. */ - export function acceleratorFor(keybinding: Keybinding, separator: string = ' ') { - const keyCodesString = keybinding.keybinding.split(' '); - return KeySequence.acceleratorFor(keyCodesString.map(k => KeyCode.parse(k)), separator); - } - /* Determine whether object is a KeyBinding */ // tslint:disable-next-line:no-any export function is(arg: Keybinding | any): arg is Keybinding { @@ -65,9 +61,9 @@ export namespace Keybinding { } export interface Keybinding { - /* Command identifier, this needs to be a unique string. */ + /** Command identifier, this needs to be a unique string. */ command: string; - /* Keybinding string as defined in packages/keymaps/README.md. */ + /** Keybinding string as defined in packages/keymaps/README.md. */ keybinding: string; /** * The optional keybinding context where this binding belongs to. @@ -81,6 +77,16 @@ export interface Keybinding { when?: string; } +export interface ResolvedKeybinding extends Keybinding { + /** + * The KeyboardLayoutService may transform the `keybinding` depending on the + * user's keyboard layout. This property holds the transformed keybinding that + * should be used in the UI. The value is undefined if the KeyboardLayoutService + * has not been called yet to resolve the keybinding. + */ + resolved?: KeyCode[]; +} + export interface ScopedKeybinding extends Keybinding { /** Current keybinding scope */ scope?: KeybindingScope; @@ -122,6 +128,9 @@ export class KeybindingRegistry { protected readonly contexts: { [id: string]: KeybindingContext } = {}; protected readonly keymaps: Keybinding[][] = [...Array(KeybindingScope.length)].map(() => []); + @inject(KeyboardLayoutService) + protected readonly keyboardLayoutService: KeyboardLayoutService; + @inject(ContributionProvider) @named(KeybindingContext) protected readonly contextProvider: ContributionProvider; @@ -140,7 +149,12 @@ export class KeybindingRegistry { @inject(ContextKeyService) protected readonly whenContextService: ContextKeyService; - onStart(): void { + async onStart(): Promise { + await this.keyboardLayoutService.initialize(); + this.keyboardLayoutService.onKeyboardLayoutChanged(newLayout => { + this.clearResolvedKeybindings(); + this.keybindingsChanged.fire(undefined); + }); this.registerContext(KeybindingContexts.NOOP_CONTEXT); this.registerContext(KeybindingContexts.DEFAULT_CONTEXT); this.registerContext(...this.contextProvider.getContributions()); @@ -149,6 +163,15 @@ export class KeybindingRegistry { } } + protected keybindingsChanged = new Emitter(); + + /** + * Event that is fired when the resolved keybindings change due to a different keyboard layout. + */ + get onKeybindingsChanged() { + return this.keybindingsChanged.event; + } + /** * Registers the keybinding context arguments into the application. Fails when an already registered * context is being registered. @@ -217,6 +240,7 @@ export class KeybindingRegistry { protected doRegisterKeybinding(binding: Keybinding, scope: KeybindingScope = KeybindingScope.DEFAULT) { try { + this.resolveKeybinding(binding); if (this.containsKeybinding(this.keymaps[scope], binding)) { throw new Error(`"${binding.keybinding}" is in collision with something else [scope:${scope}]`); } @@ -226,6 +250,31 @@ export class KeybindingRegistry { } } + /** + * Ensure that the `resolved` property of the given binding is set by calling the KeyboardLayoutService. + */ + resolveKeybinding(binding: ResolvedKeybinding): KeyCode[] { + if (!binding.resolved) { + const sequence = KeySequence.parse(binding.keybinding); + binding.resolved = sequence.map(code => this.keyboardLayoutService.resolveKeyCode(code)); + } + return binding.resolved; + } + + /** + * Clear all `resolved` properties of registered keybindings so the KeyboardLayoutService is called + * again to resolve them. This is necessary when the user's keyboard layout has changed. + */ + protected clearResolvedKeybindings(): void { + for (let i = KeybindingScope.DEFAULT; i < KeybindingScope.END; i++) { + const bindings = this.keymaps[i]; + for (let j = 0; j < bindings.length; j++) { + const binding = bindings[j] as ResolvedKeybinding; + binding.resolved = undefined; + } + } + } + /** * Checks for keySequence collisions in a list of Keybindings * @@ -233,9 +282,9 @@ export class KeybindingRegistry { * @param binding the keybinding to test collisions for */ containsKeybinding(bindings: Keybinding[], binding: Keybinding): boolean { - const collisions = this.getKeySequenceCollisions(bindings, KeySequence.parse( - this.getCurrentPlatformKeybinding(binding.keybinding)) - ).filter(b => b.context === binding.context); + const bindingKeySequence = this.resolveKeybinding(binding); + const collisions = this.getKeySequenceCollisions(bindings, bindingKeySequence) + .filter(b => b.context === binding.context); if (collisions.full.length > 0) { this.logger.warn('Collided keybinding is ignored; ', @@ -263,13 +312,70 @@ export class KeybindingRegistry { } /** - * Converts special `ctrlcmd` modifier back to `ctrl` for non-OSX users in a keybinding string. - * (`ctrlcmd` is mapped to the same actual key as `ctrl` under non-OSX users) - * - * @param keybinding The keybinding string to convert. + * Return a user visible representation of a keybinding. + */ + acceleratorFor(keybinding: Keybinding, separator: string = ' '): string[] { + const bindingKeySequence = this.resolveKeybinding(keybinding); + return this.acceleratorForSequence(bindingKeySequence, separator); + } + + /** + * Return a user visible representation of a key sequence. + */ + acceleratorForSequence(keySequence: KeySequence, separator: string = ' '): string[] { + return keySequence.map(keyCode => this.acceleratorForKeyCode(keyCode, separator)); + } + + /** + * Return a user visible representation of a key code (a key with modifiers). + */ + acceleratorForKeyCode(keyCode: KeyCode, separator: string = ' '): string { + const keyCodeResult = []; + if (keyCode.meta && isOSX) { + keyCodeResult.push('Cmd'); + } + if (keyCode.ctrl) { + keyCodeResult.push('Ctrl'); + } + if (keyCode.alt) { + keyCodeResult.push('Alt'); + } + if (keyCode.shift) { + keyCodeResult.push('Shift'); + } + if (keyCode.key) { + keyCodeResult.push(this.acceleratorForKey(keyCode.key)); + } + return keyCodeResult.join(separator); + } + + /** + * Return a user visible representation of a single key. */ - protected getCurrentPlatformKeybinding(keybinding: string): string { - return isOSX ? keybinding : keybinding.replace(/\bctrlcmd\b/, 'ctrl'); + acceleratorForKey(key: Key): string { + if (isOSX) { + if (key === Key.ARROW_LEFT) { + return '←'; + } + if (key === Key.ARROW_RIGHT) { + return '→'; + } + if (key === Key.ARROW_UP) { + return '↑'; + } + if (key === Key.ARROW_DOWN) { + return '↓'; + } + } + const keyString = this.keyboardLayoutService.getKeyboardCharacter(key); + if (key.keyCode >= Key.KEY_A.keyCode && key.keyCode <= Key.KEY_Z.keyCode || + key.keyCode >= Key.F1.keyCode && key.keyCode <= Key.F24.keyCode) { + return keyString.toUpperCase(); + } else if (keyString.length > 1) { + return keyString.charAt(0).toUpperCase() + keyString.slice(1); + } else { + return keyString; + } } /** @@ -281,8 +387,8 @@ export class KeybindingRegistry { protected getKeybindingCollisions(bindings: Keybinding[], binding: Keybinding): KeybindingRegistry.KeybindingsResult { const result = new KeybindingRegistry.KeybindingsResult(); try { - const keySequence = KeySequence.parse(this.getCurrentPlatformKeybinding(binding.keybinding)); - result.merge(this.getKeySequenceCollisions(bindings, keySequence)); + const bindingKeySequence = this.resolveKeybinding(binding); + result.merge(this.getKeySequenceCollisions(bindings, bindingKeySequence)); } catch (error) { this.logger.warn(error); } @@ -293,48 +399,28 @@ export class KeybindingRegistry { * Finds collisions for a key sequence inside a list of bindings (error-free) * * @param bindings the reference bindings - * @param keySequence the sequence to match + * @param candidate the sequence to match */ - protected getKeySequenceCollisions(bindings: Keybinding[], keySequence: KeyCode[]): KeybindingRegistry.KeybindingsResult { + protected getKeySequenceCollisions(bindings: Keybinding[], candidate: KeySequence): KeybindingRegistry.KeybindingsResult { const result = new KeybindingRegistry.KeybindingsResult(); - - /** - * compare the given KeySequence with a particular binding - */ - function compareBinding(candidate: KeyCode[], binding: Keybinding, isNormalized: boolean = false) { - const bindingKeySequence = KeySequence.parse(binding.keybinding); - const compareResult = KeySequence.compare(candidate, bindingKeySequence); - switch (compareResult) { - case KeySequence.CompareResult.FULL: { - if (isNormalized) { - result.normalized.push(binding); - } else { + for (const binding of bindings) { + try { + const bindingKeySequence = this.resolveKeybinding(binding); + const compareResult = KeySequence.compare(candidate, bindingKeySequence); + switch (compareResult) { + case KeySequence.CompareResult.FULL: { result.full.push(binding); + break; } - break; - } - case KeySequence.CompareResult.PARTIAL: { - result.partial.push(binding); - break; - } - case KeySequence.CompareResult.SHADOW: { - result.shadow.push(binding); - break; - } - default: { - // no match. Let's try with a US keborad normalized version if there is one. - const normalizedUs = candidate.map(k => k.normalizeToUsLayout()); - if (normalizedUs.indexOf(undefined) === -1) { - compareBinding(normalizedUs as KeyCode[], binding, true); + case KeySequence.CompareResult.PARTIAL: { + result.partial.push(binding); + break; + } + case KeySequence.CompareResult.SHADOW: { + result.shadow.push(binding); + break; } - break; } - } - } - - for (const registeredBinding of bindings) { - try { - compareBinding(keySequence, registeredBinding); } catch (error) { this.logger.warn(error); } @@ -356,15 +442,12 @@ export class KeybindingRegistry { matches.full = matches.full.filter( binding => this.getKeybindingCollisions(result.full, binding).full.length === 0); - matches.normalized = matches.normalized.filter( - binding => this.getKeybindingCollisions(result.normalized, binding).normalized.length === 0); matches.partial = matches.partial.filter( binding => this.getKeybindingCollisions(result.partial, binding).partial.length === 0); result.merge(matches); } this.sortKeybindingsByPriority(result.full); - this.sortKeybindingsByPriority(result.normalized); this.sortKeybindingsByPriority(result.partial); return result; } @@ -543,7 +626,7 @@ export class KeybindingRegistry { event.stopPropagation(); this.statusBar.setElement('keybinding-status', { - text: `(${KeySequence.acceleratorFor(this.keySequence, '+')}) was pressed, waiting for more keys`, + text: `(${this.acceleratorForSequence(this.keySequence, '+')}) was pressed, waiting for more keys`, alignment: StatusBarAlignment.LEFT, priority: 2 }); @@ -591,7 +674,6 @@ export class KeybindingRegistry { export namespace KeybindingRegistry { export class KeybindingsResult { full: Keybinding[] = []; - normalized: Keybinding[] = []; partial: Keybinding[] = []; shadow: Keybinding[] = []; @@ -603,7 +685,6 @@ export namespace KeybindingRegistry { */ merge(other: KeybindingsResult): KeybindingsResult { this.full.push(...other.full); - this.normalized.push(...other.normalized); this.partial.push(...other.partial); this.shadow.push(...other.shadow); return this; @@ -618,7 +699,6 @@ export namespace KeybindingRegistry { filter(fn: (binding: Keybinding) => boolean): KeybindingsResult { const result = new KeybindingsResult(); result.full = this.full.filter(fn); - result.normalized = this.normalized.filter(fn); result.partial = this.partial.filter(fn); result.shadow = this.shadow.filter(fn); return result; diff --git a/packages/core/src/browser/keyboard/browser-keyboard-module.ts b/packages/core/src/browser/keyboard/browser-keyboard-module.ts new file mode 100644 index 0000000000000..fa65ae288bbbb --- /dev/null +++ b/packages/core/src/browser/keyboard/browser-keyboard-module.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { KeyboardLayoutProvider, KeyboardLayoutChangeNotifier } from '../../common/keyboard/layout-provider'; +import { BrowserKeyboardLayoutProvider } from './keyboard-browser'; + +export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(BrowserKeyboardLayoutProvider).toSelf().inSingletonScope(); + bind(KeyboardLayoutProvider).toService(BrowserKeyboardLayoutProvider); + bind(KeyboardLayoutChangeNotifier).toService(BrowserKeyboardLayoutProvider); +}); diff --git a/packages/core/src/browser/keyboard/index.ts b/packages/core/src/browser/keyboard/index.ts new file mode 100644 index 0000000000000..a8857a48f7951 --- /dev/null +++ b/packages/core/src/browser/keyboard/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export * from './keys'; +export * from './keyboard-layout-service'; +export * from './keyboard-browser'; diff --git a/packages/core/src/browser/keyboard/keyboard-browser.ts b/packages/core/src/browser/keyboard/keyboard-browser.ts new file mode 100644 index 0000000000000..db2463043739d --- /dev/null +++ b/packages/core/src/browser/keyboard/keyboard-browser.ts @@ -0,0 +1,179 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, postConstruct } from 'inversify'; +import { isOSX, isWindows } from '../../common/os'; +import { Emitter } from '../../common/event'; +import { NativeKeyboardLayout, KeyboardLayoutProvider, KeyboardLayoutChangeNotifier } from '../../common/keyboard/layout-provider'; + +@injectable() +export class BrowserKeyboardLayoutProvider implements KeyboardLayoutProvider, KeyboardLayoutChangeNotifier { + + private linuxFrench = require('../../../src/common/keyboard/layouts/linux-fr-French.json'); + private linuxGerman = require('../../../src/common/keyboard/layouts/linux-de-German.json'); + private macUS = require('../../../src/common/keyboard/layouts/mac-en-US.json'); + private macFrench = require('../../../src/common/keyboard/layouts/mac-fr-French.json'); + private macGerman = require('../../../src/common/keyboard/layouts/mac-de-German.json'); + private winUS = require('../../../src/common/keyboard/layouts/win-en-US.json'); + private winFrench = require('../../../src/common/keyboard/layouts/win-fr-French.json'); + private winGerman = require('../../../src/common/keyboard/layouts/win-de-German.json'); + + protected get allLayouts(): NativeKeyboardLayout[] { + return [ + this.winUS, this.macUS, this.winFrench, this.macFrench, this.winGerman, this.macGerman + ]; + } + + protected nativeLayoutChanged = new Emitter(); + + get onNativeLayoutChanged() { + return this.nativeLayoutChanged.event; + } + + @postConstruct() + protected initialize() { + const keyboard = (navigator as NavigatorExtension).keyboard; + if (keyboard && keyboard.addEventListener) { + keyboard.addEventListener('layoutchange', async () => { + const newLayout = await this.getNativeLayout(); + this.nativeLayoutChanged.fire(newLayout); + }); + } + } + + getNativeLayout(): Promise { + const keyboard = (navigator as NavigatorExtension).keyboard; + if (keyboard && keyboard.getLayoutMap) { + return keyboard.getLayoutMap().then(layoutMap => this.getFromLayoutMap(layoutMap)); + } else if (navigator.language) { + return Promise.resolve(this.getFromLanguage(navigator.language)); + } else { + return Promise.resolve(isOSX ? this.macUS : this.winUS); + } + } + + /** + * @param layoutMap a keyboard layout map according to https://wicg.github.io/keyboard-map/ + */ + protected getFromLayoutMap(layoutMap: KeyboardLayoutMap): NativeKeyboardLayout { + const tester = new KeyboardTester(this.allLayouts); + for (const [code, key] of layoutMap.entries()) { + tester.updateScores({ code, key }); + } + const result = tester.getTopScoringCandidates(); + if (result.length > 0) { + return result[0]; + } else { + return isOSX ? this.macUS : this.winUS; + } + } + + /** + * @param language an IETF BCP 47 language tag + */ + protected getFromLanguage(language: string): NativeKeyboardLayout { + if (isOSX) { + if (language.startsWith('de')) { + return this.macGerman; + } else if (language.startsWith('fr')) { + return this.macFrench; + } else { + return this.macUS; + } + } else if (isWindows) { + if (language.startsWith('de')) { + return this.winGerman; + } else if (language.startsWith('fr')) { + return this.winFrench; + } else { + return this.winUS; + } + } else { + if (language.startsWith('de')) { + return this.linuxGerman; + } else if (language.startsWith('fr')) { + return this.linuxFrench; + } + } + return { + info: { 'model': 'pc105', 'layout': 'us', 'variant': '', 'options': '', 'rules': '' }, + mapping: {} + }; + } + +} + +interface NavigatorExtension extends Navigator { + keyboard: Keyboard; +} + +interface Keyboard { + getLayoutMap(): Promise; + addEventListener(type: 'layoutchange', listener: EventListenerOrEventListenerObject): void; +} + +type KeyboardLayoutMap = Map; + +interface KeyboardTestInput { + code: string; + key: string; + shiftKey?: boolean; + ctrlKey?: boolean; + altKey?: boolean; +} + +class KeyboardTester { + + private readonly scores: number[]; + + constructor(private readonly candidates: NativeKeyboardLayout[]) { + this.scores = this.candidates.map(() => 0); + } + + testCandidate(candidate: NativeKeyboardLayout, input: KeyboardTestInput): number { + let property: 'value' | 'withShift' | 'withAltGr' | 'withShiftAltGr'; + if (input.shiftKey && input.altKey) { + property = 'withShiftAltGr'; + } else if (input.shiftKey) { + property = 'withShift'; + } else if (input.altKey) { + property = 'withAltGr'; + } else { + property = 'value'; + } + const keyMapping = candidate.mapping[input.code]; + if (keyMapping && keyMapping[property]) { + return keyMapping[property] === input.key ? 1 : 0; + } else { + return 0; + } + } + + updateScores(input: KeyboardTestInput): void { + for (let i = 0; i < this.candidates.length; i++) { + this.scores[i] += this.testCandidate(this.candidates[i], input); + } + } + + getTopScoringCandidates() { + let maxScore = 0; + for (let i = 0; i < this.scores.length; i++) { + maxScore = Math.max(maxScore, this.scores[i]); + } + return this.candidates.filter((c, i) => this.scores[i] === maxScore); + } + +} diff --git a/packages/core/src/browser/keyboard/keyboard-layout-service.spec.ts b/packages/core/src/browser/keyboard/keyboard-layout-service.spec.ts new file mode 100644 index 0000000000000..9e11bd3d90878 --- /dev/null +++ b/packages/core/src/browser/keyboard/keyboard-layout-service.spec.ts @@ -0,0 +1,121 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Container, injectable } from 'inversify'; +import { Emitter } from '../../common/event'; +import { KeyCode } from './keys'; +import { KeyboardLayoutService } from './keyboard-layout-service'; +import { KeyboardLayoutProvider, NativeKeyboardLayout, KeyboardLayoutChangeNotifier } from '../../common/keyboard/layout-provider'; +import * as os from '../../common/os'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; + +describe('keyboard layout service', function () { + + let stubOSX: sinon.SinonStub; + let stubWindows: sinon.SinonStub; + + const setup = async (layout: NativeKeyboardLayout, system: 'mac' | 'win' | 'linux') => { + switch (system) { + case 'mac': + stubOSX = sinon.stub(os, 'isOSX').value(true); + stubWindows = sinon.stub(os, 'isWindows').value(false); + break; + case 'win': + stubOSX = sinon.stub(os, 'isOSX').value(false); + stubWindows = sinon.stub(os, 'isWindows').value(true); + break; + default: + stubOSX = sinon.stub(os, 'isOSX').value(false); + stubWindows = sinon.stub(os, 'isWindows').value(false); + } + const container = new Container(); + container.bind(KeyboardLayoutService).toSelf().inSingletonScope(); + @injectable() + class MockLayoutProvider implements KeyboardLayoutProvider, KeyboardLayoutChangeNotifier { + emitter = new Emitter(); + get onNativeLayoutChanged() { + return this.emitter.event; + } + getNativeLayout(): Promise { + return Promise.resolve(layout); + } + } + container.bind(KeyboardLayoutProvider).to(MockLayoutProvider); + container.bind(KeyboardLayoutChangeNotifier).to(MockLayoutProvider); + const service = container.get(KeyboardLayoutService); + await service.initialize(); + return service; + }; + + afterEach(() => { + stubOSX.restore(); + stubWindows.restore(); + }); + + it('resolves correct key bindings with German Mac layout', async () => { + const macGerman = require('../../../src/common/keyboard/layouts/mac-de-German.json'); + const service = await setup(macGerman, 'mac'); + + const toggleComment = service.resolveKeyCode(KeyCode.createKeyCode('Slash+M1')); + chai.expect(toggleComment.toString()).to.equal('meta+shift+7'); + chai.expect(service.getKeyboardCharacter(toggleComment.key!)).to.equal('7'); + + const indentLine = service.resolveKeyCode(KeyCode.createKeyCode('BracketRight+M1')); + chai.expect(indentLine.toString()).to.equal('meta+alt+ctrl+6'); + chai.expect(service.getKeyboardCharacter(indentLine.key!)).to.equal('6'); + }); + + it('resolves correct key bindings with French Mac layout', async () => { + const macFrench = require('../../../src/common/keyboard/layouts/mac-fr-French.json'); + const service = await setup(macFrench, 'mac'); + + const toggleComment = service.resolveKeyCode(KeyCode.createKeyCode('Slash+M1')); + chai.expect(toggleComment.toString()).to.equal('meta+shift+.'); + chai.expect(service.getKeyboardCharacter(toggleComment.key!)).to.equal(':'); + + const indentLine = service.resolveKeyCode(KeyCode.createKeyCode('BracketRight+M1')); + chai.expect(indentLine.toString()).to.equal('meta+shift+alt+ctrl+-'); + chai.expect(service.getKeyboardCharacter(indentLine.key!)).to.equal(')'); + }); + + it('resolves correct key bindings with German Windows layout', async () => { + const winGerman = require('../../../src/common/keyboard/layouts/win-de-German.json'); + const service = await setup(winGerman, 'win'); + + const toggleComment = service.resolveKeyCode(KeyCode.createKeyCode('Slash+M1')); + chai.expect(toggleComment.toString()).to.equal('ctrl+\\'); + chai.expect(service.getKeyboardCharacter(toggleComment.key!)).to.equal('#'); + + const indentLine = service.resolveKeyCode(KeyCode.createKeyCode('BracketRight+M1')); + chai.expect(indentLine.toString()).to.equal('ctrl+='); + chai.expect(service.getKeyboardCharacter(indentLine.key!)).to.equal('´'); + }); + + it('resolves correct key bindings with French Windows layout', async () => { + const winFrench = require('../../../src/common/keyboard/layouts/win-fr-French.json'); + const service = await setup(winFrench, 'win'); + + const toggleComment = service.resolveKeyCode(KeyCode.createKeyCode('Slash+M1')); + chai.expect(toggleComment.toString()).to.equal('ctrl+.'); + chai.expect(service.getKeyboardCharacter(toggleComment.key!)).to.equal(':'); + + const indentLine = service.resolveKeyCode(KeyCode.createKeyCode('BracketRight+M1')); + chai.expect(indentLine.toString()).to.equal('ctrl+['); + chai.expect(service.getKeyboardCharacter(indentLine.key!)).to.equal('^'); + }); + +}); diff --git a/packages/core/src/browser/keyboard/keyboard-layout-service.ts b/packages/core/src/browser/keyboard/keyboard-layout-service.ts new file mode 100644 index 0000000000000..f0bd12b7d162b --- /dev/null +++ b/packages/core/src/browser/keyboard/keyboard-layout-service.ts @@ -0,0 +1,425 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject } from 'inversify'; +import { IWindowsKeyMapping } from 'native-keymap'; +import { isWindows } from '../../common/os'; +import { NativeKeyboardLayout, KeyboardLayoutProvider, KeyboardLayoutChangeNotifier } from '../../common/keyboard/layout-provider'; +import { Emitter } from '../../common/event'; +import { KeyCode, Key } from './keys'; + +export interface KeyboardLayout { + /** + * Mapping of standard US keyboard keys to the actual key codes to use. + * See `KeyboardLayoutService.getCharacterIndex` for the index computation. + */ + readonly key2KeyCode: KeyCode[]; + /** + * Mapping of KeyboardEvent codes to the characters shown on the user's keyboard + * for the respective keys. + */ + readonly code2Character: { [code: string]: string }; +} + +@injectable() +export class KeyboardLayoutService { + + @inject(KeyboardLayoutProvider) + protected readonly layoutProvider: KeyboardLayoutProvider; + + @inject(KeyboardLayoutChangeNotifier) + protected readonly layoutChangeNotifier: KeyboardLayoutChangeNotifier; + + private currentLayout?: KeyboardLayout; + + protected updateLayout(newLayout: NativeKeyboardLayout): KeyboardLayout { + const transformed = this.transformNativeLayout(newLayout); + this.currentLayout = transformed; + this.keyboardLayoutChanged.fire(transformed); + return transformed; + } + + protected keyboardLayoutChanged = new Emitter(); + + get onKeyboardLayoutChanged() { + return this.keyboardLayoutChanged.event; + } + + async initialize(): Promise { + this.layoutChangeNotifier.onNativeLayoutChanged(newLayout => this.updateLayout(newLayout)); + const initialLayout = await this.layoutProvider.getNativeLayout(); + this.updateLayout(initialLayout); + } + + /** + * Resolve a KeyCode of a keybinding using the current keyboard layout. + * If no keyboard layout has been detected or the layout does not contain the + * key used in the KeyCode, the KeyCode is returned unchanged. + */ + resolveKeyCode(inCode: KeyCode): KeyCode { + const layout = this.currentLayout; + if (layout && inCode.key) { + for (let shift = 0; shift <= 1; shift++) { + const index = this.getCharacterIndex(inCode.key, !!shift); + const mappedCode = layout.key2KeyCode[index]; + if (mappedCode) { + const transformed = this.transformKeyCode(inCode, mappedCode, !!shift); + if (transformed) { + return transformed; + } + } + } + } + return inCode; + } + + /** + * Return the character shown on the user's keyboard for the given key. + * Use this to determine UI representations of keybindings. + */ + getKeyboardCharacter(key: Key): string { + const layout = this.currentLayout; + if (layout) { + const value = layout.code2Character[key.code]; + if (value) { + return value; + } + } + return key.easyString; + } + + protected transformKeyCode(inCode: KeyCode, mappedCode: KeyCode, keyNeedsShift: boolean): KeyCode | undefined { + if (!inCode.shift && keyNeedsShift) { + return undefined; + } + if (mappedCode.alt && (inCode.alt || inCode.ctrl || inCode.shift && !keyNeedsShift)) { + return undefined; + } + return new KeyCode({ + key: mappedCode.key, + meta: inCode.meta, + ctrl: inCode.ctrl || mappedCode.alt, + shift: inCode.shift && !keyNeedsShift || mappedCode.shift, + alt: inCode.alt || mappedCode.alt + }); + } + + protected transformNativeLayout(nativeLayout: NativeKeyboardLayout): KeyboardLayout { + const key2KeyCode: KeyCode[] = new Array(2 * (Key.MAX_KEY_CODE + 1)); + const code2Character: { [code: string]: string } = {}; + const mapping = nativeLayout.mapping; + for (const code in mapping) { + if (mapping.hasOwnProperty(code)) { + const keyMapping = mapping[code]; + const mappedKey = Key.getKey(code); + if (this.isValidKey(mappedKey)) { + if (isWindows) { + this.addWindowsKeyMapping(key2KeyCode, mappedKey, (keyMapping as IWindowsKeyMapping).vkey, keyMapping.value); + } else { + if (keyMapping.value) { + this.addKeyMapping(key2KeyCode, mappedKey, keyMapping.value, false, false); + } + if (keyMapping.withShift) { + this.addKeyMapping(key2KeyCode, mappedKey, keyMapping.withShift, true, false); + } + if (keyMapping.withAltGr) { + this.addKeyMapping(key2KeyCode, mappedKey, keyMapping.withAltGr, false, true); + } + if (keyMapping.withShiftAltGr) { + this.addKeyMapping(key2KeyCode, mappedKey, keyMapping.withShiftAltGr, true, true); + } + } + } + if (keyMapping.value) { + code2Character[code] = keyMapping.value; + } + } + } + return { key2KeyCode, code2Character }; + } + + protected isValidKey(key?: Key): key is Key { + return key !== undefined + && key !== Key.ADD + && key !== Key.SUBTRACT + && key !== Key.MULTIPLY + && key !== Key.DIVIDE + && key !== Key.DECIMAL; + } + + private addKeyMapping(key2KeyCode: KeyCode[], mappedKey: Key, value: string, shift: boolean, alt: boolean): void { + const key = VALUE_TO_KEY[value]; + if (key) { + const index = this.getCharacterIndex(key.key, key.shift); + if (key2KeyCode[index] === undefined) { + key2KeyCode[index] = new KeyCode({ + key: mappedKey, + shift, + alt, + character: value + }); + } + } + } + + private addWindowsKeyMapping(key2KeyCode: KeyCode[], mappedKey: Key, vkey: string, value: string) { + const key = VKEY_TO_KEY[vkey]; + if (key) { + const index = this.getCharacterIndex(key); + if (key2KeyCode[index] === undefined) { + key2KeyCode[index] = new KeyCode({ + key: mappedKey, + character: value + }); + } + } + } + + protected getCharacterIndex(key: Key, shift?: boolean): number { + if (shift) { + return Key.MAX_KEY_CODE + key.keyCode + 1; + } else { + return key.keyCode; + } + } + +} + +/** + * Mapping of character values to the corresponding keys on a standard US keyboard layout. + */ +const VALUE_TO_KEY: { [value: string]: { key: Key, shift?: boolean } } = { + '`': { key: Key.BACKQUOTE }, + '~': { key: Key.BACKQUOTE, shift: true }, + '1': { key: Key.DIGIT1 }, + '!': { key: Key.DIGIT1, shift: true }, + '2': { key: Key.DIGIT2 }, + '@': { key: Key.DIGIT2, shift: true }, + '3': { key: Key.DIGIT3 }, + '#': { key: Key.DIGIT3, shift: true }, + '4': { key: Key.DIGIT4 }, + '$': { key: Key.DIGIT4, shift: true }, + '5': { key: Key.DIGIT5 }, + '%': { key: Key.DIGIT5, shift: true }, + '6': { key: Key.DIGIT6 }, + '^': { key: Key.DIGIT6, shift: true }, + '7': { key: Key.DIGIT7 }, + '&': { key: Key.DIGIT7, shift: true }, + '8': { key: Key.DIGIT8 }, + '*': { key: Key.DIGIT8, shift: true }, + '9': { key: Key.DIGIT9 }, + '(': { key: Key.DIGIT9, shift: true }, + '0': { key: Key.DIGIT0 }, + ')': { key: Key.DIGIT0, shift: true }, + '-': { key: Key.MINUS }, + '_': { key: Key.MINUS, shift: true }, + '=': { key: Key.EQUAL }, + '+': { key: Key.EQUAL, shift: true }, + + 'a': { key: Key.KEY_A }, + 'A': { key: Key.KEY_A, shift: true }, + 'b': { key: Key.KEY_B }, + 'B': { key: Key.KEY_B, shift: true }, + 'c': { key: Key.KEY_C }, + 'C': { key: Key.KEY_C, shift: true }, + 'd': { key: Key.KEY_D }, + 'D': { key: Key.KEY_D, shift: true }, + 'e': { key: Key.KEY_E }, + 'E': { key: Key.KEY_E, shift: true }, + 'f': { key: Key.KEY_F }, + 'F': { key: Key.KEY_F, shift: true }, + 'g': { key: Key.KEY_G }, + 'G': { key: Key.KEY_G, shift: true }, + 'h': { key: Key.KEY_H }, + 'H': { key: Key.KEY_H, shift: true }, + 'i': { key: Key.KEY_I }, + 'I': { key: Key.KEY_I, shift: true }, + 'j': { key: Key.KEY_J }, + 'J': { key: Key.KEY_J, shift: true }, + 'k': { key: Key.KEY_K }, + 'K': { key: Key.KEY_K, shift: true }, + 'l': { key: Key.KEY_L }, + 'L': { key: Key.KEY_L, shift: true }, + 'm': { key: Key.KEY_M }, + 'M': { key: Key.KEY_M, shift: true }, + 'n': { key: Key.KEY_N }, + 'N': { key: Key.KEY_N, shift: true }, + 'o': { key: Key.KEY_O }, + 'O': { key: Key.KEY_O, shift: true }, + 'p': { key: Key.KEY_P }, + 'P': { key: Key.KEY_P, shift: true }, + 'q': { key: Key.KEY_Q }, + 'Q': { key: Key.KEY_Q, shift: true }, + 'r': { key: Key.KEY_R }, + 'R': { key: Key.KEY_R, shift: true }, + 's': { key: Key.KEY_S }, + 'S': { key: Key.KEY_S, shift: true }, + 't': { key: Key.KEY_T }, + 'T': { key: Key.KEY_T, shift: true }, + 'u': { key: Key.KEY_U }, + 'U': { key: Key.KEY_U, shift: true }, + 'v': { key: Key.KEY_V }, + 'V': { key: Key.KEY_V, shift: true }, + 'w': { key: Key.KEY_W }, + 'W': { key: Key.KEY_W, shift: true }, + 'x': { key: Key.KEY_X }, + 'X': { key: Key.KEY_X, shift: true }, + 'y': { key: Key.KEY_Y }, + 'Y': { key: Key.KEY_Y, shift: true }, + 'z': { key: Key.KEY_Z }, + 'Z': { key: Key.KEY_Z, shift: true }, + + '[': { key: Key.BRACKET_LEFT }, + '{': { key: Key.BRACKET_LEFT, shift: true }, + ']': { key: Key.BRACKET_RIGHT }, + '}': { key: Key.BRACKET_RIGHT, shift: true }, + ';': { key: Key.SEMICOLON }, + ':': { key: Key.SEMICOLON, shift: true }, + "'": { key: Key.QUOTE }, + '"': { key: Key.QUOTE, shift: true }, + ',': { key: Key.COMMA }, + '<': { key: Key.COMMA, shift: true }, + '.': { key: Key.PERIOD }, + '>': { key: Key.PERIOD, shift: true }, + '/': { key: Key.SLASH }, + '?': { key: Key.SLASH, shift: true }, + '\\': { key: Key.BACKSLASH }, + '|': { key: Key.BACKSLASH, shift: true }, + + '\t': { key: Key.TAB }, + '\r': { key: Key.ENTER }, + '\n': { key: Key.ENTER }, + ' ': { key: Key.SPACE }, +}; + +/** + * Mapping of Windows Virtual Keys to the corresponding keys on a standard US keyboard layout. + */ +const VKEY_TO_KEY: { [value: string]: Key } = { + VK_SHIFT: Key.SHIFT_LEFT, + VK_LSHIFT: Key.SHIFT_LEFT, + VK_RSHIFT: Key.SHIFT_RIGHT, + VK_CONTROL: Key.CONTROL_LEFT, + VK_LCONTROL: Key.CONTROL_LEFT, + VK_RCONTROL: Key.CONTROL_RIGHT, + VK_MENU: Key.ALT_LEFT, + VK_COMMAND: Key.OS_LEFT, + VK_LWIN: Key.OS_LEFT, + VK_RWIN: Key.OS_RIGHT, + + VK_0: Key.DIGIT0, + VK_1: Key.DIGIT1, + VK_2: Key.DIGIT2, + VK_3: Key.DIGIT3, + VK_4: Key.DIGIT4, + VK_5: Key.DIGIT5, + VK_6: Key.DIGIT6, + VK_7: Key.DIGIT7, + VK_8: Key.DIGIT8, + VK_9: Key.DIGIT9, + VK_A: Key.KEY_A, + VK_B: Key.KEY_B, + VK_C: Key.KEY_C, + VK_D: Key.KEY_D, + VK_E: Key.KEY_E, + VK_F: Key.KEY_F, + VK_G: Key.KEY_G, + VK_H: Key.KEY_H, + VK_I: Key.KEY_I, + VK_J: Key.KEY_J, + VK_K: Key.KEY_K, + VK_L: Key.KEY_L, + VK_M: Key.KEY_M, + VK_N: Key.KEY_N, + VK_O: Key.KEY_O, + VK_P: Key.KEY_P, + VK_Q: Key.KEY_Q, + VK_R: Key.KEY_R, + VK_S: Key.KEY_S, + VK_T: Key.KEY_T, + VK_U: Key.KEY_U, + VK_V: Key.KEY_V, + VK_W: Key.KEY_W, + VK_X: Key.KEY_X, + VK_Y: Key.KEY_Y, + VK_Z: Key.KEY_Z, + + VK_OEM_1: Key.SEMICOLON, + VK_OEM_2: Key.SLASH, + VK_OEM_3: Key.BACKQUOTE, + VK_OEM_4: Key.BRACKET_LEFT, + VK_OEM_5: Key.BACKSLASH, + VK_OEM_6: Key.BRACKET_RIGHT, + VK_OEM_7: Key.QUOTE, + VK_OEM_PLUS: Key.EQUAL, + VK_OEM_COMMA: Key.COMMA, + VK_OEM_MINUS: Key.MINUS, + VK_OEM_PERIOD: Key.PERIOD, + + VK_F1: Key.F1, + VK_F2: Key.F2, + VK_F3: Key.F3, + VK_F4: Key.F4, + VK_F5: Key.F5, + VK_F6: Key.F6, + VK_F7: Key.F7, + VK_F8: Key.F8, + VK_F9: Key.F9, + VK_F10: Key.F10, + VK_F11: Key.F11, + VK_F12: Key.F12, + VK_F13: Key.F13, + VK_F14: Key.F14, + VK_F15: Key.F15, + VK_F16: Key.F16, + VK_F17: Key.F17, + VK_F18: Key.F18, + VK_F19: Key.F19, + + VK_BACK: Key.BACKSPACE, + VK_TAB: Key.TAB, + VK_RETURN: Key.ENTER, + VK_CAPITAL: Key.CAPS_LOCK, + VK_ESCAPE: Key.ESCAPE, + VK_SPACE: Key.SPACE, + VK_PRIOR: Key.PAGE_UP, + VK_NEXT: Key.PAGE_DOWN, + VK_END: Key.END, + VK_HOME: Key.HOME, + VK_INSERT: Key.INSERT, + VK_DELETE: Key.DELETE, + VK_LEFT: Key.ARROW_LEFT, + VK_UP: Key.ARROW_UP, + VK_RIGHT: Key.ARROW_RIGHT, + VK_DOWN: Key.ARROW_DOWN, + + VK_NUMLOCK: Key.NUM_LOCK, + VK_NUMPAD0: Key.DIGIT0, + VK_NUMPAD1: Key.DIGIT1, + VK_NUMPAD2: Key.DIGIT2, + VK_NUMPAD3: Key.DIGIT3, + VK_NUMPAD4: Key.DIGIT4, + VK_NUMPAD5: Key.DIGIT5, + VK_NUMPAD6: Key.DIGIT6, + VK_NUMPAD7: Key.DIGIT7, + VK_NUMPAD8: Key.DIGIT8, + VK_NUMPAD9: Key.DIGIT9, + VK_MULTIPLY: Key.MULTIPLY, + VK_ADD: Key.ADD, + VK_SUBTRACT: Key.SUBTRACT, + VK_DECIMAL: Key.DECIMAL, + VK_DIVIDE: Key.DIVIDE +}; diff --git a/packages/core/src/browser/keyboard/keys.spec.ts b/packages/core/src/browser/keyboard/keys.spec.ts new file mode 100644 index 0000000000000..6c8e3042b0c4d --- /dev/null +++ b/packages/core/src/browser/keyboard/keys.spec.ts @@ -0,0 +1,230 @@ +/******************************************************************************** + * Copyright (C) 2017 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { enableJSDOM } from '../../browser/test/jsdom'; +let disableJSDOM = enableJSDOM(); + +import { KeyCode, Key, KeyModifier, KeySequence } from './keys'; +import * as os from '../../common/os'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; + +disableJSDOM(); + +/* tslint:disable:no-unused-expression */ + +const expect = chai.expect; + +describe('keys api', () => { + const equalKeyCode = (keyCode1: KeyCode, keyCode2: KeyCode): boolean => + JSON.stringify(keyCode1) === JSON.stringify(keyCode2); + + before(() => { + disableJSDOM = enableJSDOM(); + }); + + after(() => { + disableJSDOM(); + }); + + it('should parse a string to a KeyCode correctly', () => { + + const keycode = KeyCode.parse('ctrl+b'); + expect(keycode.ctrl).to.be.true; + expect(keycode.key).is.equal(Key.KEY_B); + + // Invalid keystroke string + expect(() => KeyCode.parse('ctl+b')).to.throw(Error); + + }); + + it('should parse a string containing special modifiers to a KeyCode correctly', () => { + const stub = sinon.stub(os, 'isOSX').value(false); + const keycode = KeyCode.parse('ctrl+b'); + expect(keycode.ctrl).to.be.true; + expect(keycode.key).is.equal(Key.KEY_B); + + const keycodeOption = KeyCode.parse('option+b'); + expect(keycodeOption.alt).to.be.true; + expect(keycodeOption.key).is.equal(Key.KEY_B); + + expect(() => KeyCode.parse('cmd+b')).to.throw(/OSX only/); + + const keycodeCtrlOrCommand = KeyCode.parse('ctrlcmd+b'); + expect(keycodeCtrlOrCommand.meta).to.be.false; + expect(keycodeCtrlOrCommand.ctrl).to.be.true; + expect(keycodeCtrlOrCommand.key).is.equal(Key.KEY_B); + stub.restore(); + }); + + it('should parse a string containing special modifiers to a KeyCode correctly (macOS)', () => { + KeyCode.resetKeyBindings(); + const stub = sinon.stub(os, 'isOSX').value(true); + const keycode = KeyCode.parse('ctrl+b'); + expect(keycode.ctrl).to.be.true; + expect(keycode.key).is.equal(Key.KEY_B); + + const keycodeOption = KeyCode.parse('option+b'); + expect(keycodeOption.alt).to.be.true; + expect(keycodeOption.key).is.equal(Key.KEY_B); + + const keycodeCommand = KeyCode.parse('cmd+b'); + expect(keycodeCommand.meta).to.be.true; + expect(keycodeCommand.key).is.equal(Key.KEY_B); + + const keycodeCtrlOrCommand = KeyCode.parse('ctrlcmd+b'); + expect(keycodeCtrlOrCommand.meta).to.be.true; + expect(keycodeCtrlOrCommand.ctrl).to.be.false; + expect(keycodeCtrlOrCommand.key).is.equal(Key.KEY_B); + + stub.restore(); + }); + + it('should serialize a keycode properly with BACKQUOTE + M1', () => { + const stub = sinon.stub(os, 'isOSX').value(true); + let keyCode = KeyCode.createKeyCode({ first: Key.BACKQUOTE, modifiers: [KeyModifier.CtrlCmd] }); + let keyCodeString = keyCode.toString(); + expect(keyCodeString).to.be.equal('meta+`'); + let parsedKeyCode = KeyCode.parse(keyCodeString); + expect(equalKeyCode(parsedKeyCode, keyCode)).to.be.true; + + sinon.stub(os, 'isOSX').value(false); + keyCode = KeyCode.createKeyCode({ first: Key.BACKQUOTE, modifiers: [KeyModifier.CtrlCmd] }); + keyCodeString = keyCode.toString(); + expect(keyCodeString).to.be.equal('ctrl+`'); + parsedKeyCode = KeyCode.parse(keyCodeString); + expect(equalKeyCode(parsedKeyCode, keyCode)).to.be.true; + + stub.restore(); + }); + + it('should serialize a keycode properly with a + M2 + M3', () => { + const keyCode = KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift, KeyModifier.Alt] }); + const keyCodeString = keyCode.toString(); + expect(keyCodeString).to.be.equal('shift+alt+a'); + const parsedKeyCode = KeyCode.parse(keyCodeString); + expect(equalKeyCode(parsedKeyCode, keyCode)).to.be.true; + }); + + it('the order of the modifiers should not matter when parsing the key code', () => { + const left = KeySequence.parse('shift+alt+a'); + const right = KeySequence.parse('alt+shift+a'); + expect(KeySequence.compare(left, right)).to.be.equal(KeySequence.CompareResult.FULL); + + expect(KeySequence.compare( + [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Alt, KeyModifier.Shift] })], right)).to.be.equal( + KeySequence.CompareResult.FULL); + expect(KeySequence.compare( + left, [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Alt, KeyModifier.Shift] })])).to.be.equal( + KeySequence.CompareResult.FULL); + + expect(KeySequence.compare( + [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift, KeyModifier.Alt] })], right)).to.be.equal( + KeySequence.CompareResult.FULL); + expect(KeySequence.compare( + left, [KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift, KeyModifier.Alt] })])).to.be.equal( + KeySequence.CompareResult.FULL); + }); + + it('should parse ctrl key properly on both OS X and other platforms', () => { + const event = new KeyboardEvent('keydown', { + key: Key.BACKQUOTE.easyString, + code: Key.BACKQUOTE.code, + ctrlKey: true, + }); + const stub = sinon.stub(os, 'isOSX').value(true); + expect(KeyCode.createKeyCode(event).toString()).to.be.equal('ctrl+`'); + sinon.stub(os, 'isOSX').value(false); + expect(KeyCode.createKeyCode(event).toString()).to.be.equal('ctrl+`'); + stub.restore(); + }); + + it('should serialize a keycode properly with a + M4', () => { + const stub = sinon.stub(os, 'isOSX').value(true); + const keyCode = KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.MacCtrl] }); + const keyCodeString = keyCode.toString(); + expect(keyCodeString).to.be.equal('ctrl+a'); + const parsedKeyCode = KeyCode.parse(keyCodeString); + expect(equalKeyCode(parsedKeyCode, keyCode)).to.be.true; + stub.restore(); + }); + + it('it should parse a multi keycode keybinding', () => { + const validKeyCodes = []; + validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })); + validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_C, modifiers: [KeyModifier.CtrlCmd, KeyModifier.Shift] })); + + const parsedKeyCodes = KeySequence.parse('ctrlcmd+a ctrlcmd+shift+c'); + expect(parsedKeyCodes).to.deep.equal(validKeyCodes); + }); + + it('it should parse a multi keycode keybinding with no modifiers', () => { + const validKeyCodes = []; + validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })); + validKeyCodes.push(KeyCode.createKeyCode({ first: Key.KEY_C })); + + const parsedKeyCodes = KeySequence.parse('ctrlcmd+a c'); + expect(parsedKeyCodes).to.deep.equal(validKeyCodes); + }); + + it('should compare keysequences properly', () => { + let a = KeySequence.parse('ctrlcmd+a'); + let b = KeySequence.parse('ctrlcmd+a t'); + + expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.PARTIAL); + + a = KeySequence.parse('ctrlcmd+a t'); + b = KeySequence.parse('ctrlcmd+a'); + + expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.SHADOW); + + a = KeySequence.parse('ctrlcmd+a t'); + b = KeySequence.parse('ctrlcmd+a b c'); + expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.NONE); + + a = KeySequence.parse('ctrlcmd+a t'); + b = KeySequence.parse('ctrlcmd+a a'); + expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.NONE); + + a = KeySequence.parse('ctrlcmd+a t'); + b = KeySequence.parse('ctrlcmd+a t'); + expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.FULL); + + a = KeySequence.parse('ctrlcmd+a t b'); + b = KeySequence.parse('ctrlcmd+a t b'); + expect(KeySequence.compare(a, b)).to.be.equal(KeySequence.CompareResult.FULL); + }); + + it('should be a modifier only', () => { + const keyCode = KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd] }); + expect(keyCode).to.be.deep.equal(KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd] })); + expect(keyCode.isModifierOnly()).to.be.true; + }); + + it('should be multiple modifiers only', () => { + const keyCode = KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd, KeyModifier.Alt] }); + expect(keyCode).to.be.deep.equal(KeyCode.createKeyCode({ modifiers: [KeyModifier.CtrlCmd, KeyModifier.Alt] })); + expect(keyCode.isModifierOnly()).to.be.true; + }); + + it('parse bogus keybinding', () => { + const [first, second] = KeySequence.parse(' Ctrl+sHiFt+F10 b '); + expect(first.ctrl).to.be.true; + expect(first.shift).to.be.true; + expect(first.key).is.equal(Key.F10); + expect(second.key).is.equal(Key.KEY_B); + }); +}); diff --git a/packages/core/src/browser/keyboard/keys.ts b/packages/core/src/browser/keyboard/keys.ts new file mode 100644 index 0000000000000..2efcad7b909c1 --- /dev/null +++ b/packages/core/src/browser/keyboard/keys.ts @@ -0,0 +1,649 @@ +/******************************************************************************** + * Copyright (C) 2017-2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { isOSX } from '../../common/os'; + +export type KeySequence = KeyCode[]; +export namespace KeySequence { + + export function equals(a: KeySequence, b: KeySequence) { + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (!a[i].equals(b[i])) { + return false; + } + } + return true; + } + + export enum CompareResult { + NONE = 0, + PARTIAL, + SHADOW, + FULL + } + + /* Compares two KeySequences, returns: + * FULL if the KeySequences are the same. + * PARTIAL if the KeySequence a part of b. + * SHADOW if the KeySequence b part of a. + * NONE if the KeySequences are not the same at all. + */ + export function compare(a: KeySequence, b: KeySequence): CompareResult { + let first = a; + let second = b; + let shadow = false; + + if (b.length < a.length) { + first = b; + second = a; + shadow = true; + } + + for (let i = 0; i < first.length; i++) { + if (first[i].equals(second[i]) === false) { + return KeySequence.CompareResult.NONE; + } + } + if (first.length < second.length) { + if (shadow === false) { + return KeySequence.CompareResult.PARTIAL; + } else { + return KeySequence.CompareResult.SHADOW; + } + } + return KeySequence.CompareResult.FULL; + } + + export function parse(keybinding: string): KeySequence { + const keyCodes = []; + const rawKeyCodes = keybinding.trim().split(/\s+/g); + for (const rawKeyCode of rawKeyCodes) { + const keyCode = KeyCode.parse(rawKeyCode); + if (keyCode !== undefined) { + keyCodes.push(keyCode); + } + } + return keyCodes; + } +} + +/** + * The key sequence for this binding. This key sequence should consist of one or more key strokes. Key strokes + * consist of one or more keys held down at the same time. This should be zero or more modifier keys, and zero or one other key. + * Since `M2+M3+` (Alt+Shift+) is reserved on MacOS X for writing special characters, such bindings are commonly + * undefined for platform MacOS X and redefined as `M1+M3+`. The rule applies on the `M3+M2+` sequence. + */ +export interface Keystroke { + readonly first?: Key; + readonly modifiers?: KeyModifier[]; +} + +export interface KeyCodeSchema { + key?: Partial; + ctrl?: boolean; + shift?: boolean; + alt?: boolean; + meta?: boolean; + character?: string; +} + +/** + * Representation of a pressed key combined with key modifiers. + */ +export class KeyCode { + + public readonly key: Key | undefined; + public readonly ctrl: boolean; + public readonly shift: boolean; + public readonly alt: boolean; + public readonly meta: boolean; + public readonly character: string | undefined; + + public constructor(schema: KeyCodeSchema) { + const key = schema.key; + if (key) { + if (key.code && key.keyCode && key.easyString) { + this.key = key as Key; + } else if (key.code) { + this.key = Key.getKey(key.code); + } else if (key.keyCode) { + this.key = Key.getKey(key.keyCode); + } + } + this.ctrl = !!schema.ctrl; + this.shift = !!schema.shift; + this.alt = !!schema.alt; + this.meta = !!schema.meta; + this.character = schema.character; + } + + /** + * Return true if this KeyCode only contains modifiers. + */ + public isModifierOnly() { + return this.key === undefined; + } + + /** + * Return true if the given KeyCode is equal to this one. + */ + equals(other: KeyCode): boolean { + if (this.key && (!other.key || this.key.code !== other.key.code) || !this.key && other.key) { + return false; + } + return this.ctrl === other.ctrl && this.alt === other.alt && this.shift === other.shift && this.meta === other.meta; + } + + /* + * Return a keybinding string compatible with the `Keybinding.keybinding` property. + */ + toString(): string { + const result = []; + if (this.meta) { + result.push(SpecialCases.META); + } + if (this.shift) { + result.push(Key.SHIFT_LEFT.easyString); + } + if (this.alt) { + result.push(Key.ALT_LEFT.easyString); + } + if (this.ctrl) { + result.push(Key.CONTROL_LEFT.easyString); + } + if (this.key) { + result.push(this.key.easyString); + } + return result.join('+'); + } + + /** + * Create a KeyCode from one of several input types. + */ + public static createKeyCode(input: KeyboardEvent | Keystroke | KeyCodeSchema | string): KeyCode { + if (typeof input === 'string') { + const parts = input.split('+'); + if (!KeyCode.isModifierString(parts[0])) { + return KeyCode.createKeyCode({ + first: Key.getKey(parts[0]), + modifiers: parts.slice(1) as KeyModifier[] + }); + } + return KeyCode.createKeyCode({ modifiers: parts as KeyModifier[] }); + } else if (KeyCode.isKeyboardEvent(input)) { + const key = KeyCode.toKey(input); + return new KeyCode({ + key: Key.isModifier(key.code) ? undefined : key, + meta: isOSX && input.metaKey, + shift: input.shiftKey, + alt: input.altKey, + ctrl: input.ctrlKey, + character: KeyCode.toCharacter(input) + }); + } else if ((input as Keystroke).first || (input as Keystroke).modifiers) { + const keystroke = input as Keystroke; + const schema: KeyCodeSchema = { + key: keystroke.first + }; + if (keystroke.modifiers) { + if (isOSX) { + schema.meta = keystroke.modifiers.some(mod => mod === KeyModifier.CtrlCmd); + schema.ctrl = keystroke.modifiers.some(mod => mod === KeyModifier.MacCtrl); + } else { + schema.meta = false; + schema.ctrl = keystroke.modifiers.some(mod => mod === KeyModifier.CtrlCmd); + } + schema.shift = keystroke.modifiers.some(mod => mod === KeyModifier.Shift); + schema.alt = keystroke.modifiers.some(mod => mod === KeyModifier.Alt); + } + return new KeyCode(schema); + } else { + return new KeyCode(input as KeyCodeSchema); + } + } + + private static keybindings: { [key: string]: KeyCode } = {}; + + /* Reset the key hashmap, this is for testing purposes. */ + public static resetKeyBindings() { + KeyCode.keybindings = {}; + } + + /** + * Parses a string and returns a KeyCode object. + * @param keybinding String representation of a keybinding + */ + public static parse(keybinding: string): KeyCode { + if (KeyCode.keybindings[keybinding]) { + return KeyCode.keybindings[keybinding]; + } + + const schema: KeyCodeSchema = {}; + const keys = keybinding.trim().toLowerCase().split('+'); + /* If duplicates i.e ctrl+ctrl+a or alt+alt+b or b+alt+b it is invalid */ + if (keys.length !== new Set(keys).size) { + throw new Error(`Can't parse keybinding ${keybinding} Duplicate modifiers`); + } + + for (let keyString of keys) { + if (SPECIAL_ALIASES[keyString] !== undefined) { + keyString = SPECIAL_ALIASES[keyString]; + } + const key = EASY_TO_KEY[keyString]; + + /* meta only works on macOS */ + if (keyString === SpecialCases.META) { + if (isOSX) { + schema.meta = true; + } else { + throw new Error(`Can't parse keybinding ${keybinding} meta is for OSX only`); + } + /* ctrlcmd for M1 keybindings that work on both macOS and other platforms */ + } else if (keyString === SpecialCases.CTRLCMD) { + if (isOSX) { + schema.meta = true; + } else { + schema.ctrl = true; + } + } else if (Key.isKey(key)) { + if (Key.isModifier(key.code)) { + if (key.code === Key.CONTROL_LEFT.code || key.code === Key.CONTROL_RIGHT.code) { + schema.ctrl = true; + } else if (key.code === Key.SHIFT_LEFT.code || key.code === Key.SHIFT_RIGHT.code) { + schema.shift = true; + } else if (key.code === Key.ALT_LEFT.code || key.code === Key.ALT_RIGHT.code) { + schema.alt = true; + } + } else { + schema.key = key; + } + } else { + throw new Error(`Unrecognized key '${keyString}' in '${keybinding}'`); + } + } + + KeyCode.keybindings[keybinding] = new KeyCode(schema); + return KeyCode.keybindings[keybinding]; + } + +} + +export namespace KeyCode { + + /** + * Determines a `true` of `false` value for the key code argument. + */ + export type Predicate = (keyCode: KeyCode) => boolean; + + /* + * Return true if the string is a modifier M1 to M4. + */ + export function isModifierString(key: string) { + return key === KeyModifier.CtrlCmd + || key === KeyModifier.Shift + || key === KeyModifier.Alt + || key === KeyModifier.MacCtrl; + } + + /** + * Different scopes have different execution environments. This means that they have different built-ins + * (different global object, different constructors, etc.). This may result in unexpected results. For instance, + * `[] instanceof window.frames[0].Array` will return `false`, because `Array.prototype !== window.frames[0].Array` + * and arrays inherit from the former. + * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof + * + * Note: just add another check if the current `event.type` checking is insufficient. + */ + export function isKeyboardEvent(event: object & { readonly type?: string }): event is KeyboardEvent { + if (typeof KeyboardEvent === 'undefined') { // This can happen in tests + return false; + } + if (event instanceof KeyboardEvent) { + return true; + } + const { type } = event; + if (type) { + return type === 'keypress' || type === 'keydown' || type === 'keyup'; + } + return false; + } + + /** + * Determine the pressed key of a keyboard event. This key should correspond to the physical key according + * to a standard US keyboard layout. International keyboard layouts are handled by `KeyboardLayoutService`. + * + * `keyIdentifier` is used to access this deprecated field: + * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyIdentifier + */ + export function toKey(event: KeyboardEvent & { readonly keyIdentifier?: string }): Key { + const properties: ('keyCode' | 'code' | 'keyIdentifier')[] + = ['code', 'keyCode', 'keyIdentifier']; + for (const prop of properties) { + if (event[prop]) { + const key = Key.getKey(event[prop]!); + if (key) { + return key; + } + } + } + throw new Error(`Cannot get key code from the keyboard event: ${event}.`); + } + + /** + * Determine the actual printable character that is generated from a pressed key. + * If the key does not correspond to a printable character, `undefined` is returned. + * The result may be altered by modifier keys. + */ + export function toCharacter(event: KeyboardEvent): string | undefined { + const key = event.key; + // Use the key property if it contains exactly one unicode character + if (key && Array.from(key).length === 1) { + return key; + } + const charCode = event.charCode; + // Use the charCode property if it does not correspond to a unicode control character + if (charCode && charCode > 0x1f && !(charCode >= 0x80 && charCode <= 0x9f)) { + return String.fromCharCode(charCode); + } + return undefined; + } + +} + +export enum KeyModifier { + /** + * M1 is the COMMAND key on MacOS X, and the CTRL key on most other platforms. + */ + CtrlCmd = 'M1', + /** + * M2 is the SHIFT key. + */ + Shift = 'M2', + /** + * M3 is the Option key on MacOS X, and the ALT key on most other platforms. + */ + Alt = 'M3', + /** + * M4 is the CTRL key on MacOS X, and is undefined on other platforms. + */ + MacCtrl = 'M4' +} + +export namespace KeyModifier { + /** + * The CTRL key, independently of the platform. + * _Note:_ In general `KeyModifier.CtrlCmd` should be preferred over this constant. + */ + export const CTRL: KeyModifier.MacCtrl | KeyModifier.CtrlCmd = isOSX ? KeyModifier.MacCtrl : KeyModifier.CtrlCmd; + /** + * An alias for the SHIFT key (`KeyModifier.Shift`). + */ + export const SHIFT: KeyModifier.Shift = KeyModifier.Shift; + + /** + * `true` if the argument represents a modifier. Otherwise, `false`. + */ + export function isModifier(key: string | undefined): boolean { + if (key) { + switch (key) { + case 'M1': // Fall through. + case 'M2': // Fall through. + case 'M3': // Fall through. + case 'M4': return true; + default: return false; + } + } + return false; + } +} + +export interface Key { + readonly code: string; + readonly keyCode: number; + readonly easyString: string; +} + +const CODE_TO_KEY: { [code: string]: Key } = {}; +const KEY_CODE_TO_KEY: { [keyCode: number]: Key } = {}; +const EASY_TO_KEY: { [code: string]: Key } = {}; // From 'ctrl' to Key structure +const MODIFIERS: Key[] = []; + +const SPECIAL_ALIASES: { [index: string]: string } = { + 'option': 'alt', + 'command': 'meta', + 'cmd': 'meta', + 'return': 'enter', + 'esc': 'escape', + 'mod': 'ctrl', + 'ins': 'insert', + 'del': 'delete', + 'control': 'ctrl', +}; + +export namespace SpecialCases { + export const META = 'meta'; + export const CTRLCMD = 'ctrlcmd'; +} + +export namespace Key { + + // tslint:disable-next-line:no-any + export function isKey(arg: any): arg is Key { + return typeof arg === 'object' && ('code' in arg) && ('keyCode' in arg); + } + + export function getKey(arg: string | number): Key | undefined { + if (typeof arg === 'number') { + return KEY_CODE_TO_KEY[arg]; + } else { + return CODE_TO_KEY[arg]; + } + } + + export function isModifier(arg: string | number): boolean { + if (typeof arg === 'number') { + return MODIFIERS.find(key => key.keyCode === arg) !== undefined; + } + return MODIFIERS.find(key => key.code === arg) !== undefined; + } + + export function equals(key: Key, keyCode: KeyCode): boolean { + return !!keyCode.key && key.keyCode === keyCode.key.keyCode; + } + + export const BACKSPACE: Key = { code: 'Backspace', keyCode: 8, easyString: 'backspace' }; + export const TAB: Key = { code: 'Tab', keyCode: 9, easyString: 'tab' }; + export const ENTER: Key = { code: 'Enter', keyCode: 13, easyString: 'enter' }; + export const ESCAPE: Key = { code: 'Escape', keyCode: 27, easyString: 'escape' }; + export const SPACE: Key = { code: 'Space', keyCode: 32, easyString: 'space' }; + export const PAGE_UP: Key = { code: 'PageUp', keyCode: 33, easyString: 'pageup' }; + export const PAGE_DOWN: Key = { code: 'PageDown', keyCode: 34, easyString: 'pagedown' }; + export const END: Key = { code: 'End', keyCode: 35, easyString: 'end' }; + export const HOME: Key = { code: 'Home', keyCode: 36, easyString: 'home' }; + export const ARROW_LEFT: Key = { code: 'ArrowLeft', keyCode: 37, easyString: 'left' }; + export const ARROW_UP: Key = { code: 'ArrowUp', keyCode: 38, easyString: 'up' }; + export const ARROW_RIGHT: Key = { code: 'ArrowRight', keyCode: 39, easyString: 'right' }; + export const ARROW_DOWN: Key = { code: 'ArrowDown', keyCode: 40, easyString: 'down' }; + export const INSERT: Key = { code: 'Insert', keyCode: 45, easyString: 'insert' }; + export const DELETE: Key = { code: 'Delete', keyCode: 46, easyString: 'delete' }; + + export const SHIFT_LEFT: Key = { code: 'ShiftLeft', keyCode: 16, easyString: 'shift' }; + export const SHIFT_RIGHT: Key = { code: 'ShiftRight', keyCode: 16, easyString: 'shift' }; + export const CONTROL_LEFT: Key = { code: 'ControlLeft', keyCode: 17, easyString: 'ctrl' }; + export const CONTROL_RIGHT: Key = { code: 'ControlRight', keyCode: 17, easyString: 'ctrl' }; + export const ALT_LEFT: Key = { code: 'AltLeft', keyCode: 18, easyString: 'alt' }; + export const ALT_RIGHT: Key = { code: 'AltRight', keyCode: 18, easyString: 'alt' }; + export const CAPS_LOCK: Key = { code: 'CapsLock', keyCode: 20, easyString: 'capslock' }; + export const OS_LEFT: Key = { code: 'OSLeft', keyCode: 91, easyString: 'super' }; + export const OS_RIGHT: Key = { code: 'OSRight', keyCode: 92, easyString: 'super' }; + + export const DIGIT0: Key = { code: 'Digit0', keyCode: 48, easyString: '0' }; + export const DIGIT1: Key = { code: 'Digit1', keyCode: 49, easyString: '1' }; + export const DIGIT2: Key = { code: 'Digit2', keyCode: 50, easyString: '2' }; + export const DIGIT3: Key = { code: 'Digit3', keyCode: 51, easyString: '3' }; + export const DIGIT4: Key = { code: 'Digit4', keyCode: 52, easyString: '4' }; + export const DIGIT5: Key = { code: 'Digit5', keyCode: 53, easyString: '5' }; + export const DIGIT6: Key = { code: 'Digit6', keyCode: 54, easyString: '6' }; + export const DIGIT7: Key = { code: 'Digit7', keyCode: 55, easyString: '7' }; + export const DIGIT8: Key = { code: 'Digit8', keyCode: 56, easyString: '8' }; + export const DIGIT9: Key = { code: 'Digit9', keyCode: 57, easyString: '9' }; + + export const KEY_A: Key = { code: 'KeyA', keyCode: 65, easyString: 'a' }; + export const KEY_B: Key = { code: 'KeyB', keyCode: 66, easyString: 'b' }; + export const KEY_C: Key = { code: 'KeyC', keyCode: 67, easyString: 'c' }; + export const KEY_D: Key = { code: 'KeyD', keyCode: 68, easyString: 'd' }; + export const KEY_E: Key = { code: 'KeyE', keyCode: 69, easyString: 'e' }; + export const KEY_F: Key = { code: 'KeyF', keyCode: 70, easyString: 'f' }; + export const KEY_G: Key = { code: 'KeyG', keyCode: 71, easyString: 'g' }; + export const KEY_H: Key = { code: 'KeyH', keyCode: 72, easyString: 'h' }; + export const KEY_I: Key = { code: 'KeyI', keyCode: 73, easyString: 'i' }; + export const KEY_J: Key = { code: 'KeyJ', keyCode: 74, easyString: 'j' }; + export const KEY_K: Key = { code: 'KeyK', keyCode: 75, easyString: 'k' }; + export const KEY_L: Key = { code: 'KeyL', keyCode: 76, easyString: 'l' }; + export const KEY_M: Key = { code: 'KeyM', keyCode: 77, easyString: 'm' }; + export const KEY_N: Key = { code: 'KeyN', keyCode: 78, easyString: 'n' }; + export const KEY_O: Key = { code: 'KeyO', keyCode: 79, easyString: 'o' }; + export const KEY_P: Key = { code: 'KeyP', keyCode: 80, easyString: 'p' }; + export const KEY_Q: Key = { code: 'KeyQ', keyCode: 81, easyString: 'q' }; + export const KEY_R: Key = { code: 'KeyR', keyCode: 82, easyString: 'r' }; + export const KEY_S: Key = { code: 'KeyS', keyCode: 83, easyString: 's' }; + export const KEY_T: Key = { code: 'KeyT', keyCode: 84, easyString: 't' }; + export const KEY_U: Key = { code: 'KeyU', keyCode: 85, easyString: 'u' }; + export const KEY_V: Key = { code: 'KeyV', keyCode: 86, easyString: 'v' }; + export const KEY_W: Key = { code: 'KeyW', keyCode: 87, easyString: 'w' }; + export const KEY_X: Key = { code: 'KeyX', keyCode: 88, easyString: 'x' }; + export const KEY_Y: Key = { code: 'KeyY', keyCode: 89, easyString: 'y' }; + export const KEY_Z: Key = { code: 'KeyZ', keyCode: 90, easyString: 'z' }; + + export const MULTIPLY: Key = { code: 'NumpadMultiply', keyCode: 106, easyString: 'multiply' }; + export const ADD: Key = { code: 'NumpadAdd', keyCode: 107, easyString: 'add' }; + export const DECIMAL: Key = { code: 'NumpadDecimal', keyCode: 108, easyString: 'decimal' }; + export const SUBTRACT: Key = { code: 'NumpadSubtract', keyCode: 109, easyString: 'subtract' }; + export const DIVIDE: Key = { code: 'NumpadDivide', keyCode: 111, easyString: 'divide' }; + + export const F1: Key = { code: 'F1', keyCode: 112, easyString: 'f1' }; + export const F2: Key = { code: 'F2', keyCode: 113, easyString: 'f2' }; + export const F3: Key = { code: 'F3', keyCode: 114, easyString: 'f3' }; + export const F4: Key = { code: 'F4', keyCode: 115, easyString: 'f4' }; + export const F5: Key = { code: 'F5', keyCode: 116, easyString: 'f5' }; + export const F6: Key = { code: 'F6', keyCode: 117, easyString: 'f6' }; + export const F7: Key = { code: 'F7', keyCode: 118, easyString: 'f7' }; + export const F8: Key = { code: 'F8', keyCode: 119, easyString: 'f8' }; + export const F9: Key = { code: 'F9', keyCode: 120, easyString: 'f9' }; + export const F10: Key = { code: 'F10', keyCode: 121, easyString: 'f10' }; + export const F11: Key = { code: 'F11', keyCode: 122, easyString: 'f11' }; + export const F12: Key = { code: 'F12', keyCode: 123, easyString: 'f12' }; + export const F13: Key = { code: 'F13', keyCode: 124, easyString: 'f13' }; + export const F14: Key = { code: 'F14', keyCode: 125, easyString: 'f14' }; + export const F15: Key = { code: 'F15', keyCode: 126, easyString: 'f15' }; + export const F16: Key = { code: 'F16', keyCode: 127, easyString: 'f16' }; + export const F17: Key = { code: 'F17', keyCode: 128, easyString: 'f17' }; + export const F18: Key = { code: 'F18', keyCode: 129, easyString: 'f18' }; + export const F19: Key = { code: 'F19', keyCode: 130, easyString: 'f19' }; + export const F20: Key = { code: 'F20', keyCode: 131, easyString: 'f20' }; + export const F21: Key = { code: 'F21', keyCode: 132, easyString: 'f21' }; + export const F22: Key = { code: 'F22', keyCode: 133, easyString: 'f22' }; + export const F23: Key = { code: 'F23', keyCode: 134, easyString: 'f23' }; + export const F24: Key = { code: 'F24', keyCode: 135, easyString: 'f24' }; + + export const NUM_LOCK: Key = { code: 'NumLock', keyCode: 144, easyString: 'numlock' }; + export const SEMICOLON: Key = { code: 'Semicolon', keyCode: 186, easyString: ';' }; + export const EQUAL: Key = { code: 'Equal', keyCode: 187, easyString: '=' }; + export const COMMA: Key = { code: 'Comma', keyCode: 188, easyString: ',' }; + export const MINUS: Key = { code: 'Minus', keyCode: 189, easyString: '-' }; + export const PERIOD: Key = { code: 'Period', keyCode: 190, easyString: '.' }; + export const SLASH: Key = { code: 'Slash', keyCode: 191, easyString: '/' }; + export const BACKQUOTE: Key = { code: 'Backquote', keyCode: 192, easyString: '`' }; + export const INTL_RO: Key = { code: 'IntlRo', keyCode: 193, easyString: 'intlro' }; + export const BRACKET_LEFT: Key = { code: 'BracketLeft', keyCode: 219, easyString: '[' }; + export const BACKSLASH: Key = { code: 'Backslash', keyCode: 220, easyString: '\\' }; + export const BRACKET_RIGHT: Key = { code: 'BracketRight', keyCode: 221, easyString: ']' }; + export const QUOTE: Key = { code: 'Quote', keyCode: 222, easyString: '\'' }; + export const INTL_YEN: Key = { code: 'IntlYen', keyCode: 255, easyString: 'intlyen' }; + + export const MAX_KEY_CODE = INTL_YEN.keyCode; + +} + +/*-------------------- Initialize the static key mappings --------------------*/ +(() => { + // Set the default key mappings from the constants in the Key namespace + Object.keys(Key).map(prop => Reflect.get(Key, prop)).filter(key => Key.isKey(key)).forEach(key => { + CODE_TO_KEY[key.code] = key; + KEY_CODE_TO_KEY[key.keyCode] = key; + EASY_TO_KEY[key.easyString] = key; + }); + + // Set additional key mappings + CODE_TO_KEY['Numpad0'] = Key.DIGIT0; + KEY_CODE_TO_KEY[96] = Key.DIGIT0; + CODE_TO_KEY['Numpad1'] = Key.DIGIT1; + KEY_CODE_TO_KEY[97] = Key.DIGIT1; + CODE_TO_KEY['Numpad2'] = Key.DIGIT2; + KEY_CODE_TO_KEY[98] = Key.DIGIT2; + CODE_TO_KEY['Numpad3'] = Key.DIGIT3; + KEY_CODE_TO_KEY[99] = Key.DIGIT3; + CODE_TO_KEY['Numpad4'] = Key.DIGIT4; + KEY_CODE_TO_KEY[100] = Key.DIGIT4; + CODE_TO_KEY['Numpad5'] = Key.DIGIT5; + KEY_CODE_TO_KEY[101] = Key.DIGIT5; + CODE_TO_KEY['Numpad6'] = Key.DIGIT6; + KEY_CODE_TO_KEY[102] = Key.DIGIT6; + CODE_TO_KEY['Numpad7'] = Key.DIGIT7; + KEY_CODE_TO_KEY[103] = Key.DIGIT7; + CODE_TO_KEY['Numpad8'] = Key.DIGIT8; + KEY_CODE_TO_KEY[104] = Key.DIGIT8; + CODE_TO_KEY['Numpad9'] = Key.DIGIT9; + KEY_CODE_TO_KEY[105] = Key.DIGIT9; + CODE_TO_KEY['NumpadEnter'] = Key.ENTER; + CODE_TO_KEY['NumpadEqual'] = Key.EQUAL; + CODE_TO_KEY['IntlBackslash'] = Key.BACKSLASH; + CODE_TO_KEY['MetaLeft'] = Key.OS_LEFT; // Chrome, Safari + KEY_CODE_TO_KEY[224] = Key.OS_LEFT; // Firefox on Mac + CODE_TO_KEY['MetaRight'] = Key.OS_RIGHT; // Chrome, Safari + KEY_CODE_TO_KEY[93] = Key.OS_RIGHT; // Chrome, Safari, Edge + KEY_CODE_TO_KEY[225] = Key.ALT_RIGHT; // Linux + KEY_CODE_TO_KEY[110] = Key.DECIMAL; // Mac, Windows + KEY_CODE_TO_KEY[59] = Key.SEMICOLON; // Firefox + KEY_CODE_TO_KEY[61] = Key.EQUAL; // Firefox + KEY_CODE_TO_KEY[173] = Key.MINUS; // Firefox + KEY_CODE_TO_KEY[226] = Key.BACKSLASH; // Chrome, Edge on Windows + KEY_CODE_TO_KEY[60] = Key.BACKSLASH; // Firefox on Linux + + // Set the modifier keys + MODIFIERS.push(...[Key.ALT_LEFT, Key.ALT_RIGHT, Key.CONTROL_LEFT, Key.CONTROL_RIGHT, Key.OS_LEFT, Key.OS_RIGHT, Key.SHIFT_LEFT, Key.SHIFT_RIGHT]); +})(); + +export type KeysOrKeyCodes = Key | KeyCode | (Key | KeyCode)[]; +export namespace KeysOrKeyCodes { + + export const toKeyCode = (keyOrKeyCode: Key | KeyCode) => + keyOrKeyCode instanceof KeyCode ? keyOrKeyCode : KeyCode.createKeyCode({ first: keyOrKeyCode }); + + export const toKeyCodes = (keysOrKeyCodes: KeysOrKeyCodes) => { + if (keysOrKeyCodes instanceof KeyCode) { + return [keysOrKeyCodes]; + } else if (Array.isArray(keysOrKeyCodes)) { + return keysOrKeyCodes.slice().map(toKeyCode); + } + return [toKeyCode(keysOrKeyCodes)]; + }; + +} diff --git a/packages/core/src/browser/keys.ts b/packages/core/src/browser/keys.ts index 9cbaad9558b48..bdd75191de571 100644 --- a/packages/core/src/browser/keys.ts +++ b/packages/core/src/browser/keys.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2017 TypeFox and others. + * Copyright (C) 2019 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,887 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isOSX } from '../common/os'; - -/** - * The key sequence for this binding. This key sequence should consist of one or more key strokes. Key strokes - * consist of one or more keys held down at the same time. This should be zero or more modifier keys, and zero or one other key. - * Since `M2+M3+` (Alt+Shift+) is reserved on MacOS X for writing special characters, such bindings are commonly - * undefined for platform MacOS X and redefined as `M1+M3+`. The rule applies on the `M3+M2+` sequence. - */ -export interface Keystroke { - readonly first?: Key; - readonly modifiers?: KeyModifier[]; -} - -export type KeySequence = KeyCode[]; -export namespace KeySequence { - - export function equals(a: KeySequence, b: KeySequence) { - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i++) { - if (a[i].equals(b[i]) === false) { - return false; - } - } - return true; - } - - export enum CompareResult { - NONE = 0, - PARTIAL, - SHADOW, - FULL - } - - /* Compares two KeySequences, returns: - * FULL if the KeySequences are the same. - * PARTIAL if the KeySequence a part of b. - * SHADOW if the KeySequence b part of a. - * NONE if the KeySequences are not the same at all. - */ - export function compare(a: KeySequence, b: KeySequence): CompareResult { - let first = a; - let second = b; - let shadow = false; - - if (b.length < a.length) { - first = b; - second = a; - shadow = true; - } - - for (let i = 0; i < first.length; i++) { - if (first[i].equals(second[i]) === false) { - return KeySequence.CompareResult.NONE; - } - } - if (first.length < second.length) { - if (shadow === false) { - return KeySequence.CompareResult.PARTIAL; - } else { - return KeySequence.CompareResult.SHADOW; - } - } - return KeySequence.CompareResult.FULL; - } - - export function parse(keybinding: string): KeySequence { - const keyCodes = []; - const rawKeyCodes = keybinding.trim().split(/\s+/g); - for (const rawKeyCode of rawKeyCodes) { - const keyCode = KeyCode.parse(rawKeyCode); - if (keyCode !== undefined) { - keyCodes.push(keyCode); - } - } - return keyCodes; - } - - /** - * Return an array of strings representing the keys in this keysequence. - * For example ['ctrlcmd p', 'ctrlcmd k']. The character used between - * keys can be overriden using `separator`. - */ - export function acceleratorFor(keySequence: KeySequence, separator: string = ' '): string[] { - const result: string[] = []; - for (const keyCode of keySequence) { - let keyCodeResult = ''; - let previous = false; - - if (keyCode.meta && isOSX) { - if (isOSX) { - keyCodeResult += 'Cmd'; - previous = true; - } - } - - if (keyCode.ctrl) { - if (previous) { - keyCodeResult += separator; - } - keyCodeResult += 'Ctrl'; - previous = true; - } - - if (keyCode.alt) { - if (previous) { - keyCodeResult += separator; - } - keyCodeResult += 'Alt'; - previous = true; - } - - if (keyCode.shift) { - if (previous) { - keyCodeResult += separator; - } - keyCodeResult += 'Shift'; - previous = true; - } - - if (keyCode.key) { - - if (previous) { - keyCodeResult += separator; - } - - const keyString = Key.getEasyKey(keyCode.key).easyString; - - if (keyCode.key.keyCode >= Key.KEY_A.keyCode && keyCode.key.keyCode <= Key.KEY_Z.keyCode || - keyCode.key.keyCode >= Key.F1.keyCode && keyCode.key.keyCode <= Key.F24.keyCode) { - /* We want to capitalize single letters and function keys */ - keyCodeResult += keyString.toUpperCase(); - } else if (Key.getEasyKey(keyCode.key).easyString.length > 1) { - /* We want to only capitalize the first letter */ - keyCodeResult += keyString.charAt(0).toUpperCase() + keyString.slice(1); - } else { - /* Return the symbol as is */ - keyCodeResult += keyString; - } - } - result.push(keyCodeResult); - } - return result; - } -} - -/** - * Representation of a platform independent key code. - */ -export class KeyCode { - - public readonly key: Key | undefined = undefined; - public readonly ctrl: boolean; - public readonly shift: boolean; - public readonly alt: boolean; - public readonly meta: boolean; - private static keybindings: { [key: string]: KeyCode } = {}; - - public constructor(public readonly keystroke: string, public readonly character?: string) { - const parts = keystroke.split('+'); - - if (parts.every(KeyCode.isModifierString) === false) { - this.key = Key.getKey(parts[0]); - } - - if (isOSX) { - this.meta = parts.some(part => part === KeyModifier.CtrlCmd); - this.shift = parts.some(part => part === KeyModifier.Shift); - this.alt = parts.some(part => part === KeyModifier.Alt); - this.ctrl = parts.some(part => part === KeyModifier.MacCtrl); - } else { - this.meta = false; - this.ctrl = parts.some(part => part === KeyModifier.CtrlCmd); - this.shift = parts.some(part => part === KeyModifier.Shift); - this.alt = parts.some(part => part === KeyModifier.Alt); - } - } - - /** - * Returns a normalized version of this keycode, if - * - shift or alt was pressed - * - meta or ctrl was pressed - * - a character exists - * - the character corresponds to a key on the US keyboard layout - * - * The resulting KeyCode will remove the shift/alt keys and use the character as the key. - * For instance on a german kb layout the sequence `ctrlCmd+shift+7` would be translated to `ctrlCmd+/`. - * @returns a normalized keycode or undefined - */ - public normalizeToUsLayout(): KeyCode | undefined { - if ((this.shift || this.alt) && (this.meta || this.ctrl) && this.character && EASY_TO_KEY[this.character]) { - const modifiers: KeyModifier[] = []; - if (isOSX) { - if (this.meta) { - modifiers.push(KeyModifier.CtrlCmd); - } - if (this.ctrl) { - modifiers.push(KeyModifier.MacCtrl); - } - } else { - if (this.ctrl) { - modifiers.push(KeyModifier.CtrlCmd); - } - } - return KeyCode.createKeyCode({ first: EASY_TO_KEY[this.character], modifiers }); - } - return undefined; - } - - /* Return true of string is a modifier M1 to M4 */ - public static isModifierString(key: string) { - if (key === KeyModifier.CtrlCmd - || key === KeyModifier.Shift - || key === KeyModifier.Alt - || key === KeyModifier.MacCtrl) { - return true; - } - return false; - } - - /** - * Returns true KeyCode only contains modifiers. - */ - public isModifierOnly() { - if (this.key === undefined) { - return true; - } else { - return false; - } - } - - /** - * Parses a string and returns a KeyCode object. - * @param keybinding String representation of a keybinding - */ - public static parse(keybinding: string): KeyCode { - - if (KeyCode.keybindings[keybinding]) { - return KeyCode.keybindings[keybinding]; - } - - const sequence: string[] = []; - const keys = keybinding.trim().toLowerCase().split('+'); - /* If duplicates i.e ctrl+ctrl+a or alt+alt+b or b+alt+b it is invalid */ - if (keys.length !== new Set(keys).size) { - throw new Error(`Can't parse keybinding ${keybinding} Duplicate modifiers`); - } - - for (let keyString of keys) { - if (SPECIAL_ALIASES[keyString] !== undefined) { - keyString = SPECIAL_ALIASES[keyString]; - } - const key = EASY_TO_KEY[keyString]; - - /* meta only works on macOS */ - if (keyString === SpecialCases.META) { - if (isOSX) { - sequence.push(`${KeyModifier.CtrlCmd}`); - } else { - throw new Error(`Can't parse keybinding ${keybinding} meta is for OSX only`); - } - /* ctrlcmd for M1 keybindings that work on both macOS and other platforms */ - } else if (keyString === SpecialCases.CTRLCMD) { - sequence.push(`${KeyModifier.CtrlCmd}`); - } else if (Key.isKey(key)) { - if (Key.isModifier(key.code)) { - if (key.keyCode === EasyKey.CONTROL.keyCode) { - // CTRL on MacOS X (M4) - if (isOSX) { - sequence.push(`${KeyModifier.MacCtrl}`); - } else { - sequence.push(`${KeyModifier.CtrlCmd}`); - } - } else if (key.keyCode === EasyKey.SHIFT.keyCode) { - sequence.push(`${KeyModifier.Shift}`); - } else if (key.keyCode === EasyKey.ALT.keyCode) { - sequence.push(`${KeyModifier.Alt}`); - } - } else { - sequence.unshift(key.code); - } - } else { - throw new Error(`Unrecognized key '${keyString}' in '${keybinding}'`); - } - } - - // We need to sort the modifier keys, but on the modifiers, so it always keeps the M1 less than M2, M2 less than M3 and so on order. - // We intentionally ignore other cases. - sequence.sort((left: string, right: string) => KeyModifier.isModifier(left) && KeyModifier.isModifier(right) ? left.localeCompare(right) : 0); - KeyCode.keybindings[keybinding] = new KeyCode(sequence.join('+')); - return KeyCode.keybindings[keybinding]; - } - - public static createKeyCode(event: KeyboardEvent | Keystroke): KeyCode { - if (KeyCode.isKeyboardEvent(event)) { - const code = KeyCode.toCode(event); - - const sequence: string[] = []; - if (!Key.isModifier(code)) { - sequence.push(code); - } - - // CTRL + COMMAND (M1) - if ((isOSX && event.metaKey) || (!isOSX && event.ctrlKey)) { - sequence.push(`${KeyModifier.CtrlCmd}`); - } - - // SHIFT (M2) - if (event.shiftKey) { - sequence.push(`${KeyModifier.Shift}`); - } - - // ALT (M3) - if (event.altKey) { - sequence.push(`${KeyModifier.Alt}`); - } - - // CTRL on MacOS X (M4) - if (isOSX && !event.metaKey && event.ctrlKey) { - sequence.push(`${KeyModifier.MacCtrl}`); - } - - return new KeyCode(sequence.join('+'), KeyCode.toCharacter(event)); - } else { - const key = event.first ? [event.first.code] : []; - return new KeyCode(key - .concat((event.modifiers || []).sort().map(modifier => `${modifier}`)) - .join('+')); - } - } - - /** - * Determine a code that can be turned into a Key using `Key.getKey()`. - * - * This function should consider the actual keyboard layout as much as possible, which is not supported - * by the `code` property of `KeyboardEvent`. Some keyboard layouts contain keys that cannot be represented - * with such a code (e.g. the Ö key on German keyboards). In these cases this function often returns - * the key code according to the US keyboard layout; `toCharacter()` might deliver a more appropriate hint - * on which key was actually pressed. - * - * `keyIdentifier` is used to access this deprecated field: - * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyIdentifier - */ - public static toCode(event: KeyboardEvent & { readonly keyIdentifier?: string }): string { - // A system and implementation dependent numerical code identifying the unmodified value of the pressed key. - if (event.keyCode) { - const key = Key.getKey(event.keyCode); - if (key) { - return key.code; - } - } - // A "key identifier" string that can be used to determine what key was pressed. - if (event.keyIdentifier) { - const key = Key.getKey(event.keyIdentifier); - if (key) { - return key.code; - } - } - // The numeric keyCode of the key pressed, or the character code (charCode) for an alphanumeric key pressed. - if (event.which) { - const key = Key.getKey(event.which); - if (key) { - return key.code; - } - } - // A physical key on the keyboard, as opposed to the character generated by pressing the key. - // This property returns a value which isn't altered by keyboard layout or the state of the modifier keys. - if (event.code) { - const key = Key.getKey(event.code); - if (key) { - return key.code; - } - } - throw new Error(`Cannot get key code from the keyboard event: ${event}.`); - } - - /** - * Determine the actual printable character that is generated from a pressed key. - * If the key does not correspond to a printable character, `undefined` is returned. - * The result may be altered by modifier keys. - */ - public static toCharacter(event: KeyboardEvent): string | undefined { - const key = event.key; - // Use the key property if it contains exactly one unicode character - if (key && Array.from(key).length === 1) { - return key; - } - const charCode = event.charCode; - // Use the charCode property if it does not correspond to a unicode control character - if (charCode && charCode > 0x1f && !(charCode >= 0x80 && charCode <= 0x9f)) { - return String.fromCharCode(charCode); - } - return undefined; - } - - public static equals(keyCode1: KeyCode, keyCode2: KeyCode): boolean { - return JSON.stringify(keyCode1) === JSON.stringify(keyCode2); - } - - equals(event: KeyboardEvent | KeyCode): boolean { - return (event instanceof KeyCode ? event : KeyCode.createKeyCode(event)).keystroke === this.keystroke; - } - - /* Reset the key hashmap, this is for testing purposes. */ - public static resetKeyBindings() { - KeyCode.keybindings = {}; - } - - /* Return a keybinding string compatible with the Keybinding.keybinding property. */ - toString() { - let result = ''; - let previous = false; - - if (this.meta) { - result += SpecialCases.META; - previous = true; - } - if (this.shift) { - if (previous) { - result += '+'; - } - result += EasyKey.SHIFT.easyString; - previous = true; - } - if (this.alt) { - if (previous) { - result += '+'; - } - result += EasyKey.ALT.easyString; - previous = true; - } - if (this.ctrl) { - if (previous) { - result += '+'; - } - result += EasyKey.CONTROL.easyString; - previous = true; - } - - if (this.key) { - if (previous) { - result += '+'; - } - - result += KEY_CODE_TO_EASY[this.key.keyCode].easyString; - } - return result; - } -} - -export namespace KeyCode { - - /** - * Determines a `true` of `false` value for the key code argument. - */ - export type Predicate = (keyCode: KeyCode) => boolean; - - /** - * Different scopes have different execution environments. This means that they have different built-ins - * (different global object, different constructors, etc.). This may result in unexpected results. For instance, - * `[] instanceof window.frames[0].Array` will return `false`, because `Array.prototype !== window.frames[0].Array` - * and arrays inherit from the former. - * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof - * - * Note: just add another check if the current `event.type` checking is insufficient. - */ - export function isKeyboardEvent(event: object & Readonly<{ type?: string }>): event is KeyboardEvent { - if (event instanceof KeyboardEvent) { - return true; - } - const { type } = event; - if (type) { - return type === 'keypress' || type === 'keydown' || type === 'keyup'; - } - return false; - } - -} - -export enum KeyModifier { - /** - * M1 is the COMMAND key on MacOS X, and the CTRL key on most other platforms. - */ - CtrlCmd = 'M1', - /** - * M2 is the SHIFT key. - */ - Shift = 'M2', - /** - * M3 is the Option key on MacOS X, and the ALT key on most other platforms. - */ - Alt = 'M3', - /** - * M4 is the CTRL key on MacOS X, and is undefined on other platforms. - */ - MacCtrl = 'M4' -} - -export namespace KeyModifier { - /** - * The CTRL key, independently of the platform. - * _Note:_ In general `KeyModifier.CtrlCmd` should be preferred over this constant. - */ - export const CTRL: KeyModifier.MacCtrl | KeyModifier.CtrlCmd = isOSX ? KeyModifier.MacCtrl : KeyModifier.CtrlCmd; - /** - * An alias for the SHIFT key (`KeyModifier.Shift`). - */ - export const SHIFT: KeyModifier.Shift = KeyModifier.Shift; - - /** - * `true` if the argument represents a modifier. Otherwise, `false`. - */ - export function isModifier(key: string | undefined): boolean { - if (key) { - switch (key) { - case 'M1': // Fall through. - case 'M2': // Fall through. - case 'M3': // Fall through. - case 'M4': return true; - default: return false; - } - } - return false; - } -} - -export interface Key { - readonly code: string; - readonly keyCode: number; -} - -export interface EasyKey { - readonly keyCode: number; - readonly easyString: string; -} - -const CODE_TO_KEY: { [code: string]: Key } = {}; -const KEY_CODE_TO_KEY: { [keyCode: number]: Key } = {}; -const KEY_CODE_TO_EASY: { [keyCode: number]: EasyKey } = {}; -const EASY_TO_KEY: { [code: string]: Key } = {}; // From 'ctrl' to Key structure -const MODIFIERS: Key[] = []; - -const SPECIAL_ALIASES: { [index: string]: string } = { - 'option': 'alt', - 'command': 'meta', - 'cmd': 'meta', - 'return': 'enter', - 'esc': 'escape', - 'mod': 'ctrl', - 'ins': 'insert', - 'del': 'delete', - 'control': 'ctrl', -}; - -export namespace SpecialCases { - export const META = 'meta'; - export const CTRLCMD = 'ctrlcmd'; -} - -export namespace EasyKey { - export const ENTER: EasyKey = { keyCode: 13, easyString: 'enter' }; - export const SPACE: EasyKey = { keyCode: 32, easyString: 'space' }; - export const BACKSPACE: EasyKey = { keyCode: 8, easyString: 'backspace' }; - export const TAB: EasyKey = { keyCode: 9, easyString: 'tab' }; - export const DELETE: EasyKey = { keyCode: 46, easyString: 'delete' }; - export const END: EasyKey = { keyCode: 35, easyString: 'end' }; - export const HOME: EasyKey = { keyCode: 36, easyString: 'home' }; - export const INSERT: EasyKey = { keyCode: 45, easyString: 'insert' }; - export const PAGE_DOWN: EasyKey = { keyCode: 34, easyString: 'pagedown' }; - export const PAGE_UP: EasyKey = { keyCode: 33, easyString: 'pageup' }; - export const ARROW_DOWN: EasyKey = { keyCode: 40, easyString: 'down' }; - export const ARROW_LEFT: EasyKey = { keyCode: 37, easyString: 'left' }; - export const ARROW_RIGHT: EasyKey = { keyCode: 39, easyString: 'right' }; - export const ARROW_UP: EasyKey = { keyCode: 38, easyString: 'up' }; - export const ESCAPE: EasyKey = { keyCode: 27, easyString: 'escape' }; - - export const ALT: EasyKey = { keyCode: 18, easyString: 'alt' }; - export const CAPS_LOCK: EasyKey = { keyCode: 20, easyString: 'capslock' }; - export const CONTROL: EasyKey = { keyCode: 17, easyString: 'ctrl' }; - export const OS: EasyKey = { keyCode: 91, easyString: 'super' }; - export const SHIFT: EasyKey = { keyCode: 16, easyString: 'shift' }; - - export const DIGIT1: EasyKey = { keyCode: 49, easyString: '1' }; - export const DIGIT2: EasyKey = { keyCode: 50, easyString: '2' }; - export const DIGIT3: EasyKey = { keyCode: 51, easyString: '3' }; - export const DIGIT4: EasyKey = { keyCode: 52, easyString: '4' }; - export const DIGIT5: EasyKey = { keyCode: 53, easyString: '5' }; - export const DIGIT6: EasyKey = { keyCode: 54, easyString: '6' }; - export const DIGIT7: EasyKey = { keyCode: 55, easyString: '7' }; - export const DIGIT8: EasyKey = { keyCode: 56, easyString: '8' }; - export const DIGIT9: EasyKey = { keyCode: 57, easyString: '9' }; - export const DIGIT0: EasyKey = { keyCode: 48, easyString: '0' }; - - export const KEY_A: EasyKey = { keyCode: 65, easyString: 'a' }; - export const KEY_B: EasyKey = { keyCode: 66, easyString: 'b' }; - export const KEY_C: EasyKey = { keyCode: 67, easyString: 'c' }; - export const KEY_D: EasyKey = { keyCode: 68, easyString: 'd' }; - export const KEY_E: EasyKey = { keyCode: 69, easyString: 'e' }; - export const KEY_F: EasyKey = { keyCode: 70, easyString: 'f' }; - export const KEY_G: EasyKey = { keyCode: 71, easyString: 'g' }; - export const KEY_H: EasyKey = { keyCode: 72, easyString: 'h' }; - export const KEY_I: EasyKey = { keyCode: 73, easyString: 'i' }; - export const KEY_J: EasyKey = { keyCode: 74, easyString: 'j' }; - export const KEY_K: EasyKey = { keyCode: 75, easyString: 'k' }; - export const KEY_L: EasyKey = { keyCode: 76, easyString: 'l' }; - export const KEY_M: EasyKey = { keyCode: 77, easyString: 'm' }; - export const KEY_N: EasyKey = { keyCode: 78, easyString: 'n' }; - export const KEY_O: EasyKey = { keyCode: 79, easyString: 'o' }; - export const KEY_P: EasyKey = { keyCode: 80, easyString: 'p' }; - export const KEY_Q: EasyKey = { keyCode: 81, easyString: 'q' }; - export const KEY_R: EasyKey = { keyCode: 82, easyString: 'r' }; - export const KEY_S: EasyKey = { keyCode: 83, easyString: 's' }; - export const KEY_T: EasyKey = { keyCode: 84, easyString: 't' }; - export const KEY_U: EasyKey = { keyCode: 85, easyString: 'u' }; - export const KEY_V: EasyKey = { keyCode: 86, easyString: 'v' }; - export const KEY_W: EasyKey = { keyCode: 87, easyString: 'w' }; - export const KEY_X: EasyKey = { keyCode: 88, easyString: 'x' }; - export const KEY_Y: EasyKey = { keyCode: 89, easyString: 'y' }; - export const KEY_Z: EasyKey = { keyCode: 90, easyString: 'z' }; - - export const MULTIPLY: EasyKey = { keyCode: 106, easyString: 'multiply' }; - export const ADD: EasyKey = { keyCode: 107, easyString: 'add' }; - export const DECIMAL: EasyKey = { keyCode: 108, easyString: 'decimal' }; - export const SUBTRACT: EasyKey = { keyCode: 109, easyString: 'subtract' }; - export const DIVIDE: EasyKey = { keyCode: 111, easyString: 'divide' }; - - export const F1: EasyKey = { keyCode: 112, easyString: 'f1' }; - export const F2: EasyKey = { keyCode: 113, easyString: 'f2' }; - export const F3: EasyKey = { keyCode: 114, easyString: 'f3' }; - export const F4: EasyKey = { keyCode: 115, easyString: 'f4' }; - export const F5: EasyKey = { keyCode: 116, easyString: 'f5' }; - export const F6: EasyKey = { keyCode: 117, easyString: 'f6' }; - export const F7: EasyKey = { keyCode: 118, easyString: 'f7' }; - export const F8: EasyKey = { keyCode: 119, easyString: 'f8' }; - export const F9: EasyKey = { keyCode: 120, easyString: 'f9' }; - export const F10: EasyKey = { keyCode: 121, easyString: 'f10' }; - export const F11: EasyKey = { keyCode: 122, easyString: 'f11' }; - export const F12: EasyKey = { keyCode: 123, easyString: 'f12' }; - export const F13: EasyKey = { keyCode: 124, easyString: 'f13' }; - export const F14: EasyKey = { keyCode: 125, easyString: 'f14' }; - export const F15: EasyKey = { keyCode: 126, easyString: 'f15' }; - export const F16: EasyKey = { keyCode: 127, easyString: 'f16' }; - export const F17: EasyKey = { keyCode: 128, easyString: 'f17' }; - export const F18: EasyKey = { keyCode: 129, easyString: 'f18' }; - export const F19: EasyKey = { keyCode: 130, easyString: 'f19' }; - export const F20: EasyKey = { keyCode: 131, easyString: 'f20' }; - export const F21: EasyKey = { keyCode: 132, easyString: 'f21' }; - export const F22: EasyKey = { keyCode: 133, easyString: 'f22' }; - export const F23: EasyKey = { keyCode: 134, easyString: 'f23' }; - export const F24: EasyKey = { keyCode: 135, easyString: 'f24' }; - - export const NUM_LOCK: EasyKey = { keyCode: 144, easyString: 'numlock' }; - export const COMMA: EasyKey = { keyCode: 188, easyString: ',' }; - export const PERIOD: EasyKey = { keyCode: 190, easyString: '.' }; - export const SLASH: EasyKey = { keyCode: 191, easyString: '/' }; - export const SEMICOLON: EasyKey = { keyCode: 186, easyString: ';' }; - export const QUOTE: EasyKey = { keyCode: 222, easyString: '\'' }; - export const BRACKET_LEFT: EasyKey = { keyCode: 219, easyString: '[' }; - export const BRACKET_RIGHT: EasyKey = { keyCode: 221, easyString: ']' }; - export const BACKQUOTE: EasyKey = { keyCode: 192, easyString: '`' }; - export const BACKSLASH: EasyKey = { keyCode: 220, easyString: '\\' }; - export const MINUS: EasyKey = { keyCode: 189, easyString: '-' }; - export const EQUAL: EasyKey = { keyCode: 187, easyString: '=' }; - export const INTL_RO: EasyKey = { keyCode: 193, easyString: 'intlro' }; - export const INTL_YEN: EasyKey = { keyCode: 255, easyString: 'intlyen' }; -} - -export namespace Key { - - // tslint:disable-next-line:no-any - export function isKey(arg: any): arg is Key { - return !!arg && ('code' in arg) && ('keyCode' in arg); - } - - export function getKey(arg: string | number): Key | undefined { - if (typeof arg === 'number') { - return KEY_CODE_TO_KEY[arg]; - } else { - return CODE_TO_KEY[arg]; - } - } - - export function getEasyKey(key: Key): EasyKey { - return KEY_CODE_TO_EASY[key.keyCode]; - } - - export function isModifier(arg: string | number): boolean { - if (typeof arg === 'number') { - return MODIFIERS.map(key => key.keyCode).indexOf(arg) > 0; - } - return MODIFIERS.map(key => key.code).indexOf(arg) > 0; - } - - export function equals(key: Key, keyCode: KeyCode): boolean { - return !!keyCode.key && key.keyCode === keyCode.key.keyCode; - } - - export const BACKSPACE: Key = { code: 'Backspace', keyCode: 8 }; - export const TAB: Key = { code: 'Tab', keyCode: 9 }; - export const ENTER: Key = { code: 'Enter', keyCode: 13 }; - export const ESCAPE: Key = { code: 'Escape', keyCode: 27 }; - export const SPACE: Key = { code: 'Space', keyCode: 32 }; - export const PAGE_UP: Key = { code: 'PageUp', keyCode: 33 }; - export const PAGE_DOWN: Key = { code: 'PageDown', keyCode: 34 }; - export const END: Key = { code: 'End', keyCode: 35 }; - export const HOME: Key = { code: 'Home', keyCode: 36 }; - export const ARROW_LEFT: Key = { code: 'ArrowLeft', keyCode: 37 }; - export const ARROW_UP: Key = { code: 'ArrowUp', keyCode: 38 }; - export const ARROW_RIGHT: Key = { code: 'ArrowRight', keyCode: 39 }; - export const ARROW_DOWN: Key = { code: 'ArrowDown', keyCode: 40 }; - export const INSERT: Key = { code: 'Insert', keyCode: 45 }; - export const DELETE: Key = { code: 'Delete', keyCode: 46 }; - - export const SHIFT_LEFT: Key = { code: 'ShiftLeft', keyCode: 16 }; - export const SHIFT_RIGHT: Key = { code: 'ShiftRight', keyCode: 16 }; - export const CONTROL_LEFT: Key = { code: 'ControlLeft', keyCode: 17 }; - export const CONTROL_RIGHT: Key = { code: 'ControlRight', keyCode: 17 }; - export const ALT_LEFT: Key = { code: 'AltLeft', keyCode: 18 }; - export const ALT_RIGHT: Key = { code: 'AltRight', keyCode: 18 }; - export const CAPS_LOCK: Key = { code: 'CapsLock', keyCode: 20 }; - export const OS_LEFT: Key = { code: 'OSLeft', keyCode: 91 }; - export const OS_RIGHT: Key = { code: 'OSRight', keyCode: 92 }; - - export const DIGIT0: Key = { code: 'Digit0', keyCode: 48 }; - export const DIGIT1: Key = { code: 'Digit1', keyCode: 49 }; - export const DIGIT2: Key = { code: 'Digit2', keyCode: 50 }; - export const DIGIT3: Key = { code: 'Digit3', keyCode: 51 }; - export const DIGIT4: Key = { code: 'Digit4', keyCode: 52 }; - export const DIGIT5: Key = { code: 'Digit5', keyCode: 53 }; - export const DIGIT6: Key = { code: 'Digit6', keyCode: 54 }; - export const DIGIT7: Key = { code: 'Digit7', keyCode: 55 }; - export const DIGIT8: Key = { code: 'Digit8', keyCode: 56 }; - export const DIGIT9: Key = { code: 'Digit9', keyCode: 57 }; - - export const KEY_A: Key = { code: 'KeyA', keyCode: 65 }; - export const KEY_B: Key = { code: 'KeyB', keyCode: 66 }; - export const KEY_C: Key = { code: 'KeyC', keyCode: 67 }; - export const KEY_D: Key = { code: 'KeyD', keyCode: 68 }; - export const KEY_E: Key = { code: 'KeyE', keyCode: 69 }; - export const KEY_F: Key = { code: 'KeyF', keyCode: 70 }; - export const KEY_G: Key = { code: 'KeyG', keyCode: 71 }; - export const KEY_H: Key = { code: 'KeyH', keyCode: 72 }; - export const KEY_I: Key = { code: 'KeyI', keyCode: 73 }; - export const KEY_J: Key = { code: 'KeyJ', keyCode: 74 }; - export const KEY_K: Key = { code: 'KeyK', keyCode: 75 }; - export const KEY_L: Key = { code: 'KeyL', keyCode: 76 }; - export const KEY_M: Key = { code: 'KeyM', keyCode: 77 }; - export const KEY_N: Key = { code: 'KeyN', keyCode: 78 }; - export const KEY_O: Key = { code: 'KeyO', keyCode: 79 }; - export const KEY_P: Key = { code: 'KeyP', keyCode: 80 }; - export const KEY_Q: Key = { code: 'KeyQ', keyCode: 81 }; - export const KEY_R: Key = { code: 'KeyR', keyCode: 82 }; - export const KEY_S: Key = { code: 'KeyS', keyCode: 83 }; - export const KEY_T: Key = { code: 'KeyT', keyCode: 84 }; - export const KEY_U: Key = { code: 'KeyU', keyCode: 85 }; - export const KEY_V: Key = { code: 'KeyV', keyCode: 86 }; - export const KEY_W: Key = { code: 'KeyW', keyCode: 87 }; - export const KEY_X: Key = { code: 'KeyX', keyCode: 88 }; - export const KEY_Y: Key = { code: 'KeyY', keyCode: 89 }; - export const KEY_Z: Key = { code: 'KeyZ', keyCode: 90 }; - - export const MULTIPLY: Key = { code: 'NumpadMultiply', keyCode: 106 }; - export const ADD: Key = { code: 'NumpadAdd', keyCode: 107 }; - export const DECIMAL: Key = { code: 'NumpadDecimal', keyCode: 108 }; - export const SUBTRACT: Key = { code: 'NumpadSubtract', keyCode: 109 }; - export const DIVIDE: Key = { code: 'NumpadDivide', keyCode: 111 }; - - export const F1: Key = { code: 'F1', keyCode: 112 }; - export const F2: Key = { code: 'F2', keyCode: 113 }; - export const F3: Key = { code: 'F3', keyCode: 114 }; - export const F4: Key = { code: 'F4', keyCode: 115 }; - export const F5: Key = { code: 'F5', keyCode: 116 }; - export const F6: Key = { code: 'F6', keyCode: 117 }; - export const F7: Key = { code: 'F7', keyCode: 118 }; - export const F8: Key = { code: 'F8', keyCode: 119 }; - export const F9: Key = { code: 'F9', keyCode: 120 }; - export const F10: Key = { code: 'F10', keyCode: 121 }; - export const F11: Key = { code: 'F11', keyCode: 122 }; - export const F12: Key = { code: 'F12', keyCode: 123 }; - export const F13: Key = { code: 'F13', keyCode: 124 }; - export const F14: Key = { code: 'F14', keyCode: 125 }; - export const F15: Key = { code: 'F15', keyCode: 126 }; - export const F16: Key = { code: 'F16', keyCode: 127 }; - export const F17: Key = { code: 'F17', keyCode: 128 }; - export const F18: Key = { code: 'F18', keyCode: 129 }; - export const F19: Key = { code: 'F19', keyCode: 130 }; - export const F20: Key = { code: 'F20', keyCode: 131 }; - export const F21: Key = { code: 'F21', keyCode: 132 }; - export const F22: Key = { code: 'F22', keyCode: 133 }; - export const F23: Key = { code: 'F23', keyCode: 134 }; - export const F24: Key = { code: 'F24', keyCode: 135 }; - - export const NUM_LOCK: Key = { code: 'NumLock', keyCode: 144 }; - export const SEMICOLON: Key = { code: 'Semicolon', keyCode: 186 }; - export const EQUAL: Key = { code: 'Equal', keyCode: 187 }; - export const COMMA: Key = { code: 'Comma', keyCode: 188 }; - export const MINUS: Key = { code: 'Minus', keyCode: 189 }; - export const PERIOD: Key = { code: 'Period', keyCode: 190 }; - export const SLASH: Key = { code: 'Slash', keyCode: 191 }; - export const BACKQUOTE: Key = { code: 'Backquote', keyCode: 192 }; - export const INTL_RO: Key = { code: 'IntlRo', keyCode: 193 }; - export const BRACKET_LEFT: Key = { code: 'BracketLeft', keyCode: 219 }; - export const BACKSLASH: Key = { code: 'Backslash', keyCode: 220 }; - export const BRACKET_RIGHT: Key = { code: 'BracketRight', keyCode: 221 }; - export const QUOTE: Key = { code: 'Quote', keyCode: 222 }; - export const INTL_YEN: Key = { code: 'IntlYen', keyCode: 255 }; - -} - -/*-------------------- Initialize the static key mappings --------------------*/ -(() => { - // Set the default key mappings from the constants in the Key namespace - Object.keys(Key).map(prop => Reflect.get(Key, prop)).filter(key => Key.isKey(key)).forEach(key => { - CODE_TO_KEY[key.code] = key; - KEY_CODE_TO_KEY[key.keyCode] = key; - }); - - // Set additional key mappings - CODE_TO_KEY['Numpad0'] = Key.DIGIT0; - KEY_CODE_TO_KEY[96] = Key.DIGIT0; - CODE_TO_KEY['Numpad1'] = Key.DIGIT1; - KEY_CODE_TO_KEY[97] = Key.DIGIT1; - CODE_TO_KEY['Numpad2'] = Key.DIGIT2; - KEY_CODE_TO_KEY[98] = Key.DIGIT2; - CODE_TO_KEY['Numpad3'] = Key.DIGIT3; - KEY_CODE_TO_KEY[99] = Key.DIGIT3; - CODE_TO_KEY['Numpad4'] = Key.DIGIT4; - KEY_CODE_TO_KEY[100] = Key.DIGIT4; - CODE_TO_KEY['Numpad5'] = Key.DIGIT5; - KEY_CODE_TO_KEY[101] = Key.DIGIT5; - CODE_TO_KEY['Numpad6'] = Key.DIGIT6; - KEY_CODE_TO_KEY[102] = Key.DIGIT6; - CODE_TO_KEY['Numpad7'] = Key.DIGIT7; - KEY_CODE_TO_KEY[103] = Key.DIGIT7; - CODE_TO_KEY['Numpad8'] = Key.DIGIT8; - KEY_CODE_TO_KEY[104] = Key.DIGIT8; - CODE_TO_KEY['Numpad9'] = Key.DIGIT9; - KEY_CODE_TO_KEY[105] = Key.DIGIT9; - CODE_TO_KEY['NumpadEnter'] = Key.ENTER; - CODE_TO_KEY['NumpadEqual'] = Key.EQUAL; - CODE_TO_KEY['IntlBackslash'] = Key.BACKSLASH; - CODE_TO_KEY['MetaLeft'] = Key.OS_LEFT; // Chrome, Safari - KEY_CODE_TO_KEY[224] = Key.OS_LEFT; // Firefox on Mac - CODE_TO_KEY['MetaRight'] = Key.OS_RIGHT; // Chrome, Safari - KEY_CODE_TO_KEY[93] = Key.OS_RIGHT; // Chrome, Safari, Edge - KEY_CODE_TO_KEY[225] = Key.ALT_RIGHT; // Linux - KEY_CODE_TO_KEY[110] = Key.DECIMAL; // Mac, Windows - KEY_CODE_TO_KEY[59] = Key.SEMICOLON; // Firefox - KEY_CODE_TO_KEY[61] = Key.EQUAL; // Firefox - KEY_CODE_TO_KEY[173] = Key.MINUS; // Firefox - KEY_CODE_TO_KEY[226] = Key.BACKSLASH; // Chrome, Edge on Windows - KEY_CODE_TO_KEY[60] = Key.BACKSLASH; // Firefox on Linux - - // Set the default easy key mappings from the constants in the EasyKey namespace - Object.keys(EasyKey).map(prop => Reflect.get(EasyKey, prop)).forEach(easyKey => { - EASY_TO_KEY[easyKey.easyString] = KEY_CODE_TO_KEY[easyKey.keyCode]; - KEY_CODE_TO_EASY[easyKey.keyCode] = easyKey; - }); - - // Set additional easy key mappings - KEY_CODE_TO_EASY[91] = EasyKey.OS; - - // Set the modifier keys - MODIFIERS.push(...[Key.ALT_LEFT, Key.ALT_RIGHT, Key.CONTROL_LEFT, Key.CONTROL_RIGHT, Key.OS_LEFT, Key.OS_RIGHT, Key.SHIFT_LEFT, Key.SHIFT_RIGHT]); -})(); - -export type KeysOrKeyCodes = Key | KeyCode | (Key | KeyCode)[]; -export namespace KeysOrKeyCodes { - - export const toKeyCode = (keyOrKeyCode: Key | KeyCode) => - keyOrKeyCode instanceof KeyCode ? keyOrKeyCode : KeyCode.createKeyCode({ first: keyOrKeyCode }); - - export const toKeyCodes = (keysOrKeyCodes: KeysOrKeyCodes) => { - if (keysOrKeyCodes instanceof KeyCode) { - return [keysOrKeyCodes]; - } else if (Array.isArray(keysOrKeyCodes)) { - return keysOrKeyCodes.slice().map(toKeyCode); - } - return [toKeyCode(keysOrKeyCodes)]; - }; - -} +// Reexporting here for backwards compatibility. +// Please import from '@theia/core/lib/browser' or '@theia/core/lib/browser/keyboard' instead of this module. +// This module might be removed in future releases. +import { KeySequence, Keystroke, KeyCode, KeyModifier, Key, SpecialCases, KeysOrKeyCodes } from './keyboard/keys'; +export { KeySequence, Keystroke, KeyCode, KeyModifier, Key, SpecialCases, KeysOrKeyCodes }; diff --git a/packages/core/src/browser/menu/browser-menu-plugin.ts b/packages/core/src/browser/menu/browser-menu-plugin.ts index 09ec2511df768..a432ad7397b85 100644 --- a/packages/core/src/browser/menu/browser-menu-plugin.ts +++ b/packages/core/src/browser/menu/browser-menu-plugin.ts @@ -21,7 +21,7 @@ import { CommandRegistry, ActionMenuNode, CompositeMenuNode, MenuModelRegistry, MAIN_MENU_BAR, MenuPath, ILogger } from '../../common'; -import { KeybindingRegistry, Keybinding } from '../keybinding'; +import { KeybindingRegistry } from '../keybinding'; import { FrontendApplicationContribution, FrontendApplication } from '../frontend-application'; import { ContextKeyService } from '../context-key-service'; import { Anchor } from '../context-menu-renderer'; @@ -44,6 +44,16 @@ export class BrowserMainMenuFactory { createMenuBar(): MenuBarWidget { const menuBar = new DynamicMenuBarWidget(); menuBar.id = 'theia:menubar'; + this.fillMenuBar(menuBar); + const listener = this.keybindingRegistry.onKeybindingsChanged(() => { + menuBar.clearMenus(); + this.fillMenuBar(menuBar); + }); + menuBar.disposed.connect(() => listener.dispose()); + return menuBar; + } + + protected fillMenuBar(menuBar: MenuBarWidget): void { const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR); const phosphorCommands = this.createPhosphorCommands(menuModel); // for the main menu we want all items to be visible. @@ -55,7 +65,6 @@ export class BrowserMainMenuFactory { menuBar.addMenu(menuWidget); } } - return menuBar; } createContextMenu(path: MenuPath, anchor?: Anchor): MenuWidget { @@ -106,7 +115,7 @@ export class BrowserMainMenuFactory { /* Only consider the first keybinding. */ if (bindings.length > 0) { const binding = bindings[0]; - const keys = Keybinding.acceleratorFor(binding); + const keys = this.keybindingRegistry.acceleratorFor(binding); commands.addKeyBinding({ command: command.id, keys, diff --git a/packages/core/src/browser/saveable.ts b/packages/core/src/browser/saveable.ts index 10006a62b779a..921e648182fa9 100644 --- a/packages/core/src/browser/saveable.ts +++ b/packages/core/src/browser/saveable.ts @@ -17,7 +17,7 @@ import { Widget } from '@phosphor/widgets'; import { Message } from '@phosphor/messaging'; import { Event, MaybePromise } from '../common'; -import { Key } from './keys'; +import { Key } from './keyboard/keys'; import { AbstractDialog } from './dialogs'; export interface Saveable { diff --git a/packages/core/src/browser/tree/search-box.ts b/packages/core/src/browser/tree/search-box.ts index 867b627f63b4f..d969028bf4241 100644 --- a/packages/core/src/browser/tree/search-box.ts +++ b/packages/core/src/browser/tree/search-box.ts @@ -17,7 +17,7 @@ import { SearchBoxDebounce, SearchBoxDebounceOptions } from '../tree/search-box-debounce'; import { BaseWidget, Message } from '../widgets/widget'; import { Emitter, Event } from '../../common/event'; -import { KeyCode, Key } from '../keys'; +import { KeyCode, Key } from '../keyboard/keys'; /** * Initializer properties for the search box widget. diff --git a/packages/core/src/browser/tree/tree-widget.tsx b/packages/core/src/browser/tree/tree-widget.tsx index 78045a4bad6dd..a6d828e265181 100644 --- a/packages/core/src/browser/tree/tree-widget.tsx +++ b/packages/core/src/browser/tree/tree-widget.tsx @@ -17,7 +17,7 @@ import { injectable, inject, postConstruct } from 'inversify'; import { Message } from '@phosphor/messaging'; import { Disposable, MenuPath, SelectionService } from '../../common'; -import { Key, KeyCode, KeyModifier } from '../keys'; +import { Key, KeyCode, KeyModifier } from '../keyboard/keys'; import { ContextMenuRenderer } from '../context-menu-renderer'; import { StatefulWidget } from '../shell'; import { EXPANSION_TOGGLE_CLASS, SELECTED_CLASS, COLLAPSED_CLASS, FOCUS_CLASS, Widget } from '../widgets'; diff --git a/packages/core/src/browser/widgets/widget.ts b/packages/core/src/browser/widgets/widget.ts index 08819d5ffdd5f..3e2ee7b3f17ae 100644 --- a/packages/core/src/browser/widgets/widget.ts +++ b/packages/core/src/browser/widgets/widget.ts @@ -18,7 +18,7 @@ import { injectable, decorate, unmanaged } from 'inversify'; import { Widget } from '@phosphor/widgets'; import { Message } from '@phosphor/messaging'; import { Disposable, DisposableCollection, MaybePromise } from '../../common'; -import { KeyCode, KeysOrKeyCodes } from '../keys'; +import { KeyCode, KeysOrKeyCodes } from '../keyboard/keys'; import PerfectScrollbar from 'perfect-scrollbar'; diff --git a/packages/core/src/common/keyboard/layout-provider.ts b/packages/core/src/common/keyboard/layout-provider.ts new file mode 100644 index 0000000000000..63db472c722b7 --- /dev/null +++ b/packages/core/src/common/keyboard/layout-provider.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { IKeyboardLayoutInfo, IKeyboardMapping } from 'native-keymap'; +import { Event } from '../event'; + +export const keyboardPath = '/services/keyboard'; + +export const KeyboardLayoutProvider = Symbol('KeyboardLayoutProvider'); + +export interface KeyboardLayoutProvider { + getNativeLayout(): Promise; +} + +export const KeyboardLayoutChangeNotifier = Symbol('KeyboardLayoutChangeNotifier'); + +export interface KeyboardLayoutChangeNotifier { + onNativeLayoutChanged: Event; +} + +export interface NativeKeyboardLayout { + info: IKeyboardLayoutInfo; + mapping: IKeyboardMapping; +} diff --git a/packages/core/src/common/keyboard/layouts/linux-de-German.json b/packages/core/src/common/keyboard/layouts/linux-de-German.json new file mode 100644 index 0000000000000..5337a4f14ebde --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/linux-de-German.json @@ -0,0 +1 @@ +{"info":{"model":"pc105","layout":"de","variant":"","options":"numpad:microsoft","rules":"evdev"},"mapping":{"Sleep":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"WakeUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KeyA":{"value":"a","withShift":"A","withAltGr":"æ","withShiftAltGr":"Æ"},"KeyB":{"value":"b","withShift":"B","withAltGr":"“","withShiftAltGr":"‘"},"KeyC":{"value":"c","withShift":"C","withAltGr":"¢","withShiftAltGr":"©"},"KeyD":{"value":"d","withShift":"D","withAltGr":"ð","withShiftAltGr":"Ð"},"KeyE":{"value":"e","withShift":"E","withAltGr":"€","withShiftAltGr":"€"},"KeyF":{"value":"f","withShift":"F","withAltGr":"đ","withShiftAltGr":"ª"},"KeyG":{"value":"g","withShift":"G","withAltGr":"ŋ","withShiftAltGr":"Ŋ"},"KeyH":{"value":"h","withShift":"H","withAltGr":"ħ","withShiftAltGr":"Ħ"},"KeyI":{"value":"i","withShift":"I","withAltGr":"→","withShiftAltGr":"ı"},"KeyJ":{"value":"j","withShift":"J","withAltGr":"̣","withShiftAltGr":"̇"},"KeyK":{"value":"k","withShift":"K","withAltGr":"ĸ","withShiftAltGr":"&"},"KeyL":{"value":"l","withShift":"L","withAltGr":"ł","withShiftAltGr":"Ł"},"KeyM":{"value":"m","withShift":"M","withAltGr":"µ","withShiftAltGr":"º"},"KeyN":{"value":"n","withShift":"N","withAltGr":"”","withShiftAltGr":"’"},"KeyO":{"value":"o","withShift":"O","withAltGr":"ø","withShiftAltGr":"Ø"},"KeyP":{"value":"p","withShift":"P","withAltGr":"þ","withShiftAltGr":"Þ"},"KeyQ":{"value":"q","withShift":"Q","withAltGr":"@","withShiftAltGr":"Ω"},"KeyR":{"value":"r","withShift":"R","withAltGr":"¶","withShiftAltGr":"®"},"KeyS":{"value":"s","withShift":"S","withAltGr":"ſ","withShiftAltGr":"ẞ"},"KeyT":{"value":"t","withShift":"T","withAltGr":"ŧ","withShiftAltGr":"Ŧ"},"KeyU":{"value":"u","withShift":"U","withAltGr":"↓","withShiftAltGr":"↑"},"KeyV":{"value":"v","withShift":"V","withAltGr":"„","withShiftAltGr":"‚"},"KeyW":{"value":"w","withShift":"W","withAltGr":"ł","withShiftAltGr":"Ł"},"KeyX":{"value":"x","withShift":"X","withAltGr":"«","withShiftAltGr":"‹"},"KeyY":{"value":"z","withShift":"Z","withAltGr":"←","withShiftAltGr":"¥"},"KeyZ":{"value":"y","withShift":"Y","withAltGr":"»","withShiftAltGr":"›"},"Digit1":{"value":"1","withShift":"!","withAltGr":"¹","withShiftAltGr":"¡"},"Digit2":{"value":"2","withShift":"\"","withAltGr":"²","withShiftAltGr":"⅛"},"Digit3":{"value":"3","withShift":"§","withAltGr":"³","withShiftAltGr":"£"},"Digit4":{"value":"4","withShift":"$","withAltGr":"¼","withShiftAltGr":"¤"},"Digit5":{"value":"5","withShift":"%","withAltGr":"½","withShiftAltGr":"⅜"},"Digit6":{"value":"6","withShift":"&","withAltGr":"¬","withShiftAltGr":"⅝"},"Digit7":{"value":"7","withShift":"/","withAltGr":"{","withShiftAltGr":"⅞"},"Digit8":{"value":"8","withShift":"(","withAltGr":"[","withShiftAltGr":"™"},"Digit9":{"value":"9","withShift":")","withAltGr":"]","withShiftAltGr":"±"},"Digit0":{"value":"0","withShift":"=","withAltGr":"}","withShiftAltGr":"°"},"Enter":{"value":"\r","withShift":"\r","withAltGr":"\r","withShiftAltGr":"\r"},"Escape":{"value":"\u001b","withShift":"\u001b","withAltGr":"\u001b","withShiftAltGr":"\u001b"},"Backspace":{"value":"\b","withShift":"\b","withAltGr":"\b","withShiftAltGr":"\b"},"Tab":{"value":"\t","withShift":"","withAltGr":"\t","withShiftAltGr":""},"Space":{"value":" ","withShift":" ","withAltGr":" ","withShiftAltGr":" "},"Minus":{"value":"ß","withShift":"?","withAltGr":"\\","withShiftAltGr":"¿"},"Equal":{"value":"́","withShift":"̀","withAltGr":"̧","withShiftAltGr":"̨"},"BracketLeft":{"value":"ü","withShift":"Ü","withAltGr":"̈","withShiftAltGr":"̊"},"BracketRight":{"value":"+","withShift":"*","withAltGr":"~","withShiftAltGr":"¯"},"Backslash":{"value":"#","withShift":"'","withAltGr":"’","withShiftAltGr":"̆"},"Semicolon":{"value":"ö","withShift":"Ö","withAltGr":"̋","withShiftAltGr":"̣"},"Quote":{"value":"ä","withShift":"Ä","withAltGr":"̂","withShiftAltGr":"̌"},"Backquote":{"value":"̂","withShift":"°","withAltGr":"′","withShiftAltGr":"″"},"Comma":{"value":",","withShift":";","withAltGr":"·","withShiftAltGr":"×"},"Period":{"value":".","withShift":":","withAltGr":"…","withShiftAltGr":"÷"},"Slash":{"value":"-","withShift":"_","withAltGr":"–","withShiftAltGr":"—"},"CapsLock":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F3":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F4":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F5":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F6":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F7":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F8":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F9":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F10":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F11":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F12":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PrintScreen":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ScrollLock":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Pause":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Insert":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Home":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Delete":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"End":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumLock":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDivide":{"value":"/","withShift":"/","withAltGr":"/","withShiftAltGr":"/"},"NumpadMultiply":{"value":"*","withShift":"*","withAltGr":"*","withShiftAltGr":"*"},"NumpadSubtract":{"value":"-","withShift":"-","withAltGr":"-","withShiftAltGr":"-"},"NumpadAdd":{"value":"+","withShift":"+","withAltGr":"+","withShiftAltGr":"+"},"NumpadEnter":{"value":"\r","withShift":"\r","withAltGr":"\r","withShiftAltGr":"\r"},"Numpad1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad3":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad4":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad5":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad6":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad7":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad8":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad9":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad0":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDecimal":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlBackslash":{"value":"<","withShift":">","withAltGr":"|","withShiftAltGr":"¦"},"ContextMenu":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Power":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadEqual":{"value":"=","withShift":"=","withAltGr":"=","withShiftAltGr":"="},"F13":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F14":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F15":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F16":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F17":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F18":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F19":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F20":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F21":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F22":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F23":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F24":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Open":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Help":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Select":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Again":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Undo":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Cut":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Copy":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Paste":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Find":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeMute":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadComma":{"value":".","withShift":".","withAltGr":".","withShiftAltGr":"."},"IntlRo":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KanaMode":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlYen":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Convert":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NonConvert":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang3":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang4":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang5":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadParenLeft":{"value":"(","withShift":"(","withAltGr":"(","withShiftAltGr":"("},"NumpadParenRight":{"value":")","withShift":")","withAltGr":")","withShiftAltGr":")"},"ControlLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrightnessUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrightnessDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlay":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaRecord":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaFastForward":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaRewind":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackNext":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackPrevious":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaStop":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Eject":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlayPause":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaSelect":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchMail":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"SelectTask":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchScreenSaver":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserSearch":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserHome":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserBack":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserForward":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserStop":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserRefresh":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserFavorites":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MailReply":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MailForward":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MailSend":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/linux-fr-French.json b/packages/core/src/common/keyboard/layouts/linux-fr-French.json new file mode 100644 index 0000000000000..7919963a330f7 --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/linux-fr-French.json @@ -0,0 +1 @@ +{"info":{"model":"pc105","layout":"fr","variant":",","options":"numpad:microsoft","rules":"evdev"},"mapping":{"Sleep":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"WakeUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KeyA":{"value":"q","withShift":"Q","withAltGr":"@","withShiftAltGr":"Ω"},"KeyB":{"value":"b","withShift":"B","withAltGr":"”","withShiftAltGr":"’"},"KeyC":{"value":"c","withShift":"C","withAltGr":"¢","withShiftAltGr":"©"},"KeyD":{"value":"d","withShift":"D","withAltGr":"ð","withShiftAltGr":"Ð"},"KeyE":{"value":"e","withShift":"E","withAltGr":"€","withShiftAltGr":"¢"},"KeyF":{"value":"f","withShift":"F","withAltGr":"đ","withShiftAltGr":"ª"},"KeyG":{"value":"g","withShift":"G","withAltGr":"ŋ","withShiftAltGr":"Ŋ"},"KeyH":{"value":"h","withShift":"H","withAltGr":"ħ","withShiftAltGr":"Ħ"},"KeyI":{"value":"i","withShift":"I","withAltGr":"→","withShiftAltGr":"ı"},"KeyJ":{"value":"j","withShift":"J","withAltGr":"̉","withShiftAltGr":"̛"},"KeyK":{"value":"k","withShift":"K","withAltGr":"ĸ","withShiftAltGr":"&"},"KeyL":{"value":"l","withShift":"L","withAltGr":"ł","withShiftAltGr":"Ł"},"KeyM":{"value":",","withShift":"?","withAltGr":"́","withShiftAltGr":"̋"},"KeyN":{"value":"n","withShift":"N","withAltGr":"n","withShiftAltGr":"N"},"KeyO":{"value":"o","withShift":"O","withAltGr":"ø","withShiftAltGr":"Ø"},"KeyP":{"value":"p","withShift":"P","withAltGr":"þ","withShiftAltGr":"Þ"},"KeyQ":{"value":"a","withShift":"A","withAltGr":"æ","withShiftAltGr":"Æ"},"KeyR":{"value":"r","withShift":"R","withAltGr":"¶","withShiftAltGr":"®"},"KeyS":{"value":"s","withShift":"S","withAltGr":"ß","withShiftAltGr":"§"},"KeyT":{"value":"t","withShift":"T","withAltGr":"ŧ","withShiftAltGr":"Ŧ"},"KeyU":{"value":"u","withShift":"U","withAltGr":"↓","withShiftAltGr":"↑"},"KeyV":{"value":"v","withShift":"V","withAltGr":"“","withShiftAltGr":"‘"},"KeyW":{"value":"z","withShift":"Z","withAltGr":"«","withShiftAltGr":"<"},"KeyX":{"value":"x","withShift":"X","withAltGr":"»","withShiftAltGr":">"},"KeyY":{"value":"y","withShift":"Y","withAltGr":"←","withShiftAltGr":"¥"},"KeyZ":{"value":"w","withShift":"W","withAltGr":"ł","withShiftAltGr":"Ł"},"Digit1":{"value":"&","withShift":"1","withAltGr":"¹","withShiftAltGr":"¡"},"Digit2":{"value":"é","withShift":"2","withAltGr":"~","withShiftAltGr":"⅛"},"Digit3":{"value":"\"","withShift":"3","withAltGr":"#","withShiftAltGr":"£"},"Digit4":{"value":"'","withShift":"4","withAltGr":"{","withShiftAltGr":"$"},"Digit5":{"value":"(","withShift":"5","withAltGr":"[","withShiftAltGr":"⅜"},"Digit6":{"value":"-","withShift":"6","withAltGr":"|","withShiftAltGr":"⅝"},"Digit7":{"value":"è","withShift":"7","withAltGr":"`","withShiftAltGr":"⅞"},"Digit8":{"value":"_","withShift":"8","withAltGr":"\\","withShiftAltGr":"™"},"Digit9":{"value":"ç","withShift":"9","withAltGr":"^","withShiftAltGr":"±"},"Digit0":{"value":"à","withShift":"0","withAltGr":"@","withShiftAltGr":"°"},"Enter":{"value":"\r","withShift":"\r","withAltGr":"\r","withShiftAltGr":"\r"},"Escape":{"value":"\u001b","withShift":"\u001b","withAltGr":"\u001b","withShiftAltGr":"\u001b"},"Backspace":{"value":"\b","withShift":"\b","withAltGr":"\b","withShiftAltGr":"\b"},"Tab":{"value":"\t","withShift":"","withAltGr":"\t","withShiftAltGr":""},"Space":{"value":" ","withShift":" ","withAltGr":" ","withShiftAltGr":" "},"Minus":{"value":")","withShift":"°","withAltGr":"]","withShiftAltGr":"¿"},"Equal":{"value":"=","withShift":"+","withAltGr":"}","withShiftAltGr":"̨"},"BracketLeft":{"value":"̂","withShift":"̈","withAltGr":"̈","withShiftAltGr":"̊"},"BracketRight":{"value":"$","withShift":"£","withAltGr":"¤","withShiftAltGr":"̄"},"Backslash":{"value":"*","withShift":"µ","withAltGr":"̀","withShiftAltGr":"̆"},"Semicolon":{"value":"m","withShift":"M","withAltGr":"µ","withShiftAltGr":"º"},"Quote":{"value":"ù","withShift":"%","withAltGr":"̂","withShiftAltGr":"̌"},"Backquote":{"value":"²","withShift":"~","withAltGr":"¬","withShiftAltGr":"¬"},"Comma":{"value":";","withShift":".","withAltGr":"─","withShiftAltGr":"×"},"Period":{"value":":","withShift":"/","withAltGr":"·","withShiftAltGr":"÷"},"Slash":{"value":"!","withShift":"§","withAltGr":"̣","withShiftAltGr":"̇"},"CapsLock":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F3":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F4":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F5":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F6":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F7":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F8":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F9":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F10":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F11":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F12":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PrintScreen":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ScrollLock":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Pause":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Insert":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Home":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Delete":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"End":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumLock":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDivide":{"value":"/","withShift":"/","withAltGr":"/","withShiftAltGr":"/"},"NumpadMultiply":{"value":"*","withShift":"*","withAltGr":"*","withShiftAltGr":"*"},"NumpadSubtract":{"value":"-","withShift":"-","withAltGr":"-","withShiftAltGr":"-"},"NumpadAdd":{"value":"+","withShift":"+","withAltGr":"+","withShiftAltGr":"+"},"NumpadEnter":{"value":"\r","withShift":"\r","withAltGr":"\r","withShiftAltGr":"\r"},"Numpad1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad3":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad4":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad5":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad6":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad7":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad8":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad9":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad0":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDecimal":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlBackslash":{"value":"<","withShift":">","withAltGr":"|","withShiftAltGr":"¦"},"ContextMenu":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Power":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadEqual":{"value":"=","withShift":"=","withAltGr":"=","withShiftAltGr":"="},"F13":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F14":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F15":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F16":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F17":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F18":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F19":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F20":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F21":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F22":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F23":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F24":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Open":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Help":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Select":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Again":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Undo":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Cut":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Copy":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Paste":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Find":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeMute":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadComma":{"value":".","withShift":".","withAltGr":".","withShiftAltGr":"."},"IntlRo":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KanaMode":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlYen":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Convert":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NonConvert":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang3":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang4":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang5":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadParenLeft":{"value":"(","withShift":"(","withAltGr":"(","withShiftAltGr":"("},"NumpadParenRight":{"value":")","withShift":")","withAltGr":")","withShiftAltGr":")"},"ControlLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaLeft":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaRight":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrightnessUp":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrightnessDown":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlay":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaRecord":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaFastForward":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaRewind":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackNext":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackPrevious":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaStop":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Eject":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlayPause":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaSelect":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchMail":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp2":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp1":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"SelectTask":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchScreenSaver":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserSearch":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserHome":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserBack":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserForward":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserStop":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserRefresh":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserFavorites":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MailReply":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MailForward":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MailSend":{"value":"","withShift":"","withAltGr":"","withShiftAltGr":""}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/mac-de-German.json b/packages/core/src/common/keyboard/layouts/mac-de-German.json new file mode 100644 index 0000000000000..520ce982d0ee9 --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/mac-de-German.json @@ -0,0 +1 @@ +{"info":{"id":"com.apple.keylayout.German","lang":"de"},"mapping":{"KeyA":{"value":"a","valueIsDeadKey":false,"withShift":"A","withShiftIsDeadKey":false,"withAltGr":"å","withAltGrIsDeadKey":false,"withShiftAltGr":"Å","withShiftAltGrIsDeadKey":false},"KeyB":{"value":"b","valueIsDeadKey":false,"withShift":"B","withShiftIsDeadKey":false,"withAltGr":"∫","withAltGrIsDeadKey":false,"withShiftAltGr":"‹","withShiftAltGrIsDeadKey":false},"KeyC":{"value":"c","valueIsDeadKey":false,"withShift":"C","withShiftIsDeadKey":false,"withAltGr":"ç","withAltGrIsDeadKey":false,"withShiftAltGr":"Ç","withShiftAltGrIsDeadKey":false},"KeyD":{"value":"d","valueIsDeadKey":false,"withShift":"D","withShiftIsDeadKey":false,"withAltGr":"∂","withAltGrIsDeadKey":false,"withShiftAltGr":"™","withShiftAltGrIsDeadKey":false},"KeyE":{"value":"e","valueIsDeadKey":false,"withShift":"E","withShiftIsDeadKey":false,"withAltGr":"€","withAltGrIsDeadKey":false,"withShiftAltGr":"‰","withShiftAltGrIsDeadKey":false},"KeyF":{"value":"f","valueIsDeadKey":false,"withShift":"F","withShiftIsDeadKey":false,"withAltGr":"ƒ","withAltGrIsDeadKey":false,"withShiftAltGr":"Ï","withShiftAltGrIsDeadKey":false},"KeyG":{"value":"g","valueIsDeadKey":false,"withShift":"G","withShiftIsDeadKey":false,"withAltGr":"©","withAltGrIsDeadKey":false,"withShiftAltGr":"Ì","withShiftAltGrIsDeadKey":false},"KeyH":{"value":"h","valueIsDeadKey":false,"withShift":"H","withShiftIsDeadKey":false,"withAltGr":"ª","withAltGrIsDeadKey":false,"withShiftAltGr":"Ó","withShiftAltGrIsDeadKey":false},"KeyI":{"value":"i","valueIsDeadKey":false,"withShift":"I","withShiftIsDeadKey":false,"withAltGr":"⁄","withAltGrIsDeadKey":false,"withShiftAltGr":"Û","withShiftAltGrIsDeadKey":false},"KeyJ":{"value":"j","valueIsDeadKey":false,"withShift":"J","withShiftIsDeadKey":false,"withAltGr":"º","withAltGrIsDeadKey":false,"withShiftAltGr":"ı","withShiftAltGrIsDeadKey":false},"KeyK":{"value":"k","valueIsDeadKey":false,"withShift":"K","withShiftIsDeadKey":false,"withAltGr":"∆","withAltGrIsDeadKey":false,"withShiftAltGr":"ˆ","withShiftAltGrIsDeadKey":false},"KeyL":{"value":"l","valueIsDeadKey":false,"withShift":"L","withShiftIsDeadKey":false,"withAltGr":"@","withAltGrIsDeadKey":false,"withShiftAltGr":"fl","withShiftAltGrIsDeadKey":false},"KeyM":{"value":"m","valueIsDeadKey":false,"withShift":"M","withShiftIsDeadKey":false,"withAltGr":"µ","withAltGrIsDeadKey":false,"withShiftAltGr":"˘","withShiftAltGrIsDeadKey":false},"KeyN":{"value":"n","valueIsDeadKey":false,"withShift":"N","withShiftIsDeadKey":false,"withAltGr":"~","withAltGrIsDeadKey":true,"withShiftAltGr":"›","withShiftAltGrIsDeadKey":false},"KeyO":{"value":"o","valueIsDeadKey":false,"withShift":"O","withShiftIsDeadKey":false,"withAltGr":"ø","withAltGrIsDeadKey":false,"withShiftAltGr":"Ø","withShiftAltGrIsDeadKey":false},"KeyP":{"value":"p","valueIsDeadKey":false,"withShift":"P","withShiftIsDeadKey":false,"withAltGr":"π","withAltGrIsDeadKey":false,"withShiftAltGr":"∏","withShiftAltGrIsDeadKey":false},"KeyQ":{"value":"q","valueIsDeadKey":false,"withShift":"Q","withShiftIsDeadKey":false,"withAltGr":"«","withAltGrIsDeadKey":false,"withShiftAltGr":"»","withShiftAltGrIsDeadKey":false},"KeyR":{"value":"r","valueIsDeadKey":false,"withShift":"R","withShiftIsDeadKey":false,"withAltGr":"®","withAltGrIsDeadKey":false,"withShiftAltGr":"¸","withShiftAltGrIsDeadKey":false},"KeyS":{"value":"s","valueIsDeadKey":false,"withShift":"S","withShiftIsDeadKey":false,"withAltGr":"‚","withAltGrIsDeadKey":false,"withShiftAltGr":"Í","withShiftAltGrIsDeadKey":false},"KeyT":{"value":"t","valueIsDeadKey":false,"withShift":"T","withShiftIsDeadKey":false,"withAltGr":"†","withAltGrIsDeadKey":false,"withShiftAltGr":"˝","withShiftAltGrIsDeadKey":false},"KeyU":{"value":"u","valueIsDeadKey":false,"withShift":"U","withShiftIsDeadKey":false,"withAltGr":"¨","withAltGrIsDeadKey":true,"withShiftAltGr":"Á","withShiftAltGrIsDeadKey":false},"KeyV":{"value":"v","valueIsDeadKey":false,"withShift":"V","withShiftIsDeadKey":false,"withAltGr":"√","withAltGrIsDeadKey":false,"withShiftAltGr":"◊","withShiftAltGrIsDeadKey":false},"KeyW":{"value":"w","valueIsDeadKey":false,"withShift":"W","withShiftIsDeadKey":false,"withAltGr":"∑","withAltGrIsDeadKey":false,"withShiftAltGr":"„","withShiftAltGrIsDeadKey":false},"KeyX":{"value":"x","valueIsDeadKey":false,"withShift":"X","withShiftIsDeadKey":false,"withAltGr":"≈","withAltGrIsDeadKey":false,"withShiftAltGr":"Ù","withShiftAltGrIsDeadKey":false},"KeyY":{"value":"z","valueIsDeadKey":false,"withShift":"Z","withShiftIsDeadKey":false,"withAltGr":"Ω","withAltGrIsDeadKey":false,"withShiftAltGr":"ˇ","withShiftAltGrIsDeadKey":false},"KeyZ":{"value":"y","valueIsDeadKey":false,"withShift":"Y","withShiftIsDeadKey":false,"withAltGr":"¥","withAltGrIsDeadKey":false,"withShiftAltGr":"‡","withShiftAltGrIsDeadKey":false},"Digit1":{"value":"1","valueIsDeadKey":false,"withShift":"!","withShiftIsDeadKey":false,"withAltGr":"¡","withAltGrIsDeadKey":false,"withShiftAltGr":"¬","withShiftAltGrIsDeadKey":false},"Digit2":{"value":"2","valueIsDeadKey":false,"withShift":"\"","withShiftIsDeadKey":false,"withAltGr":"“","withAltGrIsDeadKey":false,"withShiftAltGr":"”","withShiftAltGrIsDeadKey":false},"Digit3":{"value":"3","valueIsDeadKey":false,"withShift":"§","withShiftIsDeadKey":false,"withAltGr":"¶","withAltGrIsDeadKey":false,"withShiftAltGr":"#","withShiftAltGrIsDeadKey":false},"Digit4":{"value":"4","valueIsDeadKey":false,"withShift":"$","withShiftIsDeadKey":false,"withAltGr":"¢","withAltGrIsDeadKey":false,"withShiftAltGr":"£","withShiftAltGrIsDeadKey":false},"Digit5":{"value":"5","valueIsDeadKey":false,"withShift":"%","withShiftIsDeadKey":false,"withAltGr":"[","withAltGrIsDeadKey":false,"withShiftAltGr":"fi","withShiftAltGrIsDeadKey":false},"Digit6":{"value":"6","valueIsDeadKey":false,"withShift":"&","withShiftIsDeadKey":false,"withAltGr":"]","withAltGrIsDeadKey":false,"withShiftAltGr":"^","withShiftAltGrIsDeadKey":true},"Digit7":{"value":"7","valueIsDeadKey":false,"withShift":"/","withShiftIsDeadKey":false,"withAltGr":"|","withAltGrIsDeadKey":false,"withShiftAltGr":"\\","withShiftAltGrIsDeadKey":false},"Digit8":{"value":"8","valueIsDeadKey":false,"withShift":"(","withShiftIsDeadKey":false,"withAltGr":"{","withAltGrIsDeadKey":false,"withShiftAltGr":"˜","withShiftAltGrIsDeadKey":false},"Digit9":{"value":"9","valueIsDeadKey":false,"withShift":")","withShiftIsDeadKey":false,"withAltGr":"}","withAltGrIsDeadKey":false,"withShiftAltGr":"·","withShiftAltGrIsDeadKey":false},"Digit0":{"value":"0","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"≠","withAltGrIsDeadKey":false,"withShiftAltGr":"¯","withShiftAltGrIsDeadKey":false},"Enter":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Escape":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Backspace":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Tab":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Space":{"value":" ","valueIsDeadKey":false,"withShift":" ","withShiftIsDeadKey":false,"withAltGr":" ","withAltGrIsDeadKey":false,"withShiftAltGr":" ","withShiftAltGrIsDeadKey":false},"Minus":{"value":"ß","valueIsDeadKey":false,"withShift":"?","withShiftIsDeadKey":false,"withAltGr":"¿","withAltGrIsDeadKey":false,"withShiftAltGr":"˙","withShiftAltGrIsDeadKey":false},"Equal":{"value":"´","valueIsDeadKey":true,"withShift":"`","withShiftIsDeadKey":true,"withAltGr":"'","withAltGrIsDeadKey":false,"withShiftAltGr":"˚","withShiftAltGrIsDeadKey":false},"BracketLeft":{"value":"ü","valueIsDeadKey":false,"withShift":"Ü","withShiftIsDeadKey":false,"withAltGr":"•","withAltGrIsDeadKey":false,"withShiftAltGr":"°","withShiftAltGrIsDeadKey":false},"BracketRight":{"value":"+","valueIsDeadKey":false,"withShift":"*","withShiftIsDeadKey":false,"withAltGr":"±","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Backslash":{"value":"#","valueIsDeadKey":false,"withShift":"'","withShiftIsDeadKey":false,"withAltGr":"‘","withAltGrIsDeadKey":false,"withShiftAltGr":"’","withShiftAltGrIsDeadKey":false},"Semicolon":{"value":"ö","valueIsDeadKey":false,"withShift":"Ö","withShiftIsDeadKey":false,"withAltGr":"œ","withAltGrIsDeadKey":false,"withShiftAltGr":"Œ","withShiftAltGrIsDeadKey":false},"Quote":{"value":"ä","valueIsDeadKey":false,"withShift":"Ä","withShiftIsDeadKey":false,"withAltGr":"æ","withAltGrIsDeadKey":false,"withShiftAltGr":"Æ","withShiftAltGrIsDeadKey":false},"Backquote":{"value":"<","valueIsDeadKey":false,"withShift":">","withShiftIsDeadKey":false,"withAltGr":"≤","withAltGrIsDeadKey":false,"withShiftAltGr":"≥","withShiftAltGrIsDeadKey":false},"Comma":{"value":",","valueIsDeadKey":false,"withShift":";","withShiftIsDeadKey":false,"withAltGr":"∞","withAltGrIsDeadKey":false,"withShiftAltGr":"˛","withShiftAltGrIsDeadKey":false},"Period":{"value":".","valueIsDeadKey":false,"withShift":":","withShiftIsDeadKey":false,"withAltGr":"…","withAltGrIsDeadKey":false,"withShiftAltGr":"÷","withShiftAltGrIsDeadKey":false},"Slash":{"value":"-","valueIsDeadKey":false,"withShift":"_","withShiftIsDeadKey":false,"withAltGr":"–","withAltGrIsDeadKey":false,"withShiftAltGr":"—","withShiftAltGrIsDeadKey":false},"CapsLock":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F1":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F2":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F3":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F4":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F5":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F6":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F7":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F8":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F9":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F10":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F11":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F12":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Insert":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Home":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"PageUp":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Delete":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"End":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"PageDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowUp":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumLock":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadDivide":{"value":"/","valueIsDeadKey":false,"withShift":"/","withShiftIsDeadKey":false,"withAltGr":"/","withAltGrIsDeadKey":false,"withShiftAltGr":"/","withShiftAltGrIsDeadKey":false},"NumpadMultiply":{"value":"*","valueIsDeadKey":false,"withShift":"*","withShiftIsDeadKey":false,"withAltGr":"*","withAltGrIsDeadKey":false,"withShiftAltGr":"*","withShiftAltGrIsDeadKey":false},"NumpadSubtract":{"value":"-","valueIsDeadKey":false,"withShift":"-","withShiftIsDeadKey":false,"withAltGr":"-","withAltGrIsDeadKey":false,"withShiftAltGr":"-","withShiftAltGrIsDeadKey":false},"NumpadAdd":{"value":"+","valueIsDeadKey":false,"withShift":"+","withShiftIsDeadKey":false,"withAltGr":"+","withAltGrIsDeadKey":false,"withShiftAltGr":"+","withShiftAltGrIsDeadKey":false},"NumpadEnter":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Numpad1":{"value":"1","valueIsDeadKey":false,"withShift":"1","withShiftIsDeadKey":false,"withAltGr":"1","withAltGrIsDeadKey":false,"withShiftAltGr":"1","withShiftAltGrIsDeadKey":false},"Numpad2":{"value":"2","valueIsDeadKey":false,"withShift":"2","withShiftIsDeadKey":false,"withAltGr":"2","withAltGrIsDeadKey":false,"withShiftAltGr":"2","withShiftAltGrIsDeadKey":false},"Numpad3":{"value":"3","valueIsDeadKey":false,"withShift":"3","withShiftIsDeadKey":false,"withAltGr":"3","withAltGrIsDeadKey":false,"withShiftAltGr":"3","withShiftAltGrIsDeadKey":false},"Numpad4":{"value":"4","valueIsDeadKey":false,"withShift":"4","withShiftIsDeadKey":false,"withAltGr":"4","withAltGrIsDeadKey":false,"withShiftAltGr":"4","withShiftAltGrIsDeadKey":false},"Numpad5":{"value":"5","valueIsDeadKey":false,"withShift":"5","withShiftIsDeadKey":false,"withAltGr":"5","withAltGrIsDeadKey":false,"withShiftAltGr":"5","withShiftAltGrIsDeadKey":false},"Numpad6":{"value":"6","valueIsDeadKey":false,"withShift":"6","withShiftIsDeadKey":false,"withAltGr":"6","withAltGrIsDeadKey":false,"withShiftAltGr":"6","withShiftAltGrIsDeadKey":false},"Numpad7":{"value":"7","valueIsDeadKey":false,"withShift":"7","withShiftIsDeadKey":false,"withAltGr":"7","withAltGrIsDeadKey":false,"withShiftAltGr":"7","withShiftAltGrIsDeadKey":false},"Numpad8":{"value":"8","valueIsDeadKey":false,"withShift":"8","withShiftIsDeadKey":false,"withAltGr":"8","withAltGrIsDeadKey":false,"withShiftAltGr":"8","withShiftAltGrIsDeadKey":false},"Numpad9":{"value":"9","valueIsDeadKey":false,"withShift":"9","withShiftIsDeadKey":false,"withAltGr":"9","withAltGrIsDeadKey":false,"withShiftAltGr":"9","withShiftAltGrIsDeadKey":false},"Numpad0":{"value":"0","valueIsDeadKey":false,"withShift":"0","withShiftIsDeadKey":false,"withAltGr":"0","withAltGrIsDeadKey":false,"withShiftAltGr":"0","withShiftAltGrIsDeadKey":false},"NumpadDecimal":{"value":",","valueIsDeadKey":false,"withShift":",","withShiftIsDeadKey":false,"withAltGr":".","withAltGrIsDeadKey":false,"withShiftAltGr":".","withShiftAltGrIsDeadKey":false},"IntlBackslash":{"value":"^","valueIsDeadKey":true,"withShift":"°","withShiftIsDeadKey":false,"withAltGr":"„","withAltGrIsDeadKey":false,"withShiftAltGr":"“","withShiftAltGrIsDeadKey":false},"ContextMenu":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadEqual":{"value":"=","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"=","withAltGrIsDeadKey":false,"withShiftAltGr":"=","withShiftAltGrIsDeadKey":false},"F13":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F14":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F15":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F16":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F17":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F18":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F19":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F20":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AudioVolumeMute":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AudioVolumeUp":{"value":"","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"=","withShiftAltGrIsDeadKey":false},"AudioVolumeDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadComma":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"IntlRo":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"KanaMode":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"IntlYen":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ControlLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ShiftLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AltLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"MetaLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ControlRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ShiftRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AltRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"MetaRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/mac-en-US.json b/packages/core/src/common/keyboard/layouts/mac-en-US.json new file mode 100644 index 0000000000000..f30b5fbbe6b40 --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/mac-en-US.json @@ -0,0 +1 @@ +{"info":{"id":"com.apple.keylayout.US","lang":"en"},"mapping":{"KeyA":{"value":"a","valueIsDeadKey":false,"withShift":"A","withShiftIsDeadKey":false,"withAltGr":"å","withAltGrIsDeadKey":false,"withShiftAltGr":"Å","withShiftAltGrIsDeadKey":false},"KeyB":{"value":"b","valueIsDeadKey":false,"withShift":"B","withShiftIsDeadKey":false,"withAltGr":"∫","withAltGrIsDeadKey":false,"withShiftAltGr":"ı","withShiftAltGrIsDeadKey":false},"KeyC":{"value":"c","valueIsDeadKey":false,"withShift":"C","withShiftIsDeadKey":false,"withAltGr":"ç","withAltGrIsDeadKey":false,"withShiftAltGr":"Ç","withShiftAltGrIsDeadKey":false},"KeyD":{"value":"d","valueIsDeadKey":false,"withShift":"D","withShiftIsDeadKey":false,"withAltGr":"∂","withAltGrIsDeadKey":false,"withShiftAltGr":"Î","withShiftAltGrIsDeadKey":false},"KeyE":{"value":"e","valueIsDeadKey":false,"withShift":"E","withShiftIsDeadKey":false,"withAltGr":"´","withAltGrIsDeadKey":true,"withShiftAltGr":"´","withShiftAltGrIsDeadKey":false},"KeyF":{"value":"f","valueIsDeadKey":false,"withShift":"F","withShiftIsDeadKey":false,"withAltGr":"ƒ","withAltGrIsDeadKey":false,"withShiftAltGr":"Ï","withShiftAltGrIsDeadKey":false},"KeyG":{"value":"g","valueIsDeadKey":false,"withShift":"G","withShiftIsDeadKey":false,"withAltGr":"©","withAltGrIsDeadKey":false,"withShiftAltGr":"˝","withShiftAltGrIsDeadKey":false},"KeyH":{"value":"h","valueIsDeadKey":false,"withShift":"H","withShiftIsDeadKey":false,"withAltGr":"˙","withAltGrIsDeadKey":false,"withShiftAltGr":"Ó","withShiftAltGrIsDeadKey":false},"KeyI":{"value":"i","valueIsDeadKey":false,"withShift":"I","withShiftIsDeadKey":false,"withAltGr":"ˆ","withAltGrIsDeadKey":true,"withShiftAltGr":"ˆ","withShiftAltGrIsDeadKey":false},"KeyJ":{"value":"j","valueIsDeadKey":false,"withShift":"J","withShiftIsDeadKey":false,"withAltGr":"∆","withAltGrIsDeadKey":false,"withShiftAltGr":"Ô","withShiftAltGrIsDeadKey":false},"KeyK":{"value":"k","valueIsDeadKey":false,"withShift":"K","withShiftIsDeadKey":false,"withAltGr":"˚","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"KeyL":{"value":"l","valueIsDeadKey":false,"withShift":"L","withShiftIsDeadKey":false,"withAltGr":"¬","withAltGrIsDeadKey":false,"withShiftAltGr":"Ò","withShiftAltGrIsDeadKey":false},"KeyM":{"value":"m","valueIsDeadKey":false,"withShift":"M","withShiftIsDeadKey":false,"withAltGr":"µ","withAltGrIsDeadKey":false,"withShiftAltGr":"Â","withShiftAltGrIsDeadKey":false},"KeyN":{"value":"n","valueIsDeadKey":false,"withShift":"N","withShiftIsDeadKey":false,"withAltGr":"˜","withAltGrIsDeadKey":true,"withShiftAltGr":"˜","withShiftAltGrIsDeadKey":false},"KeyO":{"value":"o","valueIsDeadKey":false,"withShift":"O","withShiftIsDeadKey":false,"withAltGr":"ø","withAltGrIsDeadKey":false,"withShiftAltGr":"Ø","withShiftAltGrIsDeadKey":false},"KeyP":{"value":"p","valueIsDeadKey":false,"withShift":"P","withShiftIsDeadKey":false,"withAltGr":"π","withAltGrIsDeadKey":false,"withShiftAltGr":"∏","withShiftAltGrIsDeadKey":false},"KeyQ":{"value":"q","valueIsDeadKey":false,"withShift":"Q","withShiftIsDeadKey":false,"withAltGr":"œ","withAltGrIsDeadKey":false,"withShiftAltGr":"Œ","withShiftAltGrIsDeadKey":false},"KeyR":{"value":"r","valueIsDeadKey":false,"withShift":"R","withShiftIsDeadKey":false,"withAltGr":"®","withAltGrIsDeadKey":false,"withShiftAltGr":"‰","withShiftAltGrIsDeadKey":false},"KeyS":{"value":"s","valueIsDeadKey":false,"withShift":"S","withShiftIsDeadKey":false,"withAltGr":"ß","withAltGrIsDeadKey":false,"withShiftAltGr":"Í","withShiftAltGrIsDeadKey":false},"KeyT":{"value":"t","valueIsDeadKey":false,"withShift":"T","withShiftIsDeadKey":false,"withAltGr":"†","withAltGrIsDeadKey":false,"withShiftAltGr":"ˇ","withShiftAltGrIsDeadKey":false},"KeyU":{"value":"u","valueIsDeadKey":false,"withShift":"U","withShiftIsDeadKey":false,"withAltGr":"¨","withAltGrIsDeadKey":true,"withShiftAltGr":"¨","withShiftAltGrIsDeadKey":false},"KeyV":{"value":"v","valueIsDeadKey":false,"withShift":"V","withShiftIsDeadKey":false,"withAltGr":"√","withAltGrIsDeadKey":false,"withShiftAltGr":"◊","withShiftAltGrIsDeadKey":false},"KeyW":{"value":"w","valueIsDeadKey":false,"withShift":"W","withShiftIsDeadKey":false,"withAltGr":"∑","withAltGrIsDeadKey":false,"withShiftAltGr":"„","withShiftAltGrIsDeadKey":false},"KeyX":{"value":"x","valueIsDeadKey":false,"withShift":"X","withShiftIsDeadKey":false,"withAltGr":"≈","withAltGrIsDeadKey":false,"withShiftAltGr":"˛","withShiftAltGrIsDeadKey":false},"KeyY":{"value":"y","valueIsDeadKey":false,"withShift":"Y","withShiftIsDeadKey":false,"withAltGr":"¥","withAltGrIsDeadKey":false,"withShiftAltGr":"Á","withShiftAltGrIsDeadKey":false},"KeyZ":{"value":"z","valueIsDeadKey":false,"withShift":"Z","withShiftIsDeadKey":false,"withAltGr":"Ω","withAltGrIsDeadKey":false,"withShiftAltGr":"¸","withShiftAltGrIsDeadKey":false},"Digit1":{"value":"1","valueIsDeadKey":false,"withShift":"!","withShiftIsDeadKey":false,"withAltGr":"¡","withAltGrIsDeadKey":false,"withShiftAltGr":"⁄","withShiftAltGrIsDeadKey":false},"Digit2":{"value":"2","valueIsDeadKey":false,"withShift":"@","withShiftIsDeadKey":false,"withAltGr":"™","withAltGrIsDeadKey":false,"withShiftAltGr":"€","withShiftAltGrIsDeadKey":false},"Digit3":{"value":"3","valueIsDeadKey":false,"withShift":"#","withShiftIsDeadKey":false,"withAltGr":"£","withAltGrIsDeadKey":false,"withShiftAltGr":"‹","withShiftAltGrIsDeadKey":false},"Digit4":{"value":"4","valueIsDeadKey":false,"withShift":"$","withShiftIsDeadKey":false,"withAltGr":"¢","withAltGrIsDeadKey":false,"withShiftAltGr":"›","withShiftAltGrIsDeadKey":false},"Digit5":{"value":"5","valueIsDeadKey":false,"withShift":"%","withShiftIsDeadKey":false,"withAltGr":"∞","withAltGrIsDeadKey":false,"withShiftAltGr":"fi","withShiftAltGrIsDeadKey":false},"Digit6":{"value":"6","valueIsDeadKey":false,"withShift":"^","withShiftIsDeadKey":false,"withAltGr":"§","withAltGrIsDeadKey":false,"withShiftAltGr":"fl","withShiftAltGrIsDeadKey":false},"Digit7":{"value":"7","valueIsDeadKey":false,"withShift":"&","withShiftIsDeadKey":false,"withAltGr":"¶","withAltGrIsDeadKey":false,"withShiftAltGr":"‡","withShiftAltGrIsDeadKey":false},"Digit8":{"value":"8","valueIsDeadKey":false,"withShift":"*","withShiftIsDeadKey":false,"withAltGr":"•","withAltGrIsDeadKey":false,"withShiftAltGr":"°","withShiftAltGrIsDeadKey":false},"Digit9":{"value":"9","valueIsDeadKey":false,"withShift":"(","withShiftIsDeadKey":false,"withAltGr":"ª","withAltGrIsDeadKey":false,"withShiftAltGr":"·","withShiftAltGrIsDeadKey":false},"Digit0":{"value":"0","valueIsDeadKey":false,"withShift":")","withShiftIsDeadKey":false,"withAltGr":"º","withAltGrIsDeadKey":false,"withShiftAltGr":"‚","withShiftAltGrIsDeadKey":false},"Enter":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Escape":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Backspace":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Tab":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Space":{"value":" ","valueIsDeadKey":false,"withShift":" ","withShiftIsDeadKey":false,"withAltGr":" ","withAltGrIsDeadKey":false,"withShiftAltGr":" ","withShiftAltGrIsDeadKey":false},"Minus":{"value":"-","valueIsDeadKey":false,"withShift":"_","withShiftIsDeadKey":false,"withAltGr":"–","withAltGrIsDeadKey":false,"withShiftAltGr":"—","withShiftAltGrIsDeadKey":false},"Equal":{"value":"=","valueIsDeadKey":false,"withShift":"+","withShiftIsDeadKey":false,"withAltGr":"≠","withAltGrIsDeadKey":false,"withShiftAltGr":"±","withShiftAltGrIsDeadKey":false},"BracketLeft":{"value":"[","valueIsDeadKey":false,"withShift":"{","withShiftIsDeadKey":false,"withAltGr":"“","withAltGrIsDeadKey":false,"withShiftAltGr":"”","withShiftAltGrIsDeadKey":false},"BracketRight":{"value":"]","valueIsDeadKey":false,"withShift":"}","withShiftIsDeadKey":false,"withAltGr":"‘","withAltGrIsDeadKey":false,"withShiftAltGr":"’","withShiftAltGrIsDeadKey":false},"Backslash":{"value":"\\","valueIsDeadKey":false,"withShift":"|","withShiftIsDeadKey":false,"withAltGr":"«","withAltGrIsDeadKey":false,"withShiftAltGr":"»","withShiftAltGrIsDeadKey":false},"Semicolon":{"value":";","valueIsDeadKey":false,"withShift":":","withShiftIsDeadKey":false,"withAltGr":"…","withAltGrIsDeadKey":false,"withShiftAltGr":"Ú","withShiftAltGrIsDeadKey":false},"Quote":{"value":"'","valueIsDeadKey":false,"withShift":"\"","withShiftIsDeadKey":false,"withAltGr":"æ","withAltGrIsDeadKey":false,"withShiftAltGr":"Æ","withShiftAltGrIsDeadKey":false},"Backquote":{"value":"`","valueIsDeadKey":false,"withShift":"~","withShiftIsDeadKey":false,"withAltGr":"`","withAltGrIsDeadKey":true,"withShiftAltGr":"`","withShiftAltGrIsDeadKey":false},"Comma":{"value":",","valueIsDeadKey":false,"withShift":"<","withShiftIsDeadKey":false,"withAltGr":"≤","withAltGrIsDeadKey":false,"withShiftAltGr":"¯","withShiftAltGrIsDeadKey":false},"Period":{"value":".","valueIsDeadKey":false,"withShift":">","withShiftIsDeadKey":false,"withAltGr":"≥","withAltGrIsDeadKey":false,"withShiftAltGr":"˘","withShiftAltGrIsDeadKey":false},"Slash":{"value":"/","valueIsDeadKey":false,"withShift":"?","withShiftIsDeadKey":false,"withAltGr":"÷","withAltGrIsDeadKey":false,"withShiftAltGr":"¿","withShiftAltGrIsDeadKey":false},"CapsLock":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F1":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F2":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F3":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F4":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F5":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F6":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F7":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F8":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F9":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F10":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F11":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F12":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Insert":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Home":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"PageUp":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Delete":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"End":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"PageDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowUp":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumLock":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadDivide":{"value":"/","valueIsDeadKey":false,"withShift":"/","withShiftIsDeadKey":false,"withAltGr":"/","withAltGrIsDeadKey":false,"withShiftAltGr":"/","withShiftAltGrIsDeadKey":false},"NumpadMultiply":{"value":"*","valueIsDeadKey":false,"withShift":"*","withShiftIsDeadKey":false,"withAltGr":"*","withAltGrIsDeadKey":false,"withShiftAltGr":"*","withShiftAltGrIsDeadKey":false},"NumpadSubtract":{"value":"-","valueIsDeadKey":false,"withShift":"-","withShiftIsDeadKey":false,"withAltGr":"-","withAltGrIsDeadKey":false,"withShiftAltGr":"-","withShiftAltGrIsDeadKey":false},"NumpadAdd":{"value":"+","valueIsDeadKey":false,"withShift":"+","withShiftIsDeadKey":false,"withAltGr":"+","withAltGrIsDeadKey":false,"withShiftAltGr":"+","withShiftAltGrIsDeadKey":false},"NumpadEnter":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Numpad1":{"value":"1","valueIsDeadKey":false,"withShift":"1","withShiftIsDeadKey":false,"withAltGr":"1","withAltGrIsDeadKey":false,"withShiftAltGr":"1","withShiftAltGrIsDeadKey":false},"Numpad2":{"value":"2","valueIsDeadKey":false,"withShift":"2","withShiftIsDeadKey":false,"withAltGr":"2","withAltGrIsDeadKey":false,"withShiftAltGr":"2","withShiftAltGrIsDeadKey":false},"Numpad3":{"value":"3","valueIsDeadKey":false,"withShift":"3","withShiftIsDeadKey":false,"withAltGr":"3","withAltGrIsDeadKey":false,"withShiftAltGr":"3","withShiftAltGrIsDeadKey":false},"Numpad4":{"value":"4","valueIsDeadKey":false,"withShift":"4","withShiftIsDeadKey":false,"withAltGr":"4","withAltGrIsDeadKey":false,"withShiftAltGr":"4","withShiftAltGrIsDeadKey":false},"Numpad5":{"value":"5","valueIsDeadKey":false,"withShift":"5","withShiftIsDeadKey":false,"withAltGr":"5","withAltGrIsDeadKey":false,"withShiftAltGr":"5","withShiftAltGrIsDeadKey":false},"Numpad6":{"value":"6","valueIsDeadKey":false,"withShift":"6","withShiftIsDeadKey":false,"withAltGr":"6","withAltGrIsDeadKey":false,"withShiftAltGr":"6","withShiftAltGrIsDeadKey":false},"Numpad7":{"value":"7","valueIsDeadKey":false,"withShift":"7","withShiftIsDeadKey":false,"withAltGr":"7","withAltGrIsDeadKey":false,"withShiftAltGr":"7","withShiftAltGrIsDeadKey":false},"Numpad8":{"value":"8","valueIsDeadKey":false,"withShift":"8","withShiftIsDeadKey":false,"withAltGr":"8","withAltGrIsDeadKey":false,"withShiftAltGr":"8","withShiftAltGrIsDeadKey":false},"Numpad9":{"value":"9","valueIsDeadKey":false,"withShift":"9","withShiftIsDeadKey":false,"withAltGr":"9","withAltGrIsDeadKey":false,"withShiftAltGr":"9","withShiftAltGrIsDeadKey":false},"Numpad0":{"value":"0","valueIsDeadKey":false,"withShift":"0","withShiftIsDeadKey":false,"withAltGr":"0","withAltGrIsDeadKey":false,"withShiftAltGr":"0","withShiftAltGrIsDeadKey":false},"NumpadDecimal":{"value":".","valueIsDeadKey":false,"withShift":".","withShiftIsDeadKey":false,"withAltGr":".","withAltGrIsDeadKey":false,"withShiftAltGr":".","withShiftAltGrIsDeadKey":false},"IntlBackslash":{"value":"§","valueIsDeadKey":false,"withShift":"±","withShiftIsDeadKey":false,"withAltGr":"§","withAltGrIsDeadKey":false,"withShiftAltGr":"±","withShiftAltGrIsDeadKey":false},"ContextMenu":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadEqual":{"value":"=","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"=","withAltGrIsDeadKey":false,"withShiftAltGr":"=","withShiftAltGrIsDeadKey":false},"F13":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F14":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F15":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F16":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F17":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F18":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F19":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F20":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AudioVolumeMute":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AudioVolumeUp":{"value":"","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"=","withShiftAltGrIsDeadKey":false},"AudioVolumeDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadComma":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"IntlRo":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"KanaMode":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"IntlYen":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ControlLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ShiftLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AltLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"MetaLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ControlRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ShiftRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AltRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"MetaRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/mac-fr-French.json b/packages/core/src/common/keyboard/layouts/mac-fr-French.json new file mode 100644 index 0000000000000..a2ef79b46272c --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/mac-fr-French.json @@ -0,0 +1 @@ +{"info":{"id":"com.apple.keylayout.French","lang":"fr"},"mapping":{"KeyA":{"value":"q","valueIsDeadKey":false,"withShift":"Q","withShiftIsDeadKey":false,"withAltGr":"‡","withAltGrIsDeadKey":false,"withShiftAltGr":"Ω","withShiftAltGrIsDeadKey":false},"KeyB":{"value":"b","valueIsDeadKey":false,"withShift":"B","withShiftIsDeadKey":false,"withAltGr":"ß","withAltGrIsDeadKey":false,"withShiftAltGr":"∫","withShiftAltGrIsDeadKey":false},"KeyC":{"value":"c","valueIsDeadKey":false,"withShift":"C","withShiftIsDeadKey":false,"withAltGr":"©","withAltGrIsDeadKey":false,"withShiftAltGr":"¢","withShiftAltGrIsDeadKey":false},"KeyD":{"value":"d","valueIsDeadKey":false,"withShift":"D","withShiftIsDeadKey":false,"withAltGr":"∂","withAltGrIsDeadKey":false,"withShiftAltGr":"∆","withShiftAltGrIsDeadKey":false},"KeyE":{"value":"e","valueIsDeadKey":false,"withShift":"E","withShiftIsDeadKey":false,"withAltGr":"ê","withAltGrIsDeadKey":false,"withShiftAltGr":"Ê","withShiftAltGrIsDeadKey":false},"KeyF":{"value":"f","valueIsDeadKey":false,"withShift":"F","withShiftIsDeadKey":false,"withAltGr":"ƒ","withAltGrIsDeadKey":false,"withShiftAltGr":"·","withShiftAltGrIsDeadKey":false},"KeyG":{"value":"g","valueIsDeadKey":false,"withShift":"G","withShiftIsDeadKey":false,"withAltGr":"fi","withAltGrIsDeadKey":false,"withShiftAltGr":"fl","withShiftAltGrIsDeadKey":false},"KeyH":{"value":"h","valueIsDeadKey":false,"withShift":"H","withShiftIsDeadKey":false,"withAltGr":"Ì","withAltGrIsDeadKey":false,"withShiftAltGr":"Î","withShiftAltGrIsDeadKey":false},"KeyI":{"value":"i","valueIsDeadKey":false,"withShift":"I","withShiftIsDeadKey":false,"withAltGr":"î","withAltGrIsDeadKey":false,"withShiftAltGr":"ï","withShiftAltGrIsDeadKey":false},"KeyJ":{"value":"j","valueIsDeadKey":false,"withShift":"J","withShiftIsDeadKey":false,"withAltGr":"Ï","withAltGrIsDeadKey":false,"withShiftAltGr":"Í","withShiftAltGrIsDeadKey":false},"KeyK":{"value":"k","valueIsDeadKey":false,"withShift":"K","withShiftIsDeadKey":false,"withAltGr":"È","withAltGrIsDeadKey":false,"withShiftAltGr":"Ë","withShiftAltGrIsDeadKey":false},"KeyL":{"value":"l","valueIsDeadKey":false,"withShift":"L","withShiftIsDeadKey":false,"withAltGr":"¬","withAltGrIsDeadKey":false,"withShiftAltGr":"|","withShiftAltGrIsDeadKey":false},"KeyM":{"value":",","valueIsDeadKey":false,"withShift":"?","withShiftIsDeadKey":false,"withAltGr":"∞","withAltGrIsDeadKey":false,"withShiftAltGr":"¿","withShiftAltGrIsDeadKey":false},"KeyN":{"value":"n","valueIsDeadKey":false,"withShift":"N","withShiftIsDeadKey":false,"withAltGr":"~","withAltGrIsDeadKey":true,"withShiftAltGr":"ı","withShiftAltGrIsDeadKey":false},"KeyO":{"value":"o","valueIsDeadKey":false,"withShift":"O","withShiftIsDeadKey":false,"withAltGr":"œ","withAltGrIsDeadKey":false,"withShiftAltGr":"Œ","withShiftAltGrIsDeadKey":false},"KeyP":{"value":"p","valueIsDeadKey":false,"withShift":"P","withShiftIsDeadKey":false,"withAltGr":"π","withAltGrIsDeadKey":false,"withShiftAltGr":"∏","withShiftAltGrIsDeadKey":false},"KeyQ":{"value":"a","valueIsDeadKey":false,"withShift":"A","withShiftIsDeadKey":false,"withAltGr":"æ","withAltGrIsDeadKey":false,"withShiftAltGr":"Æ","withShiftAltGrIsDeadKey":false},"KeyR":{"value":"r","valueIsDeadKey":false,"withShift":"R","withShiftIsDeadKey":false,"withAltGr":"®","withAltGrIsDeadKey":false,"withShiftAltGr":"‚","withShiftAltGrIsDeadKey":false},"KeyS":{"value":"s","valueIsDeadKey":false,"withShift":"S","withShiftIsDeadKey":false,"withAltGr":"Ò","withAltGrIsDeadKey":false,"withShiftAltGr":"∑","withShiftAltGrIsDeadKey":false},"KeyT":{"value":"t","valueIsDeadKey":false,"withShift":"T","withShiftIsDeadKey":false,"withAltGr":"†","withAltGrIsDeadKey":false,"withShiftAltGr":"™","withShiftAltGrIsDeadKey":false},"KeyU":{"value":"u","valueIsDeadKey":false,"withShift":"U","withShiftIsDeadKey":false,"withAltGr":"º","withAltGrIsDeadKey":false,"withShiftAltGr":"ª","withShiftAltGrIsDeadKey":false},"KeyV":{"value":"v","valueIsDeadKey":false,"withShift":"V","withShiftIsDeadKey":false,"withAltGr":"◊","withAltGrIsDeadKey":false,"withShiftAltGr":"√","withShiftAltGrIsDeadKey":false},"KeyW":{"value":"z","valueIsDeadKey":false,"withShift":"Z","withShiftIsDeadKey":false,"withAltGr":"Â","withAltGrIsDeadKey":false,"withShiftAltGr":"Å","withShiftAltGrIsDeadKey":false},"KeyX":{"value":"x","valueIsDeadKey":false,"withShift":"X","withShiftIsDeadKey":false,"withAltGr":"≈","withAltGrIsDeadKey":false,"withShiftAltGr":"⁄","withShiftAltGrIsDeadKey":false},"KeyY":{"value":"y","valueIsDeadKey":false,"withShift":"Y","withShiftIsDeadKey":false,"withAltGr":"Ú","withAltGrIsDeadKey":false,"withShiftAltGr":"Ÿ","withShiftAltGrIsDeadKey":false},"KeyZ":{"value":"w","valueIsDeadKey":false,"withShift":"W","withShiftIsDeadKey":false,"withAltGr":"‹","withAltGrIsDeadKey":false,"withShiftAltGr":"›","withShiftAltGrIsDeadKey":false},"Digit1":{"value":"&","valueIsDeadKey":false,"withShift":"1","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"´","withShiftAltGrIsDeadKey":true},"Digit2":{"value":"é","valueIsDeadKey":false,"withShift":"2","withShiftIsDeadKey":false,"withAltGr":"ë","withAltGrIsDeadKey":false,"withShiftAltGr":"„","withShiftAltGrIsDeadKey":false},"Digit3":{"value":"\"","valueIsDeadKey":false,"withShift":"3","withShiftIsDeadKey":false,"withAltGr":"“","withAltGrIsDeadKey":false,"withShiftAltGr":"”","withShiftAltGrIsDeadKey":false},"Digit4":{"value":"'","valueIsDeadKey":false,"withShift":"4","withShiftIsDeadKey":false,"withAltGr":"‘","withAltGrIsDeadKey":false,"withShiftAltGr":"’","withShiftAltGrIsDeadKey":false},"Digit5":{"value":"(","valueIsDeadKey":false,"withShift":"5","withShiftIsDeadKey":false,"withAltGr":"{","withAltGrIsDeadKey":false,"withShiftAltGr":"[","withShiftAltGrIsDeadKey":false},"Digit6":{"value":"§","valueIsDeadKey":false,"withShift":"6","withShiftIsDeadKey":false,"withAltGr":"¶","withAltGrIsDeadKey":false,"withShiftAltGr":"å","withShiftAltGrIsDeadKey":false},"Digit7":{"value":"è","valueIsDeadKey":false,"withShift":"7","withShiftIsDeadKey":false,"withAltGr":"«","withAltGrIsDeadKey":false,"withShiftAltGr":"»","withShiftAltGrIsDeadKey":false},"Digit8":{"value":"!","valueIsDeadKey":false,"withShift":"8","withShiftIsDeadKey":false,"withAltGr":"¡","withAltGrIsDeadKey":false,"withShiftAltGr":"Û","withShiftAltGrIsDeadKey":false},"Digit9":{"value":"ç","valueIsDeadKey":false,"withShift":"9","withShiftIsDeadKey":false,"withAltGr":"Ç","withAltGrIsDeadKey":false,"withShiftAltGr":"Á","withShiftAltGrIsDeadKey":false},"Digit0":{"value":"à","valueIsDeadKey":false,"withShift":"0","withShiftIsDeadKey":false,"withAltGr":"ø","withAltGrIsDeadKey":false,"withShiftAltGr":"Ø","withShiftAltGrIsDeadKey":false},"Enter":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Escape":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Backspace":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Tab":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Space":{"value":" ","valueIsDeadKey":false,"withShift":" ","withShiftIsDeadKey":false,"withAltGr":" ","withAltGrIsDeadKey":false,"withShiftAltGr":" ","withShiftAltGrIsDeadKey":false},"Minus":{"value":")","valueIsDeadKey":false,"withShift":"°","withShiftIsDeadKey":false,"withAltGr":"}","withAltGrIsDeadKey":false,"withShiftAltGr":"]","withShiftAltGrIsDeadKey":false},"Equal":{"value":"-","valueIsDeadKey":false,"withShift":"_","withShiftIsDeadKey":false,"withAltGr":"—","withAltGrIsDeadKey":false,"withShiftAltGr":"–","withShiftAltGrIsDeadKey":false},"BracketLeft":{"value":"^","valueIsDeadKey":true,"withShift":"¨","withShiftIsDeadKey":true,"withAltGr":"ô","withAltGrIsDeadKey":false,"withShiftAltGr":"Ô","withShiftAltGrIsDeadKey":false},"BracketRight":{"value":"$","valueIsDeadKey":false,"withShift":"*","withShiftIsDeadKey":false,"withAltGr":"€","withAltGrIsDeadKey":false,"withShiftAltGr":"¥","withShiftAltGrIsDeadKey":false},"Backslash":{"value":"`","valueIsDeadKey":true,"withShift":"£","withShiftIsDeadKey":false,"withAltGr":"@","withAltGrIsDeadKey":false,"withShiftAltGr":"#","withShiftAltGrIsDeadKey":false},"Semicolon":{"value":"m","valueIsDeadKey":false,"withShift":"M","withShiftIsDeadKey":false,"withAltGr":"µ","withAltGrIsDeadKey":false,"withShiftAltGr":"Ó","withShiftAltGrIsDeadKey":false},"Quote":{"value":"ù","valueIsDeadKey":false,"withShift":"%","withShiftIsDeadKey":false,"withAltGr":"Ù","withAltGrIsDeadKey":false,"withShiftAltGr":"‰","withShiftAltGrIsDeadKey":false},"Backquote":{"value":"<","valueIsDeadKey":false,"withShift":">","withShiftIsDeadKey":false,"withAltGr":"≤","withAltGrIsDeadKey":false,"withShiftAltGr":"≥","withShiftAltGrIsDeadKey":false},"Comma":{"value":";","valueIsDeadKey":false,"withShift":".","withShiftIsDeadKey":false,"withAltGr":"…","withAltGrIsDeadKey":false,"withShiftAltGr":"•","withShiftAltGrIsDeadKey":false},"Period":{"value":":","valueIsDeadKey":false,"withShift":"/","withShiftIsDeadKey":false,"withAltGr":"÷","withAltGrIsDeadKey":false,"withShiftAltGr":"\\","withShiftAltGrIsDeadKey":false},"Slash":{"value":"=","valueIsDeadKey":false,"withShift":"+","withShiftIsDeadKey":false,"withAltGr":"≠","withAltGrIsDeadKey":false,"withShiftAltGr":"±","withShiftAltGrIsDeadKey":false},"CapsLock":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F1":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F2":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F3":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F4":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F5":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F6":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F7":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F8":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F9":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F10":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F11":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F12":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Insert":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Home":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"PageUp":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Delete":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"End":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"PageDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ArrowUp":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumLock":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadDivide":{"value":"/","valueIsDeadKey":false,"withShift":"/","withShiftIsDeadKey":false,"withAltGr":"/","withAltGrIsDeadKey":false,"withShiftAltGr":"/","withShiftAltGrIsDeadKey":false},"NumpadMultiply":{"value":"*","valueIsDeadKey":false,"withShift":"*","withShiftIsDeadKey":false,"withAltGr":"*","withAltGrIsDeadKey":false,"withShiftAltGr":"*","withShiftAltGrIsDeadKey":false},"NumpadSubtract":{"value":"-","valueIsDeadKey":false,"withShift":"-","withShiftIsDeadKey":false,"withAltGr":"-","withAltGrIsDeadKey":false,"withShiftAltGr":"-","withShiftAltGrIsDeadKey":false},"NumpadAdd":{"value":"+","valueIsDeadKey":false,"withShift":"+","withShiftIsDeadKey":false,"withAltGr":"+","withAltGrIsDeadKey":false,"withShiftAltGr":"+","withShiftAltGrIsDeadKey":false},"NumpadEnter":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"Numpad1":{"value":"1","valueIsDeadKey":false,"withShift":"1","withShiftIsDeadKey":false,"withAltGr":"1","withAltGrIsDeadKey":false,"withShiftAltGr":"1","withShiftAltGrIsDeadKey":false},"Numpad2":{"value":"2","valueIsDeadKey":false,"withShift":"2","withShiftIsDeadKey":false,"withAltGr":"2","withAltGrIsDeadKey":false,"withShiftAltGr":"2","withShiftAltGrIsDeadKey":false},"Numpad3":{"value":"3","valueIsDeadKey":false,"withShift":"3","withShiftIsDeadKey":false,"withAltGr":"3","withAltGrIsDeadKey":false,"withShiftAltGr":"3","withShiftAltGrIsDeadKey":false},"Numpad4":{"value":"4","valueIsDeadKey":false,"withShift":"4","withShiftIsDeadKey":false,"withAltGr":"4","withAltGrIsDeadKey":false,"withShiftAltGr":"4","withShiftAltGrIsDeadKey":false},"Numpad5":{"value":"5","valueIsDeadKey":false,"withShift":"5","withShiftIsDeadKey":false,"withAltGr":"5","withAltGrIsDeadKey":false,"withShiftAltGr":"5","withShiftAltGrIsDeadKey":false},"Numpad6":{"value":"6","valueIsDeadKey":false,"withShift":"6","withShiftIsDeadKey":false,"withAltGr":"6","withAltGrIsDeadKey":false,"withShiftAltGr":"6","withShiftAltGrIsDeadKey":false},"Numpad7":{"value":"7","valueIsDeadKey":false,"withShift":"7","withShiftIsDeadKey":false,"withAltGr":"7","withAltGrIsDeadKey":false,"withShiftAltGr":"7","withShiftAltGrIsDeadKey":false},"Numpad8":{"value":"8","valueIsDeadKey":false,"withShift":"8","withShiftIsDeadKey":false,"withAltGr":"8","withAltGrIsDeadKey":false,"withShiftAltGr":"8","withShiftAltGrIsDeadKey":false},"Numpad9":{"value":"9","valueIsDeadKey":false,"withShift":"9","withShiftIsDeadKey":false,"withAltGr":"9","withAltGrIsDeadKey":false,"withShiftAltGr":"9","withShiftAltGrIsDeadKey":false},"Numpad0":{"value":"0","valueIsDeadKey":false,"withShift":"0","withShiftIsDeadKey":false,"withAltGr":"0","withAltGrIsDeadKey":false,"withShiftAltGr":"0","withShiftAltGrIsDeadKey":false},"NumpadDecimal":{"value":",","valueIsDeadKey":false,"withShift":".","withShiftIsDeadKey":false,"withAltGr":",","withAltGrIsDeadKey":false,"withShiftAltGr":".","withShiftAltGrIsDeadKey":false},"IntlBackslash":{"value":"@","valueIsDeadKey":false,"withShift":"#","withShiftIsDeadKey":false,"withAltGr":"•","withAltGrIsDeadKey":false,"withShiftAltGr":"Ÿ","withShiftAltGrIsDeadKey":false},"ContextMenu":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadEqual":{"value":"=","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"=","withAltGrIsDeadKey":false,"withShiftAltGr":"=","withShiftAltGrIsDeadKey":false},"F13":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F14":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F15":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F16":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F17":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F18":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F19":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"F20":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AudioVolumeMute":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AudioVolumeUp":{"value":"","valueIsDeadKey":false,"withShift":"=","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"=","withShiftAltGrIsDeadKey":false},"AudioVolumeDown":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"NumpadComma":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"IntlRo":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"KanaMode":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"IntlYen":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ControlLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ShiftLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AltLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"MetaLeft":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ControlRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"ShiftRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"AltRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false},"MetaRight":{"value":"","valueIsDeadKey":false,"withShift":"","withShiftIsDeadKey":false,"withAltGr":"","withAltGrIsDeadKey":false,"withShiftAltGr":"","withShiftAltGrIsDeadKey":false}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/win-de-German.json b/packages/core/src/common/keyboard/layouts/win-de-German.json new file mode 100644 index 0000000000000..247036e7405f5 --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/win-de-German.json @@ -0,0 +1 @@ +{"info":{"name":"00000407","id":"","text":"German"},"mapping":{"Sleep":{"vkey":"VK_SLEEP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"WakeUp":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KeyA":{"vkey":"VK_A","value":"a","withShift":"A","withAltGr":"","withShiftAltGr":""},"KeyB":{"vkey":"VK_B","value":"b","withShift":"B","withAltGr":"","withShiftAltGr":""},"KeyC":{"vkey":"VK_C","value":"c","withShift":"C","withAltGr":"","withShiftAltGr":""},"KeyD":{"vkey":"VK_D","value":"d","withShift":"D","withAltGr":"","withShiftAltGr":""},"KeyE":{"vkey":"VK_E","value":"e","withShift":"E","withAltGr":"€","withShiftAltGr":""},"KeyF":{"vkey":"VK_F","value":"f","withShift":"F","withAltGr":"","withShiftAltGr":""},"KeyG":{"vkey":"VK_G","value":"g","withShift":"G","withAltGr":"","withShiftAltGr":""},"KeyH":{"vkey":"VK_H","value":"h","withShift":"H","withAltGr":"","withShiftAltGr":""},"KeyI":{"vkey":"VK_I","value":"i","withShift":"I","withAltGr":"","withShiftAltGr":""},"KeyJ":{"vkey":"VK_J","value":"j","withShift":"J","withAltGr":"","withShiftAltGr":""},"KeyK":{"vkey":"VK_K","value":"k","withShift":"K","withAltGr":"","withShiftAltGr":""},"KeyL":{"vkey":"VK_L","value":"l","withShift":"L","withAltGr":"","withShiftAltGr":""},"KeyM":{"vkey":"VK_M","value":"m","withShift":"M","withAltGr":"µ","withShiftAltGr":""},"KeyN":{"vkey":"VK_N","value":"n","withShift":"N","withAltGr":"","withShiftAltGr":""},"KeyO":{"vkey":"VK_O","value":"o","withShift":"O","withAltGr":"","withShiftAltGr":""},"KeyP":{"vkey":"VK_P","value":"p","withShift":"P","withAltGr":"","withShiftAltGr":""},"KeyQ":{"vkey":"VK_Q","value":"q","withShift":"Q","withAltGr":"@","withShiftAltGr":""},"KeyR":{"vkey":"VK_R","value":"r","withShift":"R","withAltGr":"","withShiftAltGr":""},"KeyS":{"vkey":"VK_S","value":"s","withShift":"S","withAltGr":"","withShiftAltGr":""},"KeyT":{"vkey":"VK_T","value":"t","withShift":"T","withAltGr":"","withShiftAltGr":""},"KeyU":{"vkey":"VK_U","value":"u","withShift":"U","withAltGr":"","withShiftAltGr":""},"KeyV":{"vkey":"VK_V","value":"v","withShift":"V","withAltGr":"","withShiftAltGr":""},"KeyW":{"vkey":"VK_W","value":"w","withShift":"W","withAltGr":"","withShiftAltGr":""},"KeyX":{"vkey":"VK_X","value":"x","withShift":"X","withAltGr":"","withShiftAltGr":""},"KeyY":{"vkey":"VK_Z","value":"z","withShift":"Z","withAltGr":"","withShiftAltGr":""},"KeyZ":{"vkey":"VK_Y","value":"y","withShift":"Y","withAltGr":"","withShiftAltGr":""},"Digit1":{"vkey":"VK_1","value":"1","withShift":"!","withAltGr":"","withShiftAltGr":""},"Digit2":{"vkey":"VK_2","value":"2","withShift":"\"","withAltGr":"²","withShiftAltGr":""},"Digit3":{"vkey":"VK_3","value":"3","withShift":"§","withAltGr":"³","withShiftAltGr":""},"Digit4":{"vkey":"VK_4","value":"4","withShift":"$","withAltGr":"","withShiftAltGr":""},"Digit5":{"vkey":"VK_5","value":"5","withShift":"%","withAltGr":"","withShiftAltGr":""},"Digit6":{"vkey":"VK_6","value":"6","withShift":"&","withAltGr":"","withShiftAltGr":""},"Digit7":{"vkey":"VK_7","value":"7","withShift":"/","withAltGr":"{","withShiftAltGr":""},"Digit8":{"vkey":"VK_8","value":"8","withShift":"(","withAltGr":"[","withShiftAltGr":""},"Digit9":{"vkey":"VK_9","value":"9","withShift":")","withAltGr":"]","withShiftAltGr":""},"Digit0":{"vkey":"VK_0","value":"0","withShift":"=","withAltGr":"}","withShiftAltGr":""},"Enter":{"vkey":"VK_RETURN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Escape":{"vkey":"VK_ESCAPE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Backspace":{"vkey":"VK_BACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Tab":{"vkey":"VK_TAB","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Space":{"vkey":"VK_SPACE","value":" ","withShift":" ","withAltGr":"","withShiftAltGr":""},"Minus":{"vkey":"VK_OEM_4","value":"ß","withShift":"?","withAltGr":"\\","withShiftAltGr":"ẞ"},"Equal":{"vkey":"VK_OEM_6","value":"´","withShift":"`","withAltGr":"","withShiftAltGr":""},"BracketLeft":{"vkey":"VK_OEM_1","value":"ü","withShift":"Ü","withAltGr":"","withShiftAltGr":""},"BracketRight":{"vkey":"VK_OEM_PLUS","value":"+","withShift":"*","withAltGr":"~","withShiftAltGr":""},"Backslash":{"vkey":"VK_OEM_2","value":"#","withShift":"'","withAltGr":"","withShiftAltGr":""},"Semicolon":{"vkey":"VK_OEM_3","value":"ö","withShift":"Ö","withAltGr":"","withShiftAltGr":""},"Quote":{"vkey":"VK_OEM_7","value":"ä","withShift":"Ä","withAltGr":"","withShiftAltGr":""},"Backquote":{"vkey":"VK_OEM_5","value":"^","withShift":"°","withAltGr":"","withShiftAltGr":""},"Comma":{"vkey":"VK_OEM_COMMA","value":",","withShift":";","withAltGr":"","withShiftAltGr":""},"Period":{"vkey":"VK_OEM_PERIOD","value":".","withShift":":","withAltGr":"","withShiftAltGr":""},"Slash":{"vkey":"VK_OEM_MINUS","value":"-","withShift":"_","withAltGr":"","withShiftAltGr":""},"CapsLock":{"vkey":"VK_CAPITAL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F1":{"vkey":"VK_F1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F2":{"vkey":"VK_F2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F3":{"vkey":"VK_F3","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F4":{"vkey":"VK_F4","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F5":{"vkey":"VK_F5","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F6":{"vkey":"VK_F6","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F7":{"vkey":"VK_F7","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F8":{"vkey":"VK_F8","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F9":{"vkey":"VK_F9","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F10":{"vkey":"VK_F10","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F11":{"vkey":"VK_F11","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F12":{"vkey":"VK_F12","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PrintScreen":{"vkey":"VK_SNAPSHOT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ScrollLock":{"vkey":"VK_SCROLL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Pause":{"vkey":"VK_NUMLOCK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Insert":{"vkey":"VK_INSERT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Home":{"vkey":"VK_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageUp":{"vkey":"VK_PRIOR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Delete":{"vkey":"VK_DELETE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"End":{"vkey":"VK_END","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageDown":{"vkey":"VK_NEXT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowRight":{"vkey":"VK_RIGHT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowLeft":{"vkey":"VK_LEFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowDown":{"vkey":"VK_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowUp":{"vkey":"VK_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumLock":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDivide":{"vkey":"VK_DIVIDE","value":"/","withShift":"/","withAltGr":"","withShiftAltGr":""},"NumpadMultiply":{"vkey":"VK_MULTIPLY","value":"*","withShift":"*","withAltGr":"","withShiftAltGr":""},"NumpadSubtract":{"vkey":"VK_SUBTRACT","value":"-","withShift":"-","withAltGr":"","withShiftAltGr":""},"NumpadAdd":{"vkey":"VK_ADD","value":"+","withShift":"+","withAltGr":"","withShiftAltGr":""},"NumpadEnter":{"vkey":"VK_RETURN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad1":{"vkey":"VK_END","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad2":{"vkey":"VK_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad3":{"vkey":"VK_NEXT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad4":{"vkey":"VK_LEFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad5":{"vkey":"VK_CLEAR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad6":{"vkey":"VK_RIGHT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad7":{"vkey":"VK_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad8":{"vkey":"VK_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad9":{"vkey":"VK_PRIOR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad0":{"vkey":"VK_INSERT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDecimal":{"vkey":"VK_DELETE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlBackslash":{"vkey":"VK_OEM_102","value":"<","withShift":">","withAltGr":"|","withShiftAltGr":""},"ContextMenu":{"vkey":"VK_APPS","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Power":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadEqual":{"vkey":"VK_CLEAR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F13":{"vkey":"VK_F13","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F14":{"vkey":"VK_F14","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F15":{"vkey":"VK_F15","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F16":{"vkey":"VK_F16","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F17":{"vkey":"VK_F17","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F18":{"vkey":"VK_F18","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F19":{"vkey":"VK_F19","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F20":{"vkey":"VK_F20","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F21":{"vkey":"VK_F21","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F22":{"vkey":"VK_F22","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F23":{"vkey":"VK_F23","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F24":{"vkey":"VK_F24","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Help":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Undo":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Cut":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Copy":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Paste":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeMute":{"vkey":"VK_VOLUME_MUTE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeUp":{"vkey":"VK_VOLUME_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeDown":{"vkey":"VK_VOLUME_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadComma":{"vkey":"VK_ABNT_C2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlRo":{"vkey":"VK_ABNT_C1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KanaMode":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlYen":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Convert":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NonConvert":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang1":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang2":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang3":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang4":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlLeft":{"vkey":"VK_CONTROL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftLeft":{"vkey":"VK_SHIFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltLeft":{"vkey":"VK_MENU","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaLeft":{"vkey":"VK_LWIN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlRight":{"vkey":"VK_CONTROL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftRight":{"vkey":"VK_SHIFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltRight":{"vkey":"VK_MENU","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaRight":{"vkey":"VK_RWIN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackNext":{"vkey":"VK_MEDIA_NEXT_TRACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackPrevious":{"vkey":"VK_MEDIA_PREV_TRACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaStop":{"vkey":"VK_MEDIA_STOP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Eject":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlayPause":{"vkey":"VK_MEDIA_PLAY_PAUSE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaSelect":{"vkey":"VK_LAUNCH_MEDIA_SELECT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchMail":{"vkey":"VK_LAUNCH_MAIL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp2":{"vkey":"VK_LAUNCH_APP2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp1":{"vkey":"VK_LAUNCH_APP1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserSearch":{"vkey":"VK_BROWSER_SEARCH","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserHome":{"vkey":"VK_BROWSER_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserBack":{"vkey":"VK_BROWSER_BACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserForward":{"vkey":"VK_BROWSER_FORWARD","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserStop":{"vkey":"VK_BROWSER_STOP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserRefresh":{"vkey":"VK_BROWSER_REFRESH","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserFavorites":{"vkey":"VK_BROWSER_FAVORITES","value":"","withShift":"","withAltGr":"","withShiftAltGr":""}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/win-en-US.json b/packages/core/src/common/keyboard/layouts/win-en-US.json new file mode 100644 index 0000000000000..82760a6f064eb --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/win-en-US.json @@ -0,0 +1 @@ +{"info":{"name":"00000409","id":"","text":"US"},"mapping":{"Sleep":{"vkey":"VK_SLEEP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"WakeUp":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KeyA":{"vkey":"VK_A","value":"a","withShift":"A","withAltGr":"","withShiftAltGr":""},"KeyB":{"vkey":"VK_B","value":"b","withShift":"B","withAltGr":"","withShiftAltGr":""},"KeyC":{"vkey":"VK_C","value":"c","withShift":"C","withAltGr":"","withShiftAltGr":""},"KeyD":{"vkey":"VK_D","value":"d","withShift":"D","withAltGr":"","withShiftAltGr":""},"KeyE":{"vkey":"VK_E","value":"e","withShift":"E","withAltGr":"","withShiftAltGr":""},"KeyF":{"vkey":"VK_F","value":"f","withShift":"F","withAltGr":"","withShiftAltGr":""},"KeyG":{"vkey":"VK_G","value":"g","withShift":"G","withAltGr":"","withShiftAltGr":""},"KeyH":{"vkey":"VK_H","value":"h","withShift":"H","withAltGr":"","withShiftAltGr":""},"KeyI":{"vkey":"VK_I","value":"i","withShift":"I","withAltGr":"","withShiftAltGr":""},"KeyJ":{"vkey":"VK_J","value":"j","withShift":"J","withAltGr":"","withShiftAltGr":""},"KeyK":{"vkey":"VK_K","value":"k","withShift":"K","withAltGr":"","withShiftAltGr":""},"KeyL":{"vkey":"VK_L","value":"l","withShift":"L","withAltGr":"","withShiftAltGr":""},"KeyM":{"vkey":"VK_M","value":"m","withShift":"M","withAltGr":"","withShiftAltGr":""},"KeyN":{"vkey":"VK_N","value":"n","withShift":"N","withAltGr":"","withShiftAltGr":""},"KeyO":{"vkey":"VK_O","value":"o","withShift":"O","withAltGr":"","withShiftAltGr":""},"KeyP":{"vkey":"VK_P","value":"p","withShift":"P","withAltGr":"","withShiftAltGr":""},"KeyQ":{"vkey":"VK_Q","value":"q","withShift":"Q","withAltGr":"","withShiftAltGr":""},"KeyR":{"vkey":"VK_R","value":"r","withShift":"R","withAltGr":"","withShiftAltGr":""},"KeyS":{"vkey":"VK_S","value":"s","withShift":"S","withAltGr":"","withShiftAltGr":""},"KeyT":{"vkey":"VK_T","value":"t","withShift":"T","withAltGr":"","withShiftAltGr":""},"KeyU":{"vkey":"VK_U","value":"u","withShift":"U","withAltGr":"","withShiftAltGr":""},"KeyV":{"vkey":"VK_V","value":"v","withShift":"V","withAltGr":"","withShiftAltGr":""},"KeyW":{"vkey":"VK_W","value":"w","withShift":"W","withAltGr":"","withShiftAltGr":""},"KeyX":{"vkey":"VK_X","value":"x","withShift":"X","withAltGr":"","withShiftAltGr":""},"KeyY":{"vkey":"VK_Y","value":"y","withShift":"Y","withAltGr":"","withShiftAltGr":""},"KeyZ":{"vkey":"VK_Z","value":"z","withShift":"Z","withAltGr":"","withShiftAltGr":""},"Digit1":{"vkey":"VK_1","value":"1","withShift":"!","withAltGr":"","withShiftAltGr":""},"Digit2":{"vkey":"VK_2","value":"2","withShift":"@","withAltGr":"","withShiftAltGr":""},"Digit3":{"vkey":"VK_3","value":"3","withShift":"#","withAltGr":"","withShiftAltGr":""},"Digit4":{"vkey":"VK_4","value":"4","withShift":"$","withAltGr":"","withShiftAltGr":""},"Digit5":{"vkey":"VK_5","value":"5","withShift":"%","withAltGr":"","withShiftAltGr":""},"Digit6":{"vkey":"VK_6","value":"6","withShift":"^","withAltGr":"","withShiftAltGr":""},"Digit7":{"vkey":"VK_7","value":"7","withShift":"&","withAltGr":"","withShiftAltGr":""},"Digit8":{"vkey":"VK_8","value":"8","withShift":"*","withAltGr":"","withShiftAltGr":""},"Digit9":{"vkey":"VK_9","value":"9","withShift":"(","withAltGr":"","withShiftAltGr":""},"Digit0":{"vkey":"VK_0","value":"0","withShift":")","withAltGr":"","withShiftAltGr":""},"Enter":{"vkey":"VK_RETURN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Escape":{"vkey":"VK_ESCAPE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Backspace":{"vkey":"VK_BACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Tab":{"vkey":"VK_TAB","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Space":{"vkey":"VK_SPACE","value":" ","withShift":" ","withAltGr":"","withShiftAltGr":""},"Minus":{"vkey":"VK_OEM_MINUS","value":"-","withShift":"_","withAltGr":"","withShiftAltGr":""},"Equal":{"vkey":"VK_OEM_PLUS","value":"=","withShift":"+","withAltGr":"","withShiftAltGr":""},"BracketLeft":{"vkey":"VK_OEM_4","value":"[","withShift":"{","withAltGr":"","withShiftAltGr":""},"BracketRight":{"vkey":"VK_OEM_6","value":"]","withShift":"}","withAltGr":"","withShiftAltGr":""},"Backslash":{"vkey":"VK_OEM_5","value":"\\","withShift":"|","withAltGr":"","withShiftAltGr":""},"Semicolon":{"vkey":"VK_OEM_1","value":";","withShift":":","withAltGr":"","withShiftAltGr":""},"Quote":{"vkey":"VK_OEM_7","value":"'","withShift":"\"","withAltGr":"","withShiftAltGr":""},"Backquote":{"vkey":"VK_OEM_3","value":"`","withShift":"~","withAltGr":"","withShiftAltGr":""},"Comma":{"vkey":"VK_OEM_COMMA","value":",","withShift":"<","withAltGr":"","withShiftAltGr":""},"Period":{"vkey":"VK_OEM_PERIOD","value":".","withShift":">","withAltGr":"","withShiftAltGr":""},"Slash":{"vkey":"VK_OEM_2","value":"/","withShift":"?","withAltGr":"","withShiftAltGr":""},"CapsLock":{"vkey":"VK_CAPITAL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F1":{"vkey":"VK_F1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F2":{"vkey":"VK_F2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F3":{"vkey":"VK_F3","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F4":{"vkey":"VK_F4","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F5":{"vkey":"VK_F5","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F6":{"vkey":"VK_F6","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F7":{"vkey":"VK_F7","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F8":{"vkey":"VK_F8","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F9":{"vkey":"VK_F9","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F10":{"vkey":"VK_F10","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F11":{"vkey":"VK_F11","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F12":{"vkey":"VK_F12","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PrintScreen":{"vkey":"VK_SNAPSHOT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ScrollLock":{"vkey":"VK_SCROLL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Pause":{"vkey":"VK_NUMLOCK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Insert":{"vkey":"VK_INSERT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Home":{"vkey":"VK_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageUp":{"vkey":"VK_PRIOR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Delete":{"vkey":"VK_DELETE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"End":{"vkey":"VK_END","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageDown":{"vkey":"VK_NEXT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowRight":{"vkey":"VK_RIGHT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowLeft":{"vkey":"VK_LEFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowDown":{"vkey":"VK_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowUp":{"vkey":"VK_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumLock":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDivide":{"vkey":"VK_DIVIDE","value":"/","withShift":"/","withAltGr":"","withShiftAltGr":""},"NumpadMultiply":{"vkey":"VK_MULTIPLY","value":"*","withShift":"*","withAltGr":"","withShiftAltGr":""},"NumpadSubtract":{"vkey":"VK_SUBTRACT","value":"-","withShift":"-","withAltGr":"","withShiftAltGr":""},"NumpadAdd":{"vkey":"VK_ADD","value":"+","withShift":"+","withAltGr":"","withShiftAltGr":""},"NumpadEnter":{"vkey":"VK_RETURN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad1":{"vkey":"VK_END","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad2":{"vkey":"VK_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad3":{"vkey":"VK_NEXT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad4":{"vkey":"VK_LEFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad5":{"vkey":"VK_CLEAR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad6":{"vkey":"VK_RIGHT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad7":{"vkey":"VK_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad8":{"vkey":"VK_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad9":{"vkey":"VK_PRIOR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad0":{"vkey":"VK_INSERT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDecimal":{"vkey":"VK_DELETE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlBackslash":{"vkey":"VK_OEM_102","value":"\\","withShift":"|","withAltGr":"","withShiftAltGr":""},"ContextMenu":{"vkey":"VK_APPS","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Power":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadEqual":{"vkey":"VK_CLEAR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F13":{"vkey":"VK_F13","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F14":{"vkey":"VK_F14","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F15":{"vkey":"VK_F15","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F16":{"vkey":"VK_F16","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F17":{"vkey":"VK_F17","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F18":{"vkey":"VK_F18","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F19":{"vkey":"VK_F19","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F20":{"vkey":"VK_F20","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F21":{"vkey":"VK_F21","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F22":{"vkey":"VK_F22","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F23":{"vkey":"VK_F23","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F24":{"vkey":"VK_F24","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Help":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Undo":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Cut":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Copy":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Paste":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeMute":{"vkey":"VK_VOLUME_MUTE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeUp":{"vkey":"VK_VOLUME_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeDown":{"vkey":"VK_VOLUME_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadComma":{"vkey":"VK_ABNT_C2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlRo":{"vkey":"VK_ABNT_C1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KanaMode":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlYen":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Convert":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NonConvert":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang1":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang2":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang3":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang4":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlLeft":{"vkey":"VK_CONTROL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftLeft":{"vkey":"VK_SHIFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltLeft":{"vkey":"VK_MENU","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaLeft":{"vkey":"VK_LWIN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlRight":{"vkey":"VK_CONTROL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftRight":{"vkey":"VK_SHIFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltRight":{"vkey":"VK_MENU","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaRight":{"vkey":"VK_RWIN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackNext":{"vkey":"VK_MEDIA_NEXT_TRACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackPrevious":{"vkey":"VK_MEDIA_PREV_TRACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaStop":{"vkey":"VK_MEDIA_STOP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Eject":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlayPause":{"vkey":"VK_MEDIA_PLAY_PAUSE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaSelect":{"vkey":"VK_LAUNCH_MEDIA_SELECT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchMail":{"vkey":"VK_LAUNCH_MAIL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp2":{"vkey":"VK_LAUNCH_APP2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp1":{"vkey":"VK_LAUNCH_APP1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserSearch":{"vkey":"VK_BROWSER_SEARCH","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserHome":{"vkey":"VK_BROWSER_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserBack":{"vkey":"VK_BROWSER_BACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserForward":{"vkey":"VK_BROWSER_FORWARD","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserStop":{"vkey":"VK_BROWSER_STOP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserRefresh":{"vkey":"VK_BROWSER_REFRESH","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserFavorites":{"vkey":"VK_BROWSER_FAVORITES","value":"","withShift":"","withAltGr":"","withShiftAltGr":""}}} \ No newline at end of file diff --git a/packages/core/src/common/keyboard/layouts/win-fr-French.json b/packages/core/src/common/keyboard/layouts/win-fr-French.json new file mode 100644 index 0000000000000..16d9441576d28 --- /dev/null +++ b/packages/core/src/common/keyboard/layouts/win-fr-French.json @@ -0,0 +1 @@ +{"info":{"name":"0000040C","id":"","text":"French"},"mapping":{"Sleep":{"vkey":"VK_SLEEP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"WakeUp":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KeyA":{"vkey":"VK_Q","value":"q","withShift":"Q","withAltGr":"","withShiftAltGr":""},"KeyB":{"vkey":"VK_B","value":"b","withShift":"B","withAltGr":"","withShiftAltGr":""},"KeyC":{"vkey":"VK_C","value":"c","withShift":"C","withAltGr":"","withShiftAltGr":""},"KeyD":{"vkey":"VK_D","value":"d","withShift":"D","withAltGr":"","withShiftAltGr":""},"KeyE":{"vkey":"VK_E","value":"e","withShift":"E","withAltGr":"€","withShiftAltGr":""},"KeyF":{"vkey":"VK_F","value":"f","withShift":"F","withAltGr":"","withShiftAltGr":""},"KeyG":{"vkey":"VK_G","value":"g","withShift":"G","withAltGr":"","withShiftAltGr":""},"KeyH":{"vkey":"VK_H","value":"h","withShift":"H","withAltGr":"","withShiftAltGr":""},"KeyI":{"vkey":"VK_I","value":"i","withShift":"I","withAltGr":"","withShiftAltGr":""},"KeyJ":{"vkey":"VK_J","value":"j","withShift":"J","withAltGr":"","withShiftAltGr":""},"KeyK":{"vkey":"VK_K","value":"k","withShift":"K","withAltGr":"","withShiftAltGr":""},"KeyL":{"vkey":"VK_L","value":"l","withShift":"L","withAltGr":"","withShiftAltGr":""},"KeyM":{"vkey":"VK_OEM_COMMA","value":",","withShift":"?","withAltGr":"","withShiftAltGr":""},"KeyN":{"vkey":"VK_N","value":"n","withShift":"N","withAltGr":"","withShiftAltGr":""},"KeyO":{"vkey":"VK_O","value":"o","withShift":"O","withAltGr":"","withShiftAltGr":""},"KeyP":{"vkey":"VK_P","value":"p","withShift":"P","withAltGr":"","withShiftAltGr":""},"KeyQ":{"vkey":"VK_A","value":"a","withShift":"A","withAltGr":"","withShiftAltGr":""},"KeyR":{"vkey":"VK_R","value":"r","withShift":"R","withAltGr":"","withShiftAltGr":""},"KeyS":{"vkey":"VK_S","value":"s","withShift":"S","withAltGr":"","withShiftAltGr":""},"KeyT":{"vkey":"VK_T","value":"t","withShift":"T","withAltGr":"","withShiftAltGr":""},"KeyU":{"vkey":"VK_U","value":"u","withShift":"U","withAltGr":"","withShiftAltGr":""},"KeyV":{"vkey":"VK_V","value":"v","withShift":"V","withAltGr":"","withShiftAltGr":""},"KeyW":{"vkey":"VK_Z","value":"z","withShift":"Z","withAltGr":"","withShiftAltGr":""},"KeyX":{"vkey":"VK_X","value":"x","withShift":"X","withAltGr":"","withShiftAltGr":""},"KeyY":{"vkey":"VK_Y","value":"y","withShift":"Y","withAltGr":"","withShiftAltGr":""},"KeyZ":{"vkey":"VK_W","value":"w","withShift":"W","withAltGr":"","withShiftAltGr":""},"Digit1":{"vkey":"VK_1","value":"&","withShift":"1","withAltGr":"","withShiftAltGr":""},"Digit2":{"vkey":"VK_2","value":"é","withShift":"2","withAltGr":"~","withShiftAltGr":""},"Digit3":{"vkey":"VK_3","value":"\"","withShift":"3","withAltGr":"#","withShiftAltGr":""},"Digit4":{"vkey":"VK_4","value":"'","withShift":"4","withAltGr":"{","withShiftAltGr":""},"Digit5":{"vkey":"VK_5","value":"(","withShift":"5","withAltGr":"[","withShiftAltGr":""},"Digit6":{"vkey":"VK_6","value":"-","withShift":"6","withAltGr":"|","withShiftAltGr":""},"Digit7":{"vkey":"VK_7","value":"è","withShift":"7","withAltGr":"`","withShiftAltGr":""},"Digit8":{"vkey":"VK_8","value":"_","withShift":"8","withAltGr":"\\","withShiftAltGr":""},"Digit9":{"vkey":"VK_9","value":"ç","withShift":"9","withAltGr":"^","withShiftAltGr":""},"Digit0":{"vkey":"VK_0","value":"à","withShift":"0","withAltGr":"@","withShiftAltGr":""},"Enter":{"vkey":"VK_RETURN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Escape":{"vkey":"VK_ESCAPE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Backspace":{"vkey":"VK_BACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Tab":{"vkey":"VK_TAB","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Space":{"vkey":"VK_SPACE","value":" ","withShift":" ","withAltGr":"","withShiftAltGr":""},"Minus":{"vkey":"VK_OEM_4","value":")","withShift":"°","withAltGr":"]","withShiftAltGr":""},"Equal":{"vkey":"VK_OEM_PLUS","value":"=","withShift":"+","withAltGr":"}","withShiftAltGr":""},"BracketLeft":{"vkey":"VK_OEM_6","value":"^","withShift":"¨","withAltGr":"","withShiftAltGr":""},"BracketRight":{"vkey":"VK_OEM_1","value":"$","withShift":"£","withAltGr":"¤","withShiftAltGr":""},"Backslash":{"vkey":"VK_OEM_5","value":"*","withShift":"µ","withAltGr":"","withShiftAltGr":""},"Semicolon":{"vkey":"VK_M","value":"m","withShift":"M","withAltGr":"","withShiftAltGr":""},"Quote":{"vkey":"VK_OEM_3","value":"ù","withShift":"%","withAltGr":"","withShiftAltGr":""},"Backquote":{"vkey":"VK_OEM_7","value":"²","withShift":"","withAltGr":"","withShiftAltGr":""},"Comma":{"vkey":"VK_OEM_PERIOD","value":";","withShift":".","withAltGr":"","withShiftAltGr":""},"Period":{"vkey":"VK_OEM_2","value":":","withShift":"/","withAltGr":"","withShiftAltGr":""},"Slash":{"vkey":"VK_OEM_8","value":"!","withShift":"§","withAltGr":"","withShiftAltGr":""},"CapsLock":{"vkey":"VK_CAPITAL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F1":{"vkey":"VK_F1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F2":{"vkey":"VK_F2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F3":{"vkey":"VK_F3","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F4":{"vkey":"VK_F4","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F5":{"vkey":"VK_F5","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F6":{"vkey":"VK_F6","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F7":{"vkey":"VK_F7","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F8":{"vkey":"VK_F8","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F9":{"vkey":"VK_F9","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F10":{"vkey":"VK_F10","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F11":{"vkey":"VK_F11","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F12":{"vkey":"VK_F12","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PrintScreen":{"vkey":"VK_SNAPSHOT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ScrollLock":{"vkey":"VK_SCROLL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Pause":{"vkey":"VK_NUMLOCK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Insert":{"vkey":"VK_INSERT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Home":{"vkey":"VK_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageUp":{"vkey":"VK_PRIOR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Delete":{"vkey":"VK_DELETE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"End":{"vkey":"VK_END","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"PageDown":{"vkey":"VK_NEXT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowRight":{"vkey":"VK_RIGHT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowLeft":{"vkey":"VK_LEFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowDown":{"vkey":"VK_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ArrowUp":{"vkey":"VK_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumLock":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDivide":{"vkey":"VK_DIVIDE","value":"/","withShift":"/","withAltGr":"","withShiftAltGr":""},"NumpadMultiply":{"vkey":"VK_MULTIPLY","value":"*","withShift":"*","withAltGr":"","withShiftAltGr":""},"NumpadSubtract":{"vkey":"VK_SUBTRACT","value":"-","withShift":"-","withAltGr":"","withShiftAltGr":""},"NumpadAdd":{"vkey":"VK_ADD","value":"+","withShift":"+","withAltGr":"","withShiftAltGr":""},"NumpadEnter":{"vkey":"VK_RETURN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad1":{"vkey":"VK_END","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad2":{"vkey":"VK_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad3":{"vkey":"VK_NEXT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad4":{"vkey":"VK_LEFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad5":{"vkey":"VK_CLEAR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad6":{"vkey":"VK_RIGHT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad7":{"vkey":"VK_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad8":{"vkey":"VK_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad9":{"vkey":"VK_PRIOR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Numpad0":{"vkey":"VK_INSERT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadDecimal":{"vkey":"VK_DELETE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlBackslash":{"vkey":"VK_OEM_102","value":"<","withShift":">","withAltGr":"","withShiftAltGr":""},"ContextMenu":{"vkey":"VK_APPS","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Power":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadEqual":{"vkey":"VK_CLEAR","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F13":{"vkey":"VK_F13","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F14":{"vkey":"VK_F14","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F15":{"vkey":"VK_F15","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F16":{"vkey":"VK_F16","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F17":{"vkey":"VK_F17","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F18":{"vkey":"VK_F18","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F19":{"vkey":"VK_F19","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F20":{"vkey":"VK_F20","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F21":{"vkey":"VK_F21","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F22":{"vkey":"VK_F22","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F23":{"vkey":"VK_F23","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"F24":{"vkey":"VK_F24","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Help":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Undo":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Cut":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Copy":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Paste":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeMute":{"vkey":"VK_VOLUME_MUTE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeUp":{"vkey":"VK_VOLUME_UP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AudioVolumeDown":{"vkey":"VK_VOLUME_DOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NumpadComma":{"vkey":"VK_ABNT_C2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlRo":{"vkey":"VK_ABNT_C1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"KanaMode":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"IntlYen":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Convert":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"NonConvert":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang1":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang2":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang3":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Lang4":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlLeft":{"vkey":"VK_CONTROL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftLeft":{"vkey":"VK_SHIFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltLeft":{"vkey":"VK_MENU","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaLeft":{"vkey":"VK_LWIN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ControlRight":{"vkey":"VK_CONTROL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"ShiftRight":{"vkey":"VK_SHIFT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"AltRight":{"vkey":"VK_MENU","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MetaRight":{"vkey":"VK_RWIN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackNext":{"vkey":"VK_MEDIA_NEXT_TRACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaTrackPrevious":{"vkey":"VK_MEDIA_PREV_TRACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaStop":{"vkey":"VK_MEDIA_STOP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"Eject":{"vkey":"VK_UNKNOWN","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaPlayPause":{"vkey":"VK_MEDIA_PLAY_PAUSE","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"MediaSelect":{"vkey":"VK_LAUNCH_MEDIA_SELECT","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchMail":{"vkey":"VK_LAUNCH_MAIL","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp2":{"vkey":"VK_LAUNCH_APP2","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"LaunchApp1":{"vkey":"VK_LAUNCH_APP1","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserSearch":{"vkey":"VK_BROWSER_SEARCH","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserHome":{"vkey":"VK_BROWSER_HOME","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserBack":{"vkey":"VK_BROWSER_BACK","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserForward":{"vkey":"VK_BROWSER_FORWARD","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserStop":{"vkey":"VK_BROWSER_STOP","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserRefresh":{"vkey":"VK_BROWSER_REFRESH","value":"","withShift":"","withAltGr":"","withShiftAltGr":""},"BrowserFavorites":{"vkey":"VK_BROWSER_FAVORITES","value":"","withShift":"","withAltGr":"","withShiftAltGr":""}}} \ No newline at end of file diff --git a/packages/core/src/electron-browser/keyboard/change-notifier.ts b/packages/core/src/electron-browser/keyboard/change-notifier.ts new file mode 100644 index 0000000000000..8f223485dc1de --- /dev/null +++ b/packages/core/src/electron-browser/keyboard/change-notifier.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { postConstruct, injectable } from 'inversify'; +import { ipcRenderer } from 'electron'; +import { KeyboardLayoutChangeNotifier, NativeKeyboardLayout } from '../../common/keyboard/layout-provider'; +import { Emitter } from '../../common/event'; + +/** + * Keyboard layout changes are detected by the native-keymap package. This must happen in the + * main process of Electron. The events are sent to the renderer process using Electron IPC. + */ +@injectable() +export class ElectronKeyboardLayoutChangeNotifier implements KeyboardLayoutChangeNotifier { + + protected nativeLayoutChanged = new Emitter(); + + get onNativeLayoutChanged() { + return this.nativeLayoutChanged.event; + } + + @postConstruct() + protected initialize() { + ipcRenderer.on('keyboardLayoutChanged', (event: Event, newLayout: NativeKeyboardLayout) => this.nativeLayoutChanged.fire(newLayout)); + } + +} diff --git a/packages/core/src/electron-browser/keyboard/electron-keyboard-module.ts b/packages/core/src/electron-browser/keyboard/electron-keyboard-module.ts new file mode 100644 index 0000000000000..698ec6e1bdd8b --- /dev/null +++ b/packages/core/src/electron-browser/keyboard/electron-keyboard-module.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { KeyboardLayoutProvider, keyboardPath, KeyboardLayoutChangeNotifier } from '../../common/keyboard/layout-provider'; +import { WebSocketConnectionProvider } from '../../browser/messaging/ws-connection-provider'; +import { ElectronKeyboardLayoutChangeNotifier } from './change-notifier'; + +export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(KeyboardLayoutProvider).toDynamicValue(ctx => + WebSocketConnectionProvider.createProxy(ctx.container, keyboardPath) + ).inSingletonScope(); + bind(ElectronKeyboardLayoutChangeNotifier).toSelf().inSingletonScope(); + bind(KeyboardLayoutChangeNotifier).toService(ElectronKeyboardLayoutChangeNotifier); +}); diff --git a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts index b0358b55bc777..df0cbe3acfb71 100644 --- a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts +++ b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts @@ -20,7 +20,7 @@ import { CommandRegistry, isOSX, ActionMenuNode, CompositeMenuNode, MAIN_MENU_BAR, MenuModelRegistry, MenuPath } from '../../common'; -import { PreferenceService, KeybindingRegistry, Keybinding, KeyCode, Key } from '../../browser'; +import { PreferenceService, KeybindingRegistry, Keybinding } from '../../browser'; import { ContextKeyService } from '../../browser/context-key-service'; import { Anchor } from '../../browser/context-menu-renderer'; @@ -39,12 +39,20 @@ export class ElectronMainMenuFactory { @inject(MenuModelRegistry) protected readonly menuProvider: MenuModelRegistry, @inject(KeybindingRegistry) protected readonly keybindingRegistry: KeybindingRegistry ) { - this.preferencesService.onPreferenceChanged(() => { + preferencesService.onPreferenceChanged(() => { for (const item of this._toggledCommands) { this._menu.getMenuItemById(item).checked = this.commandRegistry.isToggled(item); electron.remote.getCurrentWindow().setMenu(this._menu); } }); + keybindingRegistry.onKeybindingsChanged(() => { + const createdMenuBar = this.createMenuBar(); + if (isOSX) { + electron.remote.Menu.setApplicationMenu(createdMenuBar); + } else { + electron.remote.getCurrentWindow().setMenu(createdMenuBar); + } + }); } createMenuBar(): Electron.Menu { @@ -151,63 +159,19 @@ export class ElectronMainMenuFactory { /** * Return a user visible representation of a keybinding. */ - protected acceleratorFor(keybinding: Keybinding) { - const keyCodesString = keybinding.keybinding.split(' '); - let result = ''; + protected acceleratorFor(keybinding: Keybinding): string { + const bindingKeySequence = this.keybindingRegistry.resolveKeybinding(keybinding); // FIXME see https://github.com/electron/electron/issues/11740 // Key Sequences can't be represented properly in the electron menu. // // We can do what VS Code does, and append the chords as a suffix to the menu label. // https://github.com/theia-ide/theia/issues/1199#issuecomment-430909480 - if (keyCodesString.length > 1) { - return result; - } - - const keyCodeString = keyCodesString[0]; - const keyCode = KeyCode.parse(keyCodeString); - let previous = false; - const separator = '+'; - - if (keyCode.meta && isOSX) { - if (isOSX) { - result += 'Cmd'; - previous = true; - } - } - - if (keyCode.ctrl) { - if (previous) { - result += separator; - } - result += 'Ctrl'; - previous = true; - } - - if (keyCode.alt) { - if (previous) { - result += separator; - } - result += 'Alt'; - previous = true; - } - - if (keyCode.shift) { - if (previous) { - result += separator; - } - result += 'Shift'; - previous = true; - } - - if (keyCode.key) { - if (previous) { - result += separator; - } - - result += Key.getEasyKey(keyCode.key).easyString; + if (bindingKeySequence.length > 1) { + return ''; } - return result; + const keyCode = bindingKeySequence[0]; + return this.keybindingRegistry.acceleratorForKeyCode(keyCode, '+'); } protected async execute(command: string, anchor?: Anchor): Promise { diff --git a/packages/core/src/node/backend-application-module.ts b/packages/core/src/node/backend-application-module.ts index c3f08c69b5a48..db1e3407eef57 100644 --- a/packages/core/src/node/backend-application-module.ts +++ b/packages/core/src/node/backend-application-module.ts @@ -30,6 +30,8 @@ import { EnvVariablesServer, envVariablesPath } from './../common/env-variables' import { EnvVariablesServerImpl } from './env-variables'; import { ConnectionContainerModule } from './messaging/connection-container-module'; import { QuickPickService, quickPickServicePath } from '../common/quick-pick-service'; +import { KeyboardLayoutProvider, keyboardPath } from '../common/keyboard/layout-provider'; +import { NativeKeyboardLayoutProvider } from './keyboard/native-layout'; decorate(injectable(), ApplicationPackage); @@ -90,4 +92,12 @@ export const backendApplicationModule = new ContainerModule(bind => { const { projectPath } = container.get(BackendApplicationCliContribution); return new ApplicationPackage({ projectPath }); }).inSingletonScope(); + + bind(NativeKeyboardLayoutProvider).toSelf().inSingletonScope(); + bind(KeyboardLayoutProvider).toService(NativeKeyboardLayoutProvider); + bind(ConnectionHandler).toDynamicValue(ctx => + new JsonRpcConnectionHandler(keyboardPath, () => + ctx.container.get(KeyboardLayoutProvider) + ) + ); }); diff --git a/packages/core/src/node/keyboard/native-layout.ts b/packages/core/src/node/keyboard/native-layout.ts new file mode 100644 index 0000000000000..cc06fa0ccf446 --- /dev/null +++ b/packages/core/src/node/keyboard/native-layout.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as nativeKeymap from 'native-keymap'; +import { injectable } from 'inversify'; +import { KeyboardLayoutProvider, NativeKeyboardLayout } from '../../common/keyboard/layout-provider'; + +@injectable() +export class NativeKeyboardLayoutProvider implements KeyboardLayoutProvider { + + getNativeLayout(): Promise { + return Promise.resolve(this.getNativeLayoutSync()); + } + + protected getNativeLayoutSync(): NativeKeyboardLayout { + return { + info: nativeKeymap.getCurrentKeyboardLayout(), + mapping: nativeKeymap.getKeyMap() + }; + } + +} diff --git a/packages/core/src/typings/native-keymap.d.ts b/packages/core/src/typings/native-keymap.d.ts new file mode 100644 index 0000000000000..91eaad1cf43fc --- /dev/null +++ b/packages/core/src/typings/native-keymap.d.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'native-keymap' { + + 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; + + export function getKeyMap(): IKeyboardMapping; + + /* __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 function getCurrentKeyboardLayout(): IKeyboardLayoutInfo; + + export function onDidChangeKeyboardLayout(callback: () => void): void; + + export function isISOKeyboard(): boolean; +} diff --git a/packages/debug/src/browser/editor/debug-hover-widget.ts b/packages/debug/src/browser/editor/debug-hover-widget.ts index d85913439ca30..9a3a766821557 100644 --- a/packages/debug/src/browser/editor/debug-hover-widget.ts +++ b/packages/debug/src/browser/editor/debug-hover-widget.ts @@ -19,8 +19,8 @@ import debounce = require('lodash.debounce'); import { Widget } from '@phosphor/widgets'; import { Message } from '@phosphor/messaging'; import { injectable, postConstruct, inject, Container, interfaces } from 'inversify'; +import { Key } from '@theia/core/lib/browser'; import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree'; -import { Key } from '@theia/core/lib/browser/keys'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { DebugSessionManager } from '../debug-session-manager'; import { DebugEditor } from './debug-editor'; diff --git a/packages/file-search/src/browser/quick-file-open.ts b/packages/file-search/src/browser/quick-file-open.ts index 9e99e61c2bab7..59db00b1b392c 100644 --- a/packages/file-search/src/browser/quick-file-open.ts +++ b/packages/file-search/src/browser/quick-file-open.ts @@ -17,7 +17,8 @@ import { inject, injectable } from 'inversify'; import { QuickOpenModel, QuickOpenItem, QuickOpenMode, PrefixQuickOpenService, - OpenerService, KeybindingRegistry, QuickOpenGroupItem, QuickOpenGroupItemOptions, QuickOpenItemOptions, QuickOpenHandler, QuickOpenOptions, Keybinding + OpenerService, KeybindingRegistry, QuickOpenGroupItem, QuickOpenGroupItemOptions, QuickOpenItemOptions, + QuickOpenHandler, QuickOpenOptions } from '@theia/core/lib/browser'; import { FileSystem } from '@theia/filesystem/lib/common/filesystem'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; @@ -131,7 +132,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { const keyCommand = this.keybindingRegistry.getKeybindingsForCommand(quickFileOpen.id); if (keyCommand) { // We only consider the first keybinding. - const accel = Keybinding.acceleratorFor(keyCommand[0], '+'); + const accel = this.keybindingRegistry.acceleratorFor(keyCommand[0], '+'); return accel.join(' '); } diff --git a/packages/mini-browser/src/browser/mini-browser-content.ts b/packages/mini-browser/src/browser/mini-browser-content.ts index fa6cbf668149e..c544dadcda71b 100644 --- a/packages/mini-browser/src/browser/mini-browser-content.ts +++ b/packages/mini-browser/src/browser/mini-browser-content.ts @@ -20,12 +20,11 @@ import { Message } from '@phosphor/messaging'; import { PanelLayout, SplitPanel } from '@phosphor/widgets'; import URI from '@theia/core/lib/common/uri'; import { ILogger } from '@theia/core/lib/common/logger'; -import { Key, KeyCode } from '@theia/core/lib/browser/keys'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { FileSystem } from '@theia/filesystem/lib/common/filesystem'; import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; -import { FrontendApplicationContribution, ApplicationShell, parseCssTime } from '@theia/core/lib/browser'; +import { FrontendApplicationContribution, ApplicationShell, parseCssTime, Key, KeyCode } from '@theia/core/lib/browser'; import { FileSystemWatcher, FileChangeEvent } from '@theia/filesystem/lib/browser/filesystem-watcher'; import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable'; import { BaseWidget, addEventListener, FocusTracker, Widget } from '@theia/core/lib/browser/widgets/widget'; diff --git a/packages/monaco/src/browser/monaco-keybinding.ts b/packages/monaco/src/browser/monaco-keybinding.ts index 05fed49f0807d..816bfe908e435 100644 --- a/packages/monaco/src/browser/monaco-keybinding.ts +++ b/packages/monaco/src/browser/monaco-keybinding.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable, inject } from 'inversify'; -import { KeybindingContribution, KeybindingRegistry, Key, KeyCode, Keystroke, KeyModifier } from '@theia/core/lib/browser'; +import { KeybindingContribution, KeybindingRegistry, Key, KeyCode, Keystroke, KeyModifier, KeySequence } from '@theia/core/lib/browser'; import { EditorKeybindingContexts } from '@theia/editor/lib/browser'; import { MonacoCommands } from './monaco-command'; import { MonacoCommandRegistry } from './monaco-command-registry'; @@ -43,26 +43,14 @@ export class MonacoKeybindingContribution implements KeybindingContribution { const command = this.commands.validate(item.command); if (command) { const raw = item.keybinding; - if (raw.type === monaco.keybindings.KeybindingType.Simple) { - let keybinding = raw as monaco.keybindings.SimpleKeybinding; - // TODO: remove this temporary workaround after updating to monaco including the fix for https://github.com/Microsoft/vscode/issues/49225 - if (command === 'monaco.editor.action.refactor' && !isOSX) { - keybinding = { ...keybinding, ctrlKey: true, metaKey: false }; - } - // TODO: remove this temporary workaround with a holistic solution. - if (command === 'monaco.editor.action.commentLine' && isOSX) { - keybinding = { ...keybinding, ctrlKey: true, metaKey: false }; - } - const isInDiffEditor = item.when && /(^|[^!])\bisInDiffEditor\b/gm.test(item.when.serialize()); - registry.registerKeybinding({ - command, - keybinding: this.keyCode(keybinding).toString(), - context: isInDiffEditor ? EditorKeybindingContexts.diffEditorTextFocus : EditorKeybindingContexts.strictEditorTextFocus - - }); - } else { - // FIXME support chord keybindings properly, KeyCode does not allow it right now - } + const keybinding = raw.type === monaco.keybindings.KeybindingType.Simple + ? this.keyCode(raw as monaco.keybindings.SimpleKeybinding).toString() + : this.keySequence(raw as monaco.keybindings.ChordKeybinding).join(' '); + const isInDiffEditor = item.when && /(^|[^!])\bisInDiffEditor\b/gm.test(item.when.serialize()); + const context = isInDiffEditor + ? EditorKeybindingContexts.diffEditorTextFocus + : EditorKeybindingContexts.strictEditorTextFocus; + registry.registerKeybinding({ command, keybinding, context }); } } @@ -80,7 +68,7 @@ export class MonacoKeybindingContribution implements KeybindingContribution { protected keyCode(keybinding: monaco.keybindings.SimpleKeybinding): KeyCode { const keyCode = keybinding.keyCode; const sequence: Keystroke = { - first: Key.getKey(monaco2BrowserKeyCode(keyCode & 255)), + first: Key.getKey(monaco2BrowserKeyCode(keyCode & 0xff)), modifiers: [] }; if (keybinding.ctrlKey) { @@ -101,4 +89,11 @@ export class MonacoKeybindingContribution implements KeybindingContribution { } return KeyCode.createKeyCode(sequence); } + + protected keySequence(keybinding: monaco.keybindings.ChordKeybinding): KeySequence { + return [ + this.keyCode(keybinding.firstPart), + this.keyCode(keybinding.chordPart) + ]; + } } diff --git a/packages/monaco/src/browser/monaco-loader.ts b/packages/monaco/src/browser/monaco-loader.ts index 8e607ca693af4..fe12963d6d8c8 100644 --- a/packages/monaco/src/browser/monaco-loader.ts +++ b/packages/monaco/src/browser/monaco-loader.ts @@ -51,6 +51,7 @@ export function loadMonaco(vsRequire: any): Promise { 'vs/platform/keybinding/common/keybindingsRegistry', 'vs/platform/keybinding/common/keybindingResolver', 'vs/platform/keybinding/common/usLayoutResolvedKeybinding', + 'vs/base/common/keybindingLabels', 'vs/base/common/keyCodes', 'vs/editor/browser/editorExtensions', 'vs/editor/standalone/browser/simpleServices', @@ -74,7 +75,7 @@ export function loadMonaco(vsRequire: any): Promise { 'vs/platform/contextkey/common/contextkey', 'vs/platform/contextkey/browser/contextKeyService' ], (css: any, html: any, commands: any, actions: any, - keybindingsRegistry: any, keybindingResolver: any, resolvedKeybinding: any, + keybindingsRegistry: any, keybindingResolver: any, resolvedKeybinding: any, keybindingLabels: any, keyCodes: any, editorExtensions: any, simpleServices: any, standaloneServices: any, quickOpen: any, quickOpenWidget: any, quickOpenModel: any, filters: any, styler: any, platform: any, modes: any, suggest: any, suggestController: any, findController: any, rename: any, snippetParser: any, configuration: any, configurationModels: any, @@ -83,7 +84,7 @@ export function loadMonaco(vsRequire: any): Promise { const global: any = self; global.monaco.commands = commands; global.monaco.actions = actions; - global.monaco.keybindings = Object.assign({}, keybindingsRegistry, keybindingResolver, resolvedKeybinding, keyCodes); + global.monaco.keybindings = Object.assign({}, keybindingsRegistry, keybindingResolver, resolvedKeybinding, keybindingLabels, keyCodes); global.monaco.services = Object.assign({}, simpleServices, standaloneServices, configuration, configurationModels, codeEditorService, codeEditorServiceImpl); global.monaco.quickOpen = Object.assign({}, quickOpen, quickOpenWidget, quickOpenModel); global.monaco.filters = filters; diff --git a/packages/monaco/src/browser/monaco-quick-open-service.ts b/packages/monaco/src/browser/monaco-quick-open-service.ts index 934f4c79a84e9..5c91b22134307 100644 --- a/packages/monaco/src/browser/monaco-quick-open-service.ts +++ b/packages/monaco/src/browser/monaco-quick-open-service.ts @@ -17,8 +17,9 @@ import { injectable, inject, postConstruct } from 'inversify'; import { MessageType } from '@theia/core/lib/common/message-service-protocol'; import { - QuickOpenService, QuickOpenModel, QuickOpenOptions, QuickOpenItem, - QuickOpenGroupItem, QuickOpenMode, KeySequence, QuickOpenActionProvider, QuickOpenAction + QuickOpenService, QuickOpenModel, QuickOpenOptions, QuickOpenItem, QuickOpenGroupItem, + QuickOpenMode, KeySequence, QuickOpenActionProvider, QuickOpenAction, ResolvedKeybinding, + KeyCode, Key, KeybindingRegistry } from '@theia/core/lib/browser'; import { KEY_CODE_MAP } from './monaco-keycode-map'; import { ContextKey } from '@theia/core/lib/browser/context-key-service'; @@ -43,6 +44,9 @@ export class MonacoQuickOpenService extends QuickOpenService { @inject(MonacoContextKeyService) protected readonly contextKeyService: MonacoContextKeyService; + @inject(KeybindingRegistry) + protected readonly keybindingRegistry: KeybindingRegistry; + protected inQuickOpenKey: ContextKey; constructor() { @@ -64,7 +68,7 @@ export class MonacoQuickOpenService extends QuickOpenService { } open(model: QuickOpenModel, options?: QuickOpenOptions): void { - this.internalOpen(new MonacoQuickOpenControllerOptsImpl(model, options)); + this.internalOpen(new MonacoQuickOpenControllerOptsImpl(model, this.keybindingRegistry, options)); } showDecoration(type: MessageType): void { @@ -192,6 +196,7 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl constructor( protected readonly model: QuickOpenModel, + protected readonly keybindingService: TheiaKeybindingService, options?: QuickOpenOptions ) { this.model = model; @@ -252,7 +257,9 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl && !this.options.showItemsWithoutHighlight) { return undefined; } - const entry = item instanceof QuickOpenGroupItem ? new QuickOpenEntryGroup(item) : new QuickOpenEntry(item); + const entry = item instanceof QuickOpenGroupItem + ? new QuickOpenEntryGroup(item, this.keybindingService) + : new QuickOpenEntry(item, this.keybindingService); entry.setHighlights(labelHighlights || [], descriptionHighlights, detailHighlights); return entry; } @@ -285,7 +292,8 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl export class QuickOpenEntry extends monaco.quickOpen.QuickOpenEntry { constructor( - public readonly item: QuickOpenItem + public readonly item: QuickOpenItem, + protected readonly keybindingService: TheiaKeybindingService ) { super(); } @@ -327,53 +335,11 @@ export class QuickOpenEntry extends monaco.quickOpen.QuickOpenEntry { let keySequence: KeySequence; try { - keySequence = KeySequence.parse(keybinding.keybinding); + keySequence = this.keybindingService.resolveKeybinding(keybinding); } catch (error) { return undefined; } - - if (keySequence.length < 2) { - const keyCode = keySequence[0]; - if (keyCode.key !== undefined) { // This should not happen. - const simple = new monaco.keybindings.SimpleKeybinding( - keyCode.ctrl, - keyCode.shift, - keyCode.alt, - keyCode.meta, - KEY_CODE_MAP[keyCode.key.keyCode] - ); - return new monaco.keybindings.USLayoutResolvedKeybinding(simple, monaco.platform.OS); - } - } else if (keySequence.length === 2) { - /* FIXME only 2 keycodes are supported by monaco. */ - const first = keySequence[0]; - const second = keySequence[1]; - - if (first.key !== undefined && second.key !== undefined) { - const firstPart = new monaco.keybindings.SimpleKeybinding( - first.ctrl, - first.shift, - first.alt, - first.meta, - KEY_CODE_MAP[first.key.keyCode] - ); - - const secondPart = new monaco.keybindings.SimpleKeybinding( - second.ctrl, - second.shift, - second.alt, - second.meta, - KEY_CODE_MAP[second.key.keyCode] - ); - - return new monaco.keybindings.USLayoutResolvedKeybinding( - new monaco.keybindings.ChordKeybinding(firstPart, secondPart), - monaco.platform.OS); - } - } else { - return undefined; - } - + return new TheiaResolvedKeybinding(keySequence, this.keybindingService); } run(mode: monaco.quickOpen.Mode): boolean { @@ -394,9 +360,10 @@ export class QuickOpenEntry extends monaco.quickOpen.QuickOpenEntry { export class QuickOpenEntryGroup extends monaco.quickOpen.QuickOpenEntryGroup { constructor( - public readonly item: QuickOpenGroupItem + public readonly item: QuickOpenGroupItem, + protected readonly keybindingService: TheiaKeybindingService ) { - super(new QuickOpenEntry(item)); + super(new QuickOpenEntry(item, keybindingService)); } getGroupLabel(): string { @@ -477,3 +444,121 @@ export class MonacoQuickOpenActionProvider implements monaco.quickOpen.IActionPr return undefined; } } + +interface TheiaKeybindingService { + resolveKeybinding(binding: ResolvedKeybinding): KeyCode[]; + acceleratorForKey(key: Key): string; + acceleratorForKeyCode(keyCode: KeyCode, separator?: string): string + acceleratorForSequence(keySequence: KeySequence, separator?: string): string[]; +} + +class TheiaResolvedKeybinding extends monaco.keybindings.ResolvedKeybinding { + + protected readonly parts: { key: string | null, modifiers: monaco.keybindings.Modifiers }[]; + + constructor(protected readonly keySequence: KeySequence, keybindingService: TheiaKeybindingService) { + super(); + this.parts = keySequence.map(keyCode => ({ + // tslint:disable-next-line:no-null-keyword + key: keyCode.key ? keybindingService.acceleratorForKey(keyCode.key) : null, + modifiers: { + ctrlKey: keyCode.ctrl, + shiftKey: keyCode.shift, + altKey: keyCode.alt, + metaKey: keyCode.meta + } + })); + } + + private getKeyAndModifiers(index: number) { + if (index >= this.parts.length) { + // tslint:disable-next-line:no-null-keyword + return { key: null, modifiers: null }; + } + return this.parts[index]; + } + + public getLabel(): string { + const firstPart = this.getKeyAndModifiers(0); + const chordPart = this.getKeyAndModifiers(1); + return monaco.keybindings.UILabelProvider.toLabel(firstPart.modifiers, firstPart.key, + chordPart.modifiers, chordPart.key, monaco.platform.OS); + } + + public getAriaLabel(): string { + const firstPart = this.getKeyAndModifiers(0); + const chordPart = this.getKeyAndModifiers(1); + return monaco.keybindings.AriaLabelProvider.toLabel(firstPart.modifiers, firstPart.key, + chordPart.modifiers, chordPart.key, monaco.platform.OS); + } + + public getElectronAccelerator(): string { + const firstPart = this.getKeyAndModifiers(0); + return monaco.keybindings.ElectronAcceleratorLabelProvider.toLabel(firstPart.modifiers, firstPart.key, + // tslint:disable-next-line:no-null-keyword + null, null, monaco.platform.OS); + } + + public getUserSettingsLabel(): string { + const firstPart = this.getKeyAndModifiers(0); + const chordPart = this.getKeyAndModifiers(1); + return monaco.keybindings.UserSettingsLabelProvider.toLabel(firstPart.modifiers, firstPart.key, + chordPart.modifiers, chordPart.key, monaco.platform.OS); + } + + public isWYSIWYG(): boolean { + return true; + } + + public isChord(): boolean { + return this.parts.length >= 2; + } + + public getDispatchParts(): [string | null, string | null] { + const firstKeybinding = this.toKeybinding(0)!; + const firstPart = monaco.keybindings.USLayoutResolvedKeybinding.getDispatchStr(firstKeybinding); + const chordKeybinding = this.toKeybinding(1); + // tslint:disable-next-line:no-null-keyword + const chordPart = chordKeybinding ? monaco.keybindings.USLayoutResolvedKeybinding.getDispatchStr(chordKeybinding) : null; + return [firstPart, chordPart]; + } + + private toKeybinding(index: number): monaco.keybindings.SimpleKeybinding | null { + if (index >= this.keySequence.length) { + // tslint:disable-next-line:no-null-keyword + return null; + } + const keyCode = this.keySequence[index]; + return new monaco.keybindings.SimpleKeybinding( + keyCode.ctrl, + keyCode.shift, + keyCode.alt, + keyCode.meta, + KEY_CODE_MAP[keyCode.key!.keyCode] + ); + } + + public getParts(): [monaco.keybindings.ResolvedKeybindingPart | null, monaco.keybindings.ResolvedKeybindingPart | null] { + return [ + this.toResolvedKeybindingPart(0), + this.toResolvedKeybindingPart(1) + ]; + } + + private toResolvedKeybindingPart(index: number): monaco.keybindings.ResolvedKeybindingPart | null { + if (index >= this.parts.length) { + // tslint:disable-next-line:no-null-keyword + return null; + } + const part = this.parts[index]; + return new monaco.keybindings.ResolvedKeybindingPart( + part.modifiers.ctrlKey, + part.modifiers.shiftKey, + part.modifiers.altKey, + part.modifiers.metaKey, + part.key!, + part.key! + ); + } + +} diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index 46985f2bf6565..2e73713e57baa 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -403,25 +403,90 @@ declare module monaco.keybindings { constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, kbLabel: string, kbAriaLabel: string); } + export abstract class ResolvedKeybinding { + /** + * This prints the binding in a format suitable for displaying in the UI. + */ + public abstract getLabel(): string; /** * This prints the binding in a format suitable for ARIA. */ public abstract getAriaLabel(): string; + /** + * This prints the binding in a format suitable for electron's accelerators. + * See https://github.com/electron/electron/blob/master/docs/api/accelerator.md + */ + public abstract getElectronAccelerator(): string; + /** + * This prints the binding in a format suitable for user settings. + */ + public abstract getUserSettingsLabel(): string; + /** + * Is the user settings label reflecting the label? + */ + public abstract isWYSIWYG(): boolean; + /** + * Is the binding a chord? + */ + public abstract isChord(): boolean; + /** + * Returns the firstPart, chordPart that should be used for dispatching. + */ + public abstract getDispatchParts(): [string | null, string | null]; /** * Returns the firstPart, chordPart of the keybinding. * For simple keybindings, the second element will be null. */ - public abstract getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | undefined]; + public abstract getParts(): [ResolvedKeybindingPart | null, ResolvedKeybindingPart | null]; } export class USLayoutResolvedKeybinding extends ResolvedKeybinding { constructor(actual: Keybinding, OS: monaco.platform.OperatingSystem); + public getLabel(): string; public getAriaLabel(): string; - public getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | undefined]; + public getElectronAccelerator(): string; + public getUserSettingsLabel(): string; + public isWYSIWYG(): boolean; + public isChord(): boolean; + public getDispatchParts(): [string, string]; + public getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart]; + + public static getDispatchStr(keybinding: SimpleKeybinding): string; } + export interface Modifiers { + readonly ctrlKey: boolean; + readonly shiftKey: boolean; + readonly altKey: boolean; + readonly metaKey: boolean; + } + + export interface ModifierLabels { + readonly ctrlKey: string; + readonly shiftKey: string; + readonly altKey: string; + readonly metaKey: string; + readonly separator: string; + } + + export class ModifierLabelProvider { + + public readonly modifierLabels: ModifierLabels[]; + + constructor(mac: ModifierLabels, windows: ModifierLabels, linux?: ModifierLabels); + + public toLabel(firstPartMod: Modifiers | null, firstPartKey: string | null, + chordPartMod: Modifiers | null, chordPartKey: string | null, + OS: monaco.platform.OperatingSystem): string; + } + + export const UILabelProvider: ModifierLabelProvider; + export const AriaLabelProvider: ModifierLabelProvider; + export const ElectronAcceleratorLabelProvider: ModifierLabelProvider; + export const UserSettingsLabelProvider: ModifierLabelProvider; + } declare module monaco.services { diff --git a/packages/plugin-ext/src/main/browser/keybindings/keybindings-contribution-handler.ts b/packages/plugin-ext/src/main/browser/keybindings/keybindings-contribution-handler.ts index ccd8a27d0817b..c4ac1b7f78f6b 100644 --- a/packages/plugin-ext/src/main/browser/keybindings/keybindings-contribution-handler.ts +++ b/packages/plugin-ext/src/main/browser/keybindings/keybindings-contribution-handler.ts @@ -17,7 +17,6 @@ import { injectable, inject } from 'inversify'; import { PluginContribution, Keybinding as PluginKeybinding } from '../../../common'; import { Keybinding, KeybindingRegistry, KeybindingScope } from '@theia/core/lib/browser/keybinding'; -import { KeySequence } from '@theia/core/lib/browser'; import { ILogger } from '@theia/core/lib/common/logger'; import { OS } from '@theia/core/lib/common/os'; @@ -39,7 +38,8 @@ export class KeybindingsContributionPointHandler { const keybinding = this.toKeybinding(raw); if (keybinding) { try { - const keybindingResult = this.keybindingRegistry.getKeybindingsForKeySequence(KeySequence.parse(keybinding.keybinding)); + const bindingKeySequence = this.keybindingRegistry.resolveKeybinding(keybinding); + const keybindingResult = this.keybindingRegistry.getKeybindingsForKeySequence(bindingKeySequence); this.handleShadingKeybindings(keybinding, keybindingResult.shadow); this.handlePartialKeybindings(keybinding, keybindingResult.partial); keybindings.push(keybinding); diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index bdde7f0f98c69..d864b280458fd 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -28,7 +28,7 @@ import { import { QuickPickService } from '@theia/core/lib/common/quick-pick-service'; import { ApplicationShell, KeybindingContribution, KeyCode, Key, - KeyModifier, KeybindingRegistry, Widget, LabelProvider, WidgetOpenerOptions + KeybindingRegistry, Widget, LabelProvider, WidgetOpenerOptions } from '@theia/core/lib/browser'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { WidgetManager } from '@theia/core/lib/browser'; @@ -225,7 +225,8 @@ export class TerminalFrontendContribution implements TerminalService, CommandCon }); keybindings.registerKeybinding({ command: TerminalCommands.TERMINAL_CLEAR.id, - keybinding: 'ctrlcmd+k' + keybinding: 'ctrlcmd+k', + context: TerminalKeybindingContexts.terminalActive }); /* Register passthrough keybindings for combinations recognized by @@ -235,20 +236,20 @@ export class TerminalFrontendContribution implements TerminalService, CommandCon /* Register ctrl + k (the passed Key) as a passthrough command in the context of the terminal. */ - const regCtrl = (k: Key) => { + const regCtrl = (k: { keyCode: number, code: string }) => { keybindings.registerKeybinding({ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, - keybinding: KeyCode.createKeyCode({ first: k, modifiers: [KeyModifier.CTRL] }).toString(), + keybinding: KeyCode.createKeyCode({ key: k, ctrl: true }).toString(), context: TerminalKeybindingContexts.terminalActive, }); }; /* Register alt + k (the passed Key) as a passthrough command in the context of the terminal. */ - const regAlt = (k: Key) => { + const regAlt = (k: { keyCode: number, code: string }) => { keybindings.registerKeybinding({ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, - keybinding: KeyCode.createKeyCode({ first: k, modifiers: [KeyModifier.Alt] }).toString(), + keybinding: KeyCode.createKeyCode({ key: k, alt: true }).toString(), context: TerminalKeybindingContexts.terminalActive }); }; diff --git a/yarn.lock b/yarn.lock index 3524f8d1fe11a..f82f32fe278cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6573,6 +6573,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +native-keymap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-1.2.5.tgz#1035a9417b9a9340cf8097763a43c76d588165a5" + integrity sha1-EDWpQXuak0DPgJd2OkPHbViBZaU= + native-promise-only@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"