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