package hardware import ( "context" "fmt" "sync" "time" "github.com/rs/zerolog/log" "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 { drivers map[string]PeripheralFinder // The map of peripherals finders peripherals []Peripheral // The current list of peripherals peripheralsScanTrigger chan struct{} // Trigger the peripherals scans goWait sync.WaitGroup // Wait for goroutines to terminate } // NewHardwareManager creates a new HardwareManager func NewHardwareManager() *HardwareManager { log.Trace().Str("package", "hardware").Msg("Hardware instance created") return &HardwareManager{ drivers: make(map[string]PeripheralFinder), peripherals: make([]Peripheral, 0), peripheralsScanTrigger: make(chan struct{}), } } // Start starts to find new peripheral events func (h *HardwareManager) Start(ctx context.Context) error { for finderName, finder := range h.drivers { err := finder.Initialize() if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder") return err } err = finder.Start(ctx) if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder") return err } } // n, err := detector.Register() // if err != nil { // log.Err(err).Str("file", "hardware").Msg("error registering the usb event") // } // h.detector = n // // Run the detector // n.Run(ctx) // h.goWait.Add(1) // go func() { // defer h.goWait.Done() // for { // select { // case <-ctx.Done(): // return // case <-h.detector.EventChannel: // // Trigger hardware scans // log.Info().Str("file", "hardware").Msg("peripheral change event") // case <-h.peripheralsScanTrigger: // log.Info().Str("file", "hardware").Msg("scan triggered") // } // } // }() return nil } // GetDriver returns a register driver func (h *HardwareManager) GetDriver(driverName string) (PeripheralFinder, error) { driver, exists := h.drivers[driverName] if !exists { log.Error().Str("file", "hardware").Str("driverName", driverName).Msg("unable to get the driver") return nil, fmt.Errorf("Unable to locate the '%s' driver", driverName) } log.Debug().Str("file", "hardware").Str("driverName", driverName).Msg("got driver") return driver, nil } // RegisterDriver registers a new peripherals driver func (h *HardwareManager) RegisterDriver(driver PeripheralFinder) { h.drivers[driver.GetName()] = driver log.Info().Str("file", "hardware").Str("driverName", driver.GetName()).Msg("driver registered") } // GetPeripheral gets the peripheral object from the parent driver func (h *HardwareManager) GetPeripheral(driverName string, peripheralID string) (Peripheral, bool) { // Get the driver parentDriver, found := h.drivers[driverName] // If no driver found, return false if !found { log.Error().Str("file", "hardware").Str("driverName", driverName).Msg("unable to get the driver") return nil, false } log.Trace().Str("file", "hardware").Str("driverName", parentDriver.GetName()).Msg("driver got") // Contact the driver to get the device return parentDriver.GetPeripheral(peripheralID) } // Scan scans all the peripherals for the registered finders func (h *HardwareManager) Scan() error { h.peripheralsScanTrigger <- struct{}{} return nil } func (h *HardwareManager) wndProc(hwnd windows.HWND, msg uint32, wParam, lParam uintptr) uintptr { log.Trace().Str("file", "hardware").Uint32("msg", msg).Msg("wndProc triggered") if msg == win.WM_DEVICECHANGE { // Trigger the devices scan when the last DEVICE_CHANGE event is received if debounceTimer != nil { debounceTimer.Stop() log.Debug().Str("file", "hardware").Msg("scan debounce timer stopped") } debounceTimer = time.AfterFunc(debounceDuration, func() { log.Debug().Str("file", "hardware").Msg("peripheral changed") h.peripheralsScanTrigger <- struct{}{} }) } return win.DefWindowProc(win.HWND(hwnd), msg, wParam, lParam) } // Stop stops the hardware manager func (h *HardwareManager) Stop() error { log.Trace().Str("file", "hardware").Msg("closing the hardware manager") // Stop each finder for finderName, finder := range h.drivers { err := finder.Stop() if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to stop the finder") } } // Wait for goroutines to finish h.goWait.Wait() log.Info().Str("file", "hardware").Msg("hardware manager stopped") return nil } // 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()) log.Trace().Str("file", "hardware").Str("event", string(peripheralEvent)).Msg("emit peripheral event") } } // 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 } log.Trace().Str("file", "hardware").Any("oldList", oldList).Msg("peripheral oldList comparison") for key, value := range newPeripherals { newList[key] = value } log.Trace().Str("file", "hardware").Any("newList", newList).Msg("peripheral newList comparison") // 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 log.Trace().Str("file", "hardware").Any("oldList", oldList).Msg("peripheral oldList computed") log.Trace().Str("file", "hardware").Any("newList", newList).Msg("peripheral newList computed") return oldList, newList }