✨ feat(battery): add psutil fallback for battery info when UPower is unavailable
This commit is contained in:
1 file changed
+57
-2
+57
-2
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user