From 34f60b4c98e0c0c76fcf99c583cd4a178630bb9f Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Tue, 29 Jul 2025 12:51:03 +0545 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(battery):=20add=20psutil=20fal?= =?UTF-8?q?lback=20for=20battery=20info=20when=20UPower=20is=20unavailable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/battery.py | 59 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/services/battery.py b/services/battery.py index 828222d..b6b8e3c 100644 --- a/services/battery.py +++ b/services/battery.py @@ -1,5 +1,7 @@ from fabric.core import Property, Service, Signal from pydbus import SystemBus +import psutil +from gi.repository import GLib DeviceState = { 0: "UNKNOWN", @@ -27,10 +29,16 @@ class Battery(Service): @Property(int, "readable") def percentage(self): + if self._use_psutil_fallback: + if self._psutil_battery: + return int(self._psutil_battery.percent) + return 0 return int(self._battery.Percentage) @Property(str, "readable") def temperature(self): + if self._use_psutil_fallback: + return "N/A" # psutil doesn't provide temperature return ( f"{self._battery.Temperature}°C" if hasattr(self._battery, "Temperature") @@ -39,33 +47,60 @@ class Battery(Service): @Property(str, "readable") def time_to_empty(self): + if self._use_psutil_fallback: + if self._psutil_battery and hasattr(self._psutil_battery, 'secsleft'): + return self.seconds_to_hours_minutes(self._psutil_battery.secsleft) + return "N/A" return self.seconds_to_hours_minutes(getattr(self._battery, "TimeToEmpty", 0)) @Property(str, "readable") def time_to_full(self): + if self._use_psutil_fallback: + return "N/A" # psutil doesn't provide time to full return self.seconds_to_hours_minutes(getattr(self._battery, "TimeToFull", 0)) @Property(str, "readable") def icon_name(self): + if self._use_psutil_fallback: + return "battery" # Generic icon name for psutil fallback return self._battery.IconName @Property(str, "readable") def state(self): + if self._use_psutil_fallback: + if self._psutil_battery: + # psutil returns power_plugged boolean, convert to state + if self._psutil_battery.power_plugged: + if self._psutil_battery.percent >= 100: + return "FULLY_CHARGED" + else: + return "CHARGING" + else: + return "DISCHARGING" + return "UNKNOWN" return DeviceState.get(self._battery.State, "UNKNOWN") @Property(str, "readable") def capacity(self): + if self._use_psutil_fallback: + return "N/A" # psutil doesn't provide capacity info return f"{int(self._battery.Capacity)}%" @Property(bool, "readable", default_value=False) def is_present(self): + if self._use_psutil_fallback: + return self._psutil_battery is not None return self._battery.IsPresent @Property(str, "readable") def power_profile(self): - return self._profile_proxy.ActiveProfile + if self._profile_proxy: + return self._profile_proxy.ActiveProfile + return None def set_power_profile(self, profile: str) -> bool: + if not self._profile_proxy: + return False if profile not in [p.Profile for p in self._profile_proxy.Profiles]: return False try: @@ -79,6 +114,8 @@ class Battery(Service): def __init__(self): super().__init__() self._bus = SystemBus() + self._use_psutil_fallback = False + self._psutil_battery = None # Battery device try: @@ -87,7 +124,16 @@ class Battery(Service): ) self._battery.onPropertiesChanged = self.handle_battery_change except Exception: - return + # Fallback to psutil if UPower is not available + self._use_psutil_fallback = True + try: + self._psutil_battery = psutil.sensors_battery() + if self._psutil_battery is None: + return # No battery found + # Start periodic updates for psutil fallback + GLib.timeout_add_seconds(5, self._update_psutil_battery) + except Exception: + return # psutil battery not available either # PowerProfiles try: @@ -103,6 +149,15 @@ class Battery(Service): self.changed.emit() + def _update_psutil_battery(self): + """Update psutil battery data periodically""" + try: + self._psutil_battery = psutil.sensors_battery() + self.changed.emit() + except Exception: + pass # Continue trying + return True # Keep the timeout active + def _handle_profile_props_changed(self, changed): """Internal handler for property changes that processes only the changed properties""" if "ActiveProfile" in changed: