c30574239045b5a67e427b495764d8776bb0e7da
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-qt.cpp
1 #include <QCursor>
2 #include <QDebug>
3 #include <QList>
4 #include <QSocketNotifier>
5 #include <QtGui>
6
7 #include <cerrno>
8
9 #include "config.h"
10 #include "dhcpcd-qt.h"
11 #include "dhcpcd-about.h"
12 #include "dhcpcd-preferences.h"
13 #include "dhcpcd-wi.h"
14 #include "dhcpcd-ssidmenu.h"
15
16 DhcpcdQt::DhcpcdQt()
17 {
18
19         createActions();
20         createTrayIcon();
21
22         onLine = carrier = false;
23         lastStatus = NULL;
24         aniTimer = new QTimer(this);
25         connect(aniTimer, SIGNAL(timeout()), this, SLOT(animate()));
26         retryOpenTimer = NULL;
27
28         about = NULL;
29         preferences = NULL;
30
31         wis = new QList<DhcpcdWi *>();
32         ssidMenu = NULL;
33
34         qDebug("%s", "Connecting ...");
35         con = dhcpcd_new();
36         if (con == NULL) {
37                 qCritical("libdhcpcd: %s", strerror(errno));
38                 exit(EXIT_FAILURE);
39                 return;
40         }
41         dhcpcd_set_status_callback(con, dhcpcd_status_cb, this);
42         dhcpcd_set_if_callback(con, dhcpcd_if_cb, this);
43         dhcpcd_wpa_set_scan_callback(con, dhcpcd_wpa_scan_cb, this);
44         tryOpen();
45
46 }
47
48 DhcpcdQt::~DhcpcdQt()
49 {
50
51         qDeleteAll(*wis);
52         delete wis;
53
54         free(lastStatus);
55
56         if (con != NULL) {
57                 dhcpcd_close(con);
58                 dhcpcd_free(con);
59         }
60 }
61
62 void DhcpcdQt::animate()
63 {
64         const char *icon;
65
66         if (onLine) {
67                 if (aniCounter++ > 6) {
68                         aniTimer->stop();
69                         aniCounter = 0;
70                         return;
71                 }
72
73                 if (aniCounter % 2 == 0)
74                         icon = "network-idle";
75                 else
76                         icon = "network-transmit-receive";
77         } else {
78                 switch(aniCounter++) {
79                 case 0:
80                         icon = "network-transmit";
81                         break;
82                 case 1:
83                         icon = "network-receive";
84                         break;
85                 default:
86                         icon = "network-idle";
87                         aniCounter = 0;
88                 }
89         }
90
91         setIcon("status", icon);
92 }
93
94 void DhcpcdQt::updateOnline(bool showIf)
95 {
96         bool isOn, isCarrier;
97         char *msg;
98         DHCPCD_IF *ifs, *i;
99         QString msgs;
100
101         isOn = isCarrier = false;
102         ifs = dhcpcd_interfaces(con);
103         for (i = ifs; i; i = i->next) {
104                 if (strcmp(i->type, "link") == 0) {
105                         if (i->up)
106                                 isCarrier = true;
107                 } else {
108                         if (i->up)
109                                 isOn = true;
110                 }
111                 msg = dhcpcd_if_message(i, NULL);
112                 if (msg) {
113                         if (showIf)
114                                 qDebug() << msg;
115                         if (msgs.isEmpty())
116                                 msgs = QString::fromAscii(msg);
117                         else
118                                 msgs += '\n' + QString::fromAscii(msg);
119                         free(msg);
120                 } else if (showIf)
121                         qDebug() << i->ifname << i->reason;
122         }
123
124         if (onLine != isOn || carrier != isCarrier) {
125                 onLine = isOn;
126                 aniTimer->stop();
127                 aniCounter = 0;
128                 if (isOn) {
129                         animate();
130                         aniTimer->start(300);
131                 } else if (isCarrier) {
132                         animate();
133                         aniTimer->start(500);
134                 } else
135                         setIcon("status", "network-offline");
136         }
137
138         trayIcon->setToolTip(msgs);
139 }
140
141 void DhcpcdQt::statusCallback(const char *status)
142 {
143
144         qDebug("Status changed to %s", status);
145         if (strcmp(status, "down") == 0) {
146                 QString msg;
147                 if (lastStatus)
148                         msg = tr("Connection to dhcpcd lost");
149                 else
150                         msg = tr("dhcpcd not running");
151                 aniTimer->stop();
152                 aniCounter = 0;
153                 setIcon("status", "network-offline");
154         } else {
155                 bool refresh;
156
157                 if ((lastStatus == NULL || strcmp(lastStatus, "down") == 0)) {
158                         qDebug("Connected to dhcpcd-%s", dhcpcd_version(con));
159                         refresh = true;
160                 } else
161                         refresh = false;
162                 updateOnline(refresh);
163         }
164
165         free(lastStatus);
166         lastStatus = strdup(status);
167 }
168
169 void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con,
170     const char *status, void *d)
171 {
172         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
173
174         dhcpcdQt->statusCallback(status);
175 }
176
177 void DhcpcdQt::ifCallback(DHCPCD_IF *i)
178 {
179         char *msg;
180         bool new_msg;
181
182         updateOnline(false);
183
184         if (strcmp(i->reason, "RENEW") == 0 ||
185             strcmp(i->reason, "STOP") == 0 ||
186             strcmp(i->reason, "STOPPED") == 0)
187                 return;
188
189         msg = dhcpcd_if_message(i, &new_msg);
190         if (msg) {
191                 qDebug("%s", msg);
192                 if (new_msg) {
193                         QSystemTrayIcon::MessageIcon icon =
194                             i->up ? QSystemTrayIcon::Information :
195                             QSystemTrayIcon::Warning;
196                         trayIcon->showMessage(tr("Network Event"), msg, icon);
197                 }
198                 free(msg);
199         }
200 }
201
202 void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d)
203 {
204         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
205
206         dhcpcdQt->ifCallback(i);
207 }
208
209 DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa)
210 {
211
212         for (auto &wi : *wis) {
213                 if (wi->getWpa() == wpa)
214                         return wi;
215         }
216         return NULL;
217 }
218
219 void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa)
220 {
221         DHCPCD_WI_SCAN *scans, *s1, *s2;
222         int fd = dhcpcd_wpa_get_fd(wpa);
223         DhcpcdWi *wi;
224
225         wi = findWi(wpa);
226         if (fd == -1) {
227                 qCritical("No fd for WPA");
228                 if (wi) {
229                         wis->removeOne(wi);
230                         delete wi;
231                 }
232                 return;
233         }
234
235         DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
236         if (i == NULL) {
237                 qCritical("No interface for WPA");
238                 if (wi) {
239                         wis->removeOne(wi);
240                         delete wi;
241                 }
242                 return;
243         }
244
245         qDebug("%s: Received scan results", i->ifname);
246         scans = dhcpcd_wi_scans(i);
247         if (wi == NULL) {
248                 wi = new DhcpcdWi(this, wpa);
249                 wis->append(wi);
250         } else {
251                 QString title = tr("New Access Point");
252                 QString txt;
253                 for (s1 = scans; s1; s1 = s1->next) {
254                         for (s2 = wi->getScans(); s2; s2 = s2->next) {
255                                 if (strcmp(s1->ssid, s2->ssid) == 0)
256                                         break;
257                                 if (s2 == NULL) {
258                                         if (!txt.isEmpty()) {
259                                                 title = tr("New Access Points");
260                                                 txt += '\n';
261                                         }
262                                         txt += s1->ssid;
263                                 }
264                         }
265                 }
266                 if (!txt.isEmpty())
267                         notify(title, txt);
268         }
269         wi->setScans(scans);
270 }
271
272 void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d)
273 {
274         DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d;
275
276         dhcpcdQt->scanCallback(wpa);
277 }
278
279 bool DhcpcdQt::tryOpen() {
280         int fd = dhcpcd_open(con);
281         static int last_error;
282
283         if (fd == -1) {
284                 if (errno != last_error) {
285                         last_error = errno;
286                         qCritical("dhcpcd_open: %s", strerror(errno));
287                 }
288                 if (retryOpenTimer == NULL) {
289                         retryOpenTimer = new QTimer(this);
290                         connect(retryOpenTimer, SIGNAL(timeout()),
291                             this, SLOT(tryOpen()));
292                         retryOpenTimer->start(DHCPCD_RETRYOPEN);
293                 }
294                 return false;
295         }
296
297         if (retryOpenTimer) {
298                 delete retryOpenTimer;
299                 retryOpenTimer = NULL;
300         }
301
302         notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
303         connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
304
305         return true;
306 }
307
308 void DhcpcdQt::dispatch() {
309
310         if (dhcpcd_get_fd(con) == -1) {
311                 qWarning("dhcpcd connection lost");
312                 return;
313         }
314
315         dhcpcd_dispatch(con);
316 }
317
318 void DhcpcdQt::notify(QString &title, QString &msg,
319     QSystemTrayIcon::MessageIcon icon)
320 {
321
322         qDebug("%s", qPrintable(msg));
323         trayIcon->showMessage(title, msg, icon);
324 }
325
326
327 void DhcpcdQt::closeEvent(QCloseEvent *event)
328 {
329
330         if (trayIcon->isVisible()) {
331                 hide();
332                 event->ignore();
333         }
334 }
335
336 QIcon DhcpcdQt::getIcon(QString category, QString name)
337 {
338         QIcon icon;
339
340         if (QIcon::hasThemeIcon(name))
341                 icon = QIcon::fromTheme(name);
342         else
343                 icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg");
344         return icon;
345 }
346
347 void DhcpcdQt::setIcon(QString category, QString name)
348 {
349         QIcon icon = getIcon(category, name);
350
351         trayIcon->setIcon(icon);
352 }
353
354 QIcon DhcpcdQt::icon()
355 {
356
357         return getIcon("status", "network-transmit-receive");
358 }
359
360 void DhcpcdQt::addSsidMenu(QMenu *&menu, DHCPCD_IF *ifp, DhcpcdWi *&wi)
361 {
362         DHCPCD_WI_SCAN *scan;
363
364         for (scan = wi->getScans(); scan; scan = scan->next) {
365                 QWidgetAction *wa = new QWidgetAction(menu);
366                 DhcpcdSsidMenu *ssidMenu = new DhcpcdSsidMenu(menu, ifp, scan);
367                 wa->setDefaultWidget(ssidMenu);
368                 menu->addAction(wa);
369                 connect(ssidMenu, SIGNAL(selected(DHCPCD_IF *, DHCPCD_WI_SCAN *)),
370                     this, SLOT(connectSsid(DHCPCD_IF *, DHCPCD_WI_SCAN *)));
371         }
372 }
373
374 void DhcpcdQt::connectSsid(DHCPCD_IF *, DHCPCD_WI_SCAN *)
375 {
376
377         QMessageBox::information(this, "Not implemented",
378             "SSID selection is not yet implemented");
379 }
380
381 void DhcpcdQt::createSsidMenu()
382 {
383         DHCPCD_WPA *wpa;
384         DHCPCD_IF *ifp;
385
386         if (ssidMenu) {
387                 delete ssidMenu;
388                 ssidMenu = NULL;
389         }
390         if (wis->size() == 0)
391                 return;
392
393         ssidMenu = new QMenu(this);
394         if (wis->size() == 1) {
395                 DhcpcdWi *wi = wis->first();
396                 wpa = wi->getWpa();
397                 ifp = dhcpcd_wpa_if(wpa);
398                 addSsidMenu(ssidMenu, ifp, wi);
399         } else {
400                 for (auto &wi : *wis) {
401                         wpa = wi->getWpa();
402                         ifp = dhcpcd_wpa_if(wpa);
403                         if (ifp) {
404                                 QMenu *ifmenu = ssidMenu->addMenu(ifp->ifname);
405                                 addSsidMenu(ifmenu, ifp, wi);
406                         }
407                 }
408         }
409         ssidMenu->popup(QCursor::pos());
410 }
411
412 void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason)
413 {
414
415         if (reason == QSystemTrayIcon::Trigger)
416                 createSsidMenu();
417 }
418
419 void DhcpcdQt::dialogClosed(QDialog *dialog)
420 {
421
422         if (dialog == about)
423                 about = NULL;
424         else if (dialog == preferences)
425                 preferences = NULL;
426 }
427
428 void DhcpcdQt::showPreferences()
429 {
430
431         if (preferences == NULL) {
432                 preferences = new DhcpcdPreferences(this);
433                 preferences->show();
434         } else
435                 preferences->activateWindow();
436 }
437
438 void DhcpcdQt::showAbout()
439 {
440
441         if (about == NULL) {
442                 about = new DhcpcdAbout(this);
443                 about->show();
444         } else
445                 about->activateWindow();
446 }
447
448 void DhcpcdQt::createActions()
449 {
450
451         preferencesAction = new QAction(tr("&Preferences"), this);
452         preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network"));
453         connect(preferencesAction, SIGNAL(triggered()),
454             this, SLOT(showPreferences()));
455
456         aboutAction = new QAction(tr("&About"), this);
457         aboutAction->setIcon(QIcon::fromTheme("help-about"));
458         connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout()));
459
460         quitAction = new QAction(tr("&Quit"), this);
461         quitAction->setIcon(QIcon::fromTheme("application-exit"));
462         connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
463
464 }
465
466 void DhcpcdQt::createTrayIcon()
467 {
468
469         trayIconMenu = new QMenu(this);
470         trayIconMenu->addAction(preferencesAction);
471         trayIconMenu->addSeparator();
472         trayIconMenu->addAction(aboutAction);
473         trayIconMenu->addAction(quitAction);
474
475         trayIcon = new QSystemTrayIcon(this);
476         setIcon("status", "network-offline");
477         trayIcon->setContextMenu(trayIconMenu);
478
479         connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
480             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
481
482         trayIcon->show();
483 }