diff --git a/core/internal/server/network/backend_iwd_signals.go b/core/internal/server/network/backend_iwd_signals.go index 60ea87625..affc54644 100644 --- a/core/internal/server/network/backend_iwd_signals.go +++ b/core/internal/server/network/backend_iwd_signals.go @@ -428,6 +428,7 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) { b.stateMutex.Lock() b.state.WiFiConnected = false + b.state.WiFiSSID = "" if state == "disconnected" { b.state.NetworkStatus = StatusDisconnected } diff --git a/core/internal/server/network/backend_iwd_test.go b/core/internal/server/network/backend_iwd_test.go index 2a1d883a6..f4ac326c8 100644 --- a/core/internal/server/network/backend_iwd_test.go +++ b/core/internal/server/network/backend_iwd_test.go @@ -342,6 +342,48 @@ func TestIWDBackend_PendingPSK(t *testing.T) { assert.False(t, ok, "expired pending PSK should not be returned") } +func TestIWDBackend_SetConnectError_ClearsSSIDWhenDisconnected(t *testing.T) { + backend, _ := NewIWDBackend() + backend.state = &BackendState{ + WiFiConnected: false, + WiFiSSID: "StaleNetwork", + IsConnecting: true, + ConnectingSSID: "StaleNetwork", + } + + backend.setConnectError("bad-credentials") + + assert.False(t, backend.state.IsConnecting) + assert.Empty(t, backend.state.ConnectingSSID) + assert.Empty(t, backend.state.WiFiSSID) + assert.Equal(t, "bad-credentials", backend.state.LastError) +} + +func TestIWDBackend_FinalizeAttempt_ClearsSSIDWhenDisconnected(t *testing.T) { + backend, _ := NewIWDBackend() + backend.state = &BackendState{ + WiFiConnected: false, + WiFiSSID: "StaleNetwork", + IsConnecting: true, + ConnectingSSID: "StaleNetwork", + } + + att := &connectAttempt{ + ssid: "StaleNetwork", + netPath: "/test", + saved: false, + start: time.Now(), + deadline: time.Now().Add(15 * time.Second), + } + + backend.finalizeAttempt(att, "bad-credentials") + + assert.False(t, backend.state.IsConnecting) + assert.Empty(t, backend.state.ConnectingSSID) + assert.Empty(t, backend.state.WiFiSSID) + assert.Equal(t, "bad-credentials", backend.state.LastError) +} + type fakePromptBroker struct { asked chan PromptRequest reply PromptReply diff --git a/core/internal/server/network/backend_iwd_wifi.go b/core/internal/server/network/backend_iwd_wifi.go index 69ffd6084..858dfe9f4 100644 --- a/core/internal/server/network/backend_iwd_wifi.go +++ b/core/internal/server/network/backend_iwd_wifi.go @@ -41,6 +41,7 @@ func (b *IWDBackend) updateState() error { b.state.NetworkStatus = StatusWiFi } else { b.state.NetworkStatus = StatusDisconnected + b.state.WiFiSSID = "" } b.stateMutex.Unlock() } @@ -55,7 +56,11 @@ func (b *IWDBackend) updateState() error { if err == nil { if name, ok := nameVar.Value().(string); ok { b.stateMutex.Lock() - b.state.WiFiSSID = name + if b.state.WiFiConnected { + b.state.WiFiSSID = name + } else { + b.state.WiFiSSID = "" + } b.stateMutex.Unlock() } } @@ -417,6 +422,9 @@ func (b *IWDBackend) setConnectError(code string) { b.state.IsConnecting = false b.state.ConnectingSSID = "" b.state.LastError = code + if !b.state.WiFiConnected { + b.state.WiFiSSID = "" + } b.stateMutex.Unlock() } @@ -471,6 +479,9 @@ func (b *IWDBackend) finalizeAttempt(att *connectAttempt, code string) { b.state.IsConnecting = false b.state.ConnectingSSID = "" b.state.LastError = code + if !b.state.WiFiConnected { + b.state.WiFiSSID = "" + } b.stateMutex.Unlock() b.updateState() @@ -634,6 +645,10 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error { return fmt.Errorf("network not found: %w", err) } + if req.Password != "" { + b.storePendingPSK(req.SSID, req.Password) + } + att := &connectAttempt{ ssid: req.SSID, netPath: networkPath, @@ -731,6 +746,12 @@ func (b *IWDBackend) DisconnectWiFi() error { return fmt.Errorf("failed to disconnect: %w", call.Err) } + b.stateMutex.Lock() + b.state.WiFiConnected = false + b.state.WiFiSSID = "" + b.state.NetworkStatus = StatusDisconnected + b.stateMutex.Unlock() + b.updateState() if b.onStateChange != nil { @@ -772,6 +793,10 @@ func (b *IWDBackend) ForgetWiFiNetwork(ssid string) error { isConnected := b.state.WiFiConnected b.stateMutex.RUnlock() + if isConnected && currentSSID == ssid { + _ = b.DisconnectWiFi() + } + obj := b.conn.Object(iwdBusName, iwdObjectPath) var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant diff --git a/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml b/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml index 0f9e3d627..57af4376e 100644 --- a/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml +++ b/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml @@ -1124,7 +1124,7 @@ Rectangle { required property var modelData required property int index - readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID + readonly property bool isConnected: NetworkService.wifiConnected && modelData.ssid === NetworkService.currentWifiSSID readonly property bool isConnecting: NetworkService.isWifiConnecting && NetworkService.connectingSSID === modelData.ssid readonly property bool isPinned: root.getPinnedNetworks().includes(modelData.ssid) readonly property string networkName: modelData.ssid || I18n.tr("Unknown Network") @@ -1319,12 +1319,9 @@ Rectangle { cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y) onClicked: function (event) { - if (wifiDelegate.isConnected) { - event.accepted = true; - return; - } WifiConnectionActions.connectToNetwork(modelData, { - connected: wifiDelegate.isConnected + connected: wifiDelegate.isConnected, + disconnectWhenConnected: true }); event.accepted = true; } diff --git a/quickshell/Modules/Settings/NetworkWifiTab.qml b/quickshell/Modules/Settings/NetworkWifiTab.qml index 06b82f3a1..33524f642 100644 --- a/quickshell/Modules/Settings/NetworkWifiTab.qml +++ b/quickshell/Modules/Settings/NetworkWifiTab.qml @@ -457,7 +457,7 @@ Item { required property var modelData required property int index - readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID + readonly property bool isConnected: NetworkService.wifiConnected && modelData.ssid === NetworkService.currentWifiSSID readonly property bool isConnecting: NetworkService.isWifiConnecting && NetworkService.connectingSSID === modelData.ssid readonly property bool isPinned: root.getPinnedWifiNetworks().includes(modelData.ssid) readonly property bool isExpanded: root.expandedWifiSsid === modelData.ssid @@ -863,7 +863,7 @@ Item { required property var modelData required property int index - readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID + readonly property bool isConnected: NetworkService.wifiConnected && modelData.ssid === NetworkService.currentWifiSSID readonly property bool isConnecting: NetworkService.isWifiConnecting && NetworkService.connectingSSID === modelData.ssid readonly property bool isPinned: root.getPinnedWifiNetworks().includes(modelData.ssid) readonly property bool isOutOfRange: modelData.outOfRange || false diff --git a/quickshell/Services/DMSNetworkService.qml b/quickshell/Services/DMSNetworkService.qml index 89604ee60..e6cbfd8b8 100644 --- a/quickshell/Services/DMSNetworkService.qml +++ b/quickshell/Services/DMSNetworkService.qml @@ -902,6 +902,21 @@ Singleton { onTriggered: root.scanWifi() } + Timer { + interval: 20000 + repeat: true + running: root.isConnecting + onTriggered: { + if (Date.now() - root.pendingConnectionStartTime > 16000) { + root.isConnecting = false; + root.connectingSSID = ""; + root.connectionStatus = "failed"; + root.connectionError = "timeout"; + root.pendingConnectionSSID = ""; + } + } + } + function fetchWiredNetworkInfo(uuid) { if (!networkAvailable) return;