From 7b87b7d7b95b101e02eefc2863fb412dd557030e Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Wed, 7 Aug 2024 14:27:16 -0700 Subject: [PATCH] Add WebGL acceleration and link handling to terminal (#205) This PR adds back WebGL acceleration (enabled by default) for XTerm. It also adds back link handling and adds a new option (disabled by default) to open all links internally as a new web block. --------- Co-authored-by: sawka --- frontend/app/store/global.ts | 15 +++++ .../app/view/preview/directorypreview.tsx | 2 +- frontend/app/view/term/term.tsx | 4 +- frontend/app/view/term/termwrap.ts | 56 ++++++++++++++++++- frontend/app/view/webview/webview.tsx | 4 +- frontend/types/gotypes.d.ts | 7 +++ package.json | 2 + pkg/wconfig/settingsconfig.go | 10 +++- yarn.lock | 20 +++++++ 9 files changed, 110 insertions(+), 10 deletions(-) diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 0b2bc916..f89231cb 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -435,6 +435,20 @@ function isDev() { return cachedIsDev; } +async function openLink(uri: string) { + if (globalStore.get(atoms.settingsConfigAtom)?.web?.openlinksinternally) { + const blockDef: BlockDef = { + meta: { + view: "web", + url: uri, + }, + }; + await createBlock(blockDef); + } else { + getApi().openExternal(uri); + } +} + export { PLATFORM, WOS, @@ -451,6 +465,7 @@ export { initGlobal, initWS, isDev, + openLink, sendWSCommand, setBlockFocus, setPlatform, diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 3612f933..bffe11cd 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -425,7 +425,7 @@ function TableBody({ { label: "Open Preview in New Block", click: async () => { - const blockDef = { + const blockDef: BlockDef = { meta: { view: "preview", file: path, diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 3d7ffb29..5e64a102 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -9,14 +9,13 @@ import * as keyutil from "@/util/keyutil"; import clsx from "clsx"; import { produce } from "immer"; import * as jotai from "jotai"; +import "public/xterm.css"; import * as React from "react"; import { TermStickers } from "./termsticker"; import { TermThemeUpdater } from "./termtheme"; import { computeTheme } from "./termutil"; import { TermWrap } from "./termwrap"; -import "public/xterm.css"; - const keyMap = { Enter: "\r", Backspace: "\x7f", @@ -248,6 +247,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { }, { keydownHandler: handleTerminalKeydown, + useWebGl: !termSettings?.disablewebgl, } ); (window as any).term = termWrap; diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index eb5ebfa0..b465c99a 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -2,15 +2,36 @@ // SPDX-License-Identifier: Apache-2.0 import { WshServer } from "@/app/store/wshserver"; -import { PLATFORM, fetchWaveFile, getFileSubject, sendWSCommand } from "@/store/global"; +import { PLATFORM, fetchWaveFile, getFileSubject, openLink, sendWSCommand } from "@/store/global"; import * as services from "@/store/services"; -import { base64ToArray } from "@/util/util"; +import { base64ToArray, fireAndForget } from "@/util/util"; import { SerializeAddon } from "@xterm/addon-serialize"; +import { WebLinksAddon } from "@xterm/addon-web-links"; +import { WebglAddon } from "@xterm/addon-webgl"; import * as TermTypes from "@xterm/xterm"; import { Terminal } from "@xterm/xterm"; import { debounce } from "throttle-debounce"; import { FitAddon } from "./fitaddon"; +// detect webgl support +function detectWebGLSupport(): boolean { + try { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("webgl"); + return !!ctx; + } catch (e) { + return false; + } +} + +const WebGLSupported = detectWebGLSupport(); +let loggedWebGL = false; + +type TermWrapOptions = { + keydownHandler?: (e: KeyboardEvent) => void; + useWebGl?: boolean; +}; + export class TermWrap { blockId: string; ptyOffset: number; @@ -30,7 +51,7 @@ export class TermWrap { blockId: string, connectElem: HTMLDivElement, options: TermTypes.ITerminalOptions & TermTypes.ITerminalInitOnlyOptions, - waveOptions: { keydownHandler?: (e: KeyboardEvent) => void } + waveOptions: TermWrapOptions ) { this.blockId = blockId; this.ptyOffset = 0; @@ -41,6 +62,35 @@ export class TermWrap { this.serializeAddon = new SerializeAddon(); this.terminal.loadAddon(this.fitAddon); this.terminal.loadAddon(this.serializeAddon); + + this.terminal.loadAddon( + new WebLinksAddon((e, uri) => { + e.preventDefault(); + switch (PLATFORM) { + case "darwin": + if (e.metaKey) { + fireAndForget(() => openLink(uri)); + } + break; + default: + if (e.ctrlKey) { + fireAndForget(() => openLink(uri)); + } + break; + } + }) + ); + if (WebGLSupported && waveOptions.useWebGl) { + const webglAddon = new WebglAddon(); + webglAddon.onContextLoss(() => { + webglAddon.dispose(); + }); + this.terminal.loadAddon(webglAddon); + if (!loggedWebGL) { + console.log("loaded webgl!"); + loggedWebGL = true; + } + } this.connectElem = connectElem; this.mainFileSubject = null; this.loaded = false; diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index 4d672dfc..5187ea07 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -1,7 +1,7 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { getApi } from "@/app/store/global"; +import { openLink } from "@/app/store/global"; import { WOS, globalStore } from "@/store/global"; import * as services from "@/store/services"; import clsx from "clsx"; @@ -336,7 +336,7 @@ const WebView = memo(({ parentRef, model }: WebViewProps) => { webview.addEventListener("new-window", (e: any) => { e.preventDefault(); const newUrl = e.detail.url; - getApi().openExternal(newUrl); + openLink(newUrl); }); // Suppress errors diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 052eda94..4f814673 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -323,6 +323,7 @@ declare global { autoupdate: AutoUpdateOpts; termthemes: {[key: string]: TermThemeType}; window: WindowSettingsType; + web: WebConfigType; defaultmeta?: MetaType; presets?: {[key: string]: MetaType}; }; @@ -398,6 +399,7 @@ declare global { type TerminalConfigType = { fontsize?: number; fontfamily?: string; + disablewebgl: boolean; }; // wstore.UIContext @@ -545,6 +547,11 @@ declare global { args: any[]; }; + // wconfig.WebConfigType + type WebConfigType = { + openlinksinternally: boolean; + }; + // service.WebReturnType type WebReturnType = { success?: boolean; diff --git a/package.json b/package.json index 5e695b3b..d752570a 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,8 @@ "@types/color": "^3.0.6", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-serialize": "^0.13.0", + "@xterm/addon-web-links": "^0.11.0", + "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", "base64-js": "^1.5.1", "clsx": "^2.1.1", diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index fd63aa35..f8609546 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -25,8 +25,13 @@ type WidgetsConfigType struct { } type TerminalConfigType struct { - FontSize int `json:"fontsize,omitempty"` - FontFamily string `json:"fontfamily,omitempty"` + FontSize int `json:"fontsize,omitempty"` + FontFamily string `json:"fontfamily,omitempty"` + DisableWebGl bool `json:"disablewebgl"` +} + +type WebConfigType struct { + OpenLinksInternally bool `json:"openlinksinternally"` } type AiConfigType struct { @@ -99,6 +104,7 @@ type SettingsConfigType struct { AutoUpdate *AutoUpdateOpts `json:"autoupdate"` TermThemes TermThemesConfigType `json:"termthemes"` WindowSettings WindowSettingsType `json:"window"` + Web WebConfigType `json:"web"` DefaultMeta *waveobj.MetaMapType `json:"defaultmeta,omitempty"` Presets map[string]*waveobj.MetaMapType `json:"presets,omitempty"` diff --git a/yarn.lock b/yarn.lock index 9120cbe6..0459815e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4570,6 +4570,24 @@ __metadata: languageName: node linkType: hard +"@xterm/addon-web-links@npm:^0.11.0": + version: 0.11.0 + resolution: "@xterm/addon-web-links@npm:0.11.0" + peerDependencies: + "@xterm/xterm": ^5.0.0 + checksum: 10c0/9426bed80afa954b0ea97771d041eb44e77a64e560ce8b8ef507a5d3a763979af18ae9f74ed54007bb7e235d0daf035be2a33f90d8edfecb431caf8ba0b0664e + languageName: node + linkType: hard + +"@xterm/addon-webgl@npm:^0.18.0": + version: 0.18.0 + resolution: "@xterm/addon-webgl@npm:0.18.0" + peerDependencies: + "@xterm/xterm": ^5.0.0 + checksum: 10c0/682a3f5f128ee09a0cf1b41cbb7b2f925a5e43056e12ba0c523b93a1f5f188045caef9e31f32db933b8a7a1b12d8f9babaddfa11e6f11df0c7b265009103476c + languageName: node + linkType: hard + "@xterm/xterm@npm:^5.5.0": version: 5.5.0 resolution: "@xterm/xterm@npm:5.5.0" @@ -12352,6 +12370,8 @@ __metadata: "@vitest/coverage-istanbul": "npm:^2.0.5" "@xterm/addon-fit": "npm:^0.10.0" "@xterm/addon-serialize": "npm:^0.13.0" + "@xterm/addon-web-links": "npm:^0.11.0" + "@xterm/addon-webgl": "npm:^0.18.0" "@xterm/xterm": "npm:^5.5.0" base64-js: "npm:^1.5.1" clsx: "npm:^2.1.1"