Introduce QNetwork(Connection|Status)Monitor to Windows
Currently not available for mingw because it's missing all the interfaces. But should start working once it has them. Change-Id: I231e8b69e008c5300a83087fe9cd071acd0687cb Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
682e4795fe
commit
dc431b7e85
@ -208,6 +208,22 @@
|
||||
"main": ["gss_ctx_id_t ctx;"],
|
||||
"qmake": "LIBS += -lgssapi_krb5"
|
||||
}
|
||||
},
|
||||
"netlistmgr": {
|
||||
"label": "Network List Manager",
|
||||
"type": "compile",
|
||||
"test": {
|
||||
"include": [ "netlistmgr.h", "wrl/client.h" ],
|
||||
"main": [
|
||||
"using namespace Microsoft::WRL;",
|
||||
"ComPtr<INetworkListManager> networkListManager;",
|
||||
"ComPtr<IConnectionPoint> connectionPoint;",
|
||||
"ComPtr<IConnectionPointContainer> connectionPointContainer;",
|
||||
"networkListManager.As(&connectionPointContainer);",
|
||||
"connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents, &connectionPoint);"
|
||||
],
|
||||
"qmake": "LIBS += -lOle32"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -397,6 +413,13 @@
|
||||
"section": "Networking",
|
||||
"condition": "config.win32 && !config.winrt",
|
||||
"output": [ "publicFeature", "feature" ]
|
||||
},
|
||||
"netlistmgr": {
|
||||
"label": "Network List Manager",
|
||||
"purpose": "Use Network List Manager to keep track of network connectivity",
|
||||
"section": "Networking",
|
||||
"condition": "config.win32 && tests.netlistmgr",
|
||||
"output": [ "privateFeature" ]
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -77,6 +77,8 @@ macos | ios {
|
||||
kernel/qnetconmonitor_darwin.mm
|
||||
|
||||
LIBS_PRIVATE += -framework SystemConfiguration
|
||||
} else:qtConfig(netlistmgr) {
|
||||
SOURCES += kernel/qnetconmonitor_win.cpp
|
||||
} else {
|
||||
SOURCES += kernel/qnetconmonitor_stub.cpp
|
||||
}
|
||||
|
710
src/network/kernel/qnetconmonitor_win.cpp
Normal file
710
src/network/kernel/qnetconmonitor_win.cpp
Normal file
@ -0,0 +1,710 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2019 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 "qnetconmonitor_p.h"
|
||||
|
||||
#include "private/qobject_p.h"
|
||||
|
||||
#include <QtCore/quuid.h>
|
||||
#include <QtCore/qmetaobject.h>
|
||||
|
||||
#include <QtNetwork/qnetworkinterface.h>
|
||||
|
||||
#include <objbase.h>
|
||||
#include <netlistmgr.h>
|
||||
#include <wrl/client.h>
|
||||
#include <wrl/wrappers/corewrappers.h>
|
||||
#include <comdef.h>
|
||||
#include <iphlpapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local)
|
||||
{
|
||||
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
auto it = std::find_if(
|
||||
interfaces.cbegin(), interfaces.cend(), [&local](const QNetworkInterface &iface) {
|
||||
const auto &entries = iface.addressEntries();
|
||||
return std::any_of(entries.cbegin(), entries.cend(),
|
||||
[&local](const QNetworkAddressEntry &entry) {
|
||||
return entry.ip().isEqual(local,
|
||||
QHostAddress::TolerantConversion);
|
||||
});
|
||||
});
|
||||
if (it == interfaces.cend()) {
|
||||
qCWarning(lcNetMon, "Could not find the interface for the local address.");
|
||||
return {};
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
class QNetworkConnectionEvents final : public INetworkConnectionEvents
|
||||
{
|
||||
public:
|
||||
QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor);
|
||||
~QNetworkConnectionEvents();
|
||||
|
||||
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
|
||||
NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY connectivity) override;
|
||||
HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged(
|
||||
GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override;
|
||||
|
||||
Q_REQUIRED_RESULT
|
||||
bool setTarget(const QNetworkInterface &iface);
|
||||
Q_REQUIRED_RESULT
|
||||
bool startMonitoring();
|
||||
Q_REQUIRED_RESULT
|
||||
bool stopMonitoring();
|
||||
|
||||
private:
|
||||
ComPtr<INetworkConnection> getNetworkConnectionFromAdapterGuid(QUuid guid);
|
||||
|
||||
QUuid currentConnectionId{};
|
||||
|
||||
ComPtr<INetworkListManager> networkListManager;
|
||||
ComPtr<IConnectionPoint> connectionPoint;
|
||||
|
||||
QNetworkConnectionMonitorPrivate *monitor = nullptr;
|
||||
|
||||
QAtomicInteger<ULONG> ref = 1; // start at 1 for our own initial reference
|
||||
DWORD cookie = 0;
|
||||
};
|
||||
|
||||
class QNetworkConnectionMonitorPrivate : public QObjectPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QNetworkConnectionMonitor);
|
||||
|
||||
public:
|
||||
QNetworkConnectionMonitorPrivate();
|
||||
~QNetworkConnectionMonitorPrivate();
|
||||
|
||||
Q_REQUIRED_RESULT
|
||||
bool setTargets(const QHostAddress &local, const QHostAddress &remote);
|
||||
Q_REQUIRED_RESULT
|
||||
bool startMonitoring();
|
||||
void stopMonitoring();
|
||||
|
||||
void setConnectivity(NLM_CONNECTIVITY newConnectivity);
|
||||
|
||||
private:
|
||||
ComPtr<QNetworkConnectionEvents> connectionEvents;
|
||||
// We can assume we have access to internet/subnet when this class is created because
|
||||
// connection has already been established to the peer:
|
||||
NLM_CONNECTIVITY connectivity =
|
||||
NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET
|
||||
| NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET);
|
||||
|
||||
bool sameSubnet = false;
|
||||
bool monitoring = false;
|
||||
bool comInitFailed = false;
|
||||
bool remoteIsIPv6 = false;
|
||||
};
|
||||
|
||||
QNetworkConnectionEvents::QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor)
|
||||
: monitor(monitor)
|
||||
{
|
||||
auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_INetworkListManager, &networkListManager);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return;
|
||||
}
|
||||
|
||||
ComPtr<IConnectionPointContainer> connectionPointContainer;
|
||||
hr = networkListManager.As(&connectionPointContainer);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents,
|
||||
&connectionPoint);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to get connection point for network events:"
|
||||
<< errorStringFromHResult(hr);
|
||||
}
|
||||
}
|
||||
|
||||
QNetworkConnectionEvents::~QNetworkConnectionEvents()
|
||||
{
|
||||
Q_ASSERT(ref == 0);
|
||||
}
|
||||
|
||||
ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
|
||||
{
|
||||
ComPtr<IEnumNetworkConnections> connections;
|
||||
auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to enumerate network connections:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return nullptr;
|
||||
}
|
||||
ComPtr<INetworkConnection> connection = nullptr;
|
||||
do {
|
||||
hr = connections->Next(1, connection.GetAddressOf(), nullptr);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to get next network connection in enumeration:"
|
||||
<< errorStringFromHResult(hr);
|
||||
break;
|
||||
}
|
||||
if (connection) {
|
||||
GUID adapterId;
|
||||
hr = connection->GetAdapterId(&adapterId);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to get adapter ID from network connection:"
|
||||
<< errorStringFromHResult(hr);
|
||||
continue;
|
||||
}
|
||||
if (guid == adapterId)
|
||||
return connection;
|
||||
}
|
||||
} while (connection);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject)
|
||||
{
|
||||
if (!ppvObject)
|
||||
return E_INVALIDARG;
|
||||
|
||||
return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
|
||||
|| QueryInterfaceImpl<INetworkConnectionEvents>(this, riid, ppvObject)
|
||||
? S_OK
|
||||
: E_NOINTERFACE;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionConnectivityChanged(
|
||||
GUID connectionId, 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
|
||||
QMetaObject::invokeMethod(monitor->q_ptr,
|
||||
[this, connectionId, newConnectivity, monitor = this->monitor]() {
|
||||
if (connectionId == currentConnectionId)
|
||||
monitor->setConnectivity(newConnectivity);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionPropertyChanged(
|
||||
GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags)
|
||||
{
|
||||
Q_UNUSED(connectionId);
|
||||
Q_UNUSED(flags);
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
bool QNetworkConnectionEvents::setTarget(const QNetworkInterface &iface)
|
||||
{
|
||||
// Unset this in case it's already set to something
|
||||
currentConnectionId = QUuid{};
|
||||
|
||||
NET_LUID luid;
|
||||
if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) {
|
||||
qCWarning(lcNetMon, "Could not get the LUID for the interface.");
|
||||
return false;
|
||||
}
|
||||
GUID guid;
|
||||
if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
|
||||
qCWarning(lcNetMon, "Could not get the GUID for the interface.");
|
||||
return false;
|
||||
}
|
||||
ComPtr<INetworkConnection> connection = getNetworkConnectionFromAdapterGuid(guid);
|
||||
if (!connection) {
|
||||
qCWarning(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID.");
|
||||
return false;
|
||||
}
|
||||
auto hr = connection->GetConnectionId(&guid);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to get the connection's GUID:" << errorStringFromHResult(hr);
|
||||
return false;
|
||||
}
|
||||
currentConnectionId = guid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QNetworkConnectionEvents::startMonitoring()
|
||||
{
|
||||
if (currentConnectionId.isNull()) {
|
||||
qCWarning(lcNetMon, "Can not start monitoring, set targets first");
|
||||
return false;
|
||||
}
|
||||
if (!connectionPoint) {
|
||||
qCWarning(lcNetMon,
|
||||
"We don't have the connection point, cannot start listening to events!");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto hr = connectionPoint->Advise(this, &cookie);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QNetworkConnectionEvents::stopMonitoring()
|
||||
{
|
||||
auto hr = connectionPoint->Unadvise(cookie);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to unsubscribe from network connection events:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return false;
|
||||
}
|
||||
cookie = 0;
|
||||
currentConnectionId = QUuid{};
|
||||
return true;
|
||||
}
|
||||
|
||||
QNetworkConnectionMonitorPrivate::QNetworkConnectionMonitorPrivate()
|
||||
{
|
||||
auto hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr);
|
||||
comInitFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
connectionEvents = new QNetworkConnectionEvents(this);
|
||||
}
|
||||
|
||||
QNetworkConnectionMonitorPrivate::~QNetworkConnectionMonitorPrivate()
|
||||
{
|
||||
if (comInitFailed)
|
||||
return;
|
||||
connectionEvents.Reset();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool QNetworkConnectionMonitorPrivate::setTargets(const QHostAddress &local,
|
||||
const QHostAddress &remote)
|
||||
{
|
||||
if (comInitFailed)
|
||||
return false;
|
||||
|
||||
QNetworkInterface iface = getInterfaceFromHostAddress(local);
|
||||
if (!iface.isValid())
|
||||
return false;
|
||||
const auto &addressEntries = iface.addressEntries();
|
||||
auto it = std::find_if(
|
||||
addressEntries.cbegin(), addressEntries.cend(),
|
||||
[&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; });
|
||||
if (Q_UNLIKELY(it == addressEntries.cend())) {
|
||||
qCWarning(lcNetMon, "The address entry we were working with disappeared");
|
||||
return false;
|
||||
}
|
||||
sameSubnet = remote.isInSubnet(local, it->prefixLength());
|
||||
remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol;
|
||||
|
||||
return connectionEvents->setTarget(iface);
|
||||
}
|
||||
|
||||
void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
|
||||
{
|
||||
Q_Q(QNetworkConnectionMonitor);
|
||||
const bool reachable = q->isReachable();
|
||||
connectivity = newConnectivity;
|
||||
const bool newReachable = q->isReachable();
|
||||
if (reachable != newReachable)
|
||||
emit q->reachabilityChanged(newReachable);
|
||||
}
|
||||
|
||||
bool QNetworkConnectionMonitorPrivate::startMonitoring()
|
||||
{
|
||||
Q_ASSERT(connectionEvents);
|
||||
Q_ASSERT(!monitoring);
|
||||
if (connectionEvents->startMonitoring())
|
||||
monitoring = true;
|
||||
return monitoring;
|
||||
}
|
||||
|
||||
void QNetworkConnectionMonitorPrivate::stopMonitoring()
|
||||
{
|
||||
Q_ASSERT(connectionEvents);
|
||||
Q_ASSERT(monitoring);
|
||||
if (connectionEvents->stopMonitoring())
|
||||
monitoring = false;
|
||||
}
|
||||
|
||||
QNetworkConnectionMonitor::QNetworkConnectionMonitor()
|
||||
: QObject(*new QNetworkConnectionMonitorPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local,
|
||||
const QHostAddress &remote)
|
||||
: QObject(*new QNetworkConnectionMonitorPrivate)
|
||||
{
|
||||
setTargets(local, remote);
|
||||
}
|
||||
|
||||
QNetworkConnectionMonitor::~QNetworkConnectionMonitor() = default;
|
||||
|
||||
bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote)
|
||||
{
|
||||
if (isMonitoring()) {
|
||||
qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first");
|
||||
return false;
|
||||
}
|
||||
if (local.isNull()) {
|
||||
qCWarning(lcNetMon, "Invalid (null) local address, cannot create a reachability target");
|
||||
return false;
|
||||
}
|
||||
// Silently return false for loopback addresses instead of printing warnings later
|
||||
if (remote.isLoopback())
|
||||
return false;
|
||||
|
||||
return d_func()->setTargets(local, remote);
|
||||
}
|
||||
|
||||
bool QNetworkConnectionMonitor::startMonitoring()
|
||||
{
|
||||
Q_D(QNetworkConnectionMonitor);
|
||||
if (isMonitoring()) {
|
||||
qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first");
|
||||
return false;
|
||||
}
|
||||
return d->startMonitoring();
|
||||
}
|
||||
|
||||
bool QNetworkConnectionMonitor::isMonitoring() const
|
||||
{
|
||||
return d_func()->monitoring;
|
||||
}
|
||||
|
||||
void QNetworkConnectionMonitor::stopMonitoring()
|
||||
{
|
||||
Q_D(QNetworkConnectionMonitor);
|
||||
if (!isMonitoring()) {
|
||||
qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!");
|
||||
return;
|
||||
}
|
||||
d->stopMonitoring();
|
||||
}
|
||||
|
||||
bool QNetworkConnectionMonitor::isReachable()
|
||||
{
|
||||
Q_D(QNetworkConnectionMonitor);
|
||||
NLM_CONNECTIVITY required = d->sameSubnet
|
||||
? (d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_SUBNET : NLM_CONNECTIVITY_IPV4_SUBNET)
|
||||
: (d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET : NLM_CONNECTIVITY_IPV4_INTERNET);
|
||||
return d_func()->connectivity & required;
|
||||
}
|
||||
|
||||
class QNetworkListManagerEvents final : public INetworkListManagerEvents
|
||||
{
|
||||
public:
|
||||
QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor);
|
||||
~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;
|
||||
|
||||
Q_REQUIRED_RESULT
|
||||
bool start();
|
||||
Q_REQUIRED_RESULT
|
||||
bool stop();
|
||||
|
||||
private:
|
||||
ComPtr<INetworkListManager> networkListManager = nullptr;
|
||||
ComPtr<IConnectionPoint> connectionPoint = nullptr;
|
||||
|
||||
QNetworkStatusMonitorPrivate *monitor = nullptr;
|
||||
|
||||
QAtomicInteger<ULONG> ref = 1; // start at 1 for our own initial reference
|
||||
DWORD cookie = 0;
|
||||
};
|
||||
|
||||
class QNetworkStatusMonitorPrivate : public QObjectPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QNetworkStatusMonitor);
|
||||
|
||||
public:
|
||||
QNetworkStatusMonitorPrivate();
|
||||
~QNetworkStatusMonitorPrivate();
|
||||
|
||||
Q_REQUIRED_RESULT
|
||||
bool start();
|
||||
void stop();
|
||||
|
||||
void setConnectivity(NLM_CONNECTIVITY newConnectivity);
|
||||
|
||||
private:
|
||||
friend class QNetworkListManagerEvents;
|
||||
|
||||
ComPtr<QNetworkListManagerEvents> managerEvents;
|
||||
NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED;
|
||||
|
||||
bool monitoring = false;
|
||||
bool comInitFailed = false;
|
||||
};
|
||||
|
||||
QNetworkListManagerEvents::QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor)
|
||||
: monitor(monitor)
|
||||
{
|
||||
auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_INetworkListManager, &networkListManager);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set initial connectivity
|
||||
hr = networkListManager->GetConnectivity(&monitor->connectivity);
|
||||
if (FAILED(hr))
|
||||
qCWarning(lcNetMon) << "Could not get connectivity:" << errorStringFromHResult(hr);
|
||||
|
||||
ComPtr<IConnectionPointContainer> connectionPointContainer;
|
||||
hr = networkListManager.As(&connectionPointContainer);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = connectionPointContainer->FindConnectionPoint(IID_INetworkListManagerEvents,
|
||||
&connectionPoint);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "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
|
||||
QMetaObject::invokeMethod(monitor->q_ptr,
|
||||
[newConnectivity, monitor = this->monitor]() {
|
||||
monitor->setConnectivity(newConnectivity);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool QNetworkListManagerEvents::start()
|
||||
{
|
||||
if (!connectionPoint) {
|
||||
qCWarning(lcNetMon, "Initialization failed, can't start!");
|
||||
return false;
|
||||
}
|
||||
auto hr = connectionPoint->Advise(this, &cookie);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QNetworkListManagerEvents::stop()
|
||||
{
|
||||
Q_ASSERT(connectionPoint);
|
||||
auto hr = connectionPoint->Unadvise(cookie);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to unsubscribe from network connectivity events:"
|
||||
<< errorStringFromHResult(hr);
|
||||
return false;
|
||||
}
|
||||
cookie = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
QNetworkStatusMonitorPrivate::QNetworkStatusMonitorPrivate()
|
||||
{
|
||||
auto hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr)) {
|
||||
qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr);
|
||||
comInitFailed = true;
|
||||
return;
|
||||
}
|
||||
managerEvents = new QNetworkListManagerEvents(this);
|
||||
}
|
||||
|
||||
QNetworkStatusMonitorPrivate::~QNetworkStatusMonitorPrivate()
|
||||
{
|
||||
if (comInitFailed)
|
||||
return;
|
||||
if (monitoring)
|
||||
stop();
|
||||
managerEvents.Reset();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
void QNetworkStatusMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
|
||||
{
|
||||
Q_Q(QNetworkStatusMonitor);
|
||||
|
||||
const bool oldAccessibility = q->isNetworkAccesible();
|
||||
connectivity = newConnectivity;
|
||||
const bool accessibility = q->isNetworkAccesible();
|
||||
if (oldAccessibility != accessibility)
|
||||
emit q->onlineStateChanged(accessibility);
|
||||
}
|
||||
|
||||
bool QNetworkStatusMonitorPrivate::start()
|
||||
{
|
||||
if (comInitFailed)
|
||||
return false;
|
||||
Q_ASSERT(managerEvents);
|
||||
Q_ASSERT(!monitoring);
|
||||
if (managerEvents->start())
|
||||
monitoring = true;
|
||||
return monitoring;
|
||||
}
|
||||
|
||||
void QNetworkStatusMonitorPrivate::stop()
|
||||
{
|
||||
Q_ASSERT(managerEvents);
|
||||
Q_ASSERT(monitoring);
|
||||
if (managerEvents->stop())
|
||||
monitoring = false;
|
||||
}
|
||||
|
||||
QNetworkStatusMonitor::QNetworkStatusMonitor() : QObject(*new QNetworkStatusMonitorPrivate) {}
|
||||
|
||||
QNetworkStatusMonitor::~QNetworkStatusMonitor() {}
|
||||
|
||||
bool QNetworkStatusMonitor::start()
|
||||
{
|
||||
if (isMonitoring()) {
|
||||
qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first");
|
||||
return false;
|
||||
}
|
||||
|
||||
return d_func()->start();
|
||||
}
|
||||
|
||||
void QNetworkStatusMonitor::stop()
|
||||
{
|
||||
if (!isMonitoring()) {
|
||||
qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!");
|
||||
return;
|
||||
}
|
||||
|
||||
d_func()->stop();
|
||||
}
|
||||
|
||||
bool QNetworkStatusMonitor::isMonitoring() const
|
||||
{
|
||||
return d_func()->monitoring;
|
||||
}
|
||||
|
||||
bool QNetworkStatusMonitor::isNetworkAccesible()
|
||||
{
|
||||
return d_func()->connectivity
|
||||
& (NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET
|
||||
| NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET);
|
||||
}
|
||||
|
||||
bool QNetworkStatusMonitor::isEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void QNetworkStatusMonitor::reachabilityChanged(bool online)
|
||||
{
|
||||
Q_UNUSED(online);
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
Loading…
x
Reference in New Issue
Block a user