Disconnect when same SSID selected (#1)
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-wi.cpp
index e8b3ab015271aa4cc69d040db0109605457b0ef8..4e4d86a8a5fbeaa2a11a77f28d95a4cd0376c802 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * dhcpcd-qt
- * Copyright 2014 Roy Marples <roy@marples.name>
+ * Copyright 2014-2017 Roy Marples <roy@marples.name>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * SUCH DAMAGE.
  */
 
+#include <QAction>
 #include <QObject>
+#include <QInputDialog>
+#include <QLineEdit>
+#include <QMenu>
+#include <QMessageBox>
 #include <QSocketNotifier>
 #include <QTimer>
+#include <QWidgetAction>
 
 #include <cerrno>
 
 #include "config.h"
 #include "dhcpcd-wi.h"
+#include "dhcpcd-qt.h"
+#include "dhcpcd-ifmenu.h"
+#include "dhcpcd-ssid.h"
+#include "dhcpcd-ssidmenu.h"
 
 DhcpcdWi::DhcpcdWi(DhcpcdQt *parent, DHCPCD_WPA *wpa)
 {
 
        this->dhcpcdQt = parent;
        this->wpa = wpa;
+       menu = NULL;
        scans = NULL;
+       ssid = NULL;
 
-       int fd = dhcpcd_wpa_get_fd(wpa);
-       notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
-       connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
-       retryOpenTimer = NULL;
+       notifier = NULL;
+       pingTimer = NULL;
+       scanTimer = NULL;
 }
 
 DhcpcdWi::~DhcpcdWi()
 {
 
-       dhcpcd_wi_scans_free(scans);
-       if (notifier != NULL)
-               delete notifier;
+       close();
+       if (menu) {
+               dhcpcdQt->menuDeleted(menu);
+               menu->deleteLater();
+               menu = NULL;
+       }
+
+       if (notifier) {
+               notifier->deleteLater();
+               notifier = NULL;
+       }
+
+       if (pingTimer) {
+               pingTimer->deleteLater();
+               pingTimer = NULL;
+       }
+
+       if (ssid) {
+               ssid->deleteLater();
+               ssid = NULL;
+       }
+
+       if (scanTimer) {
+               scanTimer->deleteLater();
+               scanTimer = NULL;
+       }
 }
 
-DHCPCD_WPA * DhcpcdWi::getWpa()
+DHCPCD_WPA *DhcpcdWi::getWpa()
 {
 
        return wpa;
@@ -65,58 +99,298 @@ DHCPCD_WI_SCAN *DhcpcdWi::getScans()
 
        return scans;
 }
-void DhcpcdWi::setScans(DHCPCD_WI_SCAN *scans)
+
+bool DhcpcdWi::setScans(DHCPCD_WI_SCAN *scans)
 {
+       bool changed = false;
+
+       if (menu) {
+               QList<DhcpcdSsidMenu*> lst;
+               DHCPCD_WI_SCAN *scan;
+               DHCPCD_IF *i;
+               bool found, associated;
+               QAction *before;
+
+               i = dhcpcd_wpa_if(wpa);
+               for (scan = scans; scan; scan = scan->next) {
+                       found = false;
+                       before = NULL;
+                       associated = dhcpcd_wi_associated(i, scan);
+
+                       lst = menu->findChildren<DhcpcdSsidMenu*>();
+                       foreach(DhcpcdSsidMenu *sm, lst) {
+                               DHCPCD_WI_SCAN *s = sm->getScan();
+
+                               if (strcmp(scan->ssid, s->ssid) == 0) {
+                                       /* If association changes, remove
+                                        * the entry and re-create it
+                                        * so assoicates entries appear at
+                                        * the top */
+                                       if (associated != sm->isAssociated()) {
+                                               menu->removeAction(sm);
+                                               break;
+                                       }
+                                       sm->setScan(scan);
+                                       found = true;
+                                       break;
+                               }
+                               if (!associated &&
+                                   dhcpcd_wi_scan_compare(scan, s) < 0)
+                                       before = sm;
+                       }
+
+                       if (!found) {
+                               if (associated) {
+                                       lst = menu->findChildren<DhcpcdSsidMenu*>();
+                                       if (lst.empty())
+                                               before = NULL;
+                                       else
+                                               before = lst.at(0);
+                               }
+                               createMenuItem(menu, scan, before);
+                               changed = true;
+                       }
+               }
+
+               lst = menu->findChildren<DhcpcdSsidMenu*>();
+               foreach(DhcpcdSsidMenu *sm, lst) {
+                       DHCPCD_WI_SCAN *s = sm->getScan();
+                       for (scan = scans; scan; scan = scan->next) {
+                               if (strcmp(scan->ssid, s->ssid) == 0)
+                                       break;
+                       }
+                       if (scan == NULL) {
+                               menu->removeAction(sm);
+                               changed = true;
+                       }
+               }
+       }
 
        dhcpcd_wi_scans_free(this->scans);
        this->scans = scans;
+
+       return (changed && menu && menu->isVisible());
+}
+
+void DhcpcdWi::createMenuItem(QMenu *menu, DHCPCD_WI_SCAN *scan,
+    QAction *before)
+{
+       DhcpcdSsidMenu *ssidMenu = new DhcpcdSsidMenu(menu, this, scan);
+       menu->insertAction(before, ssidMenu);
+       connect(ssidMenu, SIGNAL(triggered(DHCPCD_WI_SCAN *)),
+           this, SLOT(connectSsid(DHCPCD_WI_SCAN *)));
+}
+
+void DhcpcdWi::createMenu1(QMenu *menu)
+{
+       DHCPCD_IF *i;
+       DHCPCD_WI_SCAN *scan;
+       QAction *before;
+
+       connect(menu, SIGNAL(aboutToShow()), this, SLOT(menuShown()));
+       connect(menu, SIGNAL(aboutToHide()), this, SLOT(menuHidden()));
+
+       i = dhcpcd_wpa_if(wpa);
+       for (scan = scans; scan; scan = scan->next) {
+               before = NULL;
+               if (dhcpcd_wi_associated(i, scan)) {
+                       QList<DhcpcdSsidMenu*> lst;
+
+                       lst = menu->findChildren<DhcpcdSsidMenu*>();
+                       if (!lst.empty())
+                               before = lst.at(0);
+               }
+               createMenuItem(menu, scan, before);
+       }
+}
+
+void DhcpcdWi::createMenu(QMenu *menu)
+{
+
+       if (this->menu && this->menu != menu)
+               this->menu->deleteLater();
+       this->menu = menu;
+       createMenu1(menu);
+}
+
+QMenu *DhcpcdWi::createIfMenu(QMenu *parent)
+{
+       DHCPCD_IF *ifp;
+       QIcon icon;
+
+       ifp = dhcpcd_wpa_if(wpa);
+       if (this->menu)
+               this->menu->deleteLater();
+       menu = new DhcpcdIfMenu(ifp, parent);
+       icon = DhcpcdQt::getIcon("devices", "network-wireless");
+       menu->setIcon(icon);
+       createMenu1(menu);
+       return menu;
 }
 
-void DhcpcdWi::wpaOpen()
+bool DhcpcdWi::open()
 {
        int fd = dhcpcd_wpa_open(wpa);
-       static int last_error;
 
        if (fd == -1) {
-               if (errno != last_error) {
-                       last_error = errno;
-                       qCritical("%s: dhcpcd_wpa_open: %s",
-                           dhcpcd_wpa_if(wpa)->ifname,
-                           strerror(last_error));
-               }
-               return;
+               qCritical("%s: dhcpcd_wpa_open: %s",
+                   dhcpcd_wpa_if(wpa)->ifname,
+                   strerror(errno));
+               dhcpcd_wpa_close(wpa);
+               return false;
        }
 
        notifier = new QSocketNotifier(fd, QSocketNotifier::Read);
        connect(notifier, SIGNAL(activated(int)), this, SLOT(dispatch()));
-       if (retryOpenTimer) {
-               delete retryOpenTimer;
-               retryOpenTimer = NULL;
+       pingTimer = new QTimer(this);
+       connect(pingTimer, SIGNAL(timeout()), this, SLOT(ping()));
+       pingTimer->start(DHCPCD_WPA_PING);
+       scanTimer = new QTimer(this);
+       connect(scanTimer, SIGNAL(timeout()), this, SLOT(scan()));
+       scanTimer->start(DHCPCD_WPA_SCAN_LONG);
+       return true;
+}
+
+void DhcpcdWi::close()
+{
+
+       if (menu)
+               menu->setVisible(false);
+
+       if (notifier)
+               notifier->setEnabled(false);
+
+       if (pingTimer)
+               pingTimer->stop();
+
+       if (ssid)
+               ssid->reject();
+
+       if (scanTimer)
+               scanTimer->stop();
+
+       if (scans) {
+               dhcpcd_wi_scans_free(scans);
+               scans = NULL;
+       }
+       if (wpa) {
+               dhcpcd_wpa_close(wpa);
+               wpa = NULL;
        }
 }
 
 void DhcpcdWi::dispatch()
 {
 
-       if (dhcpcd_wpa_get_fd(wpa) == -1) {
-               delete notifier;
-               notifier = NULL;
-               DHCPCD_IF *i = dhcpcd_wpa_if(wpa);
-               if (i == NULL ||
-                   strcmp(i->reason, "DEPARTED") == 0 ||
-                   strcmp(i->reason, "STOPPED") == 0)
+       dhcpcd_wpa_dispatch(wpa);
+}
+
+void DhcpcdWi::ping()
+{
+
+       if (!dhcpcd_wpa_ping(wpa))
+               dhcpcd_wpa_close(wpa);
+}
+
+void DhcpcdWi::connectSsid(DHCPCD_WI_SCAN *scan)
+{
+       DHCPCD_WI_SCAN s;
+       DHCPCD_IF *i;
+       int err;
+
+       /* Take a copy of scan incase it's destroyed by a scan update */
+       memcpy(&s, scan, sizeof(s));
+       s.next = NULL;
+
+       i = dhcpcd_wpa_if(wpa);
+       if (i == NULL)
+               err = DHCPCD_WPA_ERR;
+       else if (dhcpcd_wi_associated(i, &s)) {
+               /* Disconnect if same interface selected */
+               if (!dhcpcd_wpa_disconnect(wpa))
+                       err = DHCPCD_WPA_ERR_DISCONN;
+               else
+                       err = DHCPCD_WPA_SUCCESS;
+       } else if (s.flags & WSF_PSK) {
+               bool ok;
+               QString pwd;
+
+               ssid = new DhcpcdSsid(this, &s);
+               pwd = ssid->getPsk(&ok);
+               ssid->deleteLater();
+               ssid = NULL;
+               if (!ok)
                        return;
-               qWarning("%s: %s",
-                   i->ifname,
-                   qPrintable(tr("dhcpcd WPA connection lost")));
-               if (retryOpenTimer == NULL) {
-                       retryOpenTimer = new QTimer(this);
-                       connect(retryOpenTimer, SIGNAL(timeout()),
-                           this, SLOT(wpaOpen()));
-                       retryOpenTimer->start(DHCPCD_RETRYOPEN);
-               }
+               if (pwd.isNull() || pwd.isEmpty())
+                       err = dhcpcd_wpa_select(wpa, &s);
+               else
+                       err = dhcpcd_wpa_configure(wpa, &s, pwd.toLatin1());
+       } else
+               err = dhcpcd_wpa_configure(wpa, &s, NULL);
+
+       QString errt;
+       switch (err) {
+       case DHCPCD_WPA_SUCCESS:
                return;
+       case DHCPCD_WPA_ERR:
+               errt = tr("Failed.");
+               break;
+       case DHCPCD_WPA_ERR_DISCONN:
+               errt = tr("Failed to disconnect.");
+               break;
+       case DHCPCD_WPA_ERR_RECONF:
+               errt = tr("Failed to reconfigure.");
+               break;
+       case DHCPCD_WPA_ERR_SET:
+               errt = tr("Failed to set key management.");
+               break;
+       case DHCPCD_WPA_ERR_SET_PSK:
+               errt = tr("Failed to set password, probably too short.");
+               break;
+       case DHCPCD_WPA_ERR_ENABLE:
+               errt = tr("Failed to enable the network.");
+               break;
+       case DHCPCD_WPA_ERR_SELECT:
+               errt = tr("Failed to select the network.");
+               break;
+       case DHCPCD_WPA_ERR_ASSOC:
+               errt = tr("Failed to start association.");
+               break;
+       case DHCPCD_WPA_ERR_WRITE:
+               errt = tr("Failed to save wpa_supplicant configuration.\n\nYou should add update_config=1 to /etc/wpa_supplicant.conf.");
+               break;
+       default:
+               errt = strerror(errno);
+               break;
        }
 
-       dhcpcd_wpa_dispatch(wpa);
+       QMessageBox::critical(dhcpcdQt, tr("Error setting wireless properties"),
+           errt);
+}
+
+void DhcpcdWi::scan()
+{
+       DHCPCD_IF *i;
+
+       i = dhcpcd_wpa_if(wpa);
+       if (!i->up || dhcpcd_wpa_can_background_scan(wpa))
+               dhcpcd_wpa_scan(wpa);
+}
+
+void DhcpcdWi::menuHidden()
+{
+
+       if (scanTimer) {
+               scanTimer->stop();
+               scanTimer->start(DHCPCD_WPA_SCAN_LONG);
+       }
+}
+
+void DhcpcdWi::menuShown()
+{
+
+       if (scanTimer) {
+               scanTimer->stop();
+               scanTimer->start(DHCPCD_WPA_SCAN_SHORT);
+       }
 }