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