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