diff --git a/internal/hidrpc/hidrpc.go b/internal/hidrpc/hidrpc.go deleted file mode 100644 index de4d3b4..0000000 --- a/internal/hidrpc/hidrpc.go +++ /dev/null @@ -1,53 +0,0 @@ -package hidrpc - -import "fmt" - -type Handler interface { - HandleHandshake(version byte) error - HandleKeyboardReport(modifier byte, keys []byte) error - HandleKeypressReport(key byte, press bool) error - HandleKeypressKeepAlive() error - HandleKeyboardMacroReport(data []byte) error - HandleCancelKeyboardMacro() error -} - -type Server struct { - handler Handler -} - -func NewServer(handler Handler) *Server { - return &Server{handler: handler} -} - -func (s *Server) HandleMessage(data []byte) error { - msg, err := UnmarshalMessage(data) - if err != nil { - return err - } - - switch msg.Type { - case MessageTypeHandshake: - if len(msg.Data) < 1 { - return fmt.Errorf("invalid handshake length: %d", len(msg.Data)) - } - return s.handler.HandleHandshake(msg.Data[0]) - case MessageTypeKeyboardReport: - if len(msg.Data) < 7 { - return fmt.Errorf("invalid keyboard report length: %d", len(msg.Data)) - } - return s.handler.HandleKeyboardReport(msg.Data[0], msg.Data[1:7]) - case MessageTypeKeypressReport: - if len(msg.Data) < 2 { - return fmt.Errorf("invalid keypress report length: %d", len(msg.Data)) - } - return s.handler.HandleKeypressReport(msg.Data[0], msg.Data[1] != 0) - case MessageTypeKeypressKeepAlive: - return s.handler.HandleKeypressKeepAlive() - case MessageTypeKeyboardMacroReport: - return s.handler.HandleKeyboardMacroReport(msg.Data) - case MessageTypeCancelKeyboardMacro: - return s.handler.HandleCancelKeyboardMacro() - default: - return fmt.Errorf("unknown message type: 0x%02x", msg.Type) - } -} diff --git a/internal/hidrpc/message.go b/internal/hidrpc/message.go deleted file mode 100644 index 429f349..0000000 --- a/internal/hidrpc/message.go +++ /dev/null @@ -1,70 +0,0 @@ -package hidrpc - -import "fmt" - -const ( - MessageTypeHandshake = 0x01 - MessageTypeKeyboardReport = 0x02 - MessageTypePointerReport = 0x03 - MessageTypeWheelReport = 0x04 - MessageTypeKeypressReport = 0x05 - MessageTypeMouseReport = 0x06 - MessageTypeKeyboardMacroReport = 0x07 - MessageTypeCancelKeyboardMacro = 0x08 - MessageTypeKeypressKeepAlive = 0x09 - MessageTypeKeyboardLedState = 0x32 - MessageTypeKeysDownState = 0x33 - MessageTypeKeyboardMacroState = 0x34 -) - -type Message struct { - Type byte - Data []byte -} - -func MarshalKeyboardReport(modifier byte, keys []byte) []byte { - data := make([]byte, 8) - data[0] = MessageTypeKeyboardReport - data[1] = modifier - copy(data[2:], keys) - return data -} - -func MarshalHandshake(version byte) []byte { - return []byte{MessageTypeHandshake, version} -} - -func MarshalKeypressReport(key byte, press bool) []byte { - data := make([]byte, 3) - data[0] = MessageTypeKeypressReport - data[1] = key - if press { - data[2] = 1 - } else { - data[2] = 0 - } - return data -} - -func MarshalKeypressKeepAlive() []byte { - return []byte{MessageTypeKeypressKeepAlive} -} - -func MarshalKeyboardLedState(state byte) []byte { - return []byte{MessageTypeKeyboardLedState, state} -} - -func MarshalKeysDownState(modifier byte, keys []byte) []byte { - data := make([]byte, 8) - data[0] = MessageTypeKeysDownState - data[1] = modifier - copy(data[2:], keys) - return data -} - -func UnmarshalMessage(data []byte) (Message, error) { - if len(data) < 1 { - return Message{}, fmt.Errorf("empty message") - } - return Message{Type: data[0], Data: data[1:]}, nil -} diff --git a/internal/usbgadget/hid_keyboard.go b/internal/usbgadget/hid_keyboard.go index c9d21de..6f075b4 100644 --- a/internal/usbgadget/hid_keyboard.go +++ b/internal/usbgadget/hid_keyboard.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "os" - "os/exec" "reflect" "strings" "time" @@ -132,10 +131,10 @@ func (u *UsbGadget) GetKeyboardState() KeyboardState { return u.keyboardState } -func (u *UsbGadget) listenKeyboardEvents() { +func (u *UsbGadget) listenKeyboardEvents(ctx context.Context, file *os.File) { var path string - if u.keyboardHidFile != nil { - path = u.keyboardHidFile.Name() + if file != nil { + path = file.Name() } l := u.log.With().Str("listener", "keyboardEvents").Str("path", path).Logger() l.Trace().Msg("starting") @@ -144,12 +143,12 @@ func (u *UsbGadget) listenKeyboardEvents() { buf := make([]byte, hidReadBufferSize) for { select { - case <-u.keyboardStateCtx.Done(): + case <-ctx.Done(): l.Info().Msg("context done") return default: l.Trace().Msg("reading from keyboard") - if u.keyboardHidFile == nil { + if file == nil { u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil") // show the error every 100 times to avoid spamming the logs time.Sleep(time.Second) @@ -158,16 +157,26 @@ func (u *UsbGadget) listenKeyboardEvents() { // reset the counter u.resetLogSuppressionCounter("keyboardHidFileNil") - n, err := u.keyboardHidFile.Read(buf) + n, err := file.Read(buf) if err != nil { + if ctx.Err() != nil { + l.Info().Msg("context canceled while reading keyboard HID file") + return + } + u.logWithSupression("keyboardHidFileRead", 100, &l, err, "failed to read") - continue + if reopenErr := u.reopenKeyboardHidFile(); reopenErr != nil { + u.logWithSupression("keyboardHidFileReopen", 100, &l, reopenErr, "failed to reopen keyboard HID file") + } else { + u.resetLogSuppressionCounter("keyboardHidFileReopen") + } + return } u.resetLogSuppressionCounter("keyboardHidFileRead") l.Trace().Int("n", n).Bytes("buf", buf).Msg("got data from keyboard") - if n != 1 { - l.Trace().Int("n", n).Msg("expected 1 byte, got") + if n < 1 { + l.Info().Int("n", n).Msg("expected at least 1 byte, got 0") continue } u.updateKeyboardState(buf[0]) @@ -176,13 +185,52 @@ func (u *UsbGadget) listenKeyboardEvents() { }() } -func (u *UsbGadget) openKeyboardHidFile() error { +func openWithTimeout(name string, flag int, perm os.FileMode, timeout time.Duration) (*os.File, error) { + type result struct { + file *os.File + err error + } + ch := make(chan result, 1) + go func() { + f, err := os.OpenFile(name, flag, perm) + ch <- result{f, err} + }() + + select { + case r := <-ch: + return r.file, r.err + case <-time.After(timeout): + // Drain the channel in the background to close the leaked fd if the + // open eventually succeeds. + go func() { + if r := <-ch; r.file != nil { + r.file.Close() + } + }() + return nil, fmt.Errorf("open %s: timed out after %s", name, timeout) + } +} + +func (u *UsbGadget) closeKeyboardHidFileLocked() { + if u.keyboardStateCancel != nil { + u.keyboardStateCancel() + u.keyboardStateCancel = nil + } + if u.keyboardHidFile != nil { + u.keyboardHidFile.Close() + u.keyboardHidFile = nil + } +} + +func (u *UsbGadget) openKeyboardHidFileLocked(forceReopen bool) error { + if forceReopen { + u.closeKeyboardHidFileLocked() + } else if u.keyboardHidFile != nil { return nil } - var err error - u.keyboardHidFile, err = os.OpenFile("/dev/hidg0", os.O_RDWR, 0666) + file, err := openWithTimeout("/dev/hidg0", os.O_RDWR, 0666, 3*time.Second) if err != nil { if errors.Is(err, os.ErrNotExist) || strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "no such device") { u.log.Error(). @@ -197,34 +245,62 @@ func (u *UsbGadget) openKeyboardHidFile() error { return fmt.Errorf("failed to open hidg0: %w", err) } - if u.keyboardStateCancel != nil { - u.keyboardStateCancel() - } - - u.keyboardStateCtx, u.keyboardStateCancel = context.WithCancel(context.Background()) - u.listenKeyboardEvents() + ctx, cancel := context.WithCancel(context.Background()) + u.keyboardHidFile = file + u.keyboardStateCtx = ctx + u.keyboardStateCancel = cancel + u.listenKeyboardEvents(ctx, file) return nil } +func (u *UsbGadget) openKeyboardHidFile() error { + u.keyboardLock.Lock() + defer u.keyboardLock.Unlock() + + return u.openKeyboardHidFileLocked(false) +} + +func (u *UsbGadget) reopenKeyboardHidFile() error { + u.keyboardLock.Lock() + defer u.keyboardLock.Unlock() + + return u.openKeyboardHidFileLocked(true) +} + func (u *UsbGadget) OpenKeyboardHidFile() error { return u.openKeyboardHidFile() } -func (u *UsbGadget) keyboardWriteHidFile(data []byte) error { - var parts []string - for _, b := range data { - parts = append(parts, fmt.Sprintf("\\x%02x", b)) - } - hexString := strings.Join(parts, "") +func (u *UsbGadget) ReopenKeyboardHidFile() error { + return u.reopenKeyboardHidFile() +} - cmd := exec.Command("sh", "-c", fmt.Sprintf("echo -n -e '%s' > /dev/hidg0", hexString)) - err := cmd.Run() +func (u *UsbGadget) keyboardWriteHidFileLocked(modifier byte, keys []byte) error { + if len(keys) > 6 { + keys = keys[:6] + } + if len(keys) < 6 { + keys = append(keys, make([]byte, 6-len(keys))...) + } + + data := []byte{modifier, 0, keys[0], keys[1], keys[2], keys[3], keys[4], keys[5]} + + if u.keyboardHidFile == nil { + if err := u.openKeyboardHidFileLocked(false); err != nil { + return err + } + } + + _, err := u.writeWithTimeout(u.keyboardHidFile, data) if err != nil { u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0") + u.closeKeyboardHidFileLocked() return err } u.resetLogSuppressionCounter("keyboardWriteHidFile") + + u.resetUserInputTime() return nil } @@ -369,23 +445,6 @@ func (u *UsbGadget) KeypressKeepAlive() error { return nil } -func (u *UsbGadget) keyboardWriteHidFileLocked(modifier byte, keys []byte) error { - if len(keys) > 6 { - keys = keys[:6] - } - if len(keys) < 6 { - keys = append(keys, make([]byte, 6-len(keys))...) - } - - err := u.keyboardWriteHidFile([]byte{modifier, 0, keys[0], keys[1], keys[2], keys[3], keys[4], keys[5]}) - if err != nil { - return err - } - - u.resetUserInputTime() - return nil -} - func (u *UsbGadget) KeyboardReport(modifier uint8, keys []uint8) error { u.keyboardLock.Lock() defer u.keyboardLock.Unlock() diff --git a/internal/usbgadget/utils.go b/internal/usbgadget/utils.go index 363f2f6..759e5ae 100644 --- a/internal/usbgadget/utils.go +++ b/internal/usbgadget/utils.go @@ -2,10 +2,13 @@ package usbgadget import ( "bytes" + "errors" "fmt" + "os" "path/filepath" "strconv" "strings" + "time" "github.com/rs/zerolog" ) @@ -107,3 +110,36 @@ func (u *UsbGadget) resetLogSuppressionCounter(counterName string) { u.logSuppressionCounter[counterName] = 0 } } + +const hidWriteTimeout = 50 * time.Millisecond + +func (u *UsbGadget) writeWithTimeout(file *os.File, data []byte) (n int, err error) { + if err := file.SetWriteDeadline(time.Now().Add(hidWriteTimeout)); err != nil { + return -1, err + } + + n, err = file.Write(data) + if err == nil { + return + } + + u.log.Trace(). + Str("file", file.Name()). + Bytes("data", data). + Err(err). + Msg("write failed") + + if errors.Is(err, os.ErrDeadlineExceeded) { + u.logWithSupression( + fmt.Sprintf("writeWithTimeout_%s", file.Name()), + 1000, + u.log, + err, + "write timed out: %s", + file.Name(), + ) + err = nil + } + + return +} diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx index 68f36a2..5fe5cb9 100644 --- a/ui/src/components/VirtualKeyboard.tsx +++ b/ui/src/components/VirtualKeyboard.tsx @@ -270,6 +270,7 @@ function KeyboardWrapper() { setIsCapsLockActive(false); } sendKeyboardEvent([keys["CapsLock"]], []); + setTimeout(resetKeyboardState, 100); return; } } diff --git a/ui/src/hooks/hidRpc.ts b/ui/src/hooks/hidRpc.ts deleted file mode 100644 index 1707f11..0000000 --- a/ui/src/hooks/hidRpc.ts +++ /dev/null @@ -1,70 +0,0 @@ -export const MessageType = { - Handshake: 0x01, - KeyboardReport: 0x02, - PointerReport: 0x03, - WheelReport: 0x04, - KeypressReport: 0x05, - MouseReport: 0x06, - KeyboardMacroReport: 0x07, - CancelKeyboardMacro: 0x08, - KeypressKeepAlive: 0x09, - KeyboardLedState: 0x32, - KeysDownState: 0x33, - KeyboardMacroState: 0x34, -} as const; - -export function marshalKeypressReport(key: number, press: boolean): Uint8Array { - return new Uint8Array([MessageType.KeypressReport, key, press ? 1 : 0]); -} - -export function marshalKeyboardReport(modifier: number, keys: number[]): Uint8Array { - const data = new Uint8Array(8); - data[0] = MessageType.KeyboardReport; - data[1] = modifier; - for (let i = 0; i < Math.min(keys.length, 6); i++) { - data[2 + i] = keys[i]; - } - return data; -} - -export function marshalKeypressKeepAlive(): Uint8Array { - return new Uint8Array([MessageType.KeypressKeepAlive]); -} - -export function marshalHandshake(version: number): Uint8Array { - return new Uint8Array([MessageType.Handshake, version]); -} - -export interface HidRpcMessage { - type: number; - data: Uint8Array; -} - -export function unmarshalMessage(data: ArrayBuffer): HidRpcMessage { - const view = new Uint8Array(data); - return { - type: view[0], - data: view.slice(1), - }; -} - -export interface KeyboardLedState { - num_lock: boolean; - caps_lock: boolean; - scroll_lock: boolean; - compose: boolean; - kana: boolean; - shift: boolean; -} - -export function parseKeyboardLedState(data: Uint8Array): KeyboardLedState { - const raw = data[0] || 0; - return { - num_lock: !!(raw & (1 << 0)), - caps_lock: !!(raw & (1 << 1)), - scroll_lock: !!(raw & (1 << 2)), - compose: !!(raw & (1 << 3)), - kana: !!(raw & (1 << 4)), - shift: !!(raw & (1 << 6)), - }; -} diff --git a/ui/src/hooks/useHidRpc.ts b/ui/src/hooks/useHidRpc.ts deleted file mode 100644 index c20aeec..0000000 --- a/ui/src/hooks/useHidRpc.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { useCallback, useEffect, useRef } from "react"; -import { useRTCStore, useHidStore } from "./stores"; -import { - marshalKeypressReport, - marshalKeyboardReport, - marshalKeypressKeepAlive, - marshalHandshake, - unmarshalMessage, - MessageType, - parseKeyboardLedState, -} from "./hidRpc"; - -export function useHidRpc() { - const hidChannel = useRTCStore(state => state.hidChannel); - const setRpcHidReady = useHidStore(state => state.setRpcHidReady); - const setKeyboardLedState = useHidStore(state => state.setKeyboardLedState); - const setKeysDownState = useHidStore(state => state.setKeysDownState); - const rpcHidReadyRef = useRef(false); - - // Send keypress event - const reportKeypressEvent = useCallback( - (key: number, press: boolean) => { - if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") { - return false; - } - const data = marshalKeypressReport(key, press); - hidChannel.send(data); - return true; - }, - [hidChannel] - ); - - // Send keyboard report (for legacy compatibility) - const reportKeyboardEvent = useCallback( - (modifier: number, keys: number[]) => { - if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") { - return false; - } - const data = marshalKeyboardReport(modifier, keys); - hidChannel.send(data); - return true; - }, - [hidChannel] - ); - - // Send keepalive - const reportKeypressKeepAlive = useCallback(() => { - if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") { - return false; - } - const data = marshalKeypressKeepAlive(); - hidChannel.send(data); - return true; - }, [hidChannel]); - - // Handle incoming HID-RPC messages - useEffect(() => { - if (!hidChannel) return; - - const messageHandler = (event: MessageEvent) => { - const msg = unmarshalMessage(event.data); - - switch (msg.type) { - case MessageType.Handshake: - if (msg.data[0] === 1) { - rpcHidReadyRef.current = true; - setRpcHidReady(true); - } - break; - case MessageType.KeyboardLedState: - setKeyboardLedState(parseKeyboardLedState(msg.data)); - break; - case MessageType.KeysDownState: - // Parse modifier + 6 keys - if (msg.data.length >= 7) { - setKeysDownState({ - modifier: msg.data[0], - keys: Array.from(msg.data.slice(1, 7)), - }); - } - break; - } - }; - - hidChannel.addEventListener("message", messageHandler); - - // Send handshake - if (hidChannel.readyState === "open") { - hidChannel.send(marshalHandshake(1)); - } else { - hidChannel.addEventListener("open", () => { - hidChannel.send(marshalHandshake(1)); - }, { once: true }); - } - - return () => { - hidChannel.removeEventListener("message", messageHandler); - rpcHidReadyRef.current = false; - setRpcHidReady(false); - }; - }, [hidChannel, setRpcHidReady, setKeyboardLedState, setKeysDownState]); - - return { - rpcHidReady: rpcHidReadyRef.current, - reportKeypressEvent, - reportKeyboardEvent, - reportKeypressKeepAlive, - }; -} diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index 79fc01c..20706c6 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -3,12 +3,10 @@ import { useCallback, useEffect, useRef } from "react"; import notifications from "@/notifications"; import { useHidStore, useRTCStore, useSettingsStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; -import { useHidRpc } from "@/hooks/useHidRpc"; import { keys, modifiers } from "@/keyboardMappings"; export default function useKeyboard() { const [send] = useJsonRpc(); - const { rpcHidReady, reportKeypressEvent, reportKeyboardEvent, reportKeypressKeepAlive } = useHidRpc(); const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const forceHttp = useSettingsStore(state => state.forceHttp); @@ -30,80 +28,45 @@ export default function useKeyboard() { if (usbState !== "configured") return; const accModifier = modifiers.reduce((acc, val) => acc + val, 0); - // Try HID-RPC first - if (rpcHidReady && !forceHttp) { - reportKeyboardEvent(accModifier, keys); - } else { - // Fallback to JSON-RPC - send("keyboardReport", { keys, modifier: accModifier }, resp => { - if ("error" in resp) { - const msg = (resp.error.data as string) || resp.error.message || ""; - if (msg.includes("cannot send after transport endpoint shutdown") && usbState === "configured") { - notifications.error("Please check if the cable and connection are stable.", { duration: 5000 }); - } + // Fallback to JSON-RPC + send("keyboardReport", { keys, modifier: accModifier }, resp => { + if ("error" in resp) { + const msg = (resp.error.data as string) || resp.error.message || ""; + if (msg.includes("cannot send after transport endpoint shutdown") && usbState === "configured") { + notifications.error("Please check if the cable and connection are stable.", { duration: 5000 }); } - }); - } + } + }); // We do this for the info bar to display the currently pressed keys for the user updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers }); }, - [forceHttp, rpcDataChannel?.readyState, rpcHidReady, reportKeyboardEvent, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState], + [forceHttp, rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState], ); - // Send per-key press/release (new HID-RPC method) + // Send per-key press/release const sendKeypress = useCallback( (key: number, press: boolean) => { if (isReinitializingGadget || usbState !== "configured") return; - if (rpcHidReady && !forceHttp) { - reportKeypressEvent(key, press); - - // Track held keys for keepalive - if (press) { - heldKeysRef.current.add(key); - // Start keepalive interval if not already running - if (!keepaliveIntervalRef.current) { - keepaliveIntervalRef.current = setInterval(() => { - if (heldKeysRef.current.size > 0) { - reportKeypressKeepAlive(); - } - }, 50); - } - } else { - heldKeysRef.current.delete(key); - if (heldKeysRef.current.size === 0 && keepaliveIntervalRef.current) { - clearInterval(keepaliveIntervalRef.current); - keepaliveIntervalRef.current = null; - } - } - } else { - // Legacy: simulate device-side key handling - // This maintains the 6-key buffer on the frontend for legacy compatibility - // ... (existing logic would go here, but for now use sendKeyboardEvent) - // For simplicity in migration, we fall back to full state reports - const modifier = press ? 0 : 0; // Simplified - would need proper modifier tracking - sendKeyboardEvent(press ? [key] : [], [modifier]); - } + // Legacy: simulate device-side key handling + // This maintains the 6-key buffer on the frontend for legacy compatibility + // For simplicity in migration, we fall back to full state reports + const modifier = press ? 0 : 0; // Simplified - would need proper modifier tracking + sendKeyboardEvent(press ? [key] : [], [modifier]); }, - [rpcHidReady, forceHttp, reportKeypressEvent, reportKeypressKeepAlive, isReinitializingGadget, usbState, sendKeyboardEvent] + [isReinitializingGadget, usbState, sendKeyboardEvent] ); const resetKeyboardState = useCallback(() => { // Release all held keys - if (rpcHidReady && !forceHttp) { - heldKeysRef.current.forEach(key => { - reportKeypressEvent(key, false); - }); - } else { - sendKeyboardEvent([], []); - } + sendKeyboardEvent([], []); heldKeysRef.current.clear(); if (keepaliveIntervalRef.current) { clearInterval(keepaliveIntervalRef.current); keepaliveIntervalRef.current = null; } - }, [rpcHidReady, forceHttp, reportKeypressEvent, sendKeyboardEvent]); + }, [sendKeyboardEvent]); // Cleanup on unmount useEffect(() => { diff --git a/ui/src/layout/core/desktop/hooks/useKeyboardEvents.ts b/ui/src/layout/core/desktop/hooks/useKeyboardEvents.ts index cb51a54..e1b5c5e 100644 --- a/ui/src/layout/core/desktop/hooks/useKeyboardEvents.ts +++ b/ui/src/layout/core/desktop/hooks/useKeyboardEvents.ts @@ -10,7 +10,7 @@ export const useKeyboardEvents = ( pasteCaptureRef?: React.RefObject, isReinitializingGadget?: boolean ) => { - const { sendKeyboardEvent, sendKeypress, resetKeyboardState } = useKeyboard(); + const { sendKeyboardEvent, resetKeyboardState } = useKeyboard(); const { setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive } = useHidStore(); const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable); @@ -65,6 +65,11 @@ export const useKeyboardEvents = ( } if (isReinitializingGadget) return; + if (e.repeat) { + e.preventDefault(); + return; + } + e.preventDefault(); const prev = useHidStore.getState(); let code = e.code; @@ -94,15 +99,8 @@ export const useKeyboardEvents = ( }, 10); } - // Send per-key press event - const hidKey = keys[code]; - if (hidKey !== undefined) { - sendKeypress(hidKey, true); - } - - // Still update the full state for legacy compatibility and UI display sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]); - }, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]); + }, [handleModifierKeys, remapCode, sendKeyboardEvent, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]); const keyUpHandler = useCallback((e: KeyboardEvent) => { if (isOcrMode) return; @@ -124,15 +122,8 @@ export const useKeyboardEvents = ( prev.activeModifiers.filter(k => k !== modifiers[code]), ); - // Send per-key release event - const hidKey = keys[code]; - if (hidKey !== undefined) { - sendKeypress(hidKey, false); - } - - // Still update the full state for legacy compatibility and UI display sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]); - }, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]); + }, [handleModifierKeys, remapCode, sendKeyboardEvent, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]); const setupKeyboardEvents = useCallback(() => { const abortController = new AbortController(); diff --git a/ui/src/layout/core/desktop/hooks/useMouseEvents.ts b/ui/src/layout/core/desktop/hooks/useMouseEvents.ts index c4bba93..c8c2a5b 100644 --- a/ui/src/layout/core/desktop/hooks/useMouseEvents.ts +++ b/ui/src/layout/core/desktop/hooks/useMouseEvents.ts @@ -34,7 +34,9 @@ export const useMouseEvents = ( if (!force && settings.mouseMode !== "relative") return; // Don't send mouse events while reinitializing gadget if (isReinitializingGadget) return; - send("relMouseReport", { dx: calcDelta(x), dy: calcDelta(y), buttons }); + const dx = calcDelta(x); + const dy = calcDelta(y); + send("relMouseReport", { dx, dy, buttons }); setMouseMove({ x, y, buttons }); }, [send, setMouseMove, settings.mouseMode, isReinitializingGadget], diff --git a/ui/src/layout/index.pc.tsx b/ui/src/layout/index.pc.tsx index 32b6a13..5879a43 100644 --- a/ui/src/layout/index.pc.tsx +++ b/ui/src/layout/index.pc.tsx @@ -462,11 +462,6 @@ export default function PCHome() { setDiskChannel(diskDataChannel); }; - const hidDataChannel = pc.createDataChannel("hid"); - hidDataChannel.onopen = () => { - useRTCStore.getState().setHidChannel(hidDataChannel); - }; - setPeerConnection(pc); }, [ forceHttp, diff --git a/usb.go b/usb.go index 22be6ec..6100a6f 100644 --- a/usb.go +++ b/usb.go @@ -6,9 +6,6 @@ import ( "sync" "time" - "github.com/pion/webrtc/v4" - - "kvm/internal/hidrpc" "kvm/internal/usbgadget" ) @@ -40,35 +37,13 @@ func initUsbGadget() { go func() { for { checkUSBState() - time.Sleep(500 * time.Millisecond) + time.Sleep(2500 * time.Millisecond) } }() gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) { if currentSession != nil { writeJSONRPCEvent("keyboardLedState", state, currentSession) - - // Send HID-RPC LED state message - if currentSession.HidChannel != nil { - var raw byte - if state.NumLock { - raw |= usbgadget.KeyboardLedMaskNumLock - } - if state.CapsLock { - raw |= usbgadget.KeyboardLedMaskCapsLock - } - if state.ScrollLock { - raw |= usbgadget.KeyboardLedMaskScrollLock - } - if state.Compose { - raw |= usbgadget.KeyboardLedMaskCompose - } - if state.Kana { - raw |= usbgadget.KeyboardLedMaskKana - } - ledData := hidrpc.MarshalKeyboardLedState(raw) - currentSession.HidChannel.Send(ledData) - } } }) @@ -144,49 +119,6 @@ func rpcKeypressKeepAlive() error { return gadget.KeypressKeepAlive() } -func handleHidChannel(d *webrtc.DataChannel, session *Session) { - hidServer := hidrpc.NewServer(&hidRpcHandler{session: session}) - d.OnMessage(func(msg webrtc.DataChannelMessage) { - if err := hidServer.HandleMessage(msg.Data); err != nil { - usbLogger.Warn().Err(err).Msg("HID-RPC message handling error") - } - }) -} - -type hidRpcHandler struct { - session *Session -} - -func (h *hidRpcHandler) HandleHandshake(version byte) error { - if h.session.HidChannel != nil { - handshakeData := hidrpc.MarshalHandshake(version) - return h.session.HidChannel.Send(handshakeData) - } - return nil -} - -func (h *hidRpcHandler) HandleKeyboardReport(modifier byte, keys []byte) error { - return rpcKeyboardReport(modifier, keys) -} - -func (h *hidRpcHandler) HandleKeypressReport(key byte, press bool) error { - return rpcKeypressReport(key, press) -} - -func (h *hidRpcHandler) HandleKeypressKeepAlive() error { - return rpcKeypressKeepAlive() -} - -func (h *hidRpcHandler) HandleKeyboardMacroReport(data []byte) error { - // TODO: Implement macro handling - return nil -} - -func (h *hidRpcHandler) HandleCancelKeyboardMacro() error { - // TODO: Implement macro cancellation - return nil -} - func rpcAbsMouseReport(x, y int, buttons uint8) error { return gadget.AbsMouseReport(x, y, buttons) } @@ -282,28 +214,6 @@ func rpcReinitializeUsbGadget() error { gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) { if currentSession != nil { writeJSONRPCEvent("keyboardLedState", state, currentSession) - - // Send HID-RPC LED state message - if currentSession.HidChannel != nil { - var raw byte - if state.NumLock { - raw |= usbgadget.KeyboardLedMaskNumLock - } - if state.CapsLock { - raw |= usbgadget.KeyboardLedMaskCapsLock - } - if state.ScrollLock { - raw |= usbgadget.KeyboardLedMaskScrollLock - } - if state.Compose { - raw |= usbgadget.KeyboardLedMaskCompose - } - if state.Kana { - raw |= usbgadget.KeyboardLedMaskKana - } - ledData := hidrpc.MarshalKeyboardLedState(raw) - currentSession.HidChannel.Send(ledData) - } } }) gadget.SetOnHidDeviceMissing(func(device string, err error) { diff --git a/webrtc.go b/webrtc.go index 119222c..ae5d497 100644 --- a/webrtc.go +++ b/webrtc.go @@ -22,7 +22,6 @@ type Session struct { //AudioTrack *webrtc.TrackLocalStaticSample ControlChannel *webrtc.DataChannel RPCChannel *webrtc.DataChannel - HidChannel *webrtc.DataChannel DiskChannel *webrtc.DataChannel shouldUmountVirtualMedia bool } @@ -142,9 +141,6 @@ func newSession(sessionConfig SessionConfig) (*Session, error) { handleTerminalChannel(d) case "serial": handleSerialChannel(d) - case "hid": - session.HidChannel = d - go handleHidChannel(d, session) default: if strings.HasPrefix(d.Label(), uploadIdPrefix) { go handleUploadChannel(d)