Commit Graph
14 Commits
Author SHA1 Message Date
N0VA 3abf610fbf metadata 2026-09-10 07:29:39 +02:00
N0VA 782a21f864 Update Cargo.toml 2026-09-09 19:16:07 +02:00
Milad Alizadeh 78631065e2 Release 0.4.0
--outputs and --labels turn those settings back on from the command line.
Only the negatives existed before, so a config file that switched either off
could not be overridden for a single run. --no-labels is the matching
negative; --hide-labels still works.
2026-09-07 18:40:23 +01:00
Milad Alizadeh a99de502e2 Release 0.3.1
Two ways wl-pick could fail to appear at all, both fixed.

A second instance hung with no window and had to be killed: sway answers a
capture request for a toplevel another client is already capturing with
silence, and the wait for it was unbounded. Every wait before the overlay is
interactive now has a deadline, and a tile that never arrives is drawn as a
bare label. Losing the keyboard grab for good also ends a run, so a second
wl-pick replaces the first rather than stranding it on screen.

Starting wl-pick from a shell with a stale SWAYSOCK failed outright. The
running compositor's socket is now found in XDG_RUNTIME_DIR when the
environment's path does not connect.
2026-09-07 17:41:37 +01:00
Milad Alizadeh c607fc9a1c Bound every wait before the overlay is interactive
Running wl-pick twice left the second instance hung with no window at all
and no way to end it but kill.

The hang was in the capture phase, not the overlay. sway answers a capture
request for a toplevel that another client is already capturing with
silence: no frame, no failed, no stopped. captures_settled() waits for every
tile to reach one of those three, so it waited forever, holding its buffers,
having never created a layer surface. Any concurrent capture client will do
this, not just a second wl-pick.

So the phases before the overlay is interactive now have a deadline. pump_for
polls the connection with one, and a tile that never arrives is drawn as a
bare label, exactly as an outright capture failure already was.
report_unsettled names those tiles on stderr, which is the diagnostic whose
absence made this hard to find.

The first instance had a second problem: sway hands the keyboard to the new
overlay and sends the old one wl_keyboard.leave, but the dispatcher only
handled Key, so the loser sat on screen holding a grab it no longer had,
deaf to every key. That is the process that stays around. Losing the grab
for good now ends the run, so a second wl-pick started from the same
keybinding replaces the first rather than stranding it.

For good, because sway also sends leave followed immediately by enter on the
same surface -- microseconds apart -- as a focus refresh when the pointer
crosses the overlay. Treating a bare leave as terminal made a lone instance
quit itself after less than a second. Focus is tracked in the dispatcher and
the main loop only gives up once it has failed to come back.

pump had no callers left after that: every wait is now either budgeted or
focus-aware.
2026-09-07 11:47:41 +01:00
Milad Alizadeh 34548fe4f6 Release 0.3.0
Sizing is now specified as caps rather than as a thumbnail size: max-width
and max-height bound the overlay, max-columns and max-rows bound the grid
inside it, and a thumbnail is simply the one divided by the other. A
thumbnail is therefore the same size whether one window is open or thirty.

Rows past max-rows scroll, with a scrollbar, PgUp/PgDn, and the selection
kept in view. Tiles scrolled out of sight are unmapped, so live capture
skips them.

timeout = 0 now means no timeout instead of an immediate deadline.
2026-09-06 11:13:09 +01:00
Milad Alizadeh 94d8145079 Release 0.2.0
Minor, not patch: colours and sizing move into a config file and the grid
scrolls, so behaviour people could depend on has changed shape.

- ~/.config/wl-pick/config sets colours, border, thumbnail size and the
  flags, as flat `key = value` lines; a flag still beats the file.
- Sizes take sway's units. 70ppt is a percentage of the display the grid
  appears on, resolved per run, so one config suits monitors of different
  sizes. The overlay maps on the focused display explicitly, at that
  display's scale, rather than taking the largest scale in use.
- Give only tile-width and the height follows the display's aspect, which
  is roughly the shape of the windows on it.
- More rows than fit now scroll, with a scrollbar, PgUp/PgDn, and the
  selection always revealed. Tiles out of view are unmapped and skipped
  by live capture.

Also corrects rust-version, which said 1.88 while cosmic-text has needed
1.89 all along: cargo was resolving 0.1.1 against a floor its own
dependency could not meet, and said so if you looked. It now locks
cleanly.
2026-08-31 18:22:57 +01:00
Milad Alizadeh c22e3cdd31 Release 0.1.1
The published 0.1.0 defaults the label font to Berkeley Mono, which falls
back to a non-monospaced face on any machine that does not have it. That
is a defect for every user but one, so it is a patch release rather than a
new feature: no flag was added, removed or renamed.

Also silences a lint that arrived with Rust 1.98. Clippy suggests
`as_chunks` in place of `chunks_exact`, but that API is newer than the
1.88 this crate declares as its minimum, so taking the suggestion would
break the builds the manifest promises to support.
2026-08-28 10:49:14 +01:00
Milad Alizadeh 15b74d311d Add the metadata crates.io needs
repository, readme, keywords and categories, so the crate page says what
this is and is findable; the MIT text the license field was already
claiming; and rust-version, because let-chains mean 1.88 and an older
toolchain should say so rather than fail to parse.

`cargo package` builds the packaged tarball cleanly: 17 files, nothing
stray, and the release binary links nothing beyond libc, so installing it
needs no system development packages.
2026-08-24 12:03:40 +01:00
Milad Alizadeh 25d90bdcec Pick with the mouse
Click a tile to pick it, scroll to move the selection. Hovering does not
move the selection: the keyboard keeps that, and a click acts on whatever
is under the cursor instead. A click on the margin, on a gap, or on an
empty cell of a ragged last row does nothing.

Tiles are already subsurfaces, so a click on a thumbnail identifies its
tile by which surface the event arrived on — no hit-testing needed. Only
the chrome around them (padding, labels, gaps) needs Layout::hit, which
inverts the same maths elem() lays out with, and is tested against it.

Picking waits for press and release on the same tile, so sliding off a
tile before letting go is not a pick.

wp_cursor_shape_v1 sets the pointer shape, which spares us a cursor theme
and libwayland-cursor; without it the cursor keeps whatever shape the
window underneath gave it. It is optional — a compositor without it just
gets whatever shape was already there.
2026-08-23 20:25:57 +01:00
Milad Alizadeh 8d8a27c4c1 Split the client into modules, and clear out what iteration left behind
main.rs had grown to 1290 lines holding everything: the CLI, the client
state, the capture engine, the drawing, the input handling and twelve
Dispatch impls. It is now orchestration only, and the concerns live where
you would look for them — cli, app, capture, overlay — with the module
docs saying what each owns. No behaviour changed; the code moved.

Iterating in response to review left residue, now gone:

- Tile::handle was only ever written. Dropping a wayland-rs proxy does
  not destroy the object, so nothing needed it held.
- Tile::failed likewise: an earlier captures_settled() read it, and
  `settled` is what everything waits on now.
- A blanket #[allow(dead_code)] on Tile hid both of those. Fields are
  pub(crate) rather than pub so the lint keeps working.
- Tile's doc comment had drifted onto the Format enum during a patch.
- Three consecutive `if args.verbose` blocks became describe() and
  report(), and the loose ticks/releases/starved/pool_bytes counters
  became one Stats. `releases` went: frames already imply it.
- quit + quit_why + activate became one Ending enum and picked, so
  "closed by the compositor" is a state rather than a string.
- App::new took seven positional arguments, two of them bare integers in
  a row; the four that always travel together are now Settings.
- sway::scale and the display listing each called get_outputs; one call
  does both.
- scaled(rect, n) became Rect::scaled(n), and Format lives with Target
  where render() dispatches on it.

README had drifted too: the usage line still advertised --print, which
no longer exists, and omitted --format and --no-outputs. Its flag list is
now checked against --help, the memory figures are re-measured, and there
is a source layout for anyone arriving cold.
2026-08-23 19:17:04 +01:00
Milad Alizadeh 5b98615c41 Rename to wl-pick
wlgrid described the shape of the thing rather than what it is for, and
the shape is the least interesting part now that it doesn't focus
anything: it shows you what is running and reports which one you pointed
at. wl-pick says that.

The wl- prefix rather than wlr- is deliberate. The capture path is
entirely upstream ext- protocols (ext-image-copy-capture,
ext-image-capture-source, ext-foreign-toplevel-list); the only
wlroots-flavoured piece is layer-shell, which KDE, niri and labwc
implement too. What actually keeps this from running on other
compositors is the sway IPC dependency, not wlroots, so wlr- would
promise a portability that isn't there. Hyphenated because the ecosystem
hyphenates when the suffix is a real word — wl-clipboard, wl-mirror,
wl-screenrec — and reserves the smushed form for coinages like wlsunset.

Also renames the layer-shell namespace and the memfd labels, which show
up in compositor debugging.
2026-08-23 18:35:27 +01:00
Milad Alizadeh 2cd076699d Label the tiles
"title · app" centred under each thumbnail, in the same font the rofi
theme used (Berkeley Mono at pango's "small"), with the selected tile's
label inverted onto the yellow the way rofi's element background did.
Long titles are ellipsised to the cell width.

Text comes from cosmic-text, which brings real shaping. The catch is
cost: building a font system and rasterising the first glyphs takes
~55ms, nearly doubling a 65ms startup. But the capture phase is ~55ms of
sitting blocked while the compositor copies pixels, so labels are shaped
on a worker thread started before the captures and joined after them.
The measured labels phase is now 0.0ms — it costs nothing in wall clock.

Two thirds of that font cost was FontSystem::new() scanning all 1793
system faces, which even new_with_fonts() does. So the database is built
by hand: the user's own font directories first, since they are small,
and the full system scan only when the family isn't found there. An
unknown family still resolves, because that fallback is exactly the
system scan (verified: --font "No Such Font" renders in Noto).

--hide-labels restores the icon-only grid, and --font/--font-size make
the family and size settable. Adds a test that a label actually puts
pixels on the surface, one for ellipsising, and one pinning the label row
into the element geometry.
2026-08-23 10:39:37 +01:00
Milad Alizadeh dbfc06519a 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.
2026-08-23 10:07:09 +01:00