refactor: focus model, bar update/render split, flicker & title fixes; +logging rotation, Lua REPL - #16
Merged
Merged
Conversation
TheB1t
force-pushed
the
feat/logging-rotation
branch
2 times, most recently
from
April 16, 2026 19:13
9dfb1b1 to
f7f38f3
Compare
Writes to $XDG_STATE_HOME/sirenwm/ (fallback ~/.local/state/sirenwm/), rotates at 5 MiB × 5 files. WM process logs to sirenwm.log, embedded display-server child logs to sirenwm-display.log. Every LOG_* call flushes immediately so nothing is lost on crash/kill. Test harnesses switched to log_init_null() to avoid touching the user's state dir. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
X11 backend paths were emitting FocusChanged directly, leading to 3–6 duplicate events per real focus change (EnterNotify → dispatch emits, arbiter emits, FocusIn → dispatch emits again). Listeners saw the spam in the debug UI event log. Realign the boundary: only Core emits domain events. Introduce Core::ensure_focused(WindowId) — idempotent, no BackendEffect — for paths that merely reconcile core state with an authoritative backend signal (X FocusIn, reload, root fallback). dispatch(FocusWindow) is now idempotent on the event side too, always emitting the X effect but skipping FocusChanged when core already tracks the target. X11 backend changes: - Arbiter applies xcb_set_input_focus only; emits nothing. - handle_focus_event → core.ensure_focused() instead of dispatch. - handle_enter_notify drops its synchronous dispatch — the follow-up X FocusIn reconciles core via ensure_focused. - restore_visible_focus/none and on_reload_applied route through ensure_focused so the one-emit-per-transition invariant holds. Result: exactly one FocusChanged per real focus transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a live Lua REPL to the debug UI as a new tab alongside Monitors/Workspaces/Windows/Focus/Events. Input is evaluated via LuaHost::repl_eval(), which first attempts "return <code>" so bare expressions print their value, and falls back to loading the raw code as a statement block. print() output is captured for the duration of the call and appended to the REPL output pane. No sandboxing — this is a debug-only surface with full access to the live Lua state, same as the rest of the debug UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collapse the focus subsystem onto a dwm-style single-writer model. Motivation: accumulated ad-hoc fixes (FocusPriority arbiter, ensure_focused, restore_visible_focus, FocusState cache) meant five different code paths could write "who is focused" — causing desync after siren.restart() and double FocusChanged emissions across monitors. Intent chain, enforced by code and comments: focused_monitor_ -> Monitor.active_ws -> Workspace::current Core::focus(WindowId) is now the only writer. It mirrors dwm's focus(Client*) in dwm.c:789: picks a visible candidate when window is NO_WINDOW, validates the target, drives X via BackendEffect, and emits FocusChanged exactly once per call. All intent entry points (FocusWindow atom, FocusMonitor, focus_monitor_at_point, SwitchWorkspace, FocusNext/Prev, MoveWindowToWorkspace, SetWindowFullscreen, reconcile) route through it. Removed: - FocusState cache + 15 sync_focus_state() call sites — now derived - Core::ensure_focused / Core::sync_current_focus (X-driven inversions) - X11Backend::FocusPriority arbiter + pending_focus_*_ fields - X11Backend::restore_visible_focus (redundant after ReconcileNow) - X11Backend::request_focus - Workspace::advance_focus (unused) - handle_focus_event's 60-line parent-walk — collapsed to 12-line pure theft-protection (dwm.c:814) Fixed: - Borders not repainted after reload/restart (Core::focus now always emits FocusChanged on a valid window; consumers are idempotent) - Double FocusChanged on cross-monitor EnterNotify — wsman.focus_window now owns the focused_monitor_ update as part of focus intent Secondary (restart hardening, picked up from the same branch): - Re-assert managed event mask on every MapWindow backend effect so clients that replace their event mask on remap don't silently drop Enter/Focus/Structure subscriptions - libxcb: add XConnection::query_parent - Drop unused display_server input port plumbing and dead keybinding wiring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Widgets now separate expensive state mutation (update) from cheap rendering (render). update() runs on the interval schedule or on first load via prime_widgets(); render() runs on every repaint. Reactive widgets (interval=0) update+render on every redraw. Also fix a cluster of issues that showed up together: - Bar flicker on siren.restart(): Core::dispatch(ApplyMonitorTopology) no longer emits DisplayTopologyChanged. Emission is the caller's responsibility — Runtime::dispatch_display_change() posts it on true hot-plug / reload, Runtime::start() does not, since the initial topology apply is not a "change" and must not trigger reactive rebuilds that duplicate what on_start() already does. - Bar flicker on siren.reload(): BarModule::on_reload() no longer rebuilds physical windows — it only absorbs new config/theme. dispatch_display_change() posts DisplayTopologyChanged after reload, which is now the single rebuild trigger. - Focused-window title missing on bar after restart: adopt path dropped title because ExistingWindowSnapshot didn't carry it. title is now read directly from X at adopt time (not persisted across restarts) and threaded through snapshot → SetWindowMetadata. Single path through read_window_metadata; redundant read_window_title and local _NET_WM_NAME/UTF8_STRING interning removed. - New LuaHost::call_ref_method_void for widget:update() (no return). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TheB1t
force-pushed
the
feat/logging-rotation
branch
from
April 16, 2026 19:37
f7f38f3 to
319d14d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five commits on this branch, listed in git order (oldest → newest).
1.
feat(log)— XDG state dir + rotation + per-call flush$XDG_STATE_HOME/sirenwm(fallback$HOME/.local/state/sirenwm), auto-created.sirenwm.logvssirenwm-display.log(argv pre-scanned for--display-server)._LOG_EMITflushes after everyLOG_*so no lines are lost on crash.log_init_null()replaces/dev/nullin tests.run_tests_wayland.sh) now points at the new log location via aRUNTIME_LOGvariable.2.
refactor(focus)— Core is sole producer ofFocusChangedeventsEarlier boundary fix:
Core::dispatch(FocusWindow)emitsFocusChangedonly on actual state transition; the X11 arbiter becomes a mute applier. Removes 3–6×FocusChangedspam per real transition caused by three independent emit paths.3.
feat(debug_ui)— Lua REPL tabLuaHost::repl_eval.return <code>first; falls back to statement block on load failure.print()via thread-local buffer +repl_print; restores originalprintafter the call.4.
refactor(focus)— single source of truth viaCore::focusFollow-up that collapses the focus subsystem onto a dwm-style single-writer model.
Core::focus(WindowId)is now the only path that writes "who is focused". RemovesFocusStatecache,FocusPriorityarbiter,ensure_focused,restore_visible_focus,request_focus, and the 60-line parent-walk inhandle_focus_event. Intent chain:focused_monitor_→Monitor.active_ws→Workspace::current.Fixes:
FocusChangedon cross-monitor EnterNotify._NET_WM_STATE_FOCUSEDsticking on the previously focused window when switching to an empty workspace — the else-branch inCore::focus()now emitsFocusChanged(NO_WINDOW)unconditionally (symmetric with the focused branch), sinceprevread fromwsmanafter aswitch_toreflects the new workspace, not the window we are defocusing.5.
refactor(bar)— split widgetupdate/render; restart/reload flicker & title fixesupdate) from cheap rendering (render).update()runs on the interval schedule or on first load viaprime_widgets();render()runs on every repaint. Reactive widgets (interval=0) update+render on every redraw.Core::dispatch(ApplyMonitorTopology)no longer emitsDisplayTopologyChanged. Emission is the caller's responsibility —Runtime::dispatch_display_change()posts on hot-plug / reload;Runtime::start()does not (initial apply isn't a "change" and must not trigger reactive rebuilds that duplicateon_start).BarModule::on_reload()no longer rebuilds physical windows — it only absorbs new config/theme.DisplayTopologyChanged(posted bydispatch_display_change()after reload) is now the single rebuild trigger.ExistingWindowSnapshotdidn't carry it. Title is now read directly from X at adopt time (not persisted across restarts) and threaded through snapshot →SetWindowMetadata. Single path throughread_window_metadata; redundantread_window_titleand local_NET_WM_NAME/UTF8_STRINGinterning removed.LuaHost::call_ref_method_voidforwidget:update()(no return).Test plan
cmake --build build -j$(nproc)— clean.ctest --test-dir build --output-on-failure— 4/4 green (updatedHotplug.ApplyTopologyDoesNotEmitDomainEventreflects the ApplyMonitorTopology contract change).run_tests.sh) —05_focusnow passes including theFOCUSED cleared after ws switchcase.run_tests_wayland.sh) — structured-log checks now resolve against the XDG runtime log path.siren.restart()/siren.reload().FocusChangedper real change.sirenwm.logandsirenwm-display.log.print.🤖 Generated with Claude Code