diff options
Diffstat (limited to 'src/plugins/networkinformation')
22 files changed, 2132 insertions, 0 deletions
diff --git a/src/plugins/networkinformation/CMakeLists.txt b/src/plugins/networkinformation/CMakeLists.txt new file mode 100644 index 0000000000..04da816264 --- /dev/null +++ b/src/plugins/networkinformation/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(WIN32 AND QT_FEATURE_networklistmanager) + add_subdirectory(networklistmanager) +endif() + +if(LINUX AND TARGET Qt::DBus) + add_subdirectory(networkmanager) +endif() + +if(APPLE) + add_subdirectory(scnetworkreachability) +endif() + +if(ANDROID) + add_subdirectory(android) +endif() + +if(QT_FEATURE_glib AND TARGET GLIB2::GOBJECT AND TARGET GLIB2::GIO) + add_subdirectory(glib) +endif() diff --git a/src/plugins/networkinformation/android/CMakeLists.txt b/src/plugins/networkinformation/android/CMakeLists.txt new file mode 100644 index 0000000000..07d9201bbb --- /dev/null +++ b/src/plugins/networkinformation/android/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +set(java_sources + jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java +) + +qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend + INCLUDE_JARS ${QT_ANDROID_JAR} + SOURCES ${java_sources} + OUTPUT_DIR "${QT_BUILD_DIR}/jar" +) + +qt_path_join(destination ${INSTALL_DATADIR} "jar") + +install_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend + DESTINATION ${destination} + COMPONENT Devel +) + +qt_internal_add_plugin(QAndroidNetworkInformationPlugin + OUTPUT_NAME qandroidnetworkinformation + CLASS_NAME QAndroidNetworkInformationBackendFactory + PLUGIN_TYPE networkinformation + DEFAULT_IF ANDROID + SOURCES + qandroidnetworkinformationbackend.cpp + wrapper/androidconnectivitymanager.cpp wrapper/androidconnectivitymanager.h + LIBRARIES + Qt::NetworkPrivate +) + +set_property( + TARGET + QAndroidNetworkInformationPlugin + APPEND PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES + jar/Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend.jar +) + +set_property( + TARGET + QAndroidNetworkInformationPlugin + APPEND PROPERTY QT_ANDROID_PERMISSIONS + android.permission.ACCESS_NETWORK_STATE +) diff --git a/src/plugins/networkinformation/android/jar/.gitignore b/src/plugins/networkinformation/android/jar/.gitignore new file mode 100644 index 0000000000..364420a59a --- /dev/null +++ b/src/plugins/networkinformation/android/jar/.gitignore @@ -0,0 +1,6 @@ +.gradle/ +build/ +gradle/ +gradlew +gradlew.bat +local.properties diff --git a/src/plugins/networkinformation/android/jar/build.gradle b/src/plugins/networkinformation/android/jar/build.gradle new file mode 100644 index 0000000000..ea6d06c257 --- /dev/null +++ b/src/plugins/networkinformation/android/jar/build.gradle @@ -0,0 +1,51 @@ +// This is mainly used to allow Android Studio to easily read this folder as an android project. + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.4.0' + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) +} + +repositories { + google() + mavenCentral() +} + +android { + compileSdk 34 + + defaultConfig { + minSdkVersion 28 + } + + sourceSets { + main { + java.srcDir 'src/' + resources.srcDir 'libs/' + manifest.srcFile 'AndroidManifest.xml' + res.srcDirs = ['res/'] + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + android { + lintOptions { + abortOnError true + } + } +} diff --git a/src/plugins/networkinformation/android/jar/settings.gradle b/src/plugins/networkinformation/android/jar/settings.gradle new file mode 100644 index 0000000000..cbb1ff361b --- /dev/null +++ b/src/plugins/networkinformation/android/jar/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "QtAndroidNetworkInformationBackend" diff --git a/src/plugins/networkinformation/android/jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java b/src/plugins/networkinformation/android/jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java new file mode 100644 index 0000000000..fc3e00311d --- /dev/null +++ b/src/plugins/networkinformation/android/jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java @@ -0,0 +1,160 @@ +// 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 + +package org.qtproject.qt.android.networkinformation; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.NetworkRequest; +import android.net.NetworkCapabilities; +import android.net.Network; +import android.os.Build; + +class QtAndroidNetworkInformation { + private static final String LOG_TAG = "QtAndroidNetworkInformation"; + + private static native void networkConnectivityChanged(int connectivity); + private static native void genericInfoChanged(boolean captivePortal, boolean metered); + private static native void transportMediumChanged(int transportMedium); + + private static QtNetworkInformationCallback m_callback = null; + private static final Object m_lock = new Object(); + + // Keep synchronized with AndroidConnectivity in androidconnectivitymanager.h + enum AndroidConnectivity { + Connected, Unknown, Disconnected + } + + // Keep synchronized with AndroidTransport in androidconnectivitymanager.h + enum Transport { + Unknown, + Bluetooth, + Cellular, + Ethernet, + LoWPAN, + Usb, + WiFi, + WiFiAware, + } + + private static class QtNetworkInformationCallback extends NetworkCallback { + AndroidConnectivity previousState = null; + Transport previousTransport = null; + + QtNetworkInformationCallback() { + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) { + AndroidConnectivity s; + if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) + s = AndroidConnectivity.Disconnected; + else if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) + s = AndroidConnectivity.Connected; + else + s = AndroidConnectivity.Unknown; // = we _may_ have Internet access + + final Transport transport = getTransport(capabilities); + if (transport == Transport.Unknown) // If we don't have any transport media: override + s = AndroidConnectivity.Unknown; + + setState(s); + setTransportMedium(transport); + + final boolean captive + = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); + final boolean metered + = !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + genericInfoChanged(captive, metered); + } + + private Transport getTransport(NetworkCapabilities capabilities) + { + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return Transport.WiFi; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return Transport.Cellular; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) { + return Transport.Bluetooth; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return Transport.Ethernet; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) { + // Build.VERSION_CODES.O + return Transport.WiFiAware; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN)) { + // Build.VERSION_CODES.O_MR1 + return Transport.LoWPAN; + }/* else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB)) { + // Build.VERSION_CODES.S + return Transport.Usb; + }*/ // @todo: Uncomment once we can use SDK 31 + return Transport.Unknown; + } + + private void setState(AndroidConnectivity s) { + if (previousState != s) { + previousState = s; + networkConnectivityChanged(s.ordinal()); + } + } + + private void setTransportMedium(Transport t) { + if (previousTransport != t) { + previousTransport = t; + transportMediumChanged(t.ordinal()); + } + } + + @Override + public void onLost(Network network) { + setState(AndroidConnectivity.Disconnected); + } + } + + private QtAndroidNetworkInformation() { + } + + static AndroidConnectivity state() { + if (m_callback != null && m_callback.previousState != null) + return m_callback.previousState; + return AndroidConnectivity.Unknown; + } + + @SuppressLint("MissingPermission") + static void registerReceiver(final Context context) { + synchronized (m_lock) { + if (m_callback == null) { + ConnectivityManager manager = getConnectivityManager(context); + m_callback = new QtNetworkInformationCallback(); + NetworkRequest.Builder builder = new NetworkRequest.Builder(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + builder = builder.clearCapabilities(); + builder = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + builder = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); + builder = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND); + } + NetworkRequest request = builder.build(); + + // Can't use registerDefaultNetworkCallback because it doesn't let us know when + // the network disconnects! + manager.registerNetworkCallback(request, m_callback); + } + } + } + + static void unregisterReceiver(final Context context) { + synchronized (m_lock) { + if (m_callback != null) { + getConnectivityManager(context).unregisterNetworkCallback(m_callback); + m_callback = null; + } + } + } + + static ConnectivityManager getConnectivityManager(final Context context) { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } +} diff --git a/src/plugins/networkinformation/android/qandroidnetworkinformationbackend.cpp b/src/plugins/networkinformation/android/qandroidnetworkinformationbackend.cpp new file mode 100644 index 0000000000..44e4d447d2 --- /dev/null +++ b/src/plugins/networkinformation/android/qandroidnetworkinformationbackend.cpp @@ -0,0 +1,147 @@ +// 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 "wrapper/androidconnectivitymanager.h" + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoAndroid) +Q_LOGGING_CATEGORY(lcNetInfoAndroid, "qt.network.info.android"); + +static QString backendName() { + return QString::fromUtf16(QNetworkInformationBackend::PluginNames + [QNetworkInformationBackend::PluginNamesAndroidIndex]); +} + +class QAndroidNetworkInformationBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + QAndroidNetworkInformationBackend(); + ~QAndroidNetworkInformationBackend() { m_valid = false; } + + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + return featuresSupportedStatic(); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + using Feature = QNetworkInformation::Feature; + return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal + | Feature::TransportMedium); + } + + bool isValid() { return m_valid; } + +private: + Q_DISABLE_COPY_MOVE(QAndroidNetworkInformationBackend); + + void updateConnectivity(AndroidConnectivityManager::AndroidConnectivity connectivity); + void updateTransportMedium(AndroidConnectivityManager::AndroidTransport transport); + + bool m_valid = false; +}; + +class QAndroidNetworkInformationBackendFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QNetworkInformationBackendFactory_iid) + Q_INTERFACES(QNetworkInformationBackendFactory) +public: + QAndroidNetworkInformationBackendFactory() = default; + ~QAndroidNetworkInformationBackendFactory() = default; + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + return QAndroidNetworkInformationBackend::featuresSupportedStatic(); + } + + QNetworkInformationBackend * + create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + auto backend = new QAndroidNetworkInformationBackend(); + if (!backend->isValid()) + delete std::exchange(backend, nullptr); + return backend; + } + +private: + Q_DISABLE_COPY_MOVE(QAndroidNetworkInformationBackendFactory); +}; + +QAndroidNetworkInformationBackend::QAndroidNetworkInformationBackend() +{ + auto conman = AndroidConnectivityManager::getInstance(); + if (!conman) + return; + m_valid = true; + setReachability(QNetworkInformation::Reachability::Unknown); + connect(conman, &AndroidConnectivityManager::connectivityChanged, this, + &QAndroidNetworkInformationBackend::updateConnectivity); + + connect(conman, &AndroidConnectivityManager::captivePortalChanged, this, + &QAndroidNetworkInformationBackend::setBehindCaptivePortal); + + connect(conman, &AndroidConnectivityManager::transportMediumChanged, this, + &QAndroidNetworkInformationBackend::updateTransportMedium); + + connect(conman, &AndroidConnectivityManager::meteredChanged, this, + &QAndroidNetworkInformationBackend::setMetered); +} + +void QAndroidNetworkInformationBackend::updateConnectivity( + AndroidConnectivityManager::AndroidConnectivity connectivity) +{ + using AndroidConnectivity = AndroidConnectivityManager::AndroidConnectivity; + static const auto mapState = [](AndroidConnectivity state) { + switch (state) { + case AndroidConnectivity::Connected: + return QNetworkInformation::Reachability::Online; + case AndroidConnectivity::Disconnected: + return QNetworkInformation::Reachability::Disconnected; + case AndroidConnectivity::Unknown: + default: + return QNetworkInformation::Reachability::Unknown; + } + }; + + setReachability(mapState(connectivity)); +} + +void QAndroidNetworkInformationBackend::updateTransportMedium( + AndroidConnectivityManager::AndroidTransport transport) +{ + using AndroidTransport = AndroidConnectivityManager::AndroidTransport; + using TransportMedium = QNetworkInformation::TransportMedium; + static const auto mapTransport = [](AndroidTransport state) -> TransportMedium { + switch (state) { + case AndroidTransport::Cellular: + return TransportMedium::Cellular; + case AndroidTransport::WiFi: + return TransportMedium::WiFi; + case AndroidTransport::Bluetooth: + return TransportMedium::Bluetooth; + case AndroidTransport::Ethernet: + return TransportMedium::Ethernet; + // These are not covered yet (but may be in the future) + case AndroidTransport::Usb: + case AndroidTransport::LoWPAN: + case AndroidTransport::WiFiAware: + case AndroidTransport::Unknown: + return TransportMedium::Unknown; + } + }; + + setTransportMedium(mapTransport(transport)); +} + +QT_END_NAMESPACE + +#include "qandroidnetworkinformationbackend.moc" diff --git a/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp new file mode 100644 index 0000000000..3c9f952968 --- /dev/null +++ b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp @@ -0,0 +1,97 @@ +// 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 "androidconnectivitymanager.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qjnienvironment.h> + +QT_BEGIN_NAMESPACE + +using namespace QNativeInterface; + +struct AndroidConnectivityManagerInstance +{ + AndroidConnectivityManagerInstance() : connManager(new AndroidConnectivityManager) { } + std::unique_ptr<AndroidConnectivityManager> connManager = nullptr; +}; +Q_GLOBAL_STATIC(AndroidConnectivityManagerInstance, androidConnManagerInstance) + +static const char networkInformationClass[] = + "org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation"; + +static void networkConnectivityChanged(JNIEnv *env, jobject obj, jint enumValue) +{ + Q_UNUSED(env); + Q_UNUSED(obj); + const auto connectivity = + static_cast<AndroidConnectivityManager::AndroidConnectivity>(enumValue); + Q_EMIT androidConnManagerInstance->connManager->connectivityChanged(connectivity); +} +Q_DECLARE_JNI_NATIVE_METHOD(networkConnectivityChanged) + +static void genericInfoChanged(JNIEnv *env, jobject obj, jboolean captivePortal, jboolean metered) +{ + Q_UNUSED(env); + Q_UNUSED(obj); + Q_EMIT androidConnManagerInstance->connManager->captivePortalChanged(captivePortal); + Q_EMIT androidConnManagerInstance->connManager->meteredChanged(metered); +} +Q_DECLARE_JNI_NATIVE_METHOD(genericInfoChanged) + +static void transportMediumChanged(JNIEnv *env, jobject obj, jint enumValue) +{ + Q_UNUSED(env); + Q_UNUSED(obj); + const auto transport = static_cast<AndroidConnectivityManager::AndroidTransport>(enumValue); + emit androidConnManagerInstance->connManager->transportMediumChanged(transport); +} +Q_DECLARE_JNI_NATIVE_METHOD(transportMediumChanged) + +Q_DECLARE_JNI_CLASS(ConnectivityManager, "android/net/ConnectivityManager") + +AndroidConnectivityManager::AndroidConnectivityManager() +{ + if (!registerNatives()) + return; + + QJniObject::callStaticMethod<void>(networkInformationClass, "registerReceiver", + QAndroidApplication::context()); +} + +AndroidConnectivityManager *AndroidConnectivityManager::getInstance() +{ + if (!androidConnManagerInstance()) + return nullptr; + return androidConnManagerInstance->connManager->isValid() + ? androidConnManagerInstance->connManager.get() + : nullptr; +} + +bool AndroidConnectivityManager::isValid() const +{ + return registerNatives(); +} + +AndroidConnectivityManager::~AndroidConnectivityManager() +{ + QJniObject::callStaticMethod<void>(networkInformationClass, "unregisterReceiver", + QAndroidApplication::context()); +} + +bool AndroidConnectivityManager::registerNatives() const +{ + static const bool registered = []() { + QJniEnvironment env; + return env.registerNativeMethods(networkInformationClass, { + Q_JNI_NATIVE_METHOD(networkConnectivityChanged), + Q_JNI_NATIVE_METHOD(genericInfoChanged), + Q_JNI_NATIVE_METHOD(transportMediumChanged), + }); + }(); + return registered; +} + +QT_END_NAMESPACE + +#include "moc_androidconnectivitymanager.cpp" diff --git a/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.h b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.h new file mode 100644 index 0000000000..d15faf0e8e --- /dev/null +++ b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.h @@ -0,0 +1,54 @@ +// 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 ANDROIDCONNECTIVITYMANAGER_H +#define ANDROIDCONNECTIVITYMANAGER_H + +#include <QObject> +#include <QtCore/qjniobject.h> + +QT_BEGIN_NAMESPACE + +class AndroidConnectivityManager : public QObject +{ + Q_OBJECT +public: + // Keep synchronized with AndroidConnectivity in QtAndroidNetworkInformation.java + enum class AndroidConnectivity { Connected, Unknown, Disconnected }; + Q_ENUM(AndroidConnectivity); + + // Keep synchronized with Transport in QtAndroidNetworkInformation.java + enum class AndroidTransport { + Unknown, + Bluetooth, + Cellular, + Ethernet, + LoWPAN, + Usb, + WiFi, + WiFiAware, + }; + Q_ENUM(AndroidTransport); + + static AndroidConnectivityManager *getInstance(); + ~AndroidConnectivityManager(); + + inline bool isValid() const; + +Q_SIGNALS: + void connectivityChanged(AndroidConnectivity connectivity); + void captivePortalChanged(bool state); + void transportMediumChanged(AndroidTransport transport); + void meteredChanged(bool state); + +private: + friend struct AndroidConnectivityManagerInstance; + AndroidConnectivityManager(); + bool registerNatives() const; + + Q_DISABLE_COPY_MOVE(AndroidConnectivityManager); +}; + +QT_END_NAMESPACE + +#endif // ANDROIDCONNECTIVITYMANAGER_H diff --git a/src/plugins/networkinformation/glib/CMakeLists.txt b/src/plugins/networkinformation/glib/CMakeLists.txt new file mode 100644 index 0000000000..019f4f1358 --- /dev/null +++ b/src/plugins/networkinformation/glib/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QGlibNetworkInformationPlugin + OUTPUT_NAME qglib + CLASS_NAME QGlibNetworkInformationBackendFactory + PLUGIN_TYPE networkinformation + DEFAULT_IF LINUX + SOURCES + qglibnetworkinformationbackend.cpp + LIBRARIES + Qt::NetworkPrivate + GLIB2::GOBJECT + GLIB2::GIO + DEFINES + QT_NO_SIGNALS_SLOTS_KEYWORDS +) diff --git a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp new file mode 100644 index 0000000000..0b45eb9ce3 --- /dev/null +++ b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2021 Ilya Fedin <fedin-ilja2010@ya.ru> +// 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 <QtCore/qglobal.h> +#include <QtCore/private/qobject_p.h> + +#include <gio/gio.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoGlib) +Q_LOGGING_CATEGORY(lcNetInfoGlib, "qt.network.info.glib"); + +namespace { +QNetworkInformation::Reachability reachabilityFromGNetworkConnectivity(GNetworkConnectivity connectivity) +{ + switch (connectivity) { + case G_NETWORK_CONNECTIVITY_LOCAL: + return QNetworkInformation::Reachability::Disconnected; + case G_NETWORK_CONNECTIVITY_LIMITED: + case G_NETWORK_CONNECTIVITY_PORTAL: + return QNetworkInformation::Reachability::Site; + case G_NETWORK_CONNECTIVITY_FULL: + return QNetworkInformation::Reachability::Online; + } + return QNetworkInformation::Reachability::Unknown; +} +} + +static QString backendName = QStringLiteral("glib"); + +class QGlibNetworkInformationBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + QGlibNetworkInformationBackend(); + ~QGlibNetworkInformationBackend(); + + QString name() const override { return backendName; } + QNetworkInformation::Features featuresSupported() const override + { + if (!isValid()) + return {}; + return featuresSupportedStatic(); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + using Feature = QNetworkInformation::Feature; + return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal + | Feature::Metered); + } + + bool isValid() const; + +private: + Q_DISABLE_COPY_MOVE(QGlibNetworkInformationBackend) + + static void updateConnectivity(QGlibNetworkInformationBackend *backend); + static void updateMetered(QGlibNetworkInformationBackend *backend); + + GNetworkMonitor *networkMonitor = nullptr; + gulong connectivityHandlerId = 0; + gulong meteredHandlerId = 0; +}; + +class QGlibNetworkInformationBackendFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QNetworkInformationBackendFactory_iid) + Q_INTERFACES(QNetworkInformationBackendFactory) +public: + QGlibNetworkInformationBackendFactory() = default; + ~QGlibNetworkInformationBackendFactory() = default; + QString name() const override { return backendName; } + QNetworkInformation::Features featuresSupported() const override + { + return QGlibNetworkInformationBackend::featuresSupportedStatic(); + } + + QNetworkInformationBackend *create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + auto backend = new QGlibNetworkInformationBackend(); + if (!backend->isValid()) + delete std::exchange(backend, nullptr); + return backend; + } +private: + Q_DISABLE_COPY_MOVE(QGlibNetworkInformationBackendFactory) +}; + +QGlibNetworkInformationBackend::QGlibNetworkInformationBackend() +: networkMonitor(g_network_monitor_get_default()) +{ + updateConnectivity(this); + updateMetered(this); + + connectivityHandlerId = g_signal_connect_swapped(networkMonitor, "notify::connectivity", + G_CALLBACK(updateConnectivity), this); + + meteredHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-metered", + G_CALLBACK(updateMetered), this); +} + +QGlibNetworkInformationBackend::~QGlibNetworkInformationBackend() +{ + g_signal_handler_disconnect(networkMonitor, meteredHandlerId); + g_signal_handler_disconnect(networkMonitor, connectivityHandlerId); +} + +bool QGlibNetworkInformationBackend::isValid() const +{ + return QLatin1StringView(G_OBJECT_TYPE_NAME(networkMonitor)) != "GNetworkMonitorBase"_L1; +} + +void QGlibNetworkInformationBackend::updateConnectivity(QGlibNetworkInformationBackend *backend) +{ + const auto connectivityState = g_network_monitor_get_connectivity(backend->networkMonitor); + const bool behindPortal = (connectivityState == G_NETWORK_CONNECTIVITY_PORTAL); + backend->setReachability(reachabilityFromGNetworkConnectivity(connectivityState)); + backend->setBehindCaptivePortal(behindPortal); +} + +void QGlibNetworkInformationBackend::updateMetered(QGlibNetworkInformationBackend *backend) +{ + backend->setMetered(g_network_monitor_get_network_metered(backend->networkMonitor)); +} + +QT_END_NAMESPACE + +#include "qglibnetworkinformationbackend.moc" 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" diff --git a/src/plugins/networkinformation/networkmanager/CMakeLists.txt b/src/plugins/networkinformation/networkmanager/CMakeLists.txt new file mode 100644 index 0000000000..9d76dbe7b4 --- /dev/null +++ b/src/plugins/networkinformation/networkmanager/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QNetworkManagerNetworkInformationPlugin + OUTPUT_NAME qnetworkmanager + CLASS_NAME QNetworkManagerNetworkInformationBackendFactory + PLUGIN_TYPE networkinformation + DEFAULT_IF LINUX + SOURCES + qnetworkmanagernetworkinformationbackend.h + qnetworkmanagernetworkinformationbackend.cpp + qnetworkmanagerservice.h + qnetworkmanagerservice.cpp + LIBRARIES + Qt::DBus + Qt::NetworkPrivate +) diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp new file mode 100644 index 0000000000..f583d1dcf6 --- /dev/null +++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp @@ -0,0 +1,181 @@ +// 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 "qnetworkmanagernetworkinformationbackend.h" + +#include <QtCore/qglobal.h> +#include <QtCore/private/qobject_p.h> + +#include <QtDBus/qdbusmessage.h> + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoNLM) +Q_LOGGING_CATEGORY(lcNetInfoNM, "qt.network.info.networkmanager"); + +namespace { +QNetworkInformation::Reachability reachabilityFromNMState(QNetworkManagerInterface::NMState state) +{ + switch (state) { + case QNetworkManagerInterface::NM_STATE_UNKNOWN: + case QNetworkManagerInterface::NM_STATE_ASLEEP: + case QNetworkManagerInterface::NM_STATE_CONNECTING: + return QNetworkInformation::Reachability::Unknown; + case QNetworkManagerInterface::NM_STATE_DISCONNECTING: // No point in starting new connections: + case QNetworkManagerInterface::NM_STATE_DISCONNECTED: + return QNetworkInformation::Reachability::Disconnected; + case QNetworkManagerInterface::NM_STATE_CONNECTED_LOCAL: + return QNetworkInformation::Reachability::Local; + case QNetworkManagerInterface::NM_STATE_CONNECTED_SITE: + return QNetworkInformation::Reachability::Site; + case QNetworkManagerInterface::NM_STATE_CONNECTED_GLOBAL: + return QNetworkInformation::Reachability::Online; + } + return QNetworkInformation::Reachability::Unknown; +} + +QNetworkInformation::TransportMedium +transportMediumFromDeviceType(QNetworkManagerInterface::NMDeviceType type) +{ + switch (type) { + case QNetworkManagerInterface::NM_DEVICE_TYPE_ETHERNET: + return QNetworkInformation::TransportMedium::Ethernet; + case QNetworkManagerInterface::NM_DEVICE_TYPE_WIFI: + return QNetworkInformation::TransportMedium::WiFi; + case QNetworkManagerInterface::NM_DEVICE_TYPE_BT: + return QNetworkInformation::TransportMedium::Bluetooth; + case QNetworkManagerInterface::NM_DEVICE_TYPE_MODEM: + return QNetworkInformation::TransportMedium::Cellular; + + case QNetworkManagerInterface::NM_DEVICE_TYPE_UNKNOWN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_GENERIC: + case QNetworkManagerInterface::NM_DEVICE_TYPE_UNUSED1: + case QNetworkManagerInterface::NM_DEVICE_TYPE_UNUSED2: + case QNetworkManagerInterface::NM_DEVICE_TYPE_OLPC_MESH: + case QNetworkManagerInterface::NM_DEVICE_TYPE_WIMAX: + case QNetworkManagerInterface::NM_DEVICE_TYPE_INFINIBAND: + case QNetworkManagerInterface::NM_DEVICE_TYPE_BOND: + case QNetworkManagerInterface::NM_DEVICE_TYPE_VLAN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_ADSL: + case QNetworkManagerInterface::NM_DEVICE_TYPE_BRIDGE: + case QNetworkManagerInterface::NM_DEVICE_TYPE_TEAM: + case QNetworkManagerInterface::NM_DEVICE_TYPE_TUN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_IP_TUNNEL: + case QNetworkManagerInterface::NM_DEVICE_TYPE_MACVLAN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_VXLAN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_VETH: + case QNetworkManagerInterface::NM_DEVICE_TYPE_MACSEC: + case QNetworkManagerInterface::NM_DEVICE_TYPE_DUMMY: + case QNetworkManagerInterface::NM_DEVICE_TYPE_PPP: + case QNetworkManagerInterface::NM_DEVICE_TYPE_OVS_INTERFACE: + case QNetworkManagerInterface::NM_DEVICE_TYPE_OVS_PORT: + case QNetworkManagerInterface::NM_DEVICE_TYPE_OVS_BRIDGE: + case QNetworkManagerInterface::NM_DEVICE_TYPE_WPAN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_6LOWPAN: + case QNetworkManagerInterface::NM_DEVICE_TYPE_WIREGUARD: + case QNetworkManagerInterface::NM_DEVICE_TYPE_WIFI_P2P: + case QNetworkManagerInterface::NM_DEVICE_TYPE_VRF: + break; + } + // While the list is exhaustive of the enum there can be additional + // entries added in NetworkManager that isn't listed here + return QNetworkInformation::TransportMedium::Unknown; +} + +bool isMeteredFromNMMetered(QNetworkManagerInterface::NMMetered metered) +{ + switch (metered) { + case QNetworkManagerInterface::NM_METERED_YES: + case QNetworkManagerInterface::NM_METERED_GUESS_YES: + return true; + case QNetworkManagerInterface::NM_METERED_NO: + case QNetworkManagerInterface::NM_METERED_GUESS_NO: + case QNetworkManagerInterface::NM_METERED_UNKNOWN: + return false; + } + Q_UNREACHABLE_RETURN(false); +} +} // unnamed namespace + +static QString backendName() +{ + return QStringView(QNetworkInformationBackend::PluginNames + [QNetworkInformationBackend::PluginNamesLinuxIndex]).toString(); +} + +QString QNetworkManagerNetworkInformationBackend::name() const +{ + return backendName(); +} + +class QNetworkManagerNetworkInformationBackendFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QNetworkInformationBackendFactory_iid) + Q_INTERFACES(QNetworkInformationBackendFactory) +public: + QNetworkManagerNetworkInformationBackendFactory() = default; + ~QNetworkManagerNetworkInformationBackendFactory() = default; + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + if (!QNetworkManagerInterfaceBase::networkManagerAvailable()) + return {}; + return QNetworkManagerNetworkInformationBackend::featuresSupportedStatic(); + } + + QNetworkInformationBackend *create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + if (!QNetworkManagerInterfaceBase::networkManagerAvailable()) + return nullptr; + auto backend = new QNetworkManagerNetworkInformationBackend(); + if (!backend->isValid()) + delete std::exchange(backend, nullptr); + return backend; + } +private: + Q_DISABLE_COPY_MOVE(QNetworkManagerNetworkInformationBackendFactory) +}; + +QNetworkManagerNetworkInformationBackend::QNetworkManagerNetworkInformationBackend() +{ + if (!iface.isValid()) + return; + iface.setBackend(this); + onStateChanged(iface.state()); + onConnectivityChanged(iface.connectivityState()); + onDeviceTypeChanged(iface.deviceType()); + onMeteredChanged(iface.meteredState()); +} + +void QNetworkManagerNetworkInformationBackend::onStateChanged( + QNetworkManagerInterface::NMState newState) +{ + setReachability(reachabilityFromNMState(newState)); +} + +void QNetworkManagerNetworkInformationBackend::onConnectivityChanged( + QNetworkManagerInterface::NMConnectivityState connectivityState) +{ + const bool behindPortal = + (connectivityState == QNetworkManagerInterface::NM_CONNECTIVITY_PORTAL); + setBehindCaptivePortal(behindPortal); +} + +void QNetworkManagerNetworkInformationBackend::onDeviceTypeChanged( + QNetworkManagerInterface::NMDeviceType newDevice) +{ + setTransportMedium(transportMediumFromDeviceType(newDevice)); +} + +void QNetworkManagerNetworkInformationBackend::onMeteredChanged( + QNetworkManagerInterface::NMMetered metered) +{ + setMetered(isMeteredFromNMMetered(metered)); +}; + +QT_END_NAMESPACE + +#include "qnetworkmanagernetworkinformationbackend.moc" +#include "moc_qnetworkmanagernetworkinformationbackend.cpp" diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h new file mode 100644 index 0000000000..3b60f0949c --- /dev/null +++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h @@ -0,0 +1,60 @@ +// 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 QNETWORKMANAGERINFORMATIONBACKEND_H +#define QNETWORKMANAGERINFORMATIONBACKEND_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qnetworkinformation_p.h> +#include "qnetworkmanagerservice.h" + +QT_BEGIN_NAMESPACE + +class QNetworkManagerNetworkInformationBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + QNetworkManagerNetworkInformationBackend(); + ~QNetworkManagerNetworkInformationBackend() = default; + + QString name() const override; + QNetworkInformation::Features featuresSupported() const override + { + if (!isValid()) + return {}; + return featuresSupportedStatic(); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + using Feature = QNetworkInformation::Feature; + return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal + | Feature::TransportMedium | Feature::Metered); + } + + bool isValid() const { return iface.isValid(); } + + void onStateChanged(QNetworkManagerInterface::NMState state); + void onConnectivityChanged(QNetworkManagerInterface::NMConnectivityState connectivityState); + void onDeviceTypeChanged(QNetworkManagerInterface::NMDeviceType deviceType); + void onMeteredChanged(QNetworkManagerInterface::NMMetered metered); + +private: + Q_DISABLE_COPY_MOVE(QNetworkManagerNetworkInformationBackend) + + QNetworkManagerInterface iface; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp new file mode 100644 index 0000000000..c055555cac --- /dev/null +++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp @@ -0,0 +1,220 @@ +// 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 "qnetworkmanagerservice.h" +#include "qnetworkmanagernetworkinformationbackend.h" + +#include <QObject> +#include <QList> +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusError> +#include <QtDBus/QDBusInterface> +#include <QtDBus/QDBusMessage> +#include <QtDBus/QDBusReply> +#include <QtDBus/QDBusPendingCallWatcher> +#include <QtDBus/QDBusObjectPath> +#include <QtDBus/QDBusPendingCall> + +#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"_L1 + +#define NM_DBUS_INTERFACE "org.freedesktop.NetworkManager" +#define NM_DBUS_SERVICE NM_DBUS_INTERFACE ""_L1 + +#define NM_DBUS_PATH "/org/freedesktop/NetworkManager"_L1 +#define NM_CONNECTION_DBUS_INTERFACE NM_DBUS_SERVICE ".Connection.Active"_L1 +#define NM_DEVICE_DBUS_INTERFACE NM_DBUS_SERVICE ".Device"_L1 + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { +constexpr QLatin1StringView propertiesChangedKey = "PropertiesChanged"_L1; +const QString &stateKey() +{ + static auto key = u"State"_s; + return key; +} +const QString &connectivityKey() +{ + static auto key = u"Connectivity"_s; + return key; +} +const QString &primaryConnectionKey() +{ + static auto key = u"PrimaryConnection"_s; + return key; +} +} + +QNetworkManagerInterfaceBase::QNetworkManagerInterfaceBase(QObject *parent) + : QDBusAbstractInterface(NM_DBUS_SERVICE, NM_DBUS_PATH, + NM_DBUS_INTERFACE, QDBusConnection::systemBus(), parent) +{ +} + +bool QNetworkManagerInterfaceBase::networkManagerAvailable() +{ + return QNetworkManagerInterfaceBase().isValid(); +} + +QNetworkManagerInterface::QNetworkManagerInterface(QObject *parent) + : QNetworkManagerInterfaceBase(parent) +{ + if (!QDBusAbstractInterface::isValid()) + return; + + PropertiesDBusInterface managerPropertiesInterface( + NM_DBUS_SERVICE, NM_DBUS_PATH, DBUS_PROPERTIES_INTERFACE, + QDBusConnection::systemBus()); + QList<QVariant> argumentList; + argumentList << NM_DBUS_SERVICE; + QDBusPendingReply<QVariantMap> propsReply = managerPropertiesInterface.callWithArgumentList( + QDBus::Block, "GetAll"_L1, argumentList); + if (propsReply.isError()) { + validDBusConnection = false; + if (auto error = propsReply.error(); error.type() != QDBusError::AccessDenied) + qWarning() << "Failed to query NetworkManager properties:" << error.message(); + return; + } + propertyMap = propsReply.value(); + + validDBusConnection = QDBusConnection::systemBus().connect(NM_DBUS_SERVICE, NM_DBUS_PATH, + DBUS_PROPERTIES_INTERFACE, propertiesChangedKey, this, + SLOT(setProperties(QString,QMap<QString,QVariant>,QList<QString>))); +} + +QNetworkManagerInterface::~QNetworkManagerInterface() +{ + QDBusConnection::systemBus().disconnect(NM_DBUS_SERVICE, NM_DBUS_PATH, + DBUS_PROPERTIES_INTERFACE, propertiesChangedKey, this, + SLOT(setProperties(QString,QMap<QString,QVariant>,QList<QString>))); +} + +QNetworkManagerInterface::NMState QNetworkManagerInterface::state() const +{ + auto it = propertyMap.constFind(stateKey()); + if (it != propertyMap.cend()) + return static_cast<QNetworkManagerInterface::NMState>(it->toUInt()); + return QNetworkManagerInterface::NM_STATE_UNKNOWN; +} + +QNetworkManagerInterface::NMConnectivityState QNetworkManagerInterface::connectivityState() const +{ + auto it = propertyMap.constFind(connectivityKey()); + if (it != propertyMap.cend()) + return static_cast<NMConnectivityState>(it->toUInt()); + return QNetworkManagerInterface::NM_CONNECTIVITY_UNKNOWN; +} + +static std::optional<QDBusInterface> getPrimaryDevice(const QDBusObjectPath &devicePath) +{ + const QDBusInterface connection(NM_DBUS_SERVICE, devicePath.path(), + NM_CONNECTION_DBUS_INTERFACE, QDBusConnection::systemBus()); + if (!connection.isValid()) + return std::nullopt; + + const auto devicePaths = connection.property("Devices").value<QList<QDBusObjectPath>>(); + if (devicePaths.isEmpty()) + return std::nullopt; + + const QDBusObjectPath primaryDevicePath = devicePaths.front(); + return std::make_optional<QDBusInterface>(NM_DBUS_SERVICE, primaryDevicePath.path(), + NM_DEVICE_DBUS_INTERFACE, + QDBusConnection::systemBus()); +} + +std::optional<QDBusObjectPath> QNetworkManagerInterface::primaryConnectionDevicePath() const +{ + auto it = propertyMap.constFind(primaryConnectionKey()); + if (it != propertyMap.cend()) + return it->value<QDBusObjectPath>(); + return std::nullopt; +} + +auto QNetworkManagerInterface::deviceType() const -> NMDeviceType +{ + if (const auto path = primaryConnectionDevicePath()) + return extractDeviceType(*path); + return NM_DEVICE_TYPE_UNKNOWN; +} + +auto QNetworkManagerInterface::meteredState() const -> NMMetered +{ + if (const auto path = primaryConnectionDevicePath()) + return extractDeviceMetered(*path); + return NM_METERED_UNKNOWN; +} + +auto QNetworkManagerInterface::extractDeviceType(const QDBusObjectPath &devicePath) const + -> NMDeviceType +{ + const auto primaryDevice = getPrimaryDevice(devicePath); + if (!primaryDevice) + return NM_DEVICE_TYPE_UNKNOWN; + if (!primaryDevice->isValid()) + return NM_DEVICE_TYPE_UNKNOWN; + const QVariant deviceType = primaryDevice->property("DeviceType"); + if (!deviceType.isValid()) + return NM_DEVICE_TYPE_UNKNOWN; + return static_cast<NMDeviceType>(deviceType.toUInt()); +} + +auto QNetworkManagerInterface::extractDeviceMetered(const QDBusObjectPath &devicePath) const + -> NMMetered +{ + const auto primaryDevice = getPrimaryDevice(devicePath); + if (!primaryDevice) + return NM_METERED_UNKNOWN; + if (!primaryDevice->isValid()) + return NM_METERED_UNKNOWN; + const QVariant metered = primaryDevice->property("Metered"); + if (!metered.isValid()) + return NM_METERED_UNKNOWN; + return static_cast<NMMetered>(metered.toUInt()); +} + +void QNetworkManagerInterface::setBackend(QNetworkManagerNetworkInformationBackend *ourBackend) +{ + backend = ourBackend; +} + +void QNetworkManagerInterface::setProperties(const QString &interfaceName, + const QMap<QString, QVariant> &map, + const QStringList &invalidatedProperties) +{ + Q_UNUSED(interfaceName); + Q_UNUSED(invalidatedProperties); + + for (auto i = map.cbegin(), end = map.cend(); i != end; ++i) { + bool valueChanged = true; + + auto it = propertyMap.lowerBound(i.key()); + if (it != propertyMap.end() && it.key() == i.key()) { + valueChanged = (it.value() != i.value()); + *it = *i; + } else { + propertyMap.insert(it, i.key(), i.value()); + } + + if (valueChanged) { + if (i.key() == stateKey()) { + quint32 state = i.value().toUInt(); + backend->onStateChanged(static_cast<NMState>(state)); + } else if (i.key() == connectivityKey()) { + quint32 state = i.value().toUInt(); + backend->onConnectivityChanged(static_cast<NMConnectivityState>(state)); + } else if (i.key() == primaryConnectionKey()) { + const QDBusObjectPath devicePath = i->value<QDBusObjectPath>(); + backend->onDeviceTypeChanged(extractDeviceType(devicePath)); + backend->onMeteredChanged(extractDeviceMetered(devicePath)); + } else if (i.key() == "Metered"_L1) { + backend->onMeteredChanged(static_cast<NMMetered>(i->toUInt())); + } + } + } +} + +QT_END_NAMESPACE + +#include "moc_qnetworkmanagerservice.cpp" diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h new file mode 100644 index 0000000000..5201e8485b --- /dev/null +++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h @@ -0,0 +1,173 @@ +// 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 QNETWORKMANAGERSERVICE_H +#define QNETWORKMANAGERSERVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvariant.h> +#include <QtCore/qmap.h> +#include <QtDBus/qdbusabstractinterface.h> + +#include <optional> + +// Matches 'NMDeviceState' from https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html +enum NMDeviceState { + NM_DEVICE_STATE_UNKNOWN = 0, + NM_DEVICE_STATE_UNMANAGED = 10, + NM_DEVICE_STATE_UNAVAILABLE = 20, + NM_DEVICE_STATE_DISCONNECTED = 30, + NM_DEVICE_STATE_PREPARE = 40, + NM_DEVICE_STATE_CONFIG = 50, + NM_DEVICE_STATE_NEED_AUTH = 60, + NM_DEVICE_STATE_IP_CONFIG = 70, + NM_DEVICE_STATE_ACTIVATED = 100, + NM_DEVICE_STATE_DEACTIVATING = 110, + NM_DEVICE_STATE_FAILED = 120 +}; + +QT_BEGIN_NAMESPACE + +class QDBusObjectPath; +class QNetworkManagerNetworkInformationBackend; + +// This tiny class exists for the purpose of seeing if NetworkManager is available without +// initializing everything the derived/full class needs. +class QNetworkManagerInterfaceBase : public QDBusAbstractInterface +{ + Q_OBJECT +public: + explicit QNetworkManagerInterfaceBase(QObject *parent = nullptr); + ~QNetworkManagerInterfaceBase() = default; + + static bool networkManagerAvailable(); + +private: + Q_DISABLE_COPY_MOVE(QNetworkManagerInterfaceBase) +}; + +class QNetworkManagerInterface final : public QNetworkManagerInterfaceBase +{ + Q_OBJECT + +public: + // Matches 'NMState' from https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html + enum NMState { + NM_STATE_UNKNOWN = 0, + NM_STATE_ASLEEP = 10, + NM_STATE_DISCONNECTED = 20, + NM_STATE_DISCONNECTING = 30, + NM_STATE_CONNECTING = 40, + NM_STATE_CONNECTED_LOCAL = 50, + NM_STATE_CONNECTED_SITE = 60, + NM_STATE_CONNECTED_GLOBAL = 70 + }; + Q_ENUM(NMState); + // Matches 'NMConnectivityState' from + // https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMConnectivityState + enum NMConnectivityState { + NM_CONNECTIVITY_UNKNOWN = 0, + NM_CONNECTIVITY_NONE = 1, + NM_CONNECTIVITY_PORTAL = 2, + NM_CONNECTIVITY_LIMITED = 3, + NM_CONNECTIVITY_FULL = 4, + }; + Q_ENUM(NMConnectivityState); + // Matches 'NMDeviceType' from + // https://developer-old.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType + enum NMDeviceType { + NM_DEVICE_TYPE_UNKNOWN = 0, + NM_DEVICE_TYPE_GENERIC = 14, + NM_DEVICE_TYPE_ETHERNET = 1, + NM_DEVICE_TYPE_WIFI = 2, + NM_DEVICE_TYPE_UNUSED1 = 3, + NM_DEVICE_TYPE_UNUSED2 = 4, + NM_DEVICE_TYPE_BT = 5, + NM_DEVICE_TYPE_OLPC_MESH = 6, + NM_DEVICE_TYPE_WIMAX = 7, + NM_DEVICE_TYPE_MODEM = 8, + NM_DEVICE_TYPE_INFINIBAND = 9, + NM_DEVICE_TYPE_BOND = 10, + NM_DEVICE_TYPE_VLAN = 11, + NM_DEVICE_TYPE_ADSL = 12, + NM_DEVICE_TYPE_BRIDGE = 13, + NM_DEVICE_TYPE_TEAM = 15, + NM_DEVICE_TYPE_TUN = 16, + NM_DEVICE_TYPE_IP_TUNNEL = 17, + NM_DEVICE_TYPE_MACVLAN = 18, + NM_DEVICE_TYPE_VXLAN = 19, + NM_DEVICE_TYPE_VETH = 20, + NM_DEVICE_TYPE_MACSEC = 21, + NM_DEVICE_TYPE_DUMMY = 22, + NM_DEVICE_TYPE_PPP = 23, + NM_DEVICE_TYPE_OVS_INTERFACE = 24, + NM_DEVICE_TYPE_OVS_PORT = 25, + NM_DEVICE_TYPE_OVS_BRIDGE = 26, + NM_DEVICE_TYPE_WPAN = 27, + NM_DEVICE_TYPE_6LOWPAN = 28, + NM_DEVICE_TYPE_WIREGUARD = 29, + NM_DEVICE_TYPE_WIFI_P2P = 30, + NM_DEVICE_TYPE_VRF = 31, + }; + // Matches 'NMMetered' from + // https://developer-old.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMMetered + enum NMMetered { + NM_METERED_UNKNOWN, + NM_METERED_YES, + NM_METERED_NO, + NM_METERED_GUESS_YES, + NM_METERED_GUESS_NO, + }; + + explicit QNetworkManagerInterface(QObject *parent = nullptr); + ~QNetworkManagerInterface(); + + void setBackend(QNetworkManagerNetworkInformationBackend *ourBackend); + + NMState state() const; + NMConnectivityState connectivityState() const; + NMDeviceType deviceType() const; + NMMetered meteredState() const; + + bool isValid() const { return QDBusAbstractInterface::isValid() && validDBusConnection; } + +private Q_SLOTS: + void setProperties(const QString &interfaceName, const QMap<QString, QVariant> &map, + const QStringList &invalidatedProperties); + +private: + Q_DISABLE_COPY_MOVE(QNetworkManagerInterface) + + NMDeviceType extractDeviceType(const QDBusObjectPath &devicePath) const; + NMMetered extractDeviceMetered(const QDBusObjectPath &devicePath) const; + + std::optional<QDBusObjectPath> primaryConnectionDevicePath() const; + + QVariantMap propertyMap; + QNetworkManagerNetworkInformationBackend *backend = nullptr; + bool validDBusConnection = true; +}; + +class PropertiesDBusInterface : public QDBusAbstractInterface +{ +public: + PropertiesDBusInterface(const QString &service, const QString &path, const QString &interface, + const QDBusConnection &connection, QObject *parent = nullptr) + : QDBusAbstractInterface(service, path, interface.toLatin1().data(), connection, parent) + { + } +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt b/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt new file mode 100644 index 0000000000..a939ab4405 --- /dev/null +++ b/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QSCNetworkReachabilityNetworkInformationPlugin + OUTPUT_NAME qscnetworkreachability + CLASS_NAME QSCNetworkReachabilityNetworkInformationBackendFactory + PLUGIN_TYPE networkinformation + DEFAULT_IF APPLE + SOURCES + qscnetworkreachabilitynetworkinformationbackend.mm + LIBRARIES + Qt::NetworkPrivate + ${FWSystemConfiguration} +) diff --git a/src/plugins/networkinformation/scnetworkreachability/qscnetworkreachabilitynetworkinformationbackend.mm b/src/plugins/networkinformation/scnetworkreachability/qscnetworkreachabilitynetworkinformationbackend.mm new file mode 100644 index 0000000000..d1f3cb41d4 --- /dev/null +++ b/src/plugins/networkinformation/scnetworkreachability/qscnetworkreachabilitynetworkinformationbackend.mm @@ -0,0 +1,158 @@ +// 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 <QtNetwork/private/qnetconmonitor_p.h> + +#include <QtCore/qglobal.h> +#include <QtCore/private/qobject_p.h> + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoSCR) +Q_LOGGING_CATEGORY(lcNetInfoSCR, "qt.network.info.scnetworkreachability"); + +static QString backendName() +{ + return QString::fromUtf16(QNetworkInformationBackend::PluginNames + [QNetworkInformationBackend::PluginNamesAppleIndex]); +} + +class QSCNetworkReachabilityNetworkInformationBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + QSCNetworkReachabilityNetworkInformationBackend(); + ~QSCNetworkReachabilityNetworkInformationBackend(); + + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + return featuresSupportedStatic(); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + return QNetworkInformation::Features(QNetworkInformation::Feature::Reachability +#ifdef QT_PLATFORM_UIKIT + | QNetworkInformation::Feature::TransportMedium +#endif + ); + } + +private Q_SLOTS: + void reachabilityChanged(bool isOnline); + +#ifdef QT_PLATFORM_UIKIT + void isWwanChanged(bool isOnline); +#endif + +private: + Q_DISABLE_COPY_MOVE(QSCNetworkReachabilityNetworkInformationBackend); + + QNetworkConnectionMonitor ipv4Probe; + QNetworkConnectionMonitor ipv6Probe; +}; + +class QSCNetworkReachabilityNetworkInformationBackendFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QNetworkInformationBackendFactory_iid) + Q_INTERFACES(QNetworkInformationBackendFactory) +public: + QSCNetworkReachabilityNetworkInformationBackendFactory() = default; + ~QSCNetworkReachabilityNetworkInformationBackendFactory() = default; + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + return QSCNetworkReachabilityNetworkInformationBackend::featuresSupportedStatic(); + } + + QNetworkInformationBackend *create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + return new QSCNetworkReachabilityNetworkInformationBackend(); + } + +private: + Q_DISABLE_COPY_MOVE(QSCNetworkReachabilityNetworkInformationBackendFactory); +}; + +QSCNetworkReachabilityNetworkInformationBackend::QSCNetworkReachabilityNetworkInformationBackend() +{ + bool isOnline = false; +#ifdef QT_PLATFORM_UIKIT + bool isWwan = false; +#endif + if (ipv4Probe.setTargets(QHostAddress::AnyIPv4, {})) { + // We manage to create SCNetworkReachabilityRef for IPv4, let's + // read the last known state then! + isOnline |= ipv4Probe.isReachable(); +#ifdef QT_PLATFORM_UIKIT + isWwan |= ipv4Probe.isWwan(); +#endif + ipv4Probe.startMonitoring(); + } + + if (ipv6Probe.setTargets(QHostAddress::AnyIPv6, {})) { + // We manage to create SCNetworkReachability ref for IPv6, let's + // read the last known state then! + isOnline |= ipv6Probe.isReachable(); +#ifdef QT_PLATFORM_UIKIT + isWwan |= ipv6Probe.isWwan(); +#endif + ipv6Probe.startMonitoring(); + } + reachabilityChanged(isOnline); +#ifdef QT_PLATFORM_UIKIT + isWwanChanged(isWwan); +#endif + + connect(&ipv4Probe, &QNetworkConnectionMonitor::reachabilityChanged, this, + &QSCNetworkReachabilityNetworkInformationBackend::reachabilityChanged, + Qt::QueuedConnection); + connect(&ipv6Probe, &QNetworkConnectionMonitor::reachabilityChanged, this, + &QSCNetworkReachabilityNetworkInformationBackend::reachabilityChanged, + Qt::QueuedConnection); + +#ifdef QT_PLATFORM_UIKIT + connect(&ipv4Probe, &QNetworkConnectionMonitor::isWwanChanged, this, + &QSCNetworkReachabilityNetworkInformationBackend::isWwanChanged, + Qt::QueuedConnection); + connect(&ipv6Probe, &QNetworkConnectionMonitor::isWwanChanged, this, + &QSCNetworkReachabilityNetworkInformationBackend::isWwanChanged, + Qt::QueuedConnection); +#endif +} + +QSCNetworkReachabilityNetworkInformationBackend::~QSCNetworkReachabilityNetworkInformationBackend() +{ +} + +void QSCNetworkReachabilityNetworkInformationBackend::reachabilityChanged(bool isOnline) +{ + setReachability(isOnline ? QNetworkInformation::Reachability::Online + : QNetworkInformation::Reachability::Disconnected); +} + +#ifdef QT_PLATFORM_UIKIT +void QSCNetworkReachabilityNetworkInformationBackend::isWwanChanged(bool isWwan) +{ + // The reachability API from Apple only has one entry regarding transport medium: "IsWWAN"[0]. + // This is _serviceable_ on iOS where the only other credible options are "WLAN" or + // "Disconnected". But on macOS you could be connected by Ethernet as well, so how would that be + // reported? It doesn't matter anyway since "IsWWAN" is not available on macOS. + // [0]: https://developer.apple.com/documentation/systemconfiguration/scnetworkreachabilityflags/kscnetworkreachabilityflagsiswwan?language=objc + if (reachability() == QNetworkInformation::Reachability::Disconnected) { + setTransportMedium(QNetworkInformation::TransportMedium::Unknown); + } else { + setTransportMedium(isWwan ? QNetworkInformation::TransportMedium::Cellular + : QNetworkInformation::TransportMedium::WiFi); + } +} +#endif + +QT_END_NAMESPACE + +#include "qscnetworkreachabilitynetworkinformationbackend.moc" |