#!/usr/bin/env python # SPDX-License-Identifier: CC0-1.0 # Copyright © 2021 mpan; ; CC0 1.0 (THIS SCRIPT!) # Context: # Reference: https://github.com/wogscpar/upower-python # -*- encoding: utf-8 -*- # @File : .battery-warn # @Time : 2025/12/29 00:32:53 # @Author : SilverAg.L # bash pipeline was still too complicated. # Dependencies (pacman): python-dbus, libnotify, pipewire-audio import dbus from os import getenv from subprocess import run as start_process # region config AUD_OUT_EMB = ( # my laptop embedded speaker "alsa_output.pci-0000_00_1f" ".3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink" ) # better searched by editor like VSCode. AUD_FILE = "~/.local/share/.low_power.wav".replace("~", getenv("HOME"), 1) # endregion config class UPowerManager(): def __init__(self): self.UPOWER_NAME = "org.freedesktop.UPower" self.UPOWER_PATH = "/org/freedesktop/UPower" self.DBUS_PROPERTIES = "org.freedesktop.DBus.Properties" self.bus = dbus.SystemBus() def __upower(self, subpath=None, *, interface=None): if not subpath: subpath = "" upower_proxy = self.bus.get_object( self.UPOWER_NAME, self.UPOWER_PATH + subpath ) if interface is None: interface = self.UPOWER_NAME return dbus.Interface(upower_proxy, interface) def __battery(self, battery): battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) return dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) def detect_devices(self): return self.__upower().EnumerateDevices() def get_display_device(self): return self.__upower().GetDisplayDevice() def get_critical_action(self): return self.__upower().GetCriticalAction() def get_device_percentage(self, battery): return self.__battery(battery).Get( self.UPOWER_NAME + ".Device", "Percentage") def get_full_device_information(self, battery): def get_property(prop_name): return self.__battery(battery).Get( self.UPOWER_NAME + ".Device", prop_name) return { 'HasHistory': get_property("HasHistory"), 'HasStatistics': get_property("HasStatistics"), 'IsPresent': get_property("IsPresent"), 'IsRechargeable': get_property("IsRechargeable"), 'Online': get_property("Online"), 'PowerSupply': get_property("PowerSupply"), 'Capacity': get_property("Capacity"), 'Energy': get_property("Energy"), 'EnergyEmpty': get_property("EnergyEmpty"), 'EnergyFull': get_property("EnergyFull"), 'EnergyFullDesign': get_property("EnergyFullDesign"), 'EnergyRate': get_property("EnergyRate"), 'Luminosity': get_property("Luminosity"), 'Percentage': get_property("Percentage"), 'Temperature': get_property("Temperature"), 'Voltage': get_property("Voltage"), 'TimeToEmpty': get_property("TimeToEmpty"), 'TimeToFull': get_property("TimeToFull"), 'IconName': get_property("IconName"), 'Model': get_property("Model"), 'NativePath': get_property("NativePath"), 'Serial': get_property("Serial"), 'Vendor': get_property("Vendor"), 'State': get_property("State"), 'Technology': get_property("Technology"), 'Type': get_property("Type"), 'WarningLevel': get_property("WarningLevel"), 'UpdateTime': get_property("UpdateTime") } def is_lid_present(self): return bool(self.__upower(interface=self.DBUS_PROPERTIES).Get( self.UPOWER_NAME, 'LidIsPresent')) def is_lid_closed(self): return bool(self.__upower(interface=self.DBUS_PROPERTIES).Get( self.UPOWER_NAME, 'LidIsClosed')) def on_battery(self): return bool(self.__upower(interface=self.DBUS_PROPERTIES).Get( self.UPOWER_NAME, 'OnBattery')) def has_wakeup_capabilities(self): return bool(self.__upower( "/Wakeups", interface=self.DBUS_PROPERTIES ).Get(self.UPOWER_NAME + '.Wakeups', 'HasCapability')) def get_wakeups_data(self): return self.__upower( "/Wakeups", interface=self.UPOWER_NAME + '.Wakeups' ).GetData() def get_wakeups_total(self): return self.__upower( "/Wakeups", interface=self.UPOWER_NAME + '.Wakeups' ).GetTotal() def is_loading(self, battery): state = int(self.__battery(battery).Get( self.UPOWER_NAME + ".Device", "State")) return state == 1 def get_state(self, battery): state = int(self.__battery(battery).Get( self.UPOWER_NAME + ".Device", "State")) return { 0: "Unknown", 1: "Loading", 2: "Discharging", 3: "Empty", 4: "Fully charged", 5: "Pending charge", 6: "Pending discharge" }.get(state, "Unknown") def push_notification(title, message, timeout=10000): BUS_NAME = "org.freedesktop.Notifications" OBJECT_PATH = "/org/freedesktop/Notifications" INTERFACE = BUS_NAME notify = dbus.Interface( dbus.SessionBus().get_object(BUS_NAME, OBJECT_PATH), INTERFACE ) notify.Notify( "battery-warn-script", # app_name 0, # replaces_id "", # app_icon title, # summary message, # body [], # actions {}, # hints timeout # expire_timeout ) if __name__ == "__main__": upowr = UPowerManager() if not upowr.on_battery(): exit(0) devPaths = upowr.detect_devices() low_power_detected = False for devPath in devPaths: info = upowr.get_full_device_information(devPath) if info['Type'] != 2: # 2 means battery continue # seems waybar visual is 1% lower than actual. if info['Percentage'] < 21: push_notification( "Power Hint", f"{info['NativePath']} is running out. Recharge soon!" ) low_power_detected = True if low_power_detected: start_process([ "pw-play", f"--target={AUD_OUT_EMB}", AUD_FILE ])