diff --git a/.gitignore b/.gitignore index a902753..5148e52 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,3 @@ jspm_packages # Optional REPL history .node_repl_history - -build - -lib diff --git a/README.md b/README.md index 5be5d25..0682d01 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # OS Input Capture (oic) -This module adds operating system level keyboard, mouse, and window logging capabilities to your project. Unlike the other loggers available for node, this logger does not require you to be typing in a specific window, it will snag all input keystrokes and mouse button events no matter what application the user currently has active. Additionally this module allows you to take screen shots any available window on both keyboard and mouse events. +This module adds operating system level keyboard, mouse, and window logging capabilities to your project. Unlike the other loggers available for node, this logger does not require you to be typing in a specific window, it will snag all input keystrokes and mouse button events no matter what application the user currently has active. Additionally this module allows you to take screen shots of any available window on both keyboard and mouse events. + + +## Note + + +>Please make sure to use version 2.0.1 or greater of this package as it fixes a major import error. --- -##Dependencies +## Dependencies + > Currently this module only works on linux hosts and must be launched with the ability to access `/dev/input/`, which is generally restricted to `root` or `sudo` accounts. Additionally if you would like to be able to capture images of a specific window, you must have the `imagemagick` command line tool installed on the host machine. -###Node + +### Node ```js "devDependencies": { "babel-cli": "^6.11.4", @@ -26,7 +34,7 @@ This module adds operating system level keyboard, mouse, and window logging capa --- -##Installation +## Installation Install the package through npm ```bash npm -i os-input-capture @@ -36,13 +44,14 @@ npm -i os-input-capture ## Disclaimer I wrote this module to allow me to start crafting some input data sets for a learning machine project I am tinkering with. That being said, I know this module could be used for some pretty nefarious stuff. All that I ask is that you, as the user of this package, please keep in mind other peoples privacy when writing anything that uses os-input-capture, and most importantly -> ###*DO NOT USE THIS MODULE TO BE A JERK* +### *DO NOT USE THIS MODULE TO BE A JERK* + thanks. --- -##Example Usage -###Top level convenience class +## Example Usage +### Top level convenience class ```js import oic from 'os-input-capture'; @@ -51,7 +60,7 @@ import oic from 'os-input-capture'; --- NOTE: Each logger will use its respective configuration values (shown below) if you do not - explicitly provide you own during instantiation. + explicitly provide you own during instantiation. */ let options = { keyboardOptions: { @@ -74,8 +83,8 @@ let desiredLoggers = ['keyboard', 'mouse', 'window']; // creating our actual os logger! let logger = oic.OsInputCapture(desiredLoggers, options); ``` -###Creating standalone loggers -All loggers are available for import by themselves +### Creating standalone loggers +All loggers are available for use without the top level `OsInputCapture` class ```js // configuration let keyboardOptions = { @@ -86,22 +95,18 @@ let keyboardOptions = { // instantiation let kbdLogger = oic.KeyboardLogger(keyboardOptions); ``` -###How to start logging keys +### How to start logging keys By default logging is started when the `L` button is pressed on the keyboard, and stopped (or killed) when the `K` button is pressed. Currently the mouse logger has to be manually activated as shown below ```js -let mouseOptions = { - inputPath: inputPath: '/dev/input/mice', - outputDir: path.resolve(__dirname, 'mouse') -} - -let mousey = oic.MouseLogger(mouseOptions); +// creating mouse logger class with default options +let mousey = oic.MouseLogger(); mousey.active = true; ``` -###Getting screen shots +### Getting screen shots If you want to be able to get screen shots of windows, you currently have to use the top level `oic.OsInputCapture` class, as the `MouseLogger` and `KeyboardLogger` classes chain the call to `WindowLogger.get()` through `oic.OsInputCapture`. ```js -//main.js +// main.js let options = { keyboardOptions: { inputPath: '/dev/input/by-path/platform-i8042-serio-0-event-kbd', @@ -134,14 +139,12 @@ getWindow() { } } ``` -##Note about default configuration options -Each logger has a set of default options that it will fall back on if you don't specify custom overrides. Here are all -##Testing +## Testing Unit testing via jasmine is available via `npm test`. --- ## License -**os-input-capture** is Copyright (c) 2016 Brandon DeMello [@bdell](https://github.com/bdell) and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE.md file for more details. +See the included LICENSE file for more details. diff --git a/lib/keyboard-logger.js b/lib/keyboard-logger.js new file mode 100644 index 0000000..e45de68 --- /dev/null +++ b/lib/keyboard-logger.js @@ -0,0 +1,92 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _fsExtra = require('fs-extra'); + +var _fsExtra2 = _interopRequireDefault(_fsExtra); + +var _winston = require('winston'); + +var _winston2 = _interopRequireDefault(_winston); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var KeyboardLogger = function () { + function KeyboardLogger(opts, parent) { + var _this = this; + + _classCallCheck(this, KeyboardLogger); + + var defaultOpts = { + inputPath: '/dev/input/by-path/platform-i8042-serio-0-event-kbd', + outputDir: _path2.default.resolve(__dirname, 'keyboard') + }; + this.active = false; + this.opts = _lodash2.default.assign(defaultOpts, opts); + _fsExtra2.default.ensureDir(this.opts.outputDir, function (err) { + return console.log(err); + }); + this.readStream = _fs2.default.createReadStream(this.opts.inputPath).on('data', function (buffer) { + _this.handleKeyboardEvent(buffer); + }); + this.writeStream = new _winston2.default.Logger({ + transports: [new _winston2.default.transports.File({ filename: _path2.default.resolve(this.opts.outputDir, new Date(Date.now()).toISOString() + '.input.log') })] + }); + this.parent = parent; + } + + _createClass(KeyboardLogger, [{ + key: 'toggleActive', + value: function toggleActive() { + this.active = !this.active; + } + }, { + key: 'handleKeyboardEvent', + value: function handleKeyboardEvent(buffer) { + var eventData = { timeStamp: Date.now(), code: buffer.readUInt16LE(20), value: buffer.readInt32LE(44) }; + if ((eventData.code === 38 && !this.active || eventData.code === 37 && this.active) && eventData.value === 1) { + if (!_lodash2.default.isUndefined(this.parent)) { + this.parent.toggleActive(); + } + if (_lodash2.default.isUndefined(this.parent)) { + this.toggleActive(); + } + } + if (this.active) { + this.writeStream.log('info', eventData); + if (!_lodash2.default.isUndefined(this.parent)) { + if (!_lodash2.default.isUndefined(this.parent.getWindow)) { + this.parent.getWindow(); + } + } + } + } + }]); + + return KeyboardLogger; +}(); + +exports.default = function (opts, parent) { + if (!(undefined instanceof KeyboardLogger)) { + return new KeyboardLogger(opts, parent); + } +}; \ No newline at end of file diff --git a/lib/mouse-logger.js b/lib/mouse-logger.js new file mode 100644 index 0000000..34aa9c6 --- /dev/null +++ b/lib/mouse-logger.js @@ -0,0 +1,129 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _fsExtra = require('fs-extra'); + +var _fsExtra2 = _interopRequireDefault(_fsExtra); + +var _winston = require('winston'); + +var _winston2 = _interopRequireDefault(_winston); + +var _robotjs = require('robotjs'); + +var _robotjs2 = _interopRequireDefault(_robotjs); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var MouseLogger = function () { + function MouseLogger(opts, parent) { + var _this = this; + + _classCallCheck(this, MouseLogger); + + var defaultOpts = { + inputPath: '/dev/input/mice', + outputDir: _path2.default.resolve(__dirname, 'mouse') + }; + this.active = false; + this.opts = _lodash2.default.assign(defaultOpts, opts); + _fsExtra2.default.ensureDir(this.opts.outputDir, function (err) { + return console.log(err); + }); + this.readStream = _fs2.default.createReadStream(this.opts.inputPath).on('data', function (buffer) { + _this.handleMouseEvent(buffer); + }); + this.writeStream = new _winston2.default.Logger({ + transports: [new _winston2.default.transports.File({ filename: _path2.default.resolve(this.opts.outputDir, new Date(Date.now()).toISOString() + '.input.log') })] + }); + this.parent = parent; + } + + _createClass(MouseLogger, [{ + key: 'toggleActive', + value: function toggleActive() { + this.active = !this.active; + } + }, { + key: 'handleMouseEvent', + value: function handleMouseEvent(buffer) { + /* Mouse input event structuring graciously copied from + * https://gist.github.com/bfncs/2020943 + * Seriously I cannot thank you enough Marc Loehe and Tim Caswell. + * The original header follows below + */ + + ////// begin shared work ////// + + /** + * Read Linux mouse(s) in node.js + * Author: Marc Loehe (marcloehe@gmail.com) + * + * Adapted from Tim Caswell's nice solution to read a linux joystick + * http://nodebits.org/linux-joystick + * https://github.com/nodebits/linux-joystick + */ + var event = { + leftBtn: (buffer[0] & 1) > 0, // Bit 0 + rightBtn: (buffer[0] & 2) > 0, // Bit 1 + middleBtn: (buffer[0] & 4) > 0 // Bit 2 + }; + ////// end shared work ////// + + if (this.active) { + if (event.leftBtn || event.rightBtn || event.middleBtn) { + // TODO: Handle clicking and dragging elegantly + var _robot$getMousePos = _robotjs2.default.getMousePos(); + + var x = _robot$getMousePos.x; + var y = _robot$getMousePos.y; + var leftBtn = event.leftBtn; + var middleBtn = event.middleBtn; + var rightBtn = event.rightBtn; + + var eventData = { + timeStamp: Date.now(), + x: x, + y: y, + leftBtn: leftBtn, + middleBtn: middleBtn, + rightBtn: rightBtn + }; + this.writeStream.log('info', eventData); + if (!_lodash2.default.isUndefined(this.parent)) { + if (!_lodash2.default.isUndefined(this.parent.getWindow)) { + this.parent.getWindow(); + } + } + } + } + } + }]); + + return MouseLogger; +}(); + +exports.default = function (opts, parent) { + if (!(undefined instanceof MouseLogger)) { + return new MouseLogger(opts, parent); + } +}; \ No newline at end of file diff --git a/lib/os-input-capture.js b/lib/os-input-capture.js new file mode 100644 index 0000000..3858608 --- /dev/null +++ b/lib/os-input-capture.js @@ -0,0 +1,140 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _keyboardLogger = require('./keyboard-logger.js'); + +var _keyboardLogger2 = _interopRequireDefault(_keyboardLogger); + +var _mouseLogger = require('./mouse-logger.js'); + +var _mouseLogger2 = _interopRequireDefault(_mouseLogger); + +var _windowLogger = require('./window-logger.js'); + +var _windowLogger2 = _interopRequireDefault(_windowLogger); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var OsInputCapture = function () { + function OsInputCapture() { + var loggers = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + _classCallCheck(this, OsInputCapture); + + this.opts = opts; + this.active = false; + if (_lodash2.default.findIndex(loggers, function (item) { + return item === 'keyboard'; + }) !== -1) { + this.addKeyboardLogger(opts); + } + if (_lodash2.default.findIndex(loggers, function (item) { + return item === 'mouse'; + }) !== -1) { + this.addMouseLogger(opts); + } + if (_lodash2.default.findIndex(loggers, function (item) { + return item === 'window'; + }) !== -1) { + this.addWindowLogger(opts); + } + } + + _createClass(OsInputCapture, [{ + key: 'addKeyboardLogger', + value: function addKeyboardLogger(opts) { + if (_lodash2.default.isUndefined(this.keyboardLogger)) { + if (!_lodash2.default.isUndefined(opts)) { + var keyboardOptions = opts.keyboardOptions; + + this.keyboardLogger = new _keyboardLogger2.default(keyboardOptions, this); + } + if (_lodash2.default.isUndefined(opts)) { + var _keyboardOptions = this.opts.keyboardOptions; + + this.keyboardLogger = new _keyboardLogger2.default(_keyboardOptions, this); + } + } else { + throw new Error('logger already present'); + } + } + }, { + key: 'addMouseLogger', + value: function addMouseLogger(opts) { + if (_lodash2.default.isUndefined(this.mouseLogger)) { + if (!_lodash2.default.isUndefined(opts)) { + var mouseOptions = opts.mouseOptions; + + this.mouseLogger = new _mouseLogger2.default(mouseOptions, this); + } + if (_lodash2.default.isUndefined(opts)) { + var _mouseOptions = this.opts.mouseOptions; + + this.mouseLogger = new _mouseLogger2.default(_mouseOptions, this); + } + } else { + throw new Error('logger already present'); + } + } + }, { + key: 'addWindowLogger', + value: function addWindowLogger(opts) { + if (_lodash2.default.isUndefined(this.windowLogger)) { + if (!_lodash2.default.isUndefined(opts)) { + var windowOptions = opts.windowOptions; + + this.windowLogger = new _windowLogger2.default(windowOptions, this); + } + if (_lodash2.default.isUndefined(opts)) { + var _windowOptions = this.opts.windowOptions; + + this.windowLogger = new _windowLogger2.default(_windowOptions, this); + } + } else { + throw new Error('logger already present'); + } + } + }, { + key: 'toggleActive', + value: function toggleActive() { + this.active = !this.active; + if (!_lodash2.default.isUndefined(this.keyboardLogger)) { + this.keyboardLogger.toggleActive(); + } + if (!_lodash2.default.isUndefined(this.mouseLogger)) { + this.mouseLogger.toggleActive(); + } + if (!_lodash2.default.isUndefined(this.windowLogger)) { + this.windowLogger.toggleActive(); + } + } + }, { + key: 'getWindow', + value: function getWindow() { + if (!_lodash2.default.isUndefined(this.windowLogger)) { + this.windowLogger.get(); + } + } + }]); + + return OsInputCapture; +}(); + +var oic = exports; + +oic.OsInputCapture = function (loggers, opts) { + if (!(undefined instanceof OsInputCapture)) { + return new OsInputCapture(loggers, opts); + } +}; +oic.KeyboardLogger = _keyboardLogger2.default; +oic.MouseLogger = _mouseLogger2.default; +oic.WindowLogger = _windowLogger2.default; \ No newline at end of file diff --git a/lib/window-logger.js b/lib/window-logger.js new file mode 100644 index 0000000..1557299 --- /dev/null +++ b/lib/window-logger.js @@ -0,0 +1,69 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _execa = require('execa'); + +var _execa2 = _interopRequireDefault(_execa); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _fsExtra = require('fs-extra'); + +var _fsExtra2 = _interopRequireDefault(_fsExtra); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var WindowLogger = function () { + function WindowLogger(opts) { + _classCallCheck(this, WindowLogger); + + var defaultOpts = { + outputDir: _path2.default.resolve(__dirname, 'window'), + colorMode: '-monochrome', + imageType: 'jpg' + }; + this.opts = _lodash2.default.assign(defaultOpts, opts); + _fsExtra2.default.ensureDir(this.opts.outputDir, function (err) { + return console.log(err); + }); + } + + _createClass(WindowLogger, [{ + key: 'get', + value: function get() { + if (this.active) { + if (!_lodash2.default.isUndefined(this.opts.windowTitle)) { + var imgPath = _path2.default.resolve(this.opts.outputDir, new Date(Date.now()).toISOString() + '.jpg'); + var command = 'import -window "' + this.opts.windowTitle + '" ' + this.opts.colorMode + ' ' + this.opts.imageType + ':' + imgPath; + _execa2.default.shell(command); + } + } + } + }, { + key: 'toggleActive', + value: function toggleActive() { + this.active = !this.active; + } + }]); + + return WindowLogger; +}(); + +exports.default = function (opts) { + if (!(undefined instanceof WindowLogger)) { + return new WindowLogger(opts); + } +}; \ No newline at end of file diff --git a/package.json b/package.json index e272acb..f65d182 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "os-input-capture", - "version": "0.5.0", - "description": "A keyboard, mouse, and window capturing module for node.js", - "main": "lib/index.js", + "version": "2.0.2", + "description": "A keyboard, mouse, and window logging module for node.js", + "main": "lib/os-input-capture.js", "scripts": { "start": "gulp && node ./lib/os-input-capture-cli.js", "test": "gulp && echo 'oic' && ./node_modules/jasmine-es6/bin/jasmine.js ./spec/os-input-capture_spec.js && echo 'keyboard' && ./node_modules/jasmine-es6/bin/jasmine.js ./spec/keyboard-logger_spec.js && echo 'mouse' && ./node_modules/jasmine-es6/bin/jasmine.js ./spec/mouse-logger_spec.js && echo 'window' && ./node_modules/jasmine-es6/bin/jasmine.js ./spec/window-logger_spec.js", @@ -18,7 +18,8 @@ "logging", "logger", "screen", - "window" + "window", + "keylogger" ], "author": "bdell", "license": "MIT", diff --git a/spec/os-input-capture_spec.js b/spec/os-input-capture_spec.js index f096c08..e993018 100644 --- a/spec/os-input-capture_spec.js +++ b/spec/os-input-capture_spec.js @@ -79,7 +79,9 @@ describe('oic', () => { it('should use supplied opts if present', () => { let fakeOpts = { windowOptions: { - outputDir: 'fakeOutputPath' + outputDir: 'fakeOutputPath', + colorMode: '-fakeMode', + imageType: 'fakeImageType' } }; let inputLogger = new globals.oic.OsInputCapture(); diff --git a/spec/window-logger_spec.js b/spec/window-logger_spec.js index 056cb8e..3d8b8e6 100644 --- a/spec/window-logger_spec.js +++ b/spec/window-logger_spec.js @@ -12,7 +12,10 @@ let globals = {}; describe('WindowLogger', () => { beforeEach(() => { globals.defaultOpts = { - outputDir: path.resolve(__dirname, '../lib', 'window') + outputDir: path.resolve(__dirname, '../lib', 'window'), + colorMode: '-monochrome', + imageType: 'jpg' + }; globals.testOpts = { outputDir: path.resolve(__dirname, '../lib', 'window'), diff --git a/src/window-logger.js b/src/window-logger.js index 5a5d229..9097a5e 100644 --- a/src/window-logger.js +++ b/src/window-logger.js @@ -8,7 +8,9 @@ import fse from 'fs-extra'; class WindowLogger { constructor(opts) { const defaultOpts = { - outputDir: path.resolve(__dirname, 'window') + outputDir: path.resolve(__dirname, 'window'), + colorMode: '-monochrome', + imageType: 'jpg' }; this.opts = _.assign(defaultOpts, opts); fse.ensureDir(this.opts.outputDir, err => console.log(err)); @@ -17,7 +19,7 @@ class WindowLogger { if (this.active) { if (!_.isUndefined(this.opts.windowTitle)) { let imgPath = path.resolve(this.opts.outputDir, `${ new Date(Date.now()).toISOString() }.jpg`); - let command = `import -window "${ this.opts.windowTitle }" -colorspace Gray jpg:${ imgPath }`; + let command = `import -window "${ this.opts.windowTitle }" ${ this.opts.colorMode } ${ this.opts.imageType }:${ imgPath }`; execa.shell(command); } }