| Age | Commit message (Collapse) | Author | Files | Lines |
|
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.
|
|
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.
|
|
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.
|
|
customization
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).
|
|
Reported live: "looks weird and unpolished... need a lot more items".
Compared directly against the exact AGS reference this project's own
menu rebuild already targets rather than guessing:
- Highlighted rows used a flat, fully-saturated fill instead of the
reference's subtle 22%-accent-into-background wash. New decoration::
color::mix_rgb (channel-wise linear blend, generalizing brighten/
darken's fixed-target blends to an arbitrary second colour/ratio) lets
render_context_menu reproduce that same ratio.
- Every separator row was a label string of Unicode box-drawing
characters rendered as text glyphs, which render inconsistently at
small sizes - a label that's entirely U+2500 now draws a real 1px
hairline instead; a label that mixes it with real text ("--- Move to
Workspace ---", a deliberate section-header convention) still renders
as text, unchanged.
- "Select All" added to the bare-desktop menu, the one action every
mainstream desktop's own menu offers that this one lacked.
New tests needed real care: the panel's own rounded-corner distance
field softens alpha within its radius of any canvas edge, not just the
visible corners, so a naive full-row pixel scan against bg picked that
up as a false positive on the first attempt - fixed by scanning only
rows/columns confirmed (via a throwaway debug dump) to sit inside the
panel's genuinely flat interior.
Full workspace build/test/clippy clean, built and installed. Real
submenus and per-row icons remain real, separate scope - this
project's floating-menu UI has no nested-panel concept yet.
|
|
XWayland stability, GPU rendering, and multi-cursor Phase 2
The bulk of a multi-session shift's real work landed in crates/wayland.
Full root-cause/verification narrative for every item below lives in
docs/TODO.md (each has its own dated entry); this is the summary:
Desktop shell:
- Real desktop icons v2 (state/desktop_icons.rs, desktop_icons.rs):
fixed origin-baked-before-the-bar-connects, fixed-icon sort order, and
a proper Rename/Delete-to-Trash menu (window_memory.rs backs the
rename-persistence side). Rubber-band marquee multi-select.
- icon_theme.rs: real freedesktop icon-theme lookup (inherits chain,
hicolor fallback) rendering actual theme SVGs via resvg/tiny-skia,
replacing the hand-drawn placeholder glyphs.
- Context/desktop menus (decoration.rs, desktop_menu.rs, state/menu.rs)
rebuilt to match the project's own AGS panel styling: rounded floating
panel, tinted-fill row highlight, real separators, a much fuller
titlebar window-menu action set.
Layer-shell / multi-monitor:
- Layer-shell hit-testing and render positioning (input/pointer.rs,
udev/render.rs's element placement) now correctly convert LayerMap's
logical geometry into physical pixels on a fractionally-scaled output
- root cause of a bottom-anchored dock being unclickable and
unpainted while a top-anchored bar on the same output worked.
udev/outputs.rs's relayout_outputs gained the same physical/logical
split for cross-output positioning, now backed by a real unit test
(next_logical_x) built from the original measured incident numbers.
- state/geometry.rs: a window's border/decoration no longer briefly
clips when moved between differently-scaled monitors mid-drag.
XWayland / stability:
- xwayland.rs, udev/session.rs, udev/platform.rs: fixed a 100%-
reproducible cold-start XKEYBOARD crash-loop (XWayland's own stdin
inherited a real, already-owned VT; env passthrough and idle-callback
spawn timing were both real, independent gaps) that had silently taken
down all X11-app support and the global-menu registrar every session.
- state/toplevel.rs, state/lifecycle.rs: XWayland dialog detection via
WM_TRANSIENT_FOR, not just a native xdg_toplevel parent.
Rendering:
- udev/render.rs, decoration.rs: real GPU (GBM+EGL+DrmCompositor)
window-content and cursor rendering on the udev backend, falling back
to the untouched Pixman path automatically on any init failure.
- decoration/tests.rs, state/mod.rs: rounded-corner/border fixes for
interactive resize lag and cross-monitor moves.
Multi-cursor Phase 2 (virtual_pointer.rs, new; state/mod.rs, udev/
platform.rs, winit/nested_platform.rs): pins a zwlr_virtual_pointer_
unstable_v1 object to a specific window, bypassing the shared seat/
focus/pointer_pos path entirely via hand-rolled wl_pointer.enter/motion/
button/frame/leave against every WlPointer the target client has bound
(PointerHandle::client_pointers). Lets an agent operate one window while
a human uses another, genuinely simultaneously, with zero client
cooperation and no second wl_seat (confirmed a dead end: real clients
only ever bind the first seat advertised).
Full workspace build/test/clippy clean.
|
|
carve_inner_corner_pixel (an earlier ring fix) cut the
border strip's own disk using an inner circle at the *same* centre as
the outer cut, radius - border_width - correct when a titlebar sits
underneath (it shares that exact circle, by construction), but wrong
whenever client content sits underneath instead: content's own
rounded-corner mask (rounded_corners_pixman.rs's apply_corner_mask) is
centred radius from *its own* buffer's edges, and that buffer starts
border_width rows/columns inside the border strip's - a same-radius
circle offset (border_width, border_width) diagonally from the
border's own outer one, not a smaller concentric one. Reusing the
titlebar-style ring left a real, if very small, gap along part of the
seam and a thin sliver of double coverage along the rest - reported
live, at extreme zoom against a solid-colour wallpaper (chosen
specifically to make a sub-pixel gap easy to spot against, unlike the
usual desktop image): "very tiny gaps... corner radius does not
match."
round_top_corners/round_bottom_corners's own `inner_radius: Option<u32>`
parameter is now `inner: Option<InnerRing>`, an explicit (center_row,
center_col, radius) rather than an implicit "same centre, smaller
radius" - InnerRing's own doc comment has the full geometry for both
cases. render_border_top gained a `decorated` parameter to pick the
right one (titlebar-aligned when true, content-aligned - centre
shifted by border_width on both axes, radius unchanged - when false);
render_border_bottom always uses the content-aligned ring, since this
compositor never draws a bottom titlebar.
apply_corner_mask (rounded_corners_pixman.rs) made pub(crate) so the
new regression test can build a real masked content buffer via the
actual production function, not a reimplementation of its math.
border_top_and_content_mask_have_no_gap_along_the_corner_diagonal_when_undecorated
checks coverage along the diagonal ray between the two circles'
centres - the direction they're actually offset along, and so the
worst case for a gap opening up between them - against the real
render_border_top/apply_corner_mask output, not a hand-rederived
formula.
|
|
round_top_corners/round_bottom_corners only ever cut pixels *outside*
the shared corner radius (the rounded outer silhouette). Nothing cut
anything *inside* it, so a border strip's own "extra" rows (present
whenever corner_radius > border_width) stayed a solid filled disk out
to the centre column/row, then hit clip_middle_beyond_thickness's
hard, unblended rectangular cut right at the disk's own most opaque
point - a clean right-angle step, not a curve. Confirmed live,
zoomed: a real square notch bitten into an otherwise smooth arc,
reported as "squares on the inside corners of each vertex."
Added carve_inner_corner_pixel, the same smoothstep falloff as the
existing outer cut but inverted (cuts near the centre instead of far
from it), applied at radius - border_width so the corner becomes a
genuine ring of ~border_width visible thickness tapering smoothly to
transparent, instead of a filled wedge. Only render_border_top/
render_border_bottom pass an inner_radius; the titlebar's own corner
and the lock-screen box keep their existing solid-disk behaviour,
which is correct for a single flat-coloured panel with nothing of a
different colour underneath needing to show through.
Also generalizes round_top_corners with an explicit center_col
parameter, mirroring the existing center_row shift: the titlebar's own
corner circle was never shifted horizontally to match the border
strip's (only vertically), leaving a border_width-wide sliver of the
titlebar's own misaligned curve poking through at the seam.
border_top_and_titlebar_corners_meet_without_a_seam and
border_top_curve_actually_closes_within_the_side_strips_own_width
updated to match: both now compare the correct corresponding columns
(the titlebar's own buffer starts border_width columns inside the
border strip's), and the latter no longer demands exact 255 opacity at
a point that legitimately sits within the new inner cut's own
antialiasing band.
|
|
Pre-existing issues in the uncommitted rust-rewrite work (unused
imports, over-arity glyph-drawing functions, a couple of complex inline
types, or_insert_with(T::default) instead of or_default(), and one
unsimplified test-only arithmetic expression), plus two imports left
unused by switching to or_default(). None of these are behavior
changes. Full workspace build + clippy -D warnings + test suite (378
tests) all green after this.
|
|
Safety commit before reconciling this worktree with main, which has
diverged with its own separate fixes today. Nothing here is reviewed
or curated yet - this exists purely so none of this work can be lost
to a git operation, disk issue, or worktree cleanup while that
reconciliation happens.
|