| Age | Commit message (Collapse) | Author | Files | Lines |
|
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.
|
|
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.
|
|
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.
|
|
Two independent pieces landed together this pass - both real, both
scoped, see docs/TODO.md for the full narrative on each:
Fake monitors: a genuinely independent, additional wl_output with no DRM
connector/CRTC behind it at all - distinct from srd.monitor.split
(divides one real output's own placement rectangle). Researched niri's
own Headless backend first (cloned at ~/reference-wms/niri): its render()
never actually composites anything, a no-render stub for that project's
test suite only. This one is real: it renders whatever is placed on it,
on demand, whenever a zwlr_screencopy_manager_v1 client asks for a frame.
New crates/wayland/src/udev/virtual_heads.rs (create/remove a real Output
+ global, render-on-demand for screencopy, integrated into platform.rs's
monitors() as a genuine srdwm_core::Monitor so core placement/workspace
code needs zero special-casing). New IPC/CLI: srd dispatch create
fake-monitor <name> <w>x<h> / remove fake-monitor <name>. Core-side
request queue in crates/core/src/manager/fake_monitor.rs.
Placement bug, root-caused and fixed: every new window opened alone
landed in the exact same spot, "not at all like Windows" (reported live).
SmartPlacement::place tried a grid cell first, and grid's own cell count
is existing.len() + 1 - with nothing else open (opening one app at a
time, the ordinary case), that's always 1, so a 1x1 grid returns the same
single cell forever regardless of session history. Cascade had the same
bug in a second form (its own step was existing.len() % max_steps, also
always 0 with nothing open). Fixed: WindowManager::next_cascade_step (a
Cell - add_window's own target_monitor stays borrowed across the call)
advances on every real placement and is never reset by a window closing;
place() now skips grid entirely when nothing else is open, going
straight to cascade, since grid's real job (dividing space among
concurrent windows) has nothing to divide when there's no concurrency.
Full workspace build/test/clippy clean (223 core / 141 wayland / 29
platform / 24 ctl / 28 config / 10 x11 tests, 0 failed, 0 clippy
warnings), built and installed.
|
|
Right-clicking (or hovering) the maximize button opens a flyout of
fixed half/quarter positions (snap_flyout.rs renders it); picking one
applies that zone via WindowManager::apply_snap_zone, the click-driven
equivalent of dragging a window to that edge and releasing.
|
|
IPC/global-menu/output-management work
Border/titlebar decoration was built from Window.geometry (the animation's
final target) in both wayland backends' render loops, while sync_geometry
already draws a window's actual content at window_anims' interpolated rect
during any maximize/fullscreen/open-slide tween. Border and content read two
different rectangles for the whole transition, so the border visibly
detached from the window it was outlining - reported as "borders aren't
flush." Both udev.rs and winit.rs now read the same animated rect for
titlebar placement, border-strip placement, and the occlusion test against
later windows in stacking order. Verified: cargo build --workspace, cargo
clippy (0 new warnings), cargo test -p srdwm-core (111/111).
Also checkpoints substantial protocol/IPC work from prior sessions that had
accumulated uncommitted: gtk-shell1 support (gtk_shell.rs, the vendored
gtk-shell.xml, xwayland.rs's X11-side mirror) backing the global app menu;
zwlr_foreign_toplevel_manager_v1 (foreign_toplevel.rs) broadcasting distinct
maximized/minimized/fullscreen/activated state per window; output_management
(ext-output-management + layer-shell exclusive-zone reservation tracking);
workspace.rs and context_menu.rs; a Unix-socket IPC crate (platform/src/
ipc.rs) and a `srd` control-CLI crate (crates/ctl); xkb_config.rs; and a
theme module (core/src/theme.rs). A peer session working the AGS shell
concurrently verified several of these live against a running srdwm: the
global menu rendering a real app's File/Edit menu over gtk-shell1, and
foreign-toplevel correctly reporting maximized and fullscreen as independent,
non-simultaneous states with the geometry each implies (maximize stops at a
reserved top bar and past a dock; fullscreen reaches the true monitor edge).
|
|
The C++ prototype (moved to legacy-cpp/) was mostly a design skeleton:
X11 and Windows backends were partially real, Wayland created the
wlroots object graph but never wired a single event listener, macOS
was stub except monitor enumeration, and the Lua engine's srd.bind()
stored a key-combo string but never the actual closure. See
docs/PRIOR_ART.md for the full audit.
This replaces it with a Cargo workspace:
- srdwm-core: platform-independent window/workspace/monitor state,
a real master-stack tiling layout, and SmartPlacement grid/cascade/
snap-to-edge placement - fixing several bugs in the C++ version
(hardcoded 2-column grid, cascade that never cascaded, snap-to-edge
that always returned a fixed rect). 35 unit tests.
- srdwm-config: the srd Lua API via mlua, implementing the surface
docs/DEFAULTS.md always documented but the C++ engine never actually
built (srd.window.close()/focus(direction), srd.workspace.next(),
real keybinding closures, require("srd") support). 10 unit tests.
- srdwm-x11: a real reparenting WM with a drawn title bar (buttons,
drag, resize), verified live under Xephyr - frame placement and
client offset match srdwm-core's computed geometry exactly, and the
decoration renders correctly on screen.
- srdwm-wayland: a from-scratch smithay compositor (the C++ version
had nothing working to port from) - runs via the winit backend,
tracks xdg-shell toplevels through the same WindowManager and
hit-testing code X11 uses, verified to start/render/run without
crashing. Decorations are solid-color (no text yet); see
docs/IMPLEMENTATION_STATUS.md for exact scope.
- srdwm-windows / srdwm-macos: structured, cfg-gated designs informed
by komorebi/glazewm and yabai/AeroSpace respectively (see
docs/PRIOR_ART.md), honestly marked as unbuilt/unverified since this
sandbox has no Windows or macOS target.
|