mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-05-28 09:01:22 +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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -132,10 +131,10 @@ func (u *UsbGadget) GetKeyboardState() KeyboardState {
|
|||||||
return u.keyboardState
|
return u.keyboardState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) listenKeyboardEvents() {
|
func (u *UsbGadget) listenKeyboardEvents(ctx context.Context, file *os.File) {
|
||||||
var path string
|
var path string
|
||||||
if u.keyboardHidFile != nil {
|
if file != nil {
|
||||||
path = u.keyboardHidFile.Name()
|
path = file.Name()
|
||||||
}
|
}
|
||||||
l := u.log.With().Str("listener", "keyboardEvents").Str("path", path).Logger()
|
l := u.log.With().Str("listener", "keyboardEvents").Str("path", path).Logger()
|
||||||
l.Trace().Msg("starting")
|
l.Trace().Msg("starting")
|
||||||
@@ -144,12 +143,12 @@ func (u *UsbGadget) listenKeyboardEvents() {
|
|||||||
buf := make([]byte, hidReadBufferSize)
|
buf := make([]byte, hidReadBufferSize)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-u.keyboardStateCtx.Done():
|
case <-ctx.Done():
|
||||||
l.Info().Msg("context done")
|
l.Info().Msg("context done")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
l.Trace().Msg("reading from keyboard")
|
l.Trace().Msg("reading from keyboard")
|
||||||
if u.keyboardHidFile == nil {
|
if file == nil {
|
||||||
u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
|
u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
|
||||||
// show the error every 100 times to avoid spamming the logs
|
// show the error every 100 times to avoid spamming the logs
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@@ -158,16 +157,26 @@ func (u *UsbGadget) listenKeyboardEvents() {
|
|||||||
// reset the counter
|
// reset the counter
|
||||||
u.resetLogSuppressionCounter("keyboardHidFileNil")
|
u.resetLogSuppressionCounter("keyboardHidFileNil")
|
||||||
|
|
||||||
n, err := u.keyboardHidFile.Read(buf)
|
n, err := file.Read(buf)
|
||||||
if err != nil {
|
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")
|
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")
|
u.resetLogSuppressionCounter("keyboardHidFileRead")
|
||||||
|
|
||||||
l.Trace().Int("n", n).Bytes("buf", buf).Msg("got data from keyboard")
|
l.Trace().Int("n", n).Bytes("buf", buf).Msg("got data from keyboard")
|
||||||
if n != 1 {
|
if n < 1 {
|
||||||
l.Trace().Int("n", n).Msg("expected 1 byte, got")
|
l.Info().Int("n", n).Msg("expected at least 1 byte, got 0")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
u.updateKeyboardState(buf[0])
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
file, err := openWithTimeout("/dev/hidg0", os.O_RDWR, 0666, 3*time.Second)
|
||||||
u.keyboardHidFile, err = os.OpenFile("/dev/hidg0", os.O_RDWR, 0666)
|
|
||||||
if err != nil {
|
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") {
|
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().
|
u.log.Error().
|
||||||
@@ -197,34 +245,62 @@ func (u *UsbGadget) openKeyboardHidFile() error {
|
|||||||
return fmt.Errorf("failed to open hidg0: %w", err)
|
return fmt.Errorf("failed to open hidg0: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.keyboardStateCancel != nil {
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
u.keyboardStateCancel()
|
u.keyboardHidFile = file
|
||||||
}
|
u.keyboardStateCtx = ctx
|
||||||
|
u.keyboardStateCancel = cancel
|
||||||
u.keyboardStateCtx, u.keyboardStateCancel = context.WithCancel(context.Background())
|
u.listenKeyboardEvents(ctx, file)
|
||||||
u.listenKeyboardEvents()
|
|
||||||
|
|
||||||
return nil
|
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 {
|
func (u *UsbGadget) OpenKeyboardHidFile() error {
|
||||||
return u.openKeyboardHidFile()
|
return u.openKeyboardHidFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) keyboardWriteHidFile(data []byte) error {
|
func (u *UsbGadget) ReopenKeyboardHidFile() error {
|
||||||
var parts []string
|
return u.reopenKeyboardHidFile()
|
||||||
for _, b := range data {
|
}
|
||||||
parts = append(parts, fmt.Sprintf("\\x%02x", b))
|
|
||||||
}
|
|
||||||
hexString := strings.Join(parts, "")
|
|
||||||
|
|
||||||
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo -n -e '%s' > /dev/hidg0", hexString))
|
func (u *UsbGadget) keyboardWriteHidFileLocked(modifier byte, keys []byte) error {
|
||||||
err := cmd.Run()
|
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 {
|
if err != nil {
|
||||||
u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
||||||
|
u.closeKeyboardHidFileLocked()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.resetLogSuppressionCounter("keyboardWriteHidFile")
|
u.resetLogSuppressionCounter("keyboardWriteHidFile")
|
||||||
|
|
||||||
|
u.resetUserInputTime()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,23 +445,6 @@ func (u *UsbGadget) KeypressKeepAlive() error {
|
|||||||
return nil
|
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 {
|
func (u *UsbGadget) KeyboardReport(modifier uint8, keys []uint8) error {
|
||||||
u.keyboardLock.Lock()
|
u.keyboardLock.Lock()
|
||||||
defer u.keyboardLock.Unlock()
|
defer u.keyboardLock.Unlock()
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package usbgadget
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
@@ -107,3 +110,36 @@ func (u *UsbGadget) resetLogSuppressionCounter(counterName string) {
|
|||||||
u.logSuppressionCounter[counterName] = 0
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ function KeyboardWrapper() {
|
|||||||
setIsCapsLockActive(false);
|
setIsCapsLockActive(false);
|
||||||
}
|
}
|
||||||
sendKeyboardEvent([keys["CapsLock"]], []);
|
sendKeyboardEvent([keys["CapsLock"]], []);
|
||||||
|
setTimeout(resetKeyboardState, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
export const MessageType = {
|
|
||||||
Handshake: 0x01,
|
|
||||||
KeyboardReport: 0x02,
|
|
||||||
PointerReport: 0x03,
|
|
||||||
WheelReport: 0x04,
|
|
||||||
KeypressReport: 0x05,
|
|
||||||
MouseReport: 0x06,
|
|
||||||
KeyboardMacroReport: 0x07,
|
|
||||||
CancelKeyboardMacro: 0x08,
|
|
||||||
KeypressKeepAlive: 0x09,
|
|
||||||
KeyboardLedState: 0x32,
|
|
||||||
KeysDownState: 0x33,
|
|
||||||
KeyboardMacroState: 0x34,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export function marshalKeypressReport(key: number, press: boolean): Uint8Array {
|
|
||||||
return new Uint8Array([MessageType.KeypressReport, key, press ? 1 : 0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function marshalKeyboardReport(modifier: number, keys: number[]): Uint8Array {
|
|
||||||
const data = new Uint8Array(8);
|
|
||||||
data[0] = MessageType.KeyboardReport;
|
|
||||||
data[1] = modifier;
|
|
||||||
for (let i = 0; i < Math.min(keys.length, 6); i++) {
|
|
||||||
data[2 + i] = keys[i];
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function marshalKeypressKeepAlive(): Uint8Array {
|
|
||||||
return new Uint8Array([MessageType.KeypressKeepAlive]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function marshalHandshake(version: number): Uint8Array {
|
|
||||||
return new Uint8Array([MessageType.Handshake, version]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HidRpcMessage {
|
|
||||||
type: number;
|
|
||||||
data: Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unmarshalMessage(data: ArrayBuffer): HidRpcMessage {
|
|
||||||
const view = new Uint8Array(data);
|
|
||||||
return {
|
|
||||||
type: view[0],
|
|
||||||
data: view.slice(1),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface KeyboardLedState {
|
|
||||||
num_lock: boolean;
|
|
||||||
caps_lock: boolean;
|
|
||||||
scroll_lock: boolean;
|
|
||||||
compose: boolean;
|
|
||||||
kana: boolean;
|
|
||||||
shift: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseKeyboardLedState(data: Uint8Array): KeyboardLedState {
|
|
||||||
const raw = data[0] || 0;
|
|
||||||
return {
|
|
||||||
num_lock: !!(raw & (1 << 0)),
|
|
||||||
caps_lock: !!(raw & (1 << 1)),
|
|
||||||
scroll_lock: !!(raw & (1 << 2)),
|
|
||||||
compose: !!(raw & (1 << 3)),
|
|
||||||
kana: !!(raw & (1 << 4)),
|
|
||||||
shift: !!(raw & (1 << 6)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { useCallback, useEffect, useRef } from "react";
|
|
||||||
import { useRTCStore, useHidStore } from "./stores";
|
|
||||||
import {
|
|
||||||
marshalKeypressReport,
|
|
||||||
marshalKeyboardReport,
|
|
||||||
marshalKeypressKeepAlive,
|
|
||||||
marshalHandshake,
|
|
||||||
unmarshalMessage,
|
|
||||||
MessageType,
|
|
||||||
parseKeyboardLedState,
|
|
||||||
} from "./hidRpc";
|
|
||||||
|
|
||||||
export function useHidRpc() {
|
|
||||||
const hidChannel = useRTCStore(state => state.hidChannel);
|
|
||||||
const setRpcHidReady = useHidStore(state => state.setRpcHidReady);
|
|
||||||
const setKeyboardLedState = useHidStore(state => state.setKeyboardLedState);
|
|
||||||
const setKeysDownState = useHidStore(state => state.setKeysDownState);
|
|
||||||
const rpcHidReadyRef = useRef(false);
|
|
||||||
|
|
||||||
// Send keypress event
|
|
||||||
const reportKeypressEvent = useCallback(
|
|
||||||
(key: number, press: boolean) => {
|
|
||||||
if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const data = marshalKeypressReport(key, press);
|
|
||||||
hidChannel.send(data);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
[hidChannel]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send keyboard report (for legacy compatibility)
|
|
||||||
const reportKeyboardEvent = useCallback(
|
|
||||||
(modifier: number, keys: number[]) => {
|
|
||||||
if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const data = marshalKeyboardReport(modifier, keys);
|
|
||||||
hidChannel.send(data);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
[hidChannel]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send keepalive
|
|
||||||
const reportKeypressKeepAlive = useCallback(() => {
|
|
||||||
if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const data = marshalKeypressKeepAlive();
|
|
||||||
hidChannel.send(data);
|
|
||||||
return true;
|
|
||||||
}, [hidChannel]);
|
|
||||||
|
|
||||||
// Handle incoming HID-RPC messages
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hidChannel) return;
|
|
||||||
|
|
||||||
const messageHandler = (event: MessageEvent) => {
|
|
||||||
const msg = unmarshalMessage(event.data);
|
|
||||||
|
|
||||||
switch (msg.type) {
|
|
||||||
case MessageType.Handshake:
|
|
||||||
if (msg.data[0] === 1) {
|
|
||||||
rpcHidReadyRef.current = true;
|
|
||||||
setRpcHidReady(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MessageType.KeyboardLedState:
|
|
||||||
setKeyboardLedState(parseKeyboardLedState(msg.data));
|
|
||||||
break;
|
|
||||||
case MessageType.KeysDownState:
|
|
||||||
// Parse modifier + 6 keys
|
|
||||||
if (msg.data.length >= 7) {
|
|
||||||
setKeysDownState({
|
|
||||||
modifier: msg.data[0],
|
|
||||||
keys: Array.from(msg.data.slice(1, 7)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
hidChannel.addEventListener("message", messageHandler);
|
|
||||||
|
|
||||||
// Send handshake
|
|
||||||
if (hidChannel.readyState === "open") {
|
|
||||||
hidChannel.send(marshalHandshake(1));
|
|
||||||
} else {
|
|
||||||
hidChannel.addEventListener("open", () => {
|
|
||||||
hidChannel.send(marshalHandshake(1));
|
|
||||||
}, { once: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
hidChannel.removeEventListener("message", messageHandler);
|
|
||||||
rpcHidReadyRef.current = false;
|
|
||||||
setRpcHidReady(false);
|
|
||||||
};
|
|
||||||
}, [hidChannel, setRpcHidReady, setKeyboardLedState, setKeysDownState]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
rpcHidReady: rpcHidReadyRef.current,
|
|
||||||
reportKeypressEvent,
|
|
||||||
reportKeyboardEvent,
|
|
||||||
reportKeypressKeepAlive,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,10 @@ import { useCallback, useEffect, useRef } from "react";
|
|||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { useHidStore, useRTCStore, useSettingsStore } from "@/hooks/stores";
|
import { useHidStore, useRTCStore, useSettingsStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { useHidRpc } from "@/hooks/useHidRpc";
|
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
import { keys, modifiers } from "@/keyboardMappings";
|
||||||
|
|
||||||
export default function useKeyboard() {
|
export default function useKeyboard() {
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
const { rpcHidReady, reportKeypressEvent, reportKeyboardEvent, reportKeypressKeepAlive } = useHidRpc();
|
|
||||||
|
|
||||||
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
||||||
const forceHttp = useSettingsStore(state => state.forceHttp);
|
const forceHttp = useSettingsStore(state => state.forceHttp);
|
||||||
@@ -30,80 +28,45 @@ export default function useKeyboard() {
|
|||||||
if (usbState !== "configured") return;
|
if (usbState !== "configured") return;
|
||||||
const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
|
const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
|
||||||
|
|
||||||
// Try HID-RPC first
|
// Fallback to JSON-RPC
|
||||||
if (rpcHidReady && !forceHttp) {
|
send("keyboardReport", { keys, modifier: accModifier }, resp => {
|
||||||
reportKeyboardEvent(accModifier, keys);
|
if ("error" in resp) {
|
||||||
} else {
|
const msg = (resp.error.data as string) || resp.error.message || "";
|
||||||
// Fallback to JSON-RPC
|
if (msg.includes("cannot send after transport endpoint shutdown") && usbState === "configured") {
|
||||||
send("keyboardReport", { keys, modifier: accModifier }, resp => {
|
notifications.error("Please check if the cable and connection are stable.", { duration: 5000 });
|
||||||
if ("error" in resp) {
|
|
||||||
const msg = (resp.error.data as string) || resp.error.message || "";
|
|
||||||
if (msg.includes("cannot send after transport endpoint shutdown") && usbState === "configured") {
|
|
||||||
notifications.error("Please check if the cable and connection are stable.", { duration: 5000 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// We do this for the info bar to display the currently pressed keys for the user
|
// We do this for the info bar to display the currently pressed keys for the user
|
||||||
updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers });
|
updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers });
|
||||||
},
|
},
|
||||||
[forceHttp, rpcDataChannel?.readyState, rpcHidReady, reportKeyboardEvent, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
|
[forceHttp, rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send per-key press/release (new HID-RPC method)
|
// Send per-key press/release
|
||||||
const sendKeypress = useCallback(
|
const sendKeypress = useCallback(
|
||||||
(key: number, press: boolean) => {
|
(key: number, press: boolean) => {
|
||||||
if (isReinitializingGadget || usbState !== "configured") return;
|
if (isReinitializingGadget || usbState !== "configured") return;
|
||||||
|
|
||||||
if (rpcHidReady && !forceHttp) {
|
// Legacy: simulate device-side key handling
|
||||||
reportKeypressEvent(key, press);
|
// This maintains the 6-key buffer on the frontend for legacy compatibility
|
||||||
|
// For simplicity in migration, we fall back to full state reports
|
||||||
// Track held keys for keepalive
|
const modifier = press ? 0 : 0; // Simplified - would need proper modifier tracking
|
||||||
if (press) {
|
sendKeyboardEvent(press ? [key] : [], [modifier]);
|
||||||
heldKeysRef.current.add(key);
|
|
||||||
// Start keepalive interval if not already running
|
|
||||||
if (!keepaliveIntervalRef.current) {
|
|
||||||
keepaliveIntervalRef.current = setInterval(() => {
|
|
||||||
if (heldKeysRef.current.size > 0) {
|
|
||||||
reportKeypressKeepAlive();
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
heldKeysRef.current.delete(key);
|
|
||||||
if (heldKeysRef.current.size === 0 && keepaliveIntervalRef.current) {
|
|
||||||
clearInterval(keepaliveIntervalRef.current);
|
|
||||||
keepaliveIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Legacy: simulate device-side key handling
|
|
||||||
// This maintains the 6-key buffer on the frontend for legacy compatibility
|
|
||||||
// ... (existing logic would go here, but for now use sendKeyboardEvent)
|
|
||||||
// For simplicity in migration, we fall back to full state reports
|
|
||||||
const modifier = press ? 0 : 0; // Simplified - would need proper modifier tracking
|
|
||||||
sendKeyboardEvent(press ? [key] : [], [modifier]);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[rpcHidReady, forceHttp, reportKeypressEvent, reportKeypressKeepAlive, isReinitializingGadget, usbState, sendKeyboardEvent]
|
[isReinitializingGadget, usbState, sendKeyboardEvent]
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetKeyboardState = useCallback(() => {
|
const resetKeyboardState = useCallback(() => {
|
||||||
// Release all held keys
|
// Release all held keys
|
||||||
if (rpcHidReady && !forceHttp) {
|
sendKeyboardEvent([], []);
|
||||||
heldKeysRef.current.forEach(key => {
|
|
||||||
reportKeypressEvent(key, false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sendKeyboardEvent([], []);
|
|
||||||
}
|
|
||||||
heldKeysRef.current.clear();
|
heldKeysRef.current.clear();
|
||||||
if (keepaliveIntervalRef.current) {
|
if (keepaliveIntervalRef.current) {
|
||||||
clearInterval(keepaliveIntervalRef.current);
|
clearInterval(keepaliveIntervalRef.current);
|
||||||
keepaliveIntervalRef.current = null;
|
keepaliveIntervalRef.current = null;
|
||||||
}
|
}
|
||||||
}, [rpcHidReady, forceHttp, reportKeypressEvent, sendKeyboardEvent]);
|
}, [sendKeyboardEvent]);
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const useKeyboardEvents = (
|
|||||||
pasteCaptureRef?: React.RefObject<HTMLTextAreaElement>,
|
pasteCaptureRef?: React.RefObject<HTMLTextAreaElement>,
|
||||||
isReinitializingGadget?: boolean
|
isReinitializingGadget?: boolean
|
||||||
) => {
|
) => {
|
||||||
const { sendKeyboardEvent, sendKeypress, resetKeyboardState } = useKeyboard();
|
const { sendKeyboardEvent, resetKeyboardState } = useKeyboard();
|
||||||
const { setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive } = useHidStore();
|
const { setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive } = useHidStore();
|
||||||
|
|
||||||
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
||||||
@@ -65,6 +65,11 @@ export const useKeyboardEvents = (
|
|||||||
}
|
}
|
||||||
if (isReinitializingGadget) return;
|
if (isReinitializingGadget) return;
|
||||||
|
|
||||||
|
if (e.repeat) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const prev = useHidStore.getState();
|
const prev = useHidStore.getState();
|
||||||
let code = e.code;
|
let code = e.code;
|
||||||
@@ -94,15 +99,8 @@ export const useKeyboardEvents = (
|
|||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send per-key press event
|
|
||||||
const hidKey = keys[code];
|
|
||||||
if (hidKey !== undefined) {
|
|
||||||
sendKeypress(hidKey, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Still update the full state for legacy compatibility and UI display
|
|
||||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||||
}, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]);
|
}, [handleModifierKeys, remapCode, sendKeyboardEvent, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]);
|
||||||
|
|
||||||
const keyUpHandler = useCallback((e: KeyboardEvent) => {
|
const keyUpHandler = useCallback((e: KeyboardEvent) => {
|
||||||
if (isOcrMode) return;
|
if (isOcrMode) return;
|
||||||
@@ -124,15 +122,8 @@ export const useKeyboardEvents = (
|
|||||||
prev.activeModifiers.filter(k => k !== modifiers[code]),
|
prev.activeModifiers.filter(k => k !== modifiers[code]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send per-key release event
|
|
||||||
const hidKey = keys[code];
|
|
||||||
if (hidKey !== undefined) {
|
|
||||||
sendKeypress(hidKey, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Still update the full state for legacy compatibility and UI display
|
|
||||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||||
}, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]);
|
}, [handleModifierKeys, remapCode, sendKeyboardEvent, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]);
|
||||||
|
|
||||||
const setupKeyboardEvents = useCallback(() => {
|
const setupKeyboardEvents = useCallback(() => {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ export const useMouseEvents = (
|
|||||||
if (!force && settings.mouseMode !== "relative") return;
|
if (!force && settings.mouseMode !== "relative") return;
|
||||||
// Don't send mouse events while reinitializing gadget
|
// Don't send mouse events while reinitializing gadget
|
||||||
if (isReinitializingGadget) return;
|
if (isReinitializingGadget) return;
|
||||||
send("relMouseReport", { dx: calcDelta(x), dy: calcDelta(y), buttons });
|
const dx = calcDelta(x);
|
||||||
|
const dy = calcDelta(y);
|
||||||
|
send("relMouseReport", { dx, dy, buttons });
|
||||||
setMouseMove({ x, y, buttons });
|
setMouseMove({ x, y, buttons });
|
||||||
},
|
},
|
||||||
[send, setMouseMove, settings.mouseMode, isReinitializingGadget],
|
[send, setMouseMove, settings.mouseMode, isReinitializingGadget],
|
||||||
|
|||||||
@@ -462,11 +462,6 @@ export default function PCHome() {
|
|||||||
setDiskChannel(diskDataChannel);
|
setDiskChannel(diskDataChannel);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hidDataChannel = pc.createDataChannel("hid");
|
|
||||||
hidDataChannel.onopen = () => {
|
|
||||||
useRTCStore.getState().setHidChannel(hidDataChannel);
|
|
||||||
};
|
|
||||||
|
|
||||||
setPeerConnection(pc);
|
setPeerConnection(pc);
|
||||||
}, [
|
}, [
|
||||||
forceHttp,
|
forceHttp,
|
||||||
|
|||||||
92
usb.go
92
usb.go
@@ -6,9 +6,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
|
|
||||||
"kvm/internal/hidrpc"
|
|
||||||
"kvm/internal/usbgadget"
|
"kvm/internal/usbgadget"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,35 +37,13 @@ func initUsbGadget() {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
checkUSBState()
|
checkUSBState()
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(2500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) {
|
gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) {
|
||||||
if currentSession != nil {
|
if currentSession != nil {
|
||||||
writeJSONRPCEvent("keyboardLedState", state, currentSession)
|
writeJSONRPCEvent("keyboardLedState", state, currentSession)
|
||||||
|
|
||||||
// Send HID-RPC LED state message
|
|
||||||
if currentSession.HidChannel != nil {
|
|
||||||
var raw byte
|
|
||||||
if state.NumLock {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskNumLock
|
|
||||||
}
|
|
||||||
if state.CapsLock {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskCapsLock
|
|
||||||
}
|
|
||||||
if state.ScrollLock {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskScrollLock
|
|
||||||
}
|
|
||||||
if state.Compose {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskCompose
|
|
||||||
}
|
|
||||||
if state.Kana {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskKana
|
|
||||||
}
|
|
||||||
ledData := hidrpc.MarshalKeyboardLedState(raw)
|
|
||||||
currentSession.HidChannel.Send(ledData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -144,49 +119,6 @@ func rpcKeypressKeepAlive() error {
|
|||||||
return gadget.KeypressKeepAlive()
|
return gadget.KeypressKeepAlive()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHidChannel(d *webrtc.DataChannel, session *Session) {
|
|
||||||
hidServer := hidrpc.NewServer(&hidRpcHandler{session: session})
|
|
||||||
d.OnMessage(func(msg webrtc.DataChannelMessage) {
|
|
||||||
if err := hidServer.HandleMessage(msg.Data); err != nil {
|
|
||||||
usbLogger.Warn().Err(err).Msg("HID-RPC message handling error")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type hidRpcHandler struct {
|
|
||||||
session *Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hidRpcHandler) HandleHandshake(version byte) error {
|
|
||||||
if h.session.HidChannel != nil {
|
|
||||||
handshakeData := hidrpc.MarshalHandshake(version)
|
|
||||||
return h.session.HidChannel.Send(handshakeData)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hidRpcHandler) HandleKeyboardReport(modifier byte, keys []byte) error {
|
|
||||||
return rpcKeyboardReport(modifier, keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hidRpcHandler) HandleKeypressReport(key byte, press bool) error {
|
|
||||||
return rpcKeypressReport(key, press)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hidRpcHandler) HandleKeypressKeepAlive() error {
|
|
||||||
return rpcKeypressKeepAlive()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hidRpcHandler) HandleKeyboardMacroReport(data []byte) error {
|
|
||||||
// TODO: Implement macro handling
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hidRpcHandler) HandleCancelKeyboardMacro() error {
|
|
||||||
// TODO: Implement macro cancellation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rpcAbsMouseReport(x, y int, buttons uint8) error {
|
func rpcAbsMouseReport(x, y int, buttons uint8) error {
|
||||||
return gadget.AbsMouseReport(x, y, buttons)
|
return gadget.AbsMouseReport(x, y, buttons)
|
||||||
}
|
}
|
||||||
@@ -282,28 +214,6 @@ func rpcReinitializeUsbGadget() error {
|
|||||||
gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) {
|
gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) {
|
||||||
if currentSession != nil {
|
if currentSession != nil {
|
||||||
writeJSONRPCEvent("keyboardLedState", state, currentSession)
|
writeJSONRPCEvent("keyboardLedState", state, currentSession)
|
||||||
|
|
||||||
// Send HID-RPC LED state message
|
|
||||||
if currentSession.HidChannel != nil {
|
|
||||||
var raw byte
|
|
||||||
if state.NumLock {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskNumLock
|
|
||||||
}
|
|
||||||
if state.CapsLock {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskCapsLock
|
|
||||||
}
|
|
||||||
if state.ScrollLock {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskScrollLock
|
|
||||||
}
|
|
||||||
if state.Compose {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskCompose
|
|
||||||
}
|
|
||||||
if state.Kana {
|
|
||||||
raw |= usbgadget.KeyboardLedMaskKana
|
|
||||||
}
|
|
||||||
ledData := hidrpc.MarshalKeyboardLedState(raw)
|
|
||||||
currentSession.HidChannel.Send(ledData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
gadget.SetOnHidDeviceMissing(func(device string, err error) {
|
gadget.SetOnHidDeviceMissing(func(device string, err error) {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ type Session struct {
|
|||||||
//AudioTrack *webrtc.TrackLocalStaticSample
|
//AudioTrack *webrtc.TrackLocalStaticSample
|
||||||
ControlChannel *webrtc.DataChannel
|
ControlChannel *webrtc.DataChannel
|
||||||
RPCChannel *webrtc.DataChannel
|
RPCChannel *webrtc.DataChannel
|
||||||
HidChannel *webrtc.DataChannel
|
|
||||||
DiskChannel *webrtc.DataChannel
|
DiskChannel *webrtc.DataChannel
|
||||||
shouldUmountVirtualMedia bool
|
shouldUmountVirtualMedia bool
|
||||||
}
|
}
|
||||||
@@ -142,9 +141,6 @@ func newSession(sessionConfig SessionConfig) (*Session, error) {
|
|||||||
handleTerminalChannel(d)
|
handleTerminalChannel(d)
|
||||||
case "serial":
|
case "serial":
|
||||||
handleSerialChannel(d)
|
handleSerialChannel(d)
|
||||||
case "hid":
|
|
||||||
session.HidChannel = d
|
|
||||||
go handleHidChannel(d, session)
|
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(d.Label(), uploadIdPrefix) {
|
if strings.HasPrefix(d.Label(), uploadIdPrefix) {
|
||||||
go handleUploadChannel(d)
|
go handleUploadChannel(d)
|
||||||
|
|||||||
Reference in New Issue
Block a user