Compare commits

..

No commits in common. "12210fac96ef7b9ddc4d0bdf4c7e476e7455505a" and "0bb3a6f3c29442d90f360ffd7df09c7f60c1b6d8" have entirely different histories.

7 changed files with 18 additions and 95 deletions

2
go.mod
View File

@ -12,7 +12,6 @@ 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
@ -24,7 +23,6 @@ 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
View File

@ -53,8 +53,6 @@ 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=
@ -154,7 +152,6 @@ 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=

View File

@ -6,7 +6,6 @@ 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) {
@ -76,9 +75,7 @@ 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()
@ -125,10 +122,7 @@ func reportHidRPC(params any, session *Session) {
} }
if !session.hidRPCAvailable || session.HidChannel == nil { if !session.hidRPCAvailable || session.HidChannel == nil {
logger.Warn(). logger.Warn().Msg("HID RPC is not available, skipping reportHidRPC")
Bool("hidRPCAvailable", session.hidRPCAvailable).
Bool("HidChannel", session.HidChannel != nil).
Msg("HID RPC is not available, skipping reportHidRPC")
return return
} }
@ -169,9 +163,7 @@ 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)
} }

View File

@ -6,9 +6,6 @@ import (
"fmt" "fmt"
"os" "os"
"time" "time"
"github.com/rs/xid"
"github.com/rs/zerolog"
) )
var keyboardConfig = gadgetConfigItem{ var keyboardConfig = gadgetConfigItem{
@ -28,7 +25,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 * 100 const autoReleaseKeyboardInterval = time.Millisecond * 225
// 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{
@ -191,7 +188,7 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) {
} }
u.kbdAutoReleaseTimer = time.AfterFunc(autoReleaseKeyboardInterval, func() { u.kbdAutoReleaseTimer = time.AfterFunc(autoReleaseKeyboardInterval, func() {
u.performAutoRelease() u.performAutoRelease(key)
}) })
} }
@ -219,12 +216,10 @@ func (u *UsbGadget) DelayAutoRelease() {
u.log.Trace().Msg("auto-release timer reset") u.log.Trace().Msg("auto-release timer reset")
} }
func (u *UsbGadget) performAutoRelease() { func (u *UsbGadget) performAutoRelease(key byte) {
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 unlocked") defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease performed")
key := u.kbdAutoReleaseLastKey
select { select {
case <-u.keyboardStateCtx.Done(): case <-u.keyboardStateCtx.Done():
@ -233,8 +228,7 @@ func (u *UsbGadget) performAutoRelease() {
} }
// we just reset the keyboard state to 0 no matter what // we just reset the keyboard state to 0 no matter what
u.log.Trace().Uint8("key", key).Msg("auto-releasing keyboard key") _, err := u.keypressReport(0, false, false)
_, 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")
} }
@ -402,23 +396,18 @@ var KeyCodeToMaskMap = map[byte]byte{
RightSuper: ModifierMaskRightSuper, RightSuper: ModifierMaskRightSuper,
} }
func (u *UsbGadget) keypressReportNonThreadSafe(key byte, press bool, autoRelease bool) (KeysDownState, error) { func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (KeysDownState, error) {
defer u.resetUserInputTime() u.keyboardLock.Lock()
defer u.keyboardLock.Unlock()
l := u.log.With().Uint8("key", key).Bool("press", press).Bool("autoRelease", autoRelease).Logger() defer u.resetUserInputTime()
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.GetKeysDownState() var state = u.keysDownState
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...)
@ -458,34 +447,26 @@ func (u *UsbGadget) keypressReportNonThreadSafe(key byte, press bool, autoReleas
// 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 {
l.Error().Msg("keyboard buffer overflow, key not added") u.log.Error().Uint8("key", key).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?
l.Warn().Msg("key not found in buffer, nothing to release") u.log.Warn().Uint8("key", key).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 {
l.Warn().Msg("Could not write keypress report to hidg0") u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keypress report to hidg0")
} }
l.Trace().Msg("keypress report written to hidg0")
if press { if press {
{ {
l.Trace().Msg("acquiring kbdAutoReleaseLock to update last key") u.log.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")
@ -503,31 +484,6 @@ func (u *UsbGadget) keypressReportNonThreadSafe(key byte, press bool, autoReleas
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)
} }

View File

@ -121,12 +121,6 @@ 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()),

View File

@ -106,7 +106,7 @@ export interface RTCState {
rpcDataChannel: RTCDataChannel | null; rpcDataChannel: RTCDataChannel | null;
rpcHidProtocolVersion: number | null; rpcHidProtocolVersion: number | null;
setRpcHidProtocolVersion: (version: number | null) => void; setRpcHidProtocolVersion: (version: number) => 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 | null) => set({ rpcHidProtocolVersion: version }), setRpcHidProtocolVersion: (version: number) => set({ rpcHidProtocolVersion: version }),
rpcHidChannel: null, rpcHidChannel: null,
setRpcHidChannel: (channel: RTCDataChannel) => set({ rpcHidChannel: channel }), setRpcHidChannel: (channel: RTCDataChannel) => set({ rpcHidChannel: channel }),

View File

@ -167,24 +167,10 @@ 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);
}; };
}, },
[ [