mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-18 03:28:19 +01:00
feat: hid rpc channel (#755)
* feat: use hidRpcChannel to save bandwidth * chore: simplify handshake of hid rpc * add logs * chore: add timeout when writing to hid endpoints * fix issues * chore: show hid rpc version * refactor hidrpc marshal / unmarshal * add queues for keyboard / mouse event * chore: change logging level of JSONRPC send event to trace * minor changes related to logging * fix: nil check * chore: add comments and remove unused code * add useMouse * chore: log msg data only when debug or trace mode * chore: make tslint happy * chore: unlock keyboardStateLock before calling onKeysDownChange * chore: remove keyPressReportApiAvailable * chore: change version handle * chore: clean up unused functions * remove comments
This commit is contained in:
100
internal/hidrpc/hidrpc.go
Normal file
100
internal/hidrpc/hidrpc.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package hidrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||
)
|
||||
|
||||
// MessageType is the type of the HID RPC message
|
||||
type MessageType byte
|
||||
|
||||
const (
|
||||
TypeHandshake MessageType = 0x01
|
||||
TypeKeyboardReport MessageType = 0x02
|
||||
TypePointerReport MessageType = 0x03
|
||||
TypeWheelReport MessageType = 0x04
|
||||
TypeKeypressReport MessageType = 0x05
|
||||
TypeMouseReport MessageType = 0x06
|
||||
TypeKeyboardLedState MessageType = 0x32
|
||||
TypeKeydownState MessageType = 0x33
|
||||
)
|
||||
|
||||
const (
|
||||
Version byte = 0x01 // Version of the HID RPC protocol
|
||||
)
|
||||
|
||||
// GetQueueIndex returns the index of the queue to which the message should be enqueued.
|
||||
func GetQueueIndex(messageType MessageType) int {
|
||||
switch messageType {
|
||||
case TypeHandshake:
|
||||
return 0
|
||||
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState:
|
||||
return 1
|
||||
case TypePointerReport, TypeMouseReport, TypeWheelReport:
|
||||
return 2
|
||||
default:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals the HID RPC message from the data.
|
||||
func Unmarshal(data []byte, message *Message) error {
|
||||
l := len(data)
|
||||
if l < 1 {
|
||||
return fmt.Errorf("invalid data length: %d", l)
|
||||
}
|
||||
|
||||
message.t = MessageType(data[0])
|
||||
message.d = data[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal marshals the HID RPC message to the data.
|
||||
func Marshal(message *Message) ([]byte, error) {
|
||||
if message.t == 0 {
|
||||
return nil, fmt.Errorf("invalid message type: %d", message.t)
|
||||
}
|
||||
|
||||
data := make([]byte, len(message.d)+1)
|
||||
data[0] = byte(message.t)
|
||||
copy(data[1:], message.d)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// NewHandshakeMessage creates a new handshake message.
|
||||
func NewHandshakeMessage() *Message {
|
||||
return &Message{
|
||||
t: TypeHandshake,
|
||||
d: []byte{Version},
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeyboardReportMessage creates a new keyboard report message.
|
||||
func NewKeyboardReportMessage(keys []byte, modifier uint8) *Message {
|
||||
return &Message{
|
||||
t: TypeKeyboardReport,
|
||||
d: append([]byte{modifier}, keys...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeyboardLedMessage creates a new keyboard LED message.
|
||||
func NewKeyboardLedMessage(state usbgadget.KeyboardState) *Message {
|
||||
return &Message{
|
||||
t: TypeKeyboardLedState,
|
||||
d: []byte{state.Byte()},
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeydownStateMessage creates a new keydown state message.
|
||||
func NewKeydownStateMessage(state usbgadget.KeysDownState) *Message {
|
||||
data := make([]byte, len(state.Keys)+1)
|
||||
data[0] = state.Modifier
|
||||
copy(data[1:], state.Keys)
|
||||
|
||||
return &Message{
|
||||
t: TypeKeydownState,
|
||||
d: data,
|
||||
}
|
||||
}
|
||||
133
internal/hidrpc/message.go
Normal file
133
internal/hidrpc/message.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package hidrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Message ..
|
||||
type Message struct {
|
||||
t MessageType
|
||||
d []byte
|
||||
}
|
||||
|
||||
// Marshal marshals the message to a byte array.
|
||||
func (m *Message) Marshal() ([]byte, error) {
|
||||
return Marshal(m)
|
||||
}
|
||||
|
||||
func (m *Message) Type() MessageType {
|
||||
return m.t
|
||||
}
|
||||
|
||||
func (m *Message) String() string {
|
||||
switch m.t {
|
||||
case TypeHandshake:
|
||||
return "Handshake"
|
||||
case TypeKeypressReport:
|
||||
if len(m.d) < 2 {
|
||||
return fmt.Sprintf("KeypressReport{Malformed: %v}", m.d)
|
||||
}
|
||||
return fmt.Sprintf("KeypressReport{Key: %d, Press: %v}", m.d[0], m.d[1] == uint8(1))
|
||||
case TypeKeyboardReport:
|
||||
if len(m.d) < 2 {
|
||||
return fmt.Sprintf("KeyboardReport{Malformed: %v}", m.d)
|
||||
}
|
||||
return fmt.Sprintf("KeyboardReport{Modifier: %d, Keys: %v}", m.d[0], m.d[1:])
|
||||
case TypePointerReport:
|
||||
if len(m.d) < 9 {
|
||||
return fmt.Sprintf("PointerReport{Malformed: %v}", m.d)
|
||||
}
|
||||
return fmt.Sprintf("PointerReport{X: %d, Y: %d, Button: %d}", m.d[0:4], m.d[4:8], m.d[8])
|
||||
case TypeMouseReport:
|
||||
if len(m.d) < 3 {
|
||||
return fmt.Sprintf("MouseReport{Malformed: %v}", m.d)
|
||||
}
|
||||
return fmt.Sprintf("MouseReport{DX: %d, DY: %d, Button: %d}", m.d[0], m.d[1], m.d[2])
|
||||
default:
|
||||
return fmt.Sprintf("Unknown{Type: %d, Data: %v}", m.t, m.d)
|
||||
}
|
||||
}
|
||||
|
||||
// KeypressReport ..
|
||||
type KeypressReport struct {
|
||||
Key byte
|
||||
Press bool
|
||||
}
|
||||
|
||||
// KeypressReport returns the keypress report from the message.
|
||||
func (m *Message) KeypressReport() (KeypressReport, error) {
|
||||
if m.t != TypeKeypressReport {
|
||||
return KeypressReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
||||
}
|
||||
|
||||
return KeypressReport{
|
||||
Key: m.d[0],
|
||||
Press: m.d[1] == uint8(1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// KeyboardReport ..
|
||||
type KeyboardReport struct {
|
||||
Modifier byte
|
||||
Keys []byte
|
||||
}
|
||||
|
||||
// KeyboardReport returns the keyboard report from the message.
|
||||
func (m *Message) KeyboardReport() (KeyboardReport, error) {
|
||||
if m.t != TypeKeyboardReport {
|
||||
return KeyboardReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
||||
}
|
||||
|
||||
return KeyboardReport{
|
||||
Modifier: m.d[0],
|
||||
Keys: m.d[1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PointerReport ..
|
||||
type PointerReport struct {
|
||||
X int
|
||||
Y int
|
||||
Button uint8
|
||||
}
|
||||
|
||||
func toInt(b []byte) int {
|
||||
return int(b[0])<<24 + int(b[1])<<16 + int(b[2])<<8 + int(b[3])<<0
|
||||
}
|
||||
|
||||
// PointerReport returns the point report from the message.
|
||||
func (m *Message) PointerReport() (PointerReport, error) {
|
||||
if m.t != TypePointerReport {
|
||||
return PointerReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
||||
}
|
||||
|
||||
if len(m.d) != 9 {
|
||||
return PointerReport{}, fmt.Errorf("invalid message length: %d", len(m.d))
|
||||
}
|
||||
|
||||
return PointerReport{
|
||||
X: toInt(m.d[0:4]),
|
||||
Y: toInt(m.d[4:8]),
|
||||
Button: uint8(m.d[8]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MouseReport ..
|
||||
type MouseReport struct {
|
||||
DX int8
|
||||
DY int8
|
||||
Button uint8
|
||||
}
|
||||
|
||||
// MouseReport returns the mouse report from the message.
|
||||
func (m *Message) MouseReport() (MouseReport, error) {
|
||||
if m.t != TypeMouseReport {
|
||||
return MouseReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
||||
}
|
||||
|
||||
return MouseReport{
|
||||
DX: int8(m.d[0]),
|
||||
DY: int8(m.d[1]),
|
||||
Button: uint8(m.d[2]),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
package usbgadget
|
||||
|
||||
import "time"
|
||||
|
||||
const dwc3Path = "/sys/bus/platform/drivers/dwc3"
|
||||
|
||||
const hidWriteTimeout = 10 * time.Millisecond
|
||||
|
||||
@@ -86,6 +86,12 @@ type KeyboardState struct {
|
||||
Compose bool `json:"compose"`
|
||||
Kana bool `json:"kana"`
|
||||
Shift bool `json:"shift"` // This is not part of the main USB HID spec
|
||||
raw byte
|
||||
}
|
||||
|
||||
// Byte returns the raw byte representation of the keyboard state.
|
||||
func (k *KeyboardState) Byte() byte {
|
||||
return k.raw
|
||||
}
|
||||
|
||||
func getKeyboardState(b byte) KeyboardState {
|
||||
@@ -97,6 +103,7 @@ func getKeyboardState(b byte) KeyboardState {
|
||||
Compose: b&KeyboardLedMaskCompose != 0,
|
||||
Kana: b&KeyboardLedMaskKana != 0,
|
||||
Shift: b&KeyboardLedMaskShift != 0,
|
||||
raw: b,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,19 +146,26 @@ func (u *UsbGadget) GetKeysDownState() KeysDownState {
|
||||
}
|
||||
|
||||
func (u *UsbGadget) updateKeyDownState(state KeysDownState) {
|
||||
u.keyboardStateLock.Lock()
|
||||
defer u.keyboardStateLock.Unlock()
|
||||
u.log.Trace().Interface("old", u.keysDownState).Interface("new", state).Msg("acquiring keyboardStateLock for updateKeyDownState")
|
||||
|
||||
if u.keysDownState.Modifier == state.Modifier &&
|
||||
bytes.Equal(u.keysDownState.Keys, state.Keys) {
|
||||
return // No change in key down state
|
||||
// this is intentional to unlock keyboard state lock before onKeysDownChange callback
|
||||
{
|
||||
u.keyboardStateLock.Lock()
|
||||
defer u.keyboardStateLock.Unlock()
|
||||
|
||||
if u.keysDownState.Modifier == state.Modifier &&
|
||||
bytes.Equal(u.keysDownState.Keys, state.Keys) {
|
||||
return // No change in key down state
|
||||
}
|
||||
|
||||
u.log.Trace().Interface("old", u.keysDownState).Interface("new", state).Msg("keysDownState updated")
|
||||
u.keysDownState = state
|
||||
}
|
||||
|
||||
u.log.Trace().Interface("old", u.keysDownState).Interface("new", state).Msg("keysDownState updated")
|
||||
u.keysDownState = state
|
||||
|
||||
if u.onKeysDownChange != nil {
|
||||
u.log.Trace().Interface("state", state).Msg("calling onKeysDownChange")
|
||||
(*u.onKeysDownChange)(state)
|
||||
u.log.Trace().Interface("state", state).Msg("onKeysDownChange called")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +247,7 @@ func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := u.keyboardHidFile.Write(append([]byte{modifier, 0x00}, keys[:hidKeyBufferSize]...))
|
||||
_, err := u.writeWithTimeout(u.keyboardHidFile, append([]byte{modifier, 0x00}, keys[:hidKeyBufferSize]...))
|
||||
if err != nil {
|
||||
u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
||||
u.keyboardHidFile.Close()
|
||||
|
||||
@@ -74,7 +74,7 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := u.absMouseHidFile.Write(data)
|
||||
_, err := u.writeWithTimeout(u.absMouseHidFile, data)
|
||||
if err != nil {
|
||||
u.logWithSuppression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1")
|
||||
u.absMouseHidFile.Close()
|
||||
|
||||
@@ -64,7 +64,7 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := u.relMouseHidFile.Write(data)
|
||||
_, err := u.writeWithTimeout(u.relMouseHidFile, data)
|
||||
if err != nil {
|
||||
u.logWithSuppression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2")
|
||||
u.relMouseHidFile.Close()
|
||||
|
||||
@@ -3,10 +3,13 @@ package usbgadget
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
@@ -107,6 +110,31 @@ func compareFileContent(oldContent []byte, newContent []byte, looserMatch bool)
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
u.logWithSuppression(
|
||||
fmt.Sprintf("writeWithTimeout_%s", file.Name()),
|
||||
1000,
|
||||
u.log,
|
||||
err,
|
||||
"write timed out: %s",
|
||||
file.Name(),
|
||||
)
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UsbGadget) logWithSuppression(counterName string, every int, logger *zerolog.Logger, err error, msg string, args ...any) {
|
||||
u.logSuppressionLock.Lock()
|
||||
defer u.logSuppressionLock.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user