From 0072449b015b394040b0e345634086331fc7198c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 17:07:41 -0800 Subject: [PATCH] WifiManager: cache connections until new/removed connection (#37175) * new/removed conns signal * clean up * only get connections when adding/removing not every refresh * add debug * block * Revert "block" This reverts commit 30bbffca8d2db21c53d7a3601ae46bf05e2a7cd5. * rm debug * block on any new message, faster conn rem/add reaction * better names --- system/ui/lib/wifi_manager.py | 91 ++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 8912d2fd9..aa486a8c0 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -146,6 +146,7 @@ class WifiManager: self._wifi_device: str | None = None # State + self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals self._connecting_to_ssid: str = "" self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN @@ -181,7 +182,8 @@ class WifiManager: self._scan_thread.start() self._state_thread.start() - if Params is not None and self._tethering_ssid not in self._get_connections(): + self._update_connections() + if Params is not None and self._tethering_ssid not in self._connections: self._add_tethering_connection() self._tethering_password = self._get_tethering_password() @@ -235,45 +237,69 @@ class WifiManager: self._last_network_update = 0.0 def _monitor_state(self): - rule = MatchRule( - type="signal", - interface=NM_DEVICE_IFACE, - member="StateChanged", - path=self._wifi_device, + # Filter for signals + rules = ( + MatchRule( + type="signal", + interface=NM_DEVICE_IFACE, + member="StateChanged", + path=self._wifi_device, + ), + MatchRule( + type="signal", + interface=NM_SETTINGS_IFACE, + member="NewConnection", + path=NM_SETTINGS_PATH, + ), + MatchRule( + type="signal", + interface=NM_SETTINGS_IFACE, + member="ConnectionRemoved", + path=NM_SETTINGS_PATH, + ) ) - # Filter for StateChanged signal - self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule)) + for rule in rules: + self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule)) - with self._conn_monitor.filter(rule, bufsize=SIGNAL_QUEUE_SIZE) as q: + with (self._conn_monitor.filter(rules[0], bufsize=SIGNAL_QUEUE_SIZE) as state_q, + self._conn_monitor.filter(rules[1], bufsize=SIGNAL_QUEUE_SIZE) as new_conn_q, + self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q): while not self._exit: if not self._active: time.sleep(1) continue - # Block until a matching signal arrives try: - msg = self._conn_monitor.recv_until_filtered(q, timeout=1) + self._conn_monitor.recv_messages(timeout=1) except TimeoutError: continue - new_state, previous_state, change_reason = msg.body + # Connection added/removed + if len(new_conn_q) or len(removed_conn_q): + new_conn_q.clear() + removed_conn_q.clear() + self._update_connections() - # BAD PASSWORD - if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): - self.forget_connection(self._connecting_to_ssid, block=True) - self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) - self._connecting_to_ssid = "" + # Device state changes + while len(state_q): + new_state, previous_state, change_reason = state_q.popleft().body - elif new_state == NMDeviceState.ACTIVATED: - if len(self._activated): - self._update_networks() - self._enqueue_callbacks(self._activated) - self._connecting_to_ssid = "" + # BAD PASSWORD + if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): + self.forget_connection(self._connecting_to_ssid, block=True) + self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) + self._connecting_to_ssid = "" - elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: - self._connecting_to_ssid = "" - self._enqueue_callbacks(self._forgotten) + elif new_state == NMDeviceState.ACTIVATED: + if len(self._activated): + self._update_networks() + self._enqueue_callbacks(self._activated) + self._connecting_to_ssid = "" + + elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: + self._connecting_to_ssid = "" + self._enqueue_callbacks(self._forgotten) def _network_scanner(self): while not self._exit: @@ -307,7 +333,7 @@ class WifiManager: cloudlog.exception(f"Error getting adapter type {adapter_type}: {e}") return None - def _get_connections(self) -> dict[str, str]: + def _update_connections(self) -> None: settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) known_connections = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'ListConnections')).body[0] @@ -323,7 +349,7 @@ class WifiManager: ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") if ssid != "": conns[ssid] = conn_path - return conns + self._connections = conns def _get_active_connections(self): return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] @@ -413,7 +439,7 @@ class WifiManager: def forget_connection(self, ssid: str, block: bool = False): def worker(): - conn_path = self._get_connections().get(ssid, None) + conn_path = self._connections.get(ssid, None) if conn_path is not None: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) @@ -429,7 +455,7 @@ class WifiManager: def activate_connection(self, ssid: str, block: bool = False): def worker(): - conn_path = self._get_connections().get(ssid, None) + conn_path = self._connections.get(ssid, None) if conn_path is not None: if self._wifi_device is None: cloudlog.warning("No WiFi device found") @@ -465,7 +491,7 @@ class WifiManager: def set_tethering_password(self, password: str): def worker(): - conn_path = self._get_connections().get(self._tethering_ssid, None) + conn_path = self._connections.get(self._tethering_ssid, None) if conn_path is None: cloudlog.warning('No tethering connection found') return @@ -490,7 +516,7 @@ class WifiManager: threading.Thread(target=worker, daemon=True).start() def _get_tethering_password(self) -> str: - conn_path = self._get_connections().get(self._tethering_ssid, None) + conn_path = self._connections.get(self._tethering_ssid, None) if conn_path is None: cloudlog.warning('No tethering connection found') return '' @@ -601,8 +627,7 @@ class WifiManager: # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - known_connections = self._get_connections() - networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] + networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks