File indexing completed on 2024-05-05 05:36:39
0001 // SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com> 0002 // SPDX-License-Identifier: GPL-2.0-or-later 0003 0004 #include "modem.h" 0005 0006 #include <utility> 0007 0008 #include <KLocalizedString> 0009 #include <KUser> 0010 #include <QDBusReply> 0011 0012 #include <QCoroDBusPendingReply> 0013 0014 Modem::Modem(QObject *parent) 0015 : QObject{parent} 0016 { 0017 } 0018 0019 Modem::Modem(QObject *parent, ModemManager::ModemDevice::Ptr mmModem, ModemManager::Modem::Ptr mmInterface) 0020 : QObject{parent} 0021 , m_mmModem{mmModem} 0022 , m_nmModem{nullptr} 0023 , m_mmInterface{mmInterface} 0024 { 0025 // TODO multi-sim support 0026 m_sims = {new Sim{this, this, m_mmModem->sim(), m_mmInterface, m_mm3gppDevice}}; 0027 0028 connect(m_mmModem.data(), &ModemManager::ModemDevice::simAdded, this, &Modem::simsChanged); 0029 connect(m_mmModem.data(), &ModemManager::ModemDevice::simAdded, this, &Modem::hasSimChanged); 0030 connect(m_mmModem.data(), &ModemManager::ModemDevice::simRemoved, this, &Modem::simsChanged); 0031 connect(m_mmModem.data(), &ModemManager::ModemDevice::simRemoved, this, &Modem::hasSimChanged); 0032 0033 if (m_mmModem->sim()) { 0034 connect(m_mmModem->sim().get(), &ModemManager::Sim::simIdentifierChanged, this, &Modem::hasSimChanged); 0035 } 0036 0037 connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &Modem::mobileDataEnabledChanged); 0038 connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionRemoved, this, &Modem::mobileDataEnabledChanged); 0039 connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &Modem::mobileDataEnabledChanged); 0040 connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &Modem::mobileDataEnabledChanged); 0041 connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &Modem::findNetworkManagerDevice); 0042 connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &Modem::findNetworkManagerDevice); 0043 0044 // this is guaranteed to be a GSM modem 0045 m_mm3gppDevice = m_mmModem->interface(ModemManager::ModemDevice::GsmInterface).objectCast<ModemManager::Modem3gpp>(); 0046 0047 // if no sim is inserted, m_mm3gppDevice is nullptr 0048 if (m_mm3gppDevice) { 0049 m_mm3gppDevice->setTimeout(60000); // scanning networks likely takes longer than the default timeout 0050 } 0051 0052 // find networkmanager modem, if it exists 0053 findNetworkManagerDevice(); 0054 0055 // we need to initialize it after m_mm3gppDevice has been set 0056 m_details = new ModemDetails(this, this); 0057 } 0058 0059 void Modem::findNetworkManagerDevice() 0060 { 0061 m_nmModem = nullptr; 0062 0063 // find networkmanager modem device 0064 for (NetworkManager::Device::Ptr nmDevice : NetworkManager::networkInterfaces()) { 0065 if (nmDevice->udi() == m_mmModem->uni()) { 0066 m_nmModem = nmDevice.objectCast<NetworkManager::ModemDevice>(); 0067 } 0068 } 0069 0070 if (m_nmModem) { 0071 connect(m_nmModem.data(), &NetworkManager::Device::autoconnectChanged, this, &Modem::mobileDataEnabledChanged); 0072 connect(m_nmModem.data(), &NetworkManager::Device::stateChanged, this, &Modem::mobileDataEnabledChanged); 0073 connect(m_nmModem.data(), &NetworkManager::Device::availableConnectionAppeared, this, &Modem::mobileDataEnabledChanged); 0074 connect(m_nmModem.data(), &NetworkManager::Device::availableConnectionDisappeared, this, &Modem::mobileDataEnabledChanged); 0075 0076 connect(m_nmModem.data(), &NetworkManager::ModemDevice::availableConnectionChanged, this, [this]() -> void { 0077 refreshProfiles(); 0078 }); 0079 connect(m_nmModem.data(), &NetworkManager::ModemDevice::activeConnectionChanged, this, [this]() -> void { 0080 refreshProfiles(); 0081 Q_EMIT activeConnectionUniChanged(); 0082 }); 0083 connect(m_nmModem.data(), &NetworkManager::ModemDevice::stateChanged, this, [this](auto newstate, auto oldstate, auto reason) -> void { 0084 qDebug() << QStringLiteral("Modem") << m_nmModem->uni() << QStringLiteral("changed state:") << nmDeviceStateStr(oldstate) << QStringLiteral("->") 0085 << nmDeviceStateStr(newstate) << QStringLiteral("due to:") << reason; 0086 }); 0087 0088 // add connection profiles 0089 refreshProfiles(); 0090 } 0091 0092 Q_EMIT nmModemChanged(); 0093 Q_EMIT mobileDataEnabledChanged(); 0094 Q_EMIT mobileDataSupportedChanged(); 0095 } 0096 0097 ModemDetails *Modem::modemDetails() const 0098 { 0099 return m_details; 0100 } 0101 0102 QString Modem::displayId() const 0103 { 0104 // in the form /org/freedesktop/ModemManager1/Modem/0 0105 QStringList uniSplit = uni().split("/"); 0106 return uniSplit.count() == 0 ? QStringLiteral("(empty)") : QString(uniSplit[uniSplit.size() - 1]); 0107 } 0108 0109 QString Modem::uni() const 0110 { 0111 return m_mmInterface->uni(); 0112 } 0113 0114 QString Modem::activeConnectionUni() const 0115 { 0116 if (m_nmModem && m_nmModem->activeConnection() && m_nmModem->activeConnection()->connection()) { 0117 return m_nmModem->activeConnection()->connection()->uuid(); 0118 } 0119 return QString(); 0120 } 0121 0122 QCoro::Task<void> Modem::reset() 0123 { 0124 qDebug() << QStringLiteral("Resetting the modem..."); 0125 0126 QDBusReply<void> reply = co_await m_mmInterface->reset(); 0127 0128 if (!reply.isValid()) { 0129 qDebug() << QStringLiteral("Error resetting the modem:") << reply.error().message(); 0130 CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error resetting the modem: %1", reply.error().message())); 0131 } 0132 } 0133 0134 bool Modem::mobileDataSupported() const 0135 { 0136 return m_nmModem && hasSim(); 0137 } 0138 0139 bool Modem::needsAPNAdded() const 0140 { 0141 return m_nmModem && mobileDataSupported() && m_nmModem->availableConnections().count() == 0; 0142 } 0143 0144 bool Modem::mobileDataEnabled() const 0145 { 0146 // no modem -> no mobile data -> report disabled 0147 if (!m_nmModem) { 0148 return false; 0149 } 0150 0151 // mobile data already activated -> report enabled 0152 if (m_nmModem->state() == NetworkManager::Device::Activated) { 0153 return true; 0154 } 0155 0156 // autoconnect disabled on the entire modem -> report disabled 0157 if (!m_nmModem->autoconnect()) { 0158 return false; 0159 } 0160 0161 // at least one connection set to autoconnect -> report enabled 0162 for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) { 0163 if (con->settings()->autoconnect()) { 0164 return true; 0165 } 0166 } 0167 0168 // modem, but no connection, set to autoconnect -> report disabled 0169 return false; 0170 } 0171 0172 void Modem::setMobileDataEnabled(bool enabled) 0173 { 0174 if (!m_nmModem) { 0175 return; 0176 } 0177 0178 if (!enabled) { 0179 m_nmModem->setAutoconnect(false); 0180 // we need to also set all connections to not autoconnect (#182) 0181 for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) { 0182 con->settings()->setAutoconnect(false); 0183 con->update(con->settings()->toMap()); 0184 } 0185 m_nmModem->disconnectInterface(); 0186 } else { 0187 m_nmModem->setAutoconnect(true); 0188 // activate the connection that was last used 0189 QDateTime latestTimestamp; 0190 NetworkManager::Connection::Ptr latestCon; 0191 for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) { 0192 QDateTime timestamp = con->settings()->timestamp(); 0193 // if con was not used yet, skip it, otherwise: 0194 // if we have no latestTimestamp yet, con is the latest 0195 // otherwise, compare the timestamps 0196 // in case of a tie, use the first connection that was found 0197 if (!timestamp.isNull() && (latestTimestamp.isNull() || timestamp > latestTimestamp)) { 0198 latestTimestamp = timestamp; 0199 latestCon = con; 0200 } 0201 } 0202 // if we found the last used connection 0203 if (!latestCon.isNull()) { 0204 // set it to autoconnect and connect it immediately 0205 latestCon->settings()->setAutoconnect(true); 0206 latestCon->update(latestCon->settings()->toMap()); 0207 NetworkManager::activateConnection(latestCon->path(), m_nmModem->uni(), ""); 0208 } 0209 } 0210 } 0211 0212 bool Modem::isRoaming() const 0213 { 0214 if (!m_nmModem || !m_nmModem->activeConnection() || !m_nmModem->activeConnection()->connection()) { 0215 return false; 0216 } 0217 0218 auto connection = m_nmModem->activeConnection()->connection(); 0219 NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>(); 0220 0221 return gsmSetting ? !gsmSetting->homeOnly() : false; 0222 } 0223 0224 QCoro::Task<void> Modem::setIsRoaming(bool roaming) 0225 { 0226 if (!m_nmModem || !m_nmModem->activeConnection() || !m_nmModem->activeConnection()->connection()) { 0227 co_return; 0228 } 0229 0230 auto connection = m_nmModem->activeConnection()->connection(); 0231 0232 NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>(); 0233 if (gsmSetting) { 0234 gsmSetting->setHomeOnly(!roaming); // set roaming setting 0235 0236 QDBusReply<void> reply = co_await connection->update(connection->settings()->toMap()); 0237 if (!reply.isValid()) { 0238 qWarning() << QStringLiteral("Error updating connection settings for") << connection->uuid() << QStringLiteral(":") << reply.error().message() 0239 << QStringLiteral("."); 0240 CellularNetworkSettings::instance()->addMessage( 0241 InlineMessage::Error, 0242 i18n("Error updating connection settings for %1: %2.", connection->uuid(), reply.error().message())); 0243 } else { 0244 qDebug() << QStringLiteral("Successfully updated connection settings") << connection->uuid() << QStringLiteral("."); 0245 } 0246 } 0247 0248 // the connection uni has changed, refresh the profiles list 0249 refreshProfiles(); 0250 Q_EMIT activeConnectionUniChanged(); 0251 } 0252 0253 bool Modem::hasSim() const 0254 { 0255 if (!m_mmModem) { 0256 return false; 0257 } 0258 return m_mmModem && m_mmModem->sim() && m_mmModem->sim()->uni() != QStringLiteral("/"); 0259 } 0260 0261 QList<ProfileSettings *> &Modem::profileList() 0262 { 0263 return m_profileList; 0264 } 0265 0266 void Modem::refreshProfiles() 0267 { 0268 m_profileList.clear(); 0269 0270 if (!m_nmModem) { 0271 Q_EMIT profileListChanged(); 0272 qWarning() << "No NetworkManager modem found, cannot refresh profiles."; 0273 return; 0274 } 0275 0276 for (auto connection : m_nmModem->availableConnections()) { 0277 for (auto setting : connection->settings()->settings()) { 0278 if (setting.dynamicCast<NetworkManager::GsmSetting>()) { 0279 m_profileList.append(new ProfileSettings(this, setting.dynamicCast<NetworkManager::GsmSetting>(), connection)); 0280 } 0281 } 0282 } 0283 Q_EMIT profileListChanged(); 0284 } 0285 0286 QCoro::Task<void> Modem::activateProfile(const QString &connectionUni) 0287 { 0288 if (!m_nmModem) { 0289 qWarning() << "Cannot activate profile since there is no NetworkManager modem"; 0290 co_return; 0291 } 0292 0293 qDebug() << QStringLiteral("Activating profile on modem") << m_nmModem->uni() << QStringLiteral("for connection") << connectionUni << "."; 0294 0295 // cache roaming setting 0296 bool roaming = isRoaming(); 0297 0298 NetworkManager::Connection::Ptr con; 0299 0300 // disable autoconnect for all other connections 0301 for (auto connection : m_nmModem->availableConnections()) { 0302 if (connection->uuid() == connectionUni) { 0303 connection->settings()->setAutoconnect(true); 0304 con = connection; 0305 } else { 0306 connection->settings()->setAutoconnect(false); 0307 } 0308 } 0309 0310 if (!con) { 0311 qDebug() << QStringLiteral("Connection") << connectionUni << QStringLiteral("not found."); 0312 co_return; 0313 } 0314 0315 // activate connection manually 0316 // despite the documentation saying otherwise, activateConnection seems to need the DBus path, not uuid of the connection 0317 QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::activateConnection(con->path(), m_nmModem->uni(), ""); 0318 if (!reply.isValid()) { 0319 qWarning() << QStringLiteral("Error activating connection:") << reply.error().message(); 0320 CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error activating connection: %1", reply.error().message())); 0321 co_return; 0322 } 0323 0324 // set roaming settings separately (since it changes the uni) 0325 co_await setIsRoaming(roaming); 0326 } 0327 0328 QCoro::Task<void> Modem::addProfile(QString name, QString apn, QString username, QString password, QString networkType) 0329 { 0330 if (!m_nmModem) { 0331 qWarning() << "Cannot add profile since there is no NetworkManager modem"; 0332 co_return; 0333 } 0334 0335 NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Gsm)}; 0336 settings->setId(name); 0337 settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid()); 0338 settings->setAutoconnect(true); 0339 settings->addToPermissions(KUser().loginName(), QString()); 0340 0341 NetworkManager::GsmSetting::Ptr gsmSetting = settings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>(); 0342 gsmSetting->setApn(apn); 0343 gsmSetting->setUsername(username); 0344 gsmSetting->setPassword(password); 0345 gsmSetting->setPasswordFlags(password.isEmpty() ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned); 0346 gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType)); 0347 gsmSetting->setHomeOnly(!isRoaming()); 0348 0349 gsmSetting->setInitialized(true); 0350 0351 QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::addAndActivateConnection(settings->toMap(), m_nmModem->uni(), ""); 0352 if (!reply.isValid()) { 0353 qWarning() << QStringLiteral("Error adding connection:") << reply.error().message(); 0354 CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error adding connection: %1", reply.error().message())); 0355 } else { 0356 qDebug() << QStringLiteral("Successfully added a new connection") << name << QStringLiteral("with APN") << apn << "."; 0357 } 0358 } 0359 0360 QCoro::Task<void> Modem::removeProfile(const QString &connectionUni) 0361 { 0362 NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni); 0363 if (!con) { 0364 qWarning() << QStringLiteral("Could not find connection") << connectionUni << QStringLiteral("to update!"); 0365 co_return; 0366 } 0367 0368 QDBusReply<void> reply = co_await con->remove(); 0369 if (!reply.isValid()) { 0370 qWarning() << QStringLiteral("Error removing connection") << reply.error().message(); 0371 CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error removing connection: %1", reply.error().message())); 0372 } 0373 } 0374 0375 QCoro::Task<void> Modem::updateProfile(QString connectionUni, QString name, QString apn, QString username, QString password, QString networkType) 0376 { 0377 NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni); 0378 if (!con) { 0379 qWarning() << QStringLiteral("Could not find connection") << connectionUni << QStringLiteral("to update!"); 0380 co_return; 0381 } 0382 0383 NetworkManager::ConnectionSettings::Ptr conSettings = con->settings(); 0384 if (!conSettings) { 0385 qWarning() << QStringLiteral("Could not find connection settings for") << connectionUni << QStringLiteral("to update!"); 0386 co_return; 0387 } 0388 0389 conSettings->setId(name); 0390 0391 NetworkManager::GsmSetting::Ptr gsmSetting = conSettings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>(); 0392 gsmSetting->setApn(apn); 0393 gsmSetting->setUsername(username); 0394 gsmSetting->setPassword(password); 0395 gsmSetting->setPasswordFlags(password == "" ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned); 0396 gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType)); 0397 gsmSetting->setHomeOnly(!isRoaming()); 0398 0399 gsmSetting->setInitialized(true); 0400 0401 QDBusReply<void> reply = con->update(conSettings->toMap()); 0402 if (!reply.isValid()) { 0403 qWarning() << QStringLiteral("Error updating connection settings for") << connectionUni << QStringLiteral(":") << reply.error().message() 0404 << QStringLiteral("."); 0405 CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, 0406 i18n("Error updating connection settings for %1: %2.", connectionUni, reply.error().message())); 0407 } else { 0408 qDebug() << QStringLiteral("Successfully updated connection settings") << connectionUni << QStringLiteral("."); 0409 } 0410 } 0411 0412 void Modem::addDetectedProfileSettings() 0413 { 0414 if (!m_mmModem) { 0415 qWarning() << "ModemManager device missing, cannot detect profile settings"; 0416 return; 0417 } 0418 0419 if (!hasSim()) { 0420 qWarning() << "No SIM found, cannot detect profile settings"; 0421 return; 0422 } 0423 0424 if (!m_mm3gppDevice) { 0425 qWarning() << "3gpp object not found, cannot detect profile settings"; 0426 return; 0427 } 0428 0429 bool found = false; 0430 static MobileProviders mobileProviders{}; 0431 0432 QString operatorCode = m_mm3gppDevice->operatorCode(); 0433 qWarning() << QStringLiteral("Detecting profile settings. Using MCCMNC:") << operatorCode; 0434 0435 // lookup apns with mccmnc codes 0436 for (QString &provider : mobileProviders.getProvidersFromMCCMNC(operatorCode)) { 0437 qWarning() << QStringLiteral("Provider:") << provider; 0438 0439 for (auto apn : mobileProviders.getApns(provider)) { 0440 QVariantMap apnInfo = mobileProviders.getApnInfo(apn); 0441 qWarning() << QStringLiteral("Found gsm profile settings. Type:") << apnInfo[QStringLiteral("usageType")]; 0442 0443 // only add mobile data apns (not mms) 0444 if (apnInfo[QStringLiteral("usageType")].toString() == QStringLiteral("internet")) { 0445 found = true; 0446 0447 QString name = provider; 0448 if (!apnInfo[QStringLiteral("name")].isNull()) { 0449 name += " - " + apnInfo[QStringLiteral("name")].toString(); 0450 } 0451 0452 addProfile(name, 0453 apn, 0454 apnInfo[QStringLiteral("username")].toString(), 0455 apnInfo[QStringLiteral("password")].toString(), 0456 QStringLiteral("4G/3G/2G")); 0457 } 0458 0459 // TODO in the future for MMS settings, add else if here for == "mms" 0460 } 0461 } 0462 0463 if (!found) { 0464 qDebug() << QStringLiteral("No profiles were found."); 0465 Q_EMIT couldNotAutodetectSettings(); 0466 } 0467 } 0468 0469 QList<Sim *> Modem::sims() 0470 { 0471 return m_sims; 0472 } 0473 0474 ModemManager::ModemDevice::Ptr Modem::mmModemDevice() 0475 { 0476 return m_mmModem; 0477 } 0478 0479 NetworkManager::ModemDevice::Ptr Modem::nmModemDevice() 0480 { 0481 return m_nmModem; 0482 } 0483 0484 ModemManager::Modem::Ptr Modem::mmModemInterface() 0485 { 0486 return m_mmInterface; 0487 } 0488 0489 QString Modem::nmDeviceStateStr(NetworkManager::Device::State state) 0490 { 0491 if (state == NetworkManager::Device::State::UnknownState) 0492 return i18n("Unknown"); 0493 else if (state == NetworkManager::Device::State::Unmanaged) 0494 return i18n("Unmanaged"); 0495 else if (state == NetworkManager::Device::State::Unavailable) 0496 return i18n("Unavailable"); 0497 else if (state == NetworkManager::Device::State::Disconnected) 0498 return i18n("Disconnected"); 0499 else if (state == NetworkManager::Device::State::Preparing) 0500 return i18n("Preparing"); 0501 else if (state == NetworkManager::Device::State::ConfiguringHardware) 0502 return i18n("ConfiguringHardware"); 0503 else if (state == NetworkManager::Device::State::NeedAuth) 0504 return i18n("NeedAuth"); 0505 else if (state == NetworkManager::Device::State::ConfiguringIp) 0506 return i18n("ConfiguringIp"); 0507 else if (state == NetworkManager::Device::State::CheckingIp) 0508 return i18n("CheckingIp"); 0509 else if (state == NetworkManager::Device::State::WaitingForSecondaries) 0510 return i18n("WaitingForSecondaries"); 0511 else if (state == NetworkManager::Device::State::Activated) 0512 return i18n("Activated"); 0513 else if (state == NetworkManager::Device::State::Deactivating) 0514 return i18n("Deactivating"); 0515 else if (state == NetworkManager::Device::State::Failed) 0516 return i18n("Failed"); 0517 else 0518 return ""; 0519 }