Split listening to WPA events out of dhcpcd_open as its
[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 = strcmp(lastStatus, "opened") ? false : true;
189                 printf ("refresh %d\n", refresh);
190                 updateOnline(refresh);
191                 printf ("updated\n");
192         }
193
194         free(lastStatus);
195         lastStatus = strdup(status);
196
197         if (strcmp(status, "down") == 0) {
198                 if (retryOpenTimer == NULL) {
199                         retryOpenTimer = new QTimer(this);
200                         connect(retryOpenTimer, SIGNAL(timeout()),
201                             this, SLOT(tryOpen()));
202                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
203                 }
204         }
205 }
206
207 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
208     const char *status, void *d)
209 {
210         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
211
212         dhcpcdQt->statusCallback(status);
213 }
214
215 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
216 {
217         char *msg;
218         bool new_msg;
219
220         updateOnline(false);
221
222         if (strcmp(i->reason, "RENEW") == 0 ||
223             strcmp(i->reason, "STOP") == 0 ||
224             strcmp(i->reason, "STOPPED") == 0)
225                 return;
226
227         msg = dhcpcd_if_message(i, &new_msg);
228         if (msg) {
229                 qDebug("%s", msg);
230                 if (new_msg) {
231                         QSystemTrayIcon::MessageIcon icon =
232                             i->up ? QSystemTrayIcon::Information :
233                             QSystemTrayIcon::Warning;
234                         trayIcon->showMessage(tr("Network Event"), msg, icon);
235                 }
236                 free(msg);
237         }
238 }
239
240 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
241 {
242         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
243
244         dhcpcdQt->ifCallback(i);
245 }
246
247 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
248 {
249
250         for (auto &wi : *wis) {
251                 if (wi->getWpa() == wpa)
252                         return wi;
253         }
254         return NULL;
255 }
256
257 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
258 {
259         DHCPCD_WI_SCAN *scans, *s1, *s2;
260         int fd = dhcpcd_wpa_get_fd(wpa);
261         DhcpcdWi *wi;
262
263         wi = findWi(wpa);
264         if (fd == -1) {
265                 qCritical("No fd for WPA");
266                 if (wi) {
267                         wis->removeOne(wi);
268                         delete wi;
269                 }
270                 return;
271         }
272
273         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
274         if (i == NULL) {
275                 qCritical("No interface for WPA");
276                 if (wi) {
277                         wis->removeOne(wi);
278                         delete wi;
279                 }
280                 return;
281         }
282
283         qDebug("%s: Received scan results", i->ifname);
284         scans = dhcpcd_wi_scans(i);
285         if (wi == NULL) {
286                 wi = new DhcpcdWi(this, wpa);
287                 wis->append(wi);
288         } else {
289                 QString title = tr("New Access Point");
290                 QString txt;
291                 for (s1 = scans; s1; s1 = s1->next) {
292                         for (s2 = wi->getScans(); s2; s2 = s2->next) {
293                                 if (strcmp(s1->ssid, s2->ssid) == 0)
294                                         break;
295                                 if (s2 == NULL) {
296                                         if (!txt.isEmpty()) {
297                                                 title = tr("New Access Points");
298                                                 txt += '\n';
299                                         }
300                                         txt += s1->ssid;
301                                 }
302                         }
303                 }
304                 if (!txt.isEmpty())
305                         notify(title, txt);
306         }
307         wi->setScans(scans);
308 }
309
310 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
311 {
312         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
313
314         dhcpcdQt->scanCallback(wpa);
315 }
316
317 void DhcpcdQt::tryOpen() {
318         int fd = dhcpcd_open(con);
319         static int last_error;
320
321         if (fd == -1) {
322                 if (errno != last_error) {
323                         last_error = errno;
324                         qCritical("dhcpcd_open: %s", strerror(errno));
325                 }
326                 if (retryOpenTimer == NULL) {
327                         retryOpenTimer = new QTimer(this);
328                         connect(retryOpenTimer, SIGNAL(timeout()),
329                             this, SLOT(tryOpen()));
330                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
331                 }
332                 return;
333         }
334
335         /* Start listening to WPA events */
336         dhcpcd_wpa_start(con);
337
338         if (retryOpenTimer) {
339                 delete retryOpenTimer;
340                 retryOpenTimer = NULL;
341         }
342
343         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
344         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
345 }
346
347 void DhcpcdQt::dispatch() {
348
349         if (dhcpcd_get_fd(con) == -1) {
350                 qWarning("dhcpcd connection lost");
351                 return;
352         }
353
354         dhcpcd_dispatch(con);
355 }
356
357 void DhcpcdQt::notify(QString &title, QString &msg,
358     QSystemTrayIcon::MessageIcon icon)
359 {
360
361         qDebug("%s", qPrintable(msg));
362         trayIcon->showMessage(title, msg, icon);
363 }
364
365
366 void DhcpcdQt::closeEvent(QCloseEvent *event)
367 {
368
369         if (trayIcon->isVisible()) {
370                 hide();
371                 event->ignore();
372         }
373 }
374
375 QIcon DhcpcdQt::getIcon(QString category, QString name)
376 {
377         QIcon icon;
378
379         if (QIcon::hasThemeIcon(name))
380                 icon = QIcon::fromTheme(name);
381         else
382                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
383         return icon;
384 }
385
386 void DhcpcdQt::setIcon(QString category, QString name)
387 {
388         QIcon icon = getIcon(category, name);
389
390         trayIcon->setIcon(icon);
391 }
392
393 QIcon DhcpcdQt::icon()
394 {
395
396         return getIcon("status", "network-transmit-receive");
397 }
398
399 void DhcpcdQt::addSsidMenu(QMenu *&menu, DHCPCD_IF *ifp, DhcpcdWi *&wi)
400 {
401         DHCPCD_WI_SCAN *scan;
402
403         for (scan = wi->getScans(); scan; scan = scan->next) {
404                 QWidgetAction *wa = new QWidgetAction(menu);
405                 DhcpcdSsidMenu *ssidMenu = new DhcpcdSsidMenu(menu, ifp, scan);
406                 wa->setDefaultWidget(ssidMenu);
407                 menu->addAction(wa);
408                 connect(ssidMenu, SIGNAL(selected(DHCPCD_IF *, DHCPCD_WI_SCAN *)),
409                     this, SLOT(connectSsid(DHCPCD_IF *, DHCPCD_WI_SCAN *)));
410         }
411 }
412
413 void DhcpcdQt::connectSsid(DHCPCD_IF *, DHCPCD_WI_SCAN *)
414 {
415
416         QMessageBox::information(this, "Not implemented",
417             "SSID selection is not yet implemented");
418 }
419
420 void DhcpcdQt::createSsidMenu()
421 {
422         DHCPCD_WPA *wpa;
423         DHCPCD_IF *ifp;
424
425         if (ssidMenu) {
426                 delete ssidMenu;
427                 ssidMenu = NULL;
428         }
429         if (wis->size() == 0)
430                 return;
431
432         ssidMenu = new QMenu(this);
433         if (wis->size() == 1) {
434                 DhcpcdWi *wi = wis->first();
435                 wpa = wi->getWpa();
436                 ifp = dhcpcd_wpa_if(wpa);
437                 addSsidMenu(ssidMenu, ifp, wi);
438         } else {
439                 for (auto &wi : *wis) {
440                         wpa = wi->getWpa();
441                         ifp = dhcpcd_wpa_if(wpa);
442                         if (ifp) {
443                                 QMenu *ifmenu = ssidMenu->addMenu(ifp->ifname);
444                                 addSsidMenu(ifmenu, ifp, wi);
445                         }
446                 }
447         }
448         ssidMenu->popup(QCursor::pos());
449 }
450
451 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
452 {
453
454         if (reason == QSystemTrayIcon::Trigger)
455                 createSsidMenu();
456 }
457
458 void DhcpcdQt::dialogClosed(QDialog *dialog)
459 {
460
461         if (dialog == about)
462                 about = NULL;
463         else if (dialog == preferences)
464                 preferences = NULL;
465 }
466
467 void DhcpcdQt::showPreferences()
468 {
469
470         if (preferences == NULL) {
471                 preferences = new DhcpcdPreferences(this);
472                 preferences->show();
473         } else
474                 preferences->activateWindow();
475 }
476
477 void DhcpcdQt::showAbout()
478 {
479
480         if (about == NULL) {
481                 about = new DhcpcdAbout(this);
482                 about->show();
483         } else
484                 about->activateWindow();
485 }
486
487 void DhcpcdQt::createActions()
488 {
489
490         preferencesAction = new QAction(tr("&Preferences"), this);
491         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
492         connect(preferencesAction, SIGNAL(triggered()),
493             this, SLOT(showPreferences()));
494
495         aboutAction = new QAction(tr("&About"), this);
496         aboutAction->setIcon(QIcon::fromTheme("help-about"));
497         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
498
499         quitAction = new QAction(tr("&Quit"), this);
500         quitAction->setIcon(QIcon::fromTheme("application-exit"));
501         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
502
503 }
504
505 void DhcpcdQt::createTrayIcon()
506 {
507
508         trayIconMenu = new QMenu(this);
509         trayIconMenu->addAction(preferencesAction);
510         trayIconMenu->addSeparator();
511         trayIconMenu->addAction(aboutAction);
512         trayIconMenu->addAction(quitAction);
513
514         trayIcon = new QSystemTrayIcon(this);
515         setIcon("status", "network-offline");
516         trayIcon->setContextMenu(trayIconMenu);
517
518         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
519             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
520
521         trayIcon->show();
522 }