feat(hid): remove HID-RPC related code and improve keyboard handling logic

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2026-05-15 18:43:04 +08:00
parent 18f7d8425f
commit b1090c9493
13 changed files with 170 additions and 519 deletions

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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
}