From 6ee469fdcb28c4a5c6079c12a945d7a18368df9e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 1 Feb 2015 13:43:52 -0800 Subject: [PATCH] add Image element using w3mimgdisplay. --- README.md | 29 ++++++ lib/widget.js | 238 +++++++++++++++++++++++++++++++++++++++++++ test/widget-image.js | 25 +++++ 3 files changed, 292 insertions(+) create mode 100644 test/widget-image.js diff --git a/README.md b/README.md index 8b03f7b..3d17ff8 100644 --- a/README.md +++ b/README.md @@ -876,6 +876,35 @@ term.js to be installed). - inherits all from Box. +#### Image (from Box) + +Display an image in the terminal (jpeg, png, gif) using w3mimgdisplay. Requires +w3m to be installed. X11 required: works in xterm, urxvt, and possibly other +terminals. + +##### Options: + +- inherits all from Box. +- **file** - path to image +- **w3m** - path to w3mimgdisplay + +##### Properties: + +- inherits all from Box. + +##### Events: + +- inherits all from Box. + +##### Methods: + +- inherits all from Box. +- **setImage(img, callback)** - set the image in the box to a new path. +- **clearImage(callback)** - clear the current image. +- **imageSize(img, callback)** - get the size of an image file in pixels. +- **termSize(callback)** - get the size of the terminal in pixels. + + ### Positioning Offsets may be a number, a percentage (e.g. `50%`), or a keyword (e.g. diff --git a/lib/widget.js b/lib/widget.js index 8a58285..de91323 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -1558,10 +1558,12 @@ Screen.prototype.exec = function(file, args, options, callback) { , ps = this.spawn(file, args, options); ps.on('error', function(err) { + if (!callback) return; return callback(err, false); }); ps.on('exit', function(code) { + if (!callback) return; return callback(null, code === 0); }); @@ -6538,6 +6540,241 @@ Terminal.prototype.render = function() { return ret; }; +/** + * Image + * Good example of w3mimgdisplay commands: + * https://github.com/hut/ranger/blob/master/ranger/ext/img_display.py + */ + +function Image(options) { + var self = this; + + if (!(this instanceof Node)) { + return new Image(options); + } + + options = options || {}; + + Box.call(this, options); + + if (this.options.file) { + self.setImage(self.options.file); + } +} + +Image.prototype.__proto__ = Box.prototype; + +Image.prototype.type = 'image'; + +Image.w3m = '/usr/lib/w3m/w3mimgdisplay'; + +Image.prototype.render = function() { + var ret = this._render(); + if (!ret) return; + this.setImage(this.options.file); + return ret; +}; + +Image.prototype.spawn = function(file, args, opt, callback) { + var screen = this.screen + , opt = opt || {} + , spawn = require('child_process').spawn + , ps; + + ps = spawn(file, args, opt); + + if (callback) { + ps.on('error', callback); + + ps.on('exit', function(code) { + if (code !== 0) return callback(new Error('Exit Code: ' + code)); + return callback(null, code === 0); + }); + } + + return ps; +}; + +Image.prototype.setImage = function(img, callback) { + var self = this; + this.termSize(function(err, dimensions) { + if (err) { + if (!callback) return; + return callback(err); + } + + var tw = dimensions.width / self.screen.width; + var th = dimensions.height / self.screen.height; + + var file = self.options.w3m || Image.w3m; + + var opt = { + stdio: 'pipe', + env: process.env, + cwd: process.env.HOME + }; + + self.clearImage(function() { + var ps = self.spawn(file, [], opt, function(err, success) { + if (!callback) return; + return err + ? callback(err) + : callback(null, success); + }); + + // XXX Move below code to callback. + if (self.shrink) { + self.imageSize(img, function(err, size) { + if (err) return; + if (self.position.width == null) { + self.position.width = size.width / tw | 0; + } + if (self.position.height == null) { + self.position.height = size.height / th | 0; + } + }); + } + + var width = self.width * tw | 0 + , height = self.height * th | 0 + , left = self.left * tw | 0 + , top = self.top * th | 0; + + var input = '0;1;' + + left + ';' + + top + ';' + + width + ';' + + height + ';;;;;' + + img + + '\n4;\n3;\n'; + + self._props = { + left: left, + top: top, + width: width, + height: height + }; + + ps.stdin.write(input); + ps.stdin.end(); + }); + }); +}; + +Image.prototype.clearImage = function(callback) { + var self = this; + + if (!this._props) { + if (!callback) return; + return callback(null); + } + + var file = this.options.w3m || Image.w3m; + + var opt = { + stdio: 'pipe', + env: process.env, + cwd: process.env.HOME + }; + + var ps = this.spawn(file, [], opt, function(err, success) { + if (!callback) return; + return err + ? callback(err) + : callback(null, success); + }); + + var width = this._props.width + , height = this._props.height + , left = this._props.left + , top = this._props.top; + + var input = '6;' + + left + ';' + + top + ';' + + width + ';' + + height + + '\n4;\n3;\n'; + + delete this._props; + + ps.stdin.write(input); + ps.stdin.end(); +}; + +Image.prototype.imageSize = function(img, callback) { + var self = this; + var file = this.options.w3m || Image.w3m; + + var opt = { + stdio: 'pipe', + env: process.env, + cwd: process.env.HOME + }; + + var ps = this.spawn(file, [], opt); + + var buf = ''; + + ps.stdout.setEncoding('utf8'); + + ps.stdout.on('data', function(data) { + buf += data; + }); + + ps.on('error', callback); + + ps.on('exit', function() { + if (!callback) return; + var size = buf.trim().split(/\s+/); + return callback(null, { + raw: buf.trim(), + width: +size[0], + height: +size[1] + }); + }); + + var input = '5;' + img + '\n'; + + ps.stdin.write(input); + ps.stdin.end(); +}; + +Image.prototype.termSize = function(callback) { + var self = this; + var file = this.options.w3m || Image.w3m; + + var opt = { + stdio: 'pipe', + env: process.env, + cwd: process.env.HOME + }; + + var ps = this.spawn(file, ['-test'], opt); + + var buf = ''; + + ps.stdout.setEncoding('utf8'); + + ps.stdout.on('data', function(data) { + buf += data; + }); + + ps.on('error', callback); + + ps.on('exit', function() { + if (!callback) return; + var size = buf.trim().split(/\s+/); + return callback(null, { + raw: buf.trim(), + width: +size[0], + height: +size[1] + }); + }); + + ps.stdin.end(); +}; + /** * Helpers */ @@ -6655,5 +6892,6 @@ exports.Loading = exports.loading = Loading; exports.Listbar = exports.listbar = Listbar; exports.Terminal = exports.terminal = Terminal; +exports.Image = exports.image = Image; exports.helpers = helpers; diff --git a/test/widget-image.js b/test/widget-image.js new file mode 100644 index 0000000..ca44fbf --- /dev/null +++ b/test/widget-image.js @@ -0,0 +1,25 @@ +var blessed = require('../') + , screen; + +screen = blessed.screen({ + dump: __dirname + '/logs/image.log', + smartCSR: true +}); + +var img = blessed.image({ + parent: screen, + left: 'center', + top: 'center', + width: 20, + height: 20, + bg: 'green', + file: process.argv[2] || __dirname + '/test-image.png' +}); + +img.focus(); + +screen.key('q', function() { + return process.exit(0); +}); + +screen.render();