diff --git a/src/types/index.ts b/src/types/index.ts index e40b5484..79d34e1a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -111,7 +111,7 @@ export interface HostFeatureFlags { enableFileManager: boolean; // SSH only enableDocker: boolean; // SSH only enableTmuxMonitor: boolean; // SSH only - enableTerminalToolbar: boolean; // SSH only + enableTerminalToolbar: boolean; // SSH, RDP, VNC, and Telnet enableRemoteDesktop: boolean; // RDP, VNC only } diff --git a/src/ui/features/guacamole/GuacamoleApp.tsx b/src/ui/features/guacamole/GuacamoleApp.tsx index f8310d8b..9050298f 100644 --- a/src/ui/features/guacamole/GuacamoleApp.tsx +++ b/src/ui/features/guacamole/GuacamoleApp.tsx @@ -174,7 +174,7 @@ interface GuacamoleAppInnerProps { | "rdpPassword" | "vncUser" | "vncPassword" - >; + > & { enableTerminalToolbar?: boolean }; hostName: string; tabId?: string; protocol?: "rdp" | "vnc" | "telnet"; @@ -208,6 +208,7 @@ const GuacamoleAppInner = React.forwardRef< const [displayZoom, setDisplayZoom] = useState(1); const [filesystem, setFilesystem] = useState(null); const [fileBrowserOpen, setFileBrowserOpen] = useState(false); + const [toolbarHidden, setToolbarHidden] = useState(false); const [pendingUploads, setPendingUploads] = useState([]); const guacConfig = parseGuacamoleConfig(hostConfig.guacamoleConfig); @@ -586,16 +587,19 @@ const GuacamoleAppInner = React.forwardRef< onClose={() => setFileBrowserOpen(false)} /> )} - setFileBrowserOpen((open) => !open)} - onTouchModeChange={setTouchMode} - zoom={displayZoom} - /> + {hostConfig.enableTerminalToolbar !== false && !toolbarHidden && ( + setFileBrowserOpen((open) => !open)} + onTouchModeChange={setTouchMode} + zoom={displayZoom} + onHide={() => setToolbarHidden(true)} + /> + )} {shareModalOpen && guacamoleConnectionId && ( void; onTouchModeChange?: (mode: GuacamoleTouchMode) => void; zoom?: number; + onHide?: () => void; } const MODIFIER_KEYSYMS = { @@ -130,6 +132,7 @@ export const GuacamoleToolbar: React.FC = ({ onToggleFileBrowser, onTouchModeChange, zoom = 1, + onHide, }) => { const { t } = useTranslation(); const [position, setPosition] = useState({ x: 0, y: 12 }); @@ -569,6 +572,14 @@ export const GuacamoleToolbar: React.FC = ({ {t("guacamole.toolbar.collapse")} + {onHide && ( + + + + )} )} diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index c13b0935..d9e03fe5 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -1142,6 +1142,9 @@ "enableTmuxMonitorDesc": "Show this host in the Tmux Monitor and add its tmux actions to the sidebar", "enableTerminalToolbar": "Enable Terminal Toolbar", "enableTerminalToolbarDesc": "Show a quick-access toolbar docked to the active terminal for this host's tools and live stats", + "connectionToolbar": "Connection Toolbar", + "showConnectionToolbar": "Show connection toolbar", + "showConnectionToolbarDesc": "Show the toolbar in this host's SSH, RDP, VNC, and Telnet sessions. You can also hide it temporarily from the toolbar itself.", "tabProxmox": "Proxmox", "proxmoxIntegration": "Proxmox Integration", "enableProxmox": "Enable Proxmox", @@ -2043,6 +2046,7 @@ "collapse": "Collapse toolbar", "expand": "Expand toolbar", "dragHandle": "Drag to reposition", + "hide": "Hide for this session. To disable it permanently, open Host Settings → General → Connection Toolbar.", "zoomIn": "Zoom in", "zoomOut": "Zoom out", "resetZoom": "Reset zoom", diff --git a/src/ui/shell/tabUtils.tsx b/src/ui/shell/tabUtils.tsx index 59ac9339..1d609ec2 100644 --- a/src/ui/shell/tabUtils.tsx +++ b/src/ui/shell/tabUtils.tsx @@ -222,6 +222,7 @@ function hostToSSHHost(h: Host): SSHHost { enableTunnel: h.enableTunnel ?? false, enableFileManager: h.enableFileManager ?? false, enableDocker: h.enableDocker ?? false, + enableTerminalToolbar: h.enableTerminalToolbar ?? true, dockerConfig: h.dockerConfig ?? null, showTerminalInSidebar: true, showFileManagerInSidebar: true, diff --git a/src/ui/sidebar/HostEditor.tsx b/src/ui/sidebar/HostEditor.tsx index cf03a1a0..de45dbea 100644 --- a/src/ui/sidebar/HostEditor.tsx +++ b/src/ui/sidebar/HostEditor.tsx @@ -25,7 +25,6 @@ import { Globe, Info, Layers, // --- tmux-monitor --- - LayoutGrid, Network, Palette, Pencil, @@ -2193,22 +2192,6 @@ export function HostEditor({ - } - > -
- - setField("enableTerminalToolbar", v)} - /> - -
-
)} diff --git a/src/ui/sidebar/HostEditorGeneralTab.tsx b/src/ui/sidebar/HostEditorGeneralTab.tsx index 509e26b1..fea2ceff 100644 --- a/src/ui/sidebar/HostEditorGeneralTab.tsx +++ b/src/ui/sidebar/HostEditorGeneralTab.tsx @@ -8,6 +8,7 @@ import { FakeSwitch, SectionCard, SettingRow } from "@/components/section-card"; import type { Host } from "@/types/ui-types"; import { Globe, + LayoutGrid, Monitor, MousePointerClick, Plus, @@ -172,6 +173,23 @@ export function HostEditorGeneralTab({ + } + > +
+ + setField("enableTerminalToolbar", value)} + /> + +
+
+ } diff --git a/src/ui/tests/features/guacamole/GuacamoleToolbar.test.tsx b/src/ui/tests/features/guacamole/GuacamoleToolbar.test.tsx index c58dc1f9..5fe0afd7 100644 --- a/src/ui/tests/features/guacamole/GuacamoleToolbar.test.tsx +++ b/src/ui/tests/features/guacamole/GuacamoleToolbar.test.tsx @@ -78,4 +78,32 @@ describe("GuacamoleToolbar Windows key", () => { expect(zoomIn).toHaveBeenCalledOnce(); expect(resetZoom).toHaveBeenCalledOnce(); }); + + it("offers an explicit session-only hide action", () => { + const onHide = vi.fn(); + const displayRef = { + current: { + disconnect: vi.fn(), + isConnected: () => true, + sendKey: vi.fn(), + sendMouse: vi.fn(), + setClipboard: vi.fn(), + getFilesystem: () => null, + zoomIn: vi.fn(() => 1.25), + zoomOut: vi.fn(() => 0.75), + resetZoom: vi.fn(() => 1), + } satisfies GuacamoleDisplayHandle, + } as React.RefObject; + + const { getByLabelText } = render( + , + ); + + fireEvent.click(getByLabelText("guacamole.toolbar.hide")); + expect(onHide).toHaveBeenCalledOnce(); + }); });