<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/crates/wayland/src/decoration, 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-30T19:15:00+00:00</updated>
<entry>
<title>Use as_chunks for fixed-size pixel iteration</title>
<updated>2026-08-30T19:15:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-30T19:15:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=9a7f30050f8efc52340e698031a7a55a105e72c4'/>
<id>urn:sha1:9a7f30050f8efc52340e698031a7a55a105e72c4</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Indent doc list continuations</title>
<updated>2026-08-30T18:41:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-30T18:41:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=f2f9ed1b3f9c49b323ce591eb72a406058d324a4'/>
<id>urn:sha1:f2f9ed1b3f9c49b323ce591eb72a406058d324a4</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Put back the allow attribute my last commit orphaned</title>
<updated>2026-08-21T17:24:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-21T17:24:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=d440a3992e8a0fa49857210e646b0d653dfa7675'/>
<id>urn:sha1:d440a3992e8a0fa49857210e646b0d653dfa7675</id>
<content type='text'>
Inserting the title-elision helper directly above render_titlebar pushed
that function away from its own #[allow(clippy::too_many_arguments)], which
then applied to a const and left the function warning again. Third time I
have done this to an attribute in this tree; the pattern is inserting at a
"just before this function" anchor without checking what sits immediately
above it.

Also `"x".repeat(20_000)` in the new test, which clippy asked for.

Clippy is clean again - and it was not when I said it was in the previous
commit: the check printed its own "ok" line unconditionally, so three real
warnings scrolled past above it.
</content>
</entry>
<entry>
<title>Elide a long window title instead of cutting it mid-glyph</title>
<updated>2026-08-13T14:51:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-13T14:51:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=f7426a86f2ccc40a18bc6b21d92fc3f073490de1'/>
<id>urn:sha1:f7426a86f2ccc40a18bc6b21d92fc3f073490de1</id>
<content type='text'>
A title that did not fit was hard-cut at whatever character crossed the
button reservation. Nothing marked the cut, so a truncated name read as the
whole name - "annual-report-final-v7-reviewed-2026-with-appendix.ods -
LibreO" looks like a filename, not like a filename with its tail missing.

Titles now end in an ellipsis when they are shortened, which is what
Windows, GNOME and KDE all do, and it keeps the informative half: an
application or document title almost always begins distinctively and ends
in boilerplate. Whole characters are dropped until the ellipsis fits beside
what remains, so the result never overruns the buttons. A titlebar with no
room even for the ellipsis draws nothing, rather than a lone "..." that
says less than an empty titlebar does.

The mark itself is "…" where the system font has that glyph and "..." where
it does not. A missing glyph rasterizes to nothing at all, which would have
quietly reintroduced the invisible-truncation problem on any font without
it.

Also bounded the work: a client may set a title of any length - a browser
tab carrying a whole data URL - and every character cost a rasterization
before the layout could decide it did not fit. Measurement stops at 256
characters, well past anything legible in a titlebar, and a title that long
is elided many times over regardless.

Four tests: an over-long title fits its span and ends in the ellipsis mark;
a title that fits is left exactly alone; a titlebar too narrow for the
ellipsis draws nothing; a 20,000-character title never lays out more than
the cap. Checked on screen too, at 600px and at 220px.
</content>
</entry>
<entry>
<title>Lock screen: show each key's alternate character, use a UI font, space the dots</title>
<updated>2026-07-07T21:32:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-07T21:32:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=a6f98eba60f71f7250f4383e76642a901c5b8439'/>
<id>urn:sha1:a6f98eba60f71f7250f4383e76642a901c5b8439</id>
<content type='text'>
Three reports, three separate causes.

Alternate characters were invisible. A keycap drew only the character the
current shift state types, so there was no way to find a symbol without
pressing Shift and hunting for it - a physical key is labelled with both.
Each cap now draws the other state's character small and dimmed in its
top-right, skipped where the two are the same (every letter differs only by
case, which the cap already shows) and for named keys like Enter.

"Enter Password" rendered in a monospace font. find_system_font
deliberately prefers a mono face, which is right for a titlebar title or a
menu row sitting in columns and wrong for a sentence; on this machine it
resolves to DejaVu Sans Mono. New find_ui_font prefers a proportional face
from a ranked list of widely-installed families, falling back to the mono
one, and the lock screen's prose - prompt, clock, date, username, status --
uses it. Ranked rather than first-found so the result does not depend on
directory order, which is how the mono scan once picked an italic face and
rendered every titlebar in italic.

The password dots had no spacing. They were drawn as a plain string, so a
run of identical bullets separated only by their own advance read as one
smeared blob rather than countable characters. Added explicit tracking,
excluded from the centring width so the row does not sit half a gap left.

531 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Three live bug reports after a restart: icon drag, lock cursor, lock box</title>
<updated>2026-05-11T14:44:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-11T14:44:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=caec1e7c355bbe6437afe87cd3dab6b64fb91e0a'/>
<id>urn:sha1:caec1e7c355bbe6437afe87cd3dab6b64fb91e0a</id>
<content type='text'>
All three reported directly after the owner restarted into today's build.

Desktop icons could not be dragged at all in single-click mode. The press
handler opened the icon immediately when general.desktop_icon_single_click
was on, so the branch that starts a drag was unreachable and an icon could
never be moved. Deciding activation on press cannot distinguish a click from
the first instant of a drag. Every press on an icon now starts a potential
drag and release decides which it was, using a 4px movement threshold that
latches once exceeded. Double-click mode goes through the same path, so both
modes now drag identically.

The lock screen drew no cursor. The cursor push in the udev render loop sits
inside `if !locked`, and a locked head renders only the lock element list, so
nothing drew a pointer - and on a bare TTY nothing else does. The on-screen
keyboard's clicks were being handled correctly the whole time
(native_lock_click); they simply could not be aimed. The pointer is now
prepended to the lock element list, above the UI it is used to click.

The password field's opaque panel is gone. New LockConfig::box_opacity,
default 0.0: no fill, no border, no rounded rectangle, just the dots and
status text over the blurred background. Raising it restores the panel at
that opacity for anyone who wants a solid field. Drawing text on a
transparent surface needed a new blit_glyph_over: the existing blit_glyph
blends against one flat opaque colour and writes alpha 255, which would have
turned every glyph into a block of the assumed background - the same box
with its middle removed.

VERIFICATION STATUS, stated plainly: all three are code-complete and the
suite passes, but none is confirmed on screen. The nested backend's capture
pass does not draw the desktop icon grid (a gap already recorded in
winit/capture.rs), so the icon drag cannot be checked by screenshot there,
and aiming blind is what this project's own rules forbid. The two lock
changes were not visually checked either.

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>Confirm Nemo's popup works; fix the two bugs that hid it, and shadow bleed across a monitor seam</title>
<updated>2026-04-19T22:48:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-04-19T22:48:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=436d42da6ef61a5ea20d5102c4baed7bf0993606'/>
<id>urn:sha1:436d42da6ef61a5ea20d5102c4baed7bf0993606</id>
<content type='text'>
Nemo's right-click context menu was the last open punch-list item, parked
twice as untestable. It works: verified end to end in a throwaway nested
compositor, menu and submenu both, at the correct position and stacking.
The popup path itself needed no fix, so the POPUP-GEOM-DIAG/POPUP-GRAB-DIAG
diagnostics are removed.

Two real bugs turned up in the way of testing it.

zwlr_virtual_pointer was a silent no-op on the winit backend. Every
Motion/MotionAbsolute handler read UdevState::bounds() behind an early
return when state.udev was None, and that field is Some only for the DRM
backend. The protocol advertised its global, accepted create_virtual_pointer
and accepted every request, then discarded all motion with no error and no
log. That is the backend a nested instance runs on, so the only safe way to
drive a throwaway compositor - a Wayland client of that compositor, which
cannot reach any other session, unlike ydotool's /dev/uinput writes - did
not work at all. Bounds now come from WindowManager::monitors() when udev is
absent; both backends fill that list from Platform::monitors().

The winit backend's screencopy pass rendered no popups and no shadows. It
re-renders the scene offscreen, and that second scene was missing tiers, so
grim on a nested instance reported the opposite of the truth: a menu drawing
perfectly on screen photographed as absent. The DRM backend never had this,
since it serves screencopy from the on-screen frame it just drew. Border
strips are still missing from that pass, called out in the code rather than
left silent.

Also fixed, from the "windows show a bit in the other monitor" report:
shadow_rect expanded by SHADOW_SIZE on every side with no monitor-boundary
awareness, so a window flush against a seam put its 24px shadow strip on the
neighbouring screen. shadow_rect_clipped clips to the bounding box of the
monitors the window's geometry actually touches - not just its assigned
one, since a window straddling a seam really does occupy both and clipping
there would cut its shadow off mid-body. The bitmap's own extent stays
unclipped, because the src rectangle indexes into it; only the fragment list
is clipped. Six tests on the incident's own numbers. Not confirmed on
screen: the nested backend cannot produce a second monitor.

New tool: tools/virtual-pointer-click, a scriptable virtual-pointer driver
that acknowledges each command after its round-trip, so a test script can
put a screenshot between a move and the click that follows it.

489 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Fix a real regression: dynamic-mode windows lost their shadow via toggle_floating</title>
<updated>2026-01-30T07:39:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-01-30T07:39:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=2a51fb86e2a123ff9e810a0b4105c9fe5f85e3ae'/>
<id>urn:sha1:2a51fb86e2a123ff9e810a0b4105c9fe5f85e3ae</id>
<content type='text'>
The tiled-shadow-tint fix earlier today gated the shadow on Window::floating
alone. arrange_workspace only reads floating under the "tiling" layout, so
every window on this project's own default "dynamic" layout starts, and
stays, floating: false - the gate misread that as "tiled, no shadow"
regardless of which layout was actually running, so shadows silently
vanished under dynamic mode entirely, recoverable only by pressing Super+S
(toggle_floating), which then looked like that key toggles a tint rather
than floating. Fixed by checking the workspace's own layout name first:
a window is only "currently tiled" when its workspace runs "tiling" AND
it hasn't opted out via floating. DecorationSignature's floating field is
now currently_tiled, since a layout switch changes this for every window
on a workspace without touching any of their own floating fields.

Also disabled general.shadows in the user's own config per direct
request - never asked for, on by default, and a real problem for
color-accuracy work regardless of how correctly it renders otherwise.

Also fixed both context menus (titlebar and desktop) silently truncating
labels past a fixed 170px width with no indication - widened dynamically
to each menu's own real widest label via a new measure_text_width helper.
</content>
</entry>
<entry>
<title>Redesign the titlebar right-click menu: real separators/headers, live customization</title>
<updated>2026-01-28T12:20:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-01-28T12:20:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=d16f7dab25bd9508271017bc6dc15024c30cbf71'/>
<id>urn:sha1:d16f7dab25bd9508271017bc6dc15024c30cbf71</id>
<content type='text'>
Reported live: "looks very ugly currently and some of it doesn't make
sense." Both were real. Every row, including a bare divider, took one
full TITLEBAR_HEIGHT slot, so a separator was a 1px hairline in the
middle of 32px of empty space; "Move to Workspace" faked a section
caption by embedding box-drawing characters directly in an ordinary
item's label, which rendered - and behaved, until the click-dispatch
site's own special case - exactly like a clickable row that did
nothing. Separately, "Floating" was always offered even though
Window::floating only affects the "tiling" layout: toggling it under
this project's own default "dynamic" layout visibly changes nothing,
reading as a broken control rather than an inapplicable one.

ContextMenu (crates/core/src/context_menu.rs) gained real Separator
(9px) and Header (22px, non-interactive, dimmed) row kinds with their
own small heights, replacing the label-hack outright. Both backends'
rendering now sum each row's own real height instead of assuming one
uniform value, so hit-testing and pixels can't disagree about where a
row is. Floating is omitted entirely outside the tiling layout.

New, in direct response to "allow customizing from there as well": a
Customize section with live Button Style / Button Side toggles. Each
flips the matching ThemeConfig field and immediately redraws every open
window's titlebar - not routed through srd set's own path, which is
scoped to windows created after the call for lack of a redraw hook it
can reach; a menu action that didn't visibly change the titlebar you
clicked would be its own "doesn't make sense" bug.

Full workspace build/test/clippy clean (242 core tests, +8; 152
wayland, net-even after rewriting the old label-hack tests).
</content>
</entry>
</feed>
