├── .gitignore ├── mash.nimble ├── tags ├── README.md ├── mash.nim └── mashpkg └── codes.nim /.gitignore: -------------------------------------------------------------------------------- 1 | mash 2 | -------------------------------------------------------------------------------- /mash.nimble: -------------------------------------------------------------------------------- 1 | version = "0.1.3" 2 | author = "Carlo Capocasa" 3 | description = "High-accuracy Computer keyboard MIDI" 4 | license = "MIT" 5 | bin = @["mash"] 6 | 7 | # Dependencies 8 | requires "nim >= 2.0.0" 9 | requires "jacket" 10 | requires "jill >= 0.2.1" 11 | requires "rtthread" 12 | 13 | -------------------------------------------------------------------------------- /tags: -------------------------------------------------------------------------------- 1 | !_TAG_EXTRA_DESCRIPTION anonymous /Include tags for non-named objects like lambda/ 2 | !_TAG_EXTRA_DESCRIPTION fileScope /Include tags of file scope/ 3 | !_TAG_EXTRA_DESCRIPTION pseudo /Include pseudo tags/ 4 | !_TAG_EXTRA_DESCRIPTION subparser /Include tags generated by subparsers/ 5 | !_TAG_FIELD_DESCRIPTION epoch /the last modified time of the input file (only for F\/file kind tag)/ 6 | !_TAG_FIELD_DESCRIPTION file /File-restricted scoping/ 7 | !_TAG_FIELD_DESCRIPTION input /input file/ 8 | !_TAG_FIELD_DESCRIPTION name /tag name/ 9 | !_TAG_FIELD_DESCRIPTION pattern /pattern/ 10 | !_TAG_FIELD_DESCRIPTION typeref /Type and name of a variable or typedef/ 11 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ 12 | !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ 13 | !_TAG_KIND_DESCRIPTION!JSON a,array /arrays/ 14 | !_TAG_KIND_DESCRIPTION!JSON b,boolean /booleans/ 15 | !_TAG_KIND_DESCRIPTION!JSON n,number /numbers/ 16 | !_TAG_KIND_DESCRIPTION!JSON o,object /objects/ 17 | !_TAG_KIND_DESCRIPTION!JSON s,string /strings/ 18 | !_TAG_KIND_DESCRIPTION!JSON z,null /nulls/ 19 | !_TAG_KIND_DESCRIPTION!Markdown S,subsection /level 2 sections/ 20 | !_TAG_KIND_DESCRIPTION!Markdown T,l4subsection /level 4 sections/ 21 | !_TAG_KIND_DESCRIPTION!Markdown c,chapter /chapters/ 22 | !_TAG_KIND_DESCRIPTION!Markdown h,hashtag /hashtags/ 23 | !_TAG_KIND_DESCRIPTION!Markdown n,footnote /footnotes/ 24 | !_TAG_KIND_DESCRIPTION!Markdown s,section /sections/ 25 | !_TAG_KIND_DESCRIPTION!Markdown t,subsubsection /level 3 sections/ 26 | !_TAG_KIND_DESCRIPTION!Markdown u,l5subsection /level 5 sections/ 27 | !_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/ 28 | !_TAG_OUTPUT_FILESEP slash /slash or backslash/ 29 | !_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/ 30 | !_TAG_OUTPUT_VERSION 0.0 /current.age/ 31 | !_TAG_PARSER_VERSION!JSON 0.0 /current.age/ 32 | !_TAG_PARSER_VERSION!Markdown 1.1 /current.age/ 33 | !_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/ 34 | !_TAG_PROC_CWD /home/carlo/mash/ // 35 | !_TAG_PROGRAM_AUTHOR Universal Ctags Team // 36 | !_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/ 37 | !_TAG_PROGRAM_URL https://ctags.io/ /official site/ 38 | !_TAG_PROGRAM_VERSION 6.1.0 /v6.1.0/ 39 | 0 .claude/settings.local.json /^ "WebFetch(domain:jackaudio.org)"$/;" s array:permissions.allow 40 | Changelog README.md /^Changelog$/;" s chapter:Mash 41 | Installation README.md /^Installation$/;" s chapter:Mash 42 | Internals README.md /^Internals$/;" s chapter:Mash 43 | Key mapping README.md /^Key mapping$/;" s chapter:Mash 44 | License README.md /^License$/;" s chapter:Mash 45 | Limitations README.md /^Limitations$/;" s chapter:Mash 46 | Mash README.md /^Mash$/;" c 47 | Usage README.md /^Usage$/;" s chapter:Mash 48 | allow .claude/settings.local.json /^ "allow": [$/;" a object:permissions 49 | deny .claude/settings.local.json /^ "deny": []$/;" a object:permissions 50 | permissions .claude/settings.local.json /^ "permissions": {$/;" o 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mash 2 | ==== 3 | 4 | Mash is a very precise musical virtual keyboard program for Jack MIDI. Press keys on your computer keyboard, and get MIDI output on jack MIDI. 5 | 6 | It works similar to the many other musical software keyboards out there, but it's much more precise- try it and feel the difference. There is very little jitter and no more delay than jack has. So while it's not velocity sensitive (organs aren't either), it plays like a serious instrument. 7 | 8 | Installation 9 | ------------ 10 | 11 | If you don't have the Nim programming language yet, the easiest way to install it is to do 12 | 13 | ``` 14 | curl https://nim-lang.org/choosenim/init.sh -sSf | sh 15 | ``` 16 | 17 | And then install mash 18 | 19 | ``` 20 | nimble install mash 21 | ``` 22 | 23 | You need to add your user to the `input` group to allow access. 24 | 25 | ``` 26 | export USER=`whoami` 27 | echo $USER # check it's the right one 28 | sudo usermod -aG input $USER 29 | groups # check that "input" is there 30 | ``` 31 | 32 | Usage 33 | ----- 34 | 35 | Run it in a terminal. There is no output unless a note is late. 36 | 37 | ``` 38 | $ mash 39 | ``` 40 | 41 | If you get a "cannot open" error, you need to find the /dev/input device for 42 | your main keyboard. Look it up in the device list. 43 | 44 | ``` 45 | less /proc/bus/input/devices 46 | ``` 47 | 48 | Your keyboard should have 'keyboard' in the name. Once you've found it, there will be an entry 'eventX' under 'Handlers', for example `event5`. Your path is `/dev/input/eventX`. Then you run 49 | 50 | ``` 51 | mash /dev/input/event5 52 | ``` 53 | 54 | You can also run it as a service, it's quite light. 55 | 56 | Connect it to a software instrument using a jack tool. On the command line: 57 | 58 | ``` 59 | $ # list ports 60 | $ jack_lsp 61 | system:capture_1 62 | system:capture_2 63 | system:playback_1 64 | system:playback_2 65 | yoshimi:left 66 | yoshimi:right 67 | yoshimi:midi in 68 | mash:out 69 | $ jack_connect mash:out "yoshimi:midi in" 70 | 71 | ``` 72 | 73 | Now press keys and notes should arrive in your software instrument. 74 | 75 | To exit, press Ctrl-C or kill the process 76 | 77 | ``` 78 | killall keym 79 | ``` 80 | 81 | For some finetuning, you can try lower latency. A realtime kernel is recommended. Normally this value should be the same as your jack buffer size. 82 | 83 | ``` 84 | mash -n1 # 2 is default 85 | mash -n0 # 2 is default 86 | ``` 87 | 88 | By default, the keyboard handler has priority 98, but you can change it 89 | 90 | ``` 91 | mash -p90 # maybe this works even better 92 | ``` 93 | 94 | Key mapping 95 | ----------- 96 | 97 | | Keys | Notes | 98 | | - | - | 99 | | Z X C V B N M , . / | An octave and a half of white piano keys, MIDI notes 60-77 | 100 | | S D G H J L ; | The corresponding black piano keys | 101 | | Q W E R T Y U I O P [ ] | Another octave and a half of white piano keys, one octave higher, MIDI notes 72-89 | 102 | | 2 3 5 6 7 8 0 | The corresponding black piano keys | 103 | | Left/Right Arrow | Transpose on semitone up or down | 104 | | Up/Down Arrow | Transpose one octave up or down | 105 | | Esc | Send "all notes off" or panic key | 106 | | F1 - F12 | Set active channel to 1 through 12 | 107 | | Home, End, Insert, Delete | Send active channel to 13, 14, 15, 16 | 108 | 109 | This for an American keyboard. The keys are unaffected by international keyboard layouts, so the key position will be the same no matter what layout you have, but a different glyph might be printed on them. 110 | 111 | Limitations 112 | ----------- 113 | 114 | Currently mash only supports linux, and possibly other OS who have /dev/input keyboard events. Implementing other operating systems would be very cool, it just hasn't been done yet. 115 | 116 | Internals 117 | --------- 118 | 119 | Mash reads keyboard events directly from the keyboard driver via /dev/input and translates the kernel keyboard timestamp to jack sound card time via a clock sync that is done at the beginning of every jack period. The keyboard timestamps are delayed by the standard jack latency of two periods to avoid jitter. 120 | 121 | Most other keyboard instruments use the windowing system keyboard API- this is a big no-no, because there can be wildly varying delays and jitter, and there are different keyboard layouts to worry about. 122 | 123 | License 124 | ------- 125 | 126 | MIT License 127 | 128 | Changelog 129 | --------- 130 | 131 | ``` 132 | 0.1.3 Revert 0.1.1 133 | 0.1.2 Fix dependency version 134 | 0.1.1 Fix MIDI message clear spam 135 | 0.1.0 Inital release- it works! 136 | ``` 137 | 138 | -------------------------------------------------------------------------------- /mash.nim: -------------------------------------------------------------------------------- 1 | import std/[parseopt, strutils] 2 | import mashpkg/codes, rtthread, jill/[ringbuffer, os], jacket 3 | from posix import Timeval, Timespec, clock_gettime, CLOCK_MONOTONIC 4 | 5 | type 6 | RawKeyboardEvent {.packed.} = object 7 | time: Timeval 8 | kind: uint16 9 | scancode: uint16 10 | value: int32 11 | InputEventKind = enum 12 | Note, 13 | State 14 | Housekeeping 15 | StateTransition = enum 16 | SemitoneUp, 17 | SemitoneDown, 18 | OctaveUp, 19 | OctaveDown, 20 | Channel 21 | HousekeepingAction = enum 22 | AllNotesOff 23 | InputEvent {.packed.} = object 24 | usec: int64 25 | case kind: InputEventKind 26 | of Note: 27 | note: int8 28 | on: bool 29 | of State: 30 | transition: StateTransition 31 | data: int8 32 | of Housekeeping: 33 | action: HousekeepingAction 34 | 35 | const 36 | EVIOCSCLOCKID = 0x400445A0'u64 37 | 38 | transposeMin = -60'i8 39 | transposeMax = 60'i8 40 | 41 | noteOn = 0x90'u8 42 | noteOff = 0x80'u8 43 | control = 0xb0'u8 44 | 45 | proc toUsec(t: Timeval): int64 = 46 | ## older linux kernel time format with microseconds 47 | ## used by event subsystem 48 | t.tv_sec.int64 * 1_000_000 + t.tv_usec.int64 49 | 50 | proc toUsec(t: Timespec): int64 = 51 | ## newer linux kernel time format with nanoseconds 52 | ## used to retrieve system time 53 | t.tv_sec.int64 * 1_000_000 + t.tv_nsec.int64 div 1000 54 | 55 | proc getSysTime(): int64 = 56 | var ts {.noinit.}: Timespec 57 | discard clock_gettime(CLOCK_MONOTONIC, ts) 58 | ts.toUsec 59 | 60 | proc ioctl(f: FileHandle, request: culong, arg: pointer): cint {.importc: "ioctl", header: "".} 61 | ## std/posix ioctl signature doesn't match 62 | 63 | proc openDevice(path: string): File = 64 | result = open(path) 65 | var clk = CLOCK_MONOTONIC 66 | assert ioctl(result.getOSFileHandle, EVIOCSCLOCKID, clk.addr) == 0, "Could not set input subsystem to monotonic time" 67 | 68 | proc newInputEvent(rawEvent: RawKeyboardEvent): InputEvent = 69 | # echo "> ", scancode 70 | result = case rawEvent.scancode: 71 | 72 | # lower row 73 | of KEY_Z: 74 | InputEvent(kind: Note, note: 60) 75 | of KEY_S: 76 | InputEvent(kind: Note, note: 61) 77 | of KEY_X: 78 | InputEvent(kind: Note, note: 62) 79 | of KEY_D: 80 | InputEvent(kind: Note, note: 63) 81 | of KEY_C: 82 | InputEvent(kind: Note, note: 64) 83 | of KEY_V: 84 | InputEvent(kind: Note, note: 65) 85 | of KEY_G: 86 | InputEvent(kind: Note, note: 66) 87 | of KEY_B: 88 | InputEvent(kind: Note, note: 67) 89 | of KEY_H: 90 | InputEvent(kind: Note, note: 68) 91 | of KEY_N: 92 | InputEvent(kind: Note, note: 69) 93 | of KEY_J: 94 | InputEvent(kind: Note, note: 70) 95 | of KEY_M: 96 | InputEvent(kind: Note, note: 71) 97 | of KEY_COMMA: 98 | InputEvent(kind: Note, note: 72) 99 | of KEY_L: 100 | InputEvent(kind: Note, note: 73) 101 | of KEY_DOT: 102 | InputEvent(kind: Note, note: 74) 103 | of KEY_SEMICOLON: 104 | InputEvent(kind: Note, note: 75) 105 | of KEY_SLASH: 106 | InputEvent(kind: Note, note: 76) 107 | 108 | # upper row 109 | of KEY_Q: 110 | InputEvent(kind: Note, note: 72) 111 | of KEY_2: 112 | InputEvent(kind: Note, note: 73) 113 | of KEY_W: 114 | InputEvent(kind: Note, note: 74) 115 | of KEY_3: 116 | InputEvent(kind: Note, note: 75) 117 | of KEY_E: 118 | InputEvent(kind: Note, note: 76) 119 | of KEY_R: 120 | InputEvent(kind: Note, note: 77) 121 | of KEY_5: 122 | InputEvent(kind: Note, note: 78) 123 | of KEY_T: 124 | InputEvent(kind: Note, note: 79) 125 | of KEY_6: 126 | InputEvent(kind: Note, note: 80) 127 | of KEY_Y: 128 | InputEvent(kind: Note, note: 81) 129 | of KEY_7: 130 | InputEvent(kind: Note, note: 82) 131 | of KEY_U: 132 | InputEvent(kind: Note, note: 83) 133 | of KEY_I: 134 | InputEvent(kind: Note, note: 84) 135 | of KEY_9: 136 | InputEvent(kind: Note, note: 85) 137 | of KEY_O: 138 | InputEvent(kind: Note, note: 86) 139 | of KEY_0: 140 | InputEvent(kind: Note, note: 87) 141 | of KEY_P: 142 | InputEvent(kind: Note, note: 88) 143 | of KEY_LEFTBRACE: 144 | InputEvent(kind: Note, note: 89) 145 | of KEY_EQUAL: 146 | InputEvent(kind: Note, note: 90) 147 | of KEY_RIGHTBRACE: 148 | InputEvent(kind: Note, note: 91) 149 | 150 | # transpose 151 | 152 | of KEY_LEFT: 153 | InputEvent(kind: State, transition: SemitoneDown) 154 | of KEY_RIGHT: 155 | InputEvent(kind: State, transition: SemitoneUp) 156 | of KEY_UP: 157 | InputEvent(kind: State, transition: OctaveUp) 158 | of KEY_DOWN: 159 | InputEvent(kind: State, transition: OctaveDown) 160 | 161 | # channel 162 | of KEY_F1: 163 | InputEvent(kind: State, transition: Channel, data: 0) 164 | of KEY_F2: 165 | InputEvent(kind: State, transition: Channel, data: 1) 166 | of KEY_F3: 167 | InputEvent(kind: State, transition: Channel, data: 2) 168 | of KEY_F4: 169 | InputEvent(kind: State, transition: Channel, data: 3) 170 | of KEY_F5: 171 | InputEvent(kind: State, transition: Channel, data: 4) 172 | of KEY_F6: 173 | InputEvent(kind: State, transition: Channel, data: 5) 174 | of KEY_F7: 175 | InputEvent(kind: State, transition: Channel, data: 6) 176 | of KEY_F8: 177 | InputEvent(kind: State, transition: Channel, data: 7) 178 | of KEY_F9: 179 | InputEvent(kind: State, transition: Channel, data: 8) 180 | of KEY_F10: 181 | InputEvent(kind: State, transition: Channel, data: 9) 182 | of KEY_F11: 183 | InputEvent(kind: State, transition: Channel, data: 10) 184 | of KEY_F12: 185 | InputEvent(kind: State, transition: Channel, data: 11) 186 | of KEY_HOME: 187 | InputEvent(kind: State, transition: Channel, data: 12) 188 | of KEY_END: 189 | InputEvent(kind: State, transition: Channel, data: 13) 190 | of KEY_INSERT: 191 | InputEvent(kind: State, transition: Channel, data: 14) 192 | of KEY_DELETE: 193 | InputEvent(kind: State, transition: Channel, data: 15) 194 | 195 | # safety 196 | of KEY_ESC: 197 | InputEvent(kind: Housekeeping, action: AllNotesOff) 198 | 199 | # ignore other keys 200 | else: 201 | raise newException(ValueError, "unknown key") 202 | 203 | result.usec = rawEvent.time.toUsec 204 | 205 | let on = rawEvent.value.bool 206 | if result.kind == Note: 207 | result.on = on 208 | else: 209 | if not on: 210 | raise newException(ValueError, "keyup ignored") 211 | 212 | var 213 | eventThread:Thread[void] 214 | signalThread:Thread[void] 215 | eventBuffer = newRingBuffer[InputEvent](2048) 216 | terminating = false 217 | midiPort: Port 218 | midiWriterClient: Client 219 | midiWriterStatus: cint 220 | 221 | # command line option specific state 222 | latencyPeriods = 2 223 | eventThreadPriority = 98 224 | keyboardPath = "/dev/input/event3" 225 | keyboardDevice: File 226 | 227 | # midiwriter State 228 | # global for performance and convenience, not thread safe 229 | # only use in midiWriterThread or when that thread's not running 230 | # to create new ways to modify it, make a new inputEvent 231 | # so it is done in the correct order 232 | 233 | # use only one variable for octave and transpose 234 | # because transposing twelve semitones down 235 | # is conceptually the same as one octave 236 | # (the opposite view exists but is irrelevant in practice) 237 | transpose: int8 238 | 239 | # midi channel, 0 is channel 1 240 | channel: int8 241 | 242 | proc eventHandler() = 243 | while not terminating: 244 | var rawEvent:RawKeyboardEvent 245 | if keyboardDevice.readBuffer(rawEvent.addr, sizeof RawKeyboardEvent) != sizeof RawKeyboardEvent: 246 | # skip oddly-sized reads- should not happen 247 | continue 248 | if rawEvent.kind == 1'u16: # ensure keyboard events only 249 | case rawEvent.value 250 | of 0'i32, 1'i32: # press and release only 251 | try: 252 | eventBuffer.push(newInputEvent(rawEvent)) 253 | except ValueError: 254 | discard 255 | else: 256 | # ignore repeat (which would be 2'i32) 257 | discard 258 | 259 | proc `[]=`(s: ptr MidiData; i: int8; x: uint8) = 260 | cast[ptr UncheckedArray[MidiData]](s)[i] = x 261 | 262 | proc `[]`(s: ptr MidiData; i: int8): uint8 = 263 | cast[ptr UncheckedArray[MidiData]](s)[i] 264 | 265 | proc midiWriter*(numFrames: NFrames, arg: pointer): cint {.cdecl.} = 266 | let 267 | jackTime = getTime().int64 268 | sysTime = getSysTime() 269 | timeOffset = sysTime - jackTime 270 | 271 | midiOutBuffer = portGetBuffer(midiPort, numFrames) 272 | 273 | midiOutbuffer.midiClearBuffer() 274 | 275 | for event in eventBuffer.pop(): 276 | 277 | # calculate sample (jack "frame") for event to occur 278 | 279 | let 280 | frameTime = midiWriterClient.lastFrameTime() 281 | #nextFrameTime = midiWriterClient.lastFrameTime() 282 | eventJackTime = event.usec - timeOffset 283 | eventFrame = midiWriterClient.timeToFrames(eventJackTime.uint64) 284 | eventFrameFromLastFrameTime = eventFrame.int64 - frameTime.int64 285 | latency = latencyPeriods * numFrames.int 286 | var 287 | scheduledFrame = eventFrameFromLastFrameTime + latency.int 288 | # echo (frameTime, eventFrame, eventFrameFromLastFrameTime) 289 | if scheduledFrame < 0: 290 | scheduledFrame = 0 291 | stderr.write "Warning: event was late" 292 | elif scheduledFrame >= numFrames.int: 293 | break 294 | #stdout.write $scheduledFrame & " " 295 | 296 | case event.kind 297 | of Note: 298 | var data = midiOutBuffer.midiEventReserve(NFrames scheduledFrame, 3) 299 | assert not data.isNil, "could not reserve MIDI data" 300 | if event.on: 301 | data[0] = noteOn or channel.uint8 302 | data[1] = (event.note + transpose).uint8 303 | data[2] = 64'u8 # half velocity, play nice with other MIDI streams 304 | else: 305 | data[0] = noteOff or channel.uint8 306 | data[1] = (event.note + transpose).uint8 307 | data[2] = 0'u8 # signify quick release by MIDI conventions 308 | 309 | of State: 310 | 311 | # can just change the state here- scheduled events that rely 312 | # on the state have no more use for it at this point because 313 | # /dev/input events are guaranteed to arrive in order 314 | 315 | case event.transition: 316 | of SemitoneUp: 317 | transpose = min(60, transpose + 1) 318 | of SemitoneDown: 319 | transpose = max(-60, transpose - 1) 320 | # leave as is if out of range 321 | # (avoid unexpected and hard-to-correct key change that just limiting it would hae) 322 | of OctaveUp: 323 | transpose += 12 324 | if transpose > transposeMax: 325 | transpose -= 12 # not thread safe but doesn't need to be 326 | of OctaveDown: 327 | transpose -= 12 328 | if transpose < transposeMin: 329 | transpose += 12 330 | of Channel: 331 | channel = event.data 332 | 333 | of Housekeeping: 334 | var data = midiOutBuffer.midiEventReserve(NFrames scheduledFrame, 3) 335 | assert not data.isNil, "could not reserve MIDI data" 336 | data[0] = control 337 | data[1] = 123'u8 338 | data[2] = 0'u8 339 | 340 | proc usage() = 341 | echo "Usage: mash [-p98/--priority=98] [-n2/--periods=2]" 342 | quit 1 343 | 344 | var optParser = initOptParser("", {'v'}, @["version"]) 345 | for kind, key, val in optParser.getopt(): 346 | case kind 347 | of cmdShortOption, cmdLongOption: 348 | case key: 349 | of "p", "priority": 350 | eventThreadPriority = parseInt(val) 351 | if not eventThreadPriority in 1 .. 99: 352 | stderr.write "Event priority must be betwen 1 and 99\n" 353 | quit 1 354 | of "n", "periods": 355 | latencyPeriods = parseInt(val) 356 | if not latencyPeriods in 1 .. 10: 357 | stderr.write "Latency periods must be priority must be between 1 and 3. Start with 2 and see if you can get away with 1 or 0.\n" 358 | quit 1 359 | of "v", "version": 360 | echo "mash 0.1.0" 361 | quit 0 362 | else: 363 | usage() 364 | of cmdArgument: 365 | keyboardPath = key 366 | if not keyboardPath.startsWith "/dev/input": 367 | stderr.write "Your keyboard input should start with /dev/input.\n" 368 | quit 1 369 | of cmdEnd: 370 | break 371 | 372 | keyboardDevice = openDevice(keyboardPath) 373 | 374 | createThread signalThread, proc() {.thread.} = 375 | waitSignals(SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM): 376 | terminating = true 377 | 378 | blockSignals(SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM) 379 | 380 | midiWriterClient = clientOpen("mash", NoStartServer or UseExactName, midiWriterStatus.addr) 381 | assert not midiWriterClient.isNil, "Could not create jack client, jack may not be running" 382 | midiPort = midiWriterClient.portRegister("out", JackDefaultMidiType, PortIsOutput, 0) 383 | assert midiWriterClient.setProcessCallback(midiWriter) == 0, "could not set process callback" 384 | 385 | eventBuffer.lock() 386 | 387 | assert midiWriterClient.activate() == 0, "Could not connect jack" 388 | 389 | createRealtimeThread(eventThread, eventHandler, priority=cint eventThreadPriority) 390 | joinThread(eventThread) # exit when input thread does, it's simpler not to have to kill it 391 | 392 | midiWriterClient.deactivate 393 | midiWriterClient.clientClose 394 | 395 | -------------------------------------------------------------------------------- /mashpkg/codes.nim: -------------------------------------------------------------------------------- 1 | const 2 | KEY_FISHING_CHART* = 0x281'u16 3 | KEY_HIRAGANA* = 91'u16 4 | KEY_TOUCHPAD_TOGGLE* = 0x212'u16 5 | KEY_QUESTION* = 214'u16 6 | KEY_TITLE* = 0x171'u16 7 | KEY_GRAVE* = 41'u16 8 | KEY_DVD* = 0x185'u16 9 | KEY_FN_F10* = 0x1db'u16 10 | KEY_FN_F11* = 0x1dc'u16 11 | KEY_FN_F12* = 0x1dd'u16 12 | KEY_FRAMEBACK* = 0x1b4'u16 13 | KEY_SLEEP* = 142'u16 14 | KEY_KBDINPUTASSIST_ACCEPT* = 0x264'u16 15 | KEY_VCR2* = 0x17c'u16 16 | KEY_ZENKAKUHANKAKU* = 85'u16 17 | KEY_LEFTMETA* = 125'u16 18 | KEY_OPEN* = 134'u16 19 | KEY_NUMERIC_STAR* = 0x20a'u16 20 | KEY_MACRO* = 112'u16 21 | KEY_MODE* = 0x175'u16 22 | KEY_GREEN* = 0x18f'u16 23 | KEY_SAT* = 0x17d'u16 24 | KEY_SLOWREVERSE* = 0x276'u16 25 | KEY_KBDINPUTASSIST_NEXTGROUP* = 0x263'u16 26 | KEY_DATA* = 0x277'u16 27 | KEY_SELECT* = 0x161'u16 28 | KEY_MACRO30* = 0x2ad'u16 29 | KEY_MACRO1* = 0x290'u16 30 | KEY_MACRO2* = 0x291'u16 31 | KEY_MACRO4* = 0x293'u16 32 | KEY_MACRO6* = 0x295'u16 33 | KEY_MACRO8* = 0x297'u16 34 | KEY_MACRO9* = 0x298'u16 35 | KEY_BATTERY* = 236'u16 36 | KEY_CLEAR* = 0x163'u16 37 | KEY_PICKUP_PHONE* = 0x1bd'u16 38 | KEY_PAGEDOWN* = 109'u16 39 | KEY_CAPSLOCK* = 58'u16 40 | KEY_MACRO_RECORD_STOP* = 0x2b1'u16 41 | KEY_SCROLLDOWN* = 178'u16 42 | KEY_SWITCHVIDEOMODE* = 227'u16 43 | KEY_SOS* = 0x27f'u16 44 | KEY_LIST* = 0x18b'u16 45 | KEY_PLAYPAUSE* = 164'u16 46 | KEY_WORDPROCESSOR* = 0x1a5'u16 47 | KEY_CAMERA_LEFT* = 0x219'u16 48 | KEY_KBDINPUTASSIST_PREVGROUP* = 0x262'u16 49 | KEY_CAMERA* = 212'u16 50 | KEY_MOVE* = 175'u16 51 | KEY_CONNECT* = 218'u16 52 | SW_KEYPAD_SLIDE* = 0x0a'u16 53 | KEY_UWB* = 239'u16 54 | KEY_MARK_WAYPOINT* = 0x27e'u16 55 | KEY_FASTREVERSE* = 0x275'u16 56 | KEY_PROG1* = 148'u16 57 | KEY_PROG2* = 149'u16 58 | KEY_PROG3* = 202'u16 59 | KEY_PROG4* = 203'u16 60 | KEY_HOME* = 102'u16 61 | KEY_KBD_LCD_MENU4* = 0x2bb'u16 62 | KEY_EPG* = 0x16d'u16 63 | KEY_KBD_LCD_MENU5* = 0x2bc'u16 64 | KEY_NUMERIC_6* = 0x206'u16 65 | KEY_NUMERIC_8* = 0x208'u16 66 | KEY_INS_LINE* = 0x1c2'u16 67 | KEY_KPMINUS* = 74'u16 68 | KEY_KATAKANA* = 90'u16 69 | KEY_PROPS* = 130'u16 70 | KEY_DELETEFILE* = 146'u16 71 | KEY_RESTART* = 0x198'u16 72 | KEY_KBDILLUMTOGGLE* = 228'u16 73 | KEY_DOWN* = 108'u16 74 | KEY_DEL_EOS* = 0x1c1'u16 75 | KEY_CAMERA_FOCUS* = 0x210'u16 76 | KEY_NUMLOCK* = 69'u16 77 | KEY_ZOOMIN* = 0x1a2'u16 78 | KEY_KBDINPUTASSIST_PREV* = 0x260'u16 79 | KEY_NEXT_FAVORITE* = 0x270'u16 80 | KEY_AUX* = 0x186'u16 81 | KEY_VIDEOPHONE* = 0x1a0'u16 82 | KEY_EQUAL* = 13'u16 83 | KEY_DIRECTORY* = 0x18a'u16 84 | KEY_CAMERA_RIGHT* = 0x21a'u16 85 | KEY_ADDRESSBOOK* = 0x1ad'u16 86 | KEY_CONFIG* = 171'u16 87 | KEY_TUNER* = 0x182'u16 88 | KEY_PASTE* = 135'u16 89 | KEY_MACRO_PRESET1* = 0x2b3'u16 90 | KEY_CHANNELUP* = 0x192'u16 91 | KEY_RIGHT* = 106'u16 92 | KEY_MAIL* = 155'u16 93 | KEY_LEFTALT* = 56'u16 94 | KEY_WWW* = 150'u16 95 | KEY_KPEQUAL* = 117'u16 96 | KEY_HENKAN* = 92'u16 97 | KEY_FORWARD* = 159'u16 98 | KEY_DISPLAYTOGGLE* = 0x1af'u16 99 | KEY_RIGHTALT* = 100'u16 100 | KEY_LINK_PHONE* = 0x1bf'u16 101 | KEY_SPORT* = 220'u16 102 | KEY_POWER* = 116'u16 103 | KEY_ENTER* = 28'u16 104 | KEY_APOSTROPHE* = 40'u16 105 | KEY_MEDIA_TOP_MENU* = 0x26b'u16 106 | KEY_F10* = 68'u16 107 | KEY_RADIO* = 0x181'u16 108 | KEY_DATABASE* = 0x1aa'u16 109 | KEY_VOLUMEDOWN* = 114'u16 110 | KEY_BRL_DOT10* = 0x1fa'u16 111 | KEY_END* = 107'u16 112 | KEY_APPSELECT* = 0x244'u16 113 | KEY_SENDFILE* = 145'u16 114 | KEY_RIGHTSHIFT* = 54'u16 115 | KEY_ARCHIVE* = 0x169'u16 116 | KEY_NEXTSONG* = 163'u16 117 | KEY_NOTIFICATION_CENTER* = 0x1bc'u16 118 | KEY_SPREADSHEET* = 0x1a7'u16 119 | KEY_KPSLASH* = 98'u16 120 | KEY_BACK* = 158'u16 121 | KEY_KP5* = 76'u16 122 | KEY_PAGEUP* = 104'u16 123 | KEY_PLAY* = 207'u16 124 | KEY_ESC* = 1'u16 125 | KEY_CAMERA_ACCESS_DISABLE* = 0x24c'u16 126 | KEY_WPS_BUTTON* = 0x211'u16 127 | KEY_AUDIO* = 0x188'u16 128 | KEY_UNMUTE* = 0x274'u16 129 | KEY_KBD_LAYOUT_NEXT* = 0x248'u16 130 | KEY_PVR* = 0x16e'u16 131 | KEY_KPPLUSMINUS* = 118'u16 132 | KEY_SHOP* = 221'u16 133 | KEY_LEFTSHIFT* = 42'u16 134 | KEY_BASSBOOST* = 209'u16 135 | KEY_BRIGHTNESS_CYCLE* = 243'u16 136 | KEY_FILE* = 144'u16 137 | KEY_TAB* = 15'u16 138 | KEY_ACCESSIBILITY* = 0x24e'u16 139 | KEY_KPLEFTPAREN* = 179'u16 140 | KEY_BRL_DOT1* = 0x1f1'u16 141 | KEY_ROTATE_LOCK_TOGGLE* = 0x231'u16 142 | KEY_ALS_TOGGLE* = 0x230'u16 143 | KEY_EDIT* = 176'u16 144 | KEY_F12* = 88'u16 145 | KEY_MEMO* = 0x18c'u16 146 | KEY_102ND* = 86'u16 147 | KEY_MENU* = 139'u16 148 | KEY_LINEFEED* = 101'u16 149 | KEY_SPACE* = 57'u16 150 | KEY_FULL_SCREEN* = 0x174'u16 151 | KEY_INFO* = 0x166'u16 152 | KEY_WAKEUP* = 143'u16 153 | KEY_3D_MODE* = 0x26f'u16 154 | KEY_BLUE* = 0x191'u16 155 | KEY_HOMEPAGE* = 172'u16 156 | KEY_KPCOMMA* = 121'u16 157 | KEY_SHUFFLE* = 0x19a'u16 158 | KEY_ZOOMRESET* = 0x1a4'u16 159 | KEY_FAVORITES* = 0x16c'u16 160 | KEY_FN_2* = 0x1df'u16 161 | KEY_ISO* = 170'u16 162 | KEY_FN_D* = 0x1e0'u16 163 | KEY_FN_E* = 0x1e1'u16 164 | KEY_FN_F* = 0x1e2'u16 165 | KEY_FN_S* = 0x1e3'u16 166 | KEY_HANJA* = 123'u16 167 | KEY_IMAGES* = 0x1ba'u16 168 | KEY_MACRO_PRESET_CYCLE* = 0x2b2'u16 169 | KEY_AUDIO_DESC* = 0x26e'u16 170 | KEY_MAX* = 0x2ff'u16 171 | KEY_TV2* = 0x17a'u16 172 | KEY_CALENDAR* = 0x18d'u16 173 | KEY_SLOW* = 0x199'u16 174 | KEY_ATTENDANT_ON* = 0x21b'u16 175 | KEY_SAT2* = 0x17e'u16 176 | KEY_F11* = 87'u16 177 | KEY_F13* = 183'u16 178 | KEY_F14* = 184'u16 179 | KEY_F15* = 185'u16 180 | KEY_F16* = 186'u16 181 | KEY_F17* = 187'u16 182 | KEY_F18* = 188'u16 183 | KEY_F19* = 189'u16 184 | KEY_SEMICOLON* = 39'u16 185 | KEY_MACRO22* = 0x2a5'u16 186 | KEY_F20* = 190'u16 187 | KEY_F21* = 191'u16 188 | KEY_F22* = 192'u16 189 | KEY_F23* = 193'u16 190 | KEY_F24* = 194'u16 191 | KEY_AUTOPILOT_ENGAGE_TOGGLE* = 0x27d'u16 192 | KEY_SAVE* = 234'u16 193 | KEY_0* = 11'u16 194 | KEY_2* = 3'u16 195 | KEY_3* = 4'u16 196 | KEY_5* = 6'u16 197 | KEY_6* = 7'u16 198 | KEY_7* = 8'u16 199 | KEY_8* = 9'u16 200 | KEY_9* = 10'u16 201 | KEY_C* = 46'u16 202 | KEY_E* = 18'u16 203 | KEY_F* = 33'u16 204 | KEY_G* = 34'u16 205 | KEY_I* = 23'u16 206 | KEY_J* = 36'u16 207 | KEY_K* = 37'u16 208 | KEY_L* = 38'u16 209 | KEY_N* = 49'u16 210 | KEY_O* = 24'u16 211 | KEY_P* = 25'u16 212 | KEY_Q* = 16'u16 213 | KEY_S* = 31'u16 214 | KEY_T* = 20'u16 215 | KEY_U* = 22'u16 216 | KEY_X* = 45'u16 217 | KEY_Y* = 21'u16 218 | KEY_Z* = 44'u16 219 | KEY_SIDEVU_SONAR* = 0x287'u16 220 | KEY_MHP* = 0x16f'u16 221 | KEY_NUMERIC_POUND* = 0x20b'u16 222 | KEY_NAV_INFO* = 0x288'u16 223 | KEY_BUTTONCONFIG* = 0x240'u16 224 | KEY_EMOJI_PICKER* = 0x249'u16 225 | KEY_F2* = 60'u16 226 | KEY_F5* = 63'u16 227 | KEY_RECORD* = 167'u16 228 | KEY_F8* = 66'u16 229 | KEY_CHANNEL* = 0x16b'u16 230 | KEY_HANGUP_PHONE* = 0x1be'u16 231 | KEY_PAUSE* = 119'u16 232 | KEY_EJECTCD* = 161'u16 233 | KEY_COMPUTER* = 157'u16 234 | KEY_REDO* = 182'u16 235 | KEY_VIDEO* = 0x189'u16 236 | KEY_MP3* = 0x187'u16 237 | KEY_NAV_CHART* = 0x280'u16 238 | KEY_FN* = 0x1d0'u16 239 | KEY_REFRESH_RATE_TOGGLE* = 0x232'u16 240 | KEY_AGAIN* = 129'u16 241 | KEY_1* = 2'u16 242 | KEY_LEFT_UP* = 0x268'u16 243 | KEY_OPTION* = 0x165'u16 244 | KEY_KBDILLUMDOWN* = 229'u16 245 | KEY_MICMUTE* = 248'u16 246 | KEY_COMMA* = 51'u16 247 | KEY_GRAPHICSEDITOR* = 0x1a8'u16 248 | KEY_PHONE* = 169'u16 249 | KEY_A* = 30'u16 250 | KEY_BRIGHTNESS_MENU* = 0x289'u16 251 | KEY_D* = 32'u16 252 | KEY_10CHANNELSUP* = 0x1b8'u16 253 | KEY_RIGHT_UP* = 0x266'u16 254 | KEY_H* = 35'u16 255 | KEY_VOICECOMMAND* = 0x246'u16 256 | KEY_YELLOW* = 0x190'u16 257 | KEY_M* = 50'u16 258 | KEY_LANGUAGE* = 0x170'u16 259 | KEY_XFER* = 147'u16 260 | KEY_LOGOFF* = 0x1b1'u16 261 | KEY_R* = 19'u16 262 | KEY_SOUND* = 213'u16 263 | KEY_V* = 47'u16 264 | KEY_W* = 17'u16 265 | KEY_PRINT* = 210'u16 266 | KEY_COPY* = 133'u16 267 | KEY_TRADITIONAL_SONAR* = 0x285'u16 268 | KEY_FIRST* = 0x194'u16 269 | KEY_CAMERA_ZOOMOUT* = 0x216'u16 270 | KEY_BRL_DOT2* = 0x1f2'u16 271 | KEY_BRL_DOT3* = 0x1f3'u16 272 | KEY_BRL_DOT4* = 0x1f4'u16 273 | KEY_BRL_DOT5* = 0x1f5'u16 274 | KEY_BRL_DOT6* = 0x1f6'u16 275 | KEY_BRL_DOT7* = 0x1f7'u16 276 | KEY_BRL_DOT8* = 0x1f8'u16 277 | KEY_BRL_DOT9* = 0x1f9'u16 278 | KEY_4* = 5'u16 279 | KEY_BRIGHTNESSUP* = 225'u16 280 | KEY_CONTROLPANEL* = 0x243'u16 281 | KEY_CAMERA_ACCESS_TOGGLE* = 0x24d'u16 282 | KEY_CLOSECD* = 160'u16 283 | KEY_YEN* = 124'u16 284 | KEY_REWIND* = 168'u16 285 | KEY_TOUCHPAD_OFF* = 0x214'u16 286 | KEY_HELP* = 138'u16 287 | KEY_STOP_RECORD* = 0x271'u16 288 | KEY_MACRO11* = 0x29a'u16 289 | KEY_MACRO13* = 0x29c'u16 290 | KEY_MACRO17* = 0x2a0'u16 291 | KEY_MACRO18* = 0x2a1'u16 292 | KEY_MACRO19* = 0x2a2'u16 293 | KEY_RFKILL* = 247'u16 294 | KEY_MACRO20* = 0x2a3'u16 295 | KEY_MACRO21* = 0x2a4'u16 296 | KEY_MACRO23* = 0x2a6'u16 297 | KEY_MACRO24* = 0x2a7'u16 298 | KEY_MACRO25* = 0x2a8'u16 299 | KEY_MACRO26* = 0x2a9'u16 300 | KEY_MACRO27* = 0x2aa'u16 301 | KEY_B* = 48'u16 302 | KEY_LEFTCTRL* = 29'u16 303 | KEY_KPRIGHTPAREN* = 180'u16 304 | KEY_KPPLUS* = 78'u16 305 | KEY_ATTENDANT_TOGGLE* = 0x21d'u16 306 | KEY_DELETE* = 111'u16 307 | KEY_KPASTERISK* = 55'u16 308 | KEY_REFRESH* = 173'u16 309 | KEY_RIGHTCTRL* = 97'u16 310 | KEY_PAUSE_RECORD* = 0x272'u16 311 | KEY_ALL_APPLICATIONS* = 204'u16 312 | KEY_SEARCH* = 217'u16 313 | KEY_DIGITS* = 0x19d'u16 314 | KEY_STOP* = 128'u16 315 | KEY_VCR* = 0x17b'u16 316 | KEY_SEND* = 231'u16 317 | KEY_BLUETOOTH* = 237'u16 318 | KEY_ALTERASE* = 222'u16 319 | KEY_CHAT* = 216'u16 320 | KEY_VOLUMEUP* = 115'u16 321 | KEY_BRIGHTNESS_AUTO* = 244'u16 322 | KEY_CAMERA_ZOOMIN* = 0x215'u16 323 | KEY_UNDO* = 131'u16 324 | KEY_TOUCHPAD_ON* = 0x213'u16 325 | KEY_LAST* = 0x195'u16 326 | KEY_ANGLE* = 0x173'u16 327 | KEY_PRESENTATION* = 0x1a9'u16 328 | KEY_CAMERA_DOWN* = 0x218'u16 329 | KEY_REPLY* = 232'u16 330 | KEY_MACRO16* = 0x29f'u16 331 | KEY_CAMERA_ACCESS_ENABLE* = 0x24b'u16 332 | KEY_CALC* = 140'u16 333 | KEY_RIGHT_DOWN* = 0x267'u16 334 | KEY_MACRO_RECORD_START* = 0x2b0'u16 335 | KEY_RESERVED* = 0'u16 336 | KEY_DOLLAR* = 0x1b2'u16 337 | KEY_KBDILLUMUP* = 230'u16 338 | KEY_ROTATE_DISPLAY* = 153'u16 339 | KEY_PREVIOUSSONG* = 165'u16 340 | KEY_NEW* = 181'u16 341 | KEY_SELECTIVE_SCREENSHOT* = 0x27a'u16 342 | KEY_SETUP* = 141'u16 343 | KEY_KBDINPUTASSIST_CANCEL* = 0x265'u16 344 | KEY_VIDEO_NEXT* = 241'u16 345 | EV_KEY* = 0x01'u16 346 | KEY_VOICEMAIL* = 0x1ac'u16 347 | KEY_PLAYCD* = 200'u16 348 | KEY_MEDIA* = 226'u16 349 | KEY_COMPOSE* = 127'u16 350 | KEY_PLAYER* = 0x183'u16 351 | KEY_10CHANNELSDOWN* = 0x1b9'u16 352 | KEY_EMAIL* = 215'u16 353 | KEY_TASKMANAGER* = 0x241'u16 354 | KEY_RIGHTMETA* = 126'u16 355 | KEY_MSDOS* = 151'u16 356 | KEY_CLEARVU_SONAR* = 0x286'u16 357 | KEY_SCREENSAVER* = 0x245'u16 358 | KEY_NEWS* = 0x1ab'u16 359 | KEY_NUMERIC_0* = 0x200'u16 360 | KEY_NUMERIC_1* = 0x201'u16 361 | KEY_NUMERIC_2* = 0x202'u16 362 | KEY_NUMERIC_3* = 0x203'u16 363 | KEY_NUMERIC_4* = 0x204'u16 364 | KEY_NUMERIC_5* = 0x205'u16 365 | KEY_TWEN* = 0x19f'u16 366 | KEY_NUMERIC_7* = 0x207'u16 367 | KEY_NUMERIC_9* = 0x209'u16 368 | KEY_NUMERIC_A* = 0x20c'u16 369 | KEY_NUMERIC_B* = 0x20d'u16 370 | KEY_NUMERIC_C* = 0x20e'u16 371 | KEY_NUMERIC_D* = 0x20f'u16 372 | KEY_SCALE* = 120'u16 373 | KEY_NEXT* = 0x197'u16 374 | KEY_CUT* = 137'u16 375 | KEY_PREVIOUS* = 0x19c'u16 376 | KEY_VIDEO_PREV* = 242'u16 377 | KEY_MUTE* = 113'u16 378 | KEY_FN_RIGHT_SHIFT* = 0x1e5'u16 379 | KEY_TAPE* = 0x180'u16 380 | KEY_PROGRAM* = 0x16a'u16 381 | KEY_DUAL_RANGE_RADAR* = 0x283'u16 382 | KEY_EDITOR* = 0x1a6'u16 383 | KEY_DO_NOT_DISTURB* = 0x24f'u16 384 | KEY_ZOOMOUT* = 0x1a3'u16 385 | KEY_DISPLAY_OFF* = 245'u16 386 | KEY_MESSENGER* = 0x1ae'u16 387 | KEY_WLAN* = 238'u16 388 | KEY_HANGEUL* = 122'u16 389 | KEY_RED* = 0x18e'u16 390 | KEY_PRIVACY_SCREEN_TOGGLE* = 0x279'u16 391 | KEY_KBD_LCD_MENU1* = 0x2b8'u16 392 | KEY_KBD_LCD_MENU2* = 0x2b9'u16 393 | KEY_KBD_LCD_MENU3* = 0x2ba'u16 394 | KEY_WWAN* = 246'u16 395 | KEY_FINANCE* = 219'u16 396 | KEY_BACKSLASH* = 43'u16 397 | KEY_MACRO_PRESET2* = 0x2b4'u16 398 | KEY_MACRO_PRESET3* = 0x2b5'u16 399 | KEY_ONSCREEN_KEYBOARD* = 0x278'u16 400 | KEY_BRIGHTNESSDOWN* = 224'u16 401 | KEY_KPDOT* = 83'u16 402 | KEY_BRIGHTNESS_MAX* = 0x251'u16 403 | KEY_TEEN* = 0x19e'u16 404 | KEY_COFFEE* = 152'u16 405 | KEY_FN_F1* = 0x1d2'u16 406 | KEY_FN_F2* = 0x1d3'u16 407 | KEY_FN_F3* = 0x1d4'u16 408 | KEY_FN_F4* = 0x1d5'u16 409 | KEY_FN_F5* = 0x1d6'u16 410 | KEY_FN_F6* = 0x1d7'u16 411 | KEY_FN_F7* = 0x1d8'u16 412 | KEY_FN_F8* = 0x1d9'u16 413 | KEY_FN_F9* = 0x1da'u16 414 | KEY_PAUSECD* = 201'u16 415 | KEY_VENDOR* = 0x168'u16 416 | KEY_ROOT_MENU* = 0x26a'u16 417 | KEY_LEFT_DOWN* = 0x269'u16 418 | KEY_BRIGHTNESS_MIN* = 0x250'u16 419 | KEY_GAMES* = 0x1a1'u16 420 | KEY_PREVIOUS_ELEMENT* = 0x27c'u16 421 | KEY_AB* = 0x196'u16 422 | KEY_SUBTITLE* = 0x172'u16 423 | KEY_SYSRQ* = 99'u16 424 | KEY_CD* = 0x17f'u16 425 | KEY_CONTEXT_MENU* = 0x1b6'u16 426 | KEY_F1* = 59'u16 427 | KEY_F3* = 61'u16 428 | KEY_F4* = 62'u16 429 | KEY_F6* = 64'u16 430 | KEY_F7* = 65'u16 431 | KEY_F9* = 67'u16 432 | KEY_SCROLLUP* = 177'u16 433 | KEY_CANCEL* = 223'u16 434 | KEY_NUMERIC_11* = 0x26c'u16 435 | KEY_NUMERIC_12* = 0x26d'u16 436 | KEY_SCROLLLOCK* = 70'u16 437 | KEY_RIGHTBRACE* = 27'u16 438 | KEY_HP* = 211'u16 439 | KEY_SINGLE_RANGE_RADAR* = 0x282'u16 440 | KEY_FASTFORWARD* = 208'u16 441 | KEY_UNKNOWN* = 240'u16 442 | KEY_ASPECT_RATIO* = 0x177'u16 443 | KEY_TEXT* = 0x184'u16 444 | KEY_EURO* = 0x1b3'u16 445 | KEY_FN_ESC* = 0x1d1'u16 446 | KEY_INSERT* = 110'u16 447 | KEY_NEXT_ELEMENT* = 0x27b'u16 448 | KEY_CYCLEWINDOWS* = 154'u16 449 | KEY_KATAKANAHIRAGANA* = 93'u16 450 | KEY_FRONT* = 132'u16 451 | KEY_SPELLCHECK* = 0x1b0'u16 452 | KEY_FORWARDMAIL* = 233'u16 453 | KEY_MUHENKAN* = 94'u16 454 | KEY_OK* = 0x160'u16 455 | KEY_CHANNELDOWN* = 0x193'u16 456 | KEY_DEL_EOL* = 0x1c0'u16 457 | KEY_PC* = 0x178'u16 458 | KEY_KPENTER* = 96'u16 459 | KEY_KP0* = 82'u16 460 | KEY_KP1* = 79'u16 461 | KEY_KP2* = 80'u16 462 | KEY_KP3* = 81'u16 463 | KEY_KP4* = 75'u16 464 | KEY_KP6* = 77'u16 465 | KEY_KP7* = 71'u16 466 | KEY_KP8* = 72'u16 467 | KEY_KP9* = 73'u16 468 | KEY_LIGHTS_TOGGLE* = 0x21e'u16 469 | KEY_MACRO10* = 0x299'u16 470 | KEY_BACKSPACE* = 14'u16 471 | KEY_RO* = 89'u16 472 | KEY_ATTENDANT_OFF* = 0x21c'u16 473 | KEY_MACRO14* = 0x29d'u16 474 | KEY_MACRO15* = 0x29e'u16 475 | KEY_POWER2* = 0x164'u16 476 | KEY_JOURNAL* = 0x242'u16 477 | KEY_MINUS* = 12'u16 478 | KEY_VOD* = 0x273'u16 479 | KEY_KPJPCOMMA* = 95'u16 480 | KEY_LEFTBRACE* = 26'u16 481 | KEY_TV* = 0x179'u16 482 | KEY_FRAMEFORWARD* = 0x1b5'u16 483 | KEY_UP* = 103'u16 484 | KEY_SUSPEND* = 205'u16 485 | KEY_TIME* = 0x167'u16 486 | KEY_FIND* = 136'u16 487 | KEY_FN_1* = 0x1de'u16 488 | KEY_STOPCD* = 166'u16 489 | KEY_BREAK* = 0x19b'u16 490 | KEY_DICTATE* = 0x24a'u16 491 | KEY_DOCUMENTS* = 235'u16 492 | KEY_LEFT* = 105'u16 493 | KEY_EJECTCLOSECD* = 162'u16 494 | KEY_GOTO* = 0x162'u16 495 | KEY_EXIT* = 174'u16 496 | KEY_MEDIA_REPEAT* = 0x1b7'u16 497 | KEY_MACRO3* = 0x292'u16 498 | KEY_FN_B* = 0x1e4'u16 499 | KEY_MACRO5* = 0x294'u16 500 | KEY_MACRO7* = 0x296'u16 501 | KEY_SLASH* = 53'u16 502 | KEY_KEYBOARD* = 0x176'u16 503 | KEY_CAMERA_UP* = 0x217'u16 504 | KEY_DEL_LINE* = 0x1c3'u16 505 | KEY_CLOSE* = 206'u16 506 | KEY_MACRO12* = 0x29b'u16 507 | KEY_DOT* = 52'u16 508 | KEY_KBDINPUTASSIST_NEXT* = 0x261'u16 509 | KEY_ASSISTANT* = 0x247'u16 510 | KEY_MACRO28* = 0x2ab'u16 511 | KEY_MACRO29* = 0x2ac'u16 512 | KEY_RADAR_OVERLAY* = 0x284'u16 513 | KEY_BOOKMARKS* = 156'u16 514 | --------------------------------------------------------------------------------