feat(root): implement dynamic todo list refresh and UI enhancements
This change adds a mechanism to refresh the todo list dynamically when the underlying service updates, improving UI responsiveness and user experience. It also updates documentation and removes deprecated files. - New Features: - Added `_refresh_todos` method to rebuild the todo list when `get_todo_service().todos` changes. - Introduced `TodoListCapture` class for mouse capture handling of the todo list window. - Enhancements: - Updated README with corrected image paths, installation tips, and manual installation commands. - Introduced `start.py` to launch the application or lock mode. - Removed deprecated `config.json` and `debug_memory.py`. - Bug Fixes: - Fixed lock screen screenshot path in README.
This commit is contained in:
267 files changed
+24582
-394
No files matched your search
@@ -0,0 +1,202 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fabric.core.service import Property, Service, Signal
|
||||
from fabric.utils import Gio, GLib, logger
|
||||
|
||||
from utils.dbus_helper import GioDBusHelper
|
||||
|
||||
DeviceState = {
|
||||
0: "UNKNOWN",
|
||||
1: "CHARGING",
|
||||
2: "DISCHARGING",
|
||||
3: "EMPTY",
|
||||
4: "FULLY_CHARGED",
|
||||
5: "PENDING_CHARGE",
|
||||
6: "PENDING_DISCHARGE",
|
||||
}
|
||||
|
||||
PowerProfile = {
|
||||
"power-saver": "Power Saver",
|
||||
"balanced": "Balanced",
|
||||
"performance": "Performance",
|
||||
}
|
||||
|
||||
|
||||
class BatteryService(Service):
|
||||
"""Service to interact with UPower and Power Profiles via GIO D-Bus"""
|
||||
|
||||
@Signal
|
||||
def changed(self) -> None:
|
||||
"""Signal emitted when battery changes."""
|
||||
|
||||
@Signal
|
||||
def power_profile_changed(self) -> None:
|
||||
"""Signal emitted when power profile changes."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# UPower D-Bus configuration
|
||||
self.bus_name = "org.freedesktop.UPower"
|
||||
self.object_path = "/org/freedesktop/UPower/devices/DisplayDevice"
|
||||
self.interface_name = "org.freedesktop.UPower.Device"
|
||||
|
||||
self.dbus_helper = GioDBusHelper(
|
||||
bus_type=Gio.BusType.SYSTEM,
|
||||
bus_name=self.bus_name,
|
||||
object_path=self.object_path,
|
||||
interface_name=self.interface_name,
|
||||
)
|
||||
|
||||
self.proxy = self.dbus_helper.proxy
|
||||
|
||||
# Listen for PropertiesChanged signals from UPower
|
||||
self.dbus_helper.listen_signal(
|
||||
member="PropertiesChanged",
|
||||
callback=self.handle_property_change,
|
||||
)
|
||||
|
||||
# Power Profiles D-Bus configuration
|
||||
self.power_profile_bus_name = "net.hadess.PowerProfiles"
|
||||
self.power_profile_object_path = "/net/hadess/PowerProfiles"
|
||||
self.power_profile_interface_name = "net.hadess.PowerProfiles"
|
||||
|
||||
try:
|
||||
self.power_profile_helper = GioDBusHelper(
|
||||
bus_type=Gio.BusType.SYSTEM,
|
||||
bus_name=self.power_profile_bus_name,
|
||||
object_path=self.power_profile_object_path,
|
||||
interface_name=self.power_profile_interface_name,
|
||||
)
|
||||
|
||||
self.power_profile_proxy = self.power_profile_helper.proxy
|
||||
|
||||
# Listen for PropertiesChanged signals from Power Profiles
|
||||
self.power_profile_helper.listen_signal(
|
||||
member="PropertiesChanged",
|
||||
callback=self.handle_power_profile_change,
|
||||
)
|
||||
|
||||
self._power_profiles_available = True
|
||||
except Exception as e:
|
||||
logger.warning(f"[Battery] Power Profiles daemon not available: {e}")
|
||||
self.power_profile_helper = None
|
||||
self.power_profile_proxy = None
|
||||
self._power_profiles_available = False
|
||||
|
||||
def get_property(
|
||||
self,
|
||||
property: Literal[
|
||||
"Percentage",
|
||||
"Temperature",
|
||||
"TimeToEmpty",
|
||||
"TimeToFull",
|
||||
"IconName",
|
||||
"State",
|
||||
"Capacity",
|
||||
"IsPresent",
|
||||
"Vendor",
|
||||
],
|
||||
):
|
||||
try:
|
||||
result = self.proxy.get_cached_property(property)
|
||||
return result.unpack() if result is not None else None
|
||||
except Exception as e:
|
||||
logger.exception(f"[Battery] Error retrieving '{property}': {e}")
|
||||
return None
|
||||
|
||||
def get_power_profile(self) -> Optional[str]:
|
||||
"""Get the current active power profile."""
|
||||
if not self._power_profiles_available:
|
||||
return None
|
||||
|
||||
try:
|
||||
result = self.power_profile_proxy.get_cached_property("ActiveProfile")
|
||||
return result.unpack() if result is not None else None
|
||||
except Exception as e:
|
||||
logger.exception(f"[Battery] Error retrieving active power profile: {e}")
|
||||
return None
|
||||
|
||||
def set_power_profile(
|
||||
self, profile: Literal["power-saver", "balanced", "performance"]
|
||||
) -> bool:
|
||||
"""Set the active power profile."""
|
||||
if not self._power_profiles_available:
|
||||
logger.warning("[Battery] Power Profiles daemon not available")
|
||||
return False
|
||||
|
||||
if profile not in PowerProfile:
|
||||
return False
|
||||
|
||||
try:
|
||||
self.power_profile_helper.set_property(
|
||||
interface_name=self.power_profile_interface_name,
|
||||
property_name="ActiveProfile",
|
||||
value_variant=GLib.Variant("s", profile),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"[Battery] Error setting power profile to '{profile}': {e}"
|
||||
)
|
||||
return False
|
||||
|
||||
def get_available_power_profiles(self) -> Optional[list]:
|
||||
"""Get list of available power profiles."""
|
||||
if not self._power_profiles_available:
|
||||
return None
|
||||
|
||||
try:
|
||||
result = self.power_profile_proxy.get_cached_property("Profiles")
|
||||
if result is not None:
|
||||
profiles_data = result.unpack()
|
||||
# Extract profile names from the array of dictionaries
|
||||
profiles = []
|
||||
for profile_dict in profiles_data:
|
||||
if "Profile" in profile_dict:
|
||||
profiles.append(profile_dict["Profile"])
|
||||
return profiles
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"[Battery] Error retrieving available power profiles: {e}"
|
||||
)
|
||||
return None
|
||||
|
||||
def is_power_profiles_available(self) -> bool:
|
||||
"""Check if power profiles daemon is available."""
|
||||
return self._power_profiles_available
|
||||
|
||||
def get_power_profile_display_name(self, profile: str) -> str:
|
||||
"""Get display name for a power profile."""
|
||||
return PowerProfile.get(profile, profile.title())
|
||||
|
||||
@Property(int, "readable", default_value=0)
|
||||
def percentage(self) -> int:
|
||||
return self.get_property("Percentage") or 0
|
||||
|
||||
@Property(int, "readable", default_value=0)
|
||||
def state(self) -> int:
|
||||
return self.get_property("State") or 0
|
||||
|
||||
@Property(bool, "readable", default_value=False)
|
||||
def is_present(self) -> bool:
|
||||
return self.get_property("IsPresent") or False
|
||||
|
||||
def handle_property_change(self, *args):
|
||||
# Notify about property changes for OSD and other listeners
|
||||
self.notify("percentage")
|
||||
self.notify("state")
|
||||
self.notify("is-present")
|
||||
self.emit("changed")
|
||||
|
||||
def handle_power_profile_change(self, *_):
|
||||
"""Handle power profile property changes."""
|
||||
self.emit("power_profile_changed")
|
||||
Reference in new issue
Block a user