├── .git-blame-ignore
├── .gitattributes
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .npmignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── License.txt
├── PoliCheckExclusions.xml
├── README.md
├── SECURITY.md
├── ThirdPartyNotices.txt
├── binding.gyp
├── deps
└── chromium
│ ├── dom_code_data.inc
│ ├── keyboard_codes.h
│ ├── macros.h
│ └── x
│ ├── keysym_to_unicode.cc
│ └── keysym_to_unicode.h
├── index.d.ts
├── index.js
├── package-lock.json
├── package.json
├── pipeline.yml
├── src
├── common.h
├── keyboard_mac.mm
├── keyboard_win.cc
├── keyboard_x.cc
├── keymapping.cc
├── keymapping.h
├── string_conversion.cc
└── string_conversion.h
└── test
├── linux
├── de_ch.txt
├── de_neo.txt
├── en.txt
└── es.txt
├── mac
├── chinese-pinyin.txt
├── de_ch.txt
├── de_de.txt
├── en_dvorak.txt
├── en_gb.txt
├── en_intl.txt
├── en_us.txt
├── japanese-hiragana.txt
└── spanish-iso.txt
└── test.js
/.git-blame-ignore:
--------------------------------------------------------------------------------
1 | # https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
2 |
3 | # CRLF -> LF
4 | 76e18e1277a59ffc88adc9b9154d66454ccb0630
5 |
6 | # Reduce indentation
7 | 026f020d00a19f79feb816dd6a6538d8963a6864
8 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | windows:
7 | name: Windows
8 | runs-on: windows-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-node@v2
12 | with:
13 | node-version: 16
14 | - run: npm ci
15 | - run: npm test
16 |
17 | linux:
18 | name: Linux
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v2
22 | - uses: actions/setup-node@v2
23 | with:
24 | node-version: 16
25 | - run: npm ci
26 | - run: npm test
27 |
28 | macos:
29 | name: macOS
30 | runs-on: macos-latest
31 | steps:
32 | - uses: actions/checkout@v2
33 | - uses: actions/setup-node@v2
34 | with:
35 | node-version: 16
36 | # https://github.com/nodejs/node-gyp/issues/2869
37 | - run: python3 -m pip install setuptools
38 | - run: npm ci
39 | - run: npm test
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /node_modules/
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.github/
2 | /.vscode/
3 | /build/
4 | /test/
5 | /.git-blame-ignore
6 | /.gitattributes
7 | /.gitignore
8 | /.npmignore
9 | /npm-debug.log
10 | /pipeline.yml
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "preLaunchTask": "node-gyp",
6 | "name": "C++ Launch",
7 | "type": "cppdbg",
8 | "request": "launch",
9 | "program": "/usr/bin/node",
10 | "args": ["test/test.js"],
11 | "stopAtEntry": false,
12 | "cwd": "${workspaceRoot}",
13 | "environment": [],
14 | "externalConsole": true,
15 | "linux": {
16 | "MIMode": "gdb",
17 | "setupCommands": [
18 | {
19 | "description": "Enable pretty-printing for gdb",
20 | "text": "-enable-pretty-printing",
21 | "ignoreFailures": true
22 | }
23 | ]
24 | },
25 | "osx": {
26 | "MIMode": "lldb"
27 | },
28 | "windows": {
29 | "MIMode": "gdb",
30 | "setupCommands": [
31 | {
32 | "description": "Enable pretty-printing for gdb",
33 | "text": "-enable-pretty-printing",
34 | "ignoreFailures": true
35 | }
36 | ]
37 | }
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "editor.insertSpaces": true,
4 | "files.trimTrailingWhitespace": true,
5 | "editor.tabSize": 2,
6 | "files.exclude": {
7 | "**/.git": true,
8 | "**/.DS_Store": true,
9 | "build/**": true
10 | },
11 | "files.associations": {
12 | "*.inc": "cpp"
13 | },
14 | "git.branchProtection": ["main", "release/*"],
15 | "git.branchProtectionPrompt": "alwaysCommitToNewBranch",
16 | "git.branchRandomName.enable": true
17 | }
18 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "command": "node-gyp",
6 | "args": [
7 | "build"
8 | ],
9 | "problemMatcher": {
10 | "owner": "cpp",
11 | "fileLocation": [
12 | "relative",
13 | "${workspaceRoot}/build"
14 | ],
15 | "pattern": {
16 | "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
17 | "file": 1,
18 | "line": 2,
19 | "column": 3,
20 | "severity": 4,
21 | "message": 5
22 | }
23 | },
24 | "tasks": [
25 | {
26 | "label": "node-gyp",
27 | "type": "shell",
28 | "command": "node-gyp",
29 | "args": [
30 | "build"
31 | ],
32 | "problemMatcher": {
33 | "owner": "cpp",
34 | "fileLocation": [
35 | "relative",
36 | "${workspaceRoot}/build"
37 | ],
38 | "pattern": {
39 | "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
40 | "file": 1,
41 | "line": 2,
42 | "column": 3,
43 | "severity": 4,
44 | "message": 5
45 | }
46 | },
47 | "group": "build"
48 | }
49 | ]
50 | }
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) Microsoft Corporation
2 |
3 | All rights reserved.
4 |
5 | MIT License
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
8 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
9 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
16 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
17 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/PoliCheckExclusions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DEPS
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OS key mapping node module [](https://dev.azure.com/ms/node-native-keymap/_build/latest?definitionId=138&branchName=master)
2 | Returns what characters are produced by pressing keys with different modifiers on the current system keyboard layout.
3 |
4 | ## Installing
5 |
6 | * On Debian-based Linux: `sudo apt-get install libx11-dev libxkbfile-dev`
7 | * On Red Hat-based Linux: `sudo yum install libx11-devel.x86_64 libxkbfile-devel.x86_64 # or .i686`
8 | * On SUSE-based Linux: `sudo zypper install libX11-devel libxkbfile-devel`
9 | * On FreeBSD: `sudo pkg install libX11`
10 |
11 | ```sh
12 | npm install native-keymap
13 | ```
14 |
15 | ## Using
16 |
17 | ```javascript
18 | var keymap = require('native-keymap');
19 | console.log(keymap.getKeyMap());
20 | ```
21 |
22 | Example output when using standard US keyboard layout (on Windows):
23 | ```
24 | [
25 | ...
26 | Space: { vkey: 'VK_SPACE', value: ' ', withShift: ' ', withAltGr: '', withShiftAltGr: '' },
27 | Minus: { vkey: 'VK_OEM_MINUS', value: '-', withShift: '_', withAltGr: '', withShiftAltGr: '' },
28 | Equal: { vkey: 'VK_OEM_PLUS', value: '=', withShift: '+', withAltGr: '', withShiftAltGr: '' },
29 | BracketLeft: { vkey: 'VK_OEM_4', value: '[', withShift: '{', withAltGr: '', withShiftAltGr: '' },
30 | BracketRight: { vkey: 'VK_OEM_6', value: ']', withShift: '}', withAltGr: '', withShiftAltGr: '' },
31 | Backslash: { vkey: 'VK_OEM_5', value: '\\', withShift: '|', withAltGr: '', withShiftAltGr: '' },
32 | Semicolon: { vkey: 'VK_OEM_1', value: ';', withShift: ':', withAltGr: '', withShiftAltGr: '' },
33 | Quote: { vkey: 'VK_OEM_7', value: '\'', withShift: '"', withAltGr: '', withShiftAltGr: '' },
34 | Backquote: { vkey: 'VK_OEM_3', value: '`', withShift: '~', withAltGr: '', withShiftAltGr: '' },
35 | Comma: { vkey: 'VK_OEM_COMMA', value: ',', withShift: '<', withAltGr: '', withShiftAltGr: '' },
36 | Period: { vkey: 'VK_OEM_PERIOD', value: '.', withShift: '>', withAltGr: '', withShiftAltGr: '' },
37 | Slash: { vkey: 'VK_OEM_2', value: '/', withShift: '?', withAltGr: '', withShiftAltGr: '' },
38 | ...
39 | ]
40 | ```
41 |
42 | Example output when using German (Swiss) keyboard layout (on Windows):
43 | ```
44 | [
45 | ...
46 | Space: { vkey: 'VK_SPACE', value: ' ', withShift: ' ', withAltGr: '', withShiftAltGr: '' },
47 | Minus: { vkey: 'VK_OEM_4', value: '\'', withShift: '?', withAltGr: '´', withShiftAltGr: '' },
48 | Equal: { vkey: 'VK_OEM_6', value: '^', withShift: '`', withAltGr: '~', withShiftAltGr: '' },
49 | BracketLeft: { vkey: 'VK_OEM_1', value: 'ü', withShift: 'è', withAltGr: '[', withShiftAltGr: '' },
50 | BracketRight: { vkey: 'VK_OEM_3', value: '¨', withShift: '!', withAltGr: ']', withShiftAltGr: '' },
51 | Backslash: { vkey: 'VK_OEM_8', value: '$', withShift: '£', withAltGr: '}', withShiftAltGr: '' },
52 | Semicolon: { vkey: 'VK_OEM_7', value: 'ö', withShift: 'é', withAltGr: '', withShiftAltGr: '' },
53 | Quote: { vkey: 'VK_OEM_5', value: 'ä', withShift: 'à', withAltGr: '{', withShiftAltGr: '' },
54 | Backquote: { vkey: 'VK_OEM_2', value: '§', withShift: '°', withAltGr: '', withShiftAltGr: '' },
55 | Comma: { vkey: 'VK_OEM_COMMA', value: ',', withShift: ';', withAltGr: '', withShiftAltGr: '' },
56 | Period: { vkey: 'VK_OEM_PERIOD', value: '.', withShift: ':', withAltGr: '', withShiftAltGr: '' },
57 | Slash: { vkey: 'VK_OEM_MINUS', value: '-', withShift: '_', withAltGr: '', withShiftAltGr: '' },
58 | ...
59 | ]
60 | ```
61 |
62 | ## Supported OSes
63 | * linux (X11)
64 | * windows
65 | * mac
66 | * freebsd
67 |
68 | ## Known issues
69 | * only tested from the Electron Main process
70 |
71 | ## Developing
72 | * `npm install -g node-gyp`
73 | * `node-gyp configure` (for debugging use `node-gyp configure -d`)
74 | * `node-gyp build`
75 | * `npm test` (for debugging change `index.js` to load the node module from the `Debug` folder and press `F5`)
76 |
77 | ## License
78 | [MIT](https://github.com/Microsoft/node-native-keymap/blob/master/License.txt)
79 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ThirdPartyNotices.txt:
--------------------------------------------------------------------------------
1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
2 | Do Not Translate or Localize
3 |
4 | This project incorporates material from the project(s) listed below (collectively, “Third Party Code”).
5 | Microsoft is not the original author of the Third Party Code. The original copyright notice and license
6 | under which Microsoft received such Third Party Code are set out below. This Third Party Code is licensed
7 | to you under their original license terms set forth below. Microsoft reserves all other rights not
8 | expressly granted, whether by implication, estoppel or otherwise.
9 |
10 | The following files/folders contain third party software:
11 |
12 | =========================================================================================================
13 | deps/chromium/**
14 | ---------------------------------------------------------------------------------------------------------
15 | // Copyright 2015 The Chromium Authors. All rights reserved.
16 | //
17 | // Redistribution and use in source and binary forms, with or without
18 | // modification, are permitted provided that the following conditions are
19 | // met:
20 | //
21 | // * Redistributions of source code must retain the above copyright
22 | // notice, this list of conditions and the following disclaimer.
23 | // * Redistributions in binary form must reproduce the above
24 | // copyright notice, this list of conditions and the following disclaimer
25 | // in the documentation and/or other materials provided with the
26 | // distribution.
27 | // * Neither the name of Google Inc. nor the names of its
28 | // contributors may be used to endorse or promote products derived from
29 | // this software without specific prior written permission.
30 | //
31 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 | =========================================================================================================
43 |
--------------------------------------------------------------------------------
/binding.gyp:
--------------------------------------------------------------------------------
1 | {
2 | "targets": [
3 | {
4 | "target_name": "keymapping",
5 | "sources": [
6 | "src/string_conversion.cc",
7 | "src/keymapping.cc"
8 | ],
9 | 'msvs_configuration_attributes': {
10 | 'SpectreMitigation': 'Spectre'
11 | },
12 | 'msvs_settings': {
13 | 'VCCLCompilerTool': {
14 | 'AdditionalOptions': [
15 | '/guard:cf',
16 | '/w34244',
17 | '/we4267',
18 | '/ZH:SHA_256'
19 | ]
20 | },
21 | 'VCLinkerTool': {
22 | 'AdditionalOptions': [
23 | '/guard:cf'
24 | ]
25 | }
26 | },
27 | "conditions": [
28 | ['OS=="linux"', {
29 | "sources": [
30 | "deps/chromium/x/keysym_to_unicode.cc",
31 | "src/keyboard_x.cc"
32 | ],
33 | "include_dirs": [
34 | " // For size_t.
14 | #include // For memcpy.
15 |
16 | namespace base {
17 |
18 | // C++14 implementation of C++17's std::size():
19 | // http://en.cppreference.com/w/cpp/iterator/size
20 | template
21 | constexpr auto size(const Container& c) -> decltype(c.size()) {
22 | return c.size();
23 | }
24 |
25 | template
26 | constexpr size_t size(const T (&array)[N]) noexcept {
27 | return N;
28 | }
29 |
30 | }
31 |
32 | #endif // BASE_MACROS_H_
33 |
--------------------------------------------------------------------------------
/deps/chromium/x/keysym_to_unicode.h:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------------------------------------------------
2 | //
3 | // [13.01.2022] https://source.chromium.org/chromium/chromium/src/+/main:ui/events/keycodes/keysym_to_unicode.h
4 | //
5 | // ----------------------------------------------------------------------------------------------------------------
6 |
7 | // Copyright 2014 The Chromium Authors. All rights reserved.
8 | // Use of this source code is governed by a BSD-style license that can be
9 | // found in the LICENSE file.
10 |
11 | #ifndef UI_EVENTS_KEYCODES_KEYSYM_TO_UNICODE_H_
12 | #define UI_EVENTS_KEYCODES_KEYSYM_TO_UNICODE_H_
13 |
14 | #include
15 |
16 | namespace ui {
17 |
18 | // Returns a Unicode character corresponding to the given |keysym|. If the
19 | // |keysym| doesn't represent a printable character, returns zero. We don't
20 | // support characters outside the Basic Plane, and this function returns zero
21 | // in that case.
22 | uint16_t GetUnicodeCharacterFromXKeySym(unsigned long keysym);
23 |
24 | } // namespace ui
25 |
26 | #endif // UI_EVENTS_KEYCODES_KEYSYM_TO_UNICODE_H_
27 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export interface IWindowsKeyMapping {
7 | vkey: string;
8 | value: string;
9 | withShift: string;
10 | withAltGr: string;
11 | withShiftAltGr: string;
12 | }
13 | export interface IWindowsKeyboardMapping {
14 | [code: string]: IWindowsKeyMapping;
15 | }
16 | export interface ILinuxKeyMapping {
17 | value: string;
18 | withShift: string;
19 | withAltGr: string;
20 | withShiftAltGr: string;
21 | }
22 | export interface ILinuxKeyboardMapping {
23 | [code: string]: ILinuxKeyMapping;
24 | }
25 | export interface IMacKeyMapping {
26 | value: string;
27 | valueIsDeadKey: boolean;
28 | withShift: string;
29 | withShiftIsDeadKey: boolean;
30 | withAltGr: string;
31 | withAltGrIsDeadKey: boolean;
32 | withShiftAltGr: string;
33 | withShiftAltGrIsDeadKey: boolean;
34 | }
35 | export interface IMacKeyboardMapping {
36 | [code: string]: IMacKeyMapping;
37 | }
38 |
39 | export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping;
40 |
41 | export function getKeyMap(): IKeyboardMapping;
42 |
43 | export interface IWindowsKeyboardLayoutInfo {
44 | name: string;
45 | id: string;
46 | text: string;
47 | }
48 |
49 | export interface ILinuxKeyboardLayoutInfo {
50 | model: string;
51 | group: number;
52 | layout: string;
53 | variant: string;
54 | options: string;
55 | rules: string;
56 | }
57 |
58 | export interface IMacKeyboardLayoutInfo {
59 | id: string;
60 | localizedName: string;
61 | lang: string;
62 | }
63 |
64 | export type IKeyboardLayoutInfo = IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo;
65 |
66 | export function getCurrentKeyboardLayout(): IKeyboardLayoutInfo;
67 |
68 | export function onDidChangeKeyboardLayout(callback: () => void): void;
69 |
70 | export function isISOKeyboard(): boolean | undefined;
71 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | function NativeBinding() {
7 | this._tried = false;
8 | this._keymapping = null;
9 | }
10 | NativeBinding.prototype._init = function() {
11 | if (this._tried) {
12 | return;
13 | }
14 | this._tried = true;
15 | try {
16 | this._keymapping = require('./build/Release/keymapping');
17 | } catch (err) {
18 | // fallback to the debug build
19 | this._keymapping = require('./build/Debug/keymapping');
20 | }
21 | };
22 | NativeBinding.prototype.getKeyMap = function() {
23 | try {
24 | this._init();
25 | return this._keymapping.getKeyMap();
26 | } catch(err) {
27 | console.error(err);
28 | return [];
29 | }
30 | };
31 | NativeBinding.prototype.getCurrentKeyboardLayout = function() {
32 | try {
33 | this._init();
34 | return this._keymapping.getCurrentKeyboardLayout();
35 | } catch(err) {
36 | console.error(err);
37 | return null;
38 | }
39 | };
40 | NativeBinding.prototype.onDidChangeKeyboardLayout = function(callback) {
41 | try {
42 | this._init();
43 | this._keymapping.onDidChangeKeyboardLayout(callback);
44 | } catch(err) {
45 | console.error(err);
46 | }
47 | }
48 | NativeBinding.prototype.isISOKeyboard = function(callback) {
49 | try {
50 | this._init();
51 | return this._keymapping.isISOKeyboard();
52 | } catch(err) {
53 | return false;
54 | }
55 | }
56 |
57 | var binding = new NativeBinding();
58 |
59 | exports.getCurrentKeyboardLayout = function() {
60 | return binding.getCurrentKeyboardLayout();
61 | };
62 | exports.getKeyMap = function() {
63 | return binding.getKeyMap();
64 | };
65 | exports.onDidChangeKeyboardLayout = function(callback) {
66 | return binding.onDidChangeKeyboardLayout(callback);
67 | };
68 | exports.isISOKeyboard = function(callback) {
69 | return binding.isISOKeyboard(callback);
70 | };
71 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "native-keymap",
3 | "version": "3.3.5",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "native-keymap",
9 | "version": "3.3.5",
10 | "license": "MIT"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "native-keymap",
3 | "version": "3.3.5",
4 | "description": "Get OS key mapping",
5 | "main": "index.js",
6 | "typings": "index.d.ts",
7 | "scripts": {
8 | "test": "node test/test.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/Microsoft/node-native-keymap.git"
13 | },
14 | "author": "Microsoft Corporation",
15 | "license": "MIT",
16 | "bugs": {
17 | "url": "https://github.com/Microsoft/node-native-keymap/issues"
18 | },
19 | "homepage": "https://github.com/Microsoft/node-native-keymap#readme"
20 | }
21 |
--------------------------------------------------------------------------------
/pipeline.yml:
--------------------------------------------------------------------------------
1 | name: $(Date:yyyyMMdd)$(Rev:.r)
2 |
3 | trigger:
4 | batch: true
5 | branches:
6 | include:
7 | - main
8 | pr: none
9 |
10 | resources:
11 | repositories:
12 | - repository: templates
13 | type: github
14 | name: microsoft/vscode-engineering
15 | ref: main
16 | endpoint: Monaco
17 |
18 | parameters:
19 | - name: publishPackage
20 | displayName: 🚀 Publish native-keymap
21 | type: boolean
22 | default: false
23 |
24 | extends:
25 | template: azure-pipelines/npm-package/pipeline.yml@templates
26 | parameters:
27 | npmPackages:
28 | - name: native-keymap
29 |
30 | buildSteps:
31 | - script: npm ci
32 | displayName: Install dependencies
33 |
34 | testPlatforms:
35 | - name: Linux
36 | nodeVersions:
37 | - 20.x
38 | - name: MacOS
39 | nodeVersions:
40 | - 20.x
41 | - name: Windows
42 | nodeVersions:
43 | - 20.x
44 |
45 | testSteps:
46 | # https://github.com/nodejs/node-gyp/issues/2869
47 | - script: python3 -m pip install setuptools
48 | - script: npm ci
49 | displayName: Install dependencies
50 |
51 | - script: npm test
52 | displayName: Test
53 |
54 | apiScanSoftwareName: 'vscode-native-keymap'
55 | apiScanSoftwareVersion: '3.3'
56 |
57 | publishPackage: ${{ parameters.publishPackage }}
58 |
59 | policheckExclusionsFile: '$(Build.SourcesDirectory)/PoliCheckExclusions.xml'
60 |
--------------------------------------------------------------------------------
/src/common.h:
--------------------------------------------------------------------------------
1 | // """
2 | // Copyright Node.js contributors. All rights reserved.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to
6 | // deal in the Software without restriction, including without limitation the
7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 | // sell copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | // IN THE SOFTWARE.
21 | // """
22 |
23 | // Empty value so that macros here are able to return NULL or void
24 | #define NAPI_RETVAL_NOTHING // Intentionally blank #define
25 |
26 | #define GET_AND_THROW_LAST_ERROR(env) \
27 | do { \
28 | const napi_extended_error_info *error_info; \
29 | napi_get_last_error_info((env), &error_info); \
30 | bool is_pending; \
31 | napi_is_exception_pending((env), &is_pending); \
32 | /* If an exception is already pending, don't rethrow it */ \
33 | if (!is_pending) { \
34 | const char* error_message = error_info->error_message != NULL ? \
35 | error_info->error_message : \
36 | "empty error message"; \
37 | napi_throw_error((env), NULL, error_message); \
38 | } \
39 | } while (0)
40 |
41 | #define NAPI_ASSERT_BASE(env, assertion, message, ret_val) \
42 | do { \
43 | if (!(assertion)) { \
44 | napi_throw_error( \
45 | (env), \
46 | NULL, \
47 | "assertion (" #assertion ") failed: " message); \
48 | return ret_val; \
49 | } \
50 | } while (0)
51 |
52 | // Returns NULL on failed assertion.
53 | // This is meant to be used inside napi_callback methods.
54 | #define NAPI_ASSERT(env, assertion, message) \
55 | NAPI_ASSERT_BASE(env, assertion, message, NULL)
56 |
57 | // Returns empty on failed assertion.
58 | // This is meant to be used inside functions with void return type.
59 | #define NAPI_ASSERT_RETURN_VOID(env, assertion, message) \
60 | NAPI_ASSERT_BASE(env, assertion, message, NAPI_RETVAL_NOTHING)
61 |
62 | #define NAPI_CALL_BASE(env, the_call, ret_val) \
63 | do { \
64 | if ((the_call) != napi_ok) { \
65 | GET_AND_THROW_LAST_ERROR((env)); \
66 | return ret_val; \
67 | } \
68 | } while (0)
69 |
70 | // Returns NULL if the_call doesn't return napi_ok.
71 | #define NAPI_CALL(env, the_call) \
72 | NAPI_CALL_BASE(env, the_call, NULL)
73 |
74 | // Returns empty if the_call doesn't return napi_ok.
75 | #define NAPI_CALL_RETURN_VOID(env, the_call) \
76 | NAPI_CALL_BASE(env, the_call, NAPI_RETVAL_NOTHING)
77 |
78 | // Returns empty if the_call doesn't return napi_ok.
79 | #define NAPI_CALL_RETURN_STATUS(env, the_call) \
80 | do { \
81 | napi_status status = (the_call); \
82 | if (status != napi_ok) { \
83 | GET_AND_THROW_LAST_ERROR((env)); \
84 | return status; \
85 | } \
86 | } while (0)
87 |
88 | #define DECLARE_NAPI_PROPERTY(name, func) \
89 | { (name), NULL, (func), NULL, NULL, NULL, napi_default, NULL }
90 |
91 | #define DECLARE_NAPI_GETTER(name, func) \
92 | { (name), NULL, NULL, (func), NULL, NULL, napi_default, NULL }
93 |
--------------------------------------------------------------------------------
/src/keyboard_mac.mm:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | #import
7 | #include
8 |
9 | #include "string_conversion.h"
10 | #include "keymapping.h"
11 | #include "common.h"
12 | #include "../deps/chromium/macros.h"
13 |
14 | namespace {
15 |
16 | std::pair ConvertKeyCodeToText(const UCKeyboardLayout* keyboard_layout, int mac_key_code, int modifiers) {
17 |
18 | int mac_modifiers = 0;
19 | if (modifiers & kShiftKeyModifierMask)
20 | mac_modifiers |= shiftKey;
21 | if (modifiers & kControlKeyModifierMask)
22 | mac_modifiers |= controlKey;
23 | if (modifiers & kAltKeyModifierMask)
24 | mac_modifiers |= optionKey;
25 | if (modifiers & kMetaKeyModifierMask)
26 | mac_modifiers |= cmdKey;
27 |
28 | // Convert EventRecord modifiers to format UCKeyTranslate accepts. See docs
29 | // on UCKeyTranslate for more info.
30 | UInt32 modifier_key_state = (mac_modifiers >> 8) & 0xFF;
31 |
32 | UInt32 dead_key_state = 0;
33 | UniCharCount char_count = 0;
34 | UniChar character = 0;
35 | OSStatus status = UCKeyTranslate(
36 | keyboard_layout,
37 | static_cast(mac_key_code),
38 | kUCKeyActionDown,
39 | modifier_key_state,
40 | LMGetKbdLast(),
41 | kUCKeyTranslateNoDeadKeysBit,
42 | &dead_key_state,
43 | 1,
44 | &char_count,
45 | &character);
46 |
47 | bool is_dead_key = false;
48 | if (status == noErr && char_count == 0 && dead_key_state != 0) {
49 | is_dead_key = true;
50 | status = UCKeyTranslate(
51 | keyboard_layout,
52 | static_cast(mac_key_code),
53 | kUCKeyActionDown,
54 | modifier_key_state,
55 | LMGetKbdLast(),
56 | kUCKeyTranslateNoDeadKeysBit,
57 | &dead_key_state,
58 | 1,
59 | &char_count,
60 | &character);
61 | }
62 |
63 | if (status == noErr && char_count == 1 && !std::iscntrl(character)) {
64 | wchar_t value = character;
65 | return std::make_pair(is_dead_key, vscode_keyboard::UTF16toUTF8(&value, 1));
66 | }
67 | return std::make_pair(false, std::string());
68 | }
69 |
70 | } // namespace
71 |
72 | namespace vscode_keyboard {
73 |
74 |
75 | #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) {usb, mac, code}
76 | #define DOM_CODE_DECLARATION const KeycodeMapEntry usb_keycode_map[] =
77 | #include "../deps/chromium/dom_code_data.inc"
78 | #undef DOM_CODE
79 | #undef DOM_CODE_DECLARATION
80 |
81 | napi_value GetKeyMapImpl(napi_env env, napi_callback_info info) {
82 |
83 | napi_value result;
84 | NAPI_CALL(env, napi_create_object(env, &result));
85 |
86 | TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();
87 | CFDataRef layout_data = static_cast((TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)));
88 | if (!layout_data) {
89 | // TISGetInputSourceProperty returns null with Japanese keyboard layout.
90 | // Using TISCopyCurrentKeyboardLayoutInputSource to fix NULL return.
91 | source = TISCopyCurrentKeyboardLayoutInputSource();
92 | layout_data = static_cast((TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)));
93 | if (!layout_data) {
94 | // https://developer.apple.com/library/mac/documentation/TextFonts/Reference/TextInputSourcesReference/#//apple_ref/c/func/TISGetInputSourceProperty
95 | return result;
96 | }
97 | }
98 |
99 | const UCKeyboardLayout* keyboard_layout = reinterpret_cast(CFDataGetBytePtr(layout_data));
100 |
101 | size_t cnt = sizeof(usb_keycode_map) / sizeof(usb_keycode_map[0]);
102 |
103 | napi_value true_value;
104 | NAPI_CALL(env, napi_get_boolean(env, true, &true_value));
105 |
106 | napi_value false_value;
107 | NAPI_CALL(env, napi_get_boolean(env, false, &false_value));
108 |
109 | for (size_t i = 0; i < cnt; ++i) {
110 | const char *code = usb_keycode_map[i].code;
111 | int native_keycode = usb_keycode_map[i].native_keycode;
112 |
113 | if (!code || native_keycode >= 0xffff) {
114 | continue;
115 | }
116 |
117 | napi_value entry;
118 | NAPI_CALL(env, napi_create_object(env, &entry));
119 |
120 | {
121 | std::pair value = ConvertKeyCodeToText(keyboard_layout, native_keycode, 0);
122 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "value", value.second.c_str()));
123 | NAPI_CALL(env, napi_set_named_property(env, entry, "valueIsDeadKey", value.first ? true_value : false_value));
124 | }
125 |
126 | {
127 | std::pair with_shift = ConvertKeyCodeToText(keyboard_layout, native_keycode, kShiftKeyModifierMask);
128 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withShift", with_shift.second.c_str()));
129 | NAPI_CALL(env, napi_set_named_property(env, entry, "withShiftIsDeadKey", with_shift.first ? true_value : false_value));
130 | }
131 |
132 | {
133 | std::pair with_alt_gr = ConvertKeyCodeToText(keyboard_layout, native_keycode, kAltKeyModifierMask);
134 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withAltGr", with_alt_gr.second.c_str()));
135 | NAPI_CALL(env, napi_set_named_property(env, entry, "withAltGrIsDeadKey", with_alt_gr.first ? true_value : false_value));
136 | }
137 |
138 | {
139 | std::pair with_shift_alt_gr = ConvertKeyCodeToText(keyboard_layout, native_keycode, kShiftKeyModifierMask | kAltKeyModifierMask);
140 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withShiftAltGr", with_shift_alt_gr.second.c_str()));
141 | NAPI_CALL(env, napi_set_named_property(env, entry, "withShiftAltGrIsDeadKey", with_shift_alt_gr.first ? true_value : false_value));
142 | }
143 |
144 | NAPI_CALL(env, napi_set_named_property(env, result, code, entry));
145 | }
146 | return result;
147 | }
148 |
149 | napi_value GetCurrentKeyboardLayoutImpl(napi_env env, napi_callback_info info) {
150 |
151 | napi_value result;
152 | NAPI_CALL(env, napi_create_object(env, &result));
153 |
154 | TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();
155 | CFStringRef source_id = (CFStringRef) TISGetInputSourceProperty(source, kTISPropertyInputSourceID);
156 | if(source_id) {
157 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "id", std::string([(NSString *)source_id UTF8String]).c_str()));
158 | }
159 |
160 | TISInputSourceRef name_source = TISCopyCurrentKeyboardInputSource();
161 | CFStringRef localized_name = (CFStringRef) TISGetInputSourceProperty(name_source, kTISPropertyLocalizedName);
162 | if(localized_name) {
163 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "localizedName", std::string([(NSString *)localized_name UTF8String]).c_str()));
164 | }
165 |
166 | NSArray* languages = (NSArray *) TISGetInputSourceProperty(source, kTISPropertyInputSourceLanguages);
167 | if (languages && [languages count] > 0) {
168 | NSString* lang = [languages objectAtIndex:0];
169 | if (lang) {
170 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "lang", std::string([lang UTF8String]).c_str()));
171 | }
172 | }
173 |
174 | return result;
175 | }
176 |
177 | void NotificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
178 | NotificationCallbackData *data = (NotificationCallbackData *)observer;
179 | InvokeNotificationCallback(data);
180 | }
181 |
182 | void RegisterKeyboardLayoutChangeListenerImpl(NotificationCallbackData *data) {
183 | CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter();
184 |
185 | // add an observer
186 | CFNotificationCenterAddObserver(center, data, NotificationCallback,
187 | kTISNotifySelectedKeyboardInputSourceChanged, NULL,
188 | CFNotificationSuspensionBehaviorDeliverImmediately
189 | );
190 | }
191 |
192 | void DisposeKeyboardLayoutChangeListenerImpl(NotificationCallbackData *data) {
193 | CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter();
194 |
195 | // remove the observer
196 | CFNotificationCenterRemoveObserver(center, data,
197 | kTISNotifySelectedKeyboardInputSourceChanged, NULL
198 | );
199 | }
200 |
201 | napi_value IsISOKeyboardImpl(napi_env env, napi_callback_info info) {
202 | if (KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) {
203 | return napi_fetch_boolean(env, true);
204 | } else {
205 | return napi_fetch_boolean(env, false);
206 | }
207 | }
208 |
209 | } // namespace vscode_keyboard
210 |
--------------------------------------------------------------------------------
/src/keyboard_win.cc:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | #include "keymapping.h"
7 | #include "common.h"
8 |
9 | #include "../deps/chromium/macros.h"
10 |
11 | #include "string_conversion.h"
12 | #include
13 | #include
14 | #include
15 |
16 | namespace {
17 |
18 | void ClearKeyboardBuffer(UINT key_code, UINT scan_code, BYTE* keyboard_state) {
19 | memset(keyboard_state, 0, 256);
20 |
21 | wchar_t chars[5];
22 | int code = 0;
23 | do {
24 | code = ::ToUnicode(key_code, scan_code, keyboard_state, chars, 4, 0);
25 | } while (code < 0);
26 | }
27 |
28 | std::string GetStrFromKeyPress(UINT key_code, int modifiers, BYTE *keyboard_state, UINT clear_key_code, UINT clear_scan_code) {
29 | memset(keyboard_state, 0, 256);
30 |
31 | if (modifiers & kShiftKeyModifierMask) {
32 | keyboard_state[VK_SHIFT] |= 0x80;
33 | }
34 |
35 | if (modifiers & kControlKeyModifierMask) {
36 | keyboard_state[VK_CONTROL] |= 0x80;
37 | }
38 |
39 | if (modifiers & kAltKeyModifierMask) {
40 | keyboard_state[VK_MENU] |= 0x80;
41 | }
42 |
43 | UINT scan_code = ::MapVirtualKeyW(key_code, MAPVK_VK_TO_VSC);
44 |
45 | wchar_t chars[5];
46 | int code = ::ToUnicode(key_code, scan_code, keyboard_state, chars, 4, 0);
47 |
48 | if (code == -1) {
49 | // dead key
50 | if (chars[0] == 0 || iswcntrl(chars[0])) {
51 | return std::string();
52 | }
53 | code = 1;
54 | }
55 |
56 | ClearKeyboardBuffer(clear_key_code, clear_scan_code, keyboard_state);
57 |
58 | if (code <= 0 || (code == 1 && iswcntrl(chars[0]))) {
59 | return std::string();
60 | }
61 |
62 | return vscode_keyboard::UTF16toUTF8(chars, code);
63 | }
64 |
65 | } // namespace
66 |
67 | namespace vscode_keyboard {
68 |
69 | #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) {usb, win, code}
70 | #define DOM_CODE_DECLARATION const KeycodeMapEntry usb_keycode_map[] =
71 | #include "../deps/chromium/dom_code_data.inc"
72 | #undef DOM_CODE
73 | #undef DOM_CODE_DECLARATION
74 |
75 | typedef struct {
76 | int vkey;
77 | const char* str_vkey;
78 | } VKeyStrEntry;
79 |
80 | const char* VKeyToStr(int vkey) {
81 | switch (vkey) {
82 | case VK_LBUTTON: return "VK_LBUTTON"; // Left mouse button
83 | case VK_RBUTTON: return "VK_RBUTTON"; // Right mouse button
84 | case VK_CANCEL: return "VK_CANCEL"; // Control-break processing
85 | case VK_MBUTTON: return "VK_MBUTTON"; // Middle mouse button (three-button mouse)
86 | case VK_XBUTTON1: return "VK_XBUTTON1"; // X1 mouse button
87 | case VK_XBUTTON2: return "VK_XBUTTON2"; // X2 mouse button
88 | case VK_BACK: return "VK_BACK"; // BACKSPACE key
89 | case VK_TAB: return "VK_TAB"; // TAB key
90 | case VK_CLEAR: return "VK_CLEAR"; // CLEAR key
91 | case VK_RETURN: return "VK_RETURN"; // ENTER key
92 | case VK_SHIFT: return "VK_SHIFT"; // SHIFT key
93 | case VK_CONTROL: return "VK_CONTROL"; // CTRL key
94 | case VK_MENU: return "VK_MENU"; // ALT key
95 | case VK_PAUSE: return "VK_PAUSE"; // PAUSE key
96 | case VK_CAPITAL: return "VK_CAPITAL"; // CAPS LOCK key
97 | case VK_KANA: return "VK_KANA"; // IME Kana mode
98 | //case VK_HANGUL: return "VK_HANGUEL"; // IME Hangul mode
99 | case VK_JUNJA: return "VK_JUNJA"; // IME Junja mode
100 | case VK_FINAL: return "VK_FINAL"; // IME final mode
101 | case VK_HANJA: return "VK_HANJA"; // IME Hanja mode
102 | //case VK_KANJI: return "VK_KANJI"; // IME Kanji mode
103 | case VK_ESCAPE: return "VK_ESCAPE"; // ESC key
104 | case VK_CONVERT: return "VK_CONVERT"; // IME convert
105 | case VK_NONCONVERT: return "VK_NONCONVERT"; // IME nonconvert
106 | case VK_ACCEPT: return "VK_ACCEPT"; // IME accept
107 | case VK_MODECHANGE: return "VK_MODECHANGE"; // IME mode change request
108 | case VK_SPACE: return "VK_SPACE"; // SPACEBAR
109 | case VK_PRIOR: return "VK_PRIOR"; // PAGE UP key
110 | case VK_NEXT: return "VK_NEXT"; // PAGE DOWN key
111 | case VK_END: return "VK_END"; // END key
112 | case VK_HOME: return "VK_HOME"; // HOME key
113 | case VK_LEFT: return "VK_LEFT"; // LEFT ARROW key
114 | case VK_UP: return "VK_UP"; // UP ARROW key
115 | case VK_RIGHT: return "VK_RIGHT"; // RIGHT ARROW key
116 | case VK_DOWN: return "VK_DOWN"; // DOWN ARROW key
117 | case VK_SELECT: return "VK_SELECT"; // SELECT key
118 | case VK_PRINT: return "VK_PRINT"; // PRINT key
119 | case VK_EXECUTE: return "VK_EXECUTE"; // EXECUTE key
120 | case VK_SNAPSHOT: return "VK_SNAPSHOT"; // PRINT SCREEN key
121 | case VK_INSERT: return "VK_INSERT"; // INS key
122 | case VK_DELETE: return "VK_DELETE"; // DEL key
123 | case VK_HELP: return "VK_HELP"; // HELP key
124 |
125 | case '0': return "VK_0";
126 | case '1': return "VK_1";
127 | case '2': return "VK_2";
128 | case '3': return "VK_3";
129 | case '4': return "VK_4";
130 | case '5': return "VK_5";
131 | case '6': return "VK_6";
132 | case '7': return "VK_7";
133 | case '8': return "VK_8";
134 | case '9': return "VK_9";
135 | case 'A': return "VK_A";
136 | case 'B': return "VK_B";
137 | case 'C': return "VK_C";
138 | case 'D': return "VK_D";
139 | case 'E': return "VK_E";
140 | case 'F': return "VK_F";
141 | case 'G': return "VK_G";
142 | case 'H': return "VK_H";
143 | case 'I': return "VK_I";
144 | case 'J': return "VK_J";
145 | case 'K': return "VK_K";
146 | case 'L': return "VK_L";
147 | case 'M': return "VK_M";
148 | case 'N': return "VK_N";
149 | case 'O': return "VK_O";
150 | case 'P': return "VK_P";
151 | case 'Q': return "VK_Q";
152 | case 'R': return "VK_R";
153 | case 'S': return "VK_S";
154 | case 'T': return "VK_T";
155 | case 'U': return "VK_U";
156 | case 'V': return "VK_V";
157 | case 'W': return "VK_W";
158 | case 'X': return "VK_X";
159 | case 'Y': return "VK_Y";
160 | case 'Z': return "VK_Z";
161 |
162 | case VK_LWIN: return "VK_LWIN"; // Left Windows key (Natural keyboard)
163 | case VK_RWIN: return "VK_RWIN"; // Right Windows key (Natural keyboard)
164 | case VK_APPS: return "VK_APPS"; // Applications key (Natural keyboard)
165 | case VK_SLEEP: return "VK_SLEEP"; // Computer Sleep key
166 | case VK_NUMPAD0: return "VK_NUMPAD0"; // Numeric keypad 0 key
167 | case VK_NUMPAD1: return "VK_NUMPAD1"; // Numeric keypad 1 key
168 | case VK_NUMPAD2: return "VK_NUMPAD2"; // Numeric keypad 2 key
169 | case VK_NUMPAD3: return "VK_NUMPAD3"; // Numeric keypad 3 key
170 | case VK_NUMPAD4: return "VK_NUMPAD4"; // Numeric keypad 4 key
171 | case VK_NUMPAD5: return "VK_NUMPAD5"; // Numeric keypad 5 key
172 | case VK_NUMPAD6: return "VK_NUMPAD6"; // Numeric keypad 6 key
173 | case VK_NUMPAD7: return "VK_NUMPAD7"; // Numeric keypad 7 key
174 | case VK_NUMPAD8: return "VK_NUMPAD8"; // Numeric keypad 8 key
175 | case VK_NUMPAD9: return "VK_NUMPAD9"; // Numeric keypad 9 key
176 | case VK_MULTIPLY: return "VK_MULTIPLY"; // Multiply key
177 | case VK_ADD: return "VK_ADD"; // Add key
178 | case VK_SEPARATOR: return "VK_SEPARATOR"; // Separator key
179 | case VK_SUBTRACT: return "VK_SUBTRACT"; // Subtract key
180 | case VK_DECIMAL: return "VK_DECIMAL"; // Decimal key
181 | case VK_DIVIDE: return "VK_DIVIDE"; // Divide key
182 | case VK_F1: return "VK_F1"; // F1 key
183 | case VK_F2: return "VK_F2"; // F2 key
184 | case VK_F3: return "VK_F3"; // F3 key
185 | case VK_F4: return "VK_F4"; // F4 key
186 | case VK_F5: return "VK_F5"; // F5 key
187 | case VK_F6: return "VK_F6"; // F6 key
188 | case VK_F7: return "VK_F7"; // F7 key
189 | case VK_F8: return "VK_F8"; // F8 key
190 | case VK_F9: return "VK_F9"; // F9 key
191 | case VK_F10: return "VK_F10"; // F10 key
192 | case VK_F11: return "VK_F11"; // F11 key
193 | case VK_F12: return "VK_F12"; // F12 key
194 | case VK_F13: return "VK_F13"; // F13 key
195 | case VK_F14: return "VK_F14"; // F14 key
196 | case VK_F15: return "VK_F15"; // F15 key
197 | case VK_F16: return "VK_F16"; // F16 key
198 | case VK_F17: return "VK_F17"; // F17 key
199 | case VK_F18: return "VK_F18"; // F18 key
200 | case VK_F19: return "VK_F19"; // F19 key
201 | case VK_F20: return "VK_F20"; // F20 key
202 | case VK_F21: return "VK_F21"; // F21 key
203 | case VK_F22: return "VK_F22"; // F22 key
204 | case VK_F23: return "VK_F23"; // F23 key
205 | case VK_F24: return "VK_F24"; // F24 key
206 | case VK_NUMLOCK: return "VK_NUMLOCK"; // NUM LOCK key
207 | case VK_SCROLL: return "VK_SCROLL"; // SCROLL LOCK key
208 | case VK_LSHIFT: return "VK_LSHIFT"; // Left SHIFT key
209 | case VK_RSHIFT: return "VK_RSHIFT"; // Right SHIFT key
210 | case VK_LCONTROL: return "VK_LCONTROL"; // Left CONTROL key
211 | case VK_RCONTROL: return "VK_RCONTROL"; // Right CONTROL key
212 | case VK_LMENU: return "VK_LMENU"; // Left MENU key
213 | case VK_RMENU: return "VK_RMENU"; // Right MENU key
214 | case VK_BROWSER_BACK: return "VK_BROWSER_BACK"; // Browser Back key
215 | case VK_BROWSER_FORWARD: return "VK_BROWSER_FORWARD"; // Browser Forward key
216 | case VK_BROWSER_REFRESH: return "VK_BROWSER_REFRESH"; // Browser Refresh key
217 | case VK_BROWSER_STOP: return "VK_BROWSER_STOP"; // Browser Stop key
218 | case VK_BROWSER_SEARCH: return "VK_BROWSER_SEARCH"; // Browser Search key
219 | case VK_BROWSER_FAVORITES: return "VK_BROWSER_FAVORITES"; // Browser Favorites key
220 | case VK_BROWSER_HOME: return "VK_BROWSER_HOME"; // Browser Start and Home key
221 | case VK_VOLUME_MUTE: return "VK_VOLUME_MUTE"; // Volume Mute key
222 | case VK_VOLUME_DOWN: return "VK_VOLUME_DOWN"; // Volume Down key
223 | case VK_VOLUME_UP: return "VK_VOLUME_UP"; // Volume Up key
224 | case VK_MEDIA_NEXT_TRACK: return "VK_MEDIA_NEXT_TRACK"; // Next Track key
225 | case VK_MEDIA_PREV_TRACK: return "VK_MEDIA_PREV_TRACK"; // Previous Track key
226 | case VK_MEDIA_STOP: return "VK_MEDIA_STOP"; // Stop Media key
227 | case VK_MEDIA_PLAY_PAUSE: return "VK_MEDIA_PLAY_PAUSE"; // Play/Pause Media key
228 | case VK_LAUNCH_MAIL: return "VK_LAUNCH_MAIL"; // Start Mail key
229 | case VK_LAUNCH_MEDIA_SELECT: return "VK_LAUNCH_MEDIA_SELECT"; // Select Media key
230 | case VK_LAUNCH_APP1: return "VK_LAUNCH_APP1"; // Start Application 1 key
231 | case VK_LAUNCH_APP2: return "VK_LAUNCH_APP2"; // Start Application 2 key
232 | case VK_OEM_1: return "VK_OEM_1"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
233 | case VK_OEM_PLUS: return "VK_OEM_PLUS"; // For any country/region, the '+' key
234 | case VK_OEM_COMMA: return "VK_OEM_COMMA"; // For any country/region, the ',' key
235 | case VK_OEM_MINUS: return "VK_OEM_MINUS"; // For any country/region, the '-' key
236 | case VK_OEM_PERIOD: return "VK_OEM_PERIOD"; // For any country/region, the '.' key
237 | case VK_OEM_2: return "VK_OEM_2"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
238 | case VK_OEM_3: return "VK_OEM_3"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key
239 | case 0xC1: return "VK_ABNT_C1"; // Brazilian (ABNT) Keyboard
240 | case 0xC2: return "VK_ABNT_C2"; // Brazilian (ABNT) Keyboard
241 | case VK_OEM_4: return "VK_OEM_4"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
242 | case VK_OEM_5: return "VK_OEM_5"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
243 | case VK_OEM_6: return "VK_OEM_6"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
244 | case VK_OEM_7: return "VK_OEM_7"; // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
245 | case VK_OEM_8: return "VK_OEM_8"; // Used for miscellaneous characters; it can vary by keyboard.
246 | case VK_OEM_102: return "VK_OEM_102"; // Either the angle bracket key or the backslash key on the RT 102-key keyboard
247 | case VK_PROCESSKEY: return "VK_PROCESSKEY"; // IME PROCESS key
248 | case VK_PACKET: return "VK_PACKET"; // Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP // 0xE8
249 | case VK_ATTN: return "VK_ATTN"; // Attn key
250 | case VK_CRSEL: return "VK_CRSEL"; // CrSel key
251 | case VK_EXSEL: return "VK_EXSEL"; // ExSel key
252 | case VK_EREOF: return "VK_EREOF"; // Erase EOF key
253 | case VK_PLAY: return "VK_PLAY"; // Play key
254 | case VK_ZOOM: return "VK_ZOOM"; // Zoom key
255 | case VK_NONAME: return "VK_NONAME"; // Reserved
256 | case VK_PA1: return "VK_PA1"; // PA1 key
257 | case VK_OEM_CLEAR: return "VK_OEM_CLEAR"; // Clear key
258 | }
259 |
260 | return "VK_UNKNOWN";
261 | }
262 |
263 | class UseForegroundKeyboardLayoutScope {
264 | public:
265 | UseForegroundKeyboardLayoutScope() : original_layout_(GetKeyboardLayout(0)) {
266 | if (auto window = GetForegroundWindow()) {
267 | const auto thread_id = GetWindowThreadProcessId(window, nullptr);
268 | ActivateKeyboardLayout(GetKeyboardLayout(thread_id), 0);
269 | }
270 | }
271 |
272 | ~UseForegroundKeyboardLayoutScope() {
273 | ActivateKeyboardLayout(original_layout_, 0);
274 | }
275 |
276 | UseForegroundKeyboardLayoutScope(const UseForegroundKeyboardLayoutScope&) = delete;
277 | UseForegroundKeyboardLayoutScope& operator=(const UseForegroundKeyboardLayoutScope&) = delete;
278 |
279 | private:
280 | HKL original_layout_ = nullptr;
281 | };
282 |
283 | napi_value GetKeyMapImpl(napi_env env, napi_callback_info info) {
284 | UseForegroundKeyboardLayoutScope use_foreground_keyboard_layout;
285 |
286 | napi_value result;
287 | NAPI_CALL(env, napi_create_object(env, &result));
288 |
289 | UINT clear_key_code = VK_DECIMAL;
290 | UINT clear_scan_code = ::MapVirtualKeyW(clear_key_code, MAPVK_VK_TO_VSC);
291 | BYTE keyboard_state[256];
292 |
293 | size_t cnt = sizeof(usb_keycode_map) / sizeof(usb_keycode_map[0]);
294 | for (size_t i = 0; i < cnt; ++i) {
295 | const char *code = usb_keycode_map[i].code;
296 | int native_scancode = usb_keycode_map[i].native_keycode;
297 |
298 | if (!code || native_scancode <= 0) {
299 | continue;
300 | }
301 |
302 | int native_keycode = ::MapVirtualKeyW(native_scancode, MAPVK_VSC_TO_VK);
303 |
304 | napi_value entry;
305 | NAPI_CALL(env, napi_create_object(env, &entry));
306 |
307 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "vkey", VKeyToStr(native_keycode)));
308 |
309 | std::string value = GetStrFromKeyPress(native_keycode, 0, keyboard_state, clear_key_code, clear_scan_code);
310 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "value", value.c_str()));
311 |
312 | std::string with_shift = GetStrFromKeyPress(native_keycode, kShiftKeyModifierMask, keyboard_state, clear_key_code, clear_scan_code);
313 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withShift", with_shift.c_str()));
314 |
315 | std::string with_alt_gr = GetStrFromKeyPress(native_keycode, kControlKeyModifierMask | kAltKeyModifierMask, keyboard_state, clear_key_code, clear_scan_code);
316 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withAltGr", with_alt_gr.c_str()));
317 |
318 | std::string with_shift_alt_gr = GetStrFromKeyPress(native_keycode, kShiftKeyModifierMask | kControlKeyModifierMask | kAltKeyModifierMask, keyboard_state, clear_key_code, clear_scan_code);
319 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withShiftAltGr", with_shift_alt_gr.c_str()));
320 |
321 | NAPI_CALL(env, napi_set_named_property(env, result, code, entry));
322 | }
323 | return result;
324 | }
325 |
326 | std::string GetStringRegKey(std::string path, std::string name) {
327 | std::string result = "";
328 |
329 | HKEY hKey;
330 | if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, path.c_str(), 0, KEY_READ, &hKey)) {
331 | return result;
332 | }
333 |
334 | char szBuffer[512];
335 | DWORD dwBufferSize = sizeof(szBuffer);
336 |
337 | if (ERROR_SUCCESS == RegQueryValueEx(hKey, name.c_str(), 0, NULL, (LPBYTE)szBuffer, &dwBufferSize)) {
338 | result = szBuffer;
339 | }
340 |
341 | RegCloseKey(hKey);
342 |
343 | return result;
344 | }
345 |
346 | napi_value GetCurrentKeyboardLayoutImpl(napi_env env, napi_callback_info info) {
347 | UseForegroundKeyboardLayoutScope use_foreground_keyboard_layout;
348 |
349 | char chr_layout_name[KL_NAMELENGTH];
350 | if (!GetKeyboardLayoutName(chr_layout_name)) {
351 | return napi_fetch_null(env);
352 | }
353 | std::string layout_name = chr_layout_name;
354 |
355 | // https://docs.microsoft.com/windows-hardware/manufacture/desktop/windows-language-pack-default-values
356 | std::string layout_id = GetStringRegKey("System\\CurrentControlSet\\Control\\Keyboard Layouts\\" + layout_name, "Layout Id");
357 | std::string layout_text = GetStringRegKey("System\\CurrentControlSet\\Control\\Keyboard Layouts\\" + layout_name, "Layout Text");
358 |
359 | napi_value result;
360 | NAPI_CALL(env, napi_create_object(env, &result));
361 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "name", layout_name.c_str()));
362 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "id", layout_id.c_str()));
363 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "text", layout_text.c_str()));
364 | return result;
365 | }
366 |
367 | class TfInputListener : public ITfInputProcessorProfileActivationSink {
368 | private:
369 | NotificationCallbackData *data_;
370 | ULONG ref_count_;
371 | ITfSource *source_;
372 | DWORD cookie_;
373 |
374 | public:
375 | explicit TfInputListener(NotificationCallbackData *data) {
376 | data_ = data;
377 | ref_count_ = 1;
378 | source_ = NULL;
379 | cookie_ = TF_INVALID_COOKIE;
380 | }
381 |
382 | void StartListening() {
383 | HRESULT hr;
384 |
385 | ITfThreadMgr* thread_mgr;
386 | hr = CoCreateInstance(CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, (void**)&thread_mgr);
387 | if (!SUCCEEDED(hr)) {
388 | printf("native-keymap: Could not create ITfThreadMgr.\n");
389 | return;
390 | }
391 |
392 | hr = thread_mgr->QueryInterface(IID_ITfSource, (LPVOID*)&source_);
393 | if (!SUCCEEDED(hr)) {
394 | printf("native-keymap: Could not obtain ITfSource.\n");
395 | thread_mgr->Release();
396 | return;
397 | }
398 |
399 | hr = source_->AdviseSink(IID_ITfInputProcessorProfileActivationSink,
400 | static_cast(this),
401 | &cookie_);
402 |
403 | if (!SUCCEEDED(hr)) {
404 | printf("native-keymap: Could not register ITfInputProcessorProfileActivationSink.\n");
405 | }
406 | thread_mgr->Release();
407 | }
408 |
409 | void StopListening() {
410 | if (source_ != NULL) {
411 | if (cookie_ != TF_INVALID_COOKIE) {
412 | source_->UnadviseSink(cookie_);
413 | cookie_ = TF_INVALID_COOKIE;
414 | }
415 | source_->Release();
416 | source_ = NULL;
417 | }
418 | }
419 |
420 | virtual ~TfInputListener() {
421 | this->StopListening();
422 | }
423 |
424 | virtual HRESULT STDMETHODCALLTYPE OnActivated(
425 | /* [in] */ DWORD dwProfileType,
426 | /* [in] */ LANGID langid,
427 | /* [in] */ __RPC__in REFCLSID clsid,
428 | /* [in] */ __RPC__in REFGUID catid,
429 | /* [in] */ __RPC__in REFGUID guidProfile,
430 | /* [in] */ HKL hkl,
431 | /* [in] */ DWORD dwFlags) override {
432 |
433 | InvokeNotificationCallback(data_);
434 | return S_OK;
435 | }
436 |
437 | // IUnknown methods
438 | ULONG STDMETHODCALLTYPE AddRef() override {
439 | return InterlockedIncrement(&ref_count_);
440 | }
441 |
442 | ULONG STDMETHODCALLTYPE Release() override {
443 | ULONG newCount = InterlockedDecrement(&ref_count_);
444 | if (0 == newCount) {
445 | delete this;
446 | }
447 | return newCount;
448 | }
449 |
450 | virtual HRESULT STDMETHODCALLTYPE QueryInterface(IID const& riid, void** ppvObject) override {
451 | if (__uuidof(IUnknown) == riid || __uuidof(ITfInputProcessorProfileActivationSink) == riid) {
452 | *ppvObject = this;
453 | this->AddRef();
454 | return S_OK;
455 | }
456 | *ppvObject = nullptr;
457 | return E_FAIL;
458 | }
459 | };
460 |
461 | void RegisterKeyboardLayoutChangeListenerImpl(NotificationCallbackData *data) {
462 | TfInputListener* listener = new TfInputListener(data);
463 | listener->StartListening();
464 | data->listener = listener;
465 | }
466 |
467 | void DisposeKeyboardLayoutChangeListenerImpl(NotificationCallbackData *data) {
468 | TfInputListener* listener = static_cast(data->listener);
469 | listener->Release();
470 | }
471 |
472 | napi_value IsISOKeyboardImpl(napi_env env, napi_callback_info info) {
473 | return napi_fetch_undefined(env);
474 | }
475 |
476 | } // namespace vscode_keyboard
477 |
--------------------------------------------------------------------------------
/src/keyboard_x.cc:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | #include "keymapping.h"
7 | #include "string_conversion.h"
8 | #include "common.h"
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "../deps/chromium/macros.h"
16 | #include "../deps/chromium/x/keysym_to_unicode.h"
17 |
18 | typedef struct _XDisplay XDisplay;
19 |
20 | namespace {
21 |
22 | class KeyModifierMaskToXModifierMask {
23 | public:
24 | static KeyModifierMaskToXModifierMask& GetInstance() {
25 | static KeyModifierMaskToXModifierMask instance;
26 | return instance;
27 | }
28 |
29 | void Initialize(Display* display) {
30 | alt_modifier_ = 0;
31 | meta_modifier_ = 0;
32 | num_lock_modifier_ = 0;
33 | mode_switch_modifier_ = 0;
34 | level3_modifier_ = 0; // AltGr is often mapped to the level3 modifier
35 | level5_modifier_ = 0; // AltGr is mapped to the level5 modifier in the Neo layout family
36 | effective_group_index_ = 0;
37 |
38 | if (!display) {
39 | return;
40 | }
41 |
42 | // See https://www.x.org/releases/X11R7.6/doc/libX11/specs/XKB/xkblib.html#determining_keyboard_state
43 | XkbStateRec xkb_state;
44 | XkbGetState(display, XkbUseCoreKbd, &xkb_state);
45 | effective_group_index_ = xkb_state.group;
46 |
47 | XModifierKeymap* mod_map = XGetModifierMapping(display);
48 | int max_mod_keys = mod_map->max_keypermod;
49 | for (int mod_index = 0; mod_index <= 8; ++mod_index) {
50 | for (int key_index = 0; key_index < max_mod_keys; ++key_index) {
51 | int key = mod_map->modifiermap[mod_index * max_mod_keys + key_index];
52 | if (!key) {
53 | continue;
54 | }
55 |
56 | int keysym = XkbKeycodeToKeysym(display, key, 0, 0);
57 | if (!keysym) {
58 | continue;
59 | }
60 |
61 | if (keysym == XK_Alt_L || keysym == XK_Alt_R) {
62 | alt_modifier_ = 1 << mod_index;
63 | }
64 | if (keysym == XK_Mode_switch) {
65 | mode_switch_modifier_ = 1 << mod_index;
66 | }
67 | if (keysym == XK_Meta_L || keysym == XK_Super_L || keysym == XK_Meta_R || keysym == XK_Super_R) {
68 | meta_modifier_ = 1 << mod_index;
69 | }
70 | if (keysym == XK_Num_Lock) {
71 | num_lock_modifier_ = 1 << mod_index;
72 | }
73 | if (keysym == XK_ISO_Level3_Shift) {
74 | level3_modifier_ = 1 << mod_index;
75 | }
76 | if (keysym == XK_ISO_Level5_Shift) {
77 | level5_modifier_ = 1 << mod_index;
78 | }
79 | }
80 | }
81 |
82 | XFreeModifiermap(mod_map);
83 | }
84 |
85 | int XStateFromKeyMod(int keyMod) {
86 | int x_modifier = 0;
87 |
88 | // Ctrl + Alt => AltGr
89 | if (keyMod & kControlKeyModifierMask && keyMod & kAltKeyModifierMask) {
90 | x_modifier |= mode_switch_modifier_;//alt_r_modifier;
91 | } else if (keyMod & kControlKeyModifierMask) {
92 | x_modifier |= ControlMask;
93 | } else if (keyMod & kAltKeyModifierMask) {
94 | x_modifier |= alt_modifier_;
95 | }
96 |
97 | if (keyMod & kShiftKeyModifierMask) {
98 | x_modifier |= ShiftMask;
99 | }
100 |
101 | if (keyMod & kMetaKeyModifierMask) {
102 | x_modifier |= meta_modifier_;
103 | }
104 |
105 | if (keyMod & kNumLockKeyModifierMask) {
106 | x_modifier |= num_lock_modifier_;
107 | }
108 |
109 | if (keyMod & kLevel3KeyModifierMask) {
110 | x_modifier |= level3_modifier_;
111 | }
112 |
113 | if (keyMod & kLevel5KeyModifierMask) {
114 | x_modifier |= level5_modifier_;
115 | }
116 |
117 | // See https://www.x.org/releases/X11R7.6/doc/libX11/specs/XKB/xkblib.html#xkb_state_to_core_protocol_state_transformation
118 | x_modifier |= (effective_group_index_ << 13);
119 |
120 | return x_modifier;
121 | }
122 |
123 | private:
124 | KeyModifierMaskToXModifierMask() {
125 | Initialize(NULL);
126 | }
127 |
128 | int alt_modifier_;
129 | int meta_modifier_;
130 | int num_lock_modifier_;
131 | int mode_switch_modifier_;
132 | int level3_modifier_;
133 | int level5_modifier_;
134 | int effective_group_index_;
135 |
136 | KeyModifierMaskToXModifierMask(const KeyModifierMaskToXModifierMask&) = delete;
137 | KeyModifierMaskToXModifierMask& operator=(const KeyModifierMaskToXModifierMask&) = delete;
138 | };
139 |
140 | std::string GetStrFromXEvent(const XEvent* xev) {
141 | const XKeyEvent* xkey = &xev->xkey;
142 | KeySym keysym = XK_VoidSymbol;
143 | XLookupString(const_cast(xkey), NULL, 0, &keysym, NULL);
144 | uint16_t character = ui::GetUnicodeCharacterFromXKeySym(keysym);
145 |
146 | if (!character)
147 | return std::string();
148 |
149 | wchar_t value = character;
150 |
151 | return vscode_keyboard::UTF16toUTF8(&value, 1);
152 | }
153 |
154 | } // namespace
155 |
156 |
157 | namespace vscode_keyboard {
158 |
159 | #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) {usb, xkb, code}
160 | #define DOM_CODE_DECLARATION const KeycodeMapEntry usb_keycode_map[] =
161 | #include "../deps/chromium/dom_code_data.inc"
162 | #undef DOM_CODE
163 | #undef DOM_CODE_DECLARATION
164 |
165 | napi_value GetKeyMapImpl(napi_env env, napi_callback_info info) {
166 |
167 | napi_value result;
168 | NAPI_CALL(env, napi_create_object(env, &result));
169 |
170 | Display *display;
171 | if (!(display = XOpenDisplay(""))) {
172 | return result;
173 | }
174 |
175 | XEvent event;
176 | memset(&event, 0, sizeof(XEvent));
177 | XKeyEvent* key_event = &event.xkey;
178 | key_event->display = display;
179 | key_event->type = KeyPress;
180 |
181 | KeyModifierMaskToXModifierMask *mask_provider = &KeyModifierMaskToXModifierMask::GetInstance();
182 | mask_provider->Initialize(display);
183 |
184 | size_t cnt = sizeof(usb_keycode_map) / sizeof(usb_keycode_map[0]);
185 |
186 | for (size_t i = 0; i < cnt; ++i) {
187 | const char *code = usb_keycode_map[i].code;
188 | int native_keycode = usb_keycode_map[i].native_keycode;
189 |
190 | if (!code || native_keycode <= 0) {
191 | continue;
192 | }
193 |
194 | napi_value entry;
195 | NAPI_CALL(env, napi_create_object(env, &entry));
196 |
197 | key_event->keycode = native_keycode;
198 | {
199 | key_event->state = mask_provider->XStateFromKeyMod(0);
200 | std::string value = GetStrFromXEvent(&event);
201 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "value", value.c_str()));
202 | }
203 |
204 | {
205 | key_event->state = mask_provider->XStateFromKeyMod(kShiftKeyModifierMask);
206 | std::string with_shift = GetStrFromXEvent(&event);
207 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withShift", with_shift.c_str()));
208 | }
209 |
210 | {
211 | key_event->state = mask_provider->XStateFromKeyMod(kLevel3KeyModifierMask);
212 | std::string with_alt_gr = GetStrFromXEvent(&event);
213 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withAltGr", with_alt_gr.c_str()));
214 | }
215 |
216 | {
217 | key_event->state = mask_provider->XStateFromKeyMod(kShiftKeyModifierMask | kLevel3KeyModifierMask);
218 | std::string with_shift_alt_gr = GetStrFromXEvent(&event);
219 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withShiftAltGr", with_shift_alt_gr.c_str()));
220 | }
221 |
222 | {
223 | // level 5 is important for the Neo layout family
224 | key_event->state = mask_provider->XStateFromKeyMod(kLevel5KeyModifierMask);
225 | std::string with_level5 = GetStrFromXEvent(&event);
226 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withLevel5", with_level5.c_str()));
227 | }
228 |
229 | {
230 | // level3 + level5 is Level 6 in terms of the Neo layout family. (Shift + level5 has no special meaning.)
231 | key_event->state = mask_provider->XStateFromKeyMod(kLevel3KeyModifierMask | kLevel5KeyModifierMask);
232 | std::string with_level3_level5 = GetStrFromXEvent(&event);
233 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, entry, "withLevel3Level5", with_level3_level5.c_str()));
234 | }
235 |
236 | NAPI_CALL(env, napi_set_named_property(env, result, code, entry));
237 | }
238 |
239 | XFlush(display);
240 | XCloseDisplay(display);
241 |
242 | return result;
243 | }
244 |
245 | napi_value GetCurrentKeyboardLayoutImpl(napi_env env, napi_callback_info info) {
246 |
247 | napi_value result;
248 | NAPI_CALL(env, napi_get_null(env, &result));
249 |
250 | Display *display;
251 | if (!(display = XOpenDisplay(""))) {
252 | return result;
253 | }
254 |
255 | // See https://www.x.org/releases/X11R7.6/doc/libX11/specs/XKB/xkblib.html#determining_keyboard_state
256 | XkbStateRec xkb_state;
257 | XkbGetState(display, XkbUseCoreKbd, &xkb_state);
258 | int effective_group_index = xkb_state.group;
259 |
260 | XkbRF_VarDefsRec vdr;
261 | char *tmp = NULL;
262 | int res = XkbRF_GetNamesProp(display, &tmp, &vdr);
263 | if (res) {
264 | NAPI_CALL(env, napi_create_object(env, &result));
265 |
266 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "model", vdr.model ? vdr.model : ""));
267 | NAPI_CALL(env, napi_set_named_property_int32(env, result, "group", effective_group_index));
268 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "layout", vdr.layout ? vdr.layout : ""));
269 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "variant", vdr.variant ? vdr.variant : ""));
270 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "options", vdr.options ? vdr.options : ""));
271 | NAPI_CALL(env, napi_set_named_property_string_utf8(env, result, "rules", tmp ? tmp : ""));
272 | }
273 |
274 | XFlush(display);
275 | XCloseDisplay(display);
276 | return result;
277 | }
278 |
279 | typedef struct {
280 | int effective_group_index;
281 | std::string layout;
282 | std::string variant;
283 | } KbState;
284 |
285 | bool KbStatesEqual(KbState *a, KbState *b) {
286 | return (
287 | a->effective_group_index == b->effective_group_index
288 | && a->layout == b->layout
289 | && a->variant == b->variant
290 | );
291 | }
292 |
293 | void ReadKbState(Display *display, KbState *dst) {
294 | // See https://www.x.org/releases/X11R7.6/doc/libX11/specs/XKB/xkblib.html#determining_keyboard_state
295 | // Get effective group index
296 | XkbStateRec xkb_state;
297 | XkbGetState(display, XkbUseCoreKbd, &xkb_state);
298 | dst->effective_group_index = xkb_state.group;
299 |
300 | XkbRF_VarDefsRec vdr;
301 | char *tmp = NULL;
302 | int res = XkbRF_GetNamesProp(display, &tmp, &vdr);
303 | if (res) {
304 | dst->layout = (vdr.layout ? vdr.layout : "");
305 | dst->variant = (vdr.variant ? vdr.variant : "");
306 | } else {
307 | dst->layout = "";
308 | dst->variant = "";
309 | }
310 | }
311 |
312 | static void FlushAndCloseDisplay(void *arg) {
313 | Display *display = static_cast(arg);
314 | XFlush(display);
315 | XCloseDisplay(display);
316 | }
317 |
318 | void* ListenToXEvents(void *arg) {
319 | NotificationCallbackData *data = static_cast(arg);
320 |
321 | Display *display;
322 | if (!(display = XOpenDisplay(""))) {
323 | return NULL;
324 | }
325 |
326 | pthread_cleanup_push(FlushAndCloseDisplay, display);
327 |
328 | int xkblib_major = XkbMajorVersion;
329 | int xkblib_minor = XkbMinorVersion;
330 | if (!XkbLibraryVersion(&xkblib_major, &xkblib_minor)) {
331 | return NULL;
332 | }
333 |
334 | int opcode = 0;
335 | int xkb_base_event_code = 0;
336 | int xkb_base_error_code = 0;
337 | if (!XkbQueryExtension(display, &opcode, &xkb_base_event_code, &xkb_base_error_code, &xkblib_major, &xkblib_minor)) {
338 | return NULL;
339 | }
340 |
341 | // See https://www.x.org/releases/X11R7.6/doc/libX11/specs/XKB/xkblib.html#xkb_event_types
342 | // Listen only to the `XkbStateNotify` event
343 | XkbSelectEvents(display, XkbUseCoreKbd, XkbAllEventsMask, XkbStateNotifyMask);
344 |
345 | KbState last_state;
346 | ReadKbState(display, &last_state);
347 |
348 | XkbEvent event;
349 | KbState current_state;
350 | fd_set in_fds;
351 | struct timeval tv;
352 | int x11_fd = ConnectionNumber(display);
353 |
354 | while (true) {
355 | // See https://stackoverflow.com/a/8592969 which explains
356 | // the technique of waiting for an XEvent with a timeout
357 |
358 | // Create a File Description Set containing x11_fd
359 | FD_ZERO(&in_fds);
360 | FD_SET(x11_fd, &in_fds);
361 |
362 | // Set the timer to 1s.
363 | tv.tv_usec = 0;
364 | tv.tv_sec = 1;
365 |
366 | // Wait for X Event or the timer
367 | select(x11_fd + 1, &in_fds, NULL, NULL, &tv);
368 |
369 | // Handle pending XEvents
370 | while (XPending(display)) {
371 |
372 | XNextEvent(display, &event.core);
373 |
374 | if (event.type == xkb_base_event_code && event.any.xkb_type == XkbStateNotify) {
375 | ReadKbState(display, ¤t_state);
376 | // printf("current state: %d | %s | %s\n", current_state.effective_group_index, current_state.layout.c_str(), current_state.variant.c_str());
377 | if (!KbStatesEqual(&last_state, ¤t_state)) {
378 | last_state.effective_group_index = current_state.effective_group_index;
379 | last_state.layout = current_state.layout;
380 | last_state.variant = current_state.variant;
381 |
382 | InvokeNotificationCallback(data);
383 | }
384 | }
385 | }
386 | }
387 |
388 | pthread_cleanup_pop(1);
389 |
390 | return NULL;
391 | }
392 |
393 | void RegisterKeyboardLayoutChangeListenerImpl(NotificationCallbackData *data) {
394 | pthread_create(&data->tid, NULL, &ListenToXEvents, data);
395 | }
396 |
397 | void DisposeKeyboardLayoutChangeListenerImpl(NotificationCallbackData *data) {
398 | pthread_cancel(data->tid);
399 | void *res;
400 | pthread_join(data->tid, &res);
401 | }
402 |
403 | napi_value IsISOKeyboardImpl(napi_env env, napi_callback_info info) {
404 | return napi_fetch_undefined(env);
405 | }
406 |
407 | } // namespace vscode_keyboard
408 |
--------------------------------------------------------------------------------
/src/keymapping.cc:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | #define NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT
7 | #include
8 | #include