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, ctx); 55 if (ctx.code !== undefined) { 56 throw new ERR_TTY_INIT_FAILED(ctx); 57 } 58 59 net.Socket.call(this, { 60 readableHighWaterMark: 0, 61 handle: tty, 62 manualStart: true, 63 ...options, 64 }); 65 66 this.isRaw = false; 67 this.isTTY = true; 68} 69 70ObjectSetPrototypeOf(ReadStream.prototype, net.Socket.prototype); 71ObjectSetPrototypeOf(ReadStream, net.Socket); 72 73ReadStream.prototype.setRawMode = function(flag) { 74 flag = !!flag; 75 const err = this._handle?.setRawMode(flag); 76 if (err) { 77 this.emit('error', errors.errnoException(err, 'setRawMode')); 78 return this; 79 } 80 this.isRaw = flag; 81 return this; 82}; 83 84function WriteStream(fd) { 85 if (!(this instanceof WriteStream)) 86 return new WriteStream(fd); 87 if (fd >> 0 !== fd || fd < 0) 88 throw new ERR_INVALID_FD(fd); 89 90 const ctx = {}; 91 const tty = new TTY(fd, ctx); 92 if (ctx.code !== undefined) { 93 throw new ERR_TTY_INIT_FAILED(ctx); 94 } 95 96 net.Socket.call(this, { 97 readableHighWaterMark: 0, 98 handle: tty, 99 manualStart: true, 100 }); 101 102 // Prevents interleaved or dropped stdout/stderr output for terminals. 103 // As noted in the following reference, local TTYs tend to be quite fast and 104 // this behavior has become expected due historical functionality on OS X, 105 // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1). 106 // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671 107 this._handle.setBlocking(true); 108 109 const winSize = new Array(2); 110 const err = this._handle.getWindowSize(winSize); 111 if (!err) { 112 this.columns = winSize[0]; 113 this.rows = winSize[1]; 114 } 115} 116 117ObjectSetPrototypeOf(WriteStream.prototype, net.Socket.prototype); 118ObjectSetPrototypeOf(WriteStream, net.Socket); 119 120WriteStream.prototype.isTTY = true; 121 122WriteStream.prototype.getColorDepth = getColorDepth; 123 124WriteStream.prototype.hasColors = hasColors; 125 126WriteStream.prototype._refreshSize = function() { 127 const oldCols = this.columns; 128 const oldRows = this.rows; 129 const winSize = new Array(2); 130 const err = this._handle.getWindowSize(winSize); 131 if (err) { 132 this.emit('error', errors.errnoException(err, 'getWindowSize')); 133 return; 134 } 135 const { 0: newCols, 1: newRows } = winSize; 136 if (oldCols !== newCols || oldRows !== newRows) { 137 this.columns = newCols; 138 this.rows = newRows; 139 this.emit('resize'); 140 } 141}; 142 143// Backwards-compat 144WriteStream.prototype.cursorTo = function(x, y, callback) { 145 if (readline === undefined) readline = require('readline'); 146 return readline.cursorTo(this, x, y, callback); 147}; 148WriteStream.prototype.moveCursor = function(dx, dy, callback) { 149 if (readline === undefined) readline = require('readline'); 150 return readline.moveCursor(this, dx, dy, callback); 151}; 152WriteStream.prototype.clearLine = function(dir, callback) { 153 if (readline === undefined) readline = require('readline'); 154 return readline.clearLine(this, dir, callback); 155}; 156WriteStream.prototype.clearScreenDown = function(callback) { 157 if (readline === undefined) readline = require('readline'); 158 return readline.clearScreenDown(this, callback); 159}; 160WriteStream.prototype.getWindowSize = function() { 161 return [this.columns, this.rows]; 162}; 163 164module.exports = { isatty, ReadStream, WriteStream }; 165