9a9169373e0cada629917b4c3d2e918403c5ac3c
[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                 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         /* Start listening to WPA events */
334         dhcpcd_wpa_start(con);
335
336         if (retryOpenTimer) {
337                 delete retryOpenTimer;
338                 retryOpenTimer = NULL;
339         }
340
341         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
342         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
343 }
344
345 void DhcpcdQt::dispatch() {
346
347         if (dhcpcd_get_fd(con) == -1) {
348                 qWarning("dhcpcd connection lost");
349                 return;
350         }
351
352         dhcpcd_dispatch(con);
353 }
354
355 void DhcpcdQt::notify(QString &title, QString &msg,
356     QSystemTrayIcon::MessageIcon icon)
357 {
358
359         qDebug("%s", qPrintable(msg));
360         trayIcon->showMessage(title, msg, icon);
361 }
362
363
364 void DhcpcdQt::closeEvent(QCloseEvent *event)
365 {
366
367         if (trayIcon->isVisible()) {
368                 hide();
369                 event->ignore();
370         }
371 }
372
373 QIcon DhcpcdQt::getIcon(QString category, QString name)
374 {
375         QIcon icon;
376
377         if (QIcon::hasThemeIcon(name))
378                 icon = QIcon::fromTheme(name);
379         else
380                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
381         return icon;
382 }
383
384 void DhcpcdQt::setIcon(QString category, QString name)
385 {
386         QIcon icon = getIcon(category, name);
387
388         trayIcon->setIcon(icon);
389 }
390
391 QIcon DhcpcdQt::icon()
392 {
393
394         return getIcon("status", "network-transmit-receive");
395 }
396
397 void DhcpcdQt::addSsidMenu(QMenu *&menu, DHCPCD_IF *ifp, DhcpcdWi *&wi)
398 {
399         DHCPCD_WI_SCAN *scan;
400
401         for (scan = wi->getScans(); scan; scan = scan->next) {
402                 QWidgetAction *wa = new QWidgetAction(menu);
403                 DhcpcdSsidMenu *ssidMenu = new DhcpcdSsidMenu(menu, ifp, scan);
404                 wa->setDefaultWidget(ssidMenu);
405                 menu->addAction(wa);
406                 connect(ssidMenu, SIGNAL(selected(DHCPCD_IF *, DHCPCD_WI_SCAN *)),
407                     this, SLOT(connectSsid(DHCPCD_IF *, DHCPCD_WI_SCAN *)));
408         }
409 }
410
411 void DhcpcdQt::connectSsid(DHCPCD_IF *, DHCPCD_WI_SCAN *)
412 {
413
414         QMessageBox::information(this, "Not implemented",
415             "SSID selection is not yet implemented");
416 }
417
418 void DhcpcdQt::createSsidMenu()
419 {
420         DHCPCD_WPA *wpa;
421         DHCPCD_IF *ifp;
422
423         if (ssidMenu) {
424                 delete ssidMenu;
425                 ssidMenu = NULL;
426         }
427         if (wis->size() == 0)
428                 return;
429
430         ssidMenu = new QMenu(this);
431         if (wis->size() == 1) {
432                 DhcpcdWi *wi = wis->first();
433                 wpa = wi->getWpa();
434                 ifp = dhcpcd_wpa_if(wpa);
435                 addSsidMenu(ssidMenu, ifp, wi);
436         } else {
437                 for (auto &wi : *wis) {
438                         wpa = wi->getWpa();
439                         ifp = dhcpcd_wpa_if(wpa);
440                         if (ifp) {
441                                 QMenu *ifmenu = ssidMenu->addMenu(ifp->ifname);
442                                 addSsidMenu(ifmenu, ifp, wi);
443                         }
444                 }
445         }
446         ssidMenu->popup(QCursor::pos());
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 }