mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
Pre-open HID files during initialization to minimize I/O overhead during operation. Remove mouse event throttling mechanism to improve input responsiveness. Keep HID files open on write errors to avoid repeated file operations.
171 lines
4.5 KiB
Go
171 lines
4.5 KiB
Go
// Package usbgadget provides a high-level interface to manage USB gadgets
|
|
// THIS PACKAGE IS FOR INTERNAL USE ONLY AND ITS API MAY CHANGE WITHOUT NOTICE
|
|
package usbgadget
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/jetkvm/kvm/internal/logging"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// Devices is a struct that represents the USB devices that can be enabled on a USB gadget.
|
|
type Devices struct {
|
|
AbsoluteMouse bool `json:"absolute_mouse"`
|
|
RelativeMouse bool `json:"relative_mouse"`
|
|
Keyboard bool `json:"keyboard"`
|
|
MassStorage bool `json:"mass_storage"`
|
|
Audio bool `json:"audio"`
|
|
}
|
|
|
|
// Config is a struct that represents the customizations for a USB gadget.
|
|
// TODO: rename to something else that won't confuse with the USB gadget configuration
|
|
type Config struct {
|
|
VendorId string `json:"vendor_id"`
|
|
ProductId string `json:"product_id"`
|
|
SerialNumber string `json:"serial_number"`
|
|
Manufacturer string `json:"manufacturer"`
|
|
Product string `json:"product"`
|
|
|
|
strictMode bool // when it's enabled, all warnings will be converted to errors
|
|
isEmpty bool
|
|
}
|
|
|
|
var defaultUsbGadgetDevices = Devices{
|
|
AbsoluteMouse: true,
|
|
RelativeMouse: true,
|
|
Keyboard: true,
|
|
MassStorage: true,
|
|
}
|
|
|
|
// UsbGadget is a struct that represents a USB gadget.
|
|
type UsbGadget struct {
|
|
name string
|
|
udc string
|
|
kvmGadgetPath string
|
|
configC1Path string
|
|
|
|
configMap map[string]gadgetConfigItem
|
|
customConfig Config
|
|
|
|
configLock sync.Mutex
|
|
|
|
keyboardHidFile *os.File
|
|
keyboardLock sync.Mutex
|
|
absMouseHidFile *os.File
|
|
absMouseLock sync.Mutex
|
|
relMouseHidFile *os.File
|
|
relMouseLock sync.Mutex
|
|
|
|
keyboardState KeyboardState
|
|
keyboardStateLock sync.Mutex
|
|
keyboardStateCtx context.Context
|
|
keyboardStateCancel context.CancelFunc
|
|
|
|
enabledDevices Devices
|
|
|
|
strictMode bool // only intended for testing for now
|
|
|
|
absMouseAccumulatedWheelY float64
|
|
|
|
lastUserInput time.Time
|
|
|
|
tx *UsbGadgetTransaction
|
|
txLock sync.Mutex
|
|
|
|
onKeyboardStateChange *func(state KeyboardState)
|
|
|
|
log *zerolog.Logger
|
|
|
|
logSuppressionCounter map[string]int
|
|
logSuppressionLock sync.Mutex
|
|
}
|
|
|
|
const configFSPath = "/sys/kernel/config"
|
|
const gadgetPath = "/sys/kernel/config/usb_gadget"
|
|
|
|
var defaultLogger = logging.GetSubsystemLogger("usbgadget")
|
|
|
|
// NewUsbGadget creates a new UsbGadget.
|
|
func NewUsbGadget(name string, enabledDevices *Devices, config *Config, logger *zerolog.Logger) *UsbGadget {
|
|
return newUsbGadget(name, defaultGadgetConfig, enabledDevices, config, logger)
|
|
}
|
|
|
|
// PreOpenHidFiles opens all HID files to reduce input latency
|
|
func (u *UsbGadget) PreOpenHidFiles() {
|
|
if u.enabledDevices.Keyboard {
|
|
if err := u.openKeyboardHidFile(); err != nil {
|
|
u.log.Debug().Err(err).Msg("failed to pre-open keyboard HID file")
|
|
}
|
|
}
|
|
if u.enabledDevices.AbsoluteMouse {
|
|
if u.absMouseHidFile == nil {
|
|
var err error
|
|
u.absMouseHidFile, err = os.OpenFile("/dev/hidg1", os.O_RDWR, 0666)
|
|
if err != nil {
|
|
u.log.Debug().Err(err).Msg("failed to pre-open absolute mouse HID file")
|
|
}
|
|
}
|
|
}
|
|
if u.enabledDevices.RelativeMouse {
|
|
if u.relMouseHidFile == nil {
|
|
var err error
|
|
u.relMouseHidFile, err = os.OpenFile("/dev/hidg2", os.O_RDWR, 0666)
|
|
if err != nil {
|
|
u.log.Debug().Err(err).Msg("failed to pre-open relative mouse HID file")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func newUsbGadget(name string, configMap map[string]gadgetConfigItem, enabledDevices *Devices, config *Config, logger *zerolog.Logger) *UsbGadget {
|
|
if logger == nil {
|
|
logger = defaultLogger
|
|
}
|
|
|
|
if enabledDevices == nil {
|
|
enabledDevices = &defaultUsbGadgetDevices
|
|
}
|
|
|
|
if config == nil {
|
|
config = &Config{isEmpty: true}
|
|
}
|
|
|
|
keyboardCtx, keyboardCancel := context.WithCancel(context.Background())
|
|
|
|
g := &UsbGadget{
|
|
name: name,
|
|
kvmGadgetPath: path.Join(gadgetPath, name),
|
|
configC1Path: path.Join(gadgetPath, name, "configs/c.1"),
|
|
configMap: configMap,
|
|
customConfig: *config,
|
|
configLock: sync.Mutex{},
|
|
keyboardLock: sync.Mutex{},
|
|
absMouseLock: sync.Mutex{},
|
|
relMouseLock: sync.Mutex{},
|
|
txLock: sync.Mutex{},
|
|
keyboardStateCtx: keyboardCtx,
|
|
keyboardStateCancel: keyboardCancel,
|
|
keyboardState: KeyboardState{},
|
|
enabledDevices: *enabledDevices,
|
|
lastUserInput: time.Now(),
|
|
log: logger,
|
|
|
|
strictMode: config.strictMode,
|
|
|
|
logSuppressionCounter: make(map[string]int),
|
|
|
|
absMouseAccumulatedWheelY: 0,
|
|
}
|
|
if err := g.Init(); err != nil {
|
|
logger.Error().Err(err).Msg("failed to init USB gadget")
|
|
return nil
|
|
}
|
|
|
|
return g
|
|
}
|