Commit a3a61317 authored by Dorian Vogel's avatar Dorian Vogel Committed by David Edmundson

Add brightness control using ddcutil lib

Summary:
Setting up [[ http://www.ddcutil.com/ | ddcutil  ]] for non-root CLI use is required first.
Please refer to [[ https://github.com/d-vogel/QMLddcutil/blob/master/README.md | https://github.com/d-vogel/QMLddcutil/blob/master/README.md]] section.

Limitations:
Not tested for multiple DDC capable monitors setup, should only change brightness of first monitor anyway.

Reviewers: broulik, davidedmundson

Reviewed By: davidedmundson

Subscribers: strobach, davidedmundson, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D5381
parent 7f0f4fc5
......@@ -52,6 +52,17 @@ find_package(UDev REQUIRED)
find_package(XCB REQUIRED COMPONENTS XCB RANDR DPMS)
find_package(DDCUtil)
set_package_properties(DDCUtil
PROPERTIES DESCRIPTION "DDCUtil library support"
TYPE OPTIONAL
PURPOSE "Set monitor settings over DDC/CI channel"
)
if(DDCUTIL_FOUND)
add_definitions(-DWITH_DDCUTIL)
endif()
include_directories (
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/daemon
......
# - Try to find Libddcutil
# Once done this will define
#
# DDCUTIL_FOUND - system has DDCUtil
# DDCUTIL_INCLUDE_DIR - the libddcutil include directory
# DDCUTIL_LIBS - The libddcutil libraries
# Copyright (c) 2017, Dorian Vogel, <dorianvogel@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the University nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
find_package(PkgConfig)
pkg_check_modules(PC_LIBDDCUTIL QUIET ddcutil)
set(LIBDDCUTIL_DEFINITIONS ${PC_LIBDDCUTIL_CFLAGS_OTHER})
find_path(LIBDDCUTIL_INCLUDE_DIR ddcutil_c_api.h
HINTS ${PC_LIBDDCUTIL_INCLUDEDIR} ${PC_LIBDDCUTIL_INCLUDE_DIRS})
find_library(LIBDDCUTIL_LIBRARY NAMES libddcutil.so
HINTS ${PC_LIBDDCUTIL_LIBDIR} ${PC_LIBDDCUTIL_LIBRARY_DIRS} )
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBDDCUTIL_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(ddcutil DEFAULT_MSG
LIBDDCUTIL_LIBRARY LIBDDCUTIL_INCLUDE_DIR)
mark_as_advanced(LIBDDCUTIL_INCLUDE_DIR LIBDDCUTIL_LIBRARY )
set(LIBDDCUTIL_LIBRARIES ${LIBDDCUTIL_LIBRARY} )
set(LIBDDCUTIL_INCLUDE_DIRS ${LIBDDCUTIL_INCLUDE_DIR} )
......@@ -12,6 +12,7 @@ set(powerdevilupowerbackend_SRCS
upower/xrandrxcbhelper.cpp
upower/udevqtclient.cpp
upower/udevqtdevice.cpp
upower/ddcutilbrightness.cpp
)
set_source_files_properties(
......@@ -60,6 +61,7 @@ target_link_libraries(powerdevilupowerbackend
${XCB_XCB_LIBRARY}
${XCB_RANDR_LIBRARY}
powerdevilcore
ddcutil
)
install(TARGETS powerdevilupowerbackend DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/powerdevil)
/* This file is part of the KDE project
* Copyright (C) 2017 Dorian Vogel <dorianvogel@gmail.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 <powerdevil_debug.h>
#include "ddcutilbrightness.h"
DDCutilBrightness::DDCutilBrightness()
{
m_setBrightnessEventFilter.setInterval(100);
m_setBrightnessEventFilter.setSingleShot(true);
connect(&m_setBrightnessEventFilter, &QTimer::timeout, this, &DDCutilBrightness::setBrightnessAfterFilter);
}
void DDCutilBrightness::detect()
{
#ifndef WITH_DDCUTIL
qCInfo(POWERDEVIL) << "[DDCutilBrightness] compiled without DDC/CI support";
return;
#else
DDCA_Status rc;
qCDebug(POWERDEVIL) << "Check for monitors using ddca_get_displays()...";
// Inquire about detected monitors.
DDCA_Display_Info_List * dlist = ddca_get_display_info_list();
qCDebug(POWERDEVIL) << "ddca_get_display_info_list() returned "<< dlist;
qCInfo(POWERDEVIL) << "[DDCutilBrightness] " << dlist->ct << "display(s) were detected";
for (int iDisp=0;iDisp<dlist->ct;iDisp++) {
DDCA_Display_Identifier did;
DDCA_Display_Ref dref;
DDCA_Display_Handle dh = nullptr; // initialize to avoid clang analyzer warning
qCDebug(POWERDEVIL) << "Create a Display Identifier for display"<<iDisp <<
" : "<< dlist->info[iDisp].model_name;
m_displayInfoList.append(dlist->info[iDisp]);
rc = ddca_create_dispno_display_identifier(iDisp+1, &did); // ddcutil uses 1 paded indexing for displays
char * did_repr = ddca_did_repr(did);
qCDebug(POWERDEVIL) << "did="<<did_repr;
qCDebug(POWERDEVIL) << "Create a display reference from the display identifier...";
rc = ddca_get_display_ref(did, &dref);
if (rc != 0) {
qCWarning(POWERDEVIL) << "[DDCutilBrightness]: ddct_get_display_ref() returned "<<
rc<< " ("<<ddca_rc_name(rc) <<
"): "<< ddca_rc_name(rc);
continue;
}
char * dref_repr = ddca_dref_repr(dref);
qCDebug(POWERDEVIL) << "dref= " << dref_repr;
qCDebug(POWERDEVIL) << "Open the display reference, creating a display handle...";
rc = ddca_open_display(dref, &dh);
if (rc != 0) {
qCWarning(POWERDEVIL) << "[DDCutilBrightness]: ddct_open_display"<< rc;
continue;
}
qCDebug(POWERDEVIL) << "adding handle to list";
m_displayHandleList.append(dh);
qCDebug(POWERDEVIL) << "handles nb: "<<m_displayHandleList.count();
char* capabilityString;
DDCA_Capabilities* parsedCapabilities;
ddca_get_capabilities_string(dh, &capabilityString);
ddca_parse_capabilities_string(capabilityString, &parsedCapabilities);
qCInfo(POWERDEVIL) << "[DDCutilBrightness] " << parsedCapabilities->vcp_code_ct << "capabilities parsed";
m_descrToVcp_perDisp.append(QMap<QString, int>());
m_vcpTovcpValueWithDescr_perDisp.append(QMap<int, QMap<int, QString> >() );
//fill the feature description to vcp LUT
DDCA_Version_Feature_Info* featureInfo;
for (int iVcp=0;iVcp<parsedCapabilities->vcp_code_ct;iVcp++) {
int vcpCode=parsedCapabilities->vcp_codes[iVcp].feature_code;
m_vcpTovcpValueWithDescr_perDisp[iDisp].insert(vcpCode, QMap<int, QString>());
m_descrToVcp_perDisp[iDisp].insert(
QString(ddca_get_feature_name(vcpCode)), vcpCode);
ddca_get_feature_info_by_display(m_displayHandleList.at(iDisp), vcpCode, &featureInfo);
if (featureInfo == nullptr) {
continue;
}
qCDebug(POWERDEVIL) << featureInfo->feature_code<<":"<<featureInfo->desc;
if ((featureInfo->feature_flags & DDCA_SIMPLE_NC) != DDCA_SIMPLE_NC) {
continue;
}
for (int iVcpVal=0;featureInfo->sl_values[iVcpVal].value_code!=0;++iVcpVal) {
qCDebug(POWERDEVIL) << "\t"<<featureInfo->sl_values[iVcpVal].value_code
<<":"<< featureInfo->sl_values[iVcpVal].value_name;
bool thisVcpValIsSupported=false;
for (int iSupportedVcpVal=0; iSupportedVcpVal<parsedCapabilities->vcp_codes[iVcp].value_ct; iSupportedVcpVal++) {
if(parsedCapabilities->vcp_codes[iVcp].values[iSupportedVcpVal]
==featureInfo->sl_values[iVcpVal].value_code) {
thisVcpValIsSupported=true;
}
}
if (thisVcpValIsSupported) {
(m_vcpTovcpValueWithDescr_perDisp[iDisp])[vcpCode].insert(
featureInfo->sl_values[iVcpVal].value_code,
featureInfo->sl_values[iVcpVal].value_name);
}
}
}
ddca_free_display_identifier(did);
ddca_free_parsed_capabilities(parsedCapabilities);
}
#endif
}
bool DDCutilBrightness::isSupported() const
{
#ifndef WITH_DDCUTIL
return false;
#else
return !m_displayHandleList.isEmpty();
#endif
}
long DDCutilBrightness::brightness()
{
#ifdef WITH_DDCUTIL
//we check wether the timer is running, this means we received new values but did not send them yet to the monitor
//not checking that results in the brightness slider jump to the previous vqlue when changing.
if(m_setBrightnessEventFilter.isActive()) {
m_lastBrightnessKnown = m_tmpCurrentBrightness;
}
else { //FIXME: gets value for display 1
DDCA_Status rc;
DDCA_Single_Vcp_Value *returnValue;
rc = ddca_get_vcp_value(m_displayHandleList.at(0),
m_descrToVcp_perDisp.at(0).value("Brightness"),
DDCA_NON_TABLE_VCP_VALUE, &returnValue);
qCDebug(POWERDEVIL) << "[DDCutilBrightness::brightness]: ddca_get_vcp_value returned" << rc;
//check rc to prevent crash on wake from idle and the monitor has gone to powersave mode
if (rc == 0) {
m_lastBrightnessKnown = (long)returnValue->val.c.cur_val;
}
}
return m_lastBrightnessKnown;
#else
return 0;
#endif
}
long DDCutilBrightness::brightnessMax()
{
#ifdef WITH_DDCUTIL
DDCA_Status rc;
DDCA_Single_Vcp_Value *returnValue;
rc = ddca_get_vcp_value(m_displayHandleList.at(0),
m_descrToVcp_perDisp.at(0).value("Brightness"),
DDCA_NON_TABLE_VCP_VALUE, &returnValue);
qCDebug(POWERDEVIL) << "[DDCutilBrightness::brightnessMax]: ddca_get_vcp_value returned" << rc;
//check rc to prevent crash on wake from idle and the monitor has gone to powersave mode
if (rc == 0) {
m_lastMaxBrightnessKnown = (long)returnValue->val.c.max_val;
}
return m_lastMaxBrightnessKnown;
#else
return 100.0;
#endif
}
void DDCutilBrightness::setBrightness(long value)
{
m_tmpCurrentBrightness = value;
qCDebug(POWERDEVIL) << "[DDCutilBrightness]: saving brightness value: " << value;
m_setBrightnessEventFilter.start();
}
void DDCutilBrightness::setBrightnessAfterFilter()
{
#ifdef WITH_DDCUTIL
DDCA_Status rc;
for (int iDisp=0;iDisp<m_displayHandleList.count();iDisp++) { //FIXME: we set the same brightness to all monitors, plugin architecture needed
qCDebug(POWERDEVIL) << "[DDCutilBrightness]: setting brightness "<<m_tmpCurrentBrightness;
rc = ddca_set_continuous_vcp_value(m_displayHandleList.at(iDisp),
m_descrToVcp_perDisp.at(iDisp).value("Brightness"),
(int)m_tmpCurrentBrightness);
if (rc != 0) {
qCWarning(POWERDEVIL) << "[DDCutilBrightness::setBrightness] failed, trying to detect()";
detect();
} else {
m_lastBrightnessKnown = m_tmpCurrentBrightness;
}
}
#else
return;
#endif
}
/* This file is part of the KDE project
* Copyright (C) 2017 Dorian Vogel <dorianvogel@gmail.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.
*
*/
#ifndef DDCUTILBRIGHTNESS_H
#define DDCUTILBRIGHTNESS_H
#include <QObject>
#include <QVector>
#include <QTimer>
#ifdef WITH_DDCUTIL
#include <ddcutil_c_api.h>
#endif
class DDCutilBrightness: public QObject
{
Q_OBJECT
public:
DDCutilBrightness();
void detect();
bool isSupported() const;
long brightness();
long brightnessMax();
void setBrightness(long value);
private Q_SLOTS:
void setBrightnessAfterFilter();
private:
#ifdef WITH_DDCUTIL
QVector<DDCA_Display_Handle> m_displayHandleList;
QVector<DDCA_Display_Info> m_displayInfoList;
#endif //ifdef WITH_DDCUTIL
//Per display properties
//destription mapped to vcp values for easy retrieval
QVector<QMap<QString, int> > m_descrToVcp_perDisp;
QVector<QMap<int, QMap<int, QString> > > m_vcpTovcpValueWithDescr_perDisp;
long m_tmpCurrentBrightness;
QTimer m_setBrightnessEventFilter;
int m_lastBrightnessKnown;
int m_lastMaxBrightnessKnown;
};
#endif //DDCUTILBRIGHTNESS_H
......@@ -45,7 +45,7 @@ class UdevHelper;
class XRandRXCBHelper;
class XRandrBrightness;
class QPropertyAnimation;
class DDCutilBrightness;
class Q_DECL_EXPORT PowerDevilUPowerBackend : public PowerDevil::BackendInterface
{
Q_OBJECT
......@@ -101,6 +101,7 @@ private:
QMap<BrightnessControlType, int> m_cachedBrightnessMap;
XRandrBrightness *m_brightnessControl;
XRandRXCBHelper *m_randrHelper;
DDCutilBrightness *m_ddcBrightnessControl;
OrgFreedesktopUPowerInterface *m_upowerInterface;
OrgFreedesktopUPowerKbdBacklightInterface *m_kbdBacklight;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment