Welcome and Introduction

KDED refactoring Progress Update!

The past couple of weeks moved on to the other half of the editor work- the kded dialogs that pops up on NetworkManager's behalf - the secret prompt, the SIM PIN dialog, the mobile broadband wizard.

Why this needs doing

The Connection Editor isn't the only place libs/editor gets used. kded's network management module runs as NetworkManager's secret agent: whenever NetworkManager needs a password, a PIN, or a fresh mobile broadband connection, it asks the agent, and the agent has been popping up a QDialog ever since. PasswordDialog asks for Wi-Fi/PPP/VPN secrets, PinDialog unlocks a SIM, MobileConnectionWizard walks through adding a GSM/CDMA connection when you plug in a modem or pair a Bluetooth phone for DUN. All three are widgets, and the VPN half of PasswordDialog reuses the same VpnUiPlugin-returning-a-QWidget mechanism the editor already moved off of.

The straightforward half

The new module is kdedqml, structured the same way editorqml was: a small set of QObjects and a PromptWindow that hosts whichever QML file they back.

kdedqml/
├── passwordprompt.cpp / .h    secrets for a plain setting or a VPN
├── pinprompt.cpp / .h         SIM PIN/PUK unlock
├── mobilewizard.cpp / .h      GSM/CDMA connection wizard
├── promptwindow.cpp / .h      hosts one QML file + one backing QObject
└── qml/
    ├── PasswordPrompt.qml
    ├── PinPrompt.qml
    └── MobileWizard.qml

PromptWindow is the one new idea here, and it's deliberately dumb - give it a QUrl and a QObject, and it loads the QML file into a QQmlApplicationEngine, exposes the object as a context property named prompt, and shows the window. Every one of the three prompts is just "construct the backing object, hand it to a PromptWindow":

m_promptWindow->show(QUrl(QStringLiteral("qrc:/plasma-nm/kdedqml/qml/PasswordPrompt.qml")), m_dialog);

PasswordPrompt itself does the boring 90% of the work first: it duplicates what PasswordDialog already did for plain secrets (Wi-Fi retry messages, WEP/WPA key validation via NetworkManagerQt rather than a regex, the same rule as last time) and, for VPNs, reuses the AuthSetting classes the editor already has:

if (shortName == QLatin1String("ssh")) {
    m_vpnAuth = createAuth<SshAuthSetting>(hints, this, vpnSetting, m_vpnSecrets);
} else if (shortName == QLatin1String("sstp")) {
    m_vpnAuth = createAuth<SstpAuthSetting>(hints, this, vpnSetting, m_vpnSecrets);
} ...

createAuth constructs the setting and calls loadSecrets(), just pointed at secrets instead of full config. Ten VPN types wired up this way, and PasswordPrompt.qml picks the matching Auth.qml from the editor with a Loader switching on service type, exactly like the editor's own VPN page switches on it.

The half that is actually interesting

OpenConnect doesn't fit that shape at all, because it was never really a settings-and-secrets dialog. The widget version, OpenconnectAuthWidget, runs a whole login session: it drives libopenconnect on a worker thread, and the C library calls back into Qt synchronously to ask for a login form, validate a server certificate, or open a browser for single sign-on - and it expects an answer before it returns, because it's still in the middle of openconnect_obtain_cookie().

The trick the widget uses, and the one I had to keep, is that the callback doesn't wait on the GUI thread's answer via a blocking Qt connection. It emits a signal, then blocks itself on a QWaitCondition:

int OpenconnectAuthWorkerThread::validatePeerCert(void *cert, const char *reason)
{
    ...
    bool accepted = false;
    m_mutex->lock();
    Q_EMIT validatePeerCert(qFingerprint, qCertinfo, qReason, &accepted);
    m_waitForUserInput->wait(m_mutex);
    m_mutex->unlock();
    ...
}

The worker thread is asleep inside wait(), so the bool *accepted pointer it handed across threads stays valid for however long the GUI takes to answer - which for a modal QDialog::exec() was instant, but for a QML dialog the answer only comes back later, from a separate button click. So OpenconnectAuth (the new QML-facing class) splits every one of these callbacks into two halves: the slot that receives the signal just records the state and returns immediately, and a separate Q_INVOKABLE - acceptCertificate(), submitForm() - does the actual wakeAll() once the user has answered:

void OpenconnectAuth::acceptCertificate(bool accept)
{
    *m_certAcceptedPtr = accept;
    ...
    m_mutex.lock();
    m_workerWaiting.wakeAll();
    m_mutex.unlock();
}

Everything else - the dynamic login form built from oc_auth_form, the "changing the group re-submits" behaviour, the SSO web login - is the same worker thread, copied unchanged, talking to a QML WebEngineView instead of a QWebEngineView widget. The two share the same underlying Qt WebEngine types (QWebEngineLoadingInfo, QWebEngineCookieStore, QWebEngineWebAuthUxRequest), so the bridge is mostly mechanical - a WebEngineView.onWebAuthUxRequested handler calling straight into the existing OpenconnectWebAuth helper from the editor's SSO work.

Wiring it together

secretagent.cpp picks between PasswordDialog and PasswordPrompt with a type alias behind HAVE_KDEDQML, so the rest of the file barely changes:

#ifdef HAVE_KDEDQML
using SecretsPrompt = PasswordPrompt;
#else
using SecretsPrompt = PasswordDialog;
#endif

The one real change is that closing a prompt used to be m_dialog->deleteLater() scattered across cancel, reject, and kill paths; those all go through one closePrompt() now, which also closes the shared PromptWindow if there is one. bluetoothmonitor.cpp and modemmonitor.cpp get the same treatment for the mobile wizard and the PIN dialog - and the PIN one loses something along the way: it no longer calls QDialog::exec(), so unlocking a SIM doesn't block kded on a nested event loop anymore.

OpenConnect gets one more property on top of that, selfDriven, because the worker thread accepts the dialog itself once it has a cookie - there's no Ok button to press, only Cancel:

standardButtons: prompt.selfDriven ? QQC2.DialogButtonBox.Cancel : QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel

Same BUILD_EDITORQML flag as before, just gating one more directory now.

What is left

The mobile broadband wizard, PIN prompt, and OpenConnect are all wired up now. What's left is test coverage for the new kdedqml classes, and the actual port to Plasma Mobile, since PromptWindow and the three prompts were built with a phone-sized layout in mind but haven't been run on one yet.

HAVE_KDEDQML and HAVE_OPENCONNECT both mean the widget path is still there, on purpose - nothing gets to come out until the QML path has actually been exercised end to end, tests included. And this was only the kded side; the applet's Handler::showConnectionEditor() still opens the widget ConnectionEditorDialog directly for WPA-Enterprise networks it can't join with a password alone, which is the other loose thread from last time and still isn't pulled.

Thanks, see you soon.