14#include "nightcolorlogging.h"
15#include "nightcolorsettings.h"
24#include <KGlobalAccel>
25#include <KLocalizedString>
28#include <QDBusConnection>
29#include <QDBusMessage>
30#include <QDBusPendingReply>
37static const int QUICK_ADJUST_DURATION = 2000;
38static const int TEMPERATURE_STEP = 50;
39static NightColorManager *s_instance =
nullptr;
41static bool checkLocation(
double lat,
double lng)
43 return -90 <= lat && lat <= 90 && -180 <= lng && lng <= 180;
53 NightColorSettings::instance(kwinApp()->config());
62 ? QStringLiteral(
"redshift-status-off")
63 : m_daylight && m_targetTemperature != DEFAULT_DAY_TEMPERATURE ? QStringLiteral(
"redshift-status-day")
64 : QStringLiteral(
"redshift-status-on");
67 ? i18nc(
"Night Light was disabled",
"Night Light Off")
68 : i18nc(
"Night Light was enabled",
"Night Light On");
70 QDBusMessage message = QDBusMessage::createMethodCall(
71 QStringLiteral(
"org.kde.plasmashell"),
72 QStringLiteral(
"/org/kde/osdService"),
73 QStringLiteral(
"org.kde.osdService"),
74 QStringLiteral(
"showText"));
75 message.setArguments({iconName, text});
77 QDBusConnection::sessionBus().asyncCall(message);
80 m_configWatcher = KConfigWatcher::create(kwinApp()->config());
86 QAction *toggleAction =
new QAction(
this);
87 toggleAction->setProperty(
"componentName", QStringLiteral(
"kwin"));
88 toggleAction->setObjectName(QStringLiteral(
"Toggle Night Color"));
89 toggleAction->setText(i18n(
"Toggle Night Light"));
90 KGlobalAccel::setGlobalShortcut(toggleAction, QList<QKeySequence>());
107 QDBusMessage message = QDBusMessage::createMethodCall(
"org.freedesktop.login1",
108 "/org/freedesktop/login1",
109 "org.freedesktop.DBus.Properties",
110 QStringLiteral(
"Get"));
111 message.setArguments(QVariantList({
"org.freedesktop.login1.Manager", QStringLiteral(
"PreparingForSleep")}));
112 QDBusReply<QVariant> reply = QDBusConnection::systemBus().call(message);
113 bool comingFromSuspend;
114 if (reply.isValid()) {
115 comingFromSuspend = reply.value().toBool();
117 qCDebug(KWIN_NIGHTCOLOR) <<
"Failed to get PreparingForSleep Property of logind session:" << reply.error().message();
119 comingFromSuspend =
true;
122 if (comingFromSuspend) {
134 s_instance =
nullptr;
137void NightColorManager::hardReset()
141 updateTransitionTimings(
true);
142 updateTargetTemperature();
146 commitGammaRamps(currentTargetTemp());
160 m_isGloballyInhibited = !m_isGloballyInhibited;
166 return m_inhibitReferenceCount;
171 m_inhibitReferenceCount++;
173 if (m_inhibitReferenceCount == 1) {
181 m_inhibitReferenceCount--;
183 if (!m_inhibitReferenceCount) {
201 return m_currentTemp;
206 return m_targetTemperature;
221 return m_prev.first.msecsTo(m_prev.second);
231 return m_next.first.msecsTo(m_next.second);
234void NightColorManager::readConfig()
236 NightColorSettings *s = NightColorSettings::self();
239 setEnabled(s->active());
255 m_dayTargetTemp = std::clamp(s->dayTemperature(), MIN_TEMPERATURE, DEFAULT_DAY_TEMPERATURE);
256 m_nightTargetTemp = std::clamp(s->nightTemperature(), MIN_TEMPERATURE, DEFAULT_DAY_TEMPERATURE);
259 auto correctReadin = [&lat, &lng]() {
260 if (!checkLocation(lat, lng)) {
267 lat = s->latitudeAuto();
268 lng = s->longitudeAuto();
273 lat = s->latitudeFixed();
274 lng = s->longitudeFixed();
280 QTime mrB = QTime::fromString(s->morningBeginFixed(),
"hhmm");
281 QTime evB = QTime::fromString(s->eveningBeginFixed(),
"hhmm");
283 int diffME = evB > mrB ? mrB.msecsTo(evB) : evB.msecsTo(mrB);
284 int diffMin = std::min(diffME, MSC_DAY - diffME);
286 int trTime = s->transitionTime() * 1000 * 60;
287 if (trTime < 0 || diffMin <= trTime) {
291 trTime = FALLBACK_SLOW_UPDATE_TIME;
295 m_trTime = std::max(trTime / 1000 / 60, 1);
298void NightColorManager::resetAllTimers()
303 updateTransitionTimings(
false);
304 updateTargetTemperature();
305 resetQuickAdjustTimer(currentTargetTemp());
308void NightColorManager::cancelAllTimers()
310 m_slowUpdateStartTimer.reset();
311 m_slowUpdateTimer.reset();
312 m_quickAdjustTimer.reset();
315void NightColorManager::resetQuickAdjustTimer(
int targetTemp)
317 int tempDiff = std::abs(targetTemp - m_currentTemp);
319 if (tempDiff > TEMPERATURE_STEP) {
321 m_quickAdjustTimer = std::make_unique<QTimer>();
322 m_quickAdjustTimer->setSingleShot(
false);
323 connect(m_quickAdjustTimer.get(), &QTimer::timeout,
this, [
this, targetTemp]() {
324 quickAdjust(targetTemp);
327 int interval = (QUICK_ADJUST_DURATION / (m_previewTimer && m_previewTimer->isActive() ? 8 : 1)) / (tempDiff / TEMPERATURE_STEP);
331 m_quickAdjustTimer->start(interval);
339 if (!m_quickAdjustTimer) {
345 if (m_currentTemp < targetTemp) {
346 nextTemp = std::min(m_currentTemp + TEMPERATURE_STEP, targetTemp);
348 nextTemp = std::max(m_currentTemp - TEMPERATURE_STEP, targetTemp);
350 commitGammaRamps(nextTemp);
352 if (nextTemp == targetTemp) {
354 m_quickAdjustTimer.reset();
361 m_slowUpdateStartTimer.reset();
363 if (!m_running || m_quickAdjustTimer) {
375 m_slowUpdateStartTimer = std::make_unique<QTimer>();
376 m_slowUpdateStartTimer->setSingleShot(
true);
379 updateTransitionTimings(
false);
380 updateTargetTemperature();
382 const int diff = QDateTime::currentDateTime().msecsTo(m_next.first);
384 qCCritical(KWIN_NIGHTCOLOR) <<
"Error in time calculation. Deactivating Night Color.";
387 m_slowUpdateStartTimer->start(diff);
390 resetSlowUpdateTimer();
393void NightColorManager::resetSlowUpdateTimer()
395 m_slowUpdateTimer.reset();
397 const QDateTime now = QDateTime::currentDateTime();
399 const int targetTemp = isDay ? m_dayTargetTemp : m_nightTargetTemp;
402 if (m_prev.first == m_prev.second || m_currentTemp == targetTemp) {
403 commitGammaRamps(targetTemp);
407 if (m_prev.first <= now && now <= m_prev.second) {
408 int availTime = now.msecsTo(m_prev.second);
409 m_slowUpdateTimer = std::make_unique<QTimer>();
410 m_slowUpdateTimer->setSingleShot(
false);
412 connect(m_slowUpdateTimer.get(), &QTimer::timeout,
this, [
this]() {
413 slowUpdate(m_dayTargetTemp);
416 connect(m_slowUpdateTimer.get(), &QTimer::timeout,
this, [
this]() {
417 slowUpdate(m_nightTargetTemp);
422 int interval = availTime * TEMPERATURE_STEP / std::abs(targetTemp - m_currentTemp);
426 m_slowUpdateTimer->start(interval);
430void NightColorManager::slowUpdate(
int targetTemp)
432 if (!m_slowUpdateTimer) {
436 if (m_currentTemp < targetTemp) {
437 nextTemp = std::min(m_currentTemp + TEMPERATURE_STEP, targetTemp);
439 nextTemp = std::max(m_currentTemp - TEMPERATURE_STEP, targetTemp);
441 commitGammaRamps(nextTemp);
442 if (nextTemp == targetTemp) {
444 m_slowUpdateTimer.reset();
450 previewTemp = std::clamp<uint>(previewTemp, MIN_TEMPERATURE, DEFAULT_DAY_TEMPERATURE);
451 resetQuickAdjustTimer((
int)previewTemp);
452 if (m_previewTimer) {
453 m_previewTimer.reset();
455 m_previewTimer = std::make_unique<QTimer>();
456 m_previewTimer->setSingleShot(
true);
458 m_previewTimer->start(15000);
460 QDBusMessage message = QDBusMessage::createMethodCall(
461 QStringLiteral(
"org.kde.plasmashell"),
462 QStringLiteral(
"/org/kde/osdService"),
463 QStringLiteral(
"org.kde.osdService"),
464 QStringLiteral(
"showText"));
465 message.setArguments(
466 {QStringLiteral(
"redshift-status-on"),
467 i18n(
"Color Temperature Preview")});
468 QDBusConnection::sessionBus().asyncCall(message);
473 if (m_previewTimer && m_previewTimer->isActive()) {
474 updateTransitionTimings(
false);
475 updateTargetTemperature();
476 resetQuickAdjustTimer(currentTargetTemp());
480void NightColorManager::updateTargetTemperature()
493void NightColorManager::updateTransitionTimings(
bool force)
495 const auto oldPrev = m_prev;
496 const auto oldNext = m_next;
503 const QDateTime todayNow = QDateTime::currentDateTime();
505 const QDateTime nextMorB = QDateTime(todayNow.date().addDays(m_morning < todayNow.time()), m_morning);
506 const QDateTime nextMorE = nextMorB.addSecs(m_trTime * 60);
507 const QDateTime nextEveB = QDateTime(todayNow.date().addDays(m_evening < todayNow.time()), m_evening);
508 const QDateTime nextEveE = nextEveB.addSecs(m_trTime * 60);
510 if (nextEveB < nextMorB) {
513 m_prev =
DateTimes(nextMorB.addDays(-1), nextMorE.addDays(-1));
517 m_prev =
DateTimes(nextEveB.addDays(-1), nextEveE.addDays(-1));
520 const QDateTime todayNow = QDateTime::currentDateTime();
533 if (m_prev.first.date() == m_next.first.date()) {
537 m_next = getSunTimings(todayNow, lat, lng,
false);
542 m_next = getSunTimings(todayNow.addDays(1), lat, lng,
true);
546 if (force || !checkAutomaticSunTimings()) {
548 DateTimes morning = getSunTimings(todayNow, lat, lng,
true);
549 if (todayNow < morning.first) {
551 m_prev = getSunTimings(todayNow.addDays(-1), lat, lng,
false);
554 DateTimes evening = getSunTimings(todayNow, lat, lng,
false);
555 if (todayNow < evening.first) {
562 m_next = getSunTimings(todayNow.addDays(1), lat, lng,
true);
568 if (oldPrev != m_prev) {
571 if (oldNext != m_next) {
576DateTimes NightColorManager::getSunTimings(
const QDateTime &dateTime,
double latitude,
double longitude,
bool morning)
const
582 const bool beginDefined = !dateTimes.first.isNull();
583 const bool endDefined = !dateTimes.second.isNull();
584 if (!beginDefined || !endDefined) {
586 dateTimes.second = dateTimes.first.addMSecs(FALLBACK_SLOW_UPDATE_TIME);
587 }
else if (endDefined) {
588 dateTimes.first = dateTimes.second.addMSecs(-FALLBACK_SLOW_UPDATE_TIME);
593 const QTime referenceTime = morning ? QTime(6, 0) : QTime(18, 0);
594 dateTimes.first = QDateTime(dateTime.date(), referenceTime);
595 dateTimes.second = dateTimes.first.addMSecs(FALLBACK_SLOW_UPDATE_TIME);
601bool NightColorManager::checkAutomaticSunTimings()
const
603 if (m_prev.first.isValid() && m_prev.second.isValid() && m_next.first.isValid() && m_next.second.isValid()) {
604 const QDateTime todayNow = QDateTime::currentDateTime();
605 return m_prev.first <= todayNow && todayNow < m_next.first && m_prev.first.msecsTo(m_next.first) < MSC_DAY * 23. / 24;
615int NightColorManager::currentTargetTemp()
const
618 return DEFAULT_DAY_TEMPERATURE;
622 return m_nightTargetTemp;
625 const QDateTime todayNow = QDateTime::currentDateTime();
627 auto f = [
this, todayNow](
int target1,
int target2) {
628 if (todayNow <= m_prev.second) {
629 double residueQuota = todayNow.msecsTo(m_prev.second) / (double)m_prev.first.msecsTo(m_prev.second);
631 double ret = (int)((1. - residueQuota) * (double)target2 + residueQuota * (
double)target1);
633 ret = ((int)(0.1 * ret)) * 10;
641 return f(m_nightTargetTemp, m_dayTargetTemp);
643 return f(m_dayTargetTemp, m_nightTargetTemp);
647void NightColorManager::commitGammaRamps(
int temperature)
649 const QList<ColorDevice *> devices = kwinApp()->colorManager()->devices();
650 for (ColorDevice *device : devices) {
651 device->setTemperature(temperature);
654 setCurrentTemperature(temperature);
659 qCDebug(KWIN_NIGHTCOLOR,
"Received new location (lat: %f, lng: %f)", latitude, longitude);
661 if (!checkLocation(latitude, longitude)) {
666 if (std::abs(m_latAuto - latitude) < 2 && std::abs(m_lngAuto - longitude) < 1) {
670 m_latAuto = latitude;
671 m_lngAuto = longitude;
673 NightColorSettings *s = NightColorSettings::self();
674 s->setLatitudeAuto(latitude);
675 s->setLongitudeAuto(longitude);
681void NightColorManager::setEnabled(
bool enabled)
683 if (m_active == enabled) {
691void NightColorManager::setRunning(
bool running)
693 if (m_running == running) {
700void NightColorManager::setCurrentTemperature(
int temperature)
702 if (m_currentTemp == temperature) {
705 m_currentTemp = temperature;
711 if (m_mode ==
mode) {
718void NightColorManager::setDaylight(
bool daylight)
729#include "moc_nightcolormanager.cpp"
void setActive(bool active)
void deviceAdded(ColorDevice *device)
NightColorMode mode() const
static NightColorManager * self()
void targetTemperatureChanged()
QDateTime previousTransitionDateTime() const
qint64 previousTransitionDuration() const
int targetTemperature() const
void currentTemperatureChanged()
void scheduledTransitionTimingsChanged()
QDateTime scheduledTransitionDateTime() const
void quickAdjust(int targetTemp)
~NightColorManager() override
void autoLocationUpdate(double latitude, double longitude)
int currentTemperature() const
qint64 scheduledTransitionDuration() const
void resetSlowUpdateStartTimer()
void previousTransitionTimingsChanged()
void preview(uint previewTemp)
void activeChanged(bool active)
QPair< QDateTime, QDateTime > calculateSunTimings(const QDateTime &dateTime, double latitude, double longitude, bool morning)
QPair< QDateTime, QDateTime > DateTimes