mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-25 11:22:52 +00:00
373 lines
11 KiB
C++
373 lines
11 KiB
C++
/* This file is part of the KDE project
|
|
* Copyright (C) 2010-2011 Lukas Tinkl <ltinkl@redhat.com>
|
|
*
|
|
* 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"
|
|
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QDebug>
|
|
|
|
#include <sys/utsname.h>
|
|
|
|
#ifdef Q_OS_FREEBSD
|
|
#define USE_SYSCTL
|
|
#endif
|
|
|
|
#ifdef USE_SYSCTL
|
|
#include <sys/types.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#define HAS_SYSCTL(n) (sysctlbyname(n, NULL, NULL, NULL, 0) == 0)
|
|
#endif
|
|
|
|
#define PREFIX "/sys/class/backlight/"
|
|
|
|
BacklightHelper::BacklightHelper(QObject * parent)
|
|
: QObject(parent), m_isSupported(false)
|
|
{
|
|
init();
|
|
}
|
|
|
|
void BacklightHelper::init()
|
|
{
|
|
|
|
if (useWhitelistInit()) {
|
|
initUsingWhitelist();
|
|
} else {
|
|
initUsingBacklightType();
|
|
}
|
|
|
|
if (m_dirname.isEmpty()) {
|
|
initUsingSysctl();
|
|
|
|
if (m_sysctlDevice.isEmpty() || m_sysctlBrightnessLevels.isEmpty()) {
|
|
qWarning() << "no kernel backlight interface found";
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_isSupported = true;
|
|
}
|
|
|
|
void BacklightHelper::initUsingBacklightType()
|
|
{
|
|
QDir dir(PREFIX);
|
|
dir.setFilter(QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::NoDotAndDotDot | QDir::Readable);
|
|
dir.setSorting(QDir::Name | QDir::Reversed);// Reverse is needed to priorize acpi_video1 over 0
|
|
|
|
QStringList interfaces = dir.entryList();
|
|
|
|
if (interfaces.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QFile file;
|
|
QByteArray buffer;
|
|
QStringList firmware, platform, raw;
|
|
|
|
foreach(const QString & interface, interfaces) {
|
|
file.setFileName(PREFIX + interface + "/type");
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
continue;
|
|
}
|
|
|
|
buffer = file.readLine().trimmed();
|
|
if (buffer == "firmware") {
|
|
firmware.append(interface);
|
|
} else if(buffer == "platform") {
|
|
platform.append(interface);
|
|
} else if (buffer == "raw") {
|
|
raw.append(interface);
|
|
} else {
|
|
qWarning() << "Interface type not handled" << buffer;
|
|
}
|
|
|
|
file.close();
|
|
}
|
|
|
|
if (!firmware.isEmpty()) {
|
|
m_dirname = PREFIX + firmware.first();
|
|
return;
|
|
}
|
|
|
|
if (!platform.isEmpty()) {
|
|
m_dirname = PREFIX + platform.first();
|
|
return;
|
|
}
|
|
|
|
if (!raw.isEmpty()) {
|
|
m_dirname = PREFIX + raw.first();
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void BacklightHelper::initUsingWhitelist()
|
|
{
|
|
QStringList interfaces;
|
|
interfaces << "nv_backlight" << "radeon_bl" << "mbp_backlight" << "asus_laptop"
|
|
<< "toshiba" << "eeepc" << "thinkpad_screen" << "acpi_video1" << "acpi_video0"
|
|
<< "intel_backlight" << "apple_backlight" << "fujitsu-laptop" << "samsung"
|
|
<< "nvidia_backlight" << "dell_backlight" << "sony" << "pwm-backlight"
|
|
;
|
|
|
|
QDir dir;
|
|
foreach (const QString & interface, interfaces) {
|
|
dir.setPath(PREFIX + interface);
|
|
//qDebug() << "searching dir:" << dir;
|
|
if (dir.exists()) {
|
|
m_dirname = dir.path();
|
|
//qDebug() << "kernel backlight support found in" << m_dirname;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//If none of our whitelisted interface is available, get the first one (if any)
|
|
if (m_dirname.isEmpty()) {
|
|
dir.setPath(PREFIX);
|
|
dir.setFilter(QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::NoDotAndDotDot | QDir::Readable);
|
|
QStringList dirList = dir.entryList();
|
|
if (!dirList.isEmpty()) {
|
|
m_dirname = dirList.first();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool BacklightHelper::useWhitelistInit()
|
|
{
|
|
struct utsname uts;
|
|
uname(&uts);
|
|
|
|
int major, minor, patch, result;
|
|
result = sscanf(uts.release, "%d.%d", &major, &minor);
|
|
|
|
if (result != 2) {
|
|
return true; // Malformed version
|
|
}
|
|
|
|
if (major == 3) {
|
|
return false; //Kernel 3, we want type based init
|
|
}
|
|
|
|
result = sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch);
|
|
|
|
if (result != 3) {
|
|
return true; // Malformed version
|
|
}
|
|
|
|
if (patch < 37) {
|
|
return true; //Minor than 2.6.37, use whiteList based
|
|
}
|
|
|
|
return false;//Use Type based interafce
|
|
}
|
|
|
|
void BacklightHelper::initUsingSysctl()
|
|
{
|
|
#ifdef USE_SYSCTL
|
|
/*
|
|
* 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.
|
|
*/
|
|
QStringList types;
|
|
types << "lcd" << "out" << "crt" << "tv" << "ext";
|
|
foreach (const QString &type, types) {
|
|
for (int i = 0; m_sysctlDevice.isEmpty(); i++) {
|
|
QString device = QString("%1%2").arg(type, QString::number(i));
|
|
// We don't care about the value, we only want the sysctl to be there.
|
|
if (!HAS_SYSCTL(qPrintable(QString("hw.acpi.video.%1.active").arg(device)))) {
|
|
break;
|
|
}
|
|
if (HAS_SYSCTL(qPrintable(QString("hw.acpi.video.%1.brightness").arg(device))) &&
|
|
HAS_SYSCTL(qPrintable(QString("hw.acpi.video.%1.levels").arg(device)))) {
|
|
m_sysctlDevice = device;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_sysctlDevice.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
size_t len;
|
|
if (sysctlbyname(qPrintable(QString("hw.acpi.video.%1.levels").arg(m_sysctlDevice)), NULL, &len, NULL, 0) != 0 ||
|
|
len == 0) {
|
|
return;
|
|
}
|
|
int *levels = (int *)malloc(len);
|
|
if (!levels) {
|
|
return;
|
|
}
|
|
if (sysctlbyname(qPrintable(QString("hw.acpi.video.%1.levels").arg(m_sysctlDevice)), levels, &len, NULL, 0) != 0) {
|
|
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().
|
|
qSort(m_sysctlBrightnessLevels.begin(), m_sysctlBrightnessLevels.end());
|
|
#endif
|
|
}
|
|
|
|
ActionReply BacklightHelper::brightness(const QVariantMap & args)
|
|
{
|
|
Q_UNUSED(args);
|
|
|
|
ActionReply reply;
|
|
|
|
if (!m_isSupported) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
return reply;
|
|
}
|
|
|
|
// current brightness
|
|
int brightness;
|
|
|
|
#ifdef USE_SYSCTL
|
|
size_t len = sizeof(int);
|
|
if (sysctlbyname(qPrintable(QString("hw.acpi.video.%1.brightness").arg(m_sysctlDevice)), &brightness, &len, NULL, 0) != 0) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
return reply;
|
|
}
|
|
#else
|
|
QFile file(m_dirname + "/brightness");
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
reply.setErrorCode(file.error());
|
|
qWarning() << "reading brightness failed with error code " << file.error() << file.errorString();
|
|
return reply;
|
|
}
|
|
|
|
QTextStream stream(&file);
|
|
stream >> brightness;
|
|
file.close();
|
|
#endif
|
|
|
|
//qDebug() << "brightness:" << brightness;
|
|
reply.addData("brightness", brightness * 100 / maxBrightness());
|
|
//qDebug() << "data contains:" << reply.data()["brightness"];
|
|
|
|
return reply;
|
|
}
|
|
|
|
ActionReply BacklightHelper::setbrightness(const QVariantMap & args)
|
|
{
|
|
ActionReply reply;
|
|
|
|
if (!m_isSupported) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
return reply;
|
|
}
|
|
|
|
int actual_brightness = qRound(args["brightness"].toFloat() * maxBrightness() / 100);
|
|
//qDebug() << "setting brightness:" << actual_brightness;
|
|
|
|
#ifdef USE_SYSCTL
|
|
int actual_level = -1;
|
|
int d1 = 101;
|
|
// Search for the nearest level.
|
|
foreach (int level, m_sysctlBrightnessLevels) {
|
|
int d2 = qAbs(level - actual_brightness);
|
|
/*
|
|
* 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);
|
|
if (sysctlbyname(qPrintable(QString("hw.acpi.video.%1.brightness").arg(m_sysctlDevice)), NULL, NULL, &actual_level, len) != 0) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
return reply;
|
|
}
|
|
#else
|
|
QFile file(m_dirname + "/brightness");
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
reply.setErrorCode(file.error());
|
|
qWarning() << "writing brightness failed with error code " << file.error() << file.errorString();
|
|
return reply;
|
|
}
|
|
|
|
int result = file.write(QByteArray::number(actual_brightness));
|
|
file.close();
|
|
|
|
if (result == -1) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
reply.setErrorCode(file.error());
|
|
qWarning() << "writing brightness failed with error code " << file.error() << file.errorString();
|
|
}
|
|
#endif
|
|
|
|
return reply;
|
|
}
|
|
|
|
ActionReply BacklightHelper::syspath(const QVariantMap& args)
|
|
{
|
|
Q_UNUSED(args);
|
|
|
|
ActionReply reply;
|
|
|
|
if (!m_isSupported || m_dirname.isEmpty()) {
|
|
reply = ActionReply::HelperErrorReply;
|
|
return reply;
|
|
}
|
|
|
|
reply.addData("syspath", m_dirname);
|
|
|
|
return reply;
|
|
}
|
|
|
|
int BacklightHelper::maxBrightness() const
|
|
{
|
|
// maximum brightness
|
|
int max_brightness;
|
|
|
|
#ifdef USE_SYSCTL
|
|
max_brightness = m_sysctlBrightnessLevels.last();
|
|
#else
|
|
QFile file(m_dirname + "/max_brightness");
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qWarning() << "reading max brightness failed with error code " << file.error() << file.errorString();
|
|
return -1; // some non-zero value
|
|
}
|
|
|
|
QTextStream stream(&file);
|
|
stream >> max_brightness;
|
|
file.close();
|
|
#endif
|
|
|
|
//qDebug() << "max brightness:" << max_brightness;
|
|
|
|
return max_brightness ? max_brightness : -1;
|
|
}
|
|
|
|
KDE4_AUTH_HELPER_MAIN("org.kde.powerdevil.backlighthelper", BacklightHelper)
|