mirror of https://github.com/jetkvm/kvm.git
377 lines
12 KiB
Go
377 lines
12 KiB
Go
package usbgadget
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
var keyboardConfig = gadgetConfigItem{
|
|
order: 1000,
|
|
device: "hid.usb0",
|
|
path: []string{"functions", "hid.usb0"},
|
|
configPath: []string{"hid.usb0"},
|
|
attrs: gadgetAttributes{
|
|
"protocol": "1",
|
|
"subclass": "1",
|
|
"report_length": "8",
|
|
"no_out_endpoint": "0",
|
|
},
|
|
reportDesc: keyboardReportDesc,
|
|
}
|
|
|
|
// Source: https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt
|
|
var keyboardReportDesc = []byte{
|
|
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
|
0x09, 0x06, /* USAGE (Keyboard) */
|
|
0xa1, 0x01, /* COLLECTION (Application) */
|
|
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
|
0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */
|
|
0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */
|
|
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
|
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
|
0x75, 0x01, /* REPORT_SIZE (1) */
|
|
0x95, 0x08, /* REPORT_COUNT (8) */
|
|
0x81, 0x02, /* INPUT (Data,Var,Abs) */
|
|
0x95, 0x01, /* REPORT_COUNT (1) */
|
|
0x75, 0x08, /* REPORT_SIZE (8) */
|
|
0x81, 0x03, /* INPUT (Cnst,Var,Abs) */
|
|
0x95, 0x05, /* REPORT_COUNT (5) */
|
|
0x75, 0x01, /* REPORT_SIZE (1) */
|
|
|
|
0x05, 0x08, /* USAGE_PAGE (LEDs) */
|
|
0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */
|
|
0x29, 0x05, /* USAGE_MAXIMUM (Kana) */
|
|
0x91, 0x02, /* OUTPUT (Data,Var,Abs) */
|
|
0x95, 0x01, /* REPORT_COUNT (1) */
|
|
0x75, 0x03, /* REPORT_SIZE (3) */
|
|
0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */
|
|
0x95, 0x06, /* REPORT_COUNT (6) */
|
|
0x75, 0x08, /* REPORT_SIZE (8) */
|
|
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
|
0x25, 0x65, /* LOGICAL_MAXIMUM (101) */
|
|
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
|
0x19, 0x00, /* USAGE_MINIMUM (Reserved) */
|
|
0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */
|
|
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
|
|
0xc0, /* END_COLLECTION */
|
|
}
|
|
|
|
const (
|
|
hidReadBufferSize = 8
|
|
hidKeyBufferSize = 6
|
|
hidErrorRollOver = 0x01
|
|
// https://www.usb.org/sites/default/files/documents/hid1_11.pdf
|
|
// https://www.usb.org/sites/default/files/hut1_2.pdf
|
|
KeyboardLedMaskNumLock = 1 << 0
|
|
KeyboardLedMaskCapsLock = 1 << 1
|
|
KeyboardLedMaskScrollLock = 1 << 2
|
|
KeyboardLedMaskCompose = 1 << 3
|
|
KeyboardLedMaskKana = 1 << 4
|
|
// power on/off LED is 5
|
|
KeyboardLedMaskShift = 1 << 6
|
|
ValidKeyboardLedMasks = KeyboardLedMaskNumLock | KeyboardLedMaskCapsLock | KeyboardLedMaskScrollLock | KeyboardLedMaskCompose | KeyboardLedMaskKana | KeyboardLedMaskShift
|
|
)
|
|
|
|
// Synchronization between LED states and CAPS LOCK, NUM LOCK, SCROLL LOCK,
|
|
// COMPOSE, and KANA events is maintained by the host and NOT the keyboard. If
|
|
// using the keyboard descriptor in Appendix B, LED states are set by sending a
|
|
// 5-bit absolute report to the keyboard via a Set_Report(Output) request.
|
|
type KeyboardState struct {
|
|
NumLock bool `json:"num_lock"`
|
|
CapsLock bool `json:"caps_lock"`
|
|
ScrollLock bool `json:"scroll_lock"`
|
|
Compose bool `json:"compose"`
|
|
Kana bool `json:"kana"`
|
|
Shift bool `json:"shift"` // This is not part of the main USB HID spec
|
|
}
|
|
|
|
func getKeyboardState(b byte) KeyboardState {
|
|
// should we check if it's the correct usage page?
|
|
return KeyboardState{
|
|
NumLock: b&KeyboardLedMaskNumLock != 0,
|
|
CapsLock: b&KeyboardLedMaskCapsLock != 0,
|
|
ScrollLock: b&KeyboardLedMaskScrollLock != 0,
|
|
Compose: b&KeyboardLedMaskCompose != 0,
|
|
Kana: b&KeyboardLedMaskKana != 0,
|
|
Shift: b&KeyboardLedMaskShift != 0,
|
|
}
|
|
}
|
|
|
|
func (u *UsbGadget) updateKeyboardState(state byte) {
|
|
u.keyboardStateLock.Lock()
|
|
defer u.keyboardStateLock.Unlock()
|
|
|
|
if state&^ValidKeyboardLedMasks != 0 {
|
|
u.log.Error().Uint8("state", state).Msg("ignoring invalid bits")
|
|
return
|
|
}
|
|
|
|
if u.keyboardState == state {
|
|
return
|
|
}
|
|
u.log.Trace().Interface("old", u.keyboardState).Interface("new", state).Msg("keyboardState updated")
|
|
u.keyboardState = state
|
|
|
|
if u.onKeyboardStateChange != nil {
|
|
(*u.onKeyboardStateChange)(getKeyboardState(state))
|
|
}
|
|
}
|
|
|
|
func (u *UsbGadget) SetOnKeyboardStateChange(f func(state KeyboardState)) {
|
|
u.onKeyboardStateChange = &f
|
|
}
|
|
|
|
func (u *UsbGadget) GetKeyboardState() KeyboardState {
|
|
u.keyboardStateLock.Lock()
|
|
defer u.keyboardStateLock.Unlock()
|
|
|
|
return getKeyboardState(u.keyboardState)
|
|
}
|
|
|
|
const (
|
|
// https://www.usb.org/sites/default/files/documents/hid1_11.pdf Appendix C
|
|
ModifierMaskLeftControl = 0x01
|
|
ModifierMaskRightControl = 0x10
|
|
ModifierMaskLeftShift = 0x02
|
|
ModifierMaskRightShift = 0x20
|
|
ModifierMaskLeftAlt = 0x04
|
|
ModifierMaskRightAlt = 0x40
|
|
ModifierMaskLeftSuper = 0x08
|
|
ModifierMaskRightSuper = 0x80
|
|
|
|
EitherShiftMask = ModifierMaskLeftShift | ModifierMaskRightShift
|
|
EitherControlMask = ModifierMaskLeftControl | ModifierMaskRightControl
|
|
EitherAltMask = ModifierMaskLeftAlt | ModifierMaskRightAlt
|
|
EitherSuperMask = ModifierMaskLeftSuper | ModifierMaskRightSuper
|
|
)
|
|
|
|
func (u *UsbGadget) GetKeysDownState() KeysDownState {
|
|
u.keyboardStateLock.Lock()
|
|
defer u.keyboardStateLock.Unlock()
|
|
|
|
return u.keysDownState
|
|
}
|
|
|
|
func (u *UsbGadget) updateKeyDownState(state KeysDownState) {
|
|
u.keyboardStateLock.Lock()
|
|
defer u.keyboardStateLock.Unlock()
|
|
|
|
if u.keysDownState.Modifier == state.Modifier &&
|
|
bytes.Equal(u.keysDownState.Keys, state.Keys) {
|
|
return // No change in key down state
|
|
}
|
|
|
|
u.log.Trace().Interface("old", u.keysDownState).Interface("new", state).Msg("keysDownState updated")
|
|
u.keysDownState = state
|
|
|
|
if u.onKeysDownChange != nil {
|
|
(*u.onKeysDownChange)(state)
|
|
}
|
|
}
|
|
|
|
func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
|
|
u.onKeysDownChange = &f
|
|
}
|
|
|
|
func (u *UsbGadget) listenKeyboardEvents() {
|
|
var path string
|
|
if u.keyboardHidFile != nil {
|
|
path = u.keyboardHidFile.Name()
|
|
}
|
|
l := u.log.With().Str("listener", "keyboardEvents").Str("path", path).Logger()
|
|
l.Trace().Msg("starting")
|
|
|
|
go func() {
|
|
buf := make([]byte, hidReadBufferSize)
|
|
for {
|
|
select {
|
|
case <-u.keyboardStateCtx.Done():
|
|
l.Info().Msg("context done")
|
|
return
|
|
default:
|
|
l.Trace().Msg("reading from keyboard for LED state changes")
|
|
if u.keyboardHidFile == nil {
|
|
u.logWithSuppression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
|
|
// show the error every 100 times to avoid spamming the logs
|
|
time.Sleep(time.Second)
|
|
continue
|
|
}
|
|
// reset the counter
|
|
u.resetLogSuppressionCounter("keyboardHidFileNil")
|
|
|
|
n, err := u.keyboardHidFile.Read(buf)
|
|
if err != nil {
|
|
u.logWithSuppression("keyboardHidFileRead", 100, &l, err, "failed to read")
|
|
continue
|
|
}
|
|
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")
|
|
continue
|
|
}
|
|
u.updateKeyboardState(buf[0])
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (u *UsbGadget) openKeyboardHidFile() error {
|
|
if u.keyboardHidFile != nil {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
u.keyboardHidFile, err = os.OpenFile("/dev/hidg0", os.O_RDWR, 0666)
|
|
if err != nil {
|
|
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()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *UsbGadget) OpenKeyboardHidFile() error {
|
|
return u.openKeyboardHidFile()
|
|
}
|
|
|
|
func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
|
|
if err := u.openKeyboardHidFile(); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := u.keyboardHidFile.Write(append([]byte{modifier, 0x00}, keys[:]...))
|
|
if err != nil {
|
|
u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
|
u.keyboardHidFile.Close()
|
|
u.keyboardHidFile = nil
|
|
return err
|
|
}
|
|
u.resetLogSuppressionCounter("keyboardWriteHidFile")
|
|
return nil
|
|
}
|
|
|
|
func (u *UsbGadget) KeyboardReport(modifier byte, keys []byte) error {
|
|
u.keyboardLock.Lock()
|
|
defer u.keyboardLock.Unlock()
|
|
defer u.resetUserInputTime()
|
|
|
|
if len(keys) > hidKeyBufferSize {
|
|
keys = keys[:hidKeyBufferSize]
|
|
}
|
|
if len(keys) < hidKeyBufferSize {
|
|
keys = append(keys, make([]byte, hidKeyBufferSize-len(keys))...)
|
|
}
|
|
|
|
return u.keyboardWriteHidFile(modifier, keys)
|
|
}
|
|
|
|
const (
|
|
// https://www.usb.org/sites/default/files/documents/hut1_2.pdf
|
|
// Dynamic Flags (DV)
|
|
LeftControl = 0xE0
|
|
LeftShift = 0xE1
|
|
LeftAlt = 0xE2
|
|
LeftSuper = 0xE3 // Left GUI (e.g. Windows key, Apple Command key)
|
|
RightControl = 0xE4
|
|
RightShift = 0xE5
|
|
RightAlt = 0xE6
|
|
RightSuper = 0xE7 // Right GUI (e.g. Windows key, Apple Command key)
|
|
)
|
|
|
|
// KeyCodeMask maps a key code to its corresponding bit mask
|
|
type KeyCodeMask struct {
|
|
KeyCode byte
|
|
Mask byte
|
|
}
|
|
|
|
// KeyCodeToMaskMap is a slice of KeyCodeMask for quick lookup
|
|
var KeyCodeToMaskMap = map[uint8]uint8{
|
|
LeftControl: ModifierMaskLeftControl,
|
|
LeftShift: ModifierMaskLeftShift,
|
|
LeftAlt: ModifierMaskLeftAlt,
|
|
LeftSuper: ModifierMaskLeftSuper,
|
|
RightControl: ModifierMaskRightControl,
|
|
RightShift: ModifierMaskRightShift,
|
|
RightAlt: ModifierMaskRightAlt,
|
|
RightSuper: ModifierMaskRightSuper,
|
|
}
|
|
|
|
func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error) {
|
|
u.keyboardLock.Lock()
|
|
defer u.keyboardLock.Unlock()
|
|
defer u.resetUserInputTime()
|
|
|
|
var state = u.keysDownState
|
|
modifier := state.Modifier
|
|
keys := state.Keys[:]
|
|
|
|
if mask, exists := KeyCodeToMaskMap[key]; exists {
|
|
// If the key is a modifier key, we update the keyboardModifier state
|
|
// by setting or clearing the corresponding bit in the modifier byte.
|
|
// This allows us to track the state of modifier keys like Shift, Control, Alt, and Super.
|
|
if press {
|
|
modifier |= mask
|
|
} else {
|
|
modifier &^= mask
|
|
}
|
|
} else {
|
|
// handle other keys that are not modifier keys by placing or removing them
|
|
// from the key buffer since the buffer tracks currently pressed keys
|
|
overrun := true
|
|
for i := range hidKeyBufferSize {
|
|
// If we find the key in the buffer the buffer, we either remove it (if press is false)
|
|
// or do nothing (if down is true) because the buffer tracks currently pressed keys
|
|
// and if we find a zero byte, we can place the key there (if press is true)
|
|
if keys[i] == key || keys[i] == 0 {
|
|
if press {
|
|
keys[i] = key // overwrites the zero byte or the same key if already pressed
|
|
} else {
|
|
// we are releasing the key, remove it from the buffer
|
|
if keys[i] != 0 {
|
|
copy(keys[i:], keys[i+1:])
|
|
keys[hidKeyBufferSize-1] = 0 // Clear the last byte
|
|
}
|
|
}
|
|
overrun = false // We found a slot for the key
|
|
break
|
|
}
|
|
}
|
|
|
|
// If we reach here it means we didn't find an empty slot or the key in the buffer
|
|
if overrun {
|
|
if press {
|
|
u.log.Error().Uint8("key", key).Msg("keyboard buffer overflow, key not added")
|
|
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
|
for i := range keys {
|
|
keys[i] = hidErrorRollOver
|
|
}
|
|
} else {
|
|
// 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")
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := u.keyboardWriteHidFile(modifier, keys); err != nil {
|
|
u.log.Warn().Uint8("modifier", modifier).Bytes("keys", keys).Msg("Could not write keypress report to hidg0")
|
|
}
|
|
|
|
state.Modifier = modifier
|
|
state.Keys = keys
|
|
|
|
u.updateKeyDownState(state)
|
|
|
|
return state, nil
|
|
}
|