<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/crates/wayland/src/udev/session.rs, branch main</title>
<subtitle>Cross-platform window manager written in Rust.
</subtitle>
<id>https://srdusr.com/git/srdwm/atom?h=main</id>
<link rel='self' href='https://srdusr.com/git/srdwm/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/'/>
<updated>2025-10-03T20:59:00+00:00</updated>
<entry>
<title>Make the secondary-cursor sprite opt-in and expire stale entries</title>
<updated>2025-10-03T20:59:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-10-03T20:59:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704'/>
<id>urn:sha1:4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704</id>
<content type='text'>
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 &lt;bool&gt;`) 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.
</content>
</entry>
<entry>
<title>Wayland backend: desktop icons v2, menu rebuild, layer-shell/scale fixes,</title>
<updated>2025-09-10T09:24:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-09-10T09:24:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=5ee627928d0cb017e59d796c71793c35d5b65d33'/>
<id>urn:sha1:5ee627928d0cb017e59d796c71793c35d5b65d33</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Extend GPU rendering to every head, not just the first</title>
<updated>2025-05-15T18:45:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-05-15T18:45:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=54195f079810410eb9456348e7e44d382d3c4d3f'/>
<id>urn:sha1:54195f079810410eb9456348e7e44d382d3c4d3f</id>
<content type='text'>
Phase 2 of the GPU-rendering plan deliberately targeted a single head
(GpuContext::output: Option&lt;(crtc::Handle, GpuOutput)&gt;) as a narrow
proof that GBM+EGL+DrmCompositor rendering works at all on this
hardware. DrmOutputManager already supports driving several crtcs at
once - initialize_output is a per-crtc call on one shared manager,
the same way anvil drives multiple outputs - so this was purely an
unexercised restriction, not an architectural limit.

GpuContext::output is now GpuContext::outputs: Vec&lt;(crtc::Handle,
GpuOutput)&gt;, and udev/platform.rs calls initialize_output for every
connected head in its own bring-up loop instead of only the first
after that loop finishes. A head this fails for individually (already
logged, not fatal) still just has no entry and falls back to the
existing legacy Pixman path, unchanged from before.

session.rs's VBlank handler and its VT-switch resume path (which
excludes GPU-driven crtcs from the legacy set_crtc reassert loop, a
different device fd that must never issue mode-set commands against a
crtc DrmOutputManager already owns) both now look a crtc up in the
Vec instead of comparing against a single stored one.

render.rs's own render-loop lookup uses direct field access
(gpu.outputs.iter_mut().find(...)) rather than an equivalent
&amp;mut self method: the borrow checker treats a method call as
borrowing all of GpuContext, including gpu.renderer needed a few
lines later for the same head, where direct field access lets it see
the two borrows are disjoint.

Still gated behind SRDWM_GPU=1 (unset by default) and untested on
real multi-monitor hardware with the flag on - this machine has one
display, so the actual multi-head path itself only gets exercised
whenever it's set on hardware that has more than one.
</content>
</entry>
<entry>
<title>Wire VT-switch pause/activate for the GPU render path</title>
<updated>2025-04-02T21:40:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-04-02T21:40:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=4d61d76b1b73f56fc14ed977806e4d4ad313fd51'/>
<id>urn:sha1:4d61d76b1b73f56fc14ed977806e4d4ad313fd51</id>
<content type='text'>
Phase 2 (0274273) deliberately shipped without VT-switch support for
the GPU-driven head, documented as an explicit gap rather than a
silent risk. Before testing it live, wire the real fix instead of
finding out empirically - this already burned three real
reboots getting the *legacy* VT-switch path right, and DrmOutputManager
uses a genuinely different API surface (pause()/activate(), calling
through to DrmDevice's own master-lock acquire/release) than the
manual set_crtc+DPMS reassertion register_session_notifier already
does for legacy heads.

PauseSession now also calls DrmOutputManager::pause() when SRDWM_GPU=1
and a GPU context exists - a separate device/fd from the legacy Card,
so purely additive. ActivateSession calls DrmOutputManager::activate
(false), then deliberately does *not* also force a fresh render for
that head specifically: DrmCompositor::render_frame always issues a
full state commit (atomic or legacy, whichever this device negotiated
- see DrmDevice::is_atomic()), not just a buffer swap, so the
existing data.render_udev_frame() call at the end of this handler
already reasserts mode-set and CRTC-active state together for the GPU
head via render.rs's own GPU branch, the same way it always does.

Also fixed a real conflict Phase 2 introduced: the existing legacy
crtc-reassert loop (explicit set_crtc through the legacy Card/fd) used
to run for every head unconditionally, including one now driven by the
GPU path through a completely different DrmDeviceFd - two separate
fds issuing mode-set commands against the same physical CRTC, exactly
the kind of conflict that produced the worst VT-switch
incidents (EBUSY loops) when it was really one fd racing itself. The
GPU-owned crtc (if any) is now excluded from that loop.
</content>
</entry>
<entry>
<title>Wire real GBM+EGL+DrmCompositor rendering for one head (GPU Phase 2)</title>
<updated>2025-03-25T22:26:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-03-25T22:26:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=0d3c7ab727b321a36ffea66c461221b14bbc48d8'/>
<id>urn:sha1:0d3c7ab727b321a36ffea66c461221b14bbc48d8</id>
<content type='text'>
Extends the SRDWM_GPU=1 opt-in path (Phase 1, fa0c7f1) from a
capability probe into an actual, working GPU render pipeline for
exactly one head, per the plan this was built from
the plan file.

gpu.rs: probe now goes all the way through EGLContext, GlesRenderer,
DrmDevice (real DRM device, separate duped fd from the existing legacy
Card), and DrmOutputManager construction, returning both a GpuContext
and its DrmDeviceNotifier on success. GpuContext::initialize_output
drives one crtc/mode/connector through DrmOutputManager, storing the
resulting GpuOutput for the render loop to find.

Confirmed while reading smithay's own source directly (not assumed):
DrmDevice::new's disable_connectors parameter is not an atomic-vs-
legacy switch - DrmDevice::create_internal tries atomic capability
first and falls back to a Legacy internal variant automatically,
exposed via DrmDevice::is_atomic(), logged here rather than assumed.

platform.rs: probes at startup, calls initialize_output for the first
head only (Phase 2's deliberate scope - see the plan), registers the
DrmDeviceNotifier as its own calloop event source alongside (not
replacing) the existing legacy DRM-fd registration.

render.rs: render_udev_frame's per-head loop checks, before any of the
existing Pixman-specific element-building logic runs, whether this
head's crtc matches the GPU context's initialized output; if so,
renders a plain clear color through render_frame/queue_frame and
continues to the next head, completely bypassing the Pixman path for
that head. Every other head, and this same head whenever the GPU
context or its output is absent, is entirely unaffected.

session.rs: register_gpu_drm_notifier handles DrmEvent::VBlank by
calling frame_submitted() on the matching GpuOutput (required per
queue_frame's own doc comment, or the swapchain runs out of buffers)
and logs DrmEvent::Error without treating it as fatal.

Deliberately out of scope for this phase (documented in the plan):
window content/decorations/cursor on the GPU head (clear color only),
multi-monitor GPU rendering (one head only), and VT-switch pause/
resume for the GPU head specifically (DrmOutputManager's own pause()/
activate() calls are a different API surface from the
existing manual set_crtc+DPMS reassertion, and porting that pairing
correctly needs its own isolated verification pass).

SRDWM_GPU unset (the default) is unaffected: every step above only
runs when it's set to "1", and every failure at any step falls back
to the existing, untouched Pixman path with a logged reason, same
fallback contract Phase 1 already established.
</content>
</entry>
<entry>
<title>Fix total input death after VT switch back (libinput never resumed)</title>
<updated>2025-03-06T21:13:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-03-06T21:13:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=4c97f8647b9543abefbed3ece209d335de6aa9d6'/>
<id>urn:sha1:4c97f8647b9543abefbed3ece209d335de6aa9d6</id>
<content type='text'>
The kernel revokes every input device fd across a VT switch away.
libinput has a documented pair of calls for this exact case,
suspend()/resume() (libinput_suspend/libinput_resume), which reopen
every device through the session once it is reactivated. This
codebase never called either one, so after switching back to the
compositor's VT, libinput's device list stayed pointed at fds the
kernel had already revoked - reads on them don't error, they just
silently stop producing events, forever. Rendering, DRM/KMS, and
libseat's own session activation all recovered on their own, which is
what made this look like a display bug rather than an input one; it
took three real forced reboots today, with no visible input from
keyboard or mouse for 30+ minutes after switching back to tty1 each
time, to isolate it as this specific missing call.

register_libinput now returns the Libinput context (a clone of the
one already handed to LibinputInputBackend - it's a reference-counted
handle, not a deep copy, and LibinputInputBackend only exposes an
immutable accessor once it's moved into the calloop event source).
register_session_notifier takes that handle and calls suspend() on
PauseSession, resume() on ActivateSession.
</content>
</entry>
<entry>
<title>Fix VT-switch DPMS blank-screen and CSD corner-crop staircase bugs</title>
<updated>2025-03-02T23:47:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-03-02T23:47:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=f83082a3b9b75e6f1bc4187850bea1b7408cffca'/>
<id>urn:sha1:f83082a3b9b75e6f1bc4187850bea1b7408cffca</id>
<content type='text'>
VT-switch resume: reasserting the CRTC's mode-setting state was never
enough on its own - display power (DPMS) is separate KMS state, and
nothing here ever touched it after a real switch-away-and-back. Page
flips kept succeeding with zero errors logged for the rest of a real
30+ minute session while the panel itself simply stayed dark, which is
what actually explains a user report of the screen and keyboard input
never recovering after one VT switch. Sets DPMS-on unconditionally on
every resume now, the same property zwlr_output_power_v1 already
writes for an explicit client request.

Corner rendering: both backends' side-strip crop (the fix that keeps a
window's flat left/right border strips from poking a solid-coloured
square through the top/bottom strip's own rounded curve) only ever
activated for `w.decorated` windows. An undecorated/CSD window's crop
depends on its *content* actually getting masked to match - the
winit backend never checked that at all, so every CSD window with a
nonzero border_width got the uncropped, "staircase" artifact
unconditionally, confirmed live via a highlighted border colour and
raw pixel sampling. Ported the udev backend's own `border_curve_is_safe`
check (decorated OR content-will-be-masked) into winit, using the
cheap "does this surface have subsurface children" test both content-
masking code paths already gate success on, rather than duplicating
either one's real (comparatively expensive) rendering work just to
probe it.

Full workspace build + clippy -D warnings + test suite (378 tests) green.
</content>
</entry>
<entry>
<title>Merge branch 'main' into rust-rewrite</title>
<updated>2025-02-17T20:04:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-02-17T20:04:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=78d889c48e5b1336c73f28a2e2d5c8a571c7dd43'/>
<id>urn:sha1:78d889c48e5b1336c73f28a2e2d5c8a571c7dd43</id>
<content type='text'>
# Conflicts:
#	crates/config/src/engine/general.rs
#	crates/wayland/src/udev/drm.rs
#	crates/wayland/src/udev/mod.rs
#	crates/wayland/src/udev/render.rs
#	crates/wayland/src/winit/render.rs
</content>
</entry>
<entry>
<title>Checkpoint: today's fixes before reconciling with the rust-rewrite worktree</title>
<updated>2025-02-17T19:12:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-02-17T19:12:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=f7c6e9c607742ed373752aadf5761e99ec3eb64a'/>
<id>urn:sha1:f7c6e9c607742ed373752aadf5761e99ec3eb64a</id>
<content type='text'>
Fixes a live-reproduced VT-switch busy loop (failed page_flip retried
with no backoff), shadow rendering bleeding onto occluding windows
unclipped, and a winit-backend buffer-age correctness bug that left
stale cross-window pixels on screen. Committing before merging in the
much larger uncommitted rust-rewrite worktree, which independently
touches several of the same files - this is the pre-merge baseline to
diff against, not a claim that these are the final versions of these
fixes.
</content>
</entry>
<entry>
<title>Checkpoint: preserve all uncommitted rust-rewrite worktree work</title>
<updated>2025-02-15T12:56:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-02-15T12:56:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd'/>
<id>urn:sha1:0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd</id>
<content type='text'>
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.
</content>
</entry>
</feed>
