Merge pull request #454 from damian-ds7/battery-charging-treshold

Battery charging treshold
This commit is contained in:
Lemmy
2025-10-14 20:43:10 -04:00
committed by GitHub
20 changed files with 905 additions and 6 deletions
+21 -1
View File
@@ -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."
+21 -1
View File
@@ -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"
}
}
}
+21 -1
View File
@@ -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"
}
}
}
+21 -1
View File
@@ -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é"
}
}
}
+27 -1
View File
@@ -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"
}
}
}
+21 -1
View File
@@ -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": "电池管理器已禁用"
}
}
}
+3
View File
@@ -249,5 +249,8 @@
"enabled": false,
"wallpaperChange": "",
"darkModeChange": ""
},
"battery": {
"chargingMode": 0
}
}
+5
View File
@@ -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
View File
@@ -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"
+86
View File
@@ -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"
+5
View File
@@ -389,6 +389,11 @@ Singleton {
property string wallpaperChange: ""
property string darkModeChange: ""
}
// battery
property JsonObject battery: JsonObject {
property int chargingMode: 0
}
}
// -----------------------------------------------------
+137
View File
@@ -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
}
}
}
}
}
}
+1
View File
@@ -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) {
+216
View File
@@ -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)
}
}
}
}
}
+22
View File
@@ -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() {
+6
View File
@@ -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"
}
}
}
}