├── History.md ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test.js /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.1 / 2012-07-21 3 | ================== 4 | 5 | * v0.1.1 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 CoderPuppy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Term Mouse 2 | ### A mouse reporting interface 3 | 4 | Originally based on [TooTallNate's gist](https://gist.github.com/1702813), rewritten to use all the mouse reporting modes. 5 | 6 | ## Example 7 | ```javascript 8 | var mouse = require('term-mouse')(); 9 | 10 | mouse 11 | .start() 12 | .on('click', function(e) { 13 | console.log('you clicked %d,%d with the %s mouse button', e.x, e.y, e.button /* 'left', 'middle' or 'right' */); 14 | }) 15 | .on('scroll', function(e) { 16 | console.log('you scrolled %s', e.button /* 'up' or 'down' */); 17 | }); 18 | ``` 19 | 20 | ## API 21 | 22 | ```javascript 23 | var mouse = require('term-mouse')(options); 24 | ``` 25 | 26 | **Options**: 27 | - `input` - the stream to read events from, defaults to `process.stdin` 28 | - `output` - the stream to write control codes to, defaults to `process.stdout` 29 | - `utf8` - whether to use UTF-8 (1005) reporting mode, this could break things if your terminal only supports the standard reporting mode or if you're using a non-UTF-8 locale 30 | 31 | **Events**: 32 | - `event` - `move`, `buttons` and `scroll` 33 | - `move` 34 | - `buttons` - when a button is pressed or released 35 | - `scroll` 36 | - `up` - when a button is pressed 37 | - `down` - when a button is released 38 | - `click` - `up` after `down`, here there are two event objects passed: the first is from when the button was pressed, the second is from the button being released 39 | 40 | All of these include an event object: 41 | - `shift` - whether the shift key is down 42 | - `meta` - whether the meta (alt) key is down 43 | - `ctrl` - whether the control key is down 44 | - `name` - `'scroll'`, `'move'` or `'buttons'` 45 | - `button` - `'left'`, `'middle'`, `'right'` or `'none'` if the button is released 46 | - `btnNum` - the button that was pressed / released, if it's not using xterm's ASCII reporting mode then it'll be `null` when the button is released 47 | - `down` - if the button is down 48 | - `x` - the x coordinate of the cursor 49 | - `y` - the y coordinate of the cursor 50 | - `sequence` - the string that caused this event (if it's using the standard reporting mode, this could be incorrect because the real sequence is invalid UTF-8) 51 | - `buf` - the sequence that caused this event as a buffer 52 | 53 | ## Useful References 54 | I looked at http://invisible-island.net/xterm/ctlseqs/ctlseqs.html and https://www.systutorials.com/docs/linux/man/7-urxvt/ while making this. 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | var Stream = require('stream'); 4 | 5 | var buttonNames = ['left', 'middle', 'right', 'none']; 6 | function decodeBtn(v) { 7 | var e = {} 8 | e.shift = !!(v & 4); 9 | e.meta = !!(v & 8); 10 | e.control = !!(v & 16); 11 | if(v & 64) { 12 | e.name = 'scroll'; 13 | e.button = v & 1 ? 'down' : 'up'; 14 | } else { 15 | e.name = v & 32 ? 'move' : 'buttons'; 16 | e.button = buttonNames[v & 3]; 17 | e.btnNum = (v & 3) == 3 ? null : (v & 3) + 1; 18 | e.down = (v & 3) != 3; 19 | } 20 | return e; 21 | } 22 | 23 | var Mouse = module.exports = (function() { 24 | function Mouse(options) { 25 | if(!(this instanceof Mouse)) return new Mouse(options); 26 | 27 | this.options = options || {}; 28 | 29 | this.input = this.options.input || process.stdin; 30 | this.output = this.options.output || process.stdout; 31 | this.utf8 = !!this.options.utf8; 32 | 33 | process.on('exit', function() { 34 | this.stop(); 35 | }.bind(this)); 36 | 37 | this._onData = this._onData.bind(this); 38 | 39 | // Support easier events such as up, down and click. 40 | this.on('buttons', function(e) { 41 | if(e.button == 'none') { 42 | this.emit('up', e); 43 | 44 | if(this._down) { 45 | this.emit('click', this._down, e); 46 | } 47 | this._down = false; 48 | } else { 49 | this.emit('down', e); 50 | this._down = e; 51 | } 52 | }); 53 | } 54 | util.inherits(Mouse, events.EventEmitter); 55 | 56 | return (function() { 57 | var constructor = this, 58 | prototype = this.prototype; 59 | 60 | (function() { 61 | this.start = function start() { 62 | this.input.on('data', this._onData); 63 | this.input.resume(); 64 | if(this.input.setRawMode) 65 | this.input.setRawMode(true) 66 | else 67 | require('tty').setRawMode(true) 68 | if(this.utf8) this.output.write('\x1b[?1005h'); // UTF 8 69 | this.output.write('\x1b[?1015h'); // urxvt ASCII 70 | this.output.write('\x1b[?1006h'); // xterm ASCII 71 | this.output.write('\x1b[?1003h'); // up, down, drag, move 72 | 73 | return this; 74 | }; 75 | 76 | this._onData = function _onData(d) { 77 | this.emit('data', d); 78 | var other = []; 79 | sequences: for(var i = 0; i < d.length; i++) { 80 | var b = d.readUInt8(i); 81 | if(b == 0x1b && i < d.length - 1 && d.readUInt8(i + 1) == 0x5b) { // ^[[ 82 | 83 | if(other.length > 0) { 84 | this.emit('other', new Buffer(other)); 85 | other = []; 86 | } 87 | i += 2; 88 | var b = d.readUInt8(i); 89 | if(b == 0x4d /* M */) { // standard or UTF-8 90 | i++; // skip the M 91 | var e; 92 | if(this.utf8) { 93 | var s = d.toString('utf8', i); 94 | e = decodeBtn(s.charCodeAt(0) - 32); 95 | e.x = s.charCodeAt(1) - 32; 96 | e.y = s.charCodeAt(2) - 32; 97 | e.sequence = '\x1b[M' + s; 98 | var len = new Buffer(s.substr(0, 3), 'utf8').length; 99 | e.buf = d.slice(i - 3, i + len + 1); 100 | i += len - 1; 101 | } else { 102 | var s = d.slice(i - 3, i + 3); 103 | e = decodeBtn(d.readUInt8(i++)); 104 | e.x = d.readUInt8(i++) - 32; 105 | e.y = d.readUInt8(i) - 32; 106 | e.sequence = s.toString('utf8'); 107 | e.buf = s; 108 | } 109 | this.emit('event', e); 110 | this.emit(e.name, e); 111 | } else if(b == 0x3c /* < */) { // xterm ASCII 112 | var j = i; 113 | var data = []; 114 | while(true) { 115 | var v = d.readUInt8(i); 116 | data.push(v); 117 | if(i >= d.length) { 118 | throw new Error('bad'); 119 | } 120 | if(v == 0x4d /* M */ || v == 0x6d /* m */) { 121 | break; 122 | } 123 | i++; 124 | } 125 | var down = data[data.length - 1] == 0x4d /* M */; 126 | data = new Buffer(data).toString('ascii'); 127 | data = data.substring(1, data.length - 1).split(';').map(function(v) { 128 | return parseInt(v) 129 | }); 130 | var e = decodeBtn(data[0]); 131 | if(!down) { 132 | e.button = 'none'; 133 | e.down = false; 134 | } 135 | e.x = data[1]; 136 | e.y = data[2]; 137 | e.sequence = '\x1b[<' + data.join(';') + (down ? 'M' : 'm'); 138 | e.buf = d.slice(j - 2, i + 1); 139 | this.emit('event', e); 140 | this.emit(e.name, e); 141 | } else if(b >= 0x30 /* 0 */ && b <= 0x39 /* 9 */) { // urxvt ASCII 142 | var j = i; 143 | var data = []; 144 | while(true) { 145 | var v = d.readUInt8(i); 146 | data.push(v); 147 | if(i >= d.length) { 148 | throw new Error('bad'); 149 | } 150 | if(v == 0x4d /* M */) { 151 | break; 152 | } 153 | if(!((v >= 0x30 && v <= 0x39) || v == 0x3b)) { // 0-9; 154 | other = other.concat(data); 155 | continue sequences; 156 | } 157 | i++; 158 | } 159 | data = new Buffer(data).toString('ascii'); 160 | data = data.substring(0, data.length - 1).split(';').map(function(v) { 161 | return parseInt(v) - 32 162 | }); 163 | var e = decodeBtn(data[0]); 164 | e.x = data[1]; 165 | e.y = data[2]; 166 | e.sequence = '\x1b[' + data.map(function(v) { return v + 32 }).join(';') + 'M'; 167 | e.buf = d.slice(j - 2, i + 1); 168 | this.emit('event', e); 169 | this.emit(e.name, e); 170 | } 171 | } else if(b == 0x03) { // ^C 172 | this.input.pause(); // TODO: I don't know if this is necessary 173 | if(other.length > 0) { 174 | this.emit('other', new Buffer(other)); 175 | other = []; 176 | } 177 | this.emit('ctrl-c'); 178 | } else { 179 | other.push(b); 180 | } 181 | } 182 | if(other.length > 0) { 183 | this.emit('other', new Buffer(other)); 184 | other = []; 185 | } 186 | }; 187 | 188 | this.stop = function stop() { 189 | this.input.removeListener('data', this._onData); 190 | this.input.pause(); 191 | this.output.write('\x1b[?1003l'); 192 | this.output.write('\x1b[?1005l'); 193 | this.output.write('\x1b[?1015l'); 194 | this.output.write('\x1b[?1006l'); 195 | 196 | return this; 197 | }; 198 | }).call(prototype); 199 | 200 | return this; 201 | }).call(Mouse); 202 | })(); 203 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "term-mouse", 3 | "version": "0.2.2", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "term-mouse", 3 | "version": "0.2.2", 4 | "author": "CoderPuppy ", 5 | "main": "./index", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/CoderPuppy/term-mouse.git" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var mouse = require('./')(); 2 | 3 | mouse.start(); 4 | mouse.on('click', function(key) { 5 | console.log('click %s down at %d,%d', key.button, key.x, key.y); 6 | }).on('scroll', function(key) { 7 | console.log('scroll %s', key.button); 8 | }).on('move', function(key) { 9 | console.log('move to %d,%d with %s button down', key.x, key.y, key.button); 10 | }).on('other', function(d) { 11 | console.log('other', d); 12 | }); 13 | --------------------------------------------------------------------------------