From 6963f5f48ed68cb42b92e40c84014bd12cd377fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 8 Apr 2024 10:14:42 +0200 Subject: Adding upstream version 1.7.7. Signed-off-by: Daniel Baumann --- html/src/components/app.tsx | 7 +- html/src/components/terminal/index.tsx | 2 +- .../components/terminal/xterm/addons/overlay.ts | 2 +- .../src/components/terminal/xterm/addons/zmodem.ts | 20 ++++- html/src/components/terminal/xterm/index.ts | 99 ++++++++++++++++++---- 5 files changed, 110 insertions(+), 20 deletions(-) (limited to 'html/src/components') diff --git a/html/src/components/app.tsx b/html/src/components/app.tsx index 71a2d89..9057727 100644 --- a/html/src/components/app.tsx +++ b/html/src/components/app.tsx @@ -1,9 +1,10 @@ import { h, Component } from 'preact'; -import { ITerminalOptions, ITheme } from 'xterm'; -import { ClientOptions, FlowControl } from './terminal/xterm'; import { Terminal } from './terminal'; +import type { ITerminalOptions, ITheme } from '@xterm/xterm'; +import type { ClientOptions, FlowControl } from './terminal/xterm'; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const path = window.location.pathname.replace(/[/]+$/, ''); const wsUrl = [protocol, '//', window.location.host, path, '/ws', window.location.search].join(''); @@ -15,6 +16,8 @@ const clientOptions = { enableZmodem: false, enableTrzsz: false, enableSixel: false, + isWindows: false, + unicodeVersion: '11', } as ClientOptions; const termOptions = { fontSize: 13, diff --git a/html/src/components/terminal/index.tsx b/html/src/components/terminal/index.tsx index a7349fd..b42bf15 100644 --- a/html/src/components/terminal/index.tsx +++ b/html/src/components/terminal/index.tsx @@ -2,7 +2,7 @@ import { bind } from 'decko'; import { Component, h } from 'preact'; import { Xterm, XtermOptions } from './xterm'; -import 'xterm/css/xterm.css'; +import '@xterm/xterm/css/xterm.css'; import { Modal } from '../modal'; interface Props extends XtermOptions { diff --git a/html/src/components/terminal/xterm/addons/overlay.ts b/html/src/components/terminal/xterm/addons/overlay.ts index 6fa5a92..74da079 100644 --- a/html/src/components/terminal/xterm/addons/overlay.ts +++ b/html/src/components/terminal/xterm/addons/overlay.ts @@ -1,7 +1,7 @@ // ported from hterm.Terminal.prototype.showOverlay // https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js import { bind } from 'decko'; -import { ITerminalAddon, Terminal } from 'xterm'; +import { ITerminalAddon, Terminal } from '@xterm/xterm'; export class OverlayAddon implements ITerminalAddon { private terminal: Terminal; diff --git a/html/src/components/terminal/xterm/addons/zmodem.ts b/html/src/components/terminal/xterm/addons/zmodem.ts index 3d38c5c..52627ef 100644 --- a/html/src/components/terminal/xterm/addons/zmodem.ts +++ b/html/src/components/terminal/xterm/addons/zmodem.ts @@ -1,12 +1,14 @@ import { bind } from 'decko'; import { saveAs } from 'file-saver'; -import { IDisposable, ITerminalAddon, Terminal } from 'xterm'; +import { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; import * as Zmodem from 'zmodem.js/src/zmodem_browser'; import { TrzszFilter } from 'trzsz'; export interface ZmodeOptions { zmodem: boolean; trzsz: boolean; + windows: boolean; + trzszDragInitTimeout: number; onSend: () => void; sender: (data: string | Uint8Array) => void; writer: (data: string | Uint8Array) => void; @@ -54,6 +56,11 @@ export class ZmodemAddon implements ITerminalAddon { this.terminal.focus(); } + private addDisposableListener(target: EventTarget, type: string, listener: EventListener) { + target.addEventListener(type, listener); + this.disposables.push({ dispose: () => target.removeEventListener(type, listener) }); + } + @bind private trzszInit() { const { terminal } = this; @@ -68,6 +75,17 @@ export class ZmodemAddon implements ITerminalAddon { }, sendToServer: data => sender(data), terminalColumns: terminal.cols, + isWindowsShell: this.options.windows, + dragInitTimeout: this.options.trzszDragInitTimeout, + }); + const element = terminal.element as EventTarget; + this.addDisposableListener(element, 'dragover', event => event.preventDefault()); + this.addDisposableListener(element, 'drop', event => { + event.preventDefault(); + this.trzszFilter + .uploadFiles((event as DragEvent).dataTransfer?.items as DataTransferItemList) + .then(() => console.log('[ttyd] upload success')) + .catch(err => console.log('[ttyd] upload failed: ' + err)); }); this.disposables.push(terminal.onResize(size => this.trzszFilter.setTerminalColumns(size.cols))); } diff --git a/html/src/components/terminal/xterm/index.ts b/html/src/components/terminal/xterm/index.ts index e6809e4..b643f92 100644 --- a/html/src/components/terminal/xterm/index.ts +++ b/html/src/components/terminal/xterm/index.ts @@ -1,14 +1,16 @@ import { bind } from 'decko'; -import { IDisposable, ITerminalOptions, Terminal } from 'xterm'; -import { CanvasAddon } from 'xterm-addon-canvas'; -import { WebglAddon } from 'xterm-addon-webgl'; -import { FitAddon } from 'xterm-addon-fit'; -import { WebLinksAddon } from 'xterm-addon-web-links'; -import { ImageAddon } from 'xterm-addon-image'; +import type { IDisposable, ITerminalOptions } from '@xterm/xterm'; +import { Terminal } from '@xterm/xterm'; +import { CanvasAddon } from '@xterm/addon-canvas'; +import { WebglAddon } from '@xterm/addon-webgl'; +import { FitAddon } from '@xterm/addon-fit'; +import { WebLinksAddon } from '@xterm/addon-web-links'; +import { ImageAddon } from '@xterm/addon-image'; +import { Unicode11Addon } from '@xterm/addon-unicode11'; import { OverlayAddon } from './addons/overlay'; import { ZmodemAddon } from './addons/zmodem'; -import 'xterm/css/xterm.css'; +import '@xterm/xterm/css/xterm.css'; interface TtydTerminal extends Terminal { fit(): void; @@ -20,7 +22,7 @@ declare global { } } -const enum Command { +enum Command { // server side OUTPUT = '0', SET_WINDOW_TITLE = '1', @@ -44,6 +46,9 @@ export interface ClientOptions { enableTrzsz: boolean; enableSixel: boolean; titleFixed?: string; + isWindows: boolean; + trzszDragInitTimeout: number; + unicodeVersion: string; } export interface FlowControl { @@ -94,7 +99,10 @@ export class Xterm { private writeFunc = (data: ArrayBuffer) => this.writeData(new Uint8Array(data)); - constructor(private options: XtermOptions, private sendCb: () => void) {} + constructor( + private options: XtermOptions, + private sendCb: () => void + ) {} dispose() { for (const d of this.disposables) { @@ -199,13 +207,13 @@ export class Xterm { terminal.write(data, () => { this.pending = Math.max(this.pending - 1, 0); if (this.pending < lowWater) { - this.socket?.send(textEncoder.encode(Command.PAUSE)); + this.socket?.send(textEncoder.encode(Command.RESUME)); } }); this.pending++; this.written = 0; if (this.pending > highWater) { - this.socket?.send(textEncoder.encode(Command.RESUME)); + this.socket?.send(textEncoder.encode(Command.PAUSE)); } } else { terminal.write(data); @@ -218,7 +226,10 @@ export class Xterm { if (socket?.readyState !== WebSocket.OPEN) return; if (typeof data === 'string') { - socket.send(textEncoder.encode(Command.INPUT + data)); + const payload = new Uint8Array(data.length * 3 + 1); + payload[0] = Command.INPUT.charCodeAt(0); + const stats = textEncoder.encodeInto(data, payload.subarray(1)); + socket.send(payload.subarray(0, (stats.written as number) + 1)); } else { const payload = new Uint8Array(data.length + 1); payload[0] = Command.INPUT.charCodeAt(0); @@ -286,6 +297,40 @@ export class Xterm { } } + @bind + private parseOptsFromUrlQuery(query: string): Preferences { + const { terminal } = this; + const { clientOptions } = this.options; + const prefs = {} as Preferences; + const queryObj = Array.from(new URLSearchParams(query) as unknown as Iterable<[string, string]>); + + for (const [k, queryVal] of queryObj) { + let v = clientOptions[k]; + if (v === undefined) v = terminal.options[k]; + switch (typeof v) { + case 'boolean': + prefs[k] = queryVal === 'true' || queryVal === '1'; + break; + case 'number': + case 'bigint': + prefs[k] = Number.parseInt(queryVal, 10); + break; + case 'string': + prefs[k] = queryVal; + break; + case 'object': + prefs[k] = JSON.parse(queryVal); + break; + default: + console.warn(`[ttyd] maybe unknown option: ${k}=${queryVal}, treating as string`); + prefs[k] = queryVal; + break; + } + } + + return prefs; + } + @bind private onSocketData(event: MessageEvent) { const { textDecoder } = this; @@ -305,6 +350,7 @@ export class Xterm { this.applyPreferences({ ...this.options.clientOptions, ...JSON.parse(textDecoder.decode(data)), + ...this.parseOptsFromUrlQuery(window.location.search), } as Preferences); break; default: @@ -320,6 +366,8 @@ export class Xterm { this.zmodemAddon = new ZmodemAddon({ zmodem: prefs.enableZmodem, trzsz: prefs.enableTrzsz, + windows: prefs.isWindows, + trzszDragInitTimeout: prefs.trzszDragInitTimeout, onSend: this.sendCb, sender: this.sendData, writer: this.writeData, @@ -327,8 +375,8 @@ export class Xterm { this.writeFunc = data => this.zmodemAddon?.consume(data); terminal.loadAddon(register(this.zmodemAddon)); } - Object.keys(prefs).forEach(key => { - const value = prefs[key]; + + for (const [key, value] of Object.entries(prefs)) { switch (key) { case 'rendererType': this.setRendererType(value); @@ -358,6 +406,9 @@ export class Xterm { case 'enableTrzsz': if (value) console.log('[ttyd] trzsz enabled'); break; + case 'trzszDragInitTimeout': + if (value) console.log(`[ttyd] trzsz drag init timeout: ${value}`); + break; case 'enableSixel': if (value) { terminal.loadAddon(register(new ImageAddon())); @@ -370,6 +421,24 @@ export class Xterm { this.titleFixed = value; document.title = value; break; + case 'isWindows': + if (value) console.log('[ttyd] is windows'); + break; + case 'unicodeVersion': + switch (value) { + case 6: + case '6': + console.log('[ttyd] setting Unicode version: 6'); + break; + case 11: + case '11': + default: + console.log('[ttyd] setting Unicode version: 11'); + terminal.loadAddon(new Unicode11Addon()); + terminal.unicode.activeVersion = '11'; + break; + } + break; default: console.log(`[ttyd] option: ${key}=${JSON.stringify(value)}`); if (terminal.options[key] instanceof Object) { @@ -380,7 +449,7 @@ export class Xterm { if (key.indexOf('font') === 0) fitAddon.fit(); break; } - }); + } } @bind -- cgit v1.2.3