mirror of https://github.com/jetkvm/kvm.git
226 lines
6.5 KiB
Go
226 lines
6.5 KiB
Go
package usbgadget
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"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",
|
|
},
|
|
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
|
|
// 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
|
|
ValidKeyboardLedMasks = KeyboardLedMaskNumLock | KeyboardLedMaskCapsLock | KeyboardLedMaskScrollLock | KeyboardLedMaskCompose | KeyboardLedMaskKana
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
func (u *UsbGadget) updateKeyboardState(b byte) {
|
|
u.keyboardStateLock.Lock()
|
|
defer u.keyboardStateLock.Unlock()
|
|
|
|
if b&^ValidKeyboardLedMasks != 0 {
|
|
u.log.Trace().Uint8("b", b).Msg("contains invalid bits, ignoring")
|
|
return
|
|
}
|
|
|
|
newState := getKeyboardState(b)
|
|
if reflect.DeepEqual(u.keyboardState, newState) {
|
|
return
|
|
}
|
|
u.log.Info().Interface("old", u.keyboardState).Interface("new", newState).Msg("keyboardState updated")
|
|
u.keyboardState = newState
|
|
|
|
if u.onKeyboardStateChange != nil {
|
|
(*u.onKeyboardStateChange)(newState)
|
|
}
|
|
}
|
|
|
|
func (u *UsbGadget) SetOnKeyboardStateChange(f func(state KeyboardState)) {
|
|
u.onKeyboardStateChange = &f
|
|
}
|
|
|
|
func (u *UsbGadget) GetKeyboardState() KeyboardState {
|
|
u.keyboardStateLock.Lock()
|
|
defer u.keyboardStateLock.Unlock()
|
|
|
|
return u.keyboardState
|
|
}
|
|
|
|
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")
|
|
if u.keyboardHidFile == nil {
|
|
l.Error().Msg("keyboardHidFile is nil")
|
|
time.Sleep(time.Second)
|
|
continue
|
|
}
|
|
n, err := u.keyboardHidFile.Read(buf)
|
|
if err != nil {
|
|
l.Error().Err(err).Msg("failed to read")
|
|
continue
|
|
}
|
|
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(data []byte) error {
|
|
if err := u.openKeyboardHidFile(); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := u.keyboardHidFile.Write(data)
|
|
if err != nil {
|
|
u.log.Error().Err(err).Msg("failed to write to hidg0")
|
|
u.keyboardHidFile.Close()
|
|
u.keyboardHidFile = nil
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *UsbGadget) KeyboardReport(modifier uint8, keys []uint8) error {
|
|
u.keyboardLock.Lock()
|
|
defer u.keyboardLock.Unlock()
|
|
|
|
if len(keys) > 6 {
|
|
keys = keys[:6]
|
|
}
|
|
if len(keys) < 6 {
|
|
keys = append(keys, make([]uint8, 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
|
|
}
|