Files
dmxconnect/hardware/hardware.go

213 lines
5.9 KiB
Go

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 {
drivers map[string]PeripheralDriver // The map 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{
drivers: make(map[string]PeripheralDriver),
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
}
// GetDriver returns a register driver
func (h *HardwareManager) GetDriver(driverName string) (PeripheralDriver, error) {
driver, exists := h.drivers[driverName]
if !exists {
return nil, fmt.Errorf("Unable to locate the '%s' driver", driverName)
}
return driver, nil
}
// RegisterDriver registers a new peripherals driver
func (h *HardwareManager) RegisterDriver(driver PeripheralDriver) {
h.drivers[driver.GetName()] = driver
fmt.Printf("Success registered the %s driver\n", driver.GetName())
}
// GetPeripheral gets the peripheral object from the parent driver
func (h *HardwareManager) GetPeripheral(driverName string, peripheralID string) (Peripheral, bool) {
// Get the driver
parentDriver := h.drivers[driverName]
// If no driver found, return false
if parentDriver == nil {
fmt.Println("Unable to get the driver")
return nil, false
}
// Contact the driver to get the device
return parentDriver.GetPeripheral(peripheralID)
}
// Scan scans all the peripherals for the registered finders
func (h *HardwareManager) Scan(ctx context.Context) error {
if len(h.drivers) == 0 {
return fmt.Errorf("No peripherals driver registered")
}
for _, driver := range h.drivers {
finder := driver
go func() {
err := driver.Scan(ctx)
if err != nil {
fmt.Printf("Unable to scan peripherals with the %s driver: %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
}