from __future__ import annotations from dataclasses import dataclass from enum import Enum class Security(Enum): OPEN = "open" WEP = "wep" PSK = "psk" ENTERPRISE = "enterprise" @property def label(self) -> str: return { Security.OPEN: "Open", Security.WEP: "WEP", Security.PSK: "WPA", Security.ENTERPRISE: "Enterprise", }[self] class LinkState(Enum): IDLE = "idle" CONNECTING = "connecting" CONNECTED = "connected" DISCONNECTING = "disconnecting" class Connectivity(Enum): UNKNOWN = "unknown" LOCAL_ONLY = "local_only" LIMITED = "limited" PORTAL = "portal" FULL = "full" class ErrorCode(Enum): PASSWORD_REQUIRED = "password_required" WRONG_PASSWORD = "wrong_password" UNSUPPORTED_SECURITY = "unsupported_security" TIMEOUT = "timeout" BUSY = "busy" NOT_FOUND = "not_found" CANCELLED = "cancelled" UNAVAILABLE = "unavailable" FAILED = "failed" class NetworkError(Exception): def __init__(self, code: ErrorCode, detail: str = ""): super().__init__(f"{code.value}: {detail}" if detail else code.value) self.code = code self.detail = detail @dataclass(frozen=True) class WifiNetwork: network_id: str ssid: str security: Security strength: int state: LinkState = LinkState.IDLE saved: bool = False auto_connect: bool | None = None signal_dbm: float | None = None @property def is_connected(self) -> bool: return self.state is LinkState.CONNECTED @property def is_busy(self) -> bool: return self.state in (LinkState.CONNECTING, LinkState.DISCONNECTING) @dataclass(frozen=True) class LinkDetails: bssid: str | None = None frequency_mhz: int | None = None rx_mbps: float | None = None tx_mbps: float | None = None max_mbps: float | None = None @property def band(self) -> str | None: return band_label(self.frequency_mhz) @dataclass(frozen=True) class EthernetLink: interface: str connected: bool speed_mbps: int | None = None mac: str | None = None @dataclass(frozen=True) class Capabilities: forget: bool = False auto_connect: bool = False link_details: bool = False @dataclass(frozen=True) class WifiState: available: bool = False enabled: bool = False scanning: bool = False station: LinkState = LinkState.IDLE networks: tuple[WifiNetwork, ...] = () link: LinkDetails | None = None @dataclass(frozen=True) class NetworkSnapshot: backend: str wifi: WifiState ethernet: tuple[EthernetLink, ...] connectivity: Connectivity capabilities: Capabilities @property def active_network(self) -> WifiNetwork | None: return next((n for n in self.wifi.networks if n.is_connected), None) @property def pending_network(self) -> WifiNetwork | None: return next((n for n in self.wifi.networks if n.is_busy), None) @property def ethernet_connected(self) -> bool: return any(link.connected for link in self.ethernet) def strength_from_dbm(dbm: float) -> int: return max(0, min(100, int(2 * (dbm + 100)))) def signal_quality(strength: int) -> str: if strength >= 75: return "Excellent" if strength >= 50: return "Good" if strength >= 30: return "Fair" return "Weak" def signal_icon(strength: int) -> str: if strength >= 75: return "signal_wifi_4_bar" if strength >= 50: return "network_wifi_3_bar" if strength >= 25: return "network_wifi_2_bar" if strength > 0: return "network_wifi_1_bar" return "signal_wifi_0_bar" def band_label(frequency_mhz: int | None) -> str | None: if not frequency_mhz: return None if frequency_mhz < 3000: return "2.4 GHz" if frequency_mhz < 5925: return "5 GHz" return "6 GHz" def list_order(network: WifiNetwork) -> tuple: if network.is_connected: tier = 0 elif network.is_busy: tier = 1 elif network.saved: tier = 2 else: tier = 3 return (tier, -(network.strength // 10), network.ssid.casefold()) def connect_blocker( security: Security, saved: bool, passphrase: str | None ) -> ErrorCode | None: if saved or security is Security.OPEN: return None if security in (Security.WEP, Security.ENTERPRISE): return ErrorCode.UNSUPPORTED_SECURITY if not passphrase: return ErrorCode.PASSWORD_REQUIRED return None