From a10edf116e1638d21be1a687aeb0b9aec3646b1c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 27 Jan 2013 11:00:02 -0600 Subject: [PATCH] add mouse support --- lib/program.js | 231 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/lib/program.js b/lib/program.js index 91c5f1e..0a86ff2 100644 --- a/lib/program.js +++ b/lib/program.js @@ -57,6 +57,10 @@ Program.prototype.listen = function() { self.emit('keypress', ch, key); }); + this.input.on('data', function(data) { + self.emit('data', data); + }); + readline.emitKeypressEvents(this.input); this.input.resume(); @@ -72,6 +76,162 @@ Program.prototype.listen = function() { // self.x = cursor.x; // self.y = cursor.y; // }); + + this.on('newListener', function fn(type) { + if (type === 'mouse') { + self.removeListener('newListener', fn); + self.bindMouse(); + } + }); + + this.setMouse({ normalMouse: true }); + var i = 5; + //this.on('keypress', function(ch, key) { + // if (key.name === 'mouse') console.log(key); + // if (!--i) this.setMouse({ normalMouse: false }); + //}); + this.on('mouse', function(data) { + console.log(data); + if (!--i) this.setMouse({ normalMouse: false }); + }); +}; + +// XTerm mouse events +// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking +// To better understand these +// the xterm code is very helpful: +// Relevant files: +// button.c, charproc.c, misc.c +// Relevant functions in xterm/button.c: +// BtnCode, EmitButtonCode, EditorButton, SendMousePosition +Program.prototype.bindMouse = function() { + this.on('data', this._bindMouse.bind(this)); + this.bindMouse = function() {}; +}; + +Program.prototype._bindMouse = function(s) { + // send a mouse event: + // regular/utf8: ^[[M Cb Cx Cy + // urxvt: ^[[ Cb ; Cx ; Cy M + // sgr: ^[[ Cb ; Cx ; Cy M/m + // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r + // locator: CSI P e ; P b ; P r ; P c ; P p & w + // motion example of a left click: + // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< + // mouseup, mousedown, mousewheel + // left click: ^[[M 3<^[[M#3< + // mousewheel up: ^[[M`3> + var self = this; + + // FROM: node/lib/readline.js + var ch + , key = { + name: undefined, + ctrl: false, + meta: false, + shift: false + } + , parts; + + if (Buffer.isBuffer(s)) { + if (s[0] > 127 && s[1] === undefined) { + s[0] -= 128; + s = '\x1b' + s.toString('utf-8'); + } else { + s = s.toString('utf-8'); + } + } + + // XTerm / X10 + if (parts = /^\x1b\[M([\x20-\x7f\x00]{3})/.exec(s)) { + var b = parts[1].charCodeAt(0) + , x = parts[1].charCodeAt(1) + , y = parts[1].charCodeAt(2); + + key.name = 'mouse'; + key.type = 'X10'; + key.button = b; + key.x = x - 32; + key.y = y - 32; + + self.emit('keypress', null, key); + self.emit('mouse', key); + return; + } + + // URxvt + if (parts = /^\x1b\[((?:\d+;){3})M/.exec(s)) { + var parts = parts[1].split(';') + , b = +parts[0] + , x = +parts[1] + , y = +parts[2]; + + key.name = 'mouse'; + key.type = 'urxvt'; + key.button = b; + key.x = x; + key.y = y; + + self.emit('keypress', null, key); + self.emit('mouse', key); + return; + } + + // SGR + if (parts = /^\x1b\[<((?:\d+;){3})([mM])/.exec(s)) { + var down = parts[2] === 'm' + , parts = parts[1].split(';') + , b = +parts[0] + , x = +parts[1] + , y = +parts[2]; + + key.name = 'mouse'; + key.type = 'sgr'; + key.button = b; + key.x = x; + key.y = y; + key.down = down; + + self.emit('keypress', null, key); + self.emit('mouse', key); + return; + } + + // DEC + if (parts = /^\x1b\[<((?:\d+;){4})&w/.exec(s)) { + var parts = parts[1].split(';') + , b = +parts[0] + , x = +parts[1] + , y = +parts[2] + , page = +parts[3]; + + key.name = 'mouse'; + key.type = 'dec'; + key.button = b; + key.x = x; + key.y = y; + + self.emit('keypress', null, key); + self.emit('mouse', key); + return; + } + + // vt300 + if (parts = /^\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.exec(s)) { + var b = +parts[1] + , x = +parts[2] + , y = +parts[3]; + + key.name = 'mouse'; + key.type = 'vt300'; + key.button = b; + key.x = x; + key.y = y; + + self.emit('keypress', null, key); + self.emit('mouse', key); + return; + } }; Program.prototype.receive = function(text, callback) { @@ -1082,6 +1242,77 @@ Program.prototype.normalBuffer = function() { return this.resetMode('?1049'); }; +// Set Mouse +Program.prototype.setMouse = function(opt) { + // Ps = 9 -> Send Mouse X & Y on button press. See the sec- + // tion Mouse Tracking. + // Ps = 9 -> Don't send Mouse X & Y on button press. + // x10 mouse + if (opt.x10Mouse != null) { + if (opt.x10Mouse) this.setMode('?9'); + else this.resetMode('?9'); + } + + // Ps = 1 0 0 0 -> Send Mouse X & Y on button press and + // release. See the section Mouse Tracking. + // Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and + // release. See the section Mouse Tracking. + // vt200 mouse + if (opt.vt200Mouse != null) { + if (opt.vt200Mouse) this.setMode('?1000'); + else this.resetMode('?1000'); + } + + // Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. + // Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. + if (opt.hiliteTracking != null) { + if (opt.hiliteTracking) this.setMode('?1001'); + else this.resetMode('?1001'); + } + + // Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. + // Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. + // button event mouse + if (opt.normalMouse != null) { + if (opt.normalMouse) this.setMode('?1002'); + else this.resetMode('?1002'); + } + + // Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. + // Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. + // any event mouse + if (opt.normalMouse != null) { + if (opt.normalMouse) this.setMode('?1003'); + else this.resetMode('?1003'); + } + + // Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. + // Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. + if (opt.sendFocus != null) { + if (opt.sendFocus) this.setMode('?1004'); + else this.resetMode('?1004'); + } + + // Ps = 1 0 0 5 -> Enable Extended Mouse Mode. + // Ps = 1 0 0 5 -> Disable Extended Mouse Mode. + if (opt.utfMouse != null) { + if (opt.utfMouse) this.setMode('?1005'); + else this.resetMode('?1005'); + } + + // sgr mouse + if (opt.sgrMouse != null) { + if (opt.sgrMouse) this.setMode('?1006'); + else this.resetMode('?1006'); + } + + // urxvt mouse + if (opt.urxvtMouse != null) { + if (opt.urxvtMouse) this.setMode('?1015'); + else this.resetMode('?1015'); + } +}; + // CSI Ps ; Ps r // Set Scrolling Region [top;bottom] (default = full size of win- // dow) (DECSTBM).