- C 75.3%
- Go 22.3%
- Shell 2.1%
- Makefile 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| cmd/portal-view | ||
| docs | ||
| internal | ||
| native | ||
| scripts | ||
| .gitignore | ||
| CHANGES-2026-08-27.md | ||
| CODEX_HANDOFF.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
portal-view
A small Wayland utility-in-progress for mirroring one portal-selected application window onto another local Wayland surface.
The intended path is:
KWin window
-> xdg-desktop-portal-kde WINDOW picker
-> restricted PipeWire stream
-> DMA-BUF
-> EGLImage / GL texture
-> Wayland viewer
-> physical or KWin virtual output
-> optionally Sunshine/Moonlight
The hot path is designed to avoid copying frame pixels into Go memory. Go owns CLI/session orchestration; a compact native PipeWire/EGL/Wayland shim owns frame negotiation/import/presentation.
Status
The portal selection path works: portal-view connects to xdg-desktop-portal
ScreenCast over D-Bus, shows the compositor's window picker, receives the
restricted PipeWire socket FD and stream identity (v6 pipewire-serial with
v5 node-ID fallback), and supports --save/--restore restore tokens.
The native build now negotiates KWin's BGRA/BGRx shared-memory stream, uploads frames through EGL/GLES2, and displays them in a regular resizable xdg-toplevel window. DMA-BUF import remains a later optimization.
make native
./bin/portal-view
The no-flag command opens the portal window picker, then presents the selected
window in a regular resizable portal-view window. --fullscreen requests
fullscreen mode; --output NAME selects the fullscreen output when supported.
Use --list-outputs to list compositor outputs without opening the portal.
Viewer keys:
qorEscapecloses the viewer.ftoggles compositor-managed borderless fullscreen; pressing it again restores the regular window.1..9draw the frame at that integer pixel ratio (1:1, 2x, ...), centered and cropped when larger than the window.0restores fit-to-window scaling.scycles the scaling filter:auto(nearest for integer zoom, bilinear for fit),nearest,bilinear, thenbicubic(Catmull-Rom).
Scaling/fullscreen key changes also display a short mpv-style on-screen status
toast (for example scaling mode: bicubic or scaling locked: 1:1). Integer
zoom uses an integer framebuffer viewport; 1 therefore has an exact 1 source
pixel -> 1 output pixel mapping in the viewer itself.
If the selected source changes size, PipeWire may renegotiate the capture frame dimensions; the viewer updates its texture and preserves the current destination window size. The compositor owns xdg-toplevel sizing, so source resizing does not automatically resize the viewer window.
On fractional-scale Wayland outputs (e.g. 125%), the viewer negotiates
fractional-scale-v1 and viewporter so its EGL buffer matches physical pixels;
1 then maps each captured pixel to one physical pixel. Note that KWin may
already have resampled the source window while producing the portal stream,
so this cannot reconstruct font pixels lost before portal-view receives them.
For Fedora development dependencies:
./scripts/fedora-dev-deps.sh
./scripts/check-host.sh
Then hand the entire repository to a coding agent with CODEX_HANDOFF.md as the starting brief.
Running the native build
The real binary (portal picker + PipeWire stream) needs the native dev
packages and the native build tag:
make native # tests + build with -tags native
The current native viewer uses KWin-compatible MemFd/SHM negotiation for a reliable first working path. DMA-BUF remains planned: the final zero-copy path will query EGL's importable DRM formats/modifiers rather than maintaining a hard-coded AMD/Intel modifier list.
If the PipeWire stream errors, the portal error message is logged; for deep
PipeWire-side diagnostics run with PIPEWIRE_DEBUG=2.
DMA-BUF roadmap
Useful debugging switches already reserved by the CLI:
portal-view --force-shm # conservative SHM path today
portal-view --no-modifiers # reserved for the DMA-BUF implementation
portal-view -v
--no-modifiers is intentionally a no-op in the current native renderer; it is
kept so the DMA-BUF test matrix and eventual native negotiation use the same CLI.
The current working capture path is MemFd/SHM.
KDE virtual output + Sunshine
See docs/KDE_VIRTUAL_MONITOR.md.
The repo now includes scripts/kde-virtual-output.sh to create/detect/tear down
a named-resolution virtual output without guessing KWin's assigned output name.
The important part is that krfb-virtualmonitor creates a compositor-level KWin output. Sunshine therefore needs:
capture = kwin
output_name = Virtual-SunshineHeadless
rather than KMS capture.
Development
make test
make fuzz
make check
make handoff
The DMA-BUF-specific verification plan lives in docs/DMA_BUF_TEST_MATRIX.md.