| Age | Commit message (Collapse) | Author | Files | Lines |
|
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.
|
|
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.
|
|
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: 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.
|
|
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.
|
|
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.
|
|
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.
|
|
CLI verbs
Bundles related IPC-surface and CLI additions built up over this
session:
- capture_workspace IPC command + srd capture workspace CLI verb (see
the wayland-crate commit for the off-screen render this drives).
- Expanded IPC response payloads (monitors, client events, global menu
info) and their matching CLI plumbing.
Same reasoning as the core-crate sweep commit for why this is coarser
than the repo's usual convention: too entangled to split safely without
a dedicated review pass.
|
|
Exposes each window's application menu (Firefox/GTK's dbusmenu export,
X11's _GTK_APPLICATION_OBJECT_PATH-style menus via global_menu.rs) so
an external panel can render it as a system menu bar rather than each
window drawing its own, the same convention appmenu.rs/gtk_shell.rs
and appmenu_registrar.rs wire up across both backends.
|
|
A real ext-session-lock-v1 implementation drawn by the compositor
itself, not a hand-off to an external locker process: a live-blurred
background, PAM authentication on a background thread, and its own
config surface (lock_config.rs) rather than hardcoded appearance.
|
|
Same shape as the existing toggle_visibility/focus/close dispatch
actions - lets an external script (or a live diagnostic check)
drive either without a keybinding already existing to trigger it.
Immediately useful for verifying the maximize/fullscreen contract on
a live session without synthesizing any input at all.
|
|
A peer session pointed out srd clients reports a decorated window's
rect 30px taller than X11 reports the client's own content window --
correct (Window.geometry is the frame rect, TITLEBAR_HEIGHT included
on top, the same convention hit-testing/rendering already use
internally), but genuinely undocumented from an external IPC
consumer's point of view. Made the frame-vs-content distinction
explicit on the field itself rather than leaving it to be reverse-
engineered from a pixel diff.
|
|
~17 comments across the codebase still pointed at udev.rs/winit.rs/
state.rs by their old flat-file names after those became udev/,
winit/, state/ directories - found while auditing what this work
rushed, since the split verification (function/struct-name diffing,
full test suite) checked structural correctness but never comment
accuracy. Updated each to either the specific new file (e.g. "see
state.rs's REPEAT_DELAY" -> "see state/mod.rs's REPEAT_DELAY",
"udev.rs's monitors()" -> "udev/platform.rs's monitors()") or the bare
module name where the reference was already generic ("the udev/winit
backends", not a specific location).
|
|
- ipc.rs: drop a u64 -> u64 no-op cast (WindowId is a plain u64 alias).
- decoration.rs: draw_line took 8 args; bundle the two endpoints into
(i32, i32) tuples instead of four loose coordinates.
- output_management.rs: collapse a WEnum::Value if-let directly into
the outer SetTransform match arm's pattern.
|
|
IPC/global-menu/output-management work
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).
|
|
- 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.
|
|
The C++ prototype (moved to legacy-cpp/) was mostly a design skeleton:
X11 and Windows backends were partially real, Wayland created the
wlroots object graph but never wired a single event listener, macOS
was stub except monitor enumeration, and the Lua engine's srd.bind()
stored a key-combo string but never the actual closure. See
docs/PRIOR_ART.md for the full audit.
This replaces it with a Cargo workspace:
- srdwm-core: platform-independent window/workspace/monitor state,
a real master-stack tiling layout, and SmartPlacement grid/cascade/
snap-to-edge placement - fixing several bugs in the C++ version
(hardcoded 2-column grid, cascade that never cascaded, snap-to-edge
that always returned a fixed rect). 35 unit tests.
- srdwm-config: the srd Lua API via mlua, implementing the surface
docs/DEFAULTS.md always documented but the C++ engine never actually
built (srd.window.close()/focus(direction), srd.workspace.next(),
real keybinding closures, require("srd") support). 10 unit tests.
- srdwm-x11: a real reparenting WM with a drawn title bar (buttons,
drag, resize), verified live under Xephyr - frame placement and
client offset match srdwm-core's computed geometry exactly, and the
decoration renders correctly on screen.
- srdwm-wayland: a from-scratch smithay compositor (the C++ version
had nothing working to port from) - runs via the winit backend,
tracks xdg-shell toplevels through the same WindowManager and
hit-testing code X11 uses, verified to start/render/run without
crashing. Decorations are solid-color (no text yet); see
docs/IMPLEMENTATION_STATUS.md for exact scope.
- srdwm-windows / srdwm-macos: structured, cfg-gated designs informed
by komorebi/glazewm and yabai/AeroSpace respectively (see
docs/PRIOR_ART.md), honestly marked as unbuilt/unverified since this
sandbox has no Windows or macOS target.
|