1 /**
2 * Copyright (C) Francesco Fusco. All rights reserved.
3 * License: https://github.com/Fushko/gammy#license
4 */
5
6 #include <QTime>
7 #include <thread>
8 #include "gammactl.h"
9 #include "defs.h"
10 #include "utils.h"
11 #include "cfg.h"
12 #include "mediator.h"
13
GammaCtl()14 GammaCtl::GammaCtl()
15 {
16 // If auto brightness is on, start at max brightness
17 if (cfg["brt_auto"].get<bool>())
18 cfg["brt_step"] = brt_steps_max;
19
20 // If auto temp is on, start at max temp for a smooth transition
21 if (cfg["temp_auto"].get<bool>())
22 cfg["temp_step"] = 0;
23
24 setGamma(cfg["brt_step"].get<int>(), cfg["temp_step"].get<int>());
25 }
26
start()27 void GammaCtl::start()
28 {
29 LOGD << "Starting gamma control";
30
31 if (!threads.empty())
32 return;
33
34 threads.emplace_back(std::thread([this] { adjustTemperature(); }));
35 threads.emplace_back(std::thread([this] { captureScreen(); }));
36 threads.emplace_back(std::thread([this] { reapplyGamma(); }));
37 }
38
stop()39 void GammaCtl::stop()
40 {
41 LOGD << "Stopping gamma control";
42
43 if (threads.empty())
44 return;
45
46 quit = true;
47 notify_all_threads();
48
49 for (auto &t : threads)
50 t.join();
51
52 threads.clear();
53 }
54
notify_temp(bool force)55 void GammaCtl::notify_temp(bool force)
56 {
57 force_temp_change = force;
58 temp_cv.notify_one();
59 }
60
notify_ss()61 void GammaCtl::notify_ss()
62 {
63 ss_cv.notify_one();
64 }
65
notify_all_threads()66 void GammaCtl::notify_all_threads()
67 {
68 temp_cv.notify_one();
69 ss_cv.notify_one();
70 reapply_cv.notify_one();
71 }
72
reapplyGamma()73 void GammaCtl::reapplyGamma()
74 {
75 using namespace std::this_thread;
76 using namespace std::chrono;
77 using namespace std::chrono_literals;
78
79 std::mutex mtx;
80
81 while (true) {
82 {
83 std::unique_lock<std::mutex> lock(mtx);
84 reapply_cv.wait_until(lock, system_clock::now() + 5s, [&] {
85 return quit;
86 });
87 }
88
89 if (quit)
90 break;
91
92 setGamma(cfg["brt_step"], cfg["temp_step"]);
93 }
94 }
95
captureScreen()96 void GammaCtl::captureScreen()
97 {
98 LOGV << "captureScreen() start";
99
100 convar brt_cv;
101 std::thread brt_thr([&] { adjustBrightness(brt_cv); });
102 std::mutex m;
103
104 int img_delta = 0;
105 bool force = false;
106
107 int
108 prev_img_br = 0,
109 prev_min = 0,
110 prev_max = 0,
111 prev_offset = 0;
112
113 while (true) {
114 {
115 std::unique_lock<std::mutex> lock(m);
116
117 ss_cv.wait(lock, [&] {
118 return cfg["brt_auto"].get<bool>() || quit;
119 });
120 }
121
122 if (quit)
123 break;
124
125 if (cfg["brt_auto"].get<bool>())
126 force = true;
127 else
128 continue;
129
130 while (cfg["brt_auto"].get<bool>() && !quit) {
131
132 const int img_br = getScreenBrightness();
133 img_delta += abs(prev_img_br - img_br);
134
135 if (img_delta > cfg["brt_threshold"].get<int>() || force) {
136
137 img_delta = 0;
138 force = false;
139
140 {
141 std::lock_guard lock(brt_mtx);
142 this->ss_brightness = img_br;
143 br_needs_change = true;
144 }
145
146 brt_cv.notify_one();
147 }
148
149 if (cfg["brt_min"] != prev_min || cfg["brt_max"] != prev_max || cfg["brt_offset"] != prev_offset)
150 force = true;
151
152 prev_img_br = img_br;
153 prev_min = cfg["brt_min"];
154 prev_max = cfg["brt_max"];
155 prev_offset = cfg["brt_offset"];
156
157 // On Windows, we sleep in getScreenBrightness()
158 if constexpr (!windows) {
159 std::this_thread::sleep_for(std::chrono::milliseconds(cfg["brt_polling_rate"].get<int>()));
160 }
161 }
162 }
163
164 {
165 std::lock_guard<std::mutex> lock (brt_mtx);
166 br_needs_change = true;
167 }
168
169 brt_cv.notify_one();
170 brt_thr.join();
171 }
172
adjustBrightness(convar & brt_cv)173 void GammaCtl::adjustBrightness(convar &brt_cv)
174 {
175 using namespace std::this_thread;
176 using namespace std::chrono;
177
178 while (true) {
179 int img_br;
180
181 {
182 std::unique_lock<std::mutex> lock(brt_mtx);
183
184 brt_cv.wait(lock, [&] {
185 return br_needs_change;
186 });
187
188 if (quit)
189 break;
190
191 br_needs_change = false;
192 img_br = this->ss_brightness;
193 }
194
195 const int cur_step = cfg["brt_step"];
196 const int tmp = brt_steps_max
197 - int(remap(img_br, 0, 255, 0, brt_steps_max))
198 + int(remap(cfg["brt_offset"].get<int>(), 0, brt_steps_max, 0, cfg["brt_max"].get<int>()));
199 const int target_step = std::clamp(tmp, cfg["brt_min"].get<int>(), cfg["brt_max"].get<int>());
200
201 if (cur_step == target_step) {
202 LOGV << "Brt already at target (" << target_step << ')';
203 continue;
204 }
205
206 double time = 0;
207 const int FPS = cfg["brt_fps"];
208 const double slice = 1. / FPS;
209 const double duration_s = cfg["brt_speed"].get<double>() / 1000;
210 const int diff = target_step - cur_step;
211
212 while (cfg["brt_step"].get<int>() != target_step) {
213
214 if (br_needs_change || !cfg["brt_auto"].get<bool>() || quit)
215 break;
216
217 time += slice;
218 cfg["brt_step"] = int(std::round(easeOutExpo(time, cur_step, diff, duration_s)));
219
220 setGamma(cfg["brt_step"], cfg["temp_step"]);
221 mediator->notify(this, BRT_CHANGED);
222 sleep_for(milliseconds(1000 / FPS));
223 }
224 }
225 }
226
227 /**
228 * The temperature is adjusted in two steps.
229 * The first one is for quickly catching up to the proper temperature when:
230 * - time is checked sometime after the start time
231 * - the system wakes up
232 * - temperature settings change
233 */
adjustTemperature()234 void GammaCtl::adjustTemperature()
235 {
236 using namespace std::this_thread;
237 using namespace std::chrono;
238 using namespace std::chrono_literals;
239
240 QTime start_time;
241 QTime end_time;
242
243 const auto setTime = [] (QTime &t, const std::string &time_str) {
244 const auto start_h = time_str.substr(0, 2);
245 const auto start_m = time_str.substr(3, 2);
246 t = QTime(std::stoi(start_h), std::stoi(start_m));
247 };
248
249 const auto updateInterval = [&] {
250 std::string t_start = cfg["temp_sunset"];
251 const int h = std::stoi(t_start.substr(0, 2));
252 const int m = std::stoi(t_start.substr(3, 2));
253
254 const int adapt_time_s = cfg["temp_speed"].get<double>() * 60;
255 const QTime adapted_start = QTime(h, m).addSecs(-adapt_time_s);
256
257 setTime(start_time, adapted_start.toString().toStdString());
258 setTime(end_time, cfg["temp_sunrise"]);
259 };
260
261 updateInterval();
262
263 bool needs_change = cfg["temp_auto"];
264
265 convar clock_cv;
266 std::mutex clock_mtx;
267 std::mutex temp_mtx;
268
269 std::thread clock ([&] {
270 while (true) {
271 {
272 std::unique_lock<std::mutex> lk(clock_mtx);
273 clock_cv.wait_until(lk, system_clock::now() + 60s, [&] {
274 return quit;
275 });
276 }
277
278 if (quit)
279 break;
280
281 if (!cfg["temp_auto"])
282 continue;
283
284 {
285 std::lock_guard<std::mutex> lock(temp_mtx);
286 needs_change = true; // @TODO: Should be false if the state hasn't changed
287 }
288
289 temp_cv.notify_one();
290 }
291 });
292
293 bool first_step_done = false;
294
295 while (true) {
296 {
297 std::unique_lock<std::mutex> lock(temp_mtx);
298
299 temp_cv.wait(lock, [&] {
300 return needs_change || first_step_done || force_temp_change || quit;
301 });
302
303 if (quit)
304 break;
305
306 if (force_temp_change) {
307 updateInterval();
308 force_temp_change = false;
309 first_step_done = false;
310 }
311
312 needs_change = false;
313 }
314
315 if (!cfg["temp_auto"])
316 continue;
317
318
319 int target_temp = cfg["temp_low"]; // Temperature target in Kelvin
320 double duration_s = 2; // Seconds it takes to reach it
321
322 const double adapt_time_s = cfg["temp_speed"].get<double>() * 60;
323 const QDateTime cur_datetime = QDateTime::currentDateTime();
324 const QTime cur_time = cur_datetime.time();
325
326 if ((cur_time >= start_time) || (cur_time < end_time)) {
327
328 QDateTime start_datetime(cur_datetime.date(), start_time);
329
330 /* If we are earlier than both sunset and sunrise times
331 * we need to count from yesterday. */
332 if (cur_time < end_time)
333 start_datetime = start_datetime.addDays(-1);
334
335 int secs_from_start = start_datetime.secsTo(cur_datetime);
336
337 LOGV << "secs_from_start: " << secs_from_start << " adapt_time_s: " << adapt_time_s;
338
339 if (secs_from_start > adapt_time_s)
340 secs_from_start = adapt_time_s;
341
342 if (!first_step_done) {
343 target_temp = remap(secs_from_start, 0, adapt_time_s, cfg["temp_high"], cfg["temp_low"]);
344 } else {
345 duration_s = adapt_time_s - secs_from_start;
346 if (duration_s < 2)
347 duration_s = 2;
348 }
349 } else {
350 target_temp = cfg["temp_high"];
351 }
352
353 LOGV << "Temp duration: " << duration_s / 60 << " min";
354
355 int cur_step = cfg["temp_step"];
356 int target_step = int(remap(target_temp, temp_k_max, temp_k_min, temp_steps_max, 0));
357
358 if (cur_step == target_step) {
359 first_step_done = false;
360 continue;
361 }
362
363 double time = 0;
364 const int FPS = cfg["temp_fps"];
365 const double slice = 1. / FPS;
366 const int diff = target_step - cur_step;
367
368 while (cfg["temp_step"].get<int>() != target_step) {
369
370 if (force_temp_change || !cfg["temp_auto"].get<bool>() || quit)
371 break;
372
373 time += slice;
374 cfg["temp_step"] = int(easeInOutQuad(time, cur_step, diff, duration_s));
375
376 setGamma(cfg["brt_step"], cfg["temp_step"]);
377 mediator->notify(this, TEMP_CHANGED);
378 sleep_for(milliseconds(1000 / FPS));
379 }
380
381 first_step_done = true;
382 }
383
384 clock_cv.notify_one();
385 clock.join();
386 }
387