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