1 /**
2 * Copyright (C) Francesco Fusco. All rights reserved.
3 * License: https://github.com/Fushko/gammy#license
4 */
5
6 #ifdef _WIN32
7 #include <Windows.h>
8 #endif
9
10 #include <QScreen>
11 #include <QMenu>
12 #include <QtDBus/QDBusInterface>
13 #include <QtDBus/QDBusConnection>
14 #include <QShortcut>
15 #include "cfg.h"
16 #include "mainwindow.h"
17 #include "ui_mainwindow.h"
18 #include "tempscheduler.h"
19
MainWindow()20 MainWindow::MainWindow(): ui(new Ui::MainWindow), tray_icon(new QSystemTrayIcon(this))
21 {
22 tray_wnd_toggle = new QAction(cfg["wnd_show_on_startup"].get<bool>() ? hide_txt : show_txt , this);
23 tray_brt_toggle = new QAction("Auto brightness", this);
24 tray_brt_toggle->setCheckable(true);
25 tray_temp_toggle = new QAction("Auto temperature", this);
26 tray_temp_toggle->setCheckable(true);
27
28 new QShortcut(QKeySequence(Qt::Key_Escape), this, SLOT(close()));
29
30 ui->setupUi(this);
31 }
32
init()33 void MainWindow::init()
34 {
35 if (!windows && !listenWakeupSignal()) {
36 LOGE << "Gammy is unable to reset the proper brightness / temperature when resuming from suspend.";
37 }
38
39 setLabels();
40 setSliders();
41 toggleBrtSliders(cfg["brt_auto"]);
42 ui->autoBrtCheck->setChecked(cfg["brt_auto"]);
43 ui->autoTempCheck->setChecked(cfg["temp_auto"]);
44
45 QIcon icon = QIcon(":res/icons/gammy.ico");
46 createTrayIcon(icon);
47 setWindowProperties(icon);
48
49 connect(QApplication::instance(), &QApplication::aboutToQuit, this, &MainWindow::shutdown);
50 }
51
setLabels()52 void MainWindow::setLabels()
53 {
54 ui->brtLabel->setText(QStringLiteral("%1 %").arg(int(remap(cfg["brt_step"].get<int>(), 0, brt_steps_max, 0, 100))));
55 ui->minBrLabel->setText(QStringLiteral("%1 %").arg(int(ceil(remap(cfg["brt_min"].get<int>(), 0, brt_steps_max, 0, 100)))));
56 ui->maxBrLabel->setText(QStringLiteral("%1 %").arg(int(ceil(remap(cfg["brt_max"].get<int>(), 0, brt_steps_max, 0, 100)))));
57 ui->speedLabel->setText(QStringLiteral("%1 s").arg(QString::number(cfg["brt_speed"].get<int>() / 1000., 'g', 2)));
58 ui->thresholdLabel->setText(QStringLiteral("%1").arg(cfg["brt_threshold"].get<int>()));
59 ui->pollingLabel->setText(QStringLiteral("%1").arg(cfg["brt_polling_rate"].get<int>()));
60
61 double temp_kelvin = remap(temp_steps_max - cfg["temp_step"].get<int>(), 0, temp_steps_max, temp_k_max, temp_k_min);
62 temp_kelvin = floor(temp_kelvin / 100) * 100;
63 ui->tempLabel->setText(QStringLiteral("%1 K").arg(temp_kelvin));
64 }
65
setWindowProperties(QIcon & icon)66 void MainWindow::setWindowProperties(QIcon &icon)
67 {
68 QApplication::setApplicationVersion(g_app_version);
69 QApplication::setStyle("Fusion");
70 setWindowTitle("Gammy");
71 setWindowIcon(icon);
72
73 QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
74
75 // Extending brightness range doesn't work yet on Windows
76 if (windows)
77 ui->extendBr->hide();
78
79 // unused for now
80 ui->advBrSettingsBtn->setDisabled(true);
81 ui->advBrSettingsBtn->setVisible(false);
82
83 int x = cfg["wnd_x"].get<int>();
84 int y = cfg["wnd_y"].get<int>();
85
86 if (x == -1 && y == -1) {
87 move(QGuiApplication::screens().at(0)->geometry().center() - frameGeometry().center());
88 // Assume first start, show the window
89 show();
90 tray_wnd_toggle->setText(hide_txt);
91 } else {
92 setPos();
93 if (cfg["wnd_show_on_startup"].get<bool>()) {
94 show();
95 tray_wnd_toggle->setText(hide_txt);
96 }
97 }
98 }
99
setPos()100 void MainWindow::setPos()
101 {
102 const int x = cfg["wnd_x"].get<int>();
103 const int y = cfg["wnd_y"].get<int>();
104 move(x, y);
105 }
106
savePos()107 void MainWindow::savePos()
108 {
109 const QRect fg = frameGeometry();
110 cfg["wnd_x"] = fg.x();
111 cfg["wnd_y"] = fg.y();
112 }
113
setSliders()114 void MainWindow::setSliders()
115 {
116 ui->brtSlider->setValue(cfg["brt_step"]);
117 ui->extendBr->setChecked(cfg["brt_extend"]);
118
119 if (cfg["brt_extend"].get<bool>()) {
120 toggleBrtSlidersRange(true);
121 }
122
123 ui->offsetSlider->setValue(cfg["brt_offset"]);
124 ui->tempSlider->setRange(0, temp_steps_max);
125 ui->tempSlider->setValue(cfg["temp_step"]);
126 ui->speedSlider->setValue(cfg["brt_speed"]);
127 ui->thresholdSlider->setValue(cfg["brt_threshold"]);
128 ui->pollingSlider->setValue(cfg["brt_polling_rate"]);
129 }
130
createTrayIcon(QIcon & icon)131 void MainWindow::createTrayIcon(QIcon &icon)
132 {
133 QMenu *menu = createTrayMenu();
134 tray_icon->setContextMenu(menu);
135 tray_icon->setToolTip(QString("Gammy"));
136 tray_icon->setIcon(icon);
137 connect(tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated);
138 tray_icon->show();
139
140 systray_available = QSystemTrayIcon::isSystemTrayAvailable();
141
142 if (!systray_available) {
143 cfg["wnd_show_on_startup"] = true;
144 LOGE << "Systray unavailable. Closing the window will quit the app.";
145 }
146 }
147
trayIconActivated(QSystemTrayIcon::ActivationReason reason)148 void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
149 {
150 switch (reason) {
151 case QSystemTrayIcon::Trigger:
152 tray_wnd_toggle->trigger();
153 break;
154 case QSystemTrayIcon::MiddleClick:
155 if (ui->autoBrtCheck->isChecked() || ui->autoTempCheck->isChecked()) {
156 restoreDefaultBrt();
157 restoreDefaultTemp();
158 } else {
159 ui->autoBrtCheck->setChecked(true);
160 ui->autoTempCheck->setChecked(true);
161 }
162 break;
163 case QSystemTrayIcon::Unknown:
164 break;
165 case QSystemTrayIcon::Context:
166 break;
167 case QSystemTrayIcon::DoubleClick:
168 break;
169 }
170 }
171
listenWakeupSignal()172 bool MainWindow::listenWakeupSignal()
173 {
174 QDBusConnection dbus = QDBusConnection::systemBus();
175
176 if (!dbus.isConnected()) {
177 LOGE << "Cannot connect to the system D-Bus.";
178 return false;
179 }
180
181 const QString service = "org.freedesktop.login1";
182 const QString path = "/org/freedesktop/login1";
183 const QString interface = "org.freedesktop.login1.Manager";
184 const QString name = "PrepareForSleep";
185
186 QDBusInterface iface(service, path, interface, dbus, this);
187
188 if (!iface.isValid()) {
189 LOGE << "Wakeup interface not found.";
190 return false;
191 }
192
193 bool connected = dbus.connect(service, path, interface, name, this, SLOT(wakeupSlot(bool)));
194
195 if (!connected) {
196 LOGE << "Cannot connect to wakeup signal.";
197 return false;
198 }
199
200 return true;
201 }
202
wakeupSlot(bool status)203 void MainWindow::wakeupSlot(bool status)
204 {
205 // The signal emits TRUE when going to sleep. We only care about wakeup (FALSE)
206 if (status)
207 return;
208
209 mediator->notify(this, SYSTEM_WAKE_UP);
210 }
211
shutdown()212 void MainWindow::shutdown()
213 {
214 tray_icon->hide();
215
216 // Save the position only if visible to avoid skewed values
217 if (isVisible()) {
218 savePos();
219 hide();
220 }
221
222 mediator->notify(this, prev_gamma ? APP_QUIT : APP_QUIT_PURE_GAMMA);
223 config::write();
224 }
225
226 /**
227 * Fired when the window is closed.
228 * Exit only if the systray is unavailable.
229 */
closeEvent(QCloseEvent * e)230 void MainWindow::closeEvent(QCloseEvent *e)
231 {
232 if (!systray_available) {
233 return QApplication::quit();
234 }
235
236 if (isVisible()) {
237 savePos();
238 hide();
239 tray_wnd_toggle->setText(show_txt);
240 }
241
242 config::write();
243 e->ignore();
244 }
245
createTrayMenu()246 QMenu* MainWindow::createTrayMenu()
247 {
248 QMenu *menu = new QMenu(nullptr);
249
250 connect(tray_wnd_toggle, &QAction::triggered, this, [&] {
251 if (isHidden()) {
252 show();
253 tray_wnd_toggle->setText(hide_txt);
254 } else {
255 hide();
256 tray_wnd_toggle->setText(show_txt);
257 }
258 });
259 menu->addAction(tray_wnd_toggle);
260
261 menu->addSeparator();
262
263 connect(tray_brt_toggle, &QAction::triggered, this, [=] {
264 if (tray_brt_toggle->isChecked()) {
265 ui->autoBrtCheck->setChecked(true);
266 } else {
267 restoreDefaultBrt();
268 }
269 });
270
271 connect(tray_temp_toggle, &QAction::triggered, this, [=] {
272 if (tray_temp_toggle->isChecked()) {
273 ui->autoTempCheck->setChecked(true);
274 } else {
275 restoreDefaultTemp();
276 }
277 });
278
279 menu->addAction(tray_brt_toggle);
280 menu->addAction(tray_temp_toggle);
281
282 menu->addSeparator();
283
284 #ifdef _WIN32
285 QAction *run_startup = new QAction("&Run at startup", this);
286 run_startup->setCheckable(true);
287 connect(run_startup, &QAction::triggered, [=]{ toggleRegkey(run_startup->isChecked()); });
288 menu->addAction(run_startup);
289
290 LRESULT s = RegGetValueW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", L"Gammy", RRF_RT_REG_SZ, nullptr, nullptr, nullptr);
291 run_startup->setChecked(s == ERROR_SUCCESS);
292
293 menu->addSeparator();
294 #endif
295
296 QAction *quit_prev = new QAction("Quit", this);
297 connect(quit_prev, &QAction::triggered, this, [&] { prev_gamma = true; QApplication::quit(); });
298 menu->addAction(quit_prev);
299
300 if (!windows) {
301 QAction *quit_pure = new QAction("Quit (set pure gamma)", this);
302 connect(quit_pure, &QAction::triggered, this, [&] { prev_gamma = false; QApplication::quit(); });
303 menu->addAction(quit_pure);
304 }
305
306 menu->addSeparator();
307
308 QAction *about = new QAction("Gammy " + QString(g_app_version), this);
309 about->setEnabled(false);
310 menu->addAction(about);
311
312 return menu;
313 }
314
315 /**
316 * These are called by the mediator when brt/temp is getting adjusted.
317 * Triggers a valueChanged, but not a sliderMoved. This allows us
318 * to differentiate between app and user action.
319 */
setBrtSlider(int val)320 void MainWindow::setBrtSlider(int val)
321 {
322 ui->brtSlider->setValue(val);
323 }
324
setTempSlider(int val)325 void MainWindow::setTempSlider(int val)
326 {
327 ui->tempSlider->setValue(val);
328 }
329
330 /**
331 * Triggered when the user changes the slider manually via:
332 * left click, middle click, single step, page step.
333 * We need to update the gamma manually in this case.
334 */
on_brtSlider_actionTriggered(int action)335 void MainWindow::on_brtSlider_actionTriggered([[maybe_unused]] int action)
336 {
337 if (ui->autoBrtCheck->isChecked())
338 ui->autoBrtCheck->setChecked(false);
339
340 int val = ui->brtSlider->sliderPosition();
341 cfg["brt_step"] = val;
342 mediator->notify(this, GAMMA_STEP_CHANGED);
343 updateBrtLabel(val);
344 }
345
on_tempSlider_actionTriggered(int action)346 void MainWindow::on_tempSlider_actionTriggered([[maybe_unused]] int action)
347 {
348 if (ui->autoTempCheck->isChecked())
349 ui->autoTempCheck->setChecked(false);
350
351 int val = ui->tempSlider->sliderPosition();
352 cfg["temp_step"] = val;
353 mediator->notify(this, GAMMA_STEP_CHANGED);
354 updateTempLabel(val);
355 }
356
restoreDefaultBrt()357 void MainWindow::restoreDefaultBrt()
358 {
359 ui->autoBrtCheck->setChecked(false);
360 cfg["brt_step"] = brt_steps_max;
361 mediator->notify(this, GAMMA_STEP_CHANGED);
362 ui->brtSlider->setValue(brt_steps_max);
363 }
364
restoreDefaultTemp()365 void MainWindow::restoreDefaultTemp()
366 {
367 ui->autoTempCheck->setChecked(false);
368 cfg["temp_step"] = 0;
369 mediator->notify(this, GAMMA_STEP_CHANGED);
370 ui->tempSlider->setValue(0);
371 }
372
373 /**
374 * Triggered when the sliders are moved by the gamma controller.
375 * Just update the labels, since the gamma has been adjusted already.
376 */
on_brtSlider_valueChanged(int val)377 void MainWindow::on_brtSlider_valueChanged(int val)
378 {
379 updateBrtLabel(val);
380 }
381
on_tempSlider_valueChanged(int val)382 void MainWindow::on_tempSlider_valueChanged(int val)
383 {
384 updateTempLabel(val);
385 }
386
on_brRange_lowerValueChanged(int val)387 void MainWindow::on_brRange_lowerValueChanged(int val)
388 {
389 cfg["brt_min"] = val;
390 val = int(ceil(remap(val, 0, brt_steps_max, 0, 100)));
391 ui->minBrLabel->setText(QStringLiteral("%1 %").arg(val));
392 }
393
on_brRange_upperValueChanged(int val)394 void MainWindow::on_brRange_upperValueChanged(int val)
395 {
396 cfg["brt_max"] = val;
397 val = int(ceil(remap(val, 0, brt_steps_max, 0, 100)));
398 ui->maxBrLabel->setText(QStringLiteral("%1 %").arg(val));
399 }
400
toggleBrtSlidersRange(bool extend)401 void MainWindow::toggleBrtSlidersRange(bool extend)
402 {
403 int brt_limit = brt_steps_max;
404
405 if (extend)
406 brt_limit *= 2;
407
408 const int min = cfg["brt_min"].get<int>();
409 const int max = cfg["brt_max"].get<int>();
410 ui->brRange->setMaximum(brt_limit);
411
412 // We set the upper/lower values again because they reset after setMaximum
413 ui->brRange->setUpperValue(max);
414 ui->brRange->setLowerValue(min);
415
416 ui->brtSlider->setRange(100, brt_limit);
417
418 if (!cfg["brt_auto"].get<bool>()) {
419 ui->brtSlider->setValue(cfg["brt_step"]);
420 emit on_brtSlider_actionTriggered(QAbstractSlider::SliderMove);
421 }
422 }
423
updateBrtLabel(int val)424 void MainWindow::updateBrtLabel(int val)
425 {
426 val = int(ceil(remap(val, 0, brt_steps_max, 0, 100)));
427 ui->brtLabel->setText(QStringLiteral("%1 %").arg(val));
428 }
429
updateTempLabel(int val)430 void MainWindow::updateTempLabel(int val)
431 {
432 double temp_kelvin = remap(temp_steps_max - val, 0, temp_steps_max, temp_k_max, temp_k_min);
433 temp_kelvin = floor(temp_kelvin / 10) * 10;
434 ui->tempLabel->setText(QStringLiteral("%1 K").arg(temp_kelvin));
435 }
436
on_offsetSlider_valueChanged(int val)437 void MainWindow::on_offsetSlider_valueChanged(int val)
438 {
439 cfg["brt_offset"] = val;
440 ui->offsetLabel->setText(QStringLiteral("%1 %").arg(int(remap(val, 0, brt_steps_max, 0, 100))));
441 }
442
on_speedSlider_valueChanged(int val)443 void MainWindow::on_speedSlider_valueChanged(int val)
444 {
445 cfg["brt_speed"] = val;
446 ui->speedLabel->setText(QStringLiteral("%1 s").arg(QString::number(val / 1000., 'g', 2)));
447 }
448
on_thresholdSlider_valueChanged(int val)449 void MainWindow::on_thresholdSlider_valueChanged(int val)
450 {
451 cfg["brt_threshold"] = val;
452 }
453
on_pollingSlider_valueChanged(int val)454 void MainWindow::on_pollingSlider_valueChanged(int val)
455 {
456 cfg["brt_polling_rate"] = val;
457 }
458
toggleBrtSliders(bool checked)459 void MainWindow::toggleBrtSliders(bool checked)
460 {
461 ui->brtSettings->setVisible(checked);
462
463 int wnd_h;
464 int cwgt_h;
465
466 if (checked) {
467 wnd_h = wnd_h_min_auto_brt_on;
468 cwgt_h = c_wdgt_h_max_auto_brt_on;
469 } else {
470 wnd_h = wnd_h_min;
471 cwgt_h = c_wdgt_h_max;
472 }
473
474 ui->centralWidget->setMaximumHeight(cwgt_h);
475 setMinimumHeight(wnd_h);
476 resize(width(), wnd_h);
477 }
478
on_advBrSettingsBtn_toggled(bool checked)479 void MainWindow::on_advBrSettingsBtn_toggled(bool checked)
480 {
481 ui->brtSettings->setVisible(checked);
482 }
483
on_autoBrtCheck_toggled(bool checked)484 void MainWindow::on_autoBrtCheck_toggled(bool checked)
485 {
486 tray_brt_toggle->setChecked(checked);
487 cfg["brt_auto"] = checked;
488 mediator->notify(this, AUTO_BRT_TOGGLED);
489 toggleBrtSliders(checked);
490 }
491
on_autoTempCheck_toggled(bool checked)492 void MainWindow::on_autoTempCheck_toggled(bool checked)
493 {
494 tray_temp_toggle->setChecked(checked);
495 cfg["temp_auto"] = checked;
496 this->mediator->notify(this, AUTO_TEMP_TOGGLED);
497 }
498
on_extendBr_clicked(bool checked)499 void MainWindow::on_extendBr_clicked(bool checked)
500 {
501 cfg["brt_extend"] = checked;
502 toggleBrtSlidersRange(cfg["brt_extend"]);
503 }
504
on_pushButton_clicked()505 void MainWindow::on_pushButton_clicked()
506 {
507 TempScheduler ts(this->mediator);
508 ts.exec();
509 }
510
setPollingRange(int min,int max)511 void MainWindow::setPollingRange(int min, int max)
512 {
513 const int poll = cfg["brt_polling_rate"];
514
515 LOGD << "Setting polling rate slider range to: " << min << ", " << max;
516
517 ui->pollingSlider->setRange(min, max);
518
519 if (poll < min)
520 cfg["brt_polling_rate"] = min;
521 else if (poll > max)
522 cfg["brt_polling_rate"] = max;
523
524 ui->pollingLabel->setText(QString::number(poll));
525 ui->pollingSlider->setValue(poll);
526 }
527
~MainWindow()528 MainWindow::~MainWindow()
529 {
530 delete ui;
531 }
532