1067 lines
39 KiB
Python
1067 lines
39 KiB
Python
import subprocess
|
|
|
|
from fabric.utils import idle_add
|
|
from fabric.utils.helpers import (
|
|
get_relative_path,
|
|
)
|
|
from fabric.widgets.box import Box
|
|
from fabric.widgets.button import Button
|
|
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 gi.repository import Gdk, GLib
|
|
from loguru import logger
|
|
from modules.controlcenter.bluetooth import BluetoothConnections
|
|
from modules.controlcenter.expanded_player import EmbeddedExpandedPlayer
|
|
from modules.controlcenter.per_app_volume import PerAppVolumeControl
|
|
from modules.controlcenter.player import PlayerBoxStack
|
|
from modules.controlcenter.wifi import WifiConnections
|
|
from services.brightness import Brightness
|
|
from services.mpris import MprisPlayerManager
|
|
from services.network import NetworkClient
|
|
from utils.animator import Animator
|
|
from utils.animator import Animator
|
|
from utils.animator import Animator
|
|
from utils.roam import audio_service, modus_service
|
|
from widgets.wayland import WaylandWindow as Window
|
|
|
|
brightness_service = Brightness.get_initial()
|
|
|
|
|
|
class ModusControlCenter(Window):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(
|
|
layer="top",
|
|
title="modus",
|
|
anchor="top right",
|
|
margin="2px 10px 0px 0px",
|
|
exclusivity="auto",
|
|
keyboard_mode="on-demand",
|
|
name="control-center-menu",
|
|
visible=False,
|
|
**kwargs,
|
|
)
|
|
self.focus_mode = modus_service.dont_disturb
|
|
self._updating_brightness = False
|
|
self._updating_volume = False
|
|
|
|
# Flight mode and caffeine states
|
|
self.flight_mode = False
|
|
self.caffeine_mode = False
|
|
self._caffeine_process = None
|
|
|
|
# Lazy loading flags
|
|
self._music_initialized = False
|
|
self._per_app_volume_initialized = False
|
|
self._expanded_player_initialized = False
|
|
|
|
# Store references for cleanup - initialize all as None
|
|
self._signal_connections = []
|
|
self._music_widget_content = None
|
|
self._per_app_volume_widget = None
|
|
self._expanded_player_widget = None
|
|
self._mpris_manager = None # Shared MPRIS manager instance
|
|
|
|
# Initialize network service for WiFi toggle
|
|
self.network_service = NetworkClient()
|
|
self.wifi_service = None
|
|
|
|
# Initialize flight mode and caffeine states
|
|
self._check_initial_states()
|
|
|
|
# Wait for network service to be ready
|
|
|
|
self.add_keybinding("Escape", self.hide_controlcenter)
|
|
|
|
volume = 100
|
|
wlan = modus_service.sc("wlan-changed", self.wlan_changed)
|
|
bluetooth = modus_service.sc("bluetooth-changed", self.bluetooth_changed)
|
|
music = modus_service.sc("music-changed", self.audio_changed)
|
|
|
|
self.network_service.connect("wifi-device-added", self.on_network_ready)
|
|
# Store signal connections for cleanup
|
|
self._signal_connections.extend(
|
|
[
|
|
audio_service.connect("changed", self.audio_changed),
|
|
audio_service.connect("changed", self.volume_changed),
|
|
modus_service.connect("dont-disturb-changed", self.dnd_changed),
|
|
]
|
|
)
|
|
|
|
print(wlan)
|
|
self.wlan_label = Label(
|
|
label=wlan,
|
|
name="wifi-widget-label",
|
|
max_chars_width=15,
|
|
h_align="start",
|
|
ellipsization="end",
|
|
)
|
|
if bluetooth != "disabled":
|
|
if bluetooth.startswith("connected:"):
|
|
parts = bluetooth.split(":")
|
|
bluetooth_display = parts[1] if len(parts) >= 2 else "Connected"
|
|
else:
|
|
bluetooth_display = "On"
|
|
else:
|
|
bluetooth_display = "Off"
|
|
|
|
self.bluetooth_label = Label(
|
|
label=bluetooth_display,
|
|
name="bluetooth-widget-label",
|
|
max_chars_width=15,
|
|
ellipsization="end",
|
|
h_align="start",
|
|
)
|
|
self.volume_scale = Scale(
|
|
value=volume,
|
|
min_value=0,
|
|
max_value=100,
|
|
increments=(5, 5),
|
|
name="volume-widget-slider",
|
|
size=30,
|
|
h_expand=True,
|
|
)
|
|
self.volume_scale.connect("change-value", self.set_volume)
|
|
self.volume_scale.connect("scroll-event", self.on_volume_scroll)
|
|
|
|
current_brightness = brightness_service.screen_brightness
|
|
brightness_percentage = (
|
|
int((current_brightness / brightness_service.max_screen) * 100)
|
|
if brightness_service.max_screen > 0
|
|
else 50
|
|
)
|
|
|
|
self.brightness_scale = Scale(
|
|
value=brightness_percentage,
|
|
min_value=0,
|
|
max_value=100,
|
|
increments=(5, 5),
|
|
name="brightness-widget-slider",
|
|
size=30,
|
|
h_expand=True,
|
|
)
|
|
|
|
# Only connect brightness controls if brightness service is available
|
|
if brightness_service.max_screen > 0:
|
|
self.brightness_scale.connect("change-value", self.set_brightness)
|
|
self.brightness_scale.connect("scroll-event", self.on_brightness_scroll)
|
|
self._signal_connections.append(
|
|
brightness_service.connect("screen", self.brightness_changed)
|
|
)
|
|
else:
|
|
# Disable brightness scale if no backlight device available
|
|
self.brightness_scale.set_sensitive(False)
|
|
|
|
# Create placeholder music widget - lazy load content when needed
|
|
self.music_widget = Box(
|
|
name="music-widget",
|
|
h_align="start",
|
|
children=[], # Empty initially
|
|
)
|
|
|
|
self.has_bluetooth_open = False
|
|
self.has_wifi_open = False
|
|
self.has_per_app_volume_open = False
|
|
self.has_expanded_player_open = False
|
|
|
|
self.bluetooth_svg = Svg(
|
|
name="bluetooth-icon",
|
|
svg_file=get_relative_path(
|
|
"../../config/assets/icons/applets/bluetooth.svg"
|
|
if bluetooth != "disabled"
|
|
else "../../config/assets/icons/applets/bluetooth-off.svg"
|
|
),
|
|
size=42,
|
|
)
|
|
self.wifi_svg = Svg(
|
|
name="wifi-icon",
|
|
svg_file=get_relative_path(
|
|
"../../config/assets/icons/applets/wifi.svg"
|
|
if wlan != "No Connection"
|
|
else "../../config/assets/icons/applets/wifi-off.svg"
|
|
),
|
|
size=42,
|
|
)
|
|
|
|
self.bluetooth_widget = Box(
|
|
name="bluetooth-widget",
|
|
orientation="h",
|
|
children=[
|
|
Button(
|
|
name="bluetooth-icon-button",
|
|
child=self.bluetooth_svg,
|
|
on_clicked=self.toggle_bluetooth,
|
|
),
|
|
Button(
|
|
name="bluetooth-info-button",
|
|
child=Box(
|
|
name="bluetooth-widget-info",
|
|
orientation="vertical",
|
|
children=[
|
|
Label(
|
|
name="bluetooth-widget-name",
|
|
label="Bluetooth",
|
|
style_classes="ct",
|
|
h_align="start",
|
|
),
|
|
self.bluetooth_label,
|
|
],
|
|
),
|
|
on_clicked=self.open_bluetooth,
|
|
),
|
|
],
|
|
)
|
|
|
|
self.wlan_widget = Box(
|
|
name="wifi-widget",
|
|
orientation="h",
|
|
children=[
|
|
Button(
|
|
name="wifi-icon-button",
|
|
child=self.wifi_svg,
|
|
on_clicked=self.toggle_wifi,
|
|
),
|
|
Button(
|
|
name="wifi-info-button",
|
|
child=Box(
|
|
name="wifi-widget-info",
|
|
orientation="vertical",
|
|
children=[
|
|
Label(
|
|
name="wifi-widget-name",
|
|
label="Wi-Fi",
|
|
style_classes="ct",
|
|
h_align="start",
|
|
),
|
|
self.wlan_label,
|
|
],
|
|
),
|
|
on_clicked=self.open_wifi,
|
|
),
|
|
],
|
|
)
|
|
|
|
self.focus_icon = Svg(
|
|
name="focus-icon",
|
|
svg_file=get_relative_path(
|
|
"../../config/assets/icons/applets/dnd.svg"
|
|
if self.focus_mode
|
|
else "../../config/assets/icons/applets/dnd-off.svg"
|
|
),
|
|
size=42,
|
|
)
|
|
|
|
self.focus_status_label = Label(
|
|
label="On" if self.focus_mode else "Off",
|
|
style_classes="status-label",
|
|
h_align="start",
|
|
)
|
|
|
|
self.focus_widget = Button(
|
|
name="focus-widget",
|
|
child=Box(
|
|
spacing=4,
|
|
orientation="h",
|
|
children=[
|
|
self.focus_icon,
|
|
Box(
|
|
v_expand=True,
|
|
h_expand=True,
|
|
v_align="center",
|
|
orientation="v",
|
|
h_align="start",
|
|
children=[
|
|
Label(
|
|
label="Focus",
|
|
style_classes="title-widget",
|
|
h_align="start",
|
|
),
|
|
self.focus_status_label,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
on_clicked=self.set_dont_disturb,
|
|
)
|
|
|
|
self.flight_icon = Svg(
|
|
name="flight-icon",
|
|
svg_file=get_relative_path(
|
|
"../../config/assets/icons/applets/flight-on.svg"
|
|
if self.flight_mode
|
|
else "../../config/assets/icons/applets/flight-off.svg"
|
|
),
|
|
size=42,
|
|
)
|
|
|
|
self.flight_widget = Button(
|
|
name="flight-widget",
|
|
child=Box(
|
|
orientation="h",
|
|
spacing=4,
|
|
h_align="start",
|
|
v_align="center",
|
|
h_expand=True,
|
|
children=[
|
|
self.flight_icon,
|
|
Label(
|
|
label="Flight",
|
|
style_classes="title",
|
|
h_align="start",
|
|
),
|
|
],
|
|
),
|
|
on_clicked=self.toggle_flight_mode,
|
|
)
|
|
|
|
self.caffeine_icon = Svg(
|
|
name="caffeine-icon",
|
|
svg_file=get_relative_path(
|
|
"../../config/assets/icons/applets/caffeine-on.svg"
|
|
if self.caffeine_mode
|
|
else "../../config/assets/icons/applets/caffeine-off.svg"
|
|
),
|
|
size=42,
|
|
)
|
|
|
|
self.caffeine_status_label = Label(
|
|
label="On" if self.caffeine_mode else "Off",
|
|
style_classes="status-label",
|
|
h_align="start",
|
|
)
|
|
|
|
self.caffeine_widget = Button(
|
|
name="caffeine-widget",
|
|
child=Box(
|
|
orientation="h",
|
|
spacing=4,
|
|
h_align="start",
|
|
v_align="center",
|
|
children=[
|
|
self.caffeine_icon,
|
|
Box(
|
|
orientation="vertical",
|
|
v_expand=True,
|
|
v_align="center",
|
|
h_align="start",
|
|
h_expand=True,
|
|
children=[
|
|
Label(
|
|
label="Caffeine",
|
|
style_classes="title-widget",
|
|
h_align="start",
|
|
),
|
|
self.caffeine_status_label,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
on_clicked=self.toggle_caffeine,
|
|
)
|
|
|
|
# Create main widgets directly without XML
|
|
self.widgets = Box(
|
|
orientation="vertical",
|
|
h_expand=True,
|
|
name="control-center-widgets",
|
|
children=[
|
|
# Top section: Wi-Fi + Bluetooth on left, DND + Flight Mode + Caffeine on right
|
|
Box(
|
|
orientation="horizontal",
|
|
h_expand=True,
|
|
name="top-widget",
|
|
children=[
|
|
# Left side: Wi-Fi and Bluetooth
|
|
Box(
|
|
orientation="vertical",
|
|
name="wb-widget",
|
|
style_classes="menu",
|
|
spacing=5,
|
|
children=[
|
|
self.wlan_widget,
|
|
self.bluetooth_widget,
|
|
],
|
|
),
|
|
# Right side: DND, Flight Mode, and Caffeine
|
|
Box(
|
|
orientation="vertical",
|
|
h_expand=True,
|
|
name="right-side-widget",
|
|
children=[
|
|
# DND widget
|
|
Box(
|
|
orientation="vertical",
|
|
name="dnd-widget",
|
|
style_classes="menu",
|
|
children=[
|
|
self.focus_widget,
|
|
],
|
|
),
|
|
# Flight Mode and Caffeine row
|
|
Box(
|
|
orientation="horizontal",
|
|
name="flight-caffeine-row",
|
|
children=[
|
|
# Box(
|
|
# orientation="vertical",
|
|
# name="flight-widget-container",
|
|
# style_classes="menu",
|
|
# h_expand=True,
|
|
# children=[
|
|
# self.flight_widget,
|
|
# ],
|
|
# ),
|
|
Box(
|
|
orientation="vertical",
|
|
name="caffeine-widget-container",
|
|
h_expand=True,
|
|
style_classes="menu",
|
|
children=[
|
|
self.caffeine_widget,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
Box(
|
|
orientation="vertical",
|
|
name="brightness-widget",
|
|
style_classes="menu",
|
|
h_expand=True,
|
|
children=[
|
|
Label(label="Display", style_classes="title", h_align="start"),
|
|
self.brightness_scale,
|
|
Label(
|
|
label=" ", name="brightness-widget-icon", h_align="start"
|
|
),
|
|
],
|
|
),
|
|
Box(
|
|
orientation="vertical",
|
|
name="volume-widget",
|
|
style_classes="menu",
|
|
h_expand=True,
|
|
children=[
|
|
Label(label="Sound", style_classes="title", h_align="start"),
|
|
Box(
|
|
name="vol-box",
|
|
orientation="horizontal",
|
|
spacing=8,
|
|
v_expand=True,
|
|
children=[
|
|
Box(
|
|
name="vol-slider-box",
|
|
h_expand=True,
|
|
v_align="center",
|
|
v_expand=False,
|
|
children=[
|
|
self.volume_scale,
|
|
],
|
|
),
|
|
Button(
|
|
name="per-app-volume-button",
|
|
size=(36, 36),
|
|
child=Svg(
|
|
svg_file=get_relative_path(
|
|
"../../config/assets/icons/player/audio-switcher.svg"
|
|
),
|
|
name="per-app-volume-icon",
|
|
sidze=32,
|
|
),
|
|
on_clicked=self.open_per_app_volume,
|
|
),
|
|
],
|
|
),
|
|
Label(label=" ", name="volume-widget-icon", h_align="start"),
|
|
],
|
|
),
|
|
self.music_widget,
|
|
],
|
|
)
|
|
|
|
# Initialize managers directly like working version
|
|
self.wifi_man = WifiConnections(self)
|
|
self.bluetooth_man = BluetoothConnections(self)
|
|
|
|
self.has_bluetooth_open = False
|
|
self.has_wifi_open = False
|
|
|
|
# Lazy-loaded widgets - create placeholders
|
|
self.bluetooth_widgets = None
|
|
self.wifi_widgets = None
|
|
self.per_app_volume_widgets = None
|
|
self.expanded_player_widgets = None
|
|
|
|
# Create main content boxes
|
|
self.center_box = CenterBox(start_children=[self.widgets])
|
|
self.bluetooth_center_box = None
|
|
self.wifi_center_box = None
|
|
self.per_app_volume_center_box = None
|
|
self.expanded_player_center_box = None
|
|
|
|
# Create revealers for crossfade transitions
|
|
|
|
self.widgets.set_size_request(300, -1)
|
|
|
|
self.children = self.center_box
|
|
|
|
# Track current state for smooth transitions
|
|
self.current_view = "main" # main, expanded_player
|
|
|
|
# Connect to visibility changes for cleanup
|
|
self.connect("notify::visible", self._on_visibility_changed)
|
|
|
|
def _on_visibility_changed(self, widget, param):
|
|
"""Handle visibility changes for memory management"""
|
|
if not self.get_visible():
|
|
self._cleanup_when_hidden()
|
|
|
|
def _cleanup_when_hidden(self):
|
|
"""Aggressively clean up resources when widget is hidden to reduce memory usage"""
|
|
try:
|
|
# Clean up music widget content if it exists
|
|
if self._music_widget_content:
|
|
# Remove from the parent container
|
|
current_children = list(self.music_widget.children)
|
|
if self._music_widget_content in current_children:
|
|
current_children.remove(self._music_widget_content)
|
|
self.music_widget.children = current_children
|
|
|
|
# Trigger periodic cleanup before destroying
|
|
if hasattr(self._music_widget_content, "_periodic_cleanup"):
|
|
self._music_widget_content._periodic_cleanup()
|
|
|
|
# Properly destroy the music widget content
|
|
try:
|
|
self._music_widget_content.destroy()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to destroy music widget content: {e}")
|
|
self._music_widget_content = None
|
|
|
|
# Clean up shared MPRIS manager when hidden to free memory
|
|
if self._mpris_manager:
|
|
try:
|
|
self._mpris_manager.destroy()
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Failed to destroy MPRIS manager during cleanup: {e}"
|
|
)
|
|
self._mpris_manager = None
|
|
|
|
# Reset initialization flags to force recreation next time
|
|
self._music_initialized = False
|
|
|
|
# Force garbage collection
|
|
import gc
|
|
|
|
gc.collect()
|
|
|
|
logger.debug("Control center aggressive cleanup completed")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Control center cleanup failed: {e}")
|
|
|
|
def _ensure_music_widget(self):
|
|
"""Lazy load music widget content - reuse MPRIS manager"""
|
|
if not self._music_initialized:
|
|
# Create shared MPRIS manager if it doesn't exist
|
|
if self._mpris_manager is None:
|
|
self._mpris_manager = MprisPlayerManager()
|
|
|
|
self._music_widget_content = PlayerBoxStack(
|
|
self._mpris_manager, control_center=self
|
|
)
|
|
# Add to the music widget's children list
|
|
current_children = list(self.music_widget.children)
|
|
current_children.append(self._music_widget_content)
|
|
self.music_widget.children = current_children
|
|
self._music_initialized = True
|
|
|
|
def _ensure_bluetooth_widgets(self):
|
|
"""Lazy load bluetooth widgets"""
|
|
if self.bluetooth_widgets is None:
|
|
self.bluetooth_widgets = Box(
|
|
orientation="vertical",
|
|
h_expand=True,
|
|
v_expand=True,
|
|
children=[
|
|
self.bluetooth_man,
|
|
# Box(
|
|
# orientation="horizontal",
|
|
# name="top-widget",
|
|
# h_expand=True,
|
|
# children=[
|
|
# Box(
|
|
# orientation="vertical",
|
|
# name="wb-widget",
|
|
# style_classes="menu",
|
|
# spacing=5,
|
|
# children=[
|
|
# ],
|
|
# ),
|
|
# ],
|
|
# ),
|
|
],
|
|
)
|
|
self.bluetooth_center_box = Box(
|
|
h_expand=True, v_expand=True, children=[self.bluetooth_widgets]
|
|
)
|
|
self.bluetooth_center_box.set_size_request(350, -1)
|
|
|
|
def _ensure_wifi_widgets(self):
|
|
"""Lazy load wifi widgets"""
|
|
if self.wifi_widgets is None:
|
|
self.wifi_widgets = Box(
|
|
orientation="vertical",
|
|
h_expand=True,
|
|
v_expand=True,
|
|
children=[
|
|
self.wifi_man,
|
|
],
|
|
)
|
|
self.wifi_center_box = Box(
|
|
h_expand=True, v_expand=True, children=[self.wifi_widgets]
|
|
)
|
|
self.wifi_center_box.set_size_request(350, -1)
|
|
|
|
def _ensure_per_app_volume_widgets(self):
|
|
"""Lazy load per-app volume widgets"""
|
|
if self.per_app_volume_widgets is None:
|
|
if self._per_app_volume_widget is None:
|
|
self._per_app_volume_widget = PerAppVolumeControl(self)
|
|
|
|
self.per_app_volume_widgets = Box(
|
|
orientation="vertical",
|
|
h_expand=True,
|
|
name="control-center-widgets",
|
|
children=[
|
|
self._per_app_volume_widget,
|
|
],
|
|
)
|
|
self.per_app_volume_center_box = CenterBox(
|
|
start_children=[self.per_app_volume_widgets]
|
|
)
|
|
self.per_app_volume_center_box.set_size_request(300, -1)
|
|
|
|
def _ensure_expanded_player_widgets(self):
|
|
"""Lazy load expanded player widgets"""
|
|
if self.expanded_player_widgets is None:
|
|
if self._expanded_player_widget is None:
|
|
self._expanded_player_widget = EmbeddedExpandedPlayer(self)
|
|
|
|
self.expanded_player_widgets = Box(
|
|
orientation="vertical",
|
|
h_expand=True,
|
|
name="control-center-widgets",
|
|
children=[
|
|
self._expanded_player_widget,
|
|
],
|
|
)
|
|
self.expanded_player_center_box = CenterBox(
|
|
start_children=[self.expanded_player_widgets]
|
|
)
|
|
self.expanded_player_center_box.set_size_request(300, -1)
|
|
|
|
def _check_initial_states(self):
|
|
# Check if caffeine is already running
|
|
try:
|
|
result = subprocess.run(
|
|
["pgrep", "-f", "modus-inhibit"], capture_output=True, text=True
|
|
)
|
|
self.caffeine_mode = bool(result.stdout.strip())
|
|
except Exception:
|
|
self.caffeine_mode = False
|
|
|
|
# Flight mode starts as False (normal mode)
|
|
self.flight_mode = False
|
|
|
|
# Update initial labels (will be set after widgets are created)
|
|
GLib.timeout_add(100, self._update_initial_labels)
|
|
|
|
def _update_initial_labels(self):
|
|
return False
|
|
|
|
def set_dont_disturb(self, *_):
|
|
self.focus_mode = not self.focus_mode
|
|
modus_service.dont_disturb = self.focus_mode
|
|
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"
|
|
)
|
|
)
|
|
self.focus_status_label.set_label("On" if self.focus_mode else "Off")
|
|
|
|
def toggle_flight_mode(self, *_):
|
|
try:
|
|
self.flight_mode = not self.flight_mode
|
|
|
|
if self.flight_mode:
|
|
# Turn off WiFi and Bluetooth
|
|
if self.wifi_service:
|
|
self.wifi_service.wireless_enabled = False
|
|
if hasattr(self, "bluetooth_man") and hasattr(
|
|
self.bluetooth_man, "client"
|
|
):
|
|
self.bluetooth_man.client.set_enabled(False)
|
|
else:
|
|
# Turn on WiFi and Bluetooth
|
|
if self.wifi_service:
|
|
self.wifi_service.wireless_enabled = True
|
|
if hasattr(self, "bluetooth_man") and hasattr(
|
|
self.bluetooth_man, "client"
|
|
):
|
|
self.bluetooth_man.client.set_enabled(True)
|
|
|
|
# Update icon
|
|
self.flight_icon.set_from_file(
|
|
get_relative_path(
|
|
"../../config/assets/icons/applets/flight-on.svg"
|
|
if self.flight_mode
|
|
else "../../config/assets/icons/applets/flight-off.svg"
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Failed to toggle flight mode: {e}")
|
|
|
|
def toggle_caffeine(self, *_):
|
|
try:
|
|
if self.caffeine_mode:
|
|
# Turn off caffeine
|
|
inhibit_script = get_relative_path("../../utils/inhibit.py")
|
|
subprocess.run(["python3", inhibit_script, "off"], check=False)
|
|
self.caffeine_mode = False
|
|
if self._caffeine_process:
|
|
try:
|
|
self._caffeine_process.terminate()
|
|
except:
|
|
pass
|
|
self._caffeine_process = None
|
|
else:
|
|
# Turn on caffeine
|
|
inhibit_script = get_relative_path("../../utils/inhibit.py")
|
|
self._caffeine_process = subprocess.Popen(
|
|
["python3", inhibit_script, "on"], start_new_session=True
|
|
)
|
|
self.caffeine_mode = True
|
|
|
|
self.caffeine_icon.set_from_file(
|
|
get_relative_path(
|
|
"../../config/assets/icons/applets/caffeine-on.svg"
|
|
if self.caffeine_mode
|
|
else "../../config/assets/icons/applets/caffeine-off.svg"
|
|
)
|
|
)
|
|
self.caffeine_status_label.set_label("On" if self.caffeine_mode else "Off")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Failed to toggle caffeine: {e}")
|
|
|
|
def set_volume(self, _, __, volume):
|
|
self._updating_volume = True
|
|
audio_service.speaker.volume = round(volume)
|
|
self._updating_volume = False
|
|
|
|
def set_brightness(self, _, __, brightness):
|
|
self._updating_brightness = True
|
|
brightness_value = int((brightness / 100) * brightness_service.max_screen)
|
|
brightness_service.screen_brightness = brightness_value
|
|
self._updating_brightness = False
|
|
|
|
def brightness_changed(self, _, brightness_value):
|
|
if self._updating_brightness:
|
|
return
|
|
|
|
if brightness_service.max_screen > 0:
|
|
brightness_percentage = int(
|
|
(brightness_value / brightness_service.max_screen) * 100
|
|
)
|
|
|
|
GLib.idle_add(
|
|
lambda: self.brightness_scale.set_value(brightness_percentage)
|
|
)
|
|
|
|
def on_volume_scroll(self, widget, event):
|
|
current_value = self.volume_scale.get_value()
|
|
scroll_step = 5
|
|
if event.direction == Gdk.ScrollDirection.UP:
|
|
new_value = min(100, current_value + scroll_step)
|
|
elif event.direction == Gdk.ScrollDirection.DOWN:
|
|
new_value = max(0, current_value - scroll_step)
|
|
else:
|
|
return False
|
|
|
|
self.volume_scale.set_value(new_value)
|
|
return True
|
|
|
|
def on_brightness_scroll(self, widget, event):
|
|
current_value = self.brightness_scale.get_value()
|
|
scroll_step = 5
|
|
if event.direction == Gdk.ScrollDirection.UP:
|
|
new_value = min(100, current_value + scroll_step)
|
|
elif event.direction == Gdk.ScrollDirection.DOWN:
|
|
new_value = max(0, current_value - scroll_step)
|
|
else:
|
|
return False
|
|
|
|
self.brightness_scale.set_value(new_value)
|
|
return True
|
|
|
|
def toggle_bluetooth(self, *_):
|
|
"""Toggle bluetooth on/off"""
|
|
try:
|
|
# Access the bluetooth client from the bluetooth manager
|
|
if hasattr(self, "bluetooth_man") and hasattr(self.bluetooth_man, "client"):
|
|
current_state = self.bluetooth_man.client.enabled
|
|
self.bluetooth_man.client.set_enabled(not current_state)
|
|
else:
|
|
logger.warning("Bluetooth client not available for toggling")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to toggle bluetooth: {e}")
|
|
|
|
def on_network_ready(self, *_):
|
|
"""Called when network service is ready"""
|
|
self.wifi_service = self.network_service.wifi_device
|
|
if self.wifi_service:
|
|
# Connect to WiFi state changes to update icon
|
|
self.wifi_service.connect("notify::wireless-enabled", self.update_wifi_icon)
|
|
|
|
def update_wifi_icon(self, *_):
|
|
"""Update WiFi icon based on current state"""
|
|
try:
|
|
if self.wifi_service and hasattr(self, "wifi_svg"):
|
|
is_enabled = self.wifi_service.wireless_enabled
|
|
icon_file = (
|
|
"../../config/assets/icons/applets/wifi.svg"
|
|
if is_enabled
|
|
else "../../config/assets/icons/applets/wifi-off.svg"
|
|
)
|
|
self.wifi_svg.set_from_file(get_relative_path(icon_file))
|
|
except Exception as e:
|
|
logger.warning(f"Failed to update WiFi icon: {e}")
|
|
|
|
def toggle_wifi(self, *_):
|
|
"""Toggle wifi on/off"""
|
|
try:
|
|
if self.wifi_service:
|
|
self.wifi_service.toggle_wifi()
|
|
# Update icon immediately after toggle
|
|
GLib.timeout_add(100, self.update_wifi_icon)
|
|
else:
|
|
logger.warning("WiFi device not available for toggling")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to toggle wifi: {e}")
|
|
|
|
def set_children(self, children):
|
|
self.children = children
|
|
|
|
def open_bluetooth(self, *_):
|
|
self._ensure_bluetooth_widgets()
|
|
idle_add(lambda *_: self.set_children(self.bluetooth_center_box))
|
|
self.has_bluetooth_open = True
|
|
|
|
def open_wifi(self, *_):
|
|
self._ensure_wifi_widgets()
|
|
idle_add(lambda *_: self.set_children(self.wifi_center_box))
|
|
self.has_wifi_open = True
|
|
|
|
def close_bluetooth(self, *_):
|
|
if self.current_view == "expanded_player":
|
|
self._crossfade_to_view("main")
|
|
else:
|
|
idle_add(lambda *_: self.set_children(self.center_box))
|
|
self.has_bluetooth_open = False
|
|
|
|
def close_wifi(self, *_):
|
|
if self.current_view == "expanded_player":
|
|
self._crossfade_to_view("main")
|
|
else:
|
|
idle_add(lambda *_: self.set_children(self.center_box))
|
|
self.has_wifi_open = False
|
|
|
|
def open_per_app_volume(self, *_):
|
|
self._ensure_per_app_volume_widgets()
|
|
if self.current_view == "expanded_player":
|
|
# If coming from expanded player, use crossfade
|
|
self._crossfade_to_view("main")
|
|
GLib.timeout_add(
|
|
250,
|
|
lambda: idle_add(
|
|
lambda *_: self.set_children(self.per_app_volume_center_box)
|
|
),
|
|
)
|
|
else:
|
|
idle_add(lambda *_: self.set_children(self.per_app_volume_center_box))
|
|
self.has_per_app_volume_open = True
|
|
# Refresh the app list when opening
|
|
if self._per_app_volume_widget:
|
|
self._per_app_volume_widget.refresh()
|
|
|
|
def close_per_app_volume(self, *_):
|
|
if self.current_view == "expanded_player":
|
|
self._crossfade_to_view("main")
|
|
else:
|
|
idle_add(lambda *_: self.set_children(self.center_box))
|
|
self.has_per_app_volume_open = False
|
|
|
|
def open_expanded_player(self, *_):
|
|
self._ensure_expanded_player_widgets()
|
|
self._crossfade_to_view("expanded_player")
|
|
self.has_expanded_player_open = True
|
|
# Refresh the player when opening
|
|
if self._expanded_player_widget:
|
|
self._expanded_player_widget.refresh()
|
|
|
|
def close_expanded_player(self, *_):
|
|
self._crossfade_to_view("main")
|
|
self.has_expanded_player_open = False
|
|
|
|
def _crossfade_to_view(self, view_name):
|
|
"""Handle transitions between views"""
|
|
if view_name == "expanded_player":
|
|
# Show expanded player
|
|
self._ensure_expanded_player_widgets()
|
|
idle_add(lambda *_: self.set_children(self.expanded_player_center_box))
|
|
self.current_view = "expanded_player"
|
|
elif view_name == "main":
|
|
# Show main view
|
|
idle_add(lambda *_: self.set_children(self.center_box))
|
|
self.current_view = "main"
|
|
|
|
def _set_mousecapture(self, visible: bool):
|
|
if visible:
|
|
# Lazy load music widget when becoming visible
|
|
self._ensure_music_widget()
|
|
|
|
self.set_visible(visible)
|
|
if not visible:
|
|
self.close_bluetooth()
|
|
self.close_wifi()
|
|
self.close_per_app_volume()
|
|
self.close_expanded_player()
|
|
|
|
def volume_changed(
|
|
self,
|
|
_,
|
|
):
|
|
if self._updating_volume:
|
|
return
|
|
|
|
GLib.idle_add(
|
|
lambda: self.volume_scale.set_value(int(audio_service.speaker.volume))
|
|
)
|
|
|
|
def wlan_changed(self, _, wlan):
|
|
self.wifi_svg.set_from_file(
|
|
get_relative_path(
|
|
"../../config/assets/icons/applets/wifi.svg"
|
|
if wlan != "No Connection"
|
|
else "../../config/assets/icons/applets/wifi-off.svg"
|
|
)
|
|
)
|
|
if wlan != "No Connection":
|
|
if wlan.startswith("connected:"):
|
|
parts = wlan.split(":")
|
|
if len(parts) >= 2:
|
|
wifi_name = parts[1]
|
|
GLib.idle_add(
|
|
lambda: self.wlan_label.set_property("label", wifi_name)
|
|
)
|
|
else:
|
|
GLib.idle_add(
|
|
lambda: self.wlan_label.set_property("label", "Connected")
|
|
)
|
|
else:
|
|
GLib.idle_add(lambda: self.wlan_label.set_property("label", wlan))
|
|
else:
|
|
GLib.idle_add(lambda: self.wlan_label.set_property("label", wlan))
|
|
|
|
def bluetooth_changed(self, _, bluetooth):
|
|
self.bluetooth_svg.set_from_file(
|
|
get_relative_path(
|
|
"../../config/assets/icons/applets/bluetooth.svg"
|
|
if bluetooth != "disabled"
|
|
else "../../config/assets/icons/applets/bluetooth-off.svg"
|
|
)
|
|
)
|
|
if bluetooth != "disabled":
|
|
if bluetooth.startswith("connected:"):
|
|
parts = bluetooth.split(":")
|
|
if len(parts) >= 2:
|
|
device_name = parts[1]
|
|
GLib.idle_add(lambda: self.bluetooth_label.set_label(device_name))
|
|
else:
|
|
GLib.idle_add(lambda: self.bluetooth_label.set_label("Connected"))
|
|
elif bluetooth == "enabled":
|
|
GLib.idle_add(lambda: self.bluetooth_label.set_label("On"))
|
|
else:
|
|
GLib.idle_add(lambda: self.bluetooth_label.set_label("On"))
|
|
else:
|
|
GLib.idle_add(lambda: self.bluetooth_label.set_label("Off"))
|
|
|
|
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"
|
|
)
|
|
)
|
|
self.focus_status_label.set_label("On" if self.focus_mode else "Off")
|
|
|
|
def _init_mousecapture(self, mousecapture):
|
|
self._mousecapture_parent = mousecapture
|
|
|
|
def hide_controlcenter(self, *_):
|
|
self._mousecapture_parent.toggle_mousecapture()
|
|
self.set_visible(False)
|
|
|
|
def destroy(self):
|
|
"""Clean up resources when widget is destroyed"""
|
|
# Disconnect all signal connections
|
|
for connection in self._signal_connections:
|
|
try:
|
|
connection.disconnect()
|
|
except:
|
|
pass
|
|
|
|
# Clean up caffeine process
|
|
if self._caffeine_process:
|
|
try:
|
|
self._caffeine_process.terminate()
|
|
except:
|
|
pass
|
|
self._caffeine_process = None
|
|
|
|
# Clean up heavy components
|
|
if hasattr(self, "wifi_man") and self.wifi_man:
|
|
self.wifi_man.destroy()
|
|
if hasattr(self, "bluetooth_man") and self.bluetooth_man:
|
|
self.bluetooth_man.destroy()
|
|
if self._music_widget_content:
|
|
self._music_widget_content.destroy()
|
|
if self._per_app_volume_widget:
|
|
self._per_app_volume_widget.destroy()
|
|
if self._expanded_player_widget:
|
|
self._expanded_player_widget.destroy()
|
|
|
|
# Clean up shared MPRIS manager
|
|
if self._mpris_manager:
|
|
try:
|
|
self._mpris_manager.destroy()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to destroy MPRIS manager: {e}")
|
|
self._mpris_manager = None
|
|
|
|
super().destroy()
|