├── .gitignore ├── README.md ├── VialToKeymapDrawerJS.hxproj ├── VialToKeymapDrawerNeko.hxproj ├── bin ├── VialToKeymapDrawer-example.bat ├── VialToKeymapDrawer-example.sh ├── pack.bat ├── test-all.bat ├── test-one.bat ├── test.bat ├── yal-sofle.yaml └── yal-sofle │ ├── encoder-defs.txt │ ├── key-labels.txt │ ├── layer-names.txt │ ├── move-defs.txt │ └── yal-sofle.vil ├── build-neko.hxml ├── build-web.hxml ├── docs ├── FileSaver.min.js ├── VialToKeymapDrawer.js ├── examples │ ├── cepstrum.json │ ├── cepstrum.vil │ ├── corvett-piantor.json │ ├── corvett-piantor.vil │ ├── key-range-editor.mp4 │ ├── yal-sofle.json │ └── yal-sofle.vil ├── index.html ├── qmk_keyboards.js ├── style.css └── style.less ├── example.svg ├── media ├── cli.png ├── example-blue.png └── web.png └── src ├── CommandLineOption.hx ├── Main.hx ├── SysMain.hx ├── VilToDrawer.hx ├── VilToDrawerOpt.hx ├── drawer └── DrawerKeymap.hx ├── tools ├── BuildDate.hx ├── ERegTools.hx ├── JsonParserWithComments.hx └── JsonPrinterWithOrder.hx ├── via ├── ViaKeyNames.hx ├── ViaKeyNamesGen.hx └── key.ts ├── vial ├── VialKey.hx ├── VialKeyNames.hx ├── VialKeyNamesGen.hx ├── VialKeyNamesGen.n ├── VialKeymap.hx ├── VialKeysWithShiftState.hx └── keycodes.py └── web └── KeyRangeCreator.hx /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/style.min.css 2 | /docs/VialToKeymapDrawer.js.map 3 | *.n 4 | /bin/*.exe 5 | /bin/*.dll 6 | /bin/*.ndll 7 | /bin/*.zip 8 | /bin/*.svg 9 | /bin/*.png 10 | /bin/yal-sofle-*.yaml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vial layout to Keymap Drawer converter 2 | 3 | **Quick links:** [web version](https://yal-tools.github.io/vial-to-keymap-drawer/) 4 | · [itch](https://yellowafterlife.itch.io/vial-to-keymap-drawer) (for donations and pre-built binaries) 5 | 6 | Takes your Vial `.vil` layouts and converts them to YAML that you can pass to 7 | [keymap-drawer](https://github.com/caksoylar/keymap-drawer) 8 | to render your layout to SVG/PNG images that you can show to people instead 9 | of taking screenshots of Vial configurator. 10 | 11 | Also lets you label your layers/keys to make the keymap easier to read 12 | and displays combos/tap dances. 13 | 14 | ![](example.svg) 15 | 16 | ## Inevitable caveats 17 | 18 | Apparently the order in which Vial stores keys in `.vil` files does not necessarily match up 19 | with how keys are defined in QMK, therefore the keys may appear out of order, depending on the keyboard. 20 | 21 | And if your keyboard has optional keys (like a wide LShift vs narrow LShift + extra key), the way those are represented is not standardized either - they could be set to `-1`, or just left as blanks in the layout file. 22 | 23 | For this I'm giving you a bunch of tools to deal with common and uncommon mishaps - 24 | from simple (get rid of extra blank/`-1` keys, auto-fix key order in split Vial keyboards) 25 | to advanced (specify key order in a [visual editor](./docs/examples/key-range-editor.mp4) or hand-write key locations to swap or pick). 26 | 27 | This makes this tool slightly less entry-level, but good news - you'll only need to do this once per keyboard. 28 | Perhaps contribute an example `.vil` + `.json` if it's a particularly quirky one? 29 | 30 | ## Using 31 | 32 | For the web version, hopefully the use should be apparent enough, 33 | and you can load an example configuration to see how to deal with keys being out of order. 34 | 35 | The native version is invoked through command-line/terminal. 36 | 37 | On Windows this is done as following: 38 | ``` 39 | .\VialToKeymapDrawer.exe <...options> 40 | ``` 41 | 42 | On Mac/Linux this is done as following (you'll need [Neko VM](https://nekovm.org/download/) installed): 43 | ``` 44 | neko ./VialToKeymapDrawer.n <...options> 45 | ``` 46 | For a full list of supported options, run without arguments or with `--help`; 47 | 48 | Options might look like this, for instance (for a Sofle Choc): 49 | ``` 50 | --keyboard splitkb/aurora/sofle_v2/rev1 --half-after-half --mirror-right-half --move-defs yal-sofle/move-defs.txt --key-labels yal-sofle/key-labels.txt --layer-names yal-sofle/layer-names.txt --vil yal-sofle/yal-sofle.vil yal-sofle.yaml 51 | ``` 52 | Other keyboards may require less tinkering. 53 | 54 | ## Building 55 | 56 | Web: 57 | ```text 58 | haxe build-web.hxml 59 | ``` 60 | Native: 61 | ```text 62 | haxe build-neko.hxml 63 | nekotools boot bin/VialToKeymapDrawer.n 64 | ``` 65 | 66 | ## License and credits 67 | 68 | Tool by YellowAfterlife 69 | 70 | - Written in [Haxe](https://haxe.org). 71 | - Uses [FileSaver.js](https://github.com/eligrey/FileSaver.js/). 72 | 73 | Display data comes from the following: 74 | 75 | - Vial key labels: [Vial GUI](https://github.com/vial-kb/vial-gui) 76 | - VIA key labels: [VIA GUI](https://github.com/the-via/app) 77 | - QMK keyboard list: [QMK Configurator](https://config.qmk.fm/) 78 | By which I mean not the _source code_ of QMK configurator, but taken straight from the web page. 79 | 80 | As result of sourcing data from software licensed under GPLv2, this tool is probably also GPLv2. 81 | -------------------------------------------------------------------------------- /VialToKeymapDrawerJS.hxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /VialToKeymapDrawerNeko.hxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /bin/VialToKeymapDrawer-example.bat: -------------------------------------------------------------------------------- 1 | cmd /C .\VialToKeymapDrawer.exe --keyboard splitkb/aurora/sofle_v2/rev1 --half-after-half --mirror-right-half --move-defs yal-sofle/move-defs.txt --key-labels yal-sofle/key-labels.txt --layer-names yal-sofle/layer-names.txt --vil yal-sofle/yal-sofle.vil yal-sofle.yaml 2 | pause -------------------------------------------------------------------------------- /bin/VialToKeymapDrawer-example.sh: -------------------------------------------------------------------------------- 1 | neko VialToKeymapDrawer.n --keyboard splitkb/aurora/sofle_v2/rev1 --half-after-half --mirror-right-half --move-defs yal-sofle/move-defs.txt --key-labels yal-sofle/key-labels.txt --layer-names yal-sofle/layer-names.txt --vil yal-sofle/yal-sofle.vil yal-sofle.yaml 2 | -------------------------------------------------------------------------------- /bin/pack.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | del /Q VialToKeymapDrawer-Windows.zip 3 | cmd /C nekotools boot VialToKeymapDrawer.n 4 | cmd /C 7z a VialToKeymapDrawer-Windows.zip VialToKeymapDrawer.exe *.dll *.ndll ..\README.md yal-sofle VialToKeymapDrawer-example.bat 5 | 6 | del /Q VialToKeymapDrawer-Unix.zip 7 | cmd /C 7z a VialToKeymapDrawer-Unix.zip VialToKeymapDrawer.n VialToKeymapDrawer-example.sh yal-sofle ..\README.md 8 | 9 | set /p ver="Version?: " 10 | echo Uploading %ver%... 11 | 12 | cmd /C itchio-butler push VialToKeymapDrawer-Windows.zip yellowafterlife/vial-to-keymap-drawer:windows --userversion=%ver% 13 | cmd /C itchio-butler push VialToKeymapDrawer-Unix.zip yellowafterlife/vial-to-keymap-drawer:unix --userversion=%ver% 14 | pause -------------------------------------------------------------------------------- /bin/test-all.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmd /C test-one 0 3 | cmd /C test-one 1 4 | cmd /C test-one 2 5 | cmd /C test-one 3 6 | pause -------------------------------------------------------------------------------- /bin/test-one.bat: -------------------------------------------------------------------------------- 1 | neko VialToKeymapDrawer.n --keyboard sofle_rotated --half-after-half --mirror-right-half --move-defs yal-sofle/move-defs.txt --key-labels yal-sofle/key-labels.txt --layer-names yal-sofle/layer-names.txt --encoder-defs yal-sofle/encoder-defs.txt --vil yal-sofle/yal-sofle.vil --layer %1 yal-sofle-%1.yaml 2 | keymap draw yal-sofle-%1.yaml>yal-sofle-%1.svg 3 | cmd /C inkscape yal-sofle-%1.svg --export-filename="yal-sofle-%1.png" 4 | cmd /C magick yal-sofle-%1.png -background #889EC5 -flatten yal-sofle-%1-blue.png 5 | -------------------------------------------------------------------------------- /bin/test.bat: -------------------------------------------------------------------------------- 1 | cd bin 2 | neko VialToKeymapDrawer.n --keyboard sofle_rotated --half-after-half --mirror-right-half --move-defs yal-sofle/move-defs.txt --key-labels yal-sofle/key-labels.txt --layer-names yal-sofle/layer-names.txt --encoder-defs yal-sofle/encoder-defs.txt --vil yal-sofle/yal-sofle.vil --layer 0 yal-sofle.yaml 3 | keymap draw yal-sofle.yaml>yal-sofle.svg 4 | pause -------------------------------------------------------------------------------- /bin/yal-sofle.yaml: -------------------------------------------------------------------------------- 1 | layout: {"qmk_keyboard":"sofle_rotated"} 2 | layers: 3 | DEF: 4 | - {"s":"~","t":"`"} 5 | - {"s":"!","t":"1"} 6 | - {"s":"@","t":"2"} 7 | - {"s":"#","t":"3"} 8 | - {"s":"$","t":"4"} 9 | - {"s":"%","t":"5"} 10 | - {"s":"^","t":"6"} 11 | - {"s":"&","t":"7"} 12 | - {"s":"*","t":"8"} 13 | - {"s":"(","t":"9"} 14 | - {"s":")","t":"0"} 15 | - Bksp 16 | - Tab 17 | - Q 18 | - W 19 | - E 20 | - R 21 | - T 22 | - Y 23 | - U 24 | - I 25 | - O 26 | - P 27 | - {"s":"{","t":"["} 28 | - LShift 29 | - A 30 | - S 31 | - D 32 | - F 33 | - G 34 | - H 35 | - J 36 | - K 37 | - L 38 | - {"s":":","t":";"} 39 | - {"h":"RCtrl","s":"TD(1)","t":"\" '"} 40 | - LCtrl 41 | - Z 42 | - X 43 | - C 44 | - V 45 | - B 46 | - Home 47 | - End 48 | - N 49 | - M 50 | - {"s":"<","t":","} 51 | - {"s":">","t":"."} 52 | - {"s":"?","t":"/"} 53 | - {"h":"RSFT","s":"}","t":"]"} 54 | - LGui 55 | - LAlt 56 | - Space 57 | - {"h":"MO ALT","t":"Esc"} 58 | - {"h":"MO NUM","s":"TD(0)","t":"Enter"} 59 | - {"s":"|","t":"\\"} 60 | - {"s":"_","t":"-"} 61 | - {"s":"+","t":"="} 62 | - {"h":"RALT","t":"Menu"} 63 | - "NAV ON" 64 | -------------------------------------------------------------------------------- /bin/yal-sofle/encoder-defs.txt: -------------------------------------------------------------------------------- 1 | 4,5 => 0 2 | 9,5 => 1 3 | -------------------------------------------------------------------------------- /bin/yal-sofle/key-labels.txt: -------------------------------------------------------------------------------- 1 | "F16" => Mute 2 | "F21" => Scroll Down 3 | "F20" => Scroll Up 4 | "F22" => Lang 5 | "LGui+F22" => Magic 1 6 | "LAlt+F22" => Magic 2 7 | "LCtl+F22" => Magic 3 8 | "M1" => NAV ON 9 | "M0" => NAV OFF 10 | "M2" => NUM ON 11 | "M3" => NUM OFF 12 | "Browser\nFav." => Search -------------------------------------------------------------------------------- /bin/yal-sofle/layer-names.txt: -------------------------------------------------------------------------------- 1 | DEF 2 | NAV 3 | ALT 4 | NUM -------------------------------------------------------------------------------- /bin/yal-sofle/move-defs.txt: -------------------------------------------------------------------------------- 1 | 4,5 => 3,6 2 | 9,5 => 8,6 -------------------------------------------------------------------------------- /bin/yal-sofle/yal-sofle.vil: -------------------------------------------------------------------------------- 1 | {"version": 1, "uid": 12888985424971681029, "layout": [[["KC_GRAVE", "KC_1", "KC_2", "KC_3", "KC_4", "KC_5"], ["KC_TAB", "KC_Q", "KC_W", "KC_E", "KC_R", "KC_T"], ["KC_LSHIFT", "KC_A", "KC_S", "KC_D", "KC_F", "KC_G"], ["KC_LCTRL", "KC_Z", "KC_X", "KC_C", "KC_V", "KC_B"], ["KC_LGUI", "KC_LALT", "KC_SPACE", "LT2(KC_ESCAPE)", "TD(0)", "KC_HOME"], ["KC_BSPACE", "KC_0", "KC_9", "KC_8", "KC_7", "KC_6"], ["KC_LBRACKET", "KC_P", "KC_O", "KC_I", "KC_U", "KC_Y"], ["TD(1)", "KC_SCOLON", "KC_L", "KC_K", "KC_J", "KC_H"], ["RSFT_T(KC_RBRACKET)", "KC_SLASH", "KC_DOT", "KC_COMMA", "KC_M", "KC_N"], ["M1", "RALT_T(KC_APPLICATION)", "KC_EQUAL", "KC_MINUS", "KC_BSLASH", "KC_END"]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "M0", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_DELETE", "KC_END", "KC_UP", "KC_HOME", "RCTL(KC_LEFT)"], ["KC_TRNS", "KC_TRNS", "KC_RIGHT", "KC_DOWN", "KC_LEFT", "RCTL(KC_RIGHT)"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_PGDOWN", "KC_PGUP"], ["M0", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"]], [["KC_ESCAPE", "KC_F1", "KC_F2", "KC_F3", "KC_F4", "KC_F5"], ["KC_TAB", "KC_F11", "KC_F12", "KC_PAUSE", "KC_PSCREEN", "KC_WFAV"], ["KC_LSHIFT", "KC_LGUI", "KC_LSHIFT", "KC_LCTRL", "KC_LALT", "KC_F22"], ["KC_LCTRL", "LGUI(KC_F22)", "LALT(KC_F22)", "LCTL(KC_F22)", "M2", "KC_F16"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_F10", "KC_F9", "KC_F8", "KC_F7", "KC_F6"], ["KC_TRNS", "KC_DELETE", "KC_END", "KC_UP", "KC_HOME", "KC_PGUP"], ["KC_TRNS", "KC_TRNS", "KC_RIGHT", "KC_DOWN", "KC_LEFT", "KC_PGDOWN"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_INSERT", "KC_CAPSLOCK"], ["KC_SCROLLLOCK", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_HOME", "KC_UP", "KC_END", "KC_PGUP", "KC_NO"], ["KC_TRNS", "KC_LEFT", "KC_DOWN", "KC_RIGHT", "KC_PGDOWN", "KC_NO"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "M3", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_KP_MINUS", "KC_KP_ASTERISK", "KC_KP_SLASH", "KC_EQUAL", "KC_NUMLOCK"], ["KC_TRNS", "KC_DELETE", "KC_KP_9", "KC_KP_8", "KC_KP_7", "LSFT(KC_9)"], ["KC_TRNS", "KC_KP_PLUS", "KC_KP_6", "KC_KP_5", "KC_KP_4", "KC_BSPACE"], ["KC_TRNS", "KC_KP_ENTER", "KC_KP_3", "KC_KP_2", "KC_KP_1", "LSFT(KC_0)"], ["M3", "KC_KP_DOT", "KC_KP_0", "KC_KP_0", "KC_COMMA", "KC_TRNS"]]], "encoder_layout": [[["KC_DOWN", "KC_UP"], ["KC_RIGHT", "KC_LEFT"]], [["KC_TRNS", "KC_TRNS"], ["KC_F21", "KC_F20"]], [["KC_VOLD", "KC_VOLU"], ["KC_DOWN", "KC_UP"]], [["KC_TRNS", "KC_TRNS"], ["KC_F21", "KC_F20"]]], "layout_options": -1, "macro": [[["tap", "KC_F23", "TG(1)"]], [["tap", "RCTL(KC_F23)", "TG(1)"]], [["tap", "RSFT(KC_F23)", "TG(3)"]], [["tap", "KC_F23", "TG(3)"]], [["tap", "KC_END", "LSFT(KC_HOME)"]], [], [], [], [], [], [], [], [], [], [], []], "vial_protocol": 5, "via_protocol": 9, "tap_dance": [["KC_ENTER", "MO(3)", "KC_NO", "KC_NO", 250], ["KC_QUOTE", "KC_RCTRL", "KC_NO", "KC_NO", 200], ["LSFT(KC_MINUS)", "KC_LSHIFT", "KC_NO", "KC_NO", 120], ["KC_MINUS", "KC_RALT", "KC_NO", "KC_NO", 200], ["LSFT(KC_8)", "LSFT(KC_6)", "KC_NO", "KC_NO", 160], ["LSFT(KC_8)", "LSFT(KC_3)", "KC_NO", "KC_NO", 160], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 160], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 160]], "combo": [["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"]], "key_override": [], "settings": {}} -------------------------------------------------------------------------------- /build-neko.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -neko bin/VialToKeymapDrawer.n 3 | -main SysMain -------------------------------------------------------------------------------- /build-web.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -js docs/VialToKeymapDrawer.js 3 | -main Main -------------------------------------------------------------------------------- /docs/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(b,c,d){var e=new XMLHttpRequest;e.open("GET",b),e.responseType="blob",e.onload=function(){a(e.response,c,d)},e.onerror=function(){console.error("could not download file")},e.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(a,b,d,e){if(e=e||open("","_blank"),e&&(e.document.title=e.document.body.innerText="downloading..."),"string"==typeof a)return c(a,b,d);var g="application/octet-stream"===a.type,h=/constructor/i.test(f.HTMLElement)||f.safari,i=/CriOS\/[\d]+/.test(navigator.userAgent);if((i||g&&h)&&"object"==typeof FileReader){var j=new FileReader;j.onloadend=function(){var a=j.result;a=i?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),e?e.location.href=a:location=a,e=null},j.readAsDataURL(a)}else{var k=f.URL||f.webkitURL,l=k.createObjectURL(a);e?e.location=l:location.href=l,e=null,setTimeout(function(){k.revokeObjectURL(l)},4E4)}});f.saveAs=a.saveAs=a,"undefined"!=typeof module&&(module.exports=a)}); 2 | 3 | //# sourceMappingURL=FileSaver.min.js.map -------------------------------------------------------------------------------- /docs/examples/cepstrum.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "https://yal-tools.github.io/vial-to-keymap-drawer/", 3 | "fields": { 4 | "keyboard": "keebio/cepstrum/rev1", 5 | "layout": "", 6 | "half-after-half": false, 7 | "mirror-right-half": false, 8 | "omit-non-keys": 2, 9 | "key-ranges": [ 10 | "0,0-8", 11 | "0,45-53", 12 | 13 | "0,9-17", 14 | "0,54-62", 15 | 16 | "0,18-26", 17 | "0,63-71", 18 | 19 | "0,27-35", 20 | "0,72-80", 21 | 22 | "0,36-43", 23 | "0,81-89", 24 | 25 | "" 26 | ], 27 | "layer-names": [], 28 | "key-overrides": [], 29 | "notes": [ 30 | "Uses Key Ranges to order keys correctly and 'Omit KC_NO' to avoid the various SINC PCB's optional keys." 31 | ] 32 | } 33 | } -------------------------------------------------------------------------------- /docs/examples/cepstrum.vil: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cepstrum Rev. 1", 3 | "vendorProductId": 3406828887, 4 | "macros": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""], 5 | "layers": [ 6 | [ 7 | "KC_F22", 8 | "KC_PSCR", 9 | "KC_GRV", 10 | "KC_1", 11 | "KC_2", 12 | "KC_3", 13 | "KC_4", 14 | "KC_5", 15 | "KC_6", 16 | "KC_VOLU", 17 | "KC_CALC", 18 | "KC_TAB", 19 | "KC_Q", 20 | "KC_W", 21 | "KC_E", 22 | "KC_R", 23 | "KC_T", 24 | "KC_NO", 25 | "KC_VOLD", 26 | "KC_F16", 27 | "KC_ESC", 28 | "KC_A", 29 | "KC_S", 30 | "KC_D", 31 | "KC_F", 32 | "KC_G", 33 | "KC_NO", 34 | "KC_PGUP", 35 | "KC_PGDN", 36 | "KC_LSFT", 37 | "KC_NO", 38 | "KC_Z", 39 | "KC_X", 40 | "KC_C", 41 | "KC_V", 42 | "KC_B", 43 | "KC_MPRV", 44 | "KC_MNXT", 45 | "KC_LCTL", 46 | "KC_LGUI", 47 | "KC_LALT", 48 | "LT(1,KC_SPC)", 49 | "KC_NO", 50 | "KC_SPC", 51 | "KC_NO", 52 | "KC_7", 53 | "KC_8", 54 | "KC_9", 55 | "KC_0", 56 | "KC_MINS", 57 | "KC_EQL", 58 | "KC_NO", 59 | "KC_BSPC", 60 | "KC_DEL", 61 | "KC_Y", 62 | "KC_U", 63 | "KC_I", 64 | "KC_O", 65 | "KC_P", 66 | "KC_LBRC", 67 | "KC_RBRC", 68 | "KC_BSLS", 69 | "KC_APP", 70 | "KC_H", 71 | "KC_J", 72 | "KC_K", 73 | "KC_L", 74 | "KC_SCLN", 75 | "KC_QUOT", 76 | "KC_NO", 77 | "KC_ENT", 78 | "KC_HOME", 79 | "KC_N", 80 | "KC_M", 81 | "KC_COMM", 82 | "KC_DOT", 83 | "KC_SLSH", 84 | "KC_NO", 85 | "KC_RSFT", 86 | "KC_UP", 87 | "KC_END", 88 | "KC_NO", 89 | "KC_SPC", 90 | "KC_RALT", 91 | "KC_RGUI", 92 | "KC_RCTL", 93 | "KC_NO", 94 | "KC_LEFT", 95 | "KC_DOWN", 96 | "KC_RGHT" 97 | ], 98 | [ 99 | "KC_TRNS", 100 | "KC_TRNS", 101 | "KC_ESC", 102 | "KC_F1", 103 | "KC_F2", 104 | "KC_F3", 105 | "KC_F4", 106 | "KC_F5", 107 | "KC_F6", 108 | "KC_TRNS", 109 | "KC_TRNS", 110 | "KC_TRNS", 111 | "KC_TRNS", 112 | "KC_TRNS", 113 | "KC_TRNS", 114 | "KC_TRNS", 115 | "KC_TRNS", 116 | "KC_NO", 117 | "KC_TRNS", 118 | "KC_TRNS", 119 | "KC_TRNS", 120 | "KC_TRNS", 121 | "KC_TRNS", 122 | "KC_TRNS", 123 | "KC_TRNS", 124 | "KC_TRNS", 125 | "KC_NO", 126 | "KC_TRNS", 127 | "KC_TRNS", 128 | "KC_TRNS", 129 | "KC_NO", 130 | "KC_TRNS", 131 | "KC_TRNS", 132 | "KC_TRNS", 133 | "KC_TRNS", 134 | "KC_TRNS", 135 | "KC_TRNS", 136 | "KC_TRNS", 137 | "KC_TRNS", 138 | "KC_TRNS", 139 | "KC_TRNS", 140 | "KC_TRNS", 141 | "KC_NO", 142 | "KC_TRNS", 143 | "KC_NO", 144 | "KC_F7", 145 | "KC_F8", 146 | "KC_F9", 147 | "KC_F10", 148 | "KC_F11", 149 | "KC_F12", 150 | "KC_NO", 151 | "KC_TRNS", 152 | "KC_PSCR", 153 | "KC_TRNS", 154 | "KC_TRNS", 155 | "KC_TRNS", 156 | "KC_TRNS", 157 | "KC_TRNS", 158 | "KC_TRNS", 159 | "KC_TRNS", 160 | "KC_TRNS", 161 | "KC_PAUS", 162 | "KC_TRNS", 163 | "KC_TRNS", 164 | "KC_TRNS", 165 | "KC_TRNS", 166 | "KC_TRNS", 167 | "KC_TRNS", 168 | "KC_NO", 169 | "KC_TRNS", 170 | "C(KC_PGUP)", 171 | "KC_TRNS", 172 | "KC_TRNS", 173 | "KC_TRNS", 174 | "KC_TRNS", 175 | "KC_TRNS", 176 | "KC_NO", 177 | "KC_TRNS", 178 | "KC_TRNS", 179 | "C(KC_PGDN)", 180 | "KC_NO", 181 | "KC_TRNS", 182 | "KC_RGUI", 183 | "KC_RALT", 184 | "KC_TRNS", 185 | "KC_NO", 186 | "KC_TRNS", 187 | "KC_TRNS", 188 | "KC_TRNS" 189 | ], 190 | [ 191 | "KC_TRNS", 192 | "KC_TRNS", 193 | "KC_TRNS", 194 | "KC_TRNS", 195 | "KC_TRNS", 196 | "KC_TRNS", 197 | "KC_TRNS", 198 | "KC_TRNS", 199 | "KC_TRNS", 200 | "KC_TRNS", 201 | "KC_TRNS", 202 | "KC_TRNS", 203 | "KC_TRNS", 204 | "KC_TRNS", 205 | "KC_TRNS", 206 | "KC_TRNS", 207 | "KC_TRNS", 208 | "KC_TRNS", 209 | "KC_TRNS", 210 | "KC_TRNS", 211 | "KC_TRNS", 212 | "KC_TRNS", 213 | "KC_TRNS", 214 | "KC_TRNS", 215 | "KC_TRNS", 216 | "KC_TRNS", 217 | "KC_TRNS", 218 | "KC_TRNS", 219 | "KC_TRNS", 220 | "KC_TRNS", 221 | "KC_TRNS", 222 | "KC_TRNS", 223 | "KC_TRNS", 224 | "KC_TRNS", 225 | "KC_TRNS", 226 | "KC_TRNS", 227 | "KC_TRNS", 228 | "KC_TRNS", 229 | "KC_TRNS", 230 | "KC_TRNS", 231 | "KC_TRNS", 232 | "KC_TRNS", 233 | "KC_TRNS", 234 | "KC_TRNS", 235 | "KC_TRNS", 236 | "KC_TRNS", 237 | "KC_TRNS", 238 | "KC_TRNS", 239 | "KC_TRNS", 240 | "KC_TRNS", 241 | "KC_TRNS", 242 | "KC_TRNS", 243 | "KC_TRNS", 244 | "KC_TRNS", 245 | "KC_TRNS", 246 | "KC_TRNS", 247 | "KC_TRNS", 248 | "KC_TRNS", 249 | "KC_TRNS", 250 | "KC_TRNS", 251 | "KC_TRNS", 252 | "KC_TRNS", 253 | "KC_TRNS", 254 | "KC_TRNS", 255 | "KC_TRNS", 256 | "KC_TRNS", 257 | "KC_TRNS", 258 | "KC_TRNS", 259 | "KC_TRNS", 260 | "KC_TRNS", 261 | "KC_TRNS", 262 | "KC_TRNS", 263 | "KC_TRNS", 264 | "KC_TRNS", 265 | "KC_TRNS", 266 | "KC_TRNS", 267 | "KC_TRNS", 268 | "KC_TRNS", 269 | "KC_TRNS", 270 | "KC_TRNS", 271 | "KC_TRNS", 272 | "KC_TRNS", 273 | "KC_TRNS", 274 | "KC_TRNS", 275 | "KC_TRNS", 276 | "KC_TRNS", 277 | "KC_TRNS", 278 | "KC_TRNS", 279 | "KC_TRNS", 280 | "KC_TRNS" 281 | ], 282 | [ 283 | "KC_TRNS", 284 | "KC_TRNS", 285 | "KC_TRNS", 286 | "KC_TRNS", 287 | "KC_TRNS", 288 | "KC_TRNS", 289 | "KC_TRNS", 290 | "KC_TRNS", 291 | "KC_TRNS", 292 | "KC_TRNS", 293 | "KC_TRNS", 294 | "KC_TRNS", 295 | "KC_TRNS", 296 | "KC_TRNS", 297 | "KC_TRNS", 298 | "KC_TRNS", 299 | "KC_TRNS", 300 | "KC_TRNS", 301 | "KC_TRNS", 302 | "KC_TRNS", 303 | "KC_TRNS", 304 | "KC_TRNS", 305 | "KC_TRNS", 306 | "KC_TRNS", 307 | "KC_TRNS", 308 | "KC_TRNS", 309 | "KC_TRNS", 310 | "KC_TRNS", 311 | "KC_TRNS", 312 | "KC_TRNS", 313 | "KC_TRNS", 314 | "KC_TRNS", 315 | "KC_TRNS", 316 | "KC_TRNS", 317 | "KC_TRNS", 318 | "KC_TRNS", 319 | "KC_TRNS", 320 | "KC_TRNS", 321 | "KC_TRNS", 322 | "KC_TRNS", 323 | "KC_TRNS", 324 | "KC_TRNS", 325 | "KC_TRNS", 326 | "KC_TRNS", 327 | "KC_TRNS", 328 | "KC_TRNS", 329 | "KC_TRNS", 330 | "KC_TRNS", 331 | "KC_TRNS", 332 | "KC_TRNS", 333 | "KC_TRNS", 334 | "KC_TRNS", 335 | "KC_TRNS", 336 | "KC_TRNS", 337 | "KC_TRNS", 338 | "KC_TRNS", 339 | "KC_TRNS", 340 | "KC_TRNS", 341 | "KC_TRNS", 342 | "KC_TRNS", 343 | "KC_TRNS", 344 | "KC_TRNS", 345 | "KC_TRNS", 346 | "KC_TRNS", 347 | "KC_TRNS", 348 | "KC_TRNS", 349 | "KC_TRNS", 350 | "KC_TRNS", 351 | "KC_TRNS", 352 | "KC_TRNS", 353 | "KC_TRNS", 354 | "KC_TRNS", 355 | "KC_TRNS", 356 | "KC_TRNS", 357 | "KC_TRNS", 358 | "KC_TRNS", 359 | "KC_TRNS", 360 | "KC_TRNS", 361 | "KC_TRNS", 362 | "KC_TRNS", 363 | "KC_TRNS", 364 | "KC_TRNS", 365 | "KC_TRNS", 366 | "KC_TRNS", 367 | "KC_TRNS", 368 | "KC_TRNS", 369 | "KC_TRNS", 370 | "KC_TRNS", 371 | "KC_TRNS", 372 | "KC_TRNS" 373 | ] 374 | ], 375 | "encoders": [] 376 | } -------------------------------------------------------------------------------- /docs/examples/corvett-piantor.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "https://yal-tools.github.io/vial-to-keymap-drawer/", 3 | "fields": { 4 | "keyboard": "corne_rotated", 5 | "layout": "LAYOUT_split_3x6_3", 6 | "half-after-half": true, 7 | "mirror-right-half": false, 8 | "omit-m1": true, 9 | "omit-non-keys": "0", 10 | "move-defs": [], 11 | "layer-names": [], 12 | "key-overrides": [], 13 | "mark-non-keys": "unused", 14 | "notes": [ 15 | "Marks unused keys, otherwise a relatively normal configuration." 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /docs/examples/corvett-piantor.vil: -------------------------------------------------------------------------------- 1 | {"version": 1, "uid": 1113869408211332572, "layout": [[["KC_NO", "KC_Q", "KC_W", "KC_E", "KC_R", "KC_T"], ["KC_NO", "KC_A", "TD(5)", "TD(4)", "TD(3)", "KC_G"], ["KC_NO", "KC_Z", "KC_X", "KC_C", "KC_V", "KC_COMMA"], ["KC_ENTER", "KC_ENTER", "MO(1)", -1, -1, -1], ["KC_Y", "KC_U", "KC_I", "KC_O", "KC_P", "KC_NO"], ["KC_H", "TD(6)", "TD(7)", "TD(8)", "KC_SCOLON", "KC_NO"], ["KC_B", "KC_N", "KC_M", "KC_DOT", "TD(11)", "KC_NO"], ["MO(2)", "KC_SPACE", "KC_SPACE", -1, -1, -1]], [["KC_NO", "KC_GRAVE", "KC_ESCAPE", "KC_BTN2", "KC_MS_U", "KC_BTN1"], ["KC_TRNS", "KC_PGUP", "KC_WH_U", "KC_MS_L", "KC_MS_D", "KC_MS_R"], ["KC_TRNS", "KC_PGDOWN", "KC_WH_D", "KC_NO", "KC_TAB", "LSFT(KC_GRAVE)"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_NO", "KC_7", "KC_8", "KC_9", "KC_BSPACE", "KC_NO"], ["KC_LBRACKET", "KC_4", "KC_5", "KC_6", "KC_BSLASH", "KC_NO"], ["KC_RBRACKET", "KC_1", "KC_2", "KC_3", "KC_DOT", "KC_NO"], ["MO(3)", "KC_0", "KC_0", -1, -1, -1]], [["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "C_S(KC_8)", "LCTL(KC_D)", "LCTL(KC_F4)", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "M0", "KC_NO"], ["KC_TRNS", "KC_TRNS", "MO(3)", -1, -1, -1], ["KC_HOME", "KC_UP", "KC_END", "KC_MINUS", "KC_DELETE", "KC_NO"], ["KC_LEFT", "KC_DOWN", "KC_RIGHT", "KC_EQUAL", "KC_BSLASH", "KC_NO"], ["KC_BSPACE", "KC_DELETE", "KC_LBRACKET", "KC_RBRACKET", "KC_QUOTE", "KC_NO"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]], [["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_BRIU", "KC_VOLU"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_BRID", "KC_VOLD"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_NO", "KC_MUTE"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_NO", "KC_F7", "KC_F8", "KC_F9", "KC_F12", "KC_NO"], ["KC_NO", "KC_F4", "KC_F5", "KC_F6", "KC_F11", "KC_NO"], ["KC_NO", "KC_F1", "KC_F2", "KC_F3", "KC_F10", "KC_NO"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_TRNS", "LSFT(KC_7)", "LSFT(KC_8)", "LSFT(KC_9)", "LSFT(KC_MINUS)", "KC_TRNS"], ["LSFT(KC_LBRACKET)", "LSFT(KC_4)", "LSFT(KC_5)", "LSFT(KC_6)", "LSFT(KC_BSLASH)", "KC_NO"], ["LSFT(KC_RBRACKET)", "LSFT(KC_1)", "LSFT(KC_2)", "LSFT(KC_3)", "LSFT(KC_QUOTE)", "KC_NO"], ["KC_TRNS", "LSFT(KC_0)", "KC_TRNS", -1, -1, -1]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", -1, -1, -1]]], "encoder_layout": [[], [], [], [], [], [], [], [], [], []], "layout_options": -1, "macro": [[["text", "thisisunsafe"]], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []], "vial_protocol": 5, "via_protocol": 9, "tap_dance": [["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "MO(1)", "TG(1)", "KC_NO", 200], ["KC_NO", "MO(2)", "TG(2)", "KC_NO", 200], ["KC_F", "KC_LCTRL", "KC_NO", "KC_NO", 150], ["KC_D", "KC_LALT", "KC_NO", "KC_NO", 150], ["KC_S", "KC_LSHIFT", "KC_NO", "KC_NO", 100], ["KC_J", "KC_RCTRL", "KC_NO", "KC_NO", 150], ["KC_K", "KC_RALT", "KC_NO", "KC_NO", 150], ["KC_L", "KC_LSHIFT", "KC_NO", "KC_NO", 100], ["KC_LGUI", "MO(2)", "KC_NO", "KC_NO", 50], ["KC_TAB", "MO(1)", "KC_NO", "KC_NO", 50], ["KC_SLASH", "KC_RGUI", "KC_NO", "KC_NO", 150], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 0], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 200]], "combo": [["TD(3)", "KC_G", "KC_NO", "KC_NO", "KC_TAB"], ["TD(3)", "TD(4)", "KC_NO", "KC_NO", "LCTL(KC_LALT)"], ["TD(6)", "TD(7)", "KC_NO", "KC_NO", "LCTL(KC_LALT)"], ["TD(3)", "TD(5)", "KC_NO", "KC_NO", "LCTL(KC_LSHIFT)"], ["TD(6)", "TD(8)", "KC_NO", "KC_NO", "LCTL(KC_LSHIFT)"], ["TD(4)", "TD(5)", "KC_NO", "KC_NO", "LALT(KC_LSHIFT)"], ["TD(5)", "MO(1)", "KC_NO", "KC_NO", "MO(4)"], ["KC_W", "KC_E", "KC_NO", "KC_NO", "KC_ESCAPE"], ["KC_E", "KC_R", "KC_NO", "KC_NO", "KC_TAB"], ["KC_U", "KC_I", "KC_NO", "KC_NO", "KC_BSPACE"], ["KC_I", "KC_O", "KC_NO", "KC_NO", "KC_DELETE"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"]], "key_override": [{"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}, {"trigger": "KC_NO", "replacement": "KC_NO", "layers": 65535, "trigger_mods": 0, "negative_mod_mask": 0, "suppressed_mods": 0, "options": 7}], "settings": {"1": 0, "2": 30, "3": 0, "4": 175, "5": 5, "6": 5000, "7": 160, "8": 0, "9": 10, "10": 20, "11": 4, "12": 4, "13": 30, "14": 10, "15": 80, "16": 8, "17": 40, "18": 0, "19": 80, "20": 5, "21": 0}} -------------------------------------------------------------------------------- /docs/examples/key-range-editor.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YAL-Tools/vial-to-keymap-drawer/7aa058f163f786e28a92e15446af9eb39ca80d56/docs/examples/key-range-editor.mp4 -------------------------------------------------------------------------------- /docs/examples/yal-sofle.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "https://yal-tools.github.io/vial-to-keymap-drawer/", 3 | "fields": { 4 | "notes": [ 5 | "An example of fixing a few out-of-order keys. Shows off layer names, custom key labels, tap dances, and chords." 6 | ], 7 | "keyboard": "splitkb/aurora/sofle_v2/rev1", 8 | "layout": "", 9 | "include-layers": "", 10 | "half-after-half": true, 11 | "mirror-right-half": true, 12 | "omit-m1": false, 13 | "omit-non-keys": "0", 14 | "move-defs": [ 15 | "4,5 => 3,6", 16 | "9,5 => 8,6" 17 | ], 18 | "key-ranges": [], 19 | "encoder-defs": [ 20 | "4,5 => 0", 21 | "9,5 => 1" 22 | ], 23 | "layer-names": [ 24 | "DEF", 25 | "NAV", 26 | "ALT", 27 | "NUM" 28 | ], 29 | "key-overrides": [ 30 | "\"F16\" => Mute ", 31 | "\"F21\" => Scroll Down", 32 | "\"F20\" => Scroll Up", 33 | "\"F22\" => Lang", 34 | "\"LGui+F22\" => Magic 1", 35 | "\"LAlt+F22\" => Magic 2", 36 | "\"LCtl+F22\" => Magic 3", 37 | "\"M1\" => NAV ON", 38 | "\"M0\" => NAV OFF", 39 | "\"M2\" => NUM ON", 40 | "\"M3\" => NUM OFF", 41 | "\"Browser\\nFav.\" => Search" 42 | ], 43 | "mark-non-keys": "" 44 | } 45 | } -------------------------------------------------------------------------------- /docs/examples/yal-sofle.vil: -------------------------------------------------------------------------------- 1 | {"version": 1, "uid": 12888985424971681029, "layout": [[["KC_GRAVE", "KC_1", "KC_2", "KC_3", "KC_4", "KC_5"], ["KC_TAB", "KC_Q", "KC_W", "KC_E", "KC_R", "KC_T"], ["KC_LSHIFT", "KC_A", "KC_S", "KC_D", "KC_F", "KC_G"], ["KC_LCTRL", "KC_Z", "KC_X", "KC_C", "KC_V", "KC_B"], ["KC_LGUI", "KC_LALT", "KC_SPACE", "LT2(KC_ESCAPE)", "TD(0)", "KC_HOME"], ["KC_BSPACE", "KC_0", "KC_9", "KC_8", "KC_7", "KC_6"], ["KC_LBRACKET", "KC_P", "KC_O", "KC_I", "KC_U", "KC_Y"], ["TD(1)", "KC_SCOLON", "KC_L", "KC_K", "KC_J", "KC_H"], ["RSFT_T(KC_RBRACKET)", "KC_SLASH", "KC_DOT", "KC_COMMA", "KC_M", "KC_N"], ["M1", "RALT_T(KC_APPLICATION)", "KC_EQUAL", "KC_MINUS", "KC_BSLASH", "KC_END"]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "M0", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_DELETE", "KC_END", "KC_UP", "KC_HOME", "RCTL(KC_LEFT)"], ["KC_TRNS", "KC_TRNS", "KC_RIGHT", "KC_DOWN", "KC_LEFT", "RCTL(KC_RIGHT)"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_PGDOWN", "KC_PGUP"], ["M0", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"]], [["KC_ESCAPE", "KC_F1", "KC_F2", "KC_F3", "KC_F4", "KC_F5"], ["KC_TAB", "KC_F11", "KC_F12", "KC_PAUSE", "KC_PSCREEN", "KC_WFAV"], ["KC_LSHIFT", "KC_LGUI", "KC_LSHIFT", "KC_LCTRL", "KC_LALT", "KC_F22"], ["KC_LCTRL", "LGUI(KC_F22)", "LALT(KC_F22)", "LCTL(KC_F22)", "M2", "KC_F16"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_F10", "KC_F9", "KC_F8", "KC_F7", "KC_F6"], ["KC_TRNS", "KC_DELETE", "KC_END", "KC_UP", "KC_HOME", "KC_PGUP"], ["KC_TRNS", "KC_TRNS", "KC_RIGHT", "KC_DOWN", "KC_LEFT", "KC_PGDOWN"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_INSERT", "KC_CAPSLOCK"], ["KC_SCROLLLOCK", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"]], [["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_HOME", "KC_UP", "KC_END", "KC_PGUP", "KC_NO"], ["KC_TRNS", "KC_LEFT", "KC_DOWN", "KC_RIGHT", "KC_PGDOWN", "KC_NO"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_TRNS", "KC_TRNS", "M3", "KC_TRNS", "KC_TRNS"], ["KC_TRNS", "KC_KP_MINUS", "KC_KP_ASTERISK", "KC_KP_SLASH", "KC_EQUAL", "KC_NUMLOCK"], ["KC_TRNS", "KC_DELETE", "KC_KP_9", "KC_KP_8", "KC_KP_7", "LSFT(KC_9)"], ["KC_TRNS", "KC_KP_PLUS", "KC_KP_6", "KC_KP_5", "KC_KP_4", "KC_BSPACE"], ["KC_TRNS", "KC_KP_ENTER", "KC_KP_3", "KC_KP_2", "KC_KP_1", "LSFT(KC_0)"], ["M3", "KC_KP_DOT", "KC_KP_0", "KC_KP_0", "KC_COMMA", "KC_TRNS"]]], "encoder_layout": [[["KC_DOWN", "KC_UP"], ["KC_RIGHT", "KC_LEFT"]], [["KC_TRNS", "KC_TRNS"], ["KC_F21", "KC_F20"]], [["KC_VOLD", "KC_VOLU"], ["KC_DOWN", "KC_UP"]], [["KC_TRNS", "KC_TRNS"], ["KC_F21", "KC_F20"]]], "layout_options": -1, "macro": [[["tap", "KC_F23", "TG(1)"]], [["tap", "RCTL(KC_F23)", "TG(1)"]], [["tap", "RSFT(KC_F23)", "TG(3)"]], [["tap", "KC_F23", "TG(3)"]], [["tap", "KC_END", "LSFT(KC_HOME)"]], [], [], [], [], [], [], [], [], [], [], []], "vial_protocol": 5, "via_protocol": 9, "tap_dance": [["KC_ENTER", "MO(3)", "KC_NO", "KC_NO", 250], ["KC_QUOTE", "KC_RCTRL", "KC_NO", "KC_NO", 200], ["LSFT(KC_MINUS)", "KC_LSHIFT", "KC_NO", "KC_NO", 120], ["KC_MINUS", "KC_RALT", "KC_NO", "KC_NO", 200], ["LSFT(KC_8)", "LSFT(KC_6)", "KC_NO", "KC_NO", 160], ["LSFT(KC_8)", "LSFT(KC_3)", "KC_NO", "KC_NO", 160], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 160], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", 160]], "combo": [["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"], ["KC_NO", "KC_NO", "KC_NO", "KC_NO", "KC_NO"]], "key_override": [], "settings": {}} -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vial To Keymap Drawer 6 | 7 | 8 | 9 | 10 |
11 | 14 | 31 |

Vial To Keymap Drawer

32 |
33 | This tool takes your Vial .vil layouts and converts them to YAML files that 34 | you can give to keymap-drawer 35 | (web) 36 | to render your layout to SVG/PNG images!
37 | (full description and source code) 38 |
39 | 40 |
41 | Note on VIA support 42 | The tool will try to process VIA keymaps, but if you've got a split keyboard 43 | that only runs VIA, tough luck - VIA backups do not contain any information about 44 | which keys belong to which row, so you might have to enter the key ranges by hand. 45 |
46 | Consider using via2json instead. 47 |
48 | 49 | 52 | 53 | 54 | 55 | · 56 | 63 | 64 |
65 | 66 | 67 | 68 | What kind of keyboard is this? (as per Configurator) 69 |
70 | 71 | 72 | Layout name (optional; also look it up in Configurator): 73 |
74 | 75 | Include only these layer(s):
76 | 77 |
78 | 79 | 80 | Keys out of order or too many keys? 81 | 85 | 86 | 90 | 91 | 95 | 96 | Omit keys that are KC_NO on:
97 | 98 | 104 | 105 | Key move definitions (old row, old col => new row, new col):
106 | 107 | 108 | OR enter key ranges to pick from the source file:
109 | 110 | 111 | Encoder "key" locations (row, col => index):
112 | 113 | 114 | 118 |
119 | 120 |
121 | Customization 122 | Layer names:
123 | 124 | Custom key labels (layer, row, col => newkey):
125 | 126 | Mark keys that are disabled on all layers as: 127 | 133 | Note: Dashed border/invisible styles only work in dev version of keymap-drawer as of Sep 2023. 134 |
135 | 136 | Pick (or paste) your VIL layout: 137 |
138 | 139 |
140 | 141 | 142 | 146 |
147 | 148 | Output YAML-JSON: 149 | 150 | 151 | Output log: 152 | 153 | 154 |
155 | A tool by YellowAfterlife.
156 | You can 157 | give me a few dollars 158 | for making this.
159 |
160 | 161 | 192 | 193 | 194 | 195 | 196 | 197 | 204 | 205 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #899FC6; 3 | font: 15px sans-serif; 4 | line-height: 1.5; 5 | } 6 | main { 7 | position: relative; 8 | background-color: white; 9 | max-width: 640px; 10 | margin: 0 auto; 11 | padding: 0.5em; 12 | } 13 | main h1 { 14 | margin: 0; 15 | } 16 | main label:not(.inline) { 17 | display: block; 18 | } 19 | main input[type="text"], 20 | main input[type="file"], 21 | main select:not(.inline), 22 | main textarea { 23 | width: 100%; 24 | box-sizing: border-box; 25 | } 26 | main textarea.prop { 27 | font-family: sans-serif; 28 | font-size: 15px; 29 | } 30 | main textarea { 31 | resize: vertical; 32 | } 33 | main #vil-form { 34 | margin-bottom: 0.25em; 35 | } 36 | main details { 37 | padding: 0.2em 0.5em; 38 | margin: 0.5em 0; 39 | border: 1px dashed #ccc; 40 | } 41 | main details > summary { 42 | padding-left: 0.5em; 43 | } 44 | input[type="text"], 45 | textarea { 46 | padding: 0.25em 0.4em; 47 | } 48 | input[type="button"], 49 | select { 50 | padding: 0.2em 0.6em; 51 | } 52 | #key-range-editor { 53 | position: fixed; 54 | inset: 0.5em; 55 | background: white; 56 | border: 1px solid #444; 57 | box-shadow: 0 0 1em rgba(0, 0, 0, 0.1); 58 | display: flex; 59 | } 60 | #key-range-editor .sidebar { 61 | width: 8em; 62 | background: #f0f0f0; 63 | display: flex; 64 | flex-direction: column; 65 | overflow: hidden auto; 66 | padding: 0.3em; 67 | } 68 | #key-range-editor .sidebar input { 69 | margin-bottom: 0.3em; 70 | } 71 | #key-range-editor .sidebar #key-range-close { 72 | margin-top: 0.3em; 73 | margin-bottom: 0; 74 | } 75 | #key-range-editor .sidebar textarea { 76 | flex-grow: 1; 77 | } 78 | #key-range-editor .svg-ctr { 79 | flex-grow: 1; 80 | overflow-y: auto; 81 | } 82 | #key-range-editor .howto { 83 | padding: 0.5em; 84 | } 85 | #key-range-editor .svg-ctr svg { 86 | max-width: 100%; 87 | } 88 | body.night { 89 | background: #405070; 90 | color: white; 91 | } 92 | body.night a { 93 | color: #9DEC76; 94 | } 95 | body.night a:visited { 96 | color: #7ef0ff; 97 | } 98 | body.night hr, 99 | body.night details { 100 | border-color: #899FC6; 101 | } 102 | body.night input[type="text"], 103 | body.night textarea, 104 | body.night select { 105 | background: black; 106 | color: white; 107 | border: 1px solid #899FC6; 108 | } 109 | body.night input[type="button"] { 110 | background: #283248; 111 | border: 1px solid #899FC6; 112 | color: white; 113 | padding: 0.25em 0.6em; 114 | } 115 | body.night input[type="button"]:hover { 116 | background: #899FC6; 117 | } 118 | body.night main, 119 | body.night #key-range-editor .sidebar { 120 | background: #1A202D; 121 | } 122 | body.night #key-range-editor { 123 | background: #899FC6; 124 | } 125 | label[for="night-mode"] { 126 | position: absolute; 127 | right: 0.5em; 128 | top: 0.5em; 129 | } 130 | -------------------------------------------------------------------------------- /docs/style.less: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #899FC6; 3 | font: 15px sans-serif; 4 | line-height: 1.5; 5 | } 6 | main { 7 | position: relative; 8 | background-color: white; 9 | max-width: 640px; 10 | margin: 0 auto; 11 | padding: 0.5em; 12 | 13 | h1 { 14 | margin: 0; 15 | } 16 | label:not(.inline) { 17 | display: block; 18 | } 19 | input[type="text"], 20 | input[type="file"], 21 | select:not(.inline), 22 | textarea { 23 | width: 100%; 24 | box-sizing: border-box; 25 | } 26 | textarea.prop { 27 | font-family: sans-serif; 28 | font-size: 15px; 29 | } 30 | textarea { 31 | resize: vertical; 32 | } 33 | #vil-form { 34 | margin-bottom: 0.25em; 35 | } 36 | details { 37 | padding: 0.2em 0.5em; 38 | margin: 0.5em 0; 39 | border: 1px dashed #ccc; 40 | > summary { 41 | padding-left: 0.5em; 42 | } 43 | } 44 | } 45 | input[type="text"], 46 | textarea { 47 | padding: 0.25em 0.4em; 48 | } 49 | input[type="button"], select { 50 | padding: 0.2em 0.6em; 51 | } 52 | 53 | #key-range-editor { 54 | position: fixed; 55 | inset: 0.5em; 56 | background: white; 57 | border: 1px solid #444; 58 | box-shadow: 0 0 1em rgba(0, 0, 0, 0.1); 59 | display: flex; 60 | 61 | .sidebar { 62 | width: 8em; 63 | background: #f0f0f0; 64 | display: flex; 65 | flex-direction: column; 66 | overflow: hidden auto; 67 | padding: 0.3em; 68 | } 69 | .sidebar input { 70 | margin-bottom: 0.3em; 71 | } 72 | .sidebar #key-range-close { 73 | margin-top: 0.3em; 74 | margin-bottom: 0; 75 | } 76 | .sidebar textarea { 77 | flex-grow: 1; 78 | } 79 | .svg-ctr { 80 | flex-grow: 1; 81 | overflow-y: auto; 82 | } 83 | .howto { 84 | padding: 0.5em; 85 | } 86 | .svg-ctr svg { 87 | max-width: 100%; 88 | } 89 | } 90 | 91 | body.night { 92 | background: #405070; 93 | color: white; 94 | 95 | a { 96 | color: #9DEC76; 97 | } 98 | a:visited { 99 | color: #7ef0ff; 100 | } 101 | .control-border() { 102 | border: 1px solid #899FC6; 103 | } 104 | hr, details { 105 | border-color: #899FC6; 106 | } 107 | 108 | input[type="text"], 109 | textarea, 110 | select { 111 | background: black; 112 | color: white; 113 | .control-border(); 114 | } 115 | 116 | input[type="button"] { 117 | background: #283248; 118 | .control-border(); 119 | color: white; 120 | padding: 0.25em 0.6em; 121 | &:hover { 122 | background: #899FC6; 123 | } 124 | } 125 | main, #key-range-editor .sidebar { 126 | background: #1A202D; 127 | } 128 | #key-range-editor { 129 | background: #899FC6; 130 | } 131 | } 132 | 133 | label[for="night-mode"] { 134 | position: absolute; 135 | right: 0.5em; 136 | top: 0.5em; 137 | } -------------------------------------------------------------------------------- /example.svg: -------------------------------------------------------------------------------- 1 | 2 | 103 | 104 | DEF: 105 | 106 | 107 | 108 | ` 109 | ~ 110 | 111 | 112 | 113 | 1 114 | ! 115 | 116 | 117 | 118 | 2 119 | @ 120 | 121 | 122 | 123 | 3 124 | # 125 | 126 | 127 | 128 | 4 129 | $ 130 | 131 | 132 | 133 | 5 134 | % 135 | 136 | 137 | 138 | 6 139 | ^ 140 | 141 | 142 | 143 | 7 144 | & 145 | 146 | 147 | 148 | 8 149 | * 150 | 151 | 152 | 153 | ( 9 154 | _ - 155 | TD(0) 156 | 157 | 158 | 159 | ) 0 160 | + = 161 | TD(1) 162 | 163 | 164 | 165 | Bksp 166 | 167 | 168 | 169 | Tab 170 | 171 | 172 | 173 | Q 174 | 175 | 176 | 177 | W 178 | 179 | 180 | 181 | E 182 | 183 | 184 | 185 | R 186 | 187 | 188 | 189 | T 190 | 191 | 192 | 193 | Y 194 | 195 | 196 | 197 | U 198 | 199 | 200 | 201 | I 202 | 203 | 204 | 205 | O 206 | 207 | 208 | 209 | P 210 | 211 | 212 | 213 | { [ 214 | | \ 215 | TD(2) 216 | 217 | 218 | 219 | LShift 220 | 221 | 222 | 223 | A 224 | 225 | 226 | 227 | S 228 | 229 | 230 | 231 | D 232 | 233 | 234 | 235 | F 236 | 237 | 238 | 239 | G 240 | 241 | 242 | 243 | H 244 | 245 | 246 | 247 | J 248 | 249 | 250 | 251 | K 252 | 253 | 254 | 255 | L 256 | 257 | 258 | 259 | ; 260 | : 261 | 262 | 263 | 264 | ' 265 | " 266 | 267 | 268 | 269 | LCtrl 270 | 271 | 272 | 273 | Z 274 | 275 | 276 | 277 | X 278 | 279 | 280 | 281 | C 282 | 283 | 284 | 285 | V 286 | 287 | 288 | 289 | B 290 | 291 | 292 | 293 | 294 | CapsLock 295 | 296 | 297 | 298 | 299 | Mute 300 | 301 | 302 | 303 | N 304 | 305 | 306 | 307 | M 308 | 309 | 310 | 311 | , 312 | < 313 | 314 | 315 | 316 | . 317 | > 318 | 319 | 320 | 321 | / 322 | ? 323 | 324 | 325 | 326 | ] 327 | RSFT 328 | } 329 | 330 | 331 | 332 | LGui 333 | 334 | 335 | 336 | LAlt 337 | 338 | 339 | 340 | Space 341 | 342 | 343 | 344 | 345 | MONAV 346 | 347 | 348 | 349 | 350 | Enter 351 | MO MISC 352 | 353 | 354 | 355 | Esc 356 | MO MISC 357 | 358 | 359 | 360 | Left 361 | 362 | 363 | 364 | Right 365 | 366 | 367 | 368 | Up 369 | 370 | 371 | 372 | Down 373 | 374 | 375 | 376 | Esc 377 | 378 | 379 | 380 | 381 | -------------------------------------------------------------------------------- /media/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YAL-Tools/vial-to-keymap-drawer/7aa058f163f786e28a92e15446af9eb39ca80d56/media/cli.png -------------------------------------------------------------------------------- /media/example-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YAL-Tools/vial-to-keymap-drawer/7aa058f163f786e28a92e15446af9eb39ca80d56/media/example-blue.png -------------------------------------------------------------------------------- /media/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YAL-Tools/vial-to-keymap-drawer/7aa058f163f786e28a92e15446af9eb39ca80d56/media/web.png -------------------------------------------------------------------------------- /src/CommandLineOption.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | import haxe.extern.EitherType; 3 | 4 | /** 5 | * ... 6 | * @author YellowAfterlife 7 | */ 8 | class CommandLineOption { 9 | public static var prefix:String = "--"; 10 | public var name:String; 11 | public var args:Array; 12 | public var func:Dynamic; 13 | public var desc:Array; 14 | public var groupHeader:String; 15 | public function new( 16 | name:String, 17 | args:Array, 18 | func:Dynamic, 19 | desc:EitherType>, 20 | ?group:String 21 | ) { 22 | this.name = name; 23 | this.func = func; 24 | this.args = args; 25 | this.desc = desc is String ? [desc] : desc; 26 | groupHeader = group; 27 | } 28 | public static function run(args:Array, options:Array) { 29 | var i = 0; 30 | while (i < args.length) { 31 | var arg = args[i]; 32 | if (!StringTools.startsWith(arg, prefix)) { 33 | i += 1; 34 | continue; 35 | } 36 | var name = arg.substr(prefix.length); 37 | var opt = options.filter(o -> o.name == name)[0]; 38 | if (opt == null) throw '"$arg" is not a known command-line option.'; 39 | 40 | var sub = args.slice(i + 1, i + 1 + opt.args.length); 41 | try { 42 | Reflect.callMethod(opt, opt.func, sub); 43 | } catch (x:Dynamic) { 44 | throw "Error processing " + prefix + opt.name + sub + ": " + x; 45 | } 46 | args.splice(i, 1 + opt.args.length); 47 | } 48 | } 49 | public static function print(options:Array) { 50 | Sys.println("The following options are supported." 51 | + " Arguments are denoted in here"); 52 | for (i => opt in options) { 53 | if (opt.groupHeader != null) { 54 | if (i > 0) Sys.println(""); 55 | Sys.println(opt.groupHeader + ":"); 56 | } 57 | Sys.print(prefix + opt.name); 58 | for (arg in opt.args) { 59 | Sys.print(' <$arg>'); 60 | } 61 | Sys.println(""); 62 | for (line in opt.desc) { 63 | Sys.println(" " + line); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import haxe.DynamicAccess; 4 | import haxe.Http; 5 | import haxe.Json; 6 | import js.Browser; 7 | import js.Lib; 8 | import js.html.Blob; 9 | import js.html.DataListElement; 10 | import js.html.Element; 11 | import js.html.Event; 12 | import js.html.FileReader; 13 | import js.html.FormElement; 14 | import js.html.InputElement; 15 | import js.html.SelectElement; 16 | import js.html.TextAreaElement; 17 | import drawer.DrawerKeymap; 18 | import js.html.Window; 19 | import tools.ERegTools; 20 | import tools.JsonParserWithComments; 21 | import vial.VialKeymap; 22 | import VilToDrawer; 23 | using StringTools; 24 | 25 | /** 26 | * ... 27 | * @author YellowAfterlife 28 | */ 29 | class Main { 30 | public static inline var buildDate:String = tools.BuildDate.asString(); 31 | public static inline function find(id:String, ?c:Class):T { 32 | return cast Browser.document.getElementById(id); 33 | } 34 | static var fdVil:TextAreaElement = find("vil"); 35 | static var fdOut:TextAreaElement = find("out"); 36 | static var fdLog:TextAreaElement = find("log"); 37 | 38 | static var fmVil:FormElement = find("vil-form"); 39 | static var ffVil:InputElement = find("vil-picker"); 40 | 41 | static var cbHalfAfterHalf:InputElement = find("half-after-half"); 42 | static var cbMirrorRightHalf:InputElement = find("mirror-right-half"); 43 | static var cbDebugKeyPos:InputElement = find("show-key-pos"); 44 | static var ddOmitNonKeys:SelectElement = find("omit-non-keys"); 45 | static var ddMarkNonKeysAs:SelectElement = find("mark-non-keys"); 46 | static var cbOmitM1:InputElement = find("omit-m1"); 47 | static var fdKeyboard:InputElement = find("keyboard"); 48 | static var fdLayout:InputElement = find("layout"); 49 | static var fdMoveDefs:TextAreaElement = find("move-defs"); 50 | static var fdEncoderDefs:TextAreaElement = find("encoder-defs"); 51 | public static var fdKeyRanges:TextAreaElement = find("key-ranges"); 52 | 53 | static var wndKeyRangeEditor:Window = null; 54 | 55 | static var fdLayerNames:TextAreaElement = find("layer-names"); 56 | static var fdIncludeLayers:InputElement = find("include-layers"); 57 | static var fdKeyOverrides:TextAreaElement = find("key-overrides"); 58 | 59 | static var btConvert:InputElement = find("convert"); 60 | static var cbCopyAfterConvert:InputElement = find("copy-after-convert"); 61 | 62 | static var ddSample:SelectElement = find("sample"); 63 | static var btLoad:InputElement = find("load-settings"); 64 | static var btSave:InputElement = find("save-settings"); 65 | static var btClear:InputElement = find("clear"); 66 | 67 | static var fmLoad:FormElement = find("load-form"); 68 | static var ffLoad:InputElement = find("load-picker"); 69 | 70 | 71 | static var fields:Array = []; 72 | 73 | public static var latestOpt:VilToDrawerOpt = null; 74 | public static function convert_impl(forKeyRange:Bool = false) { 75 | var opt = new VilToDrawerOpt(); 76 | latestOpt = opt; 77 | 78 | fdLog.value = ""; 79 | opt.log = (level:String, v:Any) -> { 80 | fdLog.value += (fdLog.value.length > 0 ? "\n" : "") 81 | + '[$level] ' + v; 82 | }; 83 | 84 | opt.qmkKeyboard = fdKeyboard.value.trim(); 85 | opt.qmkLayout = fdLayout.value.trim(); 86 | opt.parseVil(fdVil.value); 87 | 88 | if (!forKeyRange) { 89 | opt.halfAfterHalf = cbHalfAfterHalf.checked; 90 | opt.mirrorRightHalf = cbMirrorRightHalf.checked; 91 | opt.parseMoveDefs(fdMoveDefs.value); 92 | opt.parseRangeDefs(fdKeyRanges.value); 93 | opt.parseEncoderDefs(fdEncoderDefs.value); 94 | opt.showKeyPos = cbDebugKeyPos.checked; 95 | } else { 96 | opt.combos = false; 97 | opt.outKeys = []; 98 | } 99 | opt.omitNonKeys = Std.parseInt(ddOmitNonKeys.value); 100 | opt.omitM1 = cbOmitM1.checked; 101 | 102 | opt.parseLayerNames(fdLayerNames.value); 103 | if (!forKeyRange) { 104 | opt.parseIncludeLayers(fdIncludeLayers.value); 105 | } else opt.includeLayers.push(0); 106 | opt.parseKeyOverrides(fdKeyOverrides.value); 107 | opt.markNonKeysAs = ddMarkNonKeysAs.value; 108 | if (opt.markNonKeysAs == "") opt.markNonKeysAs = null; 109 | 110 | try { 111 | var result = VilToDrawer.runTxt(opt); 112 | opt.info("Done!"); 113 | return result; 114 | } catch (x:Dynamic) { 115 | Browser.console.error("Conversion error:", x); 116 | opt.error(x); 117 | return null; 118 | } 119 | } 120 | static function convert() { 121 | var text = convert_impl(); 122 | if (text != null) { 123 | fdOut.value = text; 124 | } else fdOut.value = "error!"; 125 | } 126 | static function clear() { 127 | for (fd in fields) fd.reset(); 128 | } 129 | static function applySettings(root:WebSettings) { 130 | clear(); 131 | for (id => val in root.fields) { 132 | var fd = fields.filter(f -> f.id == id)[0]; 133 | if (fd == null) continue; 134 | fd.set(val); 135 | } 136 | } 137 | static function saveSettings() { 138 | var root:WebSettings = { 139 | resourceType: "https://yal-tools.github.io/vial-to-keymap-drawer/", 140 | fields: {}, 141 | }; 142 | for (fd in fields) { 143 | root.fields[fd.id] = fd.get(); 144 | } 145 | var blob = new Blob([Json.stringify(root, null, "\t")]); 146 | (cast Browser.window).saveAs(blob, "settings.json", "application/json"); 147 | } 148 | static function loadSample(name:String = "yal-sofle") { 149 | var sfx = "?t=" + buildDate; 150 | var rs = new Http('examples/$name.json' + sfx); 151 | rs.onData = function(s) { 152 | applySettings(Json.parse(s)); 153 | var rv = new Http('examples/$name.vil' + sfx); 154 | rv.onData = function(s) { 155 | fdVil.value = s; 156 | convert(); 157 | } 158 | rv.request(); 159 | }; 160 | rs.request(); 161 | } 162 | static function main() { 163 | var local = Browser.document.location.hostname == "localhost"; 164 | 165 | for (node in Browser.document.querySelectorAll([ 166 | "main input[id]", 167 | "main textarea[id]", 168 | "main select[id]", 169 | ].join(", "))) { 170 | var el:Element = cast node; 171 | if (el.classList.contains("transient")) continue; 172 | if (el == fdOut || el == fdVil) continue; 173 | if (el.tagName == "INPUT") { 174 | var ei:InputElement = cast el; 175 | switch (ei.type) { 176 | case "text": fields.push({ 177 | id: el.id, 178 | get: function() return ei.value, 179 | set: function(val) ei.value = val, 180 | reset: function() ei.value = "", 181 | }); 182 | case "checkbox": fields.push({ 183 | id: el.id, 184 | get: function() return ei.checked, 185 | set: function(val) ei.checked = val, 186 | reset: function() ei.checked = false, 187 | }); 188 | } 189 | } 190 | else if (el.tagName == "SELECT") { 191 | var es:SelectElement = cast el; 192 | fields.push({ 193 | id: el.id, 194 | get: function() return es.value, 195 | set: function(val) es.value = val, 196 | reset: function() es.selectedIndex = 0, 197 | }); 198 | } 199 | else { 200 | var et:TextAreaElement = cast el; 201 | fields.push({ 202 | id: el.id, 203 | get: function() { 204 | if (et.value.trim() == "") return []; 205 | return et.value.split("\n"); 206 | }, 207 | set: function(val) { 208 | if (val is Array) { 209 | et.value = (val:Array).join("\n"); 210 | } else et.value = val; 211 | }, 212 | reset: function() et.value = "", 213 | }); 214 | } 215 | } 216 | 217 | ffVil.onchange = function(e:Event) { 218 | var file = ffVil.files[0]; 219 | if (file == null) return; 220 | 221 | var fileReader = new FileReader(); 222 | fileReader.onloadend = function() fmVil.reset(); 223 | fileReader.onload = function() { 224 | fdVil.value = fileReader.result; 225 | } 226 | fileReader.readAsText(file); 227 | } 228 | 229 | btClear.onclick = function() { 230 | if (!Browser.window.confirm( 231 | "Are you sure that you want to clear settings? This cannot be undone!" 232 | )) return; 233 | clear(); 234 | } 235 | 236 | ffLoad.onchange = function() { 237 | var file = ffLoad.files[0]; 238 | if (file == null) return; 239 | 240 | var fileReader = new FileReader(); 241 | fileReader.onloadend = function() fmLoad.reset(); 242 | fileReader.onload = function() { 243 | try { 244 | applySettings(JsonParserWithComments.parse(fileReader.result)); 245 | } catch (x:Dynamic) { 246 | Browser.alert("Error loading settings: " + x); 247 | } 248 | } 249 | fileReader.readAsText(file); 250 | } 251 | 252 | btConvert.onclick = function() { 253 | convert(); 254 | if (cbCopyAfterConvert.checked) Browser.navigator.clipboard.writeText(fdOut.value); 255 | } 256 | btSave.onclick = function() saveSettings(); 257 | btLoad.onclick = function() ffLoad.click(); 258 | ddSample.onchange = function() { 259 | var name = ddSample.value; 260 | if (name == "") return; 261 | if (!Browser.window.confirm( 262 | "Are you sure that you want to replace your settings with the example? This cannot be undone!" 263 | )) { 264 | ddSample.selectedIndex = 0; 265 | return; 266 | } 267 | ddSample.selectedIndex = 0; 268 | loadSample(name); 269 | } 270 | 271 | var kbjs = Browser.document.createScriptElement(); 272 | kbjs.onload = function() { 273 | var list:DataListElement = find("qmk_keyboard"); 274 | for (kb in ((cast Browser.window).qmk_keyboards:Array)) { 275 | var opt = Browser.document.createOptionElement(); 276 | opt.value = kb; 277 | list.appendChild(opt); 278 | } 279 | }; 280 | kbjs.async = true; 281 | kbjs.src = "qmk_keyboards.js"; 282 | Browser.document.body.appendChild(kbjs); 283 | 284 | if (local) loadSample("cepstrum"); 285 | web.KeyRangeCreator.init(); 286 | Browser.console.info("Hello!"); 287 | } 288 | 289 | } 290 | typedef PageField = { 291 | id: String, 292 | reset: Void->Void, 293 | get: Void->Any, 294 | set: Any->Void, 295 | } 296 | typedef WebSettings = { 297 | resourceType:String, 298 | fields:DynamicAccess, 299 | } -------------------------------------------------------------------------------- /src/SysMain.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | import sys.io.File; 3 | using StringTools; 4 | 5 | /** 6 | * ... 7 | * @author YellowAfterlife 8 | */ 9 | class SysMain { 10 | static var config:VilToDrawerOpt = { 11 | var c = new VilToDrawerOpt(); 12 | c.log = function(level, val) { 13 | Sys.print('[$level] '); 14 | Sys.print(val); 15 | Sys.println(''); 16 | }; 17 | c; 18 | }; 19 | 20 | static function error(text:String) { 21 | Sys.println(text); 22 | Sys.exit(1); 23 | } 24 | static function getText(path:String) { 25 | try { 26 | var text = File.getContent(path); 27 | text = text.replace("\r", ""); 28 | return text; 29 | } catch (x:Dynamic) { 30 | error('Failed to read "$path":' + x); 31 | return null; 32 | } 33 | } 34 | static var cliOpts:Array = [ 35 | new CommandLineOption("help", [], function() { 36 | help(); 37 | }, [ 38 | "Shows this documentation and exits.", 39 | ]), 40 | 41 | new CommandLineOption("vil", ["path"], function(path) { 42 | config.parseVil(getText(path)); 43 | }, "Specifies a Vial .vil file to convert"), 44 | new CommandLineOption("via", ["path"], function(path) { 45 | config.parseVil(getText(path)); 46 | }, "Specifies a VIA .json file to convert"), 47 | new CommandLineOption("force-json", [], function() { 48 | config.yamlLike = false; 49 | }, [ 50 | "Outputs strict JSON instead of YAML" 51 | ]), 52 | 53 | new CommandLineOption("keyboard", ["name"], function(val) { 54 | config.qmkKeyboard = val; 55 | }, [ 56 | "Specifies a QMK keyboard name to use, such as", 57 | "corne_rotated", 58 | "splitkb/aurora/sofle_v2/rev1", 59 | "Find the name in configurator: https://config.qmk.fm/", 60 | ]), 61 | new CommandLineOption("layout", ["name"], function(val) { 62 | config.qmkLayout = val; 63 | }, [ 64 | "Specifies a physical layout to use, if keyboard has multiple.", 65 | "For example, corne_rotated has a LAYOUT_split_3x5_3.", 66 | ]), 67 | new CommandLineOption("layer", ["index(es)"], function(val) { 68 | config.parseIncludeLayers(val); 69 | }, [ 70 | "Only includes the specified layer (like `0`) or layers (like `0,1` in the output)." 71 | ]), 72 | 73 | new CommandLineOption("layer-names", ["path"], function(path) { 74 | config.parseLayerNames(getText(path)); 75 | }, [ 76 | "Specifies a file to load layer names from (one per line)", 77 | ], "Customization"), 78 | new CommandLineOption("key-labels", ["path"], function(path) { 79 | config.parseKeyOverrides(getText(path)); 80 | }, [ 81 | "Specifies a file to load key label overrides from. One per line, formatted as", 82 | "layer number, row, col => new key text", 83 | "For example,", 84 | "0,1,2 => COOL", 85 | "Would replace the label on third key in second row in first layer by COOL.", 86 | "Custom labels, KC_ names, and lean JSON objects {h:'Hold', t:'Tap'} are supported." 87 | ]), 88 | new CommandLineOption("mark-unused", ["style"], function(cn) { 89 | config.markNonKeysAs = cn; 90 | }, [ 91 | "Shows keys that are set to KC_NO or KC_TRNS on all layers in a different way.", 92 | "Supported options are:", 93 | "ghost : Default keymap-drawer style for layout-optional keys", 94 | "unused : Shows a dashed outline for the keys, without fill", 95 | "hidden : Hides the keys completely", 96 | ]), 97 | 98 | new CommandLineOption("half-after-half", [], function() { 99 | config.halfAfterHalf = true; 100 | }, [ 101 | "Specifies that keys are stored half-by-half and row-by-row", 102 | "(L1, L2, L3, R1, R2, R3 instead of L1, R1, L2, R2, L3, R3)", 103 | ], "Tweaks"), 104 | new CommandLineOption("mirror-right-half", [], function() { 105 | config.mirrorRightHalf = true; 106 | }, [ 107 | "Specifies that keys on the right half of keyboard are stored right-to-left", 108 | "Happens to keyboards where the PCB and software is identical for two halves", 109 | ]), 110 | new CommandLineOption("move-defs", ["path"], function(path) { 111 | config.parseMoveDefs(getText(path)); 112 | }, [ 113 | "Specifies a file to load key move definitions from.", 114 | "One per line, formatted as one of the folllowing:", 115 | "old row, old col => new row, new col", 116 | "old row, old col[count] => new row, new col", 117 | "old row, old start col - old end col => new row, new col", 118 | "For example,", 119 | "1,2 => 0,5", 120 | "Would move third key from second row to sixth position in the first row.", 121 | "Rows and columns are counted as they appear in .vil", 122 | ]), 123 | new CommandLineOption("encoder-defs", ["path"], function(path) { 124 | config.parseEncoderDefs(getText(path)); 125 | }, [ 126 | "Specifies a file to load encoder definitions from for Vial.", 127 | "One per line, formatted as following:", 128 | "row, col => index", 129 | "For example,", 130 | "1,2 => 0", 131 | "Would attach first encoder's mappings to third key from second row.", 132 | "Rows and columns are counted as they appear in .vil", 133 | ]), 134 | new CommandLineOption("key-ranges", ["path"], function(path) { 135 | config.parseRangeDefs(getText(path)); 136 | }, [ 137 | "Specifies a file to load key ranges from.", 138 | "One per line, formatted as one of the folllowing:", 139 | "row, start col - end col", 140 | "row, col", 141 | "This overrides all of the above tweaks and can be used if the exported layout", 142 | "is wildly different from the QMK layout.", 143 | ]), 144 | new CommandLineOption("show-key-row-col", [], function(path) { 145 | config.parseRangeDefs(getText(path)); 146 | }, [ 147 | "Shows row,column for each key inside the 'Shift' state.", 148 | "Can make it easier to figure out key ranges.", 149 | ]), 150 | new CommandLineOption("omit-m1", [], function() { 151 | config.omitM1 = true; 152 | }, [ 153 | "Omits keys are set to -1 on all layers", 154 | "Occasionally used for optional keys in Vial builds.", 155 | ]), 156 | new CommandLineOption("omit-no", ["layer-count"], function(ns) { 157 | var n = Std.parseInt(ns); 158 | if (n == null) throw '"$ns" is not a valid number.'; 159 | config.omitNonKeys = n; 160 | }, [ 161 | "Omit keys that are set to KC_NO on the given number of layers.", 162 | "Occasionally used for optional keys in VIA builds.", 163 | "Set to -1 to check all layers.", 164 | ]), 165 | ]; 166 | 167 | static function help() { 168 | CommandLineOption.print(cliOpts); 169 | Sys.exit(0); 170 | } 171 | public static function main() { 172 | var args = Sys.args().copy(); 173 | if (args.length == 0) help(); 174 | 175 | try { 176 | CommandLineOption.run(args, cliOpts); 177 | } catch (x:Dynamic) { 178 | error("Error processing arguments: " + x); 179 | } 180 | 181 | if (args.length < 1) { 182 | error("Expected a .yaml path apart of the options"); 183 | } else if (args.length > 1) { 184 | error('Multiple candidates for YAML path: ["' + args.join('","') + '"]'); 185 | } 186 | var yamlPath = args[0]; 187 | 188 | Sys.println("Converting..."); 189 | var yamlTxt = VilToDrawer.runTxt(config); 190 | Sys.println('Writing to "$yamlPath"...'); 191 | File.saveContent(args[0], yamlTxt); 192 | Sys.println("OK!"); 193 | } 194 | } -------------------------------------------------------------------------------- /src/VilToDrawer.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | import drawer.DrawerKeymap; 3 | import haxe.DynamicAccess; 4 | import haxe.Json; 5 | import tools.ERegTools; 6 | import vial.VialKeyNames; 7 | import vial.VialKeymap; 8 | import vial.VialKey; 9 | using StringTools; 10 | 11 | /** 12 | * ... 13 | * @author YellowAfterlife 14 | */ 15 | class VilToDrawer { 16 | #if js 17 | public static inline var needsHxOrder:Bool = false; 18 | #else 19 | public static inline var needsHxOrder:Bool = true; 20 | #end 21 | static function keysToInfos(keys:Array, layer:Int, row:Int, ind:Int):Array { 22 | var infos:Array = []; 23 | for (i => key in keys) { 24 | infos.push({ 25 | layer: layer, 26 | row: row, 27 | col: i, 28 | ind: ind + i, 29 | key: key, 30 | }); 31 | } 32 | return infos; 33 | } 34 | static function procViaLayers(opt:VilToDrawerOpt) { 35 | var vLayers:Array> = []; 36 | for (l => layer in opt.root.layers) { 37 | var keys:Array = keysToInfos(layer, l, 0, 0); 38 | function checkPos(row:Int, col:Int, rule:String) { 39 | if (row != 0) { 40 | opt.warn('Row should be 0 is VIA layouts (found $row in "$rule")'); 41 | } 42 | if (col < 0 || col >= keys.length) { 43 | opt.error('Column $col out of bounds for "$rule"'); 44 | return true; 45 | } 46 | return false; 47 | } 48 | if (opt.rangeDefs.length > 0) { 49 | var nkeys = []; 50 | for (rdef in opt.rangeDefs) { 51 | if (checkPos(rdef.row, rdef.col, rdef.rule)) continue; 52 | nkeys = nkeys.concat(keys.slice(rdef.col, rdef.col + rdef.count)); 53 | } 54 | keys = nkeys; 55 | } 56 | else for (def in opt.moveDefs) { 57 | if (checkPos(def.srcRow, def.srcCol, def.rule)) continue; 58 | var sub = keys.splice(def.srcCol, def.count); 59 | sub.reverse(); 60 | for (key in sub) keys.insert(def.dstCol, key); 61 | } 62 | vLayers.push(keys); 63 | } 64 | return vLayers; 65 | } 66 | static function procVialLayers(opt:VilToDrawerOpt) { 67 | var vLayers:Array> = []; 68 | for (i => layer in opt.root.layout) { // Vial 69 | var rows = []; 70 | var infId = 0; 71 | for (k => row in layer) { 72 | var infos = keysToInfos(row, i, k, infId); 73 | rows.push(infos); 74 | infId += infos.length; 75 | } 76 | function checkPos(row:Int, col:Int, rule:String) { 77 | if (row < 0 || row >= rows.length) { 78 | opt.error('Row $row out of bounds for "$rule"'); 79 | return true; 80 | } 81 | if (col < 0 || col >= rows[row].length) { 82 | opt.error('Column $col out of bounds for "$rule"'); 83 | return true; 84 | } 85 | return false; 86 | } 87 | // 88 | for (ko in opt.keyOverrides) if (ko.layer == i) { 89 | if (checkPos(ko.row, ko.col, ko.rule)) continue; 90 | rows[ko.row][ko.col].key = ko.key; 91 | } 92 | // 93 | var keys = []; 94 | if (opt.rangeDefs.length > 0) for (rd in opt.rangeDefs) { 95 | if (checkPos(rd.row, rd.col, rd.rule)) continue; 96 | keys = keys.concat(rows[rd.row].slice(rd.col, rd.count)); 97 | } 98 | else { 99 | // 100 | for (moveDef in opt.moveDefs) { 101 | if (checkPos(moveDef.srcRow, moveDef.srcCol, moveDef.rule)) continue; 102 | var sub = rows[moveDef.srcRow].splice(moveDef.srcCol, moveDef.count); 103 | sub.reverse(); 104 | for (key in sub) rows[moveDef.dstRow].insert(moveDef.dstCol, key); 105 | } 106 | // 107 | var rowCount = layer.length; 108 | var halfRowCount = rowCount >> 1; 109 | var newRows = [for (_ in 0 ... rowCount) null]; 110 | for (rk => row in rows) { 111 | var dk = rk; 112 | if (opt.halfAfterHalf) { 113 | if (dk >= halfRowCount) { 114 | dk = (dk - halfRowCount) * 2 + 1; 115 | if (opt.mirrorRightHalf) row.reverse(); 116 | } else { 117 | dk *= 2; 118 | } 119 | } 120 | newRows[dk] = row; 121 | } 122 | // 123 | for (row in newRows) for (key in row) keys.push(key); 124 | } 125 | vLayers.push(keys); 126 | } 127 | return vLayers; 128 | } 129 | static function postProcLayers(vLayers:Array>, opt:VilToDrawerOpt) { 130 | for (encDef in opt.encoderDefs) { 131 | for (vLayer in vLayers) { 132 | for (ki in vLayer.filter(ki -> ki.col == encDef.col && ki.row == encDef.row)) { 133 | ki.encoder = encDef.index; 134 | } 135 | } 136 | } 137 | var omitNonKeys = opt.omitNonKeys; 138 | if (omitNonKeys != 0) { 139 | var k = vLayers[0].length; 140 | while (--k >= 0) { 141 | if (vLayers[0][k].key != "KC_NO") continue; 142 | var isNon = true; 143 | for (l2 in 1 ... vLayers.length) { 144 | if (omitNonKeys > 0 && l2 >= omitNonKeys) continue; 145 | if (vLayers[l2][k].key == "KC_NO") continue; 146 | isNon = false; 147 | break; 148 | } 149 | if (isNon) for (vkeys in vLayers) vkeys.splice(k, 1); 150 | } 151 | } 152 | if (opt.omitM1) { 153 | var k = vLayers[0].length; 154 | while (--k >= 0) { 155 | if (!vLayers[0][k].key.isM1()) continue; 156 | var isM1 = true; 157 | for (l in 0 ... vLayers.length) { 158 | if (vLayers[l][k].key.isM1()) continue; 159 | isM1 = false; 160 | break; 161 | } 162 | if (!isM1) continue; 163 | for (l in 0 ... vLayers.length) { 164 | vLayers[l].splice(k, 1); 165 | } 166 | } 167 | } 168 | } 169 | public static function runTxt(opt:VilToDrawerOpt):String { 170 | var vkm = opt.root; 171 | // 172 | inline function getLayerName(li:Int) { 173 | return opt.getLayerName(li, true); 174 | } 175 | var isVial = !opt.isVIA; 176 | 177 | // layer -> keys[] 178 | var vLayers:Array> = isVial ? procVialLayers(opt) : procViaLayers(opt); 179 | postProcLayers(vLayers, opt); 180 | if (opt.outKeys != null) { 181 | for (vLayer in vLayers) { 182 | for (kp in vLayer) { 183 | opt.outKeys.push(kp); 184 | } 185 | } 186 | } 187 | 188 | // 189 | var dkLayers:DynamicAccess = new DynamicAccess(); 190 | var dkLayerList:Array = []; 191 | var layerNames = []; 192 | var usesEncoders = false; 193 | for (li => vkeys in vLayers) { 194 | if (opt.includeLayers.length > 0 && !opt.includeLayers.contains(li)) continue; 195 | var dkeys = []; 196 | for (k => kc in vkeys) { 197 | var dk:DrawerKey = kc.key.toDrawerKey(opt, kc.encoder != null); 198 | dk = dk.postproc(opt); 199 | // is this a held key? 200 | var mo = "MO(" + li + ")"; 201 | var lts = "LT" + li + "("; 202 | var held = false; 203 | for (vkeys2 in vLayers) { 204 | if (vkeys == vkeys2) continue; 205 | var kc2 = vkeys2[k].key; 206 | if (kc2 == null) continue; 207 | if (kc2 == mo || (kc2:String).startsWith(lts)) { 208 | held = true; 209 | break; 210 | } 211 | } 212 | 213 | if (held) { 214 | var dkx = dk.toExt(); 215 | dkx.type = "held"; 216 | if (dkx.t == VialKeyNames.map["KC_TRNS"]) dkx.t = ""; 217 | dk = dkx; 218 | } 219 | 220 | if (kc.encoder != null && isVial) { 221 | var encPair = opt.root.encoder_layout[kc.layer][kc.encoder]; 222 | var encDn = encPair[0].toDrawerKey(opt, true).postproc(opt).toFlat(DrawerKeyAction.Tap); 223 | var encUp = encPair[1].toDrawerKey(opt, true).postproc(opt).toFlat(DrawerKeyAction.Tap); 224 | var dkx = dk.toExt(); 225 | dkx.s = "↑" + encUp; 226 | dkx.h = "↓" + encDn; 227 | dkx.type = "encoder"; 228 | usesEncoders = true; 229 | dk = dkx; 230 | } 231 | 232 | if (opt.showKeyPos) { 233 | var dkx = dk.toExt(); 234 | dkx.s = kc.row + "," + kc.col; 235 | dk = dkx; 236 | } 237 | 238 | dkeys.push(dk); 239 | } 240 | var ln = getLayerName(li); 241 | dkLayers[ln] = dkeys; 242 | dkLayerList.push(dkeys); 243 | layerNames.push(ln); 244 | } 245 | if (needsHxOrder) dkLayers["__hxOrder__"] = cast layerNames; 246 | // 247 | var extraStyle:Array = []; 248 | if (usesEncoders) { 249 | //trace("enc!"); 250 | /*extraStyle = extraStyle.concat([ 251 | "rect.encoder {", 252 | "\t" + "rx: 1024;", 253 | "\t" + "ry: 1024;", 254 | "\t" + "transform: scale(1.2);", 255 | "}", 256 | ]);*/ 257 | } 258 | if (opt.markNonKeysAs != null) { 259 | var found = false; 260 | var trns = VialKeyNames.map["KC_TRNS"]; 261 | function isNon(dk:DrawerKey) { 262 | return dk == null || dk == trns; 263 | } 264 | for (k in 0 ... dkLayerList[0].length) { 265 | if (!isNon(dkLayerList[0][k])) continue; 266 | if (dkLayerList.filter(dkeys -> !isNon(dkeys[k])).length != 0) continue; 267 | for (dkeys in dkLayerList) { 268 | var dk = dkeys[k].toExt(); 269 | dk.t = ""; 270 | dk.type = opt.markNonKeysAs; 271 | dkeys[k] = dk; 272 | found = true; 273 | } 274 | } 275 | if (found) switch (opt.markNonKeysAs) { 276 | case "unused": extraStyle = extraStyle.concat([ 277 | "rect.unused, rect.combo.unused {", 278 | "\t" + "fill: transparent;", 279 | "\t" + "stroke-dasharray: 4, 6;", 280 | "\t" + "stroke-width: 2;", 281 | "}", 282 | ]); 283 | case "hidden": extraStyle = extraStyle.concat([ 284 | "rect.hidden, rect.combo.hidden {", 285 | "\t" + "fill: transparent;", 286 | "\t" + "stroke-width: 0;", 287 | "}", 288 | ]); 289 | } 290 | } 291 | // 292 | var dCombos:Array = []; 293 | if (vkm.combo != null && opt.combos) for (vCombo in vkm.combo) { 294 | var iResult = vCombo.length - 1; 295 | var inKeys = []; 296 | for (i in 0 ... iResult) if (vCombo[i].isValid()) inKeys.push(vCombo[i]); 297 | if (inKeys.length < 2) continue; 298 | var cResult = vCombo[iResult]; 299 | if (!cResult.isValid()) continue; 300 | 301 | for (li => vKeys in vLayers) { 302 | var ln = getLayerName(li); 303 | if (!dkLayers.exists(ln)) continue; 304 | var keyPos = []; 305 | for (key in inKeys) { 306 | var kp = -1; 307 | for (kid => ki in vKeys) { 308 | if (ki.key == key) { 309 | kp = kid; 310 | break; 311 | } 312 | } 313 | if (kp >= 0) keyPos.push(kp); else break; 314 | } 315 | if (keyPos.length < inKeys.length) continue; 316 | var dk = cResult.toDrawerKey(opt); 317 | dk = dk.postproc(opt); 318 | dCombos.push({ 319 | p: keyPos, 320 | k: dk, 321 | l: [ln], 322 | }); 323 | } 324 | } 325 | // 326 | var dkm:DrawerKeymap = { 327 | layout: { 328 | qmk_keyboard: opt.qmkKeyboard 329 | }, 330 | layers: dkLayers, 331 | combos: dCombos, 332 | }; 333 | if (extraStyle.length > 0) { 334 | dkm.draw_config = { 335 | svg_extra_style: extraStyle.join("\n"), 336 | }; 337 | } 338 | if (opt.qmkLayout != null && opt.qmkLayout != "") dkm.layout.qmk_layout = opt.qmkLayout; 339 | 340 | if (opt.yamlLike) { 341 | var rxId = ~/^[_a-zA-Z][_a-zA-Z0-9]*$/; 342 | function idOrJson(val:Any) { 343 | if (val is String && rxId.match(val)) return val; 344 | return Json.stringify(val); 345 | } 346 | var yb = new StringBuf(); 347 | yb.add('layout: ' + Json.stringify(dkm.layout) + '\n'); 348 | yb.add('layers:\n'); 349 | for (ln in layerNames) { 350 | yb.add(' ' + idOrJson(ln) + ':\n'); 351 | for (key in dkm.layers[ln]) { 352 | yb.add(' - ' + idOrJson(key) + '\n'); 353 | } 354 | } 355 | if (dkm.combos != null && dkm.combos.length > 0) { 356 | yb.add('combos:\n'); 357 | for (combo in dkm.combos) { 358 | yb.add(' - ' + idOrJson(combo) + '\n'); 359 | } 360 | } 361 | for (fd in Reflect.fields(dkm)) { 362 | switch (fd) { 363 | case "layout", "layers", "combos": continue; 364 | } 365 | yb.add(fd + ': ' + Json.stringify(Reflect.field(dkm, fd)) + '\n'); 366 | } 367 | return yb.toString(); 368 | } else { 369 | if (needsHxOrder) { 370 | return tools.JsonPrinterWithOrder.print(dkm, null, " "); 371 | } else { 372 | return Json.stringify(dkm, null, " "); 373 | } 374 | } 375 | } 376 | } 377 | typedef VialKeyInfo = { 378 | layer:Int, 379 | row:Int, 380 | col:Int, 381 | ind:Int, 382 | ?encoder:Int, 383 | key:VialKey 384 | }; -------------------------------------------------------------------------------- /src/VilToDrawerOpt.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | import drawer.DrawerKeymap; 3 | import haxe.DynamicAccess; 4 | import haxe.Json; 5 | import tools.ERegTools; 6 | import vial.VialKeymap; 7 | import vial.VialKey; 8 | import VilToDrawer; 9 | using StringTools; 10 | using tools.ERegTools; 11 | 12 | /** 13 | * ... 14 | * @author YellowAfterlife 15 | */ 16 | class VilToDrawerOpt { 17 | public var root:VialKeymap; 18 | public var isVIA:Bool; 19 | public var qmkKeyboard:String = null; 20 | public var qmkLayout:String = null; 21 | public var halfAfterHalf = false; 22 | public var mirrorRightHalf = false; 23 | public var omitNonKeys = 0; 24 | public var markNonKeysAs:String = null; 25 | public var omitM1 = false; 26 | public var includeLayers:Array = []; 27 | public var layerNames:Array = []; 28 | public var moveDefs:Array = []; 29 | public var rangeDefs:Array = []; 30 | public var encoderDefs:Array = []; 31 | public var keyOverrides:Array = []; 32 | /** */ 33 | public var keyNames:Map = new Map(); 34 | public var showKeyPos = false; 35 | public var yamlLike = true; 36 | 37 | public var combos = true; 38 | public var outKeys:Array = null; 39 | 40 | public function new() {} 41 | 42 | public dynamic function log(level:String, v:Any) { 43 | trace('[$level]', v); 44 | } 45 | public dynamic function info(v:Any) { 46 | log("info", v); 47 | } 48 | public dynamic function warn(v:Any) { 49 | log("warn", v); 50 | } 51 | public dynamic function error(v:Any) { 52 | log("error", v); 53 | } 54 | 55 | public function getLayerName(i:Int, long:Bool) { 56 | if (i < layerNames.length) { 57 | var l = layerNames[i]; 58 | return long ? l.long : l.short; 59 | } else return "L" + i; 60 | } 61 | 62 | public function parseVil(txt:String) { 63 | root = Json.parse(txt); 64 | isVIA = root.layers != null; 65 | } 66 | 67 | static var rxLayerShortLong = ~/^(\S{1,6})(?::.*|\s+\(.*\))$/; 68 | public function parseLayerNames(txt:String) { 69 | txt = txt.replace("\r", "").trim(); 70 | if (txt == "") return; 71 | var rx = rxLayerShortLong; 72 | for (line in txt.split("\n")) { 73 | line = line.trim(); 74 | if (rx.match(line)) { 75 | layerNames.push({ 76 | short: rx.matched(1), 77 | long: rx.matched(0), 78 | }); 79 | } else if (line.length <= 6) { 80 | layerNames.push({ 81 | short: line, 82 | long: line, 83 | }); 84 | } else { 85 | var lis = "L" + layerNames.length; 86 | layerNames.push({ 87 | short: lis, 88 | long: lis + ": " + line, 89 | }); 90 | } 91 | } 92 | } 93 | 94 | static var rxMoveDef = new EReg("^\\s*" 95 | + "(\\d+),\\s*" 96 | + "(\\d+)\\s*" 97 | + "(?:" 98 | + "\\[\\s*" + "(\\d+)" + "\\s*\\]\\s*" + "|" // row,col[n] 99 | + "\\-\\s*" + "(\\d+)" + "\\s*" // row,col-col2 100 | + ")?" 101 | + "=>\\s*" 102 | + "(\\d+),\\s*" 103 | + "(\\d+)" 104 | + "\\s*$", "gm"); 105 | public function parseMoveDefs(txt:String) { 106 | ERegTools.each(rxMoveDef, txt, function(rx:EReg) { 107 | var srcCol = Std.parseInt(rx.matched(2)); 108 | var ns = rx.matched(3); 109 | var n:Int; 110 | if (ns != null) { 111 | n = Std.parseInt(ns); 112 | } else if ((ns = rx.matched(4)) != null) { 113 | n = Std.parseInt(ns) + 1 - srcCol; 114 | } else n = 1; 115 | var at = 5; 116 | moveDefs.push({ 117 | srcRow: Std.parseInt(rx.matched(1)), 118 | srcCol: srcCol, 119 | count: n, 120 | dstRow: Std.parseInt(rx.matched(at)), 121 | dstCol: Std.parseInt(rx.matched(at + 1)), 122 | rule: rx.matched(0), 123 | }); 124 | }); 125 | } 126 | 127 | static var rxRangeDef = new EReg("^\\s*" 128 | + "(\\d+),\\s*" 129 | + "(\\d+)\\s*" 130 | + "(?:" + "\\-\\s*" + "(\\d+)" + "\\s*" + ")?" 131 | + "$", "gm"); 132 | public function parseRangeDefs(txt:String) { 133 | ERegTools.each(rxRangeDef, txt, function(rx:EReg) { 134 | var col = Std.parseInt(rx.matched(2)); 135 | var tillStr = rx.matched(3); 136 | var count:Int; 137 | if (tillStr != null) { 138 | var till = Std.parseInt(rx.matched(3)); 139 | count = till + 1 - col; 140 | } else count = 1; 141 | rangeDefs.push({ 142 | row: Std.parseInt(rx.matched(1)), 143 | col: col, 144 | count: count, 145 | rule: rx.matched(0), 146 | }); 147 | }); 148 | } 149 | 150 | public function parseIncludeLayers(txt:String) { 151 | ~/\d+/g.each(txt, function(rx:EReg) { 152 | var s = rx.matched(0); 153 | var i = Std.parseInt(s); 154 | if (i == null) { 155 | error('"$s" is not a valid layer number'); 156 | } else includeLayers.push(i); 157 | }); 158 | } 159 | 160 | static var rxKeyOverride = ~/^\s*(\d+),\s*(\d+),\s*(\d+)\s*=>\s*(.+)/gm; 161 | static var rxKeyOverrideName = ~/^\s*(".*?[^\\]")\s*=>\s*(.+)/gm; 162 | public function parseKeyOverrides(txt:String) { 163 | ERegTools.each(rxKeyOverride, txt, function(rx:EReg) { 164 | keyOverrides.push({ 165 | layer: Std.parseInt(rx.matched(1)), 166 | row: Std.parseInt(rx.matched(2)), 167 | col: Std.parseInt(rx.matched(3)), 168 | key: rx.matched(4), 169 | rule: rx.matched(0), 170 | }); 171 | }); 172 | ERegTools.each(rxKeyOverrideName, txt, function(rx:EReg) { 173 | var key = try { 174 | Json.parse(rx.matched(1)); 175 | } catch (x:Dynamic) { 176 | error("Couldn't parse " + rx.matched(1) + ": " + x); 177 | return; 178 | } 179 | keyNames[key.toLowerCase()] = rx.matched(2); 180 | }); 181 | } 182 | 183 | static var rxEncoderDef = ~/^\s*(\d+),\s*(\d+)\s*=>\s*(\d+)/gm; 184 | public function parseEncoderDefs(txt:String) { 185 | ERegTools.each(rxEncoderDef, txt, function(rx:EReg) { 186 | encoderDefs.push({ 187 | row: Std.parseInt(rx.matched(1)), 188 | col: Std.parseInt(rx.matched(2)), 189 | index: Std.parseInt(rx.matched(3)), 190 | rule: rx.matched(0), 191 | }); 192 | }); 193 | } 194 | } 195 | typedef VilToDrawerKeyOverride = { 196 | var layer:Int; 197 | var row:Int; 198 | var col:Int; 199 | var key:String; 200 | var rule:String; 201 | } 202 | typedef VilToDrawerLayerName = { 203 | var short:String; 204 | var long:String; 205 | }; 206 | typedef VilToDrawerMoveDef = { 207 | var srcRow:Int; 208 | var srcCol:Int; 209 | var dstRow:Int; 210 | var dstCol:Int; 211 | var count:Int; 212 | var rule:String; 213 | }; 214 | typedef VilToDrawerEncoderDef = { 215 | var row:Int; 216 | var col:Int; 217 | var index:Int; 218 | var rule:String; 219 | }; 220 | typedef VilToDrawerRangeDef = { 221 | var row:Int; 222 | var col:Int; 223 | var count:Int; 224 | var rule:String; 225 | }; -------------------------------------------------------------------------------- /src/drawer/DrawerKeymap.hx: -------------------------------------------------------------------------------- 1 | package drawer; 2 | import haxe.extern.EitherType; 3 | import haxe.DynamicAccess; 4 | 5 | /** 6 | * @author YellowAfterlife 7 | */ 8 | typedef DrawerKeymap = { 9 | var layout:DrawerKeymapLayout; 10 | var layers:DynamicAccess; 11 | var?combos:Array; 12 | var?draw_config:DrawerConfig; 13 | } 14 | typedef DrawerKeymapLayout = { 15 | var qmk_keyboard:String; 16 | var?qmk_layout:String; 17 | }; 18 | typedef DrawerConfig = { 19 | var svg_extra_style:String; 20 | } 21 | typedef DrawerCombo = { 22 | var p:Array; 23 | var k:DrawerKey; 24 | var l:Array; 25 | }; 26 | typedef DrawerKeyExt = { 27 | var?t:String; 28 | var?h:String; 29 | var?s:String; 30 | var?type:String; 31 | }; 32 | typedef DrawerKeyFlat = String; 33 | abstract DrawerKey(Dynamic) 34 | from DrawerKeyFlat from DrawerKeyExt 35 | to DrawerKeyFlat to DrawerKeyExt 36 | { 37 | public function isFlat():Bool { 38 | return this == null || (this is String); 39 | } 40 | public function toExt():DrawerKeyExt { 41 | if (this == null) return { t: "" }; 42 | return isFlat() ? { t: this } : this; 43 | } 44 | public function toFlat(a:DrawerKeyAction):DrawerKeyFlat { 45 | if (isFlat()) return this; 46 | return Reflect.field(this, a); 47 | } 48 | function postproc_1(s:String, opt:VilToDrawerOpt) { 49 | if (s == null) return null; 50 | var lqs = s.toLowerCase(); 51 | return opt.keyNames.exists(lqs) ? opt.keyNames[lqs] : s; 52 | } 53 | public function postproc(opt:VilToDrawerOpt):DrawerKey { 54 | if (isFlat()) return postproc_1(this, opt); 55 | var ext:DrawerKeyExt = this; 56 | if (ext.s != null && StringTools.endsWith(ext.s, "+")) { 57 | var lqs = (ext.s + ext.t).toLowerCase(); 58 | if (opt.keyNames.exists(lqs)) { 59 | ext.t = opt.keyNames[lqs]; 60 | ext.s = ""; 61 | return ext; 62 | } 63 | } 64 | if (ext.t != null) ext.t = postproc_1(ext.t, opt); 65 | if (ext.h != null) ext.h = postproc_1(ext.h, opt); 66 | if (ext.s != null) ext.s = postproc_1(ext.s, opt); 67 | return ext; 68 | } 69 | } 70 | enum abstract DrawerKeyAction(String) to String { 71 | var Tap = "t"; 72 | var Hold = "h"; 73 | var Shift = "s"; 74 | } 75 | typedef DrawerLayer = Array; 76 | -------------------------------------------------------------------------------- /src/tools/BuildDate.hx: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | /** 4 | * ... 5 | * @author YellowAfterlife 6 | */ 7 | class BuildDate { 8 | public static macro function asString() { 9 | return macro $v{DateTools.format(Date.now(), "%Y-%m-%d--%H-%M-%S")}; 10 | } 11 | } -------------------------------------------------------------------------------- /src/tools/ERegTools.hx: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | /** 4 | * ... 5 | * @author YellowAfterlife 6 | */ 7 | class ERegTools { 8 | private static var escapeRx_1:EReg = new EReg("([.*+?^${}()|[\\]\\/\\\\])", 'g'); 9 | public static function escapeRx(s:String):String { 10 | return escapeRx_1.replace(s, "\\$1"); 11 | } 12 | 13 | public static function each(r:EReg, s:String, f:EReg->Void) { 14 | var i:Int = 0; 15 | while (r.matchSub(s, i)) { 16 | var p = r.matchedPos(); 17 | //if (p.pos < i) break; // bug on --run..? 18 | //trace(p, i); 19 | f(r); 20 | i = p.pos + p.len; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/tools/JsonParserWithComments.hx: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | /** 4 | A version of Haxe's standard library JSON parser that will also accept comments, 5 | fields without quotes, fields in single quotes, trailing commas... 6 | 7 | @see https://haxe.org/manual/std-Json-parsing.html 8 | **/ 9 | class JsonParserWithComments { 10 | /** 11 | Parses given JSON-encoded `str` and returns the resulting object. 12 | 13 | JSON objects are parsed into anonymous structures and JSON arrays 14 | are parsed into `Array`. 15 | 16 | If given `str` is not valid JSON, an exception will be thrown. 17 | 18 | If `str` is null, the result is unspecified. 19 | **/ 20 | static public inline function parse(str:String):Dynamic { 21 | return new JsonParserWithComments(str).doParse(); 22 | } 23 | 24 | var str:String; 25 | var pos:Int; 26 | 27 | function new(str:String) { 28 | this.str = str; 29 | this.pos = 0; 30 | } 31 | 32 | function doParse():Dynamic { 33 | var result = parseRec(); 34 | var c; 35 | while (!StringTools.isEof(c = nextChar())) { 36 | switch (c) { 37 | case ' '.code, '\r'.code, '\n'.code, '\t'.code: 38 | // allow trailing whitespace 39 | default: 40 | invalidChar(); 41 | } 42 | } 43 | return result; 44 | } 45 | 46 | function parseRec():Dynamic { 47 | while (true) { 48 | var c = nextChar(); 49 | switch (c) { 50 | case ' '.code, '\r'.code, '\n'.code, '\t'.code: 51 | case '/'.code if (peekChar() == '/'.code): { 52 | pos++; 53 | while (pos < str.length) { 54 | c = peekChar(); 55 | if (c == '\r'.code || c == '\n'.code) { 56 | break; 57 | } else pos++; 58 | } 59 | } 60 | case '/'.code if (peekChar() == '*'.code): { 61 | pos++; 62 | while (pos < str.length) { 63 | c = peekChar(); 64 | if (c == '*'.code && peekChar(1) == '/'.code) { 65 | pos += 2; 66 | break; 67 | } else pos += 1; 68 | } 69 | }; 70 | // loop 71 | case '{'.code: 72 | var obj = {}, field = null, comma:Null = null; 73 | while (true) { 74 | var c = nextChar(); 75 | switch (c) { 76 | case ' '.code, '\r'.code, '\n'.code, '\t'.code: 77 | // loop 78 | case '}'.code: 79 | if (field != null/* || comma == false*/) 80 | invalidChar(); 81 | return obj; 82 | case ':'.code: 83 | if (field == null) 84 | invalidChar(); 85 | Reflect.setField(obj, field, parseRec()); 86 | field = null; 87 | comma = true; 88 | case ','.code: 89 | if (comma) comma = false else invalidChar(); 90 | case '"'.code: 91 | if (field != null || comma) invalidChar(); 92 | field = parseString(); 93 | case _ if (c == "_".code 94 | || c >= "a".code && c <= "z".code 95 | || c >= "A".code && c <= "Z".code 96 | ): 97 | if (field != null || comma) invalidChar(); 98 | var start = pos - 1; 99 | while (pos < str.length) { 100 | c = peekChar(); 101 | if (c == "_".code 102 | || c >= "a".code && c <= "z".code 103 | || c >= "A".code && c <= "Z".code 104 | || c >= "0".code && c <= "9".code 105 | ) { 106 | pos++; 107 | } else break; 108 | } 109 | field = str.substring(start, pos); 110 | default: 111 | invalidChar(); 112 | } 113 | } 114 | case '['.code: 115 | var arr = [], comma:Null = null; 116 | while (true) { 117 | var c = nextChar(); 118 | switch (c) { 119 | case ' '.code, '\r'.code, '\n'.code, '\t'.code: 120 | // loop 121 | case ']'.code: 122 | //if (comma == false) invalidChar(); 123 | return arr; 124 | case ','.code: 125 | if (comma) comma = false else invalidChar(); 126 | default: 127 | if (comma) invalidChar(); 128 | pos--; 129 | arr.push(parseRec()); 130 | comma = true; 131 | } 132 | } 133 | case 't'.code: 134 | var save = pos; 135 | if (nextChar() != 'r'.code || nextChar() != 'u'.code || nextChar() != 'e'.code) { 136 | pos = save; 137 | invalidChar(); 138 | } 139 | return true; 140 | case 'f'.code: 141 | var save = pos; 142 | if (nextChar() != 'a'.code || nextChar() != 'l'.code || nextChar() != 's'.code || nextChar() != 'e'.code) { 143 | pos = save; 144 | invalidChar(); 145 | } 146 | return false; 147 | case 'n'.code: 148 | var save = pos; 149 | if (nextChar() != 'u'.code || nextChar() != 'l'.code || nextChar() != 'l'.code) { 150 | pos = save; 151 | invalidChar(); 152 | } 153 | return null; 154 | case '"'.code, "'".code: 155 | return parseString(c); 156 | case '0'.code, '1'.code, '2'.code, '3'.code, '4'.code, '5'.code, '6'.code, '7'.code, '8'.code, '9'.code, '-'.code: 157 | return parseNumber(c); 158 | default: 159 | invalidChar(); 160 | } 161 | } 162 | } 163 | 164 | function parseString(quoteChar:Int = '"'.code) { 165 | var start = pos; 166 | var buf:StringBuf = null; 167 | #if target.unicode 168 | var prev = -1; 169 | inline function cancelSurrogate() { 170 | // invalid high surrogate (not followed by low surrogate) 171 | buf.addChar(0xFFFD); 172 | prev = -1; 173 | } 174 | #end 175 | while (true) { 176 | var c = nextChar(); 177 | if (c == quoteChar) 178 | break; 179 | if (c == '\\'.code) { 180 | if (buf == null) { 181 | buf = new StringBuf(); 182 | } 183 | buf.addSub(str, start, pos - start - 1); 184 | c = nextChar(); 185 | #if target.unicode 186 | if (c != "u".code && prev != -1) 187 | cancelSurrogate(); 188 | #end 189 | switch (c) { 190 | case "r".code: 191 | buf.addChar("\r".code); 192 | case "n".code: 193 | buf.addChar("\n".code); 194 | case "t".code: 195 | buf.addChar("\t".code); 196 | case "b".code: 197 | buf.addChar(8); 198 | case "f".code: 199 | buf.addChar(12); 200 | case "/".code, '\\'.code, '"'.code, "'".code: 201 | buf.addChar(c); 202 | case 'u'.code: 203 | var uc:Int = Std.parseInt("0x" + str.substr(pos, 4)); 204 | pos += 4; 205 | #if !target.unicode 206 | if (uc <= 0x7F) 207 | buf.addChar(uc); 208 | else if (uc <= 0x7FF) { 209 | buf.addChar(0xC0 | (uc >> 6)); 210 | buf.addChar(0x80 | (uc & 63)); 211 | } else if (uc <= 0xFFFF) { 212 | buf.addChar(0xE0 | (uc >> 12)); 213 | buf.addChar(0x80 | ((uc >> 6) & 63)); 214 | buf.addChar(0x80 | (uc & 63)); 215 | } else { 216 | buf.addChar(0xF0 | (uc >> 18)); 217 | buf.addChar(0x80 | ((uc >> 12) & 63)); 218 | buf.addChar(0x80 | ((uc >> 6) & 63)); 219 | buf.addChar(0x80 | (uc & 63)); 220 | } 221 | #else 222 | if (prev != -1) { 223 | if (uc < 0xDC00 || uc > 0xDFFF) 224 | cancelSurrogate(); 225 | else { 226 | buf.addChar(((prev - 0xD800) << 10) + (uc - 0xDC00) + 0x10000); 227 | prev = -1; 228 | } 229 | } else if (uc >= 0xD800 && uc <= 0xDBFF) 230 | prev = uc; 231 | else 232 | buf.addChar(uc); 233 | #end 234 | default: 235 | throw "Invalid escape sequence \\" + String.fromCharCode(c) + " at position " + (pos - 1); 236 | } 237 | start = pos; 238 | } 239 | #if !(target.unicode) 240 | // ensure utf8 chars are not cut 241 | else if (c >= 0x80) { 242 | pos++; 243 | if (c >= 0xFC) 244 | pos += 4; 245 | else if (c >= 0xF8) 246 | pos += 3; 247 | else if (c >= 0xF0) 248 | pos += 2; 249 | else if (c >= 0xE0) 250 | pos++; 251 | } 252 | #end 253 | else if (StringTools.isEof(c)) 254 | throw "Unclosed string"; 255 | } 256 | #if target.unicode 257 | if (prev != -1) 258 | cancelSurrogate(); 259 | #end 260 | if (buf == null) { 261 | return str.substr(start, pos - start - 1); 262 | } else { 263 | buf.addSub(str, start, pos - start - 1); 264 | return buf.toString(); 265 | } 266 | } 267 | 268 | inline function parseNumber(c:Int):Dynamic { 269 | var start = pos - 1; 270 | var minus = c == '-'.code, digit = !minus, zero = c == '0'.code; 271 | var point = false, e = false, pm = false, end = false; 272 | while (true) { 273 | c = nextChar(); 274 | switch (c) { 275 | case '0'.code: 276 | if (zero && !point) 277 | invalidNumber(start); 278 | if (minus) { 279 | minus = false; 280 | zero = true; 281 | } 282 | digit = true; 283 | case '1'.code, '2'.code, '3'.code, '4'.code, '5'.code, '6'.code, '7'.code, '8'.code, '9'.code: 284 | if (zero && !point) 285 | invalidNumber(start); 286 | if (minus) 287 | minus = false; 288 | digit = true; 289 | zero = false; 290 | case '.'.code: 291 | if (minus || point || e) 292 | invalidNumber(start); 293 | digit = false; 294 | point = true; 295 | case 'e'.code, 'E'.code: 296 | if (minus || zero || e) 297 | invalidNumber(start); 298 | digit = false; 299 | e = true; 300 | case '+'.code, '-'.code: 301 | if (!e || pm) 302 | invalidNumber(start); 303 | digit = false; 304 | pm = true; 305 | default: 306 | if (!digit) 307 | invalidNumber(start); 308 | pos--; 309 | end = true; 310 | } 311 | if (end) 312 | break; 313 | } 314 | 315 | var f = Std.parseFloat(str.substr(start, pos - start)); 316 | if(point) { 317 | return f; 318 | } else { 319 | var i = Std.int(f); 320 | return if (i == f) i else f; 321 | } 322 | } 323 | 324 | inline function nextChar() { 325 | return StringTools.fastCodeAt(str, pos++); 326 | } 327 | 328 | inline function peekChar(offset:Int = 0) { 329 | return StringTools.fastCodeAt(str, pos + offset); 330 | } 331 | 332 | function invalidChar() { 333 | pos--; // rewind 334 | throw "Invalid char " + StringTools.fastCodeAt(str, pos) + " at position " + pos; 335 | } 336 | 337 | function invalidNumber(start:Int) { 338 | throw "Invalid number at position " + start + ": " + str.substr(start, pos - start); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/tools/JsonPrinterWithOrder.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C)2005-2019 Haxe Foundation 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is 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 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package tools; 24 | 25 | /** 26 | An implementation of JSON printer in Haxe. 27 | 28 | This class is used by `haxe.Json` when native JSON implementation 29 | is not available. 30 | 31 | @see https://haxe.org/manual/std-Json-encoding.html 32 | **/ 33 | class JsonPrinterWithOrder { 34 | /** 35 | Encodes `o`'s value and returns the resulting JSON string. 36 | 37 | If `replacer` is given and is not null, it is used to retrieve 38 | actual object to be encoded. The `replacer` function takes two parameters, 39 | the key and the value being encoded. Initial key value is an empty string. 40 | 41 | If `space` is given and is not null, the result will be pretty-printed. 42 | Successive levels will be indented by this string. 43 | **/ 44 | static public function print(o:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String):String { 45 | var printer = new JsonPrinterWithOrder(replacer, space); 46 | printer.write("", o); 47 | return printer.buf.toString(); 48 | } 49 | 50 | var buf:#if flash flash.utils.ByteArray #else StringBuf #end; 51 | var replacer:(key:Dynamic, value:Dynamic) -> Dynamic; 52 | var indent:String; 53 | var pretty:Bool; 54 | var nind:Int; 55 | 56 | function new(replacer:(key:Dynamic, value:Dynamic) -> Dynamic, space:String) { 57 | this.replacer = replacer; 58 | this.indent = space; 59 | this.pretty = space != null; 60 | this.nind = 0; 61 | 62 | #if flash 63 | buf = new flash.utils.ByteArray(); 64 | buf.endian = flash.utils.Endian.BIG_ENDIAN; 65 | buf.position = 0; 66 | #else 67 | buf = new StringBuf(); 68 | #end 69 | } 70 | 71 | inline function ipad():Void { 72 | if (pretty) 73 | add(StringTools.lpad('', indent, nind * indent.length)); 74 | } 75 | 76 | inline function newl():Void { 77 | if (pretty) 78 | addChar('\n'.code); 79 | } 80 | 81 | function write(k:Dynamic, v:Dynamic) { 82 | if (replacer != null) 83 | v = replacer(k, v); 84 | switch (Type.typeof(v)) { 85 | case TUnknown: 86 | add('"???"'); 87 | case TObject: 88 | objString(v); 89 | case TInt: 90 | add(#if (jvm || hl) Std.string(v) #else v #end); 91 | case TFloat: 92 | add(Math.isFinite(v) ? Std.string(v) : 'null'); 93 | case TFunction: 94 | add('""'); 95 | case TClass(c): 96 | if (c == String) 97 | quote(v); 98 | else if (c == Array) { 99 | var v:Array = v; 100 | addChar('['.code); 101 | 102 | var len = v.length; 103 | var last = len - 1; 104 | for (i in 0...len) { 105 | if (i > 0) 106 | addChar(','.code) 107 | else 108 | nind++; 109 | newl(); 110 | ipad(); 111 | write(i, v[i]); 112 | if (i == last) { 113 | nind--; 114 | newl(); 115 | ipad(); 116 | } 117 | } 118 | addChar(']'.code); 119 | } else if (c == haxe.ds.StringMap) { 120 | var v:haxe.ds.StringMap = v; 121 | var o = {}; 122 | for (k in v.keys()) 123 | Reflect.setField(o, k, v.get(k)); 124 | objString(o); 125 | } else if (c == Date) { 126 | var v:Date = v; 127 | quote(v.toString()); 128 | } else 129 | classString(v); 130 | case TEnum(_): 131 | var i = Type.enumIndex(v); 132 | add(Std.string(i)); 133 | case TBool: 134 | add(#if (php || jvm || hl) (v ? 'true' : 'false') #else v #end); 135 | case TNull: 136 | add('null'); 137 | } 138 | } 139 | 140 | extern inline function addChar(c:Int) { 141 | #if flash 142 | buf.writeByte(c); 143 | #else 144 | buf.addChar(c); 145 | #end 146 | } 147 | 148 | extern inline function add(v:String) { 149 | #if flash 150 | // argument is not always a string but will be automatically casted 151 | buf.writeUTFBytes(v); 152 | #else 153 | buf.add(v); 154 | #end 155 | } 156 | 157 | function classString(v:Dynamic) { 158 | fieldsString(v, Type.getInstanceFields(Type.getClass(v))); 159 | } 160 | 161 | inline function objString(v:Dynamic) { 162 | //{ +y: 163 | var fields = Reflect.fields(v); 164 | if (fields.remove("__hxOrder__")) { 165 | fields = Reflect.field(v, "__hxOrder__"); 166 | } 167 | //} 168 | fieldsString(v, fields); 169 | } 170 | 171 | function fieldsString(v:Dynamic, fields:Array) { 172 | addChar('{'.code); 173 | var len = fields.length; 174 | var last = len - 1; 175 | var first = true; 176 | for (i in 0...len) { 177 | var f = fields[i]; 178 | var value = Reflect.field(v, f); 179 | if (Reflect.isFunction(value)) 180 | continue; 181 | if (first) { 182 | nind++; 183 | first = false; 184 | } else 185 | addChar(','.code); 186 | newl(); 187 | ipad(); 188 | quote(f); 189 | addChar(':'.code); 190 | if (pretty) 191 | addChar(' '.code); 192 | write(f, value); 193 | if (i == last) { 194 | nind--; 195 | newl(); 196 | ipad(); 197 | } 198 | } 199 | addChar('}'.code); 200 | } 201 | 202 | function quote(s:String) { 203 | #if neko 204 | if (s.length != neko.Utf8.length(s)) { 205 | quoteUtf8(s); 206 | return; 207 | } 208 | #end 209 | addChar('"'.code); 210 | var i = 0; 211 | var length = s.length; 212 | #if hl 213 | var prev = -1; 214 | #end 215 | while (i < length) { 216 | var c = StringTools.unsafeCodeAt(s, i++); 217 | switch (c) { 218 | case '"'.code: 219 | add('\\"'); 220 | case '\\'.code: 221 | add('\\\\'); 222 | case '\n'.code: 223 | add('\\n'); 224 | case '\r'.code: 225 | add('\\r'); 226 | case '\t'.code: 227 | add('\\t'); 228 | case 8: 229 | add('\\b'); 230 | case 12: 231 | add('\\f'); 232 | default: 233 | #if flash 234 | if (c >= 128) 235 | add(String.fromCharCode(c)) 236 | else 237 | addChar(c); 238 | #elseif hl 239 | if (prev >= 0) { 240 | if (c >= 0xD800 && c <= 0xDFFF) { 241 | addChar((((prev - 0xD800) << 10) | (c - 0xDC00)) + 0x10000); 242 | prev = -1; 243 | } else { 244 | addChar("□".code); 245 | prev = c; 246 | } 247 | } else { 248 | if (c >= 0xD800 && c <= 0xDFFF) 249 | prev = c; 250 | else 251 | addChar(c); 252 | } 253 | #else 254 | addChar(c); 255 | #end 256 | } 257 | } 258 | #if hl 259 | if (prev >= 0) 260 | addChar("□".code); 261 | #end 262 | addChar('"'.code); 263 | } 264 | 265 | #if neko 266 | function quoteUtf8(s:String) { 267 | var u = new neko.Utf8(); 268 | neko.Utf8.iter(s, function(c) { 269 | switch (c) { 270 | case '\\'.code, '"'.code: 271 | u.addChar('\\'.code); 272 | u.addChar(c); 273 | case '\n'.code: 274 | u.addChar('\\'.code); 275 | u.addChar('n'.code); 276 | case '\r'.code: 277 | u.addChar('\\'.code); 278 | u.addChar('r'.code); 279 | case '\t'.code: 280 | u.addChar('\\'.code); 281 | u.addChar('t'.code); 282 | case 8: 283 | u.addChar('\\'.code); 284 | u.addChar('b'.code); 285 | case 12: 286 | u.addChar('\\'.code); 287 | u.addChar('f'.code); 288 | default: 289 | u.addChar(c); 290 | } 291 | }); 292 | buf.add('"'); 293 | buf.add(u.toString()); 294 | buf.add('"'); 295 | } 296 | #end 297 | } 298 | -------------------------------------------------------------------------------- /src/via/ViaKeyNames.hx: -------------------------------------------------------------------------------- 1 | package via; 2 | /** 3 | * Generated by ViaKeyNamesGen.hx 4 | * from the-via/app: src/utils/key.ts 5 | */ 6 | class ViaKeyNames { 7 | public static var map:Map = [ 8 | "KC_NO" => "", 9 | "KC_TRNS" => "▽", 10 | "KC_ESC" => "Esc", 11 | "KC_A" => "A", 12 | "KC_B" => "B", 13 | "KC_C" => "C", 14 | "KC_D" => "D", 15 | "KC_E" => "E", 16 | "KC_F" => "F", 17 | "KC_G" => "G", 18 | "KC_H" => "H", 19 | "KC_I" => "I", 20 | "KC_J" => "J", 21 | "KC_K" => "K", 22 | "KC_L" => "L", 23 | "KC_M" => "M", 24 | "KC_N" => "N", 25 | "KC_O" => "O", 26 | "KC_P" => "P", 27 | "KC_Q" => "Q", 28 | "KC_R" => "R", 29 | "KC_S" => "S", 30 | "KC_T" => "T", 31 | "KC_U" => "U", 32 | "KC_V" => "V", 33 | "KC_W" => "W", 34 | "KC_X" => "X", 35 | "KC_Y" => "Y", 36 | "KC_Z" => "Z", 37 | "KC_1" => "!\n1", 38 | "KC_2" => "@\n2", 39 | "KC_3" => "#\n3", 40 | "KC_4" => "$\n4", 41 | "KC_5" => "%\n5", 42 | "KC_6" => "^\n6", 43 | "KC_7" => "&\n7", 44 | "KC_8" => "*\n8", 45 | "KC_9" => "(\n9", 46 | "KC_0" => ")\n0", 47 | "KC_MINS" => "_\n-", 48 | "KC_EQL" => "+\n=", 49 | "KC_GRV" => "~\n`", 50 | "KC_LBRC" => "{\n[", 51 | "KC_RBRC" => "}\n]", 52 | "KC_BSLS" => "|\n\\", 53 | "KC_SCLN" => ":\n;", 54 | "KC_QUOT" => "\"\n'", 55 | "KC_COMM" => "<\n,", 56 | "KC_DOT" => ">\n.", 57 | "KC_SLSH" => "?\n/", 58 | "KC_PEQL" => "=", 59 | "KC_PCMM" => ",", 60 | "KC_F1" => "F1", 61 | "KC_F2" => "F2", 62 | "KC_F3" => "F3", 63 | "KC_F4" => "F4", 64 | "KC_F5" => "F5", 65 | "KC_F6" => "F6", 66 | "KC_F7" => "F7", 67 | "KC_F8" => "F8", 68 | "KC_F9" => "F9", 69 | "KC_F10" => "F10", 70 | "KC_F11" => "F11", 71 | "KC_F12" => "F12", 72 | "KC_PSCR" => "Print Screen", 73 | "KC_SLCK" => "Scroll Lock", 74 | "KC_PAUS" => "Pause", 75 | "KC_TAB" => "Tab", 76 | "KC_BSPC" => "Backspace", 77 | "KC_INS" => "Insert", 78 | "KC_DEL" => "Del", 79 | "KC_HOME" => "Home", 80 | "KC_END" => "End", 81 | "KC_PGUP" => "Page Up", 82 | "KC_PGDN" => "Page Down", 83 | "KC_NLCK" => "Num\nLock", 84 | "KC_CAPS" => "Caps Lock", 85 | "KC_ENT" => "Enter", 86 | "KC_P1" => "1", 87 | "KC_P2" => "2", 88 | "KC_P3" => "3", 89 | "KC_P4" => "4", 90 | "KC_P5" => "5", 91 | "KC_P6" => "6", 92 | "KC_P7" => "7", 93 | "KC_P8" => "8", 94 | "KC_P9" => "9", 95 | "KC_P0" => "0", 96 | "KC_PSLS" => "÷", 97 | "KC_PAST" => "×", 98 | "KC_PMNS" => "-", 99 | "KC_PPLS" => "+", 100 | "KC_PDOT" => ".", 101 | "KC_PENT" => "Num\nEnter", 102 | "KC_LSFT" => "Left Shift", 103 | "KC_RSFT" => "Right Shift", 104 | "KC_LCTL" => "Left Ctrl", 105 | "KC_RCTL" => "Right Ctrl", 106 | "KC_LGUI" => "Left Win", 107 | "KC_RGUI" => "Right Win", 108 | "KC_LALT" => "Left Alt", 109 | "KC_RALT" => "Right Alt", 110 | "KC_SPC" => "Space", 111 | "KC_APP" => "Menu", 112 | "KC_LEFT" => "Left", 113 | "KC_DOWN" => "Down", 114 | "KC_UP" => "Up", 115 | "KC_RGHT" => "Right", 116 | "BR_DEC" => "Bright -", 117 | "BR_INC" => "Bright +", 118 | "EF_DEC" => "Effect -", 119 | "EF_INC" => "Effect +", 120 | "ES_DEC" => "Effect Speed -", 121 | "ES_INC" => "Effect Speed +", 122 | "H1_DEC" => "Color1 Hue -", 123 | "H1_INC" => "Color1 Hue +", 124 | "H2_DEC" => "Color2 Hue -", 125 | "H2_INC" => "Color2 Hue +", 126 | "S1_DEC" => "Color1 Sat -", 127 | "S1_INC" => "Color1 Sat +", 128 | "S2_DEC" => "Color2 Sat -", 129 | "S2_INC" => "Color2 Sat +", 130 | "KC_VOLD" => "Vol -", 131 | "KC_VOLU" => "Vol +", 132 | "KC_MUTE" => "Mute", 133 | "KC_MPLY" => "Play", 134 | "KC_MSTP" => "Media Stop", 135 | "KC_MPRV" => "Previous", 136 | "KC_MNXT" => "Next", 137 | "KC_MRWD" => "Rewind", 138 | "KC_MFFD" => "Fast Forward", 139 | "KC_MSEL" => "Select", 140 | "KC_EJCT" => "Eject", 141 | "MACRO(0)" => "M0", 142 | "MACRO(1)" => "M1", 143 | "MACRO(2)" => "M2", 144 | "MACRO(3)" => "M3", 145 | "MACRO(4)" => "M4", 146 | "MACRO(5)" => "M5", 147 | "MACRO(6)" => "M6", 148 | "MACRO(7)" => "M7", 149 | "MACRO(8)" => "M8", 150 | "MACRO(9)" => "M9", 151 | "MACRO(10)" => "M10", 152 | "MACRO(11)" => "M11", 153 | "MACRO(12)" => "M12", 154 | "MACRO(13)" => "M13", 155 | "MACRO(14)" => "M14", 156 | "MACRO(15)" => "M15", 157 | "S(KC_GRV)" => "~", 158 | "S(KC_1)" => "!", 159 | "S(KC_2)" => "@", 160 | "S(KC_3)" => "#", 161 | "S(KC_4)" => "$", 162 | "S(KC_5)" => "%", 163 | "S(KC_6)" => "^", 164 | "S(KC_7)" => "&", 165 | "S(KC_8)" => "*", 166 | "S(KC_9)" => "(", 167 | "S(KC_0)" => ")", 168 | "S(KC_MINS)" => "_", 169 | "S(KC_EQL)" => "+", 170 | "S(KC_LBRC)" => "{", 171 | "S(KC_RBRC)" => "}", 172 | "S(KC_BSLS)" => "|", 173 | "S(KC_SCLN)" => ":", 174 | "S(KC_QUOT)" => "\"", 175 | "S(KC_COMM)" => "<", 176 | "S(KC_DOT)" => ">", 177 | "S(KC_SLSH)" => "?", 178 | "KC_NUHS" => "NUHS", 179 | "KC_NUBS" => "NUBS", 180 | "KC_RO" => "Ro", 181 | "KC_JYEN" => "¥", 182 | "KC_MHEN" => "無変換", 183 | "KC_HANJ" => "漢字", 184 | "KC_HAEN" => "한영", 185 | "KC_HENK" => "変換", 186 | "KC_KANA" => "かな", 187 | "KC_GESC" => "Esc `", 188 | "KC_LSPO" => "LS (", 189 | "KC_RSPC" => "RS )", 190 | "KC_LCPO" => "LC (", 191 | "KC_RCPC" => "RC )", 192 | "KC_LAPO" => "LA (", 193 | "KC_RAPC" => "RA )", 194 | "KC_SFTENT" => "SftEnt", 195 | "RESET" => "Reset", 196 | "DEBUG" => "Debug", 197 | "MAGIC_TOGGLE_NKRO" => "Toggle NKRO", 198 | "KC_LNUM" => "Locking Num Lock", 199 | "KC_LCAP" => "Locking Caps Lock", 200 | "KC_LSCR" => "Locking Scroll Lock", 201 | "KC_PWR" => "Power", 202 | "KC_POWER" => "Power OSX", 203 | "KC_SLEP" => "Sleep", 204 | "KC_WAKE" => "Wake", 205 | "KC_CALC" => "Calc", 206 | "KC_MAIL" => "Mail", 207 | "KC_HELP" => "Help", 208 | "KC_STOP" => "Stop", 209 | "KC_ERAS" => "Alt Erase", 210 | "KC_AGAIN" => "Again", 211 | "KC_MENU" => "Menu", 212 | "KC_UNDO" => "Undo", 213 | "KC_SELECT" => "Select", 214 | "KC_EXECUTE" => "Exec", 215 | "KC_CUT" => "Cut", 216 | "KC_COPY" => "Copy", 217 | "KC_PASTE" => "Paste", 218 | "KC_FIND" => "Find", 219 | "KC_MYCM" => "My Comp", 220 | "KC_WWW_HOME" => "Home", 221 | "KC_WWW_BACK" => "Back", 222 | "KC_WWW_FORWARD" => "Forward", 223 | "KC_WWW_STOP" => "Stop", 224 | "KC_WWW_REFRESH" => "Refresh", 225 | "KC_WWW_FAVORITES" => "Favorites", 226 | "KC_WWW_SEARCH" => "Search", 227 | "KC_BRIU" => "Screen +", 228 | "KC_BRID" => "Screen -", 229 | "KC_F13" => "F13", 230 | "KC_F14" => "F14", 231 | "KC_F15" => "F15", 232 | "KC_F16" => "F16", 233 | "KC_F17" => "F17", 234 | "KC_F18" => "F18", 235 | "KC_F19" => "F19", 236 | "KC_F20" => "F20", 237 | "KC_F21" => "F21", 238 | "KC_F22" => "F22", 239 | "KC_F23" => "F23", 240 | "KC_F24" => "F24", 241 | "KC_MS_UP" => "Mouse ↑", 242 | "KC_MS_DOWN" => "Mouse ↓", 243 | "KC_MS_LEFT" => "Mouse ←", 244 | "KC_MS_RIGHT" => "Mouse →", 245 | "KC_MS_BTN1" => "Mouse Btn1", 246 | "KC_MS_BTN2" => "Mouse Btn2", 247 | "KC_MS_BTN3" => "Mouse Btn3", 248 | "KC_MS_BTN4" => "Mouse Btn4", 249 | "KC_MS_BTN5" => "Mouse Btn5", 250 | "KC_MS_BTN6" => "Mouse Btn6", 251 | "KC_MS_BTN7" => "Mouse Btn7", 252 | "KC_MS_BTN8" => "Mouse Btn8", 253 | "KC_MS_WH_UP" => "Mouse Wh ↑", 254 | "KC_MS_WH_DOWN" => "Mouse Wh ↓", 255 | "KC_MS_WH_LEFT" => "Mouse Wh ←", 256 | "KC_MS_WH_RIGHT" => "Mouse Wh →", 257 | "KC_MS_ACCEL0" => "Mouse Acc0", 258 | "KC_MS_ACCEL1" => "Mouse Acc1", 259 | "KC_MS_ACCEL2" => "Mouse Acc2", 260 | "AU_ON" => "Audio On", 261 | "AU_OFF" => "Audio Off", 262 | "AU_TOG" => "Audio Toggle", 263 | "CLICKY_TOGGLE" => "Clicky Toggle", 264 | "CLICKY_ENABLE" => "Clicky Enable", 265 | "CLICKY_DISABLE" => "Clicky Disable", 266 | "CLICKY_UP" => "Clicky Up", 267 | "CLICKY_DOWN" => "Clicky Down", 268 | "CLICKY_RESET" => "Clicky Reset", 269 | "MU_ON" => "Music On", 270 | "MU_OFF" => "Music Off", 271 | "MU_TOG" => "Music Toggle", 272 | "MU_MOD" => "Music Mode", 273 | "BL_TOGG" => "BL Toggle", 274 | "BL_ON" => "BL On", 275 | "BL_OFF" => "BL Off", 276 | "BL_DEC" => "BL -", 277 | "BL_INC" => "BL +", 278 | "BL_STEP" => "BL Cycle", 279 | "BL_BRTG" => "BR Toggle", 280 | "RGB_TOG" => "RGB Toggle", 281 | "RGB_RMOD" => "RGB Mode -", 282 | "RGB_MOD" => "RGB Mode +", 283 | "RGB_HUD" => "Hue -", 284 | "RGB_HUI" => "Hue +", 285 | "RGB_SAD" => "Sat -", 286 | "RGB_SAI" => "Sat +", 287 | "RGB_VAD" => "Bright -", 288 | "RGB_VAI" => "Bright +", 289 | "RGB_SPD" => "Effect Speed-", 290 | "RGB_SPI" => "Effect Speed+", 291 | "RGB_M_P" => "RGB Mode P", 292 | "RGB_M_B" => "RGB Mode B", 293 | "RGB_M_R" => "RGB Mode R", 294 | "RGB_M_SW" => "RGB Mode SW", 295 | "RGB_M_SN" => "RGB Mode SN", 296 | "RGB_M_K" => "RGB Mode K", 297 | "RGB_M_X" => "RGB Mode X", 298 | "RGB_M_G" => "RGB Mode G", 299 | "CUSTOM(0)" => "CUSTOM(0)", 300 | "CUSTOM(1)" => "CUSTOM(1)", 301 | "CUSTOM(2)" => "CUSTOM(2)", 302 | "CUSTOM(3)" => "CUSTOM(3)", 303 | "CUSTOM(4)" => "CUSTOM(4)", 304 | "CUSTOM(5)" => "CUSTOM(5)", 305 | "CUSTOM(6)" => "CUSTOM(6)", 306 | "CUSTOM(7)" => "CUSTOM(7)", 307 | "CUSTOM(8)" => "CUSTOM(8)", 308 | "CUSTOM(9)" => "CUSTOM(9)", 309 | "CUSTOM(10)" => "CUSTOM(10)", 310 | "CUSTOM(11)" => "CUSTOM(11)", 311 | "CUSTOM(12)" => "CUSTOM(12)", 312 | "CUSTOM(13)" => "CUSTOM(13)", 313 | "CUSTOM(14)" => "CUSTOM(14)", 314 | "CUSTOM(15)" => "CUSTOM(15)", 315 | ]; 316 | } 317 | -------------------------------------------------------------------------------- /src/via/ViaKeyNamesGen.hx: -------------------------------------------------------------------------------- 1 | package via; 2 | import haxe.Json; 3 | import sys.io.File; 4 | import tools.ERegTools; 5 | import tools.JsonParserWithComments; 6 | 7 | /** 8 | * How to run (from `src/`): 9 | * haxe --neko via/ViaKeyNamesGen.n -main via.ViaKeyNamesGen 10 | * neko via/ViaKeyNamesGen.n 11 | * @author YellowAfterlife 12 | */ 13 | class ViaKeyNamesGen { 14 | public static function main() { 15 | var ts = File.getContent("via/key.ts"); 16 | var b = new StringBuf(); 17 | for (line in [ 18 | "package via;", 19 | "/** ", 20 | " * Generated by ViaKeyNamesGen.hx", 21 | " * from the-via/app: src/utils/key.ts", 22 | " */", 23 | "class ViaKeyNames {", 24 | " public static var map:Map = [" 25 | ]) b.add(line + "\r\n"); 26 | 27 | // You know what time it is? That's right, 28 | // it's time to parse TypeScript with a modified JSON parser. 29 | var rxGetKeyCodes = new EReg("\nexport function getKeycodes" 30 | + ".+?" + "\\{\r?\n[ \t]*" + "return\\s*" 31 | + "(\\[[\\s\\S]+?\\]);\r?\n" 32 | + "\\}", ""); 33 | rxGetKeyCodes.match(ts); 34 | var expr = rxGetKeyCodes.matched(1); 35 | expr = StringTools.replace(expr, "buildLayerMenu(),", ""); 36 | var menus:Array = JsonParserWithComments.parse(expr); 37 | for (menu in menus) { 38 | for (key in menu.keycodes) { 39 | b.add("\t\t" + Json.stringify(key.code) + " => " + Json.stringify(key.name) + ",\r\n"); 40 | } 41 | } 42 | b.add("\t];\r\n"); 43 | b.add("}\r\n"); 44 | File.saveContent("via/ViaKeyNames.hx", b.toString()); 45 | Sys.println("OK!"); 46 | } 47 | } 48 | typedef IKeycodeMenu = { 49 | id:String, 50 | label:String, 51 | keycodes:Array, 52 | }; 53 | typedef IKeycodeItem = { 54 | name:String, 55 | code:String, 56 | ?keys:String, 57 | ?title:String, 58 | }; -------------------------------------------------------------------------------- /src/via/key.ts: -------------------------------------------------------------------------------- 1 | import { 2 | advancedKeycodeToString, 3 | advancedStringToKeycode, 4 | } from './advanced-keys'; 5 | import { 6 | BuiltInKeycodeModule, 7 | VIADefinitionV3, 8 | VIADefinitionV2, 9 | getLightingDefinition, 10 | KeycodeType, 11 | } from '@the-via/reader'; 12 | 13 | export interface IKeycode { 14 | name: string; 15 | code: string; 16 | title?: string; 17 | shortName?: string; 18 | keys?: string; 19 | width?: number; 20 | type?: 'container' | 'text' | 'layer'; 21 | layer?: number; 22 | } 23 | 24 | export interface IKeycodeMenu { 25 | id: string; 26 | label: string; 27 | keycodes: IKeycode[]; 28 | width?: 'label'; 29 | detailed?: string; 30 | } 31 | 32 | // Tests if label is an alpha 33 | export function isAlpha(label: string) { 34 | return /[A-Za-z]/.test(label) && label.length === 1; 35 | } 36 | 37 | // Test if label is a numpad number 38 | export function isNumpadNumber(label: string) { 39 | return /['0-9]/.test(label) && label.length === 1; 40 | } 41 | 42 | export function isArrowKey(label: string) { 43 | return /[🠗🠕🠖🠔←↑→↓]$/.test(label); 44 | } 45 | 46 | export function isNumpadSymbol(label: string) { 47 | const centeredSymbol = '-+.÷×'.split(''); 48 | return label.length === 1 && centeredSymbol.includes(label[0]); 49 | } 50 | 51 | // Test if label is a multi-legend, e.g. "!\n1" 52 | export function isMultiLegend(label: string) { 53 | const topLegend = '~!@#$%^&*()_+|{}:"<>?'.split(''); 54 | return label.length !== 1 && topLegend.includes(label[0]); 55 | } 56 | 57 | // Tests if label is a number 58 | export function isNumericOrShiftedSymbol(label: string) { 59 | const numbersTop = '!@#$%^&*()_+|~{}:"<>?1234567890'.split(''); 60 | return label.length === 1 && numbersTop.includes(label[0]); 61 | } 62 | 63 | // Tests if label is a number 64 | export function isNumericSymbol(label: string) { 65 | const numbersTop = '!@#$%^&*()_+|~{}:"<>?'.split(''); 66 | return label.length !== 1 && numbersTop.includes(label[0]); 67 | } 68 | 69 | // Maps the byte value to the keycode 70 | export function getByteForCode( 71 | code: string, 72 | basicKeyToByte: Record, 73 | ) { 74 | const byte: number | undefined = basicKeyToByte[code]; 75 | if (byte !== undefined) { 76 | return byte; 77 | } else if (isLayerCode(code)) { 78 | return getByteForLayerCode(code, basicKeyToByte); 79 | } else if (advancedStringToKeycode(code, basicKeyToByte) !== null) { 80 | return advancedStringToKeycode(code, basicKeyToByte); 81 | } 82 | throw `Could not find byte for ${code}`; 83 | } 84 | 85 | function isLayerCode(code: string) { 86 | return /([A-Za-z]+)\((\d+)\)/.test(code); 87 | } 88 | 89 | function getByteForLayerCode( 90 | keycode: string, 91 | basicKeyToByte: Record, 92 | ): number { 93 | const keycodeMatch = keycode.match(/([A-Za-z]+)\((\d+)\)/); 94 | if (keycodeMatch) { 95 | const [, code, layer] = keycodeMatch; 96 | const numLayer = parseInt(layer); 97 | switch (code) { 98 | case 'TO': { 99 | return Math.min( 100 | basicKeyToByte._QK_TO + numLayer, 101 | basicKeyToByte._QK_TO_MAX, 102 | ); 103 | } 104 | case 'MO': { 105 | return Math.min( 106 | basicKeyToByte._QK_MOMENTARY + numLayer, 107 | basicKeyToByte._QK_MOMENTARY_MAX, 108 | ); 109 | } 110 | case 'DF': { 111 | return Math.min( 112 | basicKeyToByte._QK_DEF_LAYER + numLayer, 113 | basicKeyToByte._QK_DEF_LAYER_MAX, 114 | ); 115 | } 116 | case 'TG': { 117 | return Math.min( 118 | basicKeyToByte._QK_TOGGLE_LAYER + numLayer, 119 | basicKeyToByte._QK_TOGGLE_LAYER_MAX, 120 | ); 121 | } 122 | case 'OSL': { 123 | return Math.min( 124 | basicKeyToByte._QK_ONE_SHOT_LAYER + numLayer, 125 | basicKeyToByte._QK_ONE_SHOT_LAYER_MAX, 126 | ); 127 | } 128 | case 'TT': { 129 | return Math.min( 130 | basicKeyToByte._QK_LAYER_TAP_TOGGLE + numLayer, 131 | basicKeyToByte._QK_LAYER_TAP_TOGGLE_MAX, 132 | ); 133 | } 134 | case 'CUSTOM': { 135 | return Math.min( 136 | basicKeyToByte._QK_KB + numLayer, 137 | basicKeyToByte._QK_KB_MAX, 138 | ); 139 | } 140 | case 'MACRO': { 141 | return Math.min( 142 | basicKeyToByte._QK_MACRO + numLayer, 143 | basicKeyToByte._QK_MACRO_MAX, 144 | ); 145 | } 146 | default: { 147 | throw new Error('Incorrect code'); 148 | } 149 | } 150 | } 151 | throw new Error('No match found'); 152 | } 153 | 154 | function getCodeForLayerByte( 155 | byte: number, 156 | basicKeyToByte: Record, 157 | ) { 158 | if (basicKeyToByte._QK_TO <= byte && basicKeyToByte._QK_TO_MAX >= byte) { 159 | const layer = byte - basicKeyToByte._QK_TO; 160 | return `TO(${layer})`; 161 | } else if ( 162 | basicKeyToByte._QK_MOMENTARY <= byte && 163 | basicKeyToByte._QK_MOMENTARY_MAX >= byte 164 | ) { 165 | const layer = byte - basicKeyToByte._QK_MOMENTARY; 166 | return `MO(${layer})`; 167 | } else if ( 168 | basicKeyToByte._QK_DEF_LAYER <= byte && 169 | basicKeyToByte._QK_DEF_LAYER_MAX >= byte 170 | ) { 171 | const layer = byte - basicKeyToByte._QK_DEF_LAYER; 172 | return `DF(${layer})`; 173 | } else if ( 174 | basicKeyToByte._QK_TOGGLE_LAYER <= byte && 175 | basicKeyToByte._QK_TOGGLE_LAYER_MAX >= byte 176 | ) { 177 | const layer = byte - basicKeyToByte._QK_TOGGLE_LAYER; 178 | return `TG(${layer})`; 179 | } else if ( 180 | basicKeyToByte._QK_ONE_SHOT_LAYER <= byte && 181 | basicKeyToByte._QK_ONE_SHOT_LAYER_MAX >= byte 182 | ) { 183 | const layer = byte - basicKeyToByte._QK_ONE_SHOT_LAYER; 184 | return `OSL(${layer})`; 185 | } else if ( 186 | basicKeyToByte._QK_LAYER_TAP_TOGGLE <= byte && 187 | basicKeyToByte._QK_LAYER_TAP_TOGGLE_MAX >= byte 188 | ) { 189 | const layer = byte - basicKeyToByte._QK_LAYER_TAP_TOGGLE; 190 | return `TT(${layer})`; 191 | } else if ( 192 | basicKeyToByte._QK_KB <= byte && 193 | basicKeyToByte._QK_KB_MAX >= byte 194 | ) { 195 | const n = byte - basicKeyToByte._QK_KB; 196 | return `CUSTOM(${n})`; 197 | } else if ( 198 | basicKeyToByte._QK_MACRO <= byte && 199 | basicKeyToByte._QK_MACRO_MAX >= byte 200 | ) { 201 | const n = byte - basicKeyToByte._QK_MACRO; 202 | return `MACRO(${n})`; 203 | } 204 | } 205 | 206 | export const keycodesList = getKeycodes().reduce( 207 | (p, n) => p.concat(n.keycodes), 208 | [], 209 | ); 210 | 211 | export const getByteToKey = (basicKeyToByte: Record) => 212 | Object.keys(basicKeyToByte).reduce((p, n) => { 213 | const key = basicKeyToByte[n]; 214 | if (key in p) { 215 | const basicKeycode = keycodesList.find(({code}) => code === n); 216 | if (basicKeycode) { 217 | return {...p, [key]: basicKeycode.code}; 218 | } 219 | return p; 220 | } 221 | return {...p, [key]: n}; 222 | }, {} as {[key: number]: string}); 223 | 224 | function isLayerKey(byte: number, basicKeyToByte: Record) { 225 | return [ 226 | [basicKeyToByte._QK_TO, basicKeyToByte._QK_TO_MAX], 227 | [basicKeyToByte._QK_MOMENTARY, basicKeyToByte._QK_MOMENTARY_MAX], 228 | [basicKeyToByte._QK_DEF_LAYER, basicKeyToByte._QK_DEF_LAYER_MAX], 229 | [basicKeyToByte._QK_TOGGLE_LAYER, basicKeyToByte._QK_TOGGLE_LAYER_MAX], 230 | [basicKeyToByte._QK_ONE_SHOT_LAYER, basicKeyToByte._QK_ONE_SHOT_LAYER_MAX], 231 | [ 232 | basicKeyToByte._QK_LAYER_TAP_TOGGLE, 233 | basicKeyToByte._QK_LAYER_TAP_TOGGLE_MAX, 234 | ], 235 | [basicKeyToByte._QK_KB, basicKeyToByte._QK_KB_MAX], 236 | [basicKeyToByte._QK_MACRO, basicKeyToByte._QK_MACRO_MAX], 237 | ].some((code) => byte >= code[0] && byte <= code[1]); 238 | } 239 | 240 | export function getCodeForByte( 241 | byte: number, 242 | basicKeyToByte: Record, 243 | byteToKey: Record, 244 | ) { 245 | const keycode = byteToKey[byte]; 246 | if (keycode && !keycode.startsWith('_QK')) { 247 | return keycode; 248 | } else if (isLayerKey(byte, basicKeyToByte)) { 249 | return getCodeForLayerByte(byte, basicKeyToByte); 250 | } else if ( 251 | advancedKeycodeToString(byte, basicKeyToByte, byteToKey) !== null 252 | ) { 253 | return advancedKeycodeToString(byte, basicKeyToByte, byteToKey); 254 | } else { 255 | return '0x' + Number(byte).toString(16); 256 | } 257 | } 258 | 259 | export function keycodeInMaster( 260 | keycode: string, 261 | basicKeyToByte: Record, 262 | ) { 263 | return ( 264 | keycode in basicKeyToByte || 265 | isLayerCode(keycode) || 266 | advancedStringToKeycode(keycode, basicKeyToByte) !== null 267 | ); 268 | } 269 | 270 | function shorten(str: string) { 271 | return str 272 | .split(' ') 273 | .map((word) => word.slice(0, 1) + word.slice(1).replace(/[aeiou ]/gi, '')) 274 | .join(''); 275 | } 276 | 277 | export function isCustomKeycodeByte( 278 | byte: number, 279 | basicKeyToByte: Record, 280 | ) { 281 | return byte >= basicKeyToByte._QK_KB && byte <= basicKeyToByte._QK_KB_MAX; 282 | } 283 | 284 | export function getCustomKeycodeIndex( 285 | byte: number, 286 | basicKeyToByte: Record, 287 | ) { 288 | return byte - basicKeyToByte._QK_KB; 289 | } 290 | 291 | export function isMacroKeycodeByte( 292 | byte: number, 293 | basicKeyToByte: Record, 294 | ) { 295 | return ( 296 | byte >= basicKeyToByte._QK_MACRO && byte <= basicKeyToByte._QK_MACRO_MAX 297 | ); 298 | } 299 | 300 | export function getMacroKeycodeIndex( 301 | byte: number, 302 | basicKeyToByte: Record, 303 | ) { 304 | return byte - basicKeyToByte._QK_MACRO; 305 | } 306 | 307 | export function getLabelForByte( 308 | byte: number, 309 | size = 100, 310 | basicKeyToByte: Record, 311 | byteToKey: Record, 312 | ) { 313 | const keycode = getCodeForByte(byte, basicKeyToByte, byteToKey); 314 | const basicKeycode = keycodesList.find(({code}) => code === keycode); 315 | if (!basicKeycode) { 316 | return keycode; 317 | } 318 | return getShortNameForKeycode(basicKeycode, size); 319 | } 320 | 321 | export function getShortNameForKeycode(keycode: IKeycode, size = 100) { 322 | const {code, name, shortName} = keycode; 323 | if (size <= 150 && shortName) { 324 | return shortName; 325 | } 326 | if (size === 100 && name.length > 5) { 327 | const shortenedName = shorten(name); 328 | if (!!code) { 329 | const shortCode = code.replace('KC_', '').replace('_', ' '); 330 | return shortenedName.length > 4 && shortCode.length < shortenedName.length 331 | ? shortCode 332 | : shortenedName; 333 | } 334 | return shortenedName; 335 | } 336 | return name; 337 | } 338 | 339 | export function getOtherMenu( 340 | basicKeyToByte: Record, 341 | ): IKeycodeMenu { 342 | const keycodes = Object.keys(basicKeyToByte) 343 | .filter((key) => !key.startsWith('_QK')) 344 | .filter((key) => !keycodesList.map(({code}) => code).includes(key)) 345 | .map((code) => ({ 346 | name: code.replace('KC_', '').replace(/_/g, ' '), 347 | code: code, 348 | })); 349 | 350 | return { 351 | id: 'other', 352 | label: 'Other', 353 | keycodes, 354 | }; 355 | } 356 | 357 | function buildLayerMenu(): IKeycodeMenu { 358 | const hardCodedKeycodes: IKeycode[] = [ 359 | { 360 | name: 'Fn1\n(Fn3)', 361 | code: 'FN_MO13', 362 | title: 'Hold = Layer 1, Hold with Fn2 = Layer 3', 363 | shortName: 'Fn1(3)', 364 | }, 365 | { 366 | name: 'Fn2\n(Fn3)', 367 | code: 'FN_MO23', 368 | title: 'Hold = Layer 2, Hold with Fn1 = Layer 3', 369 | shortName: 'Fn2(3)', 370 | }, 371 | { 372 | name: 'Space Fn1', 373 | code: 'LT(1,KC_SPC)', 374 | title: 'Hold = Layer 1, Tap = Space', 375 | shortName: 'Spc Fn1', 376 | }, 377 | { 378 | name: 'Space Fn2', 379 | code: 'LT(2,KC_SPC)', 380 | title: 'Hold = Layer 2, Tap = Space', 381 | shortName: 'Spc Fn2', 382 | }, 383 | { 384 | name: 'Space Fn3', 385 | code: 'LT(3,KC_SPC)', 386 | title: 'Hold = Layer 3, Tap = Space', 387 | shortName: 'Spc Fn3', 388 | }, 389 | ]; 390 | 391 | const menu: IKeycodeMenu = { 392 | id: 'layers', 393 | label: 'Layers', 394 | width: 'label', 395 | keycodes: [ 396 | { 397 | name: 'MO', 398 | code: 'MO(layer)', 399 | type: 'layer', 400 | layer: 0, 401 | title: 'Momentary turn layer on', 402 | }, 403 | { 404 | name: 'TG', 405 | code: 'TG(layer)', 406 | type: 'layer', 407 | layer: 0, 408 | title: 'Toggle layer on/off', 409 | }, 410 | { 411 | name: 'TT', 412 | code: 'TT(layer)', 413 | type: 'layer', 414 | layer: 0, 415 | title: 416 | "Normally acts like MO unless it's tapped multple times which toggles layer on", 417 | }, 418 | { 419 | name: 'OSL', 420 | code: 'OSL(layer)', 421 | type: 'layer', 422 | layer: 0, 423 | title: 'Switch to layer for one keypress', 424 | }, 425 | { 426 | name: 'TO', 427 | code: 'TO(layer)', 428 | type: 'layer', 429 | layer: 0, 430 | title: 'Turn on layer when pressed', 431 | }, 432 | { 433 | name: 'DF', 434 | code: 'DF(layer)', 435 | type: 'layer', 436 | layer: 0, 437 | title: 'Sets the default layer', 438 | }, 439 | ], 440 | }; 441 | 442 | // Statically generate layer codes from 0-9 instead of making it an input 443 | return { 444 | ...menu, 445 | keycodes: [ 446 | ...hardCodedKeycodes, 447 | ...menu.keycodes.flatMap((keycode) => { 448 | let res: IKeycode[] = []; 449 | for (let idx = 0; idx < 10; idx++) { 450 | const newTitle = (keycode.title || '').replace( 451 | 'layer', 452 | `layer ${idx}`, 453 | ); 454 | const newCode = keycode.code.replace('layer', `${idx}`); 455 | const newName = keycode.name + `(${idx})`; 456 | res = [ 457 | ...res, 458 | {...keycode, name: newName, title: newTitle, code: newCode}, 459 | ]; 460 | } 461 | return res; 462 | }), 463 | ], 464 | }; 465 | } 466 | 467 | export function getKeycodes(): IKeycodeMenu[] { 468 | return [ 469 | { 470 | id: 'basic', 471 | label: 'Basic', 472 | keycodes: [ 473 | {name: '', code: 'KC_NO', title: 'Nothing'}, 474 | {name: '▽', code: 'KC_TRNS', title: 'Pass-through'}, 475 | // TODO: remove "shortName" when multiline keycap labels are working 476 | {name: 'Esc', code: 'KC_ESC', keys: 'esc'}, 477 | {name: 'A', code: 'KC_A', keys: 'a'}, 478 | {name: 'B', code: 'KC_B', keys: 'b'}, 479 | {name: 'C', code: 'KC_C', keys: 'c'}, 480 | {name: 'D', code: 'KC_D', keys: 'd'}, 481 | {name: 'E', code: 'KC_E', keys: 'e'}, 482 | {name: 'F', code: 'KC_F', keys: 'f'}, 483 | {name: 'G', code: 'KC_G', keys: 'g'}, 484 | {name: 'H', code: 'KC_H', keys: 'h'}, 485 | {name: 'I', code: 'KC_I', keys: 'i'}, 486 | {name: 'J', code: 'KC_J', keys: 'j'}, 487 | {name: 'K', code: 'KC_K', keys: 'k'}, 488 | {name: 'L', code: 'KC_L', keys: 'l'}, 489 | {name: 'M', code: 'KC_M', keys: 'm'}, 490 | {name: 'N', code: 'KC_N', keys: 'n'}, 491 | {name: 'O', code: 'KC_O', keys: 'o'}, 492 | {name: 'P', code: 'KC_P', keys: 'p'}, 493 | {name: 'Q', code: 'KC_Q', keys: 'q'}, 494 | {name: 'R', code: 'KC_R', keys: 'r'}, 495 | {name: 'S', code: 'KC_S', keys: 's'}, 496 | {name: 'T', code: 'KC_T', keys: 't'}, 497 | {name: 'U', code: 'KC_U', keys: 'u'}, 498 | {name: 'V', code: 'KC_V', keys: 'v'}, 499 | {name: 'W', code: 'KC_W', keys: 'w'}, 500 | {name: 'X', code: 'KC_X', keys: 'x'}, 501 | {name: 'Y', code: 'KC_Y', keys: 'y'}, 502 | {name: 'Z', code: 'KC_Z', keys: 'z'}, 503 | {name: '!\n1', code: 'KC_1', keys: '1'}, 504 | {name: '@\n2', code: 'KC_2', keys: '2'}, 505 | {name: '#\n3', code: 'KC_3', keys: '3'}, 506 | {name: '$\n4', code: 'KC_4', keys: '4'}, 507 | {name: '%\n5', code: 'KC_5', keys: '5'}, 508 | {name: '^\n6', code: 'KC_6', keys: '6'}, 509 | {name: '&\n7', code: 'KC_7', keys: '7'}, 510 | {name: '*\n8', code: 'KC_8', keys: '8'}, 511 | {name: '(\n9', code: 'KC_9', keys: '9'}, 512 | {name: ')\n0', code: 'KC_0', keys: '0'}, 513 | {name: '_\n-', code: 'KC_MINS', keys: '-'}, 514 | {name: '+\n=', code: 'KC_EQL', keys: '='}, 515 | {name: '~\n`', code: 'KC_GRV', keys: '`'}, 516 | {name: '{\n[', code: 'KC_LBRC', keys: '['}, 517 | {name: '}\n]', code: 'KC_RBRC', keys: ']'}, 518 | {name: '|\n\\', code: 'KC_BSLS', keys: '\\', width: 1500}, 519 | {name: ':\n;', code: 'KC_SCLN', keys: ';'}, 520 | {name: '"\n\'', code: 'KC_QUOT', keys: "'"}, 521 | {name: '<\n,', code: 'KC_COMM', keys: ','}, 522 | {name: '>\n.', code: 'KC_DOT', keys: '.'}, 523 | {name: '?\n/', code: 'KC_SLSH', keys: '/'}, 524 | {name: '=', code: 'KC_PEQL'}, 525 | {name: ',', code: 'KC_PCMM'}, 526 | {name: 'F1', code: 'KC_F1'}, 527 | {name: 'F2', code: 'KC_F2'}, 528 | {name: 'F3', code: 'KC_F3'}, 529 | {name: 'F4', code: 'KC_F4'}, 530 | {name: 'F5', code: 'KC_F5'}, 531 | {name: 'F6', code: 'KC_F6'}, 532 | {name: 'F7', code: 'KC_F7'}, 533 | {name: 'F8', code: 'KC_F8'}, 534 | {name: 'F9', code: 'KC_F9'}, 535 | {name: 'F10', code: 'KC_F10'}, 536 | {name: 'F11', code: 'KC_F11'}, 537 | {name: 'F12', code: 'KC_F12'}, 538 | {name: 'Print Screen', code: 'KC_PSCR', shortName: 'Print'}, 539 | {name: 'Scroll Lock', code: 'KC_SLCK', shortName: 'Scroll'}, 540 | {name: 'Pause', code: 'KC_PAUS'}, 541 | {name: 'Tab', code: 'KC_TAB', keys: 'tab', width: 1500}, 542 | { 543 | name: 'Backspace', 544 | code: 'KC_BSPC', 545 | keys: 'backspace', 546 | width: 2000, 547 | shortName: 'Bksp', 548 | }, 549 | {name: 'Insert', code: 'KC_INS', keys: 'insert', shortName: 'Ins'}, 550 | {name: 'Del', code: 'KC_DEL', keys: 'delete'}, 551 | {name: 'Home', code: 'KC_HOME', keys: 'home'}, 552 | {name: 'End', code: 'KC_END', keys: 'end'}, 553 | {name: 'Page Up', code: 'KC_PGUP', keys: 'pageup', shortName: 'PgUp'}, 554 | { 555 | name: 'Page Down', 556 | code: 'KC_PGDN', 557 | keys: 'pagedown', 558 | shortName: 'PgDn', 559 | }, 560 | {name: 'Num\nLock', code: 'KC_NLCK', keys: 'num', shortName: 'N.Lck'}, 561 | {name: 'Caps Lock', code: 'KC_CAPS', keys: 'caps_lock', width: 1750}, 562 | {name: 'Enter', code: 'KC_ENT', keys: 'enter', width: 2250}, 563 | {name: '1', code: 'KC_P1', keys: 'num_1', title: 'Numpad 1'}, 564 | {name: '2', code: 'KC_P2', keys: 'num_2', title: 'Numpad 2'}, 565 | {name: '3', code: 'KC_P3', keys: 'num_3', title: 'Numpad 3'}, 566 | {name: '4', code: 'KC_P4', keys: 'num_4', title: 'Numpad 4'}, 567 | {name: '5', code: 'KC_P5', keys: 'num_5', title: 'Numpad 5'}, 568 | {name: '6', code: 'KC_P6', keys: 'num_6', title: 'Numpad 6'}, 569 | {name: '7', code: 'KC_P7', keys: 'num_7', title: 'Numpad 7'}, 570 | {name: '8', code: 'KC_P8', keys: 'num_8', title: 'Numpad 8'}, 571 | {name: '9', code: 'KC_P9', keys: 'num_9', title: 'Numpad 9'}, 572 | { 573 | name: '0', 574 | code: 'KC_P0', 575 | width: 2000, 576 | keys: 'num_0', 577 | title: 'Numpad 0', 578 | }, 579 | {name: '÷', code: 'KC_PSLS', keys: 'num_divide', title: 'Numpad ÷'}, 580 | {name: '×', code: 'KC_PAST', keys: 'num_multiply', title: 'Numpad ×'}, 581 | {name: '-', code: 'KC_PMNS', keys: 'num_subtract', title: 'Numpad -'}, 582 | {name: '+', code: 'KC_PPLS', keys: 'num_add', title: 'Numpad +'}, 583 | {name: '.', code: 'KC_PDOT', keys: 'num_decimal', title: 'Numpad .'}, 584 | { 585 | name: 'Num\nEnter', 586 | code: 'KC_PENT', 587 | shortName: 'N.Ent', 588 | title: 'Numpad Enter', 589 | }, 590 | { 591 | name: 'Left Shift', 592 | code: 'KC_LSFT', 593 | keys: 'shift', 594 | width: 2250, 595 | shortName: 'LShft', 596 | }, 597 | {name: 'Right Shift', code: 'KC_RSFT', width: 2750, shortName: 'RShft'}, 598 | {name: 'Left Ctrl', code: 'KC_LCTL', keys: 'ctrl', width: 1250}, 599 | {name: 'Right Ctrl', code: 'KC_RCTL', width: 1250, shortName: 'RCtl'}, 600 | { 601 | name: 'Left Win', 602 | code: 'KC_LGUI', 603 | keys: 'cmd', 604 | width: 1250, 605 | shortName: 'LWin', 606 | }, 607 | {name: 'Right Win', code: 'KC_RGUI', width: 1250, shortName: 'RWin'}, 608 | { 609 | name: 'Left Alt', 610 | code: 'KC_LALT', 611 | keys: 'alt', 612 | width: 1250, 613 | shortName: 'LAlt', 614 | }, 615 | {name: 'Right Alt', code: 'KC_RALT', width: 1250, shortName: 'RAlt'}, 616 | {name: 'Space', code: 'KC_SPC', keys: 'space', width: 6250}, 617 | {name: 'Menu', code: 'KC_APP', width: 1250, shortName: 'RApp'}, 618 | {name: 'Left', code: 'KC_LEFT', keys: 'left', shortName: '←'}, 619 | {name: 'Down', code: 'KC_DOWN', keys: 'down', shortName: '↓'}, 620 | {name: 'Up', code: 'KC_UP', keys: 'up', shortName: '↑'}, 621 | {name: 'Right', code: 'KC_RGHT', keys: 'right', shortName: '→'}, 622 | ], 623 | }, 624 | { 625 | id: 'wt_lighting', 626 | label: 'Lighting', 627 | width: 'label', 628 | keycodes: [ 629 | { 630 | name: 'Bright -', 631 | code: 'BR_DEC', 632 | title: 'Brightness -', 633 | shortName: 'BR -', 634 | }, 635 | { 636 | name: 'Bright +', 637 | code: 'BR_INC', 638 | title: 'Brightness +', 639 | shortName: 'BR +', 640 | }, 641 | { 642 | name: 'Effect -', 643 | code: 'EF_DEC', 644 | title: 'Effect -', 645 | shortName: 'EF -', 646 | }, 647 | { 648 | name: 'Effect +', 649 | code: 'EF_INC', 650 | title: 'Effect +', 651 | shortName: 'EF +', 652 | }, 653 | { 654 | name: 'Effect Speed -', 655 | code: 'ES_DEC', 656 | title: 'Effect Speed -', 657 | shortName: 'ES -', 658 | }, 659 | { 660 | name: 'Effect Speed +', 661 | code: 'ES_INC', 662 | title: 'Effect Speed +', 663 | shortName: 'ES +', 664 | }, 665 | { 666 | name: 'Color1 Hue -', 667 | code: 'H1_DEC', 668 | title: 'Color1 Hue -', 669 | shortName: 'H1 -', 670 | }, 671 | { 672 | name: 'Color1 Hue +', 673 | code: 'H1_INC', 674 | title: 'Color1 Hue +', 675 | shortName: 'H1 +', 676 | }, 677 | { 678 | name: 'Color2 Hue -', 679 | code: 'H2_DEC', 680 | title: 'Color2 Hue -', 681 | shortName: 'H2 -', 682 | }, 683 | { 684 | name: 'Color2 Hue +', 685 | code: 'H2_INC', 686 | title: 'Color2 Hue +', 687 | shortName: 'H2 +', 688 | }, 689 | { 690 | name: 'Color1 Sat -', 691 | code: 'S1_DEC', 692 | title: 'Color1 Sat -', 693 | shortName: 'S1 -', 694 | }, 695 | { 696 | name: 'Color1 Sat +', 697 | code: 'S1_INC', 698 | title: 'Color1 Sat +', 699 | shortName: 'S1 +', 700 | }, 701 | { 702 | name: 'Color2 Sat -', 703 | code: 'S2_DEC', 704 | title: 'Color2 Sat -', 705 | shortName: 'S2 -', 706 | }, 707 | { 708 | name: 'Color2 Sat +', 709 | code: 'S2_INC', 710 | title: 'Color2 Sat +', 711 | shortName: 'S2 +', 712 | }, 713 | ], 714 | }, 715 | { 716 | id: 'media', 717 | label: 'Media', 718 | width: 'label', 719 | keycodes: [ 720 | {name: 'Vol -', code: 'KC_VOLD', title: 'Volume Down'}, 721 | {name: 'Vol +', code: 'KC_VOLU', title: 'Volume Up'}, 722 | {name: 'Mute', code: 'KC_MUTE', title: 'Mute Audio'}, 723 | {name: 'Play', code: 'KC_MPLY', title: 'Play/Pause'}, 724 | {name: 'Media Stop', code: 'KC_MSTP', title: 'Media Stop'}, 725 | {name: 'Previous', code: 'KC_MPRV', title: 'Media Previous'}, 726 | {name: 'Next', code: 'KC_MNXT', title: 'Media Next'}, 727 | {name: 'Rewind', code: 'KC_MRWD', title: 'Rewind'}, 728 | {name: 'Fast Forward', code: 'KC_MFFD', title: 'Fast Forward'}, 729 | {name: 'Select', code: 'KC_MSEL', title: 'Media Select'}, 730 | {name: 'Eject', code: 'KC_EJCT', title: 'Media Eject'}, 731 | ], 732 | }, 733 | { 734 | id: 'macro', 735 | label: 'Macro', 736 | width: 'label', 737 | keycodes: [ 738 | {name: 'M0', code: 'MACRO(0)', title: 'Macro 0'}, 739 | {name: 'M1', code: 'MACRO(1)', title: 'Macro 1'}, 740 | {name: 'M2', code: 'MACRO(2)', title: 'Macro 2'}, 741 | {name: 'M3', code: 'MACRO(3)', title: 'Macro 3'}, 742 | {name: 'M4', code: 'MACRO(4)', title: 'Macro 4'}, 743 | {name: 'M5', code: 'MACRO(5)', title: 'Macro 5'}, 744 | {name: 'M6', code: 'MACRO(6)', title: 'Macro 6'}, 745 | {name: 'M7', code: 'MACRO(7)', title: 'Macro 7'}, 746 | {name: 'M8', code: 'MACRO(8)', title: 'Macro 8'}, 747 | {name: 'M9', code: 'MACRO(9)', title: 'Macro 9'}, 748 | {name: 'M10', code: 'MACRO(10)', title: 'Macro 10'}, 749 | {name: 'M11', code: 'MACRO(11)', title: 'Macro 11'}, 750 | {name: 'M12', code: 'MACRO(12)', title: 'Macro 12'}, 751 | {name: 'M13', code: 'MACRO(13)', title: 'Macro 13'}, 752 | {name: 'M14', code: 'MACRO(14)', title: 'Macro 14'}, 753 | {name: 'M15', code: 'MACRO(15)', title: 'Macro 15'}, 754 | ], 755 | }, 756 | buildLayerMenu(), 757 | { 758 | id: 'special', 759 | label: 'Special', 760 | width: 'label', 761 | keycodes: [ 762 | {name: '~', code: 'S(KC_GRV)', keys: '`', title: 'Shift + `'}, 763 | {name: '!', code: 'S(KC_1)', keys: '!', title: 'Shift + 1'}, 764 | {name: '@', code: 'S(KC_2)', keys: '@', title: 'Shift + 2'}, 765 | {name: '#', code: 'S(KC_3)', keys: '#', title: 'Shift + 3'}, 766 | {name: '$', code: 'S(KC_4)', keys: '$', title: 'Shift + 4'}, 767 | {name: '%', code: 'S(KC_5)', keys: '%', title: 'Shift + 5'}, 768 | {name: '^', code: 'S(KC_6)', keys: '^', title: 'Shift + 6'}, 769 | {name: '&', code: 'S(KC_7)', keys: '&', title: 'Shift + 7'}, 770 | {name: '*', code: 'S(KC_8)', keys: '*', title: 'Shift + 8'}, 771 | {name: '(', code: 'S(KC_9)', keys: '(', title: 'Shift + 9'}, 772 | {name: ')', code: 'S(KC_0)', keys: ')', title: 'Shift + 0'}, 773 | {name: '_', code: 'S(KC_MINS)', keys: '_', title: 'Shift + -'}, 774 | {name: '+', code: 'S(KC_EQL)', keys: '+', title: 'Shift + ='}, 775 | {name: '{', code: 'S(KC_LBRC)', keys: '{', title: 'Shift + ['}, 776 | {name: '}', code: 'S(KC_RBRC)', keys: '}', title: 'Shift + ]'}, 777 | {name: '|', code: 'S(KC_BSLS)', keys: '|', title: 'Shift + \\'}, 778 | {name: ':', code: 'S(KC_SCLN)', keys: ':', title: 'Shift + /'}, 779 | {name: '"', code: 'S(KC_QUOT)', keys: '"', title: "Shift + '"}, 780 | {name: '<', code: 'S(KC_COMM)', keys: '<', title: 'Shift + ,'}, 781 | {name: '>', code: 'S(KC_DOT)', keys: '>', title: 'Shift + .'}, 782 | {name: '?', code: 'S(KC_SLSH)', keys: '?', title: 'Shift + /'}, 783 | {name: 'NUHS', code: 'KC_NUHS', title: 'Non-US # and ~'}, 784 | {name: 'NUBS', code: 'KC_NUBS', title: 'Non-US \\ and |'}, 785 | {name: 'Ro', code: 'KC_RO', title: 'JIS \\ and |'}, 786 | {name: '¥', code: 'KC_JYEN', title: 'JPN Yen'}, 787 | {name: '無変換', code: 'KC_MHEN', title: 'JIS Muhenkan'}, 788 | {name: '漢字', code: 'KC_HANJ', title: 'Hanja'}, 789 | {name: '한영', code: 'KC_HAEN', title: 'HanYeong'}, 790 | {name: '変換', code: 'KC_HENK', title: 'JIS Henkan'}, 791 | {name: 'かな', code: 'KC_KANA', title: 'JIS Katakana/Hiragana'}, 792 | { 793 | name: 'Esc `', 794 | code: 'KC_GESC', 795 | title: 'Esc normally, but ` when Shift or Win is pressed', 796 | }, 797 | { 798 | name: 'LS (', 799 | code: 'KC_LSPO', 800 | title: 'Left Shift when held, ( when tapped', 801 | }, 802 | { 803 | name: 'RS )', 804 | code: 'KC_RSPC', 805 | title: 'Right Shift when held, ) when tapped', 806 | }, 807 | { 808 | name: 'LC (', 809 | code: 'KC_LCPO', 810 | title: 'Left Control when held, ( when tapped', 811 | }, 812 | { 813 | name: 'RC )', 814 | code: 'KC_RCPC', 815 | title: 'Right Control when held, ) when tapped', 816 | }, 817 | { 818 | name: 'LA (', 819 | code: 'KC_LAPO', 820 | title: 'Left Alt when held, ( when tapped', 821 | }, 822 | { 823 | name: 'RA )', 824 | code: 'KC_RAPC', 825 | title: 'Right Alt when held, ) when tapped', 826 | }, 827 | { 828 | name: 'SftEnt', 829 | code: 'KC_SFTENT', 830 | title: 'Right Shift when held, Enter when tapped', 831 | }, 832 | {name: 'Reset', code: 'RESET', title: 'Reset the keyboard'}, 833 | {name: 'Debug', code: 'DEBUG', title: 'Toggle debug mode'}, 834 | { 835 | name: 'Toggle NKRO', 836 | code: 'MAGIC_TOGGLE_NKRO', 837 | shortName: 'NKRO', 838 | title: 'Toggle NKRO', 839 | }, 840 | // I don't even think the locking stuff is enabled... 841 | {name: 'Locking Num Lock', code: 'KC_LNUM'}, 842 | {name: 'Locking Caps Lock', code: 'KC_LCAP'}, 843 | {name: 'Locking Scroll Lock', code: 'KC_LSCR'}, 844 | {name: 'Power', code: 'KC_PWR'}, 845 | {name: 'Power OSX', code: 'KC_POWER'}, 846 | {name: 'Sleep', code: 'KC_SLEP'}, 847 | {name: 'Wake', code: 'KC_WAKE'}, 848 | {name: 'Calc', code: 'KC_CALC'}, 849 | {name: 'Mail', code: 'KC_MAIL'}, 850 | {name: 'Help', code: 'KC_HELP'}, 851 | {name: 'Stop', code: 'KC_STOP'}, 852 | {name: 'Alt Erase', code: 'KC_ERAS'}, 853 | {name: 'Again', code: 'KC_AGAIN'}, 854 | {name: 'Menu', code: 'KC_MENU'}, 855 | {name: 'Undo', code: 'KC_UNDO'}, 856 | {name: 'Select', code: 'KC_SELECT'}, 857 | {name: 'Exec', code: 'KC_EXECUTE'}, 858 | {name: 'Cut', code: 'KC_CUT'}, 859 | {name: 'Copy', code: 'KC_COPY'}, 860 | {name: 'Paste', code: 'KC_PASTE'}, 861 | {name: 'Find', code: 'KC_FIND'}, 862 | {name: 'My Comp', code: 'KC_MYCM'}, 863 | {name: 'Home', code: 'KC_WWW_HOME'}, 864 | {name: 'Back', code: 'KC_WWW_BACK'}, 865 | {name: 'Forward', code: 'KC_WWW_FORWARD'}, 866 | {name: 'Stop', code: 'KC_WWW_STOP'}, 867 | {name: 'Refresh', code: 'KC_WWW_REFRESH'}, 868 | {name: 'Favorites', code: 'KC_WWW_FAVORITES'}, 869 | {name: 'Search', code: 'KC_WWW_SEARCH'}, 870 | { 871 | name: 'Screen +', 872 | code: 'KC_BRIU', 873 | shortName: 'Scr +', 874 | title: 'Screen Brightness Up', 875 | }, 876 | { 877 | name: 'Screen -', 878 | code: 'KC_BRID', 879 | shortName: 'Scr -', 880 | title: 'Screen Brightness Down', 881 | }, 882 | {name: 'F13', code: 'KC_F13'}, 883 | {name: 'F14', code: 'KC_F14'}, 884 | {name: 'F15', code: 'KC_F15'}, 885 | {name: 'F16', code: 'KC_F16'}, 886 | {name: 'F17', code: 'KC_F17'}, 887 | {name: 'F18', code: 'KC_F18'}, 888 | {name: 'F19', code: 'KC_F19'}, 889 | {name: 'F20', code: 'KC_F20'}, 890 | {name: 'F21', code: 'KC_F21'}, 891 | {name: 'F22', code: 'KC_F22'}, 892 | {name: 'F23', code: 'KC_F23'}, 893 | {name: 'F24', code: 'KC_F24'}, 894 | 895 | // TODO: move these to a new group 896 | {name: 'Mouse ↑', code: 'KC_MS_UP'}, 897 | {name: 'Mouse ↓', code: 'KC_MS_DOWN'}, 898 | {name: 'Mouse ←', code: 'KC_MS_LEFT'}, 899 | {name: 'Mouse →', code: 'KC_MS_RIGHT'}, 900 | {name: 'Mouse Btn1', code: 'KC_MS_BTN1'}, 901 | {name: 'Mouse Btn2', code: 'KC_MS_BTN2'}, 902 | {name: 'Mouse Btn3', code: 'KC_MS_BTN3'}, 903 | {name: 'Mouse Btn4', code: 'KC_MS_BTN4'}, 904 | {name: 'Mouse Btn5', code: 'KC_MS_BTN5'}, 905 | {name: 'Mouse Btn6', code: 'KC_MS_BTN6'}, 906 | {name: 'Mouse Btn7', code: 'KC_MS_BTN7'}, 907 | {name: 'Mouse Btn8', code: 'KC_MS_BTN8'}, 908 | {name: 'Mouse Wh ↑', code: 'KC_MS_WH_UP'}, 909 | {name: 'Mouse Wh ↓', code: 'KC_MS_WH_DOWN'}, 910 | {name: 'Mouse Wh ←', code: 'KC_MS_WH_LEFT'}, 911 | {name: 'Mouse Wh →', code: 'KC_MS_WH_RIGHT'}, 912 | {name: 'Mouse Acc0', code: 'KC_MS_ACCEL0'}, 913 | {name: 'Mouse Acc1', code: 'KC_MS_ACCEL1'}, 914 | {name: 'Mouse Acc2', code: 'KC_MS_ACCEL2'}, 915 | 916 | // TODO: move these to a new group 917 | {name: 'Audio On', code: 'AU_ON'}, 918 | {name: 'Audio Off', code: 'AU_OFF'}, 919 | {name: 'Audio Toggle', code: 'AU_TOG'}, 920 | {name: 'Clicky Toggle', code: 'CLICKY_TOGGLE'}, 921 | {name: 'Clicky Enable', code: 'CLICKY_ENABLE'}, 922 | {name: 'Clicky Disable', code: 'CLICKY_DISABLE'}, 923 | {name: 'Clicky Up', code: 'CLICKY_UP'}, 924 | {name: 'Clicky Down', code: 'CLICKY_DOWN'}, 925 | {name: 'Clicky Reset', code: 'CLICKY_RESET'}, 926 | {name: 'Music On', code: 'MU_ON'}, 927 | {name: 'Music Off', code: 'MU_OFF'}, 928 | {name: 'Music Toggle', code: 'MU_TOG'}, 929 | {name: 'Music Mode', code: 'MU_MOD'}, 930 | ], 931 | }, 932 | /* These are for controlling the original backlighting and bottom RGB. */ 933 | { 934 | id: 'qmk_lighting', 935 | label: 'Lighting', 936 | width: 'label', 937 | keycodes: [ 938 | {name: 'BL Toggle', code: 'BL_TOGG'}, 939 | {name: 'BL On', code: 'BL_ON'}, 940 | {name: 'BL Off', code: 'BL_OFF', shortName: 'BL Off'}, 941 | {name: 'BL -', code: 'BL_DEC'}, 942 | {name: 'BL +', code: 'BL_INC'}, 943 | {name: 'BL Cycle', code: 'BL_STEP'}, 944 | {name: 'BR Toggle', code: 'BL_BRTG'}, 945 | {name: 'RGB Toggle', code: 'RGB_TOG'}, 946 | {name: 'RGB Mode -', code: 'RGB_RMOD'}, 947 | {name: 'RGB Mode +', code: 'RGB_MOD'}, 948 | {name: 'Hue -', code: 'RGB_HUD'}, 949 | {name: 'Hue +', code: 'RGB_HUI'}, 950 | {name: 'Sat -', code: 'RGB_SAD'}, 951 | {name: 'Sat +', code: 'RGB_SAI'}, 952 | {name: 'Bright -', code: 'RGB_VAD'}, 953 | {name: 'Bright +', code: 'RGB_VAI'}, 954 | {name: 'Effect Speed-', code: 'RGB_SPD'}, 955 | {name: 'Effect Speed+', code: 'RGB_SPI'}, 956 | {name: 'RGB Mode P', code: 'RGB_M_P', title: 'Plain'}, 957 | {name: 'RGB Mode B', code: 'RGB_M_B', title: 'Breathe'}, 958 | {name: 'RGB Mode R', code: 'RGB_M_R', title: 'Rainbow'}, 959 | {name: 'RGB Mode SW', code: 'RGB_M_SW', title: 'Swirl'}, 960 | {name: 'RGB Mode SN', code: 'RGB_M_SN', title: 'Snake'}, 961 | {name: 'RGB Mode K', code: 'RGB_M_K', title: 'Knight'}, 962 | {name: 'RGB Mode X', code: 'RGB_M_X', title: 'Xmas'}, 963 | {name: 'RGB Mode G', code: 'RGB_M_G', title: 'Gradient'}, 964 | ], 965 | }, 966 | /* 967 | These custom keycodes always exist and should be filtered out if necessary 968 | Name and Title should be replaced with the correct ones from the keyboard json 969 | */ 970 | { 971 | id: 'custom', 972 | label: 'Custom', 973 | width: 'label', 974 | keycodes: [ 975 | {name: 'CUSTOM(0)', code: 'CUSTOM(0)', title: 'Custom Keycode 0'}, 976 | {name: 'CUSTOM(1)', code: 'CUSTOM(1)', title: 'Custom Keycode 1'}, 977 | {name: 'CUSTOM(2)', code: 'CUSTOM(2)', title: 'Custom Keycode 2'}, 978 | {name: 'CUSTOM(3)', code: 'CUSTOM(3)', title: 'Custom Keycode 3'}, 979 | {name: 'CUSTOM(4)', code: 'CUSTOM(4)', title: 'Custom Keycode 4'}, 980 | {name: 'CUSTOM(5)', code: 'CUSTOM(5)', title: 'Custom Keycode 5'}, 981 | {name: 'CUSTOM(6)', code: 'CUSTOM(6)', title: 'Custom Keycode 6'}, 982 | {name: 'CUSTOM(7)', code: 'CUSTOM(7)', title: 'Custom Keycode 7'}, 983 | {name: 'CUSTOM(8)', code: 'CUSTOM(8)', title: 'Custom Keycode 8'}, 984 | {name: 'CUSTOM(9)', code: 'CUSTOM(9)', title: 'Custom Keycode 9'}, 985 | {name: 'CUSTOM(10)', code: 'CUSTOM(10)', title: 'Custom Keycode 10'}, 986 | {name: 'CUSTOM(11)', code: 'CUSTOM(11)', title: 'Custom Keycode 11'}, 987 | {name: 'CUSTOM(12)', code: 'CUSTOM(12)', title: 'Custom Keycode 12'}, 988 | {name: 'CUSTOM(13)', code: 'CUSTOM(13)', title: 'Custom Keycode 13'}, 989 | {name: 'CUSTOM(14)', code: 'CUSTOM(14)', title: 'Custom Keycode 14'}, 990 | {name: 'CUSTOM(15)', code: 'CUSTOM(15)', title: 'Custom Keycode 15'}, 991 | ], 992 | }, 993 | ]; 994 | } 995 | 996 | export const categoriesForKeycodeModule = ( 997 | keycodeModule: BuiltInKeycodeModule | 'default', 998 | ) => 999 | ({ 1000 | default: ['basic', 'media', 'macro', 'layers', 'special'], 1001 | [BuiltInKeycodeModule.WTLighting]: ['wt_lighting'], 1002 | [BuiltInKeycodeModule.QMKLighting]: ['qmk_lighting'], 1003 | }[keycodeModule]); 1004 | 1005 | export const getKeycodesForKeyboard = ( 1006 | definition: VIADefinitionV3 | VIADefinitionV2, 1007 | ) => { 1008 | // v2 1009 | let includeList: string[] = []; 1010 | if ('lighting' in definition) { 1011 | const {keycodes} = getLightingDefinition(definition.lighting); 1012 | includeList = categoriesForKeycodeModule('default').concat( 1013 | keycodes === KeycodeType.None 1014 | ? [] 1015 | : keycodes === KeycodeType.QMK 1016 | ? categoriesForKeycodeModule(BuiltInKeycodeModule.QMKLighting) 1017 | : categoriesForKeycodeModule(BuiltInKeycodeModule.WTLighting), 1018 | ); 1019 | } else { 1020 | const {keycodes} = definition; 1021 | includeList = keycodes.flatMap(categoriesForKeycodeModule); 1022 | } 1023 | return getKeycodes() 1024 | .flatMap((keycodeMenu) => 1025 | includeList.includes(keycodeMenu.id) ? keycodeMenu.keycodes : [], 1026 | ) 1027 | .sort((a, b) => { 1028 | if (a.code <= b.code) { 1029 | return -1; 1030 | } else { 1031 | return 1; 1032 | } 1033 | }); 1034 | }; 1035 | -------------------------------------------------------------------------------- /src/vial/VialKey.hx: -------------------------------------------------------------------------------- 1 | package vial; 2 | import haxe.Json; 3 | import tools.JsonParserWithComments; 4 | import via.ViaKeyNames; 5 | import vial.VialKeyNames; 6 | import drawer.DrawerKeymap; 7 | import vial.VialKeysWithShiftState; 8 | using StringTools; 9 | 10 | /** 11 | * ... 12 | * @author YellowAfterlife 13 | */ 14 | abstract VialKey(String) from String to String { 15 | public function toDrawerKey(opt:VilToDrawerOpt, oneLine:Bool = false):DrawerKey { 16 | var kc:String = this; 17 | if (kc == null || kc == "" || kc == "KC_NO") return null; 18 | 19 | // `LSft_T(kc)` and alike 20 | static var rx_modTap = new EReg("^" 21 | + "(\\w+)_T" 22 | + "\\(" + "(.+)" + "\\)", 23 | ""); 24 | if (rx_modTap.match(kc)) { 25 | var key = rx_modTap.matched(1); 26 | var t:VialKey = rx_modTap.matched(2); 27 | var dk = t.toDrawerKey(opt).toExt(); 28 | dk.h = key; 29 | return dk; 30 | } 31 | 32 | // show "MO " if appropriate 33 | static var rx_layer = new EReg("^(MO|DF|TG|TT|OSL|TO)" + "\\(" + "(\\d+)" + "\\)$", ""); 34 | if (rx_layer.match(kc)) { 35 | var li = Std.parseInt(rx_layer.matched(2)); 36 | return rx_layer.matched(1) + " " + opt.getLayerName(li, false); 37 | } 38 | 39 | // `TD(x)` 40 | static var rx_td = new EReg("^TD" + "\\(" + "(\\d+)" + "\\)$", ""); 41 | if (rx_td.match(kc)) { 42 | var ti = Std.parseInt(rx_td.matched(1)); 43 | var td = opt.root.tap_dance[ti]; 44 | if (td != null) { 45 | return { 46 | s: kc, 47 | t: td.tap.toDrawerKey(opt, true).toFlat(Tap), 48 | h: td.hold.toDrawerKey(opt, true).toFlat(Tap), 49 | }; 50 | } 51 | } 52 | 53 | // `LT2(kc)` -> { t: kc, h: MO(2) } 54 | static var rx_lt = new EReg("^" 55 | + "(?:" 56 | + "LT(\\d)\\(" + "|" 57 | + "LT\\((\\d+),\\s*" 58 | + ")" + "(.+)" + "\\)" 59 | + "$", ""); 60 | if (rx_lt.match(kc)) { 61 | var t:VialKey = rx_lt.matched(3); 62 | var lts = rx_lt.matched(1) ?? rx_lt.matched(2); 63 | var h:VialKey = "MO(" + lts + ")"; 64 | var dk = t.toDrawerKey(opt).toExt(); 65 | dk.h = h.toDrawerKey(opt).toFlat(Tap); 66 | return dk; 67 | } 68 | 69 | // `LSFT(kc)` (special handling - replaces tap-state by shift-state) 70 | static var rx_shift = new EReg("^([LR]SFT)" + "\\(" + "(.+)" + "\\)$", ""); 71 | if (rx_shift.match(kc)) { 72 | var shift = rx_shift.matched(1) + "+"; 73 | var key:VialKey = rx_shift.matched(2); 74 | var dk = key.toDrawerKey(opt).toExt(); 75 | if (dk.s != null) { 76 | dk.t = dk.s; 77 | } 78 | dk.s = shift; 79 | return dk; 80 | } 81 | 82 | // other `MOD(kc)` keys 83 | static var rx_pair = new EReg("^(\\w+)" + "\\(" + "(.+)" + "\\)$", ""); 84 | if (rx_pair.match(kc)) { 85 | var f:String = rx_pair.matched(1); 86 | switch (f) { 87 | case "C": f = "Ctrl"; 88 | case "S": f = "Shift"; 89 | case "A": f = "Alt"; 90 | case "G": f = "Gui"; 91 | } 92 | var k:VialKey = rx_pair.matched(2); 93 | var dk = k.toDrawerKey(opt).toExt(); 94 | if (dk.s != null) { 95 | dk.t = dk.s + "\n" + dk.t; 96 | } 97 | dk.s = f + "+"; 98 | return dk; 99 | } 100 | 101 | // {"t":"A", "s":"B"} or "key" 102 | static var rx_json = new EReg("^(?:" + [ 103 | "\\{" + ".+" + "\\}", 104 | "\"" + ".+" + "\"", 105 | ].join("|") + ")\\s*$", ""); 106 | if (rx_json.match(kc)) { 107 | try { 108 | return JsonParserWithComments.parse(kc); 109 | } catch (x:Dynamic) { 110 | trace('Error parsing JSON "$kc":', x); 111 | return kc; 112 | } 113 | } 114 | 115 | var fullName = opt.isVIA ? ViaKeyNames.map[kc] : VialKeyNames.map[kc]; 116 | if (fullName != null) { 117 | if (oneLine) { 118 | return fullName.replace("\n", " "); 119 | } 120 | if (VialKeysWithShiftState.map.exists(kc)) { 121 | var parts = fullName.split("\n"); 122 | if (parts.length > 1) { 123 | return { s: parts[0], t: parts[1] }; 124 | } 125 | } 126 | return fullName; 127 | } 128 | 129 | if (kc.startsWith("KC_")) return kc.substr(3); 130 | return kc; 131 | } 132 | 133 | public function isValid() { 134 | if (this is Float && (cast this:Float) == -1) return false; 135 | return switch (this) { 136 | case null, "", "KC_NO": false; 137 | default: true; 138 | } 139 | } 140 | public function isM1() { 141 | return (this is Float) && (cast this:Float) == -1; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/vial/VialKeyNames.hx: -------------------------------------------------------------------------------- 1 | package vial; 2 | /** 3 | * Generated by VialKeyNamesGen.hx 4 | * from vial-kb/vial-gui: src/main/python/keycodes/keycodes.py 5 | */ 6 | class VialKeyNames { 7 | public static var map:Map = [ 8 | "KC_TRNS" => "▽", 9 | "KC_NUMLOCK" => "Num\nLock", 10 | "KC_KP_SLASH" => "/", 11 | "KC_KP_ASTERISK" => "*", 12 | "KC_KP_MINUS" => "-", 13 | "KC_KP_PLUS" => "+", 14 | "KC_KP_ENTER" => "Num\nEnter", 15 | "KC_KP_1" => "1", 16 | "KC_KP_2" => "2", 17 | "KC_KP_3" => "3", 18 | "KC_KP_4" => "4", 19 | "KC_KP_5" => "5", 20 | "KC_KP_6" => "6", 21 | "KC_KP_7" => "7", 22 | "KC_KP_8" => "8", 23 | "KC_KP_9" => "9", 24 | "KC_KP_0" => "0", 25 | "KC_KP_DOT" => ".", 26 | "KC_KP_EQUAL" => "=", 27 | "KC_KP_COMMA" => ",", 28 | "KC_PSCREEN" => "Print\nScreen", 29 | "KC_SCROLLLOCK" => "Scroll\nLock", 30 | "KC_PAUSE" => "Pause", 31 | "KC_INSERT" => "Insert", 32 | "KC_HOME" => "Home", 33 | "KC_PGUP" => "Page\nUp", 34 | "KC_DELETE" => "Del", 35 | "KC_END" => "End", 36 | "KC_PGDOWN" => "Page\nDown", 37 | "KC_RIGHT" => "Right", 38 | "KC_LEFT" => "Left", 39 | "KC_DOWN" => "Down", 40 | "KC_UP" => "Up", 41 | "KC_A" => "A", 42 | "KC_B" => "B", 43 | "KC_C" => "C", 44 | "KC_D" => "D", 45 | "KC_E" => "E", 46 | "KC_F" => "F", 47 | "KC_G" => "G", 48 | "KC_H" => "H", 49 | "KC_I" => "I", 50 | "KC_J" => "J", 51 | "KC_K" => "K", 52 | "KC_L" => "L", 53 | "KC_M" => "M", 54 | "KC_N" => "N", 55 | "KC_O" => "O", 56 | "KC_P" => "P", 57 | "KC_Q" => "Q", 58 | "KC_R" => "R", 59 | "KC_S" => "S", 60 | "KC_T" => "T", 61 | "KC_U" => "U", 62 | "KC_V" => "V", 63 | "KC_W" => "W", 64 | "KC_X" => "X", 65 | "KC_Y" => "Y", 66 | "KC_Z" => "Z", 67 | "KC_1" => "!\n1", 68 | "KC_2" => "@\n2", 69 | "KC_3" => "#\n3", 70 | "KC_4" => "$\n4", 71 | "KC_5" => "%\n5", 72 | "KC_6" => "^\n6", 73 | "KC_7" => "&\n7", 74 | "KC_8" => "*\n8", 75 | "KC_9" => "(\n9", 76 | "KC_0" => ")\n0", 77 | "KC_ENTER" => "Enter", 78 | "KC_ESCAPE" => "Esc", 79 | "KC_BSPACE" => "Bksp", 80 | "KC_TAB" => "Tab", 81 | "KC_SPACE" => "Space", 82 | "KC_MINUS" => "_\n-", 83 | "KC_EQUAL" => "+\n=", 84 | "KC_LBRACKET" => "{\n[", 85 | "KC_RBRACKET" => "}\n]", 86 | "KC_BSLASH" => "|\n\\", 87 | "KC_SCOLON" => ":\n;", 88 | "KC_QUOTE" => "\"\n'", 89 | "KC_GRAVE" => "~\n`", 90 | "KC_COMMA" => "<\n,", 91 | "KC_DOT" => ">\n.", 92 | "KC_SLASH" => "?\n/", 93 | "KC_CAPSLOCK" => "Caps\nLock", 94 | "KC_F1" => "F1", 95 | "KC_F2" => "F2", 96 | "KC_F3" => "F3", 97 | "KC_F4" => "F4", 98 | "KC_F5" => "F5", 99 | "KC_F6" => "F6", 100 | "KC_F7" => "F7", 101 | "KC_F8" => "F8", 102 | "KC_F9" => "F9", 103 | "KC_F10" => "F10", 104 | "KC_F11" => "F11", 105 | "KC_F12" => "F12", 106 | "KC_APPLICATION" => "Menu", 107 | "KC_LCTRL" => "LCtrl", 108 | "KC_LSHIFT" => "LShift", 109 | "KC_LALT" => "LAlt", 110 | "KC_LGUI" => "LGui", 111 | "KC_RCTRL" => "RCtrl", 112 | "KC_RSHIFT" => "RShift", 113 | "KC_RALT" => "RAlt", 114 | "KC_RGUI" => "RGui", 115 | "KC_NONUS_HASH" => "~\n#", 116 | "KC_NONUS_BSLASH" => "|\n\\", 117 | "KC_RO" => "_\n\\", 118 | "KC_KANA" => "カタカナ\nひらがな", 119 | "KC_JYEN" => "|\n¥", 120 | "KC_HENK" => "変換", 121 | "KC_MHEN" => "無変換", 122 | "KC_LANG1" => "한영\nかな", 123 | "KC_LANG2" => "漢字\n英数", 124 | "RESET" => "Reset", 125 | "OSM(MOD_LSFT)" => "OSM\nLSft", 126 | "OSM(MOD_LCTL)" => "OSM\nLCtl", 127 | "OSM(MOD_LALT)" => "OSM\nLAlt", 128 | "OSM(MOD_LGUI)" => "OSM\nLGUI", 129 | "OSM(MOD_RSFT)" => "OSM\nRSft", 130 | "OSM(MOD_RCTL)" => "OSM\nRCtl", 131 | "OSM(MOD_RALT)" => "OSM\nRAlt", 132 | "OSM(MOD_RGUI)" => "OSM\nRGUI", 133 | "OSM(MOD_LCTL|MOD_LSFT)" => "OSM\nCS", 134 | "OSM(MOD_LCTL|MOD_LALT)" => "OSM\nCA", 135 | "OSM(MOD_LCTL|MOD_LGUI)" => "OSM\nCG", 136 | "OSM(MOD_LSFT|MOD_LALT)" => "OSM\nSA", 137 | "OSM(MOD_LSFT|MOD_LGUI)" => "OSM\nSG", 138 | "OSM(MOD_LALT|MOD_LGUI)" => "OSM\nAG", 139 | "OSM(MOD_RCTL|MOD_RSFT)" => "OSM\nRCS", 140 | "OSM(MOD_RCTL|MOD_RALT)" => "OSM\nRCA", 141 | "OSM(MOD_RCTL|MOD_RGUI)" => "OSM\nRCG", 142 | "OSM(MOD_RSFT|MOD_RALT)" => "OSM\nRSA", 143 | "OSM(MOD_RSFT|MOD_RGUI)" => "OSM\nRSG", 144 | "OSM(MOD_RALT|MOD_RGUI)" => "OSM\nRAG", 145 | "OSM(MOD_LCTL|MOD_LSFT|MOD_LGUI)" => "OSM\nCSG", 146 | "OSM(MOD_LCTL|MOD_LALT|MOD_LGUI)" => "OSM\nCAG", 147 | "OSM(MOD_LSFT|MOD_LALT|MOD_LGUI)" => "OSM\nSAG", 148 | "OSM(MOD_RCTL|MOD_RSFT|MOD_RGUI)" => "OSM\nRCSG", 149 | "OSM(MOD_RCTL|MOD_RALT|MOD_RGUI)" => "OSM\nRCAG", 150 | "OSM(MOD_RSFT|MOD_RALT|MOD_RGUI)" => "OSM\nRSAG", 151 | "OSM(MOD_MEH)" => "OSM\nMeh", 152 | "OSM(MOD_HYPR)" => "OSM\nHyper", 153 | "OSM(MOD_RCTL|MOD_RSFT|MOD_RALT)" => "OSM\nRMeh", 154 | "OSM(MOD_RCTL|MOD_RSFT|MOD_RALT|MOD_RGUI)" => "OSM\nRHyp", 155 | "LSFT(kc)" => "LSft\n(kc)", 156 | "LCTL(kc)" => "LCtl\n(kc)", 157 | "LALT(kc)" => "LAlt\n(kc)", 158 | "LGUI(kc)" => "LGui\n(kc)", 159 | "RSFT(kc)" => "RSft\n(kc)", 160 | "RCTL(kc)" => "RCtl\n(kc)", 161 | "RALT(kc)" => "RAlt\n(kc)", 162 | "RGUI(kc)" => "RGui\n(kc)", 163 | "C_S(kc)" => "LCS\n(kc)", 164 | "LCA(kc)" => "LCA\n(kc)", 165 | "LCG(kc)" => "LCG\n(kc)", 166 | "LSA(kc)" => "LSA\n(kc)", 167 | "SGUI(kc)" => "LSG\n(kc)", 168 | "LCAG(kc)" => "LCAG\n(kc)", 169 | "RCG(kc)" => "RCG\n(kc)", 170 | "MEH(kc)" => "Meh\n(kc)", 171 | "HYPR(kc)" => "Hyper\n(kc)", 172 | "LSFT_T(kc)" => "LSft_T\n(kc)", 173 | "LCTL_T(kc)" => "LCtl_T\n(kc)", 174 | "LALT_T(kc)" => "LAlt_T\n(kc)", 175 | "LGUI_T(kc)" => "LGui_T\n(kc)", 176 | "RSFT_T(kc)" => "RSft_T\n(kc)", 177 | "RCTL_T(kc)" => "RCtl_T\n(kc)", 178 | "RALT_T(kc)" => "RAlt_T\n(kc)", 179 | "RGUI_T(kc)" => "RGui_T\n(kc)", 180 | "C_S_T(kc)" => "LCS_T\n(kc)", 181 | "LCA_T(kc)" => "LCA_T\n(kc)", 182 | "LCG_T(kc)" => "LCG_T\n(kc)", 183 | "LSA_T(kc)" => "LSA_T\n(kc)", 184 | "SGUI_T(kc)" => "LSG_T\n(kc)", 185 | "LCAG_T(kc)" => "LCAG_T\n(kc)", 186 | "RCG_T(kc)" => "RCG_T\n(kc)", 187 | "RCAG_T(kc)" => "RCAG_T\n(kc)", 188 | "MEH_T(kc)" => "Meh_T\n(kc)", 189 | "ALL_T(kc)" => "ALL_T\n(kc)", 190 | "KC_GESC" => "~\nEsc", 191 | "KC_LSPO" => "LS\n(", 192 | "KC_RSPC" => "RS\n)", 193 | "KC_LCPO" => "LC\n(", 194 | "KC_RCPC" => "RC\n)", 195 | "KC_LAPO" => "LA\n(", 196 | "KC_RAPC" => "RA\n)", 197 | "KC_SFTENT" => "RS\nEnter", 198 | "MAGIC_SWAP_CONTROL_CAPSLOCK" => "Swap\nCtrl\nCaps", 199 | "MAGIC_UNSWAP_CONTROL_CAPSLOCK" => "Unswap\nCtrl\nCaps", 200 | "MAGIC_CAPSLOCK_TO_CONTROL" => "Caps\nto\nCtrl", 201 | "MAGIC_UNCAPSLOCK_TO_CONTROL" => "Caps\nnot to\nCtrl", 202 | "MAGIC_SWAP_LCTL_LGUI" => "Swap\nLCtl\nLGui", 203 | "MAGIC_UNSWAP_LCTL_LGUI" => "Unswap\nLCtl\nLGui", 204 | "MAGIC_SWAP_RCTL_RGUI" => "Swap\nRCtl\nRGui", 205 | "MAGIC_UNSWAP_RCTL_RGUI" => "Unswap\nRCtl\nRGui", 206 | "MAGIC_SWAP_CTL_GUI" => "Swap\nCtl\nGui", 207 | "MAGIC_UNSWAP_CTL_GUI" => "Unswap\nCtl\nGui", 208 | "MAGIC_TOGGLE_CTL_GUI" => "Toggle\nCtl\nGui", 209 | "MAGIC_SWAP_LALT_LGUI" => "Swap\nLAlt\nLGui", 210 | "MAGIC_UNSWAP_LALT_LGUI" => "Unswap\nLAlt\nLGui", 211 | "MAGIC_SWAP_RALT_RGUI" => "Swap\nRAlt\nRGui", 212 | "MAGIC_UNSWAP_RALT_RGUI" => "Unswap\nRAlt\nRGui", 213 | "MAGIC_SWAP_ALT_GUI" => "Swap\nAlt\nGui", 214 | "MAGIC_UNSWAP_ALT_GUI" => "Unswap\nAlt\nGui", 215 | "MAGIC_TOGGLE_ALT_GUI" => "Toggle\nAlt\nGui", 216 | "MAGIC_NO_GUI" => "GUI\nOff", 217 | "MAGIC_UNNO_GUI" => "GUI\nOn", 218 | "MAGIC_SWAP_GRAVE_ESC" => "Swap\n`\nEsc", 219 | "MAGIC_UNSWAP_GRAVE_ESC" => "Unswap\n`\nEsc", 220 | "MAGIC_SWAP_BACKSLASH_BACKSPACE" => "Swap\n\\\nBS", 221 | "MAGIC_UNSWAP_BACKSLASH_BACKSPACE" => "Unswap\n\\\nBS", 222 | "MAGIC_HOST_NKRO" => "NKRO\nOn", 223 | "MAGIC_UNHOST_NKRO" => "NKRO\nOff", 224 | "MAGIC_TOGGLE_NKRO" => "NKRO\nToggle", 225 | "MAGIC_EE_HANDS_LEFT" => "EEH\nLeft", 226 | "MAGIC_EE_HANDS_RIGHT" => "EEH\nRight", 227 | "AU_ON" => "Audio\nON", 228 | "AU_OFF" => "Audio\nOFF", 229 | "AU_TOG" => "Audio\nToggle", 230 | "CLICKY_TOGGLE" => "Clicky\nToggle", 231 | "CLICKY_UP" => "Clicky\nUp", 232 | "CLICKY_DOWN" => "Clicky\nDown", 233 | "CLICKY_RESET" => "Clicky\nReset", 234 | "MU_ON" => "Music\nOn", 235 | "MU_OFF" => "Music\nOff", 236 | "MU_TOG" => "Music\nToggle", 237 | "MU_MOD" => "Music\nCycle", 238 | "HPT_ON" => "Haptic\nOn", 239 | "HPT_OFF" => "Haptic\nOff", 240 | "HPT_TOG" => "Haptic\nToggle", 241 | "HPT_RST" => "Haptic\nReset", 242 | "HPT_FBK" => "Haptic\nFeed\nback", 243 | "HPT_BUZ" => "Haptic\nBuzz", 244 | "HPT_MODI" => "Haptic\nNext", 245 | "HPT_MODD" => "Haptic\nPrev", 246 | "HPT_CONT" => "Haptic\nCont.", 247 | "HPT_CONI" => "Haptic\n+", 248 | "HPT_COND" => "Haptic\n-", 249 | "HPT_DWLI" => "Haptic\nDwell+", 250 | "HPT_DWLD" => "Haptic\nDwell-", 251 | "KC_ASDN" => "Auto-\nshift\nDown", 252 | "KC_ASUP" => "Auto-\nshift\nUp", 253 | "KC_ASRP" => "Auto-\nshift\nReport", 254 | "KC_ASON" => "Auto-\nshift\nOn", 255 | "KC_ASOFF" => "Auto-\nshift\nOff", 256 | "KC_ASTG" => "Auto-\nshift\nToggle", 257 | "CMB_ON" => "Combo\nOn", 258 | "CMB_OFF" => "Combo\nOff", 259 | "CMB_TOG" => "Combo\nToggle", 260 | "BL_TOGG" => "BL\nToggle", 261 | "BL_STEP" => "BL\nCycle", 262 | "BL_BRTG" => "BL\nBreath", 263 | "BL_ON" => "BL On", 264 | "BL_OFF" => "BL Off", 265 | "BL_INC" => "BL +", 266 | "BL_DEC" => "BL - ", 267 | "RGB_TOG" => "RGB\nToggle", 268 | "RGB_MOD" => "RGB\nMode +", 269 | "RGB_RMOD" => "RGB\nMode -", 270 | "RGB_HUI" => "Hue +", 271 | "RGB_HUD" => "Hue -", 272 | "RGB_SAI" => "Sat +", 273 | "RGB_SAD" => "Sat -", 274 | "RGB_VAI" => "Bright +", 275 | "RGB_VAD" => "Bright -", 276 | "RGB_SPI" => "Effect +", 277 | "RGB_SPD" => "Effect -", 278 | "RGB_M_P" => "RGB\nMode P", 279 | "RGB_M_B" => "RGB\nMode B", 280 | "RGB_M_R" => "RGB\nMode R", 281 | "RGB_M_SW" => "RGB\nMode SW", 282 | "RGB_M_SN" => "RGB\nMode SN", 283 | "RGB_M_K" => "RGB\nMode K", 284 | "RGB_M_X" => "RGB\nMode X", 285 | "RGB_M_G" => "RGB\nMode G", 286 | "RGB_M_T" => "RGB\nMode T", 287 | "KC_PWR" => "Power", 288 | "KC_SLEP" => "Sleep", 289 | "KC_WAKE" => "Wake", 290 | "KC_EXEC" => "Exec", 291 | "KC_SLCT" => "Select", 292 | "KC_AGIN" => "Again", 293 | "KC_PSTE" => "Paste", 294 | "KC_CALC" => "Calc", 295 | "KC_MAIL" => "Mail", 296 | "KC_MSEL" => "Media\nPlayer", 297 | "KC_MYCM" => "My\nPC", 298 | "KC_WSCH" => "Browser\nSearch", 299 | "KC_WHOM" => "Browser\nHome", 300 | "KC_WBAK" => "Browser\nBack", 301 | "KC_WFWD" => "Browser\nForward", 302 | "KC_WSTP" => "Browser\nStop", 303 | "KC_WREF" => "Browser\nRefresh", 304 | "KC_WFAV" => "Browser\nFav.", 305 | "KC_BRIU" => "Bright.\nUp", 306 | "KC_BRID" => "Bright.\nDown", 307 | "KC_MPRV" => "Media\nPrev", 308 | "KC_MNXT" => "Media\nNext", 309 | "KC_MUTE" => "Mute", 310 | "KC_VOLD" => "Vol -", 311 | "KC_VOLU" => "Vol +", 312 | "KC__VOLDOWN" => "Vol -\nAlt", 313 | "KC__VOLUP" => "Vol +\nAlt", 314 | "KC_MSTP" => "Media\nStop", 315 | "KC_MPLY" => "Media\nPlay", 316 | "KC_MRWD" => "Prev\nTrack\n(macOS)", 317 | "KC_MFFD" => "Next\nTrack\n(macOS)", 318 | "KC_EJCT" => "Eject", 319 | "KC_MS_U" => "Mouse\nUp", 320 | "KC_MS_D" => "Mouse\nDown", 321 | "KC_MS_L" => "Mouse\nLeft", 322 | "KC_MS_R" => "Mouse\nRight", 323 | "KC_BTN1" => "Mouse\n1", 324 | "KC_BTN2" => "Mouse\n2", 325 | "KC_BTN3" => "Mouse\n3", 326 | "KC_BTN4" => "Mouse\n4", 327 | "KC_BTN5" => "Mouse\n5", 328 | "KC_WH_U" => "Mouse\nWheel\nUp", 329 | "KC_WH_D" => "Mouse\nWheel\nDown", 330 | "KC_WH_L" => "Mouse\nWheel\nLeft", 331 | "KC_WH_R" => "Mouse\nWheel\nRight", 332 | "KC_ACL0" => "Mouse\nAccel\n0", 333 | "KC_ACL1" => "Mouse\nAccel\n1", 334 | "KC_ACL2" => "Mouse\nAccel\n2", 335 | "KC_LCAP" => "Locking\nCaps", 336 | "KC_LNUM" => "Locking\nNum", 337 | "KC_LSCR" => "Locking\nScroll", 338 | "DYN_REC_START1" => "DM1\nRec", 339 | "DYN_REC_START2" => "DM2\nRec", 340 | "DYN_REC_STOP" => "DM Rec\nStop", 341 | "DYN_MACRO_PLAY1" => "DM1\nPlay", 342 | "DYN_MACRO_PLAY2" => "DM2\nPlay", 343 | "MI_C" => "ᴹᴵᴰᴵ\nC", 344 | "MI_Cs" => "ᴹᴵᴰᴵ\nC#/Dᵇ", 345 | "MI_D" => "ᴹᴵᴰᴵ\nD", 346 | "MI_Ds" => "ᴹᴵᴰᴵ\nD#/Eᵇ", 347 | "MI_E" => "ᴹᴵᴰᴵ\nE", 348 | "MI_F" => "ᴹᴵᴰᴵ\nF", 349 | "MI_Fs" => "ᴹᴵᴰᴵ\nF#/Gᵇ", 350 | "MI_G" => "ᴹᴵᴰᴵ\nG", 351 | "MI_Gs" => "ᴹᴵᴰᴵ\nG#/Aᵇ", 352 | "MI_A" => "ᴹᴵᴰᴵ\nA", 353 | "MI_As" => "ᴹᴵᴰᴵ\nA#/Bᵇ", 354 | "MI_B" => "ᴹᴵᴰᴵ\nB", 355 | "MI_C_1" => "ᴹᴵᴰᴵ\nC₁", 356 | "MI_Cs_1" => "ᴹᴵᴰᴵ\nC#₁/Dᵇ₁", 357 | "MI_D_1" => "ᴹᴵᴰᴵ\nD₁", 358 | "MI_Ds_1" => "ᴹᴵᴰᴵ\nD#₁/Eᵇ₁", 359 | "MI_E_1" => "ᴹᴵᴰᴵ\nE₁", 360 | "MI_F_1" => "ᴹᴵᴰᴵ\nF₁", 361 | "MI_Fs_1" => "ᴹᴵᴰᴵ\nF#₁/Gᵇ₁", 362 | "MI_G_1" => "ᴹᴵᴰᴵ\nG₁", 363 | "MI_Gs_1" => "ᴹᴵᴰᴵ\nG#₁/Aᵇ₁", 364 | "MI_A_1" => "ᴹᴵᴰᴵ\nA₁", 365 | "MI_As_1" => "ᴹᴵᴰᴵ\nA#₁/Bᵇ₁", 366 | "MI_B_1" => "ᴹᴵᴰᴵ\nB₁", 367 | "MI_C_2" => "ᴹᴵᴰᴵ\nC₂", 368 | "MI_Cs_2" => "ᴹᴵᴰᴵ\nC#₂/Dᵇ₂", 369 | "MI_D_2" => "ᴹᴵᴰᴵ\nD₂", 370 | "MI_Ds_2" => "ᴹᴵᴰᴵ\nD#₂/Eᵇ₂", 371 | "MI_E_2" => "ᴹᴵᴰᴵ\nE₂", 372 | "MI_F_2" => "ᴹᴵᴰᴵ\nF₂", 373 | "MI_Fs_2" => "ᴹᴵᴰᴵ\nF#₂/Gᵇ₂", 374 | "MI_G_2" => "ᴹᴵᴰᴵ\nG₂", 375 | "MI_Gs_2" => "ᴹᴵᴰᴵ\nG#₂/Aᵇ₂", 376 | "MI_A_2" => "ᴹᴵᴰᴵ\nA₂", 377 | "MI_As_2" => "ᴹᴵᴰᴵ\nA#₂/Bᵇ₂", 378 | "MI_B_2" => "ᴹᴵᴰᴵ\nB₂", 379 | "MI_C_3" => "ᴹᴵᴰᴵ\nC₃", 380 | "MI_Cs_3" => "ᴹᴵᴰᴵ\nC#₃/Dᵇ₃", 381 | "MI_D_3" => "ᴹᴵᴰᴵ\nD₃", 382 | "MI_Ds_3" => "ᴹᴵᴰᴵ\nD#₃/Eᵇ₃", 383 | "MI_E_3" => "ᴹᴵᴰᴵ\nE₃", 384 | "MI_F_3" => "ᴹᴵᴰᴵ\nF₃", 385 | "MI_Fs_3" => "ᴹᴵᴰᴵ\nF#₃/Gᵇ₃", 386 | "MI_G_3" => "ᴹᴵᴰᴵ\nG₃", 387 | "MI_Gs_3" => "ᴹᴵᴰᴵ\nG#₃/Aᵇ₃", 388 | "MI_A_3" => "ᴹᴵᴰᴵ\nA₃", 389 | "MI_As_3" => "ᴹᴵᴰᴵ\nA#₃/Bᵇ₃", 390 | "MI_B_3" => "ᴹᴵᴰᴵ\nB₃", 391 | "MI_C_4" => "ᴹᴵᴰᴵ\nC₄", 392 | "MI_Cs_4" => "ᴹᴵᴰᴵ\nC#₄/Dᵇ₄", 393 | "MI_D_4" => "ᴹᴵᴰᴵ\nD₄", 394 | "MI_Ds_4" => "ᴹᴵᴰᴵ\nD#₄/Eᵇ₄", 395 | "MI_E_4" => "ᴹᴵᴰᴵ\nE₄", 396 | "MI_F_4" => "ᴹᴵᴰᴵ\nF₄", 397 | "MI_Fs_4" => "ᴹᴵᴰᴵ\nF#₄/Gᵇ₄", 398 | "MI_G_4" => "ᴹᴵᴰᴵ\nG₄", 399 | "MI_Gs_4" => "ᴹᴵᴰᴵ\nG#₄/Aᵇ₄", 400 | "MI_A_4" => "ᴹᴵᴰᴵ\nA₄", 401 | "MI_As_4" => "ᴹᴵᴰᴵ\nA#₄/Bᵇ₄", 402 | "MI_B_4" => "ᴹᴵᴰᴵ\nB₄", 403 | "MI_C_5" => "ᴹᴵᴰᴵ\nC₅", 404 | "MI_Cs_5" => "ᴹᴵᴰᴵ\nC#₅/Dᵇ₅", 405 | "MI_D_5" => "ᴹᴵᴰᴵ\nD₅", 406 | "MI_Ds_5" => "ᴹᴵᴰᴵ\nD#₅/Eᵇ₅", 407 | "MI_E_5" => "ᴹᴵᴰᴵ\nE₅", 408 | "MI_F_5" => "ᴹᴵᴰᴵ\nF₅", 409 | "MI_Fs_5" => "ᴹᴵᴰᴵ\nF#₅/Gᵇ₅", 410 | "MI_G_5" => "ᴹᴵᴰᴵ\nG₅", 411 | "MI_Gs_5" => "ᴹᴵᴰᴵ\nG#₅/Aᵇ₅", 412 | "MI_A_5" => "ᴹᴵᴰᴵ\nA₅", 413 | "MI_As_5" => "ᴹᴵᴰᴵ\nA#₅/Bᵇ₅", 414 | "MI_B_5" => "ᴹᴵᴰᴵ\nB₅", 415 | "MI_ALLOFF" => "ᴹᴵᴰᴵ\nNotesᵒᶠᶠ", 416 | "MI_OCT_N2" => "ᴹᴵᴰᴵ\nOct₋₂", 417 | "MI_OCT_N1" => "ᴹᴵᴰᴵ\nOct₋₁", 418 | "MI_OCT_0" => "ᴹᴵᴰᴵ\nOct₀", 419 | "MI_OCT_1" => "ᴹᴵᴰᴵ\nOct₊₁", 420 | "MI_OCT_2" => "ᴹᴵᴰᴵ\nOct₊₂", 421 | "MI_OCT_3" => "ᴹᴵᴰᴵ\nOct₊₃", 422 | "MI_OCT_4" => "ᴹᴵᴰᴵ\nOct₊₄", 423 | "MI_OCT_5" => "ᴹᴵᴰᴵ\nOct₊₅", 424 | "MI_OCT_6" => "ᴹᴵᴰᴵ\nOct₊₆", 425 | "MI_OCT_7" => "ᴹᴵᴰᴵ\nOct₊₇", 426 | "MI_OCTD" => "ᴹᴵᴰᴵ\nOctᴰᴺ", 427 | "MI_OCTU" => "ᴹᴵᴰᴵ\nOctᵁᴾ", 428 | "MI_TRNS_N6" => "ᴹᴵᴰᴵ\nTrans₋₆", 429 | "MI_TRNS_N5" => "ᴹᴵᴰᴵ\nTrans₋₅", 430 | "MI_TRNS_N4" => "ᴹᴵᴰᴵ\nTrans₋₄", 431 | "MI_TRNS_N3" => "ᴹᴵᴰᴵ\nTrans₋₃", 432 | "MI_TRNS_N2" => "ᴹᴵᴰᴵ\nTrans₋₂", 433 | "MI_TRNS_N1" => "ᴹᴵᴰᴵ\nTrans₋₁", 434 | "MI_TRNS_0" => "ᴹᴵᴰᴵ\nTrans₀", 435 | "MI_TRNS_1" => "ᴹᴵᴰᴵ\nTrans₊₁", 436 | "MI_TRNS_2" => "ᴹᴵᴰᴵ\nTrans₊₂", 437 | "MI_TRNS_3" => "ᴹᴵᴰᴵ\nTrans₊₃", 438 | "MI_TRNS_4" => "ᴹᴵᴰᴵ\nTrans₊₄", 439 | "MI_TRNS_5" => "ᴹᴵᴰᴵ\nTrans₊₅", 440 | "MI_TRNS_6" => "ᴹᴵᴰᴵ\nTrans₊₆", 441 | "MI_TRNSD" => "ᴹᴵᴰᴵ\nTransᴰᴺ", 442 | "MI_TRNSU" => "ᴹᴵᴰᴵ\nTransᵁᴾ", 443 | "MI_VEL_1" => "ᴹᴵᴰᴵ\nVel₁", 444 | "MI_VEL_2" => "ᴹᴵᴰᴵ\nVel₂", 445 | "MI_VEL_3" => "ᴹᴵᴰᴵ\nVel₃", 446 | "MI_VEL_4" => "ᴹᴵᴰᴵ\nVel₄", 447 | "MI_VEL_5" => "ᴹᴵᴰᴵ\nVel₅", 448 | "MI_VEL_6" => "ᴹᴵᴰᴵ\nVel₆", 449 | "MI_VEL_7" => "ᴹᴵᴰᴵ\nVel₇", 450 | "MI_VEL_8" => "ᴹᴵᴰᴵ\nVel₈", 451 | "MI_VEL_9" => "ᴹᴵᴰᴵ\nVel₉", 452 | "MI_VEL_10" => "ᴹᴵᴰᴵ\nVel₁₀", 453 | "MI_VELD" => "ᴹᴵᴰᴵ\nVelᴰᴺ", 454 | "MI_VELU" => "ᴹᴵᴰᴵ\nVelᵁᴾ", 455 | "MI_CH1" => "ᴹᴵᴰᴵ\nCH₁", 456 | "MI_CH2" => "ᴹᴵᴰᴵ\nCH₂", 457 | "MI_CH3" => "ᴹᴵᴰᴵ\nCH₃", 458 | "MI_CH4" => "ᴹᴵᴰᴵ\nCH₄", 459 | "MI_CH5" => "ᴹᴵᴰᴵ\nCH₅", 460 | "MI_CH6" => "ᴹᴵᴰᴵ\nCH₆", 461 | "MI_CH7" => "ᴹᴵᴰᴵ\nCH₇", 462 | "MI_CH8" => "ᴹᴵᴰᴵ\nCH₈", 463 | "MI_CH9" => "ᴹᴵᴰᴵ\nCH₉", 464 | "MI_CH10" => "ᴹᴵᴰᴵ\nCH₁₀", 465 | "MI_CH11" => "ᴹᴵᴰᴵ\nCH₁₁", 466 | "MI_CH12" => "ᴹᴵᴰᴵ\nCH₁₂", 467 | "MI_CH13" => "ᴹᴵᴰᴵ\nCH₁₃", 468 | "MI_CH14" => "ᴹᴵᴰᴵ\nCH₁₄", 469 | "MI_CH15" => "ᴹᴵᴰᴵ\nCH₁₅", 470 | "MI_CH16" => "ᴹᴵᴰᴵ\nCH₁₆", 471 | "MI_CHD" => "ᴹᴵᴰᴵ\nCHᴰᴺ", 472 | "MI_CHU" => "ᴹᴵᴰᴵ\nCHᵁᴾ", 473 | "MI_SUS" => "ᴹᴵᴰᴵ\nSust", 474 | "MI_PORT" => "ᴹᴵᴰᴵ\nPort", 475 | "MI_SOST" => "ᴹᴵᴰᴵ\nSost", 476 | "MI_SOFT" => "ᴹᴵᴰᴵ\nSPedal", 477 | "MI_LEG" => "ᴹᴵᴰᴵ\nLegat", 478 | "MI_MOD" => "ᴹᴵᴰᴵ\nModul", 479 | "MI_MODSD" => "ᴹᴵᴰᴵ\nModulᴰᴺ", 480 | "MI_MODSU" => "ᴹᴵᴰᴵ\nModulᵁᴾ", 481 | "MI_BENDD" => "ᴹᴵᴰᴵ\nBendᴰᴺ", 482 | "MI_BENDU" => "ᴹᴵᴰᴵ\nBendᵁᴾ", 483 | ]; 484 | } 485 | -------------------------------------------------------------------------------- /src/vial/VialKeyNamesGen.hx: -------------------------------------------------------------------------------- 1 | package vial; 2 | import sys.io.File; 3 | import tools.ERegTools; 4 | 5 | /** 6 | * How to run (from `src/`): 7 | * haxe --neko vial/VialKeyNamesGen.n -main vial.VialKeyNamesGen 8 | * neko vial/VialKeyNamesGen.n 9 | * @author YellowAfterlife 10 | */ 11 | class VialKeyNamesGen { 12 | public static function main() { 13 | var py = File.getContent("vial/keycodes.py"); 14 | var b = new StringBuf(); 15 | for (line in [ 16 | "package vial;", 17 | "/** ", 18 | " * Generated by VialKeyNamesGen.hx", 19 | " * from vial-kb/vial-gui: src/main/python/keycodes/keycodes.py", 20 | " */", 21 | "class VialKeyNames {", 22 | " public static var map:Map = [" 23 | ]) b.add(line + "\r\n"); 24 | ERegTools.each(~/^\s*K\((".+?"), (".+?"),.+/gm, py, function(rx:EReg) { 25 | b.add("\t\t" + rx.matched(1) + " => " + rx.matched(2) + ",\r\n"); 26 | }); 27 | b.add("\t];\r\n"); 28 | b.add("}\r\n"); 29 | File.saveContent("vial/VialKeyNames.hx", b.toString()); 30 | Sys.println("OK!"); 31 | } 32 | } -------------------------------------------------------------------------------- /src/vial/VialKeyNamesGen.n: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YAL-Tools/vial-to-keymap-drawer/7aa058f163f786e28a92e15446af9eb39ca80d56/src/vial/VialKeyNamesGen.n -------------------------------------------------------------------------------- /src/vial/VialKeymap.hx: -------------------------------------------------------------------------------- 1 | package vial; 2 | import haxe.extern.EitherType; 3 | import drawer.DrawerKeymap; 4 | using StringTools; 5 | 6 | /** 7 | * @author YellowAfterlife 8 | */ 9 | typedef VialKeymap = { 10 | // VIA: 11 | var layers:Array>; 12 | // VIAL: 13 | var version:Int; 14 | var uid:Int; 15 | var layout:Array>>; 16 | var encoder_layout:Array>>; 17 | var layout_options:Int; 18 | var vial_protocol:Int; 19 | var via_protocol:Int; 20 | var tap_dance:Array; 21 | var combo:Array>; 22 | var key_override:Array; 23 | var settings:Any; 24 | }; 25 | abstract VialKeymapTapDance(Array) { 26 | public var tap(get, set):VialKey; 27 | inline function get_tap() return this[0]; 28 | inline function set_tap(k) return this[0] = k; 29 | 30 | public var hold(get, set):VialKey; 31 | inline function get_hold() return this[1]; 32 | inline function set_hold(k) return this[1] = k; 33 | 34 | public var doubleTap(get, set):VialKey; 35 | inline function get_doubleTap() return this[2]; 36 | inline function set_doubleTap(k) return this[2] = k; 37 | 38 | public var tapHold(get, set):VialKey; 39 | inline function get_tapHold() return this[3]; 40 | inline function set_tapHold(k) return this[3] = k; 41 | 42 | public var tapTerm(get, set):Float; 43 | inline function get_tapTerm() return this[4]; 44 | inline function set_tapTerm(t) return this[4] = t; 45 | } -------------------------------------------------------------------------------- /src/vial/VialKeysWithShiftState.hx: -------------------------------------------------------------------------------- 1 | package vial; 2 | 3 | /** 4 | * Maps keys that are labelled like 5 | * "!\n1" (shift output goes first) 6 | * @author YellowAfterlife 7 | */ 8 | class VialKeysWithShiftState { 9 | public static var list:Array = [ 10 | // digits (of course) 11 | "KC_1", 12 | "KC_2", 13 | "KC_3", 14 | "KC_4", 15 | "KC_5", 16 | "KC_6", 17 | "KC_7", 18 | "KC_8", 19 | "KC_9", 20 | "KC_0", 21 | // common symbols: 22 | "KC_MINUS", 23 | "KC_EQUAL", 24 | "KC_LBRACKET", 25 | "KC_RBRACKET", 26 | "KC_BSLASH", 27 | "KC_SCOLON", 28 | "KC_QUOTE", 29 | "KC_GRAVE", 30 | "KC_COMMA", 31 | "KC_DOT", 32 | "KC_SLASH", 33 | // international: 34 | "KC_NONUS_HASH", 35 | "KC_NONUS_BSLASH", 36 | "KC_RO", 37 | "KC_KANA", 38 | "KC_JYEN", 39 | "KC_HENK", 40 | "KC_MHEN", 41 | "KC_LANG1", 42 | "KC_LANG2", 43 | // anything else..? 44 | ]; 45 | public static var map:Map = (function() { 46 | var m = new Map(); 47 | for (key in list) m[key] = true; 48 | return m; 49 | })(); 50 | } -------------------------------------------------------------------------------- /src/web/KeyRangeCreator.hx: -------------------------------------------------------------------------------- 1 | package web; 2 | import Main.find; 3 | import js.Browser; 4 | import js.html.DivElement; 5 | import js.html.Element; 6 | import js.html.FileReader; 7 | import js.html.FormElement; 8 | import js.html.InputElement; 9 | import js.html.MouseEvent; 10 | import js.html.TextAreaElement; 11 | import VilToDrawer; 12 | 13 | /** 14 | * ... 15 | * @author YellowAfterlife 16 | */ 17 | class KeyRangeCreator { 18 | public static var element:DivElement = find("key-range-editor"); 19 | public static var svgCtr:DivElement = cast element.querySelector(".svg-ctr"); 20 | public static var svgOld:DivElement = cast svgCtr.querySelector(".svg-old"); 21 | public static var svgNew:DivElement = cast svgCtr.querySelector(".svg-new"); 22 | 23 | public static var btOpen:InputElement = find("key-range-open"); 24 | public static var btClose:InputElement = find("key-range-close"); 25 | 26 | public static var btCopyYAML:InputElement = find("key-range-export"); 27 | public static var btOpenSVG:InputElement = find("key-range-import"); 28 | public static var fmOpenSVG:FormElement = find("key-range-svg-form"); 29 | public static var fpOpenSVG:InputElement = find("key-range-svg-picker"); 30 | 31 | public static var fdOut:TextAreaElement = find("key-range-preview"); 32 | 33 | public static var btCopyYAML_text = btCopyYAML.value; 34 | static function btCopyYAML_show(next:String) { 35 | var orig = btCopyYAML.value; 36 | if (orig != next) { 37 | btCopyYAML.value = "Copied!"; 38 | Browser.window.setTimeout(function() { 39 | btCopyYAML.value = btCopyYAML_text; 40 | }, 1000); 41 | } 42 | } 43 | 44 | static var keyInfo:Array = []; 45 | static var keyOrder:Array = []; 46 | static function update() { 47 | var newKeys = svgNew.querySelectorAll("g.key"); 48 | var oldKeys = svgOld.querySelectorAll("g.key"); 49 | for (i in 0 ... newKeys.length) { 50 | var ki = keyOrder[i]; 51 | var okey:Element = ki != null ? cast oldKeys[ki] : null; 52 | var nkey:Element = cast newKeys[i]; 53 | if (okey != null) { 54 | var rect = nkey.querySelector("rect").outerHTML; 55 | nkey.innerHTML = okey.innerHTML; 56 | nkey.querySelector("rect").outerHTML = rect; 57 | } else { 58 | var todo = []; 59 | for (c in nkey.children) if (c.tagName.toLowerCase() != "rect") todo.push(c); 60 | for (c in todo) nkey.removeChild(c); 61 | } 62 | } 63 | 64 | var ranges = []; 65 | var rangeStart:VialKeyInfo = null; 66 | var rangeEnd:VialKeyInfo = null; 67 | function flushRange() { 68 | var txt = rangeStart.row + "," + rangeStart.col; 69 | if (rangeEnd.col != rangeStart.col) txt += '-' + rangeEnd.col; 70 | ranges.push(txt); 71 | } 72 | for (i => ki in keyOrder) { 73 | var kp:VialKeyInfo = keyInfo[ki]; 74 | if (rangeStart == null) { 75 | rangeStart = rangeEnd = kp; 76 | } else if (kp.row != rangeEnd.row || kp.col != rangeEnd.col + 1) { 77 | flushRange(); 78 | rangeStart = rangeEnd = kp; 79 | } else rangeEnd = kp; 80 | } 81 | if (rangeStart != null) flushRange(); 82 | // 83 | Main.fdKeyRanges.value = fdOut.value = ranges.join("\n"); 84 | } 85 | 86 | static var svgMouseDown = false; 87 | static function svgReady(svg:String) { 88 | svgOld.innerHTML = svg; 89 | svgOld.onmousedown = function(e:MouseEvent) { 90 | svgMouseDown = true; 91 | var fn; 92 | fn = function(e:MouseEvent) { 93 | Browser.window.removeEventListener("mouseup", fn); 94 | svgMouseDown = false; 95 | Browser.console.log("up"); 96 | } 97 | Browser.window.addEventListener("mouseup", fn); 98 | e.preventDefault(); 99 | return false; 100 | } 101 | // 102 | var keyCount = 0; 103 | for (_key in svgOld.querySelectorAll("g.key")) { 104 | var key:Element = cast _key; 105 | var ki = keyCount++; 106 | key.onclick = function(_) { 107 | if (keyOrder.remove(ki)) { 108 | key.style.opacity = "1"; 109 | } else { 110 | key.style.opacity = "0.3"; 111 | keyOrder.push(ki); 112 | } 113 | update(); 114 | } 115 | key.onmousemove = function(_) { 116 | if (svgMouseDown) { 117 | if (!keyOrder.contains(ki)) { 118 | keyOrder.push(ki); 119 | key.style.opacity = "0.3"; 120 | update(); 121 | } 122 | } 123 | } 124 | } 125 | // 126 | svgNew.innerHTML = svg; 127 | for (el in svgNew.querySelectorAll("g.key > text:not(.tap)")) { 128 | el.parentElement.removeChild(el); 129 | } 130 | // 131 | update(); 132 | } 133 | public static function init() { 134 | btCopyYAML.onclick = function(_) { 135 | var yaml = Main.convert_impl(true); 136 | if (yaml != null) { 137 | keyInfo = Main.latestOpt.outKeys; 138 | Browser.navigator.clipboard.writeText(yaml).then(cast function() { 139 | btCopyYAML_show("Copied!"); 140 | }).catchError(cast function() { 141 | btCopyYAML_show("Can't copy!"); 142 | }); 143 | } else { 144 | btCopyYAML_show("Error!"); 145 | } 146 | } 147 | btOpenSVG.onclick = function() { 148 | fpOpenSVG.click(); 149 | } 150 | fpOpenSVG.onchange = function() { 151 | var file = fpOpenSVG.files[0]; 152 | if (file == null) return; 153 | 154 | var fileReader = new FileReader(); 155 | fileReader.onloadend = function() { 156 | fmOpenSVG.reset(); 157 | } 158 | fileReader.onload = function() { 159 | svgReady(fileReader.result); 160 | } 161 | fileReader.readAsText(file); 162 | } 163 | 164 | btOpen.onclick = function() { 165 | element.style.display = ""; 166 | } 167 | btClose.onclick = function() { 168 | element.style.display = "none"; 169 | } 170 | } 171 | } 172 | --------------------------------------------------------------------------------