Portal first-run prompt UX under niri #15

Closed
opened 2026-11-05 16:14:21 +00:00 by xavierk · 1 comment
Owner

Part of #1

Question

What is the first-run UX of the RemoteDesktop portal permission prompt under niri: when does it appear, what must the user click, and what happens on deny or re-prompt? Ground in portal docs and the actual injection code path.

Part of #1 ## Question What is the first-run UX of the RemoteDesktop portal permission prompt under niri: when does it appear, what must the user click, and what happens on deny or re-prompt? Ground in portal docs and the actual injection code path.
xavierk added the wayfinder:research label 2026-11-05 16:14:21 +00:00
xavierk self-assigned this 2026-11-05 16:25:46 +00:00
Author
Owner

Resolution: Portal first-run prompt UX under niri

Decision (agent-made under the user's explicit delegation for this run): there is no portal permission prompt on niri, by design of the desktop stack — and the spec should say so plainly. On niri, first dictation goes straight to the wtype fallback with no permission UI at all. The RemoteDesktop permission dialog exists only on desktops whose portal backend implements org.freedesktop.portal.RemoteDesktop (GNOME, KDE). Consequences for the spec and the port are listed at the end.

When the prompt appears (desktops that have one). The probe at startup never prompts: RemoteDesktopPortal.probe() only reads the interface version property (src/vocalinux/text_injection/remote_desktop_portal.py, probe), and TextInjector._portal_probe() documents that "the session handshake (and its one-time permission prompt) is deferred to the first actual injection" (src/vocalinux/text_injection/text_injector.py). The dialog is presented by the compositor's portal backend during the CreateSession → SelectDevices → Start handshake in _ensure_session(), which runs lazily on the first inject_text / tap_keysym / send_shortcut. Start() carries a 180 s budget (_START_TIMEOUT_S) precisely because it blocks on the user clicking through that dialog.

What the user clicks (GNOME/KDE). Per the upstream portal spec (RemoteDesktop docs), Start() "will typically result in the portal presenting a dialog letting the user select what to share, including devices". Voculinux requests keyboard only (_DEVICE_TYPE_KEYBOARD), so the user approves keyboard access for the session. On success with interface v2, Start returns a single-use restore_token, which the client persists owner-only (0600) at $XDG_DATA_HOME/vocalinux/remote_desktop_restore_token (_save_restore_token); the next session offers that token in SelectDevices (persist_mode 2, until revoked) and the prompt is skipped. A stale token is harmless: the spec says an invalid one is ignored and the user gets the normal prompt. On interface v1 there is no persistence and the prompt recurs on every app start.

Why niri never prompts. No portal backend implements RemoteDesktop on niri. xdg-desktop-portal-wlr implements only Screenshot and ScreenCast; the GNOME backend's RemoteDesktop path requires Mutter's private RemoteDesktop service, which niri does not run. Our own user guide already records this: "portal needs a desktop implementing the org.freedesktop.portal.RemoteDesktop interface (GNOME and KDE Plasma do; most wlroots compositors do not)" (docs/USER_GUIDE.md). Independent third-party matrices agree (wlroots-family compositors including niri ship no RemoteDesktop backend; wtype is the working route there). With no backend, the version read fails, probe() returns False, the portal backend is never selected, and dictation falls through to wtype — silently, with no dialog, nothing to click, and nothing to deny.

Deny and re-prompt (where a prompt exists). Dismissing the dialog returns response code 1, raised as "Portal request was cancelled by the user" (_await_response); the client closes the unfinished session so no half-grant lingers, never retries Start on that session, and keeps the saved restore token untouched — all pinned by tests/test_remote_desktop_portal.py (test_cancelled_start_is_not_retried, test_failed_session_is_closed). The injector then demotes the portal for the run (_demote_portal_backend) and types the remaining text through wtype/ydotool/XWayland, so a deny costs a fallback, not the dictation. Nothing negative is cached: the next app start builds a fresh session and the dialog appears again, until the user grants once (then the restore token takes over on v2).

Provenance. Resolved by agent decision under the user's explicit delegation for this wayfinder run ("answer all the wayfinder questions ... continue till all wayfinder tickets are closed"). Grounded in the portal client and injector code paths above, the pinned portal tests, the upstream org.freedesktop.portal.RemoteDesktop spec, and third-party compositor matrices for the niri-negative.

Consequences for later work.

  • The Wayland terminal injection choice (issue #6) stands unchanged, but its "portal first" rung is a GNOME/KDE rung: on niri the effective order starts at wtype. The handoff spec should state the niri first-run experience as "no prompt; wtype types directly".
  • The voculinux rename's one-time import (issue #8) copies config plus dictionary; it does not mention remote_desktop_restore_token. GNOME/KDE migrants will see one extra permission prompt after the rename unless the import carries that file too — a one-line addition for the implementation phase, moot on niri.
  • No new tickets: this answer closes the last in-scope question and graduates nothing new. Findings live in this comment; no research branch was pushed.
## Resolution: Portal first-run prompt UX under niri **Decision (agent-made under the user's explicit delegation for this run): there is no portal permission prompt on niri, by design of the desktop stack — and the spec should say so plainly.** On niri, first dictation goes straight to the `wtype` fallback with no permission UI at all. The RemoteDesktop permission dialog exists only on desktops whose portal backend implements `org.freedesktop.portal.RemoteDesktop` (GNOME, KDE). Consequences for the spec and the port are listed at the end. **When the prompt appears (desktops that have one).** The probe at startup never prompts: `RemoteDesktopPortal.probe()` only reads the interface `version` property (`src/vocalinux/text_injection/remote_desktop_portal.py`, `probe`), and `TextInjector._portal_probe()` documents that "the session handshake (and its one-time permission prompt) is deferred to the first actual injection" (`src/vocalinux/text_injection/text_injector.py`). The dialog is presented by the compositor's portal backend during the `CreateSession` → `SelectDevices` → `Start` handshake in `_ensure_session()`, which runs lazily on the first `inject_text` / `tap_keysym` / `send_shortcut`. `Start()` carries a 180 s budget (`_START_TIMEOUT_S`) precisely because it blocks on the user clicking through that dialog. **What the user clicks (GNOME/KDE).** Per the upstream portal spec ([RemoteDesktop docs](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.RemoteDesktop.html)), `Start()` "will typically result in the portal presenting a dialog letting the user select what to share, including devices". Voculinux requests keyboard only (`_DEVICE_TYPE_KEYBOARD`), so the user approves keyboard access for the session. On success with interface v2, `Start` returns a single-use `restore_token`, which the client persists owner-only (`0600`) at `$XDG_DATA_HOME/vocalinux/remote_desktop_restore_token` (`_save_restore_token`); the next session offers that token in `SelectDevices` (`persist_mode` 2, until revoked) and the prompt is skipped. A stale token is harmless: the spec says an invalid one is ignored and the user gets the normal prompt. On interface v1 there is no persistence and the prompt recurs on every app start. **Why niri never prompts.** No portal backend implements RemoteDesktop on niri. `xdg-desktop-portal-wlr` implements only Screenshot and ScreenCast; the GNOME backend's RemoteDesktop path requires Mutter's private RemoteDesktop service, which niri does not run. Our own user guide already records this: "`portal` needs a desktop implementing the `org.freedesktop.portal.RemoteDesktop` interface (GNOME and KDE Plasma do; most wlroots compositors do not)" (`docs/USER_GUIDE.md`). Independent third-party matrices agree (wlroots-family compositors including niri ship no RemoteDesktop backend; `wtype` is the working route there). With no backend, the version read fails, `probe()` returns False, the portal backend is never selected, and dictation falls through to `wtype` — silently, with no dialog, nothing to click, and nothing to deny. **Deny and re-prompt (where a prompt exists).** Dismissing the dialog returns response code 1, raised as "Portal request was cancelled by the user" (`_await_response`); the client closes the unfinished session so no half-grant lingers, never retries `Start` on that session, and keeps the saved restore token untouched — all pinned by `tests/test_remote_desktop_portal.py` (`test_cancelled_start_is_not_retried`, `test_failed_session_is_closed`). The injector then demotes the portal for the run (`_demote_portal_backend`) and types the remaining text through `wtype`/`ydotool`/XWayland, so a deny costs a fallback, not the dictation. Nothing negative is cached: the next app start builds a fresh session and the dialog appears again, until the user grants once (then the restore token takes over on v2). **Provenance.** Resolved by agent decision under the user's explicit delegation for this wayfinder run ("answer all the wayfinder questions ... continue till all wayfinder tickets are closed"). Grounded in the portal client and injector code paths above, the pinned portal tests, the upstream `org.freedesktop.portal.RemoteDesktop` spec, and third-party compositor matrices for the niri-negative. **Consequences for later work.** - The Wayland terminal injection choice (issue #6) stands unchanged, but its "portal first" rung is a GNOME/KDE rung: on niri the effective order starts at `wtype`. The handoff spec should state the niri first-run experience as "no prompt; wtype types directly". - The voculinux rename's one-time import (issue #8) copies config plus dictionary; it does not mention `remote_desktop_restore_token`. GNOME/KDE migrants will see one extra permission prompt after the rename unless the import carries that file too — a one-line addition for the implementation phase, moot on niri. - No new tickets: this answer closes the last in-scope question and graduates nothing new. Findings live in this comment; no research branch was pushed.
Sign in to join this conversation.