Commit 5e55e45d authored by Jan Grulich's avatar Jan Grulich
Browse files

Add option to easily configure and start a hotspot

Summary:
This adds a simple checkbox, which will create and stop hotspot. Hotspot can be also
configured from the KCM, where users can choose a name and password.

BUG:  413323

{F7857835}

Reviewers: #plasma, ngraham, #vdg, apol

Reviewed By: #vdg, apol

Subscribers: meven, cblack, alexde, mthw, apol, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D26392
parent 3eb9f7e6
......@@ -21,6 +21,7 @@
import QtQuick 2.2
import QtQuick.Layouts 1.2
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import org.kde.kquickcontrolsaddons 2.0
......@@ -60,6 +61,10 @@ GridLayout {
}
}
PlasmaNM.Configuration {
id: configuration
}
Row {
Layout.fillWidth: true
......@@ -124,19 +129,79 @@ GridLayout {
Row {
Layout.column: 1
PlasmaComponents.ToolButton {
PlasmaComponents3.ToolButton {
id: hotspotButton
icon {
height: 16
width: 16
name: "network-wireless-on"
}
checkable: true
text: i18n("Hotspot")
visible: handler.hotspotSupported
onClicked: {
if (configuration.hotspotConnectionPath) {
checked = false
handler.stopHotspot()
} else {
checked = true
handler.createHotspot()
}
}
PlasmaComponents3.ToolTip {
id: tooltip
}
Connections {
target: handler
onHotspotCreated: {
hotspotButton.checked = true
tooltip.text = i18n("Disable Hotspot")
}
onHotspotDisabled: {
hotspotButton.checked = false
tooltip.text = i18n("Create Hotspot")
}
}
Component.onCompleted: {
checked = configuration.hotspotConnectionPath
tooltip.text = configuration.hotspotConnectionPath ? i18n("Disable Hotspot") : i18n("Create Hotspot")
}
}
PlasmaComponents3.ToolButton {
id: searchToggleButton
iconSource: "search"
tooltip: i18ndc("plasma-nm", "button tooltip", "Search the connections")
icon {
height: 16
width: 16
name: "search"
}
checkable: true
PlasmaComponents3.ToolTip {
text: i18ndc("plasma-nm", "button tooltip", "Search the connections")
}
}
PlasmaComponents.ToolButton {
PlasmaComponents3.ToolButton {
id: openEditorButton
iconSource: "configure"
tooltip: i18n("Configure network connections...")
icon {
height: 16
width: 16
name: "configure"
}
visible: mainWindow.kcmAuthorized
PlasmaComponents3.ToolTip {
text: i18n("Configure network connections...")
}
onClicked: {
KCMShell.open(mainWindow.kcm)
}
......
......@@ -77,6 +77,37 @@ KCMNetworkmanagement::KCMNetworkmanagement(QWidget *parent, const QVariantList &
kdeclarative.setupEngine(m_ui->connectionView->engine());
kdeclarative.setupContext();
// Check if we can use AP mode to identify security type
bool useApMode = false;
bool foundInactive = false;
NetworkManager::WirelessDevice::Ptr wifiDev;
for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
if (device->type() == NetworkManager::Device::Wifi) {
wifiDev = device.objectCast<NetworkManager::WirelessDevice>();
if (wifiDev) {
if (!wifiDev->isActive()) {
foundInactive = true;
} else {
// Prefer previous device if it was inactive
if (foundInactive) {
break;
}
}
if (wifiDev->wirelessCapabilities().testFlag(NetworkManager::WirelessDevice::ApCap)) {
useApMode = true;
}
// We prefer inactive wireless card with AP capabilities
if (foundInactive && useApMode) {
break;
}
}
}
}
m_ui->connectionView->setMinimumWidth(300);
m_ui->connectionView->rootContext()->setContextProperty("alternateBaseColor", mainWidget->palette().color(QPalette::Active, QPalette::AlternateBase));
m_ui->connectionView->rootContext()->setContextProperty("backgroundColor", mainWidget->palette().color(QPalette::Active, QPalette::Window));
......@@ -84,6 +115,7 @@ KCMNetworkmanagement::KCMNetworkmanagement(QWidget *parent, const QVariantList &
m_ui->connectionView->rootContext()->setContextProperty("highlightColor", mainWidget->palette().color(QPalette::Active, QPalette::Highlight));
m_ui->connectionView->rootContext()->setContextProperty("textColor", mainWidget->palette().color(QPalette::Active, QPalette::Text));
m_ui->connectionView->rootContext()->setContextProperty("connectionModified", false);
m_ui->connectionView->rootContext()->setContextProperty("useApMode", useApMode);
m_ui->connectionView->setClearColor(Qt::transparent);
m_ui->connectionView->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_ui->connectionView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_networkmanagement/qml/main.qml"))));
......
......@@ -49,19 +49,61 @@ Dialog {
anchors.right: parent.right
anchors.topMargin: units.gridUnit
Kirigami.Heading {
id: generalLabel
level: 2
text: i18n("General")
}
QQC2.CheckBox {
id: unlockModem
text: i18n("Ask for PIN on modem detection")
onClicked: okButton.enabled = true
onClicked: configurationChanged()
Component.onCompleted: checked = configuration.unlockModemOnDetection
}
QQC2.CheckBox {
id: manageVirtualConnections
text: i18n("Show virtual connections")
onClicked: okButton.enabled = true
onClicked: configurationChanged()
Component.onCompleted: checked = configuration.manageVirtualConnections
}
Kirigami.Heading {
id: hotspotLabel
level: 2
text: i18n("Hotspot")
Component.onCompleted: visible = handler.hotspotSupported
}
QQC2.TextField {
id: hotspotName
Kirigami.FormData.label: i18n("Hotspot name:")
onTextChanged: configurationChanged()
Component.onCompleted: {
text = configuration.hotspotName
visible = handler.hotspotSupported
}
}
QQC2.TextField {
id: hotspotPassword
Kirigami.FormData.label: i18n("Hotspot password:")
validator: RegExpValidator {
regExp: if (useApMode) {
/^$|^(?:.{8,64}){1}$/
} else {
/^$|^(?:.{5}|[0-9a-fA-F]{10}|.{13}|[0-9a-fA-F]{26}){1}$/
}
}
onAcceptableInputChanged: configurationChanged()
Component.onCompleted: {
text = configuration.hotspotPassword
visible = handler.hotspotSupported
}
}
}
Row {
......@@ -94,16 +136,28 @@ Dialog {
}
}
function configurationChanged() {
if (handler.hotspotSupported) {
okButton.enabled = hotspotPassword.acceptableInput && hotspotName.text
} else {
okButton.enabled = true
}
}
onVisibleChanged: {
if (visible) {
unlockModem.checked = configuration.unlockModemOnDetection
manageVirtualConnections.checked = configuration.manageVirtualConnections
hotspotName.text = configuration.hotspotName
hotspotPassword.text = configuration.hotspotPassword
}
}
onAccepted: {
configuration.unlockModemOnDetection = unlockModem.checked
configuration.manageVirtualConnections = manageVirtualConnections.checked
configuration.hotspotName = hotspotName.text
configuration.hotspotPassword = hotspotPassword.text
}
}
......@@ -56,6 +56,10 @@ Item {
sourceModel: connectionModel
}
PlasmaNM.Configuration {
id: configuration
}
QQC2.TextField {
id: searchField
......
......@@ -838,3 +838,9 @@ Name[zh_TW]=偵測到強制入口
Urgency=Low
IconName=dialog-password
Action=Popup
[Event/FailedToCreateHotspot]
Name=Failed to create hotspot
Urgency=Low
IconName=applications-internet
Action=Popup
......@@ -22,12 +22,15 @@
#include <KConfigGroup>
#include <KSharedConfig>
#include <KUser>
Q_GLOBAL_STATIC_WITH_ARGS(KSharedConfigPtr, config, (KSharedConfig::openConfig(QLatin1String("plasma-nm"))))
static bool propManageVirtualConnectionsInitialized = false;
static bool propManageVirtualConnections = false;
bool Configuration::unlockModemOnDetection()
{
KConfigGroup grp(*config, QLatin1String("General"));
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
return grp.readEntry(QLatin1String("UnlockModemOnDetection"), true);
......@@ -38,7 +41,8 @@ bool Configuration::unlockModemOnDetection()
void Configuration::setUnlockModemOnDetection(bool unlock)
{
KConfigGroup grp(*config, QLatin1String("General"));
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
grp.writeEntry(QLatin1String("UnlockModemOnDetection"), unlock);
......@@ -47,10 +51,19 @@ void Configuration::setUnlockModemOnDetection(bool unlock)
bool Configuration::manageVirtualConnections()
{
KConfigGroup grp(*config, QLatin1String("General"));
// Avoid reading from the config file over and over
if (propManageVirtualConnectionsInitialized) {
return propManageVirtualConnections;
}
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
return grp.readEntry(QLatin1String("ManageVirtualConnections"), false);
propManageVirtualConnections = grp.readEntry(QLatin1String("ManageVirtualConnections"), false);
propManageVirtualConnectionsInitialized = true;
return propManageVirtualConnections;
}
return true;
......@@ -58,10 +71,12 @@ bool Configuration::manageVirtualConnections()
void Configuration::setManageVirtualConnections(bool manage)
{
KConfigGroup grp(*config, QLatin1String("General"));
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
grp.writeEntry(QLatin1String("ManageVirtualConnections"), manage);
propManageVirtualConnections = manage;
}
}
......@@ -72,7 +87,8 @@ bool Configuration::airplaneModeEnabled()
const bool isWifiDisabled = !NetworkManager::isWirelessEnabled() || !NetworkManager::isWirelessHardwareEnabled();
const bool isWwanDisabled = !NetworkManager::isWwanEnabled() || !NetworkManager::isWwanHardwareEnabled();
KConfigGroup grp(*config, QLatin1String("General"));
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
if (grp.readEntry(QLatin1String("AirplaneModeEnabled"), false)) {
......@@ -90,16 +106,87 @@ bool Configuration::airplaneModeEnabled()
void Configuration::setAirplaneModeEnabled(bool enabled)
{
KConfigGroup grp(*config, QLatin1String("General"));
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
grp.writeEntry(QLatin1String("AirplaneModeEnabled"), enabled);
}
}
QString Configuration::hotspotName()
{
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
KUser currentUser;
const QString defaultName = currentUser.loginName() + QLatin1String("-hotspot");
if (grp.isValid()) {
return grp.readEntry(QLatin1String("HotspotName"), defaultName);
}
return defaultName;
}
void Configuration::setHotspotName(const QString &name)
{
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
grp.writeEntry(QLatin1String("HotspotName"), name);
}
}
QString Configuration::hotspotPassword()
{
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
return grp.readEntry(QLatin1String("HotspotPassword"), QString());
}
return QString();
}
void Configuration::setHotspotPassword(const QString &password)
{
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
grp.writeEntry(QLatin1String("HotspotPassword"), password);
}
}
QString Configuration::hotspotConnectionPath()
{
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
return grp.readEntry(QLatin1String("HotspotConnectionPath"), QString());
}
return QString();
}
void Configuration::setHotspotConnectionPath(const QString &path)
{
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
grp.writeEntry(QLatin1String("HotspotConnectionPath"), path);
}
}
bool Configuration::showPasswordDialog()
{
KConfigGroup grp(*config, QLatin1String("General"));
KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("plasma-nm"));
KConfigGroup grp(config, QLatin1String("General"));
if (grp.isValid()) {
return grp.readEntry(QLatin1String("ShowPasswordDialog"), true);
......
......@@ -30,6 +30,9 @@ class Q_DECL_EXPORT Configuration : public QObject
Q_PROPERTY(bool unlockModemOnDetection READ unlockModemOnDetection WRITE setUnlockModemOnDetection)
Q_PROPERTY(bool manageVirtualConnections READ manageVirtualConnections WRITE setManageVirtualConnections)
Q_PROPERTY(bool airplaneModeEnabled READ airplaneModeEnabled WRITE setAirplaneModeEnabled)
Q_PROPERTY(QString hotspotName READ hotspotName WRITE setHotspotName)
Q_PROPERTY(QString hotspotPassword READ hotspotPassword WRITE setHotspotPassword)
Q_PROPERTY(QString hotspotConnectionPath READ hotspotConnectionPath WRITE setHotspotConnectionPath)
//Readonly constant property, as this value should only be set by the platform
Q_PROPERTY(bool showPasswordDialog READ showPasswordDialog CONSTANT)
......@@ -44,6 +47,17 @@ public:
static bool airplaneModeEnabled();
static void setAirplaneModeEnabled(bool enabled);
static QString hotspotName();
static void setHotspotName(const QString &name);
static QString hotspotPassword();
static void setHotspotPassword(const QString &password);
static bool hotspotCreated();
static QString hotspotConnectionPath();
static void setHotspotConnectionPath(const QString &path);
static bool showPasswordDialog();
};
......
......@@ -47,6 +47,7 @@ Q_PROPERTY(bool wwanEnabled READ isWwanEnabled NOTIFY wwanEnabled)
* Indicates if the mobile broadband hardware is currently enabled, i.e. the state of the RF kill switch.
*/
Q_PROPERTY(bool wwanHwEnabled READ isWwanHwEnabled NOTIFY wwanHwEnabled)
Q_OBJECT
public:
explicit EnabledConnections(QObject* parent = nullptr);
......
......@@ -20,6 +20,7 @@
#include "handler.h"
#include "connectioneditordialog.h"
#include "configuration.h"
#include "uiutils.h"
#include "debug.h"
......@@ -83,6 +84,15 @@ Handler::Handler(QObject *parent)
QStringLiteral(AGENT_IFACE),
QStringLiteral("secretsError"),
this, SLOT(secretAgentError(QString, QString)));
m_hotspotSupported = checkHotspotSupported();
if (NetworkManager::checkVersion(1, 16, 0)) {
connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionTypeChanged, [this] () {
m_hotspotSupported = checkHotspotSupported();
Q_EMIT hotspotSupportedChanged(m_hotspotSupported);
});
}
}
Handler::~Handler()
......@@ -528,6 +538,110 @@ void Handler::requestScan(const QString &interface)
}
}
void Handler::createHotspot()
{
bool foundInactive = false;
bool useApMode = false;
NetworkManager::WirelessDevice::Ptr wifiDev;
NetworkManager::ConnectionSettings::Ptr connectionSettings;
connectionSettings = NetworkManager::ConnectionSettings::Ptr(new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Wireless));
NetworkManager::WirelessSetting::Ptr wifiSetting = connectionSettings->setting(NetworkManager::Setting::Wireless).dynamicCast<NetworkManager::WirelessSetting>();
wifiSetting->setMode(NetworkManager::WirelessSetting::Adhoc);
wifiSetting->setSsid(Configuration::hotspotName().toUtf8());
for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
if (device->type() == NetworkManager::Device::Wifi) {
wifiDev = device.objectCast<NetworkManager::WirelessDevice>();
if (wifiDev) {
if (!wifiDev->isActive()) {
foundInactive = true;
} else {
// Prefer previous device if it was inactive
if (foundInactive) {
break;
}
}
if (wifiDev->wirelessCapabilities().testFlag(NetworkManager::WirelessDevice::ApCap)) {
useApMode = true;
}
// We prefer inactive wireless card with AP capabilities
if (foundInactive && useApMode) {
break;
}
}
}
}
if (!wifiDev) {
qCWarning(PLASMA_NM) << "Failed to create hotspot: missing wireless device";
return;
}
wifiSetting->setInitialized(true);
wifiSetting->setMode(useApMode ? NetworkManager::WirelessSetting::Ap :NetworkManager::WirelessSetting::Adhoc);
if (!Configuration::hotspotPassword().isEmpty()) {
NetworkManager::WirelessSecuritySetting::Ptr wifiSecurity = connectionSettings->setting(NetworkManager::Setting::WirelessSecurity).dynamicCast<NetworkManager::WirelessSecuritySetting>();
wifiSecurity->setInitialized(true);
if (useApMode) {
// Use WPA2
wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaPsk);
wifiSecurity->setPsk(Configuration::hotspotPassword());
wifiSecurity->setPskFlags(NetworkManager::Setting::AgentOwned);
} else {
// Use WEP
wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::Wep);
wifiSecurity->setWepKeyType(NetworkManager::WirelessSecuritySetting::Passphrase);
wifiSecurity->setWepTxKeyindex(0);
wifiSecurity->setWepKey0(Configuration::hotspotPassword());
wifiSecurity->setWepKeyFlags(NetworkManager::Setting::AgentOwned);
wifiSecurity->setAuthAlg(NetworkManager::WirelessSecuritySetting::Open);
}
}
NetworkManager::Ipv4Setting::Ptr ipv4Setting = connectionSettings->setting(NetworkManager::Setting::Ipv4).dynamicCast<NetworkManager::Ipv4Setting>();
ipv4Setting->setMethod(NetworkManager::Ipv4Setting::Shared);
ipv4Setting->setInitialized(true);
connectionSettings->setId(Configuration::hotspotName());
connectionSettings->setAutoconnect(false);
connectionSettings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
const QVariantMap options = { {QLatin1String("persist"), QLatin1String("volatile")} };
QDBusPendingReply<QDBusObjectPath, QDBusObjectPath, QVariantMap> reply = NetworkManager::addAndActivateConnection2(connectionSettings->toMap(), wifiDev->uni(), QString(), options);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
watcher->setProperty("action", Handler::CreateHotspot);
watcher->setProperty("connection", Configuration::hotspotName());
connect(watcher, &QDBusPendingCallWatcher::finished, this, &Handler::replyFinished);
connect(watcher, &QDBusPendingCallWatcher::finished, this, QOverload<QDBusPendingCallWatcher *>::of(&Handler::hotspotCreated));
}
void Handler::stopHotspot()
{
const QString activeConnectionPath = Configuration::hotspotConnectionPath();
if (activeConnectionPath.isEmpty()) {
return;
}
NetworkManager::ActiveConnection::Ptr hotspot = NetworkManager::findActiveConnection(activeConnectionPath);
if (!hotspot) {
return;
}
NetworkManager::deactivateConnection(activeConnectionPath);
Configuration::setHotspotConnectionPath(QString());
Q_EMIT hotspotDisabled();
}
bool Handler::checkRequestScanRateLimit(const NetworkManager::WirelessDevice::Ptr &wifiDevice)
{
QDateTime now = QDateTime::currentDateTime();
......@@ -547,6 +661,41 @@ bool Handler::checkRequestScanRateLimit(const NetworkManager::WirelessDevice::Pt
return true;
}
bool Handler::checkHotspotSupported()
{
if (NetworkManager::checkVersion(1, 16, 0)) {
bool unusedWifiFound = false;
bool wifiFound = false;
for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
if (device->type() == NetworkManager::Device::Wifi) {
wifiFound = true;
NetworkManager::WirelessDevice::Ptr wifiDev = device.objectCast<NetworkManager::WirelessDevice>();