diff --git a/package-lock.json b/package-lock.json index 44db4c5b..67eeab74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "tailwind-merge": "^3.3.1", "validator": "^13.15.15", "ws": "^8.18.3", + "xterm": "^5.3.0", "zod": "^4.0.5" }, "devDependencies": { @@ -10830,6 +10831,13 @@ "node": ">=0.4" } }, + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", diff --git a/package.json b/package.json index f25526da..74f44274 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "tailwind-merge": "^3.3.1", "validator": "^13.15.15", "ws": "^8.18.3", + "xterm": "^5.3.0", "zod": "^4.0.5" }, "devDependencies": { diff --git a/src/ui/Mobile/Apps/Terminal/Terminal.tsx b/src/ui/Mobile/Apps/Terminal/Terminal.tsx index 1739b9f2..3440dad6 100644 --- a/src/ui/Mobile/Apps/Terminal/Terminal.tsx +++ b/src/ui/Mobile/Apps/Terminal/Terminal.tsx @@ -29,6 +29,7 @@ export const Terminal = forwardRef(function SSHTerminal( const lastSentSizeRef = useRef<{ cols: number; rows: number } | null>(null); const pendingSizeRef = useRef<{ cols: number; rows: number } | null>(null); const notifyTimerRef = useRef(null); + const overlayTextareaRef = useRef(null); const DEBOUNCE_MS = 140; useEffect(() => { @@ -111,7 +112,7 @@ export const Terminal = forwardRef(function SSHTerminal( }; textarea.addEventListener('focus', preventKeyboard); - textarea.blur(); // Initial blur + textarea.blur(); return () => { textarea.removeEventListener('focus', preventKeyboard); @@ -119,6 +120,27 @@ export const Terminal = forwardRef(function SSHTerminal( } }, [terminal]); + function syncOverlay() { + if (!terminal || !overlayTextareaRef.current) return; + const buffer = terminal.buffer.active; + let text = ""; + for (let i = 0; i < buffer.length; i++) { + text += buffer.getLine(i)?.translateToString() + "\n"; + } + overlayTextareaRef.current.value = text; + } + + useEffect(() => { + if (!terminal) return; + syncOverlay(); + + const disposeRender = terminal.onRender(() => syncOverlay()); + + return () => { + disposeRender.dispose(); + }; + }, [terminal]); + function handleWindowResize() { if (!isVisibleRef.current) return; fitAddonRef.current?.fit(); @@ -169,7 +191,7 @@ export const Terminal = forwardRef(function SSHTerminal( if (!terminal || !xtermRef.current || !hostConfig) return; terminal.options = { - cursorBlink: true, + cursorBlink: false, cursorStyle: 'bar', scrollback: 10000, fontSize: 14, @@ -184,6 +206,8 @@ export const Terminal = forwardRef(function SSHTerminal( fastScrollModifier: 'alt', fastScrollSensitivity: 5, allowProposedApi: true, + disableStdin: true, + cursorInactiveStyle: "bar", }; const fitAddon = new FitAddon(); @@ -276,10 +300,26 @@ export const Terminal = forwardRef(function SSHTerminal( return (
+ className="h-full w-full m-1 relative" + style={{ opacity: visible && isVisible ? 1 : 0 }} + > +
+ +