<feed xmlns='http://www.w3.org/2005/Atom'>
<title>srdwm/crates/core/src/rules.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>2026-05-11T14:57:00+00:00</updated>
<entry>
<title>Fix spawn placement under the top bar, add per-window minimum sizes, and clean up maximize</title>
<updated>2026-05-11T14:57:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-11T14:57:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=646b37e7e3aa5931079c6b9e804f420bb43c74d5'/>
<id>urn:sha1:646b37e7e3aa5931079c6b9e804f420bb43c74d5</id>
<content type='text'>
Four reports after restarting into today's build, with a screenshot. The
screenshot was measured, not eyeballed: top bar y=0..29, a 4px accent border
at y=30..33, titlebar from y=34, bottom border at y=1027..1030, and 49px of
bare desktop below it.

Windows spawning too close to the top bar. A remembered position was
validated only by asking whether it landed on some monitor's full_geometry,
which includes the strip a top bar reserves, so an app whose remembered y was
small reopened with its titlebar under the bar. That is why it was
"sometimes": it depended on the stored value, and the live store holds
wezterm at y=44 and firefox at y=69 against a 30px bar. Remembered positions
are now clamped into the monitor's usable area.

Placement not surviving a logout. Window memory does persist, but five of the
eleven entries in the live store were saved with a second monitor attached,
at x &gt;= 2000. Those points match no current monitor and were discarded
outright, falling back to a fresh cascade, so those apps appeared to remember
nothing. Such a position is now clamped onto a monitor that exists instead.

Per-window minimum sizes. One global floor is wrong in both directions.
Three sources now, in increasing precedence: the global floor, the client's
own declared minimum (xdg_toplevel.set_min_size or ICCCM hints), and a
min_width/min_height window rule overriding both. A rule wins permanently --
the backend refreshes the client's declared minimum on every decoration
redraw and must not undo a deliberate override.

Maximize, three faults in one report. A maximized window now draws no
border: its edges are the screen's edges, and the only place maximize stops
short is the bar strip, which is exactly where the measured line was.
maximize_geometry_for no longer subtracts a bottom-anchored dock's exclusive
zone, so maximize runs to the bottom of the screen and the dock floats over
it; top, left and right are still honoured.
general.maximize_covers_dock = false restores the old behaviour. With the
border gone the window sits flush under the bar instead of with an accent
line crowding it.

Verified: seven new tests on the real numbers from the live store, and
maximize geometry measured live in a nested instance (a window maximized on a
split half reports exactly that half's rect). NOT confirmed on screen: the
border removal and the dock behaviour - the nested backend has no bar or
dock to reserve a zone, and an attempt to check the border produced a failing
control, since srd set border_width only affects windows created after it.

515 tests pass, clippy clean.
</content>
</entry>
<entry>
<title>Core window manager: real fixes plus three new rule/placement primitives</title>
<updated>2025-08-29T20:42:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2025-08-29T20:42:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=be5c4efa6866b5d23e1e81ecfc3b8d276540860f'/>
<id>urn:sha1:be5c4efa6866b5d23e1e81ecfc3b8d276540860f</id>
<content type='text'>
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).
</content>
</entry>
<entry>
<title>Accumulate core-crate additions: capture requests, focus/workspace fixes, test coverage</title>
<updated>2024-11-30T21:03:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-11-30T21:03:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=583063c3094ca0cf9f6cceba92d710b238547f6b'/>
<id>urn:sha1:583063c3094ca0cf9f6cceba92d710b238547f6b</id>
<content type='text'>
Bundles several related changes to crates/core built up over this
session rather than committed incrementally:
- WindowManager::request_capture_workspace/drain_capture_requests (new
  manager/capture.rs) - backend-agnostic queuing for an off-screen
  workspace render, see the wayland-side commit for why this exists.
- focus_window now switches workspace as a side effect when the target
  isn't on the current one, matching Hyprland's focuswindow convention
  (manager/focus.rs).
- Assorted window/rules/theme field additions and their test coverage.

Left less granular than the repo's usual one-purpose-per-commit
convention deliberately: these accumulated across a long session
without being committed as they landed, and are too entangled
line-by-line to safely split apart now without risking mis-attributing
changes to the wrong commit message.
</content>
</entry>
<entry>
<title>Add per-window resize-margin override (Hyprland's extend_border_grab_area)</title>
<updated>2024-08-22T14:06:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-08-22T14:06:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=78da614d67c09e197dffc1cebb1c5ce2357c7128'/>
<id>urn:sha1:78da614d67c09e197dffc1cebb1c5ce2357c7128</id>
<content type='text'>
MISSING.md had listed this as needing to touch srdwm_core::window::
hit_test's signature at every call site, including the honest-stub
Windows/macOS backends - overstated on closer inspection: that shared
function already takes a plain resize_margin: i32 parameter, agnostic
to where the value comes from. The only real change needed was reading
Window.resize_margin.unwrap_or(wm-wide default) instead of always the
WM-wide value, at the single call site inside WindowManager::hit_test
in core - no backend touched at all.

Window.resize_margin: Option&lt;i32&gt;, WindowRuleActions.resize_margin to
match, applied in add_window/reapply_rules_if_pending the same way
opacity already is. Settable via a rule action or
srd.window.set_resize_margin(n) on the focused window.
</content>
</entry>
<entry>
<title>Bypass smithay's Space rendering for window/layer content: real per-window opacity</title>
<updated>2024-06-22T20:34:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-06-22T20:34:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=0818b56dd9c70b1097c5ef24be0f62baaf9ec999'/>
<id>urn:sha1:0818b56dd9c70b1097c5ef24be0f62baaf9ec999</id>
<content type='text'>
The user asked for per-window opacity (MISSING.md's `windowrule = opacity`
gap) and pushed back on treating smithay's convenience wrappers as a hard
ceiling: "don't rely on smithay, it won't have everything we need." Looked
again at why opacity was ruled out earlier - `render_output`/
`space_render_elements` take one `alpha` for the whole frame's `self.space`
content, no per-element control - and found a path that doesn't need
nesting smithay's internal `SpaceRenderElements` type (the approach that
hit an unresolvable generic-bounds wall investigating fullscreen-hiding
earlier): call `render_elements_from_surface_tree` directly,
once per window and once per layer-shell surface, each with its own alpha,
wrapping the result in the *existing* `OverlayElement::Surface` variant.
That primitive was already proven safe in this codebase (cursor.rs's
client-image path, this file's own popup rendering) - reusing it here for
a window's main content is the same call, not a new one.

Both `udev.rs` and `winit.rs`'s render loops now build window content and
layer-shell surfaces themselves (`elements.rs`: `surface_content_elements`,
`output_layer_elements`, `window_wl_surface` for the Wayland/XWayland
split), in the correct front-to-back order, then call
`OutputDamageTracker::render_output` directly instead of the
`space::render_output`/`space_render_elements` convenience wrappers. Content
itself needs no occlusion clipping against `occluders` (unlike border/
titlebar bitmaps) - pushed in the same front-to-back order as everything
else, ordinary painter's-algorithm draw order already occludes it correctly,
the same property it had via `self.space`'s own order before. `self.space`
stays mapped and `resync_stacking_order`-maintained exactly as before; only
the render step stopped reading from it.

A comment in winit.rs's render loop warned that a near-identical earlier
attempt was reverted for a real ordering bug (whichever window was created
first always painted in front, regardless of focus). That bug's actual root
cause, identified and fixed since, was `Space::map_element` silently
re-stacking on every geometry sync, independent of which render path was
used - see `resync_stacking_order`. This rewrite never reads `Space`'s
internal order for rendering at all (`ids` comes from `WindowManager.order`
directly, the same source `hit_test` already trusts), so that specific bug
class can't recur here regardless of whether `resync_stacking_order` ever
drifts again.

Bonus from the same infrastructure: the bar/dock now genuinely don't render
at all (not just get covered) for a fullscreen window - `output_layer_elements`
skips `Layer::Top`/`Overlay` entirely when any visible window is fullscreen,
the hardening this work backed away from earlier for being too risky to
build via the nested-SpaceRenderElements approach. `capture_offscreen`
(winit.rs's screencopy path) picked up opacity-aware content and layer-shell
inclusion too, though not full parity with the on-screen loop (still no
border/shadow strips there - a pre-existing, separately-flagged gap).

Opacity itself: `Window.opacity` (core), `WindowRuleActions.opacity` /
`srd.rule(..., { opacity = 0.9 })`, `srd.window.set_opacity()`. Caught live,
before commit: opacity was wired into `add_window`'s own rule match but not
`reapply_rules_if_pending` - the *only* path a class-based rule actually
takes effect through for a native Wayland client, since `add_window`'s own
attempt always runs against a still-empty `app_id` (see the regression test
next to the existing one covering the identical historical bug for
`decorated`). Found by setting an isolated `SRDWM_CONFIG_PATH` test config
with `srd.rule({ class = "Alacritty" }, { opacity = 0.4 })` against a nested
instance and pixel-sampling a real screenshot: predicted blend (242,230,53)
at 0.4 over (10,10,15) is (103,98,30); measured (105,100,33).

Verified live in a nested session, screenshotting the *host* compositor
(shows the real on-screen render, unlike grim against the nested socket,
which - separately discovered this work - routes through
`capture_offscreen`): stacking order correct with two overlapping windows
(topmost fully occludes the one behind it, the exact scenario the reverted
attempt got wrong), opacity blend matches prediction. Also confirmed, by
testing the previous commit against the same scene, that upside-down
content on this backend is a pre-existing bug unrelated to this change --
noted, not fixed here.

cargo build --workspace (all 9 crates), cargo clippy --workspace (0 new
warnings), cargo test --workspace (197 tests, 0 failed, includes 2 new
regression tests).
</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>Add window rules, real config validation, and a Wayland DRM/udev backend</title>
<updated>2024-04-01T23:07:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-04-01T23:07:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/srdwm/commit/?id=43175c78a5b450eb108738187b72e80d36a7bf5d'/>
<id>urn:sha1:43175c78a5b450eb108738187b72e80d36a7bf5d</id>
<content type='text'>
- srd.rule(): match windows by title/class, apply floating/maximized/
  workspace/geometry/decoration actions on creation (crates/core/src/rules.rs)
- srd.validate_config()/srd.debug.*: real range/format checks and
  status/profiling helpers, replacing the always-true stub
- Wayland titlebar text rendering via fontdue, unit-tested without a
  display (crates/wayland/src/decoration.rs)
- Wayland precise keybinding matching, replacing the "any Super-held key"
  heuristic, sharing the keysym table with X11 (moved to
  crates/core/src/keysyms.rs)
- Wayland DRM/udev backend (crates/wayland/src/udev.rs): runs as the real
  compositor on a bare TTY via libseat/libinput/KMS, software rendering
  via Pixman + dumb buffers (no GBM/EGL required)
- srdwm_platform::detect() fix, found via VM testing: a bare TTY with no
  DISPLAY/WAYLAND_DISPLAY now correctly resolves to Wayland instead of an
  X11 backend that can never work there
- XWayland integration groundwork (crates/wayland/src/xwayland.rs): spawn,
  X11Wm, and full XwmHandler event routing into the same WindowManager/
  Space pipeline as native clients. Windows don't render yet - a real
  glamor-vs-software-renderer conflict in XWayland's own fallback path,
  root-caused via WAYLAND_DEBUG tracing and documented in
  docs/IMPLEMENTATION_STATUS.md rather than worked around blind.

All verified live in an isolated QEMU VM: X11 backend shows two
decorated, correctly-tiled xterms with real title text; the DRM/udev
Wayland backend opens the GPU, initializes input, and scans out a
rendered frame via KMS page-flip.
</content>
</entry>
</feed>
