| Age | Commit message (Collapse) | Author | Files | Lines |
|
Live incident, root-caused jointly with the AGS peer session: creating a
fake monitor visibly shrank the real primary output's usable area
(full_y stayed 0 throughout - its true position never moved) each time,
tracking almost exactly one bar height per fake monitor created.
create_virtual_head registered its new Output in udev.virtual_heads but
never in CompState::outputs, the list output_for_wl searches to resolve
a client-named wl_output back to anything. new_layer_surface's own
fallback for an output it can't resolve is landing on the primary output
- so AGS's own per-monitor bar, aimed at the fake monitor it reasonably
believed was a new real one, silently landed on the real primary output
instead, stacking its own exclusive-zone reservation on top of the real
bar already there. Two fake monitors, two misrouted bars, two zone
increments, matching the observed climb exactly.
Fixed by registering (and, on removal, deregistering) a virtual head's
Output in CompState::outputs the same way bring_up_head already does for
a real one. Also adds Monitor::is_virtual / MonitorInfo's "virtual" JSON
field, requested directly by the AGS peer session as the real
discriminator their own temporary FAKE- name-pattern match was standing
in for.
The X-position half of this same incident was AGS's own remembered-
layout restore treating a fake monitor's wl_output as a real hotplug --
already fixed on their side (readArrangeable() now filters split/virtual
outputs).
|
|
srd.monitor.split only ever ran at Lua config load despite being a plain
WindowManager mutation that every backend's monitors() already reads
fresh on each call. Adds srd dispatch set output split <name|id> <parts>
[rows|columns] (IPC set_monitor_split), same id-resolves-to-name pattern
set_output_enabled already uses.
Also removes eight log::warn!("XXX-DIAG ...") lines left behind from live
debugging in the multi-session shift that landed in 3c41fc4 - the same
"temporary, never removed" pattern already fixed twice earlier this
session. Several fired on genuinely constant interaction (every title
change, every workspace switch, every layer-shell surface hide), not
just a one-off leftover. Left xdg_shell.rs's own POPUP-GEOM-DIAG/
POPUP-GRAB-DIAG alone - that one is a still-open, self-documented
investigation, not litter.
Also documents (docs/TODO.md, not a code change) a live incident where
creating a second fake monitor visibly corrupted the real monitor's
position and kept drifting with no further input - not root-caused
srdwm-side, flagged to the AGS peer session since a fake monitor's real
wl_output global is indistinguishable from a real hotplug to GDK/GTK.
And documents a deliberate decision not to blind-port window decoration
rendering onto the experimental, never-live-tested GPU render path.
|
|
Live report: the real cursor sometimes leaves a brief ghost behind right
after moving between monitors. The bare-metal render loop already forces
a full repaint (ages = [0, 0]) on a workspace switch or any window move/
resize/open/close/restack, both added earlier for the same underlying gap:
the damage tracker's own element diffing doesn't always catch a vacated
region on its own. Neither reset noticed the pointer leaving one monitor
for another - no window moved, no workspace changed - so that head's own
vacated cursor-sized region was left entirely to the tracker's diffing,
intermittently.
Adds UdevState::last_cursor_head, compared each frame the same way the
other two resets are; only the head the pointer just left gets forced back
to ages = [0, 0] (the newly-entered head draws a genuinely new element
there and diffs correctly on its own).
|
|
Live report: a second cursor appeared uninvited and unusably (frozen,
uncontrollable) on screen. Multi-cursor Phase 1 rendered one sprite per
physical libinput pointer device that had ever reported a position, with
no way to turn it off and no expiry - so a phantom device (a real mouse's
side-button/scroll cluster enumerating as its own HID path is a common
case) that reports once and never moves again left a frozen ghost sprite
with nothing to control or dismiss it.
Adds general.multi_cursor (default false, live-settable via
`srd set multi_cursor <bool>`) and keys secondary_cursors to
(Point, Instant) so both the recording side (udev/session.rs) and the
render side (udev/render.rs) drop any entry older than
SECONDARY_CURSOR_TIMEOUT (1.5s). The "agent controls a window without
interrupting the user" use case this report also raised was never gated
on this flag - that's Multi-cursor Phase 2's pinned virtual-pointer
delivery, which never shows a visible cursor at all.
|
|
Codebase modularization, requested directly. Surveyed the whole
workspace first: at ~38k lines it's already organized by topic
(crates/core/src/manager/, crates/wayland/src/{state,udev,decoration}/
already split into small per-concern files) - crates/platform/src/
ipc.rs was the one real outlier, 1894 lines holding the socket
lifecycle, every payload type, both dispatch match statements, and its
own tests all in one file.
Split into ipc/{mod,types,dispatch,tests}.rs by concern, matching the
established pattern exactly - mod.rs keeps IpcServer itself, types.rs
the response/event structs and snapshot functions, dispatch.rs
handle_request/handle_set, tests.rs the existing suite moved verbatim.
Extracted via exact line-range copies against git's own HEAD content
(not retyped), specifically to rule out a transcription bug in a file
this central. Pure reorganization: build/test/clippy clean before and
after, exact same test count (29 in crates/platform) both times.
README.md separately corrected: it still linked to legacy-cpp/ (deleted
this shift) and described the Wayland backend as the smaller, less-done
one - backwards from current reality, where Wayland is the daily-driver
target and by far the more complete backend.
|
|
Real web research (KDE's own source tree, current as of Plasma
6.6.5/2026): com.canonical.AppMenu.Registrar + dbusmenu is still the
current, unreplaced global-menu mechanism in KDE Plasma 6, and generic
Qt apps still export via the same QGenericUnixTheme path since Qt 5.7 --
exactly what srdwm's own appmenu_registrar.rs/appmenu.rs already
implement. No newer protocol to catch up to, no code gap found. srdwm's
own scope (discovery/registration) is correctly split from AGS's
(rendering) - see the FEATURE_GAP.md entry from the previous commit.
|
|
Two research-only entries, no code changes:
- docs/FEATURE_GAP.md gains a "vs. full desktop environments" section
(KDE/GNOME/XFCE/macOS/Windows), requested directly and distinct from
the file's existing tiling-WM (niri/sway/Hyprland) comparison. Verified
rather than assumed: real app-to-app clipboard already works
(delegate_data_device!), drag-and-drop between real windows already
works; genuine gaps are compositor-level blur-behind, the
already-tracked fractional-scale wl_pointer bug's real-world cost, and
no PipeWire screencasting - with an explicit line drawn between
srdwm's own scope and AGS's (notifications, applets, alt-tab UI,
screenshot tooling are shell concerns, not compositor gaps).
- docs/TODO.md: researched "different titlebars, non-traffic-light,
right side, especially firefox/chrome" and found the requested system
already exists and is already documented (button_style, button_side/
order, glyph-always, and a real, already-correct xdg-decoration
negotiation with Firefox's own specific behavior already documented).
One real, unverified gap found via actual web research into
Chromium's own Wayland decoration history: likely_draws_own_titlebar
only matches org.gnome.* today, and Chromium's xdg-decoration support
has a documented history of inconsistency vs Firefox/GTK. Deliberately
not blind-fixed - forcing decorated=false for Chrome would be worse
than doing nothing if it already negotiates correctly; needs a live
screenshot check with a real Chrome/Chromium install first.
|
|
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.
|
|
Reported live: "try move desktop items all at once somewhere else"
didn't work. Two compounding bugs, both real: CompState::
desktop_icon_drag only ever tracked one icon id, and the click handler
that starts a drag unconditionally collapsed any existing multi-
selection down to just the grabbed icon before the drag even began.
desktop_icon_drag is now Option<DesktopIconDrag> (crates/wayland/src/
desktop_icons.rs, new type): a grab offset, the grabbed icon's own live
position, and a members list - every currently-selected icon (the
grabbed one included), each a fixed offset from the grabbed icon's own
top-left at drag start, so the group moves as one rigid unit.
input/pointer.rs's click handler now only resets to single-selection
when the grabbed icon isn't already part of the current selection --
grabbing one inside an existing multi-selection keeps the whole group
selected and dragging, matching Windows/GNOME/macOS/KDE convention.
end_desktop_icon_drag snaps every dragged icon to its own nearest free
grid cell independently, tracking newly-claimed cells across the group
so two icons landing near each other never claim the same one.
Full workspace build/test/clippy clean, built and installed. Not unit-
testable (this module has no CompState test fixture for its own
selection/drag logic, an already-documented, accepted gap) - needs a
live drag to confirm.
|
|
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.
|
|
docs/TODO.md is this shift's single consolidated pending-work list (see
its own header for why it exists alongside PANEL_SUPPORT_TODO.md/
SESSION_HANDOFF.md rather than replacing them) - every commit in this
batch has its own dated entry there with the full root-cause/
verification narrative. docs/DEFAULTS.md corrected against the real
config engine and extended for every new general.* key this shift added
(aspect_ratio rule action, phone_mode). docs/FEATURE_GAP.md is a new
survey against niri/Hyprland/sway, requested directly. docs/
IMPLEMENTATION_STATUS.md and docs/PRIOR_ART.md updated to match.
Cargo.lock reflects the new resvg/usvg/tiny-skia dependencies (real
icon-theme SVG rendering).
|
|
Live-confirmed via a controlled test (move a window between differently-
scaled monitors, screenshot immediately after vs. a couple of minutes
later): the border briefly shows clipped/missing right after a
cross-monitor tiling move, then self-corrects on a later redraw.
Working hypothesis recorded (client configure/resize/commit round-trip
lagging the compositor's own already-updated model, likely wider on a
cross-scale move than a same-monitor tiling swap), but not confirmed --
reproduction via srd dispatch move window proved inconsistent (its
direction semantics swap within a monitor as often as they cross one),
and a live mouse-drag can't be synthesized here to test directly.
Not a corruption risk: an earlier resize-lag fix already
bounds every border/titlebar crop against the decoration buffer's real
last-built size, so the worst case is a stale/incomplete frame, never an
out-of-bounds read.
|
|
Reported live, in stages: general input sluggishness, then specifically
dock/bar buttons not responding on the secondary monitor. Root-caused
jointly with a peer session (dotfiles-16), who independently instrumented
AGS itself (both bar and dock report correct visible/realized/revealed
state - the client is asking for the right thing) and srdwm's own
layer_hit_test log (the dock received zero hits across ~40 minutes while
the same output's wallpaper and bar took hundreds).
Confirmed against smithay 0.7.0's own source (desktop/wayland/layer.rs::
arrange): LayerMap::arrange() divides the output's physical mode by its
own scale before arranging layers, so LayerMap::layer_geometry() is
logical, not physical. Two call sites used it as physical, this
compositor's convention everywhere else:
- input/layers.rs::layer_surface_under_layers compared the physical
pointer position directly against logical layer geometry. On a
sub-1.0 scale output, logical space is larger than physical, so a
bottom-anchored dock's rect sat entirely past the pointer's reachable
range - permanently unclickable. A top-anchored bar only lost its own
right-hand end, which is what made this look like "the dock is
broken" rather than a scale bug affecting every layer surface there.
- elements.rs::output_layer_elements pushed the same logical position
straight into the physical framebuffer - for the dock, past the
bottom edge entirely, painting nothing.
Both fixed the same way udev/platform.rs::monitors() and udev/outputs.rs
already fix the identical unit mismatch for usable-area computation
(existing precedent, not a new technique): multiply by output.
current_scale().fractional_scale(), rounding to the nearest physical
pixel, before use.
Also removed a temporary per-pointer-motion-event diagnostic log in
layer_hit_test, still live from an earlier debugging session and
explicitly marked for removal but never removed - a real, measurable
cost on the hot input path, likely the direct cause of the separately
reported general slowness.
Full workspace test suite and clippy clean.
|
|
Live testing found v1 genuinely broken, not just rough:
1. Icons weren't rendering reliably at all - ensure_desktop_icons only
ever computed the grid's origin once, on whichever render pass
happened to be first. AGS's own top bar registers its exclusive zone
after that first pass, so origin got permanently baked in at the
pre-bar geometry. Confirmed live via a temporary diagnostic log.
Fixed by re-deriving origin from the primary monitor's current
geometry on every call instead of just the first.
2. Fixed icons (Home/Computer/Trash) always sorted before real files --
confirmed wrong via direct question. The whole list now sorts
alphabetically by label, case-insensitive, fixed icons included.
3. "Set as Wallpaper" was the wrong feature: removed entirely
(DesktopMenuAction::SetWallpaper, general.wallpaper_command,
is_image_path). The user wants that handled by their real file
manager once opened, not reimplemented here.
Also adds real menu functionality per "where are all the options":
Rename (inline text edit, new CompState::renaming_icon field and
keyboard redirect mirroring NativeLock::password's existing precedent),
Delete (moves to ~/.local/share/Trash per the freedesktop.org spec, new
trash.rs module, same-filesystem case, no confirmation - this is the
reversible move-to-trash, not a permanent delete), Empty Trash on the
Trash icon, and Open Terminal Here / Open in File Manager on the
bare-desktop menu (new general.terminal config key).
133 wayland-crate tests (up from 106), full workspace build and clippy
clean.
|
|
mid-gesture
Reported live: moving a window onto the other monitor "looks very messed
up". This machine's two real monitors have genuinely different scales
(eDP-1 at 1.0, HDMI-A-1 at ~0.843) - the exact condition needed to expose
this.
WindowManager::update_drag/update_resize only corrected w.monitor once,
at end_drag (update_resize never corrected it at all, not even at the
end) - but state/geometry.rs::sync_geometry reads that field on every
motion tick to pick which monitor's scale converts the client's physical
size into the logical points xdg_toplevel::configure sends it. Crossing
onto a different-scale monitor mid-drag kept every configure computed
against the origin monitor's stale scale for the gesture's whole
remaining duration, only self-correcting once the button came up.
Both functions now re-derive w.monitor from which monitor the window's
live geometry actually overlaps, every motion tick - the same
Rect::overlaps lookup end_drag already used once at the end, now run
continuously instead. end_drag's own fixup stays as a final-word safety
net for a drag that starts and ends between two motion ticks.
Does not close the related, already-documented gap where a client that
doesn't speak wp-fractional-scale-v1 still mismatches once settled on a
sub-1.0-scaled monitor - this only fixes the stale-during-the-gesture
half.
Two new tests, full workspace suite and clippy clean.
|
|
Closes "right-click on bare desktop" - previously a true no-op, nothing
rendered above the wallpaper at all. Requested directly: a real desktop
"just like windows does" - Home/Computer/Trash plus one icon per real
~/Desktop entry, individually draggable with persisted grid positions,
double-click to open, right-click menus (per-icon "Open" plus "Set as
Wallpaper" for image files when general.wallpaper_command is set; bare
desktop "New Folder"/"Refresh").
Architecture mirrors the existing context_menu.rs/snap_flyout.rs
"compositor-owned floating UI" pattern: desktop_icons.rs (data model, grid
layout, filesystem scan, hit-test), desktop_icons_state.rs (JSON
persistence, same shape as monitor_layout.rs), desktop_menu.rs (the new
right-click menu, reusing decoration::render_context_menu's existing
rasterizer), state/desktop_icons.rs (CompState glue: rescan/select/drag/
open/persist), and a new decoration::render_desktop_icon rasterizer --
hand-drawn glyphs, since no PNG/SVG decoding capability exists anywhere in
this workspace. Wired into both render loops (udev and winit) above the
wallpaper and below every window, and into input/pointer.rs's button/
motion handlers for selection, drag, double-click, and both menus.
Four new config keys: general.desktop_icons (default true - a directly
requested, purely visual feature, unlike the opt-in-while-experimental
general.gpu), general.file_manager, general.desktop_icon_single_click,
general.wallpaper_command (all default off/empty).
Deliberately out of scope for this pass, stated up front: move-to-trash
and "Empty Trash" (destructive, no confirmation-dialog primitive to gate
them on yet), filesystem watching, multi-select, per-mimetype icon art,
icons on any monitor but the primary one.
124 wayland-crate tests (up from 106), full workspace build and clippy
clean.
|
|
Reported live: "terminal output/everything disappears when i sometimes
resize terminal." masked_content_buffer (the udev/Pixman rounded-corner
content-masking path, live on this machine via general.rounded_corners)
rendered a window's whole surface tree into an off-screen buffer and
returned Some(bytes) unconditionally, with no check for whether that
tree actually produced any drawable elements.
content_epoch bumps on every commit, and a fast interactive resize is a
rapid-fire sequence of commits - real odds that one races ahead of the
client's own texture import, making the off-screen render legitimately
come back empty. That blank result got returned as Some and cached under
the new epoch the same as a correct one would, and rounded_content_buffer
only rebuilds on the next epoch change - so the blank buffer stayed on
screen, fully transparent, until the window's next real content change,
indefinite for an idle terminal.
masked_content_buffer now returns None when the element tree is empty,
before doing the render+readback at all - the same "give up unmasked"
pattern already used for a genuine renderer error. rounded_content_buffer
drops rather than replaces its cache entry on None, so the render loop
falls back to unmasked content for that one frame and retries the masked
path on the next.
Scoped to the udev/Pixman backend; winit masks via a GLES shader with no
equivalent failure mode.
|
|
first attempt
effective_frame_of now returns the live drag target while a window is
being interactively resized (same change as the reverted first attempt),
but two things make it safe this time instead of reintroducing the
out-of-bounds texture sample that reversion was for:
- Every src crop rect built from a window's frame width in udev/render.rs
and winit/render.rs (titlebar, top border strip, bottom border strip)
is now clamped against DecorationSignature's own recorded width/
border_width - the bitmap's actual last-built size - before reaching
MemoryRenderBufferRenderElement::from_buffer, which does not itself
validate src against the real texture size. This is a structural floor
independent of timing, not a repeat of the previous unsafe approach.
- handle_pointer_position now calls redraw_decoration_buffer once per
resize motion event (throttled to 60Hz via a new
CompState::resize_redraw_at), closing the lag at its source instead of
only catching up on the next real client commit. This also fixes the
shadow bitmap's identical commit-vs-live-position gap for free, since
redraw_decoration_buffer rebuilds all three bitmaps together.
Updates the TODO.md entry for this bug with the full before/after.
|
|
focus-staleness report no longer reproduces
tools/toplevel-activate: a standalone (not a workspace member - its own
empty [workspace] table, so building srdwm itself never has to build
this too) wayland-client + wayland-protocols-wlr binary that lists every
open zwlr_foreign_toplevel_handle_v1, activates one by index, and prints
the resulting `activated` state from the protocol's own feedback.
wayland-client 0.31.14 / wayland-protocols-wlr 0.3.12 - the exact
versions smithay 0.7.0 already pulls in, so this talks to the same real
client library srdwm itself is built against, not a possibly-drifted one.
Used it to reproduce aegis's own exact repro (nested `srdwm --wayland`,
two plain alacritty windows, activate the non-focused one, check
`srd clients`) precisely: launched a real nested instance, activated
back and forth 5 times, checked `srd clients` immediately and after a
delay each time. Every check matched the protocol's own `activated`
feedback - no staleness found, on the nested/winit backend specifically
(the peer's own repro environment). Documented in docs/TODO.md as
likely already fixed by other focus/window-management work since the
original report, not re-root-caused after the fact, but confirmed not
currently reproducible via the exact repro that found it - left open
one more round in case it resurfaces, with this tool as the fastest way
back to a live repro if it does.
|
|
Traced the whole write/read path for srd clients' focused field going
stale after a zwlr_foreign_toplevel_handle_v1.activate-driven change --
ruled out several plausible causes (a caching/staleness bug at the IPC
layer, a same-cycle dispatch-order race in the nested backend) without
finding the actual mismatch. No live repro was run: this machine has
neither pywayland nor wlrctl, and building a minimal wayland-client
test binary to call activate directly is real, separate scope. Written
up as a lead for whoever picks this back up, not a fix.
|
|
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.
|