192 lines
6.7 KiB
Python
Executable File
192 lines
6.7 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# SPDX-License-Identifier: CC0-1.0
|
|
# Copyright © 2021 mpan; <https://mpan.pl/>; CC0 1.0 (THIS SCRIPT!)
|
|
# Context: <https://bbs.archlinux.org/viewtopic.php?id=269453>
|
|
# 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 __device(self, devpath):
|
|
devproxy = self.bus.get_object(self.UPOWER_NAME, devpath)
|
|
return dbus.Interface(devproxy, 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_info(self, dev, property):
|
|
return self.__device(dev).Get(
|
|
self.UPOWER_NAME + ".Device", property)
|
|
|
|
def get_full_device_info(self, dev):
|
|
return {
|
|
'HasHistory': self.get_device_info(dev, "HasHistory"),
|
|
'HasStatistics': self.get_device_info(dev, "HasStatistics"),
|
|
'IsPresent': self.get_device_info(dev, "IsPresent"),
|
|
'IsRechargeable': self.get_device_info(dev, "IsRechargeable"),
|
|
'Online': self.get_device_info(dev, "Online"),
|
|
'PowerSupply': self.get_device_info(dev, "PowerSupply"),
|
|
'Capacity': self.get_device_info(dev, "Capacity"),
|
|
'Energy': self.get_device_info(dev, "Energy"),
|
|
'EnergyEmpty': self.get_device_info(dev, "EnergyEmpty"),
|
|
'EnergyFull': self.get_device_info(dev, "EnergyFull"),
|
|
'EnergyFullDesign': self.get_device_info(dev, "EnergyFullDesign"),
|
|
'EnergyRate': self.get_device_info(dev, "EnergyRate"),
|
|
'Luminosity': self.get_device_info(dev, "Luminosity"),
|
|
'Percentage': self.get_device_info(dev, "Percentage"),
|
|
'Temperature': self.get_device_info(dev, "Temperature"),
|
|
'Voltage': self.get_device_info(dev, "Voltage"),
|
|
'TimeToEmpty': self.get_device_info(dev, "TimeToEmpty"),
|
|
'TimeToFull': self.get_device_info(dev, "TimeToFull"),
|
|
'IconName': self.get_device_info(dev, "IconName"),
|
|
'Model': self.get_device_info(dev, "Model"),
|
|
'NativePath': self.get_device_info(dev, "NativePath"),
|
|
'Serial': self.get_device_info(dev, "Serial"),
|
|
'Vendor': self.get_device_info(dev, "Vendor"),
|
|
'State': self.get_device_info(dev, "State"),
|
|
'Technology': self.get_device_info(dev, "Technology"),
|
|
'Type': self.get_device_info(dev, "Type"),
|
|
'WarningLevel': self.get_device_info(dev, "WarningLevel"),
|
|
'UpdateTime': self.get_device_info(dev, "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):
|
|
return int(self.get_device_info(battery, "State")) == 1
|
|
|
|
def get_state(self, battery):
|
|
return {
|
|
0: "Unknown",
|
|
1: "Loading",
|
|
2: "Discharging",
|
|
3: "Empty",
|
|
4: "Fully charged",
|
|
5: "Pending charge",
|
|
6: "Pending discharge"
|
|
}.get(int(self.get_device_info(battery, "State")), "Unknown")
|
|
|
|
def is_supplying_battery(self, battery):
|
|
return (
|
|
int(self.get_device_info(battery, "Type")) == 2
|
|
and bool(self.get_device_info(battery, "PowerSupply"))
|
|
)
|
|
|
|
|
|
def push_notification(title, message, timeout=10000):
|
|
BUS_NAME = INTERFACE = "org.freedesktop.Notifications"
|
|
OBJECT_PATH = "/org/freedesktop/Notifications"
|
|
|
|
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)
|
|
|
|
low_power_detected = False
|
|
for device in upowr.detect_devices():
|
|
if not upowr.is_supplying_battery(device):
|
|
continue
|
|
# seems waybar visual is 1% lower than actual.
|
|
if upowr.get_device_info(device, "Percentage") < 21:
|
|
bat_id = upowr.get_device_info(device, "NativePath")
|
|
push_notification(
|
|
"Power Hint",
|
|
f"{bat_id} is running out. Recharge soon!"
|
|
)
|
|
low_power_detected = True
|
|
if low_power_detected:
|
|
start_process([
|
|
"pw-play",
|
|
f"--target={AUD_OUT_EMB}",
|
|
AUD_FILE
|
|
])
|