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