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