repeater/internal/wifi/iwd/signals.go

223 lines
4.7 KiB
Go

package iwd
import (
"log"
"sync"
"github.com/godbus/dbus/v5"
)
// SignalMonitor monitors D-Bus signals from iwd
type SignalMonitor struct {
conn *dbus.Conn
station *Station
// Signal channel
signalChan chan *dbus.Signal
// Callbacks
onStateChange func(state StationState, ssid string)
onScanComplete func()
// Control
stopChan chan struct{}
mu sync.RWMutex
running bool
// State tracking
lastScanning bool
}
// NewSignalMonitor creates a new signal monitor
func NewSignalMonitor(conn *dbus.Conn, station *Station) *SignalMonitor {
return &SignalMonitor{
conn: conn,
station: station,
signalChan: make(chan *dbus.Signal, 100),
stopChan: make(chan struct{}),
}
}
// OnStateChange registers a callback for state changes
func (sm *SignalMonitor) OnStateChange(callback func(state StationState, ssid string)) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.onStateChange = callback
}
// OnScanComplete registers a callback for scan completion
func (sm *SignalMonitor) OnScanComplete(callback func()) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.onScanComplete = callback
}
// Start begins monitoring D-Bus signals
func (sm *SignalMonitor) Start() error {
sm.mu.Lock()
if sm.running {
sm.mu.Unlock()
return nil
}
sm.running = true
sm.mu.Unlock()
// Subscribe to PropertiesChanged signals for Station interface
stationPath := sm.station.GetPath()
// Add signal match for PropertiesChanged on Station interface
matchOptions := []dbus.MatchOption{
dbus.WithMatchObjectPath(stationPath),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchMember("PropertiesChanged"),
}
if err := sm.conn.AddMatchSignal(matchOptions...); err != nil {
sm.mu.Lock()
sm.running = false
sm.mu.Unlock()
return err
}
// Register signal channel
sm.conn.Signal(sm.signalChan)
// Get initial scanning state
scanning, err := sm.station.IsScanning()
if err == nil {
sm.lastScanning = scanning
}
// Start monitoring goroutine
go sm.monitor()
log.Printf("D-Bus signal monitoring started for station %s", stationPath)
return nil
}
// Stop stops monitoring D-Bus signals
func (sm *SignalMonitor) Stop() {
sm.mu.Lock()
if !sm.running {
sm.mu.Unlock()
return
}
sm.running = false
sm.mu.Unlock()
// Signal stop
close(sm.stopChan)
// Remove signal channel
sm.conn.RemoveSignal(sm.signalChan)
log.Printf("D-Bus signal monitoring stopped")
}
// monitor is the main signal processing loop
func (sm *SignalMonitor) monitor() {
for {
select {
case sig := <-sm.signalChan:
sm.handleSignal(sig)
case <-sm.stopChan:
return
}
}
}
// handleSignal processes a D-Bus signal
func (sm *SignalMonitor) handleSignal(sig *dbus.Signal) {
// Only process PropertiesChanged signals
if sig.Name != "org.freedesktop.DBus.Properties.PropertiesChanged" {
return
}
// Verify signal is from Station interface
if len(sig.Body) < 2 {
return
}
interfaceName, ok := sig.Body[0].(string)
if !ok || interfaceName != StationInterface {
return
}
// Parse changed properties
changedProps, ok := sig.Body[1].(map[string]dbus.Variant)
if !ok {
return
}
// Check for State property change
if stateVariant, ok := changedProps["State"]; ok {
if state, ok := stateVariant.Value().(string); ok {
sm.handleStateChange(StationState(state))
}
}
// Check for Scanning property change
if scanningVariant, ok := changedProps["Scanning"]; ok {
if scanning, ok := scanningVariant.Value().(bool); ok {
sm.handleScanningChange(scanning)
}
}
// Check for ConnectedNetwork property change
if _, ok := changedProps["ConnectedNetwork"]; ok {
// Network connection changed, trigger state update
sm.handleConnectionChange()
}
}
// handleStateChange processes a state change
func (sm *SignalMonitor) handleStateChange(state StationState) {
sm.mu.RLock()
callback := sm.onStateChange
sm.mu.RUnlock()
if callback == nil {
return
}
// Get connected SSID if connected
ssid := ""
if state == StateConnected {
network, err := sm.station.GetConnectedNetwork()
if err == nil {
props, err := network.GetProperties()
if err == nil {
ssid = props.Name
}
}
}
callback(state, ssid)
}
// handleScanningChange processes scanning state changes
func (sm *SignalMonitor) handleScanningChange(scanning bool) {
// Detect scan completion (transition from true to false)
if sm.lastScanning && !scanning {
sm.mu.RLock()
callback := sm.onScanComplete
sm.mu.RUnlock()
if callback != nil {
callback()
}
}
sm.lastScanning = scanning
}
// handleConnectionChange processes connection changes
func (sm *SignalMonitor) handleConnectionChange() {
// Get current state and trigger state change callback
state, err := sm.station.GetState()
if err != nil {
return
}
sm.handleStateChange(state)
}