Huge updates: ControlCenter: Wi-Fi, Bluetooth, Password Entry Dialog, Notification, CustomRevealer for notif

This commit is contained in:
sankalp committed 2025-08-01 07:54:43 +05:45
1 parent 195e2d0f82
commit 10f1b25264
31 files changed
+1418 -349

No files matched your search

+15 -27
View File
@@ -1,4 +1,4 @@
from gi.repository import Gtk, Gdk, GLib
from gi.repository import Gdk, GLib, Gtk
from fabric.bluetooth import BluetoothClient, BluetoothDevice
from fabric.widgets.box import Box
@@ -7,7 +7,6 @@ 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 fabric.widgets.eventbox import EventBox
class BluetoothDeviceSlot(CenterBox):
@@ -68,8 +67,6 @@ class BluetoothConnections(Box):
self.client = BluetoothClient(on_device_added=self.on_device_added)
self.title = Box(
orientation="h",
children=[
@@ -95,10 +92,7 @@ class BluetoothConnections(Box):
)
# Connect scanning state changes to update scan button
self.client.connect(
"notify::scanning",
lambda *_: self.update_scan_label()
)
self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
self.not_paired = Box(spacing=2, orientation="vertical")
self.paired = Box(spacing=2, orientation="vertical")
@@ -108,13 +102,13 @@ class BluetoothConnections(Box):
name="bluetooth-refresh-indicator",
label="↓ Pull to scan for devices",
h_align="center",
visible=False
visible=False,
)
self.device_box = Box(
spacing=2,
orientation="vertical",
children=[self.refresh_indicator, self.paired, self.not_paired]
children=[self.refresh_indicator, self.paired, self.not_paired],
)
# Create scrolled window with pull-to-refresh
@@ -143,8 +137,6 @@ class BluetoothConnections(Box):
# Connect cleanup on destroy
self.connect("destroy", self.on_destroy)
def on_destroy(self, widget):
"""Cleanup animations when widget is destroyed"""
self.stop_bounce_animation()
@@ -165,8 +157,6 @@ class BluetoothConnections(Box):
self.bounce_duration = 60 # Total frames for animation
self.bounce_amplitude = 15 # Maximum bounce height in pixels
# Connect to scroll events
self.scrolled_window.connect("scroll-event", self.on_scroll_event)
self.scrolled_window.connect("button-press-event", self.on_button_press)
@@ -175,10 +165,10 @@ class BluetoothConnections(Box):
# Enable events
self.scrolled_window.set_events(
Gdk.EventMask.SCROLL_MASK |
Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK |
Gdk.EventMask.POINTER_MOTION_MASK
Gdk.EventMask.SCROLL_MASK
| Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK
| Gdk.EventMask.POINTER_MOTION_MASK
)
def on_scroll_event(self, widget, event):
@@ -243,22 +233,22 @@ class BluetoothConnections(Box):
def ease_out_bounce(self, t):
"""Smooth bounce easing function (0 to 1)"""
import math
if t < 1/2.75:
if t < 1 / 2.75:
return 7.5625 * t * t
elif t < 2/2.75:
t -= 1.5/2.75
elif t < 2 / 2.75:
t -= 1.5 / 2.75
return 7.5625 * t * t + 0.75
elif t < 2.5/2.75:
t -= 2.25/2.75
elif t < 2.5 / 2.75:
t -= 2.25 / 2.75
return 7.5625 * t * t + 0.9375
else:
t -= 2.625/2.75
t -= 2.625 / 2.75
return 7.5625 * t * t + 0.984375
def ease_out_elastic(self, t):
"""Elastic easing function for smoother bounce"""
import math
if t == 0 or t == 1:
return t
@@ -327,8 +317,6 @@ class BluetoothConnections(Box):
self.bounce_frame += 1
return True
def stop_bounce_animation(self):
"""Stop any ongoing animations"""
if self.bounce_timeout_id:
+31 -9
View File
@@ -1,3 +1,5 @@
from gi.repository import Gdk, GLib
from fabric.utils import idle_add
from fabric.utils.helpers import (
get_relative_path,
@@ -8,9 +10,6 @@ from fabric.widgets.centerbox import CenterBox
from fabric.widgets.label import Label
from fabric.widgets.scale import Scale
from fabric.widgets.svg import Svg
from fabric.widgets.image import Image
from gi.repository import Gdk, GLib
from modules.controlcenter.bluetooth import BluetoothConnections
from modules.controlcenter.wifi import WifiConnections
from services.brightness import Brightness
@@ -37,7 +36,7 @@ class ModusControlCenter(Window):
visible=False,
**kwargs,
)
self.focus_mode = False
self.focus_mode = modus_service.dont_disturb
self._updating_brightness = False
self._updating_volume = False
@@ -50,6 +49,7 @@ class ModusControlCenter(Window):
audio_service.connect("changed", self.audio_changed)
audio_service.connect("changed", self.volume_changed)
modus_service.connect("dont-disturb-changed", self.dnd_changed)
self.wlan_label = Label(wlan, name="wifi-widget-label", h_align="start")
if bluetooth != "disabled":
@@ -178,7 +178,11 @@ class ModusControlCenter(Window):
self.focus_icon = Svg(
name="focus-icon",
svg_file=get_relative_path("../../config/assets/icons/applets/dnd-off.svg"),
svg_file=get_relative_path(
"../../config/assets/icons/applets/dnd.svg"
if self.focus_mode
else "../../config/assets/icons/applets/dnd-off.svg"
),
size=46,
)
@@ -364,9 +368,13 @@ class ModusControlCenter(Window):
parts = wlan.split(":")
if len(parts) >= 2:
wifi_name = parts[1]
GLib.idle_add(lambda: self.wlan_label.set_property("label", wifi_name))
GLib.idle_add(
lambda: self.wlan_label.set_property("label", wifi_name)
)
else:
GLib.idle_add(lambda: self.wlan_label.set_property("label", "Connected"))
GLib.idle_add(
lambda: self.wlan_label.set_property("label", "Connected")
)
else:
GLib.idle_add(lambda: self.wlan_label.set_property("label", wlan))
else:
@@ -385,9 +393,13 @@ class ModusControlCenter(Window):
parts = bluetooth.split(":")
if len(parts) >= 2:
device_name = parts[1]
GLib.idle_add(lambda: self.bluetooth_label.set_property("label", device_name))
GLib.idle_add(
lambda: self.bluetooth_label.set_property("label", device_name)
)
else:
GLib.idle_add(lambda: self.bluetooth_label.set_property("label", "Connected"))
GLib.idle_add(
lambda: self.bluetooth_label.set_property("label", "Connected")
)
elif bluetooth == "enabled":
GLib.idle_add(lambda: self.bluetooth_label.set_property("label", "On"))
else:
@@ -398,6 +410,16 @@ class ModusControlCenter(Window):
def audio_changed(self, *_):
pass
def dnd_changed(self, _, dnd_state):
self.focus_mode = dnd_state
self.focus_icon.set_from_file(
get_relative_path(
"../../config/assets/icons/applets/dnd.svg"
if self.focus_mode
else "../../config/assets/icons/applets/dnd-off.svg"
)
)
def _init_mousecapture(self, mousecapture):
self._mousecapture_parent = mousecapture
+75 -7
View File
@@ -1,19 +1,21 @@
from gi.repository import Gdk, GLib, Gtk
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 Gdk, GLib, Gtk
from services.network import NetworkService
from widgets.wifi_password_dialog import WiFiPasswordDialog
class WifiNetworkSlot(CenterBox):
def __init__(self, network_data: dict, wifi_service, **kwargs):
def __init__(self, network_data: dict, wifi_service, parent=None, **kwargs):
super().__init__(**kwargs)
self.network_data = network_data
self.wifi_service = wifi_service
self.parent = parent # Reference to control center
# Get network info
self.ssid = network_data.get("ssid", "Unknown")
@@ -43,6 +45,9 @@ class WifiNetworkSlot(CenterBox):
style_classes=" ".join(self.styles),
)
# Initialize password dialog
self.password_dialog = None
self.start_children = [
Button(
image=self.dimage,
@@ -79,11 +84,10 @@ class WifiNetworkSlot(CenterBox):
# Reset state if disconnect failed
self._reset_disconnect_state()
else:
# Show connecting state
# Try to connect without password first (for open networks or saved connections)
self.dimage.set_property("icon-name", "network-wireless-acquiring-symbolic")
self.dimage.add_style_class("connecting")
# Connect to network (try without password first, could be enhanced for password input)
success = self.wifi_service.connect_network(self.ssid)
if success:
self.is_connected = True
@@ -92,8 +96,9 @@ class WifiNetworkSlot(CenterBox):
GLib.timeout_add(500, lambda: self._reset_connect_state())
else:
# Reset state if connect failed
# Connection failed, likely needs password - show password dialog
self._reset_connect_state()
self._show_password_dialog()
# Update display after connection attempt
self.on_changed()
@@ -133,6 +138,67 @@ class WifiNetworkSlot(CenterBox):
self.dimage.set_property("style-classes", " ".join(self.styles))
return
def _show_password_dialog(self):
"""Show the WiFi password dialog"""
# Close the control center first
if self.parent and hasattr(self.parent, "hide_controlcenter"):
self.parent.hide_controlcenter()
# Create a new dialog each time to ensure clean state
if self.password_dialog:
self.password_dialog.destroy_dialog()
self.password_dialog = WiFiPasswordDialog(
ssid=self.ssid,
on_connect_callback=self._on_password_connect,
on_cancel_callback=self._on_password_cancel,
)
self.password_dialog.show_dialog()
def _on_password_connect(self, ssid, password):
"""Handle password dialog connect action"""
if password.strip():
# Show connecting state
self.dimage.set_property("icon-name", "network-wireless-acquiring-symbolic")
self.dimage.add_style_class("connecting")
# Try to connect with password
try:
success = self.wifi_service.connect_network(ssid, password)
if success:
self.is_connected = True
# Remove connecting state after a short delay
from gi.repository import GLib
GLib.timeout_add(500, lambda: self._reset_connect_state())
else:
# Connection failed - show error in dialog immediately
self._reset_connect_state()
if self.password_dialog:
# Show error immediately without delay
self._show_connection_error()
except Exception:
# Handle any connection errors gracefully
self._reset_connect_state()
if self.password_dialog:
# Show error immediately
self._show_connection_error("Connection failed. Please try again.")
# Update display after connection attempt
self.on_changed()
def _show_connection_error(self, message="Incorrect password. Please try again."):
"""Show connection error in a separate thread to prevent UI blocking"""
if self.password_dialog:
self.password_dialog.show_error(message)
return False # Don't repeat if called from GLib.timeout_add
def _on_password_cancel(self):
"""Handle password dialog cancel action"""
# Reset any connecting state
self._reset_connect_state()
class WifiConnections(Box):
def __init__(self, parent, **kwargs):
@@ -261,7 +327,9 @@ class WifiConnections(Box):
access_points = self.wifi_service.access_points
for ap_data in access_points:
if ap_data.get("ssid") and ap_data.get("ssid") != "Unknown":
network_slot = WifiNetworkSlot(ap_data, self.wifi_service)
network_slot = WifiNetworkSlot(
ap_data, self.wifi_service, parent=self.parent
)
self.available_networks.add(network_slot)
# Update all network connection states