<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/crates/config/src/engine/support.rs, branch main</title>
<subtitle>Cross-platform window manager written in Rust.
</subtitle>
<id>https://srdusr.com/git/srdwm/atom?h=main</id>
<link rel='self' href='https://srdusr.com/git/srdwm/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/'/>
<updated>2026-07-05T14:06:00+00:00</updated>
<entry>
<title>Let the config file take back a setting changed at runtime</title>
<updated>2026-07-05T14:06:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-05T14:06:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=744757fad22db7a5ebebca9a685cdb00589a1b02'/>
<id>urn:sha1:744757fad22db7a5ebebca9a685cdb00589a1b02</id>
<content type='text'>
Reported as windows still being tinted. The tint is the drop shadow, and
init.lua sets general.shadows to false - loading that same config in a
fresh compositor reports false, while the running session reported true.

The reason it could not be corrected is a defect in the live-settings replay
added earlier today. That replay re-applies every srd set after a config
reload so the titlebar menu's Customize rows survive a save. The unintended
half is that a live override then outranked the config file permanently:
editing init.lua and saving put the override straight back, which is the
state the session was found in.

Live-always-wins and config-always-wins are both wrong. The rule is now that
the config wins for anything it states, and a live override survives only
where the config is silent. That distinction cannot come from `values`,
where defaults are seeded before any script runs so every key looks set, so
the config engine records which keys srd.set actually touched during the
load. That record is cleared and rebuilt on each load and restored along
with everything else when a reload fails.

Verified both directions: a config-stated key reverts to the file's value on
the next reload, and a key the config never mentions keeps its live
override.

529 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Expose key bindings over IPC so a launcher can list them</title>
<updated>2026-05-13T17:47:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-13T17:47:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=670c9845129fc0c4b6e0192d657eea05e1a64b49'/>
<id>urn:sha1:670c9845129fc0c4b6e0192d657eea05e1a64b49</id>
<content type='text'>
Asked whether srdwm's bindings show in the AGS launcher. They could not:
srd.bind lives entirely in the Lua engine, and nothing published a binding
anywhere a client could read it. There was no IPC command, no field in any
response, and bound_keys() was used only by main.rs to register grabs.

srd.bind now takes an optional third argument, a description, and the loaded
set is copied into the WindowManager after the initial load and after every
reload. Core neither owns nor interprets them - it has no Lua state and
never dispatches a key - it holds them so the IPC layer, which is handed a
WindowManager and nothing else, can serve them. New `srd keybindings` returns
combo and description pairs, sorted so a UI listing them does not reshuffle
on every refresh.

Every binding in the shipped config now carries a description, so the feature
is useful without the user writing any.

Verified live in a nested instance: 46 bindings published, 0 without a
description, and editing the config file updated the list without a restart
(which also exercised reload-on-write again).

Also verified, for the separate report that windows cannot be moved to
another workspace from the AGS workspace pills: the compositor side works.
`srd dispatch move workspace &lt;id&gt; 2` moved a window from workspace 1 to 2 and
correctly hid it, since workspace 1 was active. Nothing to fix here; the
missing piece is on the shell side.

515 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Fix spawn placement under the top bar, add per-window minimum sizes, and clean up maximize</title>
<updated>2026-05-11T14:57:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-11T14:57:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=646b37e7e3aa5931079c6b9e804f420bb43c74d5'/>
<id>urn:sha1:646b37e7e3aa5931079c6b9e804f420bb43c74d5</id>
<content type='text'>
Four reports after restarting into today's build, with a screenshot. The
screenshot was measured, not eyeballed: top bar y=0..29, a 4px accent border
at y=30..33, titlebar from y=34, bottom border at y=1027..1030, and 49px of
bare desktop below it.

Windows spawning too close to the top bar. A remembered position was
validated only by asking whether it landed on some monitor's full_geometry,
which includes the strip a top bar reserves, so an app whose remembered y was
small reopened with its titlebar under the bar. That is why it was
"sometimes": it depended on the stored value, and the live store holds
wezterm at y=44 and firefox at y=69 against a 30px bar. Remembered positions
are now clamped into the monitor's usable area.

Placement not surviving a logout. Window memory does persist, but five of the
eleven entries in the live store were saved with a second monitor attached,
at x &gt;= 2000. Those points match no current monitor and were discarded
outright, falling back to a fresh cascade, so those apps appeared to remember
nothing. Such a position is now clamped onto a monitor that exists instead.

Per-window minimum sizes. One global floor is wrong in both directions.
Three sources now, in increasing precedence: the global floor, the client's
own declared minimum (xdg_toplevel.set_min_size or ICCCM hints), and a
min_width/min_height window rule overriding both. A rule wins permanently --
the backend refreshes the client's declared minimum on every decoration
redraw and must not undo a deliberate override.

Maximize, three faults in one report. A maximized window now draws no
border: its edges are the screen's edges, and the only place maximize stops
short is the bar strip, which is exactly where the measured line was.
maximize_geometry_for no longer subtracts a bottom-anchored dock's exclusive
zone, so maximize runs to the bottom of the screen and the dock floats over
it; top, left and right are still honoured.
general.maximize_covers_dock = false restores the old behaviour. With the
border gone the window sits flush under the bar instead of with an accent
line crowding it.

Verified: seven new tests on the real numbers from the live store, and
maximize geometry measured live in a nested instance (a window maximized on a
split half reports exactly that half's rect). NOT confirmed on screen: the
border removal and the dock behaviour - the nested backend has no bar or
dock to reserve a zone, and an attempt to check the border produced a failing
control, since srd set border_width only affects windows created after it.

515 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Build the eight asks recovered from the previous session's transcript</title>
<updated>2026-04-26T22:18:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-04-26T22:18:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=c4dc99cc3211c4dc1a461f228402be1593b883c9'/>
<id>urn:sha1:c4dc99cc3211c4dc1a461f228402be1593b883c9</id>
<content type='text'>
The punch list was not the whole record. These are the owner's own typed
requests, read back out of the previous session's transcript rather than
guessed at, then each checked against the code before being treated as open.
Three they suspected were already done really were: dialogs already had a
Close-only titlebar, inactive dimming already existed, and the corner resize
hitbox had already been tuned.

Snap layouts on drag, asked for twice. Edge snapping worked but committed
silently on release with nothing shown first, so there was no way to know it
would happen or where. A translucent drop-target preview now follows the
drag, and throwing the pointer at a monitor's top edge drops down the
existing six-cell grid to aim at. The preview calls the same snap_zone that
end_drag does, so the two cannot disagree. Two defects found by screenshot
before landing: moving down onto the flyout closed it, and its labels
overflowed at a fixed cell width - the same "text goes out of view" fault
already fixed once for the context menu.

New File now offers real types, chosen by extension, with the de-duplication
counter placed before the extension so the file stays what it says it is.

Refresh re-reads init.lua and fires a new srd.on("refresh") handler instead
of only re-scanning the icon grid. What refresh means beyond srdwm's own
config stays the config's decision.

general.config_reload_on_write (default on) applies an edited config on save,
via an mtime sweep rather than an inotify watch: no new dependency, same
behaviour on every target, and unaffected by editors that write through a
temp file.

A real bug behind "what happens when our config fails": you lost every
keybinding. do_reload cleared the binding, handler and repeat tables before
re-executing and never restored them, so a syntax error left neither the old
config nor the new one, and the only key still working was the reload combo
nobody thinks to press. The tables are now restored on any failure and
config errors reach notify-send, not just the log.

srd.lock() and a default Mod4+Ctrl+l binding: the built-in lock screen could
not be reached from Lua at all. Native rather than shelling out, because a
lock key that shells out fails silently when the binary is not on PATH.
Default bindings added for srd.window.move and a dynamic/tiling toggle, both
of which existed with no way to reach them, plus srd.layout.get() so the
toggle reads the live workspace rather than the configured default.

Dialogs open centred, and are excluded from remembered geometry in both
directions - that table is keyed by app_id, which a dialog shares with the
window that spawned it, so dialogs inherited an unrelated position and size
and then overwrote it with their own.

theme.decorations.title_bar.button_mode (dynamic by default, or fixed) drops
the Maximize button on a window whose client pinned min == max size, where
pressing it can do nothing. Maximize is removed from the slot list rather
than skipped in place on both the render and hit-test sides, so the remaining
buttons close the gap identically; three tests pin that agreement, which is
what fails silently when it drifts.

Also: the nested backend's screencopy pass now draws both menus, the flyout
and the drag preview. Four investigations in one day started from a
screenshot missing a tier, so that pass carries an explicit list of what it
still omits and the on-screen loop points at it.

512 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Make the secondary-cursor sprite opt-in and expire stale entries</title>
<updated>2025-10-03T20:59:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-10-03T20:59:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704'/>
<id>urn:sha1:4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704</id>
<content type='text'>
Live report: a second cursor appeared uninvited and unusably (frozen,
uncontrollable) on screen. Multi-cursor Phase 1 rendered one sprite per
physical libinput pointer device that had ever reported a position, with
no way to turn it off and no expiry - so a phantom device (a real mouse's
side-button/scroll cluster enumerating as its own HID path is a common
case) that reports once and never moves again left a frozen ghost sprite
with nothing to control or dismiss it.

Adds general.multi_cursor (default false, live-settable via
`srd set multi_cursor &lt;bool&gt;`) and keys secondary_cursors to
(Point, Instant) so both the recording side (udev/session.rs) and the
render side (udev/render.rs) drop any entry older than
SECONDARY_CURSOR_TIMEOUT (1.5s). The "agent controls a window without
interrupting the user" use case this report also raised was never gated
on this flag - that's Multi-cursor Phase 2's pinned virtual-pointer
delivery, which never shows a visible cursor at all.
</content>
</entry>
<entry>
<title>Wire aspect_ratio, phone_mode and pin_input through config/IPC/CLI</title>
<updated>2025-08-31T12:04:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-08-31T12:04:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=4a55182ce17fd4474f8be1e8819a41b7ccd11045'/>
<id>urn:sha1:4a55182ce17fd4474f8be1e8819a41b7ccd11045</id>
<content type='text'>
The Lua and IPC/CLI surface for the three new core primitives
(crates/core: aspect_ratio rule action, general.phone_mode, virtual-
pointer window pinning):

- srd.rule(..., { aspect_ratio = "9:16" }): parses a "W:H" string into a
  validated (u32, u32), rejecting a malformed value as a real Lua error
  at config-load time rather than silently ignoring it.
- general.phone_mode config default, plus srd set phone_mode &lt;bool&gt; for
  the live equivalent (same shape as animations/shadows/rounded_corners).
- pin_input IPC dispatch ({"cmd":"pin_input","pid":&lt;pid&gt;,"id":&lt;window
  id&gt;}, id omitted to unpin) and its CLI surface, srd dispatch pin input
  &lt;pid&gt; &lt;window-id&gt; / unpin input &lt;pid&gt;. Keyed by the owning client's
  process id, not an opaque per-object id nothing outside the Wayland
  backend could ever learn - a controlling tool already knows its own
  pid for free.

See docs/TODO.md for the full design reasoning behind each of these.
</content>
</entry>
<entry>
<title>Fix desktop icons v1 regressions: bar overlap, wrong order; add proper menus</title>
<updated>2025-08-16T23:54:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-08-16T23:54:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=b8376bd631a72f7484c24663699a7d4c813f600e'/>
<id>urn:sha1:b8376bd631a72f7484c24663699a7d4c813f600e</id>
<content type='text'>
Live testing found v1 genuinely broken, not just rough:

1. Icons weren't rendering reliably at all - ensure_desktop_icons only
   ever computed the grid's origin once, on whichever render pass
   happened to be first. AGS's own top bar registers its exclusive zone
   after that first pass, so origin got permanently baked in at the
   pre-bar geometry. Confirmed live via a temporary diagnostic log.
   Fixed by re-deriving origin from the primary monitor's current
   geometry on every call instead of just the first.

2. Fixed icons (Home/Computer/Trash) always sorted before real files --
   confirmed wrong via direct question. The whole list now sorts
   alphabetically by label, case-insensitive, fixed icons included.

3. "Set as Wallpaper" was the wrong feature: removed entirely
   (DesktopMenuAction::SetWallpaper, general.wallpaper_command,
   is_image_path). The user wants that handled by their real file
   manager once opened, not reimplemented here.

Also adds real menu functionality per "where are all the options":
Rename (inline text edit, new CompState::renaming_icon field and
keyboard redirect mirroring NativeLock::password's existing precedent),
Delete (moves to ~/.local/share/Trash per the freedesktop.org spec, new
trash.rs module, same-filesystem case, no confirmation - this is the
reversible move-to-trash, not a permanent delete), Empty Trash on the
Trash icon, and Open Terminal Here / Open in File Manager on the
bare-desktop menu (new general.terminal config key).

133 wayland-crate tests (up from 106), full workspace build and clippy
clean.
</content>
</entry>
<entry>
<title>Add real desktop icons plus right-click desktop/icon context menus</title>
<updated>2025-08-15T21:22:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-08-15T21:22:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=cc22fe69a68e0fff027d833029aea850976488c8'/>
<id>urn:sha1:cc22fe69a68e0fff027d833029aea850976488c8</id>
<content type='text'>
Closes "right-click on bare desktop" - previously a true no-op, nothing
rendered above the wallpaper at all. Requested directly: a real desktop
"just like windows does" - Home/Computer/Trash plus one icon per real
~/Desktop entry, individually draggable with persisted grid positions,
double-click to open, right-click menus (per-icon "Open" plus "Set as
Wallpaper" for image files when general.wallpaper_command is set; bare
desktop "New Folder"/"Refresh").

Architecture mirrors the existing context_menu.rs/snap_flyout.rs
"compositor-owned floating UI" pattern: desktop_icons.rs (data model, grid
layout, filesystem scan, hit-test), desktop_icons_state.rs (JSON
persistence, same shape as monitor_layout.rs), desktop_menu.rs (the new
right-click menu, reusing decoration::render_context_menu's existing
rasterizer), state/desktop_icons.rs (CompState glue: rescan/select/drag/
open/persist), and a new decoration::render_desktop_icon rasterizer --
hand-drawn glyphs, since no PNG/SVG decoding capability exists anywhere in
this workspace. Wired into both render loops (udev and winit) above the
wallpaper and below every window, and into input/pointer.rs's button/
motion handlers for selection, drag, double-click, and both menus.

Four new config keys: general.desktop_icons (default true - a directly
requested, purely visual feature, unlike the opt-in-while-experimental
general.gpu), general.file_manager, general.desktop_icon_single_click,
general.wallpaper_command (all default off/empty).

Deliberately out of scope for this pass, stated up front: move-to-trash
and "Empty Trash" (destructive, no confirmation-dialog primitive to gate
them on yet), filesystem watching, multi-select, per-mimetype icon art,
icons on any monitor but the primary one.

124 wayland-crate tests (up from 106), full workspace build and clippy
clean.
</content>
</entry>
<entry>
<title>Add a real general.gpu config option for the GPU render path</title>
<updated>2025-05-28T07:42:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-05-28T07:42:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=6a9e50280ad8262e9fe49e390527e7490564f8dd'/>
<id>urn:sha1:6a9e50280ad8262e9fe49e390527e7490564f8dd</id>
<content type='text'>
SRDWM_GPU=1 was the only way to opt into the udev backend's GBM+EGL
GPU render path - an env var, not a real config option, with no way
to enable it from init.lua the way every other general.* flag works.

WindowManager::gpu_enabled (plain bool, false by default - unlike
rounded_corners_enabled's Option&lt;bool&gt;, GPU rendering has one
unambiguous default regardless of which backend ends up connecting,
so there's no "let the backend decide" case to preserve) is read from
general.gpu in apply_general_settings, same as every other general.*
key. gpu::probe now takes an explicit enabled: bool instead of
checking the env var itself; udev/platform.rs's call site computes it
as wm.gpu_enabled OR SRDWM_GPU=1, so the env var still works as a
quick manual override for testing without touching config, on top of
the new persistent option.

Falls back to the existing software (Pixman) path exactly as before
on any failure at any step (no GBM device, no atomic-modesetting
support, a software-only EGL renderer, ...) - gpu::probe's own
fallback behavior is unchanged, only how the initial enabled/disabled
decision gets made.
</content>
</entry>
<entry>
<title>Checkpoint: preserve all uncommitted rust-rewrite worktree work</title>
<updated>2025-02-15T12:56:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-02-15T12:56:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd'/>
<id>urn:sha1:0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd</id>
<content type='text'>
Safety commit before reconciling this worktree with main, which has
diverged with its own separate fixes today. Nothing here is reviewed
or curated yet - this exists purely so none of this work can be lost
to a git operation, disk issue, or worktree cleanup while that
reconciliation happens.
</content>
</entry>
</feed>
