diff --git a/modules/win-terminal.js b/modules/win-terminal.js new file mode 100644 index 0000000..e5a2222 --- /dev/null +++ b/modules/win-terminal.js @@ -0,0 +1,495 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var promise = require('promise'); +var duplex = require('stream').Duplex; + +var SW_HIDE = 0; +var SW_MINIMIZE = 6; +var STARTF_USESHOWWINDOW = 0x1; +var STD_INPUT_HANDLE = -10; +var STD_OUTPUT_HANDLE = -11; +var EVENT_CONSOLE_CARET = 0x4001; +var EVENT_CONSOLE_END_APPLICATION = 0x4007; +var WINEVENT_OUTOFCONTEXT = 0x000; +var WINEVENT_SKIPOWNPROCESS = 0x0002; +var CREATE_NEW_PROCESS_GROUP = 0x200; +var EVENT_CONSOLE_UPDATE_REGION = 0x4002; +var EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003; +var EVENT_CONSOLE_UPDATE_SCROLL = 0x4004; +var EVENT_CONSOLE_LAYOUT = 0x4005; +var EVENT_CONSOLE_START_APPLICATION = 0x4006; +var KEY_EVENT = 0x1; +var MAPVK_VK_TO_VSC = 0; +var WM_QUIT = 0x12; + +var GM = require('_GenericMarshal'); +var si = GM.CreateVariable(GM.PointerSize == 4 ? 68 : 104); +var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24); + +si.Deref(0, 4).toBuffer().writeUInt32LE(GM.PointerSize == 4 ? 68 : 104); // si.cb +si.Deref(GM.PointerSize == 4 ? 48 : 64, 2).toBuffer().writeUInt16LE(SW_HIDE | SW_MINIMIZE); // si.wShowWindow +si.Deref(GM.PointerSize == 4 ? 44 : 60, 4).toBuffer().writeUInt32LE(STARTF_USESHOWWINDOW); // si.dwFlags; + +var MSG = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48); + +function windows_terminal() { + this._ObjectID = 'windows_terminal'; + this._user32 = GM.CreateNativeProxy('User32.dll'); + this._user32.CreateMethod('DispatchMessageA'); + this._user32.CreateMethod('GetMessageA'); + this._user32.CreateMethod('MapVirtualKeyA'); + this._user32.CreateMethod('PostThreadMessageA'); + this._user32.CreateMethod('SetWinEventHook'); + this._user32.CreateMethod('ShowWindow'); + this._user32.CreateMethod('TranslateMessage'); + this._user32.CreateMethod('UnhookWinEvent'); + this._user32.CreateMethod('VkKeyScanA'); + this._user32.terminal = this; + + this._kernel32 = GM.CreateNativeProxy('Kernel32.dll'); + this._kernel32.CreateMethod('AllocConsole'); + this._kernel32.CreateMethod('CreateProcessA'); + this._kernel32.CreateMethod('CloseHandle'); + this._kernel32.CreateMethod('FillConsoleOutputAttribute'); + this._kernel32.CreateMethod('FillConsoleOutputCharacterA'); + this._kernel32.CreateMethod('GetConsoleScreenBufferInfo'); + this._kernel32.CreateMethod('GetConsoleWindow'); + this._kernel32.CreateMethod('GetLastError'); + this._kernel32.CreateMethod('GetStdHandle'); + this._kernel32.CreateMethod('GetThreadId'); + this._kernel32.CreateMethod('ReadConsoleOutputA'); + this._kernel32.CreateMethod('SetConsoleCursorPosition'); + this._kernel32.CreateMethod('SetConsoleScreenBufferSize'); + this._kernel32.CreateMethod('SetConsoleWindowInfo'); + this._kernel32.CreateMethod('TerminateProcess'); + this._kernel32.CreateMethod('WaitForSingleObject'); + this._kernel32.CreateMethod('WriteConsoleInputA'); + + var currentX = 0; + var currentY = 0; + + this._scrx = 0; + this._scry = 0; + + this.SendCursorUpdate = function () { + var newCsbi = GM.CreateVariable(22); + + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, newCsbi).Val == 0) { return; } + if (newCsbi.Deref(4, 2).toBuffer().readUInt16LE() != this.currentX || newCsbi.Deref(6, 2).toBuffer().readUInt16LE() != this.currentY) { + //wchar_t mywbuf[512]; + //swprintf(mywbuf, 512, TEXT("csbi.dwCursorPosition.X = %d, csbi.dwCursorPosition.Y = %d, newCsbi.dwCursorPosition.X = %d, newCsbi.dwCursorPosition.Y = %d\r\n"), csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, newCsbi.dwCursorPosition.X, newCsbi.dwCursorPosition.Y); + //OutputDebugString(mywbuf); + + //m_viewOffset = newCsbi.srWindow.Top; + //WriteMoveCursor((SerialAgent *)this->sa, (char)(newCsbi.dwCursorPosition.Y - m_viewOffset), (char)(newCsbi.dwCursorPosition.X - m_viewOffset)); + //LowStackSendData((SerialAgent *)(this->sa), "", 0); + + this.currentX = newCsbi.Deref(4, 2).toBuffer().readUInt16LE(); + this.currentY = newCsbi.Deref(6, 2).toBuffer().readUInt16LE(); + } + } + + this.ClearScreen = function () { + var CONSOLE_SCREEN_BUFFER_INFO = GM.CreateVariable(22); + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; } + + var coordScreen = GM.CreateVariable(4); + var dwConSize = CONSOLE_SCREEN_BUFFER_INFO.Deref(0, 2).toBuffer().readUInt16LE(0) * CONSOLE_SCREEN_BUFFER_INFO.Deref(2, 2).toBuffer().readUInt16LE(0); + var cCharsWritten = GM.CreateVariable(4); + + // Fill the entire screen with blanks. + if (this._kernel32.FillConsoleOutputCharacterA(this._stdoutput, 32, dwConSize, coordScreen.Deref(0, 4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; } + + // Get the current text attribute. + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; } + + // Set the buffer's attributes accordingly. + if (this._kernel32.FillConsoleOutputAttribute(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO.Deref(8, 2).toBuffer().readUInt16LE(0), dwConSize, coordScreen.Deref(0, 4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; } + + // Put the cursor at its home coordinates. + this._kernel32.SetConsoleCursorPosition(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE()); + + // Put the window to top-left. + var rect = GM.CreateVariable(8); + var srWindow = CONSOLE_SCREEN_BUFFER_INFO.Deref(10, 8).toBuffer(); + rect.Deref(4, 2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(4) - srWindow.readUInt16LE(0)); + rect.Deref(6, 2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(6) - srWindow.readUInt16LE(2)); + + this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect); + } + + this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT) { + if (this._kernel32.GetConsoleWindow().Val == 0) { + if (this._kernel32.AllocConsole().Val == 0) { + throw ('AllocConsole failed with: ' + this._kernel32.GetLastError().Val); + } + } + + this._stdinput = this._kernel32.GetStdHandle(STD_INPUT_HANDLE); + this._stdoutput = this._kernel32.GetStdHandle(STD_OUTPUT_HANDLE); + this._connected = false; + var coordScreen = GM.CreateVariable(4); + coordScreen.Deref(0, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH); + coordScreen.Deref(2, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT); + + var rect = GM.CreateVariable(8); + rect.Deref(4, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH - 1); + rect.Deref(6, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT - 1); + + if (this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect).Val == 0) { + throw ('Failed to set Console Screen Size'); + } + if (this._kernel32.SetConsoleScreenBufferSize(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE()).Val == 0) { + throw ('Failed to set Console Buffer Size'); + } + + // Hide the console window + this._user32.ShowWindow(this._kernel32.GetConsoleWindow().Val, SW_HIDE); + + this.ClearScreen(); + this._hookThread().then(function () { + // Hook Ready + this.terminal.StartCommand(); + }, console.log); + this._stream = new duplex({ + 'write': function (chunk, flush) { + if (!this.terminal.connected) { + //console.log('_write: ' + chunk); + if (!this._promise.chunk) { + this._promise.chunk = []; + } + if (typeof (chunk) == 'string') { + this._promise.chunk.push(chunk); + } else { + this._promise.chunk.push(Buffer.alloc(chunk.length)); + chunk.copy(this._promise.chunk.peek()); + } + this._promise.chunk.peek().flush = flush; + this._promise.then(function () { + var buf; + while (this.chunk.length > 0) { + buf = this.chunk.shift(); + this.terminal._WriteBuffer(buf); + buf.flush(); + } + }); + } + else { + //console.log('writeNOW: ' + chunk); + this.terminal._WriteBuffer(chunk); + flush(); + } + }, + 'final': function (flush) { + var p = this.terminal._stop(); + p.__flush = flush; + p.then(function () { this.__flush(); }); + } + }); + this._stream.terminal = this; + this._stream._promise = new promise(function (res, rej) { this._res = res; this._rej = rej; }); + this._stream._promise.terminal = this; + return (this._stream); + }; + this._stop = function () { + if (this.stopping) { return (this.stopping); } + //console.log('Stopping Terminal...'); + this.stopping = new promise(function (res, rej) { this._res = res; this._rej = rej; }); + + var threadID = this._kernel32.GetThreadId(this._user32.SetWinEventHook.async.thread()).Val; + this._user32.PostThreadMessageA(threadID, WM_QUIT, 0, 0); + return (this.stopping); + } + + this._hookThread = function () { + var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; }); + ret.terminal = this; + this._ConsoleWinEventProc = GM.GetGenericGlobalCallback(7); + this._ConsoleWinEventProc.terminal = this; + var p = this._user32.SetWinEventHook.async(EVENT_CONSOLE_CARET, EVENT_CONSOLE_END_APPLICATION, 0, this._ConsoleWinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + p.ready = ret; + p.terminal = this; + p.then(function (hwinEventHook) { + if (hwinEventHook.Val == 0) { + this.ready._rej('Error calling SetWinEventHook'); + } else { + this.terminal.hwinEventHook = hwinEventHook; + this.ready._res(); + this.terminal._GetMessage(); + } + }); + + this._ConsoleWinEventProc.on('GlobalCallback', function (hhook, dwEvent, hwnd, idObject, idChild, idEventThread, swmsEventTime) { + if (!this.terminal.hwinEventHook || this.terminal.hwinEventHook.Val != hhook.Val) { return; } + var buffer = null; + + switch (dwEvent.Val) { + case EVENT_CONSOLE_CARET: + break; + case EVENT_CONSOLE_UPDATE_REGION: + if (!this.terminal.connected) { + this.terminal.connected = true; + this.terminal._stream._promise._res(); + } + if (this.terminal._scrollTimer == null) { + buffer = this.terminal._GetScreenBuffer(LOWORD(idObject.Val), HIWORD(idObject.Val), LOWORD(idChild.Val), HIWORD(idChild.Val)); + //console.log('UPDATE REGION: [Left: ' + LOWORD(idObject.Val) + ' Top: ' + HIWORD(idObject.Val) + ' Right: ' + LOWORD(idChild.Val) + ' Bottom: ' + HIWORD(idChild.Val) + ']'); + this.terminal._SendDataBuffer(buffer); + } + break; + case EVENT_CONSOLE_UPDATE_SIMPLE: + //console.log('UPDATE SIMPLE: [X: ' + LOWORD(idObject.Val) + ' Y: ' + HIWORD(idObject.Val) + ' Char: ' + LOWORD(idChild.Val) + ' Attr: ' + HIWORD(idChild.Val) + ']'); + var simplebuffer = { data: [ Buffer.alloc(1, LOWORD(idChild.Val)) ], attributes: [ HIWORD(idChild.Val) ], width: 1, height: 1, x: LOWORD(idObject.Val), y: HIWORD(idObject.Val) }; + this.terminal._SendDataBuffer(simplebuffer); + break; + case EVENT_CONSOLE_UPDATE_SCROLL: + //console.log('UPDATE SCROLL: [dx: ' + idObject.Val + ' dy: ' + idChild.Val + ']'); + this.terminal._SendScroll(idObject.Val, idChild.Val); + break; + case EVENT_CONSOLE_LAYOUT: + //console.log('CONSOLE_LAYOUT'); + //snprintf( Buf, 512, "Event Console LAYOUT!\r\n"); + //SendLayout(); + break; + case EVENT_CONSOLE_START_APPLICATION: + //console.log('START APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']'); + //snprintf( Buf, 512, "Event Console START APPLICATION!\r\nProcess ID: %d - Child ID: %d\r\n\r\n", (int)idObject, (int)idChild); + //SendConsoleEvent(dwEvent, idObject, idChild); + break; + case EVENT_CONSOLE_END_APPLICATION: + if (idObject.Val == this.terminal._hProcessID) { + //console.log('END APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']'); + this.terminal._stop().then(function () { console.log('STOPPED'); }); + } + break; + default: + //snprintf(Buf, 512, "unknown console event.\r\n"); + console.log('Unknown event: ' + dwEvent.Val); + break; + } + + //mbstowcs_s(&l, wBuf, Buf, 512); + //OutputDebugString(wBuf); + + }); + return (ret); + } + + this._GetMessage = function () { + if (this._user32.abort) { console.log('aborting loop'); return; } + this._user32.GetMessageA.async(this._user32.SetWinEventHook.async, MSG, 0, 0, 0).then(function (ret) { + //console.log('GetMessage Response'); + if (ret.Val != 0) { + if (ret.Val == -1) { + // handle the error and possibly exit + } else { + //console.log('TranslateMessage'); + this.nativeProxy._user32.TranslateMessage.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function () { + //console.log('DispatchMessage'); + this.nativeProxy._user32.DispatchMessageA.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function () { + this.nativeProxy.terminal._GetMessage(); + }, console.log); + }, console.log); + } + } else { + this.nativeProxy.UnhookWinEvent.async(this.nativeProxy.terminal._user32.SetWinEventHook.async, this.nativeProxy.terminal.hwinEventHook) + .then(function () { + this.nativeProxy.terminal.stopping._res(); + if (this.nativeProxy.terminal._kernel32.TerminateProcess(this.nativeProxy.terminal._hProcess, 1067).Val == 0) { + var e = this.nativeProxy.terminal._kernel32.GetLastError().Val; + console.log('Unable to kill Terminal Process, error: ' + e); + } + this.nativeProxy.terminal.stopping = null; + }, function (err) { + console.log('REJECTED_UnhookWinEvent: ' + err); + }); + } + }, function (err) { + // Get Message Failed + console.log('REJECTED_GETMessage: ' + err); + }); + } + this._WriteBuffer = function (buf) { + for (var i = 0; i < buf.length; ++i) { + if (typeof (buf) == 'string') { + this._WriteCharacter(buf.charCodeAt(i), false); + } else { + this._WriteCharacter(buf[i], false); + } + } + } + this._WriteCharacter = function (key, bControlKey) { + var rec = GM.CreateVariable(20); + rec.Deref(0, 2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType + rec.Deref(4, 4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown + rec.Deref(16, 4).toBuffer().writeUInt32LE(bControlKey); // rec.Event.KeyEvent.dwControlKeyState + rec.Deref(14, 1).toBuffer()[0] = key; // rec.Event.KeyEvent.uChar.AsciiChar + rec.Deref(8, 2).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.wRepeatCount + rec.Deref(10, 2).toBuffer().writeUInt16LE(this._user32.VkKeyScanA(key).Val); // rec.Event.KeyEvent.wVirtualKeyCode + rec.Deref(12, 2).toBuffer().writeUInt16LE(this._user32.MapVirtualKeyA(this._user32.VkKeyScanA(key).Val, MAPVK_VK_TO_VSC).Val); + + var dwWritten = GM.CreateVariable(4); + if (this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val == 0) { return (false); } + + rec.Deref(4, 4).toBuffer().writeUInt16LE(0); // rec.Event.KeyEvent.bKeyDown + return (this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val != 0); + } + + // Get the current visible screen buffer + this._GetScreenBuffer = function (sx, sy, ex, ey) { + var info = GM.CreateVariable(22); + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); } + + var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1; + var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1; + + if (arguments[3] == null) { + // Use Default Parameters + sx = 0; + sy = 0; + ex = nWidth - 1; + ey = nHeight - 1; + } else { + if (this._scrx != 0) { sx += this._scrx; ex += this._scrx; } + if (this._scry != 0) { sy += this._scry; ey += this._scry; } + this._scrx = this._scry = 0; + } + + var nBuffer = GM.CreateVariable((ex - sx + 1) * (ey - sy + 1) * 4); + var size = GM.CreateVariable(4); + size.Deref(0, 2).toBuffer().writeUInt16LE(ex - sx + 1, 0); + size.Deref(2, 2).toBuffer().writeUInt16LE(ey - sy + 1, 0); + + var startCoord = GM.CreateVariable(4); + startCoord.Deref(0, 2).toBuffer().writeUInt16LE(0, 0); + startCoord.Deref(2, 2).toBuffer().writeUInt16LE(0, 0); + + var region = GM.CreateVariable(8); + region.buffer = region.toBuffer(); + region.buffer.writeUInt16LE(sx, 0); + region.buffer.writeUInt16LE(sy, 2); + region.buffer.writeUInt16LE(ex, 4); + region.buffer.writeUInt16LE(ey, 6); + + if (this._kernel32.ReadConsoleOutputA(this._stdoutput, nBuffer, size.Deref(0, 4).toBuffer().readUInt32LE(), startCoord.Deref(0, 4).toBuffer().readUInt32LE(), region).Val == 0) { + throw ('Unable to read Console Output'); + } + + // Lets convert the buffer into something simpler + //var retVal = { data: Buffer.alloc((dw - dx + 1) * (dh - dy + 1)), attributes: Buffer.alloc((dw - dx + 1) * (dh - dy + 1)), width: dw - dx + 1, height: dh - dy + 1, x: dx, y: dy }; + + var retVal = { data: [], attributes: [], width: ex - sx + 1, height: ey - sy + 1, x: sx, y: sy }; + var x, y, line, ifo, tmp, lineWidth = ex - sx + 1; + + for (y = 0; y <= (ey - sy) ; ++y) { + retVal.data.push(Buffer.alloc(lineWidth)); + retVal.attributes.push(Buffer.alloc(lineWidth)); + + line = nBuffer.Deref(y * lineWidth * 4, lineWidth * 4).toBuffer(); + for (x = 0; x < lineWidth; ++x) { + retVal.data.peek()[x] = line[x * 4]; + retVal.attributes.peek()[x] = line[2 + (x * 4)]; + } + } + + return (retVal); + } + + this._SendDataBuffer = function (data) { + // { data, attributes, width, height, x, y } + + var dy, line, attr; + for (dy = 0; dy < data.height; ++dy) { + line = data.data[dy]; + attr = data.attributes[dy]; + line.s = line.toString(); + + //line = data.data.slice(data.width * dy, (data.width * dy) + data.width); + //attr = data.attributes.slice(data.width * dy, (data.width * dy) + data.width); + this._stream.push(TranslateLine(data.x + 1, data.y + dy + 1, line, attr)); + } + } + + this._SendScroll = function _SendScroll(dx, dy) { + if (this._scrollTimer) { return; } + + var info = GM.CreateVariable(22); + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); } + + var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1; + var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1; + + this._stream.push(GetEsc('H', [nHeight - 1, 0])); + for (var i = 0; i > nHeight; ++i) { this._stream.push(Buffer.from('\r\n')); } + + var buffer = this._GetScreenBuffer(0, 0, nWidth - 1, nHeight - 1); + this._SendDataBuffer(buffer); + + this._scrollTimer = setTimeout(function (self, nw, nh) { + var buffer = self._GetScreenBuffer(0, 0, nw - 1, nh - 1); + self._SendDataBuffer(buffer); + self._scrollTimer = null; + }, 250, this, nWidth, nHeight); + } + + this.StartCommand = function StartCommand() { + if (this._kernel32.CreateProcessA(GM.CreateVariable(process.env['windir'] + '\\system32\\cmd.exe'), 0, 0, 0, 1, CREATE_NEW_PROCESS_GROUP, 0, 0, si, pi).Val == 0) { + console.log('Error Spawning CMD'); + return; + } + + this._kernel32.CloseHandle(pi.Deref(GM.PointerSize, GM.PointerSize).Deref()); // pi.hThread + this._hProcess = pi.Deref(0, GM.PointerSize).Deref(); // pi.hProcess + this._hProcessID = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE(); // pi.dwProcessId + //console.log('Ready => hProcess: ' + this._hProcess._ptr + ' PID: ' + this._hProcessID); + } +} + +function LOWORD(val) { return (val & 0xFFFF); } +function HIWORD(val) { return ((val >> 16) & 0xFFFF); } +function GetEsc(op, args) { return (Buffer.from('\x1B[' + args.join(';') + op)); } +function MeshConsole(msg) { require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": JSON.stringify(msg) }); } +function TranslateLine(x, y, data, attributes) { + var i, fcolor, bcolor, rcolor, fbright, bbright, lastAttr, fc, bc, rc, fb, bb, esc = [], output = [GetEsc('H', [y, x])]; + if (typeof attributes == 'number') { attributes = [ attributes ]; } // If we get a single attribute, turn it into an array. + + for (i = 0; i < data.length; i++) { + if (lastAttr != attributes[i]) { // To boost performance, if the attribute is the same as the last one, skip this entire part. + fc = (attributes[i] & 0x0007); + fc = ((fc & 0x0001) << 2) + (fc & 0x0002) + ((fc & 0x0004) >> 2); // Foreground color + bc = (attributes[i] & 0x0070) >> 4; + bc = ((bc & 0x0001) << 2) + (bc & 0x0002) + ((bc & 0x0004) >> 2); // Background color + rc = (attributes[i] & 0x4000); // Reverse color set + fb = (attributes[i] & 0x0008) >> 3; // Bright foreground set + bb = (attributes[i] & 0x0080); // Bright background set + + if (rc != rcolor) { if (rc != 0) { esc.push(7); } else { esc.push(0); fcolor = 7; bcolor = 0; fbright = 0; bbright = 0; } rcolor = rc; } // Reverse Color + if (fc != fcolor) { esc.push(fc + 30); fcolor = fc; } // Set the foreground color if needed + if (bc != bcolor) { esc.push(bc + 40); bcolor = bc; } // Set the background color if needed + if (fb != fbright) { esc.push(2 - fb); fbright = fb; } // Set the bright foreground color if needed + if (bb != bbright) { if (bb == 0) { esc.push(bcolor + 40); } else { esc.push(bcolor + 100); bbright = bb; } } // Set bright Background color if needed + + if (esc.length > 0) { output.push(GetEsc('m', esc)); esc = []; } + lastAttr = attributes[i]; + } + output.push(Buffer.from(String.fromCharCode(data[i]))); + } + + return Buffer.concat(output); +} + +module.exports = new windows_terminal(); \ No newline at end of file