1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
|
/* This file is part of the KDE project
* SPDX-FileCopyrightText: 2012 Alberto Villa <avilla@FreeBSD.org>
* SPDX-FileCopyrightText: 2023 Serenity Cyber Security LLC <license@futurecrew.ru>
* Author: Gleb Popov <arrowd@FreeBSD.org>
*
* SPDX-License-Identifier: LGPL-2.0-only
*
*/
#include "backlighthelper_freebsd.h"
#include <powerdevil_debug.h>
#include <QDebug>
#include <QDir>
#include <QProcess>
#include <KLocalizedString>
#include <algorithm>
#include <climits>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/utsname.h>
#define BACKLIGHT_PATH "/dev/backlight/"
// man backlight states that the brightness value is always between 0 and 100
#define BACKLIGHT_MAX_BRIGHTNESS 100
#define HAS_SYSCTL(n) (sysctlbyname(n, nullptr, nullptr, nullptr, 0) == 0)
static QByteArray runFreeBSDBacklight(const QStringList &args)
{
QProcess backlight;
backlight.setProgram(QStringLiteral("backlight"));
backlight.setArguments(args);
backlight.start(QIODevice::ReadOnly);
backlight.waitForFinished();
return backlight.readAllStandardOutput();
}
BacklightHelper::BacklightHelper(QObject *parent)
: QObject(parent)
{
initUsingFreeBSDBacklight();
if (m_devices.isEmpty()) {
initUsingSysctl();
if (m_sysctlDevice.isEmpty() || m_sysctlBrightnessLevels.isEmpty()) {
qCWarning(POWERDEVIL) << "no kernel backlight interface found";
return;
}
}
m_anim.setEasingCurve(QEasingCurve::InOutQuad);
connect(&m_anim, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
// When animating to zero, it emits a value change to 0 before starting the animation...
if (m_anim.state() == QAbstractAnimation::Running) {
writeBrightness(value.toInt());
}
});
m_isSupported = true;
}
int BacklightHelper::readFromDevice(const QString &device) const
{
bool ok;
int value = runFreeBSDBacklight({QStringLiteral("-qf"), device}).toInt(&ok);
return ok ? value : -1;
}
bool BacklightHelper::writeToDevice(const QString &device, int brightness) const
{
return 0 == QProcess::execute(QStringLiteral("backlight"), {QStringLiteral("-f"), device, QString::number(brightness)});
}
void BacklightHelper::initUsingSysctl()
{
/*
* 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.
*/
/*
* The acpi_video interface is not standartized and will not work for some vendors due to
* the sysctl being named hw.acpi.video_<vendor>.*
*/
QStringList types;
types << QStringLiteral("lcd") << QStringLiteral("out") << QStringLiteral("crt") << QStringLiteral("tv") << QStringLiteral("ext");
for (const QString &type : types) {
for (int i = 0; m_sysctlDevice.isEmpty(); i++) {
QString device = QStringLiteral("%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(QStringLiteral("hw.acpi.video.%1.active").arg(device)))) {
break;
}
if (HAS_SYSCTL(qPrintable(QStringLiteral("hw.acpi.video.%1.brightness").arg(device)))
&& HAS_SYSCTL(qPrintable(QStringLiteral("hw.acpi.video.%1.levels").arg(device)))) {
m_sysctlDevice = device;
break;
}
}
}
if (m_sysctlDevice.isEmpty()) {
return;
}
size_t len;
if (sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.levels").arg(m_sysctlDevice)), nullptr, &len, nullptr, 0) != 0 || len == 0) {
return;
}
int *levels = (int *)malloc(len);
if (!levels) {
return;
}
if (sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.levels").arg(m_sysctlDevice)), levels, &len, nullptr, 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().
std::sort(m_sysctlBrightnessLevels.begin(), m_sysctlBrightnessLevels.end());
}
void BacklightHelper::initUsingFreeBSDBacklight()
{
QDir ledsDir(QStringLiteral(BACKLIGHT_PATH));
ledsDir.setFilter(QDir::NoDotAndDotDot | QDir::System);
ledsDir.setNameFilters({QStringLiteral("backlight*")});
QStringList devices = ledsDir.entryList();
for (auto &devName : devices) {
auto devPath = QStringLiteral(BACKLIGHT_PATH) + devName;
auto output = runFreeBSDBacklight({QStringLiteral("-if"), devPath});
// % backlight -if /dev/backlight/backlight0
// Backlight name: intel_backlight
// Backlight hardware type: Panel
if (output.contains(": Panel")) {
m_devices.append(qMakePair(devPath, 100));
}
}
}
ActionReply BacklightHelper::brightness(const QVariantMap &args)
{
Q_UNUSED(args);
const int brightness = readBrightness();
if (brightness == -1) {
return ActionReply::HelperErrorReply();
}
ActionReply reply;
reply.addData(QStringLiteral("brightness"), brightness);
return reply;
}
int BacklightHelper::readBrightness() const
{
if (!m_isSupported) {
return -1;
}
int brightness = -1;
if (!m_sysctlDevice.isEmpty()) {
size_t len = sizeof(int);
if (sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.brightness").arg(m_sysctlDevice)), &brightness, &len, nullptr, 0) != 0) {
return -1;
}
} else {
brightness = readFromDevice(m_devices.constFirst().first);
}
return brightness;
}
ActionReply BacklightHelper::setbrightness(const QVariantMap &args)
{
if (!m_isSupported) {
return ActionReply::HelperErrorReply();
}
const int brightness = args.value(QStringLiteral("brightness")).toInt();
const int animationDuration = args.value(QStringLiteral("animationDuration")).toInt();
m_anim.stop();
if (animationDuration <= 0) {
writeBrightness(brightness);
return ActionReply::SuccessReply();
}
m_anim.setDuration(animationDuration);
m_anim.setStartValue(readBrightness());
m_anim.setEndValue(brightness);
m_anim.start();
return ActionReply::SuccessReply();
}
bool BacklightHelper::writeBrightness(int brightness) const
{
if (!m_sysctlDevice.isEmpty()) {
int actual_level = -1;
int d1 = 101;
// Search for the nearest level.
for (int level : m_sysctlBrightnessLevels) {
int d2 = qAbs(level - 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);
return sysctlbyname(qPrintable(QStringLiteral("hw.acpi.video.%1.brightness").arg(m_sysctlDevice)), nullptr, nullptr, &actual_level, len) == 0;
}
if (!m_devices.isEmpty()) {
const int first_maxbrightness = std::max(1, m_devices.constFirst().second);
for (const auto &device : m_devices) {
// Some monitor brightness values are ridiculously high, and can easily overflow during computation
const qint64 new_brightness_64 = static_cast<qint64>(brightness) * static_cast<qint64>(device.second) / static_cast<qint64>(first_maxbrightness);
// cautiously truncate it back
const int new_brightness = static_cast<int>(std::min(static_cast<qint64>(std::numeric_limits<int>::max()), new_brightness_64));
writeToDevice(device.first, new_brightness);
}
}
return true;
}
ActionReply BacklightHelper::syspath(const QVariantMap &args)
{
Q_UNUSED(args);
return ActionReply::HelperErrorReply();
}
ActionReply BacklightHelper::brightnessmax(const QVariantMap &args)
{
Q_UNUSED(args);
if (!m_isSupported) {
return ActionReply::HelperErrorReply();
}
int max_brightness = !m_sysctlBrightnessLevels.isEmpty() ? m_sysctlBrightnessLevels.last() : BACKLIGHT_MAX_BRIGHTNESS;
if (max_brightness <= 0) {
return ActionReply::HelperErrorReply();
}
ActionReply reply;
reply.addData(QStringLiteral("brightnessmax"), max_brightness);
// qCDebug(POWERDEVIL) << "data contains:" << reply.data()["brightnessmax"];
return reply;
}
KAUTH_HELPER_MAIN("org.kde.powerdevil.backlighthelper", BacklightHelper)
#include "moc_backlighthelper_freebsd.cpp"
|