I’ve had a keyboard-driven window switcher in my window manager for a long time. It started as a shell script piping i3’s window list into dmenu, and over the years I tweaked it slightly as I moved to Sway/Wayland and rofi.

That was enough until a new use case turned up: sharing a single window in a video call. Until recently Sway could only share a whole display, but sharing windows is now possile thanks to the xdg-desktop-portal-wlr backend, which lets you nominate an external program for picking the window. Browsers reach it through getDisplayMedia, which is handy for testing without having to join a call.

I could of course have used my dmenu-style script, but picking the right window out of a list of names is a bit of guesswork, and guessing in front of an audience is not fun. A visual chooser seemed like an obvious improvement.

My first attempt was to make rofi show me screenshots of all windows and displays. I put together a script using grim to take the screenshots, which can capture any window or display, including the ones that aren’t currently on screen. It worked, and it was decent, but it had two problems:

- The previews weren’t live. Each thumbnail is frozen at the moment you launch the picker.

- Launching it wasn’t instantaneous. Every preview meant capturing a full-resolution frame, encoding it, writing it to disk, then reading and scaling it again.

wl-pick

So I built wl-pick. It’s a small Rust program that shows a grid of live previews of every open window and display. Move around with the arrow keys or hjkl, press Enter, and it prints the selected window or output to stdout.

wl-pick just prints the selection, so what happens next is up to the caller. The common case is window switching:

swaymsg "[con_id=$(wl-pick --no-outputs | cut -f2)] focus"

It can also be used as the screencast chooser, where --format portal prints what xdg-desktop-portal-wlr expects:

[screencast]

chooser_type=simple

chooser_cmd=wl-pick --format portal

How it works

The live previews rely on capture protocols that only landed in Sway recently. Sway 1.11 added the ext-image-copy-capture-v1 protocol for capturing outputs, and 1.12 extended it to individual windows.

The trick to making it fast is that wl-pick doesn’t draw anything. Each window’s capture buffer is handed straight back to the compositor as a subsurface, along with the rectangle it should be scaled into. So the same process that captured the window at full resolution is also the one that shrinks it to a thumbnail. wl-pick allocates the memory, passes the file descriptor around, and does the arithmetic; it never looks at a pixel.

What’s left is the capture itself, and that’s mostly bandwidth rather than computation. Keeping the previews live afterwards is nearly free, because the compositor only sends a new frame when a window’s content actually changes, so a screen full of idle terminals generates no new frames.

Installation

Install it with:

cargo install wl-pick