Commit f106f53d authored by Roman Gilg's avatar Roman Gilg

ColorCorrect Library for configuring color correction by compositors

The new Night Color module for KWin is designed in a way to be fully
configurable (while safe checking values) via DBus. Also it is supposed to
get updated location data from workspace. To ease the configuration and
provide the updated location data this library is introduced.

* Abstracts away the DBus part and provides a generic adaptor to the
  compositor as QML object for that.
** Allows to stage values and send them at once as needed for KWin's atomic
   config changes.
** Informs about configuration changes.
* Provides also QML objects for other often used functionality in this
  context (sun calculator and a locator, i.e. an encapsulated connection to
  the location data service).
* For automatic location updates the library provides a KDE Daemon Module,
  which gets the current location via such a locator object and sends it to
  KWin.
* The library is named and structured in a way, such that it later can be
  extended with functionality to configure gamma values in general (normal
  color correction), i.e. as soon as the support for that has landed in KWin.

In theory the library could be used with other compositors as well, if they
use a somewhat similar mechanism as KWin. In this case though the
CompositorAdaptor class would need to be rewritten in parts, probably
subclassed.

Test plan:
Manually and auto tests.

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D5931
parent fc469c63
......@@ -136,6 +136,7 @@ add_subdirectory(libdbusmenuqt)
add_subdirectory(appmenu)
add_subdirectory(libtaskmanager)
add_subdirectory(libcolorcorrect)
add_subdirectory(components)
add_subdirectory(plasma-windowed)
......
add_subdirectory(declarative)
add_subdirectory(kded)
add_subdirectory(autotests)
set(colorcorrect_LIB_SRCS
compositorcoloradaptor.cpp
geolocator.cpp
suncalc.cpp
)
add_library(colorcorrect ${colorcorrect_LIB_SRCS})
generate_export_header(colorcorrect)
target_include_directories(colorcorrect PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>" "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/colorcorrect>")
target_link_libraries(colorcorrect
PUBLIC
KF5::Plasma
Qt5::Core
Qt5::Quick
PRIVATE
KF5::WindowSystem
KF5::I18n
Qt5::DBus)
set_target_properties(colorcorrect PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
EXPORT_NAME LibColorCorrect)
install(TARGETS colorcorrect EXPORT libcolorcorrectLibraryTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} )
install(FILES
colorcorrectconstants.h
compositorcoloradaptor.h
geolocator.h
${CMAKE_CURRENT_BINARY_DIR}/colorcorrect_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/colorcorrect COMPONENT Devel
)
write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/LibColorCorrectConfigVersion.cmake VERSION "${PROJECT_VERSION}" COMPATIBILITY AnyNewerVersion)
set(CMAKECONFIG_INSTALL_DIR ${KDE_INSTALL_LIBDIR}/cmake/LibColorCorrect)
ecm_configure_package_config_file(LibColorCorrectConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/LibColorCorrectConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/LibColorCorrectConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/LibColorCorrectConfigVersion.cmake
DESTINATION ${CMAKECONFIG_INSTALL_DIR})
install(EXPORT libcolorcorrectLibraryTargets
NAMESPACE PW::
DESTINATION ${CMAKECONFIG_INSTALL_DIR}
FILE LibColorCorrectLibraryTargets.cmake )
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/LibColorCorrectLibraryTargets.cmake")
This library can be used for configuring the color correction on compositor level. This includes:
- [TODO] Adjusting gamma levels per screen
- Setting night time color temperature (Night Color)
Currently only controlling KWin in Wayland mode is supported.
include(ECMAddTests)
set(ColorCorrectTest_SRCS nightcolortest.cpp mock_kwin.cpp)
ecm_add_test(
${ColorCorrectTest_SRCS}
TEST_NAME nightcolortest
LINK_LIBRARIES colorcorrect Qt5::Test Qt5::DBus
)
/********************************************************************
Copyright 2017 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "mock_kwin.h"
#include <QDBusConnection>
using namespace ColorCorrect;
kwin_dbus::kwin_dbus()
{
}
kwin_dbus::~kwin_dbus()
{
}
bool kwin_dbus::registerDBus()
{
bool ret;
QDBusConnection dbus = QDBusConnection::sessionBus();
ret = dbus.registerObject("/ColorCorrect", this, QDBusConnection::ExportAllContents);
ret &= dbus.registerService("org.kde.KWin");
return ret;
}
bool kwin_dbus::unregisterDBus()
{
bool ret;
QDBusConnection dbus = QDBusConnection::sessionBus();
ret = dbus.unregisterService("org.kde.KWin");
dbus.unregisterObject("/ColorCorrect");
return ret;
}
QHash<QString, QVariant> kwin_dbus::nightColorInfo()
{
return getData();
}
QHash<QString, QVariant> kwin_dbus::getData()
{
QHash<QString, QVariant> ret;
ret["Available"] = nightColorAvailable;
ret["ActiveEnabled"] = activeEnabled;
ret["Active"] = active;
ret["ModeEnabled"] = modeEnabled;
ret["Mode"] = mode;
ret["NightTemperatureEnabled"] = nightTemperatureEnabled;
ret["NightTemperature"] = nightTemperature;
ret["Running"] = running;
ret["CurrentColorTemperature"] = currentColorTemperature;
ret["LatitudeAuto"] = latitudeAuto;
ret["LongitudeAuto"] = longitudeAuto;
ret["LocationEnabled"] = locationEnabled;
ret["LatitudeFixed"] = latitudeFixed;
ret["LongitudeFixed"] = longitudeFixed;
ret["TimingsEnabled"] = timingsEnabled;
ret["MorningBeginFixed"] = morningBeginFixed.toString(Qt::ISODate);
ret["EveningBeginFixed"] = eveningBeginFixed.toString(Qt::ISODate);
ret["TransitionTime"] = transitionTime;
return ret;
}
bool kwin_dbus::nightColorConfigChange(QHash<QString, QVariant> data)
{
if (!configChangeExpectSuccess) {
return false;
}
if (data.contains("Active")) {
active = data["Active"].toBool();
}
if (data.contains("Mode")) {
mode = data["Mode"].toInt();
}
if (data.contains("NightTemperature")) {
nightTemperature = data["NightTemperature"].toInt();
}
if (data.contains("LatitudeFixed")) {
latitudeFixed = data["LatitudeFixed"].toDouble();
longitudeFixed = data["LongitudeFixed"].toDouble();
}
if (data.contains("MorningBeginFixed")) {
morningBeginFixed = QTime::fromString(data["MorningBeginFixed"].toString(), Qt::ISODate);
eveningBeginFixed = QTime::fromString(data["EveningBeginFixed"].toString(), Qt::ISODate);
transitionTime = data["TransitionTime"].toInt();
}
running = active;
emit nightColorConfigChangeSignal(getData());
return true;
}
void kwin_dbus::nightColorAutoLocationUpdate(double latitude, double longitude)
{
latitudeAuto = latitude;
longitudeAuto = longitude;
emit nightColorConfigChangeSignal(getData());
}
/********************************************************************
Copyright 2017 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef COLORCORRECT_AUTOTESTS_MOCK_KWIN_H
#define COLORCORRECT_AUTOTESTS_MOCK_KWIN_H
#include "../colorcorrectconstants.h"
#include <QObject>
#include <QVariant>
#include <QTime>
using namespace ColorCorrect;
class kwin_dbus : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.ColorCorrect")
public:
kwin_dbus();
~kwin_dbus();
bool registerDBus();
bool unregisterDBus();
bool nightColorAvailable = true;
bool activeEnabled = true;
bool active = true;
bool modeEnabled = true;
int mode = 0;
bool nightTemperatureEnabled = true;
int nightTemperature = DEFAULT_NIGHT_TEMPERATURE;
bool running = false;
int currentColorTemperature = NEUTRAL_TEMPERATURE;
double latitudeAuto = 0;
double longitudeAuto = 0;
bool locationEnabled = true;
double latitudeFixed = 0;
double longitudeFixed = 0;
bool timingsEnabled = true;
QTime morningBeginFixed = QTime(6,0,0);
QTime eveningBeginFixed = QTime(18,0,0);
int transitionTime = FALLBACK_SLOW_UPDATE_TIME;
bool configChangeExpectSuccess;
public Q_SLOTS:
QHash<QString, QVariant> nightColorInfo();
bool nightColorConfigChange(QHash<QString, QVariant> data);
void nightColorAutoLocationUpdate(double latitude, double longitude);
Q_SIGNALS:
void nightColorConfigChangeSignal(QHash<QString, QVariant> data);
private:
QHash<QString, QVariant> getData();
};
#endif //COLORCORRECT_AUTOTESTS_MOCK_KWIN_H
/********************************************************************
Copyright 2017 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "mock_kwin.h"
#include "qtest_dbus.h"
#include "../colorcorrectconstants.h"
#include "../compositorcoloradaptor.h"
#include <QtTest/QtTest>
using namespace ColorCorrect;
class TestNightColor : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testAdaptorInit();
void testStageData_data();
void testStageData();
void testAutoLocationUpdate();
private:
void setCompBackToDefault() {
m_comp->nightColorAvailable = true;
m_comp->activeEnabled = true;
m_comp->active = true;
m_comp->modeEnabled = true;
m_comp->mode = 0;
m_comp->nightTemperatureEnabled = true;
m_comp->nightTemperature = DEFAULT_NIGHT_TEMPERATURE;
m_comp->running = false;
m_comp->currentColorTemperature = NEUTRAL_TEMPERATURE;
m_comp->latitudeAuto = 0;
m_comp->longitudeAuto = 0;
m_comp->locationEnabled = true;
m_comp->latitudeFixed = 0;
m_comp->longitudeFixed = 0;
m_comp->timingsEnabled = true;
m_comp->morningBeginFixed = QTime(6,0,0);
m_comp->eveningBeginFixed = QTime(18,0,0);
m_comp->transitionTime = FALLBACK_SLOW_UPDATE_TIME;
}
kwin_dbus *m_comp = nullptr;
};
void TestNightColor::initTestCase()
{
m_comp = new kwin_dbus;
QVERIFY(m_comp->registerDBus());
}
void TestNightColor::cleanupTestCase()
{
QVERIFY(m_comp->unregisterDBus());
delete m_comp;
m_comp = nullptr;
}
void TestNightColor::testAdaptorInit()
{
m_comp->nightColorAvailable = false;
CompositorAdaptor *adaptor = new CompositorAdaptor(this);
QVERIFY(!adaptor->nightColorAvailable());
delete adaptor;
m_comp->nightColorAvailable = true;
adaptor = new CompositorAdaptor(this);
QVERIFY(adaptor->nightColorAvailable());
delete adaptor;
}
void TestNightColor::testStageData_data()
{
QTest::addColumn<bool>("active");
QTest::addColumn<int>("mode");
QTest::addColumn<int>("nightTemperature");
QTest::addColumn<double>("latitudeFixed");
QTest::addColumn<double>("longitudeFixed");
QTest::addColumn<QTime>("morningBeginFixed");
QTest::addColumn<QTime>("eveningBeginFixed");
QTest::addColumn<int>("transitionTime");
QTest::addColumn<bool>("isChange");
QTest::addColumn<bool>("isChangeAll");
QTest::addColumn<bool>("changeExpectFailure");
QTest::newRow("noChange") << true << 0 << DEFAULT_NIGHT_TEMPERATURE << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << FALLBACK_SLOW_UPDATE_TIME << false << false << false;
QTest::newRow("wrongChange") << true << 0 << 9001 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << FALLBACK_SLOW_UPDATE_TIME << true << true << true;
QTest::newRow("temperature") << true << 0 << 1000 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << FALLBACK_SLOW_UPDATE_TIME << true << true << false;
QTest::newRow("deactivate+temperature") << false << 0 << 1000 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << FALLBACK_SLOW_UPDATE_TIME << true << true << false;
QTest::newRow("location+differentMode") << true << 2 << 0 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << FALLBACK_SLOW_UPDATE_TIME << true << true << false;
QTest::newRow("time+defaultMode") << true << 0 << DEFAULT_NIGHT_TEMPERATURE << 0. << 0. << QTime(10,0,0) << QTime(20,0,0) << 1 << false << true << false;
QTest::newRow("location+mode") << true << 1 << DEFAULT_NIGHT_TEMPERATURE << 50. << -20. << QTime(6,0,0) << QTime(18,0,0) << FALLBACK_SLOW_UPDATE_TIME << true << true << false;
QTest::newRow("time+mode") << false << 0 << DEFAULT_NIGHT_TEMPERATURE << 0. << 0. << QTime(10,0,0) << QTime(20,0,0) << 1 << true << true << false;
}
void TestNightColor::testStageData()
{
QFETCH(bool, active);
QFETCH(int, mode);
QFETCH(int, nightTemperature);
QFETCH(double, latitudeFixed);
QFETCH(double, longitudeFixed);
QFETCH(QTime, morningBeginFixed);
QFETCH(QTime, eveningBeginFixed);
QFETCH(int, transitionTime);
QFETCH(bool, isChange);
QFETCH(bool, isChangeAll);
QFETCH(bool, changeExpectFailure);
setCompBackToDefault();
m_comp->configChangeExpectSuccess = !changeExpectFailure;
CompositorAdaptor *aptr = new CompositorAdaptor(this);
QSignalSpy *dataUpdateSpy = new QSignalSpy(aptr, SIGNAL(dataUpdated()));
QVERIFY(dataUpdateSpy->isValid());
QCOMPARE(aptr->checkStaged(), false);
QCOMPARE(aptr->checkStagedAll(), false);
auto setAdaptorStaged = [&aptr,
&active, &mode, &nightTemperature,
&latitudeFixed, &longitudeFixed,
&morningBeginFixed, &eveningBeginFixed, &transitionTime]() {
aptr->setActiveStaged(active);
aptr->setModeStaged(mode);
aptr->setNightTemperatureStaged(nightTemperature);
aptr->setLatitudeFixedStaged(latitudeFixed);
aptr->setLongitudeFixedStaged(longitudeFixed);
aptr->setMorningBeginFixedStaged(morningBeginFixed);
aptr->setEveningBeginFixedStaged(eveningBeginFixed);
aptr->setTransitionTimeStaged(transitionTime);
};
setAdaptorStaged();
QCOMPARE(aptr->checkStaged(), isChange);
QCOMPARE(aptr->checkStagedAll(), isChangeAll);
// send config relative to active and mode state
aptr->sendConfiguration();
// give dbus communication time
QTest::qWait(300);
QCOMPARE(dataUpdateSpy->isEmpty(), changeExpectFailure);
QCOMPARE(aptr->checkStaged(), changeExpectFailure);
// reset compositor and adaptor - now send all data at once
setCompBackToDefault();
delete aptr;
aptr = new CompositorAdaptor(this);
dataUpdateSpy = new QSignalSpy(aptr, SIGNAL(dataUpdated()));
QVERIFY(dataUpdateSpy->isValid());
QCOMPARE(aptr->checkStaged(), false);
QCOMPARE(aptr->checkStagedAll(), false);
setAdaptorStaged();
QCOMPARE(aptr->checkStaged(), isChange);
QCOMPARE(aptr->checkStagedAll(), isChangeAll);
// dump all config
aptr->sendConfigurationAll();
// give dbus communication time
QTest::qWait(300);
QCOMPARE(dataUpdateSpy->isEmpty(), changeExpectFailure);
QCOMPARE(aptr->checkStaged(), changeExpectFailure);
QCOMPARE(aptr->checkStagedAll(), changeExpectFailure);
}
void TestNightColor::testAutoLocationUpdate()
{
setCompBackToDefault();
CompositorAdaptor *aptr = new CompositorAdaptor(this);
QSignalSpy *dataUpdateSpy = new QSignalSpy(aptr, SIGNAL(dataUpdated()));
QVERIFY(dataUpdateSpy->isValid());
aptr->sendAutoLocationUpdate(10, 20);
QTest::qWait(300);
QCOMPARE(dataUpdateSpy->isEmpty(), false);
}
QTEST_GUILESS_MAIN_SYSTEM_DBUS(TestNightColor)
#include "nightcolortest.moc"
/*
Copyright 2014 Alejandro Fiestas Olivares <afiestas@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COLORCORRECT_AUTOTESTS_QTEST_DBUS_H
#define COLORCORRECT_AUTOTESTS_QTEST_DBUS_H
#include <QtTest>
#include <stdlib.h>
#define QTEST_GUILESS_MAIN_SYSTEM_DBUS(TestObject) \
int main(int argc, char *argv[]) \
{ \
QProcess dbus; \
dbus.start(QStringLiteral("dbus-launch")); \
dbus.waitForFinished(10000); \
QByteArray session = dbus.readLine(); \
if (session.isEmpty()) { \
qFatal("Couldn't execute new dbus session"); \
} \
int pos = session.indexOf('='); \
qputenv("DBUS_SESSION_BUS_ADDRESS", session.right(session.count() - pos - 1).trimmed().constData()); \
session = dbus.readLine(); \
pos = session.indexOf('='); \
QByteArray pid = session.right(session.count() - pos - 1).trimmed(); \
QCoreApplication app( argc, argv ); \
app.setApplicationName( QLatin1String("qttest") ); \
TestObject tc; \
int result = QTest::qExec( &tc, argc, argv ); \
dbus.start(QStringLiteral("kill"), QStringList() << QStringLiteral("-9") << QString::fromLatin1(pid)); \
dbus.waitForFinished(); \
return result; \
}
#endif //COLORCORRECT_AUTOTESTS_QTEST_DBUS_H
/********************************************************************
Copyright 2017 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef COLORCORRECT_CONSTANTS_H
#define COLORCORRECT_CONSTANTS_H
namespace ColorCorrect
{
// these values needs to be hold in sync with the compositor
static const int MSC_DAY = 86400000;
static const int MIN_TEMPERATURE = 1000;
static const int NEUTRAL_TEMPERATURE = 6500;
static const int DEFAULT_NIGHT_TEMPERATURE = 4500;
static const int FALLBACK_SLOW_UPDATE_TIME = 30; // in minutes
}
#endif // COLORCORRECT_CONSTANTS_H
/********************************************************************
Copyright 2017 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "compositorcoloradaptor.h"
#include <KWindowSystem>
#include <KLocalizedString>
#include <QDBusReply>
#include <QDBusInterface>
namespace ColorCorrect
{
CompositorAdaptor::CompositorAdaptor(QObject *parent)
: QObject(parent)
{
if(KWindowSystem::isPlatformX11()) {
setError(ErrorCode::ErrorCodeXSession);
}
m_iface = new QDBusInterface (QStringLiteral("org.kde.KWin"),
QStringLiteral("/ColorCorrect"),
QStringLiteral("org.kde.kwin.ColorCorrect"),