File indexing completed on 2024-06-23 05:30:43
0001 #!/usr/bin/env python3 0002 0003 # SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0004 # SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 # pylint: disable=too-many-arguments 0007 0008 # For FreeBSD CI which only has python 3.9 0009 from __future__ import annotations 0010 0011 import os 0012 import sys 0013 import threading 0014 import time 0015 from io import TextIOWrapper 0016 from typing import Any, Final 0017 0018 sys.path.append(os.path.dirname(os.path.abspath(__file__))) 0019 0020 from gi.repository import Gio, GLib 0021 from GLibMainLoopThread import GLibMainLoopThread 0022 0023 0024 def skip_doc(file_handler: TextIOWrapper) -> str: 0025 """ 0026 Gio.DBusNodeInfo.new_for_xml doesn't like doc 0027 """ 0028 introspection_xml: str = "" 0029 skip_line: bool = False 0030 for l in file_handler.readlines(): 0031 if not skip_line: 0032 if "<doc:doc>" in l: 0033 skip_line = "</doc:doc>" not in l # In case they are in the same line 0034 continue 0035 else: 0036 introspection_xml += l + "\n" 0037 else: 0038 if "</doc:doc>" in l: 0039 skip_line = False 0040 return introspection_xml 0041 0042 0043 class OrgFreedesktopUPower: 0044 """ 0045 D-Bus interfaces for org.freedesktop.UPower and org.freedesktop.UPower.Device 0046 """ 0047 0048 BUS_NAME: Final = "org.freedesktop.UPower" 0049 OBJECT_PATH: Final = "/org/freedesktop/UPower" 0050 DEVICE_IFACE_NAME: Final = "org.freedesktop.UPower.Device" 0051 BATTERY0_OBJECT_PATH: Final = "/org/freedesktop/UPower/devices/battery_BAT0" 0052 BATTERY1_OBJECT_PATH: Final = "/org/freedesktop/UPower/devices/battery_BAT1" 0053 WIRELESS_MOUSE_OBJECT_PATH: Final = "/org/freedesktop/UPower/devices/mouse_hidpp_battery_0" 0054 WIRELESS_KEYBOARD_OBJECT_PATH: Final = "/org/freedesktop/UPower/devices/keyboard_hidpp_battery_1" 0055 AC_OBJECT_PATH: Final = "/org/freedesktop/UPower/devices/line_power_AC" 0056 DISPLAY_DEVICE_OBJECT_PATH: Final = "/org/freedesktop/UPower/devices/DisplayDevice" 0057 0058 __connection: Gio.DBusConnection 0059 0060 def __init__(self, device_properties: dict[str, dict[str, GLib.Variant]] | None = None, enable_display_device: bool = True) -> None: 0061 self.__upower_reg_id: int = 0 0062 self.__device_reg_id_map: dict[str, int] = {} # object_path: reg_id 0063 self.__display_device_reg_id: int = 0 0064 0065 self.upower_properties: dict[str, GLib.Variant] = { 0066 "DaemonVersion": GLib.Variant("s", "1.90.2"), 0067 "OnBattery": GLib.Variant("b", False), 0068 } 0069 0070 if device_properties: 0071 self.device_properties = device_properties.copy() 0072 else: 0073 self.device_properties: dict[str, dict[str, GLib.Variant]] = { 0074 self.BATTERY0_OBJECT_PATH: { 0075 "NativePath": GLib.Variant("s", "BAT0"), 0076 "Vendor": GLib.Variant("s", "Konqi"), 0077 "Model": GLib.Variant("s", "Primary"), 0078 "Serial": GLib.Variant("s", "00150 2020/04/05"), 0079 "UpdateTime": GLib.Variant('t', int(time.time())), 0080 "Type": GLib.Variant("u", 2), # Battery 0081 "PowerSupply": GLib.Variant("b", True), 0082 "HasHistory": GLib.Variant("b", False), 0083 "HasStatistics": GLib.Variant("b", False), 0084 "Online": GLib.Variant("b", False), # only valid for AC 0085 "Energy": GLib.Variant("d", 20.0), 0086 "EnergyEmpty": GLib.Variant("d", 0.0), 0087 "EnergyFull": GLib.Variant("d", 40.0), # Wh 0088 "EnergyFullDesign": GLib.Variant("d", 100.0), 0089 "EnergyRate": GLib.Variant("d", -20.0), # Charging 0090 "Voltage": GLib.Variant("d", 12.184), 0091 "ChargeCycles": GLib.Variant('i', 88), 0092 "Luminosity": GLib.Variant("d", 0.0), 0093 "TimeToEmpty": GLib.Variant("x", 0), 0094 "TimeToFull": GLib.Variant("x", 0), 0095 "Percentage": GLib.Variant("d", 50), 0096 "Temperature": GLib.Variant("d", 20.0), 0097 "IsPresent": GLib.Variant("b", True), 0098 "State": GLib.Variant("u", 1), # Charging 0099 "IsRechargeable": GLib.Variant("b", True), 0100 "Capacity": GLib.Variant("d", 40.0), 0101 "Technology": GLib.Variant("u", 1), # Lithium ion 0102 "BatteryLevel": GLib.Variant("u", 1), # None (the battery does not use a coarse level of battery reporting) 0103 "WarningLevel": GLib.Variant("u", 1), # None 0104 "IconName": GLib.Variant("s", ""), 0105 }, 0106 self.BATTERY1_OBJECT_PATH: { 0107 "NativePath": GLib.Variant("s", "BAT1"), 0108 "Vendor": GLib.Variant("s", "Katie"), 0109 "Model": GLib.Variant("s", "Secondary"), 0110 "Serial": GLib.Variant("s", "00150 2020/04/05"), 0111 "UpdateTime": GLib.Variant('t', int(time.time())), 0112 "Type": GLib.Variant("u", 2), # Battery 0113 "PowerSupply": GLib.Variant("b", True), 0114 "HasHistory": GLib.Variant("b", False), 0115 "HasStatistics": GLib.Variant("b", False), 0116 "Online": GLib.Variant("b", False), # only valid for AC 0117 "Energy": GLib.Variant("d", 40.0), 0118 "EnergyEmpty": GLib.Variant("d", 0.0), 0119 "EnergyFull": GLib.Variant("d", 40.0), 0120 "EnergyFullDesign": GLib.Variant("d", 100.0), 0121 "EnergyRate": GLib.Variant("d", 0.0), # Not Charging 0122 "Voltage": GLib.Variant("d", 12.184), 0123 "ChargeCycles": GLib.Variant('i', 88), 0124 "Luminosity": GLib.Variant("d", 0.0), 0125 "TimeToEmpty": GLib.Variant("x", 0), 0126 "TimeToFull": GLib.Variant("x", 0), 0127 "Percentage": GLib.Variant("d", 100), 0128 "Temperature": GLib.Variant("d", 30.0), 0129 "IsPresent": GLib.Variant("b", True), 0130 "State": GLib.Variant("u", 4), # Fully Charged 0131 "IsRechargeable": GLib.Variant("b", True), 0132 "Capacity": GLib.Variant("d", 60.0), 0133 "Technology": GLib.Variant("u", 1), # Lithium ion 0134 "BatteryLevel": GLib.Variant("u", 1), # None (the battery does not use a coarse level of battery reporting) 0135 "WarningLevel": GLib.Variant("u", 1), # None 0136 "IconName": GLib.Variant("s", ""), 0137 }, 0138 self.AC_OBJECT_PATH: { 0139 "NativePath": GLib.Variant("s", "AC"), 0140 "Vendor": GLib.Variant("s", ""), 0141 "Model": GLib.Variant("s", ""), 0142 "Serial": GLib.Variant("s", ""), 0143 "UpdateTime": GLib.Variant('t', int(time.time())), 0144 "Type": GLib.Variant("u", 1), # Line Power 0145 "PowerSupply": GLib.Variant("b", True), 0146 "HasHistory": GLib.Variant("b", False), 0147 "HasStatistics": GLib.Variant("b", False), 0148 "Online": GLib.Variant("b", True), 0149 "Energy": GLib.Variant("d", 0.0), # only valid for batteries 0150 "EnergyEmpty": GLib.Variant("d", 0.0), # only valid for batteries 0151 "EnergyFull": GLib.Variant("d", 0.0), # only valid for batteries 0152 "EnergyFullDesign": GLib.Variant("d", 0.0), # only valid for batteries 0153 "EnergyRate": GLib.Variant("d", 0.0), # only valid for batteries 0154 "Voltage": GLib.Variant("d", 0.0), 0155 "ChargeCycles": GLib.Variant('i', -1), # only valid for batteries 0156 "Luminosity": GLib.Variant("d", 0.0), 0157 "TimeToEmpty": GLib.Variant("x", 0), # only valid for batteries 0158 "TimeToFull": GLib.Variant("x", 0), # only valid for batteries 0159 "Percentage": GLib.Variant("d", 0.0), # only valid for batteries 0160 "Temperature": GLib.Variant("d", 0.0), # only valid for batteries 0161 "IsPresent": GLib.Variant("b", False), # only valid for batteries 0162 "State": GLib.Variant("u", 0), # Unknown 0163 "IsRechargeable": GLib.Variant("b", False), 0164 "Capacity": GLib.Variant("d", 0.0), # only valid for batteries 0165 "Technology": GLib.Variant("u", 0), # Unknown, only valid for batteries 0166 "WarningLevel": GLib.Variant("u", 1), # None 0167 "BatteryLevel": GLib.Variant("u", 1), # None 0168 "IconName": GLib.Variant("s", ""), 0169 }, 0170 self.WIRELESS_MOUSE_OBJECT_PATH: { 0171 "NativePath": GLib.Variant("s", "hidpp_battery_0"), 0172 "Vendor": GLib.Variant("s", "KDE"), 0173 "Model": GLib.Variant("s", "Gaming Mouse"), 0174 "Serial": GLib.Variant("s", "1234-5678-90"), 0175 "UpdateTime": GLib.Variant('t', int(time.time())), 0176 "Type": GLib.Variant("u", 5), # Mouse 0177 "PowerSupply": GLib.Variant("b", False), 0178 "HasHistory": GLib.Variant("b", False), 0179 "HasStatistics": GLib.Variant("b", False), 0180 "Online": GLib.Variant("b", False), # only valid for AC 0181 "Energy": GLib.Variant("d", 0.0), # only valid for batteries 0182 "EnergyEmpty": GLib.Variant("d", 0.0), # only valid for batteries 0183 "EnergyFull": GLib.Variant("d", 0.0), # only valid for batteries 0184 "EnergyFullDesign": GLib.Variant("d", 0.0), # only valid for batteries 0185 "EnergyRate": GLib.Variant("d", 0.0), # only valid for batteries 0186 "Voltage": GLib.Variant("d", 0.0), 0187 "ChargeCycles": GLib.Variant('i', -1), # only valid for batteries 0188 "Luminosity": GLib.Variant("d", 0.0), 0189 "TimeToEmpty": GLib.Variant("x", 0), # only valid for batteries 0190 "TimeToFull": GLib.Variant("x", 0), # only valid for batteries 0191 "Percentage": GLib.Variant("d", 100.0), # only valid for batteries 0192 "Temperature": GLib.Variant("d", 0.0), # only valid for batteries 0193 "IsPresent": GLib.Variant("b", False), # only valid for batteries 0194 "State": GLib.Variant("u", 0), # Unknown, only valid for batteries 0195 "IsRechargeable": GLib.Variant("b", False), # only valid for batteries 0196 "Capacity": GLib.Variant("d", 0.0), # only valid for batteries 0197 "Technology": GLib.Variant("u", 0), # Unknown, only valid for batteries 0198 "WarningLevel": GLib.Variant("u", 1), # None 0199 "BatteryLevel": GLib.Variant("u", 8), # Full 0200 "IconName": GLib.Variant("s", ""), 0201 }, 0202 } 0203 self.device_object_paths: GLib.Variant = GLib.Variant("(ao)", [list(self.device_properties.keys())]) 0204 0205 self.display_device_properties: dict[str, dict[str, GLib.Variant]] = {} 0206 self.__enable_display_device: bool = enable_display_device 0207 self.update_display_device_properties(self.BATTERY0_OBJECT_PATH) 0208 0209 self.is_online: bool = False 0210 self.registered_event = threading.Event() 0211 0212 self.__owner_id: int = Gio.bus_own_name(Gio.BusType.SYSTEM, self.BUS_NAME, Gio.BusNameOwnerFlags.NONE, self.on_bus_acquired, None, None) 0213 assert self.__owner_id > 0 0214 0215 def quit(self) -> None: 0216 if self.__enable_display_device: 0217 self.__connection.unregister_object(self.__display_device_reg_id) 0218 self.__display_device_reg_id = 0 0219 [self.__connection.unregister_object(self.__device_reg_id_map[object_path]) for object_path in self.__device_reg_id_map] 0220 self.__device_reg_id_map = {} 0221 self.__connection.unregister_object(self.__upower_reg_id) 0222 self.__upower_reg_id = 0 0223 Gio.bus_unown_name(self.__owner_id) 0224 self.__connection.flush_sync(None) # Otherwise flaky 0225 0226 def set_upower_property(self, property_name: str, value: GLib.Variant) -> None: 0227 self.upower_properties[property_name] = value 0228 changed_properties = GLib.Variant("a{sv}", { 0229 property_name: self.upower_properties[property_name], 0230 }) 0231 Gio.DBusConnection.emit_signal(self.__connection, None, self.OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(GLib.Variant("s", self.BUS_NAME), changed_properties, GLib.Variant("as", ()))) 0232 0233 def add_device(self, object_path: str, properties: dict[str, GLib.Variant]) -> None: 0234 """ 0235 Helper function to add a device to upower with the given object path and properties 0236 """ 0237 assert object_path not in self.device_properties 0238 assert object_path not in self.__device_reg_id_map 0239 self.device_properties[object_path] = properties 0240 with open("../applets/batterymonitor/dbus/org.freedesktop.UPower.Device.xml", encoding="utf-8") as file_handler: 0241 introspection_data = Gio.DBusNodeInfo.new_for_xml(skip_doc(file_handler)) 0242 reg_id: int = self.__connection.register_object(object_path, introspection_data.interfaces[0], self.device_handle_method_call, self.device_handle_get_property, self.device_handle_set_property) 0243 assert reg_id > 0 0244 self.__device_reg_id_map[object_path] = reg_id 0245 0246 Gio.DBusConnection.emit_signal(self.__connection, None, self.OBJECT_PATH, "org.freedesktop.UPower", "DeviceAdded", GLib.Variant("(o)", [object_path])) 0247 0248 def remove_device(self, object_path: str) -> None: 0249 """ 0250 Helper function to remove a device from upower with the given object path 0251 """ 0252 assert object_path in self.device_properties 0253 assert object_path in self.__device_reg_id_map 0254 Gio.DBusConnection.emit_signal(self.__connection, None, self.OBJECT_PATH, "org.freedesktop.UPower", "DeviceRemoved", GLib.Variant("(o)", [object_path])) 0255 self.__connection.unregister_object(self.__device_reg_id_map[object_path]) 0256 del self.__device_reg_id_map[object_path] 0257 del self.device_properties[object_path] 0258 0259 def set_device_property(self, object_path: str, property_name: str, value: GLib.Variant, update_time: int | None = None) -> None: 0260 assert object_path != self.DISPLAY_DEVICE_OBJECT_PATH 0261 if update_time: 0262 self.device_properties[object_path]["UpdateTime"] = GLib.Variant('t', update_time) 0263 else: 0264 self.device_properties[object_path]["UpdateTime"] = GLib.Variant('t', int(time.time())) 0265 0266 self.device_properties[object_path][property_name] = value 0267 changed_properties = GLib.Variant("a{sv}", { 0268 property_name: self.device_properties[object_path][property_name], 0269 "UpdateTime": self.device_properties[object_path]["UpdateTime"], 0270 }) 0271 Gio.DBusConnection.emit_signal(self.__connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(GLib.Variant("s", self.DEVICE_IFACE_NAME), changed_properties, GLib.Variant("as", ()))) 0272 0273 if self.__enable_display_device: 0274 self.update_display_device_properties(self.BATTERY0_OBJECT_PATH) 0275 changed_properties: dict[str, GLib.Variant] = { 0276 property_name: self.display_device_properties[property_name], 0277 "UpdateTime": self.display_device_properties["UpdateTime"], 0278 } 0279 if property_name == "IsPresent": 0280 changed_properties["Energy"] = self.display_device_properties["Energy"] 0281 changed_properties["EnergyEmpty"] = self.display_device_properties["EnergyEmpty"] 0282 changed_properties["EnergyFull"] = self.display_device_properties["EnergyFull"] 0283 changed_properties["EnergyFullDesign"] = self.display_device_properties["EnergyFullDesign"] 0284 changed_properties["EnergyRate"] = self.display_device_properties["EnergyRate"] 0285 changed_properties["TimeToEmpty"] = self.display_device_properties["TimeToEmpty"] 0286 changed_properties["TimeToFull"] = self.display_device_properties["TimeToFull"] 0287 changed_properties["Percentage"] = self.display_device_properties["Percentage"] 0288 changed_properties["Capacity"] = self.display_device_properties["Capacity"] 0289 0290 Gio.DBusConnection.emit_signal(self.__connection, None, self.DISPLAY_DEVICE_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(GLib.Variant("s", self.DEVICE_IFACE_NAME), GLib.Variant("a{sv}", changed_properties), GLib.Variant("as", ()))) 0291 0292 def update_display_device_properties(self, default_object_path: str) -> None: 0293 if not self.__enable_display_device: 0294 return 0295 0296 def condition(e: str) -> bool: 0297 return self.device_properties[e]["PowerSupply"].get_boolean() and self.device_properties[e]["IsPresent"].get_boolean() 0298 0299 self.display_device_properties = { 0300 "NativePath": GLib.Variant("s", ""), 0301 "Vendor": GLib.Variant("s", ""), 0302 "Model": GLib.Variant("s", ""), 0303 "Serial": GLib.Variant("s", ""), 0304 "UpdateTime": GLib.Variant('t', self.device_properties[default_object_path]["UpdateTime"].get_uint64() if default_object_path in self.device_properties else int(time.time())), 0305 "Type": GLib.Variant("u", 2), # Battery 0306 "PowerSupply": GLib.Variant("b", True), 0307 "HasHistory": GLib.Variant("b", self.device_properties[default_object_path]["HasHistory"].get_boolean() if default_object_path in self.device_properties else False), 0308 "HasStatistics": GLib.Variant("b", self.device_properties[default_object_path]["HasStatistics"].get_boolean() if default_object_path in self.device_properties else False), 0309 "Online": GLib.Variant("b", self.device_properties[default_object_path]["Online"].get_boolean() if default_object_path in self.device_properties else False), 0310 "Energy": GLib.Variant("d", sum([self.device_properties[e]["Energy"].get_double() for e in self.device_properties if condition(e)])), 0311 "EnergyEmpty": GLib.Variant("d", sum([self.device_properties[e]["EnergyEmpty"].get_double() for e in self.device_properties if condition(e)])), 0312 "EnergyFull": GLib.Variant("d", sum([self.device_properties[e]["EnergyFull"].get_double() for e in self.device_properties if condition(e)])), # Wh 0313 "EnergyFullDesign": GLib.Variant("d", sum([self.device_properties[e]["EnergyFullDesign"].get_double() for e in self.device_properties if condition(e)])), 0314 "EnergyRate": GLib.Variant("d", sum([self.device_properties[e]["EnergyRate"].get_double() for e in self.device_properties if condition(e)])), 0315 "Voltage": GLib.Variant("d", self.device_properties[default_object_path]["Voltage"].get_double() if default_object_path in self.device_properties else 0.0), 0316 "ChargeCycles": GLib.Variant('i', self.device_properties[default_object_path]["ChargeCycles"].get_int32() if default_object_path in self.device_properties else 0), 0317 "Luminosity": GLib.Variant("d", self.device_properties[default_object_path]["Luminosity"].get_double() if default_object_path in self.device_properties else 0.0), 0318 "TimeToEmpty": GLib.Variant("x", sum([self.device_properties[e]["TimeToEmpty"].get_int64() for e in self.device_properties if condition(e)])), 0319 "TimeToFull": GLib.Variant("x", sum([self.device_properties[e]["TimeToFull"].get_int64() for e in self.device_properties if condition(e)])), 0320 "Percentage": GLib.Variant("d", 100 * sum([self.device_properties[e]["Energy"].get_double() for e in self.device_properties if condition(e)]) / sum([self.device_properties[e]["EnergyFull"].get_double() for e in self.device_properties if condition(e)])), 0321 "Temperature": GLib.Variant("d", self.device_properties[default_object_path]["Temperature"].get_double() if default_object_path in self.device_properties else 20.0), 0322 "IsPresent": GLib.Variant("b", self.device_properties[default_object_path]["IsPresent"].get_boolean() if default_object_path in self.device_properties else True), 0323 "State": GLib.Variant("u", self.device_properties[default_object_path]["State"].get_uint32() if default_object_path in self.device_properties else 1), # Charging 0324 "IsRechargeable": GLib.Variant("b", self.device_properties[default_object_path]["IsRechargeable"].get_boolean() if default_object_path in self.device_properties else True), 0325 "Capacity": GLib.Variant("d", sum([self.device_properties[e]["Capacity"].get_double() for e in self.device_properties if condition(e)])), 0326 "Technology": GLib.Variant("u", self.device_properties[default_object_path]["Technology"].get_uint32() if default_object_path in self.device_properties else 1), # Lithium ion 0327 "BatteryLevel": GLib.Variant("u", self.device_properties[default_object_path]["BatteryLevel"].get_uint32() if default_object_path in self.device_properties else 1), # None (the battery does not use a coarse level of battery reporting) 0328 "WarningLevel": GLib.Variant("u", self.device_properties[default_object_path]["WarningLevel"].get_uint32() if default_object_path in self.device_properties else 1), # None 0329 "IconName": GLib.Variant("s", self.device_properties[default_object_path]["IconName"].get_string() if default_object_path in self.device_properties else ""), 0330 } 0331 0332 def set_energy_props(self, object_path: str, energy_rate: float | None = None, percentage: float | None = None, update_time: int | None = None) -> None: 0333 """ 0334 Helper function to update energy rate and remaining time. 0335 @param percentage 0-100 0336 """ 0337 changed_properties: dict[str, GLib.Variant] = {} 0338 if energy_rate is not None: 0339 self.device_properties[object_path]["EnergyRate"] = GLib.Variant("d", energy_rate) 0340 changed_properties["EnergyRate"] = self.device_properties[object_path]["EnergyRate"] 0341 else: 0342 energy_rate = self.device_properties[object_path]["EnergyRate"].get_double() 0343 0344 if update_time is not None: 0345 self.device_properties[object_path]["UpdateTime"] = GLib.Variant('t', update_time) 0346 self.device_properties[self.BATTERY0_OBJECT_PATH]["UpdateTime"] = self.device_properties[object_path]["UpdateTime"] 0347 else: 0348 self.device_properties[object_path]["UpdateTime"] = GLib.Variant('t', self.device_properties[self.BATTERY0_OBJECT_PATH]["UpdateTime"].get_uint64() + 100) 0349 self.device_properties[self.BATTERY0_OBJECT_PATH]["UpdateTime"] = self.device_properties[object_path]["UpdateTime"] 0350 changed_properties["UpdateTime"] = self.device_properties[object_path]["UpdateTime"] 0351 0352 if percentage is not None: 0353 self.device_properties[object_path]["Percentage"] = GLib.Variant("d", percentage) 0354 self.device_properties[object_path]["Energy"] = GLib.Variant("d", percentage / 100 * self.device_properties[object_path]["EnergyFull"].get_double()) 0355 changed_properties["Percentage"] = self.device_properties[object_path]["Percentage"] 0356 changed_properties["Energy"] = self.device_properties[object_path]["Energy"] 0357 if percentage == 100: 0358 self.device_properties[object_path]["State"] = GLib.Variant("u", 4) # Charging 0359 changed_properties["State"] = self.device_properties[object_path]["State"] 0360 else: 0361 percentage = self.device_properties[object_path]["Percentage"].get_double() 0362 0363 if energy_rate < 0: 0364 self.device_properties[object_path]["TimeToEmpty"] = GLib.Variant("x", 0) 0365 self.device_properties[object_path]["TimeToFull"] = GLib.Variant("x", int((100 - percentage) / 100 * self.device_properties[object_path]["EnergyFull"].get_double() / -energy_rate * 3600)) # seconds 0366 self.device_properties[object_path]["State"] = GLib.Variant("u", 1) # Charging 0367 changed_properties["State"] = self.device_properties[object_path]["State"] 0368 elif energy_rate > 0: 0369 self.device_properties[object_path]["TimeToEmpty"] = GLib.Variant("x", int(self.device_properties[object_path]["Energy"].get_double() / energy_rate * 3600)) # seconds 0370 self.device_properties[object_path]["TimeToFull"] = GLib.Variant("x", 0) 0371 self.device_properties[object_path]["State"] = GLib.Variant("u", 2) # Discharging 0372 changed_properties["State"] = self.device_properties[object_path]["State"] 0373 else: 0374 self.device_properties[object_path]["TimeToEmpty"] = GLib.Variant("x", 0) 0375 self.device_properties[object_path]["TimeToFull"] = GLib.Variant("x", 0) 0376 0377 changed_properties["TimeToEmpty"] = self.device_properties[object_path]["TimeToEmpty"] 0378 changed_properties["TimeToFull"] = self.device_properties[object_path]["TimeToFull"] 0379 0380 Gio.DBusConnection.emit_signal(self.__connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(GLib.Variant("s", self.DEVICE_IFACE_NAME), GLib.Variant("a{sv}", changed_properties), GLib.Variant("as", ()))) 0381 0382 if self.__enable_display_device: 0383 self.update_display_device_properties(self.BATTERY0_OBJECT_PATH) 0384 changed_properties = GLib.Variant("a{sv}", { 0385 "EnergyRate": self.display_device_properties["EnergyRate"], 0386 "Percentage": self.display_device_properties["Percentage"], 0387 "Energy": self.display_device_properties["Energy"], 0388 "State": self.display_device_properties["State"], 0389 "TimeToFull": self.display_device_properties["TimeToFull"], 0390 "TimeToEmpty": self.display_device_properties["TimeToEmpty"], 0391 "UpdateTime": self.display_device_properties["UpdateTime"], 0392 }) 0393 Gio.DBusConnection.emit_signal(self.__connection, None, self.DISPLAY_DEVICE_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(GLib.Variant("s", self.DEVICE_IFACE_NAME), changed_properties, GLib.Variant("as", ()))) 0394 0395 def set_ac_plugged(self) -> None: 0396 """ 0397 Helper function to simulate plugging in the AC line 0398 """ 0399 self.device_properties[self.AC_OBJECT_PATH]["Online"] = GLib.Variant("b", True) 0400 self.device_properties[self.AC_OBJECT_PATH]["IsPresent"] = GLib.Variant("b", True) 0401 ac_changed_properties = GLib.Variant("a{sv}", { 0402 "Online": self.device_properties[self.AC_OBJECT_PATH]["Online"], 0403 "IsPresent": self.device_properties[self.AC_OBJECT_PATH]["IsPresent"], 0404 }) 0405 Gio.DBusConnection.emit_signal(self.__connection, None, self.AC_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(GLib.Variant("s", self.DEVICE_IFACE_NAME), ac_changed_properties, GLib.Variant("as", ()))) 0406 0407 self.set_energy_props(self.BATTERY0_OBJECT_PATH, -20.0, 90.0) 0408 self.set_upower_property("OnBattery", GLib.Variant("b", False)) 0409 0410 def set_ac_unplugged(self) -> None: 0411 """ 0412 Helper function to simulate unplugging the AC line 0413 """ 0414 self.device_properties[self.AC_OBJECT_PATH]["Online"] = GLib.Variant("b", False) 0415 self.device_properties[self.AC_OBJECT_PATH]["IsPresent"] = GLib.Variant("b", False) 0416 ac_changed_properties = GLib.Variant("a{sv}", { 0417 "Online": self.device_properties[self.AC_OBJECT_PATH]["Online"], 0418 "IsPresent": self.device_properties[self.AC_OBJECT_PATH]["IsPresent"], 0419 }) 0420 0421 self.set_energy_props(self.BATTERY0_OBJECT_PATH, 20.0, 80.0) 0422 self.set_upower_property("OnBattery", GLib.Variant("b", True)) 0423 0424 def on_bus_acquired(self, connection: Gio.DBusConnection, name: str, *args) -> None: 0425 """ 0426 Interface is ready, now register objects. 0427 """ 0428 self.__connection = connection 0429 0430 with open("../applets/batterymonitor/dbus/org.freedesktop.UPower.xml", encoding="utf-8") as file_handler: 0431 introspection_data = Gio.DBusNodeInfo.new_for_xml(skip_doc(file_handler)) 0432 self.__upower_reg_id = connection.register_object(self.OBJECT_PATH, introspection_data.interfaces[0], self.upower_handle_method_call, self.upower_handle_get_property, self.upower_handle_set_property) 0433 assert self.__upower_reg_id > 0 0434 0435 assert len(self.__device_reg_id_map) == 0 0436 with open("../applets/batterymonitor/dbus/org.freedesktop.UPower.Device.xml", encoding="utf-8") as file_handler: 0437 introspection_data = Gio.DBusNodeInfo.new_for_xml(skip_doc(file_handler)) 0438 for object_path in self.device_properties: 0439 reg_id: int = connection.register_object(object_path, introspection_data.interfaces[0], self.device_handle_method_call, self.device_handle_get_property, self.device_handle_set_property) 0440 assert reg_id > 0 0441 self.__device_reg_id_map[object_path] = reg_id 0442 if self.__enable_display_device: 0443 self.__display_device_reg_id = connection.register_object(self.DISPLAY_DEVICE_OBJECT_PATH, introspection_data.interfaces[0], self.device_handle_method_call, self.device_handle_get_property, self.device_handle_set_property) 0444 assert self.__display_device_reg_id > 0 0445 0446 self.is_online = True 0447 self.registered_event.set() 0448 0449 def upower_handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: 0450 """ 0451 Handles method calls for org.freedesktop.UPower 0452 """ 0453 assert interface_name == self.BUS_NAME, f"Unknown interface {interface_name}" 0454 print(f"upower call {method_name}", file=sys.stderr, flush=True) 0455 0456 if method_name == "EnumerateDevices": 0457 invocation.return_value(self.device_object_paths) 0458 elif method_name == "GetDisplayDevice": 0459 invocation.return_value(GLib.Variant("(o)", ["/org/freedesktop/UPower/devices/DisplayDevice"] if self.__enable_display_device else ["/"])) 0460 elif method_name == "GetCriticalAction": 0461 invocation.return_value(GLib.Variant("(s)", ["PowerOff"])) 0462 else: 0463 assert False, f"Unknown method {method_name}" 0464 0465 def upower_handle_get_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, value: Any): 0466 """ 0467 Handles properties for org.freedesktop.UPower 0468 """ 0469 if value not in self.upower_properties: 0470 print(f"{value} does not exist", file=sys.stderr, flush=True) 0471 return None 0472 0473 print(f"upower get_property {value}") 0474 return self.upower_properties[value] 0475 0476 def upower_handle_set_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, key: str, value: Any) -> bool: 0477 """ 0478 Handles properties for org.freedesktop.UPower 0479 """ 0480 assert False, "Only read-only properties" 0481 0482 def device_handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: 0483 """ 0484 Handles method calls for org.freedesktop.UPower.Device 0485 """ 0486 assert interface_name == self.DEVICE_IFACE_NAME, f"Unknown interface {interface_name}" 0487 assert method_name in ("Refresh", "GetHistory", "GetStatistics"), f"Unknown method {method_name}" 0488 print(f"device {object_path} call {method_name}", file=sys.stderr, flush=True) 0489 0490 def device_handle_get_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, value: Any): 0491 """ 0492 Handles properties for org.freedesktop.UPower.Device 0493 """ 0494 assert interface_name == self.DEVICE_IFACE_NAME, f"Wrong interface name {interface_name} from {sender}" 0495 assert object_path in self.device_properties or (object_path == self.DISPLAY_DEVICE_OBJECT_PATH and self.__enable_display_device), f"Unknown object path {object_path}" 0496 print(f"device {object_path} get_property {value}") 0497 0498 return self.device_properties[object_path][value] if object_path != self.DISPLAY_DEVICE_OBJECT_PATH else self.display_device_properties[value] 0499 0500 def device_handle_set_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, key: str, value: Any) -> bool: 0501 """ 0502 Handles properties for org.freedesktop.UPower.Device 0503 """ 0504 assert False, "Only read-only properties"