diff options
-rw-r--r-- | src/network/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/network/configure.cmake | 8 | ||||
-rw-r--r-- | src/network/configure.json | 6 | ||||
-rw-r--r-- | src/plugins/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/plugins/networkinformationbackends/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/plugins/networkinformationbackends/networklistmanager/CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/plugins/networkinformationbackends/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp | 370 | ||||
-rw-r--r-- | tests/manual/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/manual/qnetworkinformation/CMakeLists.txt | 8 | ||||
-rw-r--r-- | tests/manual/qnetworkinformation/tst_qnetworkinformation.cpp | 63 |
10 files changed, 465 insertions, 9 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 4ea9656ffb..9c9976441f 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -214,12 +214,12 @@ qt_internal_extend_target(Network CONDITION IOS OR MACOS ${FWSystemConfiguration} ) -qt_internal_extend_target(Network CONDITION QT_FEATURE_netlistmgr AND NOT IOS AND NOT MACOS +qt_internal_extend_target(Network CONDITION QT_FEATURE_networklistmanager AND NOT IOS AND NOT MACOS SOURCES kernel/qnetconmonitor_win.cpp ) -qt_internal_extend_target(Network CONDITION NOT IOS AND NOT MACOS AND NOT QT_FEATURE_netlistmgr +qt_internal_extend_target(Network CONDITION NOT IOS AND NOT MACOS AND NOT QT_FEATURE_networklistmanager SOURCES kernel/qnetconmonitor_stub.cpp ) diff --git a/src/network/configure.cmake b/src/network/configure.cmake index 84a3609d24..e89daaa8c4 100644 --- a/src/network/configure.cmake +++ b/src/network/configure.cmake @@ -204,8 +204,8 @@ int main(int argc, char **argv) } ") -# netlistmgr -qt_config_compile_test(netlistmgr +# networklistmanager +qt_config_compile_test(networklistmanager LABEL "Network List Manager" CODE " @@ -387,11 +387,11 @@ qt_feature("sspi" PUBLIC CONDITION WIN32 ) qt_feature_definition("sspi" "QT_NO_SSPI" NEGATE VALUE "1") -qt_feature("netlistmgr" PRIVATE +qt_feature("networklistmanager" PRIVATE SECTION "Networking" LABEL "Network List Manager" PURPOSE "Use Network List Manager to keep track of network connectivity" - CONDITION WIN32 AND TEST_netlistmgr + CONDITION WIN32 AND TEST_networklistmanager ) qt_feature("topleveldomain" PUBLIC SECTION "Networking" diff --git a/src/network/configure.json b/src/network/configure.json index 95798516e7..b037eee75c 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -214,7 +214,7 @@ }, "use": "openssl" }, - "netlistmgr": { + "networklistmanager": { "label": "Network List Manager", "type": "compile", "test": { @@ -407,11 +407,11 @@ "condition": "config.win32", "output": [ "publicFeature", "feature" ] }, - "netlistmgr": { + "networklistmanager": { "label": "Network List Manager", "purpose": "Use Network List Manager to keep track of network connectivity", "section": "Networking", - "condition": "config.win32 && tests.netlistmgr", + "condition": "config.win32 && tests.networklistmanager", "output": [ "privateFeature" ] }, "topleveldomain": { diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 9a6a8f1e39..4aea4aad94 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -20,3 +20,6 @@ endif() if(TARGET Qt::PrintSupport) add_subdirectory(printsupport) endif() +if (TARGET Qt::Network) + add_subdirectory(networkinformationbackends) +endif() diff --git a/src/plugins/networkinformationbackends/CMakeLists.txt b/src/plugins/networkinformationbackends/CMakeLists.txt new file mode 100644 index 0000000000..94a1f6baba --- /dev/null +++ b/src/plugins/networkinformationbackends/CMakeLists.txt @@ -0,0 +1,3 @@ +if(WIN32 AND QT_FEATURE_networklistmanager) + add_subdirectory(networklistmanager) +endif() diff --git a/src/plugins/networkinformationbackends/networklistmanager/CMakeLists.txt b/src/plugins/networkinformationbackends/networklistmanager/CMakeLists.txt new file mode 100644 index 0000000000..e3dcbfee97 --- /dev/null +++ b/src/plugins/networkinformationbackends/networklistmanager/CMakeLists.txt @@ -0,0 +1,8 @@ +qt_internal_add_plugin(QNetworkListManagerNetworkInformationBackend + OUTPUT_NAME networklistmanagernetworkinformationbackend + TYPE networkinformationbackends + DEFAULT_IF WINDOWS AND QT_FEATURE_networklistmanager + SOURCES qnetworklistmanagernetworkinformationbackend.cpp + PUBLIC_LIBRARIES + Qt::NetworkPrivate +) diff --git a/src/plugins/networkinformationbackends/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp b/src/plugins/networkinformationbackends/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp new file mode 100644 index 0000000000..207c82efd9 --- /dev/null +++ b/src/plugins/networkinformationbackends/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtNetwork/private/qnetworkinformation_p.h> + +#include <QtCore/qglobal.h> +#include <QtCore/private/qobject_p.h> + +#include <objbase.h> +#include <netlistmgr.h> +#include <wrl/client.h> +#include <wrl/wrappers/corewrappers.h> +#include <comdef.h> +#include <iphlpapi.h> +using namespace Microsoft::WRL; + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoNLM) +Q_LOGGING_CATEGORY(lcNetInfoNLM, "qt.network.info.netlistmanager"); + +static const QString backendName = QStringLiteral("networklistmanager"); + +namespace { +QString errorStringFromHResult(HRESULT hr) +{ + _com_error error(hr); + return QString::fromWCharArray(error.ErrorMessage()); +} + +template<typename T> +bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject) +{ + if (riid == __uuidof(T)) { + *ppvObject = static_cast<T *>(from); + from->AddRef(); + return true; + } + return false; +} + +bool testCONNECTIVITY(NLM_CONNECTIVITY connectivity, NLM_CONNECTIVITY flag) +{ + return (connectivity & flag) == flag; +} + +QNetworkInformation::Reachability reachabilityFromNLM_CONNECTIVITY(NLM_CONNECTIVITY connectivity) +{ + if (connectivity == NLM_CONNECTIVITY_DISCONNECTED) + return QNetworkInformation::Reachability::Disconnected; + if (testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV6_INTERNET) + || testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV4_INTERNET)) { + return QNetworkInformation::Reachability::Online; + } + if (testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV6_SUBNET) + || testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV4_SUBNET)) { + return QNetworkInformation::Reachability::Site; + } + if (testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV6_LOCALNETWORK) + || testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV4_LOCALNETWORK)) { + return QNetworkInformation::Reachability::Local; + } + if (testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV6_NOTRAFFIC) + || testCONNECTIVITY(connectivity, NLM_CONNECTIVITY_IPV4_NOTRAFFIC)) { + return QNetworkInformation::Reachability::Unknown; + } + + return QNetworkInformation::Reachability::Unknown; +} +} + +class QNetworkListManagerEvents; +class QNetworkListManagerNetworkInformationBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + QNetworkListManagerNetworkInformationBackend(); + ~QNetworkListManagerNetworkInformationBackend(); + + QString name() const override { return backendName; } + QNetworkInformation::Features featuresSupported() const override + { + return featuresSupportedStatic(); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + return QNetworkInformation::Features(QNetworkInformation::Feature::Reachability); + } + + [[nodiscard]] bool start(); + void stop(); + +private: + friend class QNetworkListManagerEvents; + + bool event(QEvent *event) override; + void setConnectivity(NLM_CONNECTIVITY newConnectivity); + + ComPtr<QNetworkListManagerEvents> managerEvents; + + NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED; + + bool monitoring = false; + bool comInitFailed = false; +}; + +class QNetworkListManagerNetworkInformationBackendFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QNetworkInformationBackendFactory_iid) + Q_INTERFACES(QNetworkInformationBackendFactory) +public: + QNetworkListManagerNetworkInformationBackendFactory() = default; + ~QNetworkListManagerNetworkInformationBackendFactory() = default; + QString name() const override { return backendName; } + QNetworkInformation::Features featuresSupported() const override + { + return QNetworkListManagerNetworkInformationBackend::featuresSupportedStatic(); + } + + QNetworkInformationBackend * + create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + auto backend = new QNetworkListManagerNetworkInformationBackend(); + if (!backend->start()) { + qCWarning(lcNetInfoNLM) << "Failed to start listening to events"; + delete backend; + backend = nullptr; + } + return backend; + } +}; + +class QNetworkListManagerEvents : public QObject, public INetworkListManagerEvents +{ + Q_OBJECT +public: + QNetworkListManagerEvents(); + virtual ~QNetworkListManagerEvents(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; + + ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; } + ULONG STDMETHODCALLTYPE Release() override + { + if (--ref == 0) { + delete this; + return 0; + } + return ref; + } + + HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) override; + + [[nodiscard]] bool start(); + bool stop(); + +signals: + void connectivityChanged(NLM_CONNECTIVITY); + +private: + ComPtr<INetworkListManager> networkListManager = nullptr; + ComPtr<IConnectionPoint> connectionPoint = nullptr; + + QAtomicInteger<ULONG> ref = 0; + DWORD cookie = 0; +}; + +QNetworkListManagerEvents::QNetworkListManagerEvents() : QObject(nullptr) +{ + auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER, + IID_INetworkListManager, &networkListManager); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Could not get a NetworkListManager instance:" + << errorStringFromHResult(hr); + return; + } + + ComPtr<IConnectionPointContainer> connectionPointContainer; + hr = networkListManager.As(&connectionPointContainer); + if (SUCCEEDED(hr)) { + hr = connectionPointContainer->FindConnectionPoint(IID_INetworkListManagerEvents, + &connectionPoint); + } + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Failed to get connection point for network list manager events:" + << errorStringFromHResult(hr); + } +} + +QNetworkListManagerEvents::~QNetworkListManagerEvents() +{ + Q_ASSERT(ref == 0); +} + +HRESULT STDMETHODCALLTYPE QNetworkListManagerEvents::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) + return E_INVALIDARG; + + return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject) + || QueryInterfaceImpl<INetworkListManagerEvents>(this, riid, ppvObject) + ? S_OK + : E_NOINTERFACE; +} + +HRESULT STDMETHODCALLTYPE +QNetworkListManagerEvents::ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) +{ + // This function is run on a different thread than 'monitor' is created on, so we need to run + // it on that thread + connectivityChanged(newConnectivity); + return S_OK; +} + +bool QNetworkListManagerEvents::start() +{ + if (!connectionPoint) { + qCWarning(lcNetInfoNLM, "Initialization failed, can't start!"); + return false; + } + auto hr = connectionPoint->Advise(this, &cookie); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Failed to subscribe to network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + + // Update connectivity since it might have changed since this class was constructed + NLM_CONNECTIVITY connectivity; + hr = networkListManager->GetConnectivity(&connectivity); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Could not get connectivity:" << errorStringFromHResult(hr); + } else { + emit connectivityChanged(connectivity); + } + return true; +} + +bool QNetworkListManagerEvents::stop() +{ + Q_ASSERT(connectionPoint); + auto hr = connectionPoint->Unadvise(cookie); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Failed to unsubscribe from network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + cookie = 0; + return true; +} + +QNetworkListManagerNetworkInformationBackend::QNetworkListManagerNetworkInformationBackend() +{ + auto hr = CoInitialize(nullptr); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Failed to initialize COM:" << errorStringFromHResult(hr); + comInitFailed = true; + return; + } + managerEvents = new QNetworkListManagerEvents(); + connect(managerEvents.Get(), &QNetworkListManagerEvents::connectivityChanged, this, + &QNetworkListManagerNetworkInformationBackend::setConnectivity); +} + +QNetworkListManagerNetworkInformationBackend::~QNetworkListManagerNetworkInformationBackend() +{ + if (comInitFailed) + return; + if (monitoring) + stop(); + else + CoUninitialize(); +} + +void QNetworkListManagerNetworkInformationBackend::setConnectivity(NLM_CONNECTIVITY newConnectivity) +{ + if (reachabilityFromNLM_CONNECTIVITY(connectivity) + != reachabilityFromNLM_CONNECTIVITY(newConnectivity)) { + connectivity = newConnectivity; + setReachability(reachabilityFromNLM_CONNECTIVITY(newConnectivity)); + } +} + +bool QNetworkListManagerNetworkInformationBackend::event(QEvent *event) +{ + if (event->type() == QEvent::ThreadChange && monitoring) { + stop(); + QMetaObject::invokeMethod(this, &QNetworkListManagerNetworkInformationBackend::start, + Qt::QueuedConnection); + } + + return QObject::event(event); +} + +bool QNetworkListManagerNetworkInformationBackend::start() +{ + Q_ASSERT(!monitoring); + + if (comInitFailed) { + auto hr = CoInitialize(nullptr); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Failed to initialize COM:" << errorStringFromHResult(hr); + comInitFailed = true; + return false; + } + comInitFailed = false; + } + if (!managerEvents) + managerEvents = new QNetworkListManagerEvents(); + + if (managerEvents->start()) + monitoring = true; + return monitoring; +} + +void QNetworkListManagerNetworkInformationBackend::stop() +{ + Q_ASSERT(managerEvents); + Q_ASSERT(monitoring); + // Can return false but realistically shouldn't since that would break everything: + managerEvents->stop(); + monitoring = false; + managerEvents.Reset(); + + CoUninitialize(); + comInitFailed = true; // we check this value in start() to see if we need to re-initialize +} + +QT_END_NAMESPACE + +#include "qnetworklistmanagernetworkinformationbackend.moc" diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index 9f48f2832a..f9eb23cd5f 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory(qlocale) add_subdirectory(qmimedatabase) add_subdirectory(qnetconmonitor) add_subdirectory(qnetworkaccessmanager/qget) +add_subdirectory(qnetworkinformation) #special case begin if (QT_FEATURE_openssl AND UNIX) add_subdirectory(qnetworkreply) diff --git a/tests/manual/qnetworkinformation/CMakeLists.txt b/tests/manual/qnetworkinformation/CMakeLists.txt new file mode 100644 index 0000000000..964d48c93f --- /dev/null +++ b/tests/manual/qnetworkinformation/CMakeLists.txt @@ -0,0 +1,8 @@ + +qt_internal_add_manual_test(qnetworkinformation + SOURCES + tst_qnetworkinformation.cpp + PUBLIC_LIBRARIES + Qt::Network + Qt::Test +) diff --git a/tests/manual/qnetworkinformation/tst_qnetworkinformation.cpp b/tests/manual/qnetworkinformation/tst_qnetworkinformation.cpp new file mode 100644 index 0000000000..6ef337fda9 --- /dev/null +++ b/tests/manual/qnetworkinformation/tst_qnetworkinformation.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qmetaobject.h> +#include <QtNetwork/qnetworkinformation.h> + +QByteArray nameOfEnumValue(QNetworkInformation::Reachability reachability) +{ + return QMetaEnum::fromType<QNetworkInformation::Reachability>().valueToKey(int(reachability)); +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + QTextStream writer(stdout); + + if (!QNetworkInformation::load(QNetworkInformation::Feature::Reachability)) { + qWarning("Failed to load any backend"); + writer << "Backends available: " << QNetworkInformation::availableBackends().join(", ") << '\n'; + return -1; + } + QNetworkInformation *info = QNetworkInformation::instance(); + writer << "Backend loaded: " << info->backendName() << '\n'; + writer << "Now you can make changes to the current network connection. Qt should see the " + "changes and notify about it.\n"; + QObject::connect(info, &QNetworkInformation::reachabilityChanged, + [&writer](QNetworkInformation::Reachability newStatus) { + writer << "Updated: " << nameOfEnumValue(newStatus) << '\n'; + writer.flush(); + }); + + writer << "Initial: " << nameOfEnumValue(info->reachability()) << '\n'; + writer.flush(); + + return app.exec(); +} |