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