123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717 |
- /**
- * overlayimage.js - w3m image element for blessed
- * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
- * https://github.com/chjj/blessed
- */
- /**
- * Modules
- */
- var fs = require('fs')
- , cp = require('child_process');
- var helpers = require('../helpers');
- var Node = require('./node');
- var Box = require('./box');
- /**
- * OverlayImage
- * Good example of w3mimgdisplay commands:
- * https://github.com/hut/ranger/blob/master/ranger/ext/img_display.py
- */
- function OverlayImage(options) {
- var self = this;
- if (!(this instanceof Node)) {
- return new OverlayImage(options);
- }
- options = options || {};
- Box.call(this, options);
- if (options.w3m) {
- OverlayImage.w3mdisplay = options.w3m;
- }
- if (OverlayImage.hasW3MDisplay == null) {
- if (fs.existsSync(OverlayImage.w3mdisplay)) {
- OverlayImage.hasW3MDisplay = true;
- } else if (options.search !== false) {
- var file = helpers.findFile('/usr', 'w3mimgdisplay')
- || helpers.findFile('/lib', 'w3mimgdisplay')
- || helpers.findFile('/bin', 'w3mimgdisplay');
- if (file) {
- OverlayImage.hasW3MDisplay = true;
- OverlayImage.w3mdisplay = file;
- } else {
- OverlayImage.hasW3MDisplay = false;
- }
- }
- }
- this.on('hide', function() {
- self._lastFile = self.file;
- self.clearImage();
- });
- this.on('show', function() {
- if (!self._lastFile) return;
- self.setImage(self._lastFile);
- });
- this.on('detach', function() {
- self._lastFile = self.file;
- self.clearImage();
- });
- this.on('attach', function() {
- if (!self._lastFile) return;
- self.setImage(self._lastFile);
- });
- this.onScreenEvent('resize', function() {
- self._needsRatio = true;
- });
- // Get images to overlap properly. Maybe not worth it:
- // this.onScreenEvent('render', function() {
- // self.screen.program.flush();
- // if (!self._noImage) return;
- // function display(el, next) {
- // if (el.type === 'w3mimage' && el.file) {
- // el.setImage(el.file, next);
- // } else {
- // next();
- // }
- // }
- // function done(el) {
- // el.children.forEach(recurse);
- // }
- // function recurse(el) {
- // display(el, function() {
- // var pending = el.children.length;
- // el.children.forEach(function(el) {
- // display(el, function() {
- // if (!--pending) done(el);
- // });
- // });
- // });
- // }
- // recurse(self.screen);
- // });
- this.onScreenEvent('render', function() {
- self.screen.program.flush();
- if (!self._noImage) {
- self.setImage(self.file);
- }
- });
- if (this.options.file || this.options.img) {
- this.setImage(this.options.file || this.options.img);
- }
- }
- OverlayImage.prototype.__proto__ = Box.prototype;
- OverlayImage.prototype.type = 'overlayimage';
- OverlayImage.w3mdisplay = '/usr/lib/w3m/w3mimgdisplay';
- OverlayImage.prototype.spawn = function(file, args, opt, callback) {
- var spawn = require('child_process').spawn
- , ps;
- opt = opt || {};
- ps = spawn(file, args, opt);
- ps.on('error', function(err) {
- if (!callback) return;
- return callback(err);
- });
- ps.on('exit', function(code) {
- if (!callback) return;
- if (code !== 0) return callback(new Error('Exit Code: ' + code));
- return callback(null, code === 0);
- });
- return ps;
- };
- OverlayImage.prototype.setImage = function(img, callback) {
- var self = this;
- if (this._settingImage) {
- this._queue = this._queue || [];
- this._queue.push([img, callback]);
- return;
- }
- this._settingImage = true;
- var reset = function() {
- self._settingImage = false;
- self._queue = self._queue || [];
- var item = self._queue.shift();
- if (item) {
- self.setImage(item[0], item[1]);
- }
- };
- if (OverlayImage.hasW3MDisplay === false) {
- reset();
- if (!callback) return;
- return callback(new Error('W3M Image Display not available.'));
- }
- if (!img) {
- reset();
- if (!callback) return;
- return callback(new Error('No image.'));
- }
- this.file = img;
- return this.getPixelRatio(function(err, ratio) {
- if (err) {
- reset();
- if (!callback) return;
- return callback(err);
- }
- return self.renderImage(img, ratio, function(err, success) {
- if (err) {
- reset();
- if (!callback) return;
- return callback(err);
- }
- if (self.shrink || self.options.autofit) {
- delete self.shrink;
- delete self.options.shrink;
- self.options.autofit = true;
- return self.imageSize(function(err, size) {
- if (err) {
- reset();
- if (!callback) return;
- return callback(err);
- }
- if (self._lastSize
- && ratio.tw === self._lastSize.tw
- && ratio.th === self._lastSize.th
- && size.width === self._lastSize.width
- && size.height === self._lastSize.height
- && self.aleft === self._lastSize.aleft
- && self.atop === self._lastSize.atop) {
- reset();
- if (!callback) return;
- return callback(null, success);
- }
- self._lastSize = {
- tw: ratio.tw,
- th: ratio.th,
- width: size.width,
- height: size.height,
- aleft: self.aleft,
- atop: self.atop
- };
- self.position.width = size.width / ratio.tw | 0;
- self.position.height = size.height / ratio.th | 0;
- self._noImage = true;
- self.screen.render();
- self._noImage = false;
- reset();
- return self.renderImage(img, ratio, callback);
- });
- }
- reset();
- if (!callback) return;
- return callback(null, success);
- });
- });
- };
- OverlayImage.prototype.renderImage = function(img, ratio, callback) {
- var self = this;
- if (cp.execSync) {
- callback = callback || function(err, result) { return result; };
- try {
- return callback(null, this.renderImageSync(img, ratio));
- } catch (e) {
- return callback(e);
- }
- }
- if (OverlayImage.hasW3MDisplay === false) {
- if (!callback) return;
- return callback(new Error('W3M Image Display not available.'));
- }
- if (!ratio) {
- if (!callback) return;
- return callback(new Error('No ratio.'));
- }
- // clearImage unsets these:
- var _file = self.file;
- var _lastSize = self._lastSize;
- return self.clearImage(function(err) {
- if (err) return callback(err);
- self.file = _file;
- self._lastSize = _lastSize;
- var opt = {
- stdio: 'pipe',
- env: process.env,
- cwd: process.env.HOME
- };
- var ps = self.spawn(OverlayImage.w3mdisplay, [], opt, function(err, success) {
- if (!callback) return;
- return err
- ? callback(err)
- : callback(null, success);
- });
- var width = self.width * ratio.tw | 0
- , height = self.height * ratio.th | 0
- , aleft = self.aleft * ratio.tw | 0
- , atop = self.atop * ratio.th | 0;
- var input = '0;1;'
- + aleft + ';'
- + atop + ';'
- + width + ';'
- + height + ';;;;;'
- + img
- + '\n4;\n3;\n';
- self._props = {
- aleft: aleft,
- atop: atop,
- width: width,
- height: height
- };
- ps.stdin.write(input);
- ps.stdin.end();
- });
- };
- OverlayImage.prototype.clearImage = function(callback) {
- if (cp.execSync) {
- callback = callback || function(err, result) { return result; };
- try {
- return callback(null, this.clearImageSync());
- } catch (e) {
- return callback(e);
- }
- }
- if (OverlayImage.hasW3MDisplay === false) {
- if (!callback) return;
- return callback(new Error('W3M Image Display not available.'));
- }
- if (!this._props) {
- if (!callback) return;
- return callback(null);
- }
- var opt = {
- stdio: 'pipe',
- env: process.env,
- cwd: process.env.HOME
- };
- var ps = this.spawn(OverlayImage.w3mdisplay, [], opt, function(err, success) {
- if (!callback) return;
- return err
- ? callback(err)
- : callback(null, success);
- });
- var width = this._props.width + 2
- , height = this._props.height + 2
- , aleft = this._props.aleft
- , atop = this._props.atop;
- if (this._drag) {
- aleft -= 10;
- atop -= 10;
- width += 10;
- height += 10;
- }
- var input = '6;'
- + aleft + ';'
- + atop + ';'
- + width + ';'
- + height
- + '\n4;\n3;\n';
- delete this.file;
- delete this._props;
- delete this._lastSize;
- ps.stdin.write(input);
- ps.stdin.end();
- };
- OverlayImage.prototype.imageSize = function(callback) {
- var img = this.file;
- if (cp.execSync) {
- callback = callback || function(err, result) { return result; };
- try {
- return callback(null, this.imageSizeSync());
- } catch (e) {
- return callback(e);
- }
- }
- if (OverlayImage.hasW3MDisplay === false) {
- if (!callback) return;
- return callback(new Error('W3M Image Display not available.'));
- }
- if (!img) {
- if (!callback) return;
- return callback(new Error('No image.'));
- }
- var opt = {
- stdio: 'pipe',
- env: process.env,
- cwd: process.env.HOME
- };
- var ps = this.spawn(OverlayImage.w3mdisplay, [], opt);
- var buf = '';
- ps.stdout.setEncoding('utf8');
- ps.stdout.on('data', function(data) {
- buf += data;
- });
- ps.on('error', function(err) {
- if (!callback) return;
- return callback(err);
- });
- 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();
- };
- OverlayImage.prototype.termSize = function(callback) {
- var self = this;
- if (cp.execSync) {
- callback = callback || function(err, result) { return result; };
- try {
- return callback(null, this.termSizeSync());
- } catch (e) {
- return callback(e);
- }
- }
- if (OverlayImage.hasW3MDisplay === false) {
- if (!callback) return;
- return callback(new Error('W3M Image Display not available.'));
- }
- var opt = {
- stdio: 'pipe',
- env: process.env,
- cwd: process.env.HOME
- };
- var ps = this.spawn(OverlayImage.w3mdisplay, ['-test'], opt);
- var buf = '';
- ps.stdout.setEncoding('utf8');
- ps.stdout.on('data', function(data) {
- buf += data;
- });
- ps.on('error', function(err) {
- if (!callback) return;
- return callback(err);
- });
- ps.on('exit', function() {
- if (!callback) return;
- if (!buf.trim()) {
- // Bug: w3mimgdisplay will sometimes
- // output nothing. Try again:
- return self.termSize(callback);
- }
- var size = buf.trim().split(/\s+/);
- return callback(null, {
- raw: buf.trim(),
- width: +size[0],
- height: +size[1]
- });
- });
- ps.stdin.end();
- };
- OverlayImage.prototype.getPixelRatio = function(callback) {
- var self = this;
- if (cp.execSync) {
- callback = callback || function(err, result) { return result; };
- try {
- return callback(null, this.getPixelRatioSync());
- } catch (e) {
- return callback(e);
- }
- }
- // XXX We could cache this, but sometimes it's better
- // to recalculate to be pixel perfect.
- if (this._ratio && !this._needsRatio) {
- return callback(null, this._ratio);
- }
- return this.termSize(function(err, dimensions) {
- if (err) return callback(err);
- self._ratio = {
- tw: dimensions.width / self.screen.width,
- th: dimensions.height / self.screen.height
- };
- self._needsRatio = false;
- return callback(null, self._ratio);
- });
- };
- OverlayImage.prototype.renderImageSync = function(img, ratio) {
- if (OverlayImage.hasW3MDisplay === false) {
- throw new Error('W3M Image Display not available.');
- }
- if (!ratio) {
- throw new Error('No ratio.');
- }
- // clearImage unsets these:
- var _file = this.file;
- var _lastSize = this._lastSize;
- this.clearImageSync();
- this.file = _file;
- this._lastSize = _lastSize;
- var width = this.width * ratio.tw | 0
- , height = this.height * ratio.th | 0
- , aleft = this.aleft * ratio.tw | 0
- , atop = this.atop * ratio.th | 0;
- var input = '0;1;'
- + aleft + ';'
- + atop + ';'
- + width + ';'
- + height + ';;;;;'
- + img
- + '\n4;\n3;\n';
- this._props = {
- aleft: aleft,
- atop: atop,
- width: width,
- height: height
- };
- try {
- cp.execFileSync(OverlayImage.w3mdisplay, [], {
- env: process.env,
- encoding: 'utf8',
- input: input,
- timeout: 1000
- });
- } catch (e) {
- ;
- }
- return true;
- };
- OverlayImage.prototype.clearImageSync = function() {
- if (OverlayImage.hasW3MDisplay === false) {
- throw new Error('W3M Image Display not available.');
- }
- if (!this._props) {
- return false;
- }
- var width = this._props.width + 2
- , height = this._props.height + 2
- , aleft = this._props.aleft
- , atop = this._props.atop;
- if (this._drag) {
- aleft -= 10;
- atop -= 10;
- width += 10;
- height += 10;
- }
- var input = '6;'
- + aleft + ';'
- + atop + ';'
- + width + ';'
- + height
- + '\n4;\n3;\n';
- delete this.file;
- delete this._props;
- delete this._lastSize;
- try {
- cp.execFileSync(OverlayImage.w3mdisplay, [], {
- env: process.env,
- encoding: 'utf8',
- input: input,
- timeout: 1000
- });
- } catch (e) {
- ;
- }
- return true;
- };
- OverlayImage.prototype.imageSizeSync = function() {
- var img = this.file;
- if (OverlayImage.hasW3MDisplay === false) {
- throw new Error('W3M Image Display not available.');
- }
- if (!img) {
- throw new Error('No image.');
- }
- var buf = '';
- var input = '5;' + img + '\n';
- try {
- buf = cp.execFileSync(OverlayImage.w3mdisplay, [], {
- env: process.env,
- encoding: 'utf8',
- input: input,
- timeout: 1000
- });
- } catch (e) {
- ;
- }
- var size = buf.trim().split(/\s+/);
- return {
- raw: buf.trim(),
- width: +size[0],
- height: +size[1]
- };
- };
- OverlayImage.prototype.termSizeSync = function(_, recurse) {
- if (OverlayImage.hasW3MDisplay === false) {
- throw new Error('W3M Image Display not available.');
- }
- var buf = '';
- try {
- buf = cp.execFileSync(OverlayImage.w3mdisplay, ['-test'], {
- env: process.env,
- encoding: 'utf8',
- timeout: 1000
- });
- } catch (e) {
- ;
- }
- if (!buf.trim()) {
- // Bug: w3mimgdisplay will sometimes
- // output nothing. Try again:
- recurse = recurse || 0;
- if (++recurse === 5) {
- throw new Error('Term size not determined.');
- }
- return this.termSizeSync(_, recurse);
- }
- var size = buf.trim().split(/\s+/);
- return {
- raw: buf.trim(),
- width: +size[0],
- height: +size[1]
- };
- };
- OverlayImage.prototype.getPixelRatioSync = function() {
- // XXX We could cache this, but sometimes it's better
- // to recalculate to be pixel perfect.
- if (this._ratio && !this._needsRatio) {
- return this._ratio;
- }
- this._needsRatio = false;
- var dimensions = this.termSizeSync();
- this._ratio = {
- tw: dimensions.width / this.screen.width,
- th: dimensions.height / this.screen.height
- };
- return this._ratio;
- };
- OverlayImage.prototype.displayImage = function(callback) {
- return this.screen.displayImage(this.file, callback);
- };
- /**
- * Expose
- */
- module.exports = OverlayImage;
|