Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworklistmanagerevents.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QtCore/private/qsystemerror_p.h>
6
7#include <QtCore/qpointer.h>
8
9#include <mutex>
10
11#if QT_CONFIG(cpp_winrt)
12#include <QtCore/private/qt_winrtbase_p.h>
13
14#include <winrt/Windows.Networking.Connectivity.h>
15#endif // QT_CONFIG(cpp_winrt)
16
17QT_BEGIN_NAMESPACE
18
19namespace {
20template<typename T>
21bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject)
22{
23 if (riid == __uuidof(T)) {
24 *ppvObject = static_cast<T *>(from);
25 from->AddRef();
26 return true;
27 }
28 return false;
29}
30}
31
32QNetworkListManagerEvents::QNetworkListManagerEvents() : QObject(nullptr)
33{
34 auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
35 IID_INetworkListManager, &networkListManager);
36 if (FAILED(hr)) {
37 qCWarning(lcNetInfoNLM) << "Could not get a NetworkListManager instance:"
38 << QSystemError::windowsComString(hr);
39 return;
40 }
41
42 ComPtr<IConnectionPointContainer> connectionPointContainer;
43 hr = networkListManager.As(&connectionPointContainer);
44 if (SUCCEEDED(hr)) {
45 hr = connectionPointContainer->FindConnectionPoint(IID_INetworkListManagerEvents,
46 &connectionPoint);
47 }
48 if (FAILED(hr)) {
49 qCWarning(lcNetInfoNLM) << "Failed to get connection point for network list manager events:"
50 << QSystemError::windowsComString(hr);
51 }
52}
53
55{
56 Q_ASSERT(ref == 0);
57}
58
59HRESULT STDMETHODCALLTYPE QNetworkListManagerEvents::QueryInterface(REFIID riid, void **ppvObject)
60{
61 if (!ppvObject)
62 return E_INVALIDARG;
63
64 return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
65 || QueryInterfaceImpl<INetworkListManagerEvents>(this, riid, ppvObject)
66 ? S_OK
67 : E_NOINTERFACE;
68}
69
70HRESULT STDMETHODCALLTYPE
71QNetworkListManagerEvents::ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)
72{
73 // This function is run on a different thread than 'monitor' is created on, so we need to run
74 // it on that thread
75 emit connectivityChanged(newConnectivity);
76 return S_OK;
77}
78
80{
81 if (!connectionPoint) {
82 qCWarning(lcNetInfoNLM, "Initialization failed, can't start!");
83 return false;
84 }
85 auto hr = connectionPoint->Advise(this, &cookie);
86 if (FAILED(hr)) {
87 qCWarning(lcNetInfoNLM) << "Failed to subscribe to network connectivity events:"
88 << QSystemError::windowsComString(hr);
89 return false;
90 }
91
92 // Update connectivity since it might have changed since this class was constructed
93 NLM_CONNECTIVITY connectivity;
94 hr = networkListManager->GetConnectivity(&connectivity);
95 if (FAILED(hr)) {
96 qCWarning(lcNetInfoNLM) << "Could not get connectivity:"
97 << QSystemError::windowsComString(hr);
98 } else {
99 emit connectivityChanged(connectivity);
100 }
101
102#if QT_CONFIG(cpp_winrt)
103 using namespace winrt::Windows::Networking::Connectivity;
104 using winrt::Windows::Foundation::IInspectable;
105 try {
106 // Register for changes in the network and store a token to unregister later:
107 token = NetworkInformation::NetworkStatusChanged(
108 [owner = QPointer(this)](const IInspectable sender) {
109 Q_UNUSED(sender);
110 if (owner) {
111 std::scoped_lock locker(owner->winrtLock);
112 if (owner->token)
113 owner->emitWinRTUpdates();
114 }
115 });
116 } catch (const winrt::hresult_error &ex) {
117 qCWarning(lcNetInfoNLM) << "Failed to register network status changed callback:"
118 << QSystemError::windowsComString(ex.code());
119 }
120
121 // Emit initial state
122 emitWinRTUpdates();
123#endif
124
125 return true;
126}
127
129{
130 Q_ASSERT(connectionPoint);
131 auto hr = connectionPoint->Unadvise(cookie);
132 if (FAILED(hr)) {
133 qCWarning(lcNetInfoNLM) << "Failed to unsubscribe from network connectivity events:"
134 << QSystemError::windowsComString(hr);
135 } else {
136 cookie = 0;
137 }
138 // Even if we fail we should still try to unregister from winrt events:
139
140#if QT_CONFIG(cpp_winrt)
141 // Try to synchronize unregistering with potentially in-progress callbacks
142 std::scoped_lock locker(winrtLock);
143 if (token) {
144 using namespace winrt::Windows::Networking::Connectivity;
145 // Pass the token we stored earlier to unregister:
146 NetworkInformation::NetworkStatusChanged(token);
147 token = {};
148 }
149#endif
150}
151
153{
154 if (!networkListManager)
155 return false;
156 ComPtr<IEnumNetworks> networks;
157 HRESULT hr =
158 networkListManager->GetNetworks(NLM_ENUM_NETWORK_CONNECTED, networks.GetAddressOf());
159 if (FAILED(hr) || networks == nullptr)
160 return false;
161
162 // @note: This checks all connected networks, but that might not be necessary
163 ComPtr<INetwork> network;
164 hr = networks->Next(1, network.GetAddressOf(), nullptr);
165 while (SUCCEEDED(hr) && network != nullptr) {
166 ComPtr<IPropertyBag> propertyBag;
167 hr = network.As(&propertyBag);
168 if (SUCCEEDED(hr) && propertyBag != nullptr) {
169 VARIANT variant;
170 VariantInit(&variant);
171 const auto scopedVariantClear = qScopeGuard([&variant]() { VariantClear(&variant); });
172
173 const wchar_t *versions[] = { L"NA_InternetConnectivityV6", L"NA_InternetConnectivityV4" };
174 for (const auto version : versions) {
175 hr = propertyBag->Read(version, &variant, nullptr);
176 if (SUCCEEDED(hr)
177 && (V_UINT(&variant) & NLM_INTERNET_CONNECTIVITY_WEBHIJACK)
178 == NLM_INTERNET_CONNECTIVITY_WEBHIJACK) {
179 return true;
180 }
181 }
182 }
183
184 hr = networks->Next(1, network.GetAddressOf(), nullptr);
185 }
186
187 return false;
188}
189
190#if QT_CONFIG(cpp_winrt)
191namespace {
192using namespace winrt::Windows::Networking::Connectivity;
193// NB: this isn't part of "network list manager", but sadly NLM doesn't have an
194// equivalent API (at least not that I've found...)!
195[[nodiscard]]
196QNetworkInformation::TransportMedium getTransportMedium(const ConnectionProfile &profile)
197{
198 if (profile.IsWwanConnectionProfile())
199 return QNetworkInformation::TransportMedium::Cellular;
200 if (profile.IsWlanConnectionProfile())
201 return QNetworkInformation::TransportMedium::WiFi;
202
203 NetworkAdapter adapter(nullptr);
204 try {
205 adapter = profile.NetworkAdapter();
206 } catch (const winrt::hresult_error &ex) {
207 qCWarning(lcNetInfoNLM) << "Failed to obtain network adapter:"
208 << QSystemError::windowsComString(ex.code());
209 // pass, we will return Unknown anyway
210 }
211 if (adapter == nullptr)
212 return QNetworkInformation::TransportMedium::Unknown;
213
214 // Note: Bluetooth is given an iana iftype of 6, which is the same as Ethernet.
215 // In Windows itself there is clearly a distinction between a Bluetooth PAN
216 // and an Ethernet LAN, though it is not clear how they make this distinction.
217 auto fromIanaId = [](quint32 ianaId) -> QNetworkInformation::TransportMedium {
218 // https://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib
219 switch (ianaId) {
220 case 6:
221 return QNetworkInformation::TransportMedium::Ethernet;
222 case 71: // Should be handled before entering this lambda
223 return QNetworkInformation::TransportMedium::WiFi;
224 }
225 return QNetworkInformation::TransportMedium::Unknown;
226 };
227
228 return fromIanaId(adapter.IanaInterfaceType());
229}
230
231[[nodiscard]] bool getMetered(const ConnectionProfile &profile)
232{
233 ConnectionCost cost(nullptr);
234 try {
235 cost = profile.GetConnectionCost();
236 } catch (const winrt::hresult_error &ex) {
237 qCWarning(lcNetInfoNLM) << "Failed to obtain connection cost:"
238 << QSystemError::windowsComString(ex.code());
239 // pass, we return false if we get an empty object back anyway
240 }
241 if (cost == nullptr)
242 return false;
243 NetworkCostType type = cost.NetworkCostType();
244 return type == NetworkCostType::Fixed || type == NetworkCostType::Variable;
245}
246} // unnamed namespace
247
248void QNetworkListManagerEvents::emitWinRTUpdates()
249{
250 using namespace winrt::Windows::Networking::Connectivity;
251 ConnectionProfile profile = nullptr;
252 try {
253 profile = NetworkInformation::GetInternetConnectionProfile();
254 } catch (const winrt::hresult_error &ex) {
255 qCWarning(lcNetInfoNLM) << "Failed to obtain connection profile:"
256 << QSystemError::windowsComString(ex.code());
257 // pass, we would just return early if we get an empty object back anyway
258 }
259 if (profile == nullptr)
260 return;
261 emit transportMediumChanged(getTransportMedium(profile));
262 emit isMeteredChanged(getMetered(profile));
263}
264#endif // QT_CONFIG(cpp_winrt)
265
266QT_END_NAMESPACE
267
268#include "moc_qnetworklistmanagerevents.cpp"
bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject)