From 5da357ba01ffc720a5cd3053c2bb1cb0193d506d Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 9 Sep 2025 23:31:58 +0000 Subject: [PATCH] [WIP] Cleanup: Remove hid optimization code, as it is out of scope --- hidrpc.go | 2 +- input_rpc.go | 359 ------------------------------------ internal/audio/cgo_audio.go | 6 +- internal/audio/ipc_input.go | 9 +- jsonrpc.go | 87 --------- usb.go | 10 +- 6 files changed, 9 insertions(+), 464 deletions(-) delete mode 100644 input_rpc.go diff --git a/hidrpc.go b/hidrpc.go index c5597096..74fe687f 100644 --- a/hidrpc.go +++ b/hidrpc.go @@ -35,7 +35,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) { logger.Warn().Err(err).Msg("failed to get pointer report") return } - rpcErr = rpcAbsMouseReport(uint16(pointerReport.X), uint16(pointerReport.Y), pointerReport.Button) + rpcErr = rpcAbsMouseReport(pointerReport.X, pointerReport.Y, pointerReport.Button) case hidrpc.TypeMouseReport: mouseReport, err := message.MouseReport() if err != nil { diff --git a/input_rpc.go b/input_rpc.go deleted file mode 100644 index 3e7b52ab..00000000 --- a/input_rpc.go +++ /dev/null @@ -1,359 +0,0 @@ -package kvm - -import ( - "encoding/json" - "fmt" -) - -// Constants for input validation -const ( - // MaxKeyboardKeys defines the maximum number of simultaneous key presses - // This matches the USB HID keyboard report specification - MaxKeyboardKeys = 6 -) - -// Input RPC Direct Handlers -// This module provides optimized direct handlers for high-frequency input events, -// bypassing the reflection-based RPC system for improved performance. -// -// Performance benefits: -// - Eliminates reflection overhead (~2-3ms per call) -// - Reduces memory allocations -// - Optimizes parameter parsing and validation -// - Provides faster code path for input methods -// -// The handlers maintain full compatibility with existing RPC interface -// while providing significant latency improvements for input events. - -// Ultra-fast input RPC structures for zero-allocation parsing -// Bypasses float64 conversion by using typed JSON unmarshaling - -// InputRPCRequest represents a specialized JSON-RPC request for input methods -// This eliminates the map[string]interface{} overhead and float64 conversions -type InputRPCRequest struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - ID any `json:"id,omitempty"` - // Union of all possible input parameters - only relevant fields are populated - // Fields ordered for optimal 32-bit alignment: slice pointers, 2-byte fields, 1-byte fields - Params struct { - // Slice pointers (4 bytes on 32-bit ARM) - Keys *[]uint8 `json:"keys,omitempty"` - // 2-byte fields grouped together - X *uint16 `json:"x,omitempty"` - Y *uint16 `json:"y,omitempty"` - // 1-byte fields grouped together for optimal packing - Modifier *uint8 `json:"modifier,omitempty"` - Dx *int8 `json:"dx,omitempty"` - Dy *int8 `json:"dy,omitempty"` - Buttons *uint8 `json:"buttons,omitempty"` - WheelY *int8 `json:"wheelY,omitempty"` - } `json:"params,omitempty"` -} - -// Common validation helpers for parameter parsing -// These reduce code duplication and provide consistent error messages - -// Ultra-fast inline validation macros - no function call overhead -// These prioritize the happy path (direct int parsing) for maximum performance - -// validateKeysArray extracts and validates a keys array parameter -// Ultra-optimized inline validation for maximum performance -func validateKeysArray(params map[string]interface{}, methodName string) ([]uint8, error) { - keysInterface, ok := params["keys"].([]interface{}) - if !ok { - return nil, fmt.Errorf("%s: keys parameter must be an array, got %T", methodName, params["keys"]) - } - if len(keysInterface) > MaxKeyboardKeys { - return nil, fmt.Errorf("%s: too many keys (%d), maximum is %d", methodName, len(keysInterface), MaxKeyboardKeys) - } - - keys := make([]uint8, len(keysInterface)) - for i, keyInterface := range keysInterface { - // Try int first (most common case for small integers) - if intVal, ok := keyInterface.(int); ok { - if intVal < 0 || intVal > 255 { - return nil, fmt.Errorf("%s: key at index %d value %d out of range [0-255]", methodName, i, intVal) - } - keys[i] = uint8(intVal) - continue - } - - return nil, fmt.Errorf("%s: key at index %d must be a number, got %T", methodName, i, keyInterface) - } - return keys, nil -} - -// Input parameter structures for direct RPC handlers -// These mirror the original RPC method signatures but provide -// optimized parsing from JSON map parameters. - -// KeyboardReportParams represents parameters for keyboard HID report -// Matches rpcKeyboardReport(modifier uint8, keys []uint8) -type KeyboardReportParams struct { - Modifier uint8 `json:"modifier"` // Keyboard modifier keys (Ctrl, Alt, Shift, etc.) - Keys []uint8 `json:"keys"` // Array of pressed key codes (up to 6 keys) -} - -// AbsMouseReportParams represents parameters for absolute mouse positioning -// Matches rpcAbsMouseReport(x, y uint16, buttons uint8) -type AbsMouseReportParams struct { - X uint16 `json:"x"` // Absolute X coordinate (0-32767) - Y uint16 `json:"y"` // Absolute Y coordinate (0-32767) - Buttons uint8 `json:"buttons"` // Mouse button state bitmask -} - -// RelMouseReportParams represents parameters for relative mouse movement -// Matches rpcRelMouseReport(dx, dy int8, buttons uint8) -type RelMouseReportParams struct { - Dx int8 `json:"dx"` // Relative X movement delta (-127 to +127) - Dy int8 `json:"dy"` // Relative Y movement delta (-127 to +127) - Buttons uint8 `json:"buttons"` // Mouse button state bitmask -} - -// WheelReportParams represents parameters for mouse wheel events -// Matches rpcWheelReport(wheelY int8) -type WheelReportParams struct { - WheelY int8 `json:"wheelY"` // Wheel scroll delta (-127 to +127) -} - -// Ultra-fast typed input handler - completely bypasses float64 conversions -// Uses direct JSON unmarshaling to target types for maximum performance -func handleInputRPCUltraFast(data []byte) (interface{}, error) { - var request InputRPCRequest - err := json.Unmarshal(data, &request) - if err != nil { - return nil, fmt.Errorf("failed to parse input request: %v", err) - } - - switch request.Method { - case "keyboardReport": - if request.Params.Modifier == nil || request.Params.Keys == nil { - return nil, fmt.Errorf("keyboardReport: missing required parameters") - } - keys := *request.Params.Keys - if len(keys) > MaxKeyboardKeys { - return nil, fmt.Errorf("keyboardReport: too many keys (max %d)", MaxKeyboardKeys) - } - _, err = rpcKeyboardReport(*request.Params.Modifier, keys) - return nil, err - - case "absMouseReport": - if request.Params.X == nil || request.Params.Y == nil || request.Params.Buttons == nil { - return nil, fmt.Errorf("absMouseReport: missing required parameters") - } - x, y, buttons := *request.Params.X, *request.Params.Y, *request.Params.Buttons - if x > 32767 || y > 32767 { - return nil, fmt.Errorf("absMouseReport: coordinates out of range") - } - return nil, rpcAbsMouseReport(x, y, buttons) - - case "relMouseReport": - if request.Params.Dx == nil || request.Params.Dy == nil || request.Params.Buttons == nil { - return nil, fmt.Errorf("relMouseReport: missing required parameters") - } - return nil, rpcRelMouseReport(*request.Params.Dx, *request.Params.Dy, *request.Params.Buttons) - - case "wheelReport": - if request.Params.WheelY == nil { - return nil, fmt.Errorf("wheelReport: missing wheelY parameter") - } - return nil, rpcWheelReport(*request.Params.WheelY) - - default: - return nil, fmt.Errorf("unknown input method: %s", request.Method) - } -} - -// Direct handler for keyboard reports -// Ultra-optimized path with inlined validation for maximum performance -func handleKeyboardReportDirect(params map[string]interface{}) (interface{}, error) { - // Inline modifier validation - prioritize int path - var modifier uint8 - if intVal, ok := params["modifier"].(int); ok { - if intVal < 0 || intVal > 255 { - return nil, fmt.Errorf("keyboardReport: modifier value %d out of range [0-255]", intVal) - } - modifier = uint8(intVal) - } else if floatVal, ok := params["modifier"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < 0 || floatVal > 255 { - return nil, fmt.Errorf("keyboardReport: modifier value %v invalid", floatVal) - } - modifier = uint8(floatVal) - } else { - return nil, fmt.Errorf("keyboardReport: modifier must be a number") - } - - // Extract and validate keys array - keys, err := validateKeysArray(params, "keyboardReport") - if err != nil { - return nil, err - } - - _, err = rpcKeyboardReport(modifier, keys) - return nil, err -} - -// Direct handler for absolute mouse reports -// Ultra-optimized path with inlined validation for maximum performance -func handleAbsMouseReportDirect(params map[string]interface{}) (interface{}, error) { - // Inline x coordinate validation - check float64 first (most common JSON number type) - var x uint16 - if floatVal, ok := params["x"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < 0 || floatVal > 32767 { - return nil, fmt.Errorf("absMouseReport: x value %v invalid", floatVal) - } - x = uint16(floatVal) - } else if intVal, ok := params["x"].(int); ok { - if intVal < 0 || intVal > 32767 { - return nil, fmt.Errorf("absMouseReport: x value %d out of range [0-32767]", intVal) - } - x = uint16(intVal) - } else { - return nil, fmt.Errorf("absMouseReport: x must be a number") - } - - // Inline y coordinate validation - check float64 first (most common JSON number type) - var y uint16 - if floatVal, ok := params["y"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < 0 || floatVal > 32767 { - return nil, fmt.Errorf("absMouseReport: y value %v invalid", floatVal) - } - y = uint16(floatVal) - } else if intVal, ok := params["y"].(int); ok { - if intVal < 0 || intVal > 32767 { - return nil, fmt.Errorf("absMouseReport: y value %d out of range [0-32767]", intVal) - } - y = uint16(intVal) - } else { - return nil, fmt.Errorf("absMouseReport: y must be a number") - } - - // Inline buttons validation - var buttons uint8 - if intVal, ok := params["buttons"].(int); ok { - if intVal < 0 || intVal > 255 { - return nil, fmt.Errorf("absMouseReport: buttons value %d out of range [0-255]", intVal) - } - buttons = uint8(intVal) - } else if floatVal, ok := params["buttons"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < 0 || floatVal > 255 { - return nil, fmt.Errorf("absMouseReport: buttons value %v invalid", floatVal) - } - buttons = uint8(floatVal) - } else { - return nil, fmt.Errorf("absMouseReport: buttons must be a number") - } - - return nil, rpcAbsMouseReport(x, y, buttons) -} - -// Direct handler for relative mouse reports -// Ultra-optimized path with inlined validation for maximum performance -func handleRelMouseReportDirect(params map[string]interface{}) (interface{}, error) { - // Inline dx validation - check float64 first (most common JSON number type) - var dx int8 - if floatVal, ok := params["dx"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < -128 || floatVal > 127 { - return nil, fmt.Errorf("relMouseReport: dx value %v invalid", floatVal) - } - dx = int8(floatVal) - } else if intVal, ok := params["dx"].(int); ok { - if intVal < -128 || intVal > 127 { - return nil, fmt.Errorf("relMouseReport: dx value %d out of range [-128 to 127]", intVal) - } - dx = int8(intVal) - } else { - return nil, fmt.Errorf("relMouseReport: dx must be a number") - } - - // Inline dy validation - check float64 first (most common JSON number type) - var dy int8 - if floatVal, ok := params["dy"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < -128 || floatVal > 127 { - return nil, fmt.Errorf("relMouseReport: dy value %v invalid", floatVal) - } - dy = int8(floatVal) - } else if intVal, ok := params["dy"].(int); ok { - if intVal < -128 || intVal > 127 { - return nil, fmt.Errorf("relMouseReport: dy value %d out of range [-128 to 127]", intVal) - } - dy = int8(intVal) - } else { - return nil, fmt.Errorf("relMouseReport: dy must be a number") - } - - // Inline buttons validation - check float64 first (most common JSON number type) - var buttons uint8 - if floatVal, ok := params["buttons"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < 0 || floatVal > 255 { - return nil, fmt.Errorf("relMouseReport: buttons value %v invalid", floatVal) - } - buttons = uint8(floatVal) - } else if intVal, ok := params["buttons"].(int); ok { - if intVal < 0 || intVal > 255 { - return nil, fmt.Errorf("relMouseReport: buttons value %d out of range [0-255]", intVal) - } - buttons = uint8(intVal) - } else { - return nil, fmt.Errorf("relMouseReport: buttons must be a number") - } - - return nil, rpcRelMouseReport(dx, dy, buttons) -} - -// Direct handler for wheel reports -// Ultra-optimized path with inlined validation for maximum performance -func handleWheelReportDirect(params map[string]interface{}) (interface{}, error) { - // Inline wheelY validation - var wheelY int8 - if intVal, ok := params["wheelY"].(int); ok { - if intVal < -128 || intVal > 127 { - return nil, fmt.Errorf("wheelReport: wheelY value %d out of range [-128 to 127]", intVal) - } - wheelY = int8(intVal) - } else if floatVal, ok := params["wheelY"].(float64); ok { - if floatVal != float64(int(floatVal)) || floatVal < -128 || floatVal > 127 { - return nil, fmt.Errorf("wheelReport: wheelY value %v invalid", floatVal) - } - wheelY = int8(floatVal) - } else { - return nil, fmt.Errorf("wheelReport: wheelY must be a number") - } - - return nil, rpcWheelReport(wheelY) -} - -// handleInputRPCDirect routes input method calls to their optimized direct handlers -// This is the main entry point for the fast path that bypasses reflection. -// It provides significant performance improvements for high-frequency input events. -// -// Performance monitoring: Consider adding metrics collection here to track -// latency improvements and call frequency for production monitoring. -func handleInputRPCDirect(method string, params map[string]interface{}) (interface{}, error) { - switch method { - case "keyboardReport": - return handleKeyboardReportDirect(params) - case "absMouseReport": - return handleAbsMouseReportDirect(params) - case "relMouseReport": - return handleRelMouseReportDirect(params) - case "wheelReport": - return handleWheelReportDirect(params) - default: - // This should never happen if isInputMethod is correctly implemented - return nil, fmt.Errorf("handleInputRPCDirect: unsupported method '%s'", method) - } -} - -// isInputMethod determines if a given RPC method should use the optimized direct path -// Returns true for input-related methods that have direct handlers implemented. -// This function must be kept in sync with handleInputRPCDirect. -func isInputMethod(method string) bool { - switch method { - case "keyboardReport", "absMouseReport", "relMouseReport", "wheelReport": - return true - default: - return false - } -} diff --git a/internal/audio/cgo_audio.go b/internal/audio/cgo_audio.go index 68719a57..af4ef35f 100644 --- a/internal/audio/cgo_audio.go +++ b/internal/audio/cgo_audio.go @@ -87,9 +87,9 @@ static volatile int playback_initialized = 0; // Function to dynamically update Opus encoder parameters int update_opus_encoder_params(int bitrate, int complexity, int vbr, int vbr_constraint, int signal_type, int bandwidth, int dtx) { - // This function works for both audio input and output encoder parameters - // Require either capture (output) or playback (input) initialization - if (!encoder || (!capture_initialized && !playback_initialized)) { + // This function updates encoder parameters for audio input (capture) + // Only capture uses the encoder; playback uses a separate decoder + if (!encoder || !capture_initialized) { return -1; // Audio encoder not initialized } diff --git a/internal/audio/ipc_input.go b/internal/audio/ipc_input.go index 70e8c8b4..a3d944e3 100644 --- a/internal/audio/ipc_input.go +++ b/internal/audio/ipc_input.go @@ -532,12 +532,9 @@ func (ais *AudioInputServer) processOpusConfig(data []byte) error { logger.Info().Interface("config", config).Msg("applying dynamic Opus encoder configuration") - // Ensure capture is initialized before updating encoder parameters - // The C function requires both encoder and capture_initialized to be true - if err := CGOAudioInit(); err != nil { - logger.Debug().Err(err).Msg("Audio capture already initialized or initialization failed") - // Continue anyway - capture may already be initialized - } + // Note: We don't call CGOAudioInit() here as it would destroy and recreate the encoder, + // causing temporary unavailability. The encoder should already be initialized when + // the audio input server starts. // Apply the Opus encoder configuration dynamically with retry logic var err error diff --git a/jsonrpc.go b/jsonrpc.go index d50306d4..8b327efa 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -1,7 +1,6 @@ package kvm import ( - "bytes" "context" "encoding/json" "errors" @@ -21,8 +20,6 @@ import ( "github.com/jetkvm/kvm/internal/usbgadget" ) -// Direct RPC message handling for optimal input responsiveness - type JSONRPCRequest struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` @@ -97,62 +94,6 @@ func writeJSONRPCEvent(event string, params any, session *Session) { } func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { - // Ultra-fast path for input methods - completely bypass float64 conversions - // This optimization reduces latency by 5-10ms per input event by: - // - Eliminating float64 conversion overhead entirely - // - Using direct JSON unmarshaling to target types - // - Removing map[string]interface{} allocations - // - Bypassing reflection completely - if len(message.Data) > 0 { - // Quick method detection without full JSON parsing - data := message.Data - if bytes.Contains(data, []byte(`"keyboardReport"`)) || - bytes.Contains(data, []byte(`"absMouseReport"`)) || - bytes.Contains(data, []byte(`"relMouseReport"`)) || - bytes.Contains(data, []byte(`"wheelReport"`)) { - result, err := handleInputRPCUltraFast(data) - if err != nil { - jsonRpcLogger.Error().Err(err).Msg("Error in ultra-fast input handler") - errorResponse := JSONRPCResponse{ - JSONRPC: "2.0", - Error: map[string]interface{}{ - "code": -32603, - "message": "Internal error", - "data": err.Error(), - }, - ID: nil, // Will be extracted if needed - } - writeJSONRPCResponse(errorResponse, session) - return - } - - // Extract ID for response (minimal parsing) - var requestID interface{} - if idStart := bytes.Index(data, []byte(`"id":`)); idStart != -1 { - // Simple ID extraction - assumes numeric ID - idStart += 5 - for i := idStart; i < len(data); i++ { - if data[i] >= '0' && data[i] <= '9' { - continue - } - if id, err := strconv.Atoi(string(data[idStart:i])); err == nil { - requestID = id - } - break - } - } - - response := JSONRPCResponse{ - JSONRPC: "2.0", - Result: result, - ID: requestID, - } - writeJSONRPCResponse(response, session) - return - } - } - - // Fallback to standard JSON parsing for non-input methods var request JSONRPCRequest err := json.Unmarshal(message.Data, &request) if err != nil { @@ -180,34 +121,6 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { scopedLogger.Trace().Msg("Received RPC request") - // Legacy fast path for input methods (kept as fallback) - if isInputMethod(request.Method) { - result, err := handleInputRPCDirect(request.Method, request.Params) - if err != nil { - scopedLogger.Error().Err(err).Msg("Error calling direct input handler") - errorResponse := JSONRPCResponse{ - JSONRPC: "2.0", - Error: map[string]interface{}{ - "code": -32603, - "message": "Internal error", - "data": err.Error(), - }, - ID: request.ID, - } - writeJSONRPCResponse(errorResponse, session) - return - } - - response := JSONRPCResponse{ - JSONRPC: "2.0", - Result: result, - ID: request.ID, - } - writeJSONRPCResponse(response, session) - return - } - - // Fallback to reflection-based handler for non-input methods handler, ok := rpcHandlers[request.Method] if !ok { errorResponse := JSONRPCResponse{ diff --git a/usb.go b/usb.go index f0b2b924..131cd517 100644 --- a/usb.go +++ b/usb.go @@ -51,8 +51,8 @@ func rpcKeypressReport(key byte, press bool) (usbgadget.KeysDownState, error) { return gadget.KeypressReport(key, press) } -func rpcAbsMouseReport(x uint16, y uint16, buttons uint8) error { - return gadget.AbsMouseReport(int(x), int(y), buttons) +func rpcAbsMouseReport(x int, y int, buttons uint8) error { + return gadget.AbsMouseReport(x, y, buttons) } func rpcRelMouseReport(dx int8, dy int8, buttons uint8) error { @@ -60,16 +60,10 @@ func rpcRelMouseReport(dx int8, dy int8, buttons uint8) error { } func rpcWheelReport(wheelY int8) error { - if gadget == nil { - return nil // Gracefully handle uninitialized gadget (e.g., in tests) - } return gadget.AbsMouseWheelReport(wheelY) } func rpcGetKeyboardLedState() (state usbgadget.KeyboardState) { - if gadget == nil { - return usbgadget.KeyboardState{} // Return empty state for uninitialized gadget - } return gadget.GetKeyboardState() }