From 9e5f8a425e399d5c7066e8966f22b51636591364 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 17:44:25 +0200 Subject: [PATCH 01/43] BatteryService: implement test version of charge treshold script --- Bin/set-battery-treshold.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 Bin/set-battery-treshold.sh diff --git a/Bin/set-battery-treshold.sh b/Bin/set-battery-treshold.sh new file mode 100755 index 00000000..66665826 --- /dev/null +++ b/Bin/set-battery-treshold.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env -S bash + +# Check if exectly one argument was provided +if [ "$#" -ne 1 ]; then + echo "Error: Battery level not specified" >&2 + echo "Usage: $0 " >&2 + exit 1 +fi + +# Check if argument is a number +if ! [[ "$1" =~ ^[0-9]+$ ]]; then + echo "Error: Battery level must be a number" >&2 + echo "Usage: $0 " >&2 + exit 1 +fi + +echo "$1" | pkexec tee ~/test From 90ed62ccf25df7c2d8ee976ab6fcdf7950211053 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 17:45:20 +0200 Subject: [PATCH 02/43] BatteryService: implement basic functionality to set battery treshold --- Services/BatteryService.qml | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index f927c83a..e7f17dd9 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -1,6 +1,7 @@ pragma Singleton import Quickshell +import Quickshell.Io import Quickshell.Services.UPower import qs.Commons import qs.Services @@ -8,6 +9,15 @@ import qs.Services Singleton { id: root + enum ChargingMode { + Full, + Balanced, + Conservative + } + + property int chargingMode: BatteryService.ChargingMode.Balanced + readonly property string batteryTresholdScript: Quickshell.shellDir + '/Bin/set-battery-treshold.sh' + // Choose icon based on charge and charging state function getIcon(percent, charging, isReady) { if (!isReady) { @@ -28,4 +38,37 @@ Singleton { return "battery" } } + + function setChargingMode(newMode) { + switch (newMode) { + case BatteryService.ChargingMode.Full: + BatteryService.chargingMode = newMode + chargeLimitProcess.command = [batteryTresholdScript, "100"] + break + case BatteryService.ChargingMode.Balanced: + BatteryService.chargingMode = newMode + chargeLimitProcess.command = [batteryTresholdScript, "80"] + break + case BatteryService.ChargingMode.Conservative: + BatteryService.chargingMode = newMode + chargeLimitProcess.command = [batteryTresholdScript, "60"] + break + default: + return + } + chargeLimitProcess.running = true + } + + Process { + id: chargeLimitProcess + workingDirectory: Quickshell.shellDir + running: false + stderr: StdioCollector { + onStreamFinished: { + if (this.text) { + Logger.warn("BatteryService", "ChargeLimitProcess stderr:", this.text) + } + } + } + } } From 2f515ca3c5aaf4b0ae76c93a4cd2a57bb9860879 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 17:49:27 +0200 Subject: [PATCH 03/43] BatteryPanel: implement basic battery panel with 3 radio buttons --- Assets/Translations/en.json | 13 ++-- Modules/Bar/Battery/BatteryPanel.qml | 101 +++++++++++++++++++++++++++ Modules/Bar/Widgets/Battery.qml | 1 + shell.qml | 5 ++ 4 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 Modules/Bar/Battery/BatteryPanel.qml diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ff91166a..7e842cab 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -2,7 +2,6 @@ "settings": { "general": { "title": "General", - "profile": { "section": { "label": "Profile", @@ -14,7 +13,6 @@ }, "select-avatar": "Select avatar image" }, - "ui": { "section": { "label": "User interface", @@ -1197,7 +1195,6 @@ "enter-width-pixels": "Enter width in pixels", "enter-command": "Enter command to execute (app or custom script)", "command-example": "echo \"Hello World\"", - "search-wallpapers": "Type to filter wallpapers...", "search-launcher": "Search entries... or use > for commands", "search": "Search...", @@ -1444,7 +1441,6 @@ "thunderstorm": "Thunderstorm", "unknown": "Unknown" }, - "authentication": { "failed": "Authentication failed", "error": "Authentication error" @@ -1459,6 +1455,13 @@ "charging-rate": "Charging rate: {rate} W.", "discharging-rate": "Discharging rate: {rate} W.", "charging": "Charging.", - "discharging": "Discharging." + "discharging": "Discharging.", + "panel": { + "title": "Charging mode", + "full": "Full capacity", + "balanced": "Balanced", + "conservative": "Conservative", + "footer": "Charging treshold set to {limit}%" + } } } diff --git a/Modules/Bar/Battery/BatteryPanel.qml b/Modules/Bar/Battery/BatteryPanel.qml new file mode 100644 index 00000000..92ab067e --- /dev/null +++ b/Modules/Bar/Battery/BatteryPanel.qml @@ -0,0 +1,101 @@ +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: 300 + preferredHeight: 200 + 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.Conservative, + "label": "battery.panel.conservative", + "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 + + NIcon { + icon: optionsModel[BatteryService.chargingMode].icon + pointSize: Style.fontSizeXXL * scaling + color: Color.mPrimary + } + + NText { + text: I18n.tr("battery.panel.title") + pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("tooltips.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + root.close() + } + } + } + + NDivider { + Layout.fillWidth: true + } + + ButtonGroup { + id: batteryGroup + } + + Repeater { + model: optionsModel + + NRadioButton { + ButtonGroup.group: batteryGroup + required property var modelData + text: I18n.tr(modelData.label) + checked: BatteryService.chargingMode === modelData.id + onClicked: { + BatteryService.setChargingMode(modelData.id) + } + Layout.fillWidth: true + } + } + } + } +} diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index 336ee925..83a6e850 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -96,6 +96,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) { diff --git a/shell.qml b/shell.qml index a32b7b40..c52ae02f 100644 --- a/shell.qml +++ b/shell.qml @@ -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 @@ -159,6 +160,10 @@ ShellRoot { id: wallpaperPanel objectName: "wallpaperPanel" } + BatteryPanel { + id: batteryPanel + objectName: "batteryPanel" + } } } } From c2ff74aa203fedd2f6e184c8f43d66dccc7c7b0c Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:18:19 +0200 Subject: [PATCH 04/43] BatteryService: create placeholder policy to chagne charging treshold without password --- Bin/battery-manager/battery-manager.policy | 17 +++++++++++++++++ Bin/battery-manager/battery-manager.rules | 14 ++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 Bin/battery-manager/battery-manager.policy create mode 100644 Bin/battery-manager/battery-manager.rules diff --git a/Bin/battery-manager/battery-manager.policy b/Bin/battery-manager/battery-manager.policy new file mode 100644 index 00000000..78473a7a --- /dev/null +++ b/Bin/battery-manager/battery-manager.policy @@ -0,0 +1,17 @@ + + + + + Manage battery settings for ACTUAL_USER_PLACEHOLDER + Authentication is required to manage battery settings + + no + no + yes + + /usr/bin/battery-manager-ACTUAL_USER_PLACEHOLDER + true + + diff --git a/Bin/battery-manager/battery-manager.rules b/Bin/battery-manager/battery-manager.rules new file mode 100644 index 00000000..6e927653 --- /dev/null +++ b/Bin/battery-manager/battery-manager.rules @@ -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; + } + } +}); From 4ee55484bcb119916f85b1bd5c9ae19583910598 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:20:07 +0200 Subject: [PATCH 05/43] BatteryService: implement script to change charge treshold based on provided list battery files --- Bin/battery-manager/battery-manager.sh | 60 ++++++++++++++++++++++++++ Bin/battery-manager/battery-paths.conf | 5 +++ 2 files changed, 65 insertions(+) create mode 100644 Bin/battery-manager/battery-manager.sh create mode 100644 Bin/battery-manager/battery-paths.conf diff --git a/Bin/battery-manager/battery-manager.sh b/Bin/battery-manager/battery-manager.sh new file mode 100644 index 00000000..67766a44 --- /dev/null +++ b/Bin/battery-manager/battery-manager.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +CONFIG_FILE="/etc/battery-manager/paths.conf" +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 + +if [ ! -f "$CONFIG_FILE" ]; then + echo "Error: Config file not found: $CONFIG_FILE" >&2 + log_message "ERROR: Config file not found" + exit 1 +fi + +SUCCESS_COUNT=0 +FAIL_COUNT=0 + +while IFS= read -r path; 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 < "$CONFIG_FILE" + +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 diff --git a/Bin/battery-manager/battery-paths.conf b/Bin/battery-manager/battery-paths.conf new file mode 100644 index 00000000..3a539237 --- /dev/null +++ b/Bin/battery-manager/battery-paths.conf @@ -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 From 4f6d2a594711090503ea2eaeba186b2b6cfb02e8 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:22:28 +0200 Subject: [PATCH 06/43] BatteryService: implement script to install battery-manager with onetime pass prompt --- .../install-battery-manager.sh | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 Bin/battery-manager/install-battery-manager.sh diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh new file mode 100755 index 00000000..0803ddea --- /dev/null +++ b/Bin/battery-manager/install-battery-manager.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +set -e + +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 1 +fi + +print_info "Installing Battery Manager..." +echo + +if [ -n "$PKEXEC_UID" ]; then + ACTUAL_USER=$(getent passwd "$PKEXEC_UID" | cut -d: -f1) + ACTUAL_HOME=$(getent passwd "$PKEXEC_UID" | cut -d: -f6) +else + ACTUAL_USER="$SUDO_USER" + ACTUAL_HOME="$HOME" +fi + +if [ -z "$ACTUAL_USER" ]; then + print_error "Could not determine the actual user" + exit 1 +fi + +print_info "Installing for user: $ACTUAL_USER" +echo + +print_info "Creating configuration directory..." +mkdir -p /etc/battery-manager + +if [ -f "$SCRIPT_DIR/battery-paths.conf" ]; then + cp "$SCRIPT_DIR/battery-paths.conf" /etc/battery-manager/paths.conf + print_info "Paths configuration copied from $SCRIPT_DIR/battery-paths.conf" +else + print_error "battery-paths.conf not found in $SCRIPT_DIR" + exit 1 +fi + +chmod 755 /etc/battery-manager +chmod 644 /etc/battery-manager/paths.conf +print_info "Configuration created at /etc/battery-manager/paths.conf" + +print_info "Installing battery manager script..." + +BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$ACTUAL_USER" + +if [ -f "$SCRIPT_DIR/battery-manager.sh" ]; then + cp "$SCRIPT_DIR/battery-manager.sh" "$BATTERY_MANAGER_PATH" + chmod +x "$BATTERY_MANAGER_PATH" + print_info "Battery manager script copied from $SCRIPT_DIR/battery-manager.sh" +else + print_error "battery-manager.sh not found in $SCRIPT_DIR" + exit 1 +fi + +print_info "Script installed at $BATTERY_MANAGER_PATH" + +# 3. Create log file +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" + +# 4. Create polkit policy +print_info "Creating polkit policy..." + +POLICY_FILE="/usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" + +if [ -f "$SCRIPT_DIR/battery-manager.policy" ]; then + # Update the policy file with the correct installer path and user + sed -e "s|/home/damian/Projects/noctalia/battery-charging-treshold/Bin/install-battery-manager.sh|$SCRIPT_DIR/install-battery-manager.sh|g" \ + -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ + "$SCRIPT_DIR/battery-manager.policy" > "$POLICY_FILE" + print_info "Polkit policy copied from $SCRIPT_DIR/battery-manager.policy" +else + print_error "battery-manager.policy not found in $SCRIPT_DIR" + exit 1 +fi + +print_info "Polkit policy created at $POLICY_FILE" + +# 5. Create polkit rule +print_info "Creating polkit rule..." + +RULES_FILE="/etc/polkit-1/rules.d/50-battery-manager-$ACTUAL_USER.rules" + +if [ -f "$SCRIPT_DIR/battery-manager.rules" ]; then + # Replace the placeholder with the actual user + sed "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ + "$SCRIPT_DIR/battery-manager.rules" > "$RULES_FILE" + print_info "Polkit rule copied from $SCRIPT_DIR/battery-manager.rules" +else + print_error "battery-manager.rules not found in $SCRIPT_DIR" + exit 1 +fi + +print_info "Polkit rule created for user: $ACTUAL_USER at $RULES_FILE" + +# 6. Restart polkit +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 + +echo +print_info "Installation complete!" +echo +print_info "Configuration file: /etc/battery-manager/paths.conf" +print_info "Log file: /var/log/battery-manager.log" +print_info "User-specific script: /usr/bin/battery-manager-$ACTUAL_USER" +print_info "User-specific policy: /usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" +print_info "User-specific rules: /etc/polkit-1/rules.d/50-battery-manager-$ACTUAL_USER.rules" From e661de9930b7ce98ab3c15810404c630d8c6aee7 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:23:25 +0200 Subject: [PATCH 07/43] BatteryService: modify setter script to check if first install and call isntaller script if yes --- Bin/battery-manager/set-battery-treshold.sh | 74 +++++++++++++++++++++ Bin/set-battery-treshold.sh | 17 ----- Services/BatteryService.qml | 9 ++- 3 files changed, 82 insertions(+), 18 deletions(-) create mode 100755 Bin/battery-manager/set-battery-treshold.sh delete mode 100755 Bin/set-battery-treshold.sh diff --git a/Bin/battery-manager/set-battery-treshold.sh b/Bin/battery-manager/set-battery-treshold.sh new file mode 100755 index 00000000..e4e4c8b7 --- /dev/null +++ b/Bin/battery-manager/set-battery-treshold.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +print_error() { + echo -e "$1" >&2 +} + +print_info() { + echo -e "$1" +} + +send_notification() { + local urgency="$1" + local title="$2" + local message="$3" + + if command -v notify-send >/dev/null 2>&1; then + notify-send -u "$urgency" "$title" "$message" + fi +} + +if [ "$#" -ne 1 ]; then + print_error "Battery level not specified" + echo "Usage: $0 " >&2 + exit 1 +fi + +if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -gt 100 ] || [ "$1" -lt 0 ]; then + print_error "Battery level must be a number between 0-100" + echo "Usage: $0 " >&2 + exit 1 +fi + +BATTERY_LEVEL="$1" + +CURRENT_USER="$USER" +if [ -z "$CURRENT_USER" ]; then + CURRENT_USER="$(whoami)" +fi + +BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$CURRENT_USER" + +if [ ! -f "$BATTERY_MANAGER_PATH" ]; then + print_error "Battery manager components missing for user $CURRENT_USER!" + send_notification "critical" "Battery Manager Setup Required" \ + "Battery manager needs to be set up for user $CURRENT_USER. Please authenticate when prompted." + + print_info "Running installer (authentication required)..." + + if pkexec "$SCRIPT_DIR/install-battery-manager.sh"; then + print_info "Installation completed successfully!" + send_notification "normal" "Battery Manager Installed" \ + "Battery manager has been set up successfully for $CURRENT_USER." + else + print_error "Installation failed or was cancelled" + send_notification "critical" "Installation Failed" \ + "Battery manager installation failed or was cancelled." + exit 1 + fi +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 diff --git a/Bin/set-battery-treshold.sh b/Bin/set-battery-treshold.sh deleted file mode 100755 index 66665826..00000000 --- a/Bin/set-battery-treshold.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env -S bash - -# Check if exectly one argument was provided -if [ "$#" -ne 1 ]; then - echo "Error: Battery level not specified" >&2 - echo "Usage: $0 " >&2 - exit 1 -fi - -# Check if argument is a number -if ! [[ "$1" =~ ^[0-9]+$ ]]; then - echo "Error: Battery level must be a number" >&2 - echo "Usage: $0 " >&2 - exit 1 -fi - -echo "$1" | pkexec tee ~/test diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index e7f17dd9..bb46d4f6 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -16,7 +16,7 @@ Singleton { } property int chargingMode: BatteryService.ChargingMode.Balanced - readonly property string batteryTresholdScript: Quickshell.shellDir + '/Bin/set-battery-treshold.sh' + readonly property string batteryTresholdScript: Quickshell.shellDir + '/Bin/battery-manager/set-battery-treshold.sh' // Choose icon based on charge and charging state function getIcon(percent, charging, isReady) { @@ -70,5 +70,12 @@ Singleton { } } } + stdout: StdioCollector { + onStreamFinished: { + if (this.text) { + Logger.log("BatteryService", "ChargeLimitProcess stdout:", this.text) + } + } + } } } From 0f25dfc4b45807652a2a8af9df6009b3982e12c4 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:30:08 +0200 Subject: [PATCH 08/43] BatteryService: refactor setChargingMode into set and apply functions --- Services/BatteryService.qml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index bb46d4f6..ddb49c3c 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -40,21 +40,24 @@ Singleton { } function setChargingMode(newMode) { - switch (newMode) { + if (newMode !== BatteryService.ChargingMode.Full && newMode !== BatteryService.ChargingMode.Balanced && newMode !== BatteryService.ChargingMode.Conservative) { + return + } + BatteryService.chargingMode = newMode + BatteryService.applyChargingMode() + } + + function applyChargingMode() { + switch (BatteryService.chargingMode) { case BatteryService.ChargingMode.Full: - BatteryService.chargingMode = newMode chargeLimitProcess.command = [batteryTresholdScript, "100"] break case BatteryService.ChargingMode.Balanced: - BatteryService.chargingMode = newMode chargeLimitProcess.command = [batteryTresholdScript, "80"] break case BatteryService.ChargingMode.Conservative: - BatteryService.chargingMode = newMode chargeLimitProcess.command = [batteryTresholdScript, "60"] break - default: - return } chargeLimitProcess.running = true } From 044dbf2b85749c2771e1c842cd2f8feb49d1dd33 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:38:00 +0200 Subject: [PATCH 09/43] BatteryService: add -q option to supress notifications --- Bin/battery-manager/set-battery-treshold.sh | 37 +++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/Bin/battery-manager/set-battery-treshold.sh b/Bin/battery-manager/set-battery-treshold.sh index e4e4c8b7..f771fcb1 100755 --- a/Bin/battery-manager/set-battery-treshold.sh +++ b/Bin/battery-manager/set-battery-treshold.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SUPPRESS_NOTIFICATIONS=false print_error() { echo -e "$1" >&2 @@ -15,25 +16,47 @@ send_notification() { local title="$2" local message="$3" - if command -v notify-send >/dev/null 2>&1; then + if [ "$SUPPRESS_NOTIFICATIONS" = false ] && command -v notify-send >/dev/null 2>&1; then notify-send -u "$urgency" "$title" "$message" fi } -if [ "$#" -ne 1 ]; then +while [[ $# -gt 0 ]]; do + case "$1" in + -q|--quiet) + SUPPRESS_NOTIFICATIONS=true + shift + ;; + -*) + print_error "Unknown option: $1" + echo "Usage: $0 [OPTIONS] " >&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 " >&2 + echo "Usage: $0 [OPTIONS] " >&2 + echo "Options:" >&2 + echo " -q, --quiet Suppress notifications" >&2 exit 1 fi -if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -gt 100 ] || [ "$1" -lt 0 ]; then +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 " >&2 + echo "Usage: $0 [OPTIONS] " >&2 + echo "Options:" >&2 + echo " -q, --quiet Suppress notifications" >&2 exit 1 fi -BATTERY_LEVEL="$1" - CURRENT_USER="$USER" if [ -z "$CURRENT_USER" ]; then CURRENT_USER="$(whoami)" From 33a5cc07d7debf54fc5026bd5fe4d1fd49554069 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:41:21 +0200 Subject: [PATCH 10/43] BatterySerivice: add init function which quietly updates battery treshold --- Services/BatteryService.qml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index ddb49c3c..c3ca2343 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -47,21 +47,34 @@ Singleton { BatteryService.applyChargingMode() } - function applyChargingMode() { + function applyChargingMode(quiet = false) { + let command = [batteryTresholdScript] + + if (quiet) { + command.push("-q") + } + switch (BatteryService.chargingMode) { case BatteryService.ChargingMode.Full: - chargeLimitProcess.command = [batteryTresholdScript, "100"] + command.push("100") break case BatteryService.ChargingMode.Balanced: - chargeLimitProcess.command = [batteryTresholdScript, "80"] + command.push("80") break case BatteryService.ChargingMode.Conservative: - chargeLimitProcess.command = [batteryTresholdScript, "60"] + command.push("60") break } + + chargeLimitProcess.command = command chargeLimitProcess.running = true } + function init() { + BatteryService.applyChargingMode(true) + Logger.log("BatteryService", `Applied charging mode - ${BatteryService.chargingMode}`) + } + Process { id: chargeLimitProcess workingDirectory: Quickshell.shellDir From 4b0633726aec3e68833d29cbf5bc6183d3db7b87 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 21:42:03 +0200 Subject: [PATCH 11/43] BatteryService: call init on launch --- shell.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/shell.qml b/shell.qml index c52ae02f..ec530610 100644 --- a/shell.qml +++ b/shell.qml @@ -91,6 +91,7 @@ ShellRoot { FontService.init() HooksService.init() BluetoothService.init() + BatteryService.init() } Background {} From ddc5cb0d01f0863d5ffbff656de6d92266e4b45b Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 9 Oct 2025 22:39:49 +0200 Subject: [PATCH 12/43] remove uselses comments --- Bin/battery-manager/install-battery-manager.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 0803ddea..2aa8ee77 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -66,19 +66,16 @@ fi print_info "Script installed at $BATTERY_MANAGER_PATH" -# 3. Create log file 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" -# 4. Create polkit policy print_info "Creating polkit policy..." POLICY_FILE="/usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" if [ -f "$SCRIPT_DIR/battery-manager.policy" ]; then - # Update the policy file with the correct installer path and user sed -e "s|/home/damian/Projects/noctalia/battery-charging-treshold/Bin/install-battery-manager.sh|$SCRIPT_DIR/install-battery-manager.sh|g" \ -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ "$SCRIPT_DIR/battery-manager.policy" > "$POLICY_FILE" @@ -90,13 +87,11 @@ fi print_info "Polkit policy created at $POLICY_FILE" -# 5. Create polkit rule print_info "Creating polkit rule..." RULES_FILE="/etc/polkit-1/rules.d/50-battery-manager-$ACTUAL_USER.rules" if [ -f "$SCRIPT_DIR/battery-manager.rules" ]; then - # Replace the placeholder with the actual user sed "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ "$SCRIPT_DIR/battery-manager.rules" > "$RULES_FILE" print_info "Polkit rule copied from $SCRIPT_DIR/battery-manager.rules" @@ -107,7 +102,6 @@ fi print_info "Polkit rule created for user: $ACTUAL_USER at $RULES_FILE" -# 6. Restart polkit print_info "Restarting polkit..." if systemctl restart polkit 2>/dev/null; then print_info "Polkit restarted" From bab51f039afb4ba143077f7f5fd9af0075adc2e3 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 14:21:52 +0200 Subject: [PATCH 13/43] Battery: remove unneeded home variable --- Bin/battery-manager/install-battery-manager.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 2aa8ee77..67551b30 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -22,10 +22,8 @@ echo if [ -n "$PKEXEC_UID" ]; then ACTUAL_USER=$(getent passwd "$PKEXEC_UID" | cut -d: -f1) - ACTUAL_HOME=$(getent passwd "$PKEXEC_UID" | cut -d: -f6) else ACTUAL_USER="$SUDO_USER" - ACTUAL_HOME="$HOME" fi if [ -z "$ACTUAL_USER" ]; then From 93b2746388bf0b7e19c686098cd244f00f747252 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 14:23:22 +0200 Subject: [PATCH 14/43] Battery: hardcode compatible paths into manager script, exit early if none exist --- Bin/battery-manager/battery-manager.sh | 11 +---- .../install-battery-manager.sh | 48 +++++++++++++------ 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Bin/battery-manager/battery-manager.sh b/Bin/battery-manager/battery-manager.sh index 67766a44..41e551b0 100644 --- a/Bin/battery-manager/battery-manager.sh +++ b/Bin/battery-manager/battery-manager.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash -CONFIG_FILE="/etc/battery-manager/paths.conf" LOG_FILE="/var/log/battery-manager.log" log_message() { @@ -21,16 +20,10 @@ if ! [[ "$BATTERY_LEVEL" =~ ^[0-9]+$ ]] || [ "$BATTERY_LEVEL" -gt 100 ] || [ "$B exit 1 fi -if [ ! -f "$CONFIG_FILE" ]; then - echo "Error: Config file not found: $CONFIG_FILE" >&2 - log_message "ERROR: Config file not found" - exit 1 -fi - SUCCESS_COUNT=0 FAIL_COUNT=0 -while IFS= read -r path; do +for path in "${BATTERY_PATHS[@]}"; do [[ -z "$path" || "$path" =~ ^# ]] && continue if [ -f "$path" ] && [ -w "$path" ]; then @@ -47,7 +40,7 @@ while IFS= read -r path; do echo "Skipped (not found/writable): $path" log_message "INFO: Skipped $path (not found or not writable)" fi -done < "$CONFIG_FILE" +done log_message "SUMMARY: Updated $SUCCESS_COUNT file(s), failed $FAIL_COUNT, battery level: $BATTERY_LEVEL" diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 67551b30..734e3e41 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -34,29 +34,51 @@ fi print_info "Installing for user: $ACTUAL_USER" echo -print_info "Creating configuration directory..." -mkdir -p /etc/battery-manager - if [ -f "$SCRIPT_DIR/battery-paths.conf" ]; then - cp "$SCRIPT_DIR/battery-paths.conf" /etc/battery-manager/paths.conf - print_info "Paths configuration copied from $SCRIPT_DIR/battery-paths.conf" + print_info "Paths configuration loaded from $SCRIPT_DIR/battery-paths.conf" else print_error "battery-paths.conf not found in $SCRIPT_DIR" exit 1 fi -chmod 755 /etc/battery-manager -chmod 644 /etc/battery-manager/paths.conf -print_info "Configuration created at /etc/battery-manager/paths.conf" +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 1 +fi + +print_info "Found ${#EXISTING_PATHS[@]} compatible battery control file(s)" print_info "Installing battery manager script..." - BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$ACTUAL_USER" if [ -f "$SCRIPT_DIR/battery-manager.sh" ]; then - cp "$SCRIPT_DIR/battery-manager.sh" "$BATTERY_MANAGER_PATH" + SHEBANG=$(head -n 1 "$SCRIPT_DIR/battery-manager.sh") + + echo "$SHEBANG" > "$BATTERY_MANAGER_PATH" + echo "" >> "$BATTERY_MANAGER_PATH" + + echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_PATH" + for path in "${EXISTING_PATHS[@]}"; do + echo " \"$path\"" >> "$BATTERY_MANAGER_PATH" + done + echo ")" >> "$BATTERY_MANAGER_PATH" + + echo "" >> "$BATTERY_MANAGER_PATH" + + tail -n +2 "$SCRIPT_DIR/battery-manager.sh" >> "$BATTERY_MANAGER_PATH" + chmod +x "$BATTERY_MANAGER_PATH" - print_info "Battery manager script copied from $SCRIPT_DIR/battery-manager.sh" + print_info "Battery manager script created from $SCRIPT_DIR/battery-manager.sh with compatible paths" else print_error "battery-manager.sh not found in $SCRIPT_DIR" exit 1 @@ -74,8 +96,7 @@ print_info "Creating polkit policy..." POLICY_FILE="/usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" if [ -f "$SCRIPT_DIR/battery-manager.policy" ]; then - sed -e "s|/home/damian/Projects/noctalia/battery-charging-treshold/Bin/install-battery-manager.sh|$SCRIPT_DIR/install-battery-manager.sh|g" \ - -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ + sed -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ "$SCRIPT_DIR/battery-manager.policy" > "$POLICY_FILE" print_info "Polkit policy copied from $SCRIPT_DIR/battery-manager.policy" else @@ -110,7 +131,6 @@ fi echo print_info "Installation complete!" echo -print_info "Configuration file: /etc/battery-manager/paths.conf" print_info "Log file: /var/log/battery-manager.log" print_info "User-specific script: /usr/bin/battery-manager-$ACTUAL_USER" print_info "User-specific policy: /usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" From ce0918545fc5265c4953c8b2017eb5be2844dad9 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 16:51:19 +0200 Subject: [PATCH 15/43] Battery: send different notifications depending on installation error --- Bin/battery-manager/set-battery-treshold.sh | 42 ++++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/Bin/battery-manager/set-battery-treshold.sh b/Bin/battery-manager/set-battery-treshold.sh index f771fcb1..bc26a8bb 100755 --- a/Bin/battery-manager/set-battery-treshold.sh +++ b/Bin/battery-manager/set-battery-treshold.sh @@ -64,6 +64,10 @@ fi BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$CURRENT_USER" +SUCCESS=0 +MISSING_FILES=2 +UNSUPPORTED=3 + if [ ! -f "$BATTERY_MANAGER_PATH" ]; then print_error "Battery manager components missing for user $CURRENT_USER!" send_notification "critical" "Battery Manager Setup Required" \ @@ -71,16 +75,34 @@ if [ ! -f "$BATTERY_MANAGER_PATH" ]; then print_info "Running installer (authentication required)..." - if pkexec "$SCRIPT_DIR/install-battery-manager.sh"; then - print_info "Installation completed successfully!" - send_notification "normal" "Battery Manager Installed" \ - "Battery manager has been set up successfully for $CURRENT_USER." - else - print_error "Installation failed or was cancelled" - send_notification "critical" "Installation Failed" \ - "Battery manager installation failed or was cancelled." - exit 1 - fi + pkexec "$SCRIPT_DIR/install-battery-manager.sh" + INSTALL_RESULT=$? + + case $INSTALL_RESULT in + $SUCCESS) + print_info "Installation completed successfully!" + send_notification "normal" "Battery Manager Installed" \ + "Battery manager has been set up successfully for $CURRENT_USER." + ;; + $MISSING_FILES) + print_error "Installation failed: Required files are missing" + send_notification "critical" "Installation Failed" \ + "Battery manager installation failed: Missing required files" + exit $MISSING_FILES + ;; + $UNSUPPORTED) + print_error "Installation failed: System not supported" + send_notification "critical" "Installation Failed" \ + "Battery manager installation failed: Your system is not supported" + exit $UNSUPPORTED + ;; + *) + print_error "Installation failed or was cancelled (error code: $INSTALL_RESULT)" + send_notification "critical" "Installation Failed" \ + "Battery manager installation failed or was cancelled" + exit $INSTALL_RESULT + ;; + esac fi print_info "Setting battery charging threshold to $BATTERY_LEVEL% for user $CURRENT_USER..." From 11c533fa370f5ba5f1a454b43c89995ee90a9fae Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 16:52:13 +0200 Subject: [PATCH 16/43] Battery: refactor installation script, return different error codes --- .../install-battery-manager.sh | 98 ++++++++++--------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 734e3e41..8dd845d9 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -1,7 +1,11 @@ #!/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() { @@ -14,7 +18,7 @@ print_info() { if [ "$EUID" -ne 0 ]; then print_error "This script must be run with root privileges" - exit 1 + exit $FAILURE fi print_info "Installing Battery Manager..." @@ -28,19 +32,42 @@ fi if [ -z "$ACTUAL_USER" ]; then print_error "Could not determine the actual user" - exit 1 + exit $FAILURE fi print_info "Installing for user: $ACTUAL_USER" echo -if [ -f "$SCRIPT_DIR/battery-paths.conf" ]; then - print_info "Paths configuration loaded from $SCRIPT_DIR/battery-paths.conf" -else - print_error "battery-paths.conf not found in $SCRIPT_DIR" - exit 1 +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/battery-manager.sh" ]; then + MISSING_FILES_LIST+=("battery-manager.sh") +fi + +if [ ! -f "$SCRIPT_DIR/battery-manager.policy" ]; then + MISSING_FILES_LIST+=("battery-manager.policy") +fi + +if [ ! -f "$SCRIPT_DIR/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=() @@ -53,7 +80,7 @@ done if [ ${#EXISTING_PATHS[@]} -eq 0 ]; then print_error "None of the battery control files exist. Please check your hardware compatibility." - exit 1 + exit $UNSUPPORTED fi print_info "Found ${#EXISTING_PATHS[@]} compatible battery control file(s)" @@ -61,29 +88,23 @@ print_info "Found ${#EXISTING_PATHS[@]} compatible battery control file(s)" print_info "Installing battery manager script..." BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$ACTUAL_USER" -if [ -f "$SCRIPT_DIR/battery-manager.sh" ]; then - SHEBANG=$(head -n 1 "$SCRIPT_DIR/battery-manager.sh") +SHEBANG=$(head -n 1 "$SCRIPT_DIR/battery-manager.sh") +echo "$SHEBANG" > "$BATTERY_MANAGER_PATH" +echo "" >> "$BATTERY_MANAGER_PATH" - echo "$SHEBANG" > "$BATTERY_MANAGER_PATH" - echo "" >> "$BATTERY_MANAGER_PATH" +echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_PATH" +for path in "${EXISTING_PATHS[@]}"; do + echo " \"$path\"" >> "$BATTERY_MANAGER_PATH" +done +echo ")" >> "$BATTERY_MANAGER_PATH" - echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_PATH" - for path in "${EXISTING_PATHS[@]}"; do - echo " \"$path\"" >> "$BATTERY_MANAGER_PATH" - done - echo ")" >> "$BATTERY_MANAGER_PATH" +echo "" >> "$BATTERY_MANAGER_PATH" - echo "" >> "$BATTERY_MANAGER_PATH" +tail -n +2 "$SCRIPT_DIR/battery-manager.sh" >> "$BATTERY_MANAGER_PATH" - tail -n +2 "$SCRIPT_DIR/battery-manager.sh" >> "$BATTERY_MANAGER_PATH" - - chmod +x "$BATTERY_MANAGER_PATH" - print_info "Battery manager script created from $SCRIPT_DIR/battery-manager.sh with compatible paths" -else - print_error "battery-manager.sh not found in $SCRIPT_DIR" - exit 1 -fi +chmod +x "$BATTERY_MANAGER_PATH" +print_info "Battery manager script created from $SCRIPT_DIR/battery-manager.sh with compatible paths" print_info "Script installed at $BATTERY_MANAGER_PATH" print_info "Creating log file..." @@ -95,30 +116,19 @@ print_info "Creating polkit policy..." POLICY_FILE="/usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" -if [ -f "$SCRIPT_DIR/battery-manager.policy" ]; then - sed -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ - "$SCRIPT_DIR/battery-manager.policy" > "$POLICY_FILE" - print_info "Polkit policy copied from $SCRIPT_DIR/battery-manager.policy" -else - print_error "battery-manager.policy not found in $SCRIPT_DIR" - exit 1 -fi +sed -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ + "$SCRIPT_DIR/battery-manager.policy" > "$POLICY_FILE" +print_info "Polkit policy copied from $SCRIPT_DIR/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" -if [ -f "$SCRIPT_DIR/battery-manager.rules" ]; then - sed "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ - "$SCRIPT_DIR/battery-manager.rules" > "$RULES_FILE" - print_info "Polkit rule copied from $SCRIPT_DIR/battery-manager.rules" -else - print_error "battery-manager.rules not found in $SCRIPT_DIR" - exit 1 -fi +sed "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \ + "$SCRIPT_DIR/battery-manager.rules" > "$RULES_FILE" +print_info "Polkit rule copied from $SCRIPT_DIR/battery-manager.rules" print_info "Polkit rule created for user: $ACTUAL_USER at $RULES_FILE" print_info "Restarting polkit..." From 5e607a72c20e18cbb6db66828f4b98865d6c9d4f Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 17:55:12 +0200 Subject: [PATCH 17/43] fix spelling error --- Assets/Translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7e842cab..0c6f052c 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1461,7 +1461,7 @@ "full": "Full capacity", "balanced": "Balanced", "conservative": "Conservative", - "footer": "Charging treshold set to {limit}%" + "footer": "Charging threshold set to {limit}%" } } } From dec4dad5a57cf12df26f6d7dc5943fd67ec07a1b Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 18:00:40 +0200 Subject: [PATCH 18/43] BatteryService: add separate process for installing manger, use toast messages instead of notifications --- Bin/battery-manager/set-battery-treshold.sh | 35 +------ Services/BatteryService.qml | 109 ++++++++++++++++---- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/Bin/battery-manager/set-battery-treshold.sh b/Bin/battery-manager/set-battery-treshold.sh index bc26a8bb..defcc7f1 100755 --- a/Bin/battery-manager/set-battery-treshold.sh +++ b/Bin/battery-manager/set-battery-treshold.sh @@ -66,43 +66,10 @@ BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$CURRENT_USER" SUCCESS=0 MISSING_FILES=2 -UNSUPPORTED=3 if [ ! -f "$BATTERY_MANAGER_PATH" ]; then print_error "Battery manager components missing for user $CURRENT_USER!" - send_notification "critical" "Battery Manager Setup Required" \ - "Battery manager needs to be set up for user $CURRENT_USER. Please authenticate when prompted." - - print_info "Running installer (authentication required)..." - - pkexec "$SCRIPT_DIR/install-battery-manager.sh" - INSTALL_RESULT=$? - - case $INSTALL_RESULT in - $SUCCESS) - print_info "Installation completed successfully!" - send_notification "normal" "Battery Manager Installed" \ - "Battery manager has been set up successfully for $CURRENT_USER." - ;; - $MISSING_FILES) - print_error "Installation failed: Required files are missing" - send_notification "critical" "Installation Failed" \ - "Battery manager installation failed: Missing required files" - exit $MISSING_FILES - ;; - $UNSUPPORTED) - print_error "Installation failed: System not supported" - send_notification "critical" "Installation Failed" \ - "Battery manager installation failed: Your system is not supported" - exit $UNSUPPORTED - ;; - *) - print_error "Installation failed or was cancelled (error code: $INSTALL_RESULT)" - send_notification "critical" "Installation Failed" \ - "Battery manager installation failed or was cancelled" - exit $INSTALL_RESULT - ;; - esac + exit $MISSING_FILES fi print_info "Setting battery charging threshold to $BATTERY_LEVEL% for user $CURRENT_USER..." diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index c3ca2343..d8167b9f 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -16,7 +16,11 @@ Singleton { } property int chargingMode: BatteryService.ChargingMode.Balanced - readonly property string batteryTresholdScript: Quickshell.shellDir + '/Bin/battery-manager/set-battery-treshold.sh' + 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' + + // This is false when setter is started in init so that a toast isn't shown on every startup + property bool hideSuccessToast: true // Choose icon based on charge and charging state function getIcon(percent, charging, isReady) { @@ -39,6 +43,17 @@ Singleton { } } + function getThresholdValue() { + switch (BatteryService.chargingMode) { + case BatteryService.ChargingMode.Full: + return "100" + case BatteryService.ChargingMode.Balanced: + return "80" + case BatteryService.ChargingMode.Conservative: + return "60" + } + } + function setChargingMode(newMode) { if (newMode !== BatteryService.ChargingMode.Full && newMode !== BatteryService.ChargingMode.Balanced && newMode !== BatteryService.ChargingMode.Conservative) { return @@ -47,27 +62,23 @@ Singleton { BatteryService.applyChargingMode() } - function applyChargingMode(quiet = false) { - let command = [batteryTresholdScript] + function applyChargingMode(hideToast = false) { + let command = [batterySetterScript] - if (quiet) { - command.push("-q") - } + // Currently the script sends notifications by default but quickshell + // uses toast messages so the flag is passed to supress notifs + command.push("-q") - switch (BatteryService.chargingMode) { - case BatteryService.ChargingMode.Full: - command.push("100") - break - case BatteryService.ChargingMode.Balanced: - command.push("80") - break - case BatteryService.ChargingMode.Conservative: - command.push("60") - break - } + command.push(BatteryService.getThresholdValue()) + BatteryService.hideSuccessToast = hideToast - chargeLimitProcess.command = command - chargeLimitProcess.running = true + setterProcess.command = command + setterProcess.running = true + } + + function runInstaller() { + installerProcess.command = ["pkexec", batteryInstallerScript] + installerProcess.running = true } function init() { @@ -76,20 +87,74 @@ Singleton { } Process { - id: chargeLimitProcess + id: setterProcess workingDirectory: Quickshell.shellDir running: false + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + Logger.log("BatteryService", "Battery threshold set successfully") + if (!BatteryService.hideSuccessToast) { + ToastService.showNotice("Battery Manager", `Battery threshold set to ${BatteryService.getThresholdValue()}%`) + } + } else if (exitCode === 2) { + // Initial setup required - show toast and run installer + ToastService.showWarning("Battery Manager", "Initial setup required") + BatteryService.runInstaller() + } else { + ToastService.showError("Battery Manager", "Failed to set battery threshold") + Logger.error("BatteryService", `Setter process failed with exit code: ${exitCode}`) + } + } stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("BatteryService", "ChargeLimitProcess stderr:", this.text) + Logger.warn("BatteryService", "SetterProcess stderr:", this.text) } } } stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.log("BatteryService", "ChargeLimitProcess stdout:", 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("Battery Manager", "Installed successfully") + // Installation successful, retry setting the battery threshold + BatteryService.applyChargingMode() + } else if (exitCode === 2) { + ToastService.showError("Battery Manager", "Required files are missing") + } else if (exitCode === 3) { + ToastService.showError("Battery Manager", "System is not supported") + } else { + ToastService.showError("Battery Manager", "Installation failed") + } + + if (exitCode !== 0) { + // TODO, reset do a null or smth + BatteryService.chargingMode = BatteryService.ChargingMode.Balanced + } + } + 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) } } } From f15a31462f80a90e59b691952d09c46b83bf5bb5 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 18:49:10 +0200 Subject: [PATCH 19/43] Battery: change panel labels --- Assets/Translations/en.json | 9 ++++----- Modules/Bar/Battery/BatteryPanel.qml | 14 +++++--------- Services/BatteryService.qml | 8 ++++---- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 0c6f052c..7d673f0d 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1457,11 +1457,10 @@ "charging": "Charging.", "discharging": "Discharging.", "panel": { - "title": "Charging mode", - "full": "Full capacity", - "balanced": "Balanced", - "conservative": "Conservative", - "footer": "Charging threshold set to {limit}%" + "title": "Charge threshold", + "full": "Full capacity ({percentage}%)", + "balanced": "Balanced ({percentage}%)", + "lifespan": "Extended lifespan ({percentage}%)" } } } diff --git a/Modules/Bar/Battery/BatteryPanel.qml b/Modules/Bar/Battery/BatteryPanel.qml index 92ab067e..55e08e0c 100644 --- a/Modules/Bar/Battery/BatteryPanel.qml +++ b/Modules/Bar/Battery/BatteryPanel.qml @@ -11,7 +11,7 @@ NPanel { id: root preferredWidth: 300 - preferredHeight: 200 + preferredHeight: 210 panelKeyboardFocus: true property var optionsModel: [] @@ -27,7 +27,7 @@ NPanel { "icon": "battery-3" }, { "id": BatteryService.ChargingMode.Conservative, - "label": "battery.panel.conservative", + "label": "battery.panel.lifespan", "icon": "battery-2" }] root.optionsModel = newOptions @@ -50,12 +50,6 @@ NPanel { Layout.fillWidth: true spacing: Style.marginM * scaling - NIcon { - icon: optionsModel[BatteryService.chargingMode].icon - pointSize: Style.fontSizeXXL * scaling - color: Color.mPrimary - } - NText { text: I18n.tr("battery.panel.title") pointSize: Style.fontSizeL * scaling @@ -88,7 +82,9 @@ NPanel { NRadioButton { ButtonGroup.group: batteryGroup required property var modelData - text: I18n.tr(modelData.label) + text: I18n.tr(modelData.label, { + "percentage": BatteryService.getThresholdValue(modelData.id) + }) checked: BatteryService.chargingMode === modelData.id onClicked: { BatteryService.setChargingMode(modelData.id) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index d8167b9f..739382f4 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -43,8 +43,8 @@ Singleton { } } - function getThresholdValue() { - switch (BatteryService.chargingMode) { + function getThresholdValue(chargingMode) { + switch (chargingMode) { case BatteryService.ChargingMode.Full: return "100" case BatteryService.ChargingMode.Balanced: @@ -69,7 +69,7 @@ Singleton { // uses toast messages so the flag is passed to supress notifs command.push("-q") - command.push(BatteryService.getThresholdValue()) + command.push(BatteryService.getThresholdValue(BatteryService.chargingMode)) BatteryService.hideSuccessToast = hideToast setterProcess.command = command @@ -94,7 +94,7 @@ Singleton { if (exitCode === 0) { Logger.log("BatteryService", "Battery threshold set successfully") if (!BatteryService.hideSuccessToast) { - ToastService.showNotice("Battery Manager", `Battery threshold set to ${BatteryService.getThresholdValue()}%`) + ToastService.showNotice("Battery Manager", `Battery threshold set to ${BatteryService.getThresholdValue(BatteryService.chargingMode)}%`) } } else if (exitCode === 2) { // Initial setup required - show toast and run installer From 1e2a2a1d4b1bb37516b939eb084730be46255722 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 19:04:58 +0200 Subject: [PATCH 20/43] Battery: update enum name from Conservative to Lifespan --- Modules/Bar/Battery/BatteryPanel.qml | 2 +- Services/BatteryService.qml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Bar/Battery/BatteryPanel.qml b/Modules/Bar/Battery/BatteryPanel.qml index 55e08e0c..af13ab6c 100644 --- a/Modules/Bar/Battery/BatteryPanel.qml +++ b/Modules/Bar/Battery/BatteryPanel.qml @@ -26,7 +26,7 @@ NPanel { "label": "battery.panel.balanced", "icon": "battery-3" }, { - "id": BatteryService.ChargingMode.Conservative, + "id": BatteryService.ChargingMode.Lifespan, "label": "battery.panel.lifespan", "icon": "battery-2" }] diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 739382f4..5f8ed475 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -12,7 +12,7 @@ Singleton { enum ChargingMode { Full, Balanced, - Conservative + Lifespan } property int chargingMode: BatteryService.ChargingMode.Balanced @@ -49,13 +49,13 @@ Singleton { return "100" case BatteryService.ChargingMode.Balanced: return "80" - case BatteryService.ChargingMode.Conservative: + case BatteryService.ChargingMode.Lifespan: return "60" } } function setChargingMode(newMode) { - if (newMode !== BatteryService.ChargingMode.Full && newMode !== BatteryService.ChargingMode.Balanced && newMode !== BatteryService.ChargingMode.Conservative) { + if (newMode !== BatteryService.ChargingMode.Full && newMode !== BatteryService.ChargingMode.Balanced && newMode !== BatteryService.ChargingMode.Lifespan) { return } BatteryService.chargingMode = newMode From e5976d017f4457bfde0656db583cedbf8b8d2880 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:02:39 +0200 Subject: [PATCH 21/43] Settings: add chargingMode field --- Assets/settings-default.json | 3 +++ Commons/Settings.qml | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index a80e2bb4..7b03ea66 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -193,5 +193,8 @@ "enabled": false, "wallpaperChange": "", "darkModeChange": "" + }, + "battery": { + "chargingMode": 0 } } \ No newline at end of file diff --git a/Commons/Settings.qml b/Commons/Settings.qml index c0939caf..65749eed 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -349,6 +349,11 @@ Singleton { property string wallpaperChange: "" property string darkModeChange: "" } + + // battery + property JsonObject battery: JsonObject { + property int chargingMode: 0 + } } // ----------------------------------------------------- From bcec1d0ebb5c9ad6034565c9e94b5020ae374ebe Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:03:16 +0200 Subject: [PATCH 22/43] BatteryService: add Disabled to ChargingMode enum --- Services/BatteryService.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 5f8ed475..2360cfd5 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -10,6 +10,7 @@ Singleton { id: root enum ChargingMode { + Disabled = 0, Full, Balanced, Lifespan From d4c364a51bb0a3e6f6cd69068b106413be63dd49 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:04:16 +0200 Subject: [PATCH 23/43] BatteryService: check if charging mode disabled before applying in init --- Services/BatteryService.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 2360cfd5..8fa54593 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -83,8 +83,10 @@ Singleton { } function init() { - BatteryService.applyChargingMode(true) - Logger.log("BatteryService", `Applied charging mode - ${BatteryService.chargingMode}`) + if (BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled) { + BatteryService.applyChargingMode(true) + Logger.log("BatteryService", `Applied charging mode - ${BatteryService.chargingMode}`) + } } Process { From 5ff97e88c37ed84c7b0dc79f1b49fac1a1dc0747 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:08:29 +0200 Subject: [PATCH 24/43] BatteryService: load charginMode from settings, save to settings after applying --- Services/BatteryService.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 8fa54593..c221fe5c 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -16,7 +16,7 @@ Singleton { Lifespan } - property int chargingMode: BatteryService.ChargingMode.Balanced + 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' @@ -98,6 +98,7 @@ Singleton { Logger.log("BatteryService", "Battery threshold set successfully") if (!BatteryService.hideSuccessToast) { ToastService.showNotice("Battery Manager", `Battery threshold set to ${BatteryService.getThresholdValue(BatteryService.chargingMode)}%`) + Settings.data.battery.chargingMode = BatteryService.chargingMode } } else if (exitCode === 2) { // Initial setup required - show toast and run installer From 9b44ad3c5dda7869ba7f1365138edc4b142d6b10 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:09:19 +0200 Subject: [PATCH 25/43] BatteryService: revert chragingMode to Disabled if manager installation failed --- Services/BatteryService.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index c221fe5c..90d19e8f 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -144,8 +144,7 @@ Singleton { } if (exitCode !== 0) { - // TODO, reset do a null or smth - BatteryService.chargingMode = BatteryService.ChargingMode.Balanced + BatteryService.chargingMode = BatteryService.ChargingMode.Disabled } } stderr: StdioCollector { From c1e9f0e0b3938c4294c024297d98a643edb5fa92 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:28:51 +0200 Subject: [PATCH 26/43] BatteryPanel: change percentage placeholder to percent --- Assets/Translations/en.json | 6 +++--- Modules/Bar/Battery/BatteryPanel.qml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7d673f0d..85184456 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1458,9 +1458,9 @@ "discharging": "Discharging.", "panel": { "title": "Charge threshold", - "full": "Full capacity ({percentage}%)", - "balanced": "Balanced ({percentage}%)", - "lifespan": "Extended lifespan ({percentage}%)" + "full": "Full capacity ({percent}%)", + "balanced": "Balanced ({percent}%)", + "lifespan": "Extended lifespan ({percent}%)" } } } diff --git a/Modules/Bar/Battery/BatteryPanel.qml b/Modules/Bar/Battery/BatteryPanel.qml index af13ab6c..8d88afd9 100644 --- a/Modules/Bar/Battery/BatteryPanel.qml +++ b/Modules/Bar/Battery/BatteryPanel.qml @@ -83,7 +83,7 @@ NPanel { ButtonGroup.group: batteryGroup required property var modelData text: I18n.tr(modelData.label, { - "percentage": BatteryService.getThresholdValue(modelData.id) + "percent": BatteryService.getThresholdValue(modelData.id) }) checked: BatteryService.chargingMode === modelData.id onClicked: { From 684f8b3a5392221901f62663075d275fdf49e75b Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:39:44 +0200 Subject: [PATCH 27/43] BatteryService: load toast messages from file --- Assets/Translations/en.json | 10 ++++++++++ Services/BatteryService.qml | 18 +++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 85184456..e79ee920 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1424,6 +1424,16 @@ "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" + }, "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." diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 90d19e8f..aa8699e6 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -97,15 +97,16 @@ Singleton { if (exitCode === 0) { Logger.log("BatteryService", "Battery threshold set successfully") if (!BatteryService.hideSuccessToast) { - ToastService.showNotice("Battery Manager", `Battery threshold set to ${BatteryService.getThresholdValue(BatteryService.chargingMode)}%`) + 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) { - // Initial setup required - show toast and run installer - ToastService.showWarning("Battery Manager", "Initial setup required") + ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup")) BatteryService.runInstaller() } else { - ToastService.showError("Battery Manager", "Failed to set battery threshold") + 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}`) } } @@ -132,15 +133,14 @@ Singleton { running: false onExited: (exitCode, exitStatus) => { if (exitCode === 0) { - ToastService.showNotice("Battery Manager", "Installed successfully") - // Installation successful, retry setting the battery threshold + ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-success")) BatteryService.applyChargingMode() } else if (exitCode === 2) { - ToastService.showError("Battery Manager", "Required files are missing") + ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-missing")) } else if (exitCode === 3) { - ToastService.showError("Battery Manager", "System is not supported") + ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-unsupported")) } else { - ToastService.showError("Battery Manager", "Installation failed") + ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.install-failed")) } if (exitCode !== 0) { From 586f2db53d74a640acf9f6df4bf9241c8bdbe854 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 10 Oct 2025 20:47:56 +0200 Subject: [PATCH 28/43] BatteryService: automatically hide panel on initial setup --- Services/BatteryService.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index aa8699e6..ad0046d4 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -104,6 +104,7 @@ Singleton { } } 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")) From 7fd5e952d7ee8cf3f35a1727ce4bc5d73d5d004f Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 12 Oct 2025 20:35:42 +0200 Subject: [PATCH 29/43] BatteryService: implement cycleModes function --- Services/BatteryService.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index ad0046d4..20e8babc 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -63,6 +63,12 @@ Singleton { BatteryService.applyChargingMode() } + function cycleModes() { + // Cycles charging modes from full to lifespan while skipping disabled + const nextMode = (chargingMode % 3) + 1 + setChargingMode(nextMode) + } + function applyChargingMode(hideToast = false) { let command = [batterySetterScript] From f6b4ec0df339393ec6c16a42ccc7d1c3a9458fdf Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 12 Oct 2025 20:51:05 +0200 Subject: [PATCH 30/43] IPC: implement set and cycle calls for battery manager --- Services/IPCService.qml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Services/IPCService.qml b/Services/IPCService.qml index 64b1225e..074fe8e3 100644 --- a/Services/IPCService.qml +++ b/Services/IPCService.qml @@ -166,4 +166,26 @@ Item { Settings.data.wallpaper.randomEnabled = true } } + + 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 + } + } + } } From 7773124fb2634e30c4c0792b00908413a1e64824 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 12 Oct 2025 20:51:33 +0200 Subject: [PATCH 31/43] BatteryService: add log warn if incorrect mode is set --- Services/BatteryService.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 20e8babc..868f3a35 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -57,6 +57,7 @@ Singleton { 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 From db2552da9ee165c3eedeec2232ea201e0438921e Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 12 Oct 2025 23:53:45 +0200 Subject: [PATCH 32/43] Battery: move template files to templates subdir --- .../install-battery-manager.sh | 44 +++++++++---------- .../{ => templates}/battery-manager.policy | 0 .../{ => templates}/battery-manager.rules | 0 .../{ => templates}/battery-manager.sh | 0 4 files changed, 22 insertions(+), 22 deletions(-) rename Bin/battery-manager/{ => templates}/battery-manager.policy (100%) rename Bin/battery-manager/{ => templates}/battery-manager.rules (100%) rename Bin/battery-manager/{ => templates}/battery-manager.sh (100%) diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 8dd845d9..e65c9acd 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -46,15 +46,15 @@ if [ ! -f "$SCRIPT_DIR/battery-paths.conf" ]; then MISSING_FILES_LIST+=("battery-paths.conf") fi -if [ ! -f "$SCRIPT_DIR/battery-manager.sh" ]; then +if [ ! -f "$SCRIPT_DIR/templates/battery-manager.sh" ]; then MISSING_FILES_LIST+=("battery-manager.sh") fi -if [ ! -f "$SCRIPT_DIR/battery-manager.policy" ]; then +if [ ! -f "$SCRIPT_DIR/templates/battery-manager.policy" ]; then MISSING_FILES_LIST+=("battery-manager.policy") fi -if [ ! -f "$SCRIPT_DIR/battery-manager.rules" ]; then +if [ ! -f "$SCRIPT_DIR/templates/battery-manager.rules" ]; then MISSING_FILES_LIST+=("battery-manager.rules") fi @@ -86,26 +86,26 @@ fi print_info "Found ${#EXISTING_PATHS[@]} compatible battery control file(s)" print_info "Installing battery manager script..." -BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$ACTUAL_USER" +BATTERY_MANAGER_SCRIPT="/usr/bin/battery-manager-$ACTUAL_USER" -SHEBANG=$(head -n 1 "$SCRIPT_DIR/battery-manager.sh") -echo "$SHEBANG" > "$BATTERY_MANAGER_PATH" -echo "" >> "$BATTERY_MANAGER_PATH" +SHEBANG=$(head -n 1 "$SCRIPT_DIR/templates/battery-manager.sh") +echo "$SHEBANG" > "$BATTERY_MANAGER_SCRIPT" +echo "" >> "$BATTERY_MANAGER_SCRIPT" -echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_PATH" +echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_SCRIPT" for path in "${EXISTING_PATHS[@]}"; do - echo " \"$path\"" >> "$BATTERY_MANAGER_PATH" + echo " \"$path\"" >> "$BATTERY_MANAGER_SCRIPT" done -echo ")" >> "$BATTERY_MANAGER_PATH" +echo ")" >> "$BATTERY_MANAGER_SCRIPT" -echo "" >> "$BATTERY_MANAGER_PATH" +echo "" >> "$BATTERY_MANAGER_SCRIPT" -tail -n +2 "$SCRIPT_DIR/battery-manager.sh" >> "$BATTERY_MANAGER_PATH" +tail -n +2 "$SCRIPT_DIR/templates/battery-manager.sh" >> "$BATTERY_MANAGER_SCRIPT" -chmod +x "$BATTERY_MANAGER_PATH" +chmod +x "$BATTERY_MANAGER_SCRIPT" -print_info "Battery manager script created from $SCRIPT_DIR/battery-manager.sh with compatible paths" -print_info "Script installed at $BATTERY_MANAGER_PATH" +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 @@ -117,18 +117,18 @@ 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/battery-manager.policy" > "$POLICY_FILE" + "$SCRIPT_DIR/templates/battery-manager.policy" > "$POLICY_FILE" -print_info "Polkit policy copied from $SCRIPT_DIR/battery-manager.policy" +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/battery-manager.rules" > "$RULES_FILE" + "$SCRIPT_DIR/templates/battery-manager.rules" > "$RULES_FILE" -print_info "Polkit rule copied from $SCRIPT_DIR/battery-manager.rules" +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..." @@ -142,6 +142,6 @@ echo print_info "Installation complete!" echo print_info "Log file: /var/log/battery-manager.log" -print_info "User-specific script: /usr/bin/battery-manager-$ACTUAL_USER" -print_info "User-specific policy: /usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy" -print_info "User-specific rules: /etc/polkit-1/rules.d/50-battery-manager-$ACTUAL_USER.rules" +print_info "User-specific script: $BATTERY_MANAGER_SCRIPT" +print_info "User-specific policy: $POLICY_FILE" +print_info "User-specific rules: $RULES_FILE" diff --git a/Bin/battery-manager/battery-manager.policy b/Bin/battery-manager/templates/battery-manager.policy similarity index 100% rename from Bin/battery-manager/battery-manager.policy rename to Bin/battery-manager/templates/battery-manager.policy diff --git a/Bin/battery-manager/battery-manager.rules b/Bin/battery-manager/templates/battery-manager.rules similarity index 100% rename from Bin/battery-manager/battery-manager.rules rename to Bin/battery-manager/templates/battery-manager.rules diff --git a/Bin/battery-manager/battery-manager.sh b/Bin/battery-manager/templates/battery-manager.sh similarity index 100% rename from Bin/battery-manager/battery-manager.sh rename to Bin/battery-manager/templates/battery-manager.sh From fc61e12ef95be2f4f5e224904b0634867e1ea1d1 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 12 Oct 2025 23:54:29 +0200 Subject: [PATCH 33/43] Battery: add uninstall script template, create uninstall script in installation script --- .../install-battery-manager.sh | 31 +++++++++++++++++++ .../templates/uninstall-template | 30 ++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 Bin/battery-manager/templates/uninstall-template diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index e65c9acd..1ed28413 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -138,6 +138,36 @@ 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-$ACTUAL_USER.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" +RULES_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 @@ -145,3 +175,4 @@ 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" diff --git a/Bin/battery-manager/templates/uninstall-template b/Bin/battery-manager/templates/uninstall-template new file mode 100644 index 00000000..f2cf83eb --- /dev/null +++ b/Bin/battery-manager/templates/uninstall-template @@ -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" From 331f482d9bf023540d19f477493070162203ef3b Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 00:04:31 +0200 Subject: [PATCH 34/43] Battery: remove username from uninstall script name --- Bin/battery-manager/install-battery-manager.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 1ed28413..49a8dc76 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -139,7 +139,7 @@ else fi print_info "Creating uninstall script..." -UNINSTALL_SCRIPT="$SCRIPT_DIR/uninstall-battery-manager-$ACTUAL_USER.sh" +UNINSTALL_SCRIPT="$SCRIPT_DIR/uninstall-battery-manager.sh" if [ -f "$SCRIPT_DIR/templates/uninstall-template" ]; then From 26dd5431af4d35a3e4e15cb47179394a1ee52cca Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 00:23:38 +0200 Subject: [PATCH 35/43] BatteryService: add uninstallation messages --- Assets/Translations/en.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e79ee920..7b4e11ed 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1432,7 +1432,10 @@ "install-success": "Installed successfully", "install-missing": "Required files are missing", "install-unsupported": "System is not supported", - "install-failed": "Installation failed" + "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", From 6468d600623f148467dce63c12f43605ae945b93 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:00:13 +0200 Subject: [PATCH 36/43] BatteryPanel: add message when disabled --- Assets/Translations/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7b4e11ed..36d37ebe 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1473,7 +1473,8 @@ "title": "Charge threshold", "full": "Full capacity ({percent}%)", "balanced": "Balanced ({percent}%)", - "lifespan": "Extended lifespan ({percent}%)" + "lifespan": "Extended lifespan ({percent}%)", + "disabled": "Battery manager disabled" } } } From 4f18ff559cd96b18541196a1dafc3fb3ab18572e Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:00:26 +0200 Subject: [PATCH 37/43] Battery: fix variable name --- Bin/battery-manager/install-battery-manager.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bin/battery-manager/install-battery-manager.sh b/Bin/battery-manager/install-battery-manager.sh index 49a8dc76..36315d15 100755 --- a/Bin/battery-manager/install-battery-manager.sh +++ b/Bin/battery-manager/install-battery-manager.sh @@ -153,7 +153,7 @@ echo "" >> "$UNINSTALL_SCRIPT" cat >> "$UNINSTALL_SCRIPT" << EOF SCRIPT_PATH="$BATTERY_MANAGER_SCRIPT" POLICY_PATH="$POLICY_FILE" -RULES_PATH="$RULES_FILE" +RULE_PATH="$RULES_FILE" LOG_PATH="/var/log/battery-manager.log" EOF From c6cf1038b962f1e9f347b59662d89a6cef796d70 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:01:45 +0200 Subject: [PATCH 38/43] BatteryService: add uninstaller and cleanup processes --- Services/BatteryService.qml | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 868f3a35..af2d9b95 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -19,6 +19,7 @@ Singleton { 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 false when setter is started in init so that a toast isn't shown on every startup property bool hideSuccessToast: true @@ -170,4 +171,65 @@ Singleton { } } } + + 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) + } + } + } + } } From be5ad90885668961544563bf7c985804365a89a8 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:03:18 +0200 Subject: [PATCH 39/43] BatteryService: rename hideSuccessToast to initialSetter, change logic on initial threshold setter --- Services/BatteryService.qml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index af2d9b95..104c3962 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -21,8 +21,8 @@ Singleton { 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 false when setter is started in init so that a toast isn't shown on every startup - property bool hideSuccessToast: true + // 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) { @@ -71,7 +71,7 @@ Singleton { setChargingMode(nextMode) } - function applyChargingMode(hideToast = false) { + function applyChargingMode() { let command = [batterySetterScript] // Currently the script sends notifications by default but quickshell @@ -79,7 +79,6 @@ Singleton { command.push("-q") command.push(BatteryService.getThresholdValue(BatteryService.chargingMode)) - BatteryService.hideSuccessToast = hideToast setterProcess.command = command setterProcess.running = true @@ -91,9 +90,8 @@ Singleton { } function init() { - if (BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled) { - BatteryService.applyChargingMode(true) - Logger.log("BatteryService", `Applied charging mode - ${BatteryService.chargingMode}`) + if (BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled && BatteryService.chargingMode !== BatteryService.ChargingMode.Full) { + BatteryService.applyChargingMode() } } @@ -104,12 +102,14 @@ Singleton { onExited: (exitCode, exitStatus) => { if (exitCode === 0) { Logger.log("BatteryService", "Battery threshold set successfully") - if (!BatteryService.hideSuccessToast) { - 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 + 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) From 9ca832eb5e65ea54f5bf3653a7f472e2f53073dd Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:03:58 +0200 Subject: [PATCH 40/43] BatteryService: implement toggleEnabled function --- Services/BatteryService.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 104c3962..cf64b853 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -56,6 +56,18 @@ Singleton { } } + 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}`) From 465700e036d00c7e73ef62076fed50aff43cb3dc Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:05:09 +0200 Subject: [PATCH 41/43] BatteryPanel: add battery manager toggle --- Modules/Bar/Battery/BatteryPanel.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/Bar/Battery/BatteryPanel.qml b/Modules/Bar/Battery/BatteryPanel.qml index 8d88afd9..d14dcb9e 100644 --- a/Modules/Bar/Battery/BatteryPanel.qml +++ b/Modules/Bar/Battery/BatteryPanel.qml @@ -58,6 +58,13 @@ NPanel { 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") From a0c7519b23999c32e376cd936fe5c42742a82e73 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 01:05:29 +0200 Subject: [PATCH 42/43] BatteryPanel: change content based on battery manager enabled state --- Modules/Bar/Battery/BatteryPanel.qml | 59 ++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/Modules/Bar/Battery/BatteryPanel.qml b/Modules/Bar/Battery/BatteryPanel.qml index d14dcb9e..72b67f4c 100644 --- a/Modules/Bar/Battery/BatteryPanel.qml +++ b/Modules/Bar/Battery/BatteryPanel.qml @@ -10,7 +10,7 @@ import qs.Widgets NPanel { id: root - preferredWidth: 300 + preferredWidth: 350 preferredHeight: 210 panelKeyboardFocus: true @@ -83,20 +83,53 @@ NPanel { id: batteryGroup } - Repeater { - model: optionsModel + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: Color.transparent - NRadioButton { - 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) + 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 } - Layout.fillWidth: true } } } From 2f57449d1dadabd0b4436cc2435e91ea1177d834 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 13 Oct 2025 23:34:26 +0200 Subject: [PATCH 43/43] BatteryManager: add missing translations --- Assets/Translations/de.json | 22 +++++++++++++++++++++- Assets/Translations/es.json | 22 +++++++++++++++++++++- Assets/Translations/fr.json | 22 +++++++++++++++++++++- Assets/Translations/pt.json | 28 +++++++++++++++++++++++++++- Assets/Translations/zh-CN.json | 22 +++++++++++++++++++++- 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 021a823f..a45e6199 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1376,7 +1376,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", @@ -1456,6 +1463,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." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index f33d2e0c..aa648d97 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1424,6 +1424,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." @@ -1455,6 +1468,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" + } } } diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a087896a..bf0b4aca 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1424,6 +1424,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." @@ -1455,6 +1468,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é" + } } } diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index d9b1d538..c1895420 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1424,6 +1424,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." @@ -1455,6 +1468,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" + } } } diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 8bdfbce4..7906e01e 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1424,6 +1424,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": "控制中心小部件已从状态栏中移除。要再次从状态栏访问它,您需要重新添加小部件。您也可以通过右键点击状态栏来打开它。" @@ -1455,6 +1468,13 @@ "charging-rate": "充电速率:{rate} W。", "discharging-rate": "放电速率:{rate} W。", "charging": "正在充电。", - "discharging": "正在放电。" + "discharging": "正在放电。", + "panel": { + "title": "充电阈值", + "full": "完全容量 ({percent}%)", + "balanced": "平衡 ({percent}%)", + "lifespan": "延长寿命 ({percent}%)", + "disabled": "电池管理器已禁用" + } } }