├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .node-version ├── .npmignore ├── LICENSE.md ├── README.md ├── binding.gyp ├── chrome_headers └── keycode_converter_data.inc ├── lib └── keyboard-layout.js ├── package-lock.json ├── package.json ├── spec └── keyboard-layout-spec.js └── src ├── keyboard-layout-manager-linux.cc ├── keyboard-layout-manager-mac.mm ├── keyboard-layout-manager-windows.cc ├── keyboard-layout-manager.cc └── keyboard-layout-manager.h /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | Test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: '14' 19 | - name: Install windows-build-tools 20 | if: ${{ matrix.os == 'windows-latest' }} 21 | run: npm config set msvs_version 2019 22 | - name: Install dependencies 23 | run: npm i 24 | - name: Run tests 25 | uses: GabrielBB/xvfb-action@v1 26 | with: 27 | run: npm test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | npm-debug.log 4 | *.swp 5 | build 6 | .vscode 7 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v0.11.13 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | spec 2 | script 3 | src/keyboard-layout.coffee 4 | *.coffee 5 | .npmignore 6 | .DS_Store 7 | npm-debug.log 8 | .travis.yml 9 | appveyor.yml 10 | .pairs 11 | .coffee 12 | build/ 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # keyboard-layout 3 | [![CI](https://github.com/atom/keyboard-layout/actions/workflows/ci.yml/badge.svg)](https://github.com/atom/keyboard-layout/actions/workflows/ci.yml) 4 | 5 | Read and observe the current keyboard layout. 6 | 7 | To get the current keyboard layout, call `getCurrentKeyboardLayout`. It returns 8 | the string identifier of the current layout based on the value returned by the 9 | operating system. 10 | 11 | ```js 12 | const KeyboardLayout = require('keyboard-layout') 13 | KeyboardLayout.getCurrentKeyboardLayout() // => "com.apple.keylayout.Dvorak" 14 | ``` 15 | 16 | If you want to watch for layout changes, use `onDidChangeCurrentKeyboardLayout` 17 | or `observeCurrentKeyboardLayout`. They work the same, except 18 | `observeCurrentKeyboardLayout` invokes the given callback immediately with the 19 | current layout value and then again next time it changes, whereas 20 | `onDidChangeCurrentKeyboardLayout` only invokes the callback on the next 21 | change. 22 | 23 | ```js 24 | const KeyboardLayout = require('keyboard-layout') 25 | subscription = KeyboardLayout.observeCurrentKeyboardLayout((layout) => console.log(layout)) 26 | subscription.dispose() // to unsubscribe later 27 | ``` 28 | 29 | To return characters for various modifier states based on a DOM 3 30 | `KeyboardEvent.code` value and the current system keyboard layout, use 31 | `getCurrentKeymap()`: 32 | 33 | ```js 34 | const KeyboardLayout = require('keyboard-layout') 35 | KeyboardLayout.getCurrentKeymap()['KeyS'] 36 | /* 37 | On a US layout, this returns: 38 | { 39 | unmodified: 's', 40 | withShift: 'S', 41 | withAltGraph: 'ß', 42 | withShiftAltGraph: 'Í' 43 | } 44 | */ 45 | ``` 46 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "keyboard-layout-manager", 5 | "sources": [ 6 | "src/keyboard-layout-manager.cc" 7 | ], 8 | "include_dirs": [ 9 | " 15 | // [3] Linux and hid-input.c 16 | // [4] USB HID to PS/2 Scan Code Translation Table 17 | // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf 18 | // [5] Keyboard Scan Code Specification 19 | // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc 20 | 21 | // General notes: 22 | // 23 | // This table provides the definition of ui::DomCode (UI Events |code|) values 24 | // as well as mapping between scan codes and DomCode. Some entries have no 25 | // defined scan codes; these are present only to allow those UI Events |code| 26 | // strings to be represented by DomCode. A few have a null code; these define 27 | // mappings with a DomCode:: value but no |code| string, typically because 28 | // they end up used in shortcuts but not standardized in UI Events; e.g. 29 | // DomCode::BRIGHTNESS_UP. Commented-out entries document USB codes that are 30 | // potentially interesting but not currently used. 31 | 32 | // Linux notes: 33 | // 34 | // All USB codes that are listed here and that are supported by the kernel 35 | // (as of 4.2) have their evdev/xkb scan codes recorded; if an evdev/xkb 36 | // code is 0, it is because the kernel USB driver does not handle that key. 37 | // 38 | // Some Linux kernel mappings for USB keys may seem counterintuitive: 39 | // 40 | // [L1] Although evdev 0x163 KEY_CLEAR exists, Linux does not use it 41 | // for any USB keys. Linux maps USB 0x07009c [Keyboard Clear] and 42 | // 0x0700d8 [Keypad Clear] to KEY_DELETE "Delete", so those codes are 43 | // not distinguishable by applications, and UI Events "NumpadClear" 44 | // is therefore not supported. USB 0x0700A2 [Keyboard Clear/Again] 45 | // is not mapped by the kernel at all. 46 | // 47 | // [L2] 'Menu' and 'Props' naming differs between evdev and USB / UI Events. 48 | // USB 0x010085 [System Main Menu] and USB 0x0C0040 [Menu Mode] both 49 | // map to evdev 0x8B KEY_MENU (which has no corresponding UI Events 50 | // |code|). USB 0x070076 [Keyboard Menu] does not map to KEY_MENU; 51 | // it maps to evdev 0x82 KEY_PROPS, which is not the same as USB and 52 | // UI Events "Props". USB 0x0700A3 [Props], which does correspond to 53 | // UI Events "Props", is not mapped by the kernel. (And all of these 54 | // are distinct from UI Events' "ContextMenu", which corresponds to 55 | // USB 0x070065 [Keyboard Application] via evdev 0x7F KEY_COMPOSE, 56 | // following Windows convention.) 57 | // 58 | // [L3] Linux flattens both USB 0x070048 [Keyboard Pause] and 0x0C00B1 59 | // [Media Pause] to 0x77 KEY_PAUSE. We map the former, since [1] 60 | // defines a 'Pause' code but no 'MediaPause' code. 61 | 62 | // Windows notes: 63 | // 64 | // The set of scan codes supported here may not be complete. 65 | // 66 | // [W1] Windows maps both USB 0x070094 [Lang5] and USB 0x070073 [F24] to the 67 | // same scan code, 0x76. (Microsoft's defined scan codes for F13 - F24 68 | // appear to be the result of accidentally mapping an IBM Set 3 terminal 69 | // keyboard, rather than an IBM Set 2 PC keyboard, through the BIOS 70 | // 2-to-1 table.) We map 0x76 to F24 here, since Lang5 appears unused 71 | // in practice (its declared function, Zenkaku/Hankaku switch, is 72 | // conventionally placed on Backquote by Japanese keyboards). 73 | 74 | // Macintosh notes: 75 | // 76 | // The set of scan codes supported here may not be complete. 77 | // 78 | // [M1] OS X maps USB 0x070049 [Insert] as well as USB 0x070075 [Help] to 79 | // scan code 0x72 kVK_Help. We map this to UI Events 'Insert', since 80 | // Apple keyboards with USB 0x070049 [Insert] labelled "Help" have not 81 | // been made since 2007. 82 | 83 | USB_KEYMAP_DECLARATION { 84 | 85 | // USB evdev XKB Win Mac Code 86 | USB_KEYMAP(0x000000, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NONE), // Invalid 87 | 88 | // ========================================= 89 | // Non-USB codes 90 | // ========================================= 91 | 92 | // USB evdev XKB Win Mac Code 93 | USB_KEYMAP(0x000010, 0x0000, 0x0000, 0x0000, 0xffff, "Hyper", HYPER), 94 | USB_KEYMAP(0x000011, 0x0000, 0x0000, 0x0000, 0xffff, "Super", SUPER), 95 | USB_KEYMAP(0x000012, 0x0000, 0x0000, 0x0000, 0xffff, "Fn", FN), 96 | USB_KEYMAP(0x000013, 0x0000, 0x0000, 0x0000, 0xffff, "FnLock", FN_LOCK), 97 | USB_KEYMAP(0x000014, 0x0000, 0x0000, 0x0000, 0xffff, "Suspend", SUSPEND), 98 | USB_KEYMAP(0x000015, 0x0000, 0x0000, 0x0000, 0xffff, "Resume", RESUME), 99 | USB_KEYMAP(0x000016, 0x0000, 0x0000, 0x0000, 0xffff, "Turbo", TURBO), 100 | 101 | // ========================================= 102 | // USB Usage Page 0x01: Generic Desktop Page 103 | // ========================================= 104 | 105 | // Sleep could be encoded as USB#0c0032, but there's no corresponding WakeUp 106 | // in the 0x0c USB page. 107 | // USB evdev XKB Win Mac 108 | USB_KEYMAP(0x010082, 0x008e, 0x0096, 0xe05f, 0xffff, "Sleep", SLEEP), // SystemSleep 109 | USB_KEYMAP(0x010083, 0x008f, 0x0097, 0xe063, 0xffff, "WakeUp", WAKE_UP), 110 | 111 | // ========================================= 112 | // USB Usage Page 0x07: Keyboard/Keypad Page 113 | // ========================================= 114 | 115 | // TODO(garykac): 116 | // XKB#005c ISO Level3 Shift (AltGr) 117 | // XKB#005e <>|| 118 | // XKB#006d Linefeed 119 | // XKB#008a SunProps cf. USB#0700a3 CrSel/Props 120 | // XKB#008e SunOpen 121 | // Mac#003f kVK_Function 122 | // Mac#000a kVK_ISO_Section (ISO keyboards only) 123 | // Mac#0066 kVK_JIS_Eisu (USB#07008a Henkan?) 124 | 125 | // USB evdev XKB Win Mac 126 | USB_KEYMAP(0x070000, 0x0000, 0x0000, 0x0000, 0xffff, NULL, USB_RESERVED), 127 | USB_KEYMAP(0x070001, 0x0000, 0x0000, 0x00ff, 0xffff, NULL, USB_ERROR_ROLL_OVER), 128 | USB_KEYMAP(0x070002, 0x0000, 0x0000, 0x00fc, 0xffff, NULL, USB_POST_FAIL), 129 | USB_KEYMAP(0x070003, 0x0000, 0x0000, 0x0000, 0xffff, NULL, USB_ERROR_UNDEFINED), 130 | USB_KEYMAP(0x070004, 0x001e, 0x0026, 0x001e, 0x0000, "KeyA", US_A), // aA 131 | USB_KEYMAP(0x070005, 0x0030, 0x0038, 0x0030, 0x000b, "KeyB", US_B), // bB 132 | USB_KEYMAP(0x070006, 0x002e, 0x0036, 0x002e, 0x0008, "KeyC", US_C), // cC 133 | USB_KEYMAP(0x070007, 0x0020, 0x0028, 0x0020, 0x0002, "KeyD", US_D), // dD 134 | 135 | USB_KEYMAP(0x070008, 0x0012, 0x001a, 0x0012, 0x000e, "KeyE", US_E), // eE 136 | USB_KEYMAP(0x070009, 0x0021, 0x0029, 0x0021, 0x0003, "KeyF", US_F), // fF 137 | USB_KEYMAP(0x07000a, 0x0022, 0x002a, 0x0022, 0x0005, "KeyG", US_G), // gG 138 | USB_KEYMAP(0x07000b, 0x0023, 0x002b, 0x0023, 0x0004, "KeyH", US_H), // hH 139 | USB_KEYMAP(0x07000c, 0x0017, 0x001f, 0x0017, 0x0022, "KeyI", US_I), // iI 140 | USB_KEYMAP(0x07000d, 0x0024, 0x002c, 0x0024, 0x0026, "KeyJ", US_J), // jJ 141 | USB_KEYMAP(0x07000e, 0x0025, 0x002d, 0x0025, 0x0028, "KeyK", US_K), // kK 142 | USB_KEYMAP(0x07000f, 0x0026, 0x002e, 0x0026, 0x0025, "KeyL", US_L), // lL 143 | 144 | USB_KEYMAP(0x070010, 0x0032, 0x003a, 0x0032, 0x002e, "KeyM", US_M), // mM 145 | USB_KEYMAP(0x070011, 0x0031, 0x0039, 0x0031, 0x002d, "KeyN", US_N), // nN 146 | USB_KEYMAP(0x070012, 0x0018, 0x0020, 0x0018, 0x001f, "KeyO", US_O), // oO 147 | USB_KEYMAP(0x070013, 0x0019, 0x0021, 0x0019, 0x0023, "KeyP", US_P), // pP 148 | USB_KEYMAP(0x070014, 0x0010, 0x0018, 0x0010, 0x000c, "KeyQ", US_Q), // qQ 149 | USB_KEYMAP(0x070015, 0x0013, 0x001b, 0x0013, 0x000f, "KeyR", US_R), // rR 150 | USB_KEYMAP(0x070016, 0x001f, 0x0027, 0x001f, 0x0001, "KeyS", US_S), // sS 151 | USB_KEYMAP(0x070017, 0x0014, 0x001c, 0x0014, 0x0011, "KeyT", US_T), // tT 152 | 153 | USB_KEYMAP(0x070018, 0x0016, 0x001e, 0x0016, 0x0020, "KeyU", US_U), // uU 154 | USB_KEYMAP(0x070019, 0x002f, 0x0037, 0x002f, 0x0009, "KeyV", US_V), // vV 155 | USB_KEYMAP(0x07001a, 0x0011, 0x0019, 0x0011, 0x000d, "KeyW", US_W), // wW 156 | USB_KEYMAP(0x07001b, 0x002d, 0x0035, 0x002d, 0x0007, "KeyX", US_X), // xX 157 | USB_KEYMAP(0x07001c, 0x0015, 0x001d, 0x0015, 0x0010, "KeyY", US_Y), // yY 158 | USB_KEYMAP(0x07001d, 0x002c, 0x0034, 0x002c, 0x0006, "KeyZ", US_Z), // zZ 159 | USB_KEYMAP(0x07001e, 0x0002, 0x000a, 0x0002, 0x0012, "Digit1", DIGIT1), // 1! 160 | USB_KEYMAP(0x07001f, 0x0003, 0x000b, 0x0003, 0x0013, "Digit2", DIGIT2), // 2@ 161 | 162 | USB_KEYMAP(0x070020, 0x0004, 0x000c, 0x0004, 0x0014, "Digit3", DIGIT3), // 3# 163 | USB_KEYMAP(0x070021, 0x0005, 0x000d, 0x0005, 0x0015, "Digit4", DIGIT4), // 4$ 164 | USB_KEYMAP(0x070022, 0x0006, 0x000e, 0x0006, 0x0017, "Digit5", DIGIT5), // 5% 165 | USB_KEYMAP(0x070023, 0x0007, 0x000f, 0x0007, 0x0016, "Digit6", DIGIT6), // 6^ 166 | USB_KEYMAP(0x070024, 0x0008, 0x0010, 0x0008, 0x001a, "Digit7", DIGIT7), // 7& 167 | USB_KEYMAP(0x070025, 0x0009, 0x0011, 0x0009, 0x001c, "Digit8", DIGIT8), // 8* 168 | USB_KEYMAP(0x070026, 0x000a, 0x0012, 0x000a, 0x0019, "Digit9", DIGIT9), // 9( 169 | USB_KEYMAP(0x070027, 0x000b, 0x0013, 0x000b, 0x001d, "Digit0", DIGIT0), // 0) 170 | 171 | USB_KEYMAP(0x070028, 0x001c, 0x0024, 0x001c, 0x0024, "Enter", ENTER), 172 | USB_KEYMAP(0x070029, 0x0001, 0x0009, 0x0001, 0x0035, "Escape", ESCAPE), 173 | USB_KEYMAP(0x07002a, 0x000e, 0x0016, 0x000e, 0x0033, "Backspace", BACKSPACE), 174 | USB_KEYMAP(0x07002b, 0x000f, 0x0017, 0x000f, 0x0030, "Tab", TAB), 175 | USB_KEYMAP(0x07002c, 0x0039, 0x0041, 0x0039, 0x0031, "Space", SPACE), // Spacebar 176 | USB_KEYMAP(0x07002d, 0x000c, 0x0014, 0x000c, 0x001b, "Minus", MINUS), // -_ 177 | USB_KEYMAP(0x07002e, 0x000d, 0x0015, 0x000d, 0x0018, "Equal", EQUAL), // =+ 178 | USB_KEYMAP(0x07002f, 0x001a, 0x0022, 0x001a, 0x0021, "BracketLeft", BRACKET_LEFT), 179 | 180 | USB_KEYMAP(0x070030, 0x001b, 0x0023, 0x001b, 0x001e, "BracketRight", BRACKET_RIGHT), 181 | USB_KEYMAP(0x070031, 0x002b, 0x0033, 0x002b, 0x002a, "Backslash", BACKSLASH), // \| 182 | // USB#070032 never appears on keyboards that have USB#070031. 183 | // Platforms use the same scancode as for the two keys. 184 | // Hence this code can only be generated synthetically 185 | // (e.g. in a DOM Level 3 KeyboardEvent). 186 | // The keycap varies on international keyboards: 187 | // Dan: '* Dutch: <> Ger: #' UK: #~ 188 | // TODO(garykac): Verify Mac intl keyboard. 189 | USB_KEYMAP(0x070032, 0x0000, 0x0000, 0x0000, 0xffff, "IntlHash", INTL_HASH), 190 | USB_KEYMAP(0x070033, 0x0027, 0x002f, 0x0027, 0x0029, "Semicolon", SEMICOLON), // ;: 191 | USB_KEYMAP(0x070034, 0x0028, 0x0030, 0x0028, 0x0027, "Quote", QUOTE), // '" 192 | USB_KEYMAP(0x070035, 0x0029, 0x0031, 0x0029, 0x0032, "Backquote", BACKQUOTE), // `~ 193 | USB_KEYMAP(0x070036, 0x0033, 0x003b, 0x0033, 0x002b, "Comma", COMMA), // ,< 194 | USB_KEYMAP(0x070037, 0x0034, 0x003c, 0x0034, 0x002f, "Period", PERIOD), // .> 195 | 196 | USB_KEYMAP(0x070038, 0x0035, 0x003d, 0x0035, 0x002c, "Slash", SLASH), // /? 197 | // TODO(garykac): CapsLock requires special handling for each platform. 198 | USB_KEYMAP(0x070039, 0x003a, 0x0042, 0x003a, 0x0039, "CapsLock", CAPS_LOCK), 199 | USB_KEYMAP(0x07003a, 0x003b, 0x0043, 0x003b, 0x007a, "F1", F1), 200 | USB_KEYMAP(0x07003b, 0x003c, 0x0044, 0x003c, 0x0078, "F2", F2), 201 | USB_KEYMAP(0x07003c, 0x003d, 0x0045, 0x003d, 0x0063, "F3", F3), 202 | USB_KEYMAP(0x07003d, 0x003e, 0x0046, 0x003e, 0x0076, "F4", F4), 203 | USB_KEYMAP(0x07003e, 0x003f, 0x0047, 0x003f, 0x0060, "F5", F5), 204 | USB_KEYMAP(0x07003f, 0x0040, 0x0048, 0x0040, 0x0061, "F6", F6), 205 | 206 | USB_KEYMAP(0x070040, 0x0041, 0x0049, 0x0041, 0x0062, "F7", F7), 207 | USB_KEYMAP(0x070041, 0x0042, 0x004a, 0x0042, 0x0064, "F8", F8), 208 | USB_KEYMAP(0x070042, 0x0043, 0x004b, 0x0043, 0x0065, "F9", F9), 209 | USB_KEYMAP(0x070043, 0x0044, 0x004c, 0x0044, 0x006d, "F10", F10), 210 | USB_KEYMAP(0x070044, 0x0057, 0x005f, 0x0057, 0x0067, "F11", F11), 211 | USB_KEYMAP(0x070045, 0x0058, 0x0060, 0x0058, 0x006f, "F12", F12), 212 | // PrintScreen is effectively F13 on Mac OS X. 213 | USB_KEYMAP(0x070046, 0x0063, 0x006b, 0xe037, 0xffff, "PrintScreen", PRINT_SCREEN), 214 | USB_KEYMAP(0x070047, 0x0046, 0x004e, 0x0046, 0xffff, "ScrollLock", SCROLL_LOCK), 215 | 216 | USB_KEYMAP(0x070048, 0x0077, 0x007f, 0x0045, 0xffff, "Pause", PAUSE), 217 | // USB#0x070049 Insert, labeled "Help/Insert" on Mac -- see note M1 at top. 218 | USB_KEYMAP(0x070049, 0x006e, 0x0076, 0xe052, 0x0072, "Insert", INSERT), 219 | USB_KEYMAP(0x07004a, 0x0066, 0x006e, 0xe047, 0x0073, "Home", HOME), 220 | USB_KEYMAP(0x07004b, 0x0068, 0x0070, 0xe049, 0x0074, "PageUp", PAGE_UP), 221 | // Delete (Forward Delete) named DEL because DELETE conflicts with 222 | USB_KEYMAP(0x07004c, 0x006f, 0x0077, 0xe053, 0x0075, "Delete", DEL), 223 | USB_KEYMAP(0x07004d, 0x006b, 0x0073, 0xe04f, 0x0077, "End", END), 224 | USB_KEYMAP(0x07004e, 0x006d, 0x0075, 0xe051, 0x0079, "PageDown", PAGE_DOWN), 225 | USB_KEYMAP(0x07004f, 0x006a, 0x0072, 0xe04d, 0x007c, "ArrowRight", ARROW_RIGHT), 226 | 227 | USB_KEYMAP(0x070050, 0x0069, 0x0071, 0xe04b, 0x007b, "ArrowLeft", ARROW_LEFT), 228 | USB_KEYMAP(0x070051, 0x006c, 0x0074, 0xe050, 0x007d, "ArrowDown", ARROW_DOWN), 229 | USB_KEYMAP(0x070052, 0x0067, 0x006f, 0xe048, 0x007e, "ArrowUp", ARROW_UP), 230 | USB_KEYMAP(0x070053, 0x0045, 0x004d, 0xe045, 0x0047, "NumLock", NUM_LOCK), 231 | USB_KEYMAP(0x070054, 0x0062, 0x006a, 0xe035, 0x004b, "NumpadDivide", NUMPAD_DIVIDE), 232 | USB_KEYMAP(0x070055, 0x0037, 0x003f, 0x0037, 0x0043, "NumpadMultiply", 233 | NUMPAD_MULTIPLY), // Keypad_* 234 | USB_KEYMAP(0x070056, 0x004a, 0x0052, 0x004a, 0x004e, "NumpadSubtract", 235 | NUMPAD_SUBTRACT), // Keypad_- 236 | USB_KEYMAP(0x070057, 0x004e, 0x0056, 0x004e, 0x0045, "NumpadAdd", NUMPAD_ADD), 237 | 238 | USB_KEYMAP(0x070058, 0x0060, 0x0068, 0xe01c, 0x004c, "NumpadEnter", NUMPAD_ENTER), 239 | USB_KEYMAP(0x070059, 0x004f, 0x0057, 0x004f, 0x0053, "Numpad1", NUMPAD1), // +End 240 | USB_KEYMAP(0x07005a, 0x0050, 0x0058, 0x0050, 0x0054, "Numpad2", NUMPAD2), // +Down 241 | USB_KEYMAP(0x07005b, 0x0051, 0x0059, 0x0051, 0x0055, "Numpad3", NUMPAD3), // +PageDn 242 | USB_KEYMAP(0x07005c, 0x004b, 0x0053, 0x004b, 0x0056, "Numpad4", NUMPAD4), // +Left 243 | USB_KEYMAP(0x07005d, 0x004c, 0x0054, 0x004c, 0x0057, "Numpad5", NUMPAD5), // 244 | USB_KEYMAP(0x07005e, 0x004d, 0x0055, 0x004d, 0x0058, "Numpad6", NUMPAD6), // +Right 245 | USB_KEYMAP(0x07005f, 0x0047, 0x004f, 0x0047, 0x0059, "Numpad7", NUMPAD7), // +Home 246 | 247 | USB_KEYMAP(0x070060, 0x0048, 0x0050, 0x0048, 0x005b, "Numpad8", NUMPAD8), // +Up 248 | USB_KEYMAP(0x070061, 0x0049, 0x0051, 0x0049, 0x005c, "Numpad9", NUMPAD9), // +PageUp 249 | USB_KEYMAP(0x070062, 0x0052, 0x005a, 0x0052, 0x0052, "Numpad0", NUMPAD0), // +Insert 250 | USB_KEYMAP(0x070063, 0x0053, 0x005b, 0x0053, 0x0041, "NumpadDecimal", 251 | NUMPAD_DECIMAL), // Keypad_. Delete 252 | // USB#070064 is not present on US keyboard. 253 | // This key is typically located near LeftShift key. 254 | // The keycap varies on international keyboards: 255 | // Dan: <> Dutch: ][ Ger: <> UK: \| 256 | USB_KEYMAP(0x070064, 0x0056, 0x005e, 0x0056, 0x000a, "IntlBackslash", INTL_BACKSLASH), 257 | // USB#0x070065 Application Menu (next to RWin key) -- see note L2 at top. 258 | USB_KEYMAP(0x070065, 0x007f, 0x0087, 0xe05d, 0x006e, "ContextMenu", CONTEXT_MENU), 259 | USB_KEYMAP(0x070066, 0x0074, 0x007c, 0xe05e, 0xffff, "Power", POWER), 260 | USB_KEYMAP(0x070067, 0x0075, 0x007d, 0x0059, 0x0051, "NumpadEqual", NUMPAD_EQUAL), 261 | 262 | USB_KEYMAP(0x070068, 0x00b7, 0x00bf, 0x0064, 0x0069, "F13", F13), 263 | USB_KEYMAP(0x070069, 0x00b8, 0x00c0, 0x0065, 0x006b, "F14", F14), 264 | USB_KEYMAP(0x07006a, 0x00b9, 0x00c1, 0x0066, 0x0071, "F15", F15), 265 | USB_KEYMAP(0x07006b, 0x00ba, 0x00c2, 0x0067, 0x006a, "F16", F16), 266 | USB_KEYMAP(0x07006c, 0x00bb, 0x00c3, 0x0068, 0x0040, "F17", F17), 267 | USB_KEYMAP(0x07006d, 0x00bc, 0x00c4, 0x0069, 0x004f, "F18", F18), 268 | USB_KEYMAP(0x07006e, 0x00bd, 0x00c5, 0x006a, 0x0050, "F19", F19), 269 | USB_KEYMAP(0x07006f, 0x00be, 0x00c6, 0x006b, 0x005a, "F20", F20), 270 | 271 | USB_KEYMAP(0x070070, 0x00bf, 0x00c7, 0x006c, 0xffff, "F21", F21), 272 | USB_KEYMAP(0x070071, 0x00c0, 0x00c8, 0x006d, 0xffff, "F22", F22), 273 | USB_KEYMAP(0x070072, 0x00c1, 0x00c9, 0x006e, 0xffff, "F23", F23), 274 | // USB#0x070073 -- see note W1 at top. 275 | USB_KEYMAP(0x070073, 0x00c2, 0x00ca, 0x0076, 0xffff, "F24", F24), 276 | USB_KEYMAP(0x070074, 0x0086, 0x008e, 0x0000, 0xffff, "Open", OPEN), // Execute 277 | // USB#0x070075 Help -- see note M1 at top. 278 | USB_KEYMAP(0x070075, 0x008a, 0x0092, 0xe03b, 0xffff, "Help", HELP), 279 | // USB#0x070076 Keyboard Menu -- see note L2 at top. 280 | //USB_KEYMAP(0x070076, 0x0000, 0x0000, 0x0000, 0xffff, NULL, MENU), // Menu 281 | USB_KEYMAP(0x070077, 0x0084, 0x008c, 0x0000, 0xffff, "Select", SELECT), // Select 282 | 283 | //USB_KEYMAP(0x070078, 0x0080, 0x0088, 0x0000, 0xffff, NULL, STOP), // Stop 284 | USB_KEYMAP(0x070079, 0x0081, 0x0089, 0x0000, 0xffff, "Again", AGAIN), // Again 285 | USB_KEYMAP(0x07007a, 0x0083, 0x008b, 0xe008, 0xffff, "Undo", UNDO), 286 | USB_KEYMAP(0x07007b, 0x0089, 0x0091, 0xe017, 0xffff, "Cut", CUT), 287 | USB_KEYMAP(0x07007c, 0x0085, 0x008d, 0xe018, 0xffff, "Copy", COPY), 288 | USB_KEYMAP(0x07007d, 0x0087, 0x008f, 0xe00a, 0xffff, "Paste", PASTE), 289 | USB_KEYMAP(0x07007e, 0x0088, 0x0090, 0x0000, 0xffff, "Find", FIND), // Find 290 | USB_KEYMAP(0x07007f, 0x0071, 0x0079, 0xe020, 0x004a, "AudioVolumeMute", VOLUME_MUTE), 291 | 292 | USB_KEYMAP(0x070080, 0x0073, 0x007b, 0xe030, 0x0048, "AudioVolumeUp", VOLUME_UP), 293 | USB_KEYMAP(0x070081, 0x0072, 0x007a, 0xe02e, 0x0049, "AudioVolumeDown", VOLUME_DOWN), 294 | //USB_KEYMAP(0x070082, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LOCKING_CAPS_LOCK), 295 | //USB_KEYMAP(0x070083, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LOCKING_NUM_LOCK), 296 | //USB_KEYMAP(0x070084, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LOCKING_SCROLL_LOCK), 297 | USB_KEYMAP(0x070085, 0x0079, 0x0081, 0x007e, 0x005f, "NumpadComma", NUMPAD_COMMA), 298 | 299 | // International1 300 | // USB#070086 is used on AS/400 keyboards. Standard Keypad_= is USB#070067. 301 | //USB_KEYMAP(0x070086, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_EQUAL), 302 | // USB#070087 is used for Brazilian /? and Japanese _ 'ro'. 303 | USB_KEYMAP(0x070087, 0x0059, 0x0061, 0x0073, 0x005e, "IntlRo", INTL_RO), 304 | // International2 305 | // USB#070088 is used as Japanese Hiragana/Katakana key. 306 | USB_KEYMAP(0x070088, 0x005d, 0x0065, 0x0070, 0x0068, "KanaMode", KANA_MODE), 307 | // International3 308 | // USB#070089 is used as Japanese Yen key. 309 | USB_KEYMAP(0x070089, 0x007c, 0x0084, 0x007d, 0x005d, "IntlYen", INTL_YEN), 310 | // International4 311 | // USB#07008a is used as Japanese Henkan (Convert) key. 312 | USB_KEYMAP(0x07008a, 0x005c, 0x0064, 0x0079, 0xffff, "Convert", CONVERT), 313 | // International5 314 | // USB#07008b is used as Japanese Muhenkan (No-convert) key. 315 | USB_KEYMAP(0x07008b, 0x005e, 0x0066, 0x007b, 0xffff, "NonConvert", NON_CONVERT), 316 | //USB_KEYMAP(0x07008c, 0x005f, 0x0067, 0x005c, 0xffff, NULL, INTERNATIONAL6), 317 | //USB_KEYMAP(0x07008d, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTERNATIONAL7), 318 | //USB_KEYMAP(0x07008e, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTERNATIONAL8), 319 | //USB_KEYMAP(0x07008f, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTERNATIONAL9), 320 | 321 | // LANG1 322 | // USB#070090 is used as Korean Hangul/English toggle key. 323 | USB_KEYMAP(0x070090, 0x007a, 0x0082, 0x0072, 0xffff, "Lang1", LANG1), 324 | // LANG2 325 | // USB#070091 is used as Korean Hanja conversion key. 326 | USB_KEYMAP(0x070091, 0x007b, 0x0083, 0x0071, 0xffff, "Lang2", LANG2), 327 | // LANG3 328 | // USB#070092 is used as Japanese Katakana key. 329 | USB_KEYMAP(0x070092, 0x005a, 0x0062, 0x0078, 0xffff, "Lang3", LANG3), 330 | // LANG4 331 | // USB#070093 is used as Japanese Hiragana key. 332 | USB_KEYMAP(0x070093, 0x005b, 0x0063, 0x0077, 0xffff, "Lang4", LANG4), 333 | // LANG5 334 | // USB#070094 is used as Japanese Zenkaku/Hankaku (Fullwidth/halfwidth) key. 335 | // Not mapped on Windows -- see note W1 at top. 336 | USB_KEYMAP(0x070094, 0x0055, 0x005d, 0x0000, 0xffff, "Lang5", LANG5), 337 | //USB_KEYMAP(0x070095, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG6), // LANG6 338 | //USB_KEYMAP(0x070096, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG7), // LANG7 339 | //USB_KEYMAP(0x070097, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG8), // LANG8 340 | //USB_KEYMAP(0x070098, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG9), // LANG9 341 | 342 | //USB_KEYMAP(0x070099, 0x0000, 0x0000, 0x0000, 0xffff, NULL, ALTERNATE_ERASE), 343 | //USB_KEYMAP(0x07009a, 0x0000, 0x0000, 0x0000, 0xffff, NULL, SYS_REQ), // /Attention 344 | USB_KEYMAP(0x07009b, 0x0000, 0x0000, 0x0000, 0xffff, "Abort", ABORT), // Cancel 345 | // USB#0x07009c Keyboard Clear -- see note L1 at top. 346 | //USB_KEYMAP(0x07009c, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CLEAR), // Clear 347 | //USB_KEYMAP(0x07009d, 0x0000, 0x0000, 0x0000, 0xffff, NULL, PRIOR), // Prior 348 | //USB_KEYMAP(0x07009e, 0x0000, 0x0000, 0x0000, 0xffff, NULL, RETURN), // Return 349 | //USB_KEYMAP(0x07009f, 0x0000, 0x0000, 0x0000, 0xffff, NULL, SEPARATOR), // Separator 350 | 351 | //USB_KEYMAP(0x0700a0, 0x0000, 0x0000, 0x0000, 0xffff, NULL, OUT), // Out 352 | //USB_KEYMAP(0x0700a1, 0x0000, 0x0000, 0x0000, 0xffff, NULL, OPER), // Oper 353 | //USB_KEYMAP(0x0700a2, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CLEAR_AGAIN), 354 | // USB#0x0700a3 Props -- see note L2 at top. 355 | USB_KEYMAP(0x0700a3, 0x0000, 0x0000, 0x0000, 0xffff, "Props", PROPS), // CrSel/Props 356 | //USB_KEYMAP(0x0700a4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, EX_SEL), // ExSel 357 | 358 | //USB_KEYMAP(0x0700b0, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_00), 359 | //USB_KEYMAP(0x0700b1, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_000), 360 | //USB_KEYMAP(0x0700b2, 0x0000, 0x0000, 0x0000, 0xffff, NULL, THOUSANDS_SEPARATOR), 361 | //USB_KEYMAP(0x0700b3, 0x0000, 0x0000, 0x0000, 0xffff, NULL, DECIMAL_SEPARATOR), 362 | //USB_KEYMAP(0x0700b4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CURRENCY_UNIT), 363 | //USB_KEYMAP(0x0700b5, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CURRENCY_SUBUNIT), 364 | USB_KEYMAP(0x0700b6, 0x00b3, 0x00bb, 0x0000, 0xffff, "NumpadParenLeft", 365 | NUMPAD_PAREN_LEFT), // Keypad_( 366 | USB_KEYMAP(0x0700b7, 0x00b4, 0x00bc, 0x0000, 0xffff, "NumpadParenRight", 367 | NUMPAD_PAREN_RIGHT), // Keypad_) 368 | 369 | //USB_KEYMAP(0x0700b8, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_BRACE_LEFT), 370 | //USB_KEYMAP(0x0700b9, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_BRACE_RIGHT), 371 | //USB_KEYMAP(0x0700ba, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_TAB), 372 | USB_KEYMAP(0x0700bb, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadBackspace", 373 | NUMPAD_BACKSPACE), // Keypad_Backspace 374 | //USB_KEYMAP(0x0700bc, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_A), 375 | //USB_KEYMAP(0x0700bd, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_B), 376 | //USB_KEYMAP(0x0700be, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_C), 377 | //USB_KEYMAP(0x0700bf, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_D), 378 | 379 | //USB_KEYMAP(0x0700c0, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_E), 380 | //USB_KEYMAP(0x0700c1, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_F), 381 | //USB_KEYMAP(0x0700c2, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_XOR), 382 | //USB_KEYMAP(0x0700c3, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_CARAT), 383 | //USB_KEYMAP(0x0700c4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_PERCENT), 384 | //USB_KEYMAP(0x0700c5, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_LESS_THAN), 385 | //USB_KEYMAP(0x0700c6, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_GREATER_THAN), 386 | //USB_KEYMAP(0x0700c7, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_AMERSAND), 387 | 388 | //USB_KEYMAP(0x0700c8, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_DOUBLE_AMPERSAND), 389 | //USB_KEYMAP(0x0700c9, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_VERTICAL_BAR), 390 | //USB_KEYMAP(0x0700ca, 0x0000, 0x0000, 0x0000, 0xffff, NULL, 391 | // NUMPAD_DOUBLE_VERTICAL_BAR), // Keypad_|| 392 | //USB_KEYMAP(0x0700cb, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_COLON), 393 | //USB_KEYMAP(0x0700cc, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_NUMBER), 394 | //USB_KEYMAP(0x0700cd, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_SPACE), 395 | //USB_KEYMAP(0x0700ce, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_AT), 396 | //USB_KEYMAP(0x0700cf, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_EXCLAMATION), 397 | 398 | USB_KEYMAP(0x0700d0, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryStore", 399 | NUMPAD_MEMORY_STORE), // Keypad_MemoryStore 400 | USB_KEYMAP(0x0700d1, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryRecall", 401 | NUMPAD_MEMORY_RECALL), // Keypad_MemoryRecall 402 | USB_KEYMAP(0x0700d2, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryClear", 403 | NUMPAD_MEMORY_CLEAR), // Keypad_MemoryClear 404 | USB_KEYMAP(0x0700d3, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryAdd", 405 | NUMPAD_MEMORY_ADD), // Keypad_MemoryAdd 406 | USB_KEYMAP(0x0700d4, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemorySubtract", 407 | NUMPAD_MEMORY_SUBTRACT), // Keypad_MemorySubtract 408 | //USB_KEYMAP(0x0700d5, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_MEMORY_MULTIPLE), 409 | //USB_KEYMAP(0x0700d6, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_MEMORY_DIVIDE), 410 | USB_KEYMAP(0x0700d7, 0x0076, 0x007e, 0x0000, 0xffff, NULL, NUMPAD_SIGN_CHANGE), // +/- 411 | // USB#0x0700d8 Keypad Clear -- see note L1 at top. 412 | USB_KEYMAP(0x0700d8, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadClear", NUMPAD_CLEAR), 413 | USB_KEYMAP(0x0700d9, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadClearEntry", 414 | NUMPAD_CLEAR_ENTRY), // Keypad_ClearEntry 415 | //USB_KEYMAP(0x0700da, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_BINARY), 416 | //USB_KEYMAP(0x0700db, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_OCTAL), 417 | //USB_KEYMAP(0x0700dc, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_DECIMAL), 418 | //USB_KEYMAP(0x0700dd, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_HEXADECIMAL), 419 | 420 | // USB#0700de - #0700df are reserved. 421 | USB_KEYMAP(0x0700e0, 0x001d, 0x0025, 0x001d, 0x003b, "ControlLeft", CONTROL_LEFT), 422 | USB_KEYMAP(0x0700e1, 0x002a, 0x0032, 0x002a, 0x0038, "ShiftLeft", SHIFT_LEFT), 423 | // USB#0700e2: left Alt key (Mac left Option key). 424 | USB_KEYMAP(0x0700e2, 0x0038, 0x0040, 0x0038, 0x003a, "AltLeft", ALT_LEFT), 425 | // USB#0700e3: left GUI key, e.g. Windows, Mac Command, ChromeOS Search. 426 | USB_KEYMAP(0x0700e3, 0x007d, 0x0085, 0xe05b, 0x0037, "MetaLeft", META_LEFT), 427 | USB_KEYMAP(0x0700e4, 0x0061, 0x0069, 0xe01d, 0x003e, "ControlRight", CONTROL_RIGHT), 428 | USB_KEYMAP(0x0700e5, 0x0036, 0x003e, 0x0036, 0x003c, "ShiftRight", SHIFT_RIGHT), 429 | // USB#0700e6: right Alt key (Mac right Option key). 430 | USB_KEYMAP(0x0700e6, 0x0064, 0x006c, 0xe038, 0x003d, "AltRight", ALT_RIGHT), 431 | // USB#0700e7: right GUI key, e.g. Windows, Mac Command, ChromeOS Search. 432 | USB_KEYMAP(0x0700e7, 0x007e, 0x0086, 0xe05c, 0x0036, "MetaRight", META_RIGHT), 433 | 434 | // USB#0700e8 - #07ffff are reserved 435 | 436 | // ================================== 437 | // USB Usage Page 0x0c: Consumer Page 438 | // ================================== 439 | // AL = Application Launch 440 | // AC = Application Control 441 | 442 | // TODO(garykac): Many XF86 keys have multiple scancodes mapping to them. 443 | // We need to map all of these into a canonical USB scancode without 444 | // confusing the reverse-lookup - most likely by simply returning the first 445 | // found match. 446 | 447 | // TODO(garykac): Find appropriate mappings for: 448 | // Win#e03c Music - USB#0c0193 is AL_AVCapturePlayback 449 | // Win#e064 Pictures 450 | // XKB#0080 XF86LaunchA 451 | // XKB#0099 XF86Send 452 | // XKB#009b XF86Xfer 453 | // XKB#009c XF86Launch1 454 | // XKB#009d XF86Launch2 455 | // XKB... remaining XF86 keys 456 | 457 | // KEY_BRIGHTNESS* added in Linux 3.16 458 | // http://www.usb.org/developers/hidpage/HUTRR41.pdf 459 | // USB evdev XKB Win Mac Code 460 | USB_KEYMAP(0x0c0060, 0x0166, 0x016e, 0x0000, 0xffff, NULL, INFO), 461 | USB_KEYMAP(0x0c0061, 0x0172, 0x017a, 0x0000, 0xffff, NULL, CLOSED_CAPTION_TOGGLE), 462 | USB_KEYMAP(0x0c006f, 0x00e1, 0x00e9, 0x0000, 0xffff, "BrightnessUp", BRIGHTNESS_UP), 463 | USB_KEYMAP(0x0c0070, 0x00e0, 0x00e8, 0x0000, 0xffff, "BrightnessDown", 464 | BRIGHTNESS_DOWN), // Display Brightness Decrement 465 | USB_KEYMAP(0x0c0072, 0x01af, 0x01b7, 0x0000, 0xffff, NULL, BRIGHTNESS_TOGGLE), 466 | USB_KEYMAP(0x0c0073, 0x0250, 0x0258, 0x0000, 0xffff, NULL, BRIGHTNESS_MINIMIUM), 467 | USB_KEYMAP(0x0c0074, 0x0251, 0x0259, 0x0000, 0xffff, NULL, BRIGHTNESS_MAXIMUM), 468 | USB_KEYMAP(0x0c0075, 0x00f4, 0x00fc, 0x0000, 0xffff, NULL, BRIGHTNESS_AUTO), 469 | USB_KEYMAP(0x0c0083, 0x0195, 0x019d, 0x0000, 0xffff, NULL, MEDIA_LAST), 470 | USB_KEYMAP(0x0c008c, 0x00a9, 0x00b1, 0x0000, 0xffff, NULL, LAUNCH_PHONE), 471 | USB_KEYMAP(0x0c008d, 0x016a, 0x0172, 0x0000, 0xffff, NULL, PROGRAM_GUIDE), 472 | USB_KEYMAP(0x0c0094, 0x00ae, 0x00b6, 0x0000, 0xffff, NULL, EXIT), 473 | USB_KEYMAP(0x0c009c, 0x019a, 0x01a2, 0x0000, 0xffff, NULL, CHANNEL_UP), 474 | USB_KEYMAP(0x0c009d, 0x019b, 0x01a3, 0x0000, 0xffff, NULL, CHANNEL_DOWN), 475 | 476 | // USB evdev XKB Win Mac 477 | USB_KEYMAP(0x0c00b0, 0x00cf, 0x00d7, 0x0000, 0xffff, "MediaPlay", MEDIA_PLAY), 478 | //USB_KEYMAP(0x0c00b1, 0x0077, 0x007f, 0x0000, 0xffff, "MediaPause", MEDIA_PAUSE), 479 | USB_KEYMAP(0x0c00b2, 0x00a7, 0x00af, 0x0000, 0xffff, "MediaRecord", MEDIA_RECORD), 480 | USB_KEYMAP(0x0c00b3, 0x00d0, 0x00d8, 0x0000, 0xffff, "MediaFastForward", MEDIA_FAST_FORWARD), 481 | USB_KEYMAP(0x0c00b4, 0x00a8, 0x00b0, 0x0000, 0xffff, "MediaRewind", MEDIA_REWIND), 482 | USB_KEYMAP(0x0c00b5, 0x00a3, 0x00ab, 0xe019, 0xffff, "MediaTrackNext", 483 | MEDIA_TRACK_NEXT), 484 | USB_KEYMAP(0x0c00b6, 0x00a5, 0x00ad, 0xe010, 0xffff, "MediaTrackPrevious", 485 | MEDIA_TRACK_PREVIOUS), 486 | USB_KEYMAP(0x0c00b7, 0x00a6, 0x00ae, 0xe024, 0xffff, "MediaStop", MEDIA_STOP), 487 | USB_KEYMAP(0x0c00b8, 0x00a1, 0x00a9, 0xe02c, 0xffff, "Eject", EJECT), 488 | USB_KEYMAP(0x0c00cd, 0x00a4, 0x00ac, 0xe022, 0xffff, "MediaPlayPause", 489 | MEDIA_PLAY_PAUSE), 490 | USB_KEYMAP(0x0c00cf, 0x0246, 0x024e, 0x0000, 0xffff, NULL, SPEECH_INPUT_TOGGLE), 491 | USB_KEYMAP(0x0c00e5, 0x00d1, 0x00d9, 0x0000, 0xffff, NULL, BASS_BOOST), 492 | //USB_KEYMAP(0x0c00e6, 0x0000, 0x0000, 0x0000, 0xffff, NULL, SURROUND_MODE), 493 | //USB_KEYMAP(0x0c0150, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BALANCE_RIGHT), 494 | //USB_KEYMAP(0x0c0151, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BALANCE_LEFT ), 495 | //USB_KEYMAP(0x0c0152, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BASS_INCREMENT), 496 | //USB_KEYMAP(0x0c0153, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BASS_DECREMENT), 497 | //USB_KEYMAP(0x0c0154, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_TREBLE_INCREMENT), 498 | //USB_KEYMAP(0x0c0155, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_TREBLE_DECREMENT), 499 | // USB#0c0183: AL Consumer Control Configuration 500 | USB_KEYMAP(0x0c0183, 0x00ab, 0x00b3, 0xe06d, 0xffff, "MediaSelect", MEDIA_SELECT), 501 | USB_KEYMAP(0x0c0184, 0x01a5, 0x01ad, 0x0000, 0xffff, NULL, LAUNCH_WORD_PROCESSOR), 502 | USB_KEYMAP(0x0c0186, 0x01a7, 0x01af, 0x0000, 0xffff, NULL, LAUNCH_SPREADSHEET), 503 | // USB#0x0c018a AL_EmailReader 504 | USB_KEYMAP(0x0c018a, 0x009b, 0x00a3, 0xe06c, 0xffff, "LaunchMail", LAUNCH_MAIL), 505 | // USB#0x0c018d: AL Contacts/Address Book 506 | USB_KEYMAP(0x0c018d, 0x01ad, 0x01b5, 0x0000, 0xffff, NULL, LAUNCH_CONTACTS), 507 | // USB#0x0c018e: AL Calendar/Schedule 508 | USB_KEYMAP(0x0c018e, 0x018d, 0x0195, 0x0000, 0xffff, NULL, LAUNCH_CALENDAR), 509 | // USB#0x0c018f AL Task/Project Manager 510 | //USB_KEYMAP(0x0c018f, 0x0241, 0x0249, 0x0000, 0xffff, NULL, LAUNCH_TASK_MANAGER), 511 | // USB#0x0c0190: AL Log/Journal/Timecard 512 | //USB_KEYMAP(0x0c0190, 0x0242, 0x024a, 0x0000, 0xffff, NULL, LAUNCH_LOG), 513 | // USB#0x0c0192: AL_Calculator 514 | USB_KEYMAP(0x0c0192, 0x008c, 0x0094, 0xe021, 0xffff, "LaunchApp2", LAUNCH_APP2), 515 | // USB#0c0194: My Computer (AL_LocalMachineBrowser) 516 | USB_KEYMAP(0x0c0194, 0x0090, 0x0098, 0xe06b, 0xffff, "LaunchApp1", LAUNCH_APP1), 517 | USB_KEYMAP(0x0c0196, 0x0096, 0x009e, 0x0000, 0xffff, NULL, LAUNCH_INTERNET_BROWSER), 518 | USB_KEYMAP(0x0c019C, 0x01b1, 0x01b9, 0x0000, 0xffff, NULL, LOG_OFF), 519 | // USB#0x0c019e: AL Terminal Lock/Screensaver 520 | USB_KEYMAP(0x0c019e, 0x0098, 0x00a0, 0x0000, 0xffff, NULL, LOCK_SCREEN), 521 | // USB#0x0c019f AL Control Panel 522 | USB_KEYMAP(0x0c019f, 0x0243, 0x024b, 0x0000, 0xffff, NULL, LAUNCH_CONTROL_PANEL), 523 | // USB#0x0c01a2: AL Select Task/Application 524 | USB_KEYMAP(0x0c01a2, 0x0244, 0x024c, 0x0000, 0xffff, "SelectTask", SELECT_TASK), 525 | // USB#0x0c01a7: AL_Documents 526 | USB_KEYMAP(0x0c01a7, 0x00eb, 0x00f3, 0x0000, 0xffff, NULL, LAUNCH_DOCUMENTS), 527 | USB_KEYMAP(0x0c01ab, 0x01b0, 0x01b8, 0x0000, 0xffff, NULL, SPELL_CHECK), 528 | // USB#0x0c01ae: AL Keyboard Layout 529 | USB_KEYMAP(0x0c01ae, 0x0176, 0x017e, 0x0000, 0xffff, NULL, LAUNCH_KEYBOARD_LAYOUT), 530 | USB_KEYMAP(0x0c01b1, 0x0245, 0x024d, 0x0000, 0xffff, "LaunchScreenSaver", 531 | LAUNCH_SCREEN_SAVER), // AL Screen Saver 532 | // USB#0c01b4: Home Directory (AL_FileBrowser) (Explorer) 533 | //USB_KEYMAP(0x0c01b4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LAUNCH_FILE_BROWSER), 534 | // USB#0x0c01b7: AL Audio Browser 535 | USB_KEYMAP(0x0c01b7, 0x0188, 0x0190, 0x0000, 0xffff, NULL, LAUNCH_AUDIO_BROWSER), 536 | // USB#0x0c0201: AC New 537 | USB_KEYMAP(0x0c0201, 0x00b5, 0x00bd, 0x0000, 0xffff, NULL, NEW), 538 | // USB#0x0c0203: AC Close 539 | USB_KEYMAP(0x0c0203, 0x00ce, 0x00d6, 0x0000, 0xffff, NULL, CLOSE), 540 | // USB#0x0c0207: AC Close 541 | USB_KEYMAP(0x0c0207, 0x00ea, 0x00f2, 0x0000, 0xffff, NULL, SAVE), 542 | // USB#0x0c0208: AC Print 543 | USB_KEYMAP(0x0c0208, 0x00d2, 0x00da, 0x0000, 0xffff, NULL, PRINT), 544 | // USB#0x0c0221: AC_Search 545 | USB_KEYMAP(0x0c0221, 0x00d9, 0x00e1, 0xe065, 0xffff, "BrowserSearch", BROWSER_SEARCH), 546 | // USB#0x0c0223: AC_Home 547 | USB_KEYMAP(0x0c0223, 0x00ac, 0x00b4, 0xe032, 0xffff, "BrowserHome", BROWSER_HOME), 548 | // USB#0x0c0224: AC_Back 549 | USB_KEYMAP(0x0c0224, 0x009e, 0x00a6, 0xe06a, 0xffff, "BrowserBack", BROWSER_BACK), 550 | // USB#0x0c0225: AC_Forward 551 | USB_KEYMAP(0x0c0225, 0x009f, 0x00a7, 0xe069, 0xffff, "BrowserForward", 552 | BROWSER_FORWARD), 553 | // USB#0x0c0226: AC_Stop 554 | USB_KEYMAP(0x0c0226, 0x0080, 0x0088, 0xe068, 0xffff, "BrowserStop", BROWSER_STOP), 555 | // USB#0x0c0227: AC_Refresh (Reload) 556 | USB_KEYMAP(0x0c0227, 0x00ad, 0x00b5, 0xe067, 0xffff, "BrowserRefresh", 557 | BROWSER_REFRESH), 558 | // USB#0x0c022a: AC_Bookmarks (Favorites) 559 | USB_KEYMAP(0x0c022a, 0x009c, 0x00a4, 0xe066, 0xffff, "BrowserFavorites", 560 | BROWSER_FAVORITES), 561 | USB_KEYMAP(0x0c022d, 0x01a2, 0x01aa, 0x0000, 0xffff, NULL, ZOOM_IN), 562 | USB_KEYMAP(0x0c022e, 0x01a3, 0x01ab, 0x0000, 0xffff, NULL, ZOOM_OUT), 563 | // USB#0x0c0230: AC Full Screen View 564 | //USB_KEYMAP(0x0c0230, 0x0000, 0x0000, 0x0000, 0xffff, NULL, ZOOM_FULL), 565 | // USB#0x0c0231: AC Normal View 566 | //USB_KEYMAP(0x0c0231, 0x0000, 0x0000, 0x0000, 0xffff, NULL, ZOOM_NORMAL), 567 | // USB#0x0c0232: AC View Toggle 568 | USB_KEYMAP(0x0c0232, 0x0000, 0x0000, 0x0000, 0xffff, "ZoomToggle", ZOOM_TOGGLE), 569 | // USB#0x0c0279: AC Redo/Repeat 570 | USB_KEYMAP(0x0c0279, 0x00b6, 0x00be, 0x0000, 0xffff, NULL, REDO), 571 | // USB#0x0c0289: AC_Reply 572 | USB_KEYMAP(0x0c0289, 0x00e8, 0x00f0, 0x0000, 0xffff, "MailReply", MAIL_REPLY), 573 | // USB#0x0c028b: AC_ForwardMsg (MailForward) 574 | USB_KEYMAP(0x0c028b, 0x00e9, 0x00f1, 0x0000, 0xffff, "MailForward", MAIL_FORWARD), 575 | // USB#0x0c028c: AC_Send 576 | USB_KEYMAP(0x0c028c, 0x00e7, 0x00ef, 0x0000, 0xffff, "MailSend", MAIL_SEND), 577 | }; 578 | -------------------------------------------------------------------------------- /lib/keyboard-layout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Emitter = require('event-kit').Emitter 4 | const { KeyboardLayoutManager } = require('../build/Release/keyboard-layout-manager.node') 5 | 6 | const emitter = new Emitter() 7 | const keymapsByLayoutName = new Map() 8 | 9 | const manager = new KeyboardLayoutManager(() => { 10 | emitter.emit('did-change-current-keyboard-layout', getCurrentKeyboardLayout()) 11 | }) 12 | 13 | function getCurrentKeymap () { 14 | const currentLayout = getCurrentKeyboardLayout() 15 | let currentKeymap = keymapsByLayoutName.get(currentLayout) 16 | if (!currentKeymap) { 17 | currentKeymap = manager.getCurrentKeymap() 18 | keymapsByLayoutName.set(currentLayout, currentKeymap) 19 | } 20 | return currentKeymap 21 | } 22 | 23 | function getCurrentKeyboardLayout () { 24 | return manager.getCurrentKeyboardLayout() 25 | } 26 | 27 | function getCurrentKeyboardLanguage () { 28 | return manager.getCurrentKeyboardLanguage() 29 | } 30 | 31 | function getInstalledKeyboardLanguages () { 32 | var languages = {} 33 | 34 | for (var language of (manager.getInstalledKeyboardLanguages() || [])) { 35 | languages[language] = true 36 | } 37 | 38 | return Object.keys(languages) 39 | } 40 | 41 | function onDidChangeCurrentKeyboardLayout (callback) { 42 | return emitter.on('did-change-current-keyboard-layout', callback) 43 | } 44 | 45 | function observeCurrentKeyboardLayout (callback) { 46 | callback(getCurrentKeyboardLayout()) 47 | return onDidChangeCurrentKeyboardLayout(callback) 48 | } 49 | 50 | module.exports = { 51 | getCurrentKeymap: getCurrentKeymap, 52 | getCurrentKeyboardLayout: getCurrentKeyboardLayout, 53 | getCurrentKeyboardLanguage: getCurrentKeyboardLanguage, 54 | getInstalledKeyboardLanguages: getInstalledKeyboardLanguages, 55 | onDidChangeCurrentKeyboardLayout: onDidChangeCurrentKeyboardLayout, 56 | observeCurrentKeyboardLayout: observeCurrentKeyboardLayout 57 | } 58 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keyboard-layout", 3 | "version": "2.0.17", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "amdefine": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 10 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 11 | "dev": true 12 | }, 13 | "async": { 14 | "version": "1.5.2", 15 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 16 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 17 | "dev": true 18 | }, 19 | "balanced-match": { 20 | "version": "1.0.0", 21 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 22 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 23 | "dev": true 24 | }, 25 | "brace-expansion": { 26 | "version": "1.1.11", 27 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 28 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 29 | "dev": true, 30 | "requires": { 31 | "balanced-match": "^1.0.0", 32 | "concat-map": "0.0.1" 33 | } 34 | }, 35 | "coffee-script": { 36 | "version": "1.12.7", 37 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", 38 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", 39 | "dev": true 40 | }, 41 | "coffeestack": { 42 | "version": "1.1.2", 43 | "resolved": "https://registry.npmjs.org/coffeestack/-/coffeestack-1.1.2.tgz", 44 | "integrity": "sha1-NSePO+uc5vXQraH7bgh4UrZXzpg=", 45 | "dev": true, 46 | "requires": { 47 | "coffee-script": "~1.8.0", 48 | "fs-plus": "^2.5.0", 49 | "source-map": "~0.1.43" 50 | }, 51 | "dependencies": { 52 | "coffee-script": { 53 | "version": "1.8.0", 54 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.8.0.tgz", 55 | "integrity": "sha1-nJ8dK0pSoADe0Vtll5FwNkgmPB0=", 56 | "dev": true, 57 | "requires": { 58 | "mkdirp": "~0.3.5" 59 | } 60 | } 61 | } 62 | }, 63 | "concat-map": { 64 | "version": "0.0.1", 65 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 66 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 67 | "dev": true 68 | }, 69 | "event-kit": { 70 | "version": "2.5.0", 71 | "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.0.tgz", 72 | "integrity": "sha512-tUDxeNC9JzN2Tw/f8mLtksY34v1hHmaR7lV7X4p04XSjaeUhFMfzjF6Nwov9e0EKGEx63BaKcgXKxjpQaPo0wg==" 73 | }, 74 | "fileset": { 75 | "version": "0.1.8", 76 | "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", 77 | "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=", 78 | "dev": true, 79 | "requires": { 80 | "glob": "3.x", 81 | "minimatch": "0.x" 82 | }, 83 | "dependencies": { 84 | "glob": { 85 | "version": "3.2.11", 86 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 87 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", 88 | "dev": true, 89 | "requires": { 90 | "inherits": "2", 91 | "minimatch": "0.3" 92 | }, 93 | "dependencies": { 94 | "minimatch": { 95 | "version": "0.3.0", 96 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 97 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", 98 | "dev": true, 99 | "requires": { 100 | "lru-cache": "2", 101 | "sigmund": "~1.0.0" 102 | } 103 | } 104 | } 105 | }, 106 | "minimatch": { 107 | "version": "0.4.0", 108 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", 109 | "integrity": "sha1-vSx9Bg0sjI/Xzefx8u0tWycP2xs=", 110 | "dev": true, 111 | "requires": { 112 | "lru-cache": "2", 113 | "sigmund": "~1.0.0" 114 | } 115 | } 116 | } 117 | }, 118 | "fs-plus": { 119 | "version": "2.10.1", 120 | "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-2.10.1.tgz", 121 | "integrity": "sha1-MgR4HXhAYR5jZOe2+wWMljJ8WqU=", 122 | "dev": true, 123 | "requires": { 124 | "async": "^1.5.2", 125 | "mkdirp": "^0.5.1", 126 | "rimraf": "^2.5.2", 127 | "underscore-plus": "1.x" 128 | }, 129 | "dependencies": { 130 | "mkdirp": { 131 | "version": "0.5.1", 132 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 133 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 134 | "dev": true, 135 | "requires": { 136 | "minimist": "0.0.8" 137 | } 138 | } 139 | } 140 | }, 141 | "fs.realpath": { 142 | "version": "1.0.0", 143 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 144 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 145 | "dev": true 146 | }, 147 | "gaze": { 148 | "version": "0.3.4", 149 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz", 150 | "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=", 151 | "dev": true, 152 | "requires": { 153 | "fileset": "~0.1.5", 154 | "minimatch": "~0.2.9" 155 | }, 156 | "dependencies": { 157 | "minimatch": { 158 | "version": "0.2.14", 159 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", 160 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", 161 | "dev": true, 162 | "requires": { 163 | "lru-cache": "2", 164 | "sigmund": "~1.0.0" 165 | } 166 | } 167 | } 168 | }, 169 | "glob": { 170 | "version": "7.1.2", 171 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 172 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 173 | "dev": true, 174 | "requires": { 175 | "fs.realpath": "^1.0.0", 176 | "inflight": "^1.0.4", 177 | "inherits": "2", 178 | "minimatch": "^3.0.4", 179 | "once": "^1.3.0", 180 | "path-is-absolute": "^1.0.0" 181 | } 182 | }, 183 | "inflight": { 184 | "version": "1.0.6", 185 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 186 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 187 | "dev": true, 188 | "requires": { 189 | "once": "^1.3.0", 190 | "wrappy": "1" 191 | } 192 | }, 193 | "inherits": { 194 | "version": "2.0.3", 195 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 196 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 197 | "dev": true 198 | }, 199 | "jasmine-focused": { 200 | "version": "1.0.7", 201 | "resolved": "https://registry.npmjs.org/jasmine-focused/-/jasmine-focused-1.0.7.tgz", 202 | "integrity": "sha1-uDx1fIAOaOHW78GjoaE/85/23NI=", 203 | "dev": true, 204 | "requires": { 205 | "jasmine-node": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", 206 | "underscore-plus": "1.x", 207 | "walkdir": "0.0.7" 208 | } 209 | }, 210 | "jasmine-node": { 211 | "version": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", 212 | "from": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", 213 | "dev": true, 214 | "requires": { 215 | "coffee-script": ">=1.0.1", 216 | "coffeestack": ">=1 <2", 217 | "gaze": "~0.3.2", 218 | "jasmine-reporters": ">=0.2.0", 219 | "mkdirp": "~0.3.5", 220 | "requirejs": ">=0.27.1", 221 | "underscore": ">= 1.3.1", 222 | "walkdir": ">= 0.0.1" 223 | } 224 | }, 225 | "jasmine-reporters": { 226 | "version": "2.3.2", 227 | "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz", 228 | "integrity": "sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==", 229 | "dev": true, 230 | "requires": { 231 | "mkdirp": "^0.5.1", 232 | "xmldom": "^0.1.22" 233 | }, 234 | "dependencies": { 235 | "mkdirp": { 236 | "version": "0.5.1", 237 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 238 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 239 | "dev": true, 240 | "requires": { 241 | "minimist": "0.0.8" 242 | } 243 | } 244 | } 245 | }, 246 | "lru-cache": { 247 | "version": "2.7.3", 248 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 249 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", 250 | "dev": true 251 | }, 252 | "minimatch": { 253 | "version": "3.0.4", 254 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 255 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 256 | "dev": true, 257 | "requires": { 258 | "brace-expansion": "^1.1.7" 259 | } 260 | }, 261 | "minimist": { 262 | "version": "0.0.8", 263 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 264 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 265 | "dev": true 266 | }, 267 | "mkdirp": { 268 | "version": "0.3.5", 269 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", 270 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", 271 | "dev": true 272 | }, 273 | "nan": { 274 | "version": "2.13.2", 275 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", 276 | "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" 277 | }, 278 | "once": { 279 | "version": "1.4.0", 280 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 281 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 282 | "dev": true, 283 | "requires": { 284 | "wrappy": "1" 285 | } 286 | }, 287 | "path-is-absolute": { 288 | "version": "1.0.1", 289 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 290 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 291 | "dev": true 292 | }, 293 | "requirejs": { 294 | "version": "2.3.5", 295 | "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz", 296 | "integrity": "sha512-svnO+aNcR/an9Dpi44C7KSAy5fFGLtmPbaaCeQaklUz8BQhS64tWWIIlvEA5jrWICzlO/X9KSzSeXFnZdBu8nw==", 297 | "dev": true 298 | }, 299 | "rimraf": { 300 | "version": "2.6.2", 301 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 302 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 303 | "dev": true, 304 | "requires": { 305 | "glob": "^7.0.5" 306 | } 307 | }, 308 | "sigmund": { 309 | "version": "1.0.1", 310 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 311 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 312 | "dev": true 313 | }, 314 | "source-map": { 315 | "version": "0.1.43", 316 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 317 | "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", 318 | "dev": true, 319 | "requires": { 320 | "amdefine": ">=0.0.4" 321 | } 322 | }, 323 | "underscore": { 324 | "version": "1.9.1", 325 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 326 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", 327 | "dev": true 328 | }, 329 | "underscore-plus": { 330 | "version": "1.6.8", 331 | "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz", 332 | "integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==", 333 | "dev": true, 334 | "requires": { 335 | "underscore": "~1.8.3" 336 | }, 337 | "dependencies": { 338 | "underscore": { 339 | "version": "1.8.3", 340 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", 341 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", 342 | "dev": true 343 | } 344 | } 345 | }, 346 | "walkdir": { 347 | "version": "0.0.7", 348 | "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.7.tgz", 349 | "integrity": "sha1-BNoCcKh6d4VAFzzb8KLbSZqNnik=", 350 | "dev": true 351 | }, 352 | "wrappy": { 353 | "version": "1.0.2", 354 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 355 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 356 | "dev": true 357 | }, 358 | "xmldom": { 359 | "version": "0.1.27", 360 | "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", 361 | "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", 362 | "dev": true 363 | } 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keyboard-layout", 3 | "version": "2.0.17", 4 | "description": "Read and observe the current keyboard layout on OS X.", 5 | "main": "./lib/keyboard-layout", 6 | "scripts": { 7 | "test": "jasmine-focused --captureExceptions --forceexit spec" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/atom/keyboard-layout.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/atom/keyboard-layout/issues" 15 | }, 16 | "licenses": [ 17 | { 18 | "type": "MIT", 19 | "url": "http://github.com/atom/keyboard-layout/raw/master/LICENSE.md" 20 | } 21 | ], 22 | "dependencies": { 23 | "event-kit": "^2.0.0", 24 | "nan": "^2.13.2" 25 | }, 26 | "devDependencies": { 27 | "jasmine-focused": "^1.0.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/keyboard-layout-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const KeyboardLayout = require('../lib/keyboard-layout') 4 | 5 | describe('Keyboard Layout', () => { 6 | if (process.platform === 'darwin' || process.platform === 'win32') { 7 | describe('.getCurrentKeymap()', function () { 8 | it('returns characters corresponding to the given DOM 3 keyboard event code based on the current keyboard layout', function () { 9 | const currentLayout = KeyboardLayout.getCurrentKeyboardLayout() 10 | switch (currentLayout) { 11 | case 'com.apple.keylayout.Dvorak': 12 | expect(KeyboardLayout.getCurrentKeymap()['KeyS']).toEqual({ 13 | unmodified: 'o', 14 | withShift: 'O', 15 | withAltGraph: 'ø', 16 | withAltGraphShift: 'Ø' 17 | }) 18 | break 19 | 20 | case '00010409': 21 | expect(KeyboardLayout.getCurrentKeymap()['KeyS']).toEqual({ 22 | unmodified: 'o', 23 | withShift: 'O', 24 | withAltGraph: null, 25 | withAltGraphShift: null 26 | }) 27 | break 28 | 29 | case 'com.apple.keylayout.US': 30 | expect(KeyboardLayout.getCurrentKeymap()['KeyS']).toEqual({ 31 | unmodified: 's', 32 | withShift: 'S', 33 | withAltGraph: 'ß', 34 | withAltGraphShift: 'Í' 35 | }) 36 | break; 37 | 38 | case '00000409': 39 | expect(KeyboardLayout.getCurrentKeymap()['KeyS']).toEqual({ 40 | unmodified: 's', 41 | withShift: 'S', 42 | withAltGraph: null, 43 | withAltGraphShift: null 44 | }) 45 | break 46 | 47 | default: 48 | throw new Error('No assertion defined for current keyboard layout: ' + currentLayout) 49 | } 50 | }) 51 | }) 52 | 53 | describe('.getCurrentKeyboardLayout()', () => { 54 | it('returns the current keyboard layout', () => { 55 | const layout = KeyboardLayout.getCurrentKeyboardLayout() 56 | expect(typeof layout).toBe('string') 57 | expect(layout.length).toBeGreaterThan(0) 58 | }) 59 | }) 60 | 61 | describe('.observeCurrentKeyboardLayout(callback)', () => { 62 | it('calls back immediately with the current keyboard layout', () => { 63 | const callback = jasmine.createSpy('observeCurrentKeyboardLayout') 64 | const disposable = KeyboardLayout.observeCurrentKeyboardLayout(callback) 65 | disposable.dispose() 66 | expect(callback.callCount).toBe(1) 67 | const layout = callback.argsForCall[0][0] 68 | expect(typeof layout).toBe('string') 69 | expect(layout.length).toBeGreaterThan(0) 70 | }) 71 | }) 72 | 73 | describe('.getCurrentKeyboardLanguage()', () => { 74 | it('returns the current keyboard language', () => { 75 | const language = KeyboardLayout.getCurrentKeyboardLanguage() 76 | expect(typeof language).toBe('string') 77 | expect(language.length).toBeGreaterThan(0) 78 | }) 79 | }) 80 | 81 | describe('.getInstalledKeyboardLanguages()', () => { 82 | it('returns an array of string keyboard languages', () => { 83 | const languages = KeyboardLayout.getInstalledKeyboardLanguages() 84 | expect(Array.isArray(languages)).toBe(true) 85 | 86 | if (!(process.platform === 'win32' && process.env.CI)) { 87 | expect(languages.length).toBeGreaterThan(0) 88 | } 89 | 90 | return (() => { 91 | for (const language of languages) { 92 | expect(typeof language).toBe('string') 93 | expect(language.length).toBeGreaterThan(0) 94 | } 95 | })() 96 | }) 97 | }) 98 | } 99 | 100 | // Smoke tests on Linux 101 | if (process.platform === 'linux') { 102 | describe('.getCurrentKeymap()', function () { 103 | it('returns a keymap with unmodified and shift-modified keys without blowing up (basic smoke test)', function () { 104 | const keymap = KeyboardLayout.getCurrentKeymap() 105 | expect(keymap.KeyG.unmodified).toBeDefined() 106 | expect(keymap.KeyG.withShift).toBeDefined() 107 | }) 108 | }) 109 | 110 | describe('.getCurrentKeyboardLayout()', function () { 111 | it('returns an identifier for the current keyboard layout (basic smoke test)', function () { 112 | expect(KeyboardLayout.getCurrentKeyboardLayout()).toBeDefined() 113 | }) 114 | }) 115 | } 116 | }) 117 | -------------------------------------------------------------------------------- /src/keyboard-layout-manager-linux.cc: -------------------------------------------------------------------------------- 1 | #include "keyboard-layout-manager.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | KeyboardLayoutManager::KeyboardLayoutManager(v8::Isolate *isolate, Nan::Callback *callback) : xInputContext{nullptr}, xInputMethod{nullptr}, isolate_(isolate), callback{callback} { 10 | xDisplay = XOpenDisplay(""); 11 | if (!xDisplay) { 12 | Nan::ThrowError("Could not connect to X display."); 13 | return; 14 | } 15 | 16 | xInputMethod = XOpenIM(xDisplay, 0, 0, 0); 17 | if (!xInputMethod) { 18 | return; 19 | } 20 | 21 | XIMStyles* styles = 0; 22 | if (XGetIMValues(xInputMethod, XNQueryInputStyle, &styles, NULL) || !styles) { 23 | return; 24 | } 25 | 26 | XIMStyle bestMatchStyle = 0; 27 | for (int i = 0; i < styles->count_styles; i++) { 28 | XIMStyle thisStyle = styles->supported_styles[i]; 29 | if (thisStyle == (XIMPreeditNothing | XIMStatusNothing)) 30 | { 31 | bestMatchStyle = thisStyle; 32 | break; 33 | } 34 | } 35 | XFree(styles); 36 | if (!bestMatchStyle) { 37 | return; 38 | } 39 | 40 | Window window; 41 | int revert_to; 42 | XGetInputFocus(xDisplay, &window, &revert_to); 43 | if (window != BadRequest) { 44 | xInputContext = XCreateIC( 45 | xInputMethod, XNInputStyle, bestMatchStyle, XNClientWindow, window, 46 | XNFocusWindow, window, NULL 47 | ); 48 | } 49 | } 50 | 51 | KeyboardLayoutManager::~KeyboardLayoutManager() { 52 | if (xInputContext) { 53 | XDestroyIC(xInputContext); 54 | } 55 | 56 | if (xInputMethod) { 57 | XCloseIM(xInputMethod); 58 | } 59 | 60 | XCloseDisplay(xDisplay); 61 | delete callback; 62 | }; 63 | 64 | void KeyboardLayoutManager::HandleKeyboardLayoutChanged() { 65 | } 66 | 67 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeyboardLayout) { 68 | Nan::HandleScope scope; 69 | KeyboardLayoutManager* manager = Nan::ObjectWrap::Unwrap(info.Holder()); 70 | v8::Local result; 71 | 72 | XkbRF_VarDefsRec vdr; 73 | char *tmp = NULL; 74 | if (XkbRF_GetNamesProp(manager->xDisplay, &tmp, &vdr) && vdr.layout) { 75 | XkbStateRec xkbState; 76 | XkbGetState(manager->xDisplay, XkbUseCoreKbd, &xkbState); 77 | if (vdr.variant) { 78 | result = Nan::New(std::string(vdr.layout) + "," + std::string(vdr.variant) + " [" + std::to_string(xkbState.group) + "]").ToLocalChecked(); 79 | } else { 80 | result = Nan::New(std::string(vdr.layout) + " [" + std::to_string(xkbState.group) + "]").ToLocalChecked(); 81 | } 82 | } else { 83 | result = Nan::Null(); 84 | } 85 | 86 | info.GetReturnValue().Set(result); 87 | 88 | return; 89 | } 90 | 91 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeyboardLanguage) { 92 | return GetCurrentKeyboardLayout(info); 93 | } 94 | 95 | NAN_METHOD(KeyboardLayoutManager::GetInstalledKeyboardLanguages) { 96 | Nan::HandleScope scope; 97 | return; 98 | } 99 | 100 | struct KeycodeMapEntry { 101 | uint xkbKeycode; 102 | const char *dom3Code; 103 | }; 104 | 105 | #define USB_KEYMAP_DECLARATION static const KeycodeMapEntry keyCodeMap[] = 106 | #define USB_KEYMAP(usb, evdev, xkb, win, mac, code, id) {xkb, code} 107 | 108 | #include "keycode_converter_data.inc" 109 | 110 | v8::Local CharacterForNativeCode(XIC xInputContext, XKeyEvent *keyEvent, uint xkbKeycode, uint state) { 111 | keyEvent->keycode = xkbKeycode; 112 | keyEvent->state = state; 113 | 114 | if (xInputContext) { 115 | wchar_t characters[2]; 116 | int count = XwcLookupString(xInputContext, keyEvent, characters, 2, NULL, NULL); 117 | if (count > 0 && !std::iswcntrl(characters[0])) { 118 | return Nan::New(reinterpret_cast(characters), count).ToLocalChecked(); 119 | } else { 120 | return Nan::Null(); 121 | } 122 | } else { 123 | // Graceful fallback for systems where no window is open or no input context 124 | // can be found. 125 | char characters[2]; 126 | int count = XLookupString(keyEvent, characters, 2, NULL, NULL); 127 | if (count > 0 && !std::iscntrl(characters[0])) { 128 | return Nan::New(reinterpret_cast(characters), count).ToLocalChecked(); 129 | } else { 130 | return Nan::Null(); 131 | } 132 | } 133 | } 134 | 135 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeymap) { 136 | v8::Local result = Nan::New(); 137 | KeyboardLayoutManager* manager = Nan::ObjectWrap::Unwrap(info.Holder()); 138 | v8::Local unmodifiedKey = Nan::New("unmodified").ToLocalChecked(); 139 | v8::Local withShiftKey = Nan::New("withShift").ToLocalChecked(); 140 | 141 | // Clear cached keymap 142 | XMappingEvent eventMap = {MappingNotify, 0, false, manager->xDisplay, 0, MappingKeyboard, 0, 0}; 143 | XRefreshKeyboardMapping(&eventMap); 144 | 145 | XkbStateRec xkbState; 146 | XkbGetState(manager->xDisplay, XkbUseCoreKbd, &xkbState); 147 | uint keyboardBaseState = 0x0000; 148 | if (xkbState.group == 1) { 149 | keyboardBaseState = 0x2000; 150 | } else if (xkbState.group == 2) { 151 | keyboardBaseState = 0x4000; 152 | } 153 | 154 | // Set up an event to reuse across CharacterForNativeCode calls 155 | XEvent event; 156 | memset(&event, 0, sizeof(XEvent)); 157 | XKeyEvent* keyEvent = &event.xkey; 158 | keyEvent->display = manager->xDisplay; 159 | keyEvent->type = KeyPress; 160 | 161 | size_t keyCodeMapSize = sizeof(keyCodeMap) / sizeof(keyCodeMap[0]); 162 | for (size_t i = 0; i < keyCodeMapSize; i++) { 163 | const char *dom3Code = keyCodeMap[i].dom3Code; 164 | uint xkbKeycode = keyCodeMap[i].xkbKeycode; 165 | 166 | if (dom3Code && xkbKeycode > 0x0000) { 167 | v8::Local dom3CodeKey = Nan::New(dom3Code).ToLocalChecked(); 168 | v8::Local unmodified = CharacterForNativeCode(manager->xInputContext, keyEvent, xkbKeycode, keyboardBaseState); 169 | v8::Local withShift = CharacterForNativeCode(manager->xInputContext, keyEvent, xkbKeycode, keyboardBaseState | ShiftMask); 170 | 171 | if (unmodified->IsString() || withShift->IsString()) { 172 | v8::Local entry = Nan::New(); 173 | Nan::Set(entry, unmodifiedKey, unmodified); 174 | Nan::Set(entry, withShiftKey, withShift); 175 | Nan::Set(result, dom3CodeKey, entry); 176 | } 177 | } 178 | } 179 | 180 | info.GetReturnValue().Set(result); 181 | } 182 | -------------------------------------------------------------------------------- /src/keyboard-layout-manager-mac.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import "keyboard-layout-manager.h" 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace v8; 11 | 12 | uv_loop_t *loop = uv_default_loop(); 13 | uv_async_t async; 14 | 15 | static void notificationHandler(CFNotificationCenterRef center, void *manager, CFStringRef name, const void *object, CFDictionaryRef userInfo) { 16 | async.data = manager; 17 | uv_async_send(&async); 18 | } 19 | 20 | static void asyncSendHandler(uv_async_t *handle) { 21 | (static_cast(handle->data))->HandleKeyboardLayoutChanged(); 22 | } 23 | 24 | static void RemoveCFObserver(void *arg) { 25 | auto manager = static_cast(arg); 26 | CFNotificationCenterRemoveObserver( 27 | CFNotificationCenterGetDistributedCenter(), 28 | manager, 29 | kTISNotifySelectedKeyboardInputSourceChanged, 30 | NULL 31 | ); 32 | } 33 | 34 | KeyboardLayoutManager::KeyboardLayoutManager(v8::Isolate *isolate, Nan::Callback *callback) : isolate_(isolate), callback(callback) { 35 | uv_async_init(loop, &async, (uv_async_cb) asyncSendHandler); 36 | 37 | CFNotificationCenterAddObserver( 38 | CFNotificationCenterGetDistributedCenter(), 39 | this, 40 | notificationHandler, 41 | kTISNotifySelectedKeyboardInputSourceChanged, 42 | NULL, 43 | CFNotificationSuspensionBehaviorDeliverImmediately 44 | ); 45 | 46 | #if NODE_MAJOR_VERSION >= 10 47 | node::AddEnvironmentCleanupHook( 48 | isolate, 49 | RemoveCFObserver, 50 | const_cast(static_cast(nullptr))); 51 | #endif 52 | } 53 | 54 | KeyboardLayoutManager::~KeyboardLayoutManager() { 55 | #if NODE_MAJOR_VERSION >= 10 56 | node::RemoveEnvironmentCleanupHook( 57 | isolate(), 58 | RemoveCFObserver, 59 | const_cast(static_cast(this)) 60 | ); 61 | #endif 62 | RemoveCFObserver(this); 63 | delete callback; 64 | }; 65 | 66 | void KeyboardLayoutManager::HandleKeyboardLayoutChanged() { 67 | Nan::AsyncResource async_resource("keyboard_layout:HandleKeyboardLayoutChanged"); 68 | callback->Call(0, NULL, &async_resource); 69 | } 70 | 71 | NAN_METHOD(KeyboardLayoutManager::GetInstalledKeyboardLanguages) { 72 | Nan::HandleScope scope; 73 | 74 | @autoreleasepool { 75 | std::vector ret; 76 | 77 | // NB: We have to do this whole rigamarole twice, once for IMEs (i.e. 78 | // Japanese), and once for keyboard layouts (i.e. English). 79 | NSDictionary* filter = @{ (__bridge NSString *) kTISPropertyInputSourceType : (__bridge NSString *) kTISTypeKeyboardLayout }; 80 | NSArray* keyboardLayouts = (NSArray *) TISCreateInputSourceList((__bridge CFDictionaryRef) filter, NO); 81 | 82 | for (size_t i=0; i < keyboardLayouts.count; i++) { 83 | TISInputSourceRef current = (TISInputSourceRef)[keyboardLayouts objectAtIndex:i]; 84 | 85 | NSArray* langs = (NSArray*) TISGetInputSourceProperty(current, kTISPropertyInputSourceLanguages); 86 | std::string str = std::string([(NSString*)[langs objectAtIndex:0] UTF8String]); 87 | ret.push_back(str); 88 | } 89 | 90 | filter = @{ (__bridge NSString *) kTISPropertyInputSourceType : (__bridge NSString *) kTISTypeKeyboardInputMode }; 91 | keyboardLayouts = (NSArray *) TISCreateInputSourceList((__bridge CFDictionaryRef) filter, NO); 92 | 93 | for (size_t i=0; i < keyboardLayouts.count; i++) { 94 | TISInputSourceRef current = (TISInputSourceRef)[keyboardLayouts objectAtIndex:i]; 95 | 96 | NSArray* langs = (NSArray*) TISGetInputSourceProperty(current, kTISPropertyInputSourceLanguages); 97 | std::string str = std::string([(NSString*)[langs objectAtIndex:0] UTF8String]); 98 | ret.push_back(str); 99 | } 100 | 101 | Local result = Nan::New(ret.size()); 102 | for (size_t i = 0; i < ret.size(); ++i) { 103 | const std::string& lang = ret[i]; 104 | Nan::Set(result, i, Nan::New(lang.data(), lang.size()).ToLocalChecked()); 105 | } 106 | 107 | info.GetReturnValue().Set(result); 108 | } 109 | } 110 | 111 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeyboardLanguage) { 112 | Nan::HandleScope scope; 113 | TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); 114 | 115 | NSArray* langs = (NSArray*) TISGetInputSourceProperty(source, kTISPropertyInputSourceLanguages); 116 | NSString* lang = (NSString*) [langs objectAtIndex:0]; 117 | 118 | info.GetReturnValue().Set(Nan::New([lang UTF8String]).ToLocalChecked()); 119 | } 120 | 121 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeyboardLayout) { 122 | Nan::HandleScope scope; 123 | TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); 124 | CFStringRef sourceId = (CFStringRef) TISGetInputSourceProperty(source, kTISPropertyInputSourceID); 125 | info.GetReturnValue().Set(Nan::New([(NSString *)sourceId UTF8String]).ToLocalChecked()); 126 | } 127 | 128 | struct KeycodeMapEntry { 129 | UInt16 virtualKeyCode; 130 | const char *dom3Code; 131 | }; 132 | 133 | #define USB_KEYMAP_DECLARATION static const KeycodeMapEntry keyCodeMap[] = 134 | #define USB_KEYMAP(usb, evdev, xkb, win, mac, code, id) {mac, code} 135 | 136 | #include "keycode_converter_data.inc" 137 | 138 | Local CharacterForNativeCode(const UCKeyboardLayout* keyboardLayout, UInt16 virtualKeyCode, EventModifiers modifiers) { 139 | // See https://developer.apple.com/reference/coreservices/1390584-uckeytranslate?language=objc 140 | UInt32 modifierKeyState = (modifiers >> 8) & 0xFF; 141 | UInt32 deadKeyState = 0; 142 | UniChar characters[4] = {0, 0, 0, 0}; 143 | UniCharCount charCount = 0; 144 | OSStatus status = UCKeyTranslate( 145 | keyboardLayout, 146 | static_cast(virtualKeyCode), 147 | kUCKeyActionDown, 148 | modifierKeyState, 149 | LMGetKbdLast(), 150 | kUCKeyTranslateNoDeadKeysBit, 151 | &deadKeyState, 152 | sizeof(characters) / sizeof(characters[0]), 153 | &charCount, 154 | characters); 155 | 156 | // If the previous key was dead, translate again with the same dead key 157 | // state to get a printable character. 158 | if (status == noErr && deadKeyState != 0) { 159 | status = UCKeyTranslate( 160 | keyboardLayout, 161 | static_cast(virtualKeyCode), 162 | kUCKeyActionDown, 163 | modifierKeyState, 164 | LMGetKbdLast(), 165 | kUCKeyTranslateNoDeadKeysBit, 166 | &deadKeyState, 167 | sizeof(characters) / sizeof(characters[0]), 168 | &charCount, 169 | characters); 170 | } 171 | 172 | if (status == noErr && !std::iscntrl(characters[0])) { 173 | return Nan::New(static_cast(characters), static_cast(charCount)).ToLocalChecked(); 174 | } else { 175 | return Nan::Null(); 176 | } 177 | } 178 | 179 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeymap) { 180 | TISInputSourceRef source = TISCopyCurrentKeyboardLayoutInputSource(); 181 | CFDataRef layoutData = static_cast(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)); 182 | 183 | if (layoutData == NULL) { 184 | info.GetReturnValue().Set(Nan::Null()); 185 | return; 186 | } 187 | 188 | const UCKeyboardLayout* keyboardLayout = reinterpret_cast(CFDataGetBytePtr(layoutData)); 189 | 190 | Local result = Nan::New(); 191 | Local unmodifiedKey = Nan::New("unmodified").ToLocalChecked(); 192 | Local withShiftKey = Nan::New("withShift").ToLocalChecked(); 193 | Local withAltGraphKey = Nan::New("withAltGraph").ToLocalChecked(); 194 | Local withAltGraphShiftKey = Nan::New("withAltGraphShift").ToLocalChecked(); 195 | 196 | size_t keyCodeMapSize = sizeof(keyCodeMap) / sizeof(keyCodeMap[0]); 197 | for (size_t i = 0; i < keyCodeMapSize; i++) { 198 | const char *dom3Code = keyCodeMap[i].dom3Code; 199 | int virtualKeyCode = keyCodeMap[i].virtualKeyCode; 200 | if (dom3Code && virtualKeyCode < 0xffff) { 201 | Local dom3CodeKey = Nan::New(dom3Code).ToLocalChecked(); 202 | 203 | Local unmodified = CharacterForNativeCode(keyboardLayout, virtualKeyCode, 0); 204 | Local withShift = CharacterForNativeCode(keyboardLayout, virtualKeyCode, (1 << shiftKeyBit)); 205 | Local withAltGraph = CharacterForNativeCode(keyboardLayout, virtualKeyCode, (1 << optionKeyBit)); 206 | Local withAltGraphShift = CharacterForNativeCode(keyboardLayout, virtualKeyCode, (1 << shiftKeyBit) | (1 << optionKeyBit)); 207 | 208 | if (unmodified->IsString() || withShift->IsString() || withAltGraph->IsString() || withAltGraphShift->IsString()) { 209 | Local entry = Nan::New(); 210 | Nan::Set(entry, unmodifiedKey, unmodified); 211 | Nan::Set(entry, withShiftKey, withShift); 212 | Nan::Set(entry, withAltGraphKey, withAltGraph); 213 | Nan::Set(entry, withAltGraphShiftKey, withAltGraphShift); 214 | 215 | Nan::Set(result, dom3CodeKey, entry); 216 | } 217 | } 218 | } 219 | 220 | info.GetReturnValue().Set(result); 221 | } 222 | -------------------------------------------------------------------------------- /src/keyboard-layout-manager-windows.cc: -------------------------------------------------------------------------------- 1 | #undef _WIN32_WINNT 2 | #define _WIN32_WINNT 0x0601 3 | 4 | #undef WINVER 5 | #define WINVER 0x0601 6 | 7 | #define SPACE_SCAN_CODE 0x0039 8 | 9 | #include "keyboard-layout-manager.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace v8; 16 | 17 | std::string ToUTF8(const std::wstring& string) { 18 | if (string.length() < 1) { 19 | return std::string(); 20 | } 21 | 22 | // NB: In the pathological case, each character could expand up 23 | // to 4 bytes in UTF8. 24 | int cbLen = (string.length()+1) * sizeof(char) * 4; 25 | char* buf = new char[cbLen]; 26 | int retLen = WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), buf, cbLen, NULL, NULL); 27 | buf[retLen] = 0; 28 | 29 | std::string ret; 30 | ret.assign(buf); 31 | return ret; 32 | } 33 | 34 | HKL GetForegroundWindowHKL() { 35 | DWORD dwThreadId = 0; 36 | HWND hWnd = GetForegroundWindow(); 37 | if (hWnd != NULL) { 38 | dwThreadId = GetWindowThreadProcessId(hWnd, NULL); 39 | } 40 | return GetKeyboardLayout(dwThreadId); 41 | } 42 | 43 | KeyboardLayoutManager::KeyboardLayoutManager(v8::Isolate *isolate, Nan::Callback *callback) : isolate_(isolate), callback(callback) {} 44 | 45 | KeyboardLayoutManager::~KeyboardLayoutManager() { 46 | delete callback; 47 | }; 48 | 49 | void KeyboardLayoutManager::HandleKeyboardLayoutChanged() { 50 | } 51 | 52 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeyboardLayout) { 53 | Nan::HandleScope scope; 54 | 55 | ActivateKeyboardLayout(GetForegroundWindowHKL(), 0); 56 | char layoutName[KL_NAMELENGTH]; 57 | if (::GetKeyboardLayoutName(layoutName)) 58 | info.GetReturnValue().Set(Nan::New(layoutName).ToLocalChecked()); 59 | else 60 | info.GetReturnValue().Set(Nan::Undefined()); 61 | } 62 | 63 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeyboardLanguage) { 64 | Nan::HandleScope scope; 65 | 66 | HKL layout = GetForegroundWindowHKL(); 67 | 68 | wchar_t buf[LOCALE_NAME_MAX_LENGTH]; 69 | std::wstring wstr; 70 | LCIDToLocaleName(MAKELCID((UINT)layout & 0xFFFF, SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); 71 | wstr.assign(buf); 72 | 73 | std::string str = ToUTF8(wstr); 74 | info.GetReturnValue().Set(Nan::New(str.data(), str.size()).ToLocalChecked()); 75 | } 76 | 77 | NAN_METHOD(KeyboardLayoutManager::GetInstalledKeyboardLanguages) { 78 | Nan::HandleScope scope; 79 | 80 | int layoutCount = GetKeyboardLayoutList(0, NULL); 81 | HKL* layouts = new HKL[layoutCount]; 82 | GetKeyboardLayoutList(layoutCount, layouts); 83 | 84 | Local result = Nan::New(layoutCount); 85 | wchar_t buf[LOCALE_NAME_MAX_LENGTH]; 86 | 87 | for (int i=0; i < layoutCount; i++) { 88 | std::wstring wstr; 89 | LCIDToLocaleName(MAKELCID((UINT)layouts[i] & 0xFFFF, SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); 90 | wstr.assign(buf); 91 | 92 | std::string str = ToUTF8(wstr); 93 | Nan::Set(result, i, Nan::New(str.data(), str.size()).ToLocalChecked()); 94 | } 95 | 96 | delete[] layouts; 97 | info.GetReturnValue().Set(result); 98 | } 99 | 100 | struct KeycodeMapEntry { 101 | UINT scanCode; 102 | const char *dom3Code; 103 | }; 104 | 105 | #define USB_KEYMAP_DECLARATION static const KeycodeMapEntry keyCodeMap[] = 106 | #define USB_KEYMAP(usb, evdev, xkb, win, mac, code, id) {win, code} 107 | 108 | #include "keycode_converter_data.inc" 109 | 110 | Local CharacterForNativeCode(HKL keyboardLayout, UINT keyCode, UINT scanCode, 111 | BYTE *keyboardState, bool shift, bool altGraph) { 112 | memset(keyboardState, 0, 256); 113 | if (shift) { 114 | keyboardState[VK_SHIFT] = 0x80; 115 | } 116 | 117 | if (altGraph) { 118 | keyboardState[VK_MENU] = 0x80; 119 | keyboardState[VK_CONTROL] = 0x80; 120 | } 121 | 122 | wchar_t characters[5]; 123 | int count = ToUnicodeEx(keyCode, scanCode, keyboardState, characters, 5, 0, keyboardLayout); 124 | 125 | // The check to detect and skip running this function for dead keys does not 126 | // account for modifier state. For layouts that map dead keys to AltGraph or 127 | // Shift-AltGraph we still have to detect and clear the key out of the 128 | // kernel-mode keyboard buffer so the keymap for subsequent keys is correctly 129 | // translated and not affected by the dead key. 130 | if (count == -1) { // Dead key 131 | // Dead keys are not cleared if both AltGraph and Shift is held down so 132 | // we clear this keyboard state to ensure that it is cleared correctly. 133 | keyboardState[VK_SHIFT] = 0x0; 134 | keyboardState[VK_MENU] = 0x0; 135 | keyboardState[VK_CONTROL] = 0x0; 136 | 137 | // Clear dead key out of kernel-mode keyboard buffer so subsequent translations are not affected 138 | UINT spaceKeyCode = MapVirtualKeyEx(SPACE_SCAN_CODE, MAPVK_VSC_TO_VK, keyboardLayout); 139 | ToUnicodeEx(spaceKeyCode, SPACE_SCAN_CODE, keyboardState, characters, 5, 0, keyboardLayout); 140 | 141 | // Don't translate dead keys 142 | return Nan::Null(); 143 | } else if (count > 0 && !std::iswcntrl(characters[0])) { 144 | return Nan::New(reinterpret_cast(characters), count).ToLocalChecked(); 145 | } else { 146 | return Nan::Null(); 147 | } 148 | } 149 | 150 | NAN_METHOD(KeyboardLayoutManager::GetCurrentKeymap) { 151 | BYTE keyboardState[256]; 152 | HKL keyboardLayout = GetForegroundWindowHKL(); 153 | 154 | Local result = Nan::New(); 155 | Local unmodifiedKey = Nan::New("unmodified").ToLocalChecked(); 156 | Local withShiftKey = Nan::New("withShift").ToLocalChecked(); 157 | Local withAltGraphKey = Nan::New("withAltGraph").ToLocalChecked(); 158 | Local withAltGraphShiftKey = Nan::New("withAltGraphShift").ToLocalChecked(); 159 | 160 | size_t keyCodeMapSize = sizeof(keyCodeMap) / sizeof(keyCodeMap[0]); 161 | for (size_t i = 0; i < keyCodeMapSize; i++) { 162 | const char *dom3Code = keyCodeMap[i].dom3Code; 163 | UINT scanCode = keyCodeMap[i].scanCode; 164 | 165 | if (dom3Code && scanCode > 0x0000) { 166 | UINT keyCode = MapVirtualKeyEx(scanCode, MAPVK_VSC_TO_VK, keyboardLayout); 167 | 168 | // Detect and skip dead keys. If the most significant bit of the returned 169 | // character value is 1, this is a dead key. Trying to translate it to a 170 | // character will mutate the Windows keyboard buffer and blow away pending 171 | // dead keys. To avoid this bug, we just refuse to map dead keys to 172 | // characters. 173 | if ((MapVirtualKeyEx(keyCode, MAPVK_VK_TO_CHAR, keyboardLayout) >> (sizeof(UINT) * 8 - 1))) continue; 174 | 175 | Local dom3CodeKey = Nan::New(dom3Code).ToLocalChecked(); 176 | Local unmodified = CharacterForNativeCode(keyboardLayout, keyCode, scanCode, keyboardState, false, false); 177 | Local withShift = CharacterForNativeCode(keyboardLayout, keyCode, scanCode, keyboardState, true, false); 178 | Local withAltGraph = CharacterForNativeCode(keyboardLayout, keyCode, scanCode, keyboardState, false, true); 179 | Local withAltGraphShift = CharacterForNativeCode(keyboardLayout, keyCode, scanCode, keyboardState, true, true); 180 | 181 | if (unmodified->IsString() || withShift->IsString() || withAltGraph->IsString() || withAltGraphShift->IsString()) { 182 | Local entry = Nan::New(); 183 | Nan::Set(entry, unmodifiedKey, unmodified); 184 | Nan::Set(entry, withShiftKey, withShift); 185 | Nan::Set(entry, withAltGraphKey, withAltGraph); 186 | Nan::Set(entry, withAltGraphShiftKey, withAltGraphShift); 187 | 188 | Nan::Set(result, dom3CodeKey, entry); 189 | } 190 | } 191 | } 192 | 193 | info.GetReturnValue().Set(result); 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/keyboard-layout-manager.cc: -------------------------------------------------------------------------------- 1 | #include "keyboard-layout-manager.h" 2 | 3 | using namespace v8; 4 | 5 | NAN_MODULE_INIT(init) { 6 | Nan::HandleScope scope; 7 | Local newTemplate = Nan::New(KeyboardLayoutManager::New); 8 | newTemplate->SetClassName(Nan::New("KeyboardLayoutManager").ToLocalChecked()); 9 | newTemplate->InstanceTemplate()->SetInternalFieldCount(1); 10 | 11 | Local proto = newTemplate->PrototypeTemplate(); 12 | 13 | Nan::SetMethod(proto, "getCurrentKeyboardLayout", KeyboardLayoutManager::GetCurrentKeyboardLayout); 14 | Nan::SetMethod(proto, "getCurrentKeyboardLanguage", KeyboardLayoutManager::GetCurrentKeyboardLanguage); 15 | Nan::SetMethod(proto, "getInstalledKeyboardLanguages", KeyboardLayoutManager::GetInstalledKeyboardLanguages); 16 | Nan::SetMethod(proto, "getCurrentKeymap", KeyboardLayoutManager::GetCurrentKeymap); 17 | 18 | Nan::Set(target, Nan::New("KeyboardLayoutManager").ToLocalChecked(), Nan::GetFunction(newTemplate).ToLocalChecked()); 19 | } 20 | 21 | #if NODE_MAJOR_VERSION >= 10 22 | NAN_MODULE_WORKER_ENABLED(keyboard_layout_manager, init) 23 | #else 24 | NODE_MODULE(keyboard_layout_manager, init) 25 | #endif 26 | 27 | NAN_METHOD(KeyboardLayoutManager::New) { 28 | Nan::HandleScope scope; 29 | Local callbackHandle = info[0].As(); 30 | Nan::Callback *callback = new Nan::Callback(callbackHandle); 31 | 32 | KeyboardLayoutManager *manager = new KeyboardLayoutManager(info.GetIsolate(), callback); 33 | manager->Wrap(info.This()); 34 | return; 35 | } 36 | -------------------------------------------------------------------------------- /src/keyboard-layout-manager.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_KEYBORD_LAYOUT_OBSERVER_H_ 2 | #define SRC_KEYBORD_LAYOUT_OBSERVER_H_ 3 | 4 | #include "nan.h" 5 | 6 | #if defined(__linux__) || defined(__FreeBSD__) 7 | #include 8 | #endif // __linux__ || __FreeBSD__ 9 | 10 | class KeyboardLayoutManager : public Nan::ObjectWrap { 11 | public: 12 | void HandleKeyboardLayoutChanged(); 13 | 14 | static NAN_METHOD(New); 15 | static NAN_METHOD(GetCurrentKeyboardLayout); 16 | static NAN_METHOD(GetCurrentKeyboardLanguage); 17 | static NAN_METHOD(GetInstalledKeyboardLanguages); 18 | static NAN_METHOD(GetCurrentKeymap); 19 | 20 | private: 21 | KeyboardLayoutManager(v8::Isolate* isolate, Nan::Callback *callback); 22 | ~KeyboardLayoutManager(); 23 | 24 | v8::Isolate* isolate() { return isolate_; } 25 | 26 | #if defined(__linux__) || defined(__FreeBSD__) 27 | Display *xDisplay; 28 | XIC xInputContext; 29 | XIM xInputMethod; 30 | #endif 31 | 32 | v8::Isolate *isolate_; 33 | Nan::Callback *callback; 34 | }; 35 | 36 | #endif // SRC_KEYBORD_LAYOUT_OBSERVER_H_ 37 | --------------------------------------------------------------------------------