Files
Modus/modules/bar/components/system_indicators.py
T
2025-05-23 09:55:16 +05:45

292 lines
11 KiB
Python

import subprocess
from gi.repository import Gtk, Gdk
from fabric import Fabricator
from fabric.widgets.label import Label
from fabric.widgets.box import Box
from fabric.widgets.revealer import Revealer
from fabric.bluetooth import BluetoothClient
from services import network_client, audio
import utils.icons as icons
from fabric.hyprland.widgets import Language, HyprlandEvent, get_hyprland_connection
class SystemIndicators(Box):
def __init__(self, **kwargs):
super().__init__(
orientation="h", spacing=2, name="system-indicator", **kwargs
)
self.bluetooth_icon = Label(name="system-indicator-icon")
self.wifi_icon = Label(name="system-indicator-icon")
self.volume_icon_button = Label(name="system-indicator-icon")
self.language_icon = Label(name="lang-label")
self.idle_label = Label(name="system-indicator-icon")
self.night_label = Label(name="system-indicator-icon")
self.power_label = Label(name="system-indicator-icon")
# Create event box to wrap indicators
self.event_box = Gtk.EventBox()
self.indicators_box = Box(name="indicators-container", orientation="h", spacing=2)
self.event_box.add(self.indicators_box)
# Create revealers for additional indicators
self.volume_revealer = Revealer(
name="volume-revealer",
transition_duration=250,
transition_type="slide-left",
child=self.volume_icon_button,
child_revealed=False,
)
self.idle_revealer = Revealer(
name="idle-revealer",
transition_duration=250,
transition_type="slide-left",
child=self.idle_label,
child_revealed=False,
)
self.night_revealer = Revealer(
name="night-revealer",
transition_duration=250,
transition_type="slide-left",
child=self.night_label,
child_revealed=False,
)
self.power_revealer = Revealer(
name="power-revealer",
transition_duration=250,
transition_type="slide-left",
child=self.power_label,
child_revealed=False,
)
# Add all indicators with revealers
self.indicators_box.add(self.language_icon)
self.indicators_box.add(self.wifi_icon)
self.indicators_box.add(self.bluetooth_icon)
self.indicators_box.add(self.volume_revealer)
self.indicators_box.add(self.idle_revealer)
self.indicators_box.add(self.night_revealer)
self.indicators_box.add(self.power_revealer)
# Add event box to main container
self.add(self.event_box)
# Connect click events
self.event_box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.event_box.connect("button-press-event", self.on_button_press)
self.audio_service = audio
self.bluetooth_client = BluetoothClient()
self.network_client = network_client
self.hyprland = get_hyprland_connection()
# Connect signals
self.bluetooth_client.connect("changed", self.update_bluetooth_status)
self.audio_service.connect("changed", self.update_volume_status)
self.network_client.connect("device_ready", self.update_network_status)
self.hyprland.connect("event::activelayout", self.update_language)
if self.network_client.wifi_device:
self.network_client.wifi_device.connect(
"changed", self.update_network_status
)
if self.network_client.ethernet_device:
self.network_client.ethernet_device.connect(
"changed", self.update_network_status
)
Fabricator(interval=1000, poll_from=self.update_all_statuses)
# Initial updates
self.update_bluetooth_status()
self.update_language()
self.update_idle_night_status()
self.showing_all_indicators = False
def on_button_press(self, widget, event):
if event.button == 3: # Right click
self.toggle_labels()
return True # Stop event propagation
elif event.button == 1: # Left click
self.toggle_all_indicators()
return True # Stop event propagation
return False
def toggle_all_indicators(self):
self.showing_all_indicators = not self.showing_all_indicators
# Toggle revealers
self.volume_revealer.set_reveal_child(self.showing_all_indicators)
self.idle_revealer.set_reveal_child(self.showing_all_indicators)
self.night_revealer.set_reveal_child(self.showing_all_indicators)
self.power_revealer.set_reveal_child(self.showing_all_indicators)
def toggle_labels(self):
# Toggle tooltips for all visible indicators
for child in self.indicators_box.get_children():
if isinstance(child, Label):
current_tooltip = child.get_tooltip_text()
if current_tooltip:
child.set_tooltip_text("")
else:
# Restore original tooltip based on the indicator type
if child == self.wifi_icon:
self.update_network_status()
elif child == self.bluetooth_icon:
self.update_bluetooth_status()
elif child == self.volume_icon_button:
self.update_volume_status()
elif child == self.language_icon:
self.update_language()
def update_all_statuses(self, *_args):
self.update_idle_night_status()
self.update_power_profile()
self.update_network_status()
def update_language(self, _=None, event: HyprlandEvent = None):
"""Update the language indicator based on Hyprland events."""
lang = event.data[1] if event else Language().get_label()
self.language_icon.set_tooltip_text(lang)
self.language_icon.set_label(lang[:2].lower())
def update_idle_night_status(self, *_):
# Update idle status
has_idle = (
subprocess.run(["pgrep", "-x", "wlinhibit"], capture_output=True).returncode
== 0
)
self.idle_label.set_visible(has_idle)
self.idle_label.set_markup(icons.coffee if has_idle else "")
# Update night mode status
has_night = (
subprocess.run(
["pgrep", "-x", "hyprsunset"], capture_output=True
).returncode
== 0
)
self.night_label.set_visible(has_night)
self.night_label.set_markup(icons.night if has_night else "")
def update_volume_status(self, *_):
stream = self.audio_service.speaker
if not stream:
self.volume_icon_button.set_visible(False)
return
self.volume_icon_button.set_visible(True)
# Normalize volume to integer percentage (0-100)
volume_level = stream.volume
if volume_level > 1:
volume_level = min(int(volume_level), 100)
else:
volume_level = int(volume_level * 100)
is_muted = stream.muted
if is_muted:
icon = icons.vol_off
elif volume_level > 74:
icon = icons.vol_high
elif volume_level > 0:
icon = icons.vol_medium
else:
icon = icons.vol_mute
self.volume_icon_button.set_markup(icon)
tooltip_text = "Muted" if is_muted else f"Volume: {volume_level}%"
self.volume_icon_button.set_tooltip_text(tooltip_text)
def update_bluetooth_status(self, *_):
if self.bluetooth_client.enabled:
self.bluetooth_icon.set_visible(True)
# Get a list of connected devices
connected_devices = [
device.name
for device in self.bluetooth_client.devices
if device.connected
]
if connected_devices:
self.bluetooth_icon.set_markup(icons.bluetooth_connected)
self.bluetooth_icon.set_tooltip_text(f"{', '.join(connected_devices)}")
else:
self.bluetooth_icon.set_markup(icons.bluetooth)
self.bluetooth_icon.set_tooltip_text(
"Bluetooth is enabled, no devices connected"
)
else:
self.bluetooth_icon.set_visible(True) # Keep it visible
self.bluetooth_icon.set_markup(icons.bluetooth_off)
self.bluetooth_icon.set_tooltip_text("Bluetooth is disabled")
def update_network_status(self, *_):
primary_device = self.network_client.primary_device
self.wifi_icon.set_visible(True)
if primary_device == "wifi" and self.network_client.wifi_device:
wifi_device = self.network_client.wifi_device
self.wifi_icon.set_tooltip_text(
wifi_device.ssid if wifi_device.ssid else "No WiFi Connection"
)
if wifi_device.enabled:
strength = wifi_device.strength
if strength > 0:
if strength < 25:
icon_label = icons.wifi_0
elif strength < 50:
icon_label = icons.wifi_1
elif strength < 75:
icon_label = icons.wifi_2
else:
icon_label = icons.wifi_3
else:
icon_label = icons.wifi_off
else:
icon_label = icons.wifi_off
elif primary_device == "wired":
icon_label = icons.lan
self.wifi_icon.set_tooltip_text("Wired Connection")
else:
icon_label = icons.wifi_off # No network
self.wifi_icon.set_markup(icon_label)
def update_power_profile(self, *_):
try:
result = subprocess.run(
["powerprofilesctl", "get"], capture_output=True, text=True
)
current_profile = result.stdout.strip()
profile_icons = {
"performance": icons.power_performance,
"balanced": "",
"power-saver": icons.power_saving,
}
icon = profile_icons.get(current_profile)
tooltip = (
f"Power Profile: {current_profile.capitalize()}"
if current_profile
else "Unknown Profile"
)
self.power_label.set_visible(bool(icon)) # Only show if there's an icon
self.power_label.set_markup(icon)
self.power_label.set_tooltip_text(tooltip)
except Exception as e:
self.power_label.set_visible(False)
self.power_label.set_tooltip_text(f"Error fetching power profile: {e}")