<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/docs/IMPLEMENTATION_STATUS.md, 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-09-14T21:04:00+00:00</updated>
<entry>
<title>Docs: master TODO/DEFAULTS/status updates, feature-gap survey, lockfile</title>
<updated>2025-09-14T21:04:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-09-14T21:04:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=b46dfa56504f62ecf969914dfed106bf587c4c4a'/>
<id>urn:sha1:b46dfa56504f62ecf969914dfed106bf587c4c4a</id>
<content type='text'>
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).
</content>
</entry>
<entry>
<title>Render real window content on the GPU render path</title>
<updated>2025-08-08T21:28:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-08-08T21:28:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=9e811d2f2f1037ba4ec08ab0e05b350e6cf69e66'/>
<id>urn:sha1:9e811d2f2f1037ba4ec08ab0e05b350e6cf69e66</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Correct docs/DEFAULTS.md against the real config engine, audit-style</title>
<updated>2025-05-29T18:33:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-05-29T18:33:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=49ded5541243408e50a391f95bdca037c370a709'/>
<id>urn:sha1:49ded5541243408e50a391f95bdca037c370a709</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Fix decoration drift during animated transitions; checkpoint IPC/global-menu/output-management work</title>
<updated>2024-05-30T14:10:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-05-30T14:10:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=1c175642d073689ca11b9252411ea5f8446007d0'/>
<id>urn:sha1:1c175642d073689ca11b9252411ea5f8446007d0</id>
<content type='text'>
Border/titlebar decoration was built from Window.geometry (the animation's
final target) in both wayland backends' render loops, while sync_geometry
already draws a window's actual content at window_anims' interpolated rect
during any maximize/fullscreen/open-slide tween. Border and content read two
different rectangles for the whole transition, so the border visibly
detached from the window it was outlining - reported as "borders aren't
flush." Both udev.rs and winit.rs now read the same animated rect for
titlebar placement, border-strip placement, and the occlusion test against
later windows in stacking order. Verified: cargo build --workspace, cargo
clippy (0 new warnings), cargo test -p srdwm-core (111/111).

Also checkpoints substantial protocol/IPC work from prior sessions that had
accumulated uncommitted: gtk-shell1 support (gtk_shell.rs, the vendored
gtk-shell.xml, xwayland.rs's X11-side mirror) backing the global app menu;
zwlr_foreign_toplevel_manager_v1 (foreign_toplevel.rs) broadcasting distinct
maximized/minimized/fullscreen/activated state per window; output_management
(ext-output-management + layer-shell exclusive-zone reservation tracking);
workspace.rs and context_menu.rs; a Unix-socket IPC crate (platform/src/
ipc.rs) and a `srd` control-CLI crate (crates/ctl); xkb_config.rs; and a
theme module (core/src/theme.rs). A peer session working the AGS shell
concurrently verified several of these live against a running srdwm: the
global menu rendering a real app's File/Edit menu over gtk-shell1, and
foreign-toplevel correctly reporting maximized and fullscreen as independent,
non-simultaneous states with the geometry each implies (maximize stops at a
reserved top bar and past a dock; fullscreen reaches the true monitor edge).
</content>
</entry>
<entry>
<title>Config at ~/.config/srd; cursor shapes, key repeat, pin, mouse defaults</title>
<updated>2024-05-29T12:40:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-05-29T12:40:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=3d3057ae384ef7389284af8988410889e99c6bb9'/>
<id>urn:sha1:3d3057ae384ef7389284af8988410889e99c6bb9</id>
<content type='text'>
Config path drops a level: ~/.config/srd, not ~/.config/srdwm/srd, which
said the same thing twice. No other user-facing path had the same problem --
srdwm reads the config dir and writes nothing else.

Cursor shapes. A client's own cursor surface is now rendered with the
hotspot it declared, so an I-beam over text or a hand over a link shows the
app's image instead of srdwm's arrow. The built-in arrow stays as the
fallback when no client has set one, over decorations and the desktop.
Named shapes still fall back to the arrow; most toolkits set a surface.
Decorations and cursors now share one OverlayElement type, since
render_output takes a single custom-element slice.

Key repeat (srd.bind_repeat, Hyprland's binde). Held volume, brightness and
switcher keys repeat at the seat's own rate rather than firing once. Driven
from the poll loop, not a timer source: the winit backend has no calloop
loop of its own, and poll_events already runs continuously in both backends.
Repeat stops when *that* key is released, not when any key is.

Always-on-top / pin, for the picture-in-picture and HUD rules that used it.
Window::always_on_top was another declared-but-never-read field. Enforced in
WindowManager's stacking order rather than at render time, so every consumer
of stacking_order gets it and none can forget to honour it.

Mouse-only window management, checked end to end: drag the titlebar to move,
drag any edge or corner to resize, titlebar buttons to close/maximise/
minimise, click to focus, drag to a screen edge to snap, and now
double-click the titlebar to maximise. The resize grab band went from 6px to
10px - a hairline is genuinely hard to hit with a mouse, which is why
Hyprland ships extend_border_grab_area.

Also removed the emoji status markers from docs/IMPLEMENTATION_STATUS.md.
</content>
</entry>
<entry>
<title>Draw a cursor, handle the lid, and everything a real config needs</title>
<updated>2024-05-13T22:42:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-05-13T22:42:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=dd31bf5ac2a692617d478478d36c733586b93cf8'/>
<id>urn:sha1:dd31bf5ac2a692617d478478d36c733586b93cf8</id>
<content type='text'>
Groundwork for actually daily-driving this: porting the user's Hyprland
config exposed what srdwm couldn't yet express, and testing on a bare TTY
exposed something worse.

A visible mouse cursor. Nothing drew a pointer at all - on a bare TTY the
mouse was simply invisible. It hid because the nested backend runs inside
another compositor, which draws a cursor over srdwm's window; only the DRM
backend, i.e. the actual session path, was affected. A built-in arrow is now
composited above everything on the output the pointer is on. It's a
reviewable ASCII bitmap rather than an XCursor theme: a cursor that is always
present beats a prettier one that sometimes isn't, the same reasoning as
decoration.rs's font fallback. Client-set cursor surfaces and named shapes
are still not rendered, so an app asking for an I-beam gets the arrow.

Lid switch. libinput switch events are handled and surfaced to config as
srd.on("lid_closed"/"lid_open", fn), so closing the lid can lock and suspend
instead of doing nothing.

Config-driven additions, each needed by a binding in the ported config and
none of which existed: fullscreen (Window.fullscreen was a dead field --
declared, never read or written), directional window move that swaps with
the neighbour and reorders the stack so tiling follows, focus cycling,
modifier+drag to move/resize anywhere in a window rather than only by the
titlebar, modifier+scroll to change workspace, and 8 XF86 media/power
keysyms taken from the system's own XF86keysym.h. The keysym tables are
hand-maintained in both directions and a key missing from either fails
silently, so a round-trip test now covers every one the configs bind.

Verified in the QEMU VM: a bare-TTY screendump shows a recognisable arrow at
the pointer position (113 white fill + 58 black outline pixels at screen
centre, where the pointer starts).
</content>
</entry>
<entry>
<title>udev: connector hotplug, and rescue windows on an unplugged monitor</title>
<updated>2024-04-12T20:35:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-04-12T20:35:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=a9dd8a6d4947cb537f7919c5a66ba4624af61800'/>
<id>urn:sha1:a9dd8a6d4947cb537f7919c5a66ba4624af61800</id>
<content type='text'>
Monitors were probed once at startup, so plugging or unplugging one while
srdwm was running went unnoticed. A UdevBackend event source now watches for
the kernel's `change` uevent and reconciles the head list against a fresh
connector probe - forcing a re-probe rather than trusting cached status,
since on a hotplug the cache is exactly what has gone stale.

Removing a head tears down everything it owned: the wl_output global, its
place in the Space, its DRM framebuffers and dumb buffers (dropping the Rust
structs alone leaks the kernel-side objects, which matters when a cable is
plugged repeatedly), and any lock surface for it - otherwise
confirm_lock_if_presented would wait forever on a monitor that no longer
exists. New connectors go through the same bring_up_head path as startup, so
a monitor plugged in later is set up identically to one present at boot.
Heads are then repositioned left-to-right, since removing one shifts the
rest, and layer maps re-arranged so bars follow their moved output.

set_monitors rehomes windows stranded by the change, and main.rs re-queries
the whole monitor list on MonitorAdded/MonitorRemoved rather than applying
the single monitor in the event, because the others' positions move too.

The rehoming had a bug that unit tests missed and live testing caught.
It originally keyed off Window::monitor, but that field records the monitor
a window was *assigned* at creation, not where it is: add_window always sets
it from the primary monitor, so a window placed on the second monitor by a
rule - or dragged there - still reads monitor == 0. The field-only check
saw a valid id, skipped the window, and left it at coordinates that no
longer existed: invisible and unreachable. Found by unplugging a monitor out
from under a real xterm and watching it vanish from both heads. It now keys
off geometry, with a regression test that fails against the old logic.

Verified in the QEMU VM, booting with one connector and toggling the second
at runtime: plug in -&gt; head added and rendering at its own resolution;
unplug -&gt; head removed cleanly; and an xterm at global x=1500 survived its
monitor being unplugged, reappearing at x=680 (= min(1500, 1280-600)) with
its size intact. Writing to /sys/class/drm/&lt;connector&gt;/status changes the
connector but emits no uevent on this kernel, so the signal the kernel would
send is synthesized with `udevadm trigger`; the whole reaction path is
genuinely exercised.
</content>
</entry>
<entry>
<title>Wayland: clipboard, session lock, screencopy, multi-monitor; modularize</title>
<updated>2024-04-08T21:54:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-04-08T21:54:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=709333908d5b7d3157165d1811c8bc1795ab0028'/>
<id>urn:sha1:709333908d5b7d3157165d1811c8bc1795ab0028</id>
<content type='text'>
Implements the protocols that were blocking srdwm-wayland from being a real
session, plus multi-monitor, and splits the backend into modules. Everything
here was verified by running it against real clients, not just compiled.

Protocols
- wlr-layer-shell + xdg-output: bars/launchers/notifications. xdg-output is
  not optional in practice - without it wofi segfaults rather than
  degrading, since it calls get_xdg_output without null-checking.
- Clipboard: wl_data_device_manager, primary selection, and wlr-data-control.
  Data-control is what `wl-paste --watch cliphist store` needs, as it reads
  the selection without holding focus. Selection focus now follows keyboard
  focus, without which a focused window can neither copy nor paste.
- ext-session-lock: `locked` gates rendering and input. No key is treated as
  a WM binding while locked - the config binds Mod4+Return to spawn a
  terminal, so honouring bindings at a locked screen would defeat the lock.
  The lock is confirmed only after a client-content-free frame has actually
  been presented, never at request time.
- wlr-screencopy (hand-written; smithay ships no helper) for grim/slurp.

Multi-monitor
Every connected connector becomes a head with its own buffers, damage
tracker and page-flip state, matched by CRTC so differing refresh rates
don't gate each other. Outputs are reached through primary_output/output_at/
output_for_wl rather than a single field, which kept the change to ~11 call
sites. Session lock creates one lock surface per output and waits for all of
them, so a second monitor can't still show the desktop when the locker is
told the session is safe. Modes are picked by the PREFERRED flag, not list
order, and CRTCs are never double-assigned.

Bugs found by testing, not review
- Nothing gave a newly-created window Wayland focus: a freshly-opened app
  received no keystrokes and could not paste until clicked.
- Opening a window at a locked screen stole keyboard focus. Caught by
  counting wl_keyboard.enter delivered to a client launched while locked:
  1 before the guard, 0 after. A killed locker correctly leaves it locked.
- Reading back the winit EGL window surface destroyed the GL context on the
  first screencopy capture, taking the compositor down. Root-caused by
  A/B-ing the same build with only the readback removed; capture now renders
  an offscreen pass.
- Output mode was resent every frame at 60Hz, flooding any client bound to
  wl_output with duplicate mode/done events.
- general.default_layout was defaulted and validated but never read, so
  setting it did nothing. srdwm is dynamic-first (Windows/macOS style, with
  drag-to-edge snapping); tiling is one opt-in layout, and that stays true.
- .gitignore's unanchored `srdwm` matched any path component of that name,
  silently excluding the whole crates/srdwm source crate - the binary crate
  the workspace lists as a member, so a fresh clone could not build.

Modularization
lib.rs went from ~1260 lines to 78: state, protocols, input, lock,
screencopy, winit and udev now each own one responsibility. lock is grouped
by feature rather than kind on purpose, since its security invariant spans
state, protocol handling and rendering at once.

Multi-monitor was verified in the QEMU VM with a two-output virtio-gpu: both
heads screendumped at their own resolution showing srdwm's clear colour, and
a window forced to global x=1500 landed on head 1 at head-local x=220 while
head 0 stayed empty. Known limitation, now documented: the nested winit
backend stalls while its window is occluded, because the host stops
scheduling frames and eglSwapBuffers blocks.
</content>
</entry>
<entry>
<title>Track the daily-driver blockers for srdwm-wayland as TODOs</title>
<updated>2024-04-05T14:24:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-04-05T14:24:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=592bd3716c0aa5a47a5eb43f45f55844de9db382'/>
<id>urn:sha1:592bd3716c0aa5a47a5eb43f45f55844de9db382</id>
<content type='text'>
layer-shell, clipboard (wl_data_device_manager), session-lock, and
udev-backend multi-monitor support are the gaps identified when deciding
srdwm isn't ready to add to a session picker yet - recorded in
docs/IMPLEMENTATION_STATUS.md's "Not implemented anywhere yet" section
and as inline TODOs at the relevant code (CompState's delegate_*! list,
udev.rs's find_connected_output).
</content>
</entry>
<entry>
<title>Fix XWayland: it now actually renders and receives keyboard input</title>
<updated>2024-04-04T12:18:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-04-04T12:18:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=2083ca7c88bc3e4b52ec68af384f334be1da2141'/>
<id>urn:sha1:2083ca7c88bc3e4b52ec68af384f334be1da2141</id>
<content type='text'>
Three real bugs found and fixed via WAYLAND_DEBUG=1 protocol tracing in
the QEMU VM, plus a fourth found along the way:

- XWayland tried glamor (GBM rendering) first, which fails against this
  deliberately software-only compositor; its post-failure fallback path
  never used the xwayland_shell_v1 protocol at all, so X11Surface::
  wl_surface() never resolved. Fixed by shadowing `Xwayland` on PATH with
  a wrapper script that always re-execs it with -shm (smithay's
  XWayland::spawn hardcodes its own argv and can't be bypassed either,
  since XWaylandClientData's fields are private).
- Even with -shm, set_mapped(true) was only called after wl_surface()
  already resolved, deadlocking XWayland (it never advances a window past
  surface creation until the map is granted). Fixed by calling
  set_mapped(true) unconditionally in map_window_request.
- The window then rendered as a ~1px sliver: initial geometry was seeded
  from X11Surface::geometry(), which can still be a tiny default at
  MapRequest time. Fixed by using the same 800x600 default the xdg-shell
  path already uses.
- Typing didn't reach the window until a broader, XWayland-independent
  bug was fixed: nothing in the Wayland backend ever called
  KeyboardHandle::set_focus, so no window (native or X11) could ever
  receive keyboard input. Fixed in handle_pointer_button, along with
  TitlebarHit::Close being X11-surface-blind.

Verified live: xterm launched via XWayland renders correctly sized and
decorated, and a synthetic keypress sequence (ls + Enter) executed in its
shell, screendump-confirmed.
</content>
</entry>
</feed>
