From 1b34aa0ce14e7298fcc4dc0dcaa720a37cfe9321 Mon Sep 17 00:00:00 2001 From: Mario Mohar <76663003+Mario-Mohar@users.noreply.github.com> Date: Sat, 12 Sep 2026 18:09:42 +0200 Subject: [PATCH] fix(battery): report when no charge threshold exists instead of faking success (#3385) * fix(battery): report when no charge threshold exists instead of faking success * fix(battery): hide the charge limit controls when there is no battery Follow-up to the review on #3385: the new explanation showed on desktops and VMs with no battery at all, where it reads as a fault report about hardware the machine was never going to have. Both the explanation and the apply button now require BatteryService.batteryAvailable. Hiding the button there is the same point the pull request makes elsewhere: offering a control that cannot work is what produced the false success in the first place. A machine without a battery has no charge threshold to write to, so the limit was never applicable. BatteryService.batteryAvailable is already the gate used twice in this file for the same reason. (cherry picked from commit 6b50696b8acc25e9fc6d12fe88d86060525c97f0) --- quickshell/Modules/Settings/BatteryTab.qml | 56 ++++++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/quickshell/Modules/Settings/BatteryTab.qml b/quickshell/Modules/Settings/BatteryTab.qml index a8e1c3917..74b42f5e6 100644 --- a/quickshell/Modules/Settings/BatteryTab.qml +++ b/quickshell/Modules/Settings/BatteryTab.qml @@ -16,22 +16,43 @@ Item { return Theme.surfaceText; } + // The sysfs names DMS can write, in the order they are tried. Hardware that + // exposes none of them, a Lenovo IdeaPad on ideapad_laptop for instance, + // used to run the apply script to completion without writing anything, and + // sh exiting 0 was indistinguishable from a successful write. + readonly property string thresholdFileList: "charge_control_limit_max charge_stop_threshold charge_control_end_threshold" + readonly property int noThresholdExitCode: 2 + + property bool chargeLimitSupported: true + + Process { + id: thresholdProbe + running: Qt.platform.os === "linux" + command: ["sh", "-c", "for bat in /sys/class/power_supply/BAT*; do for file in " + root.thresholdFileList + "; do [ -f \"$bat/$file\" ] && exit 0; done; done; exit 1"] + onExited: exitCode => root.chargeLimitSupported = exitCode === 0 + } + Process { id: applyLimitProcess command: ["pkexec", "sh", "-c", " +found=0 for bat in /sys/class/power_supply/BAT*; do - if [ -f \"$bat/charge_control_limit_max\" ]; then - echo " + SettingsData.batteryChargeLimit + " > \"$bat/charge_control_limit_max\" - elif [ -f \"$bat/charge_stop_threshold\" ]; then - echo " + SettingsData.batteryChargeLimit + " > \"$bat/charge_stop_threshold\" - elif [ -f \"$bat/charge_control_end_threshold\" ]; then - echo " + SettingsData.batteryChargeLimit + " > \"$bat/charge_control_end_threshold\" - fi + for file in " + root.thresholdFileList + "; do + if [ -f \"$bat/$file\" ]; then + found=1 + echo " + SettingsData.batteryChargeLimit + " > \"$bat/$file\" || exit 1 + break + fi + done done +[ \"$found\" = 1 ] || exit " + root.noThresholdExitCode + " "] running: false onExited: exitCode => { - if (exitCode !== 0) { + if (exitCode === root.noThresholdExitCode) { + root.chargeLimitSupported = false; + ToastService.showError(I18n.tr("Charge limit not supported on this hardware", "battery settings: toast title when sysfs has no charge threshold"), I18n.tr("No writable charge threshold file was found under /sys/class/power_supply.", "battery settings: why the charge limit could not be applied")); + } else if (exitCode !== 0) { ToastService.showError(I18n.tr("Failed to apply charge limit to system"), I18n.tr("Process exited with code %1").arg(exitCode)); } else { ToastService.showInfo(I18n.tr("Charge limit applied successfully"), I18n.tr("Limit set to %1%").arg(SettingsData.batteryChargeLimit)); @@ -214,9 +235,24 @@ done onSliderValueChanged: newValue => SettingsData.set("batteryChargeLimit", newValue) } + StyledText { + width: parent.width + // A machine without a battery has no charge threshold to + // write to either, but saying so there would be noise: the + // limit was never applicable in the first place. + visible: Qt.platform.os === "linux" && BatteryService.batteryAvailable && !root.chargeLimitSupported + text: I18n.tr("No writable charge threshold file was found under /sys/class/power_supply.", "battery settings: why the charge limit could not be applied") + wrapMode: Text.WordWrap + color: Theme.surfaceVariantText + font.pixelSize: Theme.fontSizeMedium + } + Row { - // charge_control_* live in Linux sysfs; no BSD equivalent - visible: Qt.platform.os === "linux" + // charge_control_* live in Linux sysfs; no BSD equivalent. + // Without a battery the apply button cannot do anything, + // and offering it anyway is what produced the false + // success this change is about. + visible: Qt.platform.os === "linux" && BatteryService.batteryAvailable && root.chargeLimitSupported width: parent.width height: applyButton.height layoutDirection: I18n.isRtl ? Qt.LeftToRight : Qt.RightToLeft