From 97c7500487e8a8da6cf3b27ff749f0a79875cdca Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sat, 6 Sep 2025 00:13:23 -0500 Subject: [PATCH 01/10] Add vibration to keyboard --- src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx index 68c40217..35f42222 100644 --- a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx +++ b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx @@ -90,6 +90,7 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { input = `\x1b${input}`; } + navigator.vibrate(20) onSendInput(input); }; -- 2.49.1 From 198a5c2796aaa51c245026fa4a05e4cab21918a7 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sat, 6 Sep 2025 00:24:20 -0500 Subject: [PATCH 02/10] Fix keyboard keys --- src/ui/Mobile/Apps/Terminal/Terminal.tsx | 11 ++ .../Mobile/Apps/Terminal/TerminalKeyboard.tsx | 184 +++++++++--------- 2 files changed, 100 insertions(+), 95 deletions(-) diff --git a/src/ui/Mobile/Apps/Terminal/Terminal.tsx b/src/ui/Mobile/Apps/Terminal/Terminal.tsx index af0c9c80..1739b9f2 100644 --- a/src/ui/Mobile/Apps/Terminal/Terminal.tsx +++ b/src/ui/Mobile/Apps/Terminal/Terminal.tsx @@ -105,6 +105,17 @@ export const Terminal = forwardRef(function SSHTerminal( textarea.setAttribute("readonly", "true"); textarea.setAttribute("inputmode", "none"); textarea.style.caretColor = "transparent"; + + const preventKeyboard = () => { + textarea.blur(); + }; + + textarea.addEventListener('focus', preventKeyboard); + textarea.blur(); // Initial blur + + return () => { + textarea.removeEventListener('focus', preventKeyboard); + }; } }, [terminal]); diff --git a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx index 35f42222..24a95178 100644 --- a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx +++ b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React, {useState, useCallback} from "react"; import Keyboard from "react-simple-keyboard"; import "react-simple-keyboard/build/css/index.css"; import "./kb-dark-theme.css"; @@ -12,29 +12,35 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { const [isCtrl, setIsCtrl] = useState(false); const [isAlt, setIsAlt] = useState(false); - const onKeyPress = async (button: string) => { - if (button === "{shift}") { - setLayoutName("shift"); + const handlePaste = useCallback(async () => { + if (navigator.clipboard?.readText) { + try { + const text = await navigator.clipboard.readText(); + if (text) { + onSendInput(text); + } + } catch (err) { + console.error("Failed to read clipboard:", err); + } + } + }, [onSendInput]); + + const onKeyPress = useCallback((button: string) => { + const layoutMap: { [key: string]: string } = { + "{shift}": "shift", + "{unshift}": "default", + "{symbols}": "symbols", + "{fn}": "fn", + "{abc}": "default", + }; + + if (layoutMap[button]) { + setLayoutName(layoutMap[button]); return; } - if (button === "{unshift}") { - setLayoutName("default"); - return; - } - if (button === "{more}") { - setLayoutName("more"); - return; - } - if (button === "{less}") { - setLayoutName("default"); - return; - } - if (button === "{hide}") { - setLayoutName("hide"); - return; - } - if (button === "{unhide}") { - setLayoutName("default"); + + if (button === "{paste}") { + handlePaste(); return; } @@ -42,25 +48,12 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { setIsCtrl(prev => !prev); return; } + if (button === "{alt}") { setIsAlt(prev => !prev); return; } - if (button === "{paste}") { - if (navigator.clipboard?.readText) { - try { - const text = await navigator.clipboard.readText(); - if (text) { - onSendInput(text); - } - } catch (err) { - - } - } - return; - } - let input = button; const specialKeyMap: { [key: string]: string } = { @@ -80,6 +73,7 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { if (isCtrl) { if (input.length === 1) { const charCode = input.toUpperCase().charCodeAt(0); + // @, A-Z, [, \, ], ^, _ if (charCode >= 64 && charCode <= 95) { input = String.fromCharCode(charCode - 64); } @@ -90,9 +84,9 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { input = `\x1b${input}`; } - navigator.vibrate(20) + navigator.vibrate(20); onSendInput(input); - }; + }, [isCtrl, isAlt, onSendInput, handlePaste]); const buttonTheme = [ { @@ -102,10 +96,6 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { { class: "hg-space-medium", buttons: "{enter} {backspace}", - }, - { - class: "hg-space-small", - buttons: "{hide} {less} {more}", } ]; @@ -116,67 +106,71 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { buttonTheme.push({class: "key-active", buttons: "{alt}"}); } + const layout = { + default: [ + "q w e r t y u i o p", + "a s d f g h j k l", + "{shift} z x c v b n m {backspace}", + "{symbols} {ctrl} {alt} {space} {enter}", + ], + shift: [ + "Q W E R T Y U I O P", + "A S D F G H J K L", + "{unshift} Z X C V B N M {backspace}", + "{symbols} {ctrl} {alt} {space} {enter}", + ], + symbols: [ + "1 2 3 4 5 6 7 8 9 0", + "! @ # $ % ^ & * ( )", + "- _ = + [ ] { } \\ |", + "~ ` ' \" ; : , . / < > ?", + "{abc} {fn} {space} {backspace}", + ], + fn: [ + "F1 F2 F3 F4 F5 F6", + "F7 F8 F9 F10 F11 F12", + "{esc} {tab} {home} {end}", + "{pgUp} {pgDn} {arrowUp} {arrowDown}", + "{abc} {arrowLeft} {arrowRight} {paste} {backspace}", + ] + }; + + const display = { + "{shift}": "⇧", + "{unshift}": "⇧", + "{backspace}": "⌫", + "{symbols}": "?123", + "{abc}": "abc", + "{fn}": "Fn", + "{space}": "space", + "{enter}": "enter", + "{arrowLeft}": "←", + "{arrowRight}": "→", + "{arrowUp}": "↑", + "{arrowDown}": "↓", + "{esc}": "esc", + "{tab}": "tab", + "{ctrl}": "ctrl", + "{alt}": "alt", + "{paste}": "paste", + "{end}": "end", + "{home}": "home", + "{pgUp}": "pgUp", + "{pgDn}": "pgDn", + }; + return (
", - "F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12", - "{arrowLeft} {arrowRight} {arrowUp} {arrowDown} {paste} {backspace}", - "{hide} {less} {space} {enter}", - ], - hide: [ - "{unhide}" - ] - }} + layout={layout} layoutName={layoutName} onKeyPress={onKeyPress} - display={{ - "{shift}": "up", - "{unshift}": "dn", - "{backspace}": "back", - "{more}": "more", - "{less}": "less", - "{space}": "space", - "{enter}": "enter", - "{arrowLeft}": "←", - "{arrowRight}": "→", - "{arrowUp}": "↑", - "{arrowDown}": "↓", - "{hide}": "hide", - "{unhide}": "unhide", - "{esc}": "esc", - "{tab}": "tab", - "{ctrl}": "ctrl", - "{alt}": "alt", - "{paste}": "paste", - "{end}": "end", - "{home}": "home", - "{pgUp}": "pgUp", - "{pgDn}": "pgDn", - }} + display={display} theme={"hg-theme-default dark-theme"} useTouchEvents={true} + disableButtonHold={true} buttonTheme={buttonTheme} />
); -} +} \ No newline at end of file -- 2.49.1 From 53a319244351fd33dd8158e113eb835d36a2dce4 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sat, 6 Sep 2025 00:31:36 -0500 Subject: [PATCH 03/10] Fix keyboard keys --- .../Mobile/Apps/Terminal/TerminalKeyboard.tsx | 164 ++++++++++-------- 1 file changed, 91 insertions(+), 73 deletions(-) diff --git a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx index 24a95178..40f19ddf 100644 --- a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx +++ b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx @@ -20,27 +20,34 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { onSendInput(text); } } catch (err) { - console.error("Failed to read clipboard:", err); + console.error("Paste failed:", err); } } }, [onSendInput]); const onKeyPress = useCallback((button: string) => { - const layoutMap: { [key: string]: string } = { - "{shift}": "shift", - "{unshift}": "default", - "{symbols}": "symbols", - "{fn}": "fn", - "{abc}": "default", - }; - - if (layoutMap[button]) { - setLayoutName(layoutMap[button]); + if (button === "{shift}") { + setLayoutName("shift"); return; } - - if (button === "{paste}") { - handlePaste(); + if (button === "{unshift}") { + setLayoutName("default"); + return; + } + if (button === "{more}") { + setLayoutName("more"); + return; + } + if (button === "{less}") { + setLayoutName("default"); + return; + } + if (button === "{hide}") { + setLayoutName("hide"); + return; + } + if (button === "{unhide}") { + setLayoutName("default"); return; } @@ -48,12 +55,16 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { setIsCtrl(prev => !prev); return; } - if (button === "{alt}") { setIsAlt(prev => !prev); return; } + if (button === "{paste}") { + handlePaste(); + return; + } + let input = button; const specialKeyMap: { [key: string]: string } = { @@ -73,7 +84,6 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { if (isCtrl) { if (input.length === 1) { const charCode = input.toUpperCase().charCodeAt(0); - // @, A-Z, [, \, ], ^, _ if (charCode >= 64 && charCode <= 95) { input = String.fromCharCode(charCode - 64); } @@ -84,9 +94,16 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { input = `\x1b${input}`; } - navigator.vibrate(20); + try { + if (navigator.vibrate) { + navigator.vibrate(20); + } + } catch (e) { + console.error("Vibration failed:", e); + } + onSendInput(input); - }, [isCtrl, isAlt, onSendInput, handlePaste]); + }, [onSendInput, handlePaste, isCtrl, isAlt]); const buttonTheme = [ { @@ -96,6 +113,10 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { { class: "hg-space-medium", buttons: "{enter} {backspace}", + }, + { + class: "hg-space-small", + buttons: "{hide} {less} {more}", } ]; @@ -106,66 +127,63 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { buttonTheme.push({class: "key-active", buttons: "{alt}"}); } - const layout = { - default: [ - "q w e r t y u i o p", - "a s d f g h j k l", - "{shift} z x c v b n m {backspace}", - "{symbols} {ctrl} {alt} {space} {enter}", - ], - shift: [ - "Q W E R T Y U I O P", - "A S D F G H J K L", - "{unshift} Z X C V B N M {backspace}", - "{symbols} {ctrl} {alt} {space} {enter}", - ], - symbols: [ - "1 2 3 4 5 6 7 8 9 0", - "! @ # $ % ^ & * ( )", - "- _ = + [ ] { } \\ |", - "~ ` ' \" ; : , . / < > ?", - "{abc} {fn} {space} {backspace}", - ], - fn: [ - "F1 F2 F3 F4 F5 F6", - "F7 F8 F9 F10 F11 F12", - "{esc} {tab} {home} {end}", - "{pgUp} {pgDn} {arrowUp} {arrowDown}", - "{abc} {arrowLeft} {arrowRight} {paste} {backspace}", - ] - }; - - const display = { - "{shift}": "⇧", - "{unshift}": "⇧", - "{backspace}": "⌫", - "{symbols}": "?123", - "{abc}": "abc", - "{fn}": "Fn", - "{space}": "space", - "{enter}": "enter", - "{arrowLeft}": "←", - "{arrowRight}": "→", - "{arrowUp}": "↑", - "{arrowDown}": "↓", - "{esc}": "esc", - "{tab}": "tab", - "{ctrl}": "ctrl", - "{alt}": "alt", - "{paste}": "paste", - "{end}": "end", - "{home}": "home", - "{pgUp}": "pgUp", - "{pgDn}": "pgDn", - }; - return (
", + "F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12", + "{arrowLeft} {arrowRight} {arrowUp} {arrowDown} {paste} {backspace}", + "{hide} {less} {space} {enter}", + ], + hide: [ + "{unhide}" + ] + }} layoutName={layoutName} onKeyPress={onKeyPress} - display={display} + display={{ + "{shift}": "up", + "{unshift}": "dn", + "{backspace}": "back", + "{more}": "more", + "{less}": "less", + "{space}": "space", + "{enter}": "enter", + "{arrowLeft}": "←", + "{arrowRight}": "→", + "{arrowUp}": "↑", + "{arrowDown}": "↓", + "{hide}": "hide", + "{unhide}": "unhide", + "{esc}": "esc", + "{tab}": "tab", + "{ctrl}": "ctrl", + "{alt}": "alt", + "{paste}": "paste", + "{end}": "end", + "{home}": "home", + "{pgUp}": "pgUp", + "{pgDn}": "pgDn", + }} theme={"hg-theme-default dark-theme"} useTouchEvents={true} disableButtonHold={true} -- 2.49.1 From bd8bd941f377e70bd273b47d6d2b02c1836cc98b Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sat, 6 Sep 2025 13:51:18 -0500 Subject: [PATCH 04/10] Fix keyboard keys --- .../Mobile/Apps/Navigation/BottomNavbar.tsx | 58 ++++++++++--------- .../Mobile/Apps/Terminal/TerminalKeyboard.tsx | 2 +- src/ui/Mobile/MobileApp.tsx | 1 + 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx b/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx index 82c9b7aa..414dd2a2 100644 --- a/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx +++ b/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx @@ -11,34 +11,36 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) { const {tabs, currentTab, setCurrentTab, removeTab} = useTabs(); return ( -
- -
-
- {tabs.map(tab => ( -
- - -
- ))} +
+
+ +
+
+ {tabs.map(tab => ( +
+ + +
+ ))} +
diff --git a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx index 40f19ddf..788fe916 100644 --- a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx +++ b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx @@ -128,7 +128,7 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { } return ( -
+
{ )}
{currentTab && } + setIsSidebarOpen(true)} /> -- 2.49.1 From ba3c60d00c81335be2a488537c561a78cafc25e4 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sat, 6 Sep 2025 15:06:20 -0500 Subject: [PATCH 05/10] Rename files, improve keyboard usability --- package-lock.json | 8 +++ package.json | 1 + src/ui/Mobile/Apps/Terminal/Terminal.tsx | 52 ++++++++++++++++--- .../Mobile/Apps/Terminal/TerminalKeyboard.tsx | 4 +- src/ui/Mobile/Apps/Terminal/kb-dark-theme.css | 2 +- src/ui/Mobile/MobileApp.tsx | 21 ++++++-- .../{Apps => }/Navigation/BottomNavbar.tsx | 8 +-- .../Navigation/Hosts/FolderCard.tsx | 2 +- .../{Apps => }/Navigation/Hosts/Host.tsx | 2 +- .../{Apps => }/Navigation/LeftSidebar.tsx | 2 +- .../{Apps => }/Navigation/Tabs/TabContext.tsx | 0 11 files changed, 81 insertions(+), 21 deletions(-) rename src/ui/Mobile/{Apps => }/Navigation/BottomNavbar.tsx (89%) rename src/ui/Mobile/{Apps => }/Navigation/Hosts/FolderCard.tsx (97%) rename src/ui/Mobile/{Apps => }/Navigation/Hosts/Host.tsx (97%) rename src/ui/Mobile/{Apps => }/Navigation/LeftSidebar.tsx (99%) rename src/ui/Mobile/{Apps => }/Navigation/Tabs/TabContext.tsx (100%) 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 }} + > +
+ +