Fix notifictions when dhcpcd is lost / re-connected.
[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 DhcpcdQt::DhcpcdQt()
44 {
45
46         createActions();
47         createTrayIcon();
48
49         onLine = carrier = false;
50         lastStatus = NULL;
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_status_callback(con, dhcpcd_status_cb, this);
70         dhcpcd_set_if_callback(con, dhcpcd_if_cb, this);
71         dhcpcd_wpa_set_scan_callback(con, dhcpcd_wpa_scan_cb, this);
72         dhcpcd_wpa_set_status_callback(con, dhcpcd_wpa_status_cb, this);
73         tryOpen();
74 }
75
76 DhcpcdQt::~DhcpcdQt()
77 {
78
79         qDeleteAll(*wis);
80         delete wis;
81
82         if (con != NULL) {
83                 dhcpcd_close(con);
84                 dhcpcd_free(con);
85         }
86
87         free(lastStatus);
88 }
89
90 void DhcpcdQt::animate()
91 {
92         const char *icon;
93
94         if (onLine) {
95                 if (aniCounter++ > 6) {
96                         aniTimer->stop();
97                         aniCounter = 0;
98                         return;
99                 }
100
101                 if (aniCounter % 2 == 0)
102                         icon = "network-idle";
103                 else
104                         icon = "network-transmit-receive";
105         } else {
106                 switch(aniCounter++) {
107                 case 0:
108                         icon = "network-transmit";
109                         break;
110                 case 1:
111                         icon = "network-receive";
112                         break;
113                 default:
114                         icon = "network-idle";
115                         aniCounter = 0;
116                 }
117         }
118
119         setIcon("status", icon);
120 }
121
122 void DhcpcdQt::updateOnline(bool showIf)
123 {
124         bool isOn, isCarrier;
125         char *msg;
126         DHCPCD_IF *ifs, *i;
127         QString msgs;
128
129         isOn = isCarrier = false;
130         ifs = dhcpcd_interfaces(con);
131         for (i = ifs; i; i = i->next) {
132                 if (strcmp(i->type, "link") == 0) {
133                         if (i->up)
134                                 isCarrier = true;
135                 } else {
136                         if (i->up)
137                                 isOn = true;
138                 }
139                 msg = dhcpcd_if_message(i, NULL);
140                 if (msg) {
141                         if (showIf)
142                                 qDebug() << msg;
143                         if (msgs.isEmpty())
144                                 msgs = QString::fromAscii(msg);
145                         else
146                                 msgs += '\n' + QString::fromAscii(msg);
147                         free(msg);
148                 } else if (showIf)
149                         qDebug() << i->ifname << i->reason;
150         }
151
152         if (onLine != isOn || carrier != isCarrier) {
153                 onLine = isOn;
154                 carrier = isCarrier;
155                 aniTimer->stop();
156                 aniCounter = 0;
157                 if (isOn) {
158                         animate();
159                         aniTimer->start(300);
160                 } else if (isCarrier) {
161                         animate();
162                         aniTimer->start(500);
163                 } else
164                         setIcon("status", "network-offline");
165         }
166
167         trayIcon->setToolTip(msgs);
168 }
169
170 void DhcpcdQt::statusCallback(const char *status)
171 {
172
173         qDebug("Status changed to %s", status);
174         if (strcmp(status, "down") == 0) {
175                 aniTimer->stop();
176                 aniCounter = 0;
177                 onLine = carrier = false;
178                 setIcon("status", "network-offline");
179                 if (notifier) {
180                         delete notifier;
181                         notifier = NULL;
182                 }
183                 trayIcon->setToolTip(tr("Not connected to dhcpcd"));
184         } else {
185                 bool refresh;
186
187                 if (lastStatus == NULL || strcmp(lastStatus, "down") == 0) {
188                         qDebug("Connected to dhcpcd-%s", dhcpcd_version(con));
189                         refresh = true;
190                 } else
191                         refresh = strcmp(lastStatus, "opened") ? false : true;
192                 updateOnline(refresh);
193         }
194
195         free(lastStatus);
196         lastStatus = strdup(status);
197
198         if (strcmp(status, "down") == 0) {
199                 if (retryOpenTimer == NULL) {
200                         retryOpenTimer = new QTimer(this);
201                         connect(retryOpenTimer, SIGNAL(timeout()),
202                             this, SLOT(tryOpen()));
203                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
204                 }
205         }
206 }
207
208 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
209     const char *status, void *d)
210 {
211         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
212
213         dhcpcdQt->statusCallback(status);
214 }
215
216 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
217 {
218         char *msg;
219         bool new_msg;
220
221         if (strcmp(i->reason, "RENEW") &&
222             strcmp(i->reason, "STOP") &&
223             strcmp(i->reason, "STOPPED"))
224         {
225                 msg = dhcpcd_if_message(i, &new_msg);
226                 if (msg) {
227                         qDebug("%s", msg);
228                         if (new_msg) {
229                                 QSystemTrayIcon::MessageIcon icon =
230                                     i->up ? QSystemTrayIcon::Information :
231                                     QSystemTrayIcon::Warning;
232                                 trayIcon->showMessage(tr("Network Event"),
233                                     msg, icon);
234                         }
235                         free(msg);
236                 }
237         }
238
239         updateOnline(false);
240 }
241
242 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
243 {
244         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
245
246         dhcpcdQt->ifCallback(i);
247 }
248
249 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
250 {
251
252         for (auto &wi : *wis) {
253                 if (wi->getWpa() == wpa)
254                         return wi;
255         }
256         return NULL;
257 }
258
259 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
260 {
261         DHCPCD_WI_SCAN *scans, *s1, *s2;
262         int fd = dhcpcd_wpa_get_fd(wpa);
263         DhcpcdWi *wi;
264
265         wi = findWi(wpa);
266         if (fd == -1) {
267                 qCritical("No fd for WPA");
268                 if (wi) {
269                         wis->removeOne(wi);
270                         delete wi;
271                 }
272                 return;
273         }
274
275         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
276         if (i == NULL) {
277                 qCritical("No interface for WPA");
278                 if (wi) {
279                         wis->removeOne(wi);
280                         delete wi;
281                 }
282                 return;
283         }
284
285         qDebug("%s: Received scan results", i->ifname);
286         scans = dhcpcd_wi_scans(i);
287         if (wi == NULL) {
288                 wi = new DhcpcdWi(this, wpa);
289                 wis->append(wi);
290         } else {
291                 QString title = tr("New Access Point");
292                 QString txt;
293                 for (s1 = scans; s1; s1 = s1->next) {
294                         for (s2 = wi->getScans(); s2; s2 = s2->next) {
295                                 if (strcmp(s1->ssid, s2->ssid) == 0)
296                                         break;
297                         }
298                         if (s2 == NULL) {
299                                 if (!txt.isEmpty()) {
300                                         title = tr("New Access Points");
301                                         txt += '\n';
302                                 }
303                                 txt += s1->ssid;
304                         }
305                 }
306                 if (!txt.isEmpty() &&
307                     (ssidMenu == NULL || !ssidMenu->isVisible()))
308                         notify(title, txt);
309         }
310
311         if (wi->setScans(scans) && ssidMenu->isVisible())
312                 ssidMenu->popup(ssidMenuPos);
313 }
314
315 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
316 {
317         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
318
319         dhcpcdQt->scanCallback(wpa);
320 }
321
322 void DhcpcdQt::wpaStatusCallback(DHCPCD_WPA *wpa, const char *status)
323 {
324         DHCPCD_IF *i;
325
326         i = dhcpcd_wpa_if(wpa);
327         qDebug("%s: WPA status %s", i->ifname, status);
328         if (strcmp(status, "down") == 0) {
329                 DhcpcdWi *wi = findWi(wpa);
330                 if (wi) {
331                         wis->removeOne(wi);
332                         delete wi;
333                 }
334         }
335 }
336
337 void DhcpcdQt::dhcpcd_wpa_status_cb(DHCPCD_WPA *wpa, const char *status,
338     void *d)
339 {
340         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
341
342         dhcpcdQt->wpaStatusCallback(wpa, status);
343 }
344
345 void DhcpcdQt::tryOpen() {
346         int fd = dhcpcd_open(con);
347         static int last_error;
348
349         if (fd == -1) {
350                 if (errno != last_error) {
351                         last_error = errno;
352                         qCritical("dhcpcd_open: %s", strerror(errno));
353                 }
354                 if (retryOpenTimer == NULL) {
355                         retryOpenTimer = new QTimer(this);
356                         connect(retryOpenTimer, SIGNAL(timeout()),
357                             this, SLOT(tryOpen()));
358                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
359                 }
360                 return;
361         }
362
363         /* Start listening to WPA events */
364         dhcpcd_wpa_start(con);
365
366         if (retryOpenTimer) {
367                 delete retryOpenTimer;
368                 retryOpenTimer = NULL;
369         }
370
371         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
372         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
373 }
374
375 void DhcpcdQt::dispatch() {
376
377         if (dhcpcd_get_fd(con) == -1) {
378                 qWarning("dhcpcd connection lost");
379                 return;
380         }
381
382         dhcpcd_dispatch(con);
383 }
384
385 void DhcpcdQt::notify(QString &title, QString &msg,
386     QSystemTrayIcon::MessageIcon icon)
387 {
388
389         qDebug("%s", qPrintable(msg));
390         trayIcon->showMessage(title, msg, icon);
391 }
392
393
394 void DhcpcdQt::closeEvent(QCloseEvent *event)
395 {
396
397         if (trayIcon->isVisible()) {
398                 hide();
399                 event->ignore();
400         }
401 }
402
403 QIcon DhcpcdQt::getIcon(QString category, QString name)
404 {
405         QIcon icon;
406
407         if (QIcon::hasThemeIcon(name))
408                 icon = QIcon::fromTheme(name);
409         else
410                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
411         return icon;
412 }
413
414 void DhcpcdQt::setIcon(QString category, QString name)
415 {
416         QIcon icon = getIcon(category, name);
417
418         trayIcon->setIcon(icon);
419 }
420
421 QIcon DhcpcdQt::icon()
422 {
423
424         return getIcon("status", "network-transmit-receive");
425 }
426
427 void DhcpcdQt::createSsidMenu()
428 {
429
430         if (ssidMenu) {
431                 delete ssidMenu;
432                 ssidMenu = NULL;
433         }
434         if (wis->size() == 0)
435                 return;
436
437         ssidMenu = new QMenu(this);
438         if (wis->size() == 1)
439                 wis->first()->createMenu(ssidMenu);
440         else {
441                 for (auto &wi : *wis)
442                         ssidMenu->addMenu(wi->createIfMenu(ssidMenu));
443         }
444         ssidMenuPos = QCursor::pos();
445         ssidMenu->popup(ssidMenuPos);
446 }
447
448 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
449 {
450
451         if (reason == QSystemTrayIcon::Trigger)
452                 createSsidMenu();
453 }
454
455 void DhcpcdQt::dialogClosed(QDialog *dialog)
456 {
457
458         if (dialog == about)
459                 about = NULL;
460         else if (dialog == preferences)
461                 preferences = NULL;
462 }
463
464 void DhcpcdQt::showPreferences()
465 {
466
467         if (preferences == NULL) {
468                 preferences = new DhcpcdPreferences(this);
469                 preferences->show();
470         } else
471                 preferences->activateWindow();
472 }
473
474 void DhcpcdQt::showAbout()
475 {
476
477         if (about == NULL) {
478                 about = new DhcpcdAbout(this);
479                 about->show();
480         } else
481                 about->activateWindow();
482 }
483
484 void DhcpcdQt::createActions()
485 {
486
487         preferencesAction = new QAction(tr("&Preferences"), this);
488         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
489         connect(preferencesAction, SIGNAL(triggered()),
490             this, SLOT(showPreferences()));
491
492         aboutAction = new QAction(tr("&About"), this);
493         aboutAction->setIcon(QIcon::fromTheme("help-about"));
494         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
495
496         quitAction = new QAction(tr("&Quit"), this);
497         quitAction->setIcon(QIcon::fromTheme("application-exit"));
498         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
499
500 }
501
502 void DhcpcdQt::createTrayIcon()
503 {
504
505         trayIconMenu = new QMenu(this);
506         trayIconMenu->addAction(preferencesAction);
507         trayIconMenu->addSeparator();
508         trayIconMenu->addAction(aboutAction);
509         trayIconMenu->addAction(quitAction);
510
511         trayIcon = new QSystemTrayIcon(this);
512         setIcon("status", "network-offline");
513         trayIcon->setContextMenu(trayIconMenu);
514
515         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
516             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
517
518         trayIcon->show();
519 }