<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/crates/platform/src/ipc/dispatch.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-08-04T22:55:00+00:00</updated>
<entry>
<title>Show a keybinding the way a person writes it, and let bind_repeat be described</title>
<updated>2026-08-04T22:55:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-04T22:55:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=9c1673fa73bb49433370a60a7b4bb16abee98a9b'/>
<id>urn:sha1:9c1673fa73bb49433370a60a7b4bb16abee98a9b</id>
<content type='text'>
Measured with dotfiles-1a, who own the launcher that displays these: `srd
keybindings` returned 84 bindings and 84 empty descriptions - 100% - so
every entry their launcher showed was a bare key combo with nothing to say
what it does. Two causes, one on each side of the boundary.

`srd.bind_repeat` never accepted a description. `srd.bind` has taken an
optional third argument all along, but its repeating sibling took only two,
and mlua drops a surplus argument silently rather than raising - so a
config that documented its repeat bindings got no error and no description.
It now takes one exactly like `bind`.

The combos themselves were reported in their internal dispatch form:
`Shift+Mod4+h`. `Mod4` is the X11 modifier's name, not a key's; nothing on
a keyboard is labelled Mod4, and the canonical Ctrl/Shift/Alt/Mod4 ordering
renders the owner's own `Super+Shift+h` binding back to them inside out.
`srd keybindings` now reports a display form - Super, and the order people
write - while everything internal keeps the canonical form it dispatches
on. The display form parses back to the same binding, so it can be pasted
into a config, and a test pins that round trip rather than trusting it.

The owner's own config now describes all 84 bindings; verified through the
real path, in a nested compositor running that config: 84 of 84 described,
zero occurrences of "Mod4".
</content>
</entry>
<entry>
<title>Report whether a listed key binding is actually grabbed</title>
<updated>2026-05-13T18:11:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-13T18:11:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=8174ced0d9d42343b18072c64491ccb06632a75f'/>
<id>urn:sha1:8174ced0d9d42343b18072c64491ccb06632a75f</id>
<content type='text'>
Requested by the AGS session while wiring its launcher to srd keybindings:
without this the launcher would list a shortcut that does nothing and give no
way to tell, which is the same silent-lie class of bug as the rest of the
work today.

The backend is handed one combo list, once, before connecting - X11 turns it
into XGrabKey calls, Wayland into its intercept set. A reload re-registers
the actions but cannot re-register the grabs, so a combination added to the
config since startup is bound as far as the config engine is concerned and
still goes straight to the focused client when pressed. That snapshot is now
recorded on the WindowManager at the exact point it is handed to the
backend, taken once rather than per-arm so the reported set and the grabbed
set cannot drift apart, and each entry in srd keybindings carries a
`grabbed` flag.

Verified live in a nested instance, both states observed: 46 bindings and
46 grabbed at startup; then appending a new combination to the config gave
47 bindings, 46 grabbed, with the new one reported as not grabbed and
carrying its description. Two unit tests cover the set being replaced rather
than accumulated, and a combo outside it reading as not grabbed.

Also recorded, from the AGS session's own checks: moving a window to a
workspace was their bug, not a missing compositor feature - the Overview's
previews had a drop target and the bar's workspace dots had none, so the
gesture worked on one surface and silently did nothing on the other. And the
static half of "are all keybindings working" is clean for the running
session: keybindings.lua was last modified 06:02:55 and the compositor
started 18:11:13, so every combination in it was grabbed at boot.

517 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>Stop a config reload from undoing a change the user just made by hand</title>
<updated>2026-04-28T14:22:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-04-28T14:22:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=3ea84a31ee671b88d8c343b3007de0e975eff31c'/>
<id>urn:sha1:3ea84a31ee671b88d8c343b3007de0e975eff31c</id>
<content type='text'>
Reloading rebuilds the theme and general settings from the config file.
That is right for a file edit, but it also wiped every live `srd set` - and
the titlebar right-click menu's "Customize" rows are built entirely out of
live `srd set`s. Changing a button style from that menu and then saving
init.lua for any unrelated reason silently reverted it.

Survivable while reloads only happened on Mod4+Ctrl+r. The reload-on-write
support added in the previous commit makes a reload happen on every save,
which turns a rare surprise into a reliable one. A control that silently
reverts is worse than no control, so this is a defect rather than a
documented quirk. The AGS peer session reached the same conclusion from the
other side while deciding whether to build Settings controls against these
values, and would have had to label them session-only.

Every setting changed live is recorded on the WindowManager as key -&gt; raw
JSON text, and replayed after each reload through the very same handle_set
that applied it, so a replayed setting cannot behave differently from a real
one. Recorded only on success, so a rejected value is never replayed, and
only for real client calls, so the replay cannot rewrite what it is reading.
Last write wins per key. Raw JSON text because core has no serde dependency
and no reason to gain one for this; the platform crate parses it back.

Verified live in a nested compositor: set button_side left and button_mode
fixed, saved an unrelated config edit, both survived, and the log reported
re-applying two live settings. Three tests cover the round trip, the
rejected-value case and the one-entry-per-key case.

A live value is still a session override rather than a persisted setting.
That distinction is now written down in DEFAULTS.md instead of being a trap.

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>Fix desktop-icon deselection and workspace-teleport-on-close; document a shadow limit</title>
<updated>2026-01-31T12:34:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-01-31T12:34:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=1f708f8aa09bf8bbce82314a76b7f34def90b798'/>
<id>urn:sha1:1f708f8aa09bf8bbce82314a76b7f34def90b798</id>
<content type='text'>
Desktop icons stayed highlighted after clicking a window: select_desktop_icon(None)
was only ever called from start_desktop_marquee, never from the one place every
focus path (click, Alt-Tab, dock IPC, scratchpad show, snap flyout) already
funnels through. Added the deselect there instead of per-caller.

Closing a focused window could silently switch the user's active workspace:
remove_window's fallback picked self.order.last(), but that list is global,
not per-workspace, so it could land on a background window elsewhere - and
focus_window already switches workspace to match whatever it's given (a real,
separate feature for a deliberate srd dispatch focus). Fixed by preferring a
same-workspace window first. New general.close_focus_follows_workspace
(default false, live-settable) controls what happens only when nothing is
left on the current workspace at all: off leaves focus at nothing, matching
Windows/GNOME/macOS; on restores the old always-follow-the-global-fallback
behaviour. Three new tests.

Also documented, not fixed: shadows can still bleed onto a neighbouring
*monitor* near a multi-output seam (shadow_rect has no monitor-boundary
awareness), found via a live cross-monitor screenshot. Moot for this
session since general.shadows is already off in the live config, but a
real, open gap for anyone who re-enables shadows on a multi-monitor setup.
</content>
</entry>
<entry>
<title>Live-expose workspace.per_monitor, titlebar buttons, and desktop icons</title>
<updated>2025-11-16T19:46:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-11-16T19:46:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=354ce158575388d17ca9c58587e8cdde02332779'/>
<id>urn:sha1:354ce158575388d17ca9c58587e8cdde02332779</id>
<content type='text'>
Closes the remaining "config-file only" gaps from the AGS capability
survey. workspace.per_monitor gets srd set per_monitor &lt;bool&gt; - safe to
flip live since a monitor with no per-monitor override already falls
back to current_workspace regardless of mode, so nothing visually jumps
on toggle.

theme.decorations.title_bar.button_style/button_side/button_order and
general.desktop_icons/desktop_icons_all_monitors all get the same
live-set + SettingsResponse readback treatment as everything else this
session. New srdwm_core::format_button_order is parse_button_order's
exact inverse, so button_order's readback matches the same string shape
srd set itself accepts.
</content>
</entry>
<entry>
<title>Make tiling's master/stack ratio live, add settings readback everywhere</title>
<updated>2025-11-07T12:04:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-11-07T12:04:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=ea78f94027ecd2c690904a6d7729e9e3cc190a20'/>
<id>urn:sha1:ea78f94027ecd2c690904a6d7729e9e3cc190a20</id>
<content type='text'>
Investigated the "tiling needs a lot of work" report directly. The
MasterStackLayout algorithm itself was already correct; the real gap was
that dragging or resizing a tiled window did nothing durable (raw
geometry that the next arrange_workspace silently discarded), and
master_ratio/master_count had no live path at all (config-file only).

A resize-drag on the shared master/stack boundary now live-adjusts
TilingConfig::master_ratio and re-arranges the group immediately; srd set
master_ratio/master_count do the same for a keybind or script. Found and
fixed a real bug while building this: start_resize's own focus_window
call re-stacks its target in self.order before the ratio-drag decision
used to be made, silently misclassifying real master-column grabs.
Fixed by deciding ratio-drag status (and freezing the membership
snapshot it depends on) before that raise happens, applying
MasterStackLayout directly against the frozen snapshot rather than
re-deriving membership from the by-then-reordered live order. Live-
verified in a nested compositor, not just unit-tested.

Also closes the readback gaps flagged directly by the AGS peer session:
border_width/border_color/corner_radius/decoration_mode/gap_inner/
gap_outer/master_ratio/master_count were all live-settable via srd set
with no way to read the current value back, and pin_input had no
readback at all. SettingsResponse now reports all of them; a new
pinned_inputs query (srd pinned inputs) lists every currently pinned
pid/window.
</content>
</entry>
<entry>
<title>Fix set_monitor_split never actually reaching srd monitors</title>
<updated>2025-10-29T20:24:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-10-29T20:24:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=a4710d792a1b16698fc30b9e97e6c08c82129d6d'/>
<id>urn:sha1:a4710d792a1b16698fc30b9e97e6c08c82129d6d</id>
<content type='text'>
Live-tested right after shipping it and caught immediately: srd dispatch
set output split returned ok, but srd monitors kept reporting the whole,
unsplit output. WindowManager::monitors is a passive cache, only
refreshed when a backend re-queries and calls set_monitors again - the
IPC handler mutated the split map directly but never triggered that
requery, unlike set_output_position's own drain site, which already
pushes a "just go recompute" event after applying.

Makes it a proper queued cross-boundary request instead, the same shape
as every other backend-owned effect on this socket: WindowManager::
request_monitor_split/drain_monitor_split_requests, dispatch queues
instead of mutating, the udev backend's poll drains it, applies via
set_monitor_split, and pushes the same recompute event. srd.monitor.
split's Lua config-time path is untouched - it runs before the very
first startup query, so it never had this problem.
</content>
</entry>
<entry>
<title>Live-expose monitor split, clean up leftover debug diagnostics</title>
<updated>2025-10-26T20:58:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-10-26T20:58:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=304f3a374408bb6a5a04ebb7d4652c631395f5c6'/>
<id>urn:sha1:304f3a374408bb6a5a04ebb7d4652c631395f5c6</id>
<content type='text'>
srd.monitor.split only ever ran at Lua config load despite being a plain
WindowManager mutation that every backend's monitors() already reads
fresh on each call. Adds srd dispatch set output split &lt;name|id&gt; &lt;parts&gt;
[rows|columns] (IPC set_monitor_split), same id-resolves-to-name pattern
set_output_enabled already uses.

Also removes eight log::warn!("XXX-DIAG ...") lines left behind from live
debugging in the multi-session shift that landed in 3c41fc4 - the same
"temporary, never removed" pattern already fixed twice earlier this
session. Several fired on genuinely constant interaction (every title
change, every workspace switch, every layer-shell surface hide), not
just a one-off leftover. Left xdg_shell.rs's own POPUP-GEOM-DIAG/
POPUP-GRAB-DIAG alone - that one is a still-open, self-documented
investigation, not litter.

Also documents (docs/TODO.md, not a code change) a live incident where
creating a second fake monitor visibly corrupted the real monitor's
position and kept drifting with no further input - not root-caused
srdwm-side, flagged to the AGS peer session since a fake monitor's real
wl_output global is indistinguishable from a real hotplug to GDK/GTK.
And documents a deliberate decision not to blind-port window decoration
rendering onto the experimental, never-live-tested GPU render path.
</content>
</entry>
</feed>
