I ended up building a keyboard module that reports when simultaneous keys are pressed. It's built on top of the keypress library.
Every time a key is pressed the keyboard module checks what keys have been pressed in the past 100ms and reports those as a single key event. It is throttled to emit events at most once every 40ms to give it time to collect key combinations. The downside is that single-key inputs now have a 40ms delay. This is a consequence of how input works in the terminal.
EventEmitter = require('events').EventEmitter
keypress = require('keypress')
_ = require('underscore')
# This module allows for reading simultaneous key events in the terminal.
module.exports = class Keyboard extends EventEmitter
_keyTimes: {}
constructor: ->
keypress(process.stdin)
process.stdin.setRawMode(true)
process.stdin.resume()
@_emitKeyStatus = _.throttle(@_emitKeyStatus, 40, leading: false)
process.stdin.on('keypress', (ch, key) => @_processKey(key))
# Batch-emits any keys recently pressed.
_emitKeyStatus: ->
currentTime = @_tuple2time(process.hrtime())
millisecond = 1000000
keys = {}
for own keyName, time of @_keyTimes
if currentTime - time < 100 * millisecond
keys[keyName] = true
@emit('keypress', keys)
_tuple2time: (tuple) ->
(tuple[0] * 1e9) + tuple[1]
_processKey: (key) ->
return unless key
return @emit('quit') if key.ctrl and key.name is 'c'
time = @_tuple2time(process.hrtime())
# Treat ctrl, shift and meta as distinct keys.
@_keyTimes.shift = time if key.shift
@_keyTimes.ctrl = time if key.ctrl
@_keyTimes.meta = time if key.meta
@_keyTimes[key.name] = time
@_emitKeyStatus()