import { Switch , Button as AntdButton } from "antd"; import { LockClosedIcon } from "@heroicons/react/16/solid"; import { useShallow } from "zustand/react/shallow"; import { AnimatePresence, motion } from "framer-motion"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Keyboard from "react-simple-keyboard"; import { useReactAt } from "i18n-auto-extractor/react"; import { isMobile } from "react-device-detect"; import Card from "@components/Card"; import "react-simple-keyboard/build/css/index.css"; import DetachIconRaw from "@/assets/detach-icon.svg"; import { cx } from "@/cva.config"; import { useHidStore, useSettingsStore, useUiStore } from "@/hooks/stores"; import useKeyboard from "@/hooks/useKeyboard"; import useKeyboardLayout from "@/hooks/useKeyboardLayout"; import { keyDisplayMap2, keys, modifiers, sKeyDisplayMap, latchingKeys } from "@/keyboardMappings"; import { dark_bg2_style} from "@/layout/theme_color"; import GoBottomSvg from "@/assets/second/gobottom.svg?react"; export const DetachIcon = ({ className }: { className?: string }) => { return Detach Icon; }; function KeyboardWrapper() { const { $at } = useReactAt(); const [layoutName, setLayoutName] = useState("default"); const keyboardRef = useRef(null); const showAttachedVirtualKeyboard = useUiStore( state => state.isAttachedVirtualKeyboardVisible, ); const { sendKeyboardEvent, resetKeyboardState } = useKeyboard(); const { selectedKeyboard } = useKeyboardLayout(); const keyDisplayMap = useMemo(() => { return selectedKeyboard.keyDisplayMap; }, [selectedKeyboard]); const virtualKeyboardLayout = useMemo(() => { return selectedKeyboard.virtualKeyboard; }, [selectedKeyboard]); const [isDragging, setIsDragging] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); const [newPosition, setNewPosition] = useState({ x: 0, y: 0 }); // State for locked modifier keys const [lockedModifiers, setLockedModifiers] = useState({ ctrl: false, alt: false, meta: false, shift: false, }); // Toggle for modifier key behavior: true = lock mode, false = direct trigger mode const [modifierLockMode, setModifierLockMode] = useState(isMobile); // Clear locked modifiers when switching to direct mode useEffect(() => { if (!modifierLockMode) { setLockedModifiers({ ctrl: false, alt: false, meta: false, shift: false, }); setLayoutName("default"); } }, [modifierLockMode]); // Force sticky mode on mobile useEffect(() => { if (isMobile) { setModifierLockMode(true); } }, [isMobile]); const [useNum, setUseNum] = useState(false); const [useFn, setUseFn] = useState(false); const [stickyModifiers, setStickyModifiers] = useState([]); const modifierButtonTheme = useMemo( () => [{ class: "hg-mod-active", buttons: stickyModifiers.join(" ") }], [stickyModifiers], ); const isCapsLockActive = useHidStore(useShallow(state => state.keyboardLedState?.caps_lock)); // HID related states const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable); const keyboardLedSync = useSettingsStore(state => state.keyboardLedSync); const isKeyboardLedManagedByHost = useMemo(() => keyboardLedSync !== "browser" && keyboardLedStateSyncAvailable, [keyboardLedSync, keyboardLedStateSyncAvailable], ); const setIsCapsLockActive = useHidStore(state => state.setIsCapsLockActive); const startDrag = useCallback((e: MouseEvent | TouchEvent) => { if (!keyboardRef.current) return; if (e instanceof TouchEvent && e.touches.length > 1) return; setIsDragging(true); const clientX = e instanceof TouchEvent ? e.touches[0].clientX : e.clientX; const clientY = e instanceof TouchEvent ? e.touches[0].clientY : e.clientY; const rect = keyboardRef.current.getBoundingClientRect(); setPosition({ x: clientX - rect.left, y: clientY - rect.top, }); }, []); const onDrag = useCallback( (e: MouseEvent | TouchEvent) => { if (!keyboardRef.current) return; if (isDragging) { const clientX = e instanceof TouchEvent ? e.touches[0].clientX : e.clientX; const clientY = e instanceof TouchEvent ? e.touches[0].clientY : e.clientY; const newX = clientX - position.x; const newY = clientY - position.y; const rect = keyboardRef.current.getBoundingClientRect(); const maxX = window.innerWidth - rect.width; const maxY = window.innerHeight - rect.height; setNewPosition({ x: Math.min(maxX, Math.max(0, newX)), y: Math.min(maxY, Math.max(0, newY)), }); } }, [isDragging, position.x, position.y], ); const endDrag = useCallback(() => { setIsDragging(false); }, []); useEffect(() => { const handle = keyboardRef.current; if (handle) { handle.addEventListener("touchstart", startDrag); handle.addEventListener("mousedown", startDrag); } document.addEventListener("mouseup", endDrag); document.addEventListener("touchend", endDrag); document.addEventListener("mousemove", onDrag); document.addEventListener("touchmove", onDrag); return () => { if (handle) { handle.removeEventListener("touchstart", startDrag); handle.removeEventListener("mousedown", startDrag); } document.removeEventListener("mouseup", endDrag); document.removeEventListener("touchend", endDrag); document.removeEventListener("mousemove", onDrag); document.removeEventListener("touchmove", onDrag); }; }, [endDrag, onDrag, startDrag]); const onKeyDown = useCallback( (key: string, e?: MouseEvent) => { if (e) { e.preventDefault(); } const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight"; const isKeyCaps = key === "CapsLock"; const cleanKey = key.replace(/[()]/g, ""); const keyHasShiftModifier = key.includes("("); // Check if this is a modifier key press const isModifierKey = key === "ControlLeft" || key === "AltLeft" || key === "MetaLeft" || key === "AltRight" || key === "MetaRight" || isKeyShift; // Handle toggle of layout for shift or caps lock const toggleLayout = () => { setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default")); }; // Handle modifier key press if (key === "ControlLeft") { if (modifierLockMode) { // Lock mode: toggle lock state setLockedModifiers(prev => ({ ...prev, ctrl: !prev.ctrl })); } else { // Direct trigger mode: send key press and release immediately sendKeyboardEvent([], [modifiers["ControlLeft"]]); setTimeout(resetKeyboardState, 100); } return; } if (key === "AltLeft" || key === "AltRight") { if (modifierLockMode) { setLockedModifiers(prev => ({ ...prev, alt: !prev.alt })); } else { sendKeyboardEvent([], [modifiers[key]]); setTimeout(resetKeyboardState, 100); } return; } if (key === "MetaLeft" || key === "MetaRight") { if (modifierLockMode) { setLockedModifiers(prev => ({ ...prev, meta: !prev.meta })); } else { sendKeyboardEvent([], [modifiers[key]]); setTimeout(resetKeyboardState, 100); } return; } if (isKeyShift) { if (modifierLockMode) { setLockedModifiers(prev => ({ ...prev, shift: !prev.shift })); if (lockedModifiers.shift) { // If unlocking shift, return to default layout setLayoutName("default"); } else { // If locking shift, switch to shift layout toggleLayout(); } } else { sendKeyboardEvent([], [modifiers["ShiftLeft"]]); setTimeout(resetKeyboardState, 100); } return; } if (key === "CtrlAltDelete") { sendKeyboardEvent( [keys["Delete"]], [modifiers["ControlLeft"], modifiers["AltLeft"]], ); setTimeout(resetKeyboardState, 100); return; } if (key === "AltMetaEscape") { sendKeyboardEvent( [keys["Escape"]], [modifiers["MetaLeft"], modifiers["AltLeft"]], ); setTimeout(resetKeyboardState, 100); return; } if (key === "CtrlAltBackspace") { sendKeyboardEvent( [keys["Backspace"]], [modifiers["ControlLeft"], modifiers["AltLeft"]], ); setTimeout(resetKeyboardState, 100); return; } if (isKeyCaps) { toggleLayout(); if (isCapsLockActive) { if (!isKeyboardLedManagedByHost) { setIsCapsLockActive(false); } sendKeyboardEvent([keys["CapsLock"]], []); setTimeout(resetKeyboardState, 100); return; } } // Handle caps lock state change if (isKeyCaps && !isKeyboardLedManagedByHost) { setIsCapsLockActive(!isCapsLockActive); } // Collect new active keys and modifiers const newKeys = keys[cleanKey] ? [keys[cleanKey]] : []; const newModifiers: number[] = []; // Add locked modifiers if (lockedModifiers.ctrl) { newModifiers.push(modifiers["ControlLeft"]); } if (lockedModifiers.alt) { newModifiers.push(modifiers["AltLeft"]); } if (lockedModifiers.meta) { newModifiers.push(modifiers["MetaLeft"]); } if (lockedModifiers.shift && !isCapsLockActive) { newModifiers.push(modifiers["ShiftLeft"]); } // Add shift modifier for keys with parentheses (if not caps lock and shift not locked) if (keyHasShiftModifier && !isCapsLockActive && !lockedModifiers.shift) { newModifiers.push(modifiers["ShiftLeft"]); } // Update current keys and modifiers sendKeyboardEvent(newKeys, newModifiers); // If shift was used as a modifier and caps lock is not active and shift is not locked, revert to default layout if (keyHasShiftModifier && !isCapsLockActive && !lockedModifiers.shift) { setLayoutName("default"); } // Auto-unlock modifiers after regular key press (not for combination keys) if (!isModifierKey && newKeys.length > 0) { setLockedModifiers({ ctrl: false, alt: false, meta: false, shift: false, }); setLayoutName("default"); } setTimeout(resetKeyboardState, 100); }, [isCapsLockActive, isKeyboardLedManagedByHost, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive, lockedModifiers, modifierLockMode], ); const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled); const setVirtualKeyboard = useHidStore(state => state.setVirtualKeyboardEnabled); const modifierLockButtons = [ lockedModifiers.ctrl ? "ControlLeft" : "", lockedModifiers.alt ? "AltLeft AltRight" : "", lockedModifiers.meta ? "MetaLeft MetaRight" : "", lockedModifiers.shift ? "ShiftLeft ShiftRight" : "", ].filter(Boolean).join(" ").trim(); return (
{virtualKeyboard && (
{isMobile ? <>
{ if (key === "Back") { setVirtualKeyboard(false); return; } if (key === "ShiftLeft" || key === "ControlLeft" || key === "AltLeft" || key === "MetaLeft") { onKeyDown(key); return; } onKeyDown(key); }} display={sKeyDisplayMap} layout={{ default: [ "Escape Tab MetaLeft PageUp ArrowUp PageDown Delete", "ShiftLeft ControlLeft AltLeft ArrowLeft ArrowDown ArrowRight Back", ], shift: [ "Escape Tab MetaLeft PageUp ArrowUp PageDown Delete", "ShiftLeft ControlLeft AltLeft ArrowLeft ArrowDown ArrowRight Back", ], }} disableButtonHold={true} syncInstanceInputs={true} debug={false} />
{ useNum? <>
{ if (key === "Fn") { setUseFn(prev => !prev); return; } onKeyDown(key); }} display={keyDisplayMap2} buttonTheme={ modifierLockMode && modifierLockButtons ? [{ class: "modifier-locked", buttons: modifierLockButtons }] : [] } layout={{ default: [ useFn ? "Fn Backquote F11 F12 BracketLeft BracketRight Backslash" : "Fn Backquote Minus Equal BracketLeft BracketRight Backslash", ], shift: [ useFn ? "Fn Backquote F11 F12 BracketLeft BracketRight Backslash" : "Fn (Backquote) (Minus) (Equal) (BracketLeft) (BracketRight) (Backslash)", ], }} disableButtonHold={true} syncInstanceInputs={true} debug={false} />
{ setUseNum(false); }} display={keyDisplayMap2} layout={{ default: [ "abc", ], shift: [ "abc", ], }} />
: <>
{ setUseNum(true); }} display={keyDisplayMap2} layout={{ default: [ "123", ], shift: [ "123", ], }} />
}
: <>
{!isMobile && (
} /> {$at("Sticky Keys")}
)}

{$at("Virtual Keyboard")}

} onClick={() => setVirtualKeyboard(false)} > {$at("Hide")}
{/*
*/}
}
)}
); } export default KeyboardWrapper;