diff --git a/lib/gpmclient.js b/lib/gpmclient.js new file mode 100644 index 0000000..43903e2 --- /dev/null +++ b/lib/gpmclient.js @@ -0,0 +1,189 @@ +var net=require('net'); +var fs=require('fs'); +var EventEmitter=require('events').EventEmitter; +var util = require('util') + +var GPM_USE_MAGIC=false; + +var GPM_MOVE=1, GPM_DRAG=2, GPM_DOWN=4, GPM_UP=8; +var GPM_DOUBLE=32,GPM_MFLAG=128; + +var GPM_REQ_NOPASTE=3, GPM_HARD=256; + +var GPM_MAGIC=0x47706D4C; +var GPM_SOCKET="/dev/gpmctl"; + + +/* + typedef struct Gpm_Connect { + unsigned short eventMask, defaultMask; + unsigned short minMod, maxMod; + int pid; + int vc; + } Gpm_Connect; +*/ +function send_config(sock, Gpm_Connect, cb) { + if (GPM_USE_MAGIC) { + var buffer=new Buffer(20); + buffer.writeUInt32LE(GPM_MAGIC, 0); + buffer.writeUInt16LE(Gpm_Connect.eventMask, 4); + buffer.writeUInt16LE(Gpm_Connect.defaultMask, 6); + buffer.writeUInt16LE(Gpm_Connect.minMod, 8); + buffer.writeUInt16LE(Gpm_Connect.maxMod, 10); + buffer.writeInt16LE(process.pid, 12); + buffer.writeInt16LE(Gpm_Connect.vc, 16); + } else { + var buffer=new Buffer(16); + buffer.writeUInt16LE(Gpm_Connect.eventMask, 0); + buffer.writeUInt16LE(Gpm_Connect.defaultMask, 2); + buffer.writeUInt16LE(Gpm_Connect.minMod, 4); + buffer.writeUInt16LE(Gpm_Connect.maxMod, 6); + buffer.writeInt16LE(Gpm_Connect.pid, 8); + buffer.writeInt16LE(Gpm_Connect.vc, 12); + } + sock.write(buffer, function () { + if (cb) cb(); + }); +} + +/* + typedef struct Gpm_Event { + unsigned char buttons, modifiers; // try to be a multiple of 4 + unsigned short vc; + short dx, dy, x, y; // displacement x,y for this event, and absolute x,y + enum Gpm_Etype type; + // clicks e.g. double click are determined by time-based processing + int clicks; + enum Gpm_Margin margin; + // wdx/y: displacement of wheels in this event. Absolute values are not + // required, because wheel movement is typically used for scrolling + // or selecting fields, not for cursor positioning. The application + // can determine when the end of file or form is reached, and not + // go any further. + // A single mouse will use wdy, "vertical scroll" wheel. + short wdx, wdy; + } Gpm_Event; + */ +function parseEvent(raw) { + var evnt={}; + evnt.buttons=raw[0]; + evnt.modifiers=raw[1]; + evnt.vc=raw.readUInt16LE(2); + evnt.dx=raw.readInt16LE(4); + evnt.dy=raw.readInt16LE(6); + evnt.x=raw.readInt16LE(8); + evnt.y=raw.readInt16LE(10); + evnt.type=raw.readInt16LE(12); + evnt.clicks=raw.readInt32LE(16); + evnt.margin=raw.readInt32LE(20); + evnt.wdx=raw.readInt16LE(24); + evnt.wdy=raw.readInt16LE(26); + return evnt; +} + +function GpmClient(options) { + if (!(this instanceof GpmClient)) { + return new GpmClient(options); + } + + EventEmitter.call(this); + + var pid=process.pid; + + // check tty for /dev/tty[n] + var tty=/tty[0-9]+$/.exec(fs.readlinkSync('/proc/'+pid+'/fd/0')); + if (tty === null) { + //TODO: should also check for /dev/input/.. + } + + var vc; + if (tty) { + var tty=tty[0]; + vc=/[0-9]+$/.exec(tty)[0]; + } + + if (tty) { + + var conf = { + eventMask: 0xffff, + defaultMask: GPM_MOVE|GPM_HARD, + minMod: 0, + maxMod: 0xffff, + pid: pid, + vc: vc + }; + + var gpm=net.createConnection(GPM_SOCKET); + this.gpm=gpm; + + gpm.on('connect', function() { + send_config(gpm, conf, function() { + conf.pid=0; + conf.vc=GPM_REQ_NOPASTE; + //send_config(gpm, conf); + }); + }); + + var self=this; + gpm.on('data', function(packet) { + var evnt=parseEvent(packet); + switch(evnt.type & 15) { + case GPM_MOVE: + if (evnt.dx || evnt.dy) self.emit('move', evnt.buttons, evnt.modifiers, evnt.x, evnt.y) + if (evnt.wdx || evnt.wdy) self.emit('mousewheel', evnt.buttons, evnt.modifiers, evnt.wdx, evnt.wdy) + break; + case GPM_DRAG: + if (evnt.dx || evnt.dy) self.emit('drag', evnt.buttons, evnt.modifiers, evnt.x, evnt.y) + if (evnt.wdx || evnt.wdy) self.emit('mousewheel', evnt.buttons, evnt.modifiers, evnt.wdx, evnt.wdy) + break; + case GPM_DOWN: + self.emit('btndown', evnt.buttons, evnt.modifiers, evnt.x, evnt.y) + if (evnt.type & GPM_DOUBLE) { + self.emit('dblclick', evnt.buttons, evnt.modifiers, evnt.x, evnt.y) + } + break; + case GPM_UP: + self.emit('btnup', evnt.buttons, evnt.modifiers, evnt.x, evnt.y) + if (!(evnt.type & GPM_MFLAG)) { + self.emit('click', evnt.buttons, evnt.modifiers, evnt.x, evnt.y) + } + break; + } + }) + gpm.on('error', function(err) { + console.log('GPM ERROR', err); + self.stop(); + }); + } + +} + + +GpmClient.prototype=new EventEmitter(); + +GpmClient.prototype.stop=function() { + if (this.gpm) this.gpm.end(); + delete this.gpm; +} + +GpmClient.prototype.ButtonName = function(btn) { + if (btn & 4) return 'left'; + if (btn & 2) return 'middle'; + if (btn & 1) return 'right'; + return '' +} + +GpmClient.prototype.hasShiftKey = function(mod) { + return (mod & 1) ? true:false; +} + +GpmClient.prototype.hasCtrlKey = function(mod) { + return (mod & 4) ? true:false; +} + +GpmClient.prototype.hasMetaKey = function(mod) { + return (mod & 8) ? true:false; +} + + +module.exports=GpmClient; diff --git a/lib/program.js b/lib/program.js index 9f2f709..2960d89 100644 --- a/lib/program.js +++ b/lib/program.js @@ -614,6 +614,97 @@ Program.prototype._bindMouse = function(s) { } }; +/* gpm support for linux vc */ +Program.prototype.enableGpm = function() { + var gpmclient=require('./gpmclient') + this.gpm=gpmclient(); + var self=this; + this.gpm.on('btndown', function(btn,modifier, x, y) { + x--, y--; + var key={ + name: 'mouse', type: 'GPM', + action: 'mousedown', + button: self.gpm.ButtonName(btn), + raw: [btn,modifier, x, y], + x: x, y: y, + shift: self.gpm.hasShiftKey(modifier), + meta: self.gpm.hasMetaKey(modifier), + ctrl: self.gpm.hasCtrlKey(modifier) + }; + self.emit('keypress', null, key); + self.emit('mouse', key); + }); + this.gpm.on('btnup', function(btn,modifier, x, y) { + x--, y--; + var key={ + name: 'mouse', type: 'GPM', + action: 'mouseup', + button: self.gpm.ButtonName(btn), + raw: [btn,modifier, x, y], + x: x, y: y, + shift: self.gpm.hasShiftKey(modifier), + meta: self.gpm.hasMetaKey(modifier), + ctrl: self.gpm.hasCtrlKey(modifier) + }; + self.emit('keypress', null, key); + self.emit('mouse', key); + }); + this.gpm.on('move', function(btn,modifier, x, y) { + x--, y--; + var key={ + name: 'mouse', type: 'GPM', + action: 'mousemove', + button: self.gpm.ButtonName(btn), + raw: [btn,modifier, x, y], + x: x, y: y, + shift: self.gpm.hasShiftKey(modifier), + meta: self.gpm.hasMetaKey(modifier), + ctrl: self.gpm.hasCtrlKey(modifier) + }; + self.emit('keypress', null, key); + self.emit('mouse', key); + }); + this.gpm.on('drag', function(btn,modifier, x, y) { + x--, y--; + var key={ + name: 'mouse', type: 'GPM', + action: 'mousemove', + button: self.gpm.ButtonName(btn), + raw: [btn,modifier, x, y], + x: x, y: y, + shift: self.gpm.hasShiftKey(modifier), + meta: self.gpm.hasMetaKey(modifier), + ctrl: self.gpm.hasCtrlKey(modifier) + }; + self.emit('keypress', null, key); + self.emit('mouse', key); + }); + this.gpm.on('nousewheel', function(btn,modifier, dx, dy) { + x--, y--; + var key={ + name: 'mouse', type: 'GPM', + action: dy>0? 'wheelup':'wheeldown', + button: self.gpm.ButtonName(btn), + raw: [btn,modifier, x, y], + x: x, y: y, + shift: self.gpm.hasShiftKey(modifier), + meta: self.gpm.hasMetaKey(modifier), + ctrl: self.gpm.hasCtrlKey(modifier) + }; + + self.emit('keypress', null, key); + self.emit('mouse', key); + }); +}; + +Program.prototype.disableGpm = function() { + if (this.gpm) { + this.gpm.stop(); + delete this.gpm; + } +}; + + // All possible responses from the terminal Program.prototype.bindResponse = function() { if (this._boundResponse) return; @@ -2724,6 +2815,7 @@ Program.prototype.enableMouse = function() { } if (this.term('linux')) { + this.enableGpm(); return this.setMouse({ vt200Mouse: true }, true); @@ -2749,6 +2841,7 @@ Program.prototype.disableMouse = function() { obj[key] = false; }); + this.disableGpm(); return this.setMouse(obj, false); };