package hardware import ( "context" "errors" "fmt" "sync" "time" "github.com/rs/zerolog/log" "github.com/wailsapp/wails/v2/pkg/runtime" ) // PeripheralEvent is trigger by the finders when the scan is complete type PeripheralEvent string // PeripheralStatus is the peripheral status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED) type PeripheralStatus 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" // PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - deactivated - activated) PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS" // PeripheralEventEmitted is triggered when a peripheral event is emitted PeripheralEventEmitted PeripheralEvent = "PERIPHERAL_EVENT_EMITTED" // PeripheralStatusDisconnected : peripheral is now disconnected PeripheralStatusDisconnected PeripheralStatus = "PERIPHERAL_DISCONNECTED" // PeripheralStatusConnecting : peripheral is now connecting PeripheralStatusConnecting PeripheralStatus = "PERIPHERAL_CONNECTING" // PeripheralStatusDeactivated : peripheral is now deactivated PeripheralStatusDeactivated PeripheralStatus = "PERIPHERAL_DEACTIVATED" // PeripheralStatusActivated : peripheral is now activated PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED" ) // Manager is the class who manages the hardware type Manager struct { wg sync.WaitGroup finders map[string]PeripheralFinder // The map of peripherals finders peripherals []*Peripheral // The current list of peripherals peripheralsScanTrigger chan struct{} // Trigger the peripherals scans } // NewManager creates a new hardware manager func NewManager() *Manager { log.Trace().Str("package", "hardware").Msg("Hardware instance created") return &Manager{ finders: make(map[string]PeripheralFinder), peripherals: make([]*Peripheral, 0), peripheralsScanTrigger: make(chan struct{}), } } // Start starts to find new peripheral events func (h *Manager) Start(ctx context.Context) error { // Initialize all the finders and their callback functions h.RegisterFinder(NewFTDIFinder(3 * time.Second)) h.RegisterFinder(NewOS2LFinder()) h.RegisterFinder(NewMIDIFinder(3 * time.Second)) for finderName, finder := range h.finders { err := finder.Initialize() if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder") return err } finder.OnArrival(func(p PeripheralInfo) { runtime.EventsEmit(ctx, string(PeripheralArrival), p) }) finder.OnRemoval(func(p PeripheralInfo) { runtime.EventsEmit(ctx, string(PeripheralRemoval), p) }) err = finder.Start(ctx) if err != nil { log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder") return err } } // Periodically scan all the finders h.wg.Add(1) go func() { defer h.wg.Done() for { select { case <-ctx.Done(): return case <-h.peripheralsScanTrigger: for finderName, finder := range h.finders { log.Trace().Str("file", "hardware").Str("finderName", finderName).Msg("force a finder to scan peripherals") finder.ForceScan() } } } }() return nil } // GetFinder returns a register finder func (h *Manager) GetFinder(finderName string) (PeripheralFinder, error) { finder, exists := h.finders[finderName] if !exists { log.Error().Str("file", "hardware").Str("finderName", finderName).Msg("unable to get the finder") return nil, fmt.Errorf("unable to locate the '%s' finder", finderName) } log.Debug().Str("file", "hardware").Str("finderName", finderName).Msg("got finder") return finder, nil } // RegisterFinder registers a new peripherals finder func (h *Manager) RegisterFinder(finder PeripheralFinder) { h.finders[finder.GetName()] = finder log.Info().Str("file", "hardware").Str("finderName", finder.GetName()).Msg("finder registered") } // Scan scans all the peripherals for the registered finders func (h *Manager) Scan() error { select { case h.peripheralsScanTrigger <- struct{}{}: return nil default: return fmt.Errorf("scan trigger not available (manager stopped?)") } } // WaitStop stops the hardware manager func (h *Manager) WaitStop() error { log.Trace().Str("file", "hardware").Msg("closing the hardware manager") // Closing trigger channel close(h.peripheralsScanTrigger) // Stop each finder var errs []error for name, f := range h.finders { if err := f.WaitStop(); err != nil { errs = append(errs, fmt.Errorf("%s: %w", name, err)) } } // Wait for goroutines to finish h.wg.Wait() // Returning errors if len(errs) > 0 { return errors.Join(errs...) } log.Info().Str("file", "hardware").Msg("hardware manager stopped") return nil }