<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/crates, 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>Move a window in steps, and let the keyboard resize one at all</title>
<updated>2026-08-25T12:49:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-25T12:49:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=8b3ef3c7b28570122126a1c8b3d6695fc3647557'/>
<id>urn:sha1:8b3ef3c7b28570122126a1c8b3d6695fc3647557</id>
<content type='text'>
Two of the owner's reported gaps, both about moving a window without a
mouse.

Super+Shift+HJKL slammed the window to the far edge of the monitor in one
press: "it should move in increments - one side, middle, other side - not
just extreme left/up/right/down". It now steps an eighth of the monitor per
press, which crosses the screen in eight, passes through the middle at the
fourth, and stops flush against the edge rather than short of it. A
fraction rather than a pixel count, so the same key feels the same on a
laptop panel and a 4K display.

Only outside a tiling layout. There a window does not own its geometry --
stepping it would be undone by the next arrange - so the neighbour swap
stays, and three tests that assumed swapping on a dynamic workspace now say
which layout they mean.

Keyboard resize did not exist. Super+right-drag has resized with the mouse
for a while, which is why this went unnoticed, but nothing resized a window
without one. `srd.window.resize("right", "grow"|"shrink")` grows or shrinks
from the far edge in that direction, leaving the top-left where it is, and
is bounded by the same minimums a drag is and by the monitor's usable area
- a window can be resized neither to nothing nor off the screen, and a
test holds each key down forty times to prove it.

Wired into the owner's config: Super+Ctrl+arrows resize (the arrow says
which way the far edge moves, so Right always widens), and Super+Ctrl+L
locks the screen. Hyprland only ever bound the lock to XF86ScreenSaver,
which most keyboards do not have; Super+L is the convention everywhere else
but is already focus-right here. Ctrl+arrows rather than Ctrl+HJKL because
Super+Ctrl+K is already kill-process.

Verified through the real path in a nested compositor running that config:
89 bindings, all 89 described, the five new ones registered.
</content>
</entry>
<entry>
<title>Look for a free spot before piling a new window on the last one</title>
<updated>2026-08-23T22:37:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-23T22:37:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=807d0ba1680cdc1322bfe9584781416339f6f76d'/>
<id>urn:sha1:807d0ba1680cdc1322bfe9584781416339f6f76d</id>
<content type='text'>
Reported: windows spawn predominantly on one side and on top of each other,
with no smart placement. Measured first, in a nested compositor: five
windows opened at 30,30 then 60,60 then 90,90 then 120,120 then 150,150 --
every pair overlapping, all in the top-left. The cascade was the only thing
running.

The grid never engaged, and could not. It asked whether a grid CELL was
free and then placed the window at that cell's corner at its own, larger
size. An ordinary 800x600 window on a 1280x800 screen overlaps every cell
of a 2x2 grid, so no cell was ever free, the grid returned nothing, and
everything fell through to the cascade.

Placement now looks for a position where the window overlaps nothing at
all, and only cascades when the screen genuinely cannot fit one - which is
what Windows does once its own screen fills up, and what makes the cascade
the right last resort rather than the first answer.

The candidates are the edges of what is already on screen: every window's
left and right edge plus the monitor's own, taken both as "put my left edge
here" and "put my right edge here", and the same vertically. That is
Openbox's place_overlap reduced to this case (~/reference-wms/openbox), and
it works because a rectangle packed against other rectangles is always
flush with one of their edges - nothing is gained by testing the space in
between.

Two things the naive version got wrong, both fixed here:

Ties go to the position nearest the middle of the monitor, and the choice
rotates through the four most central free spots. Least-overlap placement
is deterministic, so opening one window at a time - open, use, close, open
the next - put every one of them in exactly the same place, which is this
project's own earlier bug report. Every candidate rotated between is free,
so variety never costs the guarantee.

And placement now runs again once the client's real size is known. It has
to happen before the client commits anything, so it was deciding where an
800x600 placeholder should go rather than the window - a small terminal
was told it was 800x600, no two of those fit, and it cascaded. Only when
the client actually chooses a different size: re-running it otherwise
consumed a second cascade step for nothing, and the cascade wraps, which
measured as two windows landing on exactly the same spot.

287 core tests pass, clippy clean.
</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>Read the window-memory store at a moment when it can actually match</title>
<updated>2026-08-10T23:35:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-08-10T23:35:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=38d899d19b5e5204062ecab4bb4105d51afddb6b'/>
<id>urn:sha1:38d899d19b5e5204062ecab4bb4105d51afddb6b</id>
<content type='text'>
Reported twice, as two complaints: windows do not remember their size or
position across a close or a reboot, and windows spawn stacked on one side
with no smart placement. One bug.

`add_window` looks the store up by app_id. A Wayland toplevel role exists
before its client sends set_app_id, so at the moment srdwm placed a window
the app_id was the empty string, every lookup missed, and every window fell
through to the cascade - which is exactly what "they all open on top of
each other" looks like. The store was being written correctly the whole
time and read at the one moment it could not match.

The lookup now runs again the instant a real app_id arrives, which is still
before the client's first buffer, so nothing is drawn in the wrong place
first. It only moves a window still sitting where the cascade put it: a
rule's explicit geometry, a maximize, a dialog's centring and a client's own
committed size are each more specific than "wherever I last left this app",
and a test asserts none of them is overridden.

A second bug sat underneath the first and only appeared once it was fixed:
the position came back and the size did not, which is stranger than nothing
being restored. The backend keeps its own copy of "this size is only a
guess" (provisional_size) and adopt_provisional_size reads that one rather
than the core flag, so the client's next commit overwrote the size that had
just been restored. Cleared with the same call.

Verified end to end in a nested compositor, driving a real edge-drag with
the virtual-pointer tool:

  seeded store 400,300 500x400 -&gt; opened at exactly 400,300 500x400
  dragged the right edge      -&gt; 646 wide, store rewritten to 646 on release
  closed and reopened          -&gt; 400,300 646x400

Before this the same first step opened at 30,30 800x600.

Also: window_memory::save_all's nested guard now allows a write when the
instance was given its own state directory (SRDWM_STATE_PATH or
XDG_STATE_HOME). The blanket refusal added earlier kept the owner's store
safe but made the feature impossible to test without pointing a test
compositor at the real desktop, which is how this went unverified in the
first place.
</content>
</entry>
<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>Reach GTK4's window buttons too, and speak the decoration protocol GTK knows</title>
<updated>2026-07-26T23:33:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-26T23:33:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=3c47912ae0a2b81d06e4e9101b5b4e2f1e2997d9'/>
<id>urn:sha1:3c47912ae0a2b81d06e4e9101b5b4e2f1e2997d9</id>
<content type='text'>
Two findings from reading what other projects do, and then measuring this
machine rather than trusting the reading.

GTK has never implemented xdg-decoration, so a compositor that advertises
only that protocol is invisible to every GTK client on the decoration
question. What GTK does implement is KDE's older
org_kde_kwin_server_decoration - confirmed by reading libgtk-4's own symbol
strings, where the manager, the mode enum and the default-mode handler are
all present, and absent from libgtk-3. That is the channel a KDE session
uses. srdwm now advertises it alongside xdg-decoration, with both answering
from the same policy (theme.default_decorated, theme.force_server_side) so a
client is told the same thing whichever it asks through.

Measured what that actually buys, with WAYLAND_DEBUG on a real GTK4 client:
it binds the manager and receives default_mode(2) = Server, and then never
creates a decoration object for its window. So it changes nothing for a GTK
application's own header bar, and it is kept because it is the correct thing
to advertise and because clients that do honour it - Qt and KDE's own --
now get server-side decoration from srdwm instead of nothing.

The second finding is the one that fixes what was reported. GTK3 and GTK4
put their window buttons under different CSS selectors, and the generated
stylesheet named only GTK3's. A diagnostic rule proved it both ways: a flat
colour reached Nemo (GTK3) through `headerbar button.titlebutton` and
gnome-calculator (GTK4) through `windowcontrols button`, and neither
selector reached the other toolkit. Every style is now written for both, so
a GTK4 application is styled rather than silently skipped. `headerbar`
itself works in both, so the titlebar block needed no split.

Verified on screen: gnome-calculator, a GTK4/libadwaita application, now
draws its header bar in srdwm's own titlebar colour with srdwm's text
colour, where before it kept its theme's.

Also worth writing down, because it bounds what any of this can achieve: an
application's header bar is its own widget. No protocol removes it. GTK_CSD=0
does not, the KDE protocol does not, and neither does forcing server-side
decoration - that only adds a second titlebar above the first. What a
compositor can do is make the two look like one, which is what this does.
</content>
</entry>
<entry>
<title>Hide a window only when srdwm knows it has not drawn, not when a lookup says so</title>
<updated>2026-07-26T23:25:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-26T23:25:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=e20d49b0ee3dbd83499445d61eb2d65904d74311'/>
<id>urn:sha1:e20d49b0ee3dbd83499445d61eb2d65904d74311</id>
<content type='text'>
Regression I introduced two commits ago, reported live: "I can click close
where the button would normally be and it does close, but it is still
invisible."

The gate that stops an empty frame being drawn before a client paints asked
the renderer, from inside the render loop, whether a window's surface had a
buffer attached right now - and treated "no" as "do not draw". That
question is only meaningful for a native xdg-shell toplevel. An XWayland
window's surface state does not describe it the same way, so the answer came
back no on every frame and the window was never drawn again, while srdwm's
own hit-testing carried on working perfectly: an invisible window that still
takes clicks, which is a worse failure than the empty frame it was meant to
prevent.

Inverted to the fail-safe direction. `new_managed_window` - the one path
that creates a native toplevel - puts the window into
`awaiting_first_buffer`, and `commit` takes it out on the first commit that
carries a buffer. The render and capture paths test that set and nothing
else. A window is now hidden only when srdwm itself put it there, so no
window whose plumbing works differently can be hidden by a lookup that did
not apply to it: the XWayland map path never touches the set, and neither
can anything else.

The buffer question still gets asked, but only in `commit`, about a surface
it was just handed, where it is the right question.

Verified both halves: an ordinary spawn still shows no frame before content
(26 captured frames with content, 0 without), and the only way into the set
is one line in one function.
</content>
</entry>
</feed>
