feat(privileged, helper): KAuth pipeline for login surface#6
Merged
Conversation
LoginSurface rejoins the GUI. The card now shows up as the third surface in the SyncEngine, with Apply routed through KAuth to a dedicated helper that has the privileges to overwrite /etc/plasmalogin.conf. Three new pieces wired up: src/helper/plasma-wallpaper-sync-helper — standalone executable that runs as root for the duration of one action. Inherits KAuth::HelperSupport, exposes a single `writefile(args)` slot bound to the action id `dev.manuacl.plasmawallpapersync.writefile`. Refuses any path other than `/etc/plasmalogin.conf` as defense in depth, then writes atomically via QSaveFile::commit(). KAUTH_HELPER_MAIN generates the main() with all the D-Bus and service-activation plumbing. src/privileged/kauth/KAuthPrivilegedWriter — concrete PrivilegedWriter. writeAtomically() builds a KAuth::Action with the helper id, packs path/contents in a QVariantMap, fires execute() and forwards the async job's result through writeSucceeded / writeFailed. Lives outside src/core/ because it links KF6::AuthCore (which is on the core/ layering guard's deny list — the seam is preserved). data/dev.manuacl.plasmawallpapersync.policy — polkit policy. Action defaults: allow_active=auth_admin so the user sees a sudo prompt the first time per session. allow_inactive=no keeps a logged-out user from racing into the action. The shell instantiates KAuthPrivilegedWriter once at startup and hands a pointer to LoginSurface. SyncEngine.addSurface picks the new surface up; Main.qml's Repeater over `syncEngine.surfaceIds` now renders three cards automatically — no QML change needed. CMake: kauth_install_helper_files + kauth_install_actions handle the D-Bus service file, helper binary placement, and polkit policy installation in one shot. Live testing note: the Flatpak SDK install of this build CANNOT run KAuth end-to-end — the helper, polkit policy and D-Bus service file all land under /app/ inside the sandbox, invisible to the host's polkit daemon and session bus. The UI surfaces Login, Apply will fail with an action-not-registered error. End-to-end KAuth validation requires a native install (system make-install or distro package). Architecturally everything is in place; this is exactly the v0.1.0 native-release shape called out in CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: the Login card showed "(no wallpaper recorded)" on a
Flatpak install even though the user's /etc/plasmalogin.conf has a
valid wallpaper entry.
Root cause: /etc/ inside a Flatpak sandbox is the runtime's /etc/,
not the host's. With --filesystem=host:ro the host's /etc/ is
bind-mounted at /run/host/etc/. LoginSurface was hardcoded to read
from /etc/plasmalogin.conf, which under Flatpak resolves to the
runtime's empty /etc.
LoginSurface now distinguishes two paths:
- readPath: where currentImagePath() reads from. Defaults to
/run/host/etc/plasmalogin.conf under Flatpak (detected
via the /.flatpak-info sentinel) and to
/etc/plasmalogin.conf on a native install.
- writePath: what the writer is told to write to. Always
/etc/plasmalogin.conf — that's the canonical host path
the privileged helper validates against (the helper
runs on the host, where /etc/ IS the right place).
Three constructors:
- LoginSurface(writer): production defaults from defaultReadPath()
and defaultWritePath().
- LoginSurface(writer, configPath): both paths the same — used by
every existing tst_LoginSurface case, no migration needed.
- LoginSurface(writer, readPath, writePath): explicit, for the
Flatpak case and the new readPathAndWritePathCanDiffer test.
Signal filter in wireWriterSignals now keys on m_writePath (what the
writer actually emits), not on a single m_configPath.
8/8 LoginSurface tests pass (10 if you count init/cleanup), including
the new readPathAndWritePathCanDiffer regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small follow-ups discovered during the native end-to-end test on a real Bazzite + Plasma 6.10 host: 1. WallpaperLibrary.h docstring used `path/*.<ext>` notation inside a /* … */ block comment, which gcc -Wcomment warns about (the "/*" sequence reads as a nested comment opening). Switched to prose + `<file>.<ext>` notation; also updated the description to mention the one-level-subdir scan we added later. 2. tst_LoginSurface::readPathAndWritePathCanDiffer used "/etc/plasmalogin.conf" as the write target. Under Flatpak that path is redirected into the per-app sandbox dir so the FakePrivilegedWriter could create it; outside a sandbox (toolbox build sharing the host filesystem) the same test tried to write to the real /etc/ and was rejected by the kernel. Per-test temp path makes the test environment-independent. 3. PlasmaReloader hard-coded `org.kde.plasmashell` as the D-Bus service to talk to, which made tst_PlasmaReloader::desktopWithoutPlasmaShellEmitsFailure pass only when no plasmashell is reachable (Flatpak sandbox, CI container) and fail when one is (toolbox build alongside a live Plasma session). Added a test-only constructor accepting a service name; the test now targets a deliberately-fake bus name so the failure path is exercised regardless of the host's state. 4. dev/install-host-helper.sh was generating /etc/dbus-1/system.d/ *.conf by splicing the upstream conf with sed, which ended up producing nested <busconfig> elements and a dbus ReloadConfig that refused to apply. Rewrote with inline templates and added verification probes (D-Bus ReloadConfig + pkaction + service activation introspection) so future runs surface failures at the moment of install. Also preferred the build-native/ tree as the helper source when present. End-to-end manual flow validated on the host: native binary built in a fedora-toolbox dispatches KAuth, polkit-kde-authentication-agent prompts for the user's password, systemd starts the helper via D-Bus activation, the helper writes /etc/plasmalogin.conf atomically, the GUI shows "Applied to: desktop, lockscreen, login" in green. 6/6 test suites pass both in CI (Fedora 42 container, no plasmashell) and in a local toolbox (Fedora 42, plasmashell on session bus). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reuse 5.x parses any line containing "SPDX-License-Identifier:" — even inside markdown body text — as a license declaration. The CLAUDE.md bullet documenting our header convention literally writes the string, which reuse then tries to parse and rejects as an empty expression once the line wraps before the license name. Wrapping the bullet in <!-- REUSE-IgnoreStart --> / <!-- REUSE-IgnoreEnd --> markers tells reuse to skip the block. The doc text still reads normally for humans; reuse stops trying to interpret it as a real declaration. CI didn't catch this earlier because the GitHub action runs an older reuse where the strictness was lower; reuse 5.x (Fedora 42 toolbox) flags it. Same fix applies regardless of which version CI ends up on. (Also incidentally fixed an obsolete "Manu" attribution in the same bullet → "Manuel Chamorro".) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
manuacl
added a commit
that referenced
this pull request
May 26, 2026
PR #6 wired the helper into the privileged-write seam but the visible desktop wallpaper still didn't update on Apply. Root cause: nothing was telling Plasma to re-read the appletsrc — PlasmaReloader existed and was tested, but never instantiated by the shell. Wiring it up turned out to be the easy part. The harder part was the reload script itself: every variant that didn't pass the actual image through Plasma silently no-op'd on Plasma 6.10. Documented inline in PlasmaReloader.cpp, the three rejected approaches were: - `d.wallpaperPlugin = d.wallpaperPlugin` — same-value setter is short-circuited - `d.reloadConfig()` — recognized by the scripting API but the wallpaper plugin doesn't re-render in response - toggle `wallpaperPlugin = 'org.kde.color'` then back to `'org.kde.image'` — both setters happen in one script eval, Plasma batches them as a net-zero change and skips the reload The fix matches what Plasma System Settings does internally on its Wallpaper page: the script writes the Image entry through Plasma's own writeConfig, which goes through the wallpaper plugin's setter path and emits the repaint signal the renderer actually listens for. Our prior KConfig write to disk stays — it's still authoritative across restarts; this call is the live-update bridge that pokes Plasma without waiting for the next session. API: - notifyDesktopChanged() → notifyDesktopChanged(QString imagePath) Caller passes the path that just landed in the config so the script can substitute it. Single-quoted JS string literal with \\ and \' escapes for safety. - PlasmaReloader.h documents the path-on-call rationale. - Shell's connection to surfaceApplySucceeded looks up the surface by id, asks it for currentImagePath(), passes that through. tst_PlasmaReloader updated for the new signature. Note: lockscreen + login stay as no-ops on purpose — kscreenlocker re-reads on the next lock event, plasmalogin only at boot. Live reload is meaningless for them. End-to-end validated: Apply Desktop on a native install now updates the live wallpaper without a re-login. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
LoginSurface rejoins the GUI. The card now shows up as the third surface in SyncEngine, with Apply routed through KAuth to a dedicated helper that has the privileges to overwrite `/etc/plasmalogin.conf`.
Three pieces:
`SyncEngine.addSurface(&login)` is all the integration the shell needs — Main.qml's Repeater renders the third card automatically.
Test plan
Known limitation (and v3 evolution flag)
Under Flatpak, Apply on Login will fail with an action-not-registered error. This is the symptom of the v3 distribution gap noted in CLAUDE.md: a future `flatpak-spawn`-based `PrivilegedWriter` (or a portal-mediated approach) would slot into `src/privileged/flatpak/` alongside the kauth one without touching `src/core/` or `LoginSurface`. That work is deferred — for the v0.1.0 native release it isn't on the path.