Remove vanished SSID's from menu.
[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 DhcpcdQt::~DhcpcdQt()
76 {
77
78         qDeleteAll(*wis);
79         delete wis;
80
81         if (con != NULL) {
82                 dhcpcd_close(con);
83                 dhcpcd_free(con);
84         }
85
86         free(lastStatus);
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                 carrier = isCarrier;
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         if (strcmp(i->reason, "RENEW") &&
220             strcmp(i->reason, "STOP") &&
221             strcmp(i->reason, "STOPPED"))
222         {
223                 msg = dhcpcd_if_message(i, &new_msg);
224                 if (msg) {
225                         qDebug("%s", msg);
226                         if (new_msg) {
227                                 QSystemTrayIcon::MessageIcon icon =
228                                     i->up ? QSystemTrayIcon::Information :
229                                     QSystemTrayIcon::Warning;
230                                 trayIcon->showMessage(tr("Network Event"),
231                                     msg, icon);
232                         }
233                         free(msg);
234                 }
235         }
236
237         updateOnline(false);
238 }
239
240 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
241 {
242         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
243
244         dhcpcdQt->ifCallback(i);
245 }
246
247 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
248 {
249
250         for (auto &wi : *wis) {
251                 if (wi->getWpa() == wpa)
252                         return wi;
253         }
254         return NULL;
255 }
256
257 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
258 {
259         DHCPCD_WI_SCAN *scans, *s1, *s2;
260         int fd = dhcpcd_wpa_get_fd(wpa);
261         DhcpcdWi *wi;
262
263         wi = findWi(wpa);
264         if (fd == -1) {
265                 qCritical("No fd for WPA");
266                 if (wi) {
267                         wis->removeOne(wi);
268                         delete wi;
269                 }
270                 return;
271         }
272
273         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
274         if (i == NULL) {
275                 qCritical("No interface for WPA");
276                 if (wi) {
277                         wis->removeOne(wi);
278                         delete wi;
279                 }
280                 return;
281         }
282
283         qDebug("%s: Received scan results", i->ifname);
284         scans = dhcpcd_wi_scans(i);
285         if (wi == NULL) {
286                 wi = new DhcpcdWi(this, wpa);
287                 wis->append(wi);
288         } else {
289                 QString title = tr("New Access Point");
290                 QString txt;
291                 for (s1 = scans; s1; s1 = s1->next) {
292                         for (s2 = wi->getScans(); s2; s2 = s2->next) {
293                                 if (strcmp(s1->ssid, s2->ssid) == 0)
294                                         break;
295                         }
296                         if (s2 == NULL) {
297                                 if (!txt.isEmpty()) {
298                                         title = tr("New Access Points");
299                                         txt += '\n';
300                                 }
301                                 txt += s1->ssid;
302                         }
303                 }
304                 if (!txt.isEmpty() &&
305                     (ssidMenu == NULL || !ssidMenu->isVisible()))
306                         notify(title, txt);
307         }
308
309         if (wi->setScans(scans) && ssidMenu->isVisible())
310                 ssidMenu->popup(ssidMenuPos);
311 }
312
313 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
314 {
315         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
316
317         dhcpcdQt->scanCallback(wpa);
318 }
319
320 void DhcpcdQt::tryOpen() {
321         int fd = dhcpcd_open(con);
322         static int last_error;
323
324         if (fd == -1) {
325                 if (errno != last_error) {
326                         last_error = errno;
327                         qCritical("dhcpcd_open: %s", strerror(errno));
328                 }
329                 if (retryOpenTimer == NULL) {
330                         retryOpenTimer = new QTimer(this);
331                         connect(retryOpenTimer, SIGNAL(timeout()),
332                             this, SLOT(tryOpen()));
333                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
334                 }
335                 return;
336         }
337
338         /* Start listening to WPA events */
339         dhcpcd_wpa_start(con);
340
341         if (retryOpenTimer) {
342                 delete retryOpenTimer;
343                 retryOpenTimer = NULL;
344         }
345
346         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
347         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
348 }
349
350 void DhcpcdQt::dispatch() {
351
352         if (dhcpcd_get_fd(con) == -1) {
353                 qWarning("dhcpcd connection lost");
354                 return;
355         }
356
357         dhcpcd_dispatch(con);
358 }
359
360 void DhcpcdQt::notify(QString &title, QString &msg,
361     QSystemTrayIcon::MessageIcon icon)
362 {
363
364         qDebug("%s", qPrintable(msg));
365         trayIcon->showMessage(title, msg, icon);
366 }
367
368
369 void DhcpcdQt::closeEvent(QCloseEvent *event)
370 {
371
372         if (trayIcon->isVisible()) {
373                 hide();
374                 event->ignore();
375         }
376 }
377
378 QIcon DhcpcdQt::getIcon(QString category, QString name)
379 {
380         QIcon icon;
381
382         if (QIcon::hasThemeIcon(name))
383                 icon = QIcon::fromTheme(name);
384         else
385                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
386         return icon;
387 }
388
389 void DhcpcdQt::setIcon(QString category, QString name)
390 {
391         QIcon icon = getIcon(category, name);
392
393         trayIcon->setIcon(icon);
394 }
395
396 QIcon DhcpcdQt::icon()
397 {
398
399         return getIcon("status", "network-transmit-receive");
400 }
401
402 void DhcpcdQt::createSsidMenu()
403 {
404
405         if (ssidMenu) {
406                 delete ssidMenu;
407                 ssidMenu = NULL;
408         }
409         if (wis->size() == 0)
410                 return;
411
412         ssidMenu = new QMenu(this);
413         if (wis->size() == 1)
414                 wis->first()->createMenu(ssidMenu);
415         else {
416                 for (auto &wi : *wis)
417                         ssidMenu->addMenu(wi->createIfMenu(ssidMenu));
418         }
419         ssidMenuPos = QCursor::pos();
420         ssidMenu->popup(ssidMenuPos);
421 }
422
423 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
424 {
425
426         if (reason == QSystemTrayIcon::Trigger)
427                 createSsidMenu();
428 }
429
430 void DhcpcdQt::dialogClosed(QDialog *dialog)
431 {
432
433         if (dialog == about)
434                 about = NULL;
435         else if (dialog == preferences)
436                 preferences = NULL;
437 }
438
439 void DhcpcdQt::showPreferences()
440 {
441
442         if (preferences == NULL) {
443                 preferences = new DhcpcdPreferences(this);
444                 preferences->show();
445         } else
446                 preferences->activateWindow();
447 }
448
449 void DhcpcdQt::showAbout()
450 {
451
452         if (about == NULL) {
453                 about = new DhcpcdAbout(this);
454                 about->show();
455         } else
456                 about->activateWindow();
457 }
458
459 void DhcpcdQt::createActions()
460 {
461
462         preferencesAction = new QAction(tr("&Preferences"), this);
463         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
464         connect(preferencesAction, SIGNAL(triggered()),
465             this, SLOT(showPreferences()));
466
467         aboutAction = new QAction(tr("&About"), this);
468         aboutAction->setIcon(QIcon::fromTheme("help-about"));
469         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
470
471         quitAction = new QAction(tr("&Quit"), this);
472         quitAction->setIcon(QIcon::fromTheme("application-exit"));
473         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
474
475 }
476
477 void DhcpcdQt::createTrayIcon()
478 {
479
480         trayIconMenu = new QMenu(this);
481         trayIconMenu->addAction(preferencesAction);
482         trayIconMenu->addSeparator();
483         trayIconMenu->addAction(aboutAction);
484         trayIconMenu->addAction(quitAction);
485
486         trayIcon = new QSystemTrayIcon(this);
487         setIcon("status", "network-offline");
488         trayIcon->setContextMenu(trayIconMenu);
489
490         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
491             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
492
493         trayIcon->show();
494 }