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.
This commit is contained in:
1 file changed
+72
-23
+72
-23
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user