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