<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/docs/TODO.md, 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-06T09:32:00+00:00</updated>
<entry>
<title>Drop placeholder-sized window-memory entries at load</title>
<updated>2026-07-06T09:32:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-06T09:32:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=ad5a7349d28ad52032fff6885e2d8b80ea28dbce'/>
<id>urn:sha1:ad5a7349d28ad52032fff6885e2d8b80ea28dbce</id>
<content type='text'>
An earlier cleanup deleted five of them from window-memory.json by hand and
they came back within minutes. The reason is that a running compositor holds
the whole table in memory and save_all writes all of it back on the next
window close, so a hand-edited file cannot survive a running session.
Filtering on load is the only point where the fix sticks.

remove_window already refuses to record a size the client never chose, so
nothing new is captured this way; this clears what was written before that
landed. Those entries are self-perpetuating - a remembered size makes the
next launch non-provisional, which forces the client to that size rather
than asking it to pick, which writes the same value back on close - so an
affected app can never escape on its own.

A window genuinely sized exactly 800x632 loses its remembered size once and
gets it back at the next real resize or close. Two tests cover the exact
match and that sharing only one dimension is not enough.

531 tests pass, clippy clean.
</content>
</entry>
<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>Use the real user avatar on the lock screen, and let its keyboard type every character</title>
<updated>2026-07-04T12:22:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-04T12:22:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=d448a82990efb5c510064f1de5b5d32e932a4f9e'/>
<id>urn:sha1:d448a82990efb5c510064f1de5b5d32e932a4f9e</id>
<content type='text'>
Two gaps, both found by being asked about them.

~/.face was never read. The file exists here, a 300x300 JPEG, and a grep for
.face/AccountsService/avatar_path returned nothing anywhere in the codebase:
the lock screen drew a coloured circle with the user's initial
unconditionally. It now looks for ~/.face, ~/.face.icon, then
/var/lib/AccountsService/icons/$USER, which is where GNOME and KDE keep the
picture their settings UI sets. Scaled to cover the circle and
centre-cropped rather than letterboxed, and masked with a soft edge. Falls
back to the initial when nothing is set or the file will not decode.

That needed a raster decoder, since ~/.face is JPEG and the only image code
here was resvg, which is SVG-only. Added image with default features off and
only jpeg and png.

The on-screen keyboard could not type most passwords. It had letters,
digits, and the digits' own shifted symbols, and nothing else - no -, _, .,
/, =, [, ], ;, ', comma, backslash or backtick. For the case that keyboard
exists for, a session with no reachable physical keyboard, a password
containing any of those meant no way in at all. Every printable ASCII
character now has a key, with a test asserting the whole 0x20..0x7f range
rather than spot-checking.

The lock screen itself could not be screenshotted: locking a nested instance
hits the pre-existing EGL context-loss crash already recorded in docs/TODO.md,
confirmed again here. That is environmental and predates this change, so the
on-screen appearance still needs the real session. The avatar path is covered
by four tests including one that decodes the real ~/.face through the same
function the lock screen calls.

529 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Generate the GTK button stylesheet from button_style</title>
<updated>2026-07-02T07:26:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-02T07:26:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=4c6d059359537e03f677e83d345f52fae29ae746'/>
<id>urn:sha1:4c6d059359537e03f677e83d345f52fae29ae746</id>
<content type='text'>
Asked for after the previous entry declined to write GTK CSS. The objection
was to clobbering a file full of hand-written work, not to the feature, and
splitting ownership solves both.

srdwm owns srdwm-buttons.css, generated from
theme.decorations.title_bar.button_style and rewritten on every start and
config reload, and adds exactly one @import line to the user's gtk.css if it
is missing. Nothing else in that file is ever touched. The import goes first
because CSS only permits @import ahead of other rules, which also leaves the
user's own rules last and therefore able to override the generated style.

The generated CSS sets a background per button rather than un-hiding a child
image, for the reason the earlier hand-written attempt found by screenshot:
WhiteSur paints the control as the button's own background-image from a
compiled gresource, so clearing that background leaves a blank button rather
than revealing a glyph.

Verified end to end on a real GTK app, both directions, through the
generated file: traffic_lights gives Nemo coloured dots, traditional gives a
dash, a square and an X, each matching srdwm's own titlebar directly above
it. Also verified the import is added once and not duplicated, that
switching style rewrites only the generated file, and that a home without
GTK config directories has nothing written to it.

527 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Document the GTK button-style override, and why srdwm does not write it</title>
<updated>2026-07-01T07:41:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-07-01T07:41:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=695d8f5204731376aa60ad6c759d361b903e81dd'/>
<id>urn:sha1:695d8f5204731376aa60ad6c759d361b903e81dd</id>
<content type='text'>
Firefox and Nemo were reported as still showing traffic lights after the
button side was fixed. Neither was srdwm's doing.

Nemo, and every GTK app: ~/.config/gtk-3.0/gtk.css contained a deliberate
override from 2026-08-22 painting each titlebutton as a glossy macOS dot,
with the glyph hidden by opacity: 0. Its own comment records that it was
added when srdwm's own decoration drew traffic lights, so that every window
matched. srdwm's style has since changed to traditional and the stylesheet
was still enforcing the old look.

Firefox was already on its traditional variant, byte-identical to
userChrome-traditional.css with the legacy stylesheet pref enabled. It needs
only a Firefox restart.

A first attempt at the GTK fix produced invisible buttons, confirmed by
screenshot: clearing the coloured backgrounds and un-hiding the child image
left blank space, because WhiteSur paints the control as the button's own
background-image from its compiled gresource and there is no child image to
reveal. The working version supplies the icon explicitly via
-gtk-icontheme(). Verified by screenshot: Nemo's header now draws a dash, a
square and an X directly under srdwm's own titlebar drawing the same three.

Kept as a swappable pair, gtk-traditional.css and gtk-traffic-lights.css,
matching the convention Firefox's chrome directory already uses.

srdwm publishes the button layout itself but deliberately does not write
this CSS: the file is the user's, already held hand-written work, and a
compositor silently overwriting it would destroy customisation it cannot
understand.
</content>
</entry>
<entry>
<title>Stop window memory poisoning itself, and publish the decoration side to GTK</title>
<updated>2026-06-02T18:08:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-06-02T18:08:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=30d45a7b9e0ddcd7ed2836c16e77b423cf3cff28'/>
<id>urn:sha1:30d45a7b9e0ddcd7ed2836c16e77b423cf3cff28</id>
<content type='text'>
Two reports, both traced to a cause other than the one being blamed.

"Windows still spawn as squares" was not placement. On the live session
firefox was 800x632 and so were four other apps, and 800x632 is exactly the
placeholder new_managed_window assigns before a client has chosen anything.
Firefox's remembered size earlier the same day was 1389x933.

The loop: a window closes while still carrying the placeholder, the
placeholder is remembered, the next launch therefore has a remembered size
and is no longer provisional, a non-provisional window is forced to its size
instead of being asked to pick, and on close the placeholder is written back.
Every app that ever closed early gets pinned to one identical box, and no
amount of placement work can touch it because the size never came from
placement.

remove_window now refuses to remember a size the client never chose. That
alone would have been wrong: adopt_provisional_size cleared its own tracking
set but never cleared Window::size_is_provisional, so nothing would ever have
been remembered again. Both halves are covered by tests. Five poisoned
entries were dropped from the live store and the six real ones kept, with a
backup alongside it.

"When user sets decorations should override all applications": the earlier
answer was true about the protocol and wrong about the outcome. GTK never
negotiates decoration, but it does read the desktop's button-layout
preference - GTK4 through xdg-desktop-portal, GTK3 through
gtk-decoration-layout. srdwm now publishes its own button_side there at
startup and after every reload, which is precisely the job kde-gtk-config
does for KWin. Verified in both directions from a neutral starting value;
testing the second direction is what exposed an ordering bug where the
publish ran before apply_general_settings and broadcast the default instead
of the configured side.

527 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Let negotiating clients be forced to server-side decoration, and document the GTK half</title>
<updated>2026-06-01T21:18:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-06-01T21:18:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=3ef784e09f6b81b767837e048d691c4e63528e55'/>
<id>urn:sha1:3ef784e09f6b81b767837e048d691c4e63528e55</id>
<content type='text'>
Asked to research how KDE and GNOME make decorations consistent, after being
told too quickly that srdwm could only control its own titlebar.

Measured against a nested srdwm, one client at a time: Qt/KDE creates a
decoration object and asks for server-side; winit creates one and asks for
CLIENT-side; GTK never creates one at all. The xdg-decoration spec says the
compositor "can decide not to use the client's mode and enforce a different
mode instead" and that the client "must obey" - so the first two are
srdwm's to decide, and it had simply been deferring. The same spec closes
the door on the third: a client that does not negotiate continues to
self-decorate, and GTK is not having the conversation.

New theme.decorations.force_server_side, default off, overrides the client's
requested mode. Verified: with it off Alacritty draws its own content to the
window's top edge, with it on the same window gets srdwm's titlebar --
(0,0,0) versus (46,52,64) sampled at three rows. Off by default because it
cannot move a GTK button and can stack srdwm's titlebar on a client that
draws its own regardless, which is the Firefox case already recorded here.

The GTK half needs no compositor code, only the desktop setting GTK actually
reads - xdg-desktop-portal's org.gnome.desktop.wm.preferences button-layout
for GTK4, gtk-decoration-layout for GTK3. This machine was serving
"close,minimize,maximize:" (left) while srdwm's own button_side was right,
which is the entire mismatch. Documented in DEFAULTS.md with the mapping
from button_side to layout string, and noted that this is exactly what
kde-gtk-config does for KWin.

525 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Fix the maximize border on the path that actually runs, and three spawn faults</title>
<updated>2026-05-15T17:05:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-15T17:05:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=27daed83230eaefd0f41461cad20bbed7d1ad575'/>
<id>urn:sha1:27daed83230eaefd0f41461cad20bbed7d1ad575</id>
<content type='text'>
The maximize border was still drawn because the earlier fix landed on the
wrong branch. udev/render.rs has three border blocks: the SRDWM_GPU=1 path
at the top and two Pixman ones below. The patch replaced the first match in
the file, which is the GPU branch a real DRM session never runs. All four
sites across both backends are now gated on !maximized.

The verification had failed twice for a separate reason: winit/capture.rs
did not draw border strips at all, so a screenshot could never answer "is
there a border here" and the control passed for the wrong reason. Border
strips are now drawn into that pass as solid fills - corner rounding is not
reproduced, so a capture is not pixel-exact at the corners, but presence,
position, thickness and colour are. With that closed the test has a real
control: unmaximized gives 6 accent pixels at x=800..805, exactly the
configured border_width, and maximized gives none at the right edge or along
the top row. That proves the winit path; the Pixman path is the same change
at two more sites and is not separately confirmed on screen.

Windows spawning as squares, partly off-screen, and always on the left were
all SmartPlacement::grid. It returned size.min(cell), shrinking every window
to its grid cell whatever size it asked for; it scanned cells in reading
order and took the first free one, which is the leftmost; and nothing clamped
the result, so a window larger than its cell could hang off the edge with its
border out of view. The cell now decides only where a window goes, the scan
starts from a rotating cell, and both grid and cascade clamp into the usable
area. Four tests, one per reported symptom.

525 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>Confirm the monitor-seam shadow fix on screen, and retract a wrong lead</title>
<updated>2026-05-05T12:10:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-05T12:10:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=baabd91411dacd179c690f724b6cbfe6270cb131'/>
<id>urn:sha1:baabd91411dacd179c690f724b6cbfe6270cb131</id>
<content type='text'>
The seam bleed the owner reported as "windows show a bit in the other
monitor" was fixed and unit-tested but never seen. With monitor split now
working in a nested instance it can be, and it is.

Same window, same settings, same instance, same scanline. Control, right
edge at x=320 with no seam nearby: a real shadow, (9,9,13) two pixels out,
fading through (11,11,17) and (12,12,19) to the bare desktop (13,13,20) by
23px, a full 24px falloff. Seam, right edge exactly on the boundary at
x=640: (13,13,20) at every sample from two pixels out onward. Nothing
crosses.

Retracting the "SSD versus CSD" lead recorded in the previous commit. The
first attempt at this test looked like a pass until the negative control
failed - the same window off the seam had no shadow either. Narrowing it to
"Alacritty gets a shadow, Nemo does not" pointed at decoration, and that was
written down as a lead. It was neither: one temporary diagnostic in the build
path reported max=true. Nemo restores its own maximized state on startup and
the shadow gate correctly excludes a maximized window, which has no
neighbour to separate from. No bug, and decoration had nothing to do with
it. One log line beat two rounds of reasoning from symptoms; the lead is
retracted in the same file that stated it.

515 tests pass, clippy clean.
</content>
</entry>
</feed>
