Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -52,6 +52,17 @@ 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 Index: cmake/FindDDCUtil.cmake =================================================================== --- /dev/null +++ cmake/FindDDCUtil.cmake @@ -0,0 +1,53 @@ +# - 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, +# +# 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} ) Index: daemon/backends/CMakeLists.txt =================================================================== --- daemon/backends/CMakeLists.txt +++ daemon/backends/CMakeLists.txt @@ -12,6 +12,7 @@ upower/xrandrxcbhelper.cpp upower/udevqtclient.cpp upower/udevqtdevice.cpp + upower/ddcutilbrightness.cpp ) set_source_files_properties( @@ -60,6 +61,7 @@ ${XCB_XCB_LIBRARY} ${XCB_RANDR_LIBRARY} powerdevilcore + ddcutil ) install(TARGETS powerdevilupowerbackend DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/powerdevil) Index: daemon/backends/upower/ddcutilbrightness.h =================================================================== --- /dev/null +++ daemon/backends/upower/ddcutilbrightness.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Dorian Vogel + * + * 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 +#include +#include + +#ifdef WITH_DDCUTIL +#include +#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 m_displayHandleList; + QVector m_displayInfoList; +#endif //ifdef WITH_DDCUTIL + //Per display properties + //destription mapped to vcp values for easy retrieval + QVector > m_descrToVcp_perDisp; + QVector > > m_vcpTovcpValueWithDescr_perDisp; + + + long m_tmpCurrentBrightness; + QTimer m_setBrightnessEventFilter; + int m_lastBrightnessKnown; + int m_lastMaxBrightnessKnown; +}; + +#endif //DDCUTILBRIGHTNESS_H Index: daemon/backends/upower/ddcutilbrightness.cpp =================================================================== --- /dev/null +++ daemon/backends/upower/ddcutilbrightness.cpp @@ -0,0 +1,229 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Dorian Vogel + * + * 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 +#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;iDispct;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"<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="<vcp_code_ct << "capabilities parsed"; + + + m_descrToVcp_perDisp.append(QMap()); + m_vcpTovcpValueWithDescr_perDisp.append(QMap >() ); + //fill the feature description to vcp LUT + + DDCA_Version_Feature_Info* featureInfo; + for (int iVcp=0;iVcpvcp_code_ct;iVcp++) { + + int vcpCode=parsedCapabilities->vcp_codes[iVcp].feature_code; + + m_vcpTovcpValueWithDescr_perDisp[iDisp].insert(vcpCode, QMap()); + + 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<<":"<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"<sl_values[iVcpVal].value_code + <<":"<< featureInfo->sl_values[iVcpVal].value_name; + + bool thisVcpValIsSupported=false; + + for (int iSupportedVcpVal=0; iSupportedVcpValvcp_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_cachedBrightnessMap; XRandrBrightness *m_brightnessControl; XRandRXCBHelper *m_randrHelper; + DDCutilBrightness *m_ddcBrightnessControl; OrgFreedesktopUPowerInterface *m_upowerInterface; OrgFreedesktopUPowerKbdBacklightInterface *m_kbdBacklight; Index: daemon/backends/upower/powerdevilupowerbackend.cpp =================================================================== --- daemon/backends/upower/powerdevilupowerbackend.cpp +++ daemon/backends/upower/powerdevilupowerbackend.cpp @@ -38,6 +38,7 @@ #include "xrandrxcbhelper.h" #include "xrandrbrightness.h" +#include "ddcutilbrightness.h" #include "upowersuspendjob.h" #include "login1suspendjob.h" #include "udevqt.h" @@ -152,63 +153,83 @@ m_upowerInterface = new OrgFreedesktopUPowerInterface(UPOWER_SERVICE, "/org/freedesktop/UPower", QDBusConnection::systemBus(), this); m_brightnessControl = new XRandrBrightness(); if (!m_brightnessControl->isSupported()) { - qCDebug(POWERDEVIL) << "Falling back to helper to get brightness"; - - KAuth::Action brightnessAction("org.kde.powerdevil.backlighthelper.brightness"); - brightnessAction.setHelperId(HELPER_ID); - KAuth::ExecuteJob *brightnessJob = brightnessAction.execute(); - connect(brightnessJob, &KJob::result, this, - [this, brightnessJob] { - if (brightnessJob->error()) { - qCWarning(POWERDEVIL) << "org.kde.powerdevil.backlighthelper.brightness failed"; - qCDebug(POWERDEVIL) << brightnessJob->errorText(); - Q_EMIT brightnessSupportQueried(false); - return; - } - m_cachedBrightnessMap.insert(Screen, brightnessJob->data()["brightness"].toFloat()); - - KAuth::Action brightnessMaxAction("org.kde.powerdevil.backlighthelper.brightnessmax"); - brightnessMaxAction.setHelperId(HELPER_ID); - KAuth::ExecuteJob *brightnessMaxJob = brightnessMaxAction.execute(); - connect(brightnessMaxJob, &KJob::result, this, - [this, brightnessMaxJob] { - if (brightnessMaxJob->error()) { - qCWarning(POWERDEVIL) << "org.kde.powerdevil.backlighthelper.brightnessmax failed"; - qCDebug(POWERDEVIL) << brightnessMaxJob->errorText(); - } else { - m_brightnessMax = brightnessMaxJob->data()["brightnessmax"].toInt(); - } - - KAuth::Action syspathAction("org.kde.powerdevil.backlighthelper.syspath"); - syspathAction.setHelperId(HELPER_ID); - KAuth::ExecuteJob* syspathJob = syspathAction.execute(); - connect(syspathJob, &KJob::result, this, - [this, syspathJob] { - if (syspathJob->error()) { - qCWarning(POWERDEVIL) << "org.kde.powerdevil.backlighthelper.syspath failed"; - qCDebug(POWERDEVIL) << syspathJob->errorText(); - Q_EMIT brightnessSupportQueried(false); - return; - } - m_syspath = syspathJob->data()["syspath"].toString(); - m_syspath = QFileInfo(m_syspath).readLink(); + qCWarning(POWERDEVIL)<<"Xrandr not supported, trying ddc, helper"; + m_ddcBrightnessControl = new DDCutilBrightness(); + m_ddcBrightnessControl->detect(); + if (!m_ddcBrightnessControl->isSupported()) { + qCDebug(POWERDEVIL) << "Falling back to helper to get brightness"; + + KAuth::Action brightnessAction("org.kde.powerdevil.backlighthelper.brightness"); + brightnessAction.setHelperId(HELPER_ID); + KAuth::ExecuteJob *brightnessJob = brightnessAction.execute(); + connect(brightnessJob, &KJob::result, this, + [this, brightnessJob] { + if (brightnessJob->error()) { + qCWarning(POWERDEVIL) << "org.kde.powerdevil.backlighthelper.brightness failed"; + qCDebug(POWERDEVIL) << brightnessJob->errorText(); + Q_EMIT brightnessSupportQueried(false); + return; + } + m_cachedBrightnessMap.insert(Screen, brightnessJob->data()["brightness"].toFloat()); + + KAuth::Action brightnessMaxAction("org.kde.powerdevil.backlighthelper.brightnessmax"); + brightnessMaxAction.setHelperId(HELPER_ID); + KAuth::ExecuteJob *brightnessMaxJob = brightnessMaxAction.execute(); + connect(brightnessMaxJob, &KJob::result, this, + [this, brightnessMaxJob] { + if (brightnessMaxJob->error()) { + qCWarning(POWERDEVIL) << "org.kde.powerdevil.backlighthelper.brightnessmax failed"; + qCDebug(POWERDEVIL) << brightnessMaxJob->errorText(); + } else { + m_brightnessMax = brightnessMaxJob->data()["brightnessmax"].toInt(); + } - m_isLedBrightnessControl = m_syspath.contains(QLatin1String("/leds/")); - if (!m_isLedBrightnessControl) { - UdevQt::Client *client = new UdevQt::Client(QStringList("backlight"), this); - connect(client, SIGNAL(deviceChanged(UdevQt::Device)), SLOT(onDeviceChanged(UdevQt::Device))); + KAuth::Action syspathAction("org.kde.powerdevil.backlighthelper.syspath"); + syspathAction.setHelperId(HELPER_ID); + KAuth::ExecuteJob* syspathJob = syspathAction.execute(); + connect(syspathJob, &KJob::result, this, + [this, syspathJob] { + if (syspathJob->error()) { + qCWarning(POWERDEVIL) << "org.kde.powerdevil.backlighthelper.syspath failed"; + qCDebug(POWERDEVIL) << syspathJob->errorText(); + Q_EMIT brightnessSupportQueried(false); + return; + } + m_syspath = syspathJob->data()["syspath"].toString(); + m_syspath = QFileInfo(m_syspath).readLink(); + + m_isLedBrightnessControl = m_syspath.contains(QLatin1String("/leds/")); + if (!m_isLedBrightnessControl) { + UdevQt::Client *client = new UdevQt::Client(QStringList("backlight"), this); + connect(client, SIGNAL(deviceChanged(UdevQt::Device)), SLOT(onDeviceChanged(UdevQt::Device))); + } + + Q_EMIT brightnessSupportQueried(m_brightnessMax > 0); } - - Q_EMIT brightnessSupportQueried(m_brightnessMax > 0); - } - ); - syspathJob->start(); - } - ); - brightnessMaxJob->start(); + ); + syspathJob->start(); + } + ); + brightnessMaxJob->start(); + } + ); + brightnessJob->start(); + } + else{ + qCDebug(POWERDEVIL) << "Using DDCutillib"; + m_cachedBrightnessMap.insert(Screen, brightness(Screen)); + + const int duration = PowerDevilSettings::brightnessAnimationDuration(); + if (duration > 0 && brightnessMax() >= PowerDevilSettings::brightnessAnimationThreshold()) { + m_brightnessAnimation = new QPropertyAnimation(this); + m_brightnessAnimation->setTargetObject(this); + m_brightnessAnimation->setDuration(duration); + m_brightnessAnimation->setEasingCurve(QEasingCurve::InOutQuad); + connect(m_brightnessAnimation, &QPropertyAnimation::valueChanged, this, &PowerDevilUPowerBackend::animationValueChanged); + connect(m_brightnessAnimation, &QPropertyAnimation::finished, this, &PowerDevilUPowerBackend::slotScreenBrightnessChanged); } - ); - brightnessJob->start(); + Q_EMIT brightnessSupportQueried(true); + } } else { qCDebug(POWERDEVIL) << "Using XRandR"; m_randrHelper = XRandRXCBHelper::self(); @@ -398,7 +419,13 @@ //qCDebug(POWERDEVIL) << "Calling xrandr brightness"; result = (int) m_brightnessControl->brightness(); } - } else { + } else if (m_ddcBrightnessControl->isSupported()){ + if (m_brightnessAnimation && m_brightnessAnimation->state() == QPropertyAnimation::Running) { + result = m_brightnessAnimation->endValue().toInt(); + } else { + result = (int)m_ddcBrightnessControl->brightness(); + } + }else{ result = m_cachedBrightnessMap[Screen]; } qCDebug(POWERDEVIL) << "Screen brightness value: " << result; @@ -418,7 +445,9 @@ if (m_brightnessControl->isSupported()) { //qCDebug(POWERDEVIL) << "Calling xrandr brightness"; result = (int) m_brightnessControl->brightnessMax(); - } else { + } else if (m_ddcBrightnessControl->isSupported()){ + result = (int)m_ddcBrightnessControl->brightnessMax(); + }else{ result = m_brightnessMax; } qCDebug(POWERDEVIL) << "Screen brightness value max: " << result; @@ -445,6 +474,17 @@ } else { m_brightnessControl->setBrightness(value); } + } else if (m_ddcBrightnessControl->isSupported()){ + if (m_brightnessAnimation) { + m_brightnessAnimation->stop(); + disconnect(m_brightnessAnimation, &QPropertyAnimation::valueChanged, this, &PowerDevilUPowerBackend::animationValueChanged); + m_brightnessAnimation->setStartValue(brightness()); + m_brightnessAnimation->setEndValue(value); + connect(m_brightnessAnimation, &QPropertyAnimation::valueChanged, this, &PowerDevilUPowerBackend::animationValueChanged); + m_brightnessAnimation->start(); + } else { + m_ddcBrightnessControl->setBrightness((long)value); + } } else { //qCDebug(POWERDEVIL) << "Falling back to helper to set brightness"; KAuth::Action action("org.kde.powerdevil.backlighthelper.setbrightness"); @@ -471,7 +511,6 @@ } int value = brightness(Screen); - qCDebug(POWERDEVIL) << "Brightness changed!!"; if (value != m_cachedBrightnessMap[Screen] || m_isLedBrightnessControl) { m_cachedBrightnessMap[Screen] = value; onBrightnessChanged(Screen, value, brightnessMax(Screen)); @@ -674,5 +713,12 @@ void PowerDevilUPowerBackend::animationValueChanged(const QVariant &value) { - m_brightnessControl->setBrightness(value.toInt()); + if (m_brightnessControl->isSupported()) { + m_brightnessControl->setBrightness(value.toInt()); + }else if (m_ddcBrightnessControl->isSupported()) { + m_ddcBrightnessControl->setBrightness(value.toInt()); + } + else{ + qCInfo(POWERDEVIL)<<"PowerDevilUPowerBackend::animationValueChanged: brightness control not supported"; + } }