diff --git a/ideapad-battery-health/99-battery-preservation.rules b/ideapad-battery-health/99-battery-preservation.rules index 80142a02e..f1b533074 100644 --- a/ideapad-battery-health/99-battery-preservation.rules +++ b/ideapad-battery-health/99-battery-preservation.rules @@ -1,7 +1,7 @@ # Battery Preservation - udev rule # Grants write access to conservation_mode for users in the -# 'battery_ctl' group, only for BAT0 -SUBSYSTEM=="power_supply", KERNEL=="BAT0", \ +# 'battery_ctl' group, for any battery (BAT0, BAT1, etc.) +SUBSYSTEM=="power_supply", KERNEL=="BAT*", \ RUN+="/bin/chgrp battery_ctl /sys$devpath/extensions/ideapad_laptop/conservation_mode", \ RUN+="/bin/chmod g+w /sys$devpath/extensions/ideapad_laptop/conservation_mode" diff --git a/ideapad-battery-health/BarWidget.qml b/ideapad-battery-health/BarWidget.qml index 436147ba0..62ba27f13 100644 --- a/ideapad-battery-health/BarWidget.qml +++ b/ideapad-battery-health/BarWidget.qml @@ -19,7 +19,8 @@ NIconButton { readonly property string iconColorKey: cfg.iconColor ?? defaults.iconColor - readonly property var preservationMode: pluginApi?.pluginSettings?.preservationMode + readonly property var service: pluginApi?.mainInstance?.service + readonly property var preservationMode: service?.currentPreservation ?? pluginApi?.pluginSettings?.preservationMode icon: root.preservationMode == 1 ? "battery-vertical-eco" : "battery-vertical-charging" baseSize: Style.getCapsuleHeightForScreen(screen?.name) diff --git a/ideapad-battery-health/BatteryPreservationService.qml b/ideapad-battery-health/BatteryPreservationService.qml index 789da5fe3..1e3e94a69 100644 --- a/ideapad-battery-health/BatteryPreservationService.qml +++ b/ideapad-battery-health/BatteryPreservationService.qml @@ -12,10 +12,11 @@ Item { property bool isWritable: false property int desiredPreservation: 0 - readonly property string preservationFile: "/sys/class/power_supply/BAT0/extensions/ideapad_laptop/conservation_mode" + property string preservationFile: "" + readonly property string batteryDir: preservationFile !== "" ? preservationFile.replace("/extensions/ideapad_laptop/conservation_mode", "") : "" Component.onCompleted: { - batteryChecker.running = true + batteryDetector.running = true } function refresh() { @@ -38,14 +39,14 @@ Item { function setPreservation(value) { const v = value - if (v !== 0 && v !== 1) { - Logger.e("BatteryPreservation", `Invalid value: ${v}. Must be exactly 0 or 1`) - return - } + if (v !== 0 && v !== 1) { + Logger.e("BatteryPreservation", `Invalid value: ${v}. Must be exactly 0 or 1`) + return + } Logger.i("BatteryPreservation", "Set preservation mode to " + v) - + desiredPreservation = v preservationWriter.pendingPreservation = v - preservationWriter.command = ["/bin/bash", "-c", `echo ${v} > ${preservationFile}`] + preservationWriter.command = ["/bin/sh", "-c", `echo ${v} > ${preservationFile}`] preservationWriter.running = true } @@ -55,22 +56,29 @@ Item { } Process { - id: batteryChecker - command: ["test", "-f", root.preservationFile] + id: batteryDetector + command: ["sh", "-c", "for f in /sys/class/power_supply/BAT*/extensions/ideapad_laptop/conservation_mode; do [ -f \"$f\" ] && echo \"$f\" && break; done"] running: false - onExited: function (exitCode) { - if (exitCode === 0) { - root.isAvailable = true - preservationFileView.path = root.preservationFile - writeAccessChecker.running = true + stdout: StdioCollector { + onStreamFinished: { + const detected = text.trim() + if (detected !== "") { + root.preservationFile = detected + root.isAvailable = true + preservationFileView.path = detected + Logger.i("BatteryPreservation", "Detected conservation_mode at: " + detected) + writeAccessChecker.running = true + } else { + Logger.w("BatteryPreservation", "No ideapad conservation_mode file found") + } } } } Process { id: writeAccessChecker - command: ["/bin/bash", "-c", `test -w ${root.preservationFile} && echo 1 || echo 0`] + command: ["sh", "-c", `test -w ${root.preservationFile} && echo 1 || echo 0`] running: false stdout: StdioCollector { @@ -102,7 +110,7 @@ Item { onExited: function(exitCode) { if (exitCode === 0) { - service.refresh() + root.refresh() if (pluginApi && pluginApi.pluginSettings.preservationMode != pendingPreservation) { pluginApi.pluginSettings.preservationMode = pendingPreservation pluginApi.saveSettings() diff --git a/ideapad-battery-health/Main.qml b/ideapad-battery-health/Main.qml index 577994614..2b2043d56 100644 --- a/ideapad-battery-health/Main.qml +++ b/ideapad-battery-health/Main.qml @@ -1,11 +1,12 @@ import QtQuick -import Quickshell.Io +import Quickshell // Start the BatteryPreservationService to restore a previously set preservation // mode when the plugin is loaded. This is needed as FW may reset // the preservation mode when the device is fully powered off. Item { property var pluginApi: null + readonly property alias service: service BatteryPreservationService { id: service diff --git a/ideapad-battery-health/Panel.qml b/ideapad-battery-health/Panel.qml index edd44e254..71300e5e0 100644 --- a/ideapad-battery-health/Panel.qml +++ b/ideapad-battery-health/Panel.qml @@ -1,5 +1,4 @@ import QtQuick -import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io import qs.Commons @@ -15,16 +14,13 @@ Item { readonly property bool allowAttach: true anchors.fill: parent - BatteryPreservationService { - id: service - pluginApi: root.pluginApi - } + readonly property var service: pluginApi?.mainInstance?.service property string batteryModelName: "" FileView { id: modelNameView - path: "/sys/class/power_supply/BAT0/model_name" + path: service?.batteryDir ? `${service.batteryDir}/model_name` : "" printErrors: false onLoaded: { @@ -33,7 +29,7 @@ Item { } function writePreservation(value) { - if (!service.isWritable) + if (!service?.isWritable) return service.setPreservation(value) } @@ -61,9 +57,9 @@ Item { } NText { - visible: !service.isAvailable + visible: !service?.isAvailable || root.batteryModelName !== "" - text: !service.isAvailable ? "Not available on this system" : root.batteryModelName + text: !service?.isAvailable ? "Not available on this system" : root.batteryModelName pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant } @@ -79,7 +75,7 @@ Item { ColumnLayout { Layout.fillWidth: true spacing: Style.marginS - visible: service.isAvailable + visible: service?.isAvailable RowLayout { Layout.fillWidth: true @@ -94,8 +90,8 @@ Item { NToggle { id: preservationToggle Layout.fillWidth: true - enabled: service.isWritable - checked: service.currentPreservation !== 0 + enabled: service?.isWritable ?? false + checked: (service?.currentPreservation ?? 0) !== 0 onToggled: root.writePreservation(checked ? 1 : 0) } diff --git a/ideapad-battery-health/manifest.json b/ideapad-battery-health/manifest.json index 1ab2f62a7..e622d608a 100644 --- a/ideapad-battery-health/manifest.json +++ b/ideapad-battery-health/manifest.json @@ -1,9 +1,9 @@ { "id": "ideapad-battery-health", "name": "Ideapad Battery Health", - "version": "1.0.1", + "version": "1.0.2", "minNoctaliaVersion": "3.6.0", - "author": "Christophe Branchereau", + "author": "Christophe Branchereau, Marc Salat", "license": "MIT", "repository": "https://github.com/noctalia-dev/noctalia-plugins", "description": "Set the battery charging mode for ideapad laptop batteries, to extend battery lifespan", diff --git a/ideapad-battery-health/setup_rules.sh b/ideapad-battery-health/setup_rules.sh index 0434ce7f3..da8cb7a2e 100755 --- a/ideapad-battery-health/setup_rules.sh +++ b/ideapad-battery-health/setup_rules.sh @@ -36,10 +36,18 @@ fi echo "Installing udev rule" -if [ -f "/sys/class/power_supply/BAT0/extensions/ideapad_laptop/conservation_mode" ]; then +CONSERVATION_MODE="" +for f in /sys/class/power_supply/BAT*/extensions/ideapad_laptop/conservation_mode; do + if [ -f "$f" ]; then + CONSERVATION_MODE="$f" + break + fi +done + +if [ -n "$CONSERVATION_MODE" ]; then cp "$RULE_FILE_IDEAPAD" /etc/udev/rules.d/ else - echo "no battery control found (/sys/class/power_supply/BAT0/extensions/ideapad_laptop/conservation_mode), aborting" + echo "no battery control found (no ideapad_laptop conservation_mode in /sys/class/power_supply), aborting" exit 1 fi