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:
4 files changed
+1056
-505
No files matched your search
+960
-366
File diff suppressed because it is too large.
Load diff
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user