added autostart for windows and autostart for devices

This commit is contained in:
2026-02-19 09:34:38 +01:00
parent e2b853840d
commit 486cf6d239
10 changed files with 290 additions and 19 deletions
+110 -7
View File
@@ -4,8 +4,10 @@ import (
"fmt"
"log"
"net"
"strings"
"sync"
"github.com/duffy/usb-server/internal/config"
"github.com/duffy/usb-server/internal/protocol"
"github.com/duffy/usb-server/internal/usbip"
"github.com/google/uuid"
@@ -28,6 +30,8 @@ type AttachedDevice struct {
// UseManager handles receiving/using remote USB devices
type UseManager struct {
client *Client
cfg *config.Config
cfgPath string
mu sync.RWMutex
available map[string][]RemoteDevice // clientID -> devices
attached map[string]*AttachedDevice // busID@clientID -> attached info
@@ -44,9 +48,11 @@ type useTunnel struct {
}
// NewUseManager creates a use manager
func NewUseManager(client *Client) *UseManager {
func NewUseManager(client *Client, cfg *config.Config, cfgPath string) *UseManager {
um := &UseManager{
client: client,
cfg: cfg,
cfgPath: cfgPath,
available: make(map[string][]RemoteDevice),
attached: make(map[string]*AttachedDevice),
tunnels: make(map[string]*useTunnel),
@@ -210,15 +216,20 @@ func (um *UseManager) setupVHCI(clientID, busID string, granted *protocol.Device
}
key := busID + "@" + clientID
remDev := RemoteDevice{
USBDevice: protocol.USBDevice{BusID: busID},
ClientID: clientID,
}
if devInfo != nil {
remDev = *devInfo
}
um.mu.Lock()
um.tunnels[granted.TunnelID] = tunnel
um.attached[key] = &AttachedDevice{
RemoteDevice: RemoteDevice{
USBDevice: protocol.USBDevice{BusID: busID},
ClientID: clientID,
},
TunnelID: granted.TunnelID,
VHCIPort: vhciPort,
RemoteDevice: remDev,
TunnelID: granted.TunnelID,
VHCIPort: vhciPort,
}
um.mu.Unlock()
@@ -273,10 +284,102 @@ func (um *UseManager) handleDeviceList(msg *protocol.DeviceList) {
})
}
um.available[msg.ClientID] = remoteDevs
// Collect devices to auto-connect (while holding the lock to check attached map)
var toAutoConnect []RemoteDevice
for _, dev := range remoteDevs {
if dev.Status != protocol.StatusAvailable {
continue
}
key := dev.BusID + "@" + msg.ClientID
if _, attached := um.attached[key]; attached {
continue
}
if um.matchesAutoConnect(dev) {
toAutoConnect = append(toAutoConnect, dev)
}
}
um.mu.Unlock()
log.Printf("[use] received device list from %s (%s): %d devices",
msg.ClientName, msg.ClientID[:8], len(msg.Devices))
// Auto-connect matching devices (outside lock, each in its own goroutine)
for _, dev := range toAutoConnect {
log.Printf("[use] auto-connecting %s (%s:%s) from %s",
dev.Name, dev.VendorID, dev.ProductID, msg.ClientName)
go um.AttachDevice(msg.ClientID, dev.BusID)
}
}
// matchesAutoConnect checks if a device matches any auto-connect rule.
// Must be called with um.mu held (at least RLock).
func (um *UseManager) matchesAutoConnect(dev RemoteDevice) bool {
for _, rule := range um.cfg.AutoConnect {
if rule.VendorID != "" && !strings.EqualFold(rule.VendorID, dev.VendorID) {
continue
}
if rule.ProductID != "" && !strings.EqualFold(rule.ProductID, dev.ProductID) {
continue
}
if rule.BusID != "" && rule.BusID != dev.BusID {
continue
}
if rule.ClientName != "" && rule.ClientName != dev.ClientName {
continue
}
return true // all specified fields match
}
return false
}
// SetAutoConnect adds or removes an auto-connect rule for a VendorID:ProductID pair.
func (um *UseManager) SetAutoConnect(vendorID, productID string, enabled bool) error {
um.mu.Lock()
defer um.mu.Unlock()
if enabled {
// Check if rule already exists
for _, rule := range um.cfg.AutoConnect {
if strings.EqualFold(rule.VendorID, vendorID) && strings.EqualFold(rule.ProductID, productID) {
return nil // already exists
}
}
um.cfg.AutoConnect = append(um.cfg.AutoConnect, config.AutoConnectRule{
VendorID: vendorID,
ProductID: productID,
})
} else {
// Remove matching rule
filtered := um.cfg.AutoConnect[:0]
for _, rule := range um.cfg.AutoConnect {
if strings.EqualFold(rule.VendorID, vendorID) && strings.EqualFold(rule.ProductID, productID) {
continue
}
filtered = append(filtered, rule)
}
um.cfg.AutoConnect = filtered
}
if err := um.cfg.Save(um.cfgPath); err != nil {
return fmt.Errorf("saving config: %w", err)
}
log.Printf("[use] auto-connect %s:%s = %v", vendorID, productID, enabled)
return nil
}
// IsAutoConnect checks if there is an auto-connect rule for this VendorID:ProductID.
func (um *UseManager) IsAutoConnect(vendorID, productID string) bool {
um.mu.RLock()
defer um.mu.RUnlock()
for _, rule := range um.cfg.AutoConnect {
if strings.EqualFold(rule.VendorID, vendorID) && strings.EqualFold(rule.ProductID, productID) {
return true
}
}
return false
}
func (um *UseManager) handleDeviceGranted(msg *protocol.DeviceGranted) {