mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-05-26 16:15:09 +02:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user