diff --git a/core/internal/server/network/backend_iwd.go b/core/internal/server/network/backend_iwd.go index df4a2b874..8dadb7b4b 100644 --- a/core/internal/server/network/backend_iwd.go +++ b/core/internal/server/network/backend_iwd.go @@ -114,6 +114,14 @@ func (b *IWDBackend) discoverDevices() error { return fmt.Errorf("failed to get managed objects: %w", err) } + return b.applyManagedObjects(objects) +} + +func (b *IWDBackend) applyManagedObjects(objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant) error { + b.stationPath = "" + b.devicePath = "" + b.adapterPath = "" + for path, interfaces := range objects { if _, hasStation := interfaces[iwdStationInterface]; hasStation { b.stationPath = path @@ -129,6 +137,13 @@ func (b *IWDBackend) discoverDevices() error { b.stateMutex.Unlock() } } + if poweredVar, ok := devProps["Powered"]; ok { + if powered, ok := poweredVar.Value().(bool); ok { + b.stateMutex.Lock() + b.state.WiFiEnabled = powered + b.stateMutex.Unlock() + } + } } } if _, hasAdapter := interfaces[iwdAdapterInterface]; hasAdapter { @@ -136,9 +151,18 @@ func (b *IWDBackend) discoverDevices() error { } } - if b.stationPath == "" || b.devicePath == "" { + if b.devicePath == "" { return fmt.Errorf("no WiFi device found") } + if b.stationPath == "" { + b.stateMutex.Lock() + b.state.WiFiEnabled = false + b.state.WiFiConnected = false + b.state.NetworkStatus = StatusDisconnected + b.state.WiFiNetworks = nil + b.stateMutex.Unlock() + log.Infof("iwd device %s has no station interface; treating WiFi as disabled", b.devicePath) + } return nil } diff --git a/core/internal/server/network/backend_iwd_signals.go b/core/internal/server/network/backend_iwd_signals.go index 2a4deb298..25fff740b 100644 --- a/core/internal/server/network/backend_iwd_signals.go +++ b/core/internal/server/network/backend_iwd_signals.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/godbus/dbus/v5" ) @@ -35,12 +36,7 @@ func (b *IWDBackend) StartMonitoring(onStateChange func()) error { } if b.stationPath != "" { - err := b.conn.AddMatchSignal( - dbus.WithMatchObjectPath(b.stationPath), - dbus.WithMatchInterface(dbusPropertiesInterface), - dbus.WithMatchMember("PropertiesChanged"), - ) - if err != nil { + if err := b.addStationSignalMatch(b.stationPath); err != nil { return fmt.Errorf("failed to add station signal match: %w", err) } } @@ -81,6 +77,48 @@ func (b *IWDBackend) refreshWiFiNetworkState() bool { return b.updateSavedWiFiNetworks() == nil } +func (b *IWDBackend) addStationSignalMatch(path dbus.ObjectPath) error { + return b.conn.AddMatchSignal( + dbus.WithMatchObjectPath(path), + dbus.WithMatchInterface(dbusPropertiesInterface), + dbus.WithMatchMember("PropertiesChanged"), + ) +} + +func (b *IWDBackend) handleStationAdded(path dbus.ObjectPath) bool { + if path == "" || path == b.stationPath { + return false + } + + b.stationPath = path + if err := b.addStationSignalMatch(path); err != nil { + log.Warnf("Failed to add iwd station signal match for %s: %v", path, err) + } + + if err := b.updateState(); err != nil { + log.Warnf("Failed to update iwd state after station appeared: %v", err) + } + return b.refreshWiFiNetworkState() +} + +func (b *IWDBackend) handleStationRemoved(path dbus.ObjectPath) bool { + if path == "" || path != b.stationPath { + return false + } + + b.stationPath = "" + b.stateMutex.Lock() + b.state.WiFiEnabled = false + b.state.WiFiConnected = false + b.state.WiFiSSID = "" + b.state.WiFiSignal = 0 + b.state.NetworkStatus = StatusDisconnected + b.state.WiFiNetworks = nil + b.stateMutex.Unlock() + + return true +} + func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) { defer b.sigWG.Done() @@ -98,7 +136,13 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) { if sig.Name == dbusObjectManager+".InterfacesAdded" { if len(sig.Body) >= 2 { + path, _ := sig.Body[0].(dbus.ObjectPath) if interfaces, ok := sig.Body[1].(map[string]map[string]dbus.Variant); ok { + if _, ok := interfaces[iwdStationInterface]; ok { + if b.handleStationAdded(path) && b.onStateChange != nil { + b.onStateChange() + } + } if _, ok := interfaces[iwdKnownNetworkInterface]; ok { if b.refreshWiFiNetworkState() && b.onStateChange != nil { b.onStateChange() @@ -111,8 +155,15 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) { if sig.Name == dbusObjectManager+".InterfacesRemoved" { if len(sig.Body) >= 2 { + path, _ := sig.Body[0].(dbus.ObjectPath) if interfaces, ok := sig.Body[1].([]string); ok { for _, iface := range interfaces { + if iface == iwdStationInterface { + if b.handleStationRemoved(path) && b.onStateChange != nil { + b.onStateChange() + } + break + } if iface == iwdKnownNetworkInterface { if b.refreshWiFiNetworkState() && b.onStateChange != nil { b.onStateChange() diff --git a/core/internal/server/network/backend_iwd_test.go b/core/internal/server/network/backend_iwd_test.go index da1d9dc17..7e41552ae 100644 --- a/core/internal/server/network/backend_iwd_test.go +++ b/core/internal/server/network/backend_iwd_test.go @@ -169,6 +169,41 @@ func TestIWDBackend_MapIwdDBusError(t *testing.T) { } } +func TestIWDBackend_ApplyManagedObjects_DeviceWithoutStation(t *testing.T) { + backend, _ := NewIWDBackend() + + err := backend.applyManagedObjects(map[dbus.ObjectPath]map[string]map[string]dbus.Variant{ + "/net/connman/iwd/0/1": { + iwdDeviceInterface: { + "Name": dbus.MakeVariant("wlan0"), + "Powered": dbus.MakeVariant(false), + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, dbus.ObjectPath("/net/connman/iwd/0/1"), backend.devicePath) + assert.Empty(t, backend.stationPath) + + state, err := backend.GetCurrentState() + assert.NoError(t, err) + assert.Equal(t, "wlan0", state.WiFiDevice) + assert.False(t, state.WiFiEnabled) + assert.False(t, state.WiFiConnected) +} + +func TestIWDBackend_ApplyManagedObjects_NoDevice(t *testing.T) { + backend, _ := NewIWDBackend() + + err := backend.applyManagedObjects(map[dbus.ObjectPath]map[string]map[string]dbus.Variant{ + "/net/connman/iwd/0": { + iwdAdapterInterface: {}, + }, + }) + + assert.ErrorContains(t, err, "no WiFi device found") +} + func TestIWDSavedWiFiProfilesFromManagedObjects(t *testing.T) { objects := map[dbus.ObjectPath]map[string]map[string]dbus.Variant{ "/net/connman/iwd/known_network/1": {