1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 Array, 26 NumberIsInteger, 27 ObjectSetPrototypeOf, 28} = primordials; 29 30const net = require('net'); 31const { TTY, isTTY } = internalBinding('tty_wrap'); 32const errors = require('internal/errors'); 33const { ERR_INVALID_FD, ERR_TTY_INIT_FAILED } = errors.codes; 34const { 35 getColorDepth, 36 hasColors 37} = require('internal/tty'); 38 39// Lazy loaded for startup performance. 40let readline; 41 42function isatty(fd) { 43 return NumberIsInteger(fd) && fd >= 0 && fd <= 2147483647 && 44 isTTY(fd); 45} 46 47function ReadStream(fd, options) { 48 if (!(this instanceof ReadStream)) 49 return new ReadStream(fd, options); 50 if (fd >> 0 !== fd || fd < 0) 51 throw new ERR_INVALID_FD(fd); 52 53 const ctx = {}; 54 const tty = new TTY(fd, true, ctx); 55 if (ctx.code !== undefined) { 56 throw new ERR_TTY_INIT_FAILED(ctx); 57 } 58 59 net.Socket.call(this, { 60 highWaterMark: 0, 61 readable: true, 62 writable: false, 63 handle: tty, 64 ...options 65 }); 66 67 this.isRaw = false; 68 this.isTTY = true; 69} 70 71ObjectSetPrototypeOf(ReadStream.prototype, net.Socket.prototype); 72ObjectSetPrototypeOf(ReadStream, net.Socket); 73 74ReadStream.prototype.setRawMode = function(flag) { 75 flag = !!flag; 76 const err = this._handle.setRawMode(flag); 77 if (err) { 78 this.emit('error', errors.errnoException(err, 'setRawMode')); 79 return this; 80 } 81 this.isRaw = flag; 82 return this; 83}; 84 85function WriteStream(fd) { 86 if (!(this instanceof WriteStream)) 87 return new WriteStream(fd); 88 if (fd >> 0 !== fd || fd < 0) 89 throw new ERR_INVALID_FD(fd); 90 91 const ctx = {}; 92 const tty = new TTY(fd, false, ctx); 93 if (ctx.code !== undefined) { 94 throw new ERR_TTY_INIT_FAILED(ctx); 95 } 96 97 net.Socket.call(this, { 98 handle: tty, 99 readable: false, 100 writable: true 101 }); 102 103 // Prevents interleaved or dropped stdout/stderr output for terminals. 104 // As noted in the following reference, local TTYs tend to be quite fast and 105 // this behavior has become expected due historical functionality on OS X, 106 // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1). 107 // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671 108 this._handle.setBlocking(true); 109 110 const winSize = new Array(2); 111 const err = this._handle.getWindowSize(winSize); 112 if (!err) { 113 this.columns = winSize[0]; 114 this.rows = winSize[1]; 115 } 116} 117 118ObjectSetPrototypeOf(WriteStream.prototype, net.Socket.prototype); 119ObjectSetPrototypeOf(WriteStream, net.Socket); 120 121WriteStream.prototype.isTTY = true; 122 123WriteStream.prototype.getColorDepth = getColorDepth; 124 125WriteStream.prototype.hasColors = hasColors; 126 127WriteStream.prototype._refreshSize = function() { 128 const oldCols = this.columns; 129 const oldRows = this.rows; 130 const winSize = new Array(2); 131 const err = this._handle.getWindowSize(winSize); 132 if (err) { 133 this.emit('error', errors.errnoException(err, 'getWindowSize')); 134 return; 135 } 136 const { 0: newCols, 1: newRows } = winSize; 137 if (oldCols !== newCols || oldRows !== newRows) { 138 this.columns = newCols; 139 this.rows = newRows; 140 this.emit('resize'); 141 } 142}; 143 144// Backwards-compat 145WriteStream.prototype.cursorTo = function(x, y, callback) { 146 if (readline === undefined) readline = require('readline'); 147 return readline.cursorTo(this, x, y, callback); 148}; 149WriteStream.prototype.moveCursor = function(dx, dy, callback) { 150 if (readline === undefined) readline = require('readline'); 151 return readline.moveCursor(this, dx, dy, callback); 152}; 153WriteStream.prototype.clearLine = function(dir, callback) { 154 if (readline === undefined) readline = require('readline'); 155 return readline.clearLine(this, dir, callback); 156}; 157WriteStream.prototype.clearScreenDown = function(callback) { 158 if (readline === undefined) readline = require('readline'); 159 return readline.clearScreenDown(this, callback); 160}; 161WriteStream.prototype.getWindowSize = function() { 162 return [this.columns, this.rows]; 163}; 164 165module.exports = { isatty, ReadStream, WriteStream }; 166