2024-12-15 13:45:46 +01:00
|
|
|
package hardware
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"syscall"
|
|
|
|
|
"time"
|
|
|
|
|
"unsafe"
|
|
|
|
|
|
2024-12-29 13:09:46 +01:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
|
2024-12-15 13:45:46 +01:00
|
|
|
"github.com/lxn/win"
|
|
|
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// PeripheralEvent is trigger by the finders when the scan is complete
|
|
|
|
|
type PeripheralEvent string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// PeripheralArrival is triggerd when a peripheral has been connected to the system
|
|
|
|
|
PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL"
|
|
|
|
|
// PeripheralRemoval is triggered when a peripheral has been disconnected from the system
|
|
|
|
|
PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL"
|
|
|
|
|
debounceDuration = 500 * time.Millisecond
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
debounceTimer *time.Timer
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// HardwareManager is the class who manages the hardware
|
|
|
|
|
type HardwareManager struct {
|
2024-12-23 17:22:37 +01:00
|
|
|
drivers map[string]PeripheralDriver // The map of peripherals finders
|
2024-12-20 17:18:57 +01:00
|
|
|
peripherals []Peripheral // The current list of peripherals
|
|
|
|
|
deviceChangedEvent chan struct{} // The event when the devices list changed
|
2024-12-15 13:45:46 +01:00
|
|
|
ctx context.Context
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewHardwareManager creates a new HardwareManager
|
|
|
|
|
func NewHardwareManager() *HardwareManager {
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("package", "hardware").Msg("Hardware instance created")
|
2024-12-15 13:45:46 +01:00
|
|
|
return &HardwareManager{
|
2024-12-23 17:22:37 +01:00
|
|
|
drivers: make(map[string]PeripheralDriver),
|
2024-12-15 13:45:46 +01:00
|
|
|
peripherals: make([]Peripheral, 0),
|
|
|
|
|
deviceChangedEvent: make(chan struct{}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-29 13:09:46 +01:00
|
|
|
// Start starts to find new peripheral events
|
2024-12-15 13:45:46 +01:00
|
|
|
func (h *HardwareManager) Start(ctx context.Context) error {
|
2024-12-29 21:22:53 +01:00
|
|
|
// Configure wndProc callback
|
2024-12-15 13:45:46 +01:00
|
|
|
cb := windows.NewCallback(h.wndProc)
|
|
|
|
|
inst := win.GetModuleHandle(nil)
|
2024-12-29 13:09:46 +01:00
|
|
|
|
2024-12-29 21:22:53 +01:00
|
|
|
// Register window class
|
|
|
|
|
className, err := syscall.UTF16PtrFromString("DMXConnectPeripheralWatcher")
|
2024-12-15 13:45:46 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to convert window class name to UTF16: %w", err)
|
|
|
|
|
}
|
|
|
|
|
wc := win.WNDCLASSEX{
|
2024-12-29 21:22:53 +01:00
|
|
|
CbSize: uint32(unsafe.Sizeof(win.WNDCLASSEX{})),
|
2024-12-15 13:45:46 +01:00
|
|
|
HInstance: inst,
|
|
|
|
|
LpfnWndProc: cb,
|
2024-12-29 21:22:53 +01:00
|
|
|
LpszClassName: className,
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
|
|
|
|
if win.RegisterClassEx(&wc) == 0 {
|
|
|
|
|
return fmt.Errorf("failed to register window class: %w", syscall.GetLastError())
|
|
|
|
|
}
|
2024-12-29 13:09:46 +01:00
|
|
|
|
2024-12-29 21:22:53 +01:00
|
|
|
// Create hidden window
|
|
|
|
|
windowName, err := syscall.UTF16PtrFromString("usbevent.exe")
|
2024-12-15 13:45:46 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to convert window name to UTF16: %w", err)
|
|
|
|
|
}
|
2024-12-29 21:22:53 +01:00
|
|
|
hwnd := win.CreateWindowEx(
|
2024-12-15 13:45:46 +01:00
|
|
|
0,
|
|
|
|
|
wc.LpszClassName,
|
2024-12-29 21:22:53 +01:00
|
|
|
windowName,
|
|
|
|
|
win.WS_OVERLAPPEDWINDOW,
|
2024-12-15 13:45:46 +01:00
|
|
|
win.CW_USEDEFAULT,
|
|
|
|
|
win.CW_USEDEFAULT,
|
|
|
|
|
100,
|
|
|
|
|
100,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
wc.HInstance,
|
2024-12-29 21:22:53 +01:00
|
|
|
nil,
|
|
|
|
|
)
|
|
|
|
|
if hwnd == 0 {
|
2024-12-15 13:45:46 +01:00
|
|
|
return fmt.Errorf("failed to create window: %w", syscall.GetLastError())
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-29 21:22:53 +01:00
|
|
|
// Hide and update window
|
|
|
|
|
win.ShowWindow(hwnd, win.SW_HIDE)
|
|
|
|
|
win.UpdateWindow(hwnd)
|
2024-12-15 13:45:46 +01:00
|
|
|
|
2024-12-29 21:22:53 +01:00
|
|
|
// Start message loop in a goroutine
|
|
|
|
|
go messageLoop(ctx, hwnd)
|
2024-12-15 13:45:46 +01:00
|
|
|
|
|
|
|
|
// To handle the peripheral changed
|
|
|
|
|
go func() {
|
2024-12-29 13:09:46 +01:00
|
|
|
defer log.Debug().Str("file", "hardware").Msg("peripheral getter goroutine exited")
|
2024-12-15 13:45:46 +01:00
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-h.deviceChangedEvent:
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Debug().Str("file", "hardware").Msg("peripheral change event, triggering scan...")
|
|
|
|
|
err := h.Scan(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Str("file", "hardware").Msg("unable to scan peripherals")
|
|
|
|
|
}
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-29 21:22:53 +01:00
|
|
|
func messageLoop(ctx context.Context, hwnd win.HWND) {
|
|
|
|
|
defer log.Debug().Str("file", "hardware").Msg("Peripheral watcher goroutine exited")
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
win.PostQuitMessage(0) // Gracefully terminate message loop
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
var msg win.MSG
|
|
|
|
|
result := win.GetMessage(&msg, hwnd, 0, 0)
|
|
|
|
|
if result > 0 {
|
|
|
|
|
win.TranslateMessage(&msg)
|
|
|
|
|
win.DispatchMessage(&msg)
|
|
|
|
|
} else if result == 0 {
|
|
|
|
|
log.Warn().Str("file", "hardware").Msg("WM_QUIT message received")
|
|
|
|
|
return
|
|
|
|
|
} else {
|
|
|
|
|
log.Error().Str("file", "hardware").Msg("GetMessage returned an error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-23 17:22:37 +01:00
|
|
|
// GetDriver returns a register driver
|
|
|
|
|
func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error) {
|
|
|
|
|
driver, exists := h.drivers[driverName]
|
|
|
|
|
if !exists {
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Error().Str("file", "hardware").Str("driverName", driverName).Msg("unable to get the driver")
|
2024-12-23 17:22:37 +01:00
|
|
|
return nil, fmt.Errorf("Unable to locate the '%s' driver", driverName)
|
|
|
|
|
}
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Debug().Str("file", "hardware").Str("driverName", driverName).Msg("got driver")
|
2024-12-23 17:22:37 +01:00
|
|
|
return driver, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegisterDriver registers a new peripherals driver
|
|
|
|
|
func (h *HardwareManager) RegisterDriver(driver PeripheralDriver) {
|
|
|
|
|
h.drivers[driver.GetName()] = driver
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Info().Str("file", "hardware").Str("driverName", driver.GetName()).Msg("driver registered")
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-23 17:22:37 +01:00
|
|
|
// GetPeripheral gets the peripheral object from the parent driver
|
|
|
|
|
func (h *HardwareManager) GetPeripheral(driverName string, peripheralID string) (Peripheral, bool) {
|
|
|
|
|
// Get the driver
|
2024-12-29 13:09:46 +01:00
|
|
|
parentDriver, found := h.drivers[driverName]
|
2024-12-23 17:22:37 +01:00
|
|
|
// If no driver found, return false
|
2024-12-29 13:09:46 +01:00
|
|
|
if !found {
|
|
|
|
|
log.Error().Str("file", "hardware").Str("driverName", driverName).Msg("unable to get the driver")
|
2024-12-20 17:18:57 +01:00
|
|
|
return nil, false
|
|
|
|
|
}
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("file", "hardware").Str("driverName", parentDriver.GetName()).Msg("driver got")
|
2024-12-23 17:22:37 +01:00
|
|
|
// Contact the driver to get the device
|
|
|
|
|
return parentDriver.GetPeripheral(peripheralID)
|
2024-12-20 17:18:57 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-15 13:45:46 +01:00
|
|
|
// Scan scans all the peripherals for the registered finders
|
|
|
|
|
func (h *HardwareManager) Scan(ctx context.Context) error {
|
2024-12-23 17:22:37 +01:00
|
|
|
if len(h.drivers) == 0 {
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Warn().Str("file", "hardware").Msg("no driver registered")
|
|
|
|
|
return fmt.Errorf("no driver registered")
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
2024-12-23 17:22:37 +01:00
|
|
|
for _, driver := range h.drivers {
|
2024-12-26 14:55:55 +01:00
|
|
|
driverCopy := driver
|
2024-12-15 13:45:46 +01:00
|
|
|
go func() {
|
2024-12-26 14:55:55 +01:00
|
|
|
err := driverCopy.Scan(ctx)
|
2024-12-15 13:45:46 +01:00
|
|
|
if err != nil {
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Err(err).Str("file", "hardware").Str("driverName", driverCopy.GetName()).Msg("unable to scan peripheral")
|
2024-12-15 13:45:46 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
2024-12-29 21:22:53 +01:00
|
|
|
log.Trace().Str("file", "hardware").Msg("wndProc triggered")
|
2024-12-15 13:45:46 +01:00
|
|
|
switch msg {
|
|
|
|
|
case win.WM_DEVICECHANGE:
|
|
|
|
|
// Trigger the devices scan when the last DEVICE_CHANGE event is received
|
|
|
|
|
if debounceTimer != nil {
|
|
|
|
|
debounceTimer.Stop()
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("file", "hardware").Msg("scan debounce timer stopped")
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
|
|
|
|
debounceTimer = time.AfterFunc(debounceDuration, func() {
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Debug().Str("file", "hardware").Msg("peripheral changed")
|
2024-12-15 13:45:46 +01:00
|
|
|
h.deviceChangedEvent <- struct{}{}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return win.DefWindowProc(win.HWND(hwnd), msg, wParam, lParam)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// peripheralsList emits a peripheral event
|
|
|
|
|
func emitPeripheralsEvents(ctx context.Context, peripheralsList map[string]Peripheral, peripheralEvent PeripheralEvent) {
|
|
|
|
|
for _, peripheral := range peripheralsList {
|
|
|
|
|
runtime.EventsEmit(ctx, string(peripheralEvent), peripheral.GetInfo())
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("file", "hardware").Str("event", string(peripheralEvent)).Msg("emit peripheral event")
|
2024-12-15 13:45:46 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// comparePeripherals compares the peripherals to determine which has been inserted or removed
|
|
|
|
|
func comparePeripherals(oldPeripherals map[string]Peripheral, newPeripherals map[string]Peripheral) (map[string]Peripheral, map[string]Peripheral) {
|
|
|
|
|
// Duplicate the lists
|
|
|
|
|
oldList := make(map[string]Peripheral)
|
|
|
|
|
newList := make(map[string]Peripheral)
|
|
|
|
|
|
|
|
|
|
for key, value := range oldPeripherals {
|
|
|
|
|
oldList[key] = value
|
|
|
|
|
}
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("file", "hardware").Any("oldList", oldList).Msg("peripheral oldList comparison")
|
2024-12-15 13:45:46 +01:00
|
|
|
for key, value := range newPeripherals {
|
|
|
|
|
newList[key] = value
|
|
|
|
|
}
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("file", "hardware").Any("newList", newList).Msg("peripheral newList comparison")
|
2024-12-15 13:45:46 +01:00
|
|
|
// Remove in these lists all the commons peripherals
|
|
|
|
|
for key := range newList {
|
|
|
|
|
if _, exists := oldList[key]; exists {
|
|
|
|
|
delete(oldList, key)
|
|
|
|
|
delete(newList, key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Now the old list contains the removed peripherals, and the new list contains the added peripherals
|
2024-12-29 13:09:46 +01:00
|
|
|
log.Trace().Str("file", "hardware").Any("oldList", oldList).Msg("peripheral oldList computed")
|
|
|
|
|
log.Trace().Str("file", "hardware").Any("newList", newList).Msg("peripheral newList computed")
|
2024-12-15 13:45:46 +01:00
|
|
|
return oldList, newList
|
|
|
|
|
}
|