From 52a71a31c4e5d40deb2562ce2118f1b528dc9e49 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sat, 5 Sep 2026 00:23:11 +0545 Subject: [PATCH] fix(network): stop freezing shell during wifi switch and detect connect success correctly - Cache saved SSIDs and refresh them off the main thread; keep in sync via the Settings proxy's Connections property-change notify, so is_network_saved never issues a blocking D-Bus GetSettings round-trip on the GTK main loop. This prevented the panel and control-center popup from freezing while wifi devices were switched. - Treat AddAndActivateConnection/ActivateConnection results as success by truthiness instead of requiring a two-element tuple (only the ...2 variant returns one). The old check always evaluated False, so even a successful password connect was reported as a failure and re-prompted the password dialog. --- src/services/network.py | 95 +++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/src/services/network.py b/src/services/network.py index 8eb881c..12970c3 100644 --- a/src/services/network.py +++ b/src/services/network.py @@ -847,6 +847,9 @@ class NetworkClient(Service): self.ethernet_devices: dict[str, Ethernet] = {} self._airplane_mode: bool = False self._nm_proxy_handler: int | None = None + self._saved_ssids: set[str] = set() + self._saved_ssids_loaded = False + self._conn_handler_id: int | None = None super().__init__(**kwargs) bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) if bus is None: @@ -867,6 +870,14 @@ class NetworkClient(Service): "g-properties-changed", self._on_nm_props_changed ) + # Keep the cached saved-SSID list in sync when connections change, + # so looking networks up never triggers a blocking D-Bus call on the + # main thread (which froze the shell during wifi switches). + self._conn_handler_id = self._nm._settings.connect( + "g-properties-changed", self._on_settings_props_changed + ) + self._refresh_saved_ssids() + # Initial airplane mode bluetooth_blocked = rfkill_soft_blocked() wifi_enabled = self._nm.wireless_enabled @@ -875,6 +886,52 @@ class NetworkClient(Service): self.notify("primary-device") self.notify("airplane-mode") + def _on_settings_props_changed(self, proxy, changed, invalidated): + props = changed.unpack() if changed else {} + if "Connections" in props: + self._refresh_saved_ssids() + + def _collect_saved_ssids(self) -> set[str]: + """Read saved wireless SSIDs (blocking; run off the main thread).""" + saved: set[str] = set() + if not self._nm: + return saved + conn_paths = _get(self._nm._settings, "Connections") or [] + for conn_path in conn_paths: + try: + cs = _make_proxy(self._nm._bus, conn_path, NM_CONN_SETTINGS_IFACE) + settings = _call(cs, "GetSettings") + if not settings: + continue + if isinstance(settings, tuple) and settings: + settings = settings[0] + sections = settings if isinstance(settings, dict) else {} + if "802-11-wireless" not in sections: + continue + conn_id = sections.get("connection", {}).get("id") + if isinstance(conn_id, GLib.Variant): + conn_id = conn_id.unpack() + if conn_id: + saved.add(str(conn_id)) + except GLib.Error: + continue + return saved + + def _refresh_saved_ssids(self): + """Refresh saved SSIDs off the main thread, then swap cache in.""" + + def _load(): + saved = self._collect_saved_ssids() + GLib.idle_add(self._apply_saved_ssids, saved) + + import threading + + threading.Thread(target=_load, daemon=True).start() + + def _apply_saved_ssids(self, saved): + self._saved_ssids = saved + self._saved_ssids_loaded = True + def _on_nm_props_changed(self, proxy, changed, invalidated): props = changed.unpack() if changed else {} if "Devices" not in props: @@ -975,29 +1032,21 @@ class NetworkClient(Service): return None def is_network_saved(self, ssid: str) -> bool: + """Return whether ``ssid`` is a known/saved connection. + + Reads a lazily cached list of saved SSIDs instead of querying + NetworkManager synchronously, so the main thread is never blocked + while the wifi UI refreshes during connection changes. The cache is + filled in the background and kept in sync via ``Connections`` + property-change notifications. + """ + if self._saved_ssids_loaded: + return ssid in self._saved_ssids if not self._nm: return False - conn_paths = _get(self._nm._settings, "Connections") or [] - for conn_path in conn_paths: - try: - cs = _make_proxy(self._nm._bus, conn_path, NM_CONN_SETTINGS_IFACE) - settings = _call(cs, "GetSettings") - if not settings: - continue - if isinstance(settings, tuple) and settings: - settings = settings[0] - sections = settings if isinstance(settings, dict) else {} - if "802-11-wireless" not in sections: - continue - conn_section = sections.get("connection", {}) - conn_id = conn_section.get("id") - if isinstance(conn_id, GLib.Variant): - conn_id = conn_id.unpack() - if conn_id == ssid: - return True - except GLib.Error: - continue - return False + # Cold path (first lookup): resolve synchronously so behaviour is + # unchanged before the async refresh has completed. + return ssid in self._collect_saved_ssids() def connect_wifi(self, ap: AccessPointData, callback=None): if not self._nm or not self.wifi_device: @@ -1032,7 +1081,7 @@ class NetworkClient(Service): logger.info( f"[Network] activate/add_result={result!r} for ssid={ap.ssid!r}" ) - ok = isinstance(result, tuple) and result[0] + ok = bool(result) if callback: GLib.idle_add(callback, ok, "") except Exception as e: @@ -1084,7 +1133,7 @@ class NetworkClient(Service): logger.info( f"[Network] activate/add_result={result!r} for ssid={ap.ssid!r}" ) - ok = isinstance(result, tuple) and result[0] + ok = bool(result) if callback: GLib.idle_add(callback, ok, "") except Exception as e: