Disconnect when same SSID selected (#1)
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-wi.cpp
index f2c3fd3050650a7d115faefa5fb36d18affe809a..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
@@ -40,6 +40,7 @@
 #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)
@@ -49,27 +50,42 @@ DhcpcdWi::DhcpcdWi(DhcpcdQt *parent, DHCPCD_WPA *wpa)
        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()
 {
 
+       close();
        if (menu) {
-               delete menu;
+               dhcpcdQt->menuDeleted(menu);
+               menu->deleteLater();
                menu = NULL;
        }
 
        if (notifier) {
-               delete notifier;
+               notifier->deleteLater();
                notifier = NULL;
        }
 
-       dhcpcd_wi_scans_free(scans);
+       if (pingTimer) {
+               pingTimer->deleteLater();
+               pingTimer = NULL;
+       }
+
+       if (ssid) {
+               ssid->deleteLater();
+               ssid = NULL;
+       }
+
+       if (scanTimer) {
+               scanTimer->deleteLater();
+               scanTimer = NULL;
+       }
 }
 
 DHCPCD_WPA *DhcpcdWi::getWpa()
@@ -86,33 +102,53 @@ DHCPCD_WI_SCAN *DhcpcdWi::getScans()
 
 bool DhcpcdWi::setScans(DHCPCD_WI_SCAN *scans)
 {
-       int changed = 0;
+       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) {
-                       bool found = false;
-                       QAction *before = NULL;
+                       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 (memcmp(scan->bssid, s->bssid,
-                                   sizeof(scan->bssid)) == 0)
-                               {
+
+                               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 (dhcpcd_wi_scan_compare(scan, s) < 0)
+                               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++;
+                               changed = true;
                        }
                }
 
@@ -120,13 +156,12 @@ bool DhcpcdWi::setScans(DHCPCD_WI_SCAN *scans)
                foreach(DhcpcdSsidMenu *sm, lst) {
                        DHCPCD_WI_SCAN *s = sm->getScan();
                        for (scan = scans; scan; scan = scan->next) {
-                               if (memcmp(scan->bssid, s->bssid,
-                                   sizeof(scan->bssid)) == 0)
+                               if (strcmp(scan->ssid, s->ssid) == 0)
                                        break;
                        }
                        if (scan == NULL) {
                                menu->removeAction(sm);
-                               changed--;
+                               changed = true;
                        }
                }
        }
@@ -134,7 +169,7 @@ bool DhcpcdWi::setScans(DHCPCD_WI_SCAN *scans)
        dhcpcd_wi_scans_free(this->scans);
        this->scans = scans;
 
-       return !(changed == 0);
+       return (changed && menu && menu->isVisible());
 }
 
 void DhcpcdWi::createMenuItem(QMenu *menu, DHCPCD_WI_SCAN *scan,
@@ -148,15 +183,32 @@ void DhcpcdWi::createMenuItem(QMenu *menu, DHCPCD_WI_SCAN *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()));
 
-       for (scan = scans; scan; scan = scan->next)
-               createMenuItem(menu, scan);
+       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);
 }
@@ -167,6 +219,8 @@ QMenu *DhcpcdWi::createIfMenu(QMenu *parent)
        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);
@@ -174,75 +228,119 @@ QMenu *DhcpcdWi::createIfMenu(QMenu *parent)
        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::dispatch()
+void DhcpcdWi::close()
 {
 
-       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)
-                       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);
-               }
-               return;
+       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()
+{
 
        dhcpcd_wpa_dispatch(wpa);
 }
 
+void DhcpcdWi::ping()
+{
+
+       if (!dhcpcd_wpa_ping(wpa))
+               dhcpcd_wpa_close(wpa);
+}
+
 void DhcpcdWi::connectSsid(DHCPCD_WI_SCAN *scan)
 {
-       bool ok;
        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;
 
-       QString psk = QInputDialog::getText(dhcpcdQt, s.ssid,
-           tr("Pre Shared key"), QLineEdit::Normal, NULL, &ok);
-
-       if (!ok)
-               return;
+       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;
+               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 (dhcpcd_wpa_configure_psk(wpa, &s, psk.toAscii())) {
+       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;
@@ -252,6 +350,9 @@ void DhcpcdWi::connectSsid(DHCPCD_WI_SCAN *scan)
        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;
@@ -266,3 +367,30 @@ void DhcpcdWi::connectSsid(DHCPCD_WI_SCAN *scan)
        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);
+       }
+}