| Age | Commit message (Collapse) | Author | Files | Lines |
|
across a monitor seam
Nemo's right-click context menu was the last open punch-list item, parked
twice as untestable. It works: verified end to end in a throwaway nested
compositor, menu and submenu both, at the correct position and stacking.
The popup path itself needed no fix, so the POPUP-GEOM-DIAG/POPUP-GRAB-DIAG
diagnostics are removed.
Two real bugs turned up in the way of testing it.
zwlr_virtual_pointer was a silent no-op on the winit backend. Every
Motion/MotionAbsolute handler read UdevState::bounds() behind an early
return when state.udev was None, and that field is Some only for the DRM
backend. The protocol advertised its global, accepted create_virtual_pointer
and accepted every request, then discarded all motion with no error and no
log. That is the backend a nested instance runs on, so the only safe way to
drive a throwaway compositor - a Wayland client of that compositor, which
cannot reach any other session, unlike ydotool's /dev/uinput writes - did
not work at all. Bounds now come from WindowManager::monitors() when udev is
absent; both backends fill that list from Platform::monitors().
The winit backend's screencopy pass rendered no popups and no shadows. It
re-renders the scene offscreen, and that second scene was missing tiers, so
grim on a nested instance reported the opposite of the truth: a menu drawing
perfectly on screen photographed as absent. The DRM backend never had this,
since it serves screencopy from the on-screen frame it just drew. Border
strips are still missing from that pass, called out in the code rather than
left silent.
Also fixed, from the "windows show a bit in the other monitor" report:
shadow_rect expanded by SHADOW_SIZE on every side with no monitor-boundary
awareness, so a window flush against a seam put its 24px shadow strip on the
neighbouring screen. shadow_rect_clipped clips to the bounding box of the
monitors the window's geometry actually touches - not just its assigned
one, since a window straddling a seam really does occupy both and clipping
there would cut its shadow off mid-body. The bitmap's own extent stays
unclipped, because the src rectangle indexes into it; only the fragment list
is clipped. Six tests on the incident's own numbers. Not confirmed on
screen: the nested backend cannot produce a second monitor.
New tool: tools/virtual-pointer-click, a scriptable virtual-pointer driver
that acknowledges each command after its round-trip, so a test script can
put a screenshot between a move and the click that follows it.
489 tests pass, clippy clean.
|
|
shadow limit
Desktop icons stayed highlighted after clicking a window: select_desktop_icon(None)
was only ever called from start_desktop_marquee, never from the one place every
focus path (click, Alt-Tab, dock IPC, scratchpad show, snap flyout) already
funnels through. Added the deselect there instead of per-caller.
Closing a focused window could silently switch the user's active workspace:
remove_window's fallback picked self.order.last(), but that list is global,
not per-workspace, so it could land on a background window elsewhere - and
focus_window already switches workspace to match whatever it's given (a real,
separate feature for a deliberate srd dispatch focus). Fixed by preferring a
same-workspace window first. New general.close_focus_follows_workspace
(default false, live-settable) controls what happens only when nothing is
left on the current workspace at all: off leaves focus at nothing, matching
Windows/GNOME/macOS; on restores the old always-follow-the-global-fallback
behaviour. Three new tests.
Also documented, not fixed: shadows can still bleed onto a neighbouring
*monitor* near a multi-output seam (shadow_rect has no monitor-boundary
awareness), found via a live cross-monitor screenshot. Moot for this
session since general.shadows is already off in the live config, but a
real, open gap for anyone who re-enables shadows on a multi-monitor setup.
|
|
Reported live, found via a screenshot of the user's real open window:
srdwm's own server-side titlebar stacked on top of zathura's own
girara-drawn header, same class of bug Firefox/Nemo needed a fix for.
likely_draws_own_titlebar only matched org.gnome.* app ids; zathura's
app id (org.pwmt.zathura) fell through it entirely, with no rules.lua
entry to catch it either.
Broadened the heuristic to also match org.pwmt.* - PWMT's small
toolset (zathura the only one in common use) shares GNOME's own
"always draws its own header" property, on the same live evidence
that justified org.gnome.* in the first place. No rules.lua entry
needed; the heuristic catches it automatically now.
Reproduced and confirmed fixed in a disposable nested compositor
(built, tested double-decoration, then rebuilt with the fix and
retested clean) - never the live session itself.
|
|
toggle_floating
The tiled-shadow-tint fix earlier today gated the shadow on Window::floating
alone. arrange_workspace only reads floating under the "tiling" layout, so
every window on this project's own default "dynamic" layout starts, and
stays, floating: false - the gate misread that as "tiled, no shadow"
regardless of which layout was actually running, so shadows silently
vanished under dynamic mode entirely, recoverable only by pressing Super+S
(toggle_floating), which then looked like that key toggles a tint rather
than floating. Fixed by checking the workspace's own layout name first:
a window is only "currently tiled" when its workspace runs "tiling" AND
it hasn't opted out via floating. DecorationSignature's floating field is
now currently_tiled, since a layout switch changes this for every window
on a workspace without touching any of their own floating fields.
Also disabled general.shadows in the user's own config per direct
request - never asked for, on by default, and a real problem for
color-accuracy work regardless of how correctly it renders otherwise.
Also fixed both context menus (titlebar and desktop) silently truncating
labels past a fixed 170px width with no indication - widened dynamically
to each menu's own real widest label via a new measure_text_width helper.
|
|
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).
|
|
Diagnosed by a peer session (dotfiles-1a): SHADOW_SIZE is 24px, and a
tiling layout with a small gap_inner (as little as 1px live) leaves the
shadow nowhere to fall except onto the adjacent tile, darkening it by up
to SHADOW_MAX_ALPHA (~35%) on whichever side is unfocused. Not a content
tint or an opacity rule - verified against the actual rasteriser and the
live rule set before accepting the diagnosis.
A drop shadow separates a window from what's behind it; tiled windows are
coplanar and adjacent by construction, with nothing behind them to
separate from. redraw_decoration_buffer's shadow gate now requires
w.floating in addition to the existing !maximized/!fullscreen checks.
DecorationSignature gained a floating field so toggling floating on its
own invalidates the decoration cache instead of waiting for an unrelated
field to force a rebuild.
Live-verified in a nested compositor: two tiled windows show a clean
shared edge with no gradient bleeding across; floating a window still
detaches it from the tile group with its shadow intact; shadows still
toggle globally both ways.
|
|
The intro read as a marketing claim ("aiming to feel like a native window
manager... rather than a compromise") instead of stating what the project
actually does. Replaced with concrete, verifiable facts: every backend
draws a real title bar with drag/resize/minimize/maximize/close.
The status table implied Windows and macOS were unplanned ("designed, not
built") rather than real, in-progress work blocked only on hardware
access. All four backends are equal in design intent; Linux is verified
because it is the only platform with a working development machine right
now, not because the others are lower priority. Windows/macOS rows now
name the real cfg-gated code that already exists for each and state
plainly why it has never been built or run for real.
|
|
Root-caused "windows spawn small and square, not remembering placement or
size": new_managed_window hardcoded a fresh toplevel's geometry to 800x632
before the client had said anything about its own preferred size, and
sync_geometry forced that guess onto the client's very first
xdg_toplevel::configure unconditionally. Per xdg-shell, size: None on that
first configure is how every mainstream compositor lets a client pick its
own natural size instead; this one never did, so every app converged on
the same placeholder rectangle regardless of what it would have chosen.
Window::size_is_provisional marks a size that really was just the guess
(not a remembered geometry, a rule's explicit geometry action, or a
maximize/phone-mode fill, none of which are guesses). sync_geometry sends
size: None for such a window's first configure; a new adopt_provisional_size,
called from the commit handler, adopts the client's own real first size
into Window::geometry the moment it commits one, clamping only position so
a bigger-than-guessed window can't hang off its monitor's edge.
Live-verified in a nested compositor: a zenity dialog now renders at its
own compact natural size instead of being stretched to the old guess.
|
|
Attempted to screenshot the redesigned lock screen in a disposable nested
compositor instance; the nested instance's own EGL context was lost the
moment the lock engaged, before any frame rendered. Reproduced the exact
same crash on the immediately prior commit, which never touches the lock
screen, confirming this is pre-existing sandbox/EGL flakiness (already
visible as transient BAD_ALLOC errors during ordinary nested startup, not
something this change introduced) rather than a lock-screen regression.
|
|
wrong-password shake
The native lock UI was a flat bordered rectangle with three left-aligned
text lines and no shadow, clock, or identity marker - reported directly
as looking unfinished. Splits the redesign across a new transparent-canvas
header (time, date, circular avatar, username) above a redesigned,
centered password box with a real drop shadow and a dimmed placeholder
prompt, plus a genuine on-screen QWERTY-shaped keyboard with working
Shift/Backspace/Return/Space and real click hit-testing shared with the
render path via one `lock_stack_layout` function, and a damped-sine shake
on a failed attempt.
LockConfig gains show_clock/show_keyboard/avatar_bg, each independently
srd.set-able and documented in a new theme.lock.* section in DEFAULTS.md.
native_lock_render_elements now takes one NativeLockFrame struct instead
of positional buffer arguments now that it composites five optional
layers instead of two.
Full workspace build/test/clippy clean (152 wayland tests, +6 new).
|
|
The GPU (SRDWM_GPU=1/general.gpu) path had real window content but
square corners and no border/titlebar. A prior pass investigated a full
port of the Pixman path's decoration rendering and deliberately did not
attempt it blind, given no working GPU-capable hardware on this machine
to verify a single pixel of it against. Asked directly, twice, to build
it anyway rather than leave it.
Scoped smaller than a full port: border top/bottom strips and the
titlebar bitmap now render, reusing the exact cached MemoryRenderBuffers
the Pixman path already builds (renderer-agnostic pixel buffers,
imported for GlesRenderer the same generic way cursor::render_elements
already does for either renderer). Left out on purpose: occlusion-
fragment clipping against overlapping windows, and the left/right border
side strips plus the drop shadow.
Full workspace build/test/clippy clean. Explicitly not visually
verified - same reason as before, no GPU-capable hardware on this
machine.
|
|
Chrome/Chromium: launched a real google-chrome-stable in a nested
compositor and confirmed no double-titlebar - Chrome negotiates
ClientSide decoration itself, srdwm correctly doesn't stack its own SSD
on top, and the Unity-style menu row it draws is Chrome's own chrome,
not evidence of a bug. likely_draws_own_titlebar needs no new entry for
it.
Nemo: confirmed the same no-double-decoration result, but could not
safely test the actual right-click-shows-a-popup symptom - ydotool is
a uinput-level daemon shared with the live session, not scoped to the
nested compositor, so a blind synthetic click there risks landing in
the user's real desktop. Parked rather than guessed at; the existing
POPUP-GEOM-DIAG/POPUP-GRAB-DIAG diagnostics stay since the underlying
bug's status is still genuinely unknown.
|
|
Closes the remaining "config-file only" gaps from the AGS capability
survey. workspace.per_monitor gets srd set per_monitor <bool> - safe to
flip live since a monitor with no per-monitor override already falls
back to current_workspace regardless of mode, so nothing visually jumps
on toggle.
theme.decorations.title_bar.button_style/button_side/button_order and
general.desktop_icons/desktop_icons_all_monitors all get the same
live-set + SettingsResponse readback treatment as everything else this
session. New srdwm_core::format_button_order is parse_button_order's
exact inverse, so button_order's readback matches the same string shape
srd set itself accepts.
|
|
question
Investigated a live srd.monitor.scale path, the obvious next candidate
after monitor.split's own live path. Unlike split (a pure
placement computation), scale is only ever read by disable_connector_by_
name/enable_connector_by_name - applying it live means a real disable-
then-re-enable cycle on the physical connector, the same screen blank a
genuine unplug/replug causes. Parked the "is that an acceptable cost"
question rather than deciding it alone; left monitor.scale as Lua-
config/restart-only for now.
|
|
Reported by the aegis-fc peer session testing srdwm's own layer-shell
strut handling: a maximized X11 client sat 4-8px past the right and
bottom screen edges whenever its border was nonzero.
set_border_width sets the frame's native X11 border-width attribute,
which the X server draws outside a window's own declared width/height on
all four sides - unlike every other backend's own border in this
compositor (rendered as ordinary pixels inside the allocated geometry
rect). apply_geometry configured the frame at geometry's own x/y/width/
height verbatim, so a nonzero native border pushed the frame's true
visible footprint 2*border_width past every edge of what geometry
actually promised.
Fixed by shifting the configured origin inward and the configured size
down by border_width on both axes (frame_geometry_for, pulled out as a
pure function so it's unit-tested without a real X11 connection) - the
visible footprint, native border included, now lands exactly on
geometry. border_width == 0 reduces to the prior behavior exactly.
|
|
Investigated the "tiling needs a lot of work" report directly. The
MasterStackLayout algorithm itself was already correct; the real gap was
that dragging or resizing a tiled window did nothing durable (raw
geometry that the next arrange_workspace silently discarded), and
master_ratio/master_count had no live path at all (config-file only).
A resize-drag on the shared master/stack boundary now live-adjusts
TilingConfig::master_ratio and re-arranges the group immediately; srd set
master_ratio/master_count do the same for a keybind or script. Found and
fixed a real bug while building this: start_resize's own focus_window
call re-stacks its target in self.order before the ratio-drag decision
used to be made, silently misclassifying real master-column grabs.
Fixed by deciding ratio-drag status (and freezing the membership
snapshot it depends on) before that raise happens, applying
MasterStackLayout directly against the frozen snapshot rather than
re-deriving membership from the by-then-reordered live order. Live-
verified in a nested compositor, not just unit-tested.
Also closes the readback gaps flagged directly by the AGS peer session:
border_width/border_color/corner_radius/decoration_mode/gap_inner/
gap_outer/master_ratio/master_count were all live-settable via srd set
with no way to read the current value back, and pin_input had no
readback at all. SettingsResponse now reports all of them; a new
pinned_inputs query (srd pinned inputs) lists every currently pinned
pid/window.
|
|
Screenshotted the just-split display on request rather than guessing --
it showed why windows never seem to remember placement/size, plus two
real split-screen bugs.
Window memory (WindowManager::remembered_geometry) was correctly wired
on the read side, but the only writes came from end_drag/end_resize in
dragresize.rs - a real drag or resize. A window the user opens, looks
at, and closes without ever touching its edges had nothing recorded, so
reopening it always fell back to a fresh cascade placement, for what is
probably most ordinary window lifecycles. remove_window now also
snapshots geometry (same app_id-non-empty gate the drag/resize sites
use), persisted at both of its wayland-side call sites the same way the
drag/resize-release site already does.
desktop_icon_origins mirrored the full icon set onto every Monitor entry
when general.desktop_icons_all_monitors is on - which, after a
srd.monitor.split, is one entry per split part of the same physical
screen, not one per real monitor. Extracted into a separately-tested
icon_origins_for that collapses split parts of the same connector back
to one origin, keeping a genuinely separate monitor's own origin intact.
Found while fixing that: every split part also reported primary: true
(computed from the connector's name, which doesn't vary per part) --
fixed by gating on part == 0 too.
|
|
Live-tested right after shipping it and caught immediately: srd dispatch
set output split returned ok, but srd monitors kept reporting the whole,
unsplit output. WindowManager::monitors is a passive cache, only
refreshed when a backend re-queries and calls set_monitors again - the
IPC handler mutated the split map directly but never triggered that
requery, unlike set_output_position's own drain site, which already
pushes a "just go recompute" event after applying.
Makes it a proper queued cross-boundary request instead, the same shape
as every other backend-owned effect on this socket: WindowManager::
request_monitor_split/drain_monitor_split_requests, dispatch queues
instead of mutating, the udev backend's poll drains it, applies via
set_monitor_split, and pushes the same recompute event. srd.monitor.
split's Lua config-time path is untouched - it runs before the very
first startup query, so it never had this problem.
|
|
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).
|
|
Standalone (not a workspace member, same reasoning as the existing
tools/toplevel-activate), built to live-verify the Wayland crate's new
pinned-virtual-pointer delivery (crates/wayland/src/virtual_pointer.rs)
against a real client rather than reading the source: prints its own
pid so an external script can pin it via srd dispatch pin input, then
drags from one point to another on a stdin signal.
Not yet run against a live nested instance - launching one hits this
project's own nightshift deny-list guard against a bare nested-compositor
invocation, parked rather than worked around; see docs/TODO.md.
|
|
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.
|
|
The Lua and IPC/CLI surface for the three new core primitives
(crates/core: aspect_ratio rule action, general.phone_mode, virtual-
pointer window pinning):
- srd.rule(..., { aspect_ratio = "9:16" }): parses a "W:H" string into a
validated (u32, u32), rejecting a malformed value as a real Lua error
at config-load time rather than silently ignoring it.
- general.phone_mode config default, plus srd set phone_mode <bool> for
the live equivalent (same shape as animations/shadows/rounded_corners).
- pin_input IPC dispatch ({"cmd":"pin_input","pid":<pid>,"id":<window
id>}, id omitted to unpin) and its CLI surface, srd dispatch pin input
<pid> <window-id> / unpin input <pid>. Keyed by the owning client's
process id, not an opaque per-object id nothing outside the Wayland
backend could ever learn - a controlling tool already knows its own
pid for free.
See docs/TODO.md for the full design reasoning behind each of these.
|
|
Several independent, real pieces landed in crates/core this shift - see
docs/TODO.md for each one's full root-cause/verification narrative:
- "Primary" monitor is now picked by which head sits at physical (0, 0)
(the user's own configured anchor), not whichever connector DRM
happened to probe first - fixes desktop icons and new-window placement
landing on the wrong monitor depending on hotplug/probe order.
- A new window's target monitor now prioritizes the pointer's own current
monitor over the last-focused window's monitor, which goes stale the
moment the user's attention moves to empty desktop, a panel, or a dock.
- aspect_ratio window-rule action ("W:H") plus ResizeEdge::apply_aspect_
ratio: holds a floating window's aspect ratio through an interactive
resize. The real, scoped "phone monitor" primitive - matches any VM/
emulator/scrcpy window by app_id, nothing Android- or VM-specific here.
- general.phone_mode (WindowManager::phone_mode): a new window defaults
to maximized instead of floating/tiled small, unless a rule explicitly
floats it or sets maximized - the one placement default a phone-shaped
screen actually needs. Exposed read-only via IPC so a shell panel can
adapt its own chrome to the same signal.
- input_pin.rs: the core half of pinning a virtual pointer to a specific
window (Multi-cursor Phase 2) - a backend-agnostic request queue,
same cross-boundary shape output_position_requests/lock_requested
already use, since core has no real Wayland protocol object to reach
into itself.
Full workspace test suite covers all of the above (aspect-ratio resize
math for every edge case, phone-mode default-vs-rule-override behavior,
the pin-input request queue, the monitor-picking fixes).
|
|
Closes the one real gap an X11/Wayland feature-parity audit found this
session (desktop icons, window-position memory, and static exclusive-zone
reservation were already shared or Wayland-only by nature - see
docs/TODO.md's own audit entry for the full breakdown).
MenuAction/ContextMenu (row set, labels, row_at hit-testing) move from
crates/wayland/src/context_menu.rs into crates/core/src/context_menu.rs --
pure state and geometry with nothing Wayland-specific in it, so X11
needing the same rows is shared data, not duplicated logic. The Wayland
crate's own context_menu.rs is now a one-line re-export so every existing
crate::context_menu::... call site keeps working unchanged.
X11 has no compositor-level input dispatch to intercept every click the
way Wayland's input/pointer.rs does, so the X11 side
(crates/x11/src/platform/context_menu.rs, new) draws the menu into its own
small override-redirect popup window and grabs the pointer for the
duration so a click anywhere dismisses it, matching the Wayland backend's
own convention. events.rs's ButtonPress handler now reads the real button
number instead of hardcoding every press as a left click - a real latent
bug (right-clicking a titlebar button would have silently performed its
left-click action).
Live-verified end to end in an isolated Xvfb + srdwm --x11 instance: full
row set including the workspace picker, Minimize runs and closes the
menu, a second window's menu dismisses cleanly on outside click, normal
focus/click behaviour continues working afterward.
See docs/TODO.md for the full investigation and verification narrative.
|
|
The Rust rewrite (crates/) has fully superseded it - keeping both around
was actively misleading (main looked like it still shipped a C++ build),
and nothing here still depends on it.
|
|
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.
|
|
Requested directly: default icons "look very rudimentary... make slightly
blue as well, polished look".
Two changes to decoration::render_desktop_icon and its five draw_*_glyph
helpers:
- New fill_rounded_rect primitive: softly rounded corners (the same
clamp-then-distance smoothstep construction rounded_corners_pixman::
apply_corner_mask already established for content masking, not a new
technique) plus a vertical top-to-bottom gradient instead of one flat
fill - the same light-source cue buttons.rs's own glossy_shade uses
for the titlebar dots, as a plain linear gradient here. Applied to each
glyph's main body shape; small details (folder tab edge, computer
stand, trash ridges, home roof) stay flat/sharp.
- A dedicated ICON_COLOR constant (a clean mid-blue) instead of reading
theme.titlebar_fg_focused - that field is whatever the user's own
titlebar accent happens to be configured to, which could be any
colour; these glyphs want a consistent, recognisable blue palette of
their own, independent of theme.
Build/test/clippy already verified clean as part of the layer-shell scale
fix commit just before this one (same source tree, installed together).
|
|
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.
|
|
Reported live: "even where decorations are corner i should still be able
to corner resize, just its... hitbox... does not get in the way of the
close icon" - the titlebar corner holding Close/Maximize/Minimize had no
resize target at all, by design (competing with the close button was
judged worse than losing that one corner). The BUTTON_CLUSTER_MARGIN
strip between the button cluster and the frame's true edge was already
dead space no button claims, regardless of button_count - its own top
DECORATED_TOP_RESIZE_MARGIN rows now register as the diagonal corner
(TopLeft/TopRight) instead of falling through to Top/Drag, without
touching the button's own hitbox at all.
Also requested: widen the other three corners' own resize zone, since a
user reaching for a plain edge-resize instinctively aims for the middle
of that edge, not its corner - a bigger corner zone doesn't compete with
that instinct the way a bigger RESIZE_MARGIN would compete with ordinary
content clicks near an edge. CORNER_MARGIN raised from 3 to 5 (18px to
30px at the default resize_margin).
Updated one existing test whose own per-window resize_margin override
(30px) now put its plain-edge test point inside the widened corner zone
on a window too short for the two to stay apart - taller geometry, same
edge point relative to center, no change to what it actually verifies.
Added coverage for the new button-corner resize target on both sides,
and for the dead strip's own non-corner rows still just dragging as
before.
|
|
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.
|
|
Past clear-color + cursor: a GPU-driven head now renders every
visible window's real content too, via surface_content_elements (the
same generic-over-renderer helper the Pixman path uses, unmodified
against gpu.renderer instead of udev.renderer). Content pushed after
the cursor (so the cursor stays on top), in the same front-to-back
`ids` order the Pixman path's own custom_elements already relies on
for correct occlusion between windows - plain painter's-algorithm
draw order, no separate clip needed since content is window-shaped.
Deliberately the *unrounded* path: no corner masking (that's built
against PixmanRenderer specifically on this backend) and no
decorations (border, titlebar) - a GPU-driven head now shows real
window content, square corners, no chrome. Decorations are the
remaining real gap before this path has parity with the software one.
Per-window geometry/position math (geom from window_anims or
w.geometry, band for a decorated window's titlebar reservation,
content_offset clamped non-negative) mirrors the Pixman path's own
content push exactly, including an earlier double-
subtraction and negative-margin fixes - so a CSD client with a real
shadow margin positions the same way on either render path.
Untested on real GPU-enabled hardware as of this writing: builds,
passes clippy, full test suite green, and matches the existing Pixman
path's geometry logic by inspection, but SRDWM_GPU/general.gpu were
both unset on the machine this was built on - noted honestly in
gpu.rs's own module doc comment, DEFAULTS.md, and
IMPLEMENTATION_STATUS.md.
|
|
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.
|
|
parent
Window::is_dialog (close-button-only titlebar, no traffic lights) was
only ever set from a native xdg_toplevel's own parent() - redraw_
decoration_buffer's is_dialog computation called dw.toplevel(), which
is always None for an XWayland-backed DWindow (X11Surface's own
accessor is x11_surface(), a different method), so the .unwrap_or(false)
fallback made every XWayland dialog - a GTK "Save As", an app's own
"About" box, anything setting the ICCCM transient-for hint - always
draw with the full three-button titlebar and traffic-light colours,
even though the feature this was built for explicitly wanted the
opposite. Documented as a known gap at the time; now closed.
redraw_decoration_buffer now also checks X11Surface::is_transient_for()
for an XWayland window. property_notify gained a WmWindowProperty::
TransientFor arm that re-runs redraw_decoration_buffer, for a client
that sets the hint slightly after its own initial map - the same
"read fresh every call" pattern the existing xdg_toplevel::parent()
check already relied on, extended to catch a late X11 property the way
the Wayland equivalent (set_parent, any time) already was.
|
|
Grepped the whole compositor for a second reader of every documented
general.*/theme.*/layout.*/performance.*/debug.*/platform.* config key
beyond its own seed call in crates/config/src/engine/support.rs.
Several sections turned out to be entirely or mostly decorative --
accepted, stored, sometimes range/type-validated, but never actually
applied to window/render behavior - which this file presented no
differently from the real, working settings around them:
- theme.colors.* (background/foreground/primary/secondary/accent/
error/warning/success): none read past the seed call. The real,
working theme surface is theme.decorations.* below it.
- performance.* and debug.* (vsync/max_fps/window_cache_size/
event_queue_size/layout_timeout/enable_caching, logging/log_level/
profile/trace_events/show_layout_bounds/show_window_geometry): every
key in both namespaces is dead. Real frame pacing comes from the
display's own hardware vsync; RUST_LOG is the real logging control.
- layout.tiling/dynamic/floating.*: only master_ratio is real
(WindowManager::tiling.master_ratio). split_ratio, every behavior.*
table, per-layout gaps.inner/outer, snap_threshold, grid_size,
cascade_offset, smart_placement, default_position, remember_position,
always_on_top are all dead - the real gap setting for every layout
is general.window_gap.
- theme.decorations.border.focused_style/unfocused_style and
title_bar.show/font: dead - solid is the only border style this
compositor draws, and the titlebar always uses whatever system font
find_system_font picks.
- platform.x11.*/platform.wayland.*/platform.windows.*/platform.macos.*
use_*/global_hooks/accessibility_enabled: all dead - EWMH/NetWM/
xdg-shell/layer-shell/DWM/Win32/Cocoa/Core Graphics support is simply
always compiled in, never behind a toggle. platform.backend/
platform.os are the two real, but read-only, keys in this section.
Also: the Environment Variables section listed SRDWM_THEME/
SRDWM_DEBUG_LEVEL/SRDWM_PLATFORM/SRDWM_MAX_FPS/SRDWM_VSYNC, none of
which exist anywhere in the codebase - replaced with the real set
(SRDWM_CONFIG_PATH, SRDWM_STATE_PATH, SRDWM_GPU, RUST_LOG), found by
grepping every std::env::var call in the compositor's own source.
Validation Rules' numeric list was missing corner-radius entirely
(theme.decorations.border.radius, 0-100, real in the validator despite
the doc gap) and resize_margin/inactive_dim, and didn't note that
srd set (unlike the Lua config path) doesn't run these checks at all.
Added general.gpu's own documentation section (new here - see
the matching commit adding the config option itself).
Also updates IMPLEMENTATION_STATUS.md's udev-backend paragraph, which
described the real GBM+EGL+DrmCompositor GPU path as not existing at
all ("that path needs a GPU...") - it now exists, opt-in, with its
current real scope (every head, cursor, no window content yet) noted
in place of the stale absence.
|
|
SRDWM_GPU=1 was the only way to opt into the udev backend's GBM+EGL
GPU render path - an env var, not a real config option, with no way
to enable it from init.lua the way every other general.* flag works.
WindowManager::gpu_enabled (plain bool, false by default - unlike
rounded_corners_enabled's Option<bool>, GPU rendering has one
unambiguous default regardless of which backend ends up connecting,
so there's no "let the backend decide" case to preserve) is read from
general.gpu in apply_general_settings, same as every other general.*
key. gpu::probe now takes an explicit enabled: bool instead of
checking the env var itself; udev/platform.rs's call site computes it
as wm.gpu_enabled OR SRDWM_GPU=1, so the env var still works as a
quick manual override for testing without touching config, on top of
the new persistent option.
Falls back to the existing software (Pixman) path exactly as before
on any failure at any step (no GBM device, no atomic-modesetting
support, a software-only EGL renderer, ...) - gpu::probe's own
fallback behavior is unchanged, only how the initial enabled/disabled
decision gets made.
|
|
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.
|