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