Files
Modus/src/window/controlcenter/wifi.py
T
sankalp beab79a405 refactor: extract shared mixins, fix cpu waste, resolve browser icons
- Extract CloseButtonRevealerMixin, PullToRefreshMixin, BasePlayerBoxStack
- Replace mpris.py 2s+5s D-Bus polling with Seeked signal subscription
- Remove redundant desktop/main.py os.listdir() poll (FileMonitor covers)
- Remove switcher/main.py 200ms capture timer, chain from frame-ready
- Resolve browser MPRIS icons via desktop file lookup, fallback to app name
- Fix CodeQL py/clear-text-storage by rounding lat/lon and stripping from cache
2026-07-31 01:35:25 +05:45

823 lines
30 KiB
Python

from fabric.utils import GLib, idle_add, logger
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.separator import Separator
from services import dns as dns_service
from services.network import NetworkClient
from shared.dialogs.sysauth_dialog import run_auth_dialog
from shared.widgets.pull_to_refresh import PullToRefreshMixin
from shared.widgets.smooth_switch import SmoothSwitch
from shared.window.animated_scrollwindow import AnimatedScrollable
from utils.functions import (
get_wifi_connecting_icon,
get_wifi_icon_for_strength,
spawn_detached,
)
from utils.gtk_utils import svg_file
DEFAULT_DNS_PROVIDERS = [
{"label": "Cloudflare", "primary": "1.1.1.1", "secondary": "1.0.0.1"},
{"label": "Google", "primary": "8.8.8.8", "secondary": "8.8.4.4"},
{"label": "OpenDNS", "primary": "208.67.222.222", "secondary": "208.67.220.220"},
{"label": "AdGuard", "primary": "94.140.14.14", "secondary": "94.140.15.15"},
{"label": "Quad9", "primary": "9.9.9.9", "secondary": "149.112.112.112"},
]
class WifiNetworkSlot(Box):
def __init__(
self, access_point, wifi_service, network_service=None, parent=None, **kwargs
):
super().__init__(name="wifi-network-slot", **kwargs)
self.access_point = access_point
self.wifi_service = wifi_service
self.network_service = network_service
self.parent = parent # Reference to control center
self.ssid = access_point.ssid
self.bssid = access_point.bssid
self.strength = access_point.strength
self.icon_name = access_point.icon
self.is_connected = access_point.is_active
self._anim_state = None # "connecting" | "disconnecting" | None
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)
self.wifi_icon_box = Box(
children=[self.dimage],
style_classes=["wifi-icon-box"],
)
if self.is_connected:
self.dimage.add_style_class("connected")
self.network_label = Label(
label=self.ssid, name="wifi-network-name", h_align="start", h_expand=True
)
# Create lock icon for secured networks
self.lock_icon = None
if self.access_point.requires_password:
self.lock_icon = Image(
icon_name="changes-prevent-symbolic",
size=12,
name="wifi-lock-icon",
)
start_box = Box(
orientation="h",
spacing=8,
)
start_box.children = [self.wifi_icon_box, self.network_label]
if self.lock_icon:
start_box.children.append(self.lock_icon)
self.children = [
Button(
child=start_box,
h_expand=True,
name="wifi-network-button",
on_clicked=lambda *_: self.toggle_connecting(),
)
]
self.on_changed()
def update_ap(self, access_point):
"""Update existing slot with new AP data without recreating it"""
self.access_point = access_point
self.bssid = access_point.bssid
self.strength = access_point.strength
self.is_connected = access_point.is_active
if access_point.is_active:
self.dimage.remove_style_class("connecting")
self.dimage.remove_style_class("disconnecting")
self._anim_state = None
if self._anim_state is None:
wifi_icon_path = get_wifi_icon_for_strength(self.strength)
self.dimage.set_from_file(wifi_icon_path)
self.on_changed()
def toggle_connecting(self):
is_currently_connected = self.access_point.is_active
if is_currently_connected:
connecting_icon = get_wifi_connecting_icon()
self.dimage.set_from_file(connecting_icon)
self.dimage.add_style_class("disconnecting")
self._anim_state = "disconnecting"
if self.network_service:
self.network_service.disconnect_wifi()
self.is_connected = False
GLib.timeout_add(500, lambda: self._reset_disconnect_state())
else:
is_saved = self.network_service and self.network_service.is_network_saved(
self.ssid
)
if self.access_point.requires_password and not is_saved:
self._show_password_dialog()
else:
connecting_icon = get_wifi_connecting_icon()
self.dimage.set_from_file(connecting_icon)
self.dimage.add_style_class("connecting")
self._anim_state = "connecting"
def on_open_connection_result(success, message):
if not self.get_parent():
return
if success:
GLib.timeout_add(500, lambda: self._reset_connect_state())
else:
self._reset_connect_state()
if message == "Wrong password":
self._show_password_dialog()
try:
if self.network_service:
self.network_service.connect_wifi(
self.access_point, callback=on_open_connection_result
)
except Exception as e:
logger.error(
f"[WiFi] Failed to connect to {self.access_point.ssid}: {e}"
)
self._reset_connect_state()
def _reset_disconnect_state(self):
"""Reset visual state after disconnect operation"""
self.dimage.remove_style_class("disconnecting")
self._anim_state = None
wifi_icon_path = get_wifi_icon_for_strength(self.strength)
self.dimage.set_from_file(wifi_icon_path)
self.on_changed()
return False
def _reset_connect_state(self):
"""Reset visual state after connect operation"""
self.dimage.remove_style_class("connecting")
self._anim_state = None
wifi_icon_path = get_wifi_icon_for_strength(self.strength)
self.dimage.set_from_file(wifi_icon_path)
self.on_changed()
return False
def on_changed(self, *_):
self.is_connected = self.access_point.is_active
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")
else:
self.wifi_icon_box.remove_style_class("wifi-icon-box-connected")
return
def _show_password_dialog(self):
"""Show the WiFi password dialog"""
if self.parent and hasattr(self.parent, "hide_controlcenter"):
self.parent.hide_controlcenter()
run_auth_dialog(
action_id=self.ssid,
message=f'Wi-Fi network "{self.ssid}" requires a password.',
icon_name="network-wireless-symbolic",
on_result=self._on_password_result,
)
def _on_password_result(self, password):
"""Handle password dialog result"""
if password is None:
self._reset_connect_state()
return
if password.strip():
connecting_icon = get_wifi_connecting_icon()
self.dimage.set_from_file(connecting_icon)
self.dimage.add_style_class("connecting")
self._anim_state = "connecting"
def on_connection_result(success, message):
"""Handle the connection result"""
if not self.get_parent():
return
if success:
self.is_connected = True
GLib.timeout_add(500, lambda: self._reset_connect_state())
else:
self._reset_connect_state()
self._show_connection_error(message)
self.on_changed()
try:
if self.network_service:
self.network_service.connect_wifi_with_password(
self.access_point, password, callback=on_connection_result
)
except Exception as e:
logger.error(
f"[WiFi] Failed to initiate connection to {self.access_point.ssid}: {e}"
)
self._reset_connect_state()
self._show_connection_error("Connection failed. Please try again.")
self.on_changed()
def _show_connection_error(self, message="Incorrect password. Please try again."):
"""Show connection error and re-prompt for password"""
run_auth_dialog(
action_id=self.ssid,
message=f'Wi-Fi network "{self.ssid}" requires a password.',
icon_name="network-wireless-symbolic",
on_result=self._on_password_result,
error_message=message,
)
return False # Don't repeat if called from GLib.timeout_add
class DnsSwitcher:
"""DNS provider switcher with predefined and manual options.
Builds two widgets: ``button`` (the header row) and ``content_box``
(the expandable panel). Both should be added as separate children
of the parent container, matching how Other Networks is structured.
"""
def __init__(self):
self._current_dns = []
self._provider_buttons = {}
self._is_expanded = False
self._dns_label = Label(
label="Automatic",
name="dns-current-label",
h_align="end",
)
self.button = Button(
child=CenterBox(
start_children=Label("DNS", h_align="start"),
end_children=self._dns_label,
),
name="wifi-other-button",
on_clicked=self._toggle_expanded,
)
self.content_box = Box(
orientation="vertical",
spacing=4,
name="dns-content",
)
self.content_box.set_visible(False)
auto_row = self._make_provider_row("Automatic (DHCP)", "", is_auto=True)
self.content_box.pack_start(auto_row, False, False, 0)
for provider in DEFAULT_DNS_PROVIDERS:
row = self._make_provider_row(
provider["label"],
f"{provider['primary']}, {provider['secondary']}",
is_auto=False,
primary=provider["primary"],
secondary=provider["secondary"],
)
self.content_box.pack_start(row, False, False, 0)
self._load_current_dns()
def _make_provider_row(
self,
label,
subtitle,
is_auto=False,
primary=None,
secondary=None,
):
check_icon = Image(
icon_name="object-select-symbolic",
size=14,
name="dns-check-icon",
)
check_icon.set_no_show_all(True)
check_icon.hide()
title_label = Label(
label=label, h_align="start", h_expand=True, name="dns-provider-name"
)
children = [title_label]
if subtitle:
children.append(
Label(
label=subtitle,
h_align="end",
name="dns-provider-address",
)
)
children.append(check_icon)
row_button = Button(
child=Box(orientation="h", spacing=8, children=children),
name="dns-provider-button",
)
if is_auto:
row_button.connect("clicked", lambda *_: self._on_auto_selected())
else:
row_button.connect(
"clicked",
lambda *_: self._on_provider_selected(primary, secondary),
)
row_button._dns_check = check_icon
row_button._dns_primary = primary
self._provider_buttons[primary or "auto"] = row_button
return row_button
def _toggle_expanded(self, *_):
self._is_expanded = not self._is_expanded
self.content_box.set_visible(self._is_expanded)
def _load_current_dns(self):
dns_service.get_dns(callback=self._on_dns_loaded)
def _on_dns_loaded(self, dns_servers):
self._current_dns = dns_servers or []
self._update_checkmarks()
self._update_dns_label()
def _update_checkmarks(self):
key = self._current_dns[0] if self._current_dns else "auto"
for btn_key, btn in self._provider_buttons.items():
check = getattr(btn, "_dns_check", None)
if check:
if btn_key == key:
check.show()
else:
check.hide()
def _update_dns_label(self):
if not self._current_dns:
self._dns_label.set_label("Automatic")
elif len(self._current_dns) == 1:
self._dns_label.set_label(self._current_dns[0])
else:
self._dns_label.set_label(
f"{self._current_dns[0]} +{len(self._current_dns) - 1}"
)
def _on_auto_selected(self):
self._current_dns = []
self._update_checkmarks()
self._update_dns_label()
dns_service.set_dns([], callback=self._on_dns_applied)
def _on_provider_selected(self, primary, secondary):
dns = [primary]
if secondary:
dns.append(secondary)
self._current_dns = dns
self._update_checkmarks()
self._update_dns_label()
dns_service.set_dns(dns, callback=self._on_dns_applied)
def _on_dns_applied(self, success):
if not success:
self._load_current_dns()
def refresh_dns(self):
self._load_current_dns()
class WifiConnections(PullToRefreshMixin, Box):
def __init__(self, parent, show_back_button=True, network_service=None, **kwargs):
super().__init__(
spacing=8,
orientation="vertical",
name="wifi-connections",
**kwargs,
)
self.parent = parent
self.network_service = network_service or NetworkClient()
self.wifi_service = None
self.is_scanning = False # Track scanning state
self._update_in_progress = False # Prevent concurrent updates
self._destroyed = False # Track if widget is destroyed
self._signal_ids = [] # Track all service signal IDs for cleanup
self._network_ready_fired = False # Guard against duplicate device-ready
self._pending_toggle = None # Force toggle to target after stale revert
if self.network_service and self.network_service.wifi_device:
idle_add(self.on_network_ready)
else:
self._signal_ids.append(
(
self.network_service,
self.network_service.connect("device-ready", self.on_network_ready),
)
)
# Create pull-to-refresh indicator
self.refresh_indicator = Label(
name="wifi-refresh-indicator",
label="↓ Pull to scan for networks",
h_align="center",
visible=False,
style="color: #fff; font-size: 12px; padding: 5px;",
)
title_children = []
if show_back_button:
title_children.append(
Button(
image=svg_file("misc/chevron-left.svg", size=10),
on_clicked=lambda *_: self.parent.close_wifi(),
)
)
title_children.append(Label("Wi-Fi", name="wifi-title"))
self.title = Box(
orientation="h",
children=title_children,
)
self.toggle_button = SmoothSwitch(
name="toggle-button",
on_user_toggle=lambda active: self.on_toggle_changed(
self.toggle_button, active
),
)
# Create Known Network section
self.known_networks_label = Label(
label="Known Network", h_align="start", name="networks-title"
)
self.known_networks = Box(
spacing=4, orientation="vertical", name="known-networks"
)
self.known_networks_scrolled = AnimatedScrollable(
min_content_size=(303, 0),
max_content_size=(303, 200),
child=self.known_networks,
overlay_scroll=True,
animate=False,
)
# Create "No networks available" message
self.no_networks_label = Label(
label="No networks available",
h_align="center",
name="no-networks-label",
visible=False,
)
self.other_networks_button = Button(
child=CenterBox(
start_children=Label("Other Networks", h_align="start"),
end_children=self.refresh_indicator,
),
name="wifi-other-button",
on_clicked=self.toggle_other_networks,
)
self.other_networks = Box(spacing=4, orientation="vertical")
# Create scrolled window for other networks
self.other_networks_scrolled = AnimatedScrollable(
min_content_size=(303, 0),
max_content_size=(303, 300),
child=self.other_networks,
overlay_scroll=True,
animate=False,
)
self.other_networks.set_visible(False)
# Add pull-to-refresh functionality to scrolled window
self.setup_pull_to_refresh(self.other_networks_scrolled)
# Create More Settings button (same style as Other Networks button)
self.more_settings_button = Button(
child=Label("More Settings", h_align="start"),
name="wifi-other-button",
on_clicked=self.open_network_settings,
)
# DNS Switcher
self.dns_switcher = DnsSwitcher()
self.children = [
CenterBox(
start_children=self.title,
end_children=self.toggle_button,
name="wifi-widget-top",
),
Separator(orientation="h", name="separator"),
self.known_networks_label,
self.known_networks_scrolled,
self.no_networks_label,
Separator(orientation="h", name="separator"),
self.other_networks_button,
self.other_networks_scrolled,
Separator(orientation="h", name="separator"),
self.dns_switcher.button,
self.dns_switcher.content_box,
Separator(orientation="h", name="separator"),
self.more_settings_button,
]
self.connect("destroy", self.on_destroy)
self.connect("unmap", self.on_hide)
self.connect("map", self.on_show)
def on_show(self, *_):
"""Called when the widget is mapped (popup opened)"""
if self.wifi_service:
self.wifi_service.scan()
self.force_network_refresh()
if self.dns_switcher:
self.dns_switcher.refresh_dns()
def on_hide(self, *_):
"""Called when the widget is hidden (popup closed)"""
self._cancel_pending_refresh()
if self.other_networks.get_visible():
self.other_networks.set_visible(False)
self.other_networks_scrolled.snap_to_size(0)
if self.dns_switcher and self.dns_switcher._is_expanded:
self.dns_switcher._is_expanded = False
self.dns_switcher.content_box.set_visible(False)
def toggle_other_networks(self, *_):
"""Toggle the visibility of other networks section"""
current_state = self.other_networks.get_visible()
self._cancel_pending_refresh()
if current_state:
self.other_networks.set_visible(False)
self.other_networks_scrolled.snap_to_size(0)
else:
self.other_networks.set_visible(True)
if self.wifi_service:
self.wifi_service.scan()
# Defer refresh until after the height animation completes
idle_add(self._refresh_after_animation)
def on_network_ready(self, *_):
"""Called when network service is ready"""
if self._network_ready_fired and self.wifi_service:
return
wifi = self.network_service.wifi_device
if not wifi:
return
self._network_ready_fired = True
self.wifi_service = wifi
# 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(
(
self.wifi_service,
self.wifi_service.connect(
"notify::enabled", self.on_wifi_enabled_changed
),
)
)
self._signal_ids.append(
(
self.wifi_service,
self.wifi_service.connect("changed", self.update_networks),
)
)
# 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:
target = toggle_button.get_active()
self._pending_toggle = target
self.wifi_service.enabled = target
self.toggle_button.set_active(target)
def on_wifi_enabled_changed(self, *_):
"""Handle WiFi enabled state changes"""
if self.wifi_service:
if self._pending_toggle is not None:
self.toggle_button.set_active(self._pending_toggle)
self._pending_toggle = None
else:
self.toggle_button.set_active(self.wifi_service.enabled)
def open_network_settings(self, *_):
"""Open NetworkManager connection editor"""
try:
spawn_detached(["nm-connection-editor"])
if self.parent and hasattr(self.parent, "hide_controlcenter"):
self.parent.hide_controlcenter()
except FileNotFoundError as e:
logger.warning(
f"[wifi] spawn_detached(['nm-connection-editor']) failed: {e}"
)
except Exception as e:
logger.error(f"[WiFi] Failed to open network settings: {e}")
def update_networks(self, *_):
"""Update the list of available networks"""
# Prevent concurrent updates and check if destroyed
if self._update_in_progress or self._destroyed or not self.wifi_service:
return
self._update_in_progress = True
try:
# When WiFi is disabled, clear all network slots
if not self.wifi_service.enabled:
for child in self.known_networks.get_children():
self.known_networks.remove(child)
for child in self.other_networks.get_children():
self.other_networks.remove(child)
self.known_networks_scrolled.set_visible(True)
self.no_networks_label.set_visible(True)
self.other_networks_button.set_visible(True)
return
access_points = self.wifi_service.access_points
known_networks = []
other_networks = []
for access_point in access_points:
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)
# 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 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
if slot._anim_state is None:
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
if slot._anim_state is None:
slot.dimage.set_from_file(get_wifi_icon_for_strength(0))
if not self._destroyed:
has_known_networks = len(known_networks) > 0
has_other_networks = len(other_networks) > 0
has_any_networks = has_known_networks or has_other_networks
self.known_networks_scrolled.set_visible(True)
self.no_networks_label.set_visible(not has_any_networks)
self.other_networks_button.set_visible(True) # Always visible
self.refresh_network_states()
if self.other_networks.get_visible():
other_children = self.other_networks.get_children()
if other_children:
nat_h = self.other_networks.get_preferred_height()[1]
if nat_h > 0:
self.other_networks_scrolled.snap_to_size(min(nat_h, 300))
except Exception as e:
logger.error(f"[WiFi] Error during update_networks: {e}")
finally:
self._update_in_progress = False
def _is_saved_network(self, access_point):
"""Check if a network is saved/known using NetworkManager connections"""
if not self.network_service:
return False
ssid = access_point.ssid
if not ssid or ssid == "Unknown":
return False
return self.network_service.is_network_saved(ssid)
def refresh_network_states(self, *_):
"""Refresh connection states for all network slots"""
for child in self.known_networks.get_children():
if hasattr(child, "on_changed"):
child.on_changed()
for child in self.other_networks.get_children():
if hasattr(child, "on_changed"):
child.on_changed()
def force_network_refresh(self):
"""Force an immediate refresh of the network list"""
if self._update_in_progress or self._destroyed:
return
try:
self.update_networks()
except Exception as e:
logger.error(f"[WiFi] Error during forced network refresh: {e}")
def _trigger_scan(self):
if self.wifi_service:
self.wifi_service.scan()
def _trigger_refresh(self):
self.force_network_refresh()
def _get_pull_normal_label(self):
return "↓ Pull to scan for networks"
def _get_pull_ready_label(self):
return "↑ Release to scan"
def on_destroy(self, widget):
"""Cleanup when widget is destroyed"""
self._destroyed = True
self._cancel_pending_refresh()
for obj, sig_id in self._signal_ids:
try:
obj.disconnect(sig_id)
except Exception as e:
logger.error(f"[WiFi] Failed to disconnect signal {sig_id}: {e}")
self._signal_ids.clear()
self.wifi_service = None
self.network_service = None
def close_wifi(self):
"""Called when WiFi panel is being closed"""
if self.other_networks.get_visible():
self.other_networks.set_visible(False)
self.other_networks_scrolled.snap_to_size(0)
if self.dns_switcher and self.dns_switcher._is_expanded:
self.dns_switcher._is_expanded = False
self.dns_switcher.content_box.set_visible(False)