diff options
Diffstat (limited to 'src/wifi')
-rw-r--r-- | src/wifi/qwificonfiguration.cpp | 89 | ||||
-rw-r--r-- | src/wifi/qwificonfiguration.h | 54 | ||||
-rw-r--r-- | src/wifi/qwificontroller.cpp | 461 | ||||
-rw-r--r-- | src/wifi/qwificontroller_p.h | 120 | ||||
-rw-r--r-- | src/wifi/qwifidevice.cpp | 152 | ||||
-rw-r--r-- | src/wifi/qwifidevice.h | 44 | ||||
-rw-r--r-- | src/wifi/qwifielinux.cpp | 317 | ||||
-rw-r--r-- | src/wifi/qwifielinux_p.h | 42 | ||||
-rw-r--r-- | src/wifi/qwifimanager.cpp | 600 | ||||
-rw-r--r-- | src/wifi/qwifimanager.h | 118 | ||||
-rw-r--r-- | src/wifi/qwifimanager_p.h | 68 | ||||
-rw-r--r-- | src/wifi/qwifinetwork.cpp | 121 | ||||
-rw-r--r-- | src/wifi/qwifinetwork_p.h | 66 | ||||
-rw-r--r-- | src/wifi/qwifinetworklistmodel.cpp | 184 | ||||
-rw-r--r-- | src/wifi/qwifinetworklistmodel_p.h | 57 | ||||
-rw-r--r-- | src/wifi/wifi.pro | 43 |
16 files changed, 2536 insertions, 0 deletions
diff --git a/src/wifi/qwificonfiguration.cpp b/src/wifi/qwificonfiguration.cpp new file mode 100644 index 0000000..f2c859a --- /dev/null +++ b/src/wifi/qwificonfiguration.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 "qwificonfiguration.h" + +QT_BEGIN_NAMESPACE + +class QWifiConfigurationPrivate +{ + Q_DECLARE_PUBLIC(QWifiConfiguration) +public: + QWifiConfigurationPrivate(QWifiConfiguration *config); + + // member variables + QWifiConfiguration *const q_ptr; + QString m_ssid; + QString m_psk; + QString m_protocol; +}; + +QWifiConfigurationPrivate::QWifiConfigurationPrivate(QWifiConfiguration *config) + : q_ptr(config) +{ +} + + +QWifiConfiguration::QWifiConfiguration(QObject *parent) + : QObject(parent) + , d_ptr(new QWifiConfigurationPrivate(this)) +{ +} + +QWifiConfiguration::~QWifiConfiguration() +{ + delete d_ptr; +} + +void QWifiConfiguration::setSsid(const QString &ssid) +{ + Q_D(QWifiConfiguration); + d->m_ssid = ssid; +} + +QString QWifiConfiguration::ssid() const +{ + Q_D(const QWifiConfiguration); + return d->m_ssid; +} + +void QWifiConfiguration::setPassphrase(const QString &psk) +{ + Q_D(QWifiConfiguration); + d->m_psk = psk; +} + +QString QWifiConfiguration::passphrase() const +{ + Q_D(const QWifiConfiguration); + return d->m_psk; +} + +void QWifiConfiguration::setProtocol(const QString &protocol) +{ + Q_D(QWifiConfiguration); + d->m_protocol = protocol; +} + +QString QWifiConfiguration::protocol() const +{ + Q_D(const QWifiConfiguration); + return d->m_protocol; +} + +QT_END_NAMESPACE diff --git a/src/wifi/qwificonfiguration.h b/src/wifi/qwificonfiguration.h new file mode 100644 index 0000000..2253fbb --- /dev/null +++ b/src/wifi/qwificonfiguration.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFICONFIGURATION_H +#define QWIFICONFIGURATION_H + +#include <QtCore/QObject> +#include <QtCore/QString> + +class QWifiConfigurationPrivate; + +QT_BEGIN_NAMESPACE + +class Q_DECL_EXPORT QWifiConfiguration : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString ssid READ ssid WRITE setSsid) + Q_PROPERTY(QString passphrase READ passphrase WRITE setPassphrase) + Q_PROPERTY(QString protocol READ protocol WRITE setProtocol) +public: + explicit QWifiConfiguration(QObject *parent = 0); + virtual ~QWifiConfiguration(); + + void setSsid(const QString &ssid); + QString ssid() const; + void setPassphrase(const QString &psk); + QString passphrase() const; + void setProtocol(const QString &protocol); + QString protocol() const; + +private: + Q_DISABLE_COPY(QWifiConfiguration) + Q_DECLARE_PRIVATE(QWifiConfiguration) + QWifiConfigurationPrivate *const d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWIFICONFIGURATION_H diff --git a/src/wifi/qwificontroller.cpp b/src/wifi/qwificontroller.cpp new file mode 100644 index 0000000..ea49764 --- /dev/null +++ b/src/wifi/qwificontroller.cpp @@ -0,0 +1,461 @@ +/**************************************************************************** +** +** 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 "qwifimanager_p.h" +#include "qwifidevice.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QProcess> +#include <QtCore/QByteArray> +#include <QtCore/QFile> + +#ifdef Q_OS_ANDROID_NO_SDK +#include <QtNetwork/QLocalSocket> +#include <cutils/sockets.h> +#include <unistd.h> +#endif + +#ifdef Q_OS_ANDROID_NO_SDK +/* + * Work API differences between Android versions + */ +int q_wifi_start_supplicant() +{ +#if Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR < 1 + return wifi_start_supplicant(); +#else + return wifi_start_supplicant(0); +#endif +} + +int q_wifi_stop_supplicant() +{ +#if Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR < 1 + return wifi_stop_supplicant(); +#else + return wifi_stop_supplicant(0); +#endif +} + +int q_wifi_connect_to_supplicant(const char *ifname) +{ +#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1) + return wifi_connect_to_supplicant(ifname); +#else + Q_UNUSED(ifname); + return wifi_connect_to_supplicant(); +#endif +} + +void q_wifi_close_supplicant_connection(const char *ifname) +{ +#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1) + wifi_close_supplicant_connection(ifname); +#else + Q_UNUSED(ifname); + wifi_close_supplicant_connection(); +#endif +} + +int q_wifi_wait_for_event(const char *ifname, char *buf, size_t len) +{ +#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1) + return wifi_wait_for_event(ifname, buf, len); +#else + Q_UNUSED(ifname); + return wifi_wait_for_event(buf, len); +#endif +} + +int q_wifi_command(const char *ifname, const char *command, char *reply, size_t *reply_len) +{ +#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1) + return wifi_command(ifname, command, reply, reply_len); +#else + Q_UNUSED(ifname); + return wifi_command(command, reply, reply_len); +#endif +} + +/* + * This function is borrowed from /system/core/libnetutils/dhcp_utils.c + * + * Wait for a system property to be assigned a specified value. + * If desired_value is NULL, then just wait for the property to + * be created with any value. maxwait is the maximum amount of + * time in seconds to wait before giving up. + */ +const int NAP_TIME = 200; // wait for 200ms at a time when polling for property values +int wait_for_property(const char *name, const char *desired_value, int maxwait) +{ + char value[PROPERTY_VALUE_MAX] = {'\0'}; + int maxnaps = (maxwait * 1000) / NAP_TIME; + + if (maxnaps < 1) { + maxnaps = 1; + } + + while (maxnaps-- > 0) { + usleep(NAP_TIME * 1000); + if (property_get(name, value, NULL)) { + if (desired_value == NULL || + strcmp(value, desired_value) == 0) { + return 0; + } + } + } + return -1; /* failure */ +} + +#endif // Q_OS_ANDROID_NO_SDK + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(B2QT_WIFI, "qt.b2qt.wifi") + +class QWifiManagerEvent : public QEvent +{ +public: + QWifiManagerEvent(QEvent::Type type, const QByteArray &data = QByteArray()) + : QEvent(type) + , m_data(data) + { + } + + QByteArray data() const { return m_data; } + +private: + QByteArray m_data; +}; + +class QWifiEventThread : public QThread +{ +public: + QWifiEventThread(QWifiController *controller) + : m_controller(controller) + { + m_interface = QWifiDevice::wifiInterfaceName(); + } + + void run() { + qCDebug(B2QT_WIFI) << "running wifi event thread"; + QWifiManagerEvent *event = 0; + char buffer[2048]; + forever { + int size = q_wifi_wait_for_event(m_interface, buffer, sizeof(buffer) - 1); + if (size > 0) { + buffer[size] = 0; + event = 0; + qCDebug(B2QT_WIFI) << "[event]: " << buffer; + if (m_controller->isWifiThreadExitRequested()) { + qCDebug(B2QT_WIFI) << "exit wifi event thread"; + return; + } + if (strstr(buffer, "SCAN-RESULTS")) { + event = new QWifiManagerEvent(WIFI_SCAN_RESULTS); + } else if (strstr(buffer, "CTRL-EVENT-CONNECTED")) { + event = new QWifiManagerEvent(WIFI_CONNECTED); + } else if (strstr(buffer, "CTRL-EVENT-DISCONNECTED")) { + event = new QWifiManagerEvent(WIFI_DISCONNECTED); + } else if (strstr(buffer, "Trying to associate")) { + event = new QWifiManagerEvent(WIFI_AUTHENTICATING); + } else if (strstr(buffer, "Handshake failed")) { + event = new QWifiManagerEvent(WIFI_HANDSHAKE_FAILED); + } + if (event) + QCoreApplication::postEvent(m_controller->wifiManager(), event); + } + } + } + +private: + QWifiController *m_controller; + QByteArray m_interface; +}; + + +QWifiController::QWifiController(QWifiManager *manager, QWifiManagerPrivate *managerPrivate) : + m_manager(manager), + m_managerPrivate(managerPrivate), + m_exitEventThread(false), +#ifdef Q_OS_ANDROID_NO_SDK + m_qcSocket(0), +#endif + m_eventThread(0) +{ + m_interface = QWifiDevice::wifiInterfaceName(); + m_eventThread = new QWifiEventThread(this); + + qRegisterMetaType<QWifiManager::BackendState>("QWifiManager::BackendState"); +#ifdef Q_OS_ANDROID_NO_SDK + qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState"); +#endif +} + +QWifiController::~QWifiController() +{ + exitWifiEventThread(); + delete m_eventThread; +#ifdef Q_OS_ANDROID_NO_SDK + delete m_qcSocket; +#endif +} + +void QWifiController::allocateOnThisThread() +{ +#ifdef Q_OS_ANDROID_NO_SDK + m_qcSocket = new QLocalSocket; + int qcFd = socket_local_client("qconnectivity", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + if (qcFd != -1) { + m_qcSocket->setSocketDescriptor(qcFd); + } else { + qCWarning(B2QT_WIFI) << "failed to get file descriptor of a qconnectivity socket!"; + } +#endif +} + +void QWifiController::run() +{ + qCDebug(B2QT_WIFI) << "running wifi backend controller thread"; + allocateOnThisThread(); + Method method; + forever { + m_methodsMutex.lock(); + if (m_methods.isEmpty()) + methodCallRequested.wait(&m_methodsMutex); + method = m_methods.takeFirst(); + m_methodsMutex.unlock(); + switch (method) { + case InitializeBackend: + initializeBackend(); + break; + case TerminateBackend: + terminateBackend(); + break; + case AcquireIPAddress: + acquireIPAddress(); + break; + case StopDhcp: + stopDhcp(); + break; + case ExitEventLoop: + qCDebug(B2QT_WIFI) << "exit wifi backend controller thread"; + return; + } + } +} + +void QWifiController::call(Method method) +{ + QMutexLocker locker(&m_methodsMutex); + m_methods.append(method); + methodCallRequested.wakeOne(); +} + +void QWifiController::initializeBackend() +{ + qCDebug(B2QT_WIFI) << "initializing wifi backend"; + emit backendStateChanged(QWifiManager::Initializing); + +#ifdef Q_OS_ANDROID_NO_SDK + qCDebug(B2QT_WIFI) << "initialize driver"; + if (!(is_wifi_driver_loaded() || wifi_load_driver() == 0)) { + qCWarning(B2QT_WIFI) << "failed to load a driver"; + return; + } +#else + qCDebug(B2QT_WIFI) << "run ifconfig (up)"; + QProcess ifconfig; + ifconfig.start(QStringLiteral("ifconfig"), QStringList() << QLatin1String(m_interface) << QStringLiteral("up")); + ifconfig.waitForFinished(); + if (ifconfig.exitStatus() != QProcess::NormalExit && ifconfig.exitCode() != 0) { + qCWarning(B2QT_WIFI) << "failed to bring up wifi interface!"; + return; + } +#endif + resetSupplicantSocket(); + startWifiEventThread(); + qCDebug(B2QT_WIFI) << "wifi backend started successfully"; + emit backendStateChanged(QWifiManager::Running); +} + +void QWifiController::resetSupplicantSocket() const +{ + qCDebug(B2QT_WIFI) << "reset supplicant socket"; + // close down the previous connection to supplicant if + // one exists before re-connecting. + if (q_wifi_stop_supplicant() < 0) + qCWarning(B2QT_WIFI) << "failed to stop supplicant!"; + q_wifi_close_supplicant_connection(m_interface); + qCDebug(B2QT_WIFI) << "start supplicant"; + if (q_wifi_start_supplicant() != 0) { + qCWarning(B2QT_WIFI) << "failed to start a supplicant!"; + return; + } +#ifdef Q_OS_ANDROID_NO_SDK + if (wait_for_property("init.svc.wpa_supplicant", "running", 5) < 0) { + qCWarning(B2QT_WIFI) << "timed out waiting for supplicant to start!"; + return; + } +#endif + qCDebug(B2QT_WIFI) << "connect to supplicant"; + if (q_wifi_connect_to_supplicant(m_interface) != 0) { + qCWarning(B2QT_WIFI) << "failed to connect to a supplicant!"; + return; + } +} + +void QWifiController::terminateBackend() +{ + qCDebug(B2QT_WIFI) << "terminating wifi backend"; + emit backendStateChanged(QWifiManager::Terminating); + exitWifiEventThread(); + if (q_wifi_stop_supplicant() < 0) + qCWarning(B2QT_WIFI) << "failed to stop supplicant!"; + q_wifi_close_supplicant_connection(m_interface); +#ifndef Q_OS_ANDROID_NO_SDK + qCDebug(B2QT_WIFI) << "run ifconfig (down)"; + QProcess ifconfig; + ifconfig.start(QStringLiteral("ifconfig"), QStringList() << QLatin1String(m_interface) << QStringLiteral("down")); + ifconfig.waitForFinished(); + if (ifconfig.exitStatus() != QProcess::NormalExit && ifconfig.exitCode() != 0) { + qCWarning(B2QT_WIFI) << "failed to bring down wifi interface!"; + return; + } +#endif + stopDhcp(); + qCDebug(B2QT_WIFI) << "wifi backend stopped successfully"; + emit backendStateChanged(QWifiManager::NotRunning); +} + +void QWifiController::startWifiEventThread() +{ + m_exitEventThread = false; + m_eventThread->start(); +} + +void QWifiController::exitWifiEventThread() +{ + if (m_eventThread->isRunning()) { + m_exitEventThread = true; + m_managerPrivate->call(QStringLiteral("SCAN")); + m_eventThread->wait(); + } +} + +#ifdef Q_OS_ANDROID_NO_SDK +bool QWifiController::getQConnectivityReply() +{ + bool arrived = false; + if (m_qcSocket->canReadLine()) { + arrived = true; + QByteArray received = m_qcSocket->readLine(m_qcSocket->bytesAvailable()); + if (received != "success" && received != "failed") { + qCWarning(B2QT_WIFI) << "unknown message: " << received; + received = "failed"; + } + emit dhcpRequestFinished(QLatin1String(received)); + } + return arrived; +} +#else +void QWifiController::killDhcpProcess(const QString &path) const +{ + QFile pidFile(path); + if (pidFile.exists()) { + pidFile.open(QIODevice::ReadOnly); + QByteArray pid = pidFile.readAll(); + QProcess kill; + kill.start(QStringLiteral("kill"), QStringList() << QLatin1String(pid.trimmed())); + kill.waitForFinished(); + if (kill.exitStatus() != QProcess::NormalExit && kill.exitCode() != 0) + qCWarning(B2QT_WIFI) << "killing dhcp process failed!"; + } + pidFile.close(); +} +#endif + +void QWifiController::acquireIPAddress() +{ + qCDebug(B2QT_WIFI, "acquireIPAddress"); +#ifdef Q_OS_ANDROID_NO_SDK + QByteArray request = m_interface + " connect\n"; + m_qcSocket->abort(); + m_qcSocket->connectToServer(QStringLiteral(ANDROID_SOCKET_DIR "/qconnectivity")); + bool timeout = false; + if (m_qcSocket->waitForConnected()) { + m_qcSocket->write(request, request.length()); + m_qcSocket->flush(); + do { + if (m_qcSocket->waitForReadyRead()) { + if (getQConnectivityReply()) + break; + } else { + timeout = true; + qCWarning(B2QT_WIFI) << "waiting a message from qconnectivity timed out!"; + break; + } + } while (true); + } else { + timeout = true; + qCWarning(B2QT_WIFI) << "connecting to qconnectivity server socket timed out!"; + } + if (timeout) + emit dhcpRequestFinished(QStringLiteral("failed")); +#else + QString filePath = QLatin1String("/var/run/udhcpc." + m_interface + ".pid"); + killDhcpProcess(filePath); + QStringList args; + args << QStringLiteral("-R") << QStringLiteral("-n") << QStringLiteral("-p") + << filePath << QStringLiteral("-i") << QLatin1String(m_interface); + + QProcess udhcpc; + udhcpc.start(QStringLiteral("udhcpc"), args); + udhcpc.waitForFinished(); + if (udhcpc.exitStatus() != QProcess::NormalExit && udhcpc.exitCode() != 0) + qCWarning(B2QT_WIFI) << "udhcpc process failed!"; + + QString status = QLatin1String("success"); + if (udhcpc.readAll().contains("No lease")) + status = QLatin1String("failed"); + + emit dhcpRequestFinished(status); +#endif +} + +void QWifiController::stopDhcp() const +{ + qCDebug(B2QT_WIFI, "stopDhcp"); +#ifdef Q_OS_ANDROID_NO_SDK + QByteArray request = m_interface + " disconnect\n"; + m_qcSocket->abort(); + m_qcSocket->connectToServer(QStringLiteral(ANDROID_SOCKET_DIR "/qconnectivity")); + if (m_qcSocket->waitForConnected()) { + m_qcSocket->write(request, request.length()); + m_qcSocket->flush(); + } else { + qCWarning(B2QT_WIFI) << "connecting to qconnectivity server socket timed out!"; + } +#else + QString filePath = QLatin1String("/var/run/udhcpc." + m_interface + ".pid"); + killDhcpProcess(filePath); +#endif +} + +QT_END_NAMESPACE diff --git a/src/wifi/qwificontroller_p.h b/src/wifi/qwificontroller_p.h new file mode 100644 index 0000000..230b7c8 --- /dev/null +++ b/src/wifi/qwificontroller_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFICONTROLLER_H +#define QWIFICONTROLLER_H + +#include "qwifimanager.h" + +#include <QtCore/QEvent> +#include <QtCore/QVector> +#include <QtCore/QThread> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/QLoggingCategory> + +#ifdef Q_OS_ANDROID_NO_SDK +#include <hardware_legacy/wifi.h> +#include <cutils/properties.h> +#else +#include "qwifielinux_p.h" +#endif + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(B2QT_WIFI) + +#ifdef Q_OS_ANDROID_NO_SDK +int q_wifi_start_supplicant(); +int q_wifi_stop_supplicant(); +int q_wifi_connect_to_supplicant(const char *ifname); +void q_wifi_close_supplicant_connection(const char *ifname); +int q_wifi_wait_for_event(const char *ifname, char *buf, size_t len); +int q_wifi_command(const char *ifname, const char *command, char *reply, size_t *reply_len); +int wait_for_property(const char *name, const char *desired_value, int maxwait); +#endif + +const QEvent::Type WIFI_SCAN_RESULTS = (QEvent::Type) (QEvent::User + 2001); +const QEvent::Type WIFI_CONNECTED = (QEvent::Type) (QEvent::User + 2002); +const QEvent::Type WIFI_HANDSHAKE_FAILED = (QEvent::Type) (QEvent::User + 2003); +const QEvent::Type WIFI_AUTHENTICATING = (QEvent::Type) (QEvent::User + 2004); +const QEvent::Type WIFI_DISCONNECTED = (QEvent::Type) (QEvent::User + 2005); + +class QWifiManager; +class QWifiManagerPrivate; +class QWifiEventThread; +#ifdef Q_OS_ANDROID_NO_SDK +class QLocalSocket; +#endif + +class QWifiController : public QThread +{ + Q_OBJECT +public: + enum Method { + InitializeBackend, + TerminateBackend, + AcquireIPAddress, + StopDhcp, + ExitEventLoop + }; + + explicit QWifiController(QWifiManager *manager, QWifiManagerPrivate *managerPrivate); + ~QWifiController(); + + void call(Method method); + QWifiManager *wifiManager() const { return m_manager; } + bool isWifiThreadExitRequested() const { return m_exitEventThread; } + void startWifiEventThread(); + void acquireIPAddress(); + void stopDhcp() const; + void resetSupplicantSocket() const; + +signals: + void backendStateChanged(QWifiManager::BackendState backendState); + void dhcpRequestFinished(const QString &status); + +protected: + void run(); + void initializeBackend(); + void terminateBackend(); + void exitWifiEventThread(); + void allocateOnThisThread(); +#ifdef Q_OS_ANDROID_NO_SDK + bool getQConnectivityReply(); +#else + void killDhcpProcess(const QString &path) const; +#endif + +private: + QWifiManager *m_manager; + QWifiManagerPrivate *const m_managerPrivate; + bool m_exitEventThread; + QByteArray m_interface; +#ifdef Q_OS_ANDROID_NO_SDK + QLocalSocket *m_qcSocket; +#endif + QVector<Method> m_methods; + QWifiEventThread *m_eventThread; + QMutex m_methodsMutex; + QWaitCondition methodCallRequested; +}; + +QT_END_NAMESPACE + +#endif // QWIFICONTROLLER_H diff --git a/src/wifi/qwifidevice.cpp b/src/wifi/qwifidevice.cpp new file mode 100644 index 0000000..4f100c4 --- /dev/null +++ b/src/wifi/qwifidevice.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** 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 "qwifidevice.h" + +#include <QtCore/QByteArray> +#include <QtCore/QDir> +#ifdef Q_OS_ANDROID_NO_SDK +#include <hardware_legacy/wifi.h> +#include <cutils/properties.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \qmltype WifiDevice + \inqmlmodule QtWifi + \ingroup wifi-qmltypes + \brief Represents a physical device + + Use this element to query if a device is WiFi capable before attempting + to use functionality of WifiManager. + + \qml + import QtWifi 1.0 + + GroupBox { + id: wifiOptions + title: "Wifi" + visible: false + + Component.onCompleted: { + if (WifiDevice.wifiSupported()) { + var component = Qt.createComponent("WifiGroupBox.qml") + var wifi = component.createObject(wifiOptions.contentItem) + if (wifi) + wifiOptions.visible = true + } else { + print("WiFi functionality not available on this device.") + } + } + } + \endqml +*/ + +/*! + \qmlmethod bool QWifiDevice::wifiSupported() + + Returns true if the device is WiFi capable - WiFi driver and firmware has been + successfully loaded by the system, otherwise returns false. + + \sa wifiInterfaceName +*/ + +QWifiDevice::QWifiDevice() +{ +} + +QWifiDevice::~QWifiDevice() +{ +} + +bool QWifiDevice::wifiSupported() +{ +#ifdef Q_OS_ANDROID_NO_SDK + const char *fwpath = 0; + // reload wifi firmware + fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_STA); + if (!fwpath) { + qCWarning(B2QT_WIFI) << "failed to get firmware path"; + return false; + } + if (wifi_change_fw_path((const char *)fwpath)) { + qCWarning(B2QT_WIFI) << "failed to change firmware path"; + return false; + } +#endif + QByteArray ifc = wifiInterfaceName(); + bool hasInterface = QDir().exists(QString::fromLatin1("/sys/class/net/" + ifc)); + if (!hasInterface) + qCWarning(B2QT_WIFI) << "could not find wifi interface in \"/sys/class/net/\", " + "looking for interface named: " << ifc; +#ifdef Q_OS_ANDROID_NO_SDK + if (hasInterface && wifi_load_driver() == 0) { + return true; + } else { + qCWarning(B2QT_WIFI) << "wifi driver is not available"; + return false; + } +#else + return hasInterface; +#endif +} + +/*! + \fn QByteArray QWifiDevice::wifiInterfaceName() + + Returns WiFi interface name. + + \note On Android WiFi interface name is read from "wifi.interface" system property. + On Linux WiFi interface name is read from B2QT_WIFI_INTERFACE environmental variable if + it is set. The default interface name is "wlan0" if reading the designated places does not + provide an interface name. + + /sa setWifiInterfaceName +*/ + +QByteArray QWifiDevice::wifiInterfaceName() +{ + QByteArray ifc; +#ifdef Q_OS_ANDROID_NO_SDK + char interface[PROPERTY_VALUE_MAX]; + property_get("wifi.interface", interface, NULL); + ifc = interface[0] == '\0' ? "wlan0" : interface; +#else + ifc = qEnvironmentVariableIsSet("B2QT_WIFI_INTERFACE") + ? qgetenv("B2QT_WIFI_INTERFACE") : "wlan0"; +#endif + return ifc; +} + +/*! + \fn void QWifiDevice::setWifiInterfaceName(const QByteArray &name) + + A conveniece method for settings WiFi interface name. +*/ + +void QWifiDevice::setWifiInterfaceName(const QByteArray &name) +{ +#ifdef Q_OS_ANDROID_NO_SDK + property_set("wifi.interface", name); +#else + qputenv("B2QT_WIFI_INTERFACE", name); +#endif +} + +QT_END_NAMESPACE diff --git a/src/wifi/qwifidevice.h b/src/wifi/qwifidevice.h new file mode 100644 index 0000000..80b4891 --- /dev/null +++ b/src/wifi/qwifidevice.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFIDEVICE_H +#define QWIFIDEVICE_H + +#include <QtCore/QObject> +#include <QtCore/QByteArray> +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(B2QT_WIFI) + +class Q_DECL_EXPORT QWifiDevice : public QObject +{ + Q_OBJECT +public: + explicit QWifiDevice(); + virtual ~QWifiDevice(); + + Q_INVOKABLE static bool wifiSupported(); + static QByteArray wifiInterfaceName(); + static void setWifiInterfaceName(const QByteArray &name); +}; + +QT_END_NAMESPACE + +#endif // QWIFIDEVICE_H diff --git a/src/wifi/qwifielinux.cpp b/src/wifi/qwifielinux.cpp new file mode 100644 index 0000000..0dc3a39 --- /dev/null +++ b/src/wifi/qwifielinux.cpp @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** 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 "qwifielinux_p.h" +#include "qwifidevice.h" + +#include <QtCore/QFile> +#include <QtCore/QProcess> + +#include "wpa-supplicant/wpa_ctrl.h" + +#include <poll.h> +#include <unistd.h> +#include <sys/socket.h> + +QT_BEGIN_NAMESPACE + +const char SUPP_CONFIG_FILE[] = "/etc/wpa_supplicant.conf"; +const char IFACE_DIR[] = "/var/run/wpa_supplicant/"; +const char WPA_EVENT_IGNORE[] = "CTRL-EVENT-IGNORE "; + +static struct wpa_ctrl *ctrl_conn; +static struct wpa_ctrl *monitor_conn; +// socket pair used to exit from a blocking read +static int exit_sockets[2]; +QByteArray ctrlInterface; + +int wifi_connect_on_socket_path(const char *path); +int wifi_ctrl_recv(char *reply, size_t *reply_len); +int wifi_wait_on_socket(char *buf, size_t buflen); +int wifi_send_command(const char *cmd, char *reply, size_t *reply_len); +void wifi_close_sockets(); + +int q_wifi_start_supplicant() +{ + // #### TODO - if "/etc/wpa_supplicant/driver.$IFACE" exists, read driver name from there + QByteArray ifc = QWifiDevice::wifiInterfaceName(); + QString driver(QStringLiteral("wext")); + QString pidFile = QLatin1String("/var/run/wpa_supplicant." + ifc + ".pid"); + + QStringList arg; + arg << QStringLiteral("--start") << QStringLiteral("--quiet") << QStringLiteral("--name"); + arg << QStringLiteral("wpa_supplicant") << QStringLiteral("--startas"); + arg << QStringLiteral("/usr/sbin/wpa_supplicant") << QStringLiteral("--pidfile") << pidFile; + arg << QStringLiteral("--") << QStringLiteral("-B") << QStringLiteral("-P") << pidFile; + arg << QStringLiteral("-i") << QLatin1String(ifc) << QStringLiteral("-c"); + arg << QLatin1String(SUPP_CONFIG_FILE) << QStringLiteral("-D") << driver; + + QProcess ssDaemon; + ssDaemon.start(QStringLiteral("start-stop-daemon"), arg); + ssDaemon.waitForFinished(); + if (ssDaemon.exitStatus() != QProcess::NormalExit && ssDaemon.exitCode() != 0) { + qCWarning(B2QT_WIFI) << "failed to start a supplicant process!"; + return -1; + } + + QFile configFile; + configFile.setFileName(QLatin1String(SUPP_CONFIG_FILE)); + if (configFile.exists() && configFile.open(QFile::ReadOnly)) { + ctrlInterface.clear(); + while (!configFile.atEnd()) { + QByteArray line = configFile.readLine().trimmed(); + if (line.startsWith("ctrl_interface")) { + ctrlInterface = line.mid(16); + break; + } + } + configFile.close(); + if (!ctrlInterface.isEmpty()) { + // if the interface socket exists, then wpa_supplicant was invoked successfully + if (!QFile(QLatin1String(ctrlInterface + "/" + ifc)).exists()) + return -1; + } else { + qCWarning(B2QT_WIFI) << "ctrl_interface is not set in " << SUPP_CONFIG_FILE; + return -1; + } + } else { + qCWarning(B2QT_WIFI) << "could not find/read wpa_supplicant configuration file in" << SUPP_CONFIG_FILE; + return -1; + } + // reset sockets used for exiting from hung state + exit_sockets[0] = exit_sockets[1] = -1; + return 0; +} + +int q_wifi_stop_supplicant() +{ + QByteArray ifc = QWifiDevice::wifiInterfaceName(); + QString pidFile = QLatin1String("/var/run/wpa_supplicant." + ifc + ".pid"); + QStringList arg; + arg << QStringLiteral("--stop") << QStringLiteral("--quiet") << QStringLiteral("--name"); + arg << QStringLiteral("wpa_supplicant") << QStringLiteral("--pidfile") << pidFile; + QProcess ssDaemon; + ssDaemon.start(QStringLiteral("start-stop-daemon"), arg); + ssDaemon.waitForFinished(); + if (ssDaemon.exitStatus() != QProcess::NormalExit && ssDaemon.exitCode() != 0) { + qCWarning(B2QT_WIFI) << "failed to stop a supplicant process!"; + return -1; + } + + QFile::remove(QLatin1String(ctrlInterface + "/" + ifc)); + QFile::remove(pidFile); + return 0; +} + +int q_wifi_connect_to_supplicant(const char *ifname) +{ + static char path[4096]; + snprintf(path, sizeof(path), "%s/%s", IFACE_DIR, ifname); + return wifi_connect_on_socket_path(path); +} + +int wifi_connect_on_socket_path(const char *path) +{ + // establishes the control and monitor socket connections on the interface + ctrl_conn = wpa_ctrl_open(path); + if (ctrl_conn == NULL) { + qCWarning(B2QT_WIFI, "Unable to open connection to supplicant on \"%s\": %s", + path, strerror(errno)); + return -1; + } + monitor_conn = wpa_ctrl_open(path); + if (monitor_conn == NULL) { + wpa_ctrl_close(ctrl_conn); + ctrl_conn = NULL; + return -1; + } + if (wpa_ctrl_attach(monitor_conn) != 0) { + wpa_ctrl_close(monitor_conn); + wpa_ctrl_close(ctrl_conn); + ctrl_conn = monitor_conn = NULL; + return -1; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, exit_sockets) == -1) { + wpa_ctrl_close(monitor_conn); + wpa_ctrl_close(ctrl_conn); + ctrl_conn = monitor_conn = NULL; + return -1; + } + + return 0; +} + +int q_wifi_wait_for_event(const char *ifname, char *buf, size_t buflen) +{ + Q_UNUSED(ifname); + return wifi_wait_on_socket(buf, buflen); +} + +int wifi_wait_on_socket(char *buf, size_t buflen) +{ + size_t nread = buflen - 1; + int result; + char *match, *match2; + + if (monitor_conn == NULL) { + return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - connection closed"); + } + + result = wifi_ctrl_recv(buf, &nread); + + /* Terminate reception on exit socket */ + if (result == -2) { + return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - connection closed"); + } + + if (result < 0) { + qCWarning(B2QT_WIFI, "wifi_ctrl_recv failed: %s", strerror(errno)); + return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - recv error"); + } + buf[nread] = '\0'; + /* Check for EOF on the socket */ + if (result == 0 && nread == 0) { + /* Fabricate an event to pass up */ + qCWarning(B2QT_WIFI, "Received EOF on supplicant socket"); + return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - signal 0 received"); + } + /* + * Events strings are in the format + * + * IFNAME=iface <N>CTRL-EVENT-XXX + * or + * <N>CTRL-EVENT-XXX + * + * where N is the message level in numerical form (0=VERBOSE, 1=DEBUG, + * etc.) and XXX is the event name. The level information is not useful + * to us, so strip it off. + */ + + if (strncmp(buf, "IFNAME=", (sizeof("IFNAME=") - 1)) == 0) { + match = strchr(buf, ' '); + if (match != NULL) { + if (match[1] == '<') { + match2 = strchr(match + 2, '>'); + if (match2 != NULL) { + nread -= (match2 - match); + memmove(match + 1, match2 + 1, nread - (match - buf) + 1); + } + } + } else { + return snprintf(buf, buflen, "%s", WPA_EVENT_IGNORE); + } + } else if (buf[0] == '<') { + match = strchr(buf, '>'); + if (match != NULL) { + nread -= (match + 1 - buf); + memmove(buf, match + 1, nread + 1); + //qCWarning(B2QT_WIFI, "supplicant generated event without interface - %s", buf); + } + } else { + /* let the event go as is! */ + qCWarning(B2QT_WIFI, "supplicant generated event without interface and without message level - %s", buf); + } + + return nread; +} + +int wifi_ctrl_recv(char *reply, size_t *reply_len) +{ + int res = 0; + int ctrlfd = wpa_ctrl_get_fd(monitor_conn); + struct pollfd rfds[2]; + + memset(rfds, 0, 2 * sizeof(struct pollfd)); + rfds[0].fd = ctrlfd; + rfds[0].events |= POLLIN; + rfds[1].fd = exit_sockets[1]; + rfds[1].events |= POLLIN; + res = TEMP_FAILURE_RETRY(poll(rfds, 2, -1)); + if (res < 0) { + qCWarning(B2QT_WIFI, "Error poll = %d", res); + return res; + } + if (rfds[0].revents & POLLIN) { + return wpa_ctrl_recv(monitor_conn, reply, reply_len); + } + + /* it is not rfds[0], then it must be rfts[1] (i.e. the exit socket) + * or we timed out. In either case, this call has failed .. + */ + return -2; +} + +int wifi_send_command(const char *cmd, char *reply, size_t *reply_len) +{ + int ret; + if (ctrl_conn == NULL) { + qCWarning(B2QT_WIFI, "Not connected to wpa_supplicant - \"%s\" command dropped.", cmd); + return -1; + } + ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), reply, reply_len, NULL); + if (ret == -2) { + qCWarning(B2QT_WIFI, "'%s' command timed out.", cmd); + /* unblocks the monitor receive socket for termination */ + TEMP_FAILURE_RETRY(write(exit_sockets[0], "T", 1)); + return -2; + } else if (ret < 0 || strncmp(reply, "FAIL", 4) == 0) { + return -1; + } + if (strncmp(cmd, "PING", 4) == 0) { + reply[*reply_len] = '\0'; + } + return 0; +} + +int q_wifi_command(const char *ifname, const char *command, char *reply, size_t *reply_len) +{ + Q_UNUSED(ifname); + return wifi_send_command(command, reply, reply_len); +} + +void q_wifi_close_supplicant_connection(const char *ifname) +{ + Q_UNUSED(ifname) + wifi_close_sockets(); +} + +void wifi_close_sockets() +{ + if (ctrl_conn != NULL) { + wpa_ctrl_close(ctrl_conn); + ctrl_conn = NULL; + } + + if (monitor_conn != NULL) { + wpa_ctrl_close(monitor_conn); + monitor_conn = NULL; + } + + if (exit_sockets[0] >= 0) { + close(exit_sockets[0]); + exit_sockets[0] = -1; + } + + if (exit_sockets[1] >= 0) { + close(exit_sockets[1]); + exit_sockets[1] = -1; + } +} + +QT_END_NAMESPACE diff --git a/src/wifi/qwifielinux_p.h b/src/wifi/qwifielinux_p.h new file mode 100644 index 0000000..bef90c3 --- /dev/null +++ b/src/wifi/qwifielinux_p.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef LOCAL_WIFI_H +#define LOCAL_WIFI_H + +#include <QtCore/QLoggingCategory> + +#include <string.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(B2QT_WIFI) + +// This API mirrors Android's Wi-Fi libraries interface [1] and parts of implementation. +// [1] http://androidxref.com/4.4.2_r2/xref/hardware/libhardware_legacy/include/hardware_legacy/wifi.h + +int q_wifi_command(const char *ifname, const char *command, char *reply, size_t *reply_len); +int q_wifi_wait_for_event(const char *ifname, char *buf, size_t buflen); +int q_wifi_connect_to_supplicant(const char *ifname); +void q_wifi_close_supplicant_connection(const char *ifname); +int q_wifi_start_supplicant(); +int q_wifi_stop_supplicant(); + +QT_END_NAMESPACE + +#endif // LOCAL_WIFI_H 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 diff --git a/src/wifi/qwifimanager.h b/src/wifi/qwifimanager.h new file mode 100644 index 0000000..cd55f4d --- /dev/null +++ b/src/wifi/qwifimanager.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFIMANAGER_H +#define QWIFIMANAGER_H + +#include "qwificonfiguration.h" + +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtCore/QByteArray> +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(B2QT_WIFI) + +class QWifiManagerPrivate; +class QAbstractListModel; +class QWifiController; +class QWifiNetworkListModel; + +class Q_DECL_EXPORT QWifiManager : public QObject +{ + Q_OBJECT + Q_ENUMS(NetworkState) + Q_ENUMS(BackendState) + Q_PROPERTY(NetworkState networkState READ networkState NOTIFY networkStateChanged) + Q_PROPERTY(BackendState backendState READ backendState NOTIFY backendStateChanged) + Q_PROPERTY(bool scanning READ scanning WRITE setScanning NOTIFY scanningChanged) + Q_PROPERTY(QString currentSSID READ currentSSID NOTIFY currentSSIDChanged) + Q_PROPERTY(QString lastError READ lastError NOTIFY lastErrorChanged) + Q_PROPERTY(QAbstractListModel *networks READ networks CONSTANT) +public: + enum NetworkState { + Disconnected, + Authenticating, + HandshakeFailed, + ObtainingIPAddress, + DhcpRequestFailed, + Connected + }; + + enum BackendState { + Initializing, + Running, + Terminating, + NotRunning + }; + + enum Roles { + SSIDRole = Qt::UserRole + 1, + BSSIDRole = Qt::UserRole + 2, + SignalRole = Qt::UserRole + 3, + WPARole = Qt::UserRole + 4, + WPA2Role = Qt::UserRole + 5, + WEPRole = Qt::UserRole + 6, + WPSRole = Qt::UserRole + 7 + }; + + static QWifiManager *instance(); + virtual ~QWifiManager(); + + QAbstractListModel *networks() const; + QString currentSSID() const; + bool scanning() const; + void setScanning(bool scanning); + NetworkState networkState() const; + BackendState backendState() const; + QString lastError() const; + +public slots: + void start(); + void stop(); + bool connect(QWifiConfiguration *config); + void disconnect(); + +signals: + void scanningChanged(bool scanning); + void networkStateChanged(NetworkState networkState); + void backendStateChanged(BackendState backendState); + void currentSSIDChanged(const QString ¤tSSID); + void lastErrorChanged(const QString &error); + +protected: + bool event(QEvent *event); + +private slots: + void handleBackendStateChanged(BackendState backendState); + void handleDhcpRequestFinished(const QString &status); + +private: + QWifiManager(); + static QWifiManager* m_instance; + friend class QWifiController; + Q_DISABLE_COPY(QWifiManager) + Q_DECLARE_PRIVATE(QWifiManager) + QWifiManagerPrivate *const d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWIFIMANAGER_H diff --git a/src/wifi/qwifimanager_p.h b/src/wifi/qwifimanager_p.h new file mode 100644 index 0000000..ef8a0da --- /dev/null +++ b/src/wifi/qwifimanager_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFIMANAGER_P_H +#define QWIFIMANAGER_P_H + +#include "qwifimanager.h" + +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +class QWifiManagerPrivate +{ + Q_DECLARE_PUBLIC(QWifiManager) +public: + QWifiManagerPrivate(QWifiManager *manager); + virtual ~QWifiManagerPrivate(); + + // methods + QString getConnectedNetwork(); + void setCurrentSSID(); + void emitCurrentSSIDChanged(); + void handleConnected(); + void handleDisconneced(); + + void updateNetworkState(QWifiManager::NetworkState networkState); + void updateBackendState(QWifiManager::BackendState backendState); + void updateWifiState(); + + QString call(const QString &command); + bool checkedCall(const QString &command); + void updateLastError(const QString &error); + + // member variables + QWifiManager *const q_ptr; + QWifiController *m_wifiController; + QWifiNetworkListModel *m_networkListModel; + + int m_scanTimer; + bool m_scanning; + QByteArray m_interface; + QWifiManager::BackendState m_backendState; + QWifiManager::NetworkState m_networkState; + bool m_setCurrentSSID; + QString m_currentSSID; + QString m_previousSSID; + QString m_lastError; +}; + +QT_END_NAMESPACE + +#endif // QWIFIMANAGER_P_H diff --git a/src/wifi/qwifinetwork.cpp b/src/wifi/qwifinetwork.cpp new file mode 100644 index 0000000..937cbfc --- /dev/null +++ b/src/wifi/qwifinetwork.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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 "qwifinetwork_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype WifiNetwork + \inqmlmodule QtWifi + \ingroup wifi-qmltypes + \brief Represents a single WiFi network access point. + + WifiNetwork provides a various information about WiFi network, like network + name, siganl strength and supported security protocols. Instances of WifiNetwork cannot + be created directly from the QML system, see WifiManager::networks. +*/ + +/*! + \qmlproperty string WifiNetwork::bssid + \readonly + + This property holds basic service set identification of a network, used to uniquely + identify BSS. + +*/ + +/*! + \qmlproperty string WifiNetwork::ssid + \readonly + + This property holds a network name. The SSID is the informal (human) name of BSS. +*/ + +/*! + \qmlproperty int WifiNetwork::signalStrength + \readonly + + This property holds the current strength of a WiFi signal, measured in dBm. New readings are + taken every 5 seconds. + + \sa signalStrengthChanged +*/ + +/*! + \qmlproperty bool WifiNetwork::supportsWPA + \readonly + + This property holds whether network access point supports WPA security protocol. +*/ + +/*! + \qmlproperty bool WifiNetwork::supportsWPA2 + \readonly + + This property holds whether network access point supports WPA2 security protocol. +*/ + +/*! + \qmlproperty bool WifiNetwork::supportsWEP + \readonly + + This property holds whether network access point supports WEP security protocol. +*/ + +/*! + \qmlproperty bool WifiNetwork::supportsWPS + \readonly + + This property holds whether network access point supports WPS security protocol. +*/ + +/*! + \qmlsignal void WifiNetwork::signalStrengthChanged(int strength) + + This signal is emitted whenever signal strength has changed comparing the the + previous reading, the new signal's strength is \a strength. + +*/ + +QWifiNetwork::QWifiNetwork(QObject *parent) + : QObject(parent) + , m_isOutOfRange(false) +{ +} + +QWifiNetwork::~QWifiNetwork() +{ +} + +void QWifiNetwork::setSsid(const QString &ssid) +{ + m_ssid = ssid; +} + +void QWifiNetwork::setSignalStrength(int strength) +{ + m_signalStrength = strength; +} + +void QWifiNetwork::setOutOfRange(bool outOfRange) +{ + m_isOutOfRange = outOfRange; +} + +QT_END_NAMESPACE diff --git a/src/wifi/qwifinetwork_p.h b/src/wifi/qwifinetwork_p.h new file mode 100644 index 0000000..db65e2d --- /dev/null +++ b/src/wifi/qwifinetwork_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFINETWORK_H +#define QWIFINETWORK_H + +#include <QtCore/QObject> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +class QWifiNetwork : public QObject +{ + Q_OBJECT +public: + explicit QWifiNetwork(QObject *parent = 0); + virtual ~QWifiNetwork(); + + void setBssid(const QString &bssid) { m_bssid = bssid; } + QString bssid() const { return m_bssid; } + void setSsid(const QString &ssid); + QString ssid() const { return m_ssid; } + + void setSignalStrength(int strength); + int signalStrength() const { return m_signalStrength; } + + void setOutOfRange(bool isOutOfRange); + // this could be exposed in QWifiManager::Roles, + // if there is a use case for it, this would require keeping + // the out of range networks in data model, currently they are + // removed from the model. + bool isOutOfRange() const { return m_isOutOfRange; } + + void setFlags(const QString &flags) { m_flags = flags; } + QString flags() const { return m_flags; } + bool supportsWPA2() const { return m_flags.contains(QStringLiteral("WPA2")); } + bool supportsWPA() const { return m_flags.contains(QStringLiteral("WPA")); } + bool supportsWEP() const { return m_flags.contains(QStringLiteral("WEP")); } + bool supportsWPS() const { return m_flags.contains(QStringLiteral("WPS")); } + +private: + QString m_bssid; + QString m_ssid; + int m_signalStrength; + QString m_flags; + bool m_isOutOfRange; +}; + +QT_END_NAMESPACE + +#endif // QWIFINETWORK_H diff --git a/src/wifi/qwifinetworklistmodel.cpp b/src/wifi/qwifinetworklistmodel.cpp new file mode 100644 index 0000000..7a5de2a --- /dev/null +++ b/src/wifi/qwifinetworklistmodel.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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 "qwifinetworklistmodel_p.h" +#include "qwifinetwork_p.h" + +#include "qwifimanager.h" + +#include <QtCore/QSet> +#include <QtCore/QString> +#include <QtCore/QByteArray> + +QT_BEGIN_NAMESPACE + +QWifiNetworkListModel::QWifiNetworkListModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +QWifiNetworkListModel::~QWifiNetworkListModel() +{ + qDeleteAll(m_networks); + qDeleteAll(m_outOfRangeNetworks); + m_networks.clear(); + m_outOfRangeNetworks.clear(); +} + +QHash<int, QByteArray> QWifiNetworkListModel::roleNames() const +{ + QHash<int, QByteArray> names; + names.insert(QWifiManager::SSIDRole, "ssid"); + names.insert(QWifiManager::BSSIDRole, "bssid"); + names.insert(QWifiManager::SignalRole, "signalStrength"); + names.insert(QWifiManager::WPARole, "supportsWPA"); + names.insert(QWifiManager::WPA2Role, "supportsWPA2"); + names.insert(QWifiManager::WEPRole, "supportsWEP"); + names.insert(QWifiManager::WPSRole, "supportsWPS"); + return names; +} + +QVariant QWifiNetworkListModel::data(const QModelIndex &index, int role) const +{ + QWifiNetwork *n = m_networks.at(index.row()); + + switch (role) { + case QWifiManager::SSIDRole: + return n->ssid(); + break; + case QWifiManager::BSSIDRole: + return n->bssid(); + break; + case QWifiManager::SignalRole: + return n->signalStrength(); + break; + case QWifiManager::WPARole: + return n->supportsWPA(); + break; + case QWifiManager::WPA2Role: + return n->supportsWPA2(); + break; + case QWifiManager::WEPRole: + return n->supportsWEP(); + break; + case QWifiManager::WPSRole: + return n->supportsWPS(); + break; + default: + break; + } + + return QVariant(); +} + +QWifiNetwork *QWifiNetworkListModel::networkForSSID(const QString &ssid) +{ + int pos = 0; // unused + return networkForSSID(ssid, &pos); +} + +QWifiNetwork *QWifiNetworkListModel::networkForSSID(const QString &ssid, int *pos) +{ + for (int i = 0; i < m_networks.size(); ++i) { + if (m_networks.at(i)->ssid() == ssid) { + if (pos) + *pos = i; + return m_networks.at(i); + } + } + return 0; +} + +QWifiNetwork *QWifiNetworkListModel::outOfRangeListContains(const QString &ssid) +{ + for (int i = 0; i < m_outOfRangeNetworks.length(); ++i) + if (m_outOfRangeNetworks.at(i)->ssid() == ssid) + return m_outOfRangeNetworks.takeAt(i); + return 0; +} + +void QWifiNetworkListModel::parseScanResults(const QString &results) +{ + QStringList lines = results.split('\n'); + QSet<QString> sensibleNetworks; + + for (int i = 1; i < lines.size(); ++i) { + QStringList info = lines.at(i).split('\t'); + if (info.size() < 5 || info.at(4).isEmpty() || info.at(0).isEmpty()) + continue; + int pos = 0; + + QString ssid = info.at(4); + sensibleNetworks.insert(ssid); + QWifiNetwork *knownNetwork = networkForSSID(ssid, &pos); + if (!knownNetwork) + knownNetwork = outOfRangeListContains(ssid); + // signal strength is in dBm. Deprecated, but still widely used "wext" + // wifi driver reports positive values for signal strength, we workaround that. + int signalStrength = qAbs(info.at(2).trimmed().toInt()) * -1; + if (!knownNetwork) { + QWifiNetwork *network = new QWifiNetwork(); + network->setOutOfRange(false); + network->setBssid(info.at(0)); + network->setFlags(info.at(3)); + network->setSignalStrength(signalStrength); + network->setSsid(ssid); + beginInsertRows(QModelIndex(), m_networks.size(), m_networks.size()); + m_networks << network; + endInsertRows(); + } else { + if (knownNetwork->isOutOfRange()) { + // known network has come back into a range + knownNetwork->setOutOfRange(false); + beginInsertRows(QModelIndex(), m_networks.size(), m_networks.size()); + m_networks << knownNetwork; + endInsertRows(); + pos = m_networks.length() - 1; + } + // ssids are the same, compare bssids.. + if (knownNetwork->bssid() == info.at(0)) { + // same access point, simply update the signal strength + knownNetwork->setSignalStrength(signalStrength); + knownNetwork->setOutOfRange(false); + dataChanged(createIndex(pos, 0), createIndex(pos, 0)); + } else if (knownNetwork->signalStrength() < signalStrength) { + // replace with a stronger access point within the same network + m_networks.at(pos)->setOutOfRange(false); + m_networks.at(pos)->setBssid(info.at(0)); + m_networks.at(pos)->setFlags(info.at(3)); + m_networks.at(pos)->setSignalStrength(signalStrength); + m_networks.at(pos)->setSsid(ssid); + dataChanged(createIndex(pos, 0), createIndex(pos, 0)); + } + } + } + // remove out-of-range networks from the data model + for (int i = 0; i < m_networks.size();) { + if (!sensibleNetworks.contains(m_networks.at(i)->ssid())) { + beginRemoveRows(QModelIndex(), i, i); + QWifiNetwork *n = m_networks.takeAt(i); + n->setOutOfRange(true); + m_outOfRangeNetworks.append(n); + endRemoveRows(); + } else { + ++i; + } + } +} + +QT_END_NAMESPACE diff --git a/src/wifi/qwifinetworklistmodel_p.h b/src/wifi/qwifinetworklistmodel_p.h new file mode 100644 index 0000000..25a2b31 --- /dev/null +++ b/src/wifi/qwifinetworklistmodel_p.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ +#ifndef QWIFINETWORKLISTMODEL_H +#define QWIFINETWORKLISTMODEL_H + +#include <QtCore/QAbstractListModel> +#include <QtCore/QVariant> +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(B2QT_WIFI) + +class QWifiNetwork; + +class QWifiNetworkListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit QWifiNetworkListModel(QObject *parent = 0); + virtual ~QWifiNetworkListModel(); + + int rowCount(const QModelIndex &) const { return m_networks.size(); } + QVariant data(const QModelIndex &index, int role) const; + QHash<int,QByteArray> roleNames() const; + + void parseScanResults(const QString &data); + QWifiNetwork *networkForSSID(const QString &ssid); + QWifiNetwork *networkForSSID(const QString &ssid, int *pos); + QWifiNetwork *outOfRangeListContains(const QString &ssid); + +private: + QList<QWifiNetwork *> m_networks; + QList<QWifiNetwork *> m_outOfRangeNetworks; +}; + +QT_END_NAMESPACE + +#endif // QWIFINETWORKLISTMODEL_H diff --git a/src/wifi/wifi.pro b/src/wifi/wifi.pro new file mode 100644 index 0000000..e558081 --- /dev/null +++ b/src/wifi/wifi.pro @@ -0,0 +1,43 @@ +load(qt_build_config) + +TARGET = B2QtWifi +VERSION = 1.0 +CONFIG += dll warn_on + +QT += core network + +MODULE = b2qtwifi +load(qt_module) + +HEADERS += \ + $$PWD/qwifimanager.h \ + $$PWD/qwifimanager_p.h \ + $$PWD/qwifinetwork_p.h \ + $$PWD/qwifinetworklistmodel_p.h \ + $$PWD/qwificontroller_p.h \ + $$PWD/qwifidevice.h \ + $$PWD/qwificonfiguration.h + +SOURCES += \ + $$PWD/qwifimanager.cpp \ + $$PWD/qwifinetwork.cpp \ + $$PWD/qwifinetworklistmodel.cpp \ + $$PWD/qwificontroller.cpp \ + $$PWD/qwifidevice.cpp \ + $$PWD/qwificonfiguration.cpp + +android: { + LIBS += -lhardware_legacy -lcutils +} else { + DEFINES += \ + CONFIG_CTRL_IFACE \ + CONFIG_CTRL_IFACE_UNIX + + HEADERS += \ + $$PWD/qwifielinux_p.h + + SOURCES += \ + $$PWD/qwifielinux.cpp \ + $$[QT_SYSROOT]/usr/include/wpa-supplicant/wpa_ctrl.c \ + $$[QT_SYSROOT]/usr/include/wpa-supplicant/os_unix.c +} |