archive
This commit is contained in:
182 files changed
+18062
-277
No files matched your search
@@ -0,0 +1,465 @@
|
||||
from collections.abc import Iterator
|
||||
|
||||
from fabric.utils import Gdk, GLib, idle_add, logger, remove_handler
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.image import Image
|
||||
from fabric.widgets.label import Label
|
||||
|
||||
from modules.launcher.base import LauncherPlugin
|
||||
from services import NetworkClient
|
||||
|
||||
|
||||
class NetworkManagerPlugin(LauncherPlugin):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "network"
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return "network-workgroup-symbolic"
|
||||
|
||||
@property
|
||||
def keywords(self) -> list[str]:
|
||||
return ["net"]
|
||||
|
||||
def __init__(self, handler):
|
||||
super().__init__(handler)
|
||||
self._query_handler: int = 0
|
||||
self.network_client = NetworkClient()
|
||||
|
||||
self._items: list[dict] = []
|
||||
self._awaiting_password: bool = False
|
||||
self._pending_ssid: str | None = None
|
||||
self._current_search_text: str = ""
|
||||
self._signal_refresh_handler: int = 0
|
||||
self._is_active: bool = False
|
||||
self._signal_connections: list[int] = []
|
||||
|
||||
self._wire_signals()
|
||||
|
||||
def on_search(self, text: str) -> None:
|
||||
self.query_networks(text)
|
||||
|
||||
def stop(self):
|
||||
if self._query_handler:
|
||||
remove_handler(self._query_handler)
|
||||
self._query_handler = 0
|
||||
if self._signal_refresh_handler:
|
||||
GLib.source_remove(self._signal_refresh_handler)
|
||||
self._signal_refresh_handler = 0
|
||||
|
||||
if self.network_client and self._signal_connections:
|
||||
for connection_id in self._signal_connections:
|
||||
self.network_client.disconnect(connection_id)
|
||||
self._signal_connections.clear()
|
||||
|
||||
def _wire_signals(self) -> None:
|
||||
self._signal_connections.append(
|
||||
self.network_client.connect("changed", self._on_network_changed)
|
||||
)
|
||||
wifi_device = self.network_client.wifi_device
|
||||
if wifi_device:
|
||||
self._signal_connections.append(
|
||||
wifi_device.connect("changed", self._on_network_changed)
|
||||
)
|
||||
self._signal_connections.append(
|
||||
wifi_device.connect("ap-added", self._on_network_changed)
|
||||
)
|
||||
self._signal_connections.append(
|
||||
wifi_device.connect("ap-removed", self._on_network_changed)
|
||||
)
|
||||
|
||||
def _on_network_changed(self, *args, **kwargs):
|
||||
if self._awaiting_password or not self._is_active:
|
||||
return
|
||||
if self._signal_refresh_handler:
|
||||
return
|
||||
self._signal_refresh_handler = GLib.timeout_add(250, self._refresh_timeout)
|
||||
|
||||
def _refresh_timeout(self) -> bool:
|
||||
self._signal_refresh_handler = 0
|
||||
if self._is_active or self._awaiting_password:
|
||||
self._refresh_query()
|
||||
return False
|
||||
|
||||
def query_networks(self, text: str) -> None:
|
||||
self._is_active = True
|
||||
self.handler.start("networks")
|
||||
self._current_search_text = text
|
||||
|
||||
if text.lower() == "scan":
|
||||
self._perform_scan()
|
||||
return
|
||||
|
||||
self._items = self._get_network_items(text)
|
||||
if not self._items:
|
||||
self._show_no_connections()
|
||||
return
|
||||
|
||||
self._query_handler = idle_add(
|
||||
self._bake_next_slot, iter(self._items), pin=True
|
||||
)
|
||||
|
||||
def _get_network_items(self, search_text: str) -> list[dict]:
|
||||
items = []
|
||||
wifi_device = self.network_client.wifi_device
|
||||
|
||||
if not wifi_device:
|
||||
return items
|
||||
|
||||
for ap in wifi_device.access_points:
|
||||
if search_text and not ap.ssid.lower().startswith(search_text.lower()):
|
||||
continue
|
||||
if not ap.ssid or ap.ssid == "Unknown":
|
||||
continue
|
||||
items.append(
|
||||
{
|
||||
"type": "ap",
|
||||
"name": ap.ssid,
|
||||
"ap": ap,
|
||||
"device": wifi_device._device,
|
||||
"active": ap.is_active,
|
||||
}
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
def _perform_scan(self) -> None:
|
||||
wifi_device = self.network_client.wifi_device
|
||||
if not wifi_device:
|
||||
self._show_no_wifi_device()
|
||||
return
|
||||
|
||||
self._show_scanning_feedback()
|
||||
wifi_device.scan()
|
||||
GLib.timeout_add(2000, self._scan_complete_callback)
|
||||
|
||||
def _show_scanning_feedback(self) -> None:
|
||||
"""Show feedback that scanning is in progress."""
|
||||
self.handler.slot_ready(
|
||||
Button(
|
||||
style_classes="app-slot network-scanning",
|
||||
child=Box(
|
||||
orientation="h",
|
||||
spacing=12,
|
||||
children=[
|
||||
Image(icon_name="network-wireless-acquiring-symbolic", size=32),
|
||||
Label(
|
||||
label="Scanning for networks...",
|
||||
style_classes="network-scanning-text",
|
||||
h_align="center",
|
||||
),
|
||||
],
|
||||
),
|
||||
tooltip_text="Scanning for available WiFi networks",
|
||||
),
|
||||
"networks",
|
||||
)
|
||||
self.handler.done()
|
||||
|
||||
def _show_no_wifi_device(self) -> None:
|
||||
"""Show message when no WiFi device is available."""
|
||||
self.handler.slot_ready(
|
||||
Button(
|
||||
style_classes="app-slot network-no-wifi",
|
||||
child=Box(
|
||||
orientation="h",
|
||||
spacing=12,
|
||||
children=[
|
||||
Image(icon_name="network-wireless-offline-symbolic", size=32),
|
||||
Label(
|
||||
label="No WiFi device available",
|
||||
style_classes="network-no-wifi-text",
|
||||
h_align="center",
|
||||
),
|
||||
],
|
||||
),
|
||||
tooltip_text="WiFi device not found or disabled",
|
||||
),
|
||||
"networks",
|
||||
)
|
||||
self.handler.done()
|
||||
|
||||
def _scan_complete_callback(self) -> bool:
|
||||
"""Callback after scan completion to refresh the network list."""
|
||||
if self._is_active and self._current_search_text.lower() == "scan":
|
||||
# Clear the scanning feedback and show updated results
|
||||
if hasattr(self.handler, "clear_viewport"):
|
||||
self.handler.clear_viewport()
|
||||
# Show all available networks after scan (don't clear input to preserve /n prefix)
|
||||
self._current_search_text = ""
|
||||
self._items = self._get_network_items("")
|
||||
if not self._items:
|
||||
self._show_no_connections()
|
||||
else:
|
||||
self._query_handler = idle_add(
|
||||
self._bake_next_slot, iter(self._items), pin=True
|
||||
)
|
||||
return False
|
||||
|
||||
def _refresh_query(self) -> None:
|
||||
if self._awaiting_password or not self._is_active:
|
||||
return
|
||||
if self._query_handler:
|
||||
remove_handler(self._query_handler)
|
||||
self._query_handler = 0
|
||||
if hasattr(self.handler, "clear_viewport"):
|
||||
self.handler.clear_viewport()
|
||||
self.query_networks(self._current_search_text)
|
||||
|
||||
def _show_no_connections(self) -> None:
|
||||
self.handler.slot_ready(
|
||||
Button(
|
||||
style_classes="app-slot network-no-connections",
|
||||
child=Box(
|
||||
orientation="h",
|
||||
spacing=12,
|
||||
children=[
|
||||
Image(icon_name="network-offline-symbolic", size=32),
|
||||
Label(
|
||||
label="No connections found",
|
||||
style_classes="network-no-connections-text",
|
||||
h_align="center",
|
||||
),
|
||||
],
|
||||
),
|
||||
tooltip_text="No NetworkManager connections available",
|
||||
),
|
||||
"networks",
|
||||
)
|
||||
self.handler.done()
|
||||
|
||||
def _bake_next_slot(self, iterator: Iterator[dict]) -> bool:
|
||||
item = next(iterator, None)
|
||||
if item is None:
|
||||
idle_add(self.handler.done)
|
||||
return False
|
||||
self._create_slot(item)
|
||||
return True
|
||||
|
||||
def _create_slot(self, item: dict) -> None:
|
||||
ap = item["ap"]
|
||||
name = ap.ssid
|
||||
is_active = ap.is_active
|
||||
strength = ap.strength
|
||||
requires_password = ap.requires_password
|
||||
|
||||
icon_name = self._get_wifi_icon(strength)
|
||||
subtitle = f"Signal: {strength}%"
|
||||
if requires_password:
|
||||
subtitle = f"Secured • {subtitle}"
|
||||
if is_active:
|
||||
subtitle = f"Connected • {subtitle}"
|
||||
|
||||
slot_content = Box(
|
||||
orientation="h",
|
||||
spacing=12,
|
||||
children=[
|
||||
Image(icon_name=icon_name, h_align="start", size=32),
|
||||
Box(
|
||||
orientation="v",
|
||||
children=[
|
||||
Label(
|
||||
label=("* " + name) if is_active else name,
|
||||
style_classes="network-name",
|
||||
h_align="start",
|
||||
),
|
||||
Label(
|
||||
label=subtitle,
|
||||
style_classes="network-subtitle",
|
||||
h_align="start",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
slot_widget = Button(
|
||||
style_classes="app-slot network-slot",
|
||||
child=slot_content,
|
||||
tooltip_text=self._get_tooltip_text(is_active, name),
|
||||
on_clicked=lambda *_: self._handle_slot_click(item),
|
||||
)
|
||||
|
||||
slot_widget.connect(
|
||||
"key-press-event",
|
||||
lambda widget, event, item=item: self._on_slot_key_press(
|
||||
widget, event, item
|
||||
),
|
||||
)
|
||||
|
||||
self.handler.slot_ready(slot_widget, "networks")
|
||||
|
||||
def _get_tooltip_text(self, is_active: bool, ssid: str) -> str:
|
||||
if is_active:
|
||||
return (
|
||||
f"Enter: Disconnect\nShift+Enter: Remove saved connection\nSSID: {ssid}"
|
||||
)
|
||||
else:
|
||||
return f"Enter: Connect\nShift+Enter: Remove saved connection\nSSID: {ssid}"
|
||||
|
||||
def _on_slot_key_press(self, widget, event, item: dict) -> bool:
|
||||
if not event:
|
||||
return False
|
||||
|
||||
if (
|
||||
event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter
|
||||
) and event.state & Gdk.ModifierType.SHIFT_MASK:
|
||||
self._remove_saved_connection(item)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _remove_saved_connection(self, item: dict) -> None:
|
||||
ap = item["ap"]
|
||||
ssid = ap.ssid
|
||||
|
||||
wifi_device = self.network_client.wifi_device
|
||||
if not wifi_device:
|
||||
logger.error("No WiFi device available")
|
||||
return
|
||||
|
||||
client = self.network_client._client
|
||||
connections = client.get_connections()
|
||||
|
||||
for connection in connections:
|
||||
wifi_setting = connection.get_setting_wireless()
|
||||
if wifi_setting:
|
||||
connection_ssid = wifi_setting.get_ssid()
|
||||
if connection_ssid:
|
||||
connection_ssid_str = connection_ssid.get_data().decode("utf-8")
|
||||
if connection_ssid_str == ssid:
|
||||
try:
|
||||
connection.delete_async(
|
||||
None, self._on_connection_deleted, ssid
|
||||
)
|
||||
logger.info(f"Removing saved connection for '{ssid}'")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to remove connection for '{ssid}': {e}"
|
||||
)
|
||||
return
|
||||
|
||||
logger.warning(f"No saved connection found for '{ssid}'")
|
||||
|
||||
def _on_connection_deleted(self, connection, result, ssid: str) -> None:
|
||||
try:
|
||||
connection.delete_finish(result)
|
||||
logger.info(f"Successfully removed saved connection for '{ssid}'")
|
||||
if self._is_active:
|
||||
self._refresh_query()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete connection for '{ssid}': {e}")
|
||||
|
||||
def _get_wifi_icon(self, strength: int) -> str:
|
||||
if strength >= 80:
|
||||
return "network-wireless-signal-excellent"
|
||||
elif strength >= 60:
|
||||
return "network-wireless-signal-good"
|
||||
elif strength >= 40:
|
||||
return "network-wireless-signal-ok"
|
||||
elif strength >= 20:
|
||||
return "network-wireless-signal-weak"
|
||||
else:
|
||||
return "network-wireless-signal-none"
|
||||
|
||||
def _handle_slot_click(self, item: dict) -> None:
|
||||
if item.get("active"):
|
||||
wifi_device = self.network_client.wifi_device
|
||||
if wifi_device:
|
||||
wifi_device.disconnect_wifi()
|
||||
self.handler.launched()
|
||||
else:
|
||||
ap = item["ap"]
|
||||
if ap.requires_password:
|
||||
self._prompt_password(ap.ssid)
|
||||
else:
|
||||
self._connect_to_wifi(ap.ssid, "")
|
||||
self.handler.launched()
|
||||
|
||||
def _prompt_password(self, ssid: str) -> None:
|
||||
self._awaiting_password = True
|
||||
self._pending_ssid = ssid
|
||||
self.handler.start("networks")
|
||||
if hasattr(self.handler, "clear_viewport"):
|
||||
self.handler.clear_viewport()
|
||||
if hasattr(self.handler, "clear_input"):
|
||||
self.handler.clear_input()
|
||||
|
||||
self.handler.slot_ready(
|
||||
Button(
|
||||
style_classes="app-slot network-help",
|
||||
child=Box(
|
||||
orientation="v",
|
||||
spacing=8,
|
||||
children=[
|
||||
Label(
|
||||
label=f"Password for {ssid}",
|
||||
style_classes="network-help-text",
|
||||
h_align="center",
|
||||
),
|
||||
Label(
|
||||
label="Enter=Submit • Empty=Cancel",
|
||||
style_classes="network-subtext",
|
||||
h_align="center",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
"networks",
|
||||
)
|
||||
self.handler.done()
|
||||
|
||||
@property
|
||||
def is_password_mode(self) -> bool:
|
||||
return self._awaiting_password and self._pending_ssid is not None
|
||||
|
||||
def is_waiting_for_password(self) -> bool:
|
||||
return self._awaiting_password and self._pending_ssid
|
||||
|
||||
def submit_password(self, password: str) -> None:
|
||||
if not self._pending_ssid:
|
||||
return
|
||||
|
||||
ssid = self._pending_ssid
|
||||
self._awaiting_password = False
|
||||
self._pending_ssid = None
|
||||
|
||||
if hasattr(self.handler, "clear_viewport"):
|
||||
self.handler.clear_viewport()
|
||||
self._connect_to_wifi(ssid, password)
|
||||
self._is_active = False
|
||||
self.handler.launched()
|
||||
|
||||
def cancel_password(self) -> None:
|
||||
self._awaiting_password = False
|
||||
self._pending_ssid = None
|
||||
self._is_active = False
|
||||
|
||||
if hasattr(self.handler, "clear_viewport"):
|
||||
self.handler.clear_viewport()
|
||||
self.query_networks(self._current_search_text)
|
||||
|
||||
def _connect_to_wifi(self, ssid: str, password: str) -> None:
|
||||
wifi_device = self.network_client.wifi_device
|
||||
if not wifi_device:
|
||||
return
|
||||
|
||||
target_ap = None
|
||||
for ap in wifi_device.access_points:
|
||||
if ap.ssid == ssid:
|
||||
target_ap = ap
|
||||
break
|
||||
|
||||
if not target_ap:
|
||||
return
|
||||
|
||||
def callback(success: bool, message: str):
|
||||
if success:
|
||||
logger.info(f"Connected to '{ssid}'")
|
||||
else:
|
||||
logger.error(f"Failed to connect to '{ssid}': {message}")
|
||||
|
||||
wifi_device.connect_to_wifi(target_ap, password, callback)
|
||||
Reference in new issue
Block a user