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)