feat(wifi): add DNS switcher to WiFi control center panel

This commit is contained in:
sankalp committed 2026-07-28 13:03:04 +05:45
1 parent b76ab78415
commit 1d66b57ad7
1 file changed
+177
+177
View File
@@ -6,6 +6,7 @@ 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.smooth_switch import SmoothSwitch
@@ -17,6 +18,14 @@ from utils.functions import (
)
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__(
@@ -239,6 +248,160 @@ class WifiNetworkSlot(Box):
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(Box):
def __init__(self, parent, show_back_button=True, network_service=None, **kwargs):
super().__init__(
@@ -352,6 +515,9 @@ class WifiConnections(Box):
on_clicked=self.open_network_settings,
)
# DNS Switcher
self.dns_switcher = DnsSwitcher()
self.children = [
CenterBox(
start_children=self.title,
@@ -366,6 +532,9 @@ class WifiConnections(Box):
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,
]
@@ -378,6 +547,8 @@ class WifiConnections(Box):
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)"""
@@ -385,6 +556,9 @@ class WifiConnections(Box):
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"""
@@ -729,3 +903,6 @@ class WifiConnections(Box):
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)