ARCHITECTURE.md
Paneru Architecture
This document provides a high-level overview of Paneru's architecture for contributors. Paneru is a macOS window manager built using the Bevy Game Engine and its Entity Component System (ECS).
1. High-Level Overview
Paneru manages macOS windows as a sliding strip (inspired by Niri and PaperWM). The core design philosophy is Data-Driven/ECS: instead of managing windows as complex objects with internal state, we represent the "World" as a collection of simple data components (Windows, Displays, Workspaces) that are processed by systems.
The primary problem Paneru solves is providing a predictable, stable, and ergonomic tiling experience on macOS. By using Bevy's ECS, we gain:
- Declarative Logic: Systems react to changes in window properties (e.g.,
Changed<Position>). - High Performance: Parallel system execution and efficient change detection.
- Modularity: Functionality is divided into decoupled plugins and systems.
2. The Bevy Bridge
Bevy is typically used for games, so Paneru implements a custom bridge to interact with the macOS Window Server.
Event Ingestion (macOS -> ECS)
- Platform Layer:
src/platform/usesobjc2and AppKit to interface with macOS. It runs a native event loop or hooks into OS notifications. - Event Channel: macOS events (mouse moves, window creations, space changes) are sent via a thread-safe
mpscchannel. - Pump System: The
pump_eventssystem (insrc/ecs/systems.rs) reads from this channel during thePreUpdatephase and writes BevyMessages or triggersObservers. - Observers: Bevy Observers (primarily in
src/ecs/triggers.rs, with focused domains such as session restore insrc/ecs/restore.rs) react to these events to update the ECS World (e.g., spawning newWindowentities or updatingFocusedMarker).
State Synchronization (ECS -> macOS)
- Systems: Bevy systems (like
layout::position_layout_windows) calculate the intended positions and sizes of windows based on the tiling logic. - Commit Systems: In the
PostUpdatephase, specialized systems likecommit_window_positionandcommit_window_sizeidentify windows that need updating. - FFI Calls: These systems call methods on the
Windowtrait object (implemented byWindowOSinsrc/manager/windows.rs), which performs the actual accessibility API calls to move or resize the physical macOS window.
Note: All AppKit/Accessibility calls must happen on the Main Thread. Paneru ensures this by using NonSend resources and executing critical synchronization systems on the main thread.
The Lua Worker (optional lua feature)
The embedded scripting runtime is the one deliberate exception to "everything interesting happens on the main thread". A handler is arbitrary user code of unbounded duration, and pump_events is itself main-thread-pinned, so running handlers inline meant a slow script stalled the frame clock. src/lua/worker.rs runs the interpreter on a dedicated thread instead:
- Main → worker:
dispatch_lua_eventsandcommand_lua_handlerextract plain data (LuaEvent,StateSnapshot) out of the world and send it over an unbounded channel. Neither ever blocks. - Worker → main:
drain_lua_outboxnon-blockingly drains queuedCommands and flash messages onto the command bus, one frame behind. - The query round-trip:
paneru.query*still reads the live world. The worker sends a request carrying a reply channel and blocks on it;serve_lua_queriesanswers it fromQueryStateParamsinPreUpdate(before the pump) and again inPostUpdate. Shutdown drops the request queue, which unblocks any waiting handler with an error rather than a hang.
This is what keeps src/lua/runtime.rs free of any bevy import: it reaches the world only through an extract callback, which on the main thread is a direct query and on the worker is that round-trip.
3. Crate & Module Map
| Directory / Module | Responsibility Statement |
|---|---|
src/ecs/layout.rs | Tiling algorithms, column management, and coordinate calculations. |
src/ecs/systems.rs | Bevy systems for lifecycle management, event pumping, and state syncing. |
src/ecs/params.rs | High-level Bevy SystemParam abstractions for querying the World. |
src/ecs/triggers.rs | Reactive event handlers (Observers) for OS and internal events. |
src/ecs/restore.rs | Startup session restore planning and application, including window matching, layout rebuilding, and restore grace-period handling. |
src/ecs/workspace.rs | Management of virtual workspaces, display changes, and window movement between spaces. |
src/ecs/scroll.rs | Input handling for trackpad swipe gestures, inertia, and snapping. |
src/ecs/focus.rs | Focus management logic, including focus-follows-mouse and mouse-follows-focus. |
src/ecs/state.rs | Persistence of window layout and workspace state across restarts. |
src/manager/ | OS-agnostic traits (WindowApi, ProcessApi) and their macOS implementations (WindowOS). |
src/platform/ | Low-level macOS FFI, event loop integration, and workspace/input hooks. |
src/config/ | Configuration parsing, validation, and hot-reloading logic. |
src/commands.rs | Implementation of CLI subcommands. |
src/client.rs | The CLI side of the IPC protocol, and the only place JSON is produced. |
src/reader.rs | The daemon side: owns the Mach service and turns requests into events. |
crates/mach_ipc | Typed channels over Mach ports; the transport itself. Async and blocking spellings of each operation, on SendPort/RecvPort. |
src/overlay.rs | Logic for drawing active window borders and inactive window dimming. |
4. Key Data Entities
Components
Window: A wrapper around a macOS window handle (AXUIElement).Display: Represents a physical monitor and its bounds.LayoutStrip: A component attached to a Workspace/Display that manages the ordered list ofColumns.LayoutPosition/Position: The intended (layout) vs. actual (on-screen) coordinates.Bounds/WidthRatio: The size of the window and its relative width in the tiling strip.FocusedMarker: Identifies the currently focused window.ActiveWorkspaceMarker: Identifies the currently active workspace.SelectedVirtualMarker: Marks a virtual workspace that is currently selected by the user.NativeFullscreenMarker: Marks a window that is in macOS native fullscreen mode.Unmanaged: An enum identifying windows that areFloating,Minimized, orHidden.RepositionMarker/ResizeMarker: Used to signal that a window needs to be moved or resized.
Resources
WindowManager: A wrapper for the global window management state and OS bridge.Config: The current user configuration.PaneruState: The durable snapshot of managed layout, display, native workspace, and virtual workspace state used for recovery after restarts.SessionRestore: A short-lived startup resource that keeps loaded state and restore timing active until the startup grace period expires.MissionControlActive: A flag indicating if macOS Mission Control is visible (disabling tiling).FocusFollowsMouse: Tracks which window should gain focus based on mouse position.
5. Architectural Invariants
- Main Thread Only: Any interaction with
objc2,AppKit, orAccessibilityAPIs must occur on the main thread. - ECS as Source of Truth: Tiling logic must operate on ECS components (
WidthRatio,LayoutStrip). The physical macOS window state should be a reflection of the ECS state, not the other way around. - Pure Layout: Layout math (in
layout.rs) should remain as pure as possible, operating on coordinates and ratios rather than directly calling OS APIs. - Bounded Restore: Saved session state is only consulted during startup restore. After
SessionRestoreexpires, normal config and window-rule placement owns newly discovered windows. - Reactive Power Saving: Systems should use Bevy's reactive scheduling to avoid CPU usage when no windows are moving or events are occurring.
6. Session Restore
src/ecs/state.rs extracts and persists the restart snapshot. The state file is
written atomically to paneru/state.json in the XDG state directory
(~/.local/state/paneru/state.json on a default macOS setup) and is loaded
during Bevy app setup.
src/ecs/restore.rs owns startup restore. It keeps the loaded PaneruState
alive in SessionRestore for the configured grace period so applications have
time to reopen their windows. As windows arrive, restore_window_state builds a
restore plan from the saved state and the currently managed ECS windows.
Window matching prefers stable identity (window_id, pid, and bundle_id)
and uses the conservative fallback identity only when it can do so
unambiguously. The fallback includes bundle_id, window title when available,
window identifier, role, and subrole. Saved windows that are missing at startup
are ignored by default, and the restored layout is compacted around the matched
windows.
Restore rebuilds LayoutStrips, virtual workspace rows, selected virtual
workspace markers, and display associations. When the current macOS workspace
to display mapping conflicts with saved display data, the current mapping is
preferred; otherwise restore falls back to the saved display, then the active
display, then any available display. Matched startup windows skip static
[windows] placement so the saved session wins, while unmatched windows and
post-grace windows follow normal config behavior.
7. Data Flow Diagram
| 1 | graph TD |
| 2 | A[macOS Window Server] -->|Native Event| B(src/platform Layer) |
| 3 | B -->|mpsc Channel| C(pump_events System) |
| 4 | C -->|Bevy Event| D(Observer / Trigger) |
| 5 | D -->|Update ECS| E{ECS World} |
| 6 | E -->|Changed Component| F(Layout/Animation System) |
| 7 | F -->|Set RepositionMarker| E |
| 8 | E -->|PostUpdate| G(commit_window_position) |
| 9 | G -->|FFI Call| A |
| 10 | H[CommandReader] -->|Unix Socket| C |
| 11 | S[PaneruState file] -->|Startup load| R(session restore) |
| 12 | R -->|Rebuild saved strips| E |
| 13 | E -->|Periodic / exit save| S |
8. Testing Strategy
- Pure Unit Tests: Located in
src/tests.rsand alongside modules. These test layout math and configuration parsing without requiring a macOS environment. - ECS Integration Tests: Use Bevy's
ApporWorldto drive systems in isolation. macOS APIs are typically mocked via theWindowApiandWindowManagerApitraits. - Session Restore Tests:
src/tests/session_restore.rscovers restore planning, missing-window compaction, startup grace behavior, config precedence, virtual workspace restoration, and multi-display fallback. - FFI Verification: Manual or semi-automated tests on macOS to ensure the Accessibility API calls behave as expected with native windows.
- Agent Support: The
AGENTS.mdfile provides project-specific guidance for AI agents to ensure contributions follow these architectural patterns.