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