If we have a wireless interface, use the signal strength icon in the
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-qt.cpp
1 /*
2  * dhcpcd-qt
3  * Copyright 2014 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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
24  * SUCH DAMAGE.
25  */
26
27 #include <QCursor>
28 #include <QDebug>
29 #include <QList>
30 #include <QSocketNotifier>
31 #include <QtGui>
32
33 #include <cerrno>
34
35 #include "config.h"
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"
42
43 #ifdef NOTIFY
44 #include <knotification.h>
45 #endif
46
47 DhcpcdQt::DhcpcdQt()
48 {
49
50         createActions();
51         createTrayIcon();
52
53         onLine = carrier = false;
54         lastStatus = NULL;
55         aniTimer = new QTimer(this);
56         connect(aniTimer, SIGNAL(timeout()), this, SLOT(animate()));
57         notifier = NULL;
58         retryOpenTimer = NULL;
59
60         about = NULL;
61         preferences = NULL;
62
63         wis = new QList<DhcpcdWi *>();
64         ssidMenu = NULL;
65
66         qDebug("%s", "Connecting ...");
67         con = dhcpcd_new();
68         if (con == NULL) {
69                 qCritical("libdhcpcd: %s", strerror(errno));
70                 exit(EXIT_FAILURE);
71                 return;
72         }
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);
78         tryOpen();
79 }
80
81 DhcpcdQt::~DhcpcdQt()
82 {
83
84         if (ssidMenu) {
85                 ssidMenu->setVisible(false);
86                 ssidMenu->deleteLater();
87         }
88
89         if (con != NULL) {
90                 dhcpcd_close(con);
91                 dhcpcd_free(con);
92         }
93
94         free(lastStatus);
95
96         for (auto &wi : *wis)
97                 wi->deleteLater();
98         delete wis;
99 }
100
101 DHCPCD_CONNECTION *DhcpcdQt::getConnection()
102 {
103
104         return con;
105 }
106
107 QList<DhcpcdWi *> *DhcpcdQt::getWis()
108 {
109
110         return wis;
111 }
112
113 const char * DhcpcdQt::signalStrengthIcon(DHCPCD_WI_SCAN *scan)
114 {
115
116         if (scan->strength.value > 80)
117                 return "network-wireless-connected-100";
118         else if (scan->strength.value > 55)
119                 return "network-wireless-connected-75";
120         else if (scan->strength.value > 30)
121                 return "network-wireless-connected-50";
122         else if (scan->strength.value > 5)
123                 return "network-wireless-connected-25";
124         else
125                 return "network-wireless-connected-00";
126 }
127
128 DHCPCD_WI_SCAN * DhcpcdQt::getStrongestSignal()
129 {
130         DHCPCD_WI_SCAN *scan, *scans, *s;
131         DHCPCD_WPA *wpa;
132         DHCPCD_IF *i;
133
134         scan = NULL;
135         for (auto &wi : *wis) {
136                 wpa = wi->getWpa();
137                 i = dhcpcd_wpa_if(wpa);
138                 scans = wi->getScans();
139                 for (s = scans; s; s = s->next) {
140                         if (dhcpcd_wi_associated(i, s) &&
141                             (scan == NULL ||
142                             s->strength.value > scan->strength.value))
143                                 scan = s;
144                 }
145         }
146         return scan;
147 }
148
149 void DhcpcdQt::animate()
150 {
151         const char *icon;
152         DHCPCD_WI_SCAN *scan;
153
154         scan = getStrongestSignal();
155
156         if (onLine) {
157                 if (aniCounter++ > 6) {
158                         aniTimer->stop();
159                         aniCounter = 0;
160                         return;
161                 }
162
163                 if (aniCounter % 2 == 0)
164                         icon = scan ? "network-wireless-connected-00" :
165                             "network-idle";
166                 else
167                         icon = scan ? DhcpcdQt::signalStrengthIcon(scan) :
168                             "network-transmit-receive";
169         } else {
170                 if (scan) {
171                         switch(aniCounter++) {
172                         case 0:
173                                 icon = "network-wireless-connected-00";
174                                 break;
175                         case 1:
176                                 icon = "network-wireless-connected-25";
177                                 break;
178                         case 2:
179                                 icon = "network-wireless-connected-50";
180                                 break;
181                         case 3:
182                                 icon = "network-wireless-connected-75";
183                                 break;
184                         default:
185                                 icon = "network-wireless-connected-100";
186                                 aniCounter = 0;
187                         }
188                 } else {
189                         switch(aniCounter++) {
190                         case 0:
191                                 icon = "network-transmit";
192                                 break;
193                         case 1:
194                                 icon = "network-receive";
195                                 break;
196                         default:
197                                 icon = "network-idle";
198                                 aniCounter = 0;
199                         }
200                 }
201         }
202
203         setIcon("status", icon);
204 }
205
206 void DhcpcdQt::updateOnline(bool showIf)
207 {
208         bool isOn, isCarrier;
209         char *msg;
210         DHCPCD_IF *ifs, *i;
211         QString msgs;
212
213         isOn = isCarrier = false;
214         ifs = dhcpcd_interfaces(con);
215         for (i = ifs; i; i = i->next) {
216                 if (strcmp(i->type, "link") == 0) {
217                         if (i->up)
218                                 isCarrier = true;
219                 } else {
220                         if (i->up)
221                                 isOn = true;
222                 }
223                 msg = dhcpcd_if_message(i, NULL);
224                 if (msg) {
225                         if (showIf)
226                                 qDebug() << msg;
227                         if (msgs.isEmpty())
228                                 msgs = QString::fromAscii(msg);
229                         else
230                                 msgs += '\n' + QString::fromAscii(msg);
231                         free(msg);
232                 } else if (showIf)
233                         qDebug() << i->ifname << i->reason;
234         }
235
236         if (onLine != isOn || carrier != isCarrier) {
237                 onLine = isOn;
238                 carrier = isCarrier;
239                 aniTimer->stop();
240                 aniCounter = 0;
241                 if (isOn) {
242                         animate();
243                         aniTimer->start(300);
244                 } else if (isCarrier) {
245                         animate();
246                         aniTimer->start(500);
247                 } else
248                         setIcon("status", "network-offline");
249         }
250
251         trayIcon->setToolTip(msgs);
252 }
253
254 void DhcpcdQt::statusCallback(const char *status)
255 {
256
257         qDebug("Status changed to %s", status);
258         if (strcmp(status, "down") == 0) {
259                 aniTimer->stop();
260                 aniCounter = 0;
261                 onLine = carrier = false;
262                 setIcon("status", "network-offline");
263                 trayIcon->setToolTip(tr("Not connected to dhcpcd"));
264                 /* Close down everything */
265                 if (notifier) {
266                         notifier->setEnabled(false);
267                         notifier->deleteLater();
268                         notifier = NULL;
269                 }
270                 if (ssidMenu) {
271                         ssidMenu->deleteLater();
272                         ssidMenu = NULL;
273                 }
274                 preferencesAction->setEnabled(false);
275                 if (preferences) {
276                         preferences->deleteLater();
277                         preferences = NULL;
278                 }
279         } else {
280                 bool refresh;
281
282                 if (lastStatus == NULL || strcmp(lastStatus, "down") == 0) {
283                         qDebug("Connected to dhcpcd-%s", dhcpcd_version(con));
284                         refresh = true;
285                 } else
286                         refresh = strcmp(lastStatus, "opened") ? false : true;
287                 updateOnline(refresh);
288         }
289
290         free(lastStatus);
291         lastStatus = strdup(status);
292
293         if (strcmp(status, "down") == 0) {
294                 if (retryOpenTimer == NULL) {
295                         retryOpenTimer = new QTimer(this);
296                         connect(retryOpenTimer, SIGNAL(timeout()),
297                             this, SLOT(tryOpen()));
298                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
299                 }
300         }
301 }
302
303 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
304     const char *status, void *d)
305 {
306         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
307
308         dhcpcdQt->statusCallback(status);
309 }
310
311 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
312 {
313         char *msg;
314         bool new_msg;
315
316         if (strcmp(i->reason, "RENEW") &&
317             strcmp(i->reason, "STOP") &&
318             strcmp(i->reason, "STOPPED"))
319         {
320                 msg = dhcpcd_if_message(i, &new_msg);
321                 if (msg) {
322                         qDebug("%s", msg);
323                         if (new_msg) {
324                                 QSystemTrayIcon::MessageIcon icon =
325                                     i->up ? QSystemTrayIcon::Information :
326                                     QSystemTrayIcon::Warning;
327                                 QString t = tr("Network Event");
328                                 QString m = msg;
329                                 notify(t, m, icon);
330                         }
331                         free(msg);
332                 }
333         }
334
335         updateOnline(false);
336
337         if (i->wireless) {
338                 for (auto &wi : *wis) {
339                         DHCPCD_WPA *wpa = wi->getWpa();
340                         if (dhcpcd_wpa_if(wpa) == i) {
341                                 DHCPCD_WI_SCAN *scans;
342
343                                 scans = dhcpcd_wi_scans(i);
344                                 processScans(wi, scans);
345                         }
346                 }
347         }
348 }
349
350 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
351 {
352         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
353
354         dhcpcdQt->ifCallback(i);
355 }
356
357 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
358 {
359
360         for (auto &wi : *wis) {
361                 if (wi->getWpa() == wpa)
362                         return wi;
363         }
364         return NULL;
365 }
366
367 void DhcpcdQt::processScans(DhcpcdWi *wi, DHCPCD_WI_SCAN *scans)
368 {
369         DHCPCD_WI_SCAN *s1, *s2;
370
371         QString title = tr("New Access Point");
372         QString txt;
373         for (s1 = scans; s1; s1 = s1->next) {
374                 for (s2 = wi->getScans(); s2; s2 = s2->next) {
375                         if (strcmp(s1->ssid, s2->ssid) == 0)
376                                 break;
377                 }
378                 if (s2 == NULL) {
379                         if (!txt.isEmpty()) {
380                                 title = tr("New Access Points");
381                                 txt += '\n';
382                         }
383                         txt += s1->ssid;
384                 }
385         }
386         if (!txt.isEmpty() &&
387             (ssidMenu == NULL || !ssidMenu->isVisible()))
388                 notify(title, txt);
389
390         wi->setScans(scans);
391         if (ssidMenu && ssidMenu->isVisible())
392                 ssidMenu->popup(ssidMenuPos);
393 }
394
395 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
396 {
397         DHCPCD_WI_SCAN *scans;
398         int fd = dhcpcd_wpa_get_fd(wpa);
399         DhcpcdWi *wi;
400
401         wi = findWi(wpa);
402         if (fd == -1) {
403                 qCritical("No fd for WPA");
404                 if (wi) {
405                         wis->removeOne(wi);
406                         wi->deleteLater();
407                 }
408                 return;
409         }
410
411         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
412         if (i == NULL) {
413                 qCritical("No interface for WPA");
414                 if (wi) {
415                         wis->removeOne(wi);
416                         wi->deleteLater();
417                 }
418                 return;
419         }
420
421         qDebug("%s: Received scan results", i->ifname);
422         scans = dhcpcd_wi_scans(i);
423         if (wi == NULL) {
424                 wi = new DhcpcdWi(this, wpa);
425                 if (wi->open()) {
426                         wis->append(wi);
427                         wi->setScans(scans);
428                 } else
429                         wi->deleteLater();
430         } else
431                 processScans(wi, scans);
432
433         if (!aniTimer->isActive()) {
434                 DHCPCD_WI_SCAN *scan;
435
436                 scan = getStrongestSignal();
437                 if (scan)
438                         setIcon("status", DhcpcdQt::signalStrengthIcon(scan));
439         }
440 }
441
442 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
443 {
444         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
445
446         dhcpcdQt->scanCallback(wpa);
447 }
448
449 void DhcpcdQt::wpaStatusCallback(DHCPCD_WPA *wpa, const char *status)
450 {
451         DHCPCD_IF *i;
452
453         i = dhcpcd_wpa_if(wpa);
454         qDebug("%s: WPA status %s", i->ifname, status);
455         if (strcmp(status, "down") == 0) {
456                 DhcpcdWi *wi = findWi(wpa);
457                 if (wi) {
458                         wis->removeOne(wi);
459                         wi->deleteLater();
460                 }
461         }
462 }
463
464 void DhcpcdQt::dhcpcd_wpa_status_cb(DHCPCD_WPA *wpa, const char *status,
465     void *d)
466 {
467         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
468
469         dhcpcdQt->wpaStatusCallback(wpa, status);
470 }
471
472 void DhcpcdQt::tryOpen() {
473         int fd = dhcpcd_open(con, true);
474         static int last_error;
475
476         if (fd == -1) {
477                 if (errno == EACCES || errno == EPERM) {
478                         if ((fd = dhcpcd_open(con, false)) != -1)
479                                 goto unprived;
480                 }
481                 if (errno != last_error) {
482                         last_error = errno;
483                         const char *errt = strerror(errno);
484                         qCritical("dhcpcd_open: %s", errt);
485                         trayIcon->setToolTip(
486                             tr("Error connecting to dhcpcd: %1").arg(errt));
487                 }
488                 if (retryOpenTimer == NULL) {
489                         retryOpenTimer = new QTimer(this);
490                         connect(retryOpenTimer, SIGNAL(timeout()),
491                             this, SLOT(tryOpen()));
492                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
493                 }
494                 return;
495         }
496
497 unprived:
498         /* Start listening to WPA events */
499         dhcpcd_wpa_start(con);
500
501         if (retryOpenTimer) {
502                 retryOpenTimer->stop();
503                 retryOpenTimer->deleteLater();
504                 retryOpenTimer = NULL;
505         }
506
507         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
508         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
509
510         preferencesAction->setEnabled(dhcpcd_privileged(con));
511 }
512
513 void DhcpcdQt::dispatch()
514 {
515
516         dhcpcd_dispatch(con);
517 }
518
519 void DhcpcdQt::notify(QString &title, QString &msg,
520 #ifdef NOTIFY
521     QSystemTrayIcon::MessageIcon
522 #else
523     QSystemTrayIcon::MessageIcon icon
524 #endif
525     )
526 {
527
528 #ifdef NOTIFY
529         KNotification *n = new KNotification("event", this);
530         n->setTitle(title);
531         n->setText(msg);
532         n->sendEvent();
533 #else
534         //trayIcon->showMessage(title, msg, icon);
535 #endif
536 }
537
538 void DhcpcdQt::closeEvent(QCloseEvent *event)
539 {
540
541         if (trayIcon->isVisible()) {
542                 hide();
543                 event->ignore();
544         }
545 }
546
547 QIcon DhcpcdQt::getIcon(QString category, QString name)
548 {
549         QIcon icon;
550
551         if (QIcon::hasThemeIcon(name))
552                 icon = QIcon::fromTheme(name);
553         else
554                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
555
556         return icon;
557 }
558
559 void DhcpcdQt::setIcon(QString category, QString name)
560 {
561         QIcon icon = getIcon(category, name);
562
563         trayIcon->setIcon(icon);
564 }
565
566 QIcon DhcpcdQt::icon()
567 {
568
569         return getIcon("status", "network-transmit-receive");
570 }
571
572 void DhcpcdQt::menuDeleted(QMenu *menu)
573 {
574
575         if (ssidMenu == menu)
576                 ssidMenu = NULL;
577 }
578
579 void DhcpcdQt::createSsidMenu()
580 {
581
582         if (ssidMenu) {
583                 ssidMenu->deleteLater();
584                 ssidMenu = NULL;
585         }
586         if (wis->size() == 0)
587                 return;
588
589         ssidMenu = new QMenu(this);
590         if (wis->size() == 1)
591                 wis->first()->createMenu(ssidMenu);
592         else {
593                 for (auto &wi : *wis)
594                         ssidMenu->addMenu(wi->createIfMenu(ssidMenu));
595         }
596         ssidMenuPos = QCursor::pos();
597         ssidMenu->popup(ssidMenuPos);
598 }
599
600 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
601 {
602
603         if (reason == QSystemTrayIcon::Trigger)
604                 createSsidMenu();
605 }
606
607 void DhcpcdQt::dialogClosed(QDialog *dialog)
608 {
609
610         if (dialog == about)
611                 about = NULL;
612         else if (dialog == preferences)
613                 preferences = NULL;
614 }
615
616 void DhcpcdQt::showPreferences()
617 {
618
619         if (preferences == NULL) {
620                 preferences = new DhcpcdPreferences(this);
621                 preferences->show();
622         } else
623                 preferences->activateWindow();
624 }
625
626 void DhcpcdQt::showAbout()
627 {
628
629         if (about == NULL) {
630                 about = new DhcpcdAbout(this);
631                 about->show();
632         } else
633                 about->activateWindow();
634 }
635
636 void DhcpcdQt::createActions()
637 {
638
639         preferencesAction = new QAction(tr("&Preferences"), this);
640         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
641         preferencesAction->setEnabled(false);
642         connect(preferencesAction, SIGNAL(triggered()),
643             this, SLOT(showPreferences()));
644
645         aboutAction = new QAction(tr("&About"), this);
646         aboutAction->setIcon(QIcon::fromTheme("help-about"));
647         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
648
649         quitAction = new QAction(tr("&Quit"), this);
650         quitAction->setIcon(QIcon::fromTheme("application-exit"));
651         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
652
653 }
654
655 void DhcpcdQt::createTrayIcon()
656 {
657
658         trayIconMenu = new QMenu(this);
659         trayIconMenu->addAction(preferencesAction);
660         trayIconMenu->addSeparator();
661         trayIconMenu->addAction(aboutAction);
662         trayIconMenu->addAction(quitAction);
663
664         trayIcon = new QSystemTrayIcon(this);
665         setIcon("status", "network-offline");
666         trayIcon->setContextMenu(trayIconMenu);
667
668         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
669             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
670
671         trayIcon->show();
672 }