mirror of https://github.com/jetkvm/kvm.git
Compare commits
5 Commits
0bb3a6f3c2
...
12210fac96
| Author | SHA1 | Date |
|---|---|---|
|
|
12210fac96 | |
|
|
8d0c2c24ad | |
|
|
f056d8af45 | |
|
|
cc71e3273c | |
|
|
39d885c973 |
2
go.mod
2
go.mod
|
|
@ -12,6 +12,7 @@ require (
|
||||||
github.com/gin-contrib/logger v1.2.6
|
github.com/gin-contrib/logger v1.2.6
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-co-op/gocron/v2 v2.16.5
|
github.com/go-co-op/gocron/v2 v2.16.5
|
||||||
|
github.com/google/flatbuffers v25.2.10+incompatible
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/guregu/null/v6 v6.0.0
|
github.com/guregu/null/v6 v6.0.0
|
||||||
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f
|
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f
|
||||||
|
|
@ -23,6 +24,7 @@ require (
|
||||||
github.com/prometheus/common v0.66.0
|
github.com/prometheus/common v0.66.0
|
||||||
github.com/prometheus/procfs v0.17.0
|
github.com/prometheus/procfs v0.17.0
|
||||||
github.com/psanford/httpreadat v0.1.0
|
github.com/psanford/httpreadat v0.1.0
|
||||||
|
github.com/rs/xid v1.6.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f
|
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
|
|
|
||||||
3
go.sum
3
go.sum
|
|
@ -53,6 +53,8 @@ github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||||
|
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
|
@ -152,6 +154,7 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
|
|
|
||||||
10
hidrpc.go
10
hidrpc.go
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/hidrpc"
|
"github.com/jetkvm/kvm/internal/hidrpc"
|
||||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
||||||
|
|
@ -75,7 +76,9 @@ func onHidMessage(msg hidQueueMessage, session *Session) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scopedLogger.GetLevel() <= zerolog.DebugLevel {
|
||||||
scopedLogger = scopedLogger.With().Str("descr", message.String()).Logger()
|
scopedLogger = scopedLogger.With().Str("descr", message.String()).Logger()
|
||||||
|
}
|
||||||
|
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
|
|
||||||
|
|
@ -122,7 +125,10 @@ func reportHidRPC(params any, session *Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !session.hidRPCAvailable || session.HidChannel == nil {
|
if !session.hidRPCAvailable || session.HidChannel == nil {
|
||||||
logger.Warn().Msg("HID RPC is not available, skipping reportHidRPC")
|
logger.Warn().
|
||||||
|
Bool("hidRPCAvailable", session.hidRPCAvailable).
|
||||||
|
Bool("HidChannel", session.HidChannel != nil).
|
||||||
|
Msg("HID RPC is not available, skipping reportHidRPC")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +169,9 @@ func (s *Session) reportHidRPCKeyboardLedState(state usbgadget.KeyboardState) {
|
||||||
|
|
||||||
func (s *Session) reportHidRPCKeysDownState(state usbgadget.KeysDownState) {
|
func (s *Session) reportHidRPCKeysDownState(state usbgadget.KeysDownState) {
|
||||||
if !s.hidRPCAvailable {
|
if !s.hidRPCAvailable {
|
||||||
|
usbLogger.Debug().Interface("state", state).Msg("reporting keys down state")
|
||||||
writeJSONRPCEvent("keysDownState", state, s)
|
writeJSONRPCEvent("keysDownState", state, s)
|
||||||
}
|
}
|
||||||
|
usbLogger.Debug().Interface("state", state).Msg("reporting keys down state, calling reportHidRPC")
|
||||||
reportHidRPC(state, s)
|
reportHidRPC(state, s)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/xid"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var keyboardConfig = gadgetConfigItem{
|
var keyboardConfig = gadgetConfigItem{
|
||||||
|
|
@ -25,7 +28,7 @@ var keyboardConfig = gadgetConfigItem{
|
||||||
// macOS default: 15 * 15 = 225ms https://discussions.apple.com/thread/1316947?sortBy=rank
|
// macOS default: 15 * 15 = 225ms https://discussions.apple.com/thread/1316947?sortBy=rank
|
||||||
// Linux default: 250ms https://man.archlinux.org/man/kbdrate.8.en
|
// Linux default: 250ms https://man.archlinux.org/man/kbdrate.8.en
|
||||||
// Windows default: 1ms `HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response\AutoRepeatDelay`
|
// Windows default: 1ms `HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response\AutoRepeatDelay`
|
||||||
const autoReleaseKeyboardInterval = time.Millisecond * 225
|
const autoReleaseKeyboardInterval = time.Millisecond * 100
|
||||||
|
|
||||||
// Source: https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt
|
// Source: https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt
|
||||||
var keyboardReportDesc = []byte{
|
var keyboardReportDesc = []byte{
|
||||||
|
|
@ -188,7 +191,7 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u.kbdAutoReleaseTimer = time.AfterFunc(autoReleaseKeyboardInterval, func() {
|
u.kbdAutoReleaseTimer = time.AfterFunc(autoReleaseKeyboardInterval, func() {
|
||||||
u.performAutoRelease(key)
|
u.performAutoRelease()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,10 +219,12 @@ func (u *UsbGadget) DelayAutoRelease() {
|
||||||
u.log.Trace().Msg("auto-release timer reset")
|
u.log.Trace().Msg("auto-release timer reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) performAutoRelease(key byte) {
|
func (u *UsbGadget) performAutoRelease() {
|
||||||
u.log.Trace().Msg("performing autoRelease")
|
u.log.Trace().Msg("performing autoRelease")
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease performed")
|
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease unlocked")
|
||||||
|
|
||||||
|
key := u.kbdAutoReleaseLastKey
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-u.keyboardStateCtx.Done():
|
case <-u.keyboardStateCtx.Done():
|
||||||
|
|
@ -228,7 +233,8 @@ func (u *UsbGadget) performAutoRelease(key byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we just reset the keyboard state to 0 no matter what
|
// we just reset the keyboard state to 0 no matter what
|
||||||
_, err := u.keypressReport(0, false, false)
|
u.log.Trace().Uint8("key", key).Msg("auto-releasing keyboard key")
|
||||||
|
_, err := u.keypressReport(key, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.log.Warn().Uint8("key", key).Msg("failed to auto-release keyboard key")
|
u.log.Warn().Uint8("key", key).Msg("failed to auto-release keyboard key")
|
||||||
}
|
}
|
||||||
|
|
@ -396,18 +402,23 @@ var KeyCodeToMaskMap = map[byte]byte{
|
||||||
RightSuper: ModifierMaskRightSuper,
|
RightSuper: ModifierMaskRightSuper,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (KeysDownState, error) {
|
func (u *UsbGadget) keypressReportNonThreadSafe(key byte, press bool, autoRelease bool) (KeysDownState, error) {
|
||||||
u.keyboardLock.Lock()
|
|
||||||
defer u.keyboardLock.Unlock()
|
|
||||||
|
|
||||||
defer u.resetUserInputTime()
|
defer u.resetUserInputTime()
|
||||||
|
|
||||||
|
l := u.log.With().Uint8("key", key).Bool("press", press).Bool("autoRelease", autoRelease).Logger()
|
||||||
|
if l.GetLevel() <= zerolog.DebugLevel {
|
||||||
|
requestID := xid.New()
|
||||||
|
l = l.With().Str("requestID", requestID.String()).Logger()
|
||||||
|
}
|
||||||
|
|
||||||
// IMPORTANT: This code parallels the logic in the kernel's hid-gadget driver
|
// IMPORTANT: This code parallels the logic in the kernel's hid-gadget driver
|
||||||
// for handling key presses and releases. It ensures that the USB gadget
|
// for handling key presses and releases. It ensures that the USB gadget
|
||||||
// behaves similarly to a real USB HID keyboard. This logic is paralleled
|
// behaves similarly to a real USB HID keyboard. This logic is paralleled
|
||||||
// in the client/browser-side code in useKeyboard.ts so make sure to keep
|
// in the client/browser-side code in useKeyboard.ts so make sure to keep
|
||||||
// them in sync.
|
// them in sync.
|
||||||
var state = u.keysDownState
|
var state = u.GetKeysDownState()
|
||||||
|
l.Trace().Interface("state", state).Msg("got keys down state")
|
||||||
|
|
||||||
modifier := state.Modifier
|
modifier := state.Modifier
|
||||||
keys := append([]byte(nil), state.Keys...)
|
keys := append([]byte(nil), state.Keys...)
|
||||||
|
|
||||||
|
|
@ -447,26 +458,34 @@ func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (Keys
|
||||||
// If we reach here it means we didn't find an empty slot or the key in the buffer
|
// If we reach here it means we didn't find an empty slot or the key in the buffer
|
||||||
if overrun {
|
if overrun {
|
||||||
if press {
|
if press {
|
||||||
u.log.Error().Uint8("key", key).Msg("keyboard buffer overflow, key not added")
|
l.Error().Msg("keyboard buffer overflow, key not added")
|
||||||
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
||||||
for i := range keys {
|
for i := range keys {
|
||||||
keys[i] = hidErrorRollOver
|
keys[i] = hidErrorRollOver
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If we are releasing a key, and we didn't find it in a slot, who cares?
|
// If we are releasing a key, and we didn't find it in a slot, who cares?
|
||||||
u.log.Warn().Uint8("key", key).Msg("key not found in buffer, nothing to release")
|
l.Warn().Msg("key not found in buffer, nothing to release")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.GetLevel() <= zerolog.DebugLevel {
|
||||||
|
l = l.With().Uint8("modifier", modifier).Uints8("keys", keys).Logger()
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Trace().Msg("writing keypress report to hidg0")
|
||||||
|
|
||||||
err := u.keyboardWriteHidFile(modifier, keys)
|
err := u.keyboardWriteHidFile(modifier, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keypress report to hidg0")
|
l.Warn().Msg("Could not write keypress report to hidg0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.Trace().Msg("keypress report written to hidg0")
|
||||||
|
|
||||||
if press {
|
if press {
|
||||||
{
|
{
|
||||||
u.log.Trace().Msg("acquiring kbdAutoReleaseLock to update last key")
|
l.Trace().Msg("acquiring kbdAutoReleaseLock to update last key")
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
u.kbdAutoReleaseLastKey = key
|
u.kbdAutoReleaseLastKey = key
|
||||||
unlockWithLog(&u.kbdAutoReleaseLock, u.log, "last key updated")
|
unlockWithLog(&u.kbdAutoReleaseLock, u.log, "last key updated")
|
||||||
|
|
@ -484,6 +503,31 @@ func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (Keys
|
||||||
return u.UpdateKeysDown(modifier, keys), err
|
return u.UpdateKeysDown(modifier, keys), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type keypressReportResult struct {
|
||||||
|
KeysDownState KeysDownState
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (KeysDownState, error) {
|
||||||
|
u.keyboardLock.Lock()
|
||||||
|
defer u.keyboardLock.Unlock()
|
||||||
|
|
||||||
|
r := make(chan keypressReportResult)
|
||||||
|
go func() {
|
||||||
|
state, err := u.keypressReportNonThreadSafe(key, press, autoRelease)
|
||||||
|
r <- keypressReportResult{KeysDownState: state, Error: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
u.log.Warn().Msg("keypressReport timed out, possibly stuck")
|
||||||
|
return u.keysDownState, fmt.Errorf("keypressReport timed out, possibly stuck")
|
||||||
|
case ret := <-r:
|
||||||
|
u.log.Debug().Msg("keypressReport handled")
|
||||||
|
return ret.KeysDownState, ret.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error) {
|
func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error) {
|
||||||
return u.keypressReport(key, press, true)
|
return u.keypressReport(key, press, true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,12 @@ func (u *UsbGadget) writeWithTimeout(file *os.File, data []byte) (n int, err err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.log.Trace().
|
||||||
|
Str("file", file.Name()).
|
||||||
|
Bytes("data", data).
|
||||||
|
Err(err).
|
||||||
|
Msg("write failed")
|
||||||
|
|
||||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
u.logWithSuppression(
|
u.logWithSuppression(
|
||||||
fmt.Sprintf("writeWithTimeout_%s", file.Name()),
|
fmt.Sprintf("writeWithTimeout_%s", file.Name()),
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ export interface RTCState {
|
||||||
rpcDataChannel: RTCDataChannel | null;
|
rpcDataChannel: RTCDataChannel | null;
|
||||||
|
|
||||||
rpcHidProtocolVersion: number | null;
|
rpcHidProtocolVersion: number | null;
|
||||||
setRpcHidProtocolVersion: (version: number) => void;
|
setRpcHidProtocolVersion: (version: number | null) => void;
|
||||||
|
|
||||||
rpcHidChannel: RTCDataChannel | null;
|
rpcHidChannel: RTCDataChannel | null;
|
||||||
setRpcHidChannel: (channel: RTCDataChannel) => void;
|
setRpcHidChannel: (channel: RTCDataChannel) => void;
|
||||||
|
|
@ -164,7 +164,7 @@ export const useRTCStore = create<RTCState>(set => ({
|
||||||
setRpcDataChannel: (channel: RTCDataChannel) => set({ rpcDataChannel: channel }),
|
setRpcDataChannel: (channel: RTCDataChannel) => set({ rpcDataChannel: channel }),
|
||||||
|
|
||||||
rpcHidProtocolVersion: null,
|
rpcHidProtocolVersion: null,
|
||||||
setRpcHidProtocolVersion: (version: number) => set({ rpcHidProtocolVersion: version }),
|
setRpcHidProtocolVersion: (version: number | null) => set({ rpcHidProtocolVersion: version }),
|
||||||
|
|
||||||
rpcHidChannel: null,
|
rpcHidChannel: null,
|
||||||
setRpcHidChannel: (channel: RTCDataChannel) => set({ rpcHidChannel: channel }),
|
setRpcHidChannel: (channel: RTCDataChannel) => set({ rpcHidChannel: channel }),
|
||||||
|
|
|
||||||
|
|
@ -167,10 +167,24 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
onHidRpcMessage?.(message);
|
onHidRpcMessage?.(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openHandler = () => {
|
||||||
|
console.warn("HID RPC channel opened");
|
||||||
|
sendHandshake();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
console.warn("HID RPC channel closed");
|
||||||
|
setRpcHidProtocolVersion(null);
|
||||||
|
};
|
||||||
|
|
||||||
rpcHidChannel.addEventListener("message", messageHandler);
|
rpcHidChannel.addEventListener("message", messageHandler);
|
||||||
|
rpcHidChannel.addEventListener("close", closeHandler);
|
||||||
|
rpcHidChannel.addEventListener("open", openHandler);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
rpcHidChannel.removeEventListener("message", messageHandler);
|
rpcHidChannel.removeEventListener("message", messageHandler);
|
||||||
|
rpcHidChannel.removeEventListener("close", closeHandler);
|
||||||
|
rpcHidChannel.removeEventListener("open", openHandler);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue