4d0ead8f7fd3db7fc85868876f2d6e769c8d0295
[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         tryOpen();
73
74 }
75
76 DhcpcdQt::~DhcpcdQt()
77 {
78
79         qDeleteAll(*wis);
80         delete wis;
81
82         if (con != NULL) {
83                 dhcpcd_close(con);
84                 dhcpcd_free(con);
85         }
86
87         free(lastStatus);
88 }
89
90 void DhcpcdQt::animate()
91 {
92         const char *icon;
93
94         if (onLine) {
95                 if (aniCounter++ > 6) {
96                         aniTimer->stop();
97                         aniCounter = 0;
98                         return;
99                 }
100
101                 if (aniCounter % 2 == 0)
102                         icon = "network-idle";
103                 else
104                         icon = "network-transmit-receive";
105         } else {
106                 switch(aniCounter++) {
107                 case 0:
108                         icon = "network-transmit";
109                         break;
110                 case 1:
111                         icon = "network-receive";
112                         break;
113                 default:
114                         icon = "network-idle";
115                         aniCounter = 0;
116                 }
117         }
118
119         setIcon("status", icon);
120 }
121
122 void DhcpcdQt::updateOnline(bool showIf)
123 {
124         bool isOn, isCarrier;
125         char *msg;
126         DHCPCD_IF *ifs, *i;
127         QString msgs;
128
129         isOn = isCarrier = false;
130         ifs = dhcpcd_interfaces(con);
131         for (i = ifs; i; i = i->next) {
132                 if (strcmp(i->type, "link") == 0) {
133                         if (i->up)
134                                 isCarrier = true;
135                 } else {
136                         if (i->up)
137                                 isOn = true;
138                 }
139                 msg = dhcpcd_if_message(i, NULL);
140                 if (msg) {
141                         if (showIf)
142                                 qDebug() << msg;
143                         if (msgs.isEmpty())
144                                 msgs = QString::fromAscii(msg);
145                         else
146                                 msgs += '\n' + QString::fromAscii(msg);
147                         free(msg);
148                 } else if (showIf)
149                         qDebug() << i->ifname << i->reason;
150         }
151
152         if (onLine != isOn || carrier != isCarrier) {
153                 onLine = isOn;
154                 aniTimer->stop();
155                 aniCounter = 0;
156                 if (isOn) {
157                         animate();
158                         aniTimer->start(300);
159                 } else if (isCarrier) {
160                         animate();
161                         aniTimer->start(500);
162                 } else
163                         setIcon("status", "network-offline");
164         }
165
166         trayIcon->setToolTip(msgs);
167 }
168
169 void DhcpcdQt::statusCallback(const char *status)
170 {
171
172         qDebug("Status changed to %s", status);
173         if (strcmp(status, "down") == 0) {
174                 aniTimer->stop();
175                 aniCounter = 0;
176                 setIcon("status", "network-offline");
177                 if (notifier) {
178                         delete notifier;
179                         notifier = NULL;
180                 }
181                 trayIcon->setToolTip(tr("Not connected to dhcpcd"));
182         } else {
183                 bool refresh;
184
185                 if (lastStatus == NULL || strcmp(lastStatus, "down") == 0) {
186                         qDebug("Connected to dhcpcd-%s", dhcpcd_version(con));
187                         refresh = true;
188                 } else
189                         refresh = strcmp(lastStatus, "opened") ? false : true;
190                 updateOnline(refresh);
191         }
192
193         free(lastStatus);
194         lastStatus = strdup(status);
195
196         if (strcmp(status, "down") == 0) {
197                 if (retryOpenTimer == NULL) {
198                         retryOpenTimer = new QTimer(this);
199                         connect(retryOpenTimer, SIGNAL(timeout()),
200                             this, SLOT(tryOpen()));
201                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
202                 }
203         }
204 }
205
206 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
207     const char *status, void *d)
208 {
209         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
210
211         dhcpcdQt->statusCallback(status);
212 }
213
214 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
215 {
216         char *msg;
217         bool new_msg;
218
219         updateOnline(false);
220
221         if (strcmp(i->reason, "RENEW") == 0 ||
222             strcmp(i->reason, "STOP") == 0 ||
223             strcmp(i->reason, "STOPPED") == 0)
224                 return;
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"), msg, icon);
234                 }
235                 free(msg);
236         }
237 }
238
239 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
240 {
241         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
242
243         dhcpcdQt->ifCallback(i);
244 }
245
246 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
247 {
248
249         for (auto &wi : *wis) {
250                 if (wi->getWpa() == wpa)
251                         return wi;
252         }
253         return NULL;
254 }
255
256 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
257 {
258         DHCPCD_WI_SCAN *scans, *s1, *s2;
259         int fd = dhcpcd_wpa_get_fd(wpa);
260         DhcpcdWi *wi;
261
262         wi = findWi(wpa);
263         if (fd == -1) {
264                 qCritical("No fd for WPA");
265                 if (wi) {
266                         wis->removeOne(wi);
267                         delete wi;
268                 }
269                 return;
270         }
271
272         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
273         if (i == NULL) {
274                 qCritical("No interface for WPA");
275                 if (wi) {
276                         wis->removeOne(wi);
277                         delete wi;
278                 }
279                 return;
280         }
281
282         qDebug("%s: Received scan results", i->ifname);
283         scans = dhcpcd_wi_scans(i);
284         if (wi == NULL) {
285                 wi = new DhcpcdWi(this, wpa);
286                 wis->append(wi);
287         } else {
288                 QString title = tr("New Access Point");
289                 QString txt;
290                 for (s1 = scans; s1; s1 = s1->next) {
291                         for (s2 = wi->getScans(); s2; s2 = s2->next) {
292                                 if (strcmp(s1->ssid, s2->ssid) == 0)
293                                         break;
294                         }
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                 if (!txt.isEmpty())
304                         notify(title, txt);
305         }
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::createSsidMenu()
400 {
401
402         if (ssidMenu) {
403                 delete ssidMenu;
404                 ssidMenu = NULL;
405         }
406         if (wis->size() == 0)
407                 return;
408
409         ssidMenu = new QMenu(this);
410         if (wis->size() == 1)
411                 wis->first()->createMenu(ssidMenu);
412         else {
413                 for (auto &wi : *wis)
414                         ssidMenu->addMenu(wi->createIfMenu(ssidMenu));
415         }
416         ssidMenu->popup(QCursor::pos());
417 }
418
419 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
420 {
421
422         if (reason == QSystemTrayIcon::Trigger)
423                 createSsidMenu();
424 }
425
426 void DhcpcdQt::dialogClosed(QDialog *dialog)
427 {
428
429         if (dialog == about)
430                 about = NULL;
431         else if (dialog == preferences)
432                 preferences = NULL;
433 }
434
435 void DhcpcdQt::showPreferences()
436 {
437
438         if (preferences == NULL) {
439                 preferences = new DhcpcdPreferences(this);
440                 preferences->show();
441         } else
442                 preferences->activateWindow();
443 }
444
445 void DhcpcdQt::showAbout()
446 {
447
448         if (about == NULL) {
449                 about = new DhcpcdAbout(this);
450                 about->show();
451         } else
452                 about->activateWindow();
453 }
454
455 void DhcpcdQt::createActions()
456 {
457
458         preferencesAction = new QAction(tr("&Preferences"), this);
459         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
460         connect(preferencesAction, SIGNAL(triggered()),
461             this, SLOT(showPreferences()));
462
463         aboutAction = new QAction(tr("&About"), this);
464         aboutAction->setIcon(QIcon::fromTheme("help-about"));
465         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
466
467         quitAction = new QAction(tr("&Quit"), this);
468         quitAction->setIcon(QIcon::fromTheme("application-exit"));
469         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
470
471 }
472
473 void DhcpcdQt::createTrayIcon()
474 {
475
476         trayIconMenu = new QMenu(this);
477         trayIconMenu->addAction(preferencesAction);
478         trayIconMenu->addSeparator();
479         trayIconMenu->addAction(aboutAction);
480         trayIconMenu->addAction(quitAction);
481
482         trayIcon = new QSystemTrayIcon(this);
483         setIcon("status", "network-offline");
484         trayIcon->setContextMenu(trayIconMenu);
485
486         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
487             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
488
489         trayIcon->show();
490 }