File indexing completed on 2024-04-28 05:36:15

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "chargethresholdhelper.h"
0008 
0009 #include <powerdevil_debug.h>
0010 
0011 #include <KAuth/HelperSupport>
0012 
0013 #include <algorithm>
0014 
0015 #include <QDir>
0016 #include <QFile>
0017 
0018 static const QString s_powerSupplySysFsPath = QStringLiteral("/sys/class/power_supply");
0019 
0020 static const QString s_chargeStartThreshold = QStringLiteral("charge_control_start_threshold");
0021 static const QString s_chargeEndThreshold = QStringLiteral("charge_control_end_threshold");
0022 
0023 // kept for thinkpad driver retro compat for kernels < 5.9
0024 static const QString s_oldChargeStartThreshold = QStringLiteral("charge_start_threshold");
0025 static const QString s_oldChargeStopThreshold = QStringLiteral("charge_stop_threshold");
0026 
0027 ChargeThresholdHelper::ChargeThresholdHelper(QObject *parent)
0028     : QObject(parent)
0029 {
0030 }
0031 
0032 static QStringList getBatteries()
0033 {
0034     const QStringList power_supplies = QDir(s_powerSupplySysFsPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0035     QStringList batteries;
0036 
0037     for (const QString &psu : power_supplies) {
0038         QDir psuDir(s_powerSupplySysFsPath + QLatin1Char('/') + psu);
0039         QFile file(psuDir.filePath("type"));
0040         if (!file.open(QIODevice::ReadOnly)) {
0041             continue;
0042         }
0043 
0044         QString psu_type;
0045         QTextStream stream(&file);
0046         stream >> psu_type;
0047 
0048         if (psu_type.trimmed() != QLatin1String("Battery")) {
0049             continue; // Not a battery, skip
0050         }
0051 
0052         if (!psuDir.exists(s_chargeStartThreshold) && !psuDir.exists(s_oldChargeStartThreshold) && !psuDir.exists(s_chargeEndThreshold)
0053             && !psuDir.exists(s_oldChargeStopThreshold)) {
0054             continue; // No charge thresholds, skip
0055         }
0056 
0057         batteries.append(psu);
0058     }
0059 
0060     return batteries;
0061 }
0062 
0063 static QList<int> getThresholds(const QString &which)
0064 {
0065     QList<int> thresholds;
0066 
0067     const QStringList batteries = getBatteries();
0068     for (const QString &battery : batteries) {
0069         QFile file(s_powerSupplySysFsPath + QLatin1Char('/') + battery + QLatin1Char('/') + which);
0070         if (!file.open(QIODevice::ReadOnly)) {
0071             continue;
0072         }
0073 
0074         int threshold = -1;
0075         QTextStream stream(&file);
0076         stream >> threshold;
0077 
0078         if (threshold < 0 || threshold > 100) {
0079             qWarning() << file.fileName() << "contains invalid threshold" << threshold;
0080             continue;
0081         }
0082 
0083         thresholds.append(threshold);
0084     }
0085 
0086     return thresholds;
0087 }
0088 
0089 static bool setThresholds(const QString &which, int threshold)
0090 {
0091     const QStringList batteries = getBatteries();
0092     for (const QString &battery : batteries) {
0093         QFile file(s_powerSupplySysFsPath + QLatin1Char('/') + battery + QLatin1Char('/') + which);
0094         // TODO should we check the current value before writing the new one or is it clever
0095         // enough not to shred some chip by writing the same thing again?
0096         if (!file.open(QIODevice::WriteOnly)) {
0097             qWarning() << "Failed to open" << file.fileName() << "for writing";
0098             return false;
0099         }
0100 
0101         if (file.write(QByteArray::number(threshold)) == -1) {
0102             qWarning() << "Failed to write threshold into" << file.fileName();
0103             return false;
0104         }
0105     }
0106 
0107     return true;
0108 }
0109 
0110 ActionReply ChargeThresholdHelper::getthreshold(const QVariantMap &args)
0111 {
0112     Q_UNUSED(args);
0113 
0114     auto startThresholds = getThresholds(s_chargeStartThreshold);
0115     auto stopThresholds = getThresholds(s_chargeEndThreshold);
0116 
0117     if (startThresholds.isEmpty() && stopThresholds.isEmpty()) {
0118         startThresholds = getThresholds(s_oldChargeStartThreshold);
0119         stopThresholds = getThresholds(s_oldChargeStopThreshold);
0120     }
0121 
0122     if (startThresholds.isEmpty() && stopThresholds.isEmpty()) {
0123         auto reply = ActionReply::HelperErrorReply();
0124         reply.setErrorDescription(QStringLiteral("Charge thresholds are not supported by the kernel for this hardware"));
0125         return reply;
0126     }
0127 
0128     if (!startThresholds.isEmpty() && !stopThresholds.isEmpty() && startThresholds.count() != stopThresholds.count()) {
0129         auto reply = ActionReply::HelperErrorReply();
0130         reply.setErrorDescription(QStringLiteral("Charge thresholds are not supported by the kernel for this hardware"));
0131         return reply;
0132     }
0133 
0134     // In the rare case there are multiple batteries with varying charge thresholds, try to use something sensible
0135     const int startThreshold = !startThresholds.empty() ? *std::max_element(startThresholds.begin(), startThresholds.end()) : -1;
0136     const int stopThreshold = !stopThresholds.empty() ? *std::min_element(stopThresholds.begin(), stopThresholds.end()) : -1;
0137 
0138     ActionReply reply;
0139     reply.setData({
0140         {QStringLiteral("chargeStartThreshold"), startThreshold},
0141         {QStringLiteral("chargeStopThreshold"), stopThreshold},
0142     });
0143     return reply;
0144 }
0145 
0146 ActionReply ChargeThresholdHelper::setthreshold(const QVariantMap &args)
0147 {
0148     bool hasStartThreshold;
0149     const int startThreshold = args.value(QStringLiteral("chargeStartThreshold"), -1).toInt(&hasStartThreshold);
0150     hasStartThreshold &= startThreshold != -1;
0151 
0152     bool hasStopThreshold;
0153     const int stopThreshold = args.value(QStringLiteral("chargeStopThreshold"), -1).toInt(&hasStopThreshold);
0154     hasStopThreshold &= stopThreshold != -1;
0155 
0156     if ((hasStartThreshold && (startThreshold < 0 || startThreshold > 100)) || (hasStopThreshold && (stopThreshold < 0 || stopThreshold > 100))
0157         || (hasStartThreshold && hasStopThreshold && startThreshold > stopThreshold) || (!hasStartThreshold && !hasStopThreshold)) {
0158         auto reply = ActionReply::HelperErrorReply(); // is there an "invalid arguments" error?
0159         reply.setErrorDescription(QStringLiteral("Invalid thresholds provided"));
0160         return reply;
0161     }
0162 
0163     if (hasStartThreshold && !(setThresholds(s_chargeStartThreshold, startThreshold) || setThresholds(s_oldChargeStartThreshold, startThreshold))) {
0164         auto reply = ActionReply::HelperErrorReply();
0165         reply.setErrorDescription(QStringLiteral("Failed to write start charge threshold"));
0166         return reply;
0167     }
0168 
0169     if (hasStopThreshold && !(setThresholds(s_chargeEndThreshold, stopThreshold) || setThresholds(s_oldChargeStopThreshold, stopThreshold))) {
0170         auto reply = ActionReply::HelperErrorReply();
0171         reply.setErrorDescription(QStringLiteral("Failed to write stop charge threshold"));
0172         return reply;
0173     }
0174 
0175     return ActionReply();
0176 }
0177 
0178 KAUTH_HELPER_MAIN("org.kde.powerdevil.chargethresholdhelper", ChargeThresholdHelper)
0179 
0180 #include "moc_chargethresholdhelper.cpp"