diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index 83ae509..7de4571 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -1,6 +1,6 @@ import { MdOutlineContentPasteGo } from "react-icons/md"; import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu"; -import { FaKeyboard } from "react-icons/fa6"; +import { FaKeyboard, FaLock} from "react-icons/fa6"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; import { Fragment, useCallback, useRef } from "react"; import { CommandLineIcon } from "@heroicons/react/20/solid"; @@ -19,6 +19,8 @@ import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index"; import MountPopopover from "@/components/popovers/MountPopover"; import ExtensionPopover from "@/components/popovers/ExtensionPopover"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; +import useKeyboard from "@/hooks/useKeyboard"; +import { keys, modifiers } from "@/keyboardMappings"; export default function Actionbar({ requestFullscreen, @@ -56,6 +58,8 @@ export default function Actionbar({ [setDisableFocusTrap], ); + const { sendKeyboardEvent, resetKeyboardState } = useKeyboard(); + return ( <Container className="border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900"> <div @@ -262,7 +266,23 @@ export default function Actionbar({ }} /> </div> - + {useSettingsStore().actionBarCtrlAltDel && ( + <div className="hidden lg:block"> + <Button + size="XS" + theme="light" + text="Ctrl + Alt + Del" + LeadingIcon={FaLock} + onClick={() => { + sendKeyboardEvent( + [keys["Delete"]], + [modifiers["ControlLeft"], modifiers["AltLeft"]], + ); + setTimeout(resetKeyboardState, 100); + }} + /> + </div> + )} <div> <Button size="XS" diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index 52ef89d..917154b 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -308,6 +308,9 @@ interface SettingsState { keyboardLayout: string; setKeyboardLayout: (layout: string) => void; + actionBarCtrlAltDel: boolean; + setActionBarCtrlAltDel: (enabled: boolean) => void; + keyboardLedSync: KeyboardLedSync; setKeyboardLedSync: (sync: KeyboardLedSync) => void; } @@ -342,6 +345,9 @@ export const useSettingsStore = create( keyboardLayout: "en-US", setKeyboardLayout: layout => set({ keyboardLayout: layout }), + actionBarCtrlAltDel: false, + setActionBarCtrlAltDel: enabled => set({ actionBarCtrlAltDel: enabled }), + keyboardLedSync: "auto", setKeyboardLedSync: sync => set({ keyboardLedSync: sync }), }), diff --git a/ui/src/routes/devices.$id.settings.ctrlaltdel.tsx b/ui/src/routes/devices.$id.settings.ctrlaltdel.tsx new file mode 100644 index 0000000..397dc8b --- /dev/null +++ b/ui/src/routes/devices.$id.settings.ctrlaltdel.tsx @@ -0,0 +1,28 @@ +import { SettingsItem } from "./devices.$id.settings"; + +import { Checkbox } from "@/components/Checkbox"; +import { SettingsPageHeader } from "@/components/SettingsPageheader"; + +import { useSettingsStore } from "@/hooks/stores"; + +export default function SettingsCtrlAltDelRoute() { + const enableCtrlAltDel = useSettingsStore(state => state.actionBarCtrlAltDel); + const setEnableCtrlAltDel = useSettingsStore(state => state.setActionBarCtrlAltDel); + + return ( + <div className="space-y-4"> + <SettingsPageHeader + title="Action Bar" + description="Customize the action bar of your JetKVM interface" + /> + <div className="space-y-4"> + <SettingsItem title="Enable Ctrl-Alt-Del" description="Enable the Ctrl-Alt-Del key on the virtual keyboard"> + <Checkbox + checked={enableCtrlAltDel} + onChange={e => setEnableCtrlAltDel(e.target.checked)} + /> + </SettingsItem> + </div> + </div> + ); +} diff --git a/ui/src/routes/devices.$id.settings.hardware.tsx b/ui/src/routes/devices.$id.settings.hardware.tsx index 82cc6a1..44a146c 100644 --- a/ui/src/routes/devices.$id.settings.hardware.tsx +++ b/ui/src/routes/devices.$id.settings.hardware.tsx @@ -1,9 +1,10 @@ -import { useEffect } from "react"; +import { useCallback, useEffect, useState } from "react"; import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsItem } from "@routes/devices.$id.settings"; import { BacklightSettings, useSettingsStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; +import Checkbox from "@components/Checkbox"; import { SelectMenuBasic } from "@components/SelectMenuBasic"; import { UsbDeviceSetting } from "@components/UsbDeviceSetting"; @@ -11,6 +12,14 @@ import notifications from "../notifications"; import { UsbInfoSetting } from "../components/UsbInfoSetting"; import { FeatureFlag } from "../components/FeatureFlag"; +export interface ActionBarConfig { + ctrlAltDel: boolean; +} + +const defaultActionBarConfig: ActionBarConfig = { + ctrlAltDel: false, +}; + export default function SettingsHardwareRoute() { const [send] = useJsonRpc(); const settings = useSettingsStore(); @@ -71,6 +80,18 @@ export default function SettingsHardwareRoute() { }); }, [send, setBacklightSettings]); + const [actionBarConfig, setActionBarConfig] = useState<ActionBarConfig>(defaultActionBarConfig); + + const onActionBarItemChange = useCallback( + (key: keyof ActionBarConfig) => (e: React.ChangeEvent<HTMLInputElement>) => { + setActionBarConfig(prev => ({ + ...prev, + [key]: e.target.checked, + })); + }, + [], + ); + return ( <div className="space-y-4"> <SettingsPageHeader @@ -116,6 +137,15 @@ export default function SettingsHardwareRoute() { }} /> </SettingsItem> + <SettingsItem + title="Enable Ctrl+Alt+Del Action Bar" + description="Enable or disable the action bar action for sending a Ctrl+Alt+Del to the host" + > + <Checkbox + checked={actionBarConfig.ctrlAltDel} + onChange={onActionBarItemChange("ctrlAltDel")} + /> + </SettingsItem> {settings.backlightSettings.max_brightness != 0 && ( <> <SettingsItem