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