File indexing completed on 2024-05-12 05:38:44

0001 /*  This file is part of the KDE project
0002  *    SPDX-FileCopyrightText: 2010-2011 Lukas Tinkl <ltinkl@redhat.com>
0003  *
0004  *    SPDX-License-Identifier: LGPL-2.0-only
0005  *
0006  */
0007 
0008 #include "backlighthelper_linux.h"
0009 
0010 #include <powerdevil_debug.h>
0011 
0012 #include <QDebug>
0013 #include <QDir>
0014 
0015 #include <KLocalizedString>
0016 
0017 #include <algorithm>
0018 #include <climits>
0019 #include <sys/utsname.h>
0020 
0021 #define BACKLIGHT_SYSFS_PATH "/sys/class/backlight/"
0022 #define LED_SYSFS_PATH "/sys/class/leds/"
0023 
0024 BacklightHelper::BacklightHelper(QObject *parent)
0025     : QObject(parent)
0026 {
0027     init();
0028 }
0029 
0030 void BacklightHelper::init()
0031 {
0032     initUsingBacklightType();
0033 
0034     if (m_devices.isEmpty()) {
0035         qCWarning(POWERDEVIL) << "no kernel backlight interface found";
0036         return;
0037     }
0038 
0039     m_anim.setEasingCurve(QEasingCurve::InOutQuad);
0040     connect(&m_anim, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
0041         // When animating to zero, it emits a value change to 0 before starting the animation...
0042         if (m_anim.state() == QAbstractAnimation::Running) {
0043             writeBrightness(value.toInt());
0044         }
0045     });
0046 
0047     m_isSupported = true;
0048 }
0049 
0050 int BacklightHelper::readFromDevice(const QString &device, const QString &property) const
0051 {
0052     int value = -1;
0053 
0054     QFile file(device + "/" + property);
0055     if (!file.open(QIODevice::ReadOnly)) {
0056         qCWarning(POWERDEVIL) << "reading from device " << device << "/" << property << " failed with error code " << file.error() << file.errorString();
0057         return value;
0058     }
0059 
0060     QTextStream stream(&file);
0061     stream >> value;
0062     file.close();
0063 
0064     return value;
0065 }
0066 
0067 bool BacklightHelper::writeToDevice(const QString &device, int brightness) const
0068 {
0069     QFile file(device + QLatin1String("/brightness"));
0070     if (!file.open(QIODevice::WriteOnly)) {
0071         qCWarning(POWERDEVIL) << "writing to device " << device << "/brightness failed with error code " << file.error() << file.errorString();
0072         return false;
0073     }
0074 
0075     const int bytesWritten = file.write(QByteArray::number(brightness));
0076     if (bytesWritten == -1) {
0077         qCWarning(POWERDEVIL) << "writing to device " << device << "/brightness failed with error code " << file.error() << file.errorString();
0078         return false;
0079     }
0080 
0081     return true;
0082 }
0083 
0084 QStringList BacklightHelper::getBacklightTypeDevices() const
0085 {
0086     QDir ledsDir(LED_SYSFS_PATH);
0087     ledsDir.setFilter(QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::NoDotAndDotDot | QDir::Readable);
0088     ledsDir.setNameFilters({QStringLiteral("*lcd*"), QStringLiteral("*wled*")});
0089 
0090     QStringList ledInterfaces = ledsDir.entryList();
0091 
0092     if (!ledInterfaces.isEmpty()) {
0093         QStringList output;
0094         for (const QString &interface : ledInterfaces) {
0095             output.append(LED_SYSFS_PATH + interface);
0096         }
0097         return output;
0098     }
0099 
0100     QDir backlightDir(BACKLIGHT_SYSFS_PATH);
0101     backlightDir.setFilter(QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::NoDotAndDotDot | QDir::Readable);
0102     backlightDir.setSorting(QDir::Name | QDir::Reversed); // Reverse is needed to priorize acpi_video1 over 0
0103 
0104     const QStringList interfaces = backlightDir.entryList();
0105 
0106     QFile file;
0107     QByteArray buffer;
0108     QStringList firmware, platform, rawEnabled, rawAll;
0109 
0110     for (const QString &interface : interfaces) {
0111         file.setFileName(BACKLIGHT_SYSFS_PATH + interface + "/type");
0112         if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0113             continue;
0114         }
0115 
0116         buffer = file.readLine().trimmed();
0117         if (buffer == "firmware") {
0118             firmware.append(BACKLIGHT_SYSFS_PATH + interface);
0119         } else if (buffer == "platform") {
0120             platform.append(BACKLIGHT_SYSFS_PATH + interface);
0121         } else if (buffer == "raw") {
0122             QFile enabled(BACKLIGHT_SYSFS_PATH + interface + "/device/enabled");
0123             rawAll.append(BACKLIGHT_SYSFS_PATH + interface);
0124             if (enabled.open(QIODevice::ReadOnly | QIODevice::Text) && enabled.readLine().trimmed() == "enabled") {
0125                 // this backlight device is connected to a display, so append
0126                 // it to rawEnabled list
0127                 rawEnabled.append(BACKLIGHT_SYSFS_PATH + interface);
0128             }
0129         } else {
0130             qCWarning(POWERDEVIL) << "Interface type not handled" << buffer;
0131         }
0132 
0133         file.close();
0134     }
0135 
0136     if (!firmware.isEmpty())
0137         return firmware;
0138 
0139     if (!platform.isEmpty())
0140         return platform;
0141 
0142     if (!rawEnabled.isEmpty())
0143         return rawEnabled;
0144 
0145     if (!rawAll.isEmpty())
0146         return rawAll;
0147 
0148     return {};
0149 }
0150 
0151 void BacklightHelper::initUsingBacklightType()
0152 {
0153     m_devices.clear();
0154     QStringList devices = getBacklightTypeDevices();
0155 
0156     for (const QString &interface : devices) {
0157         int max_brightness = readFromDevice(interface, "max_brightness");
0158         m_devices.append(qMakePair(interface, max_brightness));
0159     }
0160 
0161     return;
0162 }
0163 
0164 ActionReply BacklightHelper::brightness(const QVariantMap &args)
0165 {
0166     Q_UNUSED(args);
0167     const int brightness = readBrightness();
0168 
0169     if (brightness == -1) {
0170         return ActionReply::HelperErrorReply();
0171     }
0172 
0173     ActionReply reply;
0174     reply.addData(QStringLiteral("brightness"), brightness);
0175     return reply;
0176 }
0177 
0178 int BacklightHelper::readBrightness() const
0179 {
0180     if (!m_isSupported) {
0181         return -1;
0182     }
0183 
0184     return readFromDevice(m_devices.constFirst().first, QLatin1String("brightness"));
0185 }
0186 
0187 ActionReply BacklightHelper::setbrightness(const QVariantMap &args)
0188 {
0189     if (!m_isSupported) {
0190         return ActionReply::HelperErrorReply();
0191     }
0192 
0193     const int brightness = args.value(QStringLiteral("brightness")).toInt();
0194     const int animationDuration = args.value(QStringLiteral("animationDuration")).toInt();
0195 
0196     m_anim.stop();
0197 
0198     if (animationDuration <= 0) {
0199         writeBrightness(brightness);
0200         return ActionReply::SuccessReply();
0201     }
0202 
0203     m_anim.setDuration(animationDuration);
0204     m_anim.setStartValue(readBrightness());
0205     m_anim.setEndValue(brightness);
0206     m_anim.start();
0207 
0208     return ActionReply::SuccessReply();
0209 }
0210 
0211 bool BacklightHelper::writeBrightness(int brightness) const
0212 {
0213     if (!m_devices.isEmpty()) {
0214         const int first_maxbrightness = std::max(1, m_devices.constFirst().second);
0215         for (const auto &device : m_devices) {
0216             // Some monitor brightness values are ridiculously high, and can easily overflow during computation
0217             const qint64 new_brightness_64 = static_cast<qint64>(brightness) * static_cast<qint64>(device.second) / static_cast<qint64>(first_maxbrightness);
0218             // cautiously truncate it back
0219             const int new_brightness = static_cast<int>(std::min(static_cast<qint64>(std::numeric_limits<int>::max()), new_brightness_64));
0220             writeToDevice(device.first, new_brightness);
0221         }
0222     }
0223 
0224     return true;
0225 }
0226 
0227 ActionReply BacklightHelper::syspath(const QVariantMap &args)
0228 {
0229     Q_UNUSED(args);
0230 
0231     ActionReply reply;
0232 
0233     if (!m_isSupported || m_devices.isEmpty()) {
0234         reply = ActionReply::HelperErrorReply();
0235         return reply;
0236     }
0237 
0238     reply.addData(QStringLiteral("syspath"), m_devices.constFirst().first);
0239 
0240     return reply;
0241 }
0242 
0243 ActionReply BacklightHelper::brightnessmax(const QVariantMap &args)
0244 {
0245     Q_UNUSED(args);
0246 
0247     ActionReply reply;
0248 
0249     if (!m_isSupported) {
0250         reply = ActionReply::HelperErrorReply();
0251         return -1;
0252     }
0253 
0254     // maximum brightness
0255     int max_brightness = readFromDevice(m_devices.constFirst().first, QLatin1String("max_brightness"));
0256 
0257     if (max_brightness <= 0) {
0258         reply = ActionReply::HelperErrorReply();
0259         return reply;
0260     }
0261 
0262     reply.addData(QStringLiteral("brightnessmax"), max_brightness);
0263     // qCDebug(POWERDEVIL) << "data contains:" << reply.data()["brightnessmax"];
0264 
0265     return reply;
0266 }
0267 
0268 KAUTH_HELPER_MAIN("org.kde.powerdevil.backlighthelper", BacklightHelper)
0269 
0270 #include "moc_backlighthelper_linux.cpp"