Set a progname to send with our command.
[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                 if (notifier) {
194                         delete notifier;
195                         notifier = NULL;
196                 }
197                 trayIcon->setToolTip(tr("Not connected to dhcpcd"));
198         } else {
199                 bool refresh;
200
201                 if (lastStatus == NULL || strcmp(lastStatus, "down") == 0) {
202                         qDebug("Connected to dhcpcd-%s", dhcpcd_version(con));
203                         refresh = true;
204                 } else
205                         refresh = strcmp(lastStatus, "opened") ? false : true;
206                 updateOnline(refresh);
207         }
208
209         free(lastStatus);
210         lastStatus = strdup(status);
211
212         if (strcmp(status, "down") == 0) {
213                 if (retryOpenTimer == NULL) {
214                         retryOpenTimer = new QTimer(this);
215                         connect(retryOpenTimer, SIGNAL(timeout()),
216                             this, SLOT(tryOpen()));
217                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
218                 }
219         }
220 }
221
222 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
223     const char *status, void *d)
224 {
225         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
226
227         dhcpcdQt->statusCallback(status);
228 }
229
230 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
231 {
232         char *msg;
233         bool new_msg;
234
235         if (strcmp(i->reason, "RENEW") &&
236             strcmp(i->reason, "STOP") &&
237             strcmp(i->reason, "STOPPED"))
238         {
239                 msg = dhcpcd_if_message(i, &new_msg);
240                 if (msg) {
241                         qDebug("%s", msg);
242                         if (new_msg) {
243                                 QSystemTrayIcon::MessageIcon icon =
244                                     i->up ? QSystemTrayIcon::Information :
245                                     QSystemTrayIcon::Warning;
246                                 trayIcon->showMessage(tr("Network Event"),
247                                     msg, icon);
248                         }
249                         free(msg);
250                 }
251         }
252
253         updateOnline(false);
254 }
255
256 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
257 {
258         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
259
260         dhcpcdQt->ifCallback(i);
261 }
262
263 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
264 {
265
266         for (auto &wi : *wis) {
267                 if (wi->getWpa() == wpa)
268                         return wi;
269         }
270         return NULL;
271 }
272
273 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
274 {
275         DHCPCD_WI_SCAN *scans, *s1, *s2;
276         int fd = dhcpcd_wpa_get_fd(wpa);
277         DhcpcdWi *wi;
278
279         wi = findWi(wpa);
280         if (fd == -1) {
281                 qCritical("No fd for WPA");
282                 if (wi) {
283                         wis->removeOne(wi);
284                         delete wi;
285                 }
286                 return;
287         }
288
289         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
290         if (i == NULL) {
291                 qCritical("No interface for WPA");
292                 if (wi) {
293                         wis->removeOne(wi);
294                         delete wi;
295                 }
296                 return;
297         }
298
299         qDebug("%s: Received scan results", i->ifname);
300         scans = dhcpcd_wi_scans(i);
301         if (wi == NULL) {
302                 wi = new DhcpcdWi(this, wpa);
303                 wis->append(wi);
304         } else {
305                 QString title = tr("New Access Point");
306                 QString txt;
307                 for (s1 = scans; s1; s1 = s1->next) {
308                         for (s2 = wi->getScans(); s2; s2 = s2->next) {
309                                 if (strcmp(s1->ssid, s2->ssid) == 0)
310                                         break;
311                         }
312                         if (s2 == NULL) {
313                                 if (!txt.isEmpty()) {
314                                         title = tr("New Access Points");
315                                         txt += '\n';
316                                 }
317                                 txt += s1->ssid;
318                         }
319                 }
320                 if (!txt.isEmpty() &&
321                     (ssidMenu == NULL || !ssidMenu->isVisible()))
322                         notify(title, txt);
323         }
324
325         if (wi->setScans(scans) && ssidMenu->isVisible())
326                 ssidMenu->popup(ssidMenuPos);
327 }
328
329 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
330 {
331         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
332
333         dhcpcdQt->scanCallback(wpa);
334 }
335
336 void DhcpcdQt::wpaStatusCallback(DHCPCD_WPA *wpa, const char *status)
337 {
338         DHCPCD_IF *i;
339
340         i = dhcpcd_wpa_if(wpa);
341         qDebug("%s: WPA status %s", i->ifname, status);
342         if (strcmp(status, "down") == 0) {
343                 DhcpcdWi *wi = findWi(wpa);
344                 if (wi) {
345                         wis->removeOne(wi);
346                         delete wi;
347                 }
348         }
349 }
350
351 void DhcpcdQt::dhcpcd_wpa_status_cb(DHCPCD_WPA *wpa, const char *status,
352     void *d)
353 {
354         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
355
356         dhcpcdQt->wpaStatusCallback(wpa, status);
357 }
358
359 void DhcpcdQt::tryOpen() {
360         int fd = dhcpcd_open(con, true);
361         static int last_error;
362
363         if (fd == -1) {
364                 if (errno == EACCES || errno == EPERM) {
365                         if ((fd = dhcpcd_open(con, false)) != -1)
366                                 goto unprived;
367                 }
368                 if (errno != last_error) {
369                         last_error = errno;
370                         const char *errt = strerror(errno);
371                         qCritical("dhcpcd_open: %s", errt);
372                         trayIcon->setToolTip(
373                             tr("Error connecting to dhcpcd: %1").arg(errt));
374                 }
375                 if (retryOpenTimer == NULL) {
376                         retryOpenTimer = new QTimer(this);
377                         connect(retryOpenTimer, SIGNAL(timeout()),
378                             this, SLOT(tryOpen()));
379                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
380                 }
381                 return;
382         }
383
384 unprived:
385         /* Start listening to WPA events */
386         dhcpcd_wpa_start(con);
387
388         if (retryOpenTimer) {
389                 delete retryOpenTimer;
390                 retryOpenTimer = NULL;
391         }
392
393         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
394         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
395
396         preferencesAction->setEnabled(dhcpcd_privileged(con));
397 }
398
399 void DhcpcdQt::dispatch() {
400
401         if (dhcpcd_get_fd(con) == -1) {
402                 qWarning("dhcpcd connection lost");
403                 return;
404         }
405
406         dhcpcd_dispatch(con);
407 }
408
409 void DhcpcdQt::notify(QString &title, QString &msg,
410     QSystemTrayIcon::MessageIcon icon)
411 {
412
413         qDebug("%s", qPrintable(msg));
414         trayIcon->showMessage(title, msg, icon);
415 }
416
417
418 void DhcpcdQt::closeEvent(QCloseEvent *event)
419 {
420
421         if (trayIcon->isVisible()) {
422                 hide();
423                 event->ignore();
424         }
425 }
426
427 QIcon DhcpcdQt::getIcon(QString category, QString name)
428 {
429         QIcon icon;
430
431         if (QIcon::hasThemeIcon(name))
432                 icon = QIcon::fromTheme(name);
433         else
434                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
435         return icon;
436 }
437
438 void DhcpcdQt::setIcon(QString category, QString name)
439 {
440         QIcon icon = getIcon(category, name);
441
442         trayIcon->setIcon(icon);
443 }
444
445 QIcon DhcpcdQt::icon()
446 {
447
448         return getIcon("status", "network-transmit-receive");
449 }
450
451 void DhcpcdQt::createSsidMenu()
452 {
453
454         if (ssidMenu) {
455                 delete ssidMenu;
456                 ssidMenu = NULL;
457         }
458         if (wis->size() == 0)
459                 return;
460
461         ssidMenu = new QMenu(this);
462         if (wis->size() == 1)
463                 wis->first()->createMenu(ssidMenu);
464         else {
465                 for (auto &wi : *wis)
466                         ssidMenu->addMenu(wi->createIfMenu(ssidMenu));
467         }
468         ssidMenuPos = QCursor::pos();
469         ssidMenu->popup(ssidMenuPos);
470 }
471
472 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
473 {
474
475         if (reason == QSystemTrayIcon::Trigger)
476                 createSsidMenu();
477 }
478
479 void DhcpcdQt::dialogClosed(QDialog *dialog)
480 {
481
482         if (dialog == about)
483                 about = NULL;
484         else if (dialog == preferences)
485                 preferences = NULL;
486 }
487
488 void DhcpcdQt::showPreferences()
489 {
490
491         if (preferences == NULL) {
492                 preferences = new DhcpcdPreferences(this);
493                 preferences->show();
494         } else
495                 preferences->activateWindow();
496 }
497
498 void DhcpcdQt::showAbout()
499 {
500
501         if (about == NULL) {
502                 about = new DhcpcdAbout(this);
503                 about->show();
504         } else
505                 about->activateWindow();
506 }
507
508 void DhcpcdQt::createActions()
509 {
510
511         preferencesAction = new QAction(tr("&Preferences"), this);
512         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
513         preferencesAction->setEnabled(false);
514         connect(preferencesAction, SIGNAL(triggered()),
515             this, SLOT(showPreferences()));
516
517         aboutAction = new QAction(tr("&About"), this);
518         aboutAction->setIcon(QIcon::fromTheme("help-about"));
519         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
520
521         quitAction = new QAction(tr("&Quit"), this);
522         quitAction->setIcon(QIcon::fromTheme("application-exit"));
523         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
524
525 }
526
527 void DhcpcdQt::createTrayIcon()
528 {
529
530         trayIconMenu = new QMenu(this);
531         trayIconMenu->addAction(preferencesAction);
532         trayIconMenu->addSeparator();
533         trayIconMenu->addAction(aboutAction);
534         trayIconMenu->addAction(quitAction);
535
536         trayIcon = new QSystemTrayIcon(this);
537         setIcon("status", "network-offline");
538         trayIcon->setContextMenu(trayIconMenu);
539
540         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
541             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
542
543         trayIcon->show();
544 }