summaryrefslogtreecommitdiffstats
path: root/html/src/components/terminal/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'html/src/components/terminal/index.tsx')
-rw-r--r--html/src/components/terminal/index.tsx91
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}`);