File indexing completed on 2024-03-24 05:49:28
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 //--------------------------------------------------------------------------------