Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ideapad-battery-health/99-battery-preservation.rules
Original file line number Diff line number Diff line change
@@ -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"

3 changes: 2 additions & 1 deletion ideapad-battery-health/BarWidget.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
42 changes: 25 additions & 17 deletions ideapad-battery-health/BatteryPreservationService.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion ideapad-battery-health/Main.qml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
20 changes: 8 additions & 12 deletions ideapad-battery-health/Panel.qml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import qs.Commons
Expand All @@ -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: {
Expand All @@ -33,7 +29,7 @@ Item {
}

function writePreservation(value) {
if (!service.isWritable)
if (!service?.isWritable)
return
service.setPreservation(value)
}
Expand Down Expand Up @@ -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
}
Expand All @@ -79,7 +75,7 @@ Item {
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
visible: service.isAvailable
visible: service?.isAvailable

RowLayout {
Layout.fillWidth: true
Expand All @@ -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)
}

Expand Down
4 changes: 2 additions & 2 deletions ideapad-battery-health/manifest.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 10 additions & 2 deletions ideapad-battery-health/setup_rules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down