diff options
Diffstat (limited to 'src/plugins/networkinformation/networklistmanager')
4 files changed, 571 insertions, 0 deletions
diff --git a/src/plugins/networkinformation/networklistmanager/CMakeLists.txt b/src/plugins/networkinformation/networklistmanager/CMakeLists.txt new file mode 100644 index 0000000000..acd3754f4e --- /dev/null +++ b/src/plugins/networkinformation/networklistmanager/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QNLMNIPlugin + OUTPUT_NAME qnetworklistmanager + CLASS_NAME QNetworkListManagerNetworkInformationBackendFactory + PLUGIN_TYPE networkinformation + DEFAULT_IF WIN32 AND QT_FEATURE_networklistmanager + EXCEPTIONS + SOURCES + qnetworklistmanagernetworkinformationbackend.cpp + qnetworklistmanagerevents.h qnetworklistmanagerevents.cpp + LIBRARIES + Qt::NetworkPrivate +) + +qt_internal_extend_target(QNLMNIPlugin CONDITION WIN32 + LIBRARIES + runtimeobject + oleaut32 +) + +# Don't repeat the target name in AUTOGEN_BUILD_DIR to work around issues with overlong paths. +set_property(TARGET QNLMNIPlugin PROPERTY + AUTOGEN_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/autogen") diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp new file mode 100644 index 0000000000..caa5046751 --- /dev/null +++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qnetworklistmanagerevents.h" +#include <QtCore/private/qsystemerror_p.h> + +#include <QtCore/qpointer.h> + +#include <mutex> + +#if QT_CONFIG(cpp_winrt) +#include <QtCore/private/qt_winrtbase_p.h> + +#include <winrt/Windows.Networking.Connectivity.h> +#endif // QT_CONFIG(cpp_winrt) + +QT_BEGIN_NAMESPACE + +namespace { +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; +} +} + +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:" + << QSystemError::windowsComString(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:" + << QSystemError::windowsComString(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 + emit 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:" + << QSystemError::windowsComString(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:" + << QSystemError::windowsComString(hr); + } else { + emit connectivityChanged(connectivity); + } + +#if QT_CONFIG(cpp_winrt) + using namespace winrt::Windows::Networking::Connectivity; + using winrt::Windows::Foundation::IInspectable; + try { + // Register for changes in the network and store a token to unregister later: + token = NetworkInformation::NetworkStatusChanged( + [owner = QPointer(this)](const IInspectable sender) { + Q_UNUSED(sender); + if (owner) { + std::scoped_lock locker(owner->winrtLock); + if (owner->token) + owner->emitWinRTUpdates(); + } + }); + } catch (const winrt::hresult_error &ex) { + qCWarning(lcNetInfoNLM) << "Failed to register network status changed callback:" + << QSystemError::windowsComString(ex.code()); + } + + // Emit initial state + emitWinRTUpdates(); +#endif + + return true; +} + +void QNetworkListManagerEvents::stop() +{ + Q_ASSERT(connectionPoint); + auto hr = connectionPoint->Unadvise(cookie); + if (FAILED(hr)) { + qCWarning(lcNetInfoNLM) << "Failed to unsubscribe from network connectivity events:" + << QSystemError::windowsComString(hr); + } else { + cookie = 0; + } + // Even if we fail we should still try to unregister from winrt events: + +#if QT_CONFIG(cpp_winrt) + // Try to synchronize unregistering with potentially in-progress callbacks + std::scoped_lock locker(winrtLock); + if (token) { + using namespace winrt::Windows::Networking::Connectivity; + // Pass the token we stored earlier to unregister: + NetworkInformation::NetworkStatusChanged(token); + token = {}; + } +#endif +} + +bool QNetworkListManagerEvents::checkBehindCaptivePortal() +{ + if (!networkListManager) + return false; + ComPtr<IEnumNetworks> networks; + HRESULT hr = + networkListManager->GetNetworks(NLM_ENUM_NETWORK_CONNECTED, networks.GetAddressOf()); + if (FAILED(hr) || networks == nullptr) + return false; + + // @note: This checks all connected networks, but that might not be necessary + ComPtr<INetwork> network; + hr = networks->Next(1, network.GetAddressOf(), nullptr); + while (SUCCEEDED(hr) && network != nullptr) { + ComPtr<IPropertyBag> propertyBag; + hr = network.As(&propertyBag); + if (SUCCEEDED(hr) && propertyBag != nullptr) { + VARIANT variant; + VariantInit(&variant); + const auto scopedVariantClear = qScopeGuard([&variant]() { VariantClear(&variant); }); + + const wchar_t *versions[] = { L"NA_InternetConnectivityV6", L"NA_InternetConnectivityV4" }; + for (const auto version : versions) { + hr = propertyBag->Read(version, &variant, nullptr); + if (SUCCEEDED(hr) + && (V_UINT(&variant) & NLM_INTERNET_CONNECTIVITY_WEBHIJACK) + == NLM_INTERNET_CONNECTIVITY_WEBHIJACK) { + return true; + } + } + } + + hr = networks->Next(1, network.GetAddressOf(), nullptr); + } + + return false; +} + +#if QT_CONFIG(cpp_winrt) +namespace { +using namespace winrt::Windows::Networking::Connectivity; +// NB: this isn't part of "network list manager", but sadly NLM doesn't have an +// equivalent API (at least not that I've found...)! +[[nodiscard]] +QNetworkInformation::TransportMedium getTransportMedium(const ConnectionProfile &profile) +{ + if (profile.IsWwanConnectionProfile()) + return QNetworkInformation::TransportMedium::Cellular; + if (profile.IsWlanConnectionProfile()) + return QNetworkInformation::TransportMedium::WiFi; + + NetworkAdapter adapter(nullptr); + try { + adapter = profile.NetworkAdapter(); + } catch (const winrt::hresult_error &ex) { + qCWarning(lcNetInfoNLM) << "Failed to obtain network adapter:" + << QSystemError::windowsComString(ex.code()); + // pass, we will return Unknown anyway + } + if (adapter == nullptr) + return QNetworkInformation::TransportMedium::Unknown; + + // Note: Bluetooth is given an iana iftype of 6, which is the same as Ethernet. + // In Windows itself there is clearly a distinction between a Bluetooth PAN + // and an Ethernet LAN, though it is not clear how they make this distinction. + auto fromIanaId = [](quint32 ianaId) -> QNetworkInformation::TransportMedium { + // https://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib + switch (ianaId) { + case 6: + return QNetworkInformation::TransportMedium::Ethernet; + case 71: // Should be handled before entering this lambda + return QNetworkInformation::TransportMedium::WiFi; + } + return QNetworkInformation::TransportMedium::Unknown; + }; + + return fromIanaId(adapter.IanaInterfaceType()); +} + +[[nodiscard]] bool getMetered(const ConnectionProfile &profile) +{ + ConnectionCost cost(nullptr); + try { + cost = profile.GetConnectionCost(); + } catch (const winrt::hresult_error &ex) { + qCWarning(lcNetInfoNLM) << "Failed to obtain connection cost:" + << QSystemError::windowsComString(ex.code()); + // pass, we return false if we get an empty object back anyway + } + if (cost == nullptr) + return false; + NetworkCostType type = cost.NetworkCostType(); + return type == NetworkCostType::Fixed || type == NetworkCostType::Variable; +} +} // unnamed namespace + +void QNetworkListManagerEvents::emitWinRTUpdates() +{ + using namespace winrt::Windows::Networking::Connectivity; + ConnectionProfile profile = nullptr; + try { + profile = NetworkInformation::GetInternetConnectionProfile(); + } catch (const winrt::hresult_error &ex) { + qCWarning(lcNetInfoNLM) << "Failed to obtain connection profile:" + << QSystemError::windowsComString(ex.code()); + // pass, we would just return early if we get an empty object back anyway + } + if (profile == nullptr) + return; + emit transportMediumChanged(getTransportMedium(profile)); + emit isMeteredChanged(getMetered(profile)); +} +#endif // QT_CONFIG(cpp_winrt) + +QT_END_NAMESPACE + +#include "moc_qnetworklistmanagerevents.cpp" diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h new file mode 100644 index 0000000000..d91cd8a4cc --- /dev/null +++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h @@ -0,0 +1,79 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QNETWORKLISTMANAGEREVENTS_H +#define QNETWORKLISTMANAGEREVENTS_H + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/qnetworkinformation.h> + +#include <QtCore/qstring.h> +#include <QtCore/qobject.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qmutex.h> + +#include <objbase.h> +#include <ocidl.h> +#include <netlistmgr.h> +#include <wrl/client.h> +#include <wrl/wrappers/corewrappers.h> + +#if QT_CONFIG(cpp_winrt) +#include <QtCore/private/qt_winrtbase_p.h> +#endif + +using namespace Microsoft::WRL; + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoNLM) + +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(); + void stop(); + + [[nodiscard]] bool checkBehindCaptivePortal(); + +signals: + void connectivityChanged(NLM_CONNECTIVITY); + void transportMediumChanged(QNetworkInformation::TransportMedium); + void isMeteredChanged(bool); + +private: + ComPtr<INetworkListManager> networkListManager = nullptr; + ComPtr<IConnectionPoint> connectionPoint = nullptr; + +#if QT_CONFIG(cpp_winrt) + void emitWinRTUpdates(); + + winrt::event_token token; + QMutex winrtLock; +#endif + + QAtomicInteger<ULONG> ref = 0; + DWORD cookie = 0; +}; + +QT_END_NAMESPACE + +#endif // QNETWORKLISTMANAGEREVENTS_H diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp new file mode 100644 index 0000000000..766648486e --- /dev/null +++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp @@ -0,0 +1,199 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtNetwork/private/qnetworkinformation_p.h> + +#include "qnetworklistmanagerevents.h" + +#include <QtCore/qglobal.h> +#include <QtCore/private/qobject_p.h> +#include <QtCore/qscopeguard.h> + +#include <QtCore/private/qfunctions_win_p.h> + +QT_BEGIN_NAMESPACE + +// Declared in qnetworklistmanagerevents.h +Q_LOGGING_CATEGORY(lcNetInfoNLM, "qt.network.info.netlistmanager"); + +static QString backendName() +{ + return QString::fromUtf16(QNetworkInformationBackend::PluginNames + [QNetworkInformationBackend::PluginNamesWindowsIndex]); +} + +namespace { +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 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 + | QNetworkInformation::Feature::CaptivePortal +#if QT_CONFIG(cpp_winrt) + | QNetworkInformation::Feature::TransportMedium + | QNetworkInformation::Feature::Metered +#endif + ); + } + + [[nodiscard]] bool start(); + void stop(); + +private: + bool event(QEvent *event) override; + void setConnectivity(NLM_CONNECTIVITY newConnectivity); + void checkCaptivePortal(); + + QComHelper comHelper; + + ComPtr<QNetworkListManagerEvents> managerEvents; + + NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED; + + bool monitoring = 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; + } +}; + +QNetworkListManagerNetworkInformationBackend::QNetworkListManagerNetworkInformationBackend() +{ + if (!comHelper.isValid()) + return; + + managerEvents = new QNetworkListManagerEvents(); + connect(managerEvents.Get(), &QNetworkListManagerEvents::connectivityChanged, this, + &QNetworkListManagerNetworkInformationBackend::setConnectivity); + + connect(managerEvents.Get(), &QNetworkListManagerEvents::transportMediumChanged, this, + &QNetworkListManagerNetworkInformationBackend::setTransportMedium); + + connect(managerEvents.Get(), &QNetworkListManagerEvents::isMeteredChanged, this, + &QNetworkListManagerNetworkInformationBackend::setMetered); +} + +QNetworkListManagerNetworkInformationBackend::~QNetworkListManagerNetworkInformationBackend() +{ + stop(); +} + +void QNetworkListManagerNetworkInformationBackend::setConnectivity(NLM_CONNECTIVITY newConnectivity) +{ + if (reachabilityFromNLM_CONNECTIVITY(connectivity) + != reachabilityFromNLM_CONNECTIVITY(newConnectivity)) { + connectivity = newConnectivity; + setReachability(reachabilityFromNLM_CONNECTIVITY(newConnectivity)); + + // @future: only check if signal is connected + checkCaptivePortal(); + } +} + +void QNetworkListManagerNetworkInformationBackend::checkCaptivePortal() +{ + setBehindCaptivePortal(managerEvents->checkBehindCaptivePortal()); +} + +bool QNetworkListManagerNetworkInformationBackend::event(QEvent *event) +{ + if (event->type() == QEvent::ThreadChange) + qFatal("Moving QNetworkListManagerNetworkInformationBackend to different thread is not supported"); + + return QObject::event(event); +} + +bool QNetworkListManagerNetworkInformationBackend::start() +{ + Q_ASSERT(!monitoring); + + if (!comHelper.isValid()) + return false; + + if (!managerEvents) + managerEvents = new QNetworkListManagerEvents(); + + if (managerEvents->start()) + monitoring = true; + return monitoring; +} + +void QNetworkListManagerNetworkInformationBackend::stop() +{ + if (monitoring) { + Q_ASSERT(managerEvents); + managerEvents->stop(); + monitoring = false; + managerEvents.Reset(); + } +} + +QT_END_NAMESPACE + +#include "qnetworklistmanagernetworkinformationbackend.moc" |