Merge commit 'garage/master'
[wpasupplicant] / wpa_supplicant / wpa_gui-qt4 / wpagui.cpp
index ba11a83..dcd33b9 100644 (file)
 #include <unistd.h>
 #endif
 
+#ifdef CONFIG_NATIVE_WINDOWS
+#include <windows.h>
+#endif /* CONFIG_NATIVE_WINDOWS */
+
+#include <cstdio>
 #include <QMessageBox>
 #include <QCloseEvent>
+#include <QImageReader>
+#include <QSettings>
 
 #include "wpagui.h"
 #include "dirent.h"
 #include "userdatarequest.h"
 #include "networkconfig.h"
 
-WpaGui::WpaGui(QWidget *parent, const char *, Qt::WFlags)
-       : QMainWindow(parent)
+#if 1
+/* Silence stdout */
+#define printf wpagui_printf
+static int wpagui_printf(const char *, ...)
+{
+       return 0;
+}
+#endif
+
+WpaGui::WpaGui(QApplication *_app, QWidget *parent, const char *, Qt::WFlags)
+       : QMainWindow(parent), app(_app)
 {
        setupUi(this);
 
+#ifdef CONFIG_NATIVE_WINDOWS
+       fileStopServiceAction = new QAction(this);
+       fileStopServiceAction->setObjectName("Stop Service");
+       fileStopServiceAction->setIconText("Stop Service");
+       fileMenu->insertAction(actionWPS, fileStopServiceAction);
+
+       fileStartServiceAction = new QAction(this);
+       fileStartServiceAction->setObjectName("Start Service");
+       fileStartServiceAction->setIconText("Start Service");
+       fileMenu->insertAction(fileStopServiceAction, fileStartServiceAction);
+
+       connect(fileStartServiceAction, SIGNAL(triggered()), this,
+               SLOT(startService()));
+       connect(fileStopServiceAction, SIGNAL(triggered()), this,
+               SLOT(stopService()));
+
+       addInterfaceAction = new QAction(this);
+       addInterfaceAction->setIconText("Add Interface");
+       fileMenu->insertAction(fileStartServiceAction, addInterfaceAction);
+
+       connect(addInterfaceAction, SIGNAL(triggered()), this,
+               SLOT(addInterface()));
+#endif /* CONFIG_NATIVE_WINDOWS */
+
        (void) statusBar();
 
+       /*
+        * Disable WPS tab by default; it will be enabled if wpa_supplicant is
+        * built with WPS support.
+        */
+       wpsTab->setEnabled(false);
+       wpaguiTab->setTabEnabled(wpaguiTab->indexOf(wpsTab), false);
+
        connect(fileEventHistoryAction, SIGNAL(triggered()), this,
                SLOT(eventHistory()));
        connect(fileSaveConfigAction, SIGNAL(triggered()), this,
                SLOT(saveConfig()));
-       connect(fileExitAction, SIGNAL(triggered()), this, SLOT(fileExit()));
+       connect(actionWPS, SIGNAL(triggered()), this, SLOT(wpsDialog()));
+       connect(fileExitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
        connect(networkAddAction, SIGNAL(triggered()), this,
                SLOT(addNetwork()));
        connect(networkEditAction, SIGNAL(triggered()), this,
@@ -75,22 +123,43 @@ WpaGui::WpaGui(QWidget *parent, const char *, Qt::WFlags)
        connect(scanNetworkButton, SIGNAL(clicked()), this, SLOT(scan()));
        connect(networkList, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
                this, SLOT(editListedNetwork()));
+       connect(wpaguiTab, SIGNAL(currentChanged(int)), this,
+               SLOT(tabChanged(int)));
+       connect(wpsPbcButton, SIGNAL(clicked()), this, SLOT(wpsPbc()));
+       connect(wpsPinButton, SIGNAL(clicked()), this, SLOT(wpsGeneratePin()));
+       connect(wpsApPinEdit, SIGNAL(textChanged(const QString &)), this,
+               SLOT(wpsApPinChanged(const QString &)));
+       connect(wpsApPinButton, SIGNAL(clicked()), this, SLOT(wpsApPin()));
 
        eh = NULL;
        scanres = NULL;
+       add_iface = NULL;
        udr = NULL;
        tray_icon = NULL;
+       startInTray = false;
        ctrl_iface = NULL;
        ctrl_conn = NULL;
        monitor_conn = NULL;
        msgNotifier = NULL;
        ctrl_iface_dir = strdup("/var/run/wpa_supplicant");
 
-       if (QSystemTrayIcon::isSystemTrayAvailable())
-               createTrayIcon();
-
        parse_argv();
 
+       if (app->isSessionRestored()) {
+               QSettings settings("wpa_supplicant", "wpa_gui");
+               settings.beginGroup("state");
+               if (app->sessionId().compare(settings.value("session_id").
+                                            toString()) == 0)
+                       startInTray = settings.value("in_tray").toBool();
+               settings.endGroup();
+       }
+
+       if (QSystemTrayIcon::isSystemTrayAvailable())
+               createTrayIcon(startInTray);
+       else
+               show();
+
+       connectedToService = false;
        textStatus->setText("connecting to wpa_supplicant");
        timer = new QTimer(this);
        connect(timer, SIGNAL(timeout()), SLOT(ping()));
@@ -105,9 +174,6 @@ WpaGui::WpaGui(QWidget *parent, const char *, Qt::WFlags)
        updateStatus();
        networkMayHaveChanged = true;
        updateNetworks();
-
-       if (tray_icon)
-               tray_icon->show();
 }
 
 
@@ -137,6 +203,12 @@ WpaGui::~WpaGui()
                scanres = NULL;
        }
 
+       if (add_iface) {
+               add_iface->close();
+               delete add_iface;
+               add_iface = NULL;
+       }
+
        if (udr) {
                udr->close();
                delete udr;
@@ -161,7 +233,7 @@ void WpaGui::parse_argv()
 {
        int c;
        for (;;) {
-               c = getopt(qApp->argc(), qApp->argv(), "i:p:");
+               c = getopt(qApp->argc(), qApp->argv(), "i:p:t");
                if (c < 0)
                        break;
                switch (c) {
@@ -173,6 +245,9 @@ void WpaGui::parse_argv()
                        free(ctrl_iface_dir);
                        ctrl_iface_dir = strdup(optarg);
                        break;
+               case 't':
+                       startInTray = true;
+                       break;
                }
        }
 }
@@ -236,6 +311,7 @@ int WpaGui::openCtrlConnection(const char *ifname)
                        ret = wpa_ctrl_request(ctrl, "INTERFACES", 10, buf,
                                               &len, NULL);
                        if (ret >= 0) {
+                               connectedToService = true;
                                buf[len] = '\0';
                                pos = strchr(buf, '\n');
                                if (pos)
@@ -247,8 +323,22 @@ int WpaGui::openCtrlConnection(const char *ifname)
 #endif /* CONFIG_CTRL_IFACE_NAMED_PIPE */
        }
 
-       if (ctrl_iface == NULL)
+       if (ctrl_iface == NULL) {
+#ifdef CONFIG_NATIVE_WINDOWS
+               static bool first = true;
+               if (first && !serviceRunning()) {
+                       first = false;
+                       if (QMessageBox::warning(
+                                   this, qAppName(),
+                                   "wpa_supplicant service is not running.\n"
+                                   "Do you want to start it?",
+                                   QMessageBox::Yes | QMessageBox::No) ==
+                           QMessageBox::Yes)
+                               startService();
+               }
+#endif /* CONFIG_NATIVE_WINDOWS */
                return -1;
+       }
 
 #ifdef CONFIG_CTRL_IFACE_UNIX
        flen = strlen(ctrl_iface_dir) + strlen(ctrl_iface) + 2;
@@ -326,6 +416,19 @@ int WpaGui::openCtrlConnection(const char *ifname)
                }
        }
 
+       len = sizeof(buf) - 1;
+       if (wpa_ctrl_request(ctrl_conn, "GET_CAPABILITY eap", 18, buf, &len,
+                            NULL) >= 0) {
+               buf[len] = '\0';
+
+               QString res(buf);
+               QStringList types = res.split(QChar(' '));
+               bool wps = types.contains("WSC");
+               actionWPS->setEnabled(wps);
+               wpsTab->setEnabled(wps);
+               wpaguiTab->setTabEnabled(wpaguiTab->indexOf(wpsTab), wps);
+       }
+
        return 0;
 }
 
@@ -371,6 +474,21 @@ void WpaGui::updateStatus()
                textSsid->clear();
                textBssid->clear();
                textIpAddress->clear();
+
+#ifdef CONFIG_NATIVE_WINDOWS
+               static bool first = true;
+               if (first && connectedToService &&
+                   (ctrl_iface == NULL || *ctrl_iface == '\0')) {
+                       first = false;
+                       if (QMessageBox::information(
+                                   this, qAppName(),
+                                   "No network interfaces in use.\n"
+                                   "Would you like to add one?",
+                                   QMessageBox::Yes | QMessageBox::No) ==
+                           QMessageBox::Yes)
+                               addInterface();
+               }
+#endif /* CONFIG_NATIVE_WINDOWS */
                return;
        }
 
@@ -585,6 +703,7 @@ void WpaGui::disconnect()
        char reply[10];
        size_t reply_len = sizeof(reply);
        ctrlRequest("DISCONNECT", reply, &reply_len);
+       stopWpsRun(false);
 }
 
 
@@ -595,9 +714,6 @@ void WpaGui::scan()
                delete scanres;
        }
 
-       if (!isVisible())
-               show();
-
        scanres = new ScanResults();
        if (scanres == NULL)
                return;
@@ -614,9 +730,6 @@ void WpaGui::eventHistory()
                delete eh;
        }
 
-       if (!isVisible())
-               show();
-
        eh = new EventHistory();
        if (eh == NULL)
                return;
@@ -671,6 +784,14 @@ void WpaGui::ping()
                updateStatus();
                updateNetworks();
        }
+
+#ifndef CONFIG_CTRL_IFACE_NAMED_PIPE
+       /* Use less frequent pings and status updates when the main window is
+        * hidden (running in taskbar). */
+       int interval = isHidden() ? 5000 : 1000;
+       if (timer->interval() != interval)
+               timer->setInterval(interval);
+#endif /* CONFIG_CTRL_IFACE_NAMED_PIPE */
 }
 
 
@@ -730,6 +851,49 @@ void WpaGui::processMsg(char *msg)
                showTrayMessage(QSystemTrayIcon::Information, 3,
                                "Connection to network established.");
                QTimer::singleShot(5 * 1000, this, SLOT(showTrayStatus()));
+               stopWpsRun(true);
+       } else if (str_match(pos, WPS_EVENT_AP_AVAILABLE_PBC)) {
+               showTrayMessage(QSystemTrayIcon::Information, 3,
+                               "Wi-Fi Protected Setup (WPS) AP\n"
+                               "in active PBC mode found.");
+               wpsStatusText->setText("WPS AP in active PBC mode found");
+               wpaguiTab->setCurrentWidget(wpsTab);
+               wpsInstructions->setText("Press the PBC button on the screen "
+                                        "to start registration");
+       } else if (str_match(pos, WPS_EVENT_AP_AVAILABLE_PIN)) {
+               showTrayMessage(QSystemTrayIcon::Information, 3,
+                               "Wi-Fi Protected Setup (WPS) AP\n"
+                               " in active PIN mode found.");
+               wpsStatusText->setText("WPS AP with recently selected "
+                                      "registrar");
+               wpaguiTab->setCurrentWidget(wpsTab);
+       } else if (str_match(pos, WPS_EVENT_AP_AVAILABLE)) {
+               showTrayMessage(QSystemTrayIcon::Information, 3,
+                               "Wi-Fi Protected Setup (WPS)\n"
+                               "AP detected.");
+               wpsStatusText->setText("WPS AP detected");
+               wpaguiTab->setCurrentWidget(wpsTab);
+       } else if (str_match(pos, WPS_EVENT_OVERLAP)) {
+               showTrayMessage(QSystemTrayIcon::Information, 3,
+                               "Wi-Fi Protected Setup (WPS)\n"
+                               "PBC mode overlap detected.");
+               wpsStatusText->setText("PBC mode overlap detected");
+               wpsInstructions->setText("More than one AP is currently in "
+                                        "active WPS PBC mode. Wait couple of "
+                                        "minutes and try again");
+               wpaguiTab->setCurrentWidget(wpsTab);
+       } else if (str_match(pos, WPS_EVENT_CRED_RECEIVED)) {
+               wpsStatusText->setText("Network configuration received");
+               wpaguiTab->setCurrentWidget(wpsTab);
+       } else if (str_match(pos, WPA_EVENT_EAP_METHOD)) {
+               if (strstr(pos, "(WSC)"))
+                       wpsStatusText->setText("Registration started");
+       } else if (str_match(pos, WPS_EVENT_M2D)) {
+               wpsStatusText->setText("Registrar does not yet know PIN");
+       } else if (str_match(pos, WPS_EVENT_FAIL)) {
+               wpsStatusText->setText("Registration failed");
+       } else if (str_match(pos, WPS_EVENT_SUCCESS)) {
+               wpsStatusText->setText("Registration succeeded");
        }
 }
 
@@ -796,6 +960,7 @@ void WpaGui::selectNetwork( const QString &sel )
        cmd.prepend("SELECT_NETWORK ");
        ctrlRequest(cmd.toAscii().constData(), reply, &reply_len);
        triggerUpdate();
+       stopWpsRun(false);
 }
 
 
@@ -1091,11 +1256,16 @@ void WpaGui::selectAdapter( const QString & sel )
 }
 
 
-void WpaGui::createTrayIcon()
+void WpaGui::createTrayIcon(bool trayOnly)
 {
+       QApplication::setQuitOnLastWindowClosed(false);
+
        tray_icon = new QSystemTrayIcon(this);
        tray_icon->setToolTip(qAppName() + " - wpa_supplicant user interface");
-       tray_icon->setIcon(QIcon(":/icons/wpa_gui.svg"));
+       if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
+               tray_icon->setIcon(QIcon(":/icons/wpa_gui.svg"));
+       else
+               tray_icon->setIcon(QIcon(":/icons/wpa_gui.png"));
 
        connect(tray_icon,
                SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
@@ -1138,6 +1308,12 @@ void WpaGui::createTrayIcon()
        tray_menu->addAction(quitAction);
 
        tray_icon->setContextMenu(tray_menu);
+
+       tray_icon->show();
+
+       if (!trayOnly)
+               show();
+       inTray = trayOnly;
 }
 
 
@@ -1160,10 +1336,14 @@ void WpaGui::trayActivated(QSystemTrayIcon::ActivationReason how)
        /* use close() here instead of hide() and allow the
         * custom closeEvent handler take care of children */
        case QSystemTrayIcon::Trigger:
-               if (isVisible())
+               ackTrayIcon = true;
+               if (isVisible()) {
                        close();
-               else
+                       inTray = true;
+               } else {
                        show();
+                       inTray = false;
+               }
                break;
        case QSystemTrayIcon::MiddleClick:
                showTrayStatus();
@@ -1217,14 +1397,6 @@ void WpaGui::showTrayStatus()
                showTrayMessage(QSystemTrayIcon::Information, 10, msg);
 }
 
-void WpaGui::fileExit()
-{
-       if (tray_icon)
-               tray_icon->hide();
-
-       close();
-}
-
 
 void WpaGui::closeEvent(QCloseEvent *event)
 {
@@ -1246,26 +1418,282 @@ void WpaGui::closeEvent(QCloseEvent *event)
                udr = NULL;
        }
 
-       if (tray_icon && tray_icon->isVisible()) {
+       if (tray_icon && !ackTrayIcon) {
                /* give user a visual hint that the tray icon exists */
                if (QSystemTrayIcon::supportsMessages()) {
                        hide();
-                       QTimer::singleShot(1 * 1000, this,
-                                          SLOT(showTrayStatus()));
-               } else if (!ackTrayIcon) {
+                       showTrayMessage(QSystemTrayIcon::Information, 3,
+                                       qAppName() + " will keep running in "
+                                       "the system tray.");
+               } else {
                        QMessageBox::information(this, qAppName() + " systray",
                                                 "The program will keep "
-                                                "running in the system tray."
-                                                " To terminate the program, "
-                                                "choose <b>Quit</b> in the "
-                                                "context menu of the system "
-                                                "tray icon.");
-                       ackTrayIcon = true;
-                       hide();
+                                                "running in the system "
+                                                "tray.");
                }
-               event->ignore();
-               return;
+               ackTrayIcon = true;
        }
 
        event->accept();
 }
+
+
+void WpaGui::wpsDialog()
+{
+       wpaguiTab->setCurrentWidget(wpsTab);
+}
+
+
+void WpaGui::tabChanged(int index)
+{
+       if (index != 2)
+               return;
+
+       if (wpsRunning)
+               return;
+
+       wpsApPinEdit->setEnabled(!bssFromScan.isEmpty());
+       if (bssFromScan.isEmpty())
+               wpsApPinButton->setEnabled(false);
+}
+
+
+void WpaGui::wpsPbc()
+{
+       char reply[20];
+       size_t reply_len = sizeof(reply);
+
+       if (ctrlRequest("WPS_PBC", reply, &reply_len) < 0)
+               return;
+
+       wpsPinEdit->setEnabled(false);
+       if (wpsStatusText->text().compare("WPS AP in active PBC mode found")) {
+               wpsInstructions->setText("Press the push button on the AP to "
+                                        "start the PBC mode.");
+       } else {
+               wpsInstructions->setText("If you have not yet done so, press "
+                                        "the push button on the AP to start "
+                                        "the PBC mode.");
+       }
+       wpsStatusText->setText("Waiting for Registrar");
+       wpsRunning = true;
+}
+
+
+void WpaGui::wpsGeneratePin()
+{
+       char reply[20];
+       size_t reply_len = sizeof(reply) - 1;
+
+       if (ctrlRequest("WPS_PIN any", reply, &reply_len) < 0)
+               return;
+
+       reply[reply_len] = '\0';
+
+       wpsPinEdit->setText(reply);
+       wpsPinEdit->setEnabled(true);
+       wpsInstructions->setText("Enter the generated PIN into the Registrar "
+                                "(either the internal one in the AP or an "
+                                "external one).");
+       wpsStatusText->setText("Waiting for Registrar");
+       wpsRunning = true;
+}
+
+
+void WpaGui::setBssFromScan(const QString &bssid)
+{
+       bssFromScan = bssid;
+       wpsApPinEdit->setEnabled(!bssFromScan.isEmpty());
+       wpsApPinButton->setEnabled(wpsApPinEdit->text().length() == 8);
+       wpsStatusText->setText("WPS AP selected from scan results");
+       wpsInstructions->setText("If you want to use an AP device PIN, e.g., "
+                                "from a label in the device, enter the eight "
+                                "digit AP PIN and click Use AP PIN button.");
+}
+
+
+void WpaGui::wpsApPinChanged(const QString &text)
+{
+       wpsApPinButton->setEnabled(text.length() == 8);
+}
+
+
+void WpaGui::wpsApPin()
+{
+       char reply[20];
+       size_t reply_len = sizeof(reply);
+
+       QString cmd("WPS_REG " + bssFromScan + " " + wpsApPinEdit->text());
+       if (ctrlRequest(cmd.toAscii().constData(), reply, &reply_len) < 0)
+               return;
+
+       wpsStatusText->setText("Waiting for AP/Enrollee");
+       wpsRunning = true;
+}
+
+
+void WpaGui::stopWpsRun(bool success)
+{
+       if (wpsRunning)
+               wpsStatusText->setText(success ? "Connected to the network" :
+                                      "Stopped");
+       else
+               wpsStatusText->setText("");
+       wpsPinEdit->setEnabled(false);
+       wpsInstructions->setText("");
+       wpsRunning = false;
+       bssFromScan = "";
+       wpsApPinEdit->setEnabled(false);
+       wpsApPinButton->setEnabled(false);
+}
+
+
+#ifdef CONFIG_NATIVE_WINDOWS
+
+#ifndef WPASVC_NAME
+#define WPASVC_NAME TEXT("wpasvc")
+#endif
+
+class ErrorMsg : public QMessageBox {
+public:
+       ErrorMsg(QWidget *parent, DWORD last_err = GetLastError());
+       void showMsg(QString msg);
+private:
+       DWORD err;
+};
+
+ErrorMsg::ErrorMsg(QWidget *parent, DWORD last_err) :
+       QMessageBox(parent), err(last_err)
+{
+       setWindowTitle("wpa_gui error");
+       setIcon(QMessageBox::Warning);
+}
+
+void ErrorMsg::showMsg(QString msg)
+{
+       LPTSTR buf;
+
+       setText(msg);
+       if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                         FORMAT_MESSAGE_FROM_SYSTEM,
+                         NULL, err, 0, (LPTSTR) (void *) &buf,
+                         0, NULL) > 0) {
+               QString msg = QString::fromWCharArray(buf);
+               setInformativeText(QString("[%1] %2").arg(err).arg(msg));
+               LocalFree(buf);
+       } else {
+               setInformativeText(QString("[%1]").arg(err));
+       }
+
+       exec();
+}
+
+
+void WpaGui::startService()
+{
+       SC_HANDLE svc, scm;
+
+       scm = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
+       if (!scm) {
+               ErrorMsg(this).showMsg("OpenSCManager failed");
+               return;
+       }
+
+       svc = OpenService(scm, WPASVC_NAME, SERVICE_START);
+       if (!svc) {
+               ErrorMsg(this).showMsg("OpenService failed");
+               CloseServiceHandle(scm);
+               return;
+       }
+
+       if (!StartService(svc, 0, NULL)) {
+               ErrorMsg(this).showMsg("Failed to start wpa_supplicant "
+                                      "service");
+       }
+
+       CloseServiceHandle(svc);
+       CloseServiceHandle(scm);
+}
+
+
+void WpaGui::stopService()
+{
+       SC_HANDLE svc, scm;
+       SERVICE_STATUS status;
+
+       scm = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
+       if (!scm) {
+               ErrorMsg(this).showMsg("OpenSCManager failed");
+               return;
+       }
+
+       svc = OpenService(scm, WPASVC_NAME, SERVICE_STOP);
+       if (!svc) {
+               ErrorMsg(this).showMsg("OpenService failed");
+               CloseServiceHandle(scm);
+               return;
+       }
+
+       if (!ControlService(svc, SERVICE_CONTROL_STOP, &status)) {
+               ErrorMsg(this).showMsg("Failed to stop wpa_supplicant "
+                                      "service");
+       }
+
+       CloseServiceHandle(svc);
+       CloseServiceHandle(scm);
+}
+
+
+bool WpaGui::serviceRunning()
+{
+       SC_HANDLE svc, scm;
+       SERVICE_STATUS status;
+       bool running = false;
+
+       scm = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
+       if (!scm) {
+               printf("OpenSCManager failed: %d\n", (int) GetLastError());
+               return false;
+       }
+
+       svc = OpenService(scm, WPASVC_NAME, SERVICE_QUERY_STATUS);
+       if (!svc) {
+               printf("OpenService failed: %d\n\n", (int) GetLastError());
+               CloseServiceHandle(scm);
+               return false;
+       }
+
+       if (QueryServiceStatus(svc, &status)) {
+               if (status.dwCurrentState != SERVICE_STOPPED)
+                       running = true;
+       }
+
+       CloseServiceHandle(svc);
+       CloseServiceHandle(scm);
+
+       return running;
+}
+
+#endif /* CONFIG_NATIVE_WINDOWS */
+
+
+void WpaGui::addInterface()
+{
+       if (add_iface) {
+               add_iface->close();
+               delete add_iface;
+       }
+       add_iface = new AddInterface(this, this);
+       add_iface->show();
+       add_iface->exec();
+}
+
+
+void WpaGui::saveState()
+{
+       QSettings settings("wpa_supplicant", "wpa_gui");
+       settings.beginGroup("state");
+       settings.setValue("session_id", app->sessionId());
+       settings.setValue("in_tray", inTray);
+       settings.endGroup();
+}