1#!/usr/local/bin/python3.8 2 3from gi.repository import Gio, GObject, CScreensaver, GLib 4from enum import IntEnum 5 6from dbusdepot.baseClient import BaseClient 7 8class DeviceType(IntEnum): 9 Unknown = 0 10 LinePower = 1 11 Battery = 2 12 Ups = 3 13 Monitor = 4 14 Mouse = 5 15 Keyboard = 6 16 Pda = 7 17 Phone = 8 18 19class DeviceState(IntEnum): 20 Unknown = 0 21 Charging = 1 22 Discharging = 2 23 Empty = 3 24 FullyCharged = 4 25 PendingCharge = 5 26 PendingDischarge = 6 27 28class UPowerClient(BaseClient): 29 """ 30 This client communicates with the upower provider, tracking the power 31 state of the system (laptops - are we on battery or plugged in?) 32 """ 33 __gsignals__ = { 34 'power-state-changed': (GObject.SignalFlags.RUN_LAST, None, ()), 35 'percentage-changed': (GObject.SignalFlags.RUN_LAST, None, (GObject.Object,)) 36 } 37 38 UPOWER_SERVICE = "org.freedesktop.UPower" 39 UPOWER_PATH = "/org/freedesktop/UPower" 40 41 def __init__(self): 42 super(UPowerClient, self).__init__(Gio.BusType.SYSTEM, 43 CScreensaver.UPowerProxy, 44 self.UPOWER_SERVICE, 45 self.UPOWER_PATH) 46 47 self.have_battery = False 48 self.plugged_in = False 49 50 self.update_state_id = 0 51 self.devices_dirty = True 52 53 self.relevant_devices = [] 54 55 def on_client_setup_complete(self): 56 self.proxy.connect("device-removed", self.on_device_added_or_removed) 57 self.proxy.connect("device-added", self.on_device_added_or_removed) 58 self.proxy.connect("notify::on-battery", self.on_battery_changed) 59 60 self.queue_update_state() 61 62 def on_device_added_or_removed(self, proxy, path): 63 self.devices_dirty = True 64 self.queue_update_state() 65 66 def on_battery_changed(self, proxy, pspec, data=None): 67 self.queue_update_state() 68 69 def queue_update_state(self): 70 if self.update_state_id > 0: 71 GObject.source_remove(self.update_state_id) 72 self.update_state_id = 0 73 74 GObject.idle_add(self.idle_update_cb) 75 76 def idle_update_cb(self, data=None): 77 if self.devices_dirty: 78 self.rescan_devices() 79 80 self.devices_dirty = False 81 82 if self.update_state(): 83 self.emit_changed() 84 85 def rescan_devices(self): 86 if len(self.relevant_devices) > 0: 87 for path, dev in self.relevant_devices: 88 dev.disconnect(dev.prop_changed_id) 89 del dev 90 del path 91 92 self.relevant_devices = [] 93 94 try: 95 # The return type for this call has to be overridden in gdbus-codegen 96 # (See the Makefile.am) - or else we get utf-8 errors (python3 issue?) 97 for path in self.proxy.call_enumerate_devices_sync(): 98 try: 99 dev = CScreensaver.UPowerDeviceProxy.new_for_bus_sync(Gio.BusType.SYSTEM, 100 Gio.DBusProxyFlags.NONE, 101 self.UPOWER_SERVICE, 102 path, 103 None) 104 105 if dev.get_property("type") in (DeviceType.Battery, DeviceType.LinePower): 106 self.relevant_devices.append((path, dev)) 107 dev.prop_changed_id = dev.connect("notify", self.on_device_properties_changed) 108 except GLib.Error: 109 print("UPowerClient had trouble connecting with device:", path, " - skipping it") 110 except GLib.Error: 111 print("UPowerClient had trouble enumerating through devices. The battery indicator will be disabled") 112 113 self.queue_update_state() 114 115 def update_state(self): 116 changes = False 117 118 old_plugged_in = self.plugged_in 119 old_have_battery = self.have_battery 120 121 # UPower doesn't necessarily have a LinePower device if there are no batteries. 122 # Default to plugged in, then. 123 new_plugged_in = True 124 new_have_battery = False 125 126 for path, dev in self.relevant_devices: 127 if dev.get_property("type") == DeviceType.LinePower: 128 new_plugged_in = dev.get_property("online") 129 if dev.get_property("type") == DeviceType.Battery: 130 new_have_battery = True 131 132 if (new_plugged_in != old_plugged_in) or (new_have_battery != old_have_battery): 133 changes = True 134 self.have_battery = new_have_battery 135 self.plugged_in = new_plugged_in 136 137 return changes 138 139 def on_device_properties_changed(self, proxy, pspec, data=None): 140 if pspec.name in ("online", "icon-name", "state"): 141 self.queue_update_state() 142 143 if pspec.name == "percentage": 144 self.emit_percentage_changed(proxy) 145 146 def emit_changed(self): 147 self.emit("power-state-changed") 148 149 def emit_percentage_changed(self, battery): 150 self.emit("percentage-changed", battery) 151 152 def get_batteries(self): 153 if len(self.relevant_devices) == 0: 154 return [] 155 156 ret = [] 157 158 for path, dev in self.relevant_devices: 159 if dev.get_property("type") == DeviceType.Battery: 160 ret.append((path, dev)) 161 162 return ret 163 164 def full_and_on_ac_or_no_batteries(self): 165 """ 166 This figures out whether the power widget should be shown or not - 167 currently we only show the widget if we have batteries and are not 168 plugged in. 169 """ 170 batteries = self.get_batteries() 171 172 if batteries == []: 173 return True 174 175 all_batteries_full = True 176 177 for path, dev in batteries: 178 if dev.get_property("state") not in (DeviceState.FullyCharged, DeviceState.Unknown): 179 all_batteries_full = False 180 break 181 182 return self.plugged_in and all_batteries_full 183 184 def on_failure(self, *args): 185 print("Failed to establish a connection with UPower - the battery indicator will be disabled.") 186