Huge updates: ControlCenter: Wi-Fi, Bluetooth, Password Entry Dialog, Notification, CustomRevealer for notif
This commit is contained in:
31 files changed
+1418
-349
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user