/* * dhcpcd-qt * Copyright 2014-2017 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "config.h" #include "dhcpcd-qt.h" #include "dhcpcd-about.h" #include "dhcpcd-preferences.h" #include "dhcpcd-wi.h" #include "dhcpcd-ifmenu.h" #include "dhcpcd-ssidmenu.h" #if defined(KNOTIFY5) #include #elif defined(KNOTIFY4) #include #endif DhcpcdQt::DhcpcdQt() { createActions(); createTrayIcon(); onLine = carrier = false; lastStatus = DHC_UNKNOWN; aniTimer = new QTimer(this); connect(aniTimer, SIGNAL(timeout()), this, SLOT(animate())); notifier = NULL; retryOpenTimer = NULL; about = NULL; preferences = NULL; wis = new QList(); ssidMenu = NULL; qDebug("%s", "Connecting ..."); con = dhcpcd_new(); if (con == NULL) { qCritical("libdhcpcd: %s", strerror(errno)); exit(EXIT_FAILURE); return; } dhcpcd_set_progname(con, "dhcpcd-qt"); dhcpcd_set_status_callback(con, dhcpcd_status_cb, this); dhcpcd_set_if_callback(con, dhcpcd_if_cb, this); dhcpcd_wpa_set_scan_callback(con, dhcpcd_wpa_scan_cb, this); dhcpcd_wpa_set_status_callback(con, dhcpcd_wpa_status_cb, this); tryOpen(); } DhcpcdQt::~DhcpcdQt() { if (ssidMenu) { ssidMenu->setVisible(false); ssidMenu->deleteLater(); } if (con != NULL) { dhcpcd_close(con); dhcpcd_free(con); } for (auto &wi : *wis) wi->deleteLater(); delete wis; } DHCPCD_CONNECTION *DhcpcdQt::getConnection() { return con; } QList *DhcpcdQt::getWis() { return wis; } const char * DhcpcdQt::signalStrengthIcon(DHCPCD_WI_SCAN *scan) { if (scan == NULL) return "network-wireless-connected-00"; if (scan->strength.value > 80) return "network-wireless-connected-100"; if (scan->strength.value > 55) return "network-wireless-connected-75"; if (scan->strength.value > 30) return "network-wireless-connected-50"; if (scan->strength.value > 5) return "network-wireless-connected-25"; return "network-wireless-connected-00"; } DHCPCD_WI_SCAN * DhcpcdQt::getStrongestSignal() { DHCPCD_WI_SCAN *scan, *scans, *s; DHCPCD_WPA *wpa; DHCPCD_IF *i; scan = NULL; for (auto &wi : *wis) { wpa = wi->getWpa(); i = dhcpcd_wpa_if(wpa); scans = wi->getScans(); for (s = scans; s; s = s->next) { if (dhcpcd_wi_associated(i, s) && (scan == NULL || s->strength.value > scan->strength.value)) scan = s; } } return scan; } void DhcpcdQt::animate() { const char *icon; DHCPCD_WI_SCAN *scan; scan = getStrongestSignal(); if (onLine) { if (aniCounter++ > 6) { aniTimer->stop(); aniCounter = 0; return; } if (aniCounter % 2 == 0) icon = scan ? "network-wireless-connected-00" : "network-idle"; else icon = scan ? DhcpcdQt::signalStrengthIcon(scan) : "network-transmit-receive"; } else { if (scan) { switch(aniCounter++) { case 0: icon = "network-wireless-connected-00"; break; case 1: icon = "network-wireless-connected-25"; break; case 2: icon = "network-wireless-connected-50"; break; case 3: icon = "network-wireless-connected-75"; break; default: icon = "network-wireless-connected-100"; aniCounter = 0; } } else { switch(aniCounter++) { case 0: icon = "network-transmit"; break; case 1: icon = "network-receive"; break; default: icon = "network-idle"; aniCounter = 0; } } } setIcon("status", icon); } void DhcpcdQt::updateOnline(bool showIf) { bool isOn, isCarrier; char *msg; DHCPCD_IF *ifs, *i; QString msgs; isOn = isCarrier = false; ifs = dhcpcd_interfaces(con); for (i = ifs; i; i = i->next) { if (i->type == DHT_LINK) { if (i->up) isCarrier = true; } else { if (i->up) isOn = true; } msg = dhcpcd_if_message(i, NULL); if (msg) { if (showIf) qDebug("%s", msg); if (msgs.isEmpty()) msgs = QString::fromLatin1(msg); else msgs += '\n' + QString::fromLatin1(msg); free(msg); } else if (showIf) qDebug("%s: %s", i->ifname, i->reason); } if (onLine != isOn || carrier != isCarrier) { onLine = isOn; carrier = isCarrier; aniTimer->stop(); aniCounter = 0; if (isOn) { animate(); aniTimer->start(300); } else if (isCarrier) { animate(); aniTimer->start(500); } else setIcon("status", "network-offline"); } trayIcon->setToolTip(msgs); } void DhcpcdQt::statusCallback(unsigned int status, const char *status_msg) { qDebug("Status changed to %s", status_msg); if (status == DHC_DOWN) { aniTimer->stop(); aniCounter = 0; onLine = carrier = false; setIcon("status", "network-offline"); trayIcon->setToolTip(tr("Not connected to dhcpcd")); /* Close down everything */ if (notifier) { notifier->setEnabled(false); notifier->deleteLater(); notifier = NULL; } if (ssidMenu) { ssidMenu->deleteLater(); ssidMenu = NULL; } preferencesAction->setEnabled(false); if (preferences) { preferences->deleteLater(); preferences = NULL; } } else { bool refresh; if (lastStatus == DHC_UNKNOWN || lastStatus == DHC_DOWN) { qDebug("Connected to dhcpcd-%s", dhcpcd_version(con)); refresh = true; } else refresh = lastStatus == DHC_OPENED ? true : false; updateOnline(refresh); } lastStatus = status; if (status == DHC_DOWN) { if (retryOpenTimer == NULL) { retryOpenTimer = new QTimer(this); connect(retryOpenTimer, SIGNAL(timeout()), this, SLOT(tryOpen())); retryOpenTimer->start(DHCPCD_RETRYOPEN); } } } void DhcpcdQt::dhcpcd_status_cb(_unused DHCPCD_CONNECTION *con, unsigned int status, const char *status_msg, void *d) { DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d; dhcpcdQt->statusCallback(status, status_msg); } void DhcpcdQt::ifCallback(DHCPCD_IF *i) { char *msg; bool new_msg; if (i->state == DHS_RENEW || i->state == DHS_STOP || i->state == DHS_STOPPED) { msg = dhcpcd_if_message(i, &new_msg); if (msg) { qDebug("%s", msg); if (new_msg) { QString t = tr("Network Event"); QString m = msg; QString icon; if (i->up) icon = "network-transmit-receive"; //else // icon = "network-transmit"; if (!i->up) icon = "network-offline"; notify(t, m, icon); } free(msg); } } updateOnline(false); if (i->wireless) { for (auto &wi : *wis) { DHCPCD_WPA *wpa = wi->getWpa(); if (dhcpcd_wpa_if(wpa) == i) { DHCPCD_WI_SCAN *scans; scans = dhcpcd_wi_scans(i); processScans(wi, scans); } } } } void DhcpcdQt::dhcpcd_if_cb(DHCPCD_IF *i, void *d) { DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d; dhcpcdQt->ifCallback(i); } DhcpcdWi *DhcpcdQt::findWi(DHCPCD_WPA *wpa) { for (auto &wi : *wis) { if (wi->getWpa() == wpa) return wi; } return NULL; } void DhcpcdQt::processScans(DhcpcdWi *wi, DHCPCD_WI_SCAN *scans) { /* Don't spam the user if we're already connected. */ if (lastStatus != DHC_CONNECTED) { QString title = tr("New Access Point"); QString txt; DHCPCD_WI_SCAN *s1, *s2; for (s1 = scans; s1; s1 = s1->next) { for (s2 = wi->getScans(); s2; s2 = s2->next) { if (strcmp(s1->ssid, s2->ssid) == 0) break; } if (s2 == NULL) { if (!txt.isEmpty()) { title = tr("New Access Points"); txt += '\n'; } txt += s1->ssid; } } if (!txt.isEmpty() && (ssidMenu == NULL || !ssidMenu->isVisible())) notify(title, txt, "network-wireless"); } if (wi->setScans(scans) && ssidMenu && ssidMenu->isVisible()) ssidMenu->popup(ssidMenuPos); } void DhcpcdQt::scanCallback(DHCPCD_WPA *wpa) { DHCPCD_WI_SCAN *scans; int fd = dhcpcd_wpa_get_fd(wpa); DhcpcdWi *wi; wi = findWi(wpa); if (fd == -1) { qCritical("No fd for WPA"); if (wi) { wis->removeOne(wi); wi->close(); wi->deleteLater(); } return; } DHCPCD_IF *i = dhcpcd_wpa_if(wpa); if (i == NULL) { qCritical("No interface for WPA"); if (wi) { wis->removeOne(wi); wi->close(); wi->deleteLater(); } return; } qDebug("%s: Received scan results", i->ifname); scans = dhcpcd_wi_scans(i); if (wi == NULL) { wi = new DhcpcdWi(this, wpa); if (wi->open()) { wis->append(wi); wi->setScans(scans); } else { wi->close(); wi->deleteLater(); } } else processScans(wi, scans); if (!aniTimer->isActive()) { DHCPCD_WI_SCAN *scan; const char *icon; scan = getStrongestSignal(); if (scan) icon = DhcpcdQt::signalStrengthIcon(scan); else if (onLine) icon = "network-transmit-receive"; else icon = "network-offline"; setIcon("status", icon); } } void DhcpcdQt::dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, void *d) { DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d; dhcpcdQt->scanCallback(wpa); } void DhcpcdQt::wpaStatusCallback(DHCPCD_WPA *wpa, unsigned int status, const char *status_msg) { DHCPCD_IF *i; i = dhcpcd_wpa_if(wpa); qDebug("%s: WPA status %s", i->ifname, status_msg); if (status == DHC_DOWN) { DhcpcdWi *wi = findWi(wpa); if (wi) { wis->removeOne(wi); wi->close(); wi->deleteLater(); } } } void DhcpcdQt::dhcpcd_wpa_status_cb(DHCPCD_WPA *wpa, unsigned int status, const char *status_msg, void *d) { DhcpcdQt *dhcpcdQt = (DhcpcdQt *)d; dhcpcdQt->wpaStatusCallback(wpa, status, status_msg); } void DhcpcdQt::tryOpen() { int fd = dhcpcd_open(con, true); static int last_error; if (fd == -1) { if (errno == EACCES || errno == EPERM) { if ((fd = dhcpcd_open(con, false)) != -1) goto unprived; } if (errno != last_error) { last_error = errno; const char *errt = strerror(errno); qCritical("dhcpcd_open: %s", errt); trayIcon->setToolTip( tr("Error connecting to dhcpcd: %1").arg(errt)); } if (retryOpenTimer == NULL) { retryOpenTimer = new QTimer(this); connect(retryOpenTimer, SIGNAL(timeout()), this, SLOT(tryOpen())); retryOpenTimer->start(DHCPCD_RETRYOPEN); } return; } unprived: /* Start listening to WPA events */ dhcpcd_wpa_start(con); if (retryOpenTimer) { retryOpenTimer->stop(); retryOpenTimer->deleteLater(); retryOpenTimer = NULL; } notifier = new QSocketNotifier(fd, QSocketNotifier::Read); connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch())); preferencesAction->setEnabled(dhcpcd_privileged(con)); } void DhcpcdQt::dispatch() { dhcpcd_dispatch(con); } void DhcpcdQt::notify(const QString &title, const QString &msg, const QString &icon) { #if defined(KNOTIFY4) || defined(KNOTIFY5) KNotification *n = new KNotification("event", this); n->setIconName(icon); n->setTitle(title); n->setText(msg); n->sendEvent(); #else #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) const QIcon i = getIcon("status", icon); #else QSystemTrayIcon::MessageIcon i = QSystemTrayIcon::Information; if (icon.compare("network-offline") == 0) i = QSystemTrayIcon::Warning; #endif trayIcon->showMessage(title, msg, i); #endif } void DhcpcdQt::closeEvent(QCloseEvent *event) { if (trayIcon->isVisible()) { hide(); event->ignore(); } } QIcon DhcpcdQt::getIcon(QString category, QString name) { QIcon icon; if (QIcon::hasThemeIcon(name)) icon = QIcon::fromTheme(name); else icon = QIcon(ICONDIR "/hicolor/scalable/" + category + "/" + name + ".svg"); return icon; } void DhcpcdQt::setIcon(QString category, QString name) { QIcon icon = getIcon(category, name); trayIcon->setIcon(icon); } QIcon DhcpcdQt::icon() { return getIcon("status", "network-transmit-receive"); } void DhcpcdQt::menuDeleted(QMenu *menu) { if (ssidMenu == menu) ssidMenu = NULL; } void DhcpcdQt::createSsidMenu() { if (ssidMenu) { ssidMenu->deleteLater(); ssidMenu = NULL; } if (wis->size() == 0) return; ssidMenu = new QMenu(this); if (wis->size() == 1) wis->first()->createMenu(ssidMenu); else { for (auto &wi : *wis) ssidMenu->addMenu(wi->createIfMenu(ssidMenu)); } ssidMenuPos = QCursor::pos(); ssidMenu->popup(ssidMenuPos); } void DhcpcdQt::iconActivated(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) createSsidMenu(); } void DhcpcdQt::dialogClosed(QDialog *dialog) { if (dialog == about) about = NULL; else if (dialog == preferences) preferences = NULL; } void DhcpcdQt::showPreferences() { if (preferences == NULL) { preferences = new DhcpcdPreferences(this); preferences->show(); } else preferences->activateWindow(); } void DhcpcdQt::showAbout() { if (about == NULL) { about = new DhcpcdAbout(this); about->show(); } else about->activateWindow(); } void DhcpcdQt::createActions() { preferencesAction = new QAction(tr("&Preferences"), this); preferencesAction->setIcon(QIcon::fromTheme("preferences-system-network")); preferencesAction->setEnabled(false); connect(preferencesAction, SIGNAL(triggered()), this, SLOT(showPreferences())); aboutAction = new QAction(tr("&About"), this); aboutAction->setIcon(QIcon::fromTheme("help-about")); connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAbout())); quitAction = new QAction(tr("&Quit"), this); quitAction->setIcon(QIcon::fromTheme("application-exit")); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); } void DhcpcdQt::createTrayIcon() { trayIconMenu = new QMenu(this); trayIconMenu->addAction(preferencesAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(aboutAction); trayIconMenu->addAction(quitAction); trayIcon = new QSystemTrayIcon(this); setIcon("status", "network-offline"); trayIcon->setContextMenu(trayIconMenu); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); trayIcon->show(); }