add lisp packages
This commit is contained in:
19
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/conpty_console_list_agent.ts
generated
vendored
Normal file
19
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/conpty_console_list_agent.ts
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
||||
*
|
||||
* This module fetches the console process list for a particular PID. It must be
|
||||
* called from a different process (child_process.fork) as there can only be a
|
||||
* single console attached to a process.
|
||||
*/
|
||||
|
||||
let getConsoleProcessList: any;
|
||||
try {
|
||||
getConsoleProcessList = require('../build/Release/conpty_console_list.node').getConsoleProcessList;
|
||||
} catch (err) {
|
||||
getConsoleProcessList = require('../build/Debug/conpty_console_list.node').getConsoleProcessList;
|
||||
}
|
||||
|
||||
const shellPid = parseInt(process.argv[2], 10);
|
||||
const consoleProcessList = getConsoleProcessList(shellPid);
|
||||
process.send({ consoleProcessList });
|
||||
process.exit(0);
|
||||
30
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/eventEmitter2.test.ts
generated
vendored
Normal file
30
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/eventEmitter2.test.ts
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { EventEmitter2 } from './eventEmitter2';
|
||||
|
||||
describe('EventEmitter2', () => {
|
||||
it('should fire listeners multiple times', () => {
|
||||
const order: string[] = [];
|
||||
const emitter = new EventEmitter2<number>();
|
||||
emitter.event(data => order.push(data + 'a'));
|
||||
emitter.event(data => order.push(data + 'b'));
|
||||
emitter.fire(1);
|
||||
emitter.fire(2);
|
||||
assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]);
|
||||
});
|
||||
|
||||
it('should not fire listeners once disposed', () => {
|
||||
const order: string[] = [];
|
||||
const emitter = new EventEmitter2<number>();
|
||||
emitter.event(data => order.push(data + 'a'));
|
||||
const disposeB = emitter.event(data => order.push(data + 'b'));
|
||||
emitter.event(data => order.push(data + 'c'));
|
||||
emitter.fire(1);
|
||||
disposeB.dispose();
|
||||
emitter.fire(2);
|
||||
assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]);
|
||||
});
|
||||
});
|
||||
48
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/eventEmitter2.ts
generated
vendored
Normal file
48
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/eventEmitter2.ts
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import { IDisposable } from './types';
|
||||
|
||||
interface IListener<T> {
|
||||
(e: T): void;
|
||||
}
|
||||
|
||||
export interface IEvent<T> {
|
||||
(listener: (e: T) => any): IDisposable;
|
||||
}
|
||||
|
||||
export class EventEmitter2<T> {
|
||||
private _listeners: IListener<T>[] = [];
|
||||
private _event?: IEvent<T>;
|
||||
|
||||
public get event(): IEvent<T> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => any) => {
|
||||
this._listeners.push(listener);
|
||||
const disposable = {
|
||||
dispose: () => {
|
||||
for (let i = 0; i < this._listeners.length; i++) {
|
||||
if (this._listeners[i] === listener) {
|
||||
this._listeners.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
return this._event;
|
||||
}
|
||||
|
||||
public fire(data: T): void {
|
||||
const queue: IListener<T>[] = [];
|
||||
for (let i = 0; i < this._listeners.length; i++) {
|
||||
queue.push(this._listeners[i]);
|
||||
}
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
queue[i].call(undefined, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/index.ts
generated
vendored
Normal file
51
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import { ITerminal, IPtyOpenOptions, IPtyForkOptions, IWindowsPtyForkOptions } from './interfaces';
|
||||
import { ArgvOrCommandLine } from './types';
|
||||
|
||||
let terminalCtor: any;
|
||||
if (process.platform === 'win32') {
|
||||
terminalCtor = require('./windowsTerminal').WindowsTerminal;
|
||||
} else {
|
||||
terminalCtor = require('./unixTerminal').UnixTerminal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forks a process as a pseudoterminal.
|
||||
* @param file The file to launch.
|
||||
* @param args The file's arguments as argv (string[]) or in a pre-escaped
|
||||
* CommandLine format (string). Note that the CommandLine option is only
|
||||
* available on Windows and is expected to be escaped properly.
|
||||
* @param options The options of the terminal.
|
||||
* @throws When the file passed to spawn with does not exists.
|
||||
* @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
|
||||
* @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||
* @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
|
||||
*/
|
||||
export function spawn(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {
|
||||
return new terminalCtor(file, args, opt);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export function fork(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {
|
||||
return new terminalCtor(file, args, opt);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export function createTerminal(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {
|
||||
return new terminalCtor(file, args, opt);
|
||||
}
|
||||
|
||||
export function open(options: IPtyOpenOptions): ITerminal {
|
||||
return terminalCtor.open(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the native API when not Windows, note that this is not public API and
|
||||
* could be removed at any time.
|
||||
*/
|
||||
export const native = (process.platform !== 'win32' ? require('../build/Release/pty.node') : null);
|
||||
136
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/interfaces.ts
generated
vendored
Normal file
136
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/interfaces.ts
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import * as net from 'net';
|
||||
|
||||
export interface IProcessEnv {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ITerminal {
|
||||
/**
|
||||
* Gets the name of the process.
|
||||
*/
|
||||
process: string;
|
||||
|
||||
/**
|
||||
* Gets the process ID.
|
||||
*/
|
||||
pid: number;
|
||||
|
||||
/**
|
||||
* The socket for the master file descriptor. This is not supported on
|
||||
* Windows.
|
||||
*/
|
||||
master: net.Socket;
|
||||
|
||||
/**
|
||||
* The socket for the slave file descriptor. This is not supported on Windows.
|
||||
*/
|
||||
slave: net.Socket;
|
||||
|
||||
/**
|
||||
* Writes data to the socket.
|
||||
* @param data The data to write.
|
||||
*/
|
||||
write(data: string): void;
|
||||
|
||||
/**
|
||||
* Resize the pty.
|
||||
* @param cols The number of columns.
|
||||
* @param rows The number of rows.
|
||||
*/
|
||||
resize(cols: number, rows: number): void;
|
||||
|
||||
/**
|
||||
* Close, kill and destroy the socket.
|
||||
*/
|
||||
destroy(): void;
|
||||
|
||||
/**
|
||||
* Kill the pty.
|
||||
* @param signal The signal to send, by default this is SIGHUP. This is not
|
||||
* supported on Windows.
|
||||
*/
|
||||
kill(signal?: string): void;
|
||||
|
||||
/**
|
||||
* Set the pty socket encoding.
|
||||
*/
|
||||
setEncoding(encoding: string | null): void;
|
||||
|
||||
/**
|
||||
* Resume the pty socket.
|
||||
*/
|
||||
resume(): void;
|
||||
|
||||
/**
|
||||
* Pause the pty socket.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Alias for ITerminal.on(eventName, listener).
|
||||
*/
|
||||
addListener(eventName: string, listener: (...args: any[]) => any): void;
|
||||
|
||||
/**
|
||||
* Adds the listener function to the end of the listeners array for the event
|
||||
* named eventName.
|
||||
* @param eventName The event name.
|
||||
* @param listener The callback function
|
||||
*/
|
||||
on(eventName: string, listener: (...args: any[]) => any): void;
|
||||
|
||||
/**
|
||||
* Returns a copy of the array of listeners for the event named eventName.
|
||||
*/
|
||||
listeners(eventName: string): Function[];
|
||||
|
||||
/**
|
||||
* Removes the specified listener from the listener array for the event named
|
||||
* eventName.
|
||||
*/
|
||||
removeListener(eventName: string, listener: (...args: any[]) => any): void;
|
||||
|
||||
/**
|
||||
* Removes all listeners, or those of the specified eventName.
|
||||
*/
|
||||
removeAllListeners(eventName: string): void;
|
||||
|
||||
/**
|
||||
* Adds a one time listener function for the event named eventName. The next
|
||||
* time eventName is triggered, this listener is removed and then invoked.
|
||||
*/
|
||||
once(eventName: string, listener: (...args: any[]) => any): void;
|
||||
}
|
||||
|
||||
interface IBasePtyForkOptions {
|
||||
name?: string;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
encoding?: string;
|
||||
handleFlowControl?: boolean;
|
||||
flowControlPause?: string;
|
||||
flowControlResume?: string;
|
||||
}
|
||||
|
||||
export interface IPtyForkOptions extends IBasePtyForkOptions {
|
||||
uid?: number;
|
||||
gid?: number;
|
||||
}
|
||||
|
||||
export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
|
||||
useConpty?: boolean;
|
||||
conptyInheritCursor?: boolean;
|
||||
}
|
||||
|
||||
export interface IPtyOpenOptions {
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
encoding?: string;
|
||||
}
|
||||
54
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/native.d.ts
generated
vendored
Normal file
54
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/native.d.ts
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
interface IConptyNative {
|
||||
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean): IConptyProcess;
|
||||
connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number };
|
||||
resize(ptyId: number, cols: number, rows: number): void;
|
||||
kill(ptyId: number): void;
|
||||
}
|
||||
|
||||
interface IWinptyNative {
|
||||
startProcess(file: string, commandLine: string, env: string[], cwd: string, cols: number, rows: number, debug: boolean): IWinptyProcess;
|
||||
resize(processHandle: number, cols: number, rows: number): void;
|
||||
kill(pid: number, innerPidHandle: number): void;
|
||||
getProcessList(pid: number): number[];
|
||||
getExitCode(innerPidHandle: number): number;
|
||||
}
|
||||
|
||||
interface IUnixNative {
|
||||
fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, onExitCallback: (code: number, signal: number) => void): IUnixProcess;
|
||||
open(cols: number, rows: number): IUnixOpenProcess;
|
||||
process(fd: number, pty: string): string;
|
||||
resize(fd: number, cols: number, rows: number): void;
|
||||
}
|
||||
|
||||
interface IConptyProcess {
|
||||
pty: number;
|
||||
fd: number;
|
||||
conin: string;
|
||||
conout: string;
|
||||
}
|
||||
|
||||
interface IWinptyProcess {
|
||||
pty: number;
|
||||
fd: number;
|
||||
conin: string;
|
||||
conout: string;
|
||||
pid: number;
|
||||
innerPid: number;
|
||||
innerPidHandle: number;
|
||||
}
|
||||
|
||||
interface IUnixProcess {
|
||||
fd: number;
|
||||
pid: number;
|
||||
pty: string;
|
||||
}
|
||||
|
||||
interface IUnixOpenProcess {
|
||||
master: number;
|
||||
slave: number;
|
||||
pty: string;
|
||||
}
|
||||
66
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/terminal.test.ts
generated
vendored
Normal file
66
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/terminal.test.ts
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { WindowsTerminal } from './windowsTerminal';
|
||||
import { UnixTerminal } from './unixTerminal';
|
||||
import { pollUntil } from './testUtils.test';
|
||||
|
||||
const terminalConstructor = (process.platform === 'win32') ? WindowsTerminal : UnixTerminal;
|
||||
const SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash';
|
||||
|
||||
let terminalCtor: WindowsTerminal | UnixTerminal;
|
||||
if (process.platform === 'win32') {
|
||||
terminalCtor = require('./windowsTerminal');
|
||||
} else {
|
||||
terminalCtor = require('./unixTerminal');
|
||||
}
|
||||
|
||||
|
||||
describe('Terminal', () => {
|
||||
describe('constructor', () => {
|
||||
it('should do basic type checks', () => {
|
||||
assert.throws(
|
||||
() => new (<any>terminalCtor)('a', 'b', { 'name': {} }),
|
||||
'name must be a string (not a object)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('automatic flow control', () => {
|
||||
it('should respect ctor flow control options', () => {
|
||||
const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123'});
|
||||
assert.equal(pty.handleFlowControl, true);
|
||||
assert.equal((pty as any)._flowControlPause, 'abc');
|
||||
assert.equal((pty as any)._flowControlResume, '123');
|
||||
});
|
||||
// TODO: I don't think this test ever worked due to pollUntil being used incorrectly
|
||||
// it('should do flow control automatically', async function(): Promise<void> {
|
||||
// // Flow control doesn't work on Windows
|
||||
// if (process.platform === 'win32') {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.timeout(10000);
|
||||
// const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME'});
|
||||
// let read: string = '';
|
||||
// pty.on('data', data => read += data);
|
||||
// pty.on('pause', () => read += 'paused');
|
||||
// pty.on('resume', () => read += 'resumed');
|
||||
// pty.write('1');
|
||||
// pty.write('PAUSE');
|
||||
// pty.write('2');
|
||||
// pty.write('RESUME');
|
||||
// pty.write('3');
|
||||
// await pollUntil(() => {
|
||||
// return stripEscapeSequences(read).endsWith('1pausedresumed23');
|
||||
// }, 100, 10);
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
function stripEscapeSequences(data: string): string {
|
||||
return data.replace(/\u001b\[0K/, '');
|
||||
}
|
||||
195
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/terminal.ts
generated
vendored
Normal file
195
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/terminal.ts
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import { Socket } from 'net';
|
||||
import { EventEmitter } from 'events';
|
||||
import { ITerminal, IPtyForkOptions } from './interfaces';
|
||||
import { EventEmitter2, IEvent } from './eventEmitter2';
|
||||
import { IExitEvent } from './types';
|
||||
|
||||
export const DEFAULT_COLS: number = 80;
|
||||
export const DEFAULT_ROWS: number = 24;
|
||||
|
||||
/**
|
||||
* Default messages to indicate PAUSE/RESUME for automatic flow control.
|
||||
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
|
||||
* the sequences can be customized in `IPtyForkOptions`.
|
||||
*/
|
||||
const FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF
|
||||
const FLOW_CONTROL_RESUME = '\x11'; // defaults to XON
|
||||
|
||||
export abstract class Terminal implements ITerminal {
|
||||
protected _socket: Socket;
|
||||
protected _pid: number;
|
||||
protected _fd: number;
|
||||
protected _pty: any;
|
||||
|
||||
protected _file: string;
|
||||
protected _name: string;
|
||||
protected _cols: number;
|
||||
protected _rows: number;
|
||||
|
||||
protected _readable: boolean;
|
||||
protected _writable: boolean;
|
||||
|
||||
protected _internalee: EventEmitter;
|
||||
private _flowControlPause: string;
|
||||
private _flowControlResume: string;
|
||||
public handleFlowControl: boolean;
|
||||
|
||||
private _onData = new EventEmitter2<string>();
|
||||
public get onData(): IEvent<string> { return this._onData.event; }
|
||||
private _onExit = new EventEmitter2<IExitEvent>();
|
||||
public get onExit(): IEvent<IExitEvent> { return this._onExit.event; }
|
||||
|
||||
public get pid(): number { return this._pid; }
|
||||
public get cols(): number { return this._cols; }
|
||||
public get rows(): number { return this._rows; }
|
||||
|
||||
constructor(opt?: IPtyForkOptions) {
|
||||
// for 'close'
|
||||
this._internalee = new EventEmitter();
|
||||
|
||||
if (!opt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do basic type checks here in case node-pty is being used within JavaScript. If the wrong
|
||||
// types go through to the C++ side it can lead to hard to diagnose exceptions.
|
||||
this._checkType('name', opt.name ? opt.name : null, 'string');
|
||||
this._checkType('cols', opt.cols ? opt.cols : null, 'number');
|
||||
this._checkType('rows', opt.rows ? opt.rows : null, 'number');
|
||||
this._checkType('cwd', opt.cwd ? opt.cwd : null, 'string');
|
||||
this._checkType('env', opt.env ? opt.env : null, 'object');
|
||||
this._checkType('uid', opt.uid ? opt.uid : null, 'number');
|
||||
this._checkType('gid', opt.gid ? opt.gid : null, 'number');
|
||||
this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string');
|
||||
|
||||
// setup flow control handling
|
||||
this.handleFlowControl = !!(opt.handleFlowControl);
|
||||
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE;
|
||||
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME;
|
||||
}
|
||||
|
||||
protected abstract _write(data: string): void;
|
||||
|
||||
public write(data: string): void {
|
||||
if (this.handleFlowControl) {
|
||||
// PAUSE/RESUME messages are not forwarded to the pty
|
||||
if (data === this._flowControlPause) {
|
||||
this.pause();
|
||||
return;
|
||||
}
|
||||
if (data === this._flowControlResume) {
|
||||
this.resume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// everything else goes to the real pty
|
||||
this._write(data);
|
||||
}
|
||||
|
||||
protected _forwardEvents(): void {
|
||||
this.on('data', e => this._onData.fire(e));
|
||||
this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal }));
|
||||
}
|
||||
|
||||
private _checkType(name: string, value: any, type: string): void {
|
||||
if (value && typeof value !== type) {
|
||||
throw new Error(`${name} must be a ${type} (not a ${typeof value})`);
|
||||
}
|
||||
}
|
||||
|
||||
/** See net.Socket.end */
|
||||
public end(data: string): void {
|
||||
this._socket.end(data);
|
||||
}
|
||||
|
||||
/** See stream.Readable.pipe */
|
||||
public pipe(dest: any, options: any): any {
|
||||
return this._socket.pipe(dest, options);
|
||||
}
|
||||
|
||||
/** See net.Socket.pause */
|
||||
public pause(): Socket {
|
||||
return this._socket.pause();
|
||||
}
|
||||
|
||||
/** See net.Socket.resume */
|
||||
public resume(): Socket {
|
||||
return this._socket.resume();
|
||||
}
|
||||
|
||||
/** See net.Socket.setEncoding */
|
||||
public setEncoding(encoding: string | null): void {
|
||||
if ((<any>this._socket)._decoder) {
|
||||
delete (<any>this._socket)._decoder;
|
||||
}
|
||||
if (encoding) {
|
||||
this._socket.setEncoding(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
public addListener(eventName: string, listener: (...args: any[]) => any): void { this.on(eventName, listener); }
|
||||
public on(eventName: string, listener: (...args: any[]) => any): void {
|
||||
if (eventName === 'close') {
|
||||
this._internalee.on('close', listener);
|
||||
return;
|
||||
}
|
||||
this._socket.on(eventName, listener);
|
||||
}
|
||||
|
||||
public emit(eventName: string, ...args: any[]): any {
|
||||
if (eventName === 'close') {
|
||||
return this._internalee.emit.apply(this._internalee, arguments);
|
||||
}
|
||||
return this._socket.emit.apply(this._socket, arguments);
|
||||
}
|
||||
|
||||
public listeners(eventName: string): Function[] {
|
||||
return this._socket.listeners(eventName);
|
||||
}
|
||||
|
||||
public removeListener(eventName: string, listener: (...args: any[]) => any): void {
|
||||
this._socket.removeListener(eventName, listener);
|
||||
}
|
||||
|
||||
public removeAllListeners(eventName: string): void {
|
||||
this._socket.removeAllListeners(eventName);
|
||||
}
|
||||
|
||||
public once(eventName: string, listener: (...args: any[]) => any): void {
|
||||
this._socket.once(eventName, listener);
|
||||
}
|
||||
|
||||
public abstract resize(cols: number, rows: number): void;
|
||||
public abstract destroy(): void;
|
||||
public abstract kill(signal?: string): void;
|
||||
|
||||
public abstract get process(): string;
|
||||
public abstract get master(): Socket;
|
||||
public abstract get slave(): Socket;
|
||||
|
||||
protected _close(): void {
|
||||
this._socket.writable = false;
|
||||
this._socket.readable = false;
|
||||
this.write = () => {};
|
||||
this.end = () => {};
|
||||
this._writable = false;
|
||||
this._readable = false;
|
||||
}
|
||||
|
||||
protected _parseEnv(env: {[key: string]: string}): string[] {
|
||||
const keys = Object.keys(env || {});
|
||||
const pairs = [];
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
pairs.push(keys[i] + '=' + env[keys[i]]);
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
}
|
||||
23
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/testUtils.test.ts
generated
vendored
Normal file
23
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/testUtils.test.ts
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
export function pollUntil(cb: () => boolean, timeout: number, interval: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const intervalId = setInterval(() => {
|
||||
if (cb()) {
|
||||
clearInterval(intervalId);
|
||||
clearTimeout(timeoutId);
|
||||
resolve();
|
||||
}
|
||||
}, interval);
|
||||
const timeoutId = setTimeout(() => {
|
||||
clearInterval(intervalId);
|
||||
if (cb()) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
15
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/types.ts
generated
vendored
Normal file
15
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
export type ArgvOrCommandLine = string[] | string;
|
||||
|
||||
export interface IExitEvent {
|
||||
exitCode: number;
|
||||
signal: number | undefined;
|
||||
}
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
708
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/unix/pty.cc
generated
vendored
Normal file
708
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/unix/pty.cc
generated
vendored
Normal file
@@ -0,0 +1,708 @@
|
||||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License)
|
||||
*
|
||||
* pty.cc:
|
||||
* This file is responsible for starting processes
|
||||
* with pseudo-terminal file descriptors.
|
||||
*
|
||||
* See:
|
||||
* man pty
|
||||
* man tty_ioctl
|
||||
* man termios
|
||||
* man forkpty
|
||||
*/
|
||||
|
||||
/**
|
||||
* Includes
|
||||
*/
|
||||
|
||||
#include <nan.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* forkpty */
|
||||
/* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */
|
||||
#if defined(__GLIBC__) || defined(__CYGWIN__)
|
||||
#include <pty.h>
|
||||
#elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
#include <util.h>
|
||||
#elif defined(__FreeBSD__)
|
||||
#include <libutil.h>
|
||||
#elif defined(__sun)
|
||||
#include <stropts.h> /* for I_PUSH */
|
||||
#else
|
||||
#include <pty.h>
|
||||
#endif
|
||||
|
||||
#include <termios.h> /* tcgetattr, tty_ioctl */
|
||||
|
||||
/* Some platforms name VWERASE and VDISCARD differently */
|
||||
#if !defined(VWERASE) && defined(VWERSE)
|
||||
#define VWERASE VWERSE
|
||||
#endif
|
||||
#if !defined(VDISCARD) && defined(VDISCRD)
|
||||
#define VDISCARD VDISCRD
|
||||
#endif
|
||||
|
||||
/* environ for execvpe */
|
||||
/* node/src/node_child_process.cc */
|
||||
#if defined(__APPLE__) && !TARGET_OS_IPHONE
|
||||
#include <crt_externs.h>
|
||||
#define environ (*_NSGetEnviron())
|
||||
#else
|
||||
extern char **environ;
|
||||
#endif
|
||||
|
||||
/* for pty_getproc */
|
||||
#if defined(__linux__)
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/sysctl.h>
|
||||
#include <libproc.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Structs
|
||||
*/
|
||||
|
||||
struct pty_baton {
|
||||
Nan::Persistent<v8::Function> cb;
|
||||
int exit_code;
|
||||
int signal_code;
|
||||
pid_t pid;
|
||||
uv_async_t async;
|
||||
uv_thread_t tid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
|
||||
NAN_METHOD(PtyFork);
|
||||
NAN_METHOD(PtyOpen);
|
||||
NAN_METHOD(PtyResize);
|
||||
NAN_METHOD(PtyGetProc);
|
||||
|
||||
/**
|
||||
* Functions
|
||||
*/
|
||||
|
||||
static int
|
||||
pty_execvpe(const char *, char **, char **);
|
||||
|
||||
static int
|
||||
pty_nonblock(int);
|
||||
|
||||
static char *
|
||||
pty_getproc(int, char *);
|
||||
|
||||
static int
|
||||
pty_openpty(int *, int *, char *,
|
||||
const struct termios *,
|
||||
const struct winsize *);
|
||||
|
||||
static pid_t
|
||||
pty_forkpty(int *, char *,
|
||||
const struct termios *,
|
||||
const struct winsize *);
|
||||
|
||||
static void
|
||||
pty_waitpid(void *);
|
||||
|
||||
static void
|
||||
pty_after_waitpid(uv_async_t *);
|
||||
|
||||
static void
|
||||
pty_after_close(uv_handle_t *);
|
||||
|
||||
NAN_METHOD(PtyFork) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 10 ||
|
||||
!info[0]->IsString() ||
|
||||
!info[1]->IsArray() ||
|
||||
!info[2]->IsArray() ||
|
||||
!info[3]->IsString() ||
|
||||
!info[4]->IsNumber() ||
|
||||
!info[5]->IsNumber() ||
|
||||
!info[6]->IsNumber() ||
|
||||
!info[7]->IsNumber() ||
|
||||
!info[8]->IsBoolean() ||
|
||||
!info[9]->IsFunction()) {
|
||||
return Nan::ThrowError(
|
||||
"Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, onexit)");
|
||||
}
|
||||
|
||||
// Make sure the process still listens to SIGINT
|
||||
signal(SIGINT, SIG_DFL);
|
||||
|
||||
// file
|
||||
Nan::Utf8String file(info[0]);
|
||||
|
||||
// args
|
||||
int i = 0;
|
||||
v8::Local<v8::Array> argv_ = v8::Local<v8::Array>::Cast(info[1]);
|
||||
int argc = argv_->Length();
|
||||
int argl = argc + 1 + 1;
|
||||
char **argv = new char*[argl];
|
||||
argv[0] = strdup(*file);
|
||||
argv[argl-1] = NULL;
|
||||
for (; i < argc; i++) {
|
||||
Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked());
|
||||
argv[i+1] = strdup(*arg);
|
||||
}
|
||||
|
||||
// env
|
||||
i = 0;
|
||||
v8::Local<v8::Array> env_ = v8::Local<v8::Array>::Cast(info[2]);
|
||||
int envc = env_->Length();
|
||||
char **env = new char*[envc+1];
|
||||
env[envc] = NULL;
|
||||
for (; i < envc; i++) {
|
||||
Nan::Utf8String pair(Nan::Get(env_, i).ToLocalChecked());
|
||||
env[i] = strdup(*pair);
|
||||
}
|
||||
|
||||
// cwd
|
||||
Nan::Utf8String cwd_(info[3]);
|
||||
char *cwd = strdup(*cwd_);
|
||||
|
||||
// size
|
||||
struct winsize winp;
|
||||
winp.ws_col = info[4]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
winp.ws_row = info[5]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
winp.ws_xpixel = 0;
|
||||
winp.ws_ypixel = 0;
|
||||
|
||||
// termios
|
||||
struct termios t = termios();
|
||||
struct termios *term = &t;
|
||||
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
||||
if (Nan::To<bool>(info[8]).FromJust()) {
|
||||
#if defined(IUTF8)
|
||||
term->c_iflag |= IUTF8;
|
||||
#endif
|
||||
}
|
||||
term->c_oflag = OPOST | ONLCR;
|
||||
term->c_cflag = CREAD | CS8 | HUPCL;
|
||||
term->c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;
|
||||
|
||||
term->c_cc[VEOF] = 4;
|
||||
term->c_cc[VEOL] = -1;
|
||||
term->c_cc[VEOL2] = -1;
|
||||
term->c_cc[VERASE] = 0x7f;
|
||||
term->c_cc[VWERASE] = 23;
|
||||
term->c_cc[VKILL] = 21;
|
||||
term->c_cc[VREPRINT] = 18;
|
||||
term->c_cc[VINTR] = 3;
|
||||
term->c_cc[VQUIT] = 0x1c;
|
||||
term->c_cc[VSUSP] = 26;
|
||||
term->c_cc[VSTART] = 17;
|
||||
term->c_cc[VSTOP] = 19;
|
||||
term->c_cc[VLNEXT] = 22;
|
||||
term->c_cc[VDISCARD] = 15;
|
||||
term->c_cc[VMIN] = 1;
|
||||
term->c_cc[VTIME] = 0;
|
||||
|
||||
#if (__APPLE__)
|
||||
term->c_cc[VDSUSP] = 25;
|
||||
term->c_cc[VSTATUS] = 20;
|
||||
#endif
|
||||
|
||||
cfsetispeed(term, B38400);
|
||||
cfsetospeed(term, B38400);
|
||||
|
||||
// uid / gid
|
||||
int uid = info[6]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
int gid = info[7]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
// fork the pty
|
||||
int master = -1;
|
||||
pid_t pid = pty_forkpty(&master, nullptr, term, &winp);
|
||||
|
||||
if (pid) {
|
||||
for (i = 0; i < argl; i++) free(argv[i]);
|
||||
delete[] argv;
|
||||
for (i = 0; i < envc; i++) free(env[i]);
|
||||
delete[] env;
|
||||
free(cwd);
|
||||
}
|
||||
|
||||
switch (pid) {
|
||||
case -1:
|
||||
return Nan::ThrowError("forkpty(3) failed.");
|
||||
case 0:
|
||||
if (strlen(cwd)) {
|
||||
if (chdir(cwd) == -1) {
|
||||
perror("chdir(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (uid != -1 && gid != -1) {
|
||||
if (setgid(gid) == -1) {
|
||||
perror("setgid(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
if (setuid(uid) == -1) {
|
||||
perror("setuid(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pty_execvpe(argv[0], argv, env);
|
||||
|
||||
perror("execvp(3) failed.");
|
||||
_exit(1);
|
||||
default:
|
||||
if (pty_nonblock(master) == -1) {
|
||||
return Nan::ThrowError("Could not set master fd to nonblocking.");
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
|
||||
Nan::Set(obj,
|
||||
Nan::New<v8::String>("fd").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(master));
|
||||
Nan::Set(obj,
|
||||
Nan::New<v8::String>("pid").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(pid));
|
||||
Nan::Set(obj,
|
||||
Nan::New<v8::String>("pty").ToLocalChecked(),
|
||||
Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
|
||||
|
||||
pty_baton *baton = new pty_baton();
|
||||
baton->exit_code = 0;
|
||||
baton->signal_code = 0;
|
||||
baton->cb.Reset(v8::Local<v8::Function>::Cast(info[9]));
|
||||
baton->pid = pid;
|
||||
baton->async.data = baton;
|
||||
|
||||
uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);
|
||||
|
||||
uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));
|
||||
|
||||
return info.GetReturnValue().Set(obj);
|
||||
}
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
NAN_METHOD(PtyOpen) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 2 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsNumber()) {
|
||||
return Nan::ThrowError("Usage: pty.open(cols, rows)");
|
||||
}
|
||||
|
||||
// size
|
||||
struct winsize winp;
|
||||
winp.ws_col = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
winp.ws_row = info[1]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
winp.ws_xpixel = 0;
|
||||
winp.ws_ypixel = 0;
|
||||
|
||||
// pty
|
||||
int master, slave;
|
||||
int ret = pty_openpty(&master, &slave, nullptr, NULL, &winp);
|
||||
|
||||
if (ret == -1) {
|
||||
return Nan::ThrowError("openpty(3) failed.");
|
||||
}
|
||||
|
||||
if (pty_nonblock(master) == -1) {
|
||||
return Nan::ThrowError("Could not set master fd to nonblocking.");
|
||||
}
|
||||
|
||||
if (pty_nonblock(slave) == -1) {
|
||||
return Nan::ThrowError("Could not set slave fd to nonblocking.");
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
|
||||
Nan::Set(obj,
|
||||
Nan::New<v8::String>("master").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(master));
|
||||
Nan::Set(obj,
|
||||
Nan::New<v8::String>("slave").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(slave));
|
||||
Nan::Set(obj,
|
||||
Nan::New<v8::String>("pty").ToLocalChecked(),
|
||||
Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
|
||||
|
||||
return info.GetReturnValue().Set(obj);
|
||||
}
|
||||
|
||||
NAN_METHOD(PtyResize) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 3 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsNumber() ||
|
||||
!info[2]->IsNumber()) {
|
||||
return Nan::ThrowError("Usage: pty.resize(fd, cols, rows)");
|
||||
}
|
||||
|
||||
int fd = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
struct winsize winp;
|
||||
winp.ws_col = info[1]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
winp.ws_row = info[2]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
winp.ws_xpixel = 0;
|
||||
winp.ws_ypixel = 0;
|
||||
|
||||
if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
|
||||
switch (errno) {
|
||||
case EBADF: return Nan::ThrowError("ioctl(2) failed, EBADF");
|
||||
case EFAULT: return Nan::ThrowError("ioctl(2) failed, EFAULT");
|
||||
case EINVAL: return Nan::ThrowError("ioctl(2) failed, EINVAL");
|
||||
case ENOTTY: return Nan::ThrowError("ioctl(2) failed, ENOTTY");
|
||||
}
|
||||
return Nan::ThrowError("ioctl(2) failed");
|
||||
}
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreground Process Name
|
||||
*/
|
||||
NAN_METHOD(PtyGetProc) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 2 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsString()) {
|
||||
return Nan::ThrowError("Usage: pty.process(fd, tty)");
|
||||
}
|
||||
|
||||
int fd = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
Nan::Utf8String tty_(info[1]);
|
||||
char *tty = strdup(*tty_);
|
||||
char *name = pty_getproc(fd, tty);
|
||||
free(tty);
|
||||
|
||||
if (name == NULL) {
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
v8::Local<v8::String> name_ = Nan::New<v8::String>(name).ToLocalChecked();
|
||||
free(name);
|
||||
return info.GetReturnValue().Set(name_);
|
||||
}
|
||||
|
||||
/**
|
||||
* execvpe
|
||||
*/
|
||||
|
||||
// execvpe(3) is not portable.
|
||||
// http://www.gnu.org/software/gnulib/manual/html_node/execvpe.html
|
||||
static int
|
||||
pty_execvpe(const char *file, char **argv, char **envp) {
|
||||
char **old = environ;
|
||||
environ = envp;
|
||||
int ret = execvp(file, argv);
|
||||
environ = old;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nonblocking FD
|
||||
*/
|
||||
|
||||
static int
|
||||
pty_nonblock(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1) return -1;
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_waitpid
|
||||
* Wait for SIGCHLD to read exit status.
|
||||
*/
|
||||
|
||||
static void
|
||||
pty_waitpid(void *data) {
|
||||
int ret;
|
||||
int stat_loc;
|
||||
|
||||
pty_baton *baton = static_cast<pty_baton*>(data);
|
||||
|
||||
errno = 0;
|
||||
|
||||
if ((ret = waitpid(baton->pid, &stat_loc, 0)) != baton->pid) {
|
||||
if (ret == -1 && errno == EINTR) {
|
||||
return pty_waitpid(baton);
|
||||
}
|
||||
if (ret == -1 && errno == ECHILD) {
|
||||
// XXX node v0.8.x seems to have this problem.
|
||||
// waitpid is already handled elsewhere.
|
||||
;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (WIFEXITED(stat_loc)) {
|
||||
baton->exit_code = WEXITSTATUS(stat_loc); // errno?
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(stat_loc)) {
|
||||
baton->signal_code = WTERMSIG(stat_loc);
|
||||
}
|
||||
|
||||
uv_async_send(&baton->async);
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_after_waitpid
|
||||
* Callback after exit status has been read.
|
||||
*/
|
||||
|
||||
static void
|
||||
pty_after_waitpid(uv_async_t *async) {
|
||||
Nan::HandleScope scope;
|
||||
pty_baton *baton = static_cast<pty_baton*>(async->data);
|
||||
|
||||
v8::Local<v8::Value> argv[] = {
|
||||
Nan::New<v8::Integer>(baton->exit_code),
|
||||
Nan::New<v8::Integer>(baton->signal_code),
|
||||
};
|
||||
|
||||
v8::Local<v8::Function> cb = Nan::New<v8::Function>(baton->cb);
|
||||
baton->cb.Reset();
|
||||
memset(&baton->cb, -1, sizeof(baton->cb));
|
||||
Nan::AsyncResource resource("pty_after_waitpid");
|
||||
resource.runInAsyncScope(Nan::GetCurrentContext()->Global(), cb, 2, argv);
|
||||
|
||||
uv_close((uv_handle_t *)async, pty_after_close);
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_after_close
|
||||
* uv_close() callback - free handle data
|
||||
*/
|
||||
|
||||
static void
|
||||
pty_after_close(uv_handle_t *handle) {
|
||||
uv_async_t *async = (uv_async_t *)handle;
|
||||
pty_baton *baton = static_cast<pty_baton*>(async->data);
|
||||
delete baton;
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_getproc
|
||||
* Taken from tmux.
|
||||
*/
|
||||
|
||||
// Taken from: tmux (http://tmux.sourceforge.net/)
|
||||
// Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
|
||||
// Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>
|
||||
// Copyright (c) 2009 Todd Carson <toc@daybefore.net>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
// OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
static char *
|
||||
pty_getproc(int fd, char *tty) {
|
||||
FILE *f;
|
||||
char *path, *buf;
|
||||
size_t len;
|
||||
int ch;
|
||||
pid_t pgrp;
|
||||
int r;
|
||||
|
||||
if ((pgrp = tcgetpgrp(fd)) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp);
|
||||
if (r == -1 || path == NULL) return NULL;
|
||||
|
||||
if ((f = fopen(path, "r")) == NULL) {
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(path);
|
||||
|
||||
len = 0;
|
||||
buf = NULL;
|
||||
while ((ch = fgetc(f)) != EOF) {
|
||||
if (ch == '\0') break;
|
||||
buf = (char *)realloc(buf, len + 2);
|
||||
if (buf == NULL) return NULL;
|
||||
buf[len++] = ch;
|
||||
}
|
||||
|
||||
if (buf != NULL) {
|
||||
buf[len] = '\0';
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
static char *
|
||||
pty_getproc(int fd, char *tty) {
|
||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
|
||||
size_t size;
|
||||
struct kinfo_proc kp;
|
||||
|
||||
if ((mib[3] = tcgetpgrp(fd)) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = sizeof kp;
|
||||
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*kp.kp_proc.p_comm == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return strdup(kp.kp_proc.p_comm);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static char *
|
||||
pty_getproc(int fd, char *tty) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* openpty(3) / forkpty(3)
|
||||
*/
|
||||
|
||||
static int
|
||||
pty_openpty(int *amaster,
|
||||
int *aslave,
|
||||
char *name,
|
||||
const struct termios *termp,
|
||||
const struct winsize *winp) {
|
||||
#if defined(__sun)
|
||||
char *slave_name;
|
||||
int slave;
|
||||
int master = open("/dev/ptmx", O_RDWR | O_NOCTTY);
|
||||
if (master == -1) return -1;
|
||||
if (amaster) *amaster = master;
|
||||
|
||||
if (grantpt(master) == -1) goto err;
|
||||
if (unlockpt(master) == -1) goto err;
|
||||
|
||||
slave_name = ptsname(master);
|
||||
if (slave_name == NULL) goto err;
|
||||
if (name) strcpy(name, slave_name);
|
||||
|
||||
slave = open(slave_name, O_RDWR | O_NOCTTY);
|
||||
if (slave == -1) goto err;
|
||||
if (aslave) *aslave = slave;
|
||||
|
||||
ioctl(slave, I_PUSH, "ptem");
|
||||
ioctl(slave, I_PUSH, "ldterm");
|
||||
ioctl(slave, I_PUSH, "ttcompat");
|
||||
|
||||
if (termp) tcsetattr(slave, TCSAFLUSH, termp);
|
||||
if (winp) ioctl(slave, TIOCSWINSZ, winp);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
close(master);
|
||||
return -1;
|
||||
#else
|
||||
return openpty(amaster, aslave, name, (termios *)termp, (winsize *)winp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static pid_t
|
||||
pty_forkpty(int *amaster,
|
||||
char *name,
|
||||
const struct termios *termp,
|
||||
const struct winsize *winp) {
|
||||
#if defined(__sun)
|
||||
int master, slave;
|
||||
|
||||
int ret = pty_openpty(&master, &slave, name, termp, winp);
|
||||
if (ret == -1) return -1;
|
||||
if (amaster) *amaster = master;
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
switch (pid) {
|
||||
case -1:
|
||||
close(master);
|
||||
close(slave);
|
||||
return -1;
|
||||
case 0:
|
||||
close(master);
|
||||
|
||||
setsid();
|
||||
|
||||
#if defined(TIOCSCTTY)
|
||||
// glibc does this
|
||||
if (ioctl(slave, TIOCSCTTY, NULL) == -1) {
|
||||
_exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
dup2(slave, 0);
|
||||
dup2(slave, 1);
|
||||
dup2(slave, 2);
|
||||
|
||||
if (slave > 2) close(slave);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
close(slave);
|
||||
return pid;
|
||||
}
|
||||
|
||||
return -1;
|
||||
#else
|
||||
return forkpty(amaster, name, (termios *)termp, (winsize *)winp);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
|
||||
NAN_MODULE_INIT(init) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::Export(target, "fork", PtyFork);
|
||||
Nan::Export(target, "open", PtyOpen);
|
||||
Nan::Export(target, "resize", PtyResize);
|
||||
Nan::Export(target, "process", PtyGetProc);
|
||||
}
|
||||
|
||||
NODE_MODULE(pty, init)
|
||||
108
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/unixTerminal.test.ts
generated
vendored
Normal file
108
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/unixTerminal.test.ts
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import { UnixTerminal } from './unixTerminal';
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import { pollUntil } from './testUtils.test';
|
||||
|
||||
const FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf8-character.txt'));
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
describe('UnixTerminal', () => {
|
||||
describe('Constructor', () => {
|
||||
it('should set a valid pts name', () => {
|
||||
const term = new UnixTerminal('/bin/bash', [], {});
|
||||
let regExp;
|
||||
if (process.platform === 'linux') {
|
||||
// https://linux.die.net/man/4/pts
|
||||
regExp = /^\/dev\/pts\/\d+$/;
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
// https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man4/pty.4.html
|
||||
regExp = /^\/dev\/tty[p-sP-S][a-z0-9]+$/;
|
||||
}
|
||||
if (regExp) {
|
||||
assert.ok(regExp.test((<any>term)._pty), '"' + (<any>term)._pty + '" should match ' + regExp.toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('PtyForkEncodingOption', () => {
|
||||
it('should default to utf8', (done) => {
|
||||
const term = new UnixTerminal('/bin/bash', [ '-c', `cat "${FIXTURES_PATH}"` ]);
|
||||
term.on('data', (data) => {
|
||||
assert.equal(typeof data, 'string');
|
||||
assert.equal(data, '\u00E6');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should return a Buffer when encoding is null', (done) => {
|
||||
const term = new UnixTerminal('/bin/bash', [ '-c', `cat "${FIXTURES_PATH}"` ], {
|
||||
encoding: null
|
||||
});
|
||||
term.on('data', (data) => {
|
||||
assert.equal(typeof data, 'object');
|
||||
assert.ok(data instanceof Buffer);
|
||||
assert.equal(0xC3, data[0]);
|
||||
assert.equal(0xA6, data[1]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should support other encodings', (done) => {
|
||||
const text = 'test æ!';
|
||||
const term = new UnixTerminal(null, ['-c', 'echo "' + text + '"'], {
|
||||
encoding: 'base64'
|
||||
});
|
||||
let buffer = '';
|
||||
term.on('data', (data) => {
|
||||
assert.equal(typeof data, 'string');
|
||||
buffer += data;
|
||||
});
|
||||
term.on('exit', () => {
|
||||
assert.equal(Buffer.alloc(8, buffer, 'base64').toString().replace('\r', '').replace('\n', ''), text);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('open', () => {
|
||||
let term: UnixTerminal;
|
||||
|
||||
afterEach(() => {
|
||||
if (term) {
|
||||
term.slave.destroy();
|
||||
term.master.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('should open a pty with access to a master and slave socket', (done) => {
|
||||
let doneCalled = false;
|
||||
term = UnixTerminal.open({});
|
||||
|
||||
let slavebuf = '';
|
||||
term.slave.on('data', (data) => {
|
||||
slavebuf += data;
|
||||
});
|
||||
|
||||
let masterbuf = '';
|
||||
term.master.on('data', (data) => {
|
||||
masterbuf += data;
|
||||
});
|
||||
|
||||
pollUntil(() => {
|
||||
if (masterbuf === 'slave\r\nmaster\r\n' && slavebuf === 'master\n') {
|
||||
done();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, 200, 10);
|
||||
|
||||
term.slave.write('slave\n');
|
||||
term.master.write('master\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
299
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/unixTerminal.ts
generated
vendored
Normal file
299
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/unixTerminal.ts
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
import * as net from 'net';
|
||||
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
|
||||
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
|
||||
import { ArgvOrCommandLine } from './types';
|
||||
import { assign } from './utils';
|
||||
|
||||
let pty: IUnixNative;
|
||||
try {
|
||||
pty = require('../build/Release/pty.node');
|
||||
} catch (outerError) {
|
||||
try {
|
||||
pty = require('../build/Debug/pty.node');
|
||||
} catch (innerError) {
|
||||
console.error('innerError', innerError);
|
||||
// Re-throw the exception from the Release require if the Debug require fails as well
|
||||
throw outerError;
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_FILE = 'sh';
|
||||
const DEFAULT_NAME = 'xterm';
|
||||
const DESTROY_SOCKET_TIMEOUT_MS = 200;
|
||||
|
||||
export class UnixTerminal extends Terminal {
|
||||
protected _fd: number;
|
||||
protected _pty: string;
|
||||
|
||||
protected _file: string;
|
||||
protected _name: string;
|
||||
|
||||
protected _readable: boolean;
|
||||
protected _writable: boolean;
|
||||
|
||||
private _boundClose: boolean;
|
||||
private _emittedClose: boolean;
|
||||
private _master: net.Socket;
|
||||
private _slave: net.Socket;
|
||||
|
||||
public get master(): net.Socket { return this._master; }
|
||||
public get slave(): net.Socket { return this._slave; }
|
||||
|
||||
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {
|
||||
super(opt);
|
||||
|
||||
if (typeof args === 'string') {
|
||||
throw new Error('args as a string is not supported on unix.');
|
||||
}
|
||||
|
||||
// Initialize arguments
|
||||
args = args || [];
|
||||
file = file || DEFAULT_FILE;
|
||||
opt = opt || {};
|
||||
opt.env = opt.env || process.env;
|
||||
|
||||
this._cols = opt.cols || DEFAULT_COLS;
|
||||
this._rows = opt.rows || DEFAULT_ROWS;
|
||||
const uid = opt.uid || -1;
|
||||
const gid = opt.gid || -1;
|
||||
const env = assign({}, opt.env);
|
||||
|
||||
if (opt.env === process.env) {
|
||||
this._sanitizeEnv(env);
|
||||
}
|
||||
|
||||
const cwd = opt.cwd || process.cwd();
|
||||
const name = opt.name || env.TERM || DEFAULT_NAME;
|
||||
env.TERM = name;
|
||||
const parsedEnv = this._parseEnv(env);
|
||||
|
||||
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
|
||||
|
||||
const onexit = (code: number, signal: number) => {
|
||||
// XXX Sometimes a data event is emitted after exit. Wait til socket is
|
||||
// destroyed.
|
||||
if (!this._emittedClose) {
|
||||
if (this._boundClose) {
|
||||
return;
|
||||
}
|
||||
this._boundClose = true;
|
||||
// From macOS High Sierra 10.13.2 sometimes the socket never gets
|
||||
// closed. A timeout is applied here to avoid the terminal never being
|
||||
// destroyed when this occurs.
|
||||
let timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
// Destroying the socket now will cause the close event to fire
|
||||
this._socket.destroy();
|
||||
}, DESTROY_SOCKET_TIMEOUT_MS);
|
||||
this.once('close', () => {
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
this.emit('exit', code, signal);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.emit('exit', code, signal);
|
||||
};
|
||||
|
||||
// fork
|
||||
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit);
|
||||
|
||||
this._socket = new PipeSocket(term.fd);
|
||||
if (encoding !== null) {
|
||||
this._socket.setEncoding(encoding);
|
||||
}
|
||||
|
||||
// setup
|
||||
this._socket.on('error', (err: any) => {
|
||||
// NOTE: fs.ReadStream gets EAGAIN twice at first:
|
||||
if (err.code) {
|
||||
if (~err.code.indexOf('EAGAIN')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// close
|
||||
this._close();
|
||||
// EIO on exit from fs.ReadStream:
|
||||
if (!this._emittedClose) {
|
||||
this._emittedClose = true;
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
// EIO, happens when someone closes our child process: the only process in
|
||||
// the terminal.
|
||||
// node < 0.6.14: errno 5
|
||||
// node >= 0.6.14: read EIO
|
||||
if (err.code) {
|
||||
if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// throw anything else
|
||||
if (this.listeners('error').length < 2) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
this._pid = term.pid;
|
||||
this._fd = term.fd;
|
||||
this._pty = term.pty;
|
||||
|
||||
this._file = file;
|
||||
this._name = name;
|
||||
|
||||
this._readable = true;
|
||||
this._writable = true;
|
||||
|
||||
this._socket.on('close', () => {
|
||||
if (this._emittedClose) {
|
||||
return;
|
||||
}
|
||||
this._emittedClose = true;
|
||||
this._close();
|
||||
this.emit('close');
|
||||
});
|
||||
|
||||
this._forwardEvents();
|
||||
}
|
||||
|
||||
protected _write(data: string): void {
|
||||
this._socket.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* openpty
|
||||
*/
|
||||
|
||||
public static open(opt: IPtyOpenOptions): UnixTerminal {
|
||||
const self: UnixTerminal = Object.create(UnixTerminal.prototype);
|
||||
opt = opt || {};
|
||||
|
||||
if (arguments.length > 1) {
|
||||
opt = {
|
||||
cols: arguments[1],
|
||||
rows: arguments[2]
|
||||
};
|
||||
}
|
||||
|
||||
const cols = opt.cols || DEFAULT_COLS;
|
||||
const rows = opt.rows || DEFAULT_ROWS;
|
||||
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
|
||||
|
||||
// open
|
||||
const term: IUnixOpenProcess = pty.open(cols, rows);
|
||||
|
||||
self._master = new PipeSocket(<number>term.master);
|
||||
if (encoding !== null) {
|
||||
self._master.setEncoding(encoding);
|
||||
}
|
||||
self._master.resume();
|
||||
|
||||
self._slave = new PipeSocket(term.slave);
|
||||
if (encoding !== null) {
|
||||
self._slave.setEncoding(encoding);
|
||||
}
|
||||
self._slave.resume();
|
||||
|
||||
self._socket = self._master;
|
||||
self._pid = null;
|
||||
self._fd = term.master;
|
||||
self._pty = term.pty;
|
||||
|
||||
self._file = process.argv[0] || 'node';
|
||||
self._name = process.env.TERM || '';
|
||||
|
||||
self._readable = true;
|
||||
self._writable = true;
|
||||
|
||||
self._socket.on('error', err => {
|
||||
self._close();
|
||||
if (self.listeners('error').length < 2) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
self._socket.on('close', () => {
|
||||
self._close();
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._close();
|
||||
|
||||
// Need to close the read stream so node stops reading a dead file
|
||||
// descriptor. Then we can safely SIGHUP the shell.
|
||||
this._socket.once('close', () => {
|
||||
this.kill('SIGHUP');
|
||||
});
|
||||
|
||||
this._socket.destroy();
|
||||
}
|
||||
|
||||
public kill(signal?: string): void {
|
||||
try {
|
||||
process.kill(this.pid, signal || 'SIGHUP');
|
||||
} catch (e) { /* swallow */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the process.
|
||||
*/
|
||||
public get process(): string {
|
||||
return pty.process(this._fd, this._pty) || this._file;
|
||||
}
|
||||
|
||||
/**
|
||||
* TTY
|
||||
*/
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
|
||||
throw new Error('resizing must be done using positive cols and rows');
|
||||
}
|
||||
pty.resize(this._fd, cols, rows);
|
||||
this._cols = cols;
|
||||
this._rows = rows;
|
||||
}
|
||||
|
||||
private _sanitizeEnv(env: IProcessEnv): void {
|
||||
// Make sure we didn't start our server from inside tmux.
|
||||
delete env['TMUX'];
|
||||
delete env['TMUX_PANE'];
|
||||
|
||||
// Make sure we didn't start our server from inside screen.
|
||||
// http://web.mit.edu/gnu/doc/html/screen_20.html
|
||||
delete env['STY'];
|
||||
delete env['WINDOW'];
|
||||
|
||||
// Delete some variables that might confuse our terminal.
|
||||
delete env['WINDOWID'];
|
||||
delete env['TERMCAP'];
|
||||
delete env['COLUMNS'];
|
||||
delete env['LINES'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps net.Socket to force the handle type "PIPE" by temporarily overwriting
|
||||
* tty_wrap.guessHandleType.
|
||||
* See: https://github.com/chjj/pty.js/issues/103
|
||||
*/
|
||||
class PipeSocket extends net.Socket {
|
||||
constructor(fd: number) {
|
||||
const { Pipe, constants } = (<any>process).binding('pipe_wrap'); // tslint:disable-line
|
||||
// @types/node has fd as string? https://github.com/DefinitelyTyped/DefinitelyTyped/pull/18275
|
||||
const handle = new Pipe(constants.SOCKET);
|
||||
handle.open(fd);
|
||||
super(<any>{ handle });
|
||||
}
|
||||
}
|
||||
9
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/utils.ts
generated
vendored
Normal file
9
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/utils.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
export function assign(target: any, ...sources: any[]): any {
|
||||
sources.forEach(source => Object.keys(source).forEach(key => target[key] = source[key]));
|
||||
return target;
|
||||
}
|
||||
455
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/conpty.cc
generated
vendored
Normal file
455
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/conpty.cc
generated
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*
|
||||
* pty.cc:
|
||||
* This file is responsible for starting processes
|
||||
* with pseudo-terminal file descriptors.
|
||||
*/
|
||||
|
||||
// node versions lower than 10 define this as 0x502 which disables many of the definitions needed to compile
|
||||
#include <node_version.h>
|
||||
#if NODE_MODULE_VERSION <= 57
|
||||
#define _WIN32_WINNT 0x600
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <nan.h>
|
||||
#include <Shlwapi.h> // PathCombine, PathIsRelative
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Windows.h>
|
||||
#include <strsafe.h>
|
||||
#include "path_util.h"
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object>);
|
||||
|
||||
// Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17134
|
||||
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
||||
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
|
||||
ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
|
||||
|
||||
typedef VOID* HPCON;
|
||||
typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE hOut, DWORD dwFlags, HPCON* phpcon);
|
||||
typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
|
||||
typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
|
||||
|
||||
#endif
|
||||
|
||||
struct pty_baton {
|
||||
int id;
|
||||
HANDLE hIn;
|
||||
HANDLE hOut;
|
||||
HPCON hpc;
|
||||
|
||||
HANDLE hShell;
|
||||
HANDLE hWait;
|
||||
Nan::Callback cb;
|
||||
uv_async_t async;
|
||||
uv_thread_t tid;
|
||||
|
||||
pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc) : id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc) {};
|
||||
};
|
||||
|
||||
static std::vector<pty_baton*> ptyHandles;
|
||||
static volatile LONG ptyCounter;
|
||||
|
||||
static pty_baton* get_pty_baton(int id) {
|
||||
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
||||
pty_baton* ptyHandle = ptyHandles[i];
|
||||
if (ptyHandle->id == id) {
|
||||
return ptyHandle;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vectorFromString(const std::basic_string<T> &str) {
|
||||
return std::vector<T>(str.begin(), str.end());
|
||||
}
|
||||
|
||||
void throwNanError(const Nan::FunctionCallbackInfo<v8::Value>* info, const char* text, const bool getLastError) {
|
||||
std::stringstream errorText;
|
||||
errorText << text;
|
||||
if (getLastError) {
|
||||
errorText << ", error code: " << GetLastError();
|
||||
}
|
||||
Nan::ThrowError(errorText.str().c_str());
|
||||
(*info).GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
// Returns a new server named pipe. It has not yet been connected.
|
||||
bool createDataServerPipe(bool write,
|
||||
std::wstring kind,
|
||||
HANDLE* hServer,
|
||||
std::wstring &name,
|
||||
const std::wstring &pipeName)
|
||||
{
|
||||
*hServer = INVALID_HANDLE_VALUE;
|
||||
|
||||
name = L"\\\\.\\pipe\\" + pipeName + L"-" + kind;
|
||||
|
||||
const DWORD winOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE/* | FILE_FLAG_OVERLAPPED */;
|
||||
|
||||
SECURITY_ATTRIBUTES sa = {};
|
||||
sa.nLength = sizeof(sa);
|
||||
|
||||
*hServer = CreateNamedPipeW(
|
||||
name.c_str(),
|
||||
/*dwOpenMode=*/winOpenMode,
|
||||
/*dwPipeMode=*/PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
/*nMaxInstances=*/1,
|
||||
/*nOutBufferSize=*/0,
|
||||
/*nInBufferSize=*/0,
|
||||
/*nDefaultTimeOut=*/30000,
|
||||
&sa);
|
||||
|
||||
return *hServer != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
|
||||
DWORD dwFlags,
|
||||
HANDLE *phInput,
|
||||
HANDLE *phOutput,
|
||||
HPCON* phPC,
|
||||
std::wstring& inName,
|
||||
std::wstring& outName,
|
||||
const std::wstring& pipeName)
|
||||
{
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
bool fLoadedDll = hLibrary != nullptr;
|
||||
if (fLoadedDll)
|
||||
{
|
||||
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "CreatePseudoConsole");
|
||||
if (pfnCreate)
|
||||
{
|
||||
if (phPC == NULL || phInput == NULL || phOutput == NULL)
|
||||
{
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
bool success = createDataServerPipe(true, L"in", phInput, inName, pipeName);
|
||||
if (!success)
|
||||
{
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
success = createDataServerPipe(false, L"out", phOutput, outName, pipeName);
|
||||
if (!success)
|
||||
{
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
return pfnCreate(size, *phInput, *phOutput, dwFlags, phPC);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed to find CreatePseudoConsole in kernel32. This is likely because
|
||||
// the user is not running a build of Windows that supports that API.
|
||||
// We should fall back to winpty in this case.
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to find kernel32. This is realy unlikely - honestly no idea how
|
||||
// this is even possible to hit. But if it does happen, fall back to winpty.
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyStartProcess) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
v8::Local<v8::Object> marshal;
|
||||
std::wstring inName, outName;
|
||||
BOOL fSuccess = FALSE;
|
||||
std::unique_ptr<wchar_t[]> mutableCommandline;
|
||||
PROCESS_INFORMATION _piClient{};
|
||||
|
||||
if (info.Length() != 6 ||
|
||||
!info[0]->IsString() ||
|
||||
!info[1]->IsNumber() ||
|
||||
!info[2]->IsNumber() ||
|
||||
!info[3]->IsBoolean() ||
|
||||
!info[4]->IsString() ||
|
||||
!info[5]->IsBoolean()) {
|
||||
Nan::ThrowError("Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor)");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::wstring filename(path_util::to_wstring(Nan::Utf8String(info[0])));
|
||||
const SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
const SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
const bool debug = Nan::To<bool>(info[3]).FromJust();
|
||||
const std::wstring pipeName(path_util::to_wstring(Nan::Utf8String(info[4])));
|
||||
const bool inheritCursor = Nan::To<bool>(info[5]).FromJust();
|
||||
|
||||
// use environment 'Path' variable to determine location of
|
||||
// the relative path that we have recieved (e.g cmd.exe)
|
||||
std::wstring shellpath;
|
||||
if (::PathIsRelativeW(filename.c_str())) {
|
||||
shellpath = path_util::get_shell_path(filename.c_str());
|
||||
} else {
|
||||
shellpath = filename;
|
||||
}
|
||||
|
||||
std::string shellpath_(shellpath.begin(), shellpath.end());
|
||||
|
||||
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
||||
std::stringstream why;
|
||||
why << "File not found: " << shellpath_;
|
||||
Nan::ThrowError(why.str().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE hIn, hOut;
|
||||
HPCON hpc;
|
||||
HRESULT hr = CreateNamedPipesAndPseudoConsole({cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName);
|
||||
|
||||
// Restore default handling of ctrl+c
|
||||
SetConsoleCtrlHandler(NULL, FALSE);
|
||||
|
||||
// Set return values
|
||||
marshal = Nan::New<v8::Object>();
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// We were able to instantiate a conpty
|
||||
const int ptyId = InterlockedIncrement(&ptyCounter);
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(), Nan::New<v8::Number>(ptyId));
|
||||
ptyHandles.insert(ptyHandles.end(), new pty_baton(ptyId, hIn, hOut, hpc));
|
||||
} else {
|
||||
Nan::ThrowError("Cannot launch conpty");
|
||||
return;
|
||||
}
|
||||
|
||||
Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
|
||||
{
|
||||
std::string coninPipeNameStr(inName.begin(), inName.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
|
||||
|
||||
std::string conoutPipeNameStr(outName.begin(), outName.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
||||
}
|
||||
info.GetReturnValue().Set(marshal);
|
||||
}
|
||||
|
||||
VOID CALLBACK OnProcessExitWinEvent(
|
||||
_In_ PVOID context,
|
||||
_In_ BOOLEAN TimerOrWaitFired) {
|
||||
pty_baton *baton = static_cast<pty_baton*>(context);
|
||||
|
||||
// Fire OnProcessExit
|
||||
uv_async_send(&baton->async);
|
||||
}
|
||||
|
||||
static void OnProcessExit(uv_async_t *async) {
|
||||
Nan::HandleScope scope;
|
||||
pty_baton *baton = static_cast<pty_baton*>(async->data);
|
||||
|
||||
UnregisterWait(baton->hWait);
|
||||
|
||||
// Get exit code
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeProcess(baton->hShell, &exitCode);
|
||||
|
||||
// Call function
|
||||
v8::Local<v8::Value> args[1] = {
|
||||
Nan::New<v8::Number>(exitCode)
|
||||
};
|
||||
|
||||
Nan::AsyncResource asyncResource("node-pty.callback");
|
||||
baton->cb.Call(1, args, &asyncResource);
|
||||
// Clean up
|
||||
baton->cb.Reset();
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyConnect) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// If we're working with conpty's we need to call ConnectNamedPipe here AFTER
|
||||
// the Socket has attempted to connect to the other end, then actually
|
||||
// spawn the process here.
|
||||
|
||||
std::stringstream errorText;
|
||||
BOOL fSuccess = FALSE;
|
||||
|
||||
if (info.Length() != 5 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsString() ||
|
||||
!info[2]->IsString() ||
|
||||
!info[3]->IsArray() ||
|
||||
!info[4]->IsFunction()) {
|
||||
Nan::ThrowError("Usage: pty.connect(id, cmdline, cwd, env, exitCallback)");
|
||||
return;
|
||||
}
|
||||
|
||||
const int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
const std::wstring cmdline(path_util::to_wstring(Nan::Utf8String(info[1])));
|
||||
const std::wstring cwd(path_util::to_wstring(Nan::Utf8String(info[2])));
|
||||
const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
|
||||
const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
|
||||
|
||||
// Prepare command line
|
||||
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
|
||||
HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
|
||||
|
||||
// Prepare cwd
|
||||
std::unique_ptr<wchar_t[]> mutableCwd = std::make_unique<wchar_t[]>(cwd.length() + 1);
|
||||
hr = StringCchCopyW(mutableCwd.get(), cwd.length() + 1, cwd.c_str());
|
||||
|
||||
// Prepare environment
|
||||
std::wstring env;
|
||||
if (!envValues.IsEmpty()) {
|
||||
std::wstringstream envBlock;
|
||||
for(uint32_t i = 0; i < envValues->Length(); i++) {
|
||||
std::wstring envValue(path_util::to_wstring(Nan::Utf8String(Nan::Get(envValues, i).ToLocalChecked())));
|
||||
envBlock << envValue << L'\0';
|
||||
}
|
||||
envBlock << L'\0';
|
||||
env = envBlock.str();
|
||||
}
|
||||
auto envV = vectorFromString(env);
|
||||
LPWSTR envArg = envV.empty() ? nullptr : envV.data();
|
||||
|
||||
// Fetch pty handle from ID and start process
|
||||
pty_baton* handle = get_pty_baton(id);
|
||||
|
||||
BOOL success = ConnectNamedPipe(handle->hIn, nullptr);
|
||||
success = ConnectNamedPipe(handle->hOut, nullptr);
|
||||
|
||||
// Attach the pseudoconsole to the client application we're creating
|
||||
STARTUPINFOEXW siEx{0};
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||
siEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||
siEx.StartupInfo.hStdError = nullptr;
|
||||
siEx.StartupInfo.hStdInput = nullptr;
|
||||
siEx.StartupInfo.hStdOutput = nullptr;
|
||||
|
||||
SIZE_T size = 0;
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
|
||||
BYTE *attrList = new BYTE[size];
|
||||
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
||||
|
||||
fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);
|
||||
if (!fSuccess) {
|
||||
return throwNanError(&info, "InitializeProcThreadAttributeList failed", true);
|
||||
}
|
||||
fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
handle->hpc,
|
||||
sizeof(HPCON),
|
||||
NULL,
|
||||
NULL);
|
||||
if (!fSuccess) {
|
||||
return throwNanError(&info, "UpdateProcThreadAttribute failed", true);
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION piClient{};
|
||||
fSuccess = !!CreateProcessW(
|
||||
nullptr,
|
||||
mutableCommandline.get(),
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
false, // bInheritHandles VERY IMPORTANT that this is false
|
||||
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
|
||||
envArg, // lpEnvironment
|
||||
mutableCwd.get(), // lpCurrentDirectory
|
||||
&siEx.StartupInfo, // lpStartupInfo
|
||||
&piClient // lpProcessInformation
|
||||
);
|
||||
if (!fSuccess) {
|
||||
return throwNanError(&info, "Cannot create process", true);
|
||||
}
|
||||
|
||||
// Update handle
|
||||
handle->hShell = piClient.hProcess;
|
||||
handle->cb.Reset(exitCallback);
|
||||
handle->async.data = handle;
|
||||
|
||||
// Setup OnProcessExit callback
|
||||
uv_async_init(uv_default_loop(), &handle->async, OnProcessExit);
|
||||
|
||||
// Setup Windows wait for process exit event
|
||||
RegisterWaitForSingleObject(&handle->hWait, piClient.hProcess, OnProcessExitWinEvent, (PVOID)handle, INFINITE, WT_EXECUTEONLYONCE);
|
||||
|
||||
// Return
|
||||
v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>(piClient.dwProcessId));
|
||||
info.GetReturnValue().Set(marshal);
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyResize) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 3 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsNumber() ||
|
||||
!info[2]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.resize(id, cols, rows)");
|
||||
return;
|
||||
}
|
||||
|
||||
int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
const pty_baton* handle = get_pty_baton(id);
|
||||
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
bool fLoadedDll = hLibrary != nullptr;
|
||||
if (fLoadedDll)
|
||||
{
|
||||
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
|
||||
if (pfnResizePseudoConsole)
|
||||
{
|
||||
COORD size = {cols, rows};
|
||||
pfnResizePseudoConsole(handle->hpc, size);
|
||||
}
|
||||
}
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyKill) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 1 ||
|
||||
!info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.kill(id)");
|
||||
return;
|
||||
}
|
||||
|
||||
int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
const pty_baton* handle = get_pty_baton(id);
|
||||
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
bool fLoadedDll = hLibrary != nullptr;
|
||||
if (fLoadedDll)
|
||||
{
|
||||
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
|
||||
if (pfnClosePseudoConsole)
|
||||
{
|
||||
pfnClosePseudoConsole(handle->hpc);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(handle->hShell);
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object> target) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::SetMethod(target, "startProcess", PtyStartProcess);
|
||||
Nan::SetMethod(target, "connect", PtyConnect);
|
||||
Nan::SetMethod(target, "resize", PtyResize);
|
||||
Nan::SetMethod(target, "kill", PtyKill);
|
||||
};
|
||||
|
||||
NODE_MODULE(pty, init);
|
||||
43
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/conpty_console_list.cc
generated
vendored
Normal file
43
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/conpty_console_list.cc
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
#include <nan.h>
|
||||
#include <windows.h>
|
||||
|
||||
static NAN_METHOD(ApiConsoleProcessList) {
|
||||
if (info.Length() != 1 ||
|
||||
!info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: getConsoleProcessList(shellPid)");
|
||||
return;
|
||||
}
|
||||
|
||||
const SHORT pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
if (!FreeConsole()) {
|
||||
Nan::ThrowError("FreeConsole failed");
|
||||
}
|
||||
if (!AttachConsole(pid)) {
|
||||
Nan::ThrowError("AttachConsole failed");
|
||||
}
|
||||
auto processList = std::vector<DWORD>(64);
|
||||
auto processCount = GetConsoleProcessList(&processList[0], processList.size());
|
||||
if (processList.size() < processCount) {
|
||||
processList.resize(processCount);
|
||||
processCount = GetConsoleProcessList(&processList[0], processList.size());
|
||||
}
|
||||
FreeConsole();
|
||||
|
||||
v8::Local<v8::Array> result = Nan::New<v8::Array>();
|
||||
for (DWORD i = 0; i < processCount; i++) {
|
||||
Nan::Set(result, i, Nan::New<v8::Number>(processList[i]));
|
||||
}
|
||||
info.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object> target) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::SetMethod(target, "getConsoleProcessList", ApiConsoleProcessList);
|
||||
};
|
||||
|
||||
NODE_MODULE(pty, init);
|
||||
73
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/path_util.cc
generated
vendored
Normal file
73
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/path_util.cc
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
#include <nan.h>
|
||||
#include <Shlwapi.h> // PathCombine
|
||||
|
||||
#include "path_util.h"
|
||||
|
||||
namespace path_util {
|
||||
|
||||
const wchar_t* to_wstring(const Nan::Utf8String& str) {
|
||||
const char *bytes = *str;
|
||||
unsigned int sizeOfStr = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, NULL, 0);
|
||||
wchar_t *output = new wchar_t[sizeOfStr];
|
||||
MultiByteToWideChar(CP_UTF8, 0, bytes, -1, output, sizeOfStr);
|
||||
return output;
|
||||
}
|
||||
|
||||
bool file_exists(std::wstring filename) {
|
||||
DWORD attr = ::GetFileAttributesW(filename.c_str());
|
||||
if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// cmd.exe -> C:\Windows\system32\cmd.exe
|
||||
std::wstring get_shell_path(std::wstring filename) {
|
||||
std::wstring shellpath;
|
||||
|
||||
if (file_exists(filename)) {
|
||||
return shellpath;
|
||||
}
|
||||
|
||||
wchar_t buffer_[MAX_ENV];
|
||||
int read = ::GetEnvironmentVariableW(L"Path", buffer_, MAX_ENV);
|
||||
if (!read) {
|
||||
return shellpath;
|
||||
}
|
||||
|
||||
std::wstring delimiter = L";";
|
||||
size_t pos = 0;
|
||||
std::vector<std::wstring> paths;
|
||||
std::wstring buffer(buffer_);
|
||||
while ((pos = buffer.find(delimiter)) != std::wstring::npos) {
|
||||
paths.push_back(buffer.substr(0, pos));
|
||||
buffer.erase(0, pos + delimiter.length());
|
||||
}
|
||||
|
||||
const wchar_t *filename_ = filename.c_str();
|
||||
|
||||
for (int i = 0; i < paths.size(); ++i) {
|
||||
std::wstring path = paths[i];
|
||||
wchar_t searchPath[MAX_PATH];
|
||||
::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
|
||||
|
||||
if (searchPath == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists(searchPath)) {
|
||||
shellpath = searchPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return shellpath;
|
||||
}
|
||||
|
||||
} // namespace path_util
|
||||
22
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/path_util.h
generated
vendored
Normal file
22
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/path_util.h
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
#ifndef NODE_PTY_PATH_UTIL_H_
|
||||
#define NODE_PTY_PATH_UTIL_H_
|
||||
|
||||
#include <nan.h>
|
||||
|
||||
#define MAX_ENV 65536
|
||||
|
||||
namespace path_util {
|
||||
|
||||
const wchar_t* to_wstring(const Nan::Utf8String& str);
|
||||
bool file_exists(std::wstring filename);
|
||||
std::wstring get_shell_path(std::wstring filename);
|
||||
|
||||
} // namespace path_util
|
||||
|
||||
#endif // NODE_PTY_PATH_UTIL_H_
|
||||
312
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/winpty.cc
generated
vendored
Normal file
312
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/win/winpty.cc
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*
|
||||
* pty.cc:
|
||||
* This file is responsible for starting processes
|
||||
* with pseudo-terminal file descriptors.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <nan.h>
|
||||
#include <Shlwapi.h> // PathCombine, PathIsRelative
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <winpty.h>
|
||||
|
||||
#include "path_util.h"
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
extern "C" void init(v8::Local<v8::Object>);
|
||||
|
||||
#define WINPTY_DBG_VARIABLE TEXT("WINPTYDBG")
|
||||
|
||||
/**
|
||||
* winpty
|
||||
*/
|
||||
static std::vector<winpty_t *> ptyHandles;
|
||||
static volatile LONG ptyCounter;
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
static winpty_t *get_pipe_handle(int handle) {
|
||||
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
||||
winpty_t *ptyHandle = ptyHandles[i];
|
||||
int current = (int)winpty_agent_process(ptyHandle);
|
||||
if (current == handle) {
|
||||
return ptyHandle;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool remove_pipe_handle(int handle) {
|
||||
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
||||
winpty_t *ptyHandle = ptyHandles[i];
|
||||
if ((int)winpty_agent_process(ptyHandle) == handle) {
|
||||
winpty_free(ptyHandle);
|
||||
ptyHandles.erase(ptyHandles.begin() + i);
|
||||
ptyHandle = nullptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void throw_winpty_error(const char *generalMsg, winpty_error_ptr_t error_ptr) {
|
||||
std::stringstream why;
|
||||
std::wstring msg(winpty_error_msg(error_ptr));
|
||||
std::string msg_(msg.begin(), msg.end());
|
||||
why << generalMsg << ": " << msg_;
|
||||
Nan::ThrowError(why.str().c_str());
|
||||
winpty_error_free(error_ptr);
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyGetExitCode) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 1 ||
|
||||
!info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.getExitCode(pidHandle)");
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeProcess((HANDLE)info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(), &exitCode);
|
||||
|
||||
info.GetReturnValue().Set(Nan::New<v8::Number>(exitCode));
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyGetProcessList) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 1 ||
|
||||
!info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.getProcessList(pid)");
|
||||
return;
|
||||
}
|
||||
|
||||
int pid = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
winpty_t *pc = get_pipe_handle(pid);
|
||||
if (pc == nullptr) {
|
||||
info.GetReturnValue().Set(Nan::New<v8::Array>(0));
|
||||
return;
|
||||
}
|
||||
int processList[64];
|
||||
const int processCount = 64;
|
||||
int actualCount = winpty_get_console_process_list(pc, processList, processCount, nullptr);
|
||||
|
||||
v8::Local<v8::Array> result = Nan::New<v8::Array>(actualCount);
|
||||
for (uint32_t i = 0; i < actualCount; i++) {
|
||||
Nan::Set(result, i, Nan::New<v8::Number>(processList[i]));
|
||||
}
|
||||
info.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyStartProcess) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 7 ||
|
||||
!info[0]->IsString() ||
|
||||
!info[1]->IsString() ||
|
||||
!info[2]->IsArray() ||
|
||||
!info[3]->IsString() ||
|
||||
!info[4]->IsNumber() ||
|
||||
!info[5]->IsNumber() ||
|
||||
!info[6]->IsBoolean()) {
|
||||
Nan::ThrowError("Usage: pty.startProcess(file, cmdline, env, cwd, cols, rows, debug)");
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream why;
|
||||
|
||||
const wchar_t *filename = path_util::to_wstring(Nan::Utf8String(info[0]));
|
||||
const wchar_t *cmdline = path_util::to_wstring(Nan::Utf8String(info[1]));
|
||||
const wchar_t *cwd = path_util::to_wstring(Nan::Utf8String(info[3]));
|
||||
|
||||
// create environment block
|
||||
std::wstring env;
|
||||
const v8::Local<v8::Array> envValues = v8::Local<v8::Array>::Cast(info[2]);
|
||||
if (!envValues.IsEmpty()) {
|
||||
|
||||
std::wstringstream envBlock;
|
||||
|
||||
for(uint32_t i = 0; i < envValues->Length(); i++) {
|
||||
std::wstring envValue(path_util::to_wstring(Nan::Utf8String(Nan::Get(envValues, i).ToLocalChecked())));
|
||||
envBlock << envValue << L'\0';
|
||||
}
|
||||
|
||||
env = envBlock.str();
|
||||
}
|
||||
|
||||
// use environment 'Path' variable to determine location of
|
||||
// the relative path that we have recieved (e.g cmd.exe)
|
||||
std::wstring shellpath;
|
||||
if (::PathIsRelativeW(filename)) {
|
||||
shellpath = path_util::get_shell_path(filename);
|
||||
} else {
|
||||
shellpath = filename;
|
||||
}
|
||||
|
||||
std::string shellpath_(shellpath.begin(), shellpath.end());
|
||||
|
||||
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
||||
why << "File not found: " << shellpath_;
|
||||
Nan::ThrowError(why.str().c_str());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int cols = info[4]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
int rows = info[5]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
bool debug = Nan::To<bool>(info[6]).FromJust();
|
||||
|
||||
// Enable/disable debugging
|
||||
SetEnvironmentVariable(WINPTY_DBG_VARIABLE, debug ? "1" : NULL); // NULL = deletes variable
|
||||
|
||||
// Create winpty config
|
||||
winpty_error_ptr_t error_ptr = nullptr;
|
||||
winpty_config_t* winpty_config = winpty_config_new(0, &error_ptr);
|
||||
if (winpty_config == nullptr) {
|
||||
throw_winpty_error("Error creating WinPTY config", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Set pty size on config
|
||||
winpty_config_set_initial_size(winpty_config, cols, rows);
|
||||
|
||||
// Start the pty agent
|
||||
winpty_t *pc = winpty_open(winpty_config, &error_ptr);
|
||||
winpty_config_free(winpty_config);
|
||||
if (pc == nullptr) {
|
||||
throw_winpty_error("Error launching WinPTY agent", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Save pty struct for later use
|
||||
ptyHandles.insert(ptyHandles.end(), pc);
|
||||
|
||||
// Create winpty spawn config
|
||||
winpty_spawn_config_t* config = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, shellpath.c_str(), cmdline, cwd, env.c_str(), &error_ptr);
|
||||
if (config == nullptr) {
|
||||
throw_winpty_error("Error creating WinPTY spawn config", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Spawn the new process
|
||||
HANDLE handle = nullptr;
|
||||
BOOL spawnSuccess = winpty_spawn(pc, config, &handle, nullptr, nullptr, &error_ptr);
|
||||
winpty_spawn_config_free(config);
|
||||
if (!spawnSuccess) {
|
||||
throw_winpty_error("Unable to start terminal process", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Set return values
|
||||
v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
||||
Nan::Set(marshal, Nan::New<v8::String>("innerPid").ToLocalChecked(), Nan::New<v8::Number>((int)GetProcessId(handle)));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("innerPidHandle").ToLocalChecked(), Nan::New<v8::Number>((int)handle));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>((int)winpty_agent_process(pc)));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(), Nan::New<v8::Number>(InterlockedIncrement(&ptyCounter)));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
|
||||
{
|
||||
LPCWSTR coninPipeName = winpty_conin_name(pc);
|
||||
std::wstring coninPipeNameWStr(coninPipeName);
|
||||
std::string coninPipeNameStr(coninPipeNameWStr.begin(), coninPipeNameWStr.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
|
||||
LPCWSTR conoutPipeName = winpty_conout_name(pc);
|
||||
std::wstring conoutPipeNameWStr(conoutPipeName);
|
||||
std::string conoutPipeNameStr(conoutPipeNameWStr.begin(), conoutPipeNameWStr.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
||||
}
|
||||
info.GetReturnValue().Set(marshal);
|
||||
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
delete filename;
|
||||
delete cmdline;
|
||||
delete cwd;
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyResize) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 3 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsNumber() ||
|
||||
!info[2]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.resize(pid, cols, rows)");
|
||||
return;
|
||||
}
|
||||
|
||||
int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
int cols = info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
int rows = info[2]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
winpty_t *pc = get_pipe_handle(handle);
|
||||
|
||||
if (pc == nullptr) {
|
||||
Nan::ThrowError("The pty doesn't appear to exist");
|
||||
return;
|
||||
}
|
||||
BOOL success = winpty_set_size(pc, cols, rows, nullptr);
|
||||
if (!success) {
|
||||
Nan::ThrowError("The pty could not be resized");
|
||||
return;
|
||||
}
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyKill) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 2 ||
|
||||
!info[0]->IsNumber() ||
|
||||
!info[1]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.kill(pid, innerPidHandle)");
|
||||
return;
|
||||
}
|
||||
|
||||
int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
HANDLE innerPidHandle = (HANDLE)info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
winpty_t *pc = get_pipe_handle(handle);
|
||||
if (pc == nullptr) {
|
||||
Nan::ThrowError("Pty seems to have been killed already");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(remove_pipe_handle(handle));
|
||||
CloseHandle(innerPidHandle);
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object> target) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::SetMethod(target, "startProcess", PtyStartProcess);
|
||||
Nan::SetMethod(target, "resize", PtyResize);
|
||||
Nan::SetMethod(target, "kill", PtyKill);
|
||||
Nan::SetMethod(target, "getExitCode", PtyGetExitCode);
|
||||
Nan::SetMethod(target, "getProcessList", PtyGetProcessList);
|
||||
};
|
||||
|
||||
NODE_MODULE(pty, init);
|
||||
94
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsPtyAgent.test.ts
generated
vendored
Normal file
94
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsPtyAgent.test.ts
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { argsToCommandLine } from './windowsPtyAgent';
|
||||
|
||||
function check(file: string, args: string | string[], expected: string): void {
|
||||
assert.equal(argsToCommandLine(file, args), expected);
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
describe('argsToCommandLine', () => {
|
||||
describe('Plain strings', () => {
|
||||
it('doesn\'t quote plain string', () => {
|
||||
check('asdf', [], 'asdf');
|
||||
});
|
||||
it('doesn\'t escape backslashes', () => {
|
||||
check('\\asdf\\qwer\\', [], '\\asdf\\qwer\\');
|
||||
});
|
||||
it('doesn\'t escape multiple backslashes', () => {
|
||||
check('asdf\\\\qwer', [], 'asdf\\\\qwer');
|
||||
});
|
||||
it('adds backslashes before quotes', () => {
|
||||
check('"asdf"qwer"', [], '\\"asdf\\"qwer\\"');
|
||||
});
|
||||
it('escapes backslashes before quotes', () => {
|
||||
check('asdf\\"qwer', [], 'asdf\\\\\\"qwer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Quoted strings', () => {
|
||||
it('quotes string with spaces', () => {
|
||||
check('asdf qwer', [], '"asdf qwer"');
|
||||
});
|
||||
it('quotes empty string', () => {
|
||||
check('', [], '""');
|
||||
});
|
||||
it('quotes string with tabs', () => {
|
||||
check('asdf\tqwer', [], '"asdf\tqwer"');
|
||||
});
|
||||
it('escapes only the last backslash', () => {
|
||||
check('\\asdf \\qwer\\', [], '"\\asdf \\qwer\\\\"');
|
||||
});
|
||||
it('doesn\'t escape multiple backslashes', () => {
|
||||
check('asdf \\\\qwer', [], '"asdf \\\\qwer"');
|
||||
});
|
||||
it('escapes backslashes before quotes', () => {
|
||||
check('asdf \\"qwer', [], '"asdf \\\\\\"qwer"');
|
||||
});
|
||||
it('escapes multiple backslashes at the end', () => {
|
||||
check('asdf qwer\\\\', [], '"asdf qwer\\\\\\\\"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple arguments', () => {
|
||||
it('joins arguments with spaces', () => {
|
||||
check('asdf', ['qwer zxcv', '', '"'], 'asdf "qwer zxcv" "" \\"');
|
||||
});
|
||||
it('array argument all in quotes', () => {
|
||||
check('asdf', ['"surounded by quotes"'], 'asdf \\"surounded by quotes\\"');
|
||||
});
|
||||
it('array argument quotes in the middle', () => {
|
||||
check('asdf', ['quotes "in the" middle'], 'asdf "quotes \\"in the\\" middle"');
|
||||
});
|
||||
it('array argument quotes near start', () => {
|
||||
check('asdf', ['"quotes" near start'], 'asdf "\\"quotes\\" near start"');
|
||||
});
|
||||
it('array argument quotes near end', () => {
|
||||
check('asdf', ['quotes "near end"'], 'asdf "quotes \\"near end\\""');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Args as CommandLine', () => {
|
||||
it('should handle empty string', () => {
|
||||
check('file', '', 'file');
|
||||
});
|
||||
it('should not change args', () => {
|
||||
check('file', 'foo bar baz', 'file foo bar baz');
|
||||
check('file', 'foo \\ba"r \baz', 'file foo \\ba"r \baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world cases', () => {
|
||||
it('quotes within quotes', () => {
|
||||
check('cmd.exe', ['/c', 'powershell -noexit -command \'Set-location \"C:\\user\"\''], 'cmd.exe /c "powershell -noexit -command \'Set-location \\\"C:\\user\\"\'"');
|
||||
});
|
||||
it('space within quotes', () => {
|
||||
check('cmd.exe', ['/k', '"C:\\Users\\alros\\Desktop\\test script.bat"'], 'cmd.exe /k \\"C:\\Users\\alros\\Desktop\\test script.bat\\"');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
313
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsPtyAgent.ts
generated
vendored
Normal file
313
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsPtyAgent.ts
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { Socket } from 'net';
|
||||
import { ArgvOrCommandLine } from './types';
|
||||
import { fork } from 'child_process';
|
||||
|
||||
let conptyNative: IConptyNative;
|
||||
let winptyNative: IWinptyNative;
|
||||
|
||||
/**
|
||||
* The amount of time to wait for additional data after the conpty shell process has exited before
|
||||
* shutting down the socket. The timer will be reset if a new data event comes in after the timer
|
||||
* has started.
|
||||
*/
|
||||
const FLUSH_DATA_INTERVAL = 20;
|
||||
|
||||
/**
|
||||
* This agent sits between the WindowsTerminal class and provides a common interface for both conpty
|
||||
* and winpty.
|
||||
*/
|
||||
export class WindowsPtyAgent {
|
||||
private _inSocket: Socket;
|
||||
private _outSocket: Socket;
|
||||
private _pid: number;
|
||||
private _innerPid: number;
|
||||
private _innerPidHandle: number;
|
||||
private _closeTimeout: NodeJS.Timer;
|
||||
private _exitCode: number | undefined;
|
||||
|
||||
private _fd: any;
|
||||
private _pty: number;
|
||||
private _ptyNative: IConptyNative | IWinptyNative;
|
||||
|
||||
public get inSocket(): Socket { return this._inSocket; }
|
||||
public get outSocket(): Socket { return this._outSocket; }
|
||||
public get fd(): any { return this._fd; }
|
||||
public get innerPid(): number { return this._innerPid; }
|
||||
public get pty(): number { return this._pty; }
|
||||
|
||||
constructor(
|
||||
file: string,
|
||||
args: ArgvOrCommandLine,
|
||||
env: string[],
|
||||
cwd: string,
|
||||
cols: number,
|
||||
rows: number,
|
||||
debug: boolean,
|
||||
private _useConpty: boolean | undefined,
|
||||
conptyInheritCursor: boolean = false
|
||||
) {
|
||||
if (this._useConpty === undefined || this._useConpty === true) {
|
||||
this._useConpty = this._getWindowsBuildNumber() >= 18309;
|
||||
}
|
||||
if (this._useConpty) {
|
||||
if (!conptyNative) {
|
||||
try {
|
||||
conptyNative = require('../build/Release/conpty.node');
|
||||
} catch (outerError) {
|
||||
try {
|
||||
conptyNative = require('../build/Debug/conpty.node');
|
||||
} catch (innerError) {
|
||||
console.error('innerError', innerError);
|
||||
// Re-throw the exception from the Release require if the Debug require fails as well
|
||||
throw outerError;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!winptyNative) {
|
||||
try {
|
||||
winptyNative = require('../build/Release/pty.node');
|
||||
} catch (outerError) {
|
||||
try {
|
||||
winptyNative = require('../build/Debug/pty.node');
|
||||
} catch (innerError) {
|
||||
console.error('innerError', innerError);
|
||||
// Re-throw the exception from the Release require if the Debug require fails as well
|
||||
throw outerError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._ptyNative = this._useConpty ? conptyNative : winptyNative;
|
||||
|
||||
// Sanitize input variable.
|
||||
cwd = path.resolve(cwd);
|
||||
|
||||
// Compose command line
|
||||
const commandLine = argsToCommandLine(file, args);
|
||||
|
||||
// Open pty session.
|
||||
let term: IConptyProcess | IWinptyProcess;
|
||||
if (this._useConpty) {
|
||||
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
|
||||
} else {
|
||||
term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
|
||||
this._pid = (term as IWinptyProcess).pid;
|
||||
this._innerPid = (term as IWinptyProcess).innerPid;
|
||||
this._innerPidHandle = (term as IWinptyProcess).innerPidHandle;
|
||||
}
|
||||
|
||||
// Not available on windows.
|
||||
this._fd = term.fd;
|
||||
|
||||
// Generated incremental number that has no real purpose besides using it
|
||||
// as a terminal id.
|
||||
this._pty = term.pty;
|
||||
|
||||
// Create terminal pipe IPC channel and forward to a local unix socket.
|
||||
this._outSocket = new Socket();
|
||||
this._outSocket.setEncoding('utf8');
|
||||
this._outSocket.connect(term.conout, () => {
|
||||
// TODO: Emit event on agent instead of socket?
|
||||
|
||||
// Emit ready event.
|
||||
this._outSocket.emit('ready_datapipe');
|
||||
});
|
||||
|
||||
this._inSocket = new Socket();
|
||||
this._inSocket.setEncoding('utf8');
|
||||
this._inSocket.connect(term.conin);
|
||||
// TODO: Wait for ready event?
|
||||
|
||||
if (this._useConpty) {
|
||||
const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, c => this._$onProcessExit(c)
|
||||
);
|
||||
this._innerPid = connect.pid;
|
||||
}
|
||||
}
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
if (this._useConpty) {
|
||||
if (this._exitCode !== undefined) {
|
||||
throw new Error('Cannot resize a pty that has already exited');
|
||||
}
|
||||
this._ptyNative.resize(this._pty, cols, rows);
|
||||
return;
|
||||
}
|
||||
this._ptyNative.resize(this._pid, cols, rows);
|
||||
}
|
||||
|
||||
public kill(): void {
|
||||
this._inSocket.readable = false;
|
||||
this._inSocket.writable = false;
|
||||
this._outSocket.readable = false;
|
||||
this._outSocket.writable = false;
|
||||
// Tell the agent to kill the pty, this releases handles to the process
|
||||
if (this._useConpty) {
|
||||
this._getConsoleProcessList().then(consoleProcessList => {
|
||||
consoleProcessList.forEach((pid: number) => {
|
||||
try {
|
||||
process.kill(pid);
|
||||
} catch (e) {
|
||||
// Ignore if process cannot be found (kill ESRCH error)
|
||||
}
|
||||
});
|
||||
(this._ptyNative as IConptyNative).kill(this._pty);
|
||||
});
|
||||
} else {
|
||||
(this._ptyNative as IWinptyNative).kill(this._pid, this._innerPidHandle);
|
||||
// Since pty.kill closes the handle it will kill most processes by itself
|
||||
// and process IDs can be reused as soon as all handles to them are
|
||||
// dropped, we want to immediately kill the entire console process list.
|
||||
// If we do not force kill all processes here, node servers in particular
|
||||
// seem to become detached and remain running (see
|
||||
// Microsoft/vscode#26807).
|
||||
const processList: number[] = (this._ptyNative as IWinptyNative).getProcessList(this._pid);
|
||||
processList.forEach(pid => {
|
||||
try {
|
||||
process.kill(pid);
|
||||
} catch (e) {
|
||||
// Ignore if process cannot be found (kill ESRCH error)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _getConsoleProcessList(): Promise<number[]> {
|
||||
return new Promise<number[]>(resolve => {
|
||||
const agent = fork(path.join(__dirname, 'conpty_console_list_agent'), [ this._innerPid.toString() ]);
|
||||
agent.on('message', message => {
|
||||
clearTimeout(timeout);
|
||||
resolve(message.consoleProcessList);
|
||||
});
|
||||
const timeout = setTimeout(() => {
|
||||
// Something went wrong, just send back the shell PID
|
||||
agent.kill();
|
||||
resolve([ this._innerPid ]);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
public get exitCode(): number {
|
||||
if (this._useConpty) {
|
||||
return this._exitCode;
|
||||
}
|
||||
return (this._ptyNative as IWinptyNative).getExitCode(this._innerPidHandle);
|
||||
}
|
||||
|
||||
private _getWindowsBuildNumber(): number {
|
||||
const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
|
||||
let buildNumber: number = 0;
|
||||
if (osVersion && osVersion.length === 4) {
|
||||
buildNumber = parseInt(osVersion[3]);
|
||||
}
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
private _generatePipeName(): string {
|
||||
return `conpty-${Math.random() * 10000000}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered from the native side when a contpy process exits.
|
||||
*/
|
||||
private _$onProcessExit(exitCode: number): void {
|
||||
this._exitCode = exitCode;
|
||||
this._flushDataAndCleanUp();
|
||||
this._outSocket.on('data', () => this._flushDataAndCleanUp());
|
||||
}
|
||||
|
||||
private _flushDataAndCleanUp(): void {
|
||||
if (this._closeTimeout) {
|
||||
clearTimeout(this._closeTimeout);
|
||||
}
|
||||
this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL);
|
||||
}
|
||||
|
||||
private _cleanUpProcess(): void {
|
||||
this._inSocket.readable = false;
|
||||
this._inSocket.writable = false;
|
||||
this._outSocket.readable = false;
|
||||
this._outSocket.writable = false;
|
||||
this._outSocket.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert argc/argv into a Win32 command-line following the escaping convention
|
||||
// documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from
|
||||
// winpty project.
|
||||
export function argsToCommandLine(file: string, args: ArgvOrCommandLine): string {
|
||||
if (isCommandLine(args)) {
|
||||
if (args.length === 0) {
|
||||
return file;
|
||||
}
|
||||
return `${argsToCommandLine(file, [])} ${args}`;
|
||||
}
|
||||
const argv = [file];
|
||||
Array.prototype.push.apply(argv, args);
|
||||
let result = '';
|
||||
for (let argIndex = 0; argIndex < argv.length; argIndex++) {
|
||||
if (argIndex > 0) {
|
||||
result += ' ';
|
||||
}
|
||||
const arg = argv[argIndex];
|
||||
// if it is empty or it contains whitespace and is not already quoted
|
||||
const hasLopsidedEnclosingQuote = xOr((arg[0] !== '"'), (arg[arg.length - 1] !== '"'));
|
||||
const hasNoEnclosingQuotes = ((arg[0] !== '"') && (arg[arg.length - 1] !== '"'));
|
||||
const quote =
|
||||
arg === '' ||
|
||||
(arg.indexOf(' ') !== -1 ||
|
||||
arg.indexOf('\t') !== -1) &&
|
||||
((arg.length > 1) &&
|
||||
(hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));
|
||||
if (quote) {
|
||||
result += '\"';
|
||||
}
|
||||
let bsCount = 0;
|
||||
for (let i = 0; i < arg.length; i++) {
|
||||
const p = arg[i];
|
||||
if (p === '\\') {
|
||||
bsCount++;
|
||||
} else if (p === '"') {
|
||||
result += repeatText('\\', bsCount * 2 + 1);
|
||||
result += '"';
|
||||
bsCount = 0;
|
||||
} else {
|
||||
result += repeatText('\\', bsCount);
|
||||
bsCount = 0;
|
||||
result += p;
|
||||
}
|
||||
}
|
||||
if (quote) {
|
||||
result += repeatText('\\', bsCount * 2);
|
||||
result += '\"';
|
||||
} else {
|
||||
result += repeatText('\\', bsCount);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isCommandLine(args: ArgvOrCommandLine): args is string {
|
||||
return typeof args === 'string';
|
||||
}
|
||||
|
||||
function repeatText(text: string, count: number): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
result += text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function xOr(arg1: boolean, arg2: boolean): boolean {
|
||||
return ((arg1 && !arg2) || (!arg1 && arg2));
|
||||
}
|
||||
205
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsTerminal.test.ts
generated
vendored
Normal file
205
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsTerminal.test.ts
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as assert from 'assert';
|
||||
import { WindowsTerminal } from './windowsTerminal';
|
||||
import * as path from 'path';
|
||||
import * as psList from 'ps-list';
|
||||
|
||||
interface IProcessState {
|
||||
// Whether the PID must exist or must not exist
|
||||
[pid: number]: boolean;
|
||||
}
|
||||
|
||||
interface IWindowsProcessTreeResult {
|
||||
name: string;
|
||||
pid: number;
|
||||
}
|
||||
|
||||
function pollForProcessState(desiredState: IProcessState, intervalMs: number = 100, timeoutMs: number = 2000): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
let tries = 0;
|
||||
const interval = setInterval(() => {
|
||||
psList({ all: true }).then(ps => {
|
||||
let success = true;
|
||||
const pids = Object.keys(desiredState).map(k => parseInt(k, 10));
|
||||
pids.forEach(pid => {
|
||||
if (desiredState[pid]) {
|
||||
if (!ps.some(p => p.pid === pid)) {
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
if (ps.some(p => p.pid === pid)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (success) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
tries++;
|
||||
if (tries * intervalMs >= timeoutMs) {
|
||||
clearInterval(interval);
|
||||
const processListing = pids.map(k => `${k}: ${desiredState[k]}`).join('\n');
|
||||
assert.fail(`Bad process state, expected:\n${processListing}`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}, intervalMs);
|
||||
});
|
||||
}
|
||||
|
||||
function pollForProcessTreeSize(pid: number, size: number, intervalMs: number = 100, timeoutMs: number = 2000): Promise<IWindowsProcessTreeResult[]> {
|
||||
return new Promise<IWindowsProcessTreeResult[]>(resolve => {
|
||||
let tries = 0;
|
||||
const interval = setInterval(() => {
|
||||
psList({ all: true }).then(ps => {
|
||||
const openList: IWindowsProcessTreeResult[] = [];
|
||||
openList.push(ps.filter(p => p.pid === pid).map(p => {
|
||||
return { name: p.name, pid: p.pid };
|
||||
})[0]);
|
||||
const list: IWindowsProcessTreeResult[] = [];
|
||||
while (openList.length) {
|
||||
const current = openList.shift();
|
||||
ps.filter(p => p.ppid === current.pid).map(p => {
|
||||
return { name: p.name, pid: p.pid };
|
||||
}).forEach(p => openList.push(p));
|
||||
list.push(current);
|
||||
}
|
||||
const success = list.length === size;
|
||||
if (success) {
|
||||
clearInterval(interval);
|
||||
resolve(list);
|
||||
return;
|
||||
}
|
||||
tries++;
|
||||
if (tries * intervalMs >= timeoutMs) {
|
||||
clearInterval(interval);
|
||||
assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}, intervalMs);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
describe('WindowsTerminal', () => {
|
||||
describe('kill', () => {
|
||||
it('should not crash parent process', (done) => {
|
||||
const term = new WindowsTerminal('cmd.exe', [], {});
|
||||
term.kill();
|
||||
// Add done call to deferred function queue to ensure the kill call has completed
|
||||
(<any>term)._defer(done);
|
||||
});
|
||||
it('should kill the process tree', function (done: Mocha.Done): void {
|
||||
this.timeout(5000);
|
||||
const term = new WindowsTerminal('cmd.exe', [], {});
|
||||
// Start sub-processes
|
||||
term.write('powershell.exe\r');
|
||||
term.write('notepad.exe\r');
|
||||
term.write('node.exe\r');
|
||||
pollForProcessTreeSize(term.pid, 4, 500, 5000).then(list => {
|
||||
assert.equal(list[0].name, 'cmd.exe');
|
||||
assert.equal(list[1].name, 'powershell.exe');
|
||||
assert.equal(list[2].name, 'notepad.exe');
|
||||
assert.equal(list[3].name, 'node.exe');
|
||||
term.kill();
|
||||
const desiredState: IProcessState = {};
|
||||
desiredState[list[0].pid] = false;
|
||||
desiredState[list[1].pid] = false;
|
||||
desiredState[list[2].pid] = true;
|
||||
desiredState[list[3].pid] = false;
|
||||
pollForProcessState(desiredState).then(() => {
|
||||
// Kill notepad before done
|
||||
process.kill(list[2].pid);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resize', () => {
|
||||
it('should throw a non-native exception when resizing an invalid value', () => {
|
||||
const term = new WindowsTerminal('cmd.exe', [], {});
|
||||
assert.throws(() => term.resize(-1, -1));
|
||||
assert.throws(() => term.resize(0, 0));
|
||||
assert.doesNotThrow(() => term.resize(1, 1));
|
||||
});
|
||||
it('should throw an non-native exception when resizing a killed terminal', (done) => {
|
||||
const term = new WindowsTerminal('cmd.exe', [], {});
|
||||
(<any>term)._defer(() => {
|
||||
term.on('exit', () => {
|
||||
assert.throws(() => term.resize(1, 1));
|
||||
done();
|
||||
});
|
||||
term.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Args as CommandLine', () => {
|
||||
it('should not fail running a file containing a space in the path', (done) => {
|
||||
const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');
|
||||
if (!fs.existsSync(spaceFolder)) {
|
||||
fs.mkdirSync(spaceFolder);
|
||||
}
|
||||
|
||||
const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');
|
||||
const data = fs.readFileSync(`${process.env.windir}\\System32\\cmd.exe`);
|
||||
fs.writeFileSync(cmdCopiedPath, data);
|
||||
|
||||
if (!fs.existsSync(cmdCopiedPath)) {
|
||||
// Skip test if git bash isn't installed
|
||||
return;
|
||||
}
|
||||
const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', {});
|
||||
let result = '';
|
||||
term.on('data', (data) => {
|
||||
result += data;
|
||||
});
|
||||
term.on('exit', () => {
|
||||
assert.ok(result.indexOf('hello world') >= 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('env', () => {
|
||||
it('should set environment variables of the shell', (done) => {
|
||||
const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { env: { FOO: 'BAR' }});
|
||||
let result = '';
|
||||
term.on('data', (data) => {
|
||||
result += data;
|
||||
});
|
||||
term.on('exit', () => {
|
||||
assert.ok(result.indexOf('BAR') >= 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('On close', () => {
|
||||
it('should return process zero exit codes', (done) => {
|
||||
const term = new WindowsTerminal('cmd.exe', '/C exit');
|
||||
term.on('exit', (code) => {
|
||||
assert.equal(code, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return process non-zero exit codes', (done) => {
|
||||
const term = new WindowsTerminal('cmd.exe', '/C exit 2');
|
||||
term.on('exit', (code) => {
|
||||
assert.equal(code, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
187
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsTerminal.ts
generated
vendored
Normal file
187
lisp/emacs-application-framework/app/terminal/node_modules/node-pty/src/windowsTerminal.ts
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
import { Socket } from 'net';
|
||||
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
|
||||
import { WindowsPtyAgent } from './windowsPtyAgent';
|
||||
import { IPtyOpenOptions, IWindowsPtyForkOptions } from './interfaces';
|
||||
import { ArgvOrCommandLine } from './types';
|
||||
import { assign } from './utils';
|
||||
|
||||
const DEFAULT_FILE = 'cmd.exe';
|
||||
const DEFAULT_NAME = 'Windows Shell';
|
||||
|
||||
export class WindowsTerminal extends Terminal {
|
||||
private _isReady: boolean;
|
||||
private _deferreds: any[];
|
||||
private _agent: WindowsPtyAgent;
|
||||
|
||||
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) {
|
||||
super(opt);
|
||||
|
||||
// Initialize arguments
|
||||
args = args || [];
|
||||
file = file || DEFAULT_FILE;
|
||||
opt = opt || {};
|
||||
opt.env = opt.env || process.env;
|
||||
|
||||
if (opt.encoding) {
|
||||
console.warn('Setting encoding on Windows is not supported');
|
||||
}
|
||||
|
||||
const env = assign({}, opt.env);
|
||||
this._cols = opt.cols || DEFAULT_COLS;
|
||||
this._rows = opt.rows || DEFAULT_ROWS;
|
||||
const cwd = opt.cwd || process.cwd();
|
||||
const name = opt.name || env.TERM || DEFAULT_NAME;
|
||||
const parsedEnv = this._parseEnv(env);
|
||||
|
||||
// If the terminal is ready
|
||||
this._isReady = false;
|
||||
|
||||
// Functions that need to run after `ready` event is emitted.
|
||||
this._deferreds = [];
|
||||
|
||||
// Create new termal.
|
||||
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.conptyInheritCursor);
|
||||
this._socket = this._agent.outSocket;
|
||||
|
||||
// Not available until `ready` event emitted.
|
||||
this._pid = this._agent.innerPid;
|
||||
this._fd = this._agent.fd;
|
||||
this._pty = this._agent.pty;
|
||||
|
||||
// The forked windows terminal is not available until `ready` event is
|
||||
// emitted.
|
||||
this._socket.on('ready_datapipe', () => {
|
||||
|
||||
// These events needs to be forwarded.
|
||||
['connect', 'data', 'end', 'timeout', 'drain'].forEach(event => {
|
||||
this._socket.on(event, () => {
|
||||
|
||||
// Wait until the first data event is fired then we can run deferreds.
|
||||
if (!this._isReady && event === 'data') {
|
||||
|
||||
// Terminal is now ready and we can avoid having to defer method
|
||||
// calls.
|
||||
this._isReady = true;
|
||||
|
||||
// Execute all deferred methods
|
||||
this._deferreds.forEach(fn => {
|
||||
// NB! In order to ensure that `this` has all its references
|
||||
// updated any variable that need to be available in `this` before
|
||||
// the deferred is run has to be declared above this forEach
|
||||
// statement.
|
||||
fn.run();
|
||||
});
|
||||
|
||||
// Reset
|
||||
this._deferreds = [];
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Shutdown if `error` event is emitted.
|
||||
this._socket.on('error', err => {
|
||||
// Close terminal session.
|
||||
this._close();
|
||||
|
||||
// EIO, happens when someone closes our child process: the only process
|
||||
// in the terminal.
|
||||
// node < 0.6.14: errno 5
|
||||
// node >= 0.6.14: read EIO
|
||||
if ((<any>err).code) {
|
||||
if (~(<any>err).code.indexOf('errno 5') || ~(<any>err).code.indexOf('EIO')) return;
|
||||
}
|
||||
|
||||
// Throw anything else.
|
||||
if (this.listeners('error').length < 2) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup after the socket is closed.
|
||||
this._socket.on('close', () => {
|
||||
this.emit('exit', this._agent.exitCode);
|
||||
this._close();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this._file = file;
|
||||
this._name = name;
|
||||
|
||||
this._readable = true;
|
||||
this._writable = true;
|
||||
|
||||
this._forwardEvents();
|
||||
}
|
||||
|
||||
protected _write(data: string): void {
|
||||
this._defer(this._doWrite, data);
|
||||
}
|
||||
|
||||
private _doWrite(data: string): void {
|
||||
this._agent.inSocket.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* openpty
|
||||
*/
|
||||
|
||||
public static open(options?: IPtyOpenOptions): void {
|
||||
throw new Error('open() not supported on windows, use Fork() instead.');
|
||||
}
|
||||
|
||||
/**
|
||||
* TTY
|
||||
*/
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
|
||||
throw new Error('resizing must be done using positive cols and rows');
|
||||
}
|
||||
this._defer(() => {
|
||||
this._agent.resize(cols, rows);
|
||||
this._cols = cols;
|
||||
this._rows = rows;
|
||||
});
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._defer(() => {
|
||||
this.kill();
|
||||
});
|
||||
}
|
||||
|
||||
public kill(signal?: string): void {
|
||||
this._defer(() => {
|
||||
if (signal) {
|
||||
throw new Error('Signals not supported on windows.');
|
||||
}
|
||||
this._close();
|
||||
this._agent.kill();
|
||||
});
|
||||
}
|
||||
|
||||
private _defer<A extends any>(deferredFn: (arg?: A) => void, arg?: A): void {
|
||||
// If the terminal is ready, execute.
|
||||
if (this._isReady) {
|
||||
deferredFn.call(this, arg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue until terminal is ready.
|
||||
this._deferreds.push({
|
||||
run: () => deferredFn.call(this, arg)
|
||||
});
|
||||
}
|
||||
|
||||
public get process(): string { return this._name; }
|
||||
public get master(): Socket { throw new Error('master is not supported on Windows'); }
|
||||
public get slave(): Socket { throw new Error('slave is not supported on Windows'); }
|
||||
}
|
||||
Reference in New Issue
Block a user