This commit is contained in:
sankalp committed 2025-07-21 07:29:27 +05:45
1 parent 44b06150e1
commit 1504423633
5 files changed
+982 -22

No files matched your search

+21 -21
View File
@@ -1,27 +1,27 @@
$wallpaper = /home/sankalp/Pictures/wallpapers/example-1.jpg
$background = 1a1110
$foreground = f1dedd
$wallpaper = /home/sankalp/Pictures/wallpapers/2.png
$background = 131318
$foreground = e4e1e9
$primary = ffb3ad
$secondary = e7bdb9
$tertiary = e1c28c
$surface = 1a1110
$surface_bright = 423736
$outline = a08c8a
$primary = bcc2ff
$secondary = c4c5dd
$tertiary = e6bad6
$surface = 131318
$surface_bright = 39393f
$outline = 90909a
$error = ff5945
$shadow = 000000
$red = ffb4aa
$green = b7d085
$yellow = dec56e
$blue = cebdfe
$magenta = fcb0d5
$cyan = 84d5c4
$red = ffb2b9
$green = 95d5a7
$yellow = b8cf84
$blue = bdc2ff
$magenta = e4b7f3
$cyan = 82d3e2
$white = 82d3e0
$red_dim = ff5a44
$green_dim = 89ad42
$yellow_dim = bb9b2b
$blue_dim = 8359fc
$magenta_dim = f84ea1
$cyan_dim = 3eb59c
$red_dim = ff4c5c
$green_dim = 4cb86a
$yellow_dim = 8baa43
$blue_dim = 5764ff
$magenta_dim = c360e4
$cyan_dim = 30b5ce
+418
View File
@@ -0,0 +1,418 @@
from fabric.bluetooth import BluetoothClient, BluetoothDevice
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.scrolledwindow import ScrolledWindow
from gi.repository import GLib, Gtk
import subprocess
import utils.icons as icons
class BluetoothDeviceSlot(CenterBox):
def __init__(self, device: BluetoothDevice, **kwargs):
super().__init__(name="bluetooth-device", **kwargs)
self.device = device
self.device.connect("changed", self.on_changed)
self.device.connect(
"notify::closed", lambda *_: self.device.closed and self.destroy()
)
self.connection_label = Label(markup=icons.bluetooth_disconnected, style="font-size: 22px;")
self.connect_button = Button(
label="Connect",
on_clicked=lambda *_: self._handle_device_action(),
)
# Create device name label that we can update
self.device_name_label = Label(
label=device.alias or device.name,
h_expand=True,
h_align="start",
ellipsization="end"
)
self.start_children = [
Box(
spacing=10,
h_expand=True,
h_align="fill",
children=[
Image(icon_name=device.icon_name + "-symbolic", size=16),
self.device_name_label,
self.connection_label,
],
)
]
self.end_children = self.connect_button
self.device.emit("changed")
def _handle_device_action(self):
"""Handle connect/disconnect button click"""
if self.device.connected:
# Disconnect
self.device.connected = False
elif self.device.paired:
# Connect to already paired device
self.device.connected = True
else:
# Pair new device using bluetoothctl
self._pair_device_with_bluetoothctl()
def _pair_device_with_bluetoothctl(self):
"""Pair device using bluetoothctl command"""
try:
device_name = self.device.alias or self.device.name
device_address = self.device.address
# Show pairing notification
subprocess.run([
"notify-send",
"Bluetooth Pairing",
f"Attempting to pair with {device_name}...",
"--icon=bluetooth",
"--urgency=normal",
"--app-name=Bluetooth"
], check=False)
# Use bluetoothctl to pair the device
# This will trigger the system's pairing agent which should show the pairing dialog
pairing_process = subprocess.Popen([
"bluetoothctl", "pair", device_address
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Monitor the pairing process in a separate thread
GLib.timeout_add(100, self._monitor_pairing_process, pairing_process, device_name)
except Exception as e:
print(f"Failed to start pairing process: {e}")
self._show_pairing_failed_notification()
def _monitor_pairing_process(self, process, device_name):
"""Monitor the bluetoothctl pairing process"""
if process.poll() is None:
# Process still running
return True
# Process finished, check result
stdout, stderr = process.communicate()
return_code = process.returncode
if return_code == 0 and "Pairing successful" in stdout:
self._show_pairing_success_notification()
# Auto-trust and connect the device after successful pairing
self._trust_and_connect_device()
else:
self._show_pairing_failed_notification()
return False # Stop monitoring
def _trust_and_connect_device(self):
"""Trust and connect to the device after successful pairing"""
try:
device_address = self.device.address
# Trust the device
subprocess.run(["bluetoothctl", "trust", device_address], check=False)
# Connect to the device
GLib.timeout_add(1000, lambda: setattr(self.device, 'connected', True))
except Exception as e:
print(f"Failed to trust/connect device: {e}")
def _show_pairing_success_notification(self):
"""Show notification when pairing succeeds"""
try:
device_name = self.device.alias or self.device.name
subprocess.run([
"notify-send",
"Bluetooth Paired",
f"Successfully paired with {device_name}",
"--icon=bluetooth",
"--urgency=normal",
"--app-name=Bluetooth"
], check=False)
except Exception as e:
print(f"Failed to send pairing success notification: {e}")
def _show_pairing_failed_notification(self):
"""Show notification when pairing fails"""
try:
device_name = self.device.alias or self.device.name
subprocess.run([
"notify-send",
"Bluetooth Pairing Failed",
f"Failed to pair with {device_name}",
"--icon=bluetooth-disabled",
"--urgency=normal",
"--app-name=Bluetooth"
], check=False)
except Exception as e:
print(f"Failed to send pairing failed notification: {e}")
def on_changed(self, *_):
# Update connection icon
self.connection_label.set_markup(
icons.bluetooth_connected if self.device.connected else icons.bluetooth_disconnected
)
# Check for pairing state changes and show notifications
base_name = self.device.alias or self.device.name
# Track pairing success/failure
if hasattr(self, '_was_pairing') and self._was_pairing:
if self.device.paired and not self.device.connecting:
# Pairing succeeded
self._show_pairing_success_notification()
self._was_pairing = False
elif not self.device.connecting and not self.device.paired:
# Pairing failed
self._show_pairing_failed_notification()
self._was_pairing = False
# Update device name with status
if self.device.connecting:
if self.device.paired:
status_text = f"{base_name}\nConnecting..."
self.connect_button.set_label("Connecting...")
else:
status_text = f"{base_name}\nPairing..."
self.connect_button.set_label("Pairing...")
self._was_pairing = True # Track that we're pairing
elif self.device.connected:
battery_info = ""
if self.device.battery_percentage > 0:
battery_info = f" • {self.device.battery_percentage:.0f}%"
status_text = f"{base_name}\nConnected{battery_info}"
self.connect_button.set_label("Disconnect")
else:
status_text = base_name
if self.device.paired:
self.connect_button.set_label("Connect")
else:
self.connect_button.set_label("Pair")
self.device_name_label.set_label(status_text)
class BluetoothTab:
"""Bluetooth device management tab for settings"""
def __init__(self):
self.bluetooth_client = BluetoothClient(on_device_added=self.on_device_added)
self.device_widgets = {}
# Connect to bluetooth client signals
self.bluetooth_client.connect("notify::enabled", self._on_bluetooth_changed)
self.bluetooth_client.connect("notify::scanning", self._update_scan_button)
self.bluetooth_client.connect("device-removed", self.on_device_removed)
def create_bluetooth_tab(self):
"""Create the Bluetooth tab content"""
main_vbox = Box(
orientation="v",
spacing=0,
style="padding: 0; margin: 15px;"
)
# Set fixed size for the main container to match GUI window
main_vbox.set_size_request(620, 580)
# Create widgets first
self.bluetooth_subtitle = Label(
markup="<span>Find and connect to Bluetooth devices</span>",
h_align="start"
)
self.bluetooth_switch = Gtk.Switch()
self.scan_icon = Label(name="bluetooth-tab-icon", markup=icons.radar, style="font-size: 18px;")
self.scan_button = Button(
name="bluetooth-scan",
child=self.scan_icon,
tooltip_text="Scan for devices",
on_clicked=lambda *_: self.bluetooth_client.toggle_scan(),
style="margin:10px;"
)
# Header section with title and controls
header_box = CenterBox(
name="bluetooth-header",
style="margin-bottom: 16px;",
start_children=[
Box(
orientation="v",
spacing=4,
children=[
Label(
markup="<span size='large'><b>Bluetooth</b></span>",
h_align="start"
),
self.bluetooth_subtitle,
]
)
],
end_children=[
Box(
orientation="h",
spacing=8,
children=[
self.bluetooth_switch,
self.scan_button,
]
)
]
)
# Set up switch
self.bluetooth_switch.set_valign(Gtk.Align.CENTER)
self.bluetooth_switch.connect("notify::active", self._on_bluetooth_switch_toggled)
main_vbox.add(header_box)
# Device containers
self.paired_box = Box(spacing=20, orientation="v")
self.available_box = Box(spacing=20, orientation="v", )
content_box = Box(
spacing=8,
orientation="v",
children=[
Label(name="bluetooth-section", markup="<b>Paired Devices</b>", h_align="start"),
self.paired_box,
Label(name="bluetooth-section", markup="<b>Available Devices</b>", h_align="start"),
self.available_box,
]
)
# Devices list in scrolled window
devices_scrolled = ScrolledWindow(
name="bluetooth-devices",
h_scrollbar_policy="never",
v_scrollbar_policy="automatic",
h_expand=True,
v_expand=True,
propagate_width=False,
propagate_height=False,
child=content_box,
)
devices_scrolled.set_size_request(-1, 500)
main_vbox.add(devices_scrolled)
# Initialize bluetooth status and populate existing devices
self._update_bluetooth_status()
self._populate_existing_devices()
self.bluetooth_client.notify("enabled")
self.bluetooth_client.notify("scanning")
return main_vbox
def _update_bluetooth_status(self):
"""Update Bluetooth status display"""
if self.bluetooth_client.enabled:
self.bluetooth_switch.set_active(True)
self.bluetooth_subtitle.set_markup(
"<span>Find and connect to Bluetooth devices</span>"
)
self.scan_button.set_sensitive(True)
else:
self.bluetooth_switch.set_active(False)
self.bluetooth_subtitle.set_markup(
"<span>Bluetooth is turned off</span>"
)
self.scan_button.set_sensitive(False)
def _on_bluetooth_changed(self, *args):
"""Handle bluetooth state changes"""
GLib.idle_add(self._update_bluetooth_status)
GLib.idle_add(self._refresh_all_devices)
def _update_scan_button(self, *args):
"""Update scan button appearance"""
if self.bluetooth_client.scanning:
self.scan_icon.set_markup(icons.loader)
self.scan_icon.add_style_class("scanning")
self.scan_button.add_style_class("scanning")
self.scan_button.set_tooltip_text("Stop scanning for Bluetooth devices")
else:
self.scan_icon.set_markup(icons.radar)
self.scan_icon.remove_style_class("scanning")
self.scan_button.remove_style_class("scanning")
self.scan_button.set_tooltip_text("Scan for Bluetooth devices")
def _populate_existing_devices(self):
"""Populate existing devices when tab is created"""
if not self.bluetooth_client or not self.bluetooth_client.enabled:
return
devices = self.bluetooth_client.devices
for device in devices:
self.on_device_added(self.bluetooth_client, device.address)
def on_device_added(self, client: BluetoothClient, address: str):
"""Handle device added"""
# Check if device already exists to prevent duplicates
if address in self.device_widgets:
return
if not (device := client.get_device(address)):
return
slot = BluetoothDeviceSlot(device)
# Store the slot in device_widgets to track it
self.device_widgets[address] = slot
if device.paired or device.trusted:
return self.paired_box.add(slot)
else:
return self.available_box.add(slot)
def on_device_removed(self, client: BluetoothClient, address: str):
"""Handle device removed"""
if address in self.device_widgets:
slot = self.device_widgets[address]
# Remove from UI
parent = slot.get_parent()
if parent:
parent.remove(slot)
# Remove from tracking
del self.device_widgets[address]
def _on_bluetooth_switch_toggled(self, switch, gparam):
"""Handle Bluetooth switch toggle"""
is_active = switch.get_active()
self.bluetooth_client.enabled = is_active
# Refresh devices when bluetooth is toggled
if is_active:
GLib.timeout_add(1000, self._populate_existing_devices)
else:
self._clear_all_devices()
def _clear_all_devices(self):
"""Clear all device widgets"""
for child in self.paired_box.get_children():
self.paired_box.remove(child)
for child in self.available_box.get_children():
self.available_box.remove(child)
self.device_widgets.clear()
def _refresh_all_devices(self):
"""Clear and repopulate all devices"""
self._clear_all_devices()
self._populate_existing_devices()
+14 -1
View File
@@ -18,6 +18,8 @@ from PIL import Image
from config.data import APP_NAME, APP_NAME_CAP, NOTIF_POS_DEFAULT, NOTIF_POS_KEY
from config.settings.utils import backup_and_replace, bind_vars, start_config
from config.settings.wifi_tab import WiFiTab
from config.settings.bluetooth_tab import BluetoothTab
gi.require_version("Gtk", "3.0")
@@ -37,6 +39,10 @@ class HyprConfGUI(Window):
self.show_lock_checkbox = show_lock_checkbox
self.show_idle_checkbox = show_idle_checkbox
# Initialize WiFi and Bluetooth tabs
self.wifi_tab = WiFiTab()
self.bluetooth_tab = BluetoothTab()
root_box = Box(orientation="v", spacing=10, style="margin: 10px;")
self.add(root_box)
@@ -49,12 +55,19 @@ class HyprConfGUI(Window):
v_expand=False,
h_expand=False,
)
self.wifi_tab_content = self.wifi_tab.create_wifi_tab()
self.bluetooth_tab_content = self.bluetooth_tab.create_bluetooth_tab()
self.key_bindings_tab_content = self.create_key_bindings_tab()
self.appearance_tab_content = self.create_appearance_tab()
self.system_tab_content = self.create_system_tab()
self.about_tab_content = self.create_about_tab()
self.tab_stack.add_titled(
self.wifi_tab_content, "Wifi", "Wifi"
)
self.tab_stack.add_titled(
self.bluetooth_tab_content, "Bluetooth", "Bluetooth"
)
self.tab_stack.add_titled(
self.key_bindings_tab_content, "key_bindings", "Key Bindings"
)
+525
View File
@@ -0,0 +1,525 @@
import gi
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GLib, Gtk
from services.network import NetworkClient
from utils import icons
import subprocess
gi.require_version("Gtk", "3.0")
class WiFiTab:
"""WiFi network management tab for settings"""
def __init__(self):
self.network_client = NetworkClient()
self.access_point_widgets = {}
self.password_entries = {}
self.connecting_to = None
# Connect to network client signals
self.network_client.connect("ready", self._on_network_ready)
self.network_client.connect("changed", self._on_network_changed)
if self.network_client.is_ready:
self._setup_network_signals()
def _on_network_ready(self, *args):
"""Called when network client is ready"""
self._setup_network_signals()
self._refresh_networks()
def _setup_network_signals(self):
"""Setup signals for WiFi device"""
if self.network_client.wifi_device:
self.network_client.wifi_device.connect("changed", self._on_wifi_changed)
self.network_client.wifi_device.connect("ap-added", self._on_ap_added)
self.network_client.wifi_device.connect("ap-removed", self._on_ap_removed)
def _on_network_changed(self, *args):
"""Called when network state changes"""
self._refresh_networks()
def _on_wifi_changed(self, *args):
"""Called when WiFi state changes"""
self._refresh_networks()
def _on_ap_added(self, device, ap):
"""Called when access point is added"""
self._refresh_networks()
def _on_ap_removed(self, device, ap):
"""Called when access point is removed"""
self._refresh_networks()
def create_wifi_tab(self):
"""Create the WiFi tab content"""
main_vbox = Box(
orientation="v",
spacing=0,
style="padding: 0; margin: 15px;"
)
# Set fixed size for the main container to match GUI window
main_vbox.set_size_request(620, 580) # Match GUI window content area
# Header section with title and controls
header_box = Box(
orientation="h",
spacing=12,
)
# WiFi section
wifi_section = Box(orientation="v", spacing=4)
# WiFi title
wifi_title = Label(
markup="<span size='large'><b>Wi-Fi</b></span>",
h_align="start"
)
wifi_section.add(wifi_title)
# WiFi subtitle
self.wifi_subtitle = Label(
markup="<span>Find and connect to Wi-Fi networks</span>",
h_align="start"
)
wifi_section.add(self.wifi_subtitle)
header_box.add(wifi_section)
# Spacer to push controls to the right
spacer = Box()
spacer.set_hexpand(True)
header_box.add(spacer)
# Controls section
controls_box = Box(orientation="h", spacing=12)
# WiFi toggle switch
self.wifi_switch = Gtk.Switch()
self.wifi_switch.set_valign(Gtk.Align.CENTER)
self.wifi_switch.connect("notify::active", self._on_wifi_switch_toggled)
controls_box.add(self.wifi_switch)
# Refresh button
self.refresh_button = Button()
self.refresh_icon = Label(name="wifi-tab-icon", markup=icons.reload, style="font-size: 18px;")
self.refresh_button.add(self.refresh_icon)
self.refresh_button.set_size_request(40, 40)
self.refresh_button.set_tooltip_text("Refresh networks")
self.refresh_button.set_style("margin:10px;")
self.refresh_button.connect("clicked", self._on_refresh_clicked)
controls_box.add(self.refresh_button)
header_box.add(controls_box)
main_vbox.add(header_box)
# Networks list in scrolled window
self.networks_scrolled = ScrolledWindow(
h_scrollbar_policy="never",
v_scrollbar_policy="automatic",
h_expand=True,
v_expand=True,
propagate_width=False,
propagate_height=False,
)
# Set size to match available space (580 - header space ≈ 500)
self.networks_scrolled.set_size_request(-1, 500)
self.networks_container = Box(
orientation="v",
spacing=0,
)
self.networks_scrolled.add(self.networks_container)
main_vbox.add(self.networks_scrolled)
# Initialize the display
GLib.timeout_add(100, self._refresh_networks)
return main_vbox
def _refresh_networks(self):
"""Refresh the networks display"""
# Clear existing widgets
for child in self.networks_container.get_children():
self.networks_container.remove(child)
self.access_point_widgets.clear()
self.password_entries.clear()
# Update WiFi status
self._update_wifi_status()
# Show available networks if WiFi is enabled
if (self.network_client.wifi_device and
self.network_client.wifi_device.wireless_enabled):
self._populate_networks()
self.networks_container.show_all()
return False
def _update_wifi_status(self):
"""Update WiFi status display"""
if not self.network_client.wifi_device:
self.wifi_subtitle.set_markup(
"<span>No WiFi device available</span>"
)
self.wifi_switch.set_sensitive(False)
return
if self.network_client.wifi_device.wireless_enabled:
self.wifi_switch.set_active(True)
self.wifi_subtitle.set_markup(
"<span>Find and connect to Wi-Fi networks</span>"
)
self.refresh_button.set_sensitive(True)
else:
self.wifi_switch.set_active(False)
self.wifi_subtitle.set_markup(
"<span>Wi-Fi is turned off</span>"
)
self.refresh_button.set_sensitive(False)
def _populate_networks(self):
"""Populate the networks list"""
if not self.network_client.wifi_device:
self._show_status_message("No WiFi device available", "error")
return
access_points = self.network_client.wifi_device.access_points
if not access_points:
self._show_status_message(
"No networks found\nClick 'Scan for Networks' to refresh",
"info"
)
return
# Sort access points by signal strength (connected first, then by strength)
sorted_aps = sorted(
access_points,
key=lambda ap: (not ap.is_active, -ap.strength)
)
for ap in sorted_aps:
self._create_network_widget(ap)
def _show_status_message(self, message, message_type="info"):
"""Show a status message in the networks container"""
status_box = Box(
orientation="v",
spacing=16,
style="padding: 60px;"
)
status_box.set_halign(Gtk.Align.CENTER)
status_box.set_valign(Gtk.Align.CENTER)
# Icon based on message type
if message_type == "error":
icon_markup = icons.cloud_off
message_label = Label(
markup=f"<span size='medium'>{message}</span>",
h_align="center",
style="color: var(--error);"
)
elif message_type == "scanning":
icon_markup = icons.loader
message_label = Label(
markup=f"<span size='medium'>{message}</span>",
h_align="center",
style="color: var(--blue);"
)
else: # info
icon_markup = icons.wifi_off
message_label = Label(
markup=f"<span size='medium'>{message}</span>",
h_align="center",
style="color: var(--outline);"
)
icon_label = Label(name="wifi-tab-icon", markup=icon_markup, style="font-size: 18px;")
icon_label.set_halign(Gtk.Align.CENTER)
status_box.add(icon_label)
message_label.set_line_wrap(True)
message_label.set_justify(Gtk.Justification.CENTER)
message_label.set_halign(Gtk.Align.CENTER)
status_box.add(message_label)
self.networks_container.add(status_box)
def _create_network_widget(self, ap):
"""Create widget for a single network"""
# Main container for this network (Box to allow adding password entry)
network_container = Box(orientation="v", spacing=0)
# Clickable network button
network_button = Button(
style="background-color: transparent; border: none; padding: 0; margin: 0;"
)
network_button.connect("clicked", lambda btn, access_point=ap: self._on_network_clicked(access_point))
# Network info row
info_box = Box(orientation="h", spacing=12)
info_box.set_margin_left(16)
info_box.set_margin_right(16)
info_box.set_margin_top(12)
info_box.set_margin_bottom(12)
# WiFi signal icon
strength = ap.strength
if strength >= 75:
icon_markup = icons.wifi_3
elif strength >= 50:
icon_markup = icons.wifi_2
elif strength >= 25:
icon_markup = icons.wifi_1
else:
icon_markup = icons.wifi_0
signal_label = Label(name="wifi-tab-icon", markup=icon_markup, style="font-size: 24px;")
signal_label.set_size_request(30, -1)
info_box.add(signal_label)
# Network name
network_name = ap.ssid
if ap.is_active:
network_markup = f"<b>{network_name}</b>\nConnected"
network_label = Label(markup=network_markup, h_align="start", style="color: var(--foreground);")
else:
network_markup = f"{network_name}"
network_label = Label(markup=network_markup, h_align="start", style="color: var(--foreground);")
network_label.set_line_wrap(True)
info_box.add(network_label)
# Spacer to push lock icon to the right
spacer = Box()
spacer.set_hexpand(True)
info_box.add(spacer)
# Lock icon for secured networks
if ap.requires_password:
lock_label = Label(name="wifi-tab-icon", markup=icons.lock, style="font-size: 22px;")
info_box.add(lock_label)
network_button.add(info_box)
network_container.add(network_button)
# Store reference
self.access_point_widgets[ap.ssid] = {
'container': network_container,
'button': network_button,
'info_box': info_box,
'network_label': network_label,
'ap': ap
}
self.networks_container.add(network_container)
def _on_network_clicked(self, ap):
"""Handle network item click"""
if ap.is_active:
# If connected, disconnect from this network
self._disconnect_network(ap)
else:
# If not connected, check if password is needed
if ap.requires_password and ap.ssid not in self.password_entries:
# Show password entry interface
self._show_password_entry(ap)
else:
# Connect directly (either no password needed or password already entered)
self._connect_network(ap)
def _on_wifi_switch_toggled(self, switch, gparam):
"""Handle WiFi switch toggle"""
if self.network_client.wifi_device:
is_active = switch.get_active()
self.network_client.wifi_device.wireless_enabled = is_active
GLib.timeout_add(500, self._refresh_networks)
def _on_refresh_clicked(self, button):
"""Handle refresh button click"""
if self.network_client.wifi_device and self.network_client.wifi_device.wireless_enabled:
self.refresh_icon.set_markup(icons.loader)
button.set_sensitive(False)
# Clear current networks and show scanning message
for child in self.networks_container.get_children():
self.networks_container.remove(child)
self._show_status_message("Scanning for networks...", "scanning")
self.networks_container.show_all()
self.network_client.wifi_device.scan()
# Re-enable button after scan
GLib.timeout_add(3000, lambda: [
self.refresh_icon.set_markup(icons.reload),
button.set_sensitive(True),
self._refresh_networks()
])
def _connect_network(self, ap):
"""Connect to a network"""
# Get password if available (either from entry or stored)
password = ""
if ap.requires_password:
if ap.ssid in self.password_entries:
# Get password from stored entry widget
password_widget = self.password_entries[ap.ssid]
if hasattr(password_widget, 'get_text'):
password = password_widget.get_text()
else:
password = str(password_widget) # If it's stored as string
else:
# No password available, this shouldn't happen if called correctly
print(f"Warning: No password available for {ap.ssid}")
return
self.connecting_to = ap.ssid
# Hide password entry if shown
if ap.ssid in self.password_entries:
self._hide_password_entry(ap)
# Update network widget to show connecting state
if ap.ssid in self.access_point_widgets:
network_label = self.access_point_widgets[ap.ssid]['network_label']
network_label.set_markup(f"{ap.ssid}\nConnecting...")
network_label.set_style("color: var(--blue);")
try:
# Connect to network
result = self.network_client.wifi_device.connect_to_wifi(ap, password)
if result is False:
# Connection failed immediately
self._show_connection_error(ap.ssid, "Failed to connect")
self._reset_connection_state(ap.ssid)
else:
# Reset button after timeout if still connecting
GLib.timeout_add(15000, lambda: self._reset_connection_state(ap.ssid))
except Exception as e:
self._show_connection_error(ap.ssid, f"Connection error: {str(e)}")
self._reset_connection_state(ap.ssid)
def _show_connection_error(self, ssid, error_message):
try:
subprocess.run([
"notify-send",
"WiFi Connection Failed",
f"Failed to connect to {ssid}\n{error_message}",
"--icon=network-wireless-offline",
"--urgency=normal",
"--app-name=WiFi"
], check=False)
except Exception as e:
print(f"Failed to send connection error notification: {e}")
def _show_password_entry(self, ap):
"""Show password entry for a network"""
if ap.ssid in self.access_point_widgets:
container = self.access_point_widgets[ap.ssid]['container']
# Check if password entry is already shown
if len(container.get_children()) > 1:
return # Already showing password entry
# Password entry row with theme styling
password_box = Box(
orientation="h",
spacing=8,
style="padding: 8px; margin: 4px 16px 8px 16px; border-radius: 8px;"
)
password_entry = Entry(
placeholder_text="Enter network password",
style="background-color: var(--surface-bright); border: 1px solid var(--outline); border-radius: 8px;"
)
password_entry.set_visibility(False) # Hide password characters
password_entry.set_hexpand(True)
password_entry.connect("activate", lambda entry, access_point=ap: self._on_password_entered(access_point, entry))
password_box.add(password_entry)
# Show/Hide password toggle
show_password_btn = Button(
style="border-radius: 8px;"
)
self.show_password_icon = Label(name="wifi-tab-icon", markup=icons.spy,style="font-size: 18px;")
show_password_btn.add(self.show_password_icon)
show_password_btn.set_tooltip_text("Show/Hide password")
show_password_btn.connect("clicked", lambda btn, entry=password_entry: self._toggle_password_visibility(entry, btn))
password_box.add(show_password_btn)
connect_btn = Button(
style="border-radius: 8px;"
)
connect_icon = Label(name="wifi-tab-icon", markup=icons.connect,style="font-size: 18px;")
connect_btn.add(connect_icon)
connect_btn.connect("clicked", lambda btn, access_point=ap, entry=password_entry: self._on_password_entered(access_point, entry))
password_box.add(connect_btn)
cancel_btn = Button(
style="border-radius: 8px;"
)
cancel_icon = Label(name="wifi-tab-icon", markup=icons.cancel,style="font-size: 18px;")
cancel_btn.add(cancel_icon)
cancel_btn.connect("clicked", lambda btn, access_point=ap: self._hide_password_entry(access_point))
password_box.add(cancel_btn)
container.add(password_box)
container.show_all()
# Store reference and focus
self.password_entries[ap.ssid] = password_entry
GLib.timeout_add(100, lambda: password_entry.grab_focus())
def _toggle_password_visibility(self, entry, button):
"""Toggle password visibility"""
current_visibility = entry.get_visibility()
entry.set_visibility(not current_visibility)
# Use spy icon for hidden, key icon for visible
self.show_password_icon.set_markup(icons.key if not current_visibility else icons.spy)
def _on_password_entered(self, ap, entry):
"""Handle password entry"""
password = entry.get_text()
self.password_entries[ap.ssid] = password
self._hide_password_entry(ap)
self._connect_network(ap)
def _hide_password_entry(self, ap):
"""Hide password entry for a network"""
if ap.ssid in self.access_point_widgets:
container = self.access_point_widgets[ap.ssid]['container']
children = container.get_children()
if len(children) > 1: # Remove password entry box
container.remove(children[-1])
def _disconnect_network(self, ap):
"""Disconnect from a network"""
if self.network_client.wifi_device:
self.network_client.wifi_device.disconnect_wifi()
GLib.timeout_add(1000, self._refresh_networks)
def _reset_connection_state(self, ssid):
"""Reset connection state for a network"""
if ssid in self.access_point_widgets:
# Reset the network label to normal state
ap = self.access_point_widgets[ssid]['ap']
network_label = self.access_point_widgets[ssid]['network_label']
if ap.is_active:
network_markup = f"<b>{ap.ssid}</b>\nConnected"
network_label.set_style("color: var(--foreground);")
else:
network_markup = f"{ap.ssid}"
network_label.set_style("color: var(--foreground);")
network_label.set_markup(network_markup)
self.connecting_to = None
self._refresh_networks()
return False
+4
View File
@@ -52,6 +52,9 @@ tooltip > * {
border-radius: 10px;
}
#workspaces,
#workspaces-container,
#launcher,
@@ -68,3 +71,4 @@ tooltip > * {
#dock {
box-shadow: 0 0 3px alpha(black, 0.7);
}