From 31f6f73a691ff49fc3aeba062f8561e6c981baff Mon Sep 17 00:00:00 2001 From: Gatis Paeglis Date: Fri, 28 Mar 2014 13:32:05 +0100 Subject: Port QtWifi to eLinux Current limitations: - Wifi adapter must be connected before staring the device (and ethernet cable should not be connected at the same time). "allow-hotplug wlan0" in /etc/network/interfaces doesn't seem to help for detecting wifi adapter on-the-fly. Change-Id: I8be407e9e042fc86136efd3e220680fb2ce64bd6 Reviewed-by: Eirik Aavitsland --- src/imports/wifi/pluginmain.cpp | 24 +-- src/imports/wifi/qwifi_elinux.cpp | 264 +++++++++++++++++++++++++++++ src/imports/wifi/qwifi_elinux.h | 34 ++++ src/imports/wifi/qwifimanager.cpp | 75 +++++++- src/imports/wifi/qwifimanager.h | 30 +++- src/imports/wifi/qwifinetworklistmodel.cpp | 13 +- src/imports/wifi/wifi.pro | 23 ++- 7 files changed, 433 insertions(+), 30 deletions(-) create mode 100644 src/imports/wifi/qwifi_elinux.cpp create mode 100644 src/imports/wifi/qwifi_elinux.h (limited to 'src/imports/wifi') diff --git a/src/imports/wifi/pluginmain.cpp b/src/imports/wifi/pluginmain.cpp index fdf07e9..6dc6cc9 100644 --- a/src/imports/wifi/pluginmain.cpp +++ b/src/imports/wifi/pluginmain.cpp @@ -19,12 +19,14 @@ #include "qwifimanager.h" #include +#include #include #include #include +#ifdef Q_OS_ANDROID #include - +#endif /*! \qmltype Interface \inqmlmodule Qt.labs.wifi @@ -65,18 +67,16 @@ public: Q_INVOKABLE bool wifiSupported() const { bool supported = false; - if (wifi_load_driver() == 0 && wifi_start_supplicant(0) == 0) { - char interface[PROPERTY_VALUE_MAX]; - property_get("wifi.interface", interface, NULL); - // standard linux kernel path - QByteArray path; - path.append("/sys/class/net/").append(interface); - supported = QDir().exists(path.constData()); - if (!supported) - qWarning() << "QWifiGlobal: could not find wifi interface in " << path; - } else { +#ifdef Q_OS_ANDROID + if (wifi_load_driver() == 0 && wifi_start_supplicant(0) == 0) + supported = true; + else qWarning() << "QWifiGlobal: wifi driver is not available"; - } +#else + supported = QDir().exists(QStringLiteral("/sys/class/net/wlan0")); + if (!supported) + qWarning() << "QWifiGlobal: could not find wifi interface in /sys/class/net/"; +#endif return supported; } }; diff --git a/src/imports/wifi/qwifi_elinux.cpp b/src/imports/wifi/qwifi_elinux.cpp new file mode 100644 index 0000000..551d887 --- /dev/null +++ b/src/imports/wifi/qwifi_elinux.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** 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://qt.digia.com/ +** +****************************************************************************/ +#include "qwifi_elinux.h" + +#include + +#include "wpa-supplicant/wpa_ctrl.h" + +#include +#include +#include + +static const char SUPP_CONFIG_FILE[] = "/etc/wpa_supplicant.conf"; +static const char IFACE_DIR[] = "/var/run/wpa_supplicant/"; +static const char WIFI[] = "wlan0"; + +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]; + +static const char IFNAME[] = "IFNAME="; +#define IFNAMELEN (sizeof(IFNAME) - 1) +static const char WPA_EVENT_IGNORE[] = "CTRL-EVENT-IGNORE "; + +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() +{ + // NOTE: supplicant started when bringing up the wifi interface in + // QWifiManager::connectToBackend() by: + // QProcess::execute(QStringLiteral("ifup") + + /* Clear out any stale socket files that might be left over. */ + //wpa_ctrl_cleanup(); + /* Reset sockets used for exiting from hung state */ + exit_sockets[0] = exit_sockets[1] = -1; + return 0; +} + +int q_wifi_stop_supplicant() +{ + // NOTE: supplicant stopped when bringing down the wifi + // interface in QWifiManager::disconnectFromBackend() by: + // QProcess::execute(QStringLiteral("ifdown") + return 0; +} + +int q_wifi_connect_to_supplicant(const char *ifname) +{ + Q_UNUSED(ifname); + static char path[4096]; + snprintf(path, sizeof(path), "%s/%s", IFACE_DIR, WIFI); + 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) { + qWarning("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) { + qWarning("wifi_ctrl_recv failed: %s\n", 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 */ + qWarning("Received EOF on supplicant socket\n"); + return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - signal 0 received"); + } + /* + * Events strings are in the format + * + * IFNAME=iface CTRL-EVENT-XXX + * or + * 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, IFNAMELEN) == 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); + //qWarning("supplicant generated event without interface - %s\n", buf); + } + } else { + /* let the event go as is! */ + qWarning("supplicant generated event without interface and without message level - %s\n", 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) { + qWarning("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) { + qWarning("Not connected to wpa_supplicant - \"%s\" command dropped.\n", cmd); + return -1; + } + ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), reply, reply_len, NULL); + if (ret == -2) { + qWarning("'%s' command timed out.\n", 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; + } +} diff --git a/src/imports/wifi/qwifi_elinux.h b/src/imports/wifi/qwifi_elinux.h new file mode 100644 index 0000000..92a04f8 --- /dev/null +++ b/src/imports/wifi/qwifi_elinux.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** 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://qt.digia.com/ +** +****************************************************************************/ +#ifndef LOCAL_WIFI_H +#define LOCAL_WIFI_H + +#include + +// This API mirrors Android's Wi-Fi libraries interface [1] and implementation, excluding Android OS specific parts. +// [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(); + +#endif // LOCAL_WIFI_H diff --git a/src/imports/wifi/qwifimanager.cpp b/src/imports/wifi/qwifimanager.cpp index 7615f70..6e5e245 100644 --- a/src/imports/wifi/qwifimanager.cpp +++ b/src/imports/wifi/qwifimanager.cpp @@ -19,10 +19,13 @@ #include "qwifimanager.h" #include - +#ifdef Q_OS_ANDROID #include #include #include +#else +#include +#endif static const char SUPPLICANT_SVC[] = "init.svc.wpa_supplicant"; static const char WIFI_INTERFACE[] = "wifi.interface"; @@ -34,6 +37,7 @@ 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); +#ifdef Q_OS_ANDROID /* * Work around API differences between Android versions */ @@ -125,6 +129,7 @@ static int wait_for_property(const char *name, const char *desired_value, int ma } return -1; /* failure */ } +#endif class QWifiManagerEvent : public QEvent { @@ -410,17 +415,25 @@ QWifiManager::QWifiManager() , m_eventThread(0) , m_scanTimer(0) , m_scanning(false) +#ifdef Q_OS_ANDROID , m_daemonClientSocket(0) +#endif , m_exitingEventThread(false) , m_startingUp(true) , m_network(0) { +#ifdef Q_OS_ANDROID char interface[PROPERTY_VALUE_MAX]; property_get(WIFI_INTERFACE, interface, NULL); m_interface = interface; - if (QT_WIFI_DEBUG) qDebug("QWifiManager: using wifi interface: %s", m_interface.constData()); +#else + m_interface = "wlan0"; // use envvar for the interface name? + m_dhcpRunner = new ProcessRunner(m_interface); + QObject::connect(m_dhcpRunner, &ProcessRunner::processFinished, this, &QWifiManager::handleDhcpFinished); +#endif + qDebug("QWifiManager: using wifi interface: %s", m_interface.constData()); m_eventThread = new QWifiManagerEventThread(this, m_interface); - +#ifdef Q_OS_ANDROID m_daemonClientSocket = new QLocalSocket; int qconnFd = socket_local_client("qconnectivity", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); if (qconnFd != -1) { @@ -444,15 +457,22 @@ QWifiManager::QWifiManager() disconnectFromBackend(); } } +#else + m_backendReady = false; + emit backendReadyChanged(); +#endif } QWifiManager::~QWifiManager() { exitEventThread(); delete m_eventThread; +#ifdef Q_OS_ANDROID delete m_daemonClientSocket; +#endif } +#ifdef Q_OS_ANDROID void QWifiManager::handleDhcpReply() { if (m_daemonClientSocket->canReadLine()) { @@ -487,6 +507,14 @@ void QWifiManager::connectedToDaemon() m_daemonClientSocket->write(m_request.constData(), m_request.length()); m_daemonClientSocket->flush(); } +#endif + +void QWifiManager::handleDhcpFinished() +{ + // ### TODO - could be that dhcp request fails, how to determine? + updateNetworkState(Connected); + call("SAVE_CONFIG"); +} void QWifiManager::start() { @@ -506,22 +534,31 @@ void QWifiManager::stop() void QWifiManager::connectToBackend() { + // ### TODO: maybe it makes sense to move this functions in non-gui thread +#ifdef Q_OS_ANDROID if (!(is_wifi_driver_loaded() || wifi_load_driver() == 0)) { qWarning("QWifiManager: failed to load a driver"); return; } +#else + QProcess::execute(QStringLiteral("ifup"), QStringList() << m_interface.constData()); +#endif if (q_wifi_start_supplicant() != 0) { qWarning("QWifiManager: failed to start a supplicant"); return; } +#ifdef Q_OS_ANDROID if (wait_for_property(SUPPLICANT_SVC, "running", 5) < 0) { qWarning("QWifiManager: Timed out waiting for supplicant to start"); return; } +#endif if (q_wifi_connect_to_supplicant(m_interface.constData()) == 0) { m_backendReady = true; emit backendReadyChanged(); +#ifdef Q_OS_ANDROID property_set(QT_WIFI_BACKEND, "running"); +#endif } else { qWarning("QWifiManager: failed to connect to a supplicant"); return; @@ -538,7 +575,12 @@ void QWifiManager::disconnectFromBackend() if (q_wifi_stop_supplicant() < 0) qWarning("QWifiManager: failed to stop supplicant"); q_wifi_close_supplicant_connection(m_interface.constData()); + setScanning(false); +#ifdef Q_OS_ANDROID property_set(QT_WIFI_BACKEND, "stopped"); +#else + QProcess::execute(QStringLiteral("ifdown"), QStringList() << m_interface.constData()); +#endif m_backendReady = false; emit backendReadyChanged(); } @@ -633,7 +675,6 @@ void QWifiManager::connect(QWifiNetwork *network, const QString &passphrase) return; } updateNetworkState(Authenticating); - call("DISABLE_NETWORK all"); if (!m_connectedSSID.isEmpty()) { m_connectedSSID.clear(); @@ -695,13 +736,35 @@ void QWifiManager::connect(QWifiNetwork *network, const QString &passphrase) void QWifiManager::disconnect() { call("DISCONNECT"); +#ifdef Q_OS_ANDROID QByteArray req = m_interface; sendDhcpRequest(req.append(" disconnect")); +#endif m_connectedSSID.clear(); updateNetworkState(Disconnected); emit connectedSSIDChanged(m_connectedSSID); } +void ProcessRunner::run() +{ + // kill existing udhcpc instance + QString filePath = QString("/var/run/udhcpc.").append(m_ifc).append(".pid"); + QFile pidFile(filePath); + if (pidFile.open(QIODevice::ReadOnly)) { + QByteArray pid = pidFile.readAll(); + pidFile.close(); + QProcess::execute(QStringLiteral("kill"), QStringList() << pid.trimmed().constData()); + } else { + qWarning() << "QWifiManager - Failed to read" << filePath; + } + QStringList args; + args << QStringLiteral("-R") << QStringLiteral("-n") << QStringLiteral("-p") + << filePath << QStringLiteral("-i") << m_ifc; + // start DHCP client + QProcess::execute(QStringLiteral("udhcpc"), args); + emit processFinished(); +} + void QWifiManager::handleConnected() { QList lists = call("LIST_NETWORKS").split('\n'); @@ -732,6 +795,10 @@ void QWifiManager::handleConnected() } updateNetworkState(ObtainingIPAddress); +#ifdef Q_OS_ANDROID QByteArray req = m_interface; sendDhcpRequest(req.append(" connect")); +#else + m_dhcpRunner->start(); +#endif } diff --git a/src/imports/wifi/qwifimanager.h b/src/imports/wifi/qwifimanager.h index 162190e..3af123f 100644 --- a/src/imports/wifi/qwifimanager.h +++ b/src/imports/wifi/qwifimanager.h @@ -20,15 +20,32 @@ #define QWIFIMANAGER_H #include +#include #include -#include +#ifdef Q_OS_ANDROID +#include #include +#endif #include "qwifinetworklistmodel.h" class QWifiManagerEventThread; +class ProcessRunner : public QThread +{ + Q_OBJECT +public: + ProcessRunner(const QByteArray &ifc) : m_ifc(ifc) {} + void run(); + +signals: + void processFinished(); + +private: + QByteArray m_ifc; +}; + class QWifiManager : public QObject { Q_OBJECT @@ -74,21 +91,26 @@ signals: protected: bool event(QEvent *); - void sendDhcpRequest(const QByteArray &request); void handleConnected(); void connectToBackend(); void disconnectFromBackend(); void exitEventThread(); + QByteArray call(const char *command) const; bool checkedCall(const char *command) const; void updateNetworkState(NetworkState state); protected slots: +#if defined(FORCE_MOC) + void sendDhcpRequest(const QByteArray &request); void connectedToDaemon(); void handleDhcpReply(); +#endif + void handleDhcpFinished(); private: friend class QWifiManagerEventThread; + friend class ProcessRunner; QString m_connectedSSID; QWifiNetworkListModel m_networkListModel; @@ -100,7 +122,11 @@ private: QByteArray m_interface; NetworkState m_state; +#ifdef Q_OS_ANDROID QLocalSocket *m_daemonClientSocket; +#else + ProcessRunner *m_dhcpRunner; +#endif QByteArray m_request; bool m_exitingEventThread; bool m_startingUp; diff --git a/src/imports/wifi/qwifinetworklistmodel.cpp b/src/imports/wifi/qwifinetworklistmodel.cpp index 4fbf25f..f95af36 100644 --- a/src/imports/wifi/qwifinetworklistmodel.cpp +++ b/src/imports/wifi/qwifinetworklistmodel.cpp @@ -88,14 +88,15 @@ void QWifiNetworkListModel::parseScanResults(const QByteArray &results) QWifiNetwork *knownNetwork = networkForSSID(info.at(4), &pos); if (!knownNetwork) knownNetwork = outOfRangeListContains(info.at(4)); - + // 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)); - // signal strength is in dBm - network->setSignalStrength(info.at(2).toInt()); + network->setSignalStrength(signalStrength); network->setSsid(info.at(4)); beginInsertRows(QModelIndex(), m_networks.size(), m_networks.size()); m_networks << network; @@ -112,15 +113,15 @@ void QWifiNetworkListModel::parseScanResults(const QByteArray &results) // ssids are the same, compare bssids.. if (knownNetwork->bssid() == info.at(0)) { // same access point, simply update the signal strength - knownNetwork->setSignalStrength(info.at(2).toInt()); + knownNetwork->setSignalStrength(signalStrength); knownNetwork->setOutOfRange(false); dataChanged(createIndex(pos, 0), createIndex(pos, 0)); - } else if (knownNetwork->signalStrength() < info.at(2).toInt()) { + } 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(info.at(2).toInt()); + m_networks.at(pos)->setSignalStrength(signalStrength); m_networks.at(pos)->setSsid(info.at(4)); dataChanged(createIndex(pos, 0), createIndex(pos, 0)); } diff --git a/src/imports/wifi/wifi.pro b/src/imports/wifi/wifi.pro index b920978..fcb6cdd 100644 --- a/src/imports/wifi/wifi.pro +++ b/src/imports/wifi/wifi.pro @@ -4,18 +4,29 @@ TARGET = qwifimodule TARGETPATH = Qt/labs/wifi IMPORT_VERSION = 0.1 +HEADERS += \ + qwifimanager.h \ + qwifinetwork.h \ + qwifinetworklistmodel.h + SOURCES += \ pluginmain.cpp \ qwifimanager.cpp \ qwifinetwork.cpp \ qwifinetworklistmodel.cpp -HEADERS += \ - qwifimanager.h \ - qwifinetwork.h \ - qwifinetworklistmodel.h +android: { + LIBS += -lhardware_legacy -lcutils + DEFINES += FORCE_MOC +} else { + DEFINES += CONFIG_CTRL_IFACE \ + CONFIG_CTRL_IFACE_UNIX -LIBS += -lhardware_legacy -lcutils + HEADERS += qwifi_elinux.h + SOURCES += \ + qwifi_elinux.cpp \ + $$[QT_SYSROOT]/usr/include/wpa-supplicant/wpa_ctrl.c \ + $$[QT_SYSROOT]/usr/include/wpa-supplicant/os_unix.c +} load(qml_plugin) - -- cgit v1.2.3