Files
Modus/modules/dock/components/battery.py
T

249 lines
9.2 KiB
Python

from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.circularprogressbar import CircularProgressBar
from fabric.widgets.label import Label
from gi.repository import Gdk, GLib
import config.data as data
import utils.icons as icons
from services.battery import Battery as BatteryService
class Battery(Box):
def __init__(self, **kwargs):
orientation = "v" if data.VERTICAL else "h"
super().__init__(
name="battery", spacing=0, orientation=orientation, visible=True, **kwargs
)
self._battery = BatteryService()
self._battery.changed.connect(self.update_battery)
self.icon = Label(name="battery-icon", markup=icons.battery)
self.circle = CircularProgressBar(
name="battery-circle",
value=0,
size=26,
line_width=2,
start_angle=150,
end_angle=390,
style_classes="battery",
child=self.icon,
)
self.circle_button = Button(
name="battery-button",
child=self.circle,
)
self.circle_button.add_events(
Gdk.EventMask.ENTER_NOTIFY_MASK
| Gdk.EventMask.LEAVE_NOTIFY_MASK
| Gdk.EventMask.BUTTON_PRESS_MASK
)
self.circle_button.connect("enter-notify-event", self.on_button_enter)
self.circle_button.connect("leave-notify-event", self.on_button_leave)
self.circle_button.connect("button-press-event", self.on_button_press)
self.level = Label(
name="battery-level",
style_classes="battery",
label="0%",
)
self.battery_box = Box(
name="battery-box",
orientation=orientation,
spacing=0,
children=[self.circle_button],
)
if not data.VERTICAL:
self.battery_box.add(self.level)
self.circle_button.set_has_tooltip(True)
self.battery_box.set_has_tooltip(True)
self.level.set_has_tooltip(True)
self.circle_button.connect("query-tooltip", self.on_query_tooltip)
self.battery_box.connect("query-tooltip", self.on_query_tooltip)
self.level.connect("query-tooltip", self.on_query_tooltip)
self.add(self.battery_box)
GLib.idle_add(self.update_battery)
def on_button_enter(self, widget, event):
window = widget.get_window()
if window:
window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
def on_button_leave(self, widget, event):
window = widget.get_window()
if window:
window.set_cursor(None)
def on_button_press(self, widget, event):
if event.button == 1: # Left click
try:
if (
not hasattr(self._battery, "_profile_proxy")
or not self._battery._profile_proxy
):
return False
current_profile = self._battery._profile_proxy.ActiveProfile
profiles = self._battery._profile_proxy.Profiles
profile_names = []
for profile in profiles:
if isinstance(profile, dict) and "Profile" in profile:
profile_names.append(profile["Profile"])
elif hasattr(profile, "Profile"):
profile_names.append(profile.Profile)
elif isinstance(profile, str):
profile_names.append(profile)
if profile_names and current_profile in profile_names:
next_index = (profile_names.index(current_profile) + 1) % len(
profile_names
)
next_profile = profile_names[next_index]
self._battery._profile_proxy.ActiveProfile = next_profile
else:
return False
except Exception:
return False
return True
return False
def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
tooltip.set_markup(self.get_tooltip_text())
return True
def get_tooltip_text(self):
if not self._battery.is_present:
return "No battery detected"
percentage = self._battery.percentage
state = self._battery.state
capacity = int(float(self._battery.capacity.rstrip("%")))
tooltip_points = []
if state == "FULLY_CHARGED":
status = f"{icons.bat_full} Fully Charged"
elif state == "CHARGING":
status = f"{icons.bat_charging} Charging"
elif percentage <= 15 and state == "DISCHARGING":
status = f"{icons.bat_low} Low Battery"
elif state == "DISCHARGING":
status = f"{icons.bat_discharging} Discharging"
else:
status = "Battery"
tooltip_points.append(f"• Status: {status}")
tooltip_points.append(f"• Level: {int(percentage)}%")
tooltip_points.append(f"• Battery Health: {capacity}%")
power_profile = self._battery.power_profile
if power_profile:
if power_profile == "power-saver":
profile_icon = icons.power_saving
elif power_profile == "performance":
profile_icon = icons.power_performance
else: # balanced
profile_icon = icons.power_balanced
tooltip_points.append(f"• Profile: {profile_icon} {power_profile}")
temp = self._battery.temperature
if temp != "N/A":
tooltip_points.append(f"• Temperature: {temp}")
if state == "CHARGING":
time_to_full = self._battery.time_to_full
if time_to_full:
tooltip_points.append(f"• Time until full: {time_to_full}")
elif state == "DISCHARGING":
time_to_empty = self._battery.time_to_empty
if time_to_empty:
tooltip_points.append(f"• Time remaining: {time_to_empty}")
return "\n".join(tooltip_points)
def update_battery(self, *args):
if not self._battery.is_present:
self.set_visible(False)
return True
percentage = self._battery.percentage
state = self._battery.state
charging = state in ["CHARGING", "FULLY_CHARGED"]
power_profile = self._battery.power_profile
self.circle.set_value(percentage / 100.0)
if percentage < 100:
self.level.set_label(f"{int(percentage)}%")
self.level.set_visible(True)
else:
self.level.set_visible(False)
self.circle.remove_style_class("power-saver")
self.circle.remove_style_class("performance")
self.circle.remove_style_class("balanced")
self.circle.remove_style_class("discharging")
self.circle.remove_style_class("discharging-low")
self.circle.remove_style_class("discharging-critical")
self.icon.remove_style_class("discharging")
self.icon.remove_style_class("discharging-low")
self.icon.remove_style_class("discharging-critical")
if power_profile:
self.circle.add_style_class(power_profile)
if power_profile == "power-saver":
self.icon.set_markup(icons.power_saving)
elif power_profile == "performance":
self.icon.set_markup(icons.power_performance)
else: # balanced
# In balanced mode, show discharging icons when discharging
if state == "DISCHARGING":
if percentage <= 15:
self.icon.set_markup(icons.bat_low)
self.circle.add_style_class("discharging-critical")
self.icon.add_style_class("discharging-critical")
elif percentage <= 30:
self.icon.set_markup(icons.alert)
self.circle.add_style_class("discharging-low")
self.icon.add_style_class("discharging-low")
else:
self.icon.set_markup(icons.bat_discharging)
self.circle.add_style_class("discharging")
self.icon.add_style_class("discharging")
elif state == "CHARGING":
self.icon.set_markup(icons.charging)
else:
self.icon.set_markup(icons.battery)
else:
if state == "FULLY_CHARGED":
self.icon.set_markup(icons.battery)
elif state == "CHARGING":
self.icon.set_markup(icons.charging)
elif state == "DISCHARGING":
self.icon.set_markup(icons.discharging)
else:
self.icon.set_markup(icons.battery)
if percentage <= 15 and not charging:
self.icon.add_style_class("alert")
self.circle.add_style_class("alert")
else:
self.icon.remove_style_class("alert")
self.circle.remove_style_class("alert")
# Only show if enabled in configuration
if data.DOCK_COMPONENTS_VISIBILITY.get("battery", True):
self.set_visible(True)
else:
self.set_visible(False)
return True