diff options
Diffstat (limited to '')
-rw-r--r-- | html/src/components/terminal/index.tsx | 91 |
1 files changed, 63 insertions, 28 deletions
diff --git a/html/src/components/terminal/index.tsx b/html/src/components/terminal/index.tsx index 1efba2e..af1ab36 100644 --- a/html/src/components/terminal/index.tsx +++ b/html/src/components/terminal/index.tsx @@ -1,14 +1,18 @@ import { bind } from 'decko'; import { Component, h } from 'preact'; -import { ITerminalOptions, RendererType, Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import { 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 { OverlayAddon } from './overlay'; import { ZmodemAddon, FlowControl } from '../zmodem'; import 'xterm/css/xterm.css'; +import worker from 'xterm-addon-image/lib/xterm-addon-image-worker'; + +const imageWorkerUrl = window.URL.createObjectURL(new Blob([worker], { type: 'text/javascript' })); interface TtydTerminal extends Terminal { fit(): void; @@ -33,8 +37,10 @@ const enum Command { RESUME = '3', } +export type RendererType = 'dom' | 'canvas' | 'webgl'; + export interface ClientOptions { - rendererType: 'dom' | 'canvas' | 'webgl'; + rendererType: RendererType; disableLeaveAlert: boolean; disableResizeOverlay: boolean; titleFixed: string; @@ -58,6 +64,7 @@ export class Xterm extends Component<Props> { private overlayAddon: OverlayAddon; private zmodemAddon: ZmodemAddon; private webglAddon: WebglAddon; + private canvasAddon: CanvasAddon; private socket: WebSocket; private token: string; @@ -176,6 +183,7 @@ export class Xterm extends Component<Props> { terminal.loadAddon(overlayAddon); terminal.loadAddon(new WebLinksAddon()); terminal.loadAddon(this.zmodemAddon); + terminal.loadAddon(new ImageAddon(imageWorkerUrl)); terminal.onTitleChange(data => { if (data && data !== '' && !this.titleFixed) { @@ -208,9 +216,16 @@ export class Xterm extends Component<Props> { } @bind - private setRendererType(value: 'webgl' | RendererType) { + private setRendererType(value: RendererType) { const { terminal } = this; - + const disposeCanvasRenderer = () => { + try { + this.canvasAddon?.dispose(); + } catch { + // ignore + } + this.canvasAddon = undefined; + }; const disposeWebglRenderer = () => { try { this.webglAddon?.dispose(); @@ -219,33 +234,50 @@ export class Xterm extends Component<Props> { } this.webglAddon = undefined; }; + const enableCanvasRenderer = () => { + if (this.canvasAddon) return; + this.canvasAddon = new CanvasAddon(); + disposeWebglRenderer(); + try { + this.terminal.loadAddon(this.canvasAddon); + console.log(`[ttyd] canvas renderer loaded`); + } catch (e) { + console.log(`[ttyd] canvas renderer could not be loaded, falling back to dom renderer`, e); + disposeCanvasRenderer(); + } + }; + const enableWebglRenderer = () => { + if (this.webglAddon) return; + this.webglAddon = new WebglAddon(); + disposeCanvasRenderer(); + try { + this.webglAddon.onContextLoss(() => { + this.webglAddon?.dispose(); + }); + terminal.loadAddon(this.webglAddon); + console.log(`[ttyd] WebGL renderer loaded`); + } catch (e) { + console.log(`[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer`, e); + disposeWebglRenderer(); + enableCanvasRenderer(); + } + }; switch (value) { + case 'canvas': + enableCanvasRenderer(); + break; case 'webgl': - if (this.webglAddon) return; - try { - if (window.WebGL2RenderingContext && document.createElement('canvas').getContext('webgl2')) { - this.webglAddon = new WebglAddon(); - this.webglAddon.onContextLoss(() => { - disposeWebglRenderer(); - }); - terminal.loadAddon(this.webglAddon); - console.log(`[ttyd] WebGL renderer enabled`); - } - } catch (e) { - console.warn(`[ttyd] webgl2 init error`, e); - } + enableWebglRenderer(); break; + case 'dom': default: - disposeWebglRenderer(); - console.log(`[ttyd] option: rendererType=${value}`); - terminal.options.rendererType = value; break; } } @bind - private applyOptions(options: any) { + private applyOptions(options: ITerminalOptions) { const { terminal, fitAddon } = this; Object.keys(options).forEach(key => { @@ -270,6 +302,7 @@ export class Xterm extends Component<Props> { if (value) { console.log(`[ttyd] Reconnect disabled`); this.reconnect = false; + this.doReconnect = false; } break; case 'titleFixed': @@ -368,7 +401,8 @@ export class Xterm extends Component<Props> { break; case Command.SET_PREFERENCES: const prefs = JSON.parse(textDecoder.decode(data)); - this.applyOptions(Object.assign({}, this.props.clientOptions, prefs)); + const options = Object.assign({}, this.props.clientOptions, prefs) as ITerminalOptions; + this.applyOptions(options); break; default: console.warn(`[ttyd] unknown command: ${cmd}`); @@ -379,10 +413,11 @@ export class Xterm extends Component<Props> { @bind private onTerminalResize(size: { cols: number; rows: number }) { const { overlayAddon, socket, textEncoder, resizeOverlay } = this; - if (socket && socket.readyState === WebSocket.OPEN) { - const msg = JSON.stringify({ columns: size.cols, rows: size.rows }); - socket.send(textEncoder.encode(Command.RESIZE_TERMINAL + msg)); - } + if (!socket || socket.readyState !== WebSocket.OPEN) return; + + const msg = JSON.stringify({ columns: size.cols, rows: size.rows }); + socket.send(textEncoder.encode(Command.RESIZE_TERMINAL + msg)); + if (resizeOverlay) { setTimeout(() => { overlayAddon.showOverlay(`${size.cols}x${size.rows}`); |