Add wlgrid: a window-thumbnail grid overlay for wlroots
A switcher to replace a wlthumbs + rofi pipeline, with the same look (gruvbox, ceil(sqrt(n)) columns capped at 4, 16:9 tiles, a yellow selection filling the element padding) but no thumbnails anywhere. Each window is captured straight into a wl_shm buffer that is handed to its own wl_subsurface, with wp_viewporter giving the compositor the rectangle to scale it into. So there is no PNG encode, no scaler, no full-resolution bitmap in this process, and the capture buffers are never even mapped here — the compositor writes those pages and samples them again for display. Opens in ~65ms for 8 windows (55ms of which is the compositor reading pixels back out of the GPU) and holds ~9MB of RSS. All capture sessions are opened before a single roundtrip and every frame goes in flight together, the same batching wlthumbs uses, because the readback is bandwidth-bound rather than latency-bound. sway remains the source of truth: the window list, the con_ids and the focusing all come from its IPC socket, joined to the Wayland side by foreign_toplevel_identifier. Navigation reads raw evdev keycodes so it is layout-independent, which does mean virtual-keyboard clients that invent their own keymap can't drive it; that resolves when filtering brings xkb. Labels, type-to-filter and live previews are next.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# wlgrid
|
||||
|
||||
A window switcher for wlroots compositors: a thumbnail grid overlay that looks
|
||||
like a rofi theme, and focuses the window you pick.
|
||||
|
||||
It replaces a `wlthumbs | rofi` pipeline. The difference is that no thumbnails
|
||||
exist: each window is captured straight into a `wl_shm` buffer that is handed to
|
||||
its own `wl_subsurface`, and `wp_viewporter` tells the compositor which rectangle
|
||||
to scale it into. There is no image encoding, no scaler, and no full-resolution
|
||||
bitmap in this process — which is also why it holds ~9 MB of RSS and appears in
|
||||
about 60 ms.
|
||||
|
||||
```
|
||||
sway-tree 0.6ms window list + con_ids over sway IPC
|
||||
toplevels 0.2ms ext-foreign-toplevel-list handles
|
||||
constraints 1.5ms every capture session's buffer size, in one roundtrip
|
||||
capture 55.0ms 8 windows, all frames in flight at once
|
||||
mapped 4.2ms layer surface + subsurfaces on screen
|
||||
```
|
||||
|
||||
The capture phase is the compositor reading full-resolution window pixels out of
|
||||
the GPU. It is bandwidth-bound (~1.1 GB/s here) and unaffected by how large the
|
||||
thumbnails are.
|
||||
|
||||
## Status
|
||||
|
||||
Working, and usable as a switcher today: a static grid with keyboard navigation.
|
||||
Labels, filtering and live previews are next — see the roadmap.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
wlgrid [--print] [--verbose] [--timeout SECS]
|
||||
```
|
||||
|
||||
- `--print` writes the selected sway `con_id` to stdout instead of focusing it
|
||||
- `--verbose` prints phase timings and how many windows were captured
|
||||
- `--timeout SECS` exits after a deadline (an escape hatch: the overlay takes an
|
||||
exclusive keyboard grab)
|
||||
|
||||
Bind it in sway:
|
||||
|
||||
```
|
||||
bindsym $mod+Tab exec wlgrid
|
||||
```
|
||||
|
||||
| key | |
|
||||
|---|---|
|
||||
| `→` `←` / `Tab` `Shift+Tab` | next / previous window |
|
||||
| `↑` `↓` | move a row |
|
||||
| `Home` `End` | first / last |
|
||||
| `Enter` | focus the selection |
|
||||
| `Escape` / `q` | cancel, leaving focus alone |
|
||||
|
||||
Navigation reads raw evdev keycodes, so it is layout-independent — but it also
|
||||
means virtual-keyboard clients such as `wtype` (which invent their own keymap)
|
||||
cannot drive it. That goes away with xkb support, which filtering needs anyway.
|
||||
|
||||
## Look
|
||||
|
||||
Colours, font metrics and grid geometry come from the rofi theme this replaces
|
||||
(gruvbox dark, a yellow selection filling the element padding, `ceil(sqrt(n))`
|
||||
columns capped at 4, 16:9 tiles) and live in `src/theme.rs`. They will move to a
|
||||
config file so they can't drift from the `.rasi`.
|
||||
|
||||
## Requirements
|
||||
|
||||
A wlroots compositor advertising `ext-image-copy-capture-v1`,
|
||||
`ext-image-capture-source-v1` (with the foreign-toplevel source manager),
|
||||
`ext-foreign-toplevel-list-v1`, `wlr-layer-shell-unstable-v1` and
|
||||
`wp_viewporter` — sway 1.11+, and in principle Hyprland, labwc and jay, though
|
||||
only sway is tested. sway is also the source of truth for the window list and
|
||||
for focusing, over its IPC socket.
|
||||
|
||||
Known upstream issue: holding per-toplevel capture sessions open makes windows
|
||||
blurry on **fractionally scaled** outputs
|
||||
([sway#9113](https://github.com/swaywm/sway/issues/9113)). Integer scales are
|
||||
unaffected. It matters more once previews are live.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- **M2** labels: real text via cosmic-text, `--hide-labels` for an icon-only grid
|
||||
- **M3** type-to-filter with fzf-quality fuzzy matching (and xkb keyboard input)
|
||||
- **M4** live previews: keep the capture sessions open and re-capture on a rate
|
||||
limit, `--live all|current|none`
|
||||
- **M5** dmabuf capture, so the pixels never leave the GPU at all
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
cargo build --release
|
||||
cargo test # grid geometry
|
||||
```
|
||||
Reference in New Issue
Block a user