srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates
AgeCommit message (Collapse)AuthorFilesLines
2025-03-06Fix total input death after VT switch back (libinput never resumed)srdusr2-5/+54
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.
2025-03-03Fix VT-switch DPMS blank-screen and CSD corner-crop staircase bugssrdusr2-7/+75
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.
2025-02-21Remove resolved corner-rounding diagnosticssrdusr2-54/+0
The top-rounds-but-bottom-doesn't investigation these were tracking is closed: live-verified via a real screenshot (pixel-level, not eyeballed) that both corners round correctly on both a decorated window and an undecorated/CSD one relying on content masking. Removes three log::debug! blocks (corner-mask state, TOP/BOTTOM border strip position dumps, and a raw alpha-byte dump of the bottom border buffer) that were firing on every single render pass regardless of whether anything changed, adding real per-frame overhead for output no longer needed. Build + clippy + test (378 passing) all still green.
2025-02-18Fix clippy warnings surfaced by the mergesrdusr8-11/+19
Pre-existing issues in the uncommitted rust-rewrite work (unused imports, over-arity glyph-drawing functions, a couple of complex inline types, or_insert_with(T::default) instead of or_default(), and one unsimplified test-only arithmetic expression), plus two imports left unused by switching to or_default(). None of these are behavior changes. Full workspace build + clippy -D warnings + test suite (378 tests) all green after this.
2025-02-17Merge branch 'main' into rust-rewritesrdusr6-26/+161
# 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
2025-02-17Checkpoint: today's fixes before reconciling with the rust-rewrite worktreesrdusr6-23/+163
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.
2025-02-15Checkpoint: preserve all uncommitted rust-rewrite worktree worksrdusr77-3158/+10407
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.
2025-02-14Fix workspace switches undoing themselves within millisecondssrdusr2-19/+32
sync()'s per-tick platform.focus(id) re-assertion (added to keep real Wayland/X11 keyboard focus following core's own bookkeeping) ran unconditionally on every dirty tick, including when nothing about focus had actually changed. focus_window (core) has its own, separate side effect of switching to the focused window's workspace when it differs from the current one - correct when focus genuinely moves to a window on another workspace, but this call was never gated on focus having changed at all: switching workspace via activate_workspace left the still-focused window's own workspace field untouched, so the very next dirty tick's blind re-assertion of that same focus saw a mismatch against the just-changed current_workspace and switched straight back. Confirmed live via temporary core-side logging: two switch_workspace calls a few milliseconds apart, the second one undoing the first every single time, for every workspace switch that didn't also change which window was focused. Gated the re-assertion on the focused id actually changing since the last sync() call. Real focus-follows-real-platform-focus still happens on every genuine change, which is all the original fix needed.
2025-02-13Implement the Wayland implicit pointer grabsrdusr4-21/+76
Every pointer motion event re-ran the same popup/layer/content hit-test from scratch and delivered focus to whatever it found right now - there was no notion of "a button is held, keep delivering to the surface that received the press" at all, which is standard, expected Wayland compositor behavior (every real compositor does this; it's how dragging, text selection, and scrollbar-thumb dragging all stay coherent even when the pointer briefly leaves the widget's bounds mid-gesture). Without it, a real human's hand drifting even slightly outside the pressed surface mid-drag - trivially easy during a fast, non-perfectly- straight mouse motion - sent that client an unrequested `leave` event in the middle of its own gesture. GTK's drag recognizers (a GtkHeaderBar's move-the-window gesture, concretely) treat a mid-gesture leave as "this isn't coherent, abort," which reads as "dragging this window by its title bar does nothing at all" - live-reproduced this work on Nemo, and consistent with move_request never having fired once all session despite real attempts. pointer_button_grab captures the (surface, origin) resolved on a button press once the held-button count goes from 0 to 1, and every event under the grab - motion or button, this press's or a later one overlapping it - is delivered there instead of wherever a fresh hit-test lands, until every held button is back up.
2025-02-11Add temporary diagnostics for two live-reproduced bugssrdusr2-3/+20
1. A popup's xdg_popup.grab (Firefox's own right-click menu, concretely) receiving zero pointer input at all - no hover highlight, no click effect, not even dismiss-on-miss - logs whether grab_popup actually succeeds, since a silent failure there would explain exactly this. 2. srd dispatch activate_workspace returning {"ok":true} without ever changing the current workspace, confirmed via a raw socket request bypassing the CLI entirely. switch_workspace's own logic reads correct; logs its actual inputs/state to find out why the real process disagrees with it. Remove once both are resolved.
2025-02-10Reap spawned child processes instead of leaking zombiessrdusr1-0/+16
Every srd.spawn/Command::spawn call fires and forgets its Child handle by design (a compositor's main loop can't block waiting on an arbitrary launched command), but nothing else was reaping them either, so every one that exited stayed a zombie for the rest of the session. Confirmed live via an AGS peer session's own ps: six zombies from four different programs, spread across half an hour of ordinary use. Explicitly ignoring SIGCHLD is the standard fix for exactly this case -- the kernel reaps exited children itself, no waitpid loop needed.
2025-02-09Fix dropdown/context-menu popups positioned wrong for CSD windowssrdusr1-1/+16
popup_targets (used for both drawing and hit-testing xdg_popup surfaces) never subtracted the xdg_surface::set_window_geometry content offset the rest of the codebase already accounts for - a CSD window's popups were placed relative to its raw, unshifted buffer origin instead of its real visible content, self-consistently wrong in both rendering and click routing.
2025-02-06Fix layer surfaces spuriously hiding/re-showing on their own realizationsrdusr6-27/+53
sync_layer_visibility could not tell a real hide (null-buffer commit on an already-visible surface) apart from a layer-shell client's ordinary realization sequence (commit with no buffer -> configure -> ack-commit with no buffer again -> attach real content): both look like "committed, no buffer" from has_buffer alone. Every layer surface's first realization was spuriously unmapped and immediately remapped, doubling LayerMap arrange() passes on every single popup open. Live-reproduced via an AGS peer session: a full-monitor click-outside-to- close popup surface came back from a hit-test with geometry wider than the real output after several open/close cycles on a wl_surface GTK had reused across role destroy/recreate, and sat in the Top layer above every real window with no input region set - silently swallowing clicks meant for windows, dropdowns, and CSD title bars alike. layer_surfaces_shown_once now gates the hide path on a surface having actually shown a buffer at least once, and is cleared in layer_destroyed so a reused wl_surface's next role starts clean rather than inheriting the previous role's flag.
2025-02-05Add temporary tracing to layer-surface hit-testing for a live dock-input reportsrdusr1-0/+23
Logs the layer kind, namespace, arranged geometry, surface-local hit point, and the surface's actual committed input region for every candidate the hit-test walks. Answers two open questions from a live report of a dock receiving zero pointer input despite a correctly-set input region as measured from the client side: whether arrange() is giving the surface its full requested size or clamping it to the exclusive zone, and whether this compositor's own view of the committed input region actually matches what the client set. Remove once that's settled.
2025-02-04Fix layer-surface hit-testing giving up after the topmost bbox match failssrdusr1-4/+25
layer_surface_under_layers used smithay's LayerMap::layer_under(), which returns only the single topmost surface (by z-order) whose *bounding box* contains the point - not its real input region. If that one surface's input region excluded the point, the old code gave up on the whole layer-kind instead of falling through to whatever real surface is stacked underneath it. Concretely: any other surface on the same layer-kind with a bbox overlapping the target - a mapped-but-mostly-transparent backdrop/dismiss popup, concretely - would silently swallow every hover and click meant for whatever's underneath, with no way to reach it at all. Same failure shape as an already-fixed AGS-side bug (Overview's own bbox-wide input-region fallback), just compositor-side and not limited to that one instance. Now walks every candidate on a layer-kind topmost-first and tries the next one down when a candidate's actual input region doesn't cover the point, instead of stopping at the first bounding-box match.
2025-02-04Fix CSD windows rendering with a wallpaper-visible gap at their cornersrdusr4-4/+57
srdwm never read xdg_surface.set_window_geometry anywhere. A CSD client (GTK4/Firefox) declares its real visible content as a sub-rect inset within a larger buffer that also reserves an invisible shadow margin - that margin stays reserved in the buffer even once the tiled-state hint tells the client to stop drawing the shadow itself. Every render path was positioning content at the client's raw buffer origin instead of subtracting that declared offset, leaving the margin's width/height as a gap with wallpaper visible through it at the window's top-left corner. Confirmed by pixel-diffing the gap against the real wallpaper at that exact screen position: an exact match, ruling out "just the client's own dark theme." Fixed in three places that have to move together: udev/render.rs and winit/render.rs's content-positioning code, and sync_geometry's space.map_element call. That last one matters as much as the other two - it's what smithay's Space (and therefore click hit-testing) reads, so a render-only fix would have traded a visible gap for an invisible, same-size hit-test offset in the other direction. Also applied to the new capture-workspace off-screen render for the same reason. This likely also explains real dropdown/context-menu misplacement (and the clicks landing on the wrong spot) in CSD apps: popup positioning anchors against the same window position this fix corrects.
2025-02-03Fix focusing a minimized window not restoring itsrdusr2-0/+37
focus_window marked the target focused without clearing minimized, so a dock icon's Activate (or the plain "focus" IPC command) on a minimized window left it focused but still excluded from visible_windows/rendering - reads exactly like the click did nothing, since the window never actually reappears. Every focus_window caller gets the restore for free now. Added a Super+n keybind for minimize too, since none existed at all before this.
2025-01-17Accumulate config engine, build metadata, and doc updatessrdusr6-3/+121
Remaining files from the backlog: Lua config engine additions (general/register/support/window), workspace Cargo.toml/ Cargo.lock churn from the new dependencies added elsewhere in this sweep, srdwm/main.rs wiring, and docs (DEFAULTS.md plus a new SESSION_HANDOFF.md written mid-session for continuity across a restart - see that file's own header for what it is and isn't).
2024-12-24Accumulate wayland-crate additions: capture rendering, focus/Space sync, VT ↵srdusr25-268/+1642
resume, plumbing Bundles the remaining wayland-crate changes built up here, touching both backends (udev and winit) and the shared input/rendering code: - udev/capture.rs: off-screen Pixman render of an arbitrary (not necessarily on-screen) workspace's window content to a PPM file -- what crates/core's capture-request queue drives, for a workspace switcher's thumbnail previews. wlr-screencopy structurally can't do this (it can only see what an output is presenting), which is why this exists as a separate render path rather than reusing it. - input::focus_window now also raises the window in smithay's own Space, not just core's stacking order - Space is what actually renders on top and what pointer hit-testing reads, so any focus path that skipped this (an IPC "focus" dispatch, concretely) left a window genuinely focused while still rendering, and receiving clicks, underneath whatever was already topmost. Both backends' poll loops now re-sync this after any IPC mutation. - udev/session.rs's VT-switch resume fix (drains a stale pending page flip before reasserting CRTCs) already has its own earlier, cleanly isolated commit - not duplicated here. - Assorted decoration/cursor/rounded-corners/output-management/ screencopy/XWayland changes and their cross-backend wiring. Coarser than the repo's usual one-purpose-per-commit convention, deliberately - see the core-crate sweep commit's own message for why.
2024-12-05Accumulate platform/ctl-crate additions: capture workspace, IPC responses, ↵srdusr4-51/+1115
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.
2024-11-30Accumulate core-crate additions: capture requests, focus/workspace fixes, ↵srdusr10-30/+533
test coverage 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.
2024-11-26Add Monitor::maximize_geometry: maximize covers a dock, still stops at a top barsrdusr4-4/+91
toggle_maximize previously targeted full_geometry outright (past every reserved zone), on an earlier request specifically about the dock - which also silently let it extend behind a top bar's zone, reported back as its own bug once live-tested. maximize_geometry is a third rect distinct from geometry (every zone) and full_geometry (none): full_geometry with only a top-anchored bar's exclusive zone subtracted back out. New test locks in dock-covered/bar-respected together.
2024-11-01Add Snap-Layouts flyout (Windows 11-style maximize-button hover menu)srdusr4-3/+266
Right-clicking (or hovering) the maximize button opens a flyout of fixed half/quarter positions (snap_flyout.rs renders it); picking one applies that zone via WindowManager::apply_snap_zone, the click-driven equivalent of dragging a window to that edge and releasing.
2024-10-31Add global-menu support (dbusmenu/appmenu) for Wayland and X11 clientssrdusr8-13/+533
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.
2024-10-30Add srdwm's own native session-lock screen (blur, PAM auth)srdusr6-0/+790
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.
2024-08-29Fix VT-switch resume getting stuck on a stale pending page flipsrdusr1-1/+24
A flip issued right before a VT switch away could still be undelivered when the session resumed - the kernel refuses a new page flip on a CRTC with one already outstanding, which showed up live as a black screen that never recovered across two switch attempts, with a rapid repeating "Device or resource busy" loop in the log. Drain and apply any pending DRM events before reasserting CRTCs and rendering again on resume, so a stale flip from before the switch can't collide with the fresh one.
2024-08-25Fix global-menu source misclassification for appmenu-gtk-module's shimsrdusr1-19/+85
read_global_menu unconditionally preferred MenuSource::Gtk whenever a GTK menubar path resolved - but appmenu-gtk-module exports a plain Gtk.Window's menu (no GtkApplication, so no _GTK_APPLICATION_OBJECT_ PATH/_GTK_WINDOW_OBJECT_PATH) through its Unity-compatibility shim, unity.-prefixed actions and all, while still setting _GTK_MENUBAR_ OBJECT_PATH. Labeling that Gtk meant a consumer inserted app/win action groups the app never populated instead of a unity one it did -- every menu item rendered, but permanently insensitive, since none of them resolved against a group that existed. This was flagged as a known risk in this function's own doc comment when `source` was first added, but the priority logic itself never got the fix. Root-caused live by an AGS peer session: read the actual exported menu content off the bus for a real appmenu-gtk-module app and found unity.-prefixed actions at the GTK atom's own path, with app_path/ window_path both empty - confirming that emptiness is the reliable tell, not which atom happened to resolve. Fixed by preferring Unity whenever app_path/window_path are both absent, even if a GTK menubar path resolved - the path itself doesn't change, only the label. Pulled the decision out into a standalone classify_menu_source function so it's unit-testable without a real X connection, with the exact live case as a regression test.
2024-08-24Implement general.focus_follows_mouse/auto_raise; remove the rest as deadsrdusr4-10/+63
Auditing "clicking behavior and basics": general.focus_follows_mouse, general.mouse_follows_focus, general.auto_raise, general.auto_focus, the entire window.* namespace (8 more keys, a full duplicate of the same four plus remember_position/size/state), and general. smart_placement/border_width were all seeded into default_config() and documented in DEFAULTS.md, but none were read anywhere - srd.set()/ srd.get() on any of them silently succeeded while doing nothing. focus_follows_mouse is real, well-defined, and directly relevant to clicking basics - implemented it plus auto_raise (raise, not just focus, on hover) rather than just deleting the promise like the others. WindowManager gained focus_follows_mouse/auto_raise bools, wired from apply_general_settings the same way every other general.* flag is. handle_pointer_position now tracks whichever window (content or decoration) is under the pointer and, when the setting is on and that differs from the currently-focused window, focuses it through the same focus_window() free function every click-driven focus change already uses (real keyboard focus, not just core state) - skipped entirely while dragging/resizing or over a layer-shell surface, so the pointer sweeping over other windows mid-drag or hovering a bar can't steal focus from what's actually being manipulated. mouse_follows_focus (pointer warp on keybinding-driven focus change) and auto_focus (no clear distinct meaning beyond click-to-focus) stay unimplemented and are now undocumented rather than promised.
2024-08-23Round the bottom border strip's corners to match the topsrdusr7-23/+132
MISSING.md listed the border frame's bottom/left/right strips as staying square while the top one (and the titlebar above it) rounds -- "unrelated to client content rounding... not attempted." The left/ right strips genuinely can't participate (border_strips' geometry has them span only the height between the top and bottom strips, no corner to round), but the bottom strip is exactly the same shape as the top one and had no reason left to stay square. decoration::render_border_bottom mirrors render_border_top exactly (round_bottom_corners mirrors round_top_corners), cached the same way in a new border_bottom_decorations map, and drawn in both render loops via the same all-or-nothing occlusion check the top strip already uses - pulled out of the left/right strips' per-fragment occlusion splitting into its own dedicated bitmap path, matching top's existing trade-off (cropping a rounded bitmap's source rect per fragment is real extra work for a strip this thin) rather than inventing a new one. One real bug caught before it shipped: round_bottom_corners' corner- centre math (height - r - 1) panics on unsigned underflow whenever the radius clamp lands on the strip's own full height (a real, common case - a 2px-thick test strip hits it immediately). Fixed by computing the centre as a signed offset instead, mirroring how the existing dx/dy distance math already avoids the same class of issue.
2024-08-22Add per-window resize-margin override (Hyprland's extend_border_grab_area)srdusr8-1/+50
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<i32>, 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.
2024-08-21Add toggle_maximize/toggle_fullscreen IPC dispatch actionssrdusr2-2/+20
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.
2024-08-20Send tiled xdg_toplevel state so GTK stops reserving its own shadowsrdusr2-0/+30
No configure this compositor ever sent set any xdg_toplevel::State bit at all - confirmed by grepping the whole crate, zero hits before this. GTK4 (Firefox concretely) reads the tiled bits to decide whether to reserve an invisible client-side shadow margin around its own content, independent of server- vs client-side decoration; with none ever sent it always assumed "floating, might need a shadow" and kept reserving one. That margin sits inside the committed buffer but is functionally invisible, so this compositor's own border - drawn at the full geometry, margin included, since nothing here knew the margin existed - ended up visibly offset from where the client's real chrome began. Root-caused from a live screenshot: Firefox's srdwm-drawn border sat clearly up-and-left of its actual toolbar, not framing it. Reported as "border is not with the window at start" and, more generally, borders never feeling like part of the window they're drawn around - which this is: decoration and content genuinely disagreeing about where the window's edge is. Sets all four Tiled* bits unconditionally on every xdg_toplevel configure - the same technique river/dwl use, telling every window it's flush against something and should skip its own shadow regardless of whether it's in a literal tiling layout, which is the outcome actually wanted here: this compositor draws the frame, nothing else should also be reserving room for one. Not visually verified against a live client yet - this needs an actual GTK app rendering under a restarted session to confirm the shadow margin is really gone, which no offline test can substitute for.
2024-08-20Give corner resize real priority over a plain edgesrdusr1-11/+118
Reported live, repeatedly: corners felt like they had no priority over sides. They didn't, structurally - a corner only ever registered in the exact pixel square where both edges' own resize_margin zones happened to overlap (6x6px at the default 6px margin), nothing wider. A click a little further along either axis, still clearly aiming for the corner, fell back to a single straight-edge resize instead. resize_edge_at now checks a separate, wider corner zone (CORNER_MARGIN * resize_margin) first, so a corner claims a real, deliberately larger target the way GNOME/KDE already do - diagonal resize is a harder grab than a straight edge and deserves more room, not the same or less. Also fixed a related, previously-unreachable case: a *decorated* window's whole titlebar band returned Drag/Close/Maximize/Minimize unconditionally, so top-left/top-right diagonal resize never had a code path at all, even at the titlebar's own corner pixels. Added top-left resize as a small, genuine corner square (both axes, not just one) checked before drag/buttons. Deliberately did NOT add the matching top-right case: that corner is the close button on every mainstream desktop, and trading a well-known, expected target for a rarely-wanted one at exactly the spot a miss costs the most isn't a trade worth making. Four new regression tests cover: reaching a corner past the old tight margin, falling back to a plain edge just past the new wider one, the newly-reachable decorated top-left corner, and confirming top-right still closes rather than competing with resize.
2024-08-19Document that ClientInfo's geometry is the frame rect, not contentsrdusr1-0/+10
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.
2024-08-14Handle late title/class updates on XWayland windowssrdusr1-1/+48
map_window_request only ever read window.title()/.class() once, at MapRequest - for a client whose managed window doesn't carry WM_NAME/WM_CLASS at that exact moment (or the properties simply arrive later), Window.title/app_id stayed permanently empty. That reaches srd.rule's class matching, this compositor's own titlebar text, and every wlr-foreign-toplevel-management listener (a dock's running indicator, an app switcher, icon lookup) - confirmed live via a peer session's AGS instance rendering blank rows for Spotify and OpenSnitch. Implements XwmHandler::property_notify (previously unhandled, a no-op default) to re-read title/class on WmWindowProperty::Title/Class and update Window plus notify rule/decoration/foreign-toplevel listeners on an actual change - the XWayland-side mirror of sync_toplevel_metadata, which already exists for exactly this problem on the native xdg-shell path (see its own doc comment). Not fully verified against the specific live case that surfaced this: whether XWayland's X11Wm delivers property_notify for a *managed* window whose real WM_NAME/WM_CLASS live only on an unmanaged sibling/ child (rather than arriving late on the same window) is still an open question - this fixes the well-documented "arrives late" case with certainty, and may or may not cover that harder case too.
2024-08-09Fix closing an XWayland window doing nothing on both backendssrdusr2-4/+19
Platform::close only ever called w.toplevel(), which is None for an XWayland window - closing one (the WM's own close binding, or `srd dispatch close`) silently did nothing at all, on both udev and winit. Found live: a leftover untitled fullscreen window wouldn't close via srd dispatch close even after several seconds, tracing back to this. Fixed by falling back to X11Surface::close() when there's no xdg toplevel - it already handles both cases smithay-side (a polite WM_DELETE_WINDOW for a cooperating client, outright destroy_window for one that doesn't support it), so no new logic was needed, just calling it.
2024-08-09Fix a misleading workspace comment and drop four dead config keyssrdusr3-5/+24
visible_windows' doc comment claimed windows show "on the active workspace of whichever monitor they're assigned to" - the code never reads w.monitor at all; current_workspace is one flat value shared by every monitor, not per-output. Documented that explicitly on both the field and the method, since this is a real behavioral difference from Hyprland worth a reader actually seeing, not just an inaccurate comment to fix quietly. monitor.primary_workspace/monitor.workspace_count describe a per- monitor-workspace design that doesn't exist; workspace.auto_switch/ workspace.persistent were never wired to any behavior. All four were seeded into default_config() and documented in DEFAULTS.md, so srd.set()/srd.get() on them silently succeeded while doing nothing -- removed from both, matching the precedent already set by general. rounded_corners' deliberate absence from default_config for a different reason (backend-dependent default rather than unbuilt).
2024-08-08Fix clicks landing on a window hidden on another workspacesrdusr2-5/+39
WindowManager::hit_test/window_at filtered only by `!w.minimized`, never by workspace - but a window on a workspace that isn't current is not minimized, it's just not shown. Rendering (visible_windows/ visible_windows_front_to_back) already restricted to the current workspace; hit-testing didn't, so a click landing on where an invisible window's stale on-screen geometry happened to sit routed to that window instead of whatever was actually visible underneath. Reported live. Fixed by adding the same workspace check rendering already uses, plus a regression test with two identically-positioned windows on different workspaces.
2024-08-08Fix a stale comment in cursor.rs's own shape-fallback dispatchsrdusr1-1/+2
Named Pointer/Crosshair/Move/text/four resize-direction shapes all have dedicated art now, but the comment picking Pointer/Crosshair as its examples of "shapes we don't draw" predates that - misleading about this file's own current behavior. Swapped in shapes that genuinely still fall back to the arrow (Grab, Wait, Help, NotAllowed).
2024-08-03Fix doc-comment file references left stale by today's six module splitssrdusr16-40/+40
~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).
2024-07-31Split crates/wayland/src/winit.rs (889 lines) into winit/srdusr8-889/+912
Pure reorganization, no behavior change - verified by diffing the function-name and struct-name sets before/after (both identical) plus a full cargo test pass. mod.rs keeps the module doc comment, imports, WaylandPlatform's struct definition, and TARGET_FRAME_TIME, plus mod declarations. The rest splits by concern: - connect.rs: connect(), the ~200-line setup/init function. - run.rs: accept_clients, pump_winit - the small per-poll pair. - render.rs: render_frame, the per-frame render loop (left as one intact ~330-line function, same reasoning as udev's render.rs: its structure is deliberate and already documented inline, not a target for further decomposition in a pure reorganization pass). - capture.rs: capture_offscreen, the screencopy path. - events.rs: handle_winit_event. - platform.rs: `impl Platform for WaylandPlatform`. No tests module existed in the original file, so none was split out. A handful of methods/functions (accept_clients, pump_winit, render_frame, capture_offscreen, handle_winit_event) went from private to pub(super): called across what are now sibling submodules, which Rust's privacy model doesn't let see each other's private items.
2024-07-31Split crates/x11/src/lib.rs (925 lines) into platform/srdusr8-923/+949
Pure reorganization, no behavior change - verified by diffing the function-name and struct/trait-name sets before/after (both identical) plus a full cargo test pass. lib.rs is now a thin shim (mod declaration + pub use), same pattern crates/config used, since a crate root can't itself become a directory. platform/mod.rs keeps the atom table, Frame/X11Platform's struct definitions, the small free-function helpers (err, modmask_for_keycode_in_mod_slots, rgb_to_pixel), and the ClonedForRender trait+impl. The rest splits by concern: - connect.rs: connect, keymap/modifier helpers, grab_keybindings. - window.rs: manage_new_window and the other per-client lifecycle methods (window_title/class, supports_wm_delete, unmanage, frame_for). - events.rs: handle_event, the X11 event-dispatch loop. - actions.rs: raise_and_focus/request_close/sync_geometry/ redraw_all_decorations. - trait_impl.rs: `impl Platform for X11Platform` - named to avoid clippy's module_inception lint, since the containing directory is already named `platform`. - tests.rs: unsplit, same reasoning as every other split this pass. A handful of X11Platform methods (frame_for, manage_new_window, unmanage, raise_and_focus, request_close, sync_geometry, keycode_to_keysym, modifiers_from_state, handle_event) went from private to pub(super): called across what are now sibling submodules, which Rust's privacy model doesn't let see each other's private items.
2024-07-30Split crates/wayland/src/state.rs (1276 lines) into state/srdusr10-1276/+1308
Pure reorganization, no behavior change - verified by diffing the function-name and struct-name sets before/after (both identical) plus a full cargo test pass. mod.rs keeps ClientState/OutputEntry/CompState/ WindowAnim/RepeatState's definitions, the key-repeat impl, and the output-lookup impl (all small and tightly coupled to the type definitions), plus mod declarations. The one large impl CompState block (previously ~600 lines) splits by concern: - lifecycle.rs: new_managed_window, set_decorated_from_mode, redraw_decoration_buffer, remove_window. - layers.rs: ensure_layer_initial_configure. - focus.rs: set_keyboard_focus, set_window_activated. - menu.rs: open/close/run_context_menu_action, is_double_click. - geometry.rs: raise_pinned, sync_geometry. - tick.rs: tick_dirty_broadcasts, tick_animations, resync_stacking_order. - toplevel.rs: the with_toplevel_title/app_id/sync_toplevel_metadata free functions. - tests.rs: unsplit, same reasoning as every other split this pass. CompState's fields were already pub(crate) (this crate's existing convention, unlike core's/config's plain-private), so no field- visibility changes were needed - only resync_stacking_order (called from geometry.rs, defined in tick.rs) needed bumping from private to pub(crate), matching that same convention.
2024-07-29Split crates/config/src/lib.rs (1441 lines) into engine/srdusr10-1434/+1475
Pure reorganization, no behavior change - verified by diffing the function-name and struct/enum-name sets before/after (both identical) plus a full cargo test pass. lib.rs is now a thin shim (mod declarations + pub use) since a crate root can't itself become a directory; all the actual content moved into engine/, split along the Lua API's own srd.*/srd.window.*/srd.layout.*/srd.workspace.*/ srd.theme.* namespace groupings the file's own section comments already used: - mod.rs: SharedState, Engine, ConfigError, and Engine's core methods (new/get/set/dispatch/reload/...). - register.rs: register_srd_module, which wires every fn_* builder from every other file into the srd Lua table - the one place that genuinely needs to see all of them. - general.rs/window.rs/layout.rs/workspace.rs/theme.rs: the fn_* builder methods themselves, one file per srd.* sub-namespace. - support.rs: free functions shared across those (do_reload, parse_direction, flatten_table_into, validate, default_config) and the WindowAction enum. - tests.rs: the ~300-line test module, left unsplit for the same shared-helper reason manager/tests.rs and udev's tests were. ~40 fn_* methods and the support.rs free functions/enum went from private to pub(super): called across what are now sibling submodules, which Rust's privacy model doesn't let see each other's private items.
2024-07-16Split crates/wayland/src/udev.rs (1643 lines) into udev/srdusr7-1643/+1665
Pure reorganization, no behavior change - verified by diffing the function-name and struct-name sets before/after (both identical) plus a full cargo test pass. Struct definitions (Card, DrmBuffer, UdevHead, UdevState) and their small impls stay in mod.rs, since default (crate- scoped) privacy there is visible to every descendant submodule without further changes. The rest splits by concern: - render.rs: the per-frame impl CompState block (render_udev_frame and the gamma/output-power methods) - still one ~520-line function, left intact rather than decomposed, given how much of its structure (the self.udev.as_mut() disjoint-borrow pattern threaded through it) is deliberate and already documented inline. - outputs.rs: hotplug reprobe/relayout (impl CompState). - platform.rs: UdevPlatform's struct/connect logic and its `impl Platform for UdevPlatform`, previously split apart in the flat file by ~250 lines of unrelated DRM/session code sitting between them. - drm.rs: mode/CRTC/framebuffer probing and setup. - session.rs: libseat/libinput/udev-monitor calloop registration and the libinput event handler. A few free functions and one struct (ConnectorProbe, bring_up_head, probe_connected, pick_crtc, the register_* functions) went from module-private to pub(crate): called across what are now sibling submodules, which - unlike a defining module's own descendants -- Rust's privacy model doesn't let see each other's private items. Matches this crate's existing pub(crate) convention rather than introducing pub(super), which crates/core's manager/ split used instead to match *that* crate's plain-private convention.
2024-07-14Fix keybindings silently never firing when a named key is lowercasesrdusr1-2/+46
canonicalize_key_combo already reordered multi-modifier combos into dispatch's canonical Ctrl/Shift/Alt/Mod4 order, but passed the key name through verbatim. keysyms::keysym_to_name capitalizes every named key ("Space", "Return", "Escape", "BackSpace", ...) while leaving letters/digits alone, so srd.bind("Super+space", ...) stored "Mod4+space" while a real Space keypress dispatches as "Mod4+Space" -- never matching. Accepted silently at config-load time, so the only live symptom was the bind's own callback never running at all. Root-caused live: keybindings.lua's Super+space bind had a temporary diagnostic added (logs to /tmp/superspace.log before spawning ags) to tell "key never fired" apart from "key fired but ags failed" - the log file never existed, meaning the callback itself never ran. Fix: round-trip the key name through name_to_keysym (already case- insensitive) and back through keysym_to_name before storing, so any case the config writes normalizes to dispatch's canonical form.
2024-07-11Split crates/core/src/manager.rs (2048 lines) into manager/srdusr11-2048/+2128
Pure reorganization, no behavior change - verified by diffing the function-name set before/after (identical 130 functions) plus a full cargo test pass. WindowManager's struct/field definitions, Default, new(), and the three trivial constructors (add_rule/register_layout/ available_layouts) stay in mod.rs; the rest of the single ~950-line impl block is split into one file per the section comments the file already had (monitors, windows, focus, winops, hittest, dragresize, workspaces, layout). Three methods called across section boundaries (monitor_for, windows_on_workspace, cycle_focus) went from private to pub(super) - Rust's privacy model doesn't let sibling submodules see each other's private items, only a defining module's own descendants. The ~1000-line test module moves to manager/tests.rs unsplit: its helpers (wm_with_monitor, two_monitors, monitor_with_dock) are shared across tests for every section, so splitting further would mean duplicating them or adding another shared-support file for little benefit.
2024-07-10Fix three pre-existing clippy warningssrdusr3-14/+13
- 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.
2024-07-09Rounded corners on udev/Pixman backend, opt-in and off by defaultsrdusr10-9/+325
CPU-side rounded corners for the software-only udev/Pixman renderer, which has no shader stage to hook the existing GLES version into. Reads a window's own committed wl_shm buffer, punches premultiplied- alpha holes into the four corner regions, and hands the masked copy to MemoryRenderBuffer - the same path already used for titlebar/ border/shadow bitmaps, so it composites through the ordinary unmasked path and the corners genuinely disappear rather than being painted over. Cached per window, invalidated by a per-commit content_epoch counter rather than rebuilt every frame, so an idle window costs nothing once masked. general.rounded_corners now defaults per backend instead of one global true: on for GLES/winit (a real GPU shader, no measurable cost), off for udev/Pixman (an untested-on-real-hardware CPU cost for constantly-repainting clients) - WindowManager.rounded_corners_enabled is Option<bool> so the backend can tell "unset" from "explicitly off".
2024-06-24Real rounded corners on client content (GLES/winit backend), via a custom shadersrdusr9-12/+305
decoration.rs's round_top_corners only ever clipped the compositor's own titlebar/border bitmap - its own doc comment already said why nothing more had been done: clipping arbitrary client content needs a real per-pixel mask, "a much bigger change than this cosmetic pass". That's this change, for the one backend that can do it cheaply: the udev backend's PixmanRenderer is software-only with no shader stage at all, but GlesRenderer (winit) has a real custom-shader path (`compile_custom_texture_shader`, `TextureShaderElement`) that a first look at smithay's higher-level convenience APIs missed entirely. crates/wayland/src/rounded_corners.rs: a GLSL fragment shader masking a window's texture against a rounded-rect signed-distance field while sampling it (the same technique cosmic-comp/niri use for GPU-side rounded corners) - built by hand from the surface's own committed texture/view/ damage state (`RendererSurfaceState`'s public accessors), since no smithay convenience wrapper builds a masked element at all (`CropRenderElement` only crops to a rectangle). A decorated window rounds only its bottom two corners - the top two are already rounded, on the titlebar's own CPU bitmap, by decoration.rs, at the exact same `CORNER_RADIUS` (now `pub(crate)`, shared between the two so the curve reads as one continuous radius, not two different ones meeting at a seam) - an undecorated/CSD window rounds all four, since its content is the window's whole visible extent. Falls back to plain unrounded content on any failure (shader didn't compile, no committed buffer yet, a single-pixel-buffer surface), same "always show something over a prettier maybe-nothing" reasoning cursor.rs's built-in-arrow fallback already uses. Deliberately scoped to a window's *main* surface only, not subsurfaces - documented as a real, if narrow, follow-up rather than attempted here. `TextureShaderElement` only implements `RenderElement<GlesRenderer>`, not the generic `RenderElement<R>` every `OverlayElement<R>` variant needs, so it can't be added to that shared enum without breaking `OverlayElement< PixmanRenderer>` (used identically by udev.rs) the moment a GLES-only variant showed up in it. `WinitElement<=GlesRenderer>` (new, winit.rs-only) wraps the existing enum as one variant instead of touching it - this is the same lesson as the fullscreen-hiding investigation earlier this session, just resolved cleanly this time: nesting a *foreign* generic type inside your own hits real bound-resolution walls; wrapping your own already-working type inside a new concrete-renderer enum doesn't, because smithay's own `render_elements!` macro documents exactly this `<=ConcreteRenderer>` form. Config: `general.rounded_corners` (default `true`), `srd.window` unaffected - this is a `general.*` compositor-behavior knob, not a per-window rule action like `opacity`. Verified live: shader compiles without error on this machine's real Mesa/ llvmpipe GL driver, and a decorated wezterm window's bottom-left and bottom-right corners both show a real, smoothly anti-aliased curve on the actual client-rendered pixels (not a compositor bitmap) in a host-session screenshot - qualitatively sharper than `round_top_corners`' deliberate hard cutoff, since a GPU shader can afford a ~2px smoothstep a CPU bitmap pass isn't worth adding for. cargo build --workspace (all 9 crates), cargo clippy --workspace (0 new warnings), cargo test --workspace (197 tests, 0 failed).