3 * Copyright 2014-2017 Roy Marples <roy@marples.name>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 #include <QSocketNotifier>
36 #include "dhcpcd-qt.h"
37 #include "dhcpcd-about.h"
38 #include "dhcpcd-preferences.h"
39 #include "dhcpcd-wi.h"
40 #include "dhcpcd-ifmenu.h"
41 #include "dhcpcd-ssidmenu.h"
44 #include <knotification.h>
53 onLine = carrier = false;
54 lastStatus = DHC_UNKNOWN;
55 aniTimer = new QTimer(this);
56 connect(aniTimer, SIGNAL(timeout()), this, SLOT(animate()));
58 retryOpenTimer = NULL;
63 wis = new QList<DhcpcdWi *>();
66 qDebug("%s", "Connecting ...");
69 qCritical("libdhcpcd: %s", strerror(errno));
73 dhcpcd_set_progname(con, "dhcpcd-qt");
74 dhcpcd_set_status_callback(con, dhcpcd_status_cb, this);
75 dhcpcd_set_if_callback(con, dhcpcd_if_cb, this);
76 dhcpcd_wpa_set_scan_callback(con, dhcpcd_wpa_scan_cb, this);
77 dhcpcd_wpa_set_status_callback(con, dhcpcd_wpa_status_cb, this);
85 ssidMenu->setVisible(false);
86 ssidMenu->deleteLater();
99 DHCPCD_CONNECTION *DhcpcdQt::getConnection()
105 QList<DhcpcdWi *> *DhcpcdQt::getWis()
111 const char * DhcpcdQt::signalStrengthIcon(DHCPCD_WI_SCAN *scan)
115 return "network-wireless-connected-00";
116 if (scan->strength.value > 80)
117 return "network-wireless-connected-100";
118 if (scan->strength.value > 55)
119 return "network-wireless-connected-75";
120 if (scan->strength.value > 30)
121 return "network-wireless-connected-50";
122 if (scan->strength.value > 5)
123 return "network-wireless-connected-25";
124 return "network-wireless-connected-00";
127 DHCPCD_WI_SCAN * DhcpcdQt::getStrongestSignal()
129 DHCPCD_WI_SCAN *scan, *scans, *s;
134 for (auto &wi : *wis) {
136 i = dhcpcd_wpa_if(wpa);
137 scans = wi->getScans();
138 for (s = scans; s; s = s->next) {
139 if (dhcpcd_wi_associated(i, s) &&
141 s->strength.value > scan->strength.value))
148 void DhcpcdQt::animate()
151 DHCPCD_WI_SCAN *scan;
153 scan = getStrongestSignal();
156 if (aniCounter++ > 6) {
162 if (aniCounter % 2 == 0)
163 icon = scan ? "network-wireless-connected-00" :
166 icon = scan ? DhcpcdQt::signalStrengthIcon(scan) :
167 "network-transmit-receive";
170 switch(aniCounter++) {
172 icon = "network-wireless-connected-00";
175 icon = "network-wireless-connected-25";
178 icon = "network-wireless-connected-50";
181 icon = "network-wireless-connected-75";
184 icon = "network-wireless-connected-100";
188 switch(aniCounter++) {
190 icon = "network-transmit";
193 icon = "network-receive";
196 icon = "network-idle";
202 setIcon("status", icon);
205 void DhcpcdQt::updateOnline(bool showIf)
207 bool isOn, isCarrier;
212 isOn = isCarrier = false;
213 ifs = dhcpcd_interfaces(con);
214 for (i = ifs; i; i = i->next) {
215 if (i->type == DHT_LINK) {
222 msg = dhcpcd_if_message(i, NULL);
227 msgs = QString::fromLatin1(msg);
229 msgs += '\n' + QString::fromLatin1(msg);
232 qDebug("%s: %s", i->ifname, i->reason);
235 if (onLine != isOn || carrier != isCarrier) {
242 aniTimer->start(300);
243 } else if (isCarrier) {
245 aniTimer->start(500);
247 setIcon("status", "network-offline");
250 trayIcon->setToolTip(msgs);
253 void DhcpcdQt::statusCallback(unsigned int status, const char *status_msg)
256 qDebug("Status changed to %s", status_msg);
257 if (status == DHC_DOWN) {
260 onLine = carrier = false;
261 setIcon("status", "network-offline");
262 trayIcon->setToolTip(tr("Not connected to dhcpcd"));
263 /* Close down everything */
265 notifier->setEnabled(false);
266 notifier->deleteLater();
270 ssidMenu->deleteLater();
273 preferencesAction->setEnabled(false);
275 preferences->deleteLater();
281 if (lastStatus == DHC_UNKNOWN || lastStatus == DHC_DOWN) {
282 qDebug("Connected to dhcpcd-%s", dhcpcd_version(con));
285 refresh = lastStatus == DHC_OPENED ? true : false;
286 updateOnline(refresh);
291 if (status == DHC_DOWN) {
292 if (retryOpenTimer == NULL) {
293 retryOpenTimer = new QTimer(this);
294 connect(retryOpenTimer, SIGNAL(timeout()),
295 this, SLOT(tryOpen()));
296 retryOpenTimer->start(DHCPCD_RETRYOPEN);
301 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
302 unsigned int status, const char *status_msg, void *d)
304 DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
306 dhcpcdQt->statusCallback(status, status_msg);
309 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
314 if (i->state == DHS_RENEW ||
315 i->state == DHS_STOP || i->state == DHS_STOPPED)
317 msg = dhcpcd_if_message(i, &new_msg);
321 QString t = tr("Network Event");
326 icon = "network-transmit-receive";
328 // icon = "network-transmit";
330 icon = "network-offline";
340 for (auto &wi : *wis) {
341 DHCPCD_WPA *wpa = wi->getWpa();
342 if (dhcpcd_wpa_if(wpa) == i) {
343 DHCPCD_WI_SCAN *scans;
345 scans = dhcpcd_wi_scans(i);
346 processScans(wi, scans);
352 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
354 DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
356 dhcpcdQt->ifCallback(i);
359 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
362 for (auto &wi : *wis) {
363 if (wi->getWpa() == wpa)
369 void DhcpcdQt::processScans(DhcpcdWi *wi, DHCPCD_WI_SCAN *scans)
373 /* Don't spam the user if we're already connected. */
374 i = dhcpcd_wpa_if(wi->getWpa());
377 QString title = tr("New Access Point");
379 DHCPCD_WI_SCAN *s1, *s2;
381 for (s1 = scans; s1; s1 = s1->next) {
382 for (s2 = wi->getScans(); s2; s2 = s2->next) {
383 if (strcmp(s1->ssid, s2->ssid) == 0)
387 if (!txt.isEmpty()) {
388 title = tr("New Access Points");
394 if (!txt.isEmpty() &&
395 (ssidMenu == NULL || !ssidMenu->isVisible()))
396 notify(title, txt, "network-wireless");
399 if (wi->setScans(scans) && ssidMenu && ssidMenu->isVisible())
400 ssidMenu->popup(ssidMenuPos);
403 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
405 DHCPCD_WI_SCAN *scans;
406 int fd = dhcpcd_wpa_get_fd(wpa);
411 qCritical("No fd for WPA");
420 DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
422 qCritical("No interface for WPA");
431 qDebug("%s: Received scan results", i->ifname);
432 scans = dhcpcd_wi_scans(i);
434 wi = new DhcpcdWi(this, wpa);
443 processScans(wi, scans);
445 if (!aniTimer->isActive()) {
446 DHCPCD_WI_SCAN *scan;
449 scan = getStrongestSignal();
451 icon = DhcpcdQt::signalStrengthIcon(scan);
453 icon = "network-transmit-receive";
455 icon = "network-offline";
457 setIcon("status", icon);
461 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
463 DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
465 dhcpcdQt->scanCallback(wpa);
468 void DhcpcdQt::wpaStatusCallback(DHCPCD_WPA *wpa,
469 unsigned int status, const char *status_msg)
473 i = dhcpcd_wpa_if(wpa);
474 qDebug("%s: WPA status %s", i->ifname, status_msg);
475 if (status == DHC_DOWN) {
476 DhcpcdWi *wi = findWi(wpa);
485 void DhcpcdQt::dhcpcd_wpa_status_cb(DHCPCD_WPA *wpa,
486 unsigned int status, const char *status_msg, void *d)
488 DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
490 dhcpcdQt->wpaStatusCallback(wpa, status, status_msg);
493 void DhcpcdQt::tryOpen() {
494 int fd = dhcpcd_open(con, true);
495 static int last_error;
498 if (errno == EACCES || errno == EPERM) {
499 if ((fd = dhcpcd_open(con, false)) != -1)
502 if (errno != last_error) {
504 const char *errt = strerror(errno);
505 qCritical("dhcpcd_open: %s", errt);
506 trayIcon->setToolTip(
507 tr("Error connecting to dhcpcd: %1").arg(errt));
509 if (retryOpenTimer == NULL) {
510 retryOpenTimer = new QTimer(this);
511 connect(retryOpenTimer, SIGNAL(timeout()),
512 this, SLOT(tryOpen()));
513 retryOpenTimer->start(DHCPCD_RETRYOPEN);
519 /* Start listening to WPA events */
520 dhcpcd_wpa_start(con);
522 if (retryOpenTimer) {
523 retryOpenTimer->stop();
524 retryOpenTimer->deleteLater();
525 retryOpenTimer = NULL;
528 notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
529 connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
531 preferencesAction->setEnabled(dhcpcd_privileged(con));
534 void DhcpcdQt::dispatch()
537 dhcpcd_dispatch(con);
540 void DhcpcdQt::notify(const QString &title, const QString &msg,
545 KNotification *n = new KNotification("event", this);
550 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
551 const QIcon i = getIcon("status", icon);
553 QSystemTrayIcon::MessageIcon i = QSystemTrayIcon::Information;
555 if (icon.compare("network-offline") == 0)
556 i = QSystemTrayIcon::Warning;
558 trayIcon->showMessage(title, msg, i);
562 void DhcpcdQt::closeEvent(QCloseEvent *event)
565 if (trayIcon->isVisible()) {
571 QIcon DhcpcdQt::getIcon(QString category, QString name)
575 if (QIcon::hasThemeIcon(name))
576 icon = QIcon::fromTheme(name);
578 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
583 void DhcpcdQt::setIcon(QString category, QString name)
585 QIcon icon = getIcon(category, name);
587 trayIcon->setIcon(icon);
590 QIcon DhcpcdQt::icon()
593 return getIcon("status", "network-transmit-receive");
596 void DhcpcdQt::menuDeleted(QMenu *menu)
599 if (ssidMenu == menu)
603 void DhcpcdQt::createSsidMenu()
607 ssidMenu->deleteLater();
610 if (wis->size() == 0)
613 ssidMenu = new QMenu(this);
614 if (wis->size() == 1)
615 wis->first()->createMenu(ssidMenu);
617 for (auto &wi : *wis)
618 ssidMenu->addMenu(wi->createIfMenu(ssidMenu));
620 ssidMenuPos = QCursor::pos();
621 ssidMenu->popup(ssidMenuPos);
624 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
627 if (reason == QSystemTrayIcon::Trigger)
631 void DhcpcdQt::dialogClosed(QDialog *dialog)
636 else if (dialog == preferences)
640 void DhcpcdQt::showPreferences()
643 if (preferences == NULL) {
644 preferences = new DhcpcdPreferences(this);
647 preferences->activateWindow();
650 void DhcpcdQt::showAbout()
654 about = new DhcpcdAbout(this);
657 about->activateWindow();
660 void DhcpcdQt::createActions()
663 preferencesAction = new QAction(tr("&Preferences"), this);
664 preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
665 preferencesAction->setEnabled(false);
666 connect(preferencesAction, SIGNAL(triggered()),
667 this, SLOT(showPreferences()));
669 aboutAction = new QAction(tr("&About"), this);
670 aboutAction->setIcon(QIcon::fromTheme("help-about"));
671 connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
673 quitAction = new QAction(tr("&Quit"), this);
674 quitAction->setIcon(QIcon::fromTheme("application-exit"));
675 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
679 void DhcpcdQt::createTrayIcon()
682 trayIconMenu = new QMenu(this);
683 trayIconMenu->addAction(preferencesAction);
684 trayIconMenu->addSeparator();
685 trayIconMenu->addAction(aboutAction);
686 trayIconMenu->addAction(quitAction);
688 trayIcon = new QSystemTrayIcon(this);
689 setIcon("status", "network-offline");
690 trayIcon->setContextMenu(trayIconMenu);
692 connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
693 this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));