A tiny Wayland client that captures thumbnails of every open window (and, optionally, every monitor) over a single Wayland connection, using the wlroots capture protocols:
ext-foreign-toplevel-list-v1— enumerate windowsext-image-capture-source-v1— a capture source from a toplevel handle or outputext-image-copy-capture-v1— copy a frame into awl_shmbuffer
It scales each frame in-process and writes a small PNG, printing one tab-separated row per source:
TYPE <TAB> ID <TAB> THUMBPATH <TAB> APP <TAB> TITLE
TYPE—windoworoutputID— the window'sforeign_toplevel_identifier(matches sway'sforeign_toplevel_identifier) or the output name (e.g.DP-1)THUMBPATH— path to the PNG, or empty if that source failed to captureAPP—app_id(window) ormake model(output)TITLE— window title, or output name
It does one thing: dump window/monitor thumbnails for a shell front-end (e.g. a rofi grid) to consume. It does not draw any UI.
wlthumbs [-size N] [-dir DIR] [-windows] [-outputs]
-size Nmax thumbnail dimension in px (default 256)-dir DIRoutput directory (default: a fresh temp dir under$XDG_RUNTIME_DIR)-windowscapture windows (default true)-outputsalso capture monitors (default false)
Each thumbnail is named <ID>.png (the ID column), so a caller that already
knows the id can find the file directly — "$dir/$id.png" — without parsing
stdout; a missing file means that source failed to capture.
The caller owns -dir's lifetime — wlthumbs never deletes thumbnails (a front-end
typically reads them after wlthumbs exits). With no -dir, PNGs land in a temp dir
under $XDG_RUNTIME_DIR that you should clean up yourself.
Set WLTHUMBS_TIME=1 to print phase timings to stderr.
Example — thumbnail every window, then feed a rofi grid:
wlthumbs -size 220 | while IFS=$'\t' read -r type id thumb app title; do
printf '%s\x00icon\x1f%s\n' "$title" "$thumb"
done | rofi -dmenu -show-iconsgo build -o wlthumbs .
Requires a wlroots-based compositor (sway, etc.) advertising the three ext-*
protocols above. Pure Go — no cgo.
- Captures run sequentially on the one connection (wlroots does not parallelize them); each frame is scaled/encoded on a worker goroutine while the next window is captured, so encode cost hides under capture time.
- The protocol bindings in
proto/are generated from the XML inprotocol/and committed. Seegenerate.shto regenerate (and the manual patch it documents). - Two go-wayland quirks are worked around in the code:
Registry.Bindwriting a padded string length (rejected by newer libwayland — seebindinwayland.go), and no support for server-allocated objects (seeproto/register.go).
main.go— CLI flags, orchestration, phase timing (WLTHUMBS_TIME)wayland.go— the Wayland client: connect/bind globals, enumerate toplevels and outputs, and capture one frame per sourceimage.go— shm pixel-format handling, output-transform, scaling, PNG outputproto/— generated protocol bindings (+register.go); seegenerate.sh