package hardware import ( "context" "fmt" "syscall" "time" "unsafe" "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 { finders []PeripheralFinder // The list of peripherals finders peripherals []Peripheral // The current list of peripherals deviceChangedEvent chan struct{} // The event when the devices list changed ctx context.Context } // NewHardwareManager creates a new HardwareManager func NewHardwareManager() *HardwareManager { return &HardwareManager{ finders: make([]PeripheralFinder, 0), peripherals: make([]Peripheral, 0), deviceChangedEvent: make(chan struct{}), } } // Start starts to finding new device events func (h *HardwareManager) Start(ctx context.Context) error { cb := windows.NewCallback(h.wndProc) inst := win.GetModuleHandle(nil) cn, err := syscall.UTF16PtrFromString("DMXConnect device watcher") if err != nil { return fmt.Errorf("failed to convert window class name to UTF16: %w", err) } wc := win.WNDCLASSEX{ HInstance: inst, LpfnWndProc: cb, LpszClassName: cn, } wc.CbSize = uint32(unsafe.Sizeof(wc)) if win.RegisterClassEx(&wc) == 0 { return fmt.Errorf("failed to register window class: %w", syscall.GetLastError()) } wName, err := syscall.UTF16PtrFromString("usbevent.exe") if err != nil { return fmt.Errorf("failed to convert window name to UTF16: %w", err) } wdw := win.CreateWindowEx( 0, wc.LpszClassName, wName, win.WS_MINIMIZE|win.WS_OVERLAPPEDWINDOW, win.CW_USEDEFAULT, win.CW_USEDEFAULT, 100, 100, 0, 0, wc.HInstance, nil) if wdw == 0 { return fmt.Errorf("failed to create window: %w", syscall.GetLastError()) } _ = win.ShowWindow(wdw, win.SW_HIDE) win.UpdateWindow(wdw) // To continuously get the devices events from Windows go func() { for { select { case <-ctx.Done(): return default: var msg win.MSG got := win.GetMessage(&msg, win.HWND(windows.HWND(wdw)), 0, 0) if got == 0 { win.TranslateMessage(&msg) win.DispatchMessage(&msg) } } } }() // To handle the peripheral changed go func() { for { select { case <-ctx.Done(): return case <-h.deviceChangedEvent: fmt.Println("This is the list of devices") h.Scan(ctx) } } }() return nil } // RegisterFinder registers a new peripherals finder func (h *HardwareManager) RegisterFinder(finder PeripheralFinder) { h.finders = append(h.finders, finder) fmt.Printf("Success registered the %s finder\n", finder.GetName()) } // Scan scans all the peripherals for the registered finders func (h *HardwareManager) Scan(ctx context.Context) error { if len(h.finders) == 0 { return fmt.Errorf("No peripherals finder registered") } for _, finder := range h.finders { finder := finder go func() { err := finder.Scan(ctx) if err != nil { fmt.Printf("Unable to scan peripherals with the %s finder: %s\n", finder.GetName(), err) return } }() } return nil } func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_DEVICECHANGE: // Trigger the devices scan when the last DEVICE_CHANGE event is received if debounceTimer != nil { debounceTimer.Stop() } debounceTimer = time.AfterFunc(debounceDuration, func() { fmt.Printf("Devices list has changed, refresh the devices list\n") 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()) } } // 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 } for key, value := range newPeripherals { newList[key] = value } // 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 fmt.Printf("%s\n", oldList) fmt.Printf("%s\n", newList) return oldList, newList }