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