Changeset View
Changeset View
Standalone View
Standalone View
colorcorrection/manager.cpp
- This file was added.
1 | /******************************************************************** | ||||
---|---|---|---|---|---|
2 | KWin - the KDE window manager | ||||
3 | This file is part of the KDE project. | ||||
4 | | ||||
5 | Copyright 2017 Roman Gilg <subdiff@gmail.com> | ||||
6 | | ||||
7 | This program is free software; you can redistribute it and/or modify | ||||
8 | it under the terms of the GNU General Public License as published by | ||||
9 | the Free Software Foundation; either version 2 of the License, or | ||||
10 | (at your option) any later version. | ||||
11 | | ||||
12 | This program is distributed in the hope that it will be useful, | ||||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
15 | GNU General Public License for more details. | ||||
16 | | ||||
17 | You should have received a copy of the GNU General Public License | ||||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
19 | *********************************************************************/ | ||||
20 | #include "manager.h" | ||||
21 | #include "colorcorrectdbusinterface.h" | ||||
22 | #include "suncalc.h" | ||||
23 | #include "gammaramp.h" | ||||
24 | #include <colorcorrect_logging.h> | ||||
25 | | ||||
26 | #include <main.h> | ||||
27 | #include <platform.h> | ||||
28 | #include <screens.h> | ||||
29 | #include <workspace.h> | ||||
30 | #include <logind.h> | ||||
31 | | ||||
32 | #include <colorcorrect_settings.h> | ||||
33 | | ||||
34 | #include <QTimer> | ||||
35 | #include <QDBusConnection> | ||||
36 | #include <QSocketNotifier> | ||||
37 | | ||||
38 | #include <sys/timerfd.h> | ||||
39 | #include <unistd.h> | ||||
40 | #include <fcntl.h> | ||||
41 | | ||||
42 | namespace KWin { | ||||
43 | namespace ColorCorrect { | ||||
44 | | ||||
45 | static const int QUICK_ADJUST_DURATION = 2000; | ||||
46 | static const int TEMPERATURE_STEP = 50; | ||||
47 | | ||||
48 | static bool checkLocation(double lat, double lng) | ||||
49 | { | ||||
50 | return -90 <= lat && lat <= 90 && -180 <= lng && lng <= 180; | ||||
51 | } | ||||
52 | | ||||
53 | Manager::Manager(QObject *parent) | ||||
54 | : QObject(parent) | ||||
55 | { | ||||
56 | m_iface = new ColorCorrectDBusInterface(this); | ||||
57 | connect(kwinApp(), &Application::workspaceCreated, this, &Manager::init); | ||||
58 | } | ||||
59 | | ||||
60 | void Manager::init() | ||||
61 | { | ||||
62 | Settings::instance(kwinApp()->config()); | ||||
63 | // we may always read in the current config | ||||
64 | readConfig(); | ||||
65 | | ||||
66 | if (!kwinApp()->platform()->supportsGammaControl()) { | ||||
67 | // at least update the sun timings to make the values accessible via dbus | ||||
68 | updateSunTimings(true); | ||||
69 | return; | ||||
70 | } | ||||
71 | | ||||
72 | connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, | ||||
73 | [this](bool active) { | ||||
74 | if (active) { | ||||
75 | hardReset(); | ||||
76 | } else { | ||||
77 | cancelAllTimers(); | ||||
78 | } | ||||
79 | } | ||||
80 | ); | ||||
81 | | ||||
82 | #ifdef Q_OS_LINUX | ||||
83 | // monitor for system clock changes - from the time dataengine | ||||
84 | auto timeChangedFd = ::timerfd_create(CLOCK_REALTIME, O_CLOEXEC | O_NONBLOCK); | ||||
85 | ::itimerspec timespec; | ||||
86 | //set all timers to 0, which creates a timer that won't do anything | ||||
87 | ::memset(×pec, 0, sizeof(timespec)); | ||||
88 | | ||||
89 | // Monitor for the time changing (flags == TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET). | ||||
90 | // However these are not exposed in glibc so value is hardcoded: | ||||
91 | ::timerfd_settime(timeChangedFd, 3, ×pec, 0); | ||||
92 | | ||||
93 | connect(this, &QObject::destroyed, [timeChangedFd]() { | ||||
94 | ::close(timeChangedFd); | ||||
95 | }); | ||||
96 | | ||||
97 | auto notifier = new QSocketNotifier(timeChangedFd, QSocketNotifier::Read, this); | ||||
98 | connect(notifier, &QSocketNotifier::activated, this, [this](int fd) { | ||||
99 | uint64_t c; | ||||
100 | ::read(fd, &c, 8); | ||||
101 | | ||||
102 | // check if we're resuming from suspend - in this case do a hard reset | ||||
103 | // Note: We're using the time clock to detect a suspend phase instead of connecting to the | ||||
104 | // provided logind dbus signal, because this signal would be received way too late. | ||||
105 | QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.login1", | ||||
106 | "/org/freedesktop/login1", | ||||
107 | "org.freedesktop.DBus.Properties", | ||||
108 | QStringLiteral("Get")); | ||||
109 | message.setArguments(QVariantList({"org.freedesktop.login1.Manager", QStringLiteral("PreparingForSleep")})); | ||||
110 | QDBusReply<QVariant> reply = QDBusConnection::systemBus().call(message); | ||||
111 | bool comingFromSuspend; | ||||
112 | if (reply.isValid()) { | ||||
113 | comingFromSuspend = reply.value().toBool(); | ||||
114 | } else { | ||||
115 | qCDebug(KWIN_COLORCORRECTION) << "Failed to get PreparingForSleep Property of logind session:" << reply.error().message(); | ||||
116 | // Always do a hard reset in case we have no further information. | ||||
117 | comingFromSuspend = true; | ||||
118 | } | ||||
119 | | ||||
120 | if (comingFromSuspend) { | ||||
121 | hardReset(); | ||||
122 | } else { | ||||
123 | resetAllTimers(); | ||||
124 | } | ||||
125 | }); | ||||
126 | #else | ||||
127 | // TODO: Alternative method for BSD. | ||||
128 | #endif | ||||
129 | | ||||
130 | hardReset(); | ||||
131 | } | ||||
132 | | ||||
133 | void Manager::hardReset() | ||||
134 | { | ||||
135 | cancelAllTimers(); | ||||
136 | updateSunTimings(true); | ||||
137 | if (kwinApp()->platform()->supportsGammaControl() && m_active) { | ||||
138 | m_running = true; | ||||
139 | commitGammaRamps(currentTargetTemp()); | ||||
140 | } | ||||
141 | resetAllTimers(); | ||||
142 | } | ||||
143 | | ||||
144 | void Manager::reparseConfigAndReset() | ||||
145 | { | ||||
146 | cancelAllTimers(); | ||||
147 | readConfig(); | ||||
148 | hardReset(); | ||||
149 | } | ||||
150 | | ||||
151 | void Manager::readConfig() | ||||
152 | { | ||||
153 | Settings *s = Settings::self(); | ||||
154 | s->load(); | ||||
155 | | ||||
156 | m_active = s->active(); | ||||
157 | m_mode = s->mode(); | ||||
158 | m_nightTargetTemp = qBound(MIN_TEMPERATURE, s->nightTemperature(), NEUTRAL_TEMPERATURE); | ||||
159 | | ||||
160 | double lat, lng; | ||||
161 | auto correctReadin = [&lat, &lng]() { | ||||
162 | if (!checkLocation(lat, lng)) { | ||||
163 | // out of domain | ||||
164 | lat = 0; | ||||
165 | lng = 0; | ||||
166 | } | ||||
167 | }; | ||||
168 | // automatic | ||||
169 | lat = s->latitudeAuto(); | ||||
170 | lng = s->longitudeAuto(); | ||||
171 | correctReadin(); | ||||
172 | m_latAuto = lat; | ||||
173 | m_lngAuto = lng; | ||||
174 | // fixed location | ||||
175 | lat = s->latitudeFixed(); | ||||
176 | lng = s->longitudeFixed(); | ||||
177 | correctReadin(); | ||||
178 | m_latFixed = lat; | ||||
179 | m_lngFixed = lng; | ||||
180 | | ||||
181 | // fixed timings | ||||
182 | QTime mrB = QTime::fromString(s->morningBeginFixed(), "hhmm"); | ||||
183 | QTime evB = QTime::fromString(s->eveningBeginFixed(), "hhmm"); | ||||
184 | | ||||
185 | int diffME = mrB.msecsTo(evB); | ||||
186 | if (diffME <= 0) { | ||||
187 | // morning not strictly before evening - use defaults | ||||
188 | mrB = QTime(6,0); | ||||
189 | evB = QTime(18,0); | ||||
190 | diffME = mrB.msecsTo(evB); | ||||
191 | } | ||||
192 | int diffMin = qMin(diffME, MSC_DAY - diffME); | ||||
193 | | ||||
194 | int trTime = s->transitionTime() * 1000 * 60; | ||||
195 | if (trTime < 0 || diffMin <= trTime) { | ||||
196 | // transition time too long - use defaults | ||||
197 | mrB = QTime(6,0); | ||||
198 | evB = QTime(18,0); | ||||
199 | trTime = FALLBACK_SLOW_UPDATE_TIME; | ||||
200 | } | ||||
201 | m_morning = mrB; | ||||
202 | m_evening = evB; | ||||
203 | m_trTime = qMax(trTime / 1000 / 60, 1); | ||||
204 | } | ||||
205 | | ||||
206 | void Manager::resetAllTimers() | ||||
207 | { | ||||
208 | cancelAllTimers(); | ||||
209 | if (kwinApp()->platform()->supportsGammaControl()) { | ||||
210 | if (m_active) { | ||||
211 | m_running = true; | ||||
212 | } | ||||
213 | // we do this also for active being false in order to reset the temperature back to the day value | ||||
214 | resetQuickAdjustTimer(); | ||||
215 | } else { | ||||
216 | m_running = false; | ||||
217 | } | ||||
218 | } | ||||
219 | | ||||
220 | void Manager::cancelAllTimers() | ||||
221 | { | ||||
222 | delete m_slowUpdateStartTimer; | ||||
223 | delete m_slowUpdateTimer; | ||||
224 | delete m_quickAdjustTimer; | ||||
225 | | ||||
226 | m_slowUpdateStartTimer = nullptr; | ||||
227 | m_slowUpdateTimer = nullptr; | ||||
228 | m_quickAdjustTimer = nullptr; | ||||
229 | } | ||||
230 | | ||||
231 | void Manager::resetQuickAdjustTimer() | ||||
232 | { | ||||
233 | updateSunTimings(false); | ||||
234 | | ||||
235 | int tempDiff = qAbs(currentTargetTemp() - m_currentTemp); | ||||
236 | // allow tolerance of one TEMPERATURE_STEP to compensate if a slow update is coincidental | ||||
237 | if (tempDiff > TEMPERATURE_STEP) { | ||||
238 | cancelAllTimers(); | ||||
239 | m_quickAdjustTimer = new QTimer(this); | ||||
240 | m_quickAdjustTimer->setSingleShot(false); | ||||
241 | connect(m_quickAdjustTimer, &QTimer::timeout, this, &Manager::quickAdjust); | ||||
242 | | ||||
243 | int interval = QUICK_ADJUST_DURATION / (tempDiff / TEMPERATURE_STEP); | ||||
244 | if (interval == 0) { | ||||
245 | interval = 1; | ||||
246 | } | ||||
247 | m_quickAdjustTimer->start(interval); | ||||
248 | } else { | ||||
249 | resetSlowUpdateStartTimer(); | ||||
250 | } | ||||
251 | } | ||||
252 | | ||||
253 | void Manager::quickAdjust() | ||||
254 | { | ||||
255 | if (!m_quickAdjustTimer) { | ||||
256 | return; | ||||
257 | } | ||||
258 | | ||||
259 | int nextTemp; | ||||
260 | int targetTemp = currentTargetTemp(); | ||||
261 | | ||||
262 | if (m_currentTemp < targetTemp) { | ||||
263 | nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); | ||||
264 | } else { | ||||
265 | nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); | ||||
266 | } | ||||
267 | commitGammaRamps(nextTemp); | ||||
268 | | ||||
269 | if (nextTemp == targetTemp) { | ||||
270 | // stop timer, we reached the target temp | ||||
271 | delete m_quickAdjustTimer; | ||||
272 | m_quickAdjustTimer = nullptr; | ||||
273 | resetSlowUpdateStartTimer(); | ||||
274 | } | ||||
275 | } | ||||
276 | | ||||
277 | void Manager::resetSlowUpdateStartTimer() | ||||
278 | { | ||||
279 | delete m_slowUpdateStartTimer; | ||||
280 | m_slowUpdateStartTimer = nullptr; | ||||
281 | | ||||
282 | if (!m_running || m_quickAdjustTimer) { | ||||
283 | // only reenable the slow update start timer when quick adjust is not active anymore | ||||
284 | return; | ||||
285 | } | ||||
286 | | ||||
287 | // set up the next slow update | ||||
288 | m_slowUpdateStartTimer = new QTimer(this); | ||||
289 | m_slowUpdateStartTimer->setSingleShot(true); | ||||
290 | connect(m_slowUpdateStartTimer, &QTimer::timeout, this, &Manager::resetSlowUpdateStartTimer); | ||||
291 | | ||||
292 | updateSunTimings(false); | ||||
293 | int diff; | ||||
294 | if (m_mode == NightColorMode::Timings) { | ||||
295 | // Timings mode is in local time | ||||
296 | diff = QDateTime::currentDateTime().msecsTo(m_next.first); | ||||
297 | } else { | ||||
298 | diff = QDateTime::currentDateTimeUtc().msecsTo(m_next.first); | ||||
299 | } | ||||
300 | if (diff <= 0) { | ||||
301 | qCCritical(KWIN_COLORCORRECTION) << "Error in time calculation. Deactivating Night Color."; | ||||
302 | return; | ||||
303 | } | ||||
304 | m_slowUpdateStartTimer->start(diff); | ||||
305 | | ||||
306 | // start the current slow update | ||||
307 | resetSlowUpdateTimer(); | ||||
308 | } | ||||
309 | | ||||
310 | void Manager::resetSlowUpdateTimer() | ||||
311 | { | ||||
312 | delete m_slowUpdateTimer; | ||||
313 | m_slowUpdateTimer = nullptr; | ||||
314 | | ||||
315 | QDateTime now = QDateTime::currentDateTimeUtc(); | ||||
316 | bool isDay = daylight(); | ||||
317 | int targetTemp = isDay ? m_dayTargetTemp : m_nightTargetTemp; | ||||
318 | | ||||
319 | if (m_prev.first == m_prev.second) { | ||||
320 | // transition time is zero | ||||
321 | commitGammaRamps(isDay ? m_dayTargetTemp : m_nightTargetTemp); | ||||
322 | return; | ||||
323 | } | ||||
324 | | ||||
325 | if (m_prev.first <= now && now <= m_prev.second) { | ||||
326 | int availTime = now.msecsTo(m_prev.second); | ||||
327 | m_slowUpdateTimer = new QTimer(this); | ||||
328 | m_slowUpdateTimer->setSingleShot(false); | ||||
329 | if (isDay) { | ||||
330 | connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_dayTargetTemp);}); | ||||
331 | } else { | ||||
332 | connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_nightTargetTemp);}); | ||||
333 | } | ||||
334 | | ||||
335 | // calculate interval such as temperature is changed by TEMPERATURE_STEP K per timer timeout | ||||
336 | int interval = availTime / (qAbs(targetTemp - m_currentTemp) / TEMPERATURE_STEP); | ||||
337 | if (interval == 0) { | ||||
338 | interval = 1; | ||||
339 | } | ||||
340 | m_slowUpdateTimer->start(interval); | ||||
341 | } | ||||
342 | } | ||||
343 | | ||||
344 | void Manager::slowUpdate(int targetTemp) | ||||
345 | { | ||||
346 | if (!m_slowUpdateTimer) { | ||||
347 | return; | ||||
348 | } | ||||
349 | int nextTemp; | ||||
350 | if (m_currentTemp < targetTemp) { | ||||
351 | nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); | ||||
352 | } else { | ||||
353 | nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); | ||||
354 | } | ||||
355 | commitGammaRamps(nextTemp); | ||||
356 | if (nextTemp == targetTemp) { | ||||
357 | // stop timer, we reached the target temp | ||||
358 | delete m_slowUpdateTimer; | ||||
359 | m_slowUpdateTimer = nullptr; | ||||
360 | } | ||||
361 | } | ||||
362 | | ||||
363 | void Manager::updateSunTimings(bool force) | ||||
364 | { | ||||
365 | QDateTime todayNow = QDateTime::currentDateTimeUtc(); | ||||
366 | | ||||
367 | if (m_mode == NightColorMode::Timings) { | ||||
368 | | ||||
369 | QDateTime todayNowLocal = QDateTime::currentDateTime(); | ||||
370 | | ||||
371 | QDateTime morB = QDateTime(todayNowLocal.date(), m_morning); | ||||
372 | QDateTime morE = morB.addSecs(m_trTime * 60); | ||||
373 | QDateTime eveB = QDateTime(todayNowLocal.date(), m_evening); | ||||
374 | QDateTime eveE = eveB.addSecs(m_trTime * 60); | ||||
375 | | ||||
376 | if (morB <= todayNowLocal && todayNowLocal < eveB) { | ||||
377 | m_next = DateTimes(eveB, eveE); | ||||
378 | m_prev = DateTimes(morB, morE); | ||||
379 | } else if (todayNowLocal < morB) { | ||||
380 | m_next = DateTimes(morB, morE); | ||||
381 | m_prev = DateTimes(eveB.addDays(-1), eveE.addDays(-1)); | ||||
382 | } else { | ||||
383 | m_next = DateTimes(morB.addDays(1), morE.addDays(1)); | ||||
384 | m_prev = DateTimes(eveB, eveE); | ||||
385 | } | ||||
386 | return; | ||||
387 | } | ||||
388 | | ||||
389 | double lat, lng; | ||||
390 | if (m_mode == NightColorMode::Automatic) { | ||||
391 | lat = m_latAuto; | ||||
392 | lng = m_lngAuto; | ||||
393 | } else { | ||||
394 | lat = m_latFixed; | ||||
395 | lng = m_lngFixed; | ||||
396 | } | ||||
397 | | ||||
398 | if (!force) { | ||||
399 | // first try by only switching the timings | ||||
400 | if (daylight()) { | ||||
401 | // next is morning | ||||
402 | m_prev = m_next; | ||||
403 | m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); | ||||
404 | } else { | ||||
405 | // next is evening | ||||
406 | m_prev = m_next; | ||||
407 | m_next = getSunTimings(todayNow.date(), lat, lng, false); | ||||
408 | } | ||||
409 | } | ||||
410 | | ||||
411 | if (force || !checkAutomaticSunTimings()) { | ||||
412 | // in case this fails, reset them | ||||
413 | DateTimes morning = getSunTimings(todayNow.date(), lat, lng, true); | ||||
414 | if (todayNow < morning.first) { | ||||
415 | m_prev = getSunTimings(todayNow.date().addDays(-1), lat, lng, false); | ||||
416 | m_next = morning; | ||||
417 | } else { | ||||
418 | DateTimes evening = getSunTimings(todayNow.date(), lat, lng, false); | ||||
419 | if (todayNow < evening.first) { | ||||
420 | m_prev = morning; | ||||
421 | m_next = evening; | ||||
422 | } else { | ||||
423 | m_prev = evening; | ||||
424 | m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); | ||||
425 | } | ||||
426 | } | ||||
427 | } | ||||
428 | } | ||||
429 | | ||||
430 | DateTimes Manager::getSunTimings(QDate date, double latitude, double longitude, bool morning) const | ||||
431 | { | ||||
432 | Times times = calculateSunTimings(date, latitude, longitude, morning); | ||||
433 | // At locations near the poles it is possible, that we can't | ||||
434 | // calculate some or all sun timings (midnight sun). | ||||
435 | // In this case try to fallback to sensible default values. | ||||
436 | bool beginDefined = !times.first.isNull(); | ||||
437 | bool endDefined = !times.second.isNull(); | ||||
438 | if (!beginDefined || !endDefined) { | ||||
439 | if (beginDefined) { | ||||
440 | times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); | ||||
441 | } else if (endDefined) { | ||||
442 | times.first = times.second.addMSecs( - FALLBACK_SLOW_UPDATE_TIME); | ||||
443 | } else { | ||||
444 | // Just use default values for morning and evening, but the user | ||||
445 | // will probably deactivate Night Color anyway if he is living | ||||
446 | // in a region without clear sun rise and set. | ||||
447 | times.first = morning ? QTime(6,0,0) : QTime(18,0,0); | ||||
448 | times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); | ||||
449 | } | ||||
450 | } | ||||
451 | return DateTimes(QDateTime(date, times.first, Qt::UTC), QDateTime(date, times.second, Qt::UTC)); | ||||
452 | } | ||||
453 | | ||||
454 | bool Manager::checkAutomaticSunTimings() const | ||||
455 | { | ||||
456 | if (m_prev.first.isValid() && m_prev.second.isValid() && | ||||
457 | m_next.first.isValid() && m_next.second.isValid()) { | ||||
458 | QDateTime todayNow = QDateTime::currentDateTimeUtc(); | ||||
459 | return m_prev.first <= todayNow && todayNow < m_next.first && | ||||
460 | m_prev.first.msecsTo(m_next.first) < MSC_DAY * 23./24; | ||||
461 | } | ||||
462 | return false; | ||||
463 | } | ||||
464 | | ||||
465 | bool Manager::daylight() const | ||||
466 | { | ||||
467 | return m_prev.first.date() == m_next.first.date(); | ||||
468 | } | ||||
469 | | ||||
470 | int Manager::currentTargetTemp() const | ||||
471 | { | ||||
472 | if (!m_active) { | ||||
473 | return NEUTRAL_TEMPERATURE; | ||||
474 | } | ||||
475 | | ||||
476 | QDateTime todayNow = QDateTime::currentDateTimeUtc(); | ||||
477 | | ||||
478 | auto f = [this, todayNow](int target1, int target2) { | ||||
479 | if (todayNow <= m_prev.second) { | ||||
480 | double residueQuota = todayNow.msecsTo(m_prev.second) / (double)m_prev.first.msecsTo(m_prev.second); | ||||
481 | | ||||
482 | double ret = (int)((1. - residueQuota) * (double)target2 + residueQuota * (double)target1); | ||||
483 | // remove single digits | ||||
484 | ret = ((int)(0.1 * ret)) * 10; | ||||
485 | return (int)ret; | ||||
486 | } else { | ||||
487 | return target2; | ||||
488 | } | ||||
489 | }; | ||||
490 | | ||||
491 | if (daylight()) { | ||||
492 | return f(m_nightTargetTemp, m_dayTargetTemp); | ||||
493 | } else { | ||||
494 | return f(m_dayTargetTemp, m_nightTargetTemp); | ||||
495 | } | ||||
496 | } | ||||
497 | | ||||
498 | void Manager::commitGammaRamps(int temperature) | ||||
499 | { | ||||
500 | int nscreens = Screens::self()->count(); | ||||
501 | | ||||
502 | for (int screen = 0; screen < nscreens; screen++) { | ||||
503 | int rampsize = kwinApp()->platform()->gammaRampSize(screen); | ||||
504 | GammaRamp ramp(rampsize); | ||||
505 | | ||||
506 | /* | ||||
507 | * The gamma calculation below is based on the Redshift app: | ||||
508 | * https://github.com/jonls/redshift | ||||
509 | */ | ||||
510 | | ||||
511 | // linear default state | ||||
512 | for (int i = 0; i < rampsize; i++) { | ||||
513 | uint16_t value = (double)i / rampsize * (UINT16_MAX + 1); | ||||
514 | ramp.red[i] = value; | ||||
515 | ramp.green[i] = value; | ||||
516 | ramp.blue[i] = value; | ||||
517 | } | ||||
518 | | ||||
519 | // approximate white point | ||||
520 | float whitePoint[3]; | ||||
521 | float alpha = (temperature % 100) / 100.; | ||||
522 | int bbCIndex = ((temperature - 1000) / 100) * 3; | ||||
523 | whitePoint[0] = (1. - alpha) * blackbodyColor[bbCIndex] + alpha * blackbodyColor[bbCIndex + 3]; | ||||
524 | whitePoint[1] = (1. - alpha) * blackbodyColor[bbCIndex + 1] + alpha * blackbodyColor[bbCIndex + 4]; | ||||
525 | whitePoint[2] = (1. - alpha) * blackbodyColor[bbCIndex + 2] + alpha * blackbodyColor[bbCIndex + 5]; | ||||
526 | | ||||
527 | for (int i = 0; i < rampsize; i++) { | ||||
528 | ramp.red[i] = (double)ramp.red[i] / (UINT16_MAX+1) * whitePoint[0] * (UINT16_MAX+1); | ||||
529 | ramp.green[i] = (double)ramp.green[i] / (UINT16_MAX+1) * whitePoint[1] * (UINT16_MAX+1); | ||||
530 | ramp.blue[i] = (double)ramp.blue[i] / (UINT16_MAX+1) * whitePoint[2] * (UINT16_MAX+1); | ||||
531 | } | ||||
532 | | ||||
533 | if (kwinApp()->platform()->setGammaRamp(screen, ramp)) { | ||||
534 | m_currentTemp = temperature; | ||||
535 | m_failedCommitAttempts = 0; | ||||
536 | } else { | ||||
537 | m_failedCommitAttempts++; | ||||
538 | if (m_failedCommitAttempts < 10) { | ||||
539 | qCWarning(KWIN_COLORCORRECTION).nospace() << "Committing Gamma Ramp failed for screen " << screen << | ||||
540 | ". Trying " << (10 - m_failedCommitAttempts) << " times more."; | ||||
541 | } else { | ||||
542 | // TODO: On multi monitor setups we could try to rollback earlier changes for already commited outputs | ||||
543 | qCWarning(KWIN_COLORCORRECTION) << "Gamma Ramp commit failed too often. Deactivating color correction for now."; | ||||
544 | m_failedCommitAttempts = 0; // reset so we can try again later (i.e. after suspend phase or config change) | ||||
545 | m_running = false; | ||||
546 | cancelAllTimers(); | ||||
547 | } | ||||
548 | } | ||||
549 | } | ||||
550 | } | ||||
551 | | ||||
552 | QHash<QString, QVariant> Manager::info() const | ||||
553 | { | ||||
554 | return QHash<QString, QVariant> { | ||||
555 | { QStringLiteral("Available"), kwinApp()->platform()->supportsGammaControl() }, | ||||
556 | | ||||
557 | { QStringLiteral("ActiveEnabled"), true}, | ||||
558 | { QStringLiteral("Active"), m_active}, | ||||
559 | | ||||
560 | { QStringLiteral("ModeEnabled"), true}, | ||||
561 | { QStringLiteral("Mode"), (int)m_mode}, | ||||
562 | | ||||
563 | { QStringLiteral("NightTemperatureEnabled"), true}, | ||||
564 | { QStringLiteral("NightTemperature"), m_nightTargetTemp}, | ||||
565 | | ||||
566 | { QStringLiteral("Running"), m_running}, | ||||
567 | { QStringLiteral("CurrentColorTemperature"), m_currentTemp}, | ||||
568 | | ||||
569 | { QStringLiteral("LatitudeAuto"), m_latAuto}, | ||||
570 | { QStringLiteral("LongitudeAuto"), m_lngAuto}, | ||||
571 | | ||||
572 | { QStringLiteral("LocationEnabled"), true}, | ||||
573 | { QStringLiteral("LatitudeFixed"), m_latFixed}, | ||||
574 | { QStringLiteral("LongitudeFixed"), m_lngFixed}, | ||||
575 | | ||||
576 | { QStringLiteral("TimingsEnabled"), true}, | ||||
577 | { QStringLiteral("MorningBeginFixed"), m_morning.toString(Qt::ISODate)}, | ||||
578 | { QStringLiteral("EveningBeginFixed"), m_evening.toString(Qt::ISODate)}, | ||||
579 | { QStringLiteral("TransitionTime"), m_trTime}, | ||||
580 | }; | ||||
581 | } | ||||
582 | | ||||
583 | bool Manager::changeConfiguration(QHash<QString, QVariant> data) | ||||
584 | { | ||||
585 | bool activeUpdate, modeUpdate, tempUpdate, locUpdate, timeUpdate; | ||||
586 | activeUpdate = modeUpdate = tempUpdate = locUpdate = timeUpdate = false; | ||||
587 | | ||||
588 | bool active = m_active; | ||||
589 | NightColorMode mode = m_mode; | ||||
590 | int nightT = m_nightTargetTemp; | ||||
591 | | ||||
592 | double lat = m_latFixed; | ||||
593 | double lng = m_lngFixed; | ||||
594 | | ||||
595 | QTime mor = m_morning; | ||||
596 | QTime eve = m_evening; | ||||
597 | int trT = m_trTime; | ||||
598 | | ||||
599 | QHash<QString, QVariant>::const_iterator iter1, iter2, iter3; | ||||
600 | | ||||
601 | iter1 = data.constFind("Active"); | ||||
602 | if (iter1 != data.constEnd()) { | ||||
603 | if (!iter1.value().canConvert<bool>()) { | ||||
604 | return false; | ||||
605 | } | ||||
606 | bool act = iter1.value().toBool(); | ||||
607 | activeUpdate = m_active != act; | ||||
608 | active = act; | ||||
609 | } | ||||
610 | | ||||
611 | iter1 = data.constFind("Mode"); | ||||
612 | if (iter1 != data.constEnd()) { | ||||
613 | if (!iter1.value().canConvert<int>()) { | ||||
614 | return false; | ||||
615 | } | ||||
616 | int mo = iter1.value().toInt(); | ||||
617 | if (mo < 0 || 2 < mo) { | ||||
618 | return false; | ||||
619 | } | ||||
620 | NightColorMode moM; | ||||
621 | switch (mo) { | ||||
622 | case 0: | ||||
623 | moM = NightColorMode::Automatic; | ||||
624 | break; | ||||
625 | case 1: | ||||
626 | moM = NightColorMode::Location; | ||||
627 | break; | ||||
628 | case 2: | ||||
629 | moM = NightColorMode::Timings; | ||||
630 | } | ||||
631 | modeUpdate = m_mode != moM; | ||||
632 | mode = moM; | ||||
633 | } | ||||
634 | | ||||
635 | iter1 = data.constFind("NightTemperature"); | ||||
636 | if (iter1 != data.constEnd()) { | ||||
637 | if (!iter1.value().canConvert<int>()) { | ||||
638 | return false; | ||||
639 | } | ||||
640 | int nT = iter1.value().toInt(); | ||||
641 | if (nT < MIN_TEMPERATURE || NEUTRAL_TEMPERATURE < nT) { | ||||
642 | return false; | ||||
643 | } | ||||
644 | tempUpdate = m_nightTargetTemp != nT; | ||||
645 | nightT = nT; | ||||
646 | } | ||||
647 | | ||||
648 | iter1 = data.constFind("LatitudeFixed"); | ||||
649 | iter2 = data.constFind("LongitudeFixed"); | ||||
650 | if (iter1 != data.constEnd() && iter2 != data.constEnd()) { | ||||
651 | if (!iter1.value().canConvert<double>() || !iter2.value().canConvert<double>()) { | ||||
652 | return false; | ||||
653 | } | ||||
654 | double la = iter1.value().toDouble(); | ||||
655 | double ln = iter2.value().toDouble(); | ||||
656 | if (!checkLocation(la, ln)) { | ||||
657 | return false; | ||||
658 | } | ||||
659 | locUpdate = m_latFixed != la || m_lngFixed != ln; | ||||
660 | lat = la; | ||||
661 | lng = ln; | ||||
662 | } | ||||
663 | | ||||
664 | iter1 = data.constFind("MorningBeginFixed"); | ||||
665 | iter2 = data.constFind("EveningBeginFixed"); | ||||
666 | iter3 = data.constFind("TransitionTime"); | ||||
667 | if (iter1 != data.constEnd() && iter2 != data.constEnd() && iter3 != data.constEnd()) { | ||||
668 | if (!iter1.value().canConvert<QString>() || !iter2.value().canConvert<QString>() || !iter3.value().canConvert<int>()) { | ||||
669 | return false; | ||||
670 | } | ||||
671 | QTime mo = QTime::fromString(iter1.value().toString(), Qt::ISODate); | ||||
672 | QTime ev = QTime::fromString(iter2.value().toString(), Qt::ISODate); | ||||
673 | if (!mo.isValid() || !ev.isValid()) { | ||||
674 | return false; | ||||
675 | } | ||||
676 | int tT = iter3.value().toInt(); | ||||
677 | | ||||
678 | int diffME = mo.msecsTo(ev); | ||||
679 | if (diffME <= 0 || qMin(diffME, MSC_DAY - diffME) <= tT * 60 * 1000 || tT < 1) { | ||||
680 | // morning not strictly before evening, transition time too long or transition time out of bounds | ||||
681 | return false; | ||||
682 | } | ||||
683 | | ||||
684 | timeUpdate = m_morning != mo || m_evening != ev || m_trTime != tT; | ||||
685 | mor = mo; | ||||
686 | eve = ev; | ||||
687 | trT = tT; | ||||
688 | } | ||||
689 | | ||||
690 | if (!(activeUpdate || modeUpdate || tempUpdate || locUpdate || timeUpdate)) { | ||||
691 | return true; | ||||
692 | } | ||||
693 | | ||||
694 | bool resetNeeded = activeUpdate || modeUpdate || tempUpdate || | ||||
695 | (locUpdate && mode == NightColorMode::Location) || | ||||
696 | (timeUpdate && mode == NightColorMode::Timings); | ||||
697 | | ||||
698 | if (resetNeeded) { | ||||
699 | cancelAllTimers(); | ||||
700 | } | ||||
701 | | ||||
702 | Settings *s = Settings::self(); | ||||
703 | if (activeUpdate) { | ||||
704 | m_active = active; | ||||
705 | s->setActive(active); | ||||
706 | } | ||||
707 | | ||||
708 | if (modeUpdate) { | ||||
709 | m_mode = mode; | ||||
710 | s->setMode(mode); | ||||
711 | } | ||||
712 | | ||||
713 | if (tempUpdate) { | ||||
714 | m_nightTargetTemp = nightT; | ||||
715 | s->setNightTemperature(nightT); | ||||
716 | } | ||||
717 | | ||||
718 | if (locUpdate) { | ||||
719 | m_latFixed = lat; | ||||
720 | m_lngFixed = lng; | ||||
721 | s->setLatitudeFixed(lat); | ||||
722 | s->setLongitudeFixed(lng); | ||||
723 | } | ||||
724 | | ||||
725 | if (timeUpdate) { | ||||
726 | m_morning = mor; | ||||
727 | m_evening = eve; | ||||
728 | m_trTime = trT; | ||||
729 | s->setMorningBeginFixed(mor.toString("hhmm")); | ||||
730 | s->setEveningBeginFixed(eve.toString("hhmm")); | ||||
731 | s->setTransitionTime(trT); | ||||
732 | } | ||||
733 | s->save(); | ||||
734 | | ||||
735 | if (resetNeeded) { | ||||
736 | resetAllTimers(); | ||||
737 | } | ||||
738 | emit configChange(info()); | ||||
739 | return true; | ||||
740 | } | ||||
741 | | ||||
742 | void Manager::autoLocationUpdate(double latitude, double longitude) | ||||
743 | { | ||||
744 | if (!checkLocation(latitude, longitude)) { | ||||
745 | return; | ||||
746 | } | ||||
747 | | ||||
748 | // we tolerate small deviations with minimal impact on sun timings | ||||
749 | if (qAbs(m_latAuto - latitude) < 2 && qAbs(m_lngAuto - longitude) < 1) { | ||||
750 | return; | ||||
751 | } | ||||
752 | cancelAllTimers(); | ||||
753 | m_latAuto = latitude; | ||||
754 | m_lngAuto = longitude; | ||||
755 | | ||||
756 | Settings *s = Settings::self(); | ||||
757 | s->setLatitudeAuto(latitude); | ||||
758 | s->setLongitudeAuto(longitude); | ||||
759 | s->save(); | ||||
760 | | ||||
761 | resetAllTimers(); | ||||
762 | emit configChange(info()); | ||||
763 | } | ||||
764 | | ||||
765 | } | ||||
766 | } |