How Does Hyprland Manage State Transitions for Windows?

Hyprland employs a signal-driven architecture using immutable state snapshots, dedicated state trackers, and animation controllers to ensure atomic and deterministic window state transitions across workspaces and monitors.

Managing window state transitions in a dynamic Wayland compositor requires rigorous coordination between user input, logical state, and visual output. In the Hyprland repository, this complexity is abstracted into a modular pipeline where every visual change is backed by an immutable state object. The system guarantees consistency by routing all modifications through centralized state trackers that emit events, drive animations, and commit final states atomically.

The Core Architecture: CWindow and CWindowState

The foundation of Hyprland's state management rests on two primary components: the window object itself and its immutable state representation.

The CWindow Object

Each X11 or Wayland surface is encapsulated by a CWindow instance defined in src/desktop/view/Window.hpp. This class implements several critical interfaces—IView, CGeometricMovableAnimated, and IAlphaModifiable—which grant it capabilities for geometry manipulation, animation support, and opacity control. Rather than storing mutable properties directly, CWindow delegates state authority to external trackers, ensuring that its internal representation remains synchronized with the compositor's global state.

Immutable State Snapshots

Window properties such as size, position, monitor assignment, workspace ID, and opacity are stored in CWindowState, located in src/desktop/state/WindowState.hpp. This structure acts as an immutable snapshot; whenever a property changes, the system generates a new CWindowState instance rather than mutating the existing one. This functional approach eliminates race conditions and allows the animation system to interpolate between distinct, well-defined states without side effects.

The State Transition Pipeline

When a user initiates an action—such as moving a window to another workspace or toggling fullscreen—Hyprland executes a five-phase pipeline to transition from the current state to the target state.

1. Requesting Changes via State Trackers

State modifications originate at the WorkspaceStateTracker (src/state/WorkspaceStateTracker.cpp) or MonitorStateTracker (src/state/MonitorStateTracker.cpp). These trackers maintain authoritative maps of windows to their current states for each workspace and monitor. When a CWindow method such as setFullscreen() or moveToWorkspace() is invoked, it delegates to the tracker's requestStateChange() method, passing the window reference and the desired transition type (e.g., WindowStateChange::MOVE).

2. Event Propagation Through the Event Bus

Upon receiving a request, the tracker instantiates a CWindowStateChangeEvent and publishes it through Hyprland's internal event bus managed by EventManager (src/managers/EventManager.cpp). This publish-subscribe mechanism decouples state logic from visual rendering, allowing multiple subsystems—including layout managers, focus controllers, and animation handlers—to react to the same state transition without direct dependencies.

3. Animation Interpolation

The CWindowAnimationController (src/desktop/view/animationControllers/WindowAnimationController.hpp) subscribes to state-change events and manages the visual transition between the old and new CWindowState snapshots. By interpolating geometry and opacity values frame-by-frame, the controller drives smooth animations—such as fade-ins, slide transitions, and resize effects—while updating the OpenGL renderer each frame. This ensures that the visual presentation never diverges from the underlying logical state.

4. Committing the Final State

Once the animation concludes, the new CWindowState is committed to the tracker, replacing the previous snapshot atomically. The WorkspacePlacementController (src/state/WorkspacePlacementController.cpp) then finalizes the placement by updating focus stacks, recalculating split layouts, and ensuring the window is correctly assigned to its target monitor. This commit phase guarantees that queries via hyprctl reflect the definitive state only after all visual transitions complete.

Practical Implementation Examples

The following examples demonstrate how to trigger and observe state transitions programmatically and via command-line interfaces.

Programmatic Window Movement (C++)

To move a window between workspaces while ensuring proper state tracking:

#include <hyprland/src/desktop/view/Window.hpp>
#include <hyprland/src/state/WorkspaceStateTracker.hpp>

// Assuming `window` is a valid CWindow* and `targetWorkspace` is a CWorkspace*
window->setFullscreen(false);                       // Exit fullscreen if needed
window->moveToWorkspace(targetWorkspace->id());     // Request workspace switch
WindowStateTracker::get()->requestStateChange(window, WindowStateChange::MOVE);

Command-Line State Transitions (Bash)

Use hyprctl to dispatch state changes that follow the same pipeline:


# Move the active window to workspace 3

hyprctl dispatch movetoworkspace 3

# Toggle fullscreen for the focused window

hyprctl dispatch fullscreen

Observing Transitions via Event Hooks (Lua)

Listen to state-change events to execute custom logic during transitions:

hypr.on("windowStateChange", function(window, oldState, newState)
    hypr.notify("Window " .. window:title() .. " moved from " ..
                oldState:workspace() .. " to " .. newState:workspace())
end)

Key Source Files and Their Roles

Understanding the following files is essential for modifying or debugging Hyprland's state management behavior:

Summary

  • Immutable State Model: Hyprland uses CWindowState snapshots to ensure deterministic state representation without mutation side effects.
  • Centralized Trackers: WorkspaceStateTracker and MonitorStateTracker serve as the single source of truth for window assignments, routing all changes through requestStateChange().
  • Event-Driven Architecture: State transitions emit CWindowStateChangeEvent objects through EventManager, decoupling logic from rendering.
  • Animated Interpolation: CWindowAnimationController smoothly transitions between old and new states before atomic commitment.
  • Atomic Commitment: Final state application occurs only after animations complete, ensuring hyprctl queries and internal logic remain synchronized.

Frequently Asked Questions

How does Hyprland ensure window states remain consistent during rapid user actions?

Hyprland achieves consistency by treating CWindowState objects as immutable snapshots stored in centralized trackers. When rapid actions occur, each generates a distinct state-change event queued through the EventManager, preventing race conditions. The system processes these sequentially, with the CWindowAnimationController interpolating between well-defined states rather than applying partial updates.

What triggers a window state transition in Hyprland?

State transitions are triggered by three primary sources: user input handled through hyprctl dispatchers, direct API calls to CWindow methods like moveToWorkspace() or setFullscreen(), and internal layout manager requests. All sources ultimately invoke WorkspaceStateTracker::requestStateChange(), ensuring uniform handling regardless of origin.

Can custom animations be defined for specific window state transitions?

Yes. The CWindowAnimationController in src/desktop/view/animationControllers/WindowAnimationController.hpp listens for CWindowStateChangeEvent objects and determines interpolation strategies based on the transition type and window properties. Developers can extend this controller to implement custom easing functions or duration logic by modifying how the controller calculates intermediate frames between old and new CWindowState snapshots.

Where does Hyprland store the authoritative state for window geometry and workspace assignment?

Authoritative state resides in the WorkspaceStateTracker and MonitorStateTracker instances, which maintain maps of window pointers to their current CWindowState objects. While CWindow provides the interface for requesting changes, the trackers hold the ground truth, committing new states only after animations complete via the WorkspacePlacementController.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →