From 0ef60189c07c4d48066e18486077534f61e7f745 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Wed, 13 Aug 2025 16:39:55 -0500 Subject: [PATCH] Fix handling of meta keys in client --- jsonrpc.go | 63 +++++++++++++++++++------------ ui/src/components/WebRTCVideo.tsx | 14 ++++++- ui/src/keyboardMappings.ts | 10 ++++- 3 files changed, 60 insertions(+), 27 deletions(-) diff --git a/jsonrpc.go b/jsonrpc.go index 81b6a65..dedf586 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -13,6 +13,7 @@ import ( "time" "github.com/pion/webrtc/v4" + "github.com/rs/zerolog" "go.bug.st/serial" "github.com/jetkvm/kvm/internal/usbgadget" @@ -134,7 +135,7 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { } scopedLogger.Trace().Msg("Calling RPC handler") - result, err := callRPCHandler(handler, request.Params) + result, err := callRPCHandler(scopedLogger, handler, request.Params) if err != nil { scopedLogger.Error().Err(err).Msg("Error calling RPC handler") errorResponse := JSONRPCResponse{ @@ -472,7 +473,7 @@ type RPCHandler struct { } // call the handler but recover from a panic to ensure our RPC thread doesn't collapse on malformed calls -func callRPCHandler(handler RPCHandler, params map[string]interface{}) (result interface{}, err error) { +func callRPCHandler(logger zerolog.Logger, handler RPCHandler, params map[string]interface{}) (result interface{}, err error) { // Use defer to recover from a panic defer func() { if r := recover(); r != nil { @@ -486,11 +487,11 @@ func callRPCHandler(handler RPCHandler, params map[string]interface{}) (result i }() // Call the handler - result, err = riskyCallRPCHandler(handler, params) - return result, err + result, err = riskyCallRPCHandler(logger, handler, params) + return result, err // do not combine these two lines into one, as it breaks the above defer function's setting of err } -func riskyCallRPCHandler(handler RPCHandler, params map[string]interface{}) (interface{}, error) { +func riskyCallRPCHandler(logger zerolog.Logger, handler RPCHandler, params map[string]interface{}) (interface{}, error) { handlerValue := reflect.ValueOf(handler.Func) handlerType := handlerValue.Type() @@ -499,20 +500,24 @@ func riskyCallRPCHandler(handler RPCHandler, params map[string]interface{}) (int } numParams := handlerType.NumIn() - args := make([]reflect.Value, numParams) - // Get the parameter names from the RPCHandler - paramNames := handler.Params + paramNames := handler.Params // Get the parameter names from the RPCHandler if len(paramNames) != numParams { - return nil, errors.New("mismatch between handler parameters and defined parameter names") + err := fmt.Errorf("mismatch between handler parameters (%d) and defined parameter names (%d)", numParams, len(paramNames)) + logger.Error().Strs("paramNames", paramNames).Err(err).Msg("Cannot call RPC handler") + return nil, err } + args := make([]reflect.Value, numParams) + for i := 0; i < numParams; i++ { paramType := handlerType.In(i) paramName := paramNames[i] paramValue, ok := params[paramName] if !ok { - return nil, errors.New("missing parameter: " + paramName) + err := fmt.Errorf("missing parameter: %s", paramName) + logger.Error().Err(err).Msg("Cannot marshal arguments for RPC handler") + return nil, err } convertedValue := reflect.ValueOf(paramValue) @@ -529,7 +534,7 @@ func riskyCallRPCHandler(handler RPCHandler, params map[string]interface{}) (int if elemValue.Kind() == reflect.Float64 && paramType.Elem().Kind() == reflect.Uint8 { intValue := int(elemValue.Float()) if intValue < 0 || intValue > 255 { - return nil, fmt.Errorf("value out of range for uint8: %v", intValue) + return nil, fmt.Errorf("value out of range for uint8: %v for parameter %s", intValue, paramName) } newSlice.Index(j).SetUint(uint64(intValue)) } else { @@ -545,12 +550,12 @@ func riskyCallRPCHandler(handler RPCHandler, params map[string]interface{}) (int } else if paramType.Kind() == reflect.Struct && convertedValue.Kind() == reflect.Map { jsonData, err := json.Marshal(convertedValue.Interface()) if err != nil { - return nil, fmt.Errorf("failed to marshal map to JSON: %v", err) + return nil, fmt.Errorf("failed to marshal map to JSON: %v for parameter %s", err, paramName) } newStruct := reflect.New(paramType).Interface() if err := json.Unmarshal(jsonData, newStruct); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON into struct: %v", err) + return nil, fmt.Errorf("failed to unmarshal JSON into struct: %v for parameter %s", err, paramName) } args[i] = reflect.ValueOf(newStruct).Elem() } else { @@ -561,6 +566,7 @@ func riskyCallRPCHandler(handler RPCHandler, params map[string]interface{}) (int } } + logger.Trace().Interface("args", args).Msg("Calling RPC handler") results := handlerValue.Call(args) if len(results) == 0 { @@ -568,23 +574,32 @@ func riskyCallRPCHandler(handler RPCHandler, params map[string]interface{}) (int } if len(results) == 1 { - if results[0].Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) { - if !results[0].IsNil() { - return nil, results[0].Interface().(error) + if ok, err := asError(results[0]); ok { + return nil, err + } + return results[0].Interface(), nil + } + + if len(results) == 2 { + if ok, err := asError(results[1]); ok { + if err != nil { + return nil, err } - return nil, nil } return results[0].Interface(), nil } - if len(results) == 2 && results[1].Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) { - if !results[1].IsNil() { - return nil, results[1].Interface().(error) - } - return results[0].Interface(), nil - } + return nil, fmt.Errorf("too many return values from handler: %d", len(results)) +} - return nil, errors.New("unexpected return values from handler") +func asError(value reflect.Value) (bool, error) { + if value.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) { + if value.IsNil() { + return true, nil + } + return true, value.Interface().(error) + } + return false, nil } func rpcSetMassStorageMode(mode string) (string, error) { diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index ec86149..c377154 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -337,17 +337,21 @@ export default function WebRTCVideo() { const code = getAdjustedKeyCode(e); const hidKey = keys[code]; + if (hidKey === undefined) { + console.warn(`Key down not mapped: ${code}`); + return; + } + // When pressing the meta key + another key, the key will never trigger a keyup // event, so we need to clear the keys after a short delay // https://bugs.chromium.org/p/chromium/issues/detail?id=28089 // https://bugzilla.mozilla.org/show_bug.cgi?id=1299553 - if (e.metaKey) { + if (e.metaKey && hidKey < 0xE0) { setTimeout(() => { sendKeypressEvent(hidKey, false); }, 10); } sendKeypressEvent(hidKey, true); - }, [sendKeypressEvent], ); @@ -357,6 +361,12 @@ export default function WebRTCVideo() { e.preventDefault(); const code = getAdjustedKeyCode(e); const hidKey = keys[code]; + + if (hidKey === undefined) { + console.warn(`Key up not mapped: ${code}`); + return; + } + sendKeypressEvent(hidKey, false); }, [sendKeypressEvent], diff --git a/ui/src/keyboardMappings.ts b/ui/src/keyboardMappings.ts index 1395a59..7122254 100644 --- a/ui/src/keyboardMappings.ts +++ b/ui/src/keyboardMappings.ts @@ -14,7 +14,7 @@ export const keys = { CapsLock: 0x39, Comma: 0x36, Compose: 0x65, - ContextMenu: 0, + ContextMenu: 0x65, // same as Compose Delete: 0x4c, Digit0: 0x27, Digit1: 0x1e, @@ -121,6 +121,14 @@ export const keys = { Space: 0x2c, SystemRequest: 0x9a, Tab: 0x2b, + ControlLeft: 0xe0, + ControlRight: 0xe4, + ShiftLeft: 0xe1, + ShiftRight: 0xe5, + AltLeft: 0xe2, + AltRight: 0xe6, + MetaLeft: 0xe3, + MetaRight: 0xe7, } as Record; export const modifiers = {