File indexing completed on 2024-04-21 16:30:34

0001 // SPDX-License-Identifier: GPL-3.0-or-later
0002 /*
0003   Copyright 2017 - 2023 Martin Koller, kollix@aon.at
0004 
0005   This file is part of liquidshell.
0006 
0007   liquidshell is free software: you can redistribute it and/or modify
0008   it under the terms of the GNU General Public License as published by
0009   the Free Software Foundation, either version 3 of the License, or
0010   (at your option) any later version.
0011 
0012   liquidshell is distributed in the hope that it will be useful,
0013   but WITHOUT ANY WARRANTY; without even the implied warranty of
0014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015   GNU General Public License for more details.
0016 
0017   You should have received a copy of the GNU General Public License
0018   along with liquidshell.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include <NetworkList.hxx>
0022 
0023 #include <QCheckBox>
0024 #include <QToolButton>
0025 #include <QScrollBar>
0026 #include <QStyle>
0027 #include <QTimer>
0028 #include <QDebug>
0029 
0030 #include <kio_version.h>
0031 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0032 #  include <KIO/ApplicationLauncherJob>
0033 #  include <KIO/JobUiDelegateFactory>
0034 #else
0035 #  include <KRun>
0036 #endif
0037 
0038 #include <KLocalizedString>
0039 #include <KService>
0040 
0041 #include <NetworkManagerQt/Settings>
0042 #include <NetworkManagerQt/WirelessDevice>
0043 #include <NetworkManagerQt/WirelessSetting>
0044 #include <NetworkManagerQt/Utils>
0045 #include <networkmanagerqt_version.h>
0046 
0047 #include <sys/types.h>
0048 #include <pwd.h>
0049 #include <algorithm>
0050 
0051 //--------------------------------------------------------------------------------
0052 
0053 NetworkButton::NetworkButton(NetworkManager::Connection::Ptr c, NetworkManager::Device::Ptr dev,
0054                              NetworkManager::AccessPoint::Ptr accessPoint)
0055   : connection(c), device(dev)
0056 {
0057   setCheckable(true);
0058 
0059   if ( connection )
0060   {
0061     for (const NetworkManager::ActiveConnection::Ptr &ac : NetworkManager::activeConnections())
0062     {
0063       if ( ac->uuid() == c->uuid() )
0064       {
0065         setChecked(true);
0066         break;
0067       }
0068     }
0069   }
0070 
0071   if ( accessPoint )
0072   {
0073     ssid = accessPoint->ssid();
0074     rawSsid = accessPoint->rawSsid();
0075     wpaFlags = accessPoint->rsnFlags() ? accessPoint->rsnFlags() : accessPoint->wpaFlags();
0076   }
0077 
0078   connect(this, &NetworkButton::toggled, this, &NetworkButton::toggleNetworkStatus);
0079 }
0080 
0081 //--------------------------------------------------------------------------------
0082 
0083 bool NetworkButton::compare(const NetworkButton *left, const NetworkButton *right)
0084 {
0085   if ( left->wpaFlags && !right->wpaFlags )
0086     return true;
0087 
0088   return left->ssid.localeAwareCompare(right->ssid) < 0;
0089 }
0090 
0091 //--------------------------------------------------------------------------------
0092 
0093 void NetworkButton::toggleNetworkStatus(bool on)
0094 {
0095   if ( on )
0096   {
0097     if ( !connection )  // no connection yet -> create one
0098     {
0099       // the connMap content was "reverse-engineered" by using qdbusviewer and the result of getting
0100       // GetSettings of one of theSettings.Connection elements
0101 
0102       NMVariantMapMap connMap;
0103       QVariantMap map;
0104       map.insert("id", ssid);
0105 
0106       // ensure to not need root password by creating only for the current user
0107       struct passwd *pwd = getpwuid(geteuid());
0108       if ( pwd )
0109         map.insert("permissions", QStringList(QString("user:") + QString::fromUtf8(pwd->pw_name)));
0110 
0111       connMap.insert("connection", map);
0112 
0113       QVariantMap wirelessMap;
0114       wirelessMap.insert("ssid", rawSsid);
0115 
0116       if ( wpaFlags )
0117       {
0118         wirelessMap.insert("security", "802-11-wireless-security");
0119 
0120         QVariantMap security;
0121         if ( wpaFlags & NetworkManager::AccessPoint::KeyMgmtPsk )
0122           security.insert("key-mgmt", QString("wpa-psk"));
0123 #if (NETWORKMANAGERQT_VERSION >= QT_VERSION_CHECK(5, 63, 0))
0124         else if ( wpaFlags & NetworkManager::AccessPoint::KeyMgmtSAE )
0125           security.insert("key-mgmt", QString("sae"));
0126 #endif
0127         else
0128         {
0129           // TODO: other types - find value names
0130         }
0131 
0132         connMap.insert("802-11-wireless-security", security);
0133       }
0134 
0135       connMap.insert("802-11-wireless", wirelessMap);
0136 
0137       QDBusPendingReply<QDBusObjectPath, QDBusObjectPath> call =
0138           NetworkManager::addAndActivateConnection(connMap, device->uni(), QString());
0139 
0140       /*
0141       QDBusPendingCallWatcher *pendingCallWatcher = new QDBusPendingCallWatcher(call, this);
0142       connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this,
0143               [this](QDBusPendingCallWatcher *w)
0144               {
0145                 w->deleteLater();
0146                 QDBusPendingReply<QDBusObjectPath, QDBusObjectPath> reply = *w;
0147                 qDebug() << reply.error();
0148               }
0149              );
0150       */
0151 
0152       // without closing our popup, the user can not enter the password in the password dialog which appears
0153       window()->close();
0154 
0155       return;
0156     }
0157 
0158     switch ( connection->settings()->connectionType() )
0159     {
0160       case NetworkManager::ConnectionSettings::Wired:
0161       {
0162         NetworkManager::activateConnection(connection->path(), QString(), QString());
0163         break;
0164       }
0165 
0166       case NetworkManager::ConnectionSettings::Wireless:
0167       {
0168         NetworkManager::activateConnection(connection->path(), device->uni(), QString());
0169         break;
0170       }
0171 
0172       case NetworkManager::ConnectionSettings::Vpn:
0173       {
0174         NetworkManager::ActiveConnection::Ptr conn(NetworkManager::primaryConnection());
0175         if ( conn && !conn->devices().isEmpty() )
0176           NetworkManager::activateConnection(connection->path(), conn->devices()[0], QString());
0177         break;
0178       }
0179 
0180       default: ; // TODO
0181     }
0182   }
0183   else if ( connection )
0184   {
0185     for (const NetworkManager::ActiveConnection::Ptr &ac : NetworkManager::activeConnections())
0186     {
0187       if ( ac->uuid() == connection->uuid() )
0188       {
0189         NetworkManager::deactivateConnection(ac->path());
0190         break;
0191       }
0192     }
0193   }
0194 }
0195 
0196 //--------------------------------------------------------------------------------
0197 //--------------------------------------------------------------------------------
0198 //--------------------------------------------------------------------------------
0199 
0200 NetworkList::NetworkList(QWidget *parent)
0201   : QFrame(parent)
0202 {
0203   setWindowFlags(windowFlags() | Qt::Popup);
0204   setFrameShape(QFrame::StyledPanel);
0205 
0206   QVBoxLayout *vbox = new QVBoxLayout(this);
0207   hbox = new QHBoxLayout;
0208   vbox->addLayout(hbox);
0209 
0210   network = new QToolButton;
0211   network->setIcon(QIcon::fromTheme("network-wired"));
0212   network->setIconSize(QSize(22, 22));
0213   network->setCheckable(true);
0214   connect(network, &QToolButton::clicked, [](bool on) { NetworkManager::setNetworkingEnabled(on); });
0215   connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, this, &NetworkList::statusUpdate);
0216   hbox->addWidget(network);
0217 
0218   wireless = new QToolButton;
0219   wireless->setIcon(QIcon::fromTheme("network-wireless"));
0220   wireless->setIconSize(QSize(22, 22));
0221   wireless->setCheckable(true);
0222   connect(wireless, &QToolButton::clicked, [](bool on) { NetworkManager::setWirelessEnabled(on); });
0223   connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, this, &NetworkList::statusUpdate);
0224   hbox->addWidget(wireless);
0225 
0226   statusUpdate();
0227 
0228   hbox->addStretch();
0229 
0230   QToolButton *configure = new QToolButton;
0231   configure->setIcon(QIcon::fromTheme("configure"));
0232   configure->setIconSize(QSize(22, 22));
0233   configure->setToolTip(i18n("Configure Network Connections"));
0234   connect(configure, &QToolButton::clicked, this, &NetworkList::openConfigureDialog);
0235   hbox->addWidget(configure);
0236 
0237   // show connections
0238   QWidget *widget = new QWidget;
0239   connectionsVbox = new QVBoxLayout(widget);
0240   connectionsVbox->setContentsMargins(QMargins());
0241   connectionsVbox->setSizeConstraint(QLayout::SetMinAndMaxSize);
0242 
0243   scroll = new QScrollArea;
0244   scroll->setWidgetResizable(true);
0245   scroll->setWidget(widget);
0246 
0247   vbox->addWidget(scroll);
0248 
0249   fillConnections();
0250 
0251   QTimer *checkConnectionsTimer = new QTimer(this);
0252   checkConnectionsTimer->setInterval(1000);
0253   connect(checkConnectionsTimer, &QTimer::timeout, this, &NetworkList::fillConnections);
0254   checkConnectionsTimer->start();
0255 }
0256 
0257 //--------------------------------------------------------------------------------
0258 
0259 void NetworkList::openConfigureDialog()
0260 {
0261   // newer plasma has already a KCM
0262   KService::Ptr service = KService::serviceByDesktopName("kcm_networkmanagement");
0263 
0264   if ( !service )
0265     service = new KService("", "kde5-nm-connection-editor", "");
0266 
0267 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0268     auto *job = new KIO::ApplicationLauncherJob(service);
0269     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0270     job->start();
0271 #else
0272     KRun::runApplication(*service, QList<QUrl>(), this);
0273 #endif
0274 
0275   close();
0276 }
0277 
0278 //--------------------------------------------------------------------------------
0279 
0280 void NetworkList::statusUpdate()
0281 {
0282   network->setChecked(NetworkManager::isNetworkingEnabled());
0283   wireless->setChecked(NetworkManager::isWirelessEnabled());
0284 
0285   if ( NetworkManager::isNetworkingEnabled() )
0286     network->setToolTip(i18n("Networking is enabled. Click to disable"));
0287   else
0288     network->setToolTip(i18n("Networking is disabled. Click to enable"));
0289 
0290   if ( NetworkManager::isWirelessEnabled() )
0291     wireless->setToolTip(i18n("Wireless Networking is enabled. Click to disable"));
0292   else
0293     wireless->setToolTip(i18n("Wireless Networking is disabled. Click to enable"));
0294 }
0295 
0296 //--------------------------------------------------------------------------------
0297 
0298 void NetworkList::fillConnections()
0299 {
0300   QLayoutItem *child;
0301   while ( (child = connectionsVbox->takeAt(0)) )
0302   {
0303     delete child->widget();
0304     delete child;
0305   }
0306 
0307   NetworkManager::Connection::List allConnections = NetworkManager::listConnections();
0308 
0309   // show VPN networks on top
0310   for (const NetworkManager::Connection::Ptr &c : allConnections)
0311   {
0312     if ( !c->isValid() )
0313       continue;
0314 
0315     if ( c->settings()->connectionType() == NetworkManager::ConnectionSettings::Vpn )
0316     {
0317       NetworkButton *vpn = new NetworkButton(c);
0318       vpn->setText(c->name());
0319       vpn->setIcon(QIcon::fromTheme("security-high"));
0320       connectionsVbox->addWidget(vpn);
0321       vpn->show();
0322     }
0323   }
0324 
0325   // wired networks
0326   for (const NetworkManager::Connection::Ptr &c : allConnections)
0327   {
0328     if ( !c->isValid() )
0329       continue;
0330 
0331     if ( (c->settings()->connectionType() == NetworkManager::ConnectionSettings::Wired) &&
0332          !c->uuid().isEmpty() )
0333     {
0334       NetworkButton *net = new NetworkButton(c);
0335       net->setText(c->name());
0336       net->setIcon(QIcon::fromTheme("network-wired"));
0337       connectionsVbox->addWidget(net);
0338       net->show();
0339     }
0340   }
0341 
0342   // show available wifi networks
0343   if ( NetworkManager::isWirelessEnabled() )
0344   {
0345     QVector<NetworkButton *> entries;
0346 
0347     for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces())
0348     {
0349       if ( device->type() != NetworkManager::Device::Wifi )
0350         continue;
0351 
0352       NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast<NetworkManager::WirelessDevice>();
0353 
0354       for (const NetworkManager::WirelessNetwork::Ptr &network : wifiDevice->networks())
0355       {
0356         NetworkManager::AccessPoint::Ptr accessPoint = network->referenceAccessPoint();
0357 
0358         if ( !accessPoint )
0359           continue;
0360 
0361         // check if we have a connection for this SSID
0362         NetworkManager::Connection::Ptr conn;
0363         for (const NetworkManager::Connection::Ptr &c : allConnections)
0364         {
0365           if ( c->isValid() && (c->settings()->connectionType() == NetworkManager::ConnectionSettings::Wireless) )
0366           {
0367             NetworkManager::Setting::Ptr setting = c->settings()->setting(NetworkManager::Setting::Wireless);
0368             NetworkManager::WirelessSetting::Ptr s = setting.staticCast<NetworkManager::WirelessSetting>();
0369 
0370             if ( s->ssid() == network->ssid() )
0371             {
0372               conn = c;
0373               break;
0374             }
0375           }
0376         }
0377 
0378         NetworkButton *net = new NetworkButton(conn, device, accessPoint);
0379 
0380         if ( conn )
0381           net->setText(QString("%1 (%2%)").arg(conn->name()).arg(network->signalStrength()));
0382         else
0383           net->setText(QString("%1 (%2%)").arg(network->ssid()).arg(network->signalStrength()));
0384 
0385         net->setIcon(QIcon::fromTheme("network-wireless"));
0386 
0387         if ( accessPoint->wpaFlags() || accessPoint->rsnFlags() )
0388           net->setIcon2(QIcon::fromTheme("object-locked"));
0389         else
0390         {
0391           // make it more obvious when an access point is not secured
0392           // by not showing any "lock" icon (also the unlocked icon is hardly different than the locked one,
0393           // so the user might easily miss the "insecure" icon)
0394           //net->setIcon2(QIcon::fromTheme("object-unlocked"));
0395         }
0396 
0397         entries.append(net);
0398       }
0399     }
0400 
0401     // sort entries: secure before insecure APs
0402     std::stable_sort(entries.begin(), entries.end(), &NetworkButton::compare);
0403     foreach (NetworkButton *entry, entries)
0404     {
0405       connectionsVbox->addWidget(entry);
0406       entry->show();
0407     }
0408   }
0409 
0410 #if 0
0411   // TEST
0412   static int count = 15;
0413   for (int i = 0; i < count; i++)
0414   {
0415     NetworkButton *net = new NetworkButton();
0416     net->setText(QString("dummy %1").arg(i));
0417     net->setIcon(QIcon::fromTheme("network-wired"));
0418     connectionsVbox->addWidget(net);
0419     net->show();
0420   }
0421   count -= 3;
0422   if ( count <= 0 ) count = 15;
0423 #endif
0424 
0425   connectionsVbox->addStretch();
0426   adjustSize();
0427   emit changed();
0428 
0429   // WA_UnderMouse is only updated with Enter/Leave events in QApplication, but this status
0430   // is used to render the button hovered or not. Therefore we must update the flag on our own here
0431   QPoint p = scroll->widget()->mapFromGlobal(QCursor::pos());
0432   for (int i = 0; i < connectionsVbox->count(); i++)
0433   {
0434     QWidget *button = connectionsVbox->itemAt(i)->widget();
0435     if ( button )
0436       button->setAttribute(Qt::WA_UnderMouse, button->geometry().contains(p));
0437   }
0438 }
0439 
0440 //--------------------------------------------------------------------------------
0441 
0442 QSize NetworkList::sizeHint() const
0443 {
0444   QWidget *w = scroll->widget();
0445   QSize s;
0446 
0447   s.setHeight(frameWidth() +
0448               contentsMargins().top() +
0449               layout()->contentsMargins().top() +
0450               hbox->sizeHint().height() +
0451               ((layout()->spacing() == -1) ? style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) : layout()->spacing()) +
0452               scroll->frameWidth() +
0453               scroll->contentsMargins().top() +
0454               w->sizeHint().height() +
0455               scroll->contentsMargins().bottom() +
0456               scroll->frameWidth() +
0457               layout()->contentsMargins().bottom() +
0458               contentsMargins().bottom() +
0459               frameWidth()
0460              );
0461 
0462   s.setWidth(frameWidth() +
0463              contentsMargins().left() +
0464              layout()->contentsMargins().left() +
0465              scroll->frameWidth() +
0466              scroll->contentsMargins().left() +
0467              w->sizeHint().width() +
0468              scroll->verticalScrollBar()->sizeHint().width() +
0469              scroll->contentsMargins().right() +
0470              scroll->frameWidth() +
0471              layout()->contentsMargins().right() +
0472              contentsMargins().right() +
0473              frameWidth()
0474             );
0475   return s;
0476 }
0477 
0478 //--------------------------------------------------------------------------------