refactor(network): replace nmcli with native D-Bus for Wi-Fi management

This commit fully migrates the Python network service away from external wlan0: connected to Amar 2GNet
	"MEDIATEK MT7921 802.11ax"
	wifi (mt7921e), 2C:98:11:76:9B:2B, hw, mtu 1500
	ip4 default, ip6 default
	inet4 192.168.18.70/24
	route4 192.168.18.0/24 metric 600
	route4 default via 192.168.18.1 metric 600
	inet6 2405:acc0:1306:351c:ac94:2135:f9a8:5c89/64
	inet6 fe80::4057:8edd:a684:5e33/64
	route6 fe80::/64 metric 1024
	route6 2405:acc0:1306:351c::/64 metric 600
	route6 default via fe80::1 metric 600

lo: connected (externally) to lo
	"lo"
	loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536
	inet4 127.0.0.1/8
	inet6 ::1/128

docker0: connected (externally) to docker0
	"docker0"
	bridge, 0A:89:72:7F:4D:75, sw, mtu 1500
	inet4 172.17.0.1/16
	route4 172.17.0.0/16 metric 0

p2p-dev-wlan0: disconnected
	"p2p-dev-wlan0"
	wifi-p2p, hw

enp2s0: unavailable
	"Realtek RTL8111/8168/8211/8411"
	ethernet (r8169), 40:C2:BA:53:D5:E4, hw, mtu 1500

DNS configuration:
	servers: 192.168.18.1
	interface: wlan0

	servers: fe80::1
	interface: wlan0

Use "nmcli device show" to get complete information about known devices and
"nmcli connection show" to get an overview on active connection profiles.

Consult nmcli(1) and nmcli-examples(7) manual pages for complete usage details.
subprocess calls, implementing native D-Bus communication for a more robust and
integrated architecture.

Key changes:
- :
  - Implemented native D-Bus methods for , , and .
  - Updated  to track , , and  state.
  - Fixed a bug where D-Bus  returned a tuple, preventing known networks from being parsed correctly.
- :
  - Wired UI events to the new D-Bus connection methods.
  - Fixed a race condition in  initialization where it missed the synchronous  signal, leaving the UI permanently disabled.
  - Removed the  early-return optimization in  that was preventing the active CSS state from applying to already-known networks.
  - Cleaned up unused variables to satisfy  linting and removed redundant  blocks.
- :
  - Removed the 5-second timeout hack, as the new D-Bus service properly fires callbacks upon connection success/failure.
  - Removed hardcoded WPA2 text to make the dialog security-type agnostic.
- :
  - Cleaned up active state styling so only the SVG icon box highlights (using ) when a network is connected.
This commit is contained in:
sankalp committed 2026-07-21 14:33:35 +05:45
1 parent 8ac26d6c26
commit d97477b2b9
4 files changed
+1056 -505

No files matched your search

+960 -366
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,6 +1,6 @@
from enum import Enum, auto
from fabric.utils import Gdk, GLib, Gtk, logger
from fabric.utils import Gdk, GLib, Gtk, idle_add, logger
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -518,7 +518,7 @@ class BluetoothConnections(Box):
):
self.client.scan()
# Defer refresh until after the height animation completes
GLib.idle_add(self._refresh_after_animation)
idle_add(self._refresh_after_animation)
def _cancel_pending_refresh(self):
if hasattr(self, "_anim_finished_handler") and self._anim_finished_handler:
+1 -17
View File
@@ -767,23 +767,7 @@ class ModusControlCenter(AppletWindow):
def toggle_flight_mode(self, *_):
try:
self.flight_mode = not self.flight_mode
if self.flight_mode:
# Turn off WiFi and Bluetooth
if self.wifi_service:
self.wifi_service.wireless_enabled = False
if hasattr(self, "bluetooth_man") and hasattr(
self.bluetooth_man, "client"
):
self.bluetooth_man.client.set_enabled(False)
else:
# Turn on WiFi and Bluetooth
if self.wifi_service:
self.wifi_service.wireless_enabled = True
if hasattr(self, "bluetooth_man") and hasattr(
self.bluetooth_man, "client"
):
self.bluetooth_man.client.set_enabled(True)
self.network_service.set_airplane_mode(self.flight_mode)
# Update icon
self.flight_icon.dynamic_file(
+93 -120
View File
@@ -1,4 +1,4 @@
from fabric.utils import Gdk, GLib, logger
from fabric.utils import Gdk, GLib, idle_add, logger
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -35,9 +35,8 @@ class WifiNetworkSlot(Box):
self.is_connected = access_point.is_active
self.styles = [
"connected" if self.is_connected else "",
]
if self.is_connected:
self.add_style_class("connected")
wifi_icon_path = get_wifi_icon_for_strength(self.strength)
self.dimage = svg_file(wifi_icon_path, size=28)
@@ -108,15 +107,8 @@ class WifiNetworkSlot(Box):
self.dimage.set_from_file(connecting_icon)
self.dimage.add_style_class("disconnecting")
if self.wifi_service and self.wifi_service._device:
spawn_detached(
[
"nmcli",
"device",
"disconnect",
self.wifi_service._device.get_iface(),
]
)
if self.network_service:
self.network_service.disconnect_wifi()
self.is_connected = False
GLib.timeout_add(500, lambda: self._reset_disconnect_state())
else:
@@ -150,8 +142,8 @@ class WifiNetworkSlot(Box):
try:
if self.network_service:
self.network_service.connect_wifi_bssid(
self.access_point.bssid, callback=on_open_connection_result
self.network_service.connect_wifi(
self.access_point, callback=on_open_connection_result
)
except Exception as e:
logger.error(
@@ -180,9 +172,10 @@ class WifiNetworkSlot(Box):
def on_changed(self, *_):
self.is_connected = self.access_point.is_active
self.styles = [
"connected" if self.is_connected else "",
]
if self.is_connected:
self.add_style_class("connected")
else:
self.remove_style_class("connected")
if self.is_connected:
self.wifi_icon_box.add_style_class("wifi-icon-box-connected")
@@ -220,12 +213,7 @@ class WifiNetworkSlot(Box):
GLib.timeout_add(500, lambda: self._reset_connect_state())
if (
self.password_dialog
and self.password_dialog.connection_timeout_id
):
GLib.source_remove(self.password_dialog.connection_timeout_id)
self.password_dialog.connection_timeout_id = None
if self.password_dialog:
self.password_dialog.is_connecting = False
else:
self._reset_connect_state()
@@ -237,7 +225,7 @@ class WifiNetworkSlot(Box):
try:
if self.network_service:
self.network_service.connect_wifi_with_password(
self.access_point.bssid, password, callback=on_connection_result
self.access_point, password, callback=on_connection_result
)
except Exception as e:
logger.error(
@@ -278,8 +266,8 @@ class WifiConnections(Box):
self._network_ready_fired = False # Guard against duplicate device-ready
self._pending_toggle = None # Force toggle to target after stale revert
if network_service and network_service.wifi_device:
self.on_network_ready()
if self.network_service and self.network_service.wifi_device:
idle_add(self.on_network_ready)
else:
self._signal_ids.append(
(
@@ -411,7 +399,7 @@ class WifiConnections(Box):
if self.wifi_service:
self.wifi_service.scan()
# Defer refresh until after the height animation completes
GLib.idle_add(self._refresh_after_animation)
idle_add(self._refresh_after_animation)
def _cancel_pending_refresh(self):
if hasattr(self, "_anim_finished_handler") and self._anim_finished_handler:
@@ -449,7 +437,16 @@ class WifiConnections(Box):
return
self._network_ready_fired = True
self.wifi_service = wifi
self.toggle_button.set_active(self.wifi_service.enabled)
# Defer the initial sync: the D-Bus cached property may not yet be
# populated when device-ready fires inside _init_devices, causing
# enabled to read as False even when WiFi is on.
idle_add(
lambda: (
self.toggle_button.set_active(self.wifi_service.enabled)
if self.wifi_service
else False
)
)
self._signal_ids.append(
(
@@ -469,6 +466,9 @@ class WifiConnections(Box):
# Initial network update
self.update_networks()
# Trigger initial scan so AP list is populated
self.wifi_service.scan()
def on_toggle_changed(self, toggle_button, *_):
"""Handle WiFi toggle button changes"""
if self.wifi_service:
@@ -508,109 +508,82 @@ class WifiConnections(Box):
self._update_in_progress = True
try:
current_known_ssids = {
child.ssid
for child in self.known_networks.get_children()
if hasattr(child, "ssid")
}
current_other_ssids = {
child.ssid
for child in self.other_networks.get_children()
if hasattr(child, "ssid")
}
access_points = self.wifi_service.access_points
known_networks = []
other_networks = []
new_known_ssids = set()
new_other_ssids = set()
for access_point in access_points:
try:
if access_point.ssid and access_point.ssid != "Unknown":
if access_point.is_active or self._is_saved_network(
access_point
):
known_networks.append(access_point)
new_known_ssids.add(access_point.ssid)
else:
other_networks.append(access_point)
new_other_ssids.add(access_point.ssid)
except Exception as e:
logger.warning(
f"[WiFi] Failed to process access point {access_point}: {e}"
)
continue
if access_point.ssid and access_point.ssid != "Unknown":
if access_point.is_active or self._is_saved_network(access_point):
known_networks.append(access_point)
else:
other_networks.append(access_point)
known_changed = current_known_ssids != new_known_ssids
other_changed = current_other_ssids != new_other_ssids
# Get existing networks
existing_known = {
child.ssid: child for child in self.known_networks.get_children()
}
existing_other = {
child.ssid: child for child in self.other_networks.get_children()
}
if known_changed or other_changed:
# Get existing networks
existing_known = {
child.ssid: child for child in self.known_networks.get_children()
}
existing_other = {
child.ssid: child for child in self.other_networks.get_children()
}
for access_point in known_networks:
if not self._destroyed:
if access_point.ssid in existing_known:
slot = existing_known.pop(access_point.ssid)
slot.update_ap(access_point)
elif access_point.ssid in existing_other:
slot = existing_other.pop(access_point.ssid)
slot.update_ap(access_point)
self.other_networks.remove(slot)
self.known_networks.add(slot)
else:
network_slot = WifiNetworkSlot(
access_point,
self.wifi_service,
network_service=self.network_service,
parent=self.parent,
)
self.known_networks.add(network_slot)
for access_point in known_networks:
if not self._destroyed:
if access_point.ssid in existing_known:
slot = existing_known.pop(access_point.ssid)
slot.update_ap(access_point)
elif access_point.ssid in existing_other:
slot = existing_other.pop(access_point.ssid)
slot.update_ap(access_point)
self.other_networks.remove(slot)
self.known_networks.add(slot)
else:
network_slot = WifiNetworkSlot(
access_point,
self.wifi_service,
network_service=self.network_service,
parent=self.parent,
)
self.known_networks.add(network_slot)
for access_point in other_networks:
if not self._destroyed:
if access_point.ssid in existing_other:
slot = existing_other.pop(access_point.ssid)
slot.update_ap(access_point)
elif access_point.ssid in existing_known:
slot = existing_known.pop(access_point.ssid)
slot.update_ap(access_point)
self.known_networks.remove(slot)
self.other_networks.add(slot)
else:
network_slot = WifiNetworkSlot(
access_point,
self.wifi_service,
network_service=self.network_service,
parent=self.parent,
)
self.other_networks.add(network_slot)
for access_point in other_networks:
if not self._destroyed:
if access_point.ssid in existing_other:
slot = existing_other.pop(access_point.ssid)
slot.update_ap(access_point)
elif access_point.ssid in existing_known:
slot = existing_known.pop(access_point.ssid)
slot.update_ap(access_point)
self.known_networks.remove(slot)
self.other_networks.add(slot)
else:
network_slot = WifiNetworkSlot(
access_point,
self.wifi_service,
network_service=self.network_service,
parent=self.parent,
)
self.other_networks.add(network_slot)
for slot in existing_known.values():
if hasattr(slot, "access_point"):
slot.strength = 0
from shared.widgets.wifi_icon import get_wifi_icon_for_strength
for slot in existing_known.values():
if hasattr(slot, "access_point"):
slot.strength = 0
from shared.widgets.wifi_icon import get_wifi_icon_for_strength
if not slot.dimage.has_style_class(
"connecting"
) and not slot.dimage.has_style_class("disconnecting"):
slot.dimage.set_from_file(get_wifi_icon_for_strength(0))
if not slot.dimage.has_style_class(
"connecting"
) and not slot.dimage.has_style_class("disconnecting"):
slot.dimage.set_from_file(get_wifi_icon_for_strength(0))
for slot in existing_other.values():
if hasattr(slot, "access_point"):
slot.strength = 0
from shared.widgets.wifi_icon import get_wifi_icon_for_strength
for slot in existing_other.values():
if hasattr(slot, "access_point"):
slot.strength = 0
from shared.widgets.wifi_icon import get_wifi_icon_for_strength
if not slot.dimage.has_style_class(
"connecting"
) and not slot.dimage.has_style_class("disconnecting"):
slot.dimage.set_from_file(get_wifi_icon_for_strength(0))
if not slot.dimage.has_style_class(
"connecting"
) and not slot.dimage.has_style_class("disconnecting"):
slot.dimage.set_from_file(get_wifi_icon_for_strength(0))
if not self._destroyed:
has_known_networks = len(known_networks) > 0