用 Noctalia Shell 替代各种琐碎组件
This commit is contained in:
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
# Copyright © 2021 mpan; <https://mpan.pl/>; CC0 1.0 (THIS SCRIPT!)
|
||||
# Arch 论坛贴: <https://bbs.archlinux.org/viewtopic.php?id=269453>
|
||||
# UPower 实现: https://github.com/wogscpar/upower-python
|
||||
|
||||
# -*- encoding: utf-8 -*-
|
||||
# @File : .battery-warn
|
||||
# @Time : 2025/12/29 00:32:53
|
||||
# @Author : SilverAg.L
|
||||
|
||||
# 搁 bash 折腾管道还是太原始了。
|
||||
# 包依赖 (pacman): python-dbus, libnotify, pipewire-audio
|
||||
|
||||
import dbus
|
||||
|
||||
from os import getenv
|
||||
from subprocess import run as start_process
|
||||
|
||||
# region config
|
||||
# 哪怕设备名也不稳定。pipewire 滚了几轮,桌面扬声器居然不见了。
|
||||
# AUD_OUT_EMB = ( # my laptop embedded speaker
|
||||
# "alsa_output.pci-0000_00_1f"
|
||||
# ".3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink"
|
||||
# )
|
||||
# 环境变量展开这一块。同时也是为了查找起来方便。毕竟已经有 gtklock 这个例外了。
|
||||
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
|
||||
])
|
||||
@@ -1,153 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
THIS_COMMAND=$(basename "$0")
|
||||
IMG_MAGICK="magick"
|
||||
SERVICE_NAME="swaybg.service"
|
||||
|
||||
WP_DIR="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
WP_FILE="$WP_DIR/wallpaper"
|
||||
BLUR_WP="$WP_DIR/wallpaper_blur"
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $THIS_COMMAND [-d output_path] image1 image2 ...
|
||||
Options:
|
||||
-d, --dir OUTPUT_PATH Directory to store chosen image. (default: $WP_DIR)
|
||||
-h, --help Show this help message and exit.
|
||||
Notes:
|
||||
- This script needs 'swaybg.service' to be set up for overview background support (unless awww #521 solved). See niri documentation for details.
|
||||
- Web URLs are also supported (via 'curl' or 'wget' downloading).
|
||||
- If multiple images are provided, one will be randomly picked each time the script is executed.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# showing help shouldn't require any dependencies.
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-d|--dir)
|
||||
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
|
||||
WP_DIR="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
usage ;;
|
||||
--)
|
||||
shift; break ;;
|
||||
-*)
|
||||
echo "Unknown option: $1"; usage ;;
|
||||
*)
|
||||
break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v awww >/dev/null 2>&1; then
|
||||
echo "x) 'awww' not found. Unable to comply." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if command -v magick >/dev/null 2>&1; then
|
||||
IMG_MAGICK="magick"
|
||||
elif command -v convert >/dev/null 2>&1; then
|
||||
IMG_MAGICK="convert"
|
||||
else
|
||||
echo "x) 'magick' or 'convert' not found. Image processing unavailable." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
set_wallpaper() {
|
||||
fsize=$($IMG_MAGICK identify -format "%w %h" -- "$1" 2>/dev/null) || {
|
||||
echo "Invalid image file." >&2
|
||||
exit 10
|
||||
}
|
||||
finput=$1
|
||||
|
||||
read -r fw fh <<< "$fsize"
|
||||
(( fw > 3840 && fh > 2160 )) && {
|
||||
finput=$(mktemp "${TMPDIR:-/tmp}/chbg.XXXXXXXX.webp")
|
||||
trap 'rm -f "$finput"' EXIT
|
||||
echo " -> image too large, resizing ..."
|
||||
# echo "DEBUG: resized image at $finput"
|
||||
$IMG_MAGICK "$1" -resize "3840x2160^" \
|
||||
-quality 90 \
|
||||
-define webp:method=6 \
|
||||
-define webp:alpha-quality=100 "$finput"
|
||||
}
|
||||
|
||||
echo -n " -> making blurred version of '${1##*/}' ... "
|
||||
cp "$finput" "$WP_FILE"
|
||||
$IMG_MAGICK "$WP_FILE" -filter Gaussian -blur 0x30 "$BLUR_WP"
|
||||
echo "Done."
|
||||
|
||||
awww img "$WP_FILE" --transition-type=random
|
||||
|
||||
if [[ -f "$HOME/.config/systemd/user/niri.service.wants/$SERVICE_NAME" ]]; then
|
||||
echo " -> restarting $SERVICE_NAME ..."
|
||||
systemctl --user restart "$SERVICE_NAME"
|
||||
else
|
||||
echo " !) '$SERVICE_NAME' didn't loaded by niri. Won't reload backdrop." >&2
|
||||
echo " => See niri documentation for setting up swaybg systemd unit." >&2
|
||||
echo " => Or wait for awww #521 on codeberg being resolved." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
imagepool=()
|
||||
lastresult=""
|
||||
|
||||
# Read from stdin if data is available
|
||||
if [ ! -t 0 ]; then
|
||||
while IFS= read -r line; do
|
||||
imagepool+=("$line")
|
||||
done
|
||||
fi
|
||||
|
||||
# Read from positional arguments
|
||||
for arg in "$@"; do
|
||||
imagepool+=("$arg")
|
||||
done
|
||||
|
||||
if [ ${#imagepool[@]} -eq 0 ]; then
|
||||
echo "No images provided." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p -- "$WP_DIR" "/tmp/$USER"
|
||||
|
||||
if [ -f "/tmp/$USER/chbg.last-slide.log" ]; then
|
||||
lastresult=$(cat "/tmp/$USER/chbg.last-slide.log")
|
||||
fi
|
||||
|
||||
# Select a random image from the pool and set it as wallpaper
|
||||
while : ; do
|
||||
randomimage="${imagepool[RANDOM % ${#imagepool[@]}]}"
|
||||
if [[ "$randomimage" == "http://"* || "$randomimage" == "https://"* ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$randomimage" != "$lastresult" ] || [ ${#imagepool[@]} -eq 1 ]; then
|
||||
echo "$randomimage" > "/tmp/$USER/chbg.last-slide.log"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Selected: $randomimage"
|
||||
|
||||
request_wallpaper() {
|
||||
tmpfile=$(mktemp)
|
||||
trap 'rm -f "$tmpfile"' EXIT
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -sSL "$1" -o "$tmpfile"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO "$tmpfile" "$1"
|
||||
else
|
||||
echo "x) Unable to fetch image without 'curl' or 'wget'." >&2
|
||||
exit 2
|
||||
fi
|
||||
set_wallpaper "$tmpfile"
|
||||
}
|
||||
|
||||
if [ -f "$randomimage" ]; then
|
||||
set_wallpaper "$randomimage"
|
||||
else
|
||||
request_wallpaper "$randomimage"
|
||||
fi
|
||||
Reference in New Issue
Block a user