Feat: Terminal size sync

This commit is contained in:
unitwk 2024-03-11 17:40:50 +08:00
parent 50a7a5cb9e
commit 70019f8701
6 changed files with 54 additions and 22 deletions

View File

@ -15,9 +15,10 @@ export default class DockerResizeCommand extends InstanceCommand {
async exec(instance: Instance, size?: IResizeOptions): Promise<any> {
const dockerProcess = instance?.process as Partial<DockerProcessAdapter>;
if (typeof dockerProcess?.container?.resize === "function") {
const { w, h } = instance.computeTerminalSize();
await dockerProcess?.container?.resize({
h: size.h,
w: size.w
h,
w
});
}
}

View File

@ -2,20 +2,16 @@ import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import { GoPtyProcessAdapter } from "./pty_start";
interface IResizeOptions {
h: number;
w: number;
}
export default class PtyResizeCommand extends InstanceCommand {
constructor() {
super("ResizeTTY");
}
async exec(instance: Instance, size?: IResizeOptions): Promise<any> {
async exec(instance: Instance): Promise<any> {
const pty = instance.process as Partial<GoPtyProcessAdapter>;
if (typeof pty?.resize === "function") {
pty?.resize(size.w, size.h);
const { w, h } = instance.computeTerminalSize();
pty?.resize(w, h);
}
}
}

View File

@ -13,7 +13,6 @@ import FunctionDispatcher from "../commands/dispatcher";
import { IInstanceProcess } from "./interface";
import StartCommand from "../commands/start";
import { configureEntityParams } from "common";
import { PTY_PATH } from "../../const";
import { OpenFrp } from "../commands/task/openfrp";
// The instance does not need to store additional information persistently
@ -26,6 +25,13 @@ interface IInstanceInfo {
openFrpStatus: boolean;
}
interface IWatcherInfo {
terminalSize: {
w: number;
h: number;
};
}
// instance class
export default class Instance extends EventEmitter {
public static readonly STATUS_BUSY = -1;
@ -60,6 +66,8 @@ export default class Instance extends EventEmitter {
openFrpStatus: false
};
public watchers: Map<string, IWatcherInfo> = new Map();
public process: IInstanceProcess;
// When initializing an instance, the instance must be initialized through uuid and configuration class, otherwise the instance will be unavailable
@ -352,4 +360,20 @@ export default class Instance extends EventEmitter {
clearPreset() {
this.presetCommandManager.clearPreset();
}
computeTerminalSize() {
let minW = this.config.terminalOption.ptyWindowCol;
let minH = this.config.terminalOption.ptyWindowRow;
for (const iterator of this.watchers.values()) {
const { w, h } = iterator.terminalSize;
if (w && h) {
if (w < minW) minW = w;
if (h < minH) minH = h;
}
}
return {
w: minW,
h: minH
};
}
}

View File

@ -125,11 +125,21 @@ routerApp.on("stream/write", async (ctx, data) => {
});
// handle terminal resize
// interface IResizeOptions {
// h: number;
// w: number;
// }
routerApp.on("stream/resize", async (ctx, data) => {
try {
const instanceUuid = ctx.session?.stream?.instanceUuid;
const instance = InstanceSubsystem.getInstance(instanceUuid);
if (instance) await instance.execPreset("resize", data);
instance.watchers.set(ctx.socket.id, {
terminalSize: {
w: Number(data.w) || 0,
h: Number(data.h) || 0
}
});
if (instance) await instance.execPreset("resize");
} catch (error) {
// protocol.responseError(ctx, error);
}

View File

@ -195,6 +195,8 @@ class InstanceSubsystem extends EventEmitter {
stopForward(targetInstanceUuid: string, socket: Socket) {
try {
const instance = this.getInstance(targetInstanceUuid);
instance.watchers.delete(socket.id);
this.instanceStream.cannelForward(socket, targetInstanceUuid);
} catch (err) {}
}

View File

@ -150,15 +150,13 @@ export function useTerminal() {
};
const refreshWindowSize = (w: number, h: number) => {
if (cachedSize.h !== h || cachedSize.w !== w) {
cachedSize = {
w,
h
};
socket?.emit("stream/resize", {
data: cachedSize
});
}
cachedSize = {
w,
h
};
socket?.emit("stream/resize", {
data: cachedSize
});
};
const initTerminalWindow = (element: HTMLElement) => {
@ -183,13 +181,14 @@ export function useTerminal() {
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(element);
// Auto resize pty win size
fitAddon.fit();
refreshWindowSize(term.cols - 1, term.rows - 1);
fitAddonTask = setInterval(() => {
fitAddon.fit();
refreshWindowSize(term.cols - 1, term.rows - 1);
// Auto resize pty win size
}, 1000);
}, 2000);
term.onData((data) => {
socket?.emit("stream/write", {