diff --git a/package.json b/package.json index b9f4f2e..d86eeb1 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "dependencies": { "@tauri-apps/api": "^2.0.0", "@tauri-apps/plugin-clipboard-manager": "^2.0.0", + "@tauri-apps/plugin-opener": "^2.0.0", "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^5.5.0", "react": "^18.3.0", "react-dom": "^18.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aab158b..3eb8b88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,15 @@ importers: '@tauri-apps/plugin-clipboard-manager': specifier: ^2.0.0 version: 2.3.2 + '@tauri-apps/plugin-opener': + specifier: ^2.0.0 + version: 2.5.4 '@xterm/addon-fit': specifier: ^0.10.0 version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/addon-web-links': + specifier: ^0.12.0 + version: 0.12.0 '@xterm/xterm': specifier: ^5.5.0 version: 5.5.0 @@ -511,6 +517,9 @@ packages: '@tauri-apps/plugin-clipboard-manager@2.3.2': resolution: {integrity: sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ==} + '@tauri-apps/plugin-opener@2.5.4': + resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -580,6 +589,9 @@ packages: peerDependencies: '@xterm/xterm': ^5.0.0 + '@xterm/addon-web-links@0.12.0': + resolution: {integrity: sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==} + '@xterm/xterm@5.5.0': resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} @@ -1182,6 +1194,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.11.0 + '@tauri-apps/plugin-opener@2.5.4': + dependencies: + '@tauri-apps/api': 2.11.0 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.3 @@ -1274,6 +1290,8 @@ snapshots: dependencies: '@xterm/xterm': 5.5.0 + '@xterm/addon-web-links@0.12.0': {} + '@xterm/xterm@5.5.0': {} assertion-error@2.0.1: {} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 65febb2..28063fb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,6 +16,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } tauri-plugin-clipboard-manager = "2" +tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 8d355d9..8071b47 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -8,6 +8,7 @@ "core:event:default", "core:window:default", "clipboard-manager:allow-read-text", - "clipboard-manager:allow-write-text" + "clipboard-manager:allow-write-text", + "opener:allow-open-url" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 06dea5f..dfbf1aa 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -16,6 +16,7 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_clipboard_manager::init()) + .plugin(tauri_plugin_opener::init()) .manage(PtyManager::new()) .invoke_handler(tauri::generate_handler![ commands::list_distros, diff --git a/src/components/XtermPane.tsx b/src/components/XtermPane.tsx index 022502b..7222800 100644 --- a/src/components/XtermPane.tsx +++ b/src/components/XtermPane.tsx @@ -1,11 +1,13 @@ import { useRef, useEffect } from "react"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; +import { WebLinksAddon } from "@xterm/addon-web-links"; import type { UnlistenFn } from "@tauri-apps/api/event"; import { readText as clipboardReadText, writeText as clipboardWriteText, } from "@tauri-apps/plugin-clipboard-manager"; +import { openUrl } from "@tauri-apps/plugin-opener"; import { spawnPane, writeToPane, @@ -124,6 +126,16 @@ export default function XtermPane({ const fit = new FitAddon(); fitRef.current = fit; term.loadAddon(fit); + // Underlines http(s) URLs in the terminal output and routes clicks + // through Tauri's opener plugin so they open in the user's default + // browser (WebView2 won't navigate on a plain window.open). + term.loadAddon( + new WebLinksAddon((_event, uri) => { + void openUrl(uri).catch((err) => + console.warn("openUrl failed:", err), + ); + }), + ); term.open(container); // Initial size — fit before asking the PTY for its dimensions.