EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
win-terminal.js
Go to the documentation of this file.
1/*
2Copyright 2018-2022 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17var promise = require('promise');
18var duplex = require('stream').Duplex;
19
20var SW_HIDE = 0;
21var SW_MINIMIZE = 6;
22var STARTF_USESHOWWINDOW = 0x1;
23var STD_INPUT_HANDLE = -10;
24var STD_OUTPUT_HANDLE = -11;
25var EVENT_CONSOLE_CARET = 0x4001;
26var EVENT_CONSOLE_END_APPLICATION = 0x4007;
27var WINEVENT_OUTOFCONTEXT = 0x000;
28var WINEVENT_SKIPOWNPROCESS = 0x0002;
29var CREATE_NEW_PROCESS_GROUP = 0x200;
30var EVENT_CONSOLE_UPDATE_REGION = 0x4002;
31var EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003;
32var EVENT_CONSOLE_UPDATE_SCROLL = 0x4004;
33var EVENT_CONSOLE_LAYOUT = 0x4005;
34var EVENT_CONSOLE_START_APPLICATION = 0x4006;
35var KEY_EVENT = 0x1;
36var MAPVK_VK_TO_VSC = 0;
37var WM_QUIT = 0x12;
38
39var GM = require('_GenericMarshal');
40var si = GM.CreateVariable(GM.PointerSize == 4 ? 68 : 104);
41var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24);
42
43si.Deref(0, 4).toBuffer().writeUInt32LE(GM.PointerSize == 4 ? 68 : 104); // si.cb
44si.Deref(GM.PointerSize == 4 ? 48 : 64, 2).toBuffer().writeUInt16LE(SW_HIDE | SW_MINIMIZE); // si.wShowWindow
45si.Deref(GM.PointerSize == 4 ? 44 : 60, 4).toBuffer().writeUInt32LE(STARTF_USESHOWWINDOW); // si.dwFlags;
46
47var MSG = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48);
48
49function windows_terminal() {
50 this._ObjectID = 'windows_terminal';
51 this._user32 = GM.CreateNativeProxy('User32.dll');
52 this._user32.CreateMethod('DispatchMessageA');
53 this._user32.CreateMethod('GetMessageA');
54 this._user32.CreateMethod('MapVirtualKeyA');
55 this._user32.CreateMethod('PostThreadMessageA');
56 this._user32.CreateMethod('SetWinEventHook');
57 this._user32.CreateMethod('ShowWindow');
58 this._user32.CreateMethod('TranslateMessage');
59 this._user32.CreateMethod('UnhookWinEvent');
60 this._user32.CreateMethod('VkKeyScanA');
61 this._user32.terminal = this;
62
63 this._kernel32 = GM.CreateNativeProxy('Kernel32.dll');
64 this._kernel32.CreateMethod('AllocConsole');
65 this._kernel32.CreateMethod('CreateProcessA');
66 this._kernel32.CreateMethod('CloseHandle');
67 this._kernel32.CreateMethod('FillConsoleOutputAttribute');
68 this._kernel32.CreateMethod('FillConsoleOutputCharacterA');
69 this._kernel32.CreateMethod('GetConsoleScreenBufferInfo');
70 this._kernel32.CreateMethod('GetConsoleWindow');
71 this._kernel32.CreateMethod('GetLastError');
72 this._kernel32.CreateMethod('GetStdHandle');
73 this._kernel32.CreateMethod('GetThreadId');
74 this._kernel32.CreateMethod('ReadConsoleOutputA');
75 this._kernel32.CreateMethod('SetConsoleCursorPosition');
76 this._kernel32.CreateMethod('SetConsoleScreenBufferSize');
77 this._kernel32.CreateMethod('SetConsoleWindowInfo');
78 this._kernel32.CreateMethod('TerminateProcess');
79 this._kernel32.CreateMethod('WaitForSingleObject');
80 this._kernel32.CreateMethod('WriteConsoleInputA');
81
82 var currentX = 0;
83 var currentY = 0;
84
85 this._scrx = 0;
86 this._scry = 0;
87
88 this.SendCursorUpdate = function () {
89 var newCsbi = GM.CreateVariable(22);
90
91 if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, newCsbi).Val == 0) { return; }
92 if (newCsbi.Deref(4, 2).toBuffer().readUInt16LE() != this.currentX || newCsbi.Deref(6, 2).toBuffer().readUInt16LE() != this.currentY)
93 {
94 //
95 // Reference for CONSOLE_SCREEN_BUFFER_INFO can be found at:
96 // https://learn.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
97 //
98
99 this.currentX = newCsbi.Deref(4, 2).toBuffer().readUInt16LE();
100 this.currentY = newCsbi.Deref(6, 2).toBuffer().readUInt16LE();
101 }
102 }
103
104 this.ClearScreen = function ()
105 {
106 //
107 // Reference for CONSOLE_SCREEN_BUFFER_INFO can be found at:
108 // https://learn.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
109 //
110
111 //
112 // Reference for GetConsoleScreenBufferInfo can be found at:
113 // https://learn.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
114 //
115
116 //
117 // Reference for FillConsoleOutputCharacter can be found at:
118 // https://learn.microsoft.com/en-us/windows/console/fillconsoleoutputcharacter
119 //
120
121 //
122 // Reference for FillConsoleOutputAttribute can be found at:
123 // https://learn.microsoft.com/en-us/windows/console/fillconsoleoutputattribute
124 //
125
126 //
127 // Reference for SetConsoleCursorPosition can be found at:
128 // https://learn.microsoft.com/en-us/windows/console/setconsolecursorposition
129 //
130
131 //
132 // Reference for SetConsoleWindowInfo can be fount at:
133 // https://learn.microsoft.com/en-us/windows/console/setconsolewindowinfo
134 //
135
136 var CONSOLE_SCREEN_BUFFER_INFO = GM.CreateVariable(22);
137 if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; }
138
139 var coordScreen = GM.CreateVariable(4);
140 var dwConSize = CONSOLE_SCREEN_BUFFER_INFO.Deref(0, 2).toBuffer().readUInt16LE(0) * CONSOLE_SCREEN_BUFFER_INFO.Deref(2, 2).toBuffer().readUInt16LE(0);
141 var cCharsWritten = GM.CreateVariable(4);
142
143 // Fill the entire screen with blanks.
144 if (this._kernel32.FillConsoleOutputCharacterA(this._stdoutput, 32, dwConSize, coordScreen.Deref(0, 4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; }
145
146 // Get the current text attribute.
147 if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; }
148
149 // Set the buffer's attributes accordingly.
150 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; }
151
152 // Put the cursor at its home coordinates.
153 this._kernel32.SetConsoleCursorPosition(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE());
154
155 // Put the window to top-left.
156 var rect = GM.CreateVariable(8);
157 var srWindow = CONSOLE_SCREEN_BUFFER_INFO.Deref(10, 8).toBuffer();
158 rect.Deref(4, 2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(4) - srWindow.readUInt16LE(0));
159 rect.Deref(6, 2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(6) - srWindow.readUInt16LE(2));
160
161 this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect);
162 }
163
164 // This does a rudimentary check if the platform is capable of PowerShell
165 this.PowerShellCapable = function()
166 {
167 if (require('os').arch() == 'x64')
168 {
169 return (require('fs').existsSync(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe'));
170 }
171 else
172 {
173 return (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'));
174 }
175 }
176
177 // Starts a Legacy Windows Terminal Session
178 this.StartEx = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT, terminalTarget)
179 {
180 // The older windows terminal does not support
181 CONSOLE_SCREEN_WIDTH = 80;
182 CONSOLE_SCREEN_HEIGHT = 25;
183
184 if (this._stream != null)
185 {
186 throw ('Concurrent terminal sessions are not supported on Windows.');
187 }
188 this.stopping = null;
189 if (this._kernel32.GetConsoleWindow().Val == 0) {
190 if (this._kernel32.AllocConsole().Val == 0) {
191 throw ('AllocConsole failed with: ' + this._kernel32.GetLastError().Val);
192 }
193 }
194
195 this._stdinput = this._kernel32.GetStdHandle(STD_INPUT_HANDLE);
196 this._stdoutput = this._kernel32.GetStdHandle(STD_OUTPUT_HANDLE);
197 this._connected = false;
198
199 // Coord structure can be found at: https://learn.microsoft.com/en-us/windows/console/coord-str
200 var coordScreen = GM.CreateVariable(4);
201 coordScreen.Deref(0, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH);
202 coordScreen.Deref(2, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT);
203
204 var rect = GM.CreateVariable(8);
205 rect.Deref(4, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH - 1);
206 rect.Deref(6, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT - 1);
207
208 //
209 // Reference for SetConsoleWindowInfo can be found at:
210 // https://learn.microsoft.com/en-us/windows/console/setconsolewindowinfo
211 //
212 if (this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect).Val == 0)
213 {
214 throw ('Failed to set Console Screen Size');
215 }
216
217 //
218 // Reference for SetConsoleScreenBufferSize can be found at:
219 // https://learn.microsoft.com/en-us/windows/console/setconsolescreenbuffersize
220 //
221 if (this._kernel32.SetConsoleScreenBufferSize(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE()).Val == 0)
222 {
223 throw ('Failed to set Console Buffer Size');
224 }
225
226 // Hide the console window
227 this._user32.ShowWindow(this._kernel32.GetConsoleWindow().Val, SW_HIDE);
228
229 this.ClearScreen();
230 this._hookThread(terminalTarget).then(function ()
231 {
232 // Hook Ready
233 this.terminal.StartCommand(this.userArgs[0]);
234 }, console.log);
235 this._stream = new duplex(
236 {
237 'write': function (chunk, flush)
238 {
239 if (!this.terminal.connected)
240 {
241 //console.log('_write: ' + chunk);
242 if (!this._promise.chunk)
243 {
244 this._promise.chunk = [];
245 }
246 if (typeof (chunk) == 'string')
247 {
248 this._promise.chunk.push(chunk);
249 } else
250 {
251 this._promise.chunk.push(Buffer.alloc(chunk.length));
252 chunk.copy(this._promise.chunk.peek());
253 }
254 this._promise.chunk.peek().flush = flush;
255 this._promise.then(function ()
256 {
257 var buf;
258 while (this.chunk.length > 0)
259 {
260 buf = this.chunk.shift();
261 this.terminal._WriteBuffer(buf);
262 buf.flush();
263 }
264 });
265 }
266 else
267 {
268 //console.log('writeNOW: ' + chunk);
269 this.terminal._WriteBuffer(chunk);
270 flush();
271 }
272 return (true);
273 },
274 'final': function (flush)
275 {
276 var p = this.terminal._stop();
277 p.__flush = flush;
278 p.then(function () { this.__flush(); });
279 }
280 });
281 this._stream.terminal = this;
282 this._stream._promise = new promise(function (res, rej) { this._res = res; this._rej = rej; });
283 this._stream._promise.terminal = this;
284 this._stream.prependOnceListener('end', function ()
285 {
286 this.terminal._stream = null;
287 });
288 return (this._stream);
289 };
290 this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
291 {
292 return (this.StartEx(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT, process.env['windir'] + '\\System32\\cmd.exe'));
293 }
294 this.StartPowerShell = function StartPowerShell(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
295 {
296 if (require('os').arch() == 'x64')
297 {
298 if (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'))
299 {
300 return (this.StartEx(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT, process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'));
301 }
302 else
303 {
304 return (this.StartEx(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT, process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe'));
305 }
306 }
307 else
308 {
309 return (this.StartEx(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT, process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'));
310 }
311 }
312
313 this._stop = function () {
314 if (this.stopping) { return (this.stopping); }
315 //console.log('Stopping Terminal...');
316 this._ConsoleWinEventProc.removeAllListeners('GlobalCallback');
317 this.stopping = new promise(function (res, rej) { this._res = res; this._rej = rej; });
318
319 var threadID = this._kernel32.GetThreadId(this._user32.SetWinEventHook.async.thread()).Val;
320 this._user32.PostThreadMessageA(threadID, WM_QUIT, 0, 0);
321 this._stream.emit('end');
322 return (this.stopping);
323 }
324
325 //
326 // This function uses the SetWinEventHook() method, so we can hook
327 // All events between EVENT_CONSOLE_CARET and EVENT_CONSOLE_END_APPLICATION
328 //
329 this._hookThread = function ()
330 {
331 //
332 // Reference for SetWinEventHook() can be found at:
333 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook
334 //
335 var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
336 ret.userArgs = [];
337 for (var a in arguments)
338 {
339 ret.userArgs.push(arguments[a]);
340 }
341 ret.terminal = this;
342 this._ConsoleWinEventProc = GM.GetGenericGlobalCallback(7);
343 this._ConsoleWinEventProc.terminal = this;
344 var p = this._user32.SetWinEventHook.async(EVENT_CONSOLE_CARET, EVENT_CONSOLE_END_APPLICATION, 0, this._ConsoleWinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
345 p.ready = ret;
346 p.terminal = this;
347 p.then(function (hwinEventHook)
348 {
349 if (hwinEventHook.Val == 0)
350 {
351 this.ready._rej('Error calling SetWinEventHook');
352 } else
353 {
354 this.terminal.hwinEventHook = hwinEventHook;
355 this.ready._res();
356 this.terminal._GetMessage();
357 }
358 });
359
360 //
361 // This is the WINEVENTPROC callback for the WinEventHook we set
362 //
363 this._ConsoleWinEventProc.on('GlobalCallback', function (hhook, dwEvent, hwnd, idObject, idChild, idEventThread, swmsEventTime)
364 {
365 //
366 // Reference for WINEVENTPROC can be found at:
367 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wineventproc
368 //
369 if (!this.terminal.hwinEventHook || this.terminal.hwinEventHook.Val != hhook.Val) { return; }
370 var buffer = null;
371
372 //
373 // Reference for Console WinEvents can be found at:
374 // https://learn.microsoft.com/en-us/windows/console/console-winevents
375 //
376
377 switch (dwEvent.Val)
378 {
379 case EVENT_CONSOLE_CARET:
380 // The console caret has moved
381 break;
382 case EVENT_CONSOLE_UPDATE_REGION:
383 // More than one character has changed
384 if (!this.terminal.connected) {
385 this.terminal.connected = true;
386 this.terminal._stream._promise._res();
387 }
388 if (this.terminal._scrollTimer == null) {
389 buffer = this.terminal._GetScreenBuffer(LOWORD(idObject.Val), HIWORD(idObject.Val), LOWORD(idChild.Val), HIWORD(idChild.Val));
390 //console.log('UPDATE REGION: [Left: ' + LOWORD(idObject.Val) + ' Top: ' + HIWORD(idObject.Val) + ' Right: ' + LOWORD(idChild.Val) + ' Bottom: ' + HIWORD(idChild.Val) + ']');
391 this.terminal._SendDataBuffer(buffer);
392 }
393 break;
394 case EVENT_CONSOLE_UPDATE_SIMPLE:
395 // A single character has changed
396 //console.log('UPDATE SIMPLE: [X: ' + LOWORD(idObject.Val) + ' Y: ' + HIWORD(idObject.Val) + ' Char: ' + LOWORD(idChild.Val) + ' Attr: ' + HIWORD(idChild.Val) + ']');
397 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) };
398 this.terminal._SendDataBuffer(simplebuffer);
399 break;
400 case EVENT_CONSOLE_UPDATE_SCROLL:
401 // The console has scrolled
402 //console.log('UPDATE SCROLL: [dx: ' + idObject.Val + ' dy: ' + idChild.Val + ']');
403 this.terminal._SendScroll(idObject.Val, idChild.Val);
404 break;
405 case EVENT_CONSOLE_LAYOUT:
406 // The console layout has changed.
407 //console.log('CONSOLE_LAYOUT');
408 //snprintf( Buf, 512, "Event Console LAYOUT!\r\n");
409 //SendLayout();
410 break;
411 case EVENT_CONSOLE_START_APPLICATION:
412 // A new console process has started
413 //console.log('START APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']');
414 //snprintf( Buf, 512, "Event Console START APPLICATION!\r\nProcess ID: %d - Child ID: %d\r\n\r\n", (int)idObject, (int)idChild);
415 //SendConsoleEvent(dwEvent, idObject, idChild);
416 break;
417 case EVENT_CONSOLE_END_APPLICATION:
418 // A console process has exited
419 if (idObject.Val == this.terminal._hProcessID)
420 {
421 //console.log('END APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']');
422 this.terminal._hProcess = null;
423 this.terminal._stop().then(function () { console.log('STOPPED'); });
424 }
425 break;
426 default:
427 //snprintf(Buf, 512, "unknown console event.\r\n");
428 console.log('Unknown event: ' + dwEvent.Val);
429 break;
430 }
431
432 //mbstowcs_s(&l, wBuf, Buf, 512);
433 //OutputDebugString(wBuf);
434
435 });
436 return (ret);
437 }
438
439 // Retrieves a message from the calling thread's message queue
440 this._GetMessage = function ()
441 {
442 //
443 // Reference for GetMessage() can be found at:
444 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage
445 //
446
447 //
448 // Reference for TranslateMessage() can be found at:
449 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-translatemessage
450 //
451
452 //
453 // Reference for DispatchMessage() can be found at:
454 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-dispatchmessage
455 //
456
457 if (this._user32.abort) { console.log('aborting loop'); return; }
458 this._user32.GetMessageA.async(this._user32.SetWinEventHook.async, MSG, 0, 0, 0).then(function (ret)
459 {
460 //console.log('GetMessage Response');
461 if (ret.Val != 0)
462 {
463 if (ret.Val == -1)
464 {
465 // handle the error and possibly exit
466 }
467 else
468 {
469 // Translates virtual-key messages into character messages
470 //console.log('TranslateMessage');
471 this.nativeProxy._user32.TranslateMessage.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function ()
472 {
473 // Dispatches a message to a window procedure
474 //console.log('DispatchMessage');
475 this.nativeProxy._user32.DispatchMessageA.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function ()
476 {
477 this.nativeProxy.terminal._GetMessage();
478 }, console.log);
479 }, console.log);
480 }
481 } else
482 {
483 this.nativeProxy.UnhookWinEvent.async(this.nativeProxy.terminal._user32.SetWinEventHook.async, this.nativeProxy.terminal.hwinEventHook)
484 .then(function ()
485 {
486 if (this.nativeProxy.terminal._hProcess == null) { return; }
487
488 this.nativeProxy.terminal.stopping._res();
489 if (this.nativeProxy.terminal._kernel32.TerminateProcess(this.nativeProxy.terminal._hProcess, 1067).Val == 0)
490 {
491 var e = this.nativeProxy.terminal._kernel32.GetLastError().Val;
492 console.log('Unable to kill Terminal Process, error: ' + e);
493 }
494 this.nativeProxy.terminal.stopping = null;
495 }, function (err)
496 {
497 console.log('REJECTED_UnhookWinEvent: ' + err);
498 });
499 }
500 }, function (err)
501 {
502 // Get Message Failed
503 console.log('REJECTED_GETMessage: ' + err);
504 });
505 }
506
507 this._WriteBuffer = function (buf)
508 {
509 for (var i = 0; i < buf.length; ++i)
510 {
511 if (typeof (buf) == 'string')
512 {
513 this._WriteCharacter(buf.charCodeAt(i), false);
514 } else
515 {
516 this._WriteCharacter(buf[i], false);
517 }
518 }
519 }
520 this._WriteCharacter = function (key, bControlKey)
521 {
522 //
523 // Reference for WriteConsoleInput() can be found at:
524 // https://learn.microsoft.com/en-us/windows/console/writeconsoleinput
525 //
526
527 //
528 // Reference for INPUT_RECORD can be found at:
529 // https://learn.microsoft.com/en-us/windows/console/input-record-str
530 //
531
532 var rec = GM.CreateVariable(20);
533 rec.Deref(0, 2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType
534 rec.Deref(4, 4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown
535 rec.Deref(16, 4).toBuffer().writeUInt32LE(bControlKey); // rec.Event.KeyEvent.dwControlKeyState
536 rec.Deref(14, 1).toBuffer()[0] = key; // rec.Event.KeyEvent.uChar.AsciiChar
537 rec.Deref(8, 2).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.wRepeatCount
538 rec.Deref(10, 2).toBuffer().writeUInt16LE(this._user32.VkKeyScanA(key).Val); // rec.Event.KeyEvent.wVirtualKeyCode
539 rec.Deref(12, 2).toBuffer().writeUInt16LE(this._user32.MapVirtualKeyA(this._user32.VkKeyScanA(key).Val, MAPVK_VK_TO_VSC).Val);
540
541 var dwWritten = GM.CreateVariable(4);
542 if (this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val == 0) { return (false); }
543
544 rec.Deref(4, 4).toBuffer().writeUInt16LE(0); // rec.Event.KeyEvent.bKeyDown
545 return (this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val != 0);
546 }
547
548 // Get the current visible screen buffer
549 this._GetScreenBuffer = function (sx, sy, ex, ey)
550 {
551 //
552 // Reference for GetConsoleScreenBufferInfo() can be found at:
553 // https://learn.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
554 //
555
556 //
557 // Reference for ReadConsoleOutput() can be found at:
558 // https://learn.microsoft.com/en-us/windows/console/readconsoleoutput
559 //
560
561 var info = GM.CreateVariable(22);
562 if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); }
563
564 var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1;
565 var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1;
566
567 if (arguments[3] == null)
568 {
569 // Use Default Parameters
570 sx = 0;
571 sy = 0;
572 ex = nWidth - 1;
573 ey = nHeight - 1;
574 } else
575 {
576 if (this._scrx != 0) { sx += this._scrx; ex += this._scrx; }
577 if (this._scry != 0) { sy += this._scry; ey += this._scry; }
578 this._scrx = this._scry = 0;
579 }
580
581 var nBuffer = GM.CreateVariable((ex - sx + 1) * (ey - sy + 1) * 4);
582 var size = GM.CreateVariable(4);
583 size.Deref(0, 2).toBuffer().writeUInt16LE(ex - sx + 1, 0);
584 size.Deref(2, 2).toBuffer().writeUInt16LE(ey - sy + 1, 0);
585
586 var startCoord = GM.CreateVariable(4);
587 startCoord.Deref(0, 2).toBuffer().writeUInt16LE(0, 0);
588 startCoord.Deref(2, 2).toBuffer().writeUInt16LE(0, 0);
589
590 var region = GM.CreateVariable(8);
591 region.buffer = region.toBuffer();
592 region.buffer.writeUInt16LE(sx, 0);
593 region.buffer.writeUInt16LE(sy, 2);
594 region.buffer.writeUInt16LE(ex, 4);
595 region.buffer.writeUInt16LE(ey, 6);
596
597 if (this._kernel32.ReadConsoleOutputA(this._stdoutput, nBuffer, size.Deref(0, 4).toBuffer().readUInt32LE(), startCoord.Deref(0, 4).toBuffer().readUInt32LE(), region).Val == 0)
598 {
599 throw ('Unable to read Console Output');
600 }
601
602 // Lets convert the buffer into something simpler
603 //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 };
604
605 var retVal = { data: [], attributes: [], width: ex - sx + 1, height: ey - sy + 1, x: sx, y: sy };
606 var x, y, line, ifo, tmp, lineWidth = ex - sx + 1;
607
608 for (y = 0; y <= (ey - sy) ; ++y)
609 {
610 retVal.data.push(Buffer.alloc(lineWidth));
611 retVal.attributes.push(Buffer.alloc(lineWidth));
612
613 line = nBuffer.Deref(y * lineWidth * 4, lineWidth * 4).toBuffer();
614 for (x = 0; x < lineWidth; ++x)
615 {
616 retVal.data.peek()[x] = line[x * 4];
617 retVal.attributes.peek()[x] = line[2 + (x * 4)];
618 }
619 }
620
621 return (retVal);
622 }
623
624 this._SendDataBuffer = function (data)
625 {
626 // { data, attributes, width, height, x, y }
627 if (this._stream != null)
628 {
629 var dy, line, attr;
630 for (dy = 0; dy < data.height; ++dy)
631 {
632 line = data.data[dy];
633 attr = data.attributes[dy];
634 line.s = line.toString();
635
636 //line = data.data.slice(data.width * dy, (data.width * dy) + data.width);
637 //attr = data.attributes.slice(data.width * dy, (data.width * dy) + data.width);
638 this._stream.push(TranslateLine(data.x + 1, data.y + dy + 1, line, attr));
639 }
640 }
641 }
642
643 this._SendScroll = function _SendScroll(dx, dy)
644 {
645 //
646 // Reference for GetConsoleScreenBufferInfo() can be found at:
647 // https://learn.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
648 //
649
650 if (this._scrollTimer || this._stream == null) { return; }
651
652 var info = GM.CreateVariable(22);
653 if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); }
654
655 var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1;
656 var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1;
657
658 this._stream.push(GetEsc('H', [nHeight - 1, 0]));
659 for (var i = 0; i > nHeight; ++i) { this._stream.push(Buffer.from('\r\n')); }
660
661 var buffer = this._GetScreenBuffer(0, 0, nWidth - 1, nHeight - 1);
662 this._SendDataBuffer(buffer);
663
664 this._scrollTimer = setTimeout(function (self, nw, nh) {
665 var buffer = self._GetScreenBuffer(0, 0, nw - 1, nh - 1);
666 self._SendDataBuffer(buffer);
667 self._scrollTimer = null;
668 }, 250, this, nWidth, nHeight);
669 }
670
671 this.StartCommand = function StartCommand(target) {
672 if (this._kernel32.CreateProcessA(GM.CreateVariable(target), 0, 0, 0, 1, CREATE_NEW_PROCESS_GROUP, 0, 0, si, pi).Val == 0)
673 {
674 console.log('Error Spawning CMD');
675 return;
676 }
677
678 this._kernel32.CloseHandle(pi.Deref(GM.PointerSize, GM.PointerSize).Deref()); // pi.hThread
679 this._hProcess = pi.Deref(0, GM.PointerSize).Deref(); // pi.hProcess
680 this._hProcessID = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE(); // pi.dwProcessId
681 //console.log('Ready => hProcess: ' + this._hProcess._ptr + ' PID: ' + this._hProcessID);
682 }
683}
684
685function LOWORD(val) { return (val & 0xFFFF); }
686function HIWORD(val) { return ((val >> 16) & 0xFFFF); }
687function GetEsc(op, args) { return (Buffer.from('\x1B[' + args.join(';') + op)); }
688function MeshConsole(msg) { require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": JSON.stringify(msg) }); }
689function TranslateLine(x, y, data, attributes)
690{
691 var i, fcolor, bcolor, rcolor, fbright, bbright, lastAttr, fc, bc, rc, fb, bb, esc = [], output = [GetEsc('H', [y, x])];
692 if (typeof attributes == 'number') { attributes = [attributes]; } // If we get a single attribute, turn it into an array.
693
694 for (i = 0; i < data.length; i++)
695 {
696 if (lastAttr != attributes[i])
697 { // To boost performance, if the attribute is the same as the last one, skip this entire part.
698 fc = (attributes[i] & 0x0007);
699 fc = ((fc & 0x0001) << 2) + (fc & 0x0002) + ((fc & 0x0004) >> 2); // Foreground color
700 bc = (attributes[i] & 0x0070) >> 4;
701 bc = ((bc & 0x0001) << 2) + (bc & 0x0002) + ((bc & 0x0004) >> 2); // Background color
702 rc = (attributes[i] & 0x4000); // Reverse color set
703 fb = (attributes[i] & 0x0008) >> 3; // Bright foreground set
704 bb = (attributes[i] & 0x0080); // Bright background set
705
706 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
707 if (fc != fcolor) { esc.push(fc + 30); fcolor = fc; } // Set the foreground color if needed
708 if (bc != bcolor) { esc.push(bc + 40); bcolor = bc; } // Set the background color if needed
709 if (fb != fbright) { esc.push(2 - fb); fbright = fb; } // Set the bright foreground color if needed
710 if (bb != bbright) { if (bb == 0) { esc.push(bcolor + 40); } else { esc.push(bcolor + 100); bbright = bb; } } // Set bright Background color if needed
711
712 if (esc.length > 0) { output.push(GetEsc('m', esc)); esc = []; }
713 lastAttr = attributes[i];
714 }
715 output.push(Buffer.from(String.fromCharCode(data[i])));
716 }
717
718 return Buffer.concat(output);
719}
720
721module.exports = new windows_terminal();