[plasma/plasma-nm] /: cellular: Overhaul roaming setting and state per SIM

Devin Lin <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 1edd68db8fa5b5f3e44dd77d6e4acf69f817936d by Devin Lin.
Committed on 25/07/2026 at 02:59.
Pushed by devinlin into branch 'master'.

cellular: Overhaul roaming setting and state per SIM

BUG: 515488

Overhaul the roaming setting in the cellular model (for Plasma Mobile), so that roaming states are tracked globally within a SIM. This allows for the currently set roaming state to transfer over when the connection changes.

M  +3    -4    kcms/kcm_cellularnetwork/ui/SimPage.qml
M  +23   -32   libs/cellular/cellularconnectionprofile.cpp
M  +1    -3    libs/cellular/cellularconnectionprofile.h
M  +92   -9    libs/cellular/cellularmodem.cpp
M  +12   -2    libs/cellular/cellularmodem.h

https://invent.kde.org/plasma/plasma-nm/-/commit/1edd68db8fa5b5f3e44dd77d6e4acf69f817936d

diff --git a/kcms/kcm_cellularnetwork/ui/SimPage.qml b/kcms/kcm_cellularnetwork/ui/SimPage.qml
index 757b86b67..13947d7a8 100644
--- a/kcms/kcm_cellularnetwork/ui/SimPage.qml
+++ b/kcms/kcm_cellularnetwork/ui/SimPage.qml
@@ -60,14 +60,13 @@ FormCard.FormCardPage {
             description: i18n("Allow mobile data when on a roaming network.")
 
             property Cellular.CellularModem modem: sim ? sim.modem : null
-            property int activeProfileIndex: modem ? modem.profiles.indexOfConnection(modem.activeConnectionUni) : -1
-            property bool shouldBeChecked: activeProfileIndex >= 0 ? modem.profiles.roamingAllowedForConnection(modem.activeConnectionUni) : false
+            property bool shouldBeChecked: modem && modem.roamingAllowed
 
-            enabled: modem && modem.mobileDataEnabled && activeProfileIndex >= 0
+            enabled: modem && modem.activeConnectionUni.length > 0 && !modem.profileOperationInProgress
             checked: shouldBeChecked
 
             onToggled: {
-                modem.profiles.setRoamingAllowed(activeProfileIndex, checked);
+                modem.setRoamingAllowed(checked);
             }
         }
 
diff --git a/libs/cellular/cellularconnectionprofile.cpp b/libs/cellular/cellularconnectionprofile.cpp
index e82a167dc..d834fcb9b 100644
--- a/libs/cellular/cellularconnectionprofile.cpp
+++ b/libs/cellular/cellularconnectionprofile.cpp
@@ -107,52 +107,43 @@ void CellularConnectionProfile::refreshProfiles()
     Q_EMIT profilesChanged();
 }
 
-int CellularConnectionProfile::indexOfConnection(const QString &connectionUni) const
+QCoro::Task<bool> CellularConnectionProfile::setRoamingAllowed(QString connectionUni, bool allowed)
 {
-    for (int i = 0; i < m_list.count(); ++i) {
-        if (m_list[i].connectionUni == connectionUni) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-bool CellularConnectionProfile::roamingAllowedForConnection(const QString &connectionUni) const
-{
-    int idx = indexOfConnection(connectionUni);
-    if (idx < 0) {
-        return false;
-    }
-    return m_list[idx].roamingAllowed;
-}
-
-QCoro::Task<void> CellularConnectionProfile::setRoamingAllowed(int index, bool allowed)
-{
-    if (index < 0 || index >= m_list.count()) {
-        co_return;
-    }
-
-    const QString &connectionUni = m_list[index].connectionUni;
     NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
     if (!con) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << u"Could not find connection"_s << connectionUni << u"to update roaming!"_s;
-        co_return;
+        co_return false;
     }
 
-    NetworkManager::GsmSetting::Ptr gsmSetting = con->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
+    NetworkManager::ConnectionSettings::Ptr settings = con->settings();
+    NetworkManager::GsmSetting::Ptr gsmSetting = settings ? settings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>() : nullptr;
     if (!gsmSetting) {
-        co_return;
+        co_return false;
+    }
+
+    const bool oldHomeOnly = gsmSetting->homeOnly();
+    if (oldHomeOnly == !allowed) {
+        co_return true;
     }
 
     gsmSetting->setHomeOnly(!allowed);
 
-    QDBusReply<void> reply = co_await con->update(con->settings()->toMap());
+    QDBusReply<void> reply = co_await con->update(settings->toMap());
     if (!reply.isValid()) {
+        gsmSetting->setHomeOnly(oldHomeOnly);
         qCWarning(PLASMA_NM_CELLULAR_LOG) << u"Error updating roaming for"_s << connectionUni << u":"_s << reply.error().message();
-    } else {
-        m_list[index].roamingAllowed = allowed;
-        Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {RoamingAllowed});
+        co_return false;
     }
+
+    for (int index = 0; index < m_list.count(); ++index) {
+        if (m_list[index].connectionUni == connectionUni) {
+            m_list[index].roamingAllowed = allowed;
+            Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {RoamingAllowed});
+            break;
+        }
+    }
+
+    co_return true;
 }
 
 QString CellularConnectionProfile::networkTypeStr(NetworkManager::GsmSetting::NetworkType networkType)
diff --git a/libs/cellular/cellularconnectionprofile.h b/libs/cellular/cellularconnectionprofile.h
index f2b07fb43..572bde34d 100644
--- a/libs/cellular/cellularconnectionprofile.h
+++ b/libs/cellular/cellularconnectionprofile.h
@@ -43,9 +43,7 @@ public:
     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
     QHash<int, QByteArray> roleNames() const override;
 
-    Q_INVOKABLE QCoro::Task<void> setRoamingAllowed(int index, bool allowed);
-    Q_INVOKABLE int indexOfConnection(const QString &connectionUni) const;
-    Q_INVOKABLE bool roamingAllowedForConnection(const QString &connectionUni) const;
+    QCoro::Task<bool> setRoamingAllowed(QString connectionUni, bool allowed);
 
     void setNmModem(NetworkManager::ModemDevice::Ptr nmModem);
     void refreshProfiles();
diff --git a/libs/cellular/cellularmodem.cpp b/libs/cellular/cellularmodem.cpp
index c83739e78..2485a313f 100644
--- a/libs/cellular/cellularmodem.cpp
+++ b/libs/cellular/cellularmodem.cpp
@@ -262,6 +262,22 @@ bool CellularModem::simEmpty() const
     return !hasSim();
 }
 
+bool CellularModem::roamingAllowed() const
+{
+    NetworkManager::Connection::Ptr connection = activeProfile();
+    if (!connection || !connection->settings()) {
+        return false;
+    }
+
+    NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
+    return gsmSetting && !gsmSetting->homeOnly();
+}
+
+bool CellularModem::profileOperationInProgress() const
+{
+    return m_profileOperationInProgress;
+}
+
 int CellularModem::signalStrength() const
 {
     return m_mmInterface ? m_mmInterface->signalQuality().signal : 0;
@@ -329,15 +345,41 @@ void CellularModem::refreshProfiles()
 {
     m_profiles->refreshProfiles();
     Q_EMIT profilesChanged();
+    Q_EMIT roamingAllowedChanged();
+}
+
+NetworkManager::Connection::Ptr CellularModem::activeProfile() const
+{
+    const QString connectionUni = activeConnectionUni();
+    if (connectionUni.isEmpty()) {
+        return {};
+    }
+
+    return NetworkManager::findConnectionByUuid(connectionUni);
+}
+
+void CellularModem::setProfileOperationInProgress(bool inProgress)
+{
+    if (m_profileOperationInProgress == inProgress) {
+        return;
+    }
+
+    m_profileOperationInProgress = inProgress;
+    Q_EMIT profileOperationInProgressChanged();
 }
 
-QCoro::Task<void> CellularModem::activateProfile(const QString &connectionUni)
+QCoro::Task<void> CellularModem::activateProfile(QString connectionUni)
 {
     if (!m_nmModem) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << "Cannot activate profile since there is no NetworkManager modem";
         co_return;
     }
 
+    if (m_profileOperationInProgress) {
+        qCWarning(PLASMA_NM_CELLULAR_LOG) << "Operation already in progress";
+        co_return;
+    }
+
     qCDebug(PLASMA_NM_CELLULAR_LOG) << "Activating profile on modem" << m_nmModem->uni() << "for connection" << connectionUni;
 
     NetworkManager::Connection::Ptr con;
@@ -357,25 +399,62 @@ QCoro::Task<void> CellularModem::activateProfile(const QString &connectionUni)
         co_return;
     }
 
+    setProfileOperationInProgress(true);
+    const bool allowRoaming = roamingAllowed();
+    if (!(co_await m_profiles->setRoamingAllowed(con->uuid(), allowRoaming))) {
+        addError(i18n("Could not apply the roaming preference. Activating the access point with its existing setting."));
+    }
+
     // despite the documentation saying otherwise, activateConnection seems to need the DBus path, not uuid of the connection
     QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::activateConnection(con->path(), m_nmModem->uni(), QString());
     if (!reply.isValid()) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << "Error activating connection:" << reply.error().message();
         addError(i18n("Error activating connection: %1", reply.error().message()));
+        setProfileOperationInProgress(false);
         co_return;
     }
 
     refreshProfiles();
     Q_EMIT activeConnectionUniChanged();
+    setProfileOperationInProgress(false);
+}
+
+QCoro::Task<void> CellularModem::setRoamingAllowed(bool allowed)
+{
+    if (m_profileOperationInProgress) {
+        co_return;
+    }
+
+    setProfileOperationInProgress(true);
+    NetworkManager::Connection::Ptr connection = activeProfile();
+    if (!connection) {
+        addError(i18n("Cannot change roaming because no access point is available."));
+        Q_EMIT roamingAllowedChanged();
+        setProfileOperationInProgress(false);
+        co_return;
+    }
+
+    if (!(co_await m_profiles->setRoamingAllowed(connection->uuid(), allowed))) {
+        addError(i18n("Error applying roaming preference to the selected access point."));
+        Q_EMIT roamingAllowedChanged();
+        setProfileOperationInProgress(false);
+        co_return;
+    }
+
+    refreshProfiles();
+    setProfileOperationInProgress(false);
 }
 
 QCoro::Task<void> CellularModem::addProfile(QString name, QString apn, QString username, QString password, QString networkType)
 {
-    if (!m_nmModem) {
+    if (!m_nmModem || m_profileOperationInProgress) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << "Cannot add profile since there is no NetworkManager modem";
         co_return;
     }
 
+    setProfileOperationInProgress(true);
+    const bool allowRoaming = roamingAllowed();
+
     NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Gsm)};
     settings->setId(name);
     settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
@@ -388,7 +467,8 @@ QCoro::Task<void> CellularModem::addProfile(QString name, QString apn, QString u
     gsmSetting->setPassword(password);
     gsmSetting->setPasswordFlags(password.isEmpty() ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
     gsmSetting->setNetworkType(CellularConnectionProfile::networkTypeFlag(networkType));
-    gsmSetting->setHomeOnly(true); // New profiles default to roaming disabled
+    // New profiles inherit the active profile's roaming preference.
+    gsmSetting->setHomeOnly(!allowRoaming);
 
     gsmSetting->setInitialized(true);
 
@@ -402,7 +482,10 @@ QCoro::Task<void> CellularModem::addProfile(QString name, QString apn, QString u
         addError(i18n("Error adding connection: %1", reply.error().message()));
     } else {
         qCDebug(PLASMA_NM_CELLULAR_LOG) << "Successfully added a new connection" << name << "with APN" << apn;
+        refreshProfiles();
     }
+
+    setProfileOperationInProgress(false);
 }
 
 QCoro::Task<void> CellularModem::removeProfile(const QString &connectionUni)
@@ -442,7 +525,7 @@ QCoro::Task<void> CellularModem::updateProfile(QString connectionUni, QString na
     gsmSetting->setPassword(password);
     gsmSetting->setPasswordFlags(password.isEmpty() ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
     gsmSetting->setNetworkType(CellularConnectionProfile::networkTypeFlag(networkType));
-    // Note: roaming is not set here, it's managed per-profile via CellularConnectionProfile::setRoamingAllowed()
+    // Note: roaming is not set here, it's managed per-profile
 
     gsmSetting->setInitialized(true);
 
@@ -459,21 +542,21 @@ QCoro::Task<void> CellularModem::updateProfile(QString connectionUni, QString na
     }
 }
 
-void CellularModem::addDetectedProfileSettings()
+QCoro::Task<void> CellularModem::addDetectedProfileSettings()
 {
     if (!m_mmModem) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << "ModemManager device missing, cannot detect profile settings";
-        return;
+        co_return;
     }
 
     if (!hasSim() || !m_mmModem->sim()) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << "No SIM found, cannot detect profile settings";
-        return;
+        co_return;
     }
 
     if (!m_mm3gppDevice) {
         qCWarning(PLASMA_NM_CELLULAR_LOG) << "3gpp object not found, cannot detect profile settings";
-        return;
+        co_return;
     }
 
     bool found = false;
@@ -500,7 +583,7 @@ void CellularModem::addDetectedProfileSettings()
                     name += u" - "_s + apnInfo[u"name"_s].toString();
                 }
 
-                addProfile(name, apn, apnInfo[u"username"_s].toString(), apnInfo[u"password"_s].toString(), u"4G/3G/2G"_s);
+                co_await addProfile(name, apn, apnInfo[u"username"_s].toString(), apnInfo[u"password"_s].toString(), u"4G/3G/2G"_s);
             }
         }
     }
diff --git a/libs/cellular/cellularmodem.h b/libs/cellular/cellularmodem.h
index 2350ba604..da0c03cfc 100644
--- a/libs/cellular/cellularmodem.h
+++ b/libs/cellular/cellularmodem.h
@@ -48,6 +48,8 @@ class PLASMANM_CELLULAR_EXPORT CellularModem : public QObject
     Q_PROPERTY(bool simLocked READ simLocked NOTIFY simLockedChanged)
     Q_PROPERTY(bool simEmpty READ simEmpty NOTIFY simEmptyChanged)
     Q_PROPERTY(QString activeConnectionUni READ activeConnectionUni NOTIFY activeConnectionUniChanged)
+    Q_PROPERTY(bool roamingAllowed READ roamingAllowed NOTIFY roamingAllowedChanged)
+    Q_PROPERTY(bool profileOperationInProgress READ profileOperationInProgress NOTIFY profileOperationInProgressChanged)
 
     Q_PROPERTY(bool mobileDataEnabled READ mobileDataEnabled WRITE setMobileDataEnabled NOTIFY mobileDataEnabledChanged)
     Q_PROPERTY(bool mobileDataSupported READ mobileDataSupported NOTIFY mobileDataSupportedChanged)
@@ -78,6 +80,8 @@ public:
     bool hasSim() const;
     bool simLocked() const;
     bool simEmpty() const;
+    bool roamingAllowed() const;
+    bool profileOperationInProgress() const;
 
     int signalStrength() const;
     QString operatorName() const;
@@ -86,11 +90,12 @@ public:
     Q_INVOKABLE void clearErrors();
 
     // Connection profile management
-    Q_INVOKABLE QCoro::Task<void> activateProfile(const QString &connectionUni);
+    Q_INVOKABLE QCoro::Task<void> activateProfile(QString connectionUni);
+    Q_INVOKABLE QCoro::Task<void> setRoamingAllowed(bool allowed);
     Q_INVOKABLE QCoro::Task<void> addProfile(QString name, QString apn, QString username, QString password, QString networkType);
     Q_INVOKABLE QCoro::Task<void> removeProfile(const QString &connectionUni);
     Q_INVOKABLE QCoro::Task<void> updateProfile(QString connectionUni, QString name, QString apn, QString username, QString password, QString networkType);
-    Q_INVOKABLE void addDetectedProfileSettings(); // detect modem connection settings (ex. apn) and add a new connection
+    Q_INVOKABLE QCoro::Task<void> addDetectedProfileSettings(); // detect modem connection settings (ex. apn) and add a new connection
 
     QList<CellularSim *> sims();
 
@@ -102,6 +107,8 @@ public:
 Q_SIGNALS:
     void modemDetailsChanged();
     void profilesChanged();
+    void roamingAllowedChanged();
+    void profileOperationInProgressChanged();
     void uniChanged();
     void displayIdChanged();
     void activeConnectionUniChanged();
@@ -123,6 +130,8 @@ private:
     void refreshSims();
     void refreshProfiles();
     void addError(const QString &message);
+    NetworkManager::Connection::Ptr activeProfile() const;
+    void setProfileOperationInProgress(bool inProgress);
 
     CellularModemDetails *m_details = nullptr;
     CellularConnectionProfile *m_profiles = nullptr;
@@ -134,4 +143,5 @@ private:
 
     QList<CellularSim *> m_sims;
     QStringList m_errors;
+    bool m_profileOperationInProgress = false;
 };
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.