File indexing completed on 2024-04-28 16:55:13

0001 /*
0002  * Copyright 2020 Kai Uwe Broulik <kde@broulik.de>
0003  *
0004  * This program is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU General Public License as
0006  * published by the Free Software Foundation; either version 2 of
0007  * the License or (at your option) version 3 or any later version
0008  * accepted by the membership of KDE e.V. (or its successor approved
0009  * by the membership of KDE e.V.), which shall act as a proxy
0010  * defined in Section 14 of version 3 of the license.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015  * GNU General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU General Public License
0018  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
0019  */
0020 
0021 #include "chargethresholdhelper.h"
0022 
0023 #include <powerdevil_debug.h>
0024 
0025 #include <KAuth/HelperSupport>
0026 
0027 #include <algorithm>
0028 
0029 #include <QDir>
0030 #include <QFile>
0031 
0032 static const QString s_powerSupplySysFsPath = QStringLiteral("/sys/class/power_supply");
0033 
0034 static const QString s_chargeStartThreshold = QStringLiteral("charge_control_start_threshold");
0035 static const QString s_chargeEndThreshold = QStringLiteral("charge_control_end_threshold");
0036 
0037 // kept for thinkpad driver retro compat for kernels < 5.9
0038 static const QString s_oldChargeStartThreshold = QStringLiteral("charge_start_threshold");
0039 static const QString s_oldChargeStopThreshold = QStringLiteral("charge_stop_threshold");
0040 
0041 ChargeThresholdHelper::ChargeThresholdHelper(QObject *parent)
0042     : QObject(parent)
0043 {
0044 }
0045 
0046 static QStringList getBatteries()
0047 {
0048     const QStringList power_supplies = QDir(s_powerSupplySysFsPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0049     QStringList batteries;
0050 
0051     for (const QString &psu : power_supplies) {
0052         QDir psuDir(s_powerSupplySysFsPath + QLatin1Char('/') + psu);
0053         QFile file(psuDir.filePath("type"));
0054         if (!file.open(QIODevice::ReadOnly)) {
0055             continue;
0056         }
0057 
0058         QString psu_type;
0059         QTextStream stream(&file);
0060         stream >> psu_type;
0061 
0062         if (psu_type.trimmed() != QLatin1String("Battery")) {
0063             continue; // Not a battery, skip
0064         }
0065 
0066         if (!psuDir.exists(s_chargeStartThreshold) && !psuDir.exists(s_oldChargeStartThreshold)
0067             && !psuDir.exists(s_chargeEndThreshold) && !psuDir.exists(s_oldChargeStopThreshold)) {
0068             continue; // No charge thresholds, skip
0069         }
0070 
0071         batteries.append(psu);
0072     }
0073 
0074     return batteries;
0075 }
0076 
0077 static QVector<int> getThresholds(const QString &which)
0078 {
0079     QVector<int> thresholds;
0080 
0081     const QStringList batteries = getBatteries();
0082     for (const QString &battery : batteries) {
0083         QFile file(s_powerSupplySysFsPath + QLatin1Char('/') + battery + QLatin1Char('/') + which);
0084         if (!file.open(QIODevice::ReadOnly)) {
0085             continue;
0086         }
0087 
0088         int threshold = -1;
0089         QTextStream stream(&file);
0090         stream >> threshold;
0091 
0092         if (threshold < 0 || threshold > 100) {
0093             qWarning() << file.fileName() << "contains invalid threshold" << threshold;
0094             continue;
0095         }
0096 
0097         thresholds.append(threshold);
0098     }
0099 
0100     return thresholds;
0101 }
0102 
0103 static bool setThresholds(const QString &which, int threshold)
0104 {
0105     const QStringList batteries = getBatteries();
0106     for (const QString &battery : batteries) {
0107         QFile file(s_powerSupplySysFsPath + QLatin1Char('/') + battery + QLatin1Char('/') + which);
0108         // TODO should we check the current value before writing the new one or is it clever
0109         // enough not to shred some chip by writing the same thing again?
0110         if (!file.open(QIODevice::WriteOnly)) {
0111             qWarning() << "Failed to open" << file.fileName() << "for writing";
0112             return false;
0113         }
0114 
0115         if (file.write(QByteArray::number(threshold)) == -1) {
0116             qWarning() << "Failed to write threshold into" << file.fileName();
0117             return false;
0118         }
0119     }
0120 
0121     return true;
0122 }
0123 
0124 ActionReply ChargeThresholdHelper::getthreshold(const QVariantMap &args)
0125 {
0126     Q_UNUSED(args);
0127 
0128     auto startThresholds = getThresholds(s_chargeStartThreshold);
0129     auto stopThresholds = getThresholds(s_chargeEndThreshold);
0130 
0131     if (startThresholds.isEmpty() && stopThresholds.isEmpty()) {
0132         startThresholds = getThresholds(s_oldChargeStartThreshold);
0133         stopThresholds = getThresholds(s_oldChargeStopThreshold);
0134     }
0135 
0136     if (startThresholds.isEmpty() && stopThresholds.isEmpty()) {
0137         auto reply = ActionReply::HelperErrorReply();
0138         reply.setErrorDescription(QStringLiteral("Charge thresholds are not supported by the kernel for this hardware"));
0139         return reply;
0140     }
0141 
0142     if (!startThresholds.isEmpty() && !stopThresholds.isEmpty() && startThresholds.count() != stopThresholds.count()) {
0143         auto reply = ActionReply::HelperErrorReply();
0144         reply.setErrorDescription(QStringLiteral("Charge thresholds are not supported by the kernel for this hardware"));
0145         return reply;
0146     }
0147 
0148     // In the rare case there are multiple batteries with varying charge thresholds, try to use something sensible
0149     const int startThreshold = !startThresholds.empty() ? *std::max_element(startThresholds.begin(), startThresholds.end()) : -1;
0150     const int stopThreshold = !stopThresholds.empty() ? *std::min_element(stopThresholds.begin(), stopThresholds.end()) : -1;
0151 
0152     ActionReply reply;
0153     reply.setData({
0154         {QStringLiteral("chargeStartThreshold"), startThreshold},
0155         {QStringLiteral("chargeStopThreshold"), stopThreshold}
0156     });
0157     return reply;
0158 }
0159 
0160 ActionReply ChargeThresholdHelper::setthreshold(const QVariantMap &args)
0161 {
0162     bool hasStartThreshold;
0163     const int startThreshold = args.value(QStringLiteral("chargeStartThreshold"), -1).toInt(&hasStartThreshold);
0164     hasStartThreshold &= startThreshold != -1;
0165 
0166     bool hasStopThreshold;
0167     const int stopThreshold = args.value(QStringLiteral("chargeStopThreshold"), -1).toInt(&hasStopThreshold);
0168     hasStopThreshold &= stopThreshold != -1;
0169 
0170     if ((hasStartThreshold && (startThreshold < 0|| startThreshold > 100))
0171             || (hasStopThreshold && (stopThreshold < 0 || stopThreshold > 100))
0172             || (hasStartThreshold && hasStopThreshold && startThreshold > stopThreshold)
0173             || (!hasStartThreshold && !hasStopThreshold)) {
0174         auto reply = ActionReply::HelperErrorReply(); // is there an "invalid arguments" error?
0175         reply.setErrorDescription(QStringLiteral("Invalid thresholds provided"));
0176         return reply;
0177     }
0178 
0179     if (hasStartThreshold && !(setThresholds(s_chargeStartThreshold, startThreshold) || setThresholds(s_oldChargeStartThreshold, startThreshold))) {
0180         auto reply = ActionReply::HelperErrorReply();
0181         reply.setErrorDescription(QStringLiteral("Failed to write start charge threshold"));
0182         return reply;
0183     }
0184 
0185     if (hasStopThreshold && !(setThresholds(s_chargeEndThreshold, stopThreshold) || setThresholds(s_oldChargeStopThreshold, stopThreshold))) {
0186         auto reply = ActionReply::HelperErrorReply();
0187         reply.setErrorDescription(QStringLiteral("Failed to write stop charge threshold"));
0188         return reply;
0189     }
0190 
0191     return ActionReply();
0192 }
0193 
0194 KAUTH_HELPER_MAIN("org.kde.powerdevil.chargethresholdhelper", ChargeThresholdHelper)