Skip to content

Instantly share code, notes, and snippets.

@reconbot
Last active August 24, 2019 01:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reconbot/844f9ab4dd8d2b950385d66badce49b9 to your computer and use it in GitHub Desktop.
Save reconbot/844f9ab4dd8d2b950385d66badce49b9 to your computer and use it in GitHub Desktop.
This parses the ms serial mouse wire protol into randomly named events.
const { Transform } = require('stream')
const debug = require('debug')('serialport/mouse-parser')
const StartByteMarkerByte = 0b01000000
const LeftMouseButtonByte = 0b00100000
const RightMouseButtonByte = 0b00010000
const isStart = byte => byte & StartByteMarkerByte
const DEFAULT_STATE = Object.freeze({
leftMouseButton: false,
rightMouseButton: false,
})
/**
* A transform stream that does something pretty cool.
* @extends Transform
* @param {Object} options parser options object
* @example
// To use the `MouseParser` stream:
const MouseParser = require('mouse-parser')
const mouseParser = new MouseParser()
mouseParser.on('data', console.log)
mouseParser.write(Buffer.from([1,2,3]))
*/
class MouseParser extends Transform {
constructor(options = {}) {
super({
readableObjectMode: true,
...options,
})
this.buffer = []
this._state = DEFAULT_STATE
}
get state() {
return this._state
}
_transform(chunk, encoding, cb) {
const data = [...this.buffer, ...Array.from(chunk)]
debug('processing', data)
let packet = []
for (const byte of data) {
if (isStart(byte) && packet.length > 0) {
this._emitEvent(packet)
packet = []
}
packet.push(byte)
if (packet.length === 3) {
this._emitEvent(packet)
packet = []
}
}
debug('buffering', packet)
this.buffer = packet
cb()
}
_flush(cb) {
this._emitEvent(this.buffer)
this.buffer = []
cb()
}
/**
* Converts a 3 byte packet into events
* @param {[number, number number]} packet]
*
* Data layout
D7 D6 D5 D4 D3 D2 D1 D0
Byte 1 X 1 LB RB Y7 Y6 X7 X6
Byte 2 X 0 X5 X4 X3 X2 X1 X0
Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
LB is the state of the left button (1 means down)
RB is the state of the right button (1 means down)
X7-X0 movement in X direction since last packet (signed byte)
Y7-Y0 movement in Y direction since last packet (signed byte)
*
*/
_emitEvent(packet) {
debug('emitting packet', packet)
if (packet.length !== 3) {
debug(`bad packet length ${packet}`)
return
}
const leftMouseButton = Boolean(packet[0] & LeftMouseButtonByte)
const rightMouseButton = Boolean(packet[0] & RightMouseButtonByte)
const events = []
if (this._state.leftMouseButton !== leftMouseButton) {
debug('left mouse click', leftMouseButton)
events.push({ type: 'onClick' })
}
if (this._state.rightMouseButton !== rightMouseButton) {
debug('right mouse click', rightMouseButton)
events.push({ type: 'onRightClick' })
}
if (events.length) {
this._state = Object.freeze({ leftMouseButton, rightMouseButton })
debug('updating state', this._state)
}
// xy events
const xBuffer = Buffer.from([((packet[0] & 0b0000011) << 6) | (packet[1] & 0b00111111)])
const x = xBuffer.readInt8()
const yBuffer = Buffer.from([((packet[0] & 0b0001100) << 4) | (packet[2] & 0b00111111)])
const y = yBuffer.readInt8()
if (x || y) {
events.push({ event: 'move', x, y })
}
for (const event of events) {
this.push({ ...event, state: this._state })
}
}
}
module.exports = { MouseParser }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment