gpmclient.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * gpmclient.js - support the gpm mouse protocol
  3. * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
  4. * https://github.com/chjj/blessed
  5. */
  6. var net = require('net');
  7. var fs = require('fs');
  8. var EventEmitter = require('events').EventEmitter;
  9. var GPM_USE_MAGIC = false;
  10. var GPM_MOVE = 1
  11. , GPM_DRAG = 2
  12. , GPM_DOWN = 4
  13. , GPM_UP = 8;
  14. var GPM_DOUBLE = 32
  15. , GPM_MFLAG = 128;
  16. var GPM_REQ_NOPASTE = 3
  17. , GPM_HARD = 256;
  18. var GPM_MAGIC = 0x47706D4C;
  19. var GPM_SOCKET = '/dev/gpmctl';
  20. // typedef struct Gpm_Connect {
  21. // unsigned short eventMask, defaultMask;
  22. // unsigned short minMod, maxMod;
  23. // int pid;
  24. // int vc;
  25. // } Gpm_Connect;
  26. function send_config(socket, Gpm_Connect, callback) {
  27. var buffer;
  28. if (GPM_USE_MAGIC) {
  29. buffer = new Buffer(20);
  30. buffer.writeUInt32LE(GPM_MAGIC, 0);
  31. buffer.writeUInt16LE(Gpm_Connect.eventMask, 4);
  32. buffer.writeUInt16LE(Gpm_Connect.defaultMask, 6);
  33. buffer.writeUInt16LE(Gpm_Connect.minMod, 8);
  34. buffer.writeUInt16LE(Gpm_Connect.maxMod, 10);
  35. buffer.writeInt16LE(process.pid, 12);
  36. buffer.writeInt16LE(Gpm_Connect.vc, 16);
  37. } else {
  38. buffer = new Buffer(16);
  39. buffer.writeUInt16LE(Gpm_Connect.eventMask, 0);
  40. buffer.writeUInt16LE(Gpm_Connect.defaultMask, 2);
  41. buffer.writeUInt16LE(Gpm_Connect.minMod, 4);
  42. buffer.writeUInt16LE(Gpm_Connect.maxMod, 6);
  43. buffer.writeInt16LE(Gpm_Connect.pid, 8);
  44. buffer.writeInt16LE(Gpm_Connect.vc, 12);
  45. }
  46. socket.write(buffer, function() {
  47. if (callback) callback();
  48. });
  49. }
  50. // typedef struct Gpm_Event {
  51. // unsigned char buttons, modifiers; // try to be a multiple of 4
  52. // unsigned short vc;
  53. // short dx, dy, x, y; // displacement x,y for this event, and absolute x,y
  54. // enum Gpm_Etype type;
  55. // // clicks e.g. double click are determined by time-based processing
  56. // int clicks;
  57. // enum Gpm_Margin margin;
  58. // // wdx/y: displacement of wheels in this event. Absolute values are not
  59. // // required, because wheel movement is typically used for scrolling
  60. // // or selecting fields, not for cursor positioning. The application
  61. // // can determine when the end of file or form is reached, and not
  62. // // go any further.
  63. // // A single mouse will use wdy, "vertical scroll" wheel.
  64. // short wdx, wdy;
  65. // } Gpm_Event;
  66. function parseEvent(raw) {
  67. var evnt = {};
  68. evnt.buttons = raw[0];
  69. evnt.modifiers = raw[1];
  70. evnt.vc = raw.readUInt16LE(2);
  71. evnt.dx = raw.readInt16LE(4);
  72. evnt.dy = raw.readInt16LE(6);
  73. evnt.x = raw.readInt16LE(8);
  74. evnt.y = raw.readInt16LE(10);
  75. evnt.type = raw.readInt16LE(12);
  76. evnt.clicks = raw.readInt32LE(16);
  77. evnt.margin = raw.readInt32LE(20);
  78. evnt.wdx = raw.readInt16LE(24);
  79. evnt.wdy = raw.readInt16LE(26);
  80. return evnt;
  81. }
  82. function GpmClient(options) {
  83. if (!(this instanceof GpmClient)) {
  84. return new GpmClient(options);
  85. }
  86. EventEmitter.call(this);
  87. var pid = process.pid;
  88. // check tty for /dev/tty[n]
  89. var path;
  90. try {
  91. path = fs.readlinkSync('/proc/' + pid + '/fd/0');
  92. } catch (e) {
  93. ;
  94. }
  95. var tty = /tty[0-9]+$/.exec(path);
  96. if (tty === null) {
  97. // TODO: should also check for /dev/input/..
  98. }
  99. var vc;
  100. if (tty) {
  101. tty = tty[0];
  102. vc = +/[0-9]+$/.exec(tty)[0];
  103. }
  104. var self = this;
  105. if (tty) {
  106. fs.stat(GPM_SOCKET, function(err, stat) {
  107. if (err || !stat.isSocket()) {
  108. return;
  109. }
  110. var conf = {
  111. eventMask: 0xffff,
  112. defaultMask: GPM_MOVE | GPM_HARD,
  113. minMod: 0,
  114. maxMod: 0xffff,
  115. pid: pid,
  116. vc: vc
  117. };
  118. var gpm = net.createConnection(GPM_SOCKET);
  119. this.gpm = gpm;
  120. gpm.on('connect', function() {
  121. send_config(gpm, conf, function() {
  122. conf.pid = 0;
  123. conf.vc = GPM_REQ_NOPASTE;
  124. //send_config(gpm, conf);
  125. });
  126. });
  127. gpm.on('data', function(packet) {
  128. var evnt = parseEvent(packet);
  129. switch (evnt.type & 15) {
  130. case GPM_MOVE:
  131. if (evnt.dx || evnt.dy) {
  132. self.emit('move', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
  133. }
  134. if (evnt.wdx || evnt.wdy) {
  135. self.emit('mousewheel',
  136. evnt.buttons, evnt.modifiers,
  137. evnt.x, evnt.y, evnt.wdx, evnt.wdy);
  138. }
  139. break;
  140. case GPM_DRAG:
  141. if (evnt.dx || evnt.dy) {
  142. self.emit('drag', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
  143. }
  144. if (evnt.wdx || evnt.wdy) {
  145. self.emit('mousewheel',
  146. evnt.buttons, evnt.modifiers,
  147. evnt.x, evnt.y, evnt.wdx, evnt.wdy);
  148. }
  149. break;
  150. case GPM_DOWN:
  151. self.emit('btndown', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
  152. if (evnt.type & GPM_DOUBLE) {
  153. self.emit('dblclick', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
  154. }
  155. break;
  156. case GPM_UP:
  157. self.emit('btnup', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
  158. if (!(evnt.type & GPM_MFLAG)) {
  159. self.emit('click', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
  160. }
  161. break;
  162. }
  163. });
  164. gpm.on('error', function() {
  165. self.stop();
  166. });
  167. });
  168. }
  169. }
  170. GpmClient.prototype.__proto__ = EventEmitter.prototype;
  171. GpmClient.prototype.stop = function() {
  172. if (this.gpm) {
  173. this.gpm.end();
  174. }
  175. delete this.gpm;
  176. };
  177. GpmClient.prototype.ButtonName = function(btn) {
  178. if (btn & 4) return 'left';
  179. if (btn & 2) return 'middle';
  180. if (btn & 1) return 'right';
  181. return '';
  182. };
  183. GpmClient.prototype.hasShiftKey = function(mod) {
  184. return (mod & 1) ? true : false;
  185. };
  186. GpmClient.prototype.hasCtrlKey = function(mod) {
  187. return (mod & 4) ? true : false;
  188. };
  189. GpmClient.prototype.hasMetaKey = function(mod) {
  190. return (mod & 8) ? true : false;
  191. };
  192. module.exports = GpmClient;