mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge pull request #454 from damian-ds7/battery-charging-treshold
Battery charging treshold
This commit is contained in:
@@ -1446,7 +1446,14 @@
|
||||
"charging-rate": "Laderate: {rate} W.",
|
||||
"discharging-rate": "Entladerate: {rate} W.",
|
||||
"charging": "Wird geladen.",
|
||||
"discharging": "Wird entladen."
|
||||
"discharging": "Wird entladen.",
|
||||
"panel": {
|
||||
"title": "Ladeschwelle",
|
||||
"full": "Volle Kapazität ({percent}%)",
|
||||
"balanced": "Ausgeglichen ({percent}%)",
|
||||
"lifespan": "Verlängerte Lebensdauer ({percent}%)",
|
||||
"disabled": "Batteriemanager deaktiviert"
|
||||
}
|
||||
},
|
||||
"authentication": {
|
||||
"failed": "Authentifizierung fehlgeschlagen",
|
||||
@@ -1601,6 +1608,19 @@
|
||||
"low": "Niedriger Batteriestand",
|
||||
"low-desc": "Batterie ist bei {percent}%. Bitte schließen Sie das Ladegerät an."
|
||||
},
|
||||
"battery-manager": {
|
||||
"title": "Batterieschwelle",
|
||||
"set-success-desc": "Batterieschwelle auf {percent}% gesetzt",
|
||||
"initial-setup": "Ersteinrichtung erforderlich",
|
||||
"set-failed": "Fehler beim Setzen der Batterieschwelle",
|
||||
"install-success": "Erfolgreich installiert",
|
||||
"install-missing": "Erforderliche Dateien fehlen",
|
||||
"install-unsupported": "System wird nicht unterstützt",
|
||||
"install-failed": "Installation fehlgeschlagen",
|
||||
"uninstall-setup": "Deinstallation, Authentifizierung erforderlich",
|
||||
"uninstall-success": "Erfolgreich deinstalliert",
|
||||
"uninstall-failed": "Deinstallation fehlgeschlagen"
|
||||
},
|
||||
"missing-control-center": {
|
||||
"label": "Letztes Control-Center-Widget entfernt",
|
||||
"description": "Das Control-Center-Widget wurde aus der Leiste entfernt. Um es erneut über die Leiste zu öffnen, fügen Sie das Widget wieder hinzu. Sie können es auch durch Rechtsklick auf die Leiste öffnen."
|
||||
|
||||
@@ -1574,6 +1574,19 @@
|
||||
"low": "Low battery",
|
||||
"low-desc": "Battery is at {percent}%. Please connect the charger."
|
||||
},
|
||||
"battery-manager": {
|
||||
"title": "Battery threshold",
|
||||
"set-success-desc": "Battery threshold set to {percent}%",
|
||||
"initial-setup": "Initial setup required",
|
||||
"set-failed": "Failed to set battery threshold",
|
||||
"install-success": "Installed successfully",
|
||||
"install-missing": "Required files are missing",
|
||||
"install-unsupported": "System is not supported",
|
||||
"install-failed": "Installation failed",
|
||||
"uninstall-setup": "Uninstalling, authentication required",
|
||||
"uninstall-success": "Uninstalled successfully",
|
||||
"uninstall-failed": "Uninstallation failed"
|
||||
},
|
||||
"missing-control-center": {
|
||||
"label": "Last Control Center widget removed",
|
||||
"description": "The Control Center widget has been removed from the bar. To access it from the bar again, you will need to re-add the widget. You can open it with right clicking on the bar too."
|
||||
@@ -1605,6 +1618,13 @@
|
||||
"charging-rate": "Charging rate: {rate} W.",
|
||||
"discharging-rate": "Discharging rate: {rate} W.",
|
||||
"charging": "Charging.",
|
||||
"discharging": "Discharging."
|
||||
"discharging": "Discharging.",
|
||||
"panel": {
|
||||
"title": "Charge threshold",
|
||||
"full": "Full capacity ({percent}%)",
|
||||
"balanced": "Balanced ({percent}%)",
|
||||
"lifespan": "Extended lifespan ({percent}%)",
|
||||
"disabled": "Battery manager disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,6 +1573,19 @@
|
||||
"low": "Batería baja",
|
||||
"low-desc": "La batería está al {percent}%. Por favor, conecta el cargador."
|
||||
},
|
||||
"battery-manager": {
|
||||
"title": "Umbral de batería",
|
||||
"set-success-desc": "Umbral de batería establecido en {percent}%",
|
||||
"initial-setup": "Configuración inicial requerida",
|
||||
"set-failed": "No se pudo establecer el umbral de batería",
|
||||
"install-success": "Instalado correctamente",
|
||||
"install-missing": "Faltan archivos requeridos",
|
||||
"install-unsupported": "El sistema no es compatible",
|
||||
"install-failed": "Error en la instalación",
|
||||
"uninstall-setup": "Desinstalando, se requiere autenticación",
|
||||
"uninstall-success": "Desinstalado correctamente",
|
||||
"uninstall-failed": "Error en la desinstalación"
|
||||
},
|
||||
"missing-control-center": {
|
||||
"label": "Se eliminó el último widget del Centro de control",
|
||||
"description": "El widget del Centro de control se eliminó de la barra. Para acceder a él nuevamente desde la barra, debes volver a añadir el widget. También puedes abrirlo haciendo clic derecho en la barra."
|
||||
@@ -1604,6 +1617,13 @@
|
||||
"charging-rate": "Tasa de carga: {rate} W.",
|
||||
"discharging-rate": "Tasa de descarga: {rate} W.",
|
||||
"charging": "Cargando.",
|
||||
"discharging": "Descargando."
|
||||
"discharging": "Descargando.",
|
||||
"panel": {
|
||||
"title": "Umbral de carga",
|
||||
"full": "Capacidad total ({percent}%)",
|
||||
"balanced": "Equilibrado ({percent}%)",
|
||||
"lifespan": "Vida útil prolongada ({percent}%)",
|
||||
"disabled": "Administrador de batería deshabilitado"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,6 +1573,19 @@
|
||||
"low": "Batterie faible",
|
||||
"low-desc": "La batterie est à {percent}%. Veuillez brancher le chargeur."
|
||||
},
|
||||
"battery-manager": {
|
||||
"title": "Seuil de batterie",
|
||||
"set-success-desc": "Seuil de batterie défini à {percent}%",
|
||||
"initial-setup": "Configuration initiale requise",
|
||||
"set-failed": "Échec de la définition du seuil de batterie",
|
||||
"install-success": "Installation réussie",
|
||||
"install-missing": "Fichiers requis manquants",
|
||||
"install-unsupported": "Système non pris en charge",
|
||||
"install-failed": "Échec de l'installation",
|
||||
"uninstall-setup": "Désinstallation, authentification requise",
|
||||
"uninstall-success": "Désinstallation réussie",
|
||||
"uninstall-failed": "Échec de la désinstallation"
|
||||
},
|
||||
"missing-control-center": {
|
||||
"label": "Dernier widget du Centre de contrôle supprimé",
|
||||
"description": "Le widget du Centre de contrôle a été retiré de la barre. Pour y accéder à nouveau depuis la barre, veuillez ré‑ajouter le widget. Vous pouvez aussi l'ouvrir en cliquant avec le bouton droit sur la barre."
|
||||
@@ -1604,6 +1617,13 @@
|
||||
"charging-rate": "Taux de charge : {rate} W.",
|
||||
"discharging-rate": "Taux de décharge : {rate} W.",
|
||||
"charging": "En charge.",
|
||||
"discharging": "En décharge."
|
||||
"discharging": "En décharge.",
|
||||
"panel": {
|
||||
"title": "Seuil de charge",
|
||||
"full": "Capacité totale ({percent}%)",
|
||||
"balanced": "Équilibré ({percent}%)",
|
||||
"lifespan": "Durée de vie prolongée ({percent}%)",
|
||||
"disabled": "Gestionnaire de batterie désactivé"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,6 +1573,19 @@
|
||||
"low": "Bateria Fraca",
|
||||
"low-desc": "A bateria está em {percent}%. Por favor, conecte o carregador."
|
||||
},
|
||||
"battery-manager": {
|
||||
"title": "Limite da bateria",
|
||||
"set-success-desc": "Limite da bateria definido para {percent}%",
|
||||
"initial-setup": "Configuração inicial necessária",
|
||||
"set-failed": "Falha ao definir o limite da bateria",
|
||||
"install-success": "Instalado com sucesso",
|
||||
"install-missing": "Arquivos necessários ausentes",
|
||||
"install-unsupported": "Sistema não suportado",
|
||||
"install-failed": "Falha na instalação",
|
||||
"uninstall-setup": "Desinstalando, autenticação necessária",
|
||||
"uninstall-success": "Desinstalado com sucesso",
|
||||
"uninstall-failed": "Falha na desinstalação"
|
||||
},
|
||||
"missing-control-center": {
|
||||
"label": "Último widget da Central de Controle removido",
|
||||
"description": "O widget da Central de Controle foi removido da barra. Para acessá-lo novamente pela barra, adicione o widget novamente. Você também pode abri-lo clicando com o botão direito na barra."
|
||||
@@ -1604,6 +1617,19 @@
|
||||
"charging-rate": "Taxa de carregamento: {rate} W.",
|
||||
"discharging-rate": "Taxa de descarregamento: {rate} W.",
|
||||
"charging": "Carregando.",
|
||||
"discharging": "Descarregando."
|
||||
"discharging": "Descarregando.",
|
||||
"battery-manager": {
|
||||
"title": "Limite da bateria",
|
||||
"set-success-desc": "Limite da bateria definido para {percent}%",
|
||||
"initial-setup": "Configuração inicial necessária",
|
||||
"set-failed": "Falha ao definir o limite da bateria",
|
||||
"install-success": "Instalado com sucesso",
|
||||
"install-missing": "Arquivos necessários ausentes",
|
||||
"install-unsupported": "Sistema não suportado",
|
||||
"install-failed": "Falha na instalação",
|
||||
"uninstall-setup": "Desinstalando, autenticação necessária",
|
||||
"uninstall-success": "Desinstalado com sucesso",
|
||||
"uninstall-failed": "Falha na desinstalação"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,6 +1573,19 @@
|
||||
"low": "电量低",
|
||||
"low-desc": "电量为 {percent}%。请连接充电器。"
|
||||
},
|
||||
"battery-manager": {
|
||||
"title": "电池阈值",
|
||||
"set-success-desc": "电池阈值已设置为 {percent}%",
|
||||
"initial-setup": "需要初始设置",
|
||||
"set-failed": "设置电池阈值失败",
|
||||
"install-success": "安装成功",
|
||||
"install-missing": "缺少必要文件",
|
||||
"install-unsupported": "系统不受支持",
|
||||
"install-failed": "安装失败",
|
||||
"uninstall-setup": "正在卸载,需要身份验证",
|
||||
"uninstall-success": "卸载成功",
|
||||
"uninstall-failed": "卸载失败"
|
||||
},
|
||||
"missing-control-center": {
|
||||
"label": "最后一个控制中心小部件已移除",
|
||||
"description": "控制中心小部件已从状态栏中移除。要再次从状态栏访问它,您需要重新添加小部件。您也可以通过右键点击状态栏来打开它。"
|
||||
@@ -1604,6 +1617,13 @@
|
||||
"charging-rate": "充电速率:{rate} W。",
|
||||
"discharging-rate": "放电速率:{rate} W。",
|
||||
"charging": "正在充电。",
|
||||
"discharging": "正在放电。"
|
||||
"discharging": "正在放电。",
|
||||
"panel": {
|
||||
"title": "充电阈值",
|
||||
"full": "完全容量 ({percent}%)",
|
||||
"balanced": "平衡 ({percent}%)",
|
||||
"lifespan": "延长寿命 ({percent}%)",
|
||||
"disabled": "电池管理器已禁用"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,5 +249,8 @@
|
||||
"enabled": false,
|
||||
"wallpaperChange": "",
|
||||
"darkModeChange": ""
|
||||
},
|
||||
"battery": {
|
||||
"chargingMode": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Battery charge control paths
|
||||
# Add one path per line
|
||||
/sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
/sys/class/power_supply/BAT1/charge_control_end_threshold
|
||||
/sys/class/power_supply/BAT0/charge_stop_threshold
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SUCCESS=0
|
||||
FAILURE=1
|
||||
MISSING_FILES=2
|
||||
UNSUPPORTED=3
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
print_error() {
|
||||
echo -e "$1" >&2
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "$1"
|
||||
}
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
print_error "This script must be run with root privileges"
|
||||
exit $FAILURE
|
||||
fi
|
||||
|
||||
print_info "Installing Battery Manager..."
|
||||
echo
|
||||
|
||||
if [ -n "$PKEXEC_UID" ]; then
|
||||
ACTUAL_USER=$(getent passwd "$PKEXEC_UID" | cut -d: -f1)
|
||||
else
|
||||
ACTUAL_USER="$SUDO_USER"
|
||||
fi
|
||||
|
||||
if [ -z "$ACTUAL_USER" ]; then
|
||||
print_error "Could not determine the actual user"
|
||||
exit $FAILURE
|
||||
fi
|
||||
|
||||
print_info "Installing for user: $ACTUAL_USER"
|
||||
echo
|
||||
|
||||
print_info "Checking required files..."
|
||||
|
||||
MISSING_FILES_LIST=()
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/battery-paths.conf" ]; then
|
||||
MISSING_FILES_LIST+=("battery-paths.conf")
|
||||
fi
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/templates/battery-manager.sh" ]; then
|
||||
MISSING_FILES_LIST+=("battery-manager.sh")
|
||||
fi
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/templates/battery-manager.policy" ]; then
|
||||
MISSING_FILES_LIST+=("battery-manager.policy")
|
||||
fi
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/templates/battery-manager.rules" ]; then
|
||||
MISSING_FILES_LIST+=("battery-manager.rules")
|
||||
fi
|
||||
|
||||
if [ ${#MISSING_FILES_LIST[@]} -gt 0 ]; then
|
||||
print_error "Missing required files in $SCRIPT_DIR:"
|
||||
for file in "${MISSING_FILES_LIST[@]}"; do
|
||||
print_error " - $file"
|
||||
done
|
||||
exit $MISSING_FILES
|
||||
fi
|
||||
|
||||
print_info "All required files found"
|
||||
|
||||
print_info "Checking battery paths..."
|
||||
BATTERY_PATHS=($(grep -v '^#' "$SCRIPT_DIR/battery-paths.conf" | grep -v '^$'))
|
||||
EXISTING_PATHS=()
|
||||
|
||||
for path in "${BATTERY_PATHS[@]}"; do
|
||||
if [ -f "$path" ]; then
|
||||
EXISTING_PATHS+=("$path")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#EXISTING_PATHS[@]} -eq 0 ]; then
|
||||
print_error "None of the battery control files exist. Please check your hardware compatibility."
|
||||
exit $UNSUPPORTED
|
||||
fi
|
||||
|
||||
print_info "Found ${#EXISTING_PATHS[@]} compatible battery control file(s)"
|
||||
|
||||
print_info "Installing battery manager script..."
|
||||
BATTERY_MANAGER_SCRIPT="/usr/bin/battery-manager-$ACTUAL_USER"
|
||||
|
||||
SHEBANG=$(head -n 1 "$SCRIPT_DIR/templates/battery-manager.sh")
|
||||
echo "$SHEBANG" > "$BATTERY_MANAGER_SCRIPT"
|
||||
echo "" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
for path in "${EXISTING_PATHS[@]}"; do
|
||||
echo " \"$path\"" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
done
|
||||
echo ")" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
echo "" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
tail -n +2 "$SCRIPT_DIR/templates/battery-manager.sh" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
chmod +x "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
print_info "Battery manager script created from $SCRIPT_DIR/templates/battery-manager.sh with compatible paths"
|
||||
print_info "Script installed at $BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
print_info "Creating log file..."
|
||||
touch /var/log/battery-manager.log
|
||||
chmod 644 /var/log/battery-manager.log
|
||||
print_info "Log file created at /var/log/battery-manager.log"
|
||||
|
||||
print_info "Creating polkit policy..."
|
||||
|
||||
POLICY_FILE="/usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy"
|
||||
|
||||
sed -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \
|
||||
"$SCRIPT_DIR/templates/battery-manager.policy" > "$POLICY_FILE"
|
||||
|
||||
print_info "Polkit policy copied from $SCRIPT_DIR/templates/battery-manager.policy"
|
||||
print_info "Polkit policy created at $POLICY_FILE"
|
||||
print_info "Creating polkit rule..."
|
||||
|
||||
RULES_FILE="/etc/polkit-1/rules.d/50-battery-manager-$ACTUAL_USER.rules"
|
||||
|
||||
sed "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \
|
||||
"$SCRIPT_DIR/templates/battery-manager.rules" > "$RULES_FILE"
|
||||
|
||||
print_info "Polkit rule copied from $SCRIPT_DIR/templates/battery-manager.rules"
|
||||
print_info "Polkit rule created for user: $ACTUAL_USER at $RULES_FILE"
|
||||
|
||||
print_info "Restarting polkit..."
|
||||
if systemctl restart polkit 2>/dev/null; then
|
||||
print_info "Polkit restarted"
|
||||
else
|
||||
print_info "Could not restart polkit automatically, you may need to reboot"
|
||||
fi
|
||||
|
||||
print_info "Creating uninstall script..."
|
||||
UNINSTALL_SCRIPT="$SCRIPT_DIR/uninstall-battery-manager.sh"
|
||||
|
||||
|
||||
if [ -f "$SCRIPT_DIR/templates/uninstall-template" ]; then
|
||||
SHEBANG=$(head -n 1 "$SCRIPT_DIR/templates/uninstall-template")
|
||||
else
|
||||
SHEBANG="#!/usr/bin/env bash"
|
||||
fi
|
||||
echo "$SHEBANG" > "$UNINSTALL_SCRIPT"
|
||||
echo "" >> "$UNINSTALL_SCRIPT"
|
||||
|
||||
cat >> "$UNINSTALL_SCRIPT" << EOF
|
||||
SCRIPT_PATH="$BATTERY_MANAGER_SCRIPT"
|
||||
POLICY_PATH="$POLICY_FILE"
|
||||
RULE_PATH="$RULES_FILE"
|
||||
LOG_PATH="/var/log/battery-manager.log"
|
||||
|
||||
EOF
|
||||
|
||||
if [ -f "$SCRIPT_DIR/templates/uninstall-template" ]; then
|
||||
tail -n +2 "$SCRIPT_DIR/templates/uninstall-template" >> "$UNINSTALL_SCRIPT"
|
||||
fi
|
||||
|
||||
chmod 744 "$UNINSTALL_SCRIPT"
|
||||
chown root:root "$UNINSTALL_SCRIPT"
|
||||
|
||||
print_info "Uninstall script created at $UNINSTALL_SCRIPT"
|
||||
|
||||
|
||||
echo
|
||||
print_info "Installation complete!"
|
||||
echo
|
||||
print_info "Log file: /var/log/battery-manager.log"
|
||||
print_info "User-specific script: $BATTERY_MANAGER_SCRIPT"
|
||||
print_info "User-specific policy: $POLICY_FILE"
|
||||
print_info "User-specific rules: $RULES_FILE"
|
||||
print_info "User-specific uninstall script: $UNINSTALL_SCRIPT"
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SUPPRESS_NOTIFICATIONS=false
|
||||
|
||||
print_error() {
|
||||
echo -e "$1" >&2
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "$1"
|
||||
}
|
||||
|
||||
send_notification() {
|
||||
local urgency="$1"
|
||||
local title="$2"
|
||||
local message="$3"
|
||||
|
||||
if [ "$SUPPRESS_NOTIFICATIONS" = false ] && command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send -u "$urgency" "$title" "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-q|--quiet)
|
||||
SUPPRESS_NOTIFICATIONS=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
print_error "Unknown option: $1"
|
||||
echo "Usage: $0 [OPTIONS] <number>" >&2
|
||||
echo "Options:" >&2
|
||||
echo " -q, --quiet Suppress notifications" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
BATTERY_LEVEL="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$BATTERY_LEVEL" ]; then
|
||||
print_error "Battery level not specified"
|
||||
echo "Usage: $0 [OPTIONS] <number>" >&2
|
||||
echo "Options:" >&2
|
||||
echo " -q, --quiet Suppress notifications" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$BATTERY_LEVEL" =~ ^[0-9]+$ ]] || [ "$BATTERY_LEVEL" -gt 100 ] || [ "$BATTERY_LEVEL" -lt 0 ]; then
|
||||
print_error "Battery level must be a number between 0-100"
|
||||
echo "Usage: $0 [OPTIONS] <number>" >&2
|
||||
echo "Options:" >&2
|
||||
echo " -q, --quiet Suppress notifications" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_USER="$USER"
|
||||
if [ -z "$CURRENT_USER" ]; then
|
||||
CURRENT_USER="$(whoami)"
|
||||
fi
|
||||
|
||||
BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$CURRENT_USER"
|
||||
|
||||
SUCCESS=0
|
||||
MISSING_FILES=2
|
||||
|
||||
if [ ! -f "$BATTERY_MANAGER_PATH" ]; then
|
||||
print_error "Battery manager components missing for user $CURRENT_USER!"
|
||||
exit $MISSING_FILES
|
||||
fi
|
||||
|
||||
print_info "Setting battery charging threshold to $BATTERY_LEVEL% for user $CURRENT_USER..."
|
||||
|
||||
if pkexec "$BATTERY_MANAGER_PATH" "$BATTERY_LEVEL"; then
|
||||
print_info "Battery charging threshold set to $BATTERY_LEVEL%"
|
||||
send_notification "normal" "Battery Threshold Updated" \
|
||||
"Battery charging threshold has been set to $BATTERY_LEVEL%"
|
||||
else
|
||||
print_error "Failed to set battery charging threshold"
|
||||
send_notification "critical" "Battery Threshold Failed" \
|
||||
"Failed to set battery charging threshold to $BATTERY_LEVEL%"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<action id="com.local.battery-manager.ACTUAL_USER_PLACEHOLDER">
|
||||
<description>Manage battery settings for ACTUAL_USER_PLACEHOLDER</description>
|
||||
<message>Authentication is required to manage battery settings</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/battery-manager-ACTUAL_USER_PLACEHOLDER</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
||||
@@ -0,0 +1,14 @@
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "com.local.battery-manager.ACTUAL_USER_PLACEHOLDER" &&
|
||||
subject.user == "ACTUAL_USER_PLACEHOLDER") {
|
||||
|
||||
// Check if the parent process is quickshell or set-battery-threshold
|
||||
var pid = subject.pid;
|
||||
var ppid = polkit.spawn(["ps", "-o", "ppid=", "-p", pid.toString()]).trim();
|
||||
var parentCmd = polkit.spawn(["ps", "-o", "comm=", "-p", ppid]).trim();
|
||||
|
||||
if (parentCmd.indexOf("quickshell") !== -1 || parentCmd.indexOf("set-battery-treshold") !== -1) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LOG_FILE="/var/log/battery-manager.log"
|
||||
|
||||
log_message() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: No battery level provided" >&2
|
||||
log_message "ERROR: No battery level provided"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BATTERY_LEVEL="$1"
|
||||
|
||||
if ! [[ "$BATTERY_LEVEL" =~ ^[0-9]+$ ]] || [ "$BATTERY_LEVEL" -gt 100 ] || [ "$BATTERY_LEVEL" -lt 0 ]; then
|
||||
echo "Error: Invalid battery level. Must be 0-100" >&2
|
||||
log_message "ERROR: Invalid battery level: $BATTERY_LEVEL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUCCESS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
for path in "${BATTERY_PATHS[@]}"; do
|
||||
[[ -z "$path" || "$path" =~ ^# ]] && continue
|
||||
|
||||
if [ -f "$path" ] && [ -w "$path" ]; then
|
||||
if echo "$BATTERY_LEVEL" > "$path" 2>/dev/null; then
|
||||
echo "Updated: $path"
|
||||
log_message "SUCCESS: Updated $path to $BATTERY_LEVEL"
|
||||
((SUCCESS_COUNT++))
|
||||
else
|
||||
echo "Failed to write: $path" >&2
|
||||
log_message "ERROR: Failed to write to $path"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
else
|
||||
echo "Skipped (not found/writable): $path"
|
||||
log_message "INFO: Skipped $path (not found or not writable)"
|
||||
fi
|
||||
done
|
||||
|
||||
log_message "SUMMARY: Updated $SUCCESS_COUNT file(s), failed $FAIL_COUNT, battery level: $BATTERY_LEVEL"
|
||||
|
||||
if [ "$SUCCESS_COUNT" -eq 0 ]; then
|
||||
echo "Error: No battery files were updated" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully updated $SUCCESS_COUNT battery file(s)"
|
||||
exit 0
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uninstalling battery manager..."
|
||||
|
||||
if [ -f "$SCRIPT_PATH" ]; then
|
||||
rm -f "$SCRIPT_PATH"
|
||||
echo "Removed script from $SCRIPT_PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$POLICY_PATH" ]; then
|
||||
rm -f "$POLICY_PATH"
|
||||
echo "Removed policy file from $POLICY_PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$RULE_PATH" ]; then
|
||||
rm -f "$RULE_PATH"
|
||||
echo "Removed udev rule from $RULE_PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$LOG_PATH" ]; then
|
||||
rm -f "$LOG_PATH"
|
||||
echo "Removed log file from $LOG_PATH"
|
||||
fi
|
||||
|
||||
echo "Uninstallation completed successfully"
|
||||
@@ -389,6 +389,11 @@ Singleton {
|
||||
property string wallpaperChange: ""
|
||||
property string darkModeChange: ""
|
||||
}
|
||||
|
||||
// battery
|
||||
property JsonObject battery: JsonObject {
|
||||
property int chargingMode: 0
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: 350
|
||||
preferredHeight: 210
|
||||
panelKeyboardFocus: true
|
||||
|
||||
property var optionsModel: []
|
||||
|
||||
function updateOptionsModel() {
|
||||
let newOptions = [{
|
||||
"id": BatteryService.ChargingMode.Full,
|
||||
"label": "battery.panel.full",
|
||||
"icon": "battery-4"
|
||||
}, {
|
||||
"id": BatteryService.ChargingMode.Balanced,
|
||||
"label": "battery.panel.balanced",
|
||||
"icon": "battery-3"
|
||||
}, {
|
||||
"id": BatteryService.ChargingMode.Lifespan,
|
||||
"label": "battery.panel.lifespan",
|
||||
"icon": "battery-2"
|
||||
}]
|
||||
root.optionsModel = newOptions
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
updateOptionsModel()
|
||||
}
|
||||
|
||||
panelContent: Rectangle {
|
||||
color: Color.transparent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// HEADER
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: I18n.tr("battery.panel.title")
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: batteryManagerSwitch
|
||||
checked: BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled
|
||||
onToggled: checked => BatteryService.toggleEnabled(checked)
|
||||
baseSize: Style.baseWidgetSize * 0.65 * scaling
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: batteryGroup
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Color.transparent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
Repeater {
|
||||
model: optionsModel
|
||||
|
||||
NRadioButton {
|
||||
visible: BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled
|
||||
ButtonGroup.group: batteryGroup
|
||||
required property var modelData
|
||||
text: I18n.tr(modelData.label, {
|
||||
"percent": BatteryService.getThresholdValue(modelData.id)
|
||||
})
|
||||
checked: BatteryService.chargingMode === modelData.id
|
||||
onClicked: {
|
||||
BatteryService.setChargingMode(modelData.id)
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: BatteryService.chargingMode === BatteryService.ChargingMode.Disabled
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("battery.panel.disabled")
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,7 @@ Item {
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
|
||||
onClicked: PanelService.getPanel("batteryPanel")?.toggle(this)
|
||||
tooltipText: {
|
||||
let lines = []
|
||||
if (testMode) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
@@ -8,6 +9,21 @@ import qs.Services
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
enum ChargingMode {
|
||||
Disabled = 0,
|
||||
Full,
|
||||
Balanced,
|
||||
Lifespan
|
||||
}
|
||||
|
||||
property int chargingMode: Settings.data.battery.chargingMode
|
||||
readonly property string batterySetterScript: Quickshell.shellDir + '/Bin/battery-manager/set-battery-treshold.sh'
|
||||
readonly property string batteryInstallerScript: Quickshell.shellDir + '/Bin/battery-manager/install-battery-manager.sh'
|
||||
readonly property string batteryUninstallerScript: Quickshell.shellDir + '/Bin/battery-manager/uninstall-battery-manager.sh'
|
||||
|
||||
// This is used to omit toast message and writing mode to settings on startup
|
||||
property bool initialSetter: true
|
||||
|
||||
// Choose icon based on charge and charging state
|
||||
function getIcon(percent, charging, isReady) {
|
||||
if (!isReady) {
|
||||
@@ -28,4 +44,204 @@ Singleton {
|
||||
return "battery"
|
||||
}
|
||||
}
|
||||
|
||||
function getThresholdValue(chargingMode) {
|
||||
switch (chargingMode) {
|
||||
case BatteryService.ChargingMode.Full:
|
||||
return "100"
|
||||
case BatteryService.ChargingMode.Balanced:
|
||||
return "80"
|
||||
case BatteryService.ChargingMode.Lifespan:
|
||||
return "60"
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEnabled(enabled) {
|
||||
if (enabled) {
|
||||
setChargingMode(BatteryService.ChargingMode.Full)
|
||||
} else {
|
||||
BatteryService.chargingMode = BatteryService.ChargingMode.Disabled
|
||||
BatteryService.initialSetter = true
|
||||
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-setup"))
|
||||
PanelService.getPanel("batteryPanel")?.toggle(this)
|
||||
uninstallerProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
function setChargingMode(newMode) {
|
||||
if (newMode !== BatteryService.ChargingMode.Full && newMode !== BatteryService.ChargingMode.Balanced && newMode !== BatteryService.ChargingMode.Lifespan) {
|
||||
Logger.warn("BatteryService", `Invalid charging mode set ${newMode}`)
|
||||
return
|
||||
}
|
||||
BatteryService.chargingMode = newMode
|
||||
BatteryService.applyChargingMode()
|
||||
}
|
||||
|
||||
function cycleModes() {
|
||||
// Cycles charging modes from full to lifespan while skipping disabled
|
||||
const nextMode = (chargingMode % 3) + 1
|
||||
setChargingMode(nextMode)
|
||||
}
|
||||
|
||||
function applyChargingMode() {
|
||||
let command = [batterySetterScript]
|
||||
|
||||
// Currently the script sends notifications by default but quickshell
|
||||
// uses toast messages so the flag is passed to supress notifs
|
||||
command.push("-q")
|
||||
|
||||
command.push(BatteryService.getThresholdValue(BatteryService.chargingMode))
|
||||
|
||||
setterProcess.command = command
|
||||
setterProcess.running = true
|
||||
}
|
||||
|
||||
function runInstaller() {
|
||||
installerProcess.command = ["pkexec", batteryInstallerScript]
|
||||
installerProcess.running = true
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled && BatteryService.chargingMode !== BatteryService.ChargingMode.Full) {
|
||||
BatteryService.applyChargingMode()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: setterProcess
|
||||
workingDirectory: Quickshell.shellDir
|
||||
running: false
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
Logger.log("BatteryService", "Battery threshold set successfully")
|
||||
if (BatteryService.initialSetter) {
|
||||
BatteryService.initialSetter = false
|
||||
return
|
||||
}
|
||||
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-success-desc", {
|
||||
"percent": BatteryService.getThresholdValue(BatteryService.chargingMode)
|
||||
}))
|
||||
Settings.data.battery.chargingMode = BatteryService.chargingMode
|
||||
} else if (exitCode === 2) {
|
||||
ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup"))
|
||||
PanelService.getPanel("batteryPanel")?.toggle(this)
|
||||
BatteryService.runInstaller()
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-failed"))
|
||||
Logger.error("BatteryService", `Setter process failed with exit code: ${exitCode}`)
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.warn("BatteryService", "SetterProcess stderr:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.log("BatteryService", "SetterProcess stdout:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Installer process - installs battery manager components
|
||||
Process {
|
||||
id: installerProcess
|
||||
workingDirectory: Quickshell.shellDir
|
||||
running: false
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-success"))
|
||||
BatteryService.applyChargingMode()
|
||||
} else if (exitCode === 2) {
|
||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-missing"))
|
||||
} else if (exitCode === 3) {
|
||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-unsupported"))
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-failed"))
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
BatteryService.chargingMode = BatteryService.ChargingMode.Disabled
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.warn("BatteryService", "InstallerProcess stderr:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.log("BatteryService", "InstallerProcess stdout:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: uninstallerProcess
|
||||
workingDirectory: Quickshell.shellDir
|
||||
command: ["pkexec", batteryUninstallerScript]
|
||||
running: false
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
Logger.log("BatteryService", "Battery Manager uninstalled successfully")
|
||||
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-success"))
|
||||
Settings.data.battery.chargingMode = BatteryService.chargingMode
|
||||
cleanupProcess.running = true
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-failed"))
|
||||
Logger.error("BatteryService", `Uninstaller process failed with exit code: ${exitCode}`)
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.warn("BatteryService", "UninstallerProcess stderr:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.log("BatteryService", "UninstallerProcess stdout:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup process - deletes uninstaller after it sucessfull ;
|
||||
Process {
|
||||
id: cleanupProcess
|
||||
workingDirectory: Quickshell.shellDir
|
||||
command: ["rm", "-rf", batteryUninstallerScript]
|
||||
running: false
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
Logger.log("BatteryService", "Battery Manager uninstalled successfully")
|
||||
} else {
|
||||
Logger.error("BatteryService", `Cleanup process failed with exit code: ${exitCode}`)
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.warn("BatteryService", "CleanupProcess stderr:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text) {
|
||||
Logger.log("BatteryService", "CleanupProcess stdout:", this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,28 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "batteryManager"
|
||||
|
||||
function cycle() {
|
||||
BatteryService.cycleModes()
|
||||
}
|
||||
|
||||
function set(mode: string) {
|
||||
switch (mode) {
|
||||
case "full":
|
||||
BatteryService.setChargingMode(BatteryService.ChargingMode.Full)
|
||||
break
|
||||
case "balanced":
|
||||
BatteryService.setChargingMode(BatteryService.ChargingMode.Balanced)
|
||||
break
|
||||
case "lifespan":
|
||||
BatteryService.setChargingMode(BatteryService.ChargingMode.Lifespan)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "media"
|
||||
function playPause() {
|
||||
|
||||
@@ -28,6 +28,7 @@ import qs.Modules.SessionMenu
|
||||
import qs.Modules.Bar
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Bar.Bluetooth
|
||||
import qs.Modules.Bar.Battery
|
||||
import qs.Modules.Bar.Calendar
|
||||
import qs.Modules.Bar.WiFi
|
||||
|
||||
@@ -87,6 +88,7 @@ ShellRoot {
|
||||
FontService.init()
|
||||
HooksService.init()
|
||||
BluetoothService.init()
|
||||
BatteryService.init()
|
||||
}
|
||||
|
||||
Background {}
|
||||
@@ -160,6 +162,10 @@ ShellRoot {
|
||||
id: wallpaperPanel
|
||||
objectName: "wallpaperPanel"
|
||||
}
|
||||
BatteryPanel {
|
||||
id: batteryPanel
|
||||
objectName: "batteryPanel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user