diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ add_subdirectory(appmenu) add_subdirectory(libtaskmanager) +add_subdirectory(libcolorcorrect) add_subdirectory(components) add_subdirectory(plasma-windowed) diff --git a/libcolorcorrect/CMakeLists.txt b/libcolorcorrect/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libcolorcorrect/CMakeLists.txt @@ -0,0 +1,59 @@ +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 "$" "$") + +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 ) diff --git a/libcolorcorrect/LibColorCorrectConfig.cmake.in b/libcolorcorrect/LibColorCorrectConfig.cmake.in new file mode 100644 --- /dev/null +++ b/libcolorcorrect/LibColorCorrectConfig.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/LibColorCorrectLibraryTargets.cmake") diff --git a/libcolorcorrect/README b/libcolorcorrect/README new file mode 100644 --- /dev/null +++ b/libcolorcorrect/README @@ -0,0 +1,5 @@ +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. diff --git a/libcolorcorrect/autotests/CMakeLists.txt b/libcolorcorrect/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libcolorcorrect/autotests/CMakeLists.txt @@ -0,0 +1,9 @@ +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 +) diff --git a/libcolorcorrect/autotests/mock_kwin.h b/libcolorcorrect/autotests/mock_kwin.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/autotests/mock_kwin.h @@ -0,0 +1,80 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#ifndef COLORCORRECT_AUTOTESTS_MOCK_KWIN_H +#define COLORCORRECT_AUTOTESTS_MOCK_KWIN_H + +#include "../colorcorrectconstants.h" + +#include +#include +#include + +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 nightColorInfo(); + bool nightColorConfigChange(QHash data); + void nightColorAutoLocationUpdate(double latitude, double longitude); + +Q_SIGNALS: + void nightColorConfigChangeSignal(QHash data); + +private: + QHash getData(); +}; + +#endif //COLORCORRECT_AUTOTESTS_MOCK_KWIN_H diff --git a/libcolorcorrect/autotests/mock_kwin.cpp b/libcolorcorrect/autotests/mock_kwin.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/autotests/mock_kwin.cpp @@ -0,0 +1,122 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#include "mock_kwin.h" + +#include + +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 kwin_dbus::nightColorInfo() +{ + return getData(); +} + +QHash kwin_dbus::getData() +{ + QHash 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 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()); +} diff --git a/libcolorcorrect/autotests/nightcolortest.cpp b/libcolorcorrect/autotests/nightcolortest.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/autotests/nightcolortest.cpp @@ -0,0 +1,204 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#include "mock_kwin.h" +#include "qtest_dbus.h" + +#include "../colorcorrectconstants.h" +#include "../compositorcoloradaptor.h" + +#include + +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("active"); + QTest::addColumn("mode"); + QTest::addColumn("nightTemperature"); + QTest::addColumn("latitudeFixed"); + QTest::addColumn("longitudeFixed"); + QTest::addColumn("morningBeginFixed"); + QTest::addColumn("eveningBeginFixed"); + QTest::addColumn("transitionTime"); + QTest::addColumn("isChange"); + QTest::addColumn("isChangeAll"); + QTest::addColumn("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" diff --git a/libcolorcorrect/autotests/qtest_dbus.h b/libcolorcorrect/autotests/qtest_dbus.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/autotests/qtest_dbus.h @@ -0,0 +1,51 @@ +/* + Copyright 2014 Alejandro Fiestas Olivares + + 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 . +*/ + + +#ifndef COLORCORRECT_AUTOTESTS_QTEST_DBUS_H +#define COLORCORRECT_AUTOTESTS_QTEST_DBUS_H + +#include +#include + +#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 diff --git a/libcolorcorrect/colorcorrectconstants.h b/libcolorcorrect/colorcorrectconstants.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/colorcorrectconstants.h @@ -0,0 +1,32 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#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 diff --git a/libcolorcorrect/compositorcoloradaptor.h b/libcolorcorrect/compositorcoloradaptor.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/compositorcoloradaptor.h @@ -0,0 +1,468 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#ifndef COLORCORRECT_COMPOSITORADAPTOR_H +#define COLORCORRECT_COMPOSITORADAPTOR_H + +#include +#include +#include +#include + +#include "colorcorrect_export.h" +#include "colorcorrectconstants.h" + +class QDBusInterface; + +namespace ColorCorrect +{ + +class COLORCORRECT_EXPORT CompositorAdaptor : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int error READ error NOTIFY errorChanged) + Q_PROPERTY(QString errorText READ errorText NOTIFY errorTextChanged) + + Q_PROPERTY(bool nightColorAvailable READ nightColorAvailable CONSTANT) + Q_PROPERTY(int minimalTemperature READ minimalTemperature CONSTANT) + Q_PROPERTY(int neutralTemperature READ neutralTemperature CONSTANT) + + Q_PROPERTY(bool activeEnabled READ activeEnabled NOTIFY activeEnabledChanged) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + Q_PROPERTY(bool activeStaged READ activeStaged WRITE setActiveStaged NOTIFY activeStagedChanged) + Q_PROPERTY(bool activeDefault READ activeDefault CONSTANT) + + Q_PROPERTY(bool modeEnabled READ modeEnabled NOTIFY modeEnabledChanged) + Q_PROPERTY(int mode READ mode NOTIFY modeChanged) + Q_PROPERTY(int modeStaged READ modeStaged WRITE setModeStaged NOTIFY modeStagedChanged) + Q_PROPERTY(int modeDefault READ modeDefault CONSTANT) + + Q_PROPERTY(bool nightTemperatureEnabled READ nightTemperatureEnabled NOTIFY nightTemperatureEnabledChanged) + Q_PROPERTY(int nightTemperature READ nightTemperature NOTIFY nightTemperatureChanged) + Q_PROPERTY(int nightTemperatureStaged READ nightTemperatureStaged WRITE setNightTemperatureStaged NOTIFY nightTemperatureStagedChanged) + Q_PROPERTY(int nightTemperatureDefault READ nightTemperatureDefault CONSTANT) + + Q_PROPERTY(int curColorT READ curColorT WRITE setCurColorT NOTIFY curColorTChanged) + + Q_PROPERTY(double latitudeAuto READ latitudeAuto NOTIFY latitudeAutoChanged) + Q_PROPERTY(double longitudeAuto READ longitudeAuto NOTIFY longitudeAutoChanged) + + Q_PROPERTY(bool locationEnabled READ locationEnabled NOTIFY locationEnabledChanged) + Q_PROPERTY(double latitudeFixed READ latitudeFixed NOTIFY latitudeFixedChanged) + Q_PROPERTY(double longitudeFixed READ longitudeFixed NOTIFY longitudeFixedChanged) + Q_PROPERTY(double latitudeFixedStaged READ latitudeFixedStaged WRITE setLatitudeFixedStaged NOTIFY latitudeFixedStagedChanged) + Q_PROPERTY(double longitudeFixedStaged READ longitudeFixedStaged WRITE setLongitudeFixedStaged NOTIFY longitudeFixedStagedChanged) + Q_PROPERTY(double latitudeFixedDefault READ latitudeFixedDefault CONSTANT) + Q_PROPERTY(double longitudeFixedDefault READ longitudeFixedDefault CONSTANT) + + Q_PROPERTY(bool timingsEnabled READ timingsEnabled NOTIFY timingsEnabledChanged) + + Q_PROPERTY(QTime morningBeginFixed READ morningBeginFixed NOTIFY morningBeginFixedChanged) + Q_PROPERTY(QTime morningBeginFixedStaged READ morningBeginFixedStaged WRITE setMorningBeginFixedStaged NOTIFY morningBeginFixedStagedChanged) + Q_PROPERTY(QTime eveningBeginFixed READ eveningBeginFixed NOTIFY eveningBeginFixedChanged) + Q_PROPERTY(QTime eveningBeginFixedStaged READ eveningBeginFixedStaged WRITE setEveningBeginFixedStaged NOTIFY eveningBeginFixedStagedChanged) + Q_PROPERTY(int transitionTime READ transitionTime NOTIFY transitionTimeChanged) + Q_PROPERTY(int transitionTimeStaged READ transitionTimeStaged WRITE setTransitionTimeStaged NOTIFY transitionTimeStagedChanged) + Q_PROPERTY(QTime morningBeginFixedDefault READ morningBeginFixedDefault CONSTANT) + Q_PROPERTY(QTime eveningBeginFixedDefault READ eveningBeginFixedDefault CONSTANT) + Q_PROPERTY(int transitionTimeDefault READ transitionTimeDefault CONSTANT) + +public: + enum class ErrorCode { + // no error + ErrorCodeSuccess = 0, + // couldn't establish connection to compositor + ErrorCodeConnectionFailed, + // rendering backend doesn't support hardware color correction + ErrorCodeBackendNoSupport, + // it's an X session - no native color correction in general on X + ErrorCodeXSession + }; + Q_ENUMS(ErrorCode) + + enum class Mode { + ModeAutomatic, + ModeLocation, + ModeTimings + }; + Q_ENUMS(Mode) + + explicit CompositorAdaptor(QObject *parent = nullptr); + virtual ~CompositorAdaptor() = default; + + int error() { + return (int)m_error; + } + void setError(ErrorCode error); + + QString errorText() { + return m_errorText; + } + + /* + * General + */ + bool nightColorAvailable() const { + return m_nightColorAvailable; + } + int minimalTemperature() { + return MIN_TEMPERATURE; + } + int neutralTemperature() { + return NEUTRAL_TEMPERATURE; + } + + bool activeEnabled() const { + return m_activeEnabled; + } + bool active() const { + return m_active; + } + bool activeStaged() const { + return m_activeStaged; + } + void setActiveStaged(bool set) { + if (m_activeStaged == set) { + return; + } + m_activeStaged = set; + emit activeStagedChanged(); + } + bool activeDefault() { + return true; + } + + bool modeEnabled() const { + return m_modeEnabled; + } + int mode() const { + return (int)m_mode; + } + int modeStaged() const { + return (int)m_modeStaged; + } + void setModeStaged(int mode) { + if (mode < 0 || 2 < mode || (int)m_modeStaged == mode) { + return; + } + m_modeStaged = (Mode)mode; + emit modeStagedChanged(); + } + int modeDefault() { + return (int)Mode::ModeAutomatic; + } + /* + * Color Temperature + */ + bool nightTemperatureEnabled() const { + return m_nightTemperatureEnabled; + } + int nightTemperature() const { + return m_nightTemperature; + } + int nightTemperatureStaged() const { + return m_nightTemperatureStaged; + } + void setNightTemperatureStaged(int val) { + if (m_nightTemperatureStaged == val) { + return; + } + m_nightTemperatureStaged = val; + emit nightTemperatureStagedChanged(); + } + int nightTemperatureDefault() { + return DEFAULT_NIGHT_TEMPERATURE; + } + int curColorT() { + return m_curColorT; + } + void setCurColorT(int val) { + if (m_nightTemperature == val) { + return; + } + m_curColorT = val; + emit curColorTChanged(); + } + /* + * Location + */ + bool locationEnabled() const { + return m_locationEnabled; + } + + double latitudeAuto() const { + return m_latitudeAuto; + } + double longitudeAuto() const { + return m_longitudeAuto; + } + + double latitudeFixed() const { + return m_latitudeFixed; + } + double latitudeFixedStaged() const { + return m_latitudeFixedStaged; + } + void setLatitudeFixedStaged(double val) { + if (m_latitudeFixedStaged == val) { + return; + } + m_latitudeFixedStaged = val; + emit latitudeFixedStagedChanged(); + } + + double longitudeFixed() const { + return m_longitudeFixed; + } + double longitudeFixedStaged() const { + return m_longitudeFixedStaged; + } + void setLongitudeFixedStaged(double val) { + if (m_longitudeFixedStaged == val) { + return; + } + m_longitudeFixedStaged = val; + emit longitudeFixedStagedChanged(); + } + double latitudeFixedDefault() { + return 0.; + } + double longitudeFixedDefault() { + return 0.; + } + /* + * Timings + */ + bool timingsEnabled() const { + return m_timingsEnabled; + } + + QTime morningBeginFixed() const { + return m_morningBeginFixed; + } + QTime morningBeginFixedStaged() const { + return m_morningBeginFixedStaged; + } + void setMorningBeginFixedStaged(QTime time) { + if (m_morningBeginFixedStaged == time) { + return; + } + m_morningBeginFixedStaged = time; + emit morningBeginFixedStagedChanged(); + } + + QTime eveningBeginFixed() const { + return m_eveningBeginFixed; + } + QTime eveningBeginFixedStaged() const { + return m_eveningBeginFixedStaged; + } + void setEveningBeginFixedStaged(QTime time) { + if (m_eveningBeginFixedStaged == time) { + return; + } + m_eveningBeginFixedStaged = time; + emit eveningBeginFixedStagedChanged(); + } + // saved in minutes + int transitionTime() const { + return m_transitionTime; + } + int transitionTimeStaged() const { + return m_transitionTimeStaged; + } + void setTransitionTimeStaged(int time) { + if (m_transitionTimeStaged == time) { + return; + } + m_transitionTimeStaged = time; + emit transitionTimeStagedChanged(); + } + QTime morningBeginFixedDefault() { + return QTime(6,0,0); + } + QTime eveningBeginFixedDefault() { + return QTime(18,0,0); + } + int transitionTimeDefault() { + return FALLBACK_SLOW_UPDATE_TIME; + } + + /** + * @brief Reloads data and resets staged values. + * + * Reloads current data from compositor, also resets all staged values. + * For data updates without resetting staged values, don't use this method + * and instead connect to the compDataUpdated signal. + * + * @return void + * @see compDataUpdated + * @since 5.12 + **/ + Q_INVOKABLE void reloadData(); + /** + * @brief Send subset of staged values. + * + * Send a relevant subset of staged values to the compositor in order + * to trigger a configuration change. If active will be set to false, no + * other data will be sent. Otherwise additionally staged temperature and + * mode values will be sent and for the requested mode relevant data, i.e. + * in Automatic mode no other data, in Location mode staged latitude or + * longitude values and in Timings mode the morning and evening begin, as + * well as the transition time. + * + * Returns true, if the configuration was succesfully applied. + * + * @return bool + * @see sendConfigurationAll + * @since 5.12 + **/ + Q_INVOKABLE bool sendConfiguration(); + /** + * @brief Send all staged values. + * + * Send all currently staged values to the compositor in order + * to trigger a configuration change. + * + * Returns true, if the configuration was succesfully applied. + * + * @return bool + * @see sendConfiguration + * @since 5.12 + **/ + Q_INVOKABLE bool sendConfigurationAll(); + /** + * @brief Send automatic location data. + * + * Updated auto location data is provided by the workspace. This is + * in general already done by the KDE Daemon. + * + * @return void + * @since 5.12 + **/ + Q_INVOKABLE void sendAutoLocationUpdate(double latitude, double longitude); + /** + * @brief Check changes in subset of staged values. + * + * Compares staged to current values relative to chosen activation state and mode, + * returns true if there is a difference. + * + * @return bool + * @see checkStagedAll + * @since 5.12 + **/ + Q_INVOKABLE bool checkStaged(); + /** + * @brief Check changes in staged values. + * + * Compares every staged to its current value, returns true if there is a difference. + * + * @return bool + * @see checkStaged + * @since 5.12 + **/ + Q_INVOKABLE bool checkStagedAll(); + +private Q_SLOTS: + void compDataUpdated(QHash data); + +Q_SIGNALS: + void errorChanged(); + void errorTextChanged(); + + void activeEnabledChanged(); + void activeChanged(); + void activeStagedChanged(); + void modeEnabledChanged(); + void modeChanged(); + void modeStagedChanged(); + + void curColorTChanged(); + void nightTemperatureEnabledChanged(); + void nightTemperatureChanged(); + void nightTemperatureStagedChanged(); + + void latitudeAutoChanged(); + void longitudeAutoChanged(); + + void locationEnabledChanged(); + void latitudeFixedChanged(); + void latitudeFixedStagedChanged(); + void longitudeFixedChanged(); + void longitudeFixedStagedChanged(); + + void timingsEnabledChanged(); + void morningBeginFixedChanged(); + void eveningBeginFixedChanged(); + void morningBeginFixedStagedChanged(); + void eveningBeginFixedStagedChanged(); + + void transitionTimeChanged(); + void transitionTimeStagedChanged(); + + void dataUpdated(); + void stagedDataReset(); + + +private: + bool resetData(QHash data); + void resetDataAndStaged(QHash data); + QDBusInterface *m_iface; + + QHash getData(); + + ErrorCode m_error = ErrorCode::ErrorCodeSuccess; + QString m_errorText = ""; + + bool m_nightColorAvailable = false; + + bool m_activeEnabled = true; + bool m_active = false; + bool m_activeStaged = false; + + bool m_modeEnabled = true; + Mode m_mode = Mode::ModeAutomatic; + Mode m_modeStaged = Mode::ModeAutomatic; + + bool m_nightTemperatureEnabled = true; + int m_nightTemperature = DEFAULT_NIGHT_TEMPERATURE; + int m_nightTemperatureStaged = DEFAULT_NIGHT_TEMPERATURE; + int m_curColorT; + + double m_latitudeAuto; + double m_longitudeAuto; + + bool m_locationEnabled = true; + double m_latitudeFixed = 0; + double m_longitudeFixed = 0; + double m_latitudeFixedStaged = 0; + double m_longitudeFixedStaged = 0; + + bool m_timingsEnabled = true; + + QTime m_morningBeginFixed = QTime(6,0,0); + QTime m_eveningBeginFixed = QTime(18,0,0); + QTime m_morningBeginFixedStaged = QTime(6,0,0); + QTime m_eveningBeginFixedStaged = QTime(18,0,0); + + int m_transitionTime = FALLBACK_SLOW_UPDATE_TIME; + int m_transitionTimeStaged = FALLBACK_SLOW_UPDATE_TIME; +}; + +} + +#endif // COLORCORRECT_COMPOSITORADAPTOR_H diff --git a/libcolorcorrect/compositorcoloradaptor.cpp b/libcolorcorrect/compositorcoloradaptor.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/compositorcoloradaptor.cpp @@ -0,0 +1,285 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#include "compositorcoloradaptor.h" + +#include +#include + +#include +#include + +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"), + QDBusConnection::sessionBus(), + this); + + if (!m_iface->connection().connect(QString(), QStringLiteral("/ColorCorrect"), + QStringLiteral("org.kde.kwin.ColorCorrect"), QStringLiteral("nightColorConfigChanged"), + this, SLOT(compDataUpdated(QHash)))) { + setError(ErrorCode::ErrorCodeConnectionFailed); + return; + } + reloadData(); +} + +void CompositorAdaptor::setError(ErrorCode error) { + if (m_error == error || m_error == ErrorCode::ErrorCodeXSession) { + return; + } + m_error = error; + switch(error) { + case ErrorCode::ErrorCodeConnectionFailed: + m_errorText = i18nc("Critical error message", "Failed to connect to the Window Manager"); + break; + case ErrorCode::ErrorCodeBackendNoSupport: + m_errorText = i18nc("Critical error message", "Rendering backend doesn't support Color Correction."); + break; + case ErrorCode::ErrorCodeXSession: + m_errorText = i18nc("Critical error message", "You're currently using the X Windowing System. This functionality is Wayland exclusive."); + break; + default: + m_errorText = ""; + } + emit errorChanged(); + emit errorTextChanged(); +} + +void CompositorAdaptor::compDataUpdated(QHash data) +{ + resetData(data); + emit dataUpdated(); +} + +void CompositorAdaptor::reloadData() +{ + QHash info = getData(); + if (info.isEmpty()) { + return; + } + resetDataAndStaged(info); +} + +#define SETTER(out, in, emitsignal) \ +if (out != in) { \ + out = in; \ + emit emitsignal; \ +} + +void CompositorAdaptor::resetDataAndStaged(QHash data) +{ + if (!resetData(data)) { + return; + } + + SETTER(m_activeStaged, m_active, activeStagedChanged()) + SETTER(m_modeStaged, m_mode, modeStagedChanged()) + + SETTER(m_nightTemperatureStaged, m_nightTemperature, nightTemperatureStagedChanged()) + + SETTER(m_latitudeFixedStaged, m_latitudeFixed, latitudeFixedStagedChanged()) + SETTER(m_longitudeFixedStaged, m_longitudeFixed, longitudeFixedStagedChanged()) + + SETTER(m_morningBeginFixedStaged, m_morningBeginFixed, morningBeginFixedStagedChanged()) + SETTER(m_eveningBeginFixedStaged, m_eveningBeginFixed, eveningBeginFixedStagedChanged()) + SETTER(m_transitionTimeStaged, m_transitionTime, transitionTimeStagedChanged()) + + emit stagedDataReset(); +} + +bool CompositorAdaptor::resetData(QHash data) +{ + m_nightColorAvailable = data["Available"].toBool(); + + if (!m_nightColorAvailable) { + setError(ErrorCode::ErrorCodeBackendNoSupport); + return false; + } + + SETTER(m_activeEnabled, data["ActiveEnabled"].toBool(), activeEnabledChanged()) + SETTER(m_active, data["Active"].toBool(), activeChanged()) + SETTER(m_mode, (Mode)data["Mode"].toInt(), modeChanged()) + + SETTER(m_nightTemperatureEnabled, data["NightTemperatureEnabled"].toBool(), nightTemperatureEnabledChanged()) + SETTER(m_nightTemperature, data["NightTemperature"].toInt(), nightTemperatureChanged()) + SETTER(m_curColorT, data["CurrentColorTemperature"].toInt(), curColorTChanged()) + + SETTER(m_locationEnabled, data["LocationEnabled"].toBool(), locationEnabledChanged()) + SETTER(m_latitudeAuto, data["LatitudeAuto"].toDouble(), latitudeAutoChanged()) + SETTER(m_longitudeAuto, data["LongitudeAuto"].toDouble(), longitudeAutoChanged()) + SETTER(m_latitudeFixed, data["LatitudeFixed"].toDouble(), latitudeFixedChanged()) + SETTER(m_longitudeFixed, data["LongitudeFixed"].toDouble(), longitudeFixedChanged()) + + SETTER(m_timingsEnabled, data["TimingsEnabled"].toBool(), timingsEnabledChanged()) + SETTER(m_morningBeginFixed, QTime::fromString(data["MorningBeginFixed"].toString()), morningBeginFixedChanged()) + SETTER(m_eveningBeginFixed, QTime::fromString(data["EveningBeginFixed"].toString()), eveningBeginFixedChanged()) + SETTER(m_transitionTime, data["TransitionTime"].toInt(), transitionTimeChanged()) + + return true; +} + +#undef SETTER + +QHash CompositorAdaptor::getData() +{ + QDBusReply > reply = m_iface->call("nightColorInfo"); + if (reply.isValid()) { + return reply.value(); + } else { + setError(ErrorCode::ErrorCodeConnectionFailed); + return QHash(); + } +} + +bool CompositorAdaptor::sendConfiguration() +{ + if (!m_iface) { + return false; + } + QHash data; + data["Active"] = m_activeStaged; + + if (m_activeStaged) { + data["Mode"] = (int)m_modeStaged; + + data["NightTemperature"] = m_nightTemperatureStaged; + + if (m_modeStaged == Mode::ModeLocation) { + data["LatitudeFixed"] = m_latitudeFixedStaged; + data["LongitudeFixed"] = m_longitudeFixedStaged; + } + + if (m_modeStaged == Mode::ModeTimings) { + data["MorningBeginFixed"] = m_morningBeginFixedStaged.toString(Qt::ISODate); + data["EveningBeginFixed"] = m_eveningBeginFixedStaged.toString(Qt::ISODate); + data["TransitionTime"] = m_transitionTimeStaged; + } + } + + QDBusReply reply = m_iface->call("setNightColorConfig", data); + if (reply.isValid() && reply) { + m_active = m_activeStaged; + if (m_activeStaged) { + m_mode = m_modeStaged; + m_nightTemperature = m_nightTemperatureStaged; + + if (m_mode == Mode::ModeLocation) { + m_latitudeFixed = m_latitudeFixedStaged; + m_longitudeFixed = m_longitudeFixedStaged; + } + + if (m_mode == Mode::ModeTimings) { + m_morningBeginFixed = m_morningBeginFixedStaged; + m_eveningBeginFixed = m_eveningBeginFixedStaged; + m_transitionTime = m_transitionTimeStaged; + } + } + return true; + } + return false; +} + +bool CompositorAdaptor::sendConfigurationAll() +{ + if (!m_iface) { + return false; + } + QHash data; + data["Active"] = m_activeStaged; + data["Mode"] = (int)m_modeStaged; + data["NightTemperature"] = m_nightTemperatureStaged; + data["LatitudeFixed"] = m_latitudeFixedStaged; + data["LongitudeFixed"] = m_longitudeFixedStaged; + data["MorningBeginFixed"] = m_morningBeginFixedStaged.toString(Qt::ISODate); + data["EveningBeginFixed"] = m_eveningBeginFixedStaged.toString(Qt::ISODate); + data["TransitionTime"] = m_transitionTimeStaged; + + QDBusReply reply = m_iface->call("setNightColorConfig", data); + if (reply.isValid() && reply) { + m_active = m_activeStaged; + m_mode = m_modeStaged; + m_nightTemperature = m_nightTemperatureStaged; + + m_latitudeFixed = m_latitudeFixedStaged; + m_longitudeFixed = m_longitudeFixedStaged; + + m_morningBeginFixed = m_morningBeginFixedStaged; + m_eveningBeginFixed = m_eveningBeginFixedStaged; + m_transitionTime = m_transitionTimeStaged; + return true; + } + return false; +} + +void CompositorAdaptor::sendAutoLocationUpdate(double latitude, double longitude) +{ + if (m_mode == Mode::ModeAutomatic) { + m_iface->call("nightColorAutoLocationUpdate", latitude, longitude); + } +} + +bool CompositorAdaptor::checkStaged() +{ + bool actChange = m_active != m_activeStaged; + if (!m_activeStaged) { + return actChange; + } + + bool baseDataChange = actChange || + m_mode != m_modeStaged || + m_nightTemperature != m_nightTemperatureStaged; + switch(m_modeStaged) { + case Mode::ModeAutomatic: + return baseDataChange; + case Mode::ModeLocation: + return baseDataChange || + m_latitudeFixed != m_latitudeFixedStaged || + m_longitudeFixed != m_longitudeFixedStaged; + case Mode::ModeTimings: + return baseDataChange || + m_morningBeginFixed != m_morningBeginFixedStaged || + m_eveningBeginFixed != m_eveningBeginFixedStaged || + m_transitionTime != m_transitionTimeStaged; + default: + // never reached + return false; + } +} + +bool CompositorAdaptor::checkStagedAll() +{ + return m_active != m_activeStaged || + m_mode != m_modeStaged || + m_nightTemperature != m_nightTemperatureStaged || + m_latitudeFixed != m_latitudeFixedStaged || + m_longitudeFixed != m_longitudeFixedStaged || + m_morningBeginFixed != m_morningBeginFixedStaged || + m_eveningBeginFixed != m_eveningBeginFixedStaged || + m_transitionTime != m_transitionTimeStaged; +} + +} diff --git a/libcolorcorrect/declarative/CMakeLists.txt b/libcolorcorrect/declarative/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libcolorcorrect/declarative/CMakeLists.txt @@ -0,0 +1,10 @@ +include_directories(${CMAKE_CURRENT_BINARY_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_library(colorcorrectplugin SHARED colorcorrectplugin.cpp) + +target_link_libraries(colorcorrectplugin + Qt5::Qml + colorcorrect) + +install(TARGETS colorcorrectplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/colorcorrect) +install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/colorcorrect) diff --git a/libcolorcorrect/declarative/colorcorrectplugin.h b/libcolorcorrect/declarative/colorcorrectplugin.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/declarative/colorcorrectplugin.h @@ -0,0 +1,37 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ + +#ifndef COLORCORRECTPLUGIN_H +#define COLORCORRECTPLUGIN_H + +#include + +namespace ColorCorrect +{ + +class ColorCorrectPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + virtual void registerTypes(const char *uri); +}; + +} + +#endif // COLORCORRECTPLUGIN_H diff --git a/libcolorcorrect/declarative/colorcorrectplugin.cpp b/libcolorcorrect/declarative/colorcorrectplugin.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/declarative/colorcorrectplugin.cpp @@ -0,0 +1,37 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ + +#include "colorcorrectplugin.h" +#include "compositorcoloradaptor.h" +#include "geolocator.h" +#include "suncalc.h" + +#include +#include + +namespace ColorCorrect +{ + +void ColorCorrectPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(uri == QLatin1String("org.kde.colorcorrect")); + qmlRegisterType(uri, 0, 1, "CompositorAdaptor"); + qmlRegisterType(uri, 0, 1, "Geolocator"); + qmlRegisterType(uri, 0, 1, "SunCalc"); +} + +} diff --git a/libcolorcorrect/declarative/qmldir b/libcolorcorrect/declarative/qmldir new file mode 100644 --- /dev/null +++ b/libcolorcorrect/declarative/qmldir @@ -0,0 +1,3 @@ +module org.kde.colorcorrect + +plugin colorcorrectplugin diff --git a/libcolorcorrect/geolocator.h b/libcolorcorrect/geolocator.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/geolocator.h @@ -0,0 +1,73 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#ifndef COLORCORRECT_GEOLOCATOR_H +#define COLORCORRECT_GEOLOCATOR_H + +#include "colorcorrect_export.h" + +#include +#include + +namespace Plasma +{ + +class DataEngine; + +} + +namespace ColorCorrect +{ + +class COLORCORRECT_EXPORT Geolocator : public QObject, public Plasma::DataEngineConsumer +{ + Q_OBJECT + + Q_PROPERTY(double latitude READ latitude NOTIFY latitudeChanged) + Q_PROPERTY(double longitude READ longitude NOTIFY longitudeChanged) + +public: + explicit Geolocator(QObject *parent = nullptr); + virtual ~Geolocator() = default; + + double latitude() const { + return m_latitude; + } + double longitude() const { + return m_longitude; + } + +public Q_SLOTS: + /** + * Called when data is updated by Plasma::DataEngine + */ + void dataUpdated(const QString &source, const Plasma::DataEngine::Data &data); + +Q_SIGNALS: + void locationChanged(double latitude, double longitude); + void latitudeChanged(); + void longitudeChanged(); + +private: + Plasma::DataEngine *m_engine = nullptr; + + double m_latitude = 0; + double m_longitude = 0; +}; + +} + +#endif // COLORCORRECT_GEOLOCATOR_H diff --git a/libcolorcorrect/geolocator.cpp b/libcolorcorrect/geolocator.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/geolocator.cpp @@ -0,0 +1,81 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#include "geolocator.h" + +namespace ColorCorrect +{ + +Geolocator::Geolocator(QObject *parent) + : QObject(parent) +{ + m_engine = dataEngine("geolocation"); + + if (m_engine && m_engine->isValid()) { + m_engine->connectSource("location", this); + } +} + +void Geolocator::dataUpdated(const QString &source, const Plasma::DataEngine::Data &data) +{ + Q_UNUSED(source); + + if (!m_engine) { + return; + } + + if (data.isEmpty()) { + return; + } + + auto readGeoDouble = [](const Plasma::DataEngine::Data &data, const QString &key) -> double { + const Plasma::DataEngine::Data::ConstIterator it = data.find(key); + + if (it == data.end()) { + return qQNaN(); + } + + bool ok = false; + double result = it.value().toDouble(&ok); + if (!ok) { + result = qQNaN(); + } + return result; + }; + + double lat = readGeoDouble(data, QStringLiteral("latitude")); + double lng = readGeoDouble(data, QStringLiteral("longitude")); + if (std::isnan(lat) || std::isnan(lng)) { + return; + } + + bool isChanged = false; + if (m_latitude != lat) { + m_latitude = lat; + isChanged = true; + emit latitudeChanged(); + } + if (m_longitude != lng) { + m_longitude = lng; + isChanged = true; + emit longitudeChanged(); + } + if (isChanged) { + emit locationChanged(m_latitude, m_longitude); + } +} + +} diff --git a/libcolorcorrect/kded/CMakeLists.txt b/libcolorcorrect/kded/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libcolorcorrect/kded/CMakeLists.txt @@ -0,0 +1,11 @@ +include_directories (${CMAKE_CURRENT_BINARY_DIR}/..) + +add_library(colorcorrectlocationupdater MODULE locationupdater.cpp) + +kcoreaddons_desktop_to_json(colorcorrectlocationupdater colorcorrectlocationupdater.desktop) +target_link_libraries(colorcorrectlocationupdater + KF5::DBusAddons + KF5::CoreAddons + colorcorrect) + +install(TARGETS colorcorrectlocationupdater DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kded) diff --git a/libcolorcorrect/kded/colorcorrectlocationupdater.desktop b/libcolorcorrect/kded/colorcorrectlocationupdater.desktop new file mode 100644 --- /dev/null +++ b/libcolorcorrect/kded/colorcorrectlocationupdater.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Service +Name=ColorCorrect Geolocation Updater +Name[de]=ColorCorrect Ortsaktualisierer +ServiceTypes=KDEDModule +X-KDE-ServiceTypes=KDEDModule +X-KDE-Library=colorcorrectlocationupdater +X-KDE-FactoryName=colorcorrectlocationupdater +X-KDE-DBus-ModuleName=colorcorrectlocationupdater +X-KDE-Kded-autoload=true +Comment=Sends updated location data to the compositor +Comment[de]=Sendet aktualisierte Ortsdaten an den Compositor diff --git a/libcolorcorrect/kded/locationupdater.h b/libcolorcorrect/kded/locationupdater.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/kded/locationupdater.h @@ -0,0 +1,42 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#ifndef LOCATIONUPDATER_H +#define LOCATIONUPDATER_H + +#include + +namespace ColorCorrect +{ +class Geolocator; +class CompositorAdaptor; +} + +class LocationUpdater : public KDEDModule +{ + Q_OBJECT +public: + LocationUpdater(QObject* parent, const QList &); + +public Q_SLOTS: + void sendLocation(double latitude, double longitude); + +private: + ColorCorrect::CompositorAdaptor *m_adaptor; + ColorCorrect::Geolocator *m_locator; +}; + +#endif diff --git a/libcolorcorrect/kded/locationupdater.cpp b/libcolorcorrect/kded/locationupdater.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/kded/locationupdater.cpp @@ -0,0 +1,42 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#include "locationupdater.h" + +#include +#include + +#include "../compositorcoloradaptor.h" +#include "../geolocator.h" + +K_PLUGIN_FACTORY_WITH_JSON(LocationUpdaterFactory, + "colorcorrectlocationupdater.json", + registerPlugin();) + +LocationUpdater::LocationUpdater(QObject *parent, const QList &) + : KDEDModule(parent) +{ + m_adaptor = new ColorCorrect::CompositorAdaptor(this); + m_locator = new ColorCorrect::Geolocator(this); + connect(m_locator, &ColorCorrect::Geolocator::locationChanged, this, &LocationUpdater::sendLocation); +} + +void LocationUpdater::sendLocation(double latitude, double longitude) +{ + m_adaptor->sendAutoLocationUpdate(latitude, longitude); +} + +#include diff --git a/libcolorcorrect/suncalc.h b/libcolorcorrect/suncalc.h new file mode 100644 --- /dev/null +++ b/libcolorcorrect/suncalc.h @@ -0,0 +1,59 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#ifndef COLORCORRECT_SUNCALC_H +#define COLORCORRECT_SUNCALC_H + +#include +#include +#include +#include +#include + +#include "colorcorrect_export.h" + +namespace ColorCorrect +{ + +/* + * The purpose of this class is solely to enable workspace to present the + * resulting timings of the current or requested configuration to the user. + * The final calculations happen in similar functions in the compositor. + * + * The functions provided by this class here therefore needs to be hold in + * sync with the functions in the compositor. + */ + +class COLORCORRECT_EXPORT SunCalc : public QObject +{ + Q_OBJECT + +public: + /** + * Calculates for a given location and date two of the + * following sun timings in their temporal order: + * - Nautical dawn and sunrise for the morning + * - Sunset and nautical dusk for the evening + * @since 5.12 + **/ + Q_INVOKABLE QVariantMap getMorningTimings(double latitude, double longitude); + Q_INVOKABLE QVariantMap getEveningTimings(double latitude, double longitude); + +}; + +} + +#endif // COLORCORRECT_SUNCALC_H diff --git a/libcolorcorrect/suncalc.cpp b/libcolorcorrect/suncalc.cpp new file mode 100644 --- /dev/null +++ b/libcolorcorrect/suncalc.cpp @@ -0,0 +1,175 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +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 . +*********************************************************************/ +#include "suncalc.h" +#include "colorcorrectconstants.h" + +#include +#include + +namespace ColorCorrect { + +static const double TWILIGHT_NAUT = -12.0; +static const double TWILIGHT_CIVIL = -6.0; +static const double SUN_RISE_SET = -0.833; +static const double SUN_HIGH = 2.0; + +QVariantMap calculateSunTimings(double latitude, double longitude, bool morning) +{ + // calculations based on http://aa.quae.nl/en/reken/zonpositie.html + // accuracy: +/- 5min + + // positioning + const double rad = M_PI / 180.; + const double earthObliquity = 23.4397; // epsilon + + const double lat = latitude; // phi + const double lng = -longitude; // lw + + // times + QDate prompt = QDate::currentDate(); + const double juPrompt = prompt.toJulianDay(); // J + const double ju2000 = 2451545.; // J2000 + + // geometry + auto mod360 = [](double number) -> double { + return std::fmod(number, 360.); + }; + + auto sin = [&rad](double angle) -> double { + return std::sin(angle * rad); + }; + auto cos = [&rad](double angle) -> double { + return std::cos(angle * rad); + }; + auto asin = [&rad](double val) -> double { + return std::asin(val) / rad; + }; + auto acos = [&rad](double val) -> double { + return std::acos(val) / rad; + }; + + auto anomaly = [&](const double date) -> double { // M + return mod360(357.5291 + 0.98560028 * (date - ju2000)); + }; + + auto center = [&sin](double anomaly) -> double { // C + return 1.9148 * sin(anomaly) + 0.02 * sin(2 * anomaly) + 0.0003 * sin(3 * anomaly); + }; + + auto ecliptLngMean = [](double anom) -> double { // Mean ecliptical longitude L_sun = Mean Anomaly + Perihelion + 180° + return anom + 282.9372; // anom + 102.9372 + 180° + }; + + auto ecliptLng = [&](double anom) -> double { // lambda = L_sun + C + return ecliptLngMean(anom) + center(anom); + }; + + auto declination = [&](const double date) -> double { // delta + const double anom = anomaly(date); + const double eclLng = ecliptLng(anom); + + return mod360(asin(sin(earthObliquity) * sin(eclLng))); + }; + + // sun hour angle at specific angle + auto hourAngle = [&](const double date, double angle) -> double { // H_t + const double decl = declination(date); + const double ret0 = (sin(angle) - sin(lat) * sin(decl)) / (cos(lat) * cos(decl)); + + double ret = mod360(acos( ret0 )); + if (180. < ret) { + ret = ret - 360.; + } + return ret; + }; + + /* + * Sun positions + */ + + // transit is at noon + auto getTransit = [&](const double date) -> double { // Jtransit + const double juMeanSolTime = juPrompt - ju2000 - 0.0009 - lng / 360.; // n_x = J - J_2000 - J_0 - l_w / 360° + const double juTrEstimate = date + qRound64(juMeanSolTime) - juMeanSolTime; // J_x = J + n - n_x + const double anom = anomaly(juTrEstimate); // M + const double eclLngM = ecliptLngMean(anom); // L_sun + + return juTrEstimate + 0.0053 * sin(anom) - 0.0068 * sin(2 * eclLngM); + }; + + auto getSunMorning = [&hourAngle](const double angle, const double transit) -> double { + return transit - hourAngle(transit, angle) / 360.; + }; + + auto getSunEvening = [&hourAngle](const double angle, const double transit) -> double { + return transit + hourAngle(transit, angle) / 360.; + }; + + /* + * Begin calculations + */ + + // noon - sun at the highest point + const double juNoon = getTransit(juPrompt); + + double begin, end; + if (morning) { + begin = getSunMorning(TWILIGHT_CIVIL, juNoon); + end = getSunMorning(SUN_HIGH, juNoon); + } else { + begin = getSunEvening(SUN_HIGH, juNoon); + end = getSunEvening(TWILIGHT_CIVIL, juNoon); + } + // transform to QDateTime + begin += 0.5; + end += 0.5; + + QTime timeBegin, timeEnd; + + if (std::isnan(begin)) { + timeBegin = QTime(); + } else { + double timePart = begin - (int)begin; + timeBegin = QTime::fromMSecsSinceStartOfDay((int)( timePart * MSC_DAY )); + } + if (std::isnan(end)) { + timeEnd = QTime(); + } else { + double timePart = end - (int)end; + timeEnd = QTime::fromMSecsSinceStartOfDay((int)( timePart * MSC_DAY )); + } + + QDateTime dateTimeBegin(prompt, timeBegin, Qt::UTC); + QDateTime dateTimeEnd(prompt, timeEnd, Qt::UTC); + + QVariantMap map; + map.insert("begin", dateTimeBegin.toLocalTime()); + map.insert("end", dateTimeEnd.toLocalTime()); + return map; +} + +QVariantMap SunCalc::getMorningTimings(double latitude, double longitude) +{ + return calculateSunTimings(latitude, longitude, true); +} + +QVariantMap SunCalc::getEveningTimings(double latitude, double longitude) +{ + return calculateSunTimings(latitude, longitude, false); +} + +}