✨ feat(battery): add psutil fallback for battery info when UPower is unavailable

This commit is contained in:
sankalp committed 2025-07-29 12:51:03 +05:45
1 parent 7bcd9b27b2
commit 34f60b4c98
1 file changed
+57 -2
+57 -2
View File
@@ -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: