Built-in xdg-desktop-portal backend — design notes#
Status: ScreenCast shipped (M2).
margo-portalis a real daemon implementingorg.freedesktop.impl.portal.ScreenCast; window + monitor share work natively — capture is the compositor's own Mutter shim → PipeWire, the picker is mshell's Screenshare chooser — andassets/margo-portals.confroutesScreenCast=margo;gnome. No GNOME portal package is needed for screen-share. Screenshot (M1) and RemoteDesktop (M3) are still served by the gnome backend; the sections below remain the plan for hosting them natively too.
Why a built-in backend#
Margo already implements the Wayland-side capture protocols
(wlr-screencopy-v1, linux-dmabuf-v1, wp-presentation) and runs
its own dispatch loop. Forcing the portal layer through xdp-wlr means
two extra D-Bus hops, an external process the user has to remember to
keep updated, and policy splitting: the portal-wlr restriction list
doesn't see margo's window-rule-driven block_out_from_screencast
flag, so blackout-on-screencast clients leak through portal-routed
capture.
Goals:
- Single policy: window-rule
block_out_from_screencast:1suppresses content in capture flows the user goes through portals (browser screen-share, screenshot tools using xdp Screenshot). - No xdp-wlr install:
pacman -Rdd xdg-desktop-portal-wlrshould leave a working session. - Region / window picker: Screenshot's interactive region chooser should integrate with margo's overview / focus state instead of spawning slurp blindly.
Architecture#
┌─────────────────────┐
browser ────────► │ xdg-desktop-portal │ (xdp main daemon)
└─────────┬───────────┘
│ dbus method call
▼
┌─────────────────────┐
│ margo-portal │ ← new crate, this doc
│ (separate binary) │
└────┬───────┬────────┘
│ │
│ │ wayland (wl_display)
│ ▼
│ ┌─────────────┐
│ │ margo │ (compositor)
│ │ │
│ └─────────────┘
│ ▲
│ │ wlr-screencopy / dmabuf
│ │
│ │
▼ │
spawn grim / wf-recorder
(where helpful)
margo-portal runs as a user-systemd service activated lazily by D-Bus
("org.freedesktop.impl.portal.desktop.margo"), implements the relevant
org.freedesktop.impl.portal.* interfaces, and where it can't answer a
request natively (e.g. arbitrary file rec) shells out to grim /
wf-recorder against the live Wayland connection.
Scoped milestones#
The full portal surface is too large for a single sprint. Ship in priority order, each milestone independently usable.
M1 — Screenshot (region + full screen)#
org.freedesktop.impl.portal.Screenshot.Screenshot()→ margo-portal opens a wlr-screencopy frame, encodes PNG, returns the file URI to xdp.Screenshot.PickColor()→ wlr-screencopy single-pixel sample.- Interactive flag: when true, spawn slurp for region selection then resolve as above.
Estimated size: ~400 LOC + zbus + image crate. Replaces 80 % of what xdp-wlr is needed for.
M2 — ScreenCast (browser screen-share)#
org.freedesktop.impl.portal.ScreenCast.CreateSession(),SelectSources(),Start().- Source picker: native dialog showing margo's outputs + visible windows (already enumerable via dwl-ipc-v2).
- Pipewire stream: re-publish the existing wlr-screencopy frames into
pipewire's portal node. The
block_out_from_screencastflag is honoured by the renderer already; the portal just inherits the filter for free.
Estimated size: ~600 LOC + pipewire-rs.
M3 — RemoteDesktop#
Out of scope until M1+M2 settle; remote-desktop adds an input-injection surface that needs careful design (margo's keyboard / pointer paths don't currently take synthetic events). Defer.
M4 — File chooser polish#
xdp-gtk handles FileChooser fine and probably always will. We don't
re-implement; we just ensure margo's window-rule for the
xdg-desktop-portal-(gtk|gnome|wlr) toplevels (already in
config.example) keeps the dialog floating + sized + focused on first
map.
Why not just use xdp-wlr?#
- xdp-wlr is a
tinywl-shaped reference impl maintained on a thin resource budget — its bug tracker has open year-old issues. - The
block_out_from_screencastwindow-rule margo carries is invisible to xdp-wlr (it doesn't look at any compositor state beyond the wlr protocols themselves). - The "select region" UX in xdp-wlr is "spawn slurp, hope the user knows what slurp is".
Crate layout#
margo-portal/
├── Cargo.toml
├── src/
│ ├── lib.rs # zbus bus connection, service registration
│ ├── interfaces/
│ │ ├── mod.rs
│ │ ├── screenshot.rs # M1
│ │ └── screencast.rs # M2
│ ├── wayland.rs # connection to margo via dwl-ipc-v2 +
│ │ # screencopy capture
│ └── bin/
│ └── margo-portal.rs # main, wires the bus
├── assets/
│ └── margo-portal.service # systemd user unit
└── docs/
└── interfaces.md # auto-generated from xml stubs
A single binary keeps deployment simple. margo-portal activates from
org.freedesktop.impl.portal.desktop.margo at first xdp call and
sticks around for the session.
Build deps#
zbus = "5"— D-Bus client + servicetokio = "1"(rt-multi-thread + macros) — zbus 5 requires asyncimage = "0.25"— PNG / JPEG encoding for Screenshot outputpipewire = "0.8"— for M2 only; gate behind--features screencastslurp+grimruntime dependencies (already optdepends on margo-git)
How packagers ship this#
Once M1 lands:
- margo-git PKGBUILD installs
/usr/lib/margo/margo-portaland/usr/share/dbus-1/services/org.freedesktop.impl.portal.desktop.margo.service - Updates
/usr/share/xdg-desktop-portal/margo-portals.confto switchorg.freedesktop.impl.portal.Screenshot=margo. - Drops
xdg-desktop-portal-wlrfrom optdepends — only listed for user systems still on the legacy path.
Why this is [ ] and not done#
The work to make this real is well scoped (~1500 LOC for M1+M2), but multiplied by the testing matrix (browser screen share, third-party screenshot tools, sandboxed apps via xdp's session token flow) it's a multi-week effort. This document exists so the next person picking it up doesn't re-derive the architecture from scratch.
Implementation tracker: see issue [TBD] on the GitHub repo.