NapCatQQ/packages/napcat-pty/windowsTerminal.ts
手瓜一十雪 4360775eff
refactor: 整体重构 (#1381)
* feat: pnpm new

* Refactor build and release workflows, update dependencies

Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
2025-11-13 15:39:42 +08:00

207 lines
6.0 KiB
TypeScript

/**
* 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';
// @ts-check
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from '@homebridge/node-pty-prebuilt-multiarch/src/terminal';
import { WindowsPtyAgent } from './windowsPtyAgent';
import { IPtyOpenOptions, IWindowsPtyForkOptions } from '@homebridge/node-pty-prebuilt-multiarch/src/interfaces';
import { ArgvOrCommandLine } from '@homebridge/node-pty-prebuilt-multiarch/src/types';
import { assign } from '@homebridge/node-pty-prebuilt-multiarch/src/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);
this._checkType('args', args, 'string', true);
// 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.useConptyDll, 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._deferNoArgs(() => {
this._agent.resize(cols, rows);
this._cols = cols;
this._rows = rows;
});
}
public clear (): void {
this._deferNoArgs(() => {
this._agent.clear();
});
}
public destroy (): void {
this._deferNoArgs(() => {
this.kill();
});
}
public kill (signal?: string): void {
this._deferNoArgs(() => {
if (signal) {
throw new Error('Signals not supported on windows.');
}
this._close();
this._agent.kill();
});
}
// @ts-expect-error - This is a private method that is not part of the public API.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private _deferNoArgs<A> (deferredFn: () => void): void {
// If the terminal is ready, execute.
if (this._isReady) {
deferredFn.call(this);
return;
}
// Queue until terminal is ready.
this._deferreds.push({
run: () => deferredFn.call(this),
});
}
private _defer<A> (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'); }
}