backlighthelper.cpp 10.4 KB
Newer Older
1
/*  This file is part of the KDE project
Lukáš Tinkl's avatar
Lukáš Tinkl committed
2
 *    Copyright (C) 2010-2011 Lukas Tinkl <ltinkl@redhat.com>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Library General Public
 *    License version 2 as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Library General Public License for more details.
 *
 *    You should have received a copy of the GNU Library General Public License
 *    along with this library; see the file COPYING.LIB.  If not, write to
 *    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *    Boston, MA 02110-1301, USA.
 *
 */

#include "backlighthelper.h"

22 23
#include <powerdevil_debug.h>

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
24 25 26
#include <QDir>
#include <QDebug>

27
#include <KLocalizedString>
28

Nicolas Fella's avatar
Nicolas Fella committed
29
#include <algorithm>
30
#include <sys/utsname.h>
31

32 33 34 35 36 37 38 39
#ifdef Q_OS_FREEBSD
#define USE_SYSCTL
#endif

#ifdef USE_SYSCTL
#include <sys/types.h>
#include <sys/sysctl.h>

Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
40
#define HAS_SYSCTL(n) (sysctlbyname(n, nullptr, nullptr, nullptr, 0) == 0)
41 42
#endif

43 44
#define BACKLIGHT_SYSFS_PATH "/sys/class/backlight/"
#define LED_SYSFS_PATH "/sys/class/leds/"
45

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
46
BacklightHelper::BacklightHelper(QObject *parent) : QObject(parent)
47 48 49 50 51 52
{
    init();
}

void BacklightHelper::init()
{
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
53
    initUsingBacklightType();
54 55

    if (m_dirname.isEmpty()) {
56 57 58
        initUsingSysctl();

        if (m_sysctlDevice.isEmpty() || m_sysctlBrightnessLevels.isEmpty()) {
59
            qCWarning(POWERDEVIL) << "no kernel backlight interface found";
60 61
            return;
        }
62 63
    }

64 65 66 67 68
    m_anim.setEasingCurve(QEasingCurve::InOutQuad);
    connect(&m_anim, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
        writeBrightness(value.toInt());
    });

69 70 71 72 73
    m_isSupported = true;
}

void BacklightHelper::initUsingBacklightType()
{
74 75 76
    QDir backlightDir(BACKLIGHT_SYSFS_PATH);
    backlightDir.setFilter(QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::NoDotAndDotDot | QDir::Readable);
    backlightDir.setSorting(QDir::Name | QDir::Reversed);// Reverse is needed to priorize acpi_video1 over 0
77

Nicolas Fella's avatar
Nicolas Fella committed
78
    const QStringList interfaces = backlightDir.entryList();
79 80 81

    QFile file;
    QByteArray buffer;
82
    QStringList firmware, platform, raw, leds;
83

Nicolas Fella's avatar
Nicolas Fella committed
84
    for (const QString & interface : interfaces) {
85
        file.setFileName(BACKLIGHT_SYSFS_PATH + interface + "/type");
86 87 88 89 90 91
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            continue;
        }

        buffer = file.readLine().trimmed();
        if (buffer == "firmware") {
92
            firmware.append(interface);
93
        } else if(buffer == "platform") {
94
            platform.append(interface);
95
        } else if (buffer == "raw") {
96
            raw.append(interface);
97
        } else {
98
            qCWarning(POWERDEVIL) << "Interface type not handled" << buffer;
99 100 101 102 103
        }

        file.close();
    }

104
    QDir ledsDir(LED_SYSFS_PATH);
105
    ledsDir.setFilter(QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::NoDotAndDotDot | QDir::Readable);
106 107 108 109 110 111 112 113 114
    ledsDir.setNameFilters({QStringLiteral("*lcd*"), QStringLiteral("*wled*")});

    QStringList ledInterfaces = ledsDir.entryList();

    if (!ledInterfaces.isEmpty()) {
        m_dirname = LED_SYSFS_PATH + ledInterfaces.constFirst();
        return;
    }

115
    if (!firmware.isEmpty()) {
116
        m_dirname = BACKLIGHT_SYSFS_PATH + firmware.constFirst();
117 118 119 120
        return;
    }

    if (!platform.isEmpty()) {
121
        m_dirname = BACKLIGHT_SYSFS_PATH + platform.constFirst();
122 123 124 125
        return;
    }

    if (!raw.isEmpty()) {
126
        m_dirname = BACKLIGHT_SYSFS_PATH + raw.constFirst();
127 128
        return;
    }
129

130 131
}

132 133 134 135
void BacklightHelper::initUsingSysctl()
{
#ifdef USE_SYSCTL
    /*
136 137 138 139
     * lcd0 is, in theory, the correct device, but some vendors have custom ACPI implementations
     * which cannot be interpreted. In that case, devices should be reported as "out", but
     * FreeBSD doesn't care (yet), so they can appear as any other type. Let's search for the first
     * device with brightness management, then.
140 141
     */
    QStringList types;
142
    types << QStringLiteral("lcd") << QStringLiteral("out") << QStringLiteral("crt") << QStringLiteral("tv") << QStringLiteral("ext");
143
    for (const QString &type : types) {
144
        for (int i = 0; m_sysctlDevice.isEmpty(); i++) {
145
            QString device = QStringLiteral("%1%2").arg(type, QString::number(i));
146
            // We don't care about the value, we only want the sysctl to be there.
147
            if (!HAS_SYSCTL(qPrintable(QStringLiteral("hw.acpi.video.%1.active").arg(device)))) {
148 149
                break;
            }
150 151
            if (HAS_SYSCTL(qPrintable(QStringLiteral("hw.acpi.video.%1.brightness").arg(device))) &&
                HAS_SYSCTL(qPrintable(QStringLiteral("hw.acpi.video.%1.levels").arg(device)))) {
152 153 154 155 156 157 158 159 160 161 162
                m_sysctlDevice = device;
                break;
            }
        }
    }

    if (m_sysctlDevice.isEmpty()) {
        return;
    }

    size_t len;
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
163
    if (sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.levels").arg(m_sysctlDevice)), nullptr, &len, nullptr, 0) != 0 ||
164 165 166 167 168 169 170
        len == 0) {
        return;
    }
    int *levels = (int *)malloc(len);
    if (!levels) {
        return;
    }
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
171
    if (sysctlbyname(qPrintable(QString("hw.acpi.video.%1.levels").arg(m_sysctlDevice)), levels, &len, nullptr, 0) != 0) {
172 173 174 175 176 177 178 179 180 181
        free(levels);
        return;
    }
    // acpi_video(4) supports only some predefined brightness levels.
    int nlevels = len / sizeof(int);
    for (int i = 0; i < nlevels; i++) {
        m_sysctlBrightnessLevels << levels[i];
    }
    free(levels);
    // Sorting helps when finding max value and when scanning for the nearest level in setbrightness().
Nicolas Fella's avatar
Nicolas Fella committed
182
    std::sort(m_sysctlBrightnessLevels.begin(), m_sysctlBrightnessLevels.end());
183 184 185
#endif
}

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
186
ActionReply BacklightHelper::brightness(const QVariantMap &args)
187 188
{
    Q_UNUSED(args);
189 190 191 192 193
    const int brightness = readBrightness();

    if (brightness == -1) {
        return ActionReply::HelperErrorReply();
    }
194 195

    ActionReply reply;
196 197 198
    reply.addData("brightness", brightness);
    return reply;
}
199

200 201
int BacklightHelper::readBrightness() const
{
202
    if (!m_isSupported) {
203
        return -1;
204 205
    }

206 207 208 209
    int brightness;

#ifdef USE_SYSCTL
    size_t len = sizeof(int);
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
210
    if (sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.brightness").arg(m_sysctlDevice)), &brightness, &len, nullptr, 0) != 0) {
211
        return -1;
212 213
    }
#else
214 215
    QFile file(m_dirname + "/brightness");
    if (!file.open(QIODevice::ReadOnly)) {
216
        qCWarning(POWERDEVIL) << "reading brightness failed with error code " << file.error() << file.errorString();
217
        return -1;
218 219 220 221 222
    }

    QTextStream stream(&file);
    stream >> brightness;
    file.close();
223
#endif
224

225
    return brightness;
226 227
}

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
228
ActionReply BacklightHelper::setbrightness(const QVariantMap &args)
229 230
{
    if (!m_isSupported) {
231
        return ActionReply::HelperErrorReply();
232 233
    }

234 235 236 237
    const int brightness = args.value(QStringLiteral("brightness")).toInt();
    const int animationDuration = args.value(QStringLiteral("animationDuration")).toInt();

    m_anim.stop();
238 239 240 241 242 243

    if (animationDuration <= 0) {
        writeBrightness(brightness);
        return ActionReply::SuccessReply();
    }

244 245 246 247 248 249 250
    m_anim.setDuration(animationDuration);
    m_anim.setStartValue(readBrightness());
    m_anim.setEndValue(brightness);
    m_anim.start();

    return ActionReply::SuccessReply();
}
251

252 253
bool BacklightHelper::writeBrightness(int brightness) const
{
254 255 256 257
#ifdef USE_SYSCTL
    int actual_level = -1;
    int d1 = 101;
    // Search for the nearest level.
258
    for (int level : m_sysctlBrightnessLevels) {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
259
        int d2 = qAbs(level - brightness);
260 261 262 263 264 265 266 267 268 269 270 271 272 273
        /*
         * The list is sorted, so we break when it starts diverging. There may be repeated values,
         * so we keep going on equal gap (e.g., value = 7.5, levels = 0 0 10 ...: we don't break at
         * the second '0' so we can get to the '10'). This also means that the value will always
         * round off to the bigger level when in the middle (e.g., value = 5, levels = 0 10 ...:
         * value rounds off to 10).
         */
        if (d2 > d1) {
            break;
        }
        actual_level = level;
        d1 = d2;
    }
    size_t len = sizeof(int);
274
    return sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.brightness").arg(m_sysctlDevice)), nullptr, nullptr, &actual_level, len) == 0;
275
#else
276
    QFile file(m_dirname + QLatin1String("/brightness"));
277
    if (!file.open(QIODevice::WriteOnly)) {
278
        qCWarning(POWERDEVIL) << "writing brightness failed with error code " << file.error() << file.errorString();
279
        return false;
280 281
    }

282 283
    const int bytesWritten = file.write(QByteArray::number(brightness));
    if (bytesWritten == -1) {
284
        qCWarning(POWERDEVIL) << "writing brightness failed with error code " << file.error() << file.errorString();
285
        return false;
286
    }
287 288

    return true;
289
#endif
290 291

    return false;
292 293
}

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
294
ActionReply BacklightHelper::syspath(const QVariantMap &args)
295 296 297 298 299 300
{
    Q_UNUSED(args);

    ActionReply reply;

    if (!m_isSupported || m_dirname.isEmpty()) {
301
        reply = ActionReply::HelperErrorReply();
302 303 304 305 306 307 308 309
        return reply;
    }

    reply.addData("syspath", m_dirname);

    return reply;
}

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
310
ActionReply BacklightHelper::brightnessmax(const QVariantMap &args)
311
{
Nikita Skovoroda's avatar
Nikita Skovoroda committed
312 313 314 315 316 317 318 319 320
    Q_UNUSED(args);

    ActionReply reply;

    if (!m_isSupported) {
        reply = ActionReply::HelperErrorReply();
        return -1;
    }

321
    // maximum brightness
322 323 324 325 326
    int max_brightness;

#ifdef USE_SYSCTL
    max_brightness = m_sysctlBrightnessLevels.last();
#else
327
    QFile file(m_dirname + QLatin1String("/max_brightness"));
328
    if (!file.open(QIODevice::ReadOnly)) {
Nikita Skovoroda's avatar
Nikita Skovoroda committed
329 330
        reply = ActionReply::HelperErrorReply();
//         reply.setErrorCode(file.error());
331
        qCWarning(POWERDEVIL) << "reading max brightness failed with error code " << file.error() << file.errorString();
Nikita Skovoroda's avatar
Nikita Skovoroda committed
332
        return reply;
333 334 335 336 337
    }

    QTextStream stream(&file);
    stream >> max_brightness;
    file.close();
338 339
#endif

340
    //qCDebug(POWERDEVIL) << "max brightness:" << max_brightness;
341

Nikita Skovoroda's avatar
Nikita Skovoroda committed
342 343 344 345 346
    if (max_brightness <= 0) {
        reply = ActionReply::HelperErrorReply();
        return reply;
    }

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
347 348
    reply.addData("brightnessmax", max_brightness);
    //qCDebug(POWERDEVIL) << "data contains:" << reply.data()["brightnessmax"];
Nikita Skovoroda's avatar
Nikita Skovoroda committed
349 350

    return reply;
351 352
}

353
KAUTH_HELPER_MAIN("org.kde.powerdevil.backlighthelper", BacklightHelper)