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"