summaryrefslogtreecommitdiffstats
path: root/src/wifi/qwifimanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wifi/qwifimanager.cpp')
-rw-r--r--src/wifi/qwifimanager.cpp600
1 files changed, 600 insertions, 0 deletions
diff --git a/src/wifi/qwifimanager.cpp b/src/wifi/qwifimanager.cpp
new file mode 100644
index 0000000..de35224
--- /dev/null
+++ b/src/wifi/qwifimanager.cpp
@@ -0,0 +1,600 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://www.qt.io
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://www.qt.io
+**
+****************************************************************************/
+#include "qwificontroller_p.h"
+#include "qwifinetworklistmodel_p.h"
+#include "qwifinetwork_p.h"
+#include "qwifimanager_p.h"
+#include "qwifidevice.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QProcess>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmlmodule QtWifi 1.0
+ \title QtWifi Module
+ \ingroup qtee-qmlmodules
+ \brief A module for managing wireless network connectivity.
+
+ Provides QML types for controlling WiFi networks - handling WiFi backend initialization,
+ retrieving information from nearby WiFi access points, setting up and bringing down WiFi
+ connections, querying DHCP server for IP address.
+
+ The import command for adding these QML types is:
+
+ \code
+ import QtWifi 1.0
+ \endcode
+
+ \section1 API Reference
+*/
+
+/*!
+
+ \qmltype WifiManager
+ \inqmlmodule QtWifi
+ \ingroup wifi-qmltypes
+ \brief Main interface to the WiFi functionality.
+
+ WifiManager is a singleton type that provides information about the WiFi backend and
+ available networks, use it to control the WiFi backend, scan for wireless networks
+ and connect to selected network. WifiManager provides events for backend and network
+ state changes.
+
+ */
+
+/*!
+ \qmlsignal void WifiManager::networkStateChanged(NetworkState networkState)
+
+ This signal is emitted whenever changes in a network state occur. The network name for
+ which the state changes events are send can be obtained from currentSSID.
+
+ \sa networkState
+*/
+
+/*!
+ \qmlsignal void WifiManager::backendStateChanged(BackendState backendState)
+
+ This signal is emitted whenever changes in a backend state occur.
+
+ \sa start, stop
+*/
+
+/*!
+ \qmlsignal void WifiManager::currentSSIDChanged(string currentSSID)
+
+ This signal is emitted when switching between different WiFi networks.
+
+ \sa start, stop
+*/
+
+/*!
+ \qmlsignal void WifiManager::scanningChanged(bool scanning)
+
+ This signal is emitted when device starts or stops to scan for available wifi networks.
+
+ \sa scanning
+
+*/
+
+// must be in the same order as in enum {} definiton
+const char *nsText[] = { "Disconnected", "Authenticating", "HandshakeFailed",
+ "ObtainingIPAddress", "DhcpRequestFailed", "Connected" };
+const char *bsText[] = { "Initializing", "Running", "Terminating", "NotRunning" };
+
+QWifiManagerPrivate::QWifiManagerPrivate(QWifiManager *manager)
+ : q_ptr(manager)
+ , m_networkListModel(new QWifiNetworkListModel())
+ , m_scanTimer(0)
+ , m_scanning(false)
+ , m_interface(QWifiDevice::wifiInterfaceName())
+ , m_backendState(QWifiManager::NotRunning)
+ , m_networkState(QWifiManager::Disconnected)
+ , m_setCurrentSSID(true)
+{
+ qCDebug(B2QT_WIFI) << "using wifi interface: " << m_interface;
+}
+
+QWifiManagerPrivate::~QWifiManagerPrivate()
+{
+ delete m_wifiController;
+ delete m_networkListModel;
+}
+
+QString QWifiManagerPrivate::getConnectedNetwork()
+{
+ QStringList lists = call(QStringLiteral("LIST_NETWORKS")).split('\n');
+ QString connectedNetwork;
+ for (int i = 1; i < lists.size(); ++i) {
+ if (lists.at(i).toUpper().contains(QStringLiteral("[CURRENT]"))) {
+ connectedNetwork = lists.at(i);
+ break;
+ }
+ }
+ return connectedNetwork;
+}
+
+void QWifiManagerPrivate::emitCurrentSSIDChanged()
+{
+ Q_Q(QWifiManager);
+ if (m_previousSSID != m_currentSSID) {
+ qCDebug(B2QT_WIFI) << "current SSID: " << m_previousSSID << " -> " << m_currentSSID;
+ m_previousSSID = m_currentSSID;
+ emit q->currentSSIDChanged(m_currentSSID);
+ }
+}
+
+void QWifiManagerPrivate::setCurrentSSID()
+{
+ qCDebug(B2QT_WIFI, "setCurrentSSID");
+ m_setCurrentSSID = false;
+ QString connectedNetwork = getConnectedNetwork();
+ if (!connectedNetwork.isEmpty()) {
+ QString ssid = connectedNetwork.split('\t').at(1);
+ QWifiNetwork *network = m_networkListModel->networkForSSID(ssid);
+ if (network) {
+ m_currentSSID = network->ssid();
+ emitCurrentSSIDChanged();
+ if (call(QStringLiteral("STATUS")).contains(QStringLiteral("wpa_state=COMPLETED")))
+ updateNetworkState(QWifiManager::Connected);
+ }
+ }
+}
+
+void QWifiManagerPrivate::handleConnected()
+{
+ qCDebug(B2QT_WIFI, "handleConnected");
+ QString connectedNetwork = getConnectedNetwork();
+ if (connectedNetwork.isEmpty())
+ return;
+
+ m_currentSSID = connectedNetwork.split('\t').at(1);
+ qCDebug(B2QT_WIFI) << "connected network: " << m_currentSSID;
+ updateNetworkState(QWifiManager::ObtainingIPAddress);
+ m_wifiController->call(QWifiController::AcquireIPAddress);
+}
+
+void QWifiManagerPrivate::handleDisconneced()
+{
+ updateNetworkState(QWifiManager::Disconnected);
+}
+
+void QWifiManagerPrivate::updateNetworkState(QWifiManager::NetworkState networkState)
+{
+ Q_Q(QWifiManager);
+ qCDebug(B2QT_WIFI, "network state: %s -> %s", nsText[m_networkState], nsText[networkState]);
+ if (m_networkState == networkState)
+ return;
+
+ m_networkState = networkState;
+ emit q->networkStateChanged(m_networkState);
+}
+
+void QWifiManagerPrivate::updateBackendState(QWifiManager::BackendState backendState)
+{
+ Q_Q(QWifiManager);
+ qCDebug(B2QT_WIFI, "backend state: %s -> %s", bsText[m_backendState], bsText[backendState]);
+ if (m_backendState == backendState)
+ return;
+
+ m_backendState = backendState;
+ emit q->backendStateChanged(m_backendState);
+}
+
+void QWifiManagerPrivate::updateWifiState()
+{
+ bool supplicantRunning = false;
+#ifdef Q_OS_ANDROID_NO_SDK
+ char supplicantState[PROPERTY_VALUE_MAX];
+ if (property_get("init.svc.wpa_supplicant", supplicantState, 0)) {
+ if (strcmp(supplicantState, "running") == 0)
+ supplicantRunning = true;
+ }
+#else
+ QProcess ps;
+ ps.start(QStringLiteral("ps"));
+ ps.waitForFinished();
+ if (ps.readAll().contains("wpa_supplicant"))
+ supplicantRunning = true;
+#endif
+ if (supplicantRunning) {
+ m_wifiController->resetSupplicantSocket();
+ m_wifiController->startWifiEventThread();
+ m_backendState = QWifiManager::Running;
+ }
+}
+
+QString QWifiManagerPrivate::call(const QString &command)
+{
+ if (m_backendState != QWifiManager::Running)
+ return QString();
+
+ char data[2048];
+ size_t len = sizeof(data) - 1; // -1: room to add a 0-terminator
+ QString actualCommand = command;
+#ifdef Q_OS_ANDROID_NO_SDK
+#if !(Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR < 4)
+ QString prefix = QLatin1String("IFNAME=" + m_interface + " ");
+ actualCommand.prepend(prefix);
+#endif
+#endif
+ if (q_wifi_command(m_interface, actualCommand.toLatin1(), data, &len) < 0) {
+ qCDebug(B2QT_WIFI) << "call to supplicant failed: " << actualCommand;
+ return QString();
+ }
+ if (len < sizeof(data))
+ data[len] = 0;
+
+ QString result = QLatin1String(data);
+ return result;
+}
+
+bool QWifiManagerPrivate::checkedCall(const QString &command)
+{
+ return call(command).trimmed().toUpper() == QLatin1String("OK");
+}
+
+void QWifiManagerPrivate::updateLastError(const QString &error)
+{
+ Q_Q(QWifiManager);
+ qCWarning(B2QT_WIFI) << error;
+ m_lastError = error;
+ emit q->lastErrorChanged(m_lastError);
+}
+
+
+QWifiManager* QWifiManager::m_instance = 0;
+QWifiManager* QWifiManager::instance()
+{
+ if (!m_instance)
+ m_instance = new QWifiManager();
+ return m_instance;
+}
+
+QWifiManager::QWifiManager()
+ : d_ptr(new QWifiManagerPrivate(this))
+{
+ Q_D(QWifiManager);
+
+ if (!QWifiDevice::wifiSupported())
+ qCWarning(B2QT_WIFI) << "WifiManager may not work as expected on this device. Use the API provided by QtWifi "
+ "library to verify if device has support for Wi-Fi before creating an instance of wifi manager!";
+
+ d->m_wifiController = new QWifiController(this, d_ptr);
+ QObject::connect(d->m_wifiController, &QWifiController::backendStateChanged,
+ this, &QWifiManager::handleBackendStateChanged);
+ QObject::connect(d->m_wifiController, &QWifiController::dhcpRequestFinished,
+ this, &QWifiManager::handleDhcpRequestFinished);
+ d->m_wifiController->start();
+
+ d->updateWifiState();
+}
+
+QWifiManager::~QWifiManager()
+{
+ Q_D(QWifiManager);
+ d->m_wifiController->call(QWifiController::ExitEventLoop);
+ d->m_wifiController->wait();
+ delete d_ptr;
+}
+
+/*!
+ \qmlproperty WifiNetworkListModel WifiManager::networks
+ \readonly
+
+ This property holds a list of networks that can be sensed by a device. Use the returned
+ model as data model in ListView, model is updated every 5 seconds.
+
+ WifiNetworkListModel is a simple data model consisting of WifiNetwork objects, accessed with
+ the "network" data role name. Instances of WifiNetwork cannot be created directly from the QML system.
+
+*/
+
+QAbstractListModel *QWifiManager::networks() const
+{
+ Q_D(const QWifiManager);
+ return d->m_networkListModel;
+}
+
+/*!
+ \qmlproperty string WifiManager::currentSSID
+ \readonly
+
+ This property holds the network name for which the networkState changes events are sent or
+ or an empty string when there is no active network.
+*/
+
+QString QWifiManager::currentSSID() const
+{
+ Q_D(const QWifiManager);
+ return d->m_currentSSID;
+}
+
+/*!
+ \qmlproperty enumeration WifiManager::networkState
+ \readonly
+
+ This property holds the current state of the network connection.
+
+ \list
+ \li \e WifiManager.Disconnected - Not connected to any network
+ \li \e WifiManager.Authenticating - Verifying password with the network provider
+ \li \e WifiManager.HandshakeFailed - Incorrect password provided
+ \li \e WifiManager.ObtainingIPAddress - Requesting IP address from DHCP server
+ \li \e WifiManager.DhcpRequestFailed - Could not retrieve IP address
+ \li \e WifiManager.Connected - Ready to process network requests
+ \endlist
+*/
+
+QWifiManager::NetworkState QWifiManager::networkState() const
+{
+ Q_D(const QWifiManager);
+ return d->m_networkState;
+}
+
+/*!
+ \qmlproperty enumeration WifiManager::backendState
+ \readonly
+
+ This property holds the current state of the WiFi backend.
+
+ \list
+ \li \e WifiManager.Initializing - Wireless supplicant is starting up
+ \li \e WifiManager.Running - Supplicant is initialized and ready to process commands
+ \li \e WifiManager.Terminating - Shutting down wireless supplicant
+ \li \e WifiManager.NotRunning - Wireless supplicant process is not running
+ \endlist
+*/
+
+QWifiManager::BackendState QWifiManager::backendState() const
+{
+ Q_D(const QWifiManager);
+ return d->m_backendState;
+}
+
+/*!
+ \qmlmethod void WifiManager::start()
+
+ Start the WiFi backend. This function returns immediately, the backendState
+ change events are delivered asynchronously.
+
+ \sa stop, backendState
+ */
+
+void QWifiManager::start()
+{
+ Q_D(QWifiManager);
+ d->m_wifiController->call(QWifiController::InitializeBackend);
+}
+
+/*!
+ \qmlmethod void WifiManager::stop()
+
+ Stop the WiFi backend and if connected to any network shut down network connection.
+ This function returns immediately, the backendState change events are delivered asynchronously.
+
+ \sa start, backendState
+ */
+
+void QWifiManager::stop()
+{
+ Q_D(QWifiManager);
+ d->m_wifiController->call(QWifiController::TerminateBackend);
+}
+
+void QWifiManager::handleBackendStateChanged(BackendState backendState)
+{
+ Q_D(QWifiManager);
+ switch (backendState) {
+ case Running:
+ d->m_setCurrentSSID = true;
+ break;
+ case NotRunning:
+ d->updateNetworkState(Disconnected);
+ break;
+ default:
+ break;
+ }
+ d->updateBackendState(backendState);
+}
+
+void QWifiManager::handleDhcpRequestFinished(const QString &status)
+{
+ Q_D(QWifiManager);
+ qCDebug(B2QT_WIFI) << "handleDhcpRequestFinished: " << status << " for " << d->m_currentSSID;
+ if (status == QLatin1String("success")) {
+ d->emitCurrentSSIDChanged();
+ d->updateNetworkState(Connected);
+ d->call(QStringLiteral("SAVE_CONFIG"));
+ } else {
+ d->updateNetworkState(DhcpRequestFailed);
+ }
+}
+
+void QWifiManager::setScanning(bool scanning)
+{
+ Q_D(QWifiManager);
+ if (d->m_scanning == scanning)
+ return;
+
+ d->m_scanning = scanning;
+ emit scanningChanged(d->m_scanning);
+ if (d->m_scanning) {
+ d->call(QStringLiteral("SCAN"));
+ // ### TODO android has property for this - wifi.supplicant_scan_interval
+ d->m_scanTimer = startTimer(5000);
+ } else {
+ killTimer(d->m_scanTimer);
+ }
+}
+
+/*!
+ \qmlproperty bool WifiManager::scanning
+
+ This property holds whether or not the backend is scanning for WiFi networks. To
+ preserve battery energy, set this property to false when scanning is not required.
+
+ Before starting to scan for networks, you need to initialize the WiFi backend.
+
+ \sa start
+*/
+
+bool QWifiManager::scanning() const
+{
+ Q_D(const QWifiManager);
+ return d->m_scanning;
+}
+
+QString QWifiManager::lastError() const
+{
+ Q_D(const QWifiManager);
+ return d->m_lastError;
+}
+
+bool QWifiManager::event(QEvent *event)
+{
+ Q_D(QWifiManager);
+ switch ((int) event->type()) {
+ case WIFI_SCAN_RESULTS:
+ d->m_networkListModel->parseScanResults(d->call(QStringLiteral("SCAN_RESULTS")));
+ if (d->m_setCurrentSSID || d->m_currentSSID.isEmpty())
+ d->setCurrentSSID();
+ return true;
+ case WIFI_CONNECTED:
+ d->handleConnected();
+ return true;
+ case WIFI_DISCONNECTED:
+ d->handleDisconneced();
+ return true;
+ case WIFI_AUTHENTICATING:
+ d->updateNetworkState(Authenticating);
+ d->emitCurrentSSIDChanged();
+ return true;
+ case WIFI_HANDSHAKE_FAILED:
+ d->updateNetworkState(HandshakeFailed);
+ return true;
+ case QEvent::Timer: {
+ int tid = static_cast<QTimerEvent *>(event)->timerId();
+ if (tid == d->m_scanTimer) {
+ d->call(QStringLiteral("SCAN"));
+ return true;
+ }
+ break;
+ }
+ }
+ return QObject::event(event);
+}
+
+
+/*!
+ \qmlmethod void WifiManager::connect(WifiNetwork network, string passphrase)
+
+ Connect to network \a network and use passphrase \a passphrase for authentication.
+
+ \sa disconnect, networkState
+ */
+
+bool QWifiManager::connect(QWifiConfiguration *config)
+{
+ Q_D(QWifiManager);
+ if (d->m_backendState != Running) {
+ qCWarning(B2QT_WIFI) << "start wifi backend before calling connect() !";
+ return false;
+ }
+
+ d->call(QStringLiteral("DISABLE_NETWORK all"));
+
+ d->m_currentSSID = config->ssid();
+ bool networkKnown = false;
+ QString id;
+ QString listResult = d->call(QStringLiteral("LIST_NETWORKS"));
+ QStringList lines = listResult.split('\n');
+ foreach (const QString &line, lines) {
+ if (line.contains(d->m_currentSSID)) {
+ id = line.split('\t').at(0);
+ networkKnown = true;
+ break;
+ }
+ }
+
+ if (!networkKnown) {
+ bool ok;
+ id = d->call(QStringLiteral("ADD_NETWORK")).trimmed();
+ id.toInt(&ok);
+ if (!ok) {
+ d->updateLastError(QStringLiteral("failed to add network!"));
+ return false;
+ }
+ }
+
+ bool ok = true;
+ QChar q = QLatin1Char('"');
+ QString setNetworkCommand = QLatin1String("SET_NETWORK ") + id;
+ if (!networkKnown) {
+ ok = ok && d->checkedCall(setNetworkCommand + QLatin1String(" ssid ") + q + d->m_currentSSID + q);
+ }
+
+ QString key_mgmt;
+ QString protocol = config->protocol().toUpper();
+ QString psk = config->passphrase();
+ // ref: http://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf
+ if (protocol.isEmpty() || protocol.contains(QStringLiteral("WPA"))) {
+ // ### todo - password length has limits (see IEEE 802.11), we need to check
+ // for those limits here. Supplicant gives only a meaningless "fail" message.
+ ok = ok && d->checkedCall(setNetworkCommand + QLatin1String(" psk ") + q + psk + q);
+ key_mgmt = QLatin1String("WPA-PSK");
+ } else if (protocol.contains(QStringLiteral("WEP"))) {
+ ok = ok && d->checkedCall(setNetworkCommand + QLatin1String(" wep_key0 ") + q + psk + q);
+ ok = ok && d->checkedCall(setNetworkCommand + QLatin1String(" auth_alg OPEN SHARED"));
+ key_mgmt = QLatin1String("NONE");
+ } else if (protocol.contains(QStringLiteral("WPS")) && psk.length() == 0) {
+ // open network
+ key_mgmt = QLatin1String("NONE");
+ }
+ ok = ok && d->checkedCall(setNetworkCommand + QLatin1String(" key_mgmt ") + key_mgmt);
+ if (!ok) {
+ if (!networkKnown)
+ d->call(QLatin1String("REMOVE_NETWORK ") + id);
+ d->updateLastError(QLatin1String("failed to set properties on network: ") + id);
+ return false;
+ }
+
+ d->call(QLatin1String("SELECT_NETWORK ") + id);
+ d->call(QStringLiteral("RECONNECT"));
+
+ return true;
+}
+
+/*!
+ \qmlmethod void WifiManager::disconnect()
+
+ Disconnect from currently connected network connection.
+
+ \sa connect, networkState
+*/
+
+void QWifiManager::disconnect()
+{
+ Q_D(QWifiManager);
+ d->call(QStringLiteral("DISCONNECT"));
+ d->m_wifiController->call(QWifiController::StopDhcp);
+}
+
+QT_END_NAMESPACE