How Hyprland Parses and Applies Workspace Rules: A Deep Dive into the Source Code
Hyprland parses workspace rules during configuration loading, stores them as CWorkspaceRule objects in a global WorkspaceRuleManager, and applies them at runtime when workspaces are created to bind them to specific monitors or set persistent flags.
Hyprland’s workspace rule system lets users bind workspaces to monitors, mark them as persistent, or configure special attributes directly from hyprland.conf. This guide examines the implementation in the hyprwm/Hyprland repository to explain exactly how raw configuration strings transform into enforced window management behavior.
The Three-Stage Pipeline: From Config to Runtime
Workspace rule processing follows a distinct pipeline: parsing, storage, and application. Each stage is handled by specific components in the source tree.
Stage 1: Parsing Configuration Lines
When Hyprland loads its configuration, the parser identifies lines beginning with the workspace keyword. In src/config/shared/workspace/WorkspaceRuleManager.cpp, the parser instantiates a CWorkspaceRule object for every valid rule encountered.
Each CWorkspaceRule stores:
m_workspaceString: The raw selector (e.g.,"3"or"chat")m_workspaceId: The resolved numeric IDm_monitor: The target monitor name (e.g.,"DP-1")m_persistent,m_enabled: Boolean flags for persistence and initial state
The parser normalizes workspace selectors, resolving named workspaces to IDs and handling special workspace prefixes (special:) by assigning IDs in the SPECIAL_WORKSPACE_START range defined in the headers.
Stage 2: Storing Rules in WorkspaceRuleManager
All parsed CWorkspaceRule objects are maintained by the global WorkspaceRuleManager, accessible via Config::workspaceRuleMgr(). This singleton provides the primary API for rule retrieval:
getBoundMonitorForWS(const std::string& wsName): Returns the monitor name a workspace should occupygetAllWorkspaceRules(): Returns the complete vector for iteration
The manager persists these rules for the entire session, ensuring that configuration changes (hot-reloads) update the rule set atomically.
Stage 3: Applying Rules During Workspace Creation
Rules are enforced when workspaces are instantiated, not merely when the config loads. In src/state/WorkspaceState.cpp, the CWorkspaceStateTracker::createWorkspace() method queries the rule manager before finalizing workspace placement:
// Inside CWorkspaceStateTracker::createWorkspace(...)
if (const auto pMonitor = Config::workspaceRuleMgr()->getBoundMonitorForWS(NAME); pMonitor)
// The workspace is bound to a monitor → assign it
If a monitor binding exists, the workspace is added to that monitor’s workspace list, and the monitor is marked in the internal m_seenMonitorWorkspaceMap. Persistent workspaces are tracked separately in the manager’s persistentWorkspaceIDs vector to prevent ID reuse across sessions.
Core Data Structures and Source Files
Understanding the implementation requires familiarity with these key files:
| File | Purpose |
|---|---|
src/config/shared/workspace/WorkspaceRule.hpp |
Defines the CWorkspaceRule class with member variables like m_workspaceId and m_monitor |
src/config/shared/workspace/WorkspaceRuleManager.hpp |
Declares the WorkspaceRuleManager singleton API |
src/config/shared/workspace/WorkspaceRuleManager.cpp |
Implements parsing logic and rule storage |
src/state/WorkspaceState.cpp |
Contains CWorkspaceStateTracker where rules are applied during workspace creation |
src/state/WorkspaceQueryCore.hpp |
Provides utilities for locating workspaces by ID or name during rule processing |
Workspace Rule Syntax and Normalization
The configuration parser supports several rule variants:
# Bind workspace 1 to DisplayPort-1
workspace = 1 monitor:DP-1
# Create a persistent named workspace on HDMI-A-1
workspace = code persistent:true monitor:HDMI-A-1
# Define a disabled special workspace
workspace = special:something enabled:false
When parsing, Hyprland distinguishes between:
- Numeric selectors: Resolved directly to
m_workspaceId - Named selectors: Mapped to internal IDs, with persistence tracked separately
- Special selectors: Prefixed with
special:, assigned IDs fromSPECIAL_WORKSPACE_STARTupward
Practical Configuration Examples
To pin workspace 3 to a specific monitor and keep it alive through restarts:
workspace = 3 monitor:HDMI-A-1 persistent:true
To create a named workspace for chat applications:
workspace = chat monitor:DP-2 persistent:true
Querying Rules Programmatically
Plugin developers can access workspace rules through the public API:
#include <config/shared/workspace/WorkspaceRuleManager.hpp>
auto* ruleMgr = Config::workspaceRuleMgr();
if (auto monitor = ruleMgr->getBoundMonitorForWS("3")) {
Hyprland::Log::info("Workspace 3 is bound to monitor {}", *monitor);
}
This allows external tools and plugins to respect user-defined workspace constraints when manipulating window state.
Summary
- Parsing: Configuration lines starting with
workspaceare parsed intoCWorkspaceRuleobjects inWorkspaceRuleManager.cpp, storing selectors, monitor bindings, and flags. - Storage: Rules are maintained in the global
WorkspaceRuleManagersingleton, providing lookup methods likegetBoundMonitorForWS(). - Application: During workspace creation in
WorkspaceState.cpp,CWorkspaceStateTrackerqueries the manager to enforce monitor bindings and persistence. - Special handling: Named and special workspaces are normalized to numeric IDs, with persistent workspaces tracked separately to survive configuration reloads.
Frequently Asked Questions
What is the difference between numeric and named workspace rules in Hyprland?
Numeric rules (e.g., workspace = 1) reference workspaces by their ID directly, while named rules (e.g., workspace = chat) create a mapping between a string name and an internal ID. Both store the resolved ID in m_workspaceId, but named workspaces require additional lookup resolution during parsing. Special workspaces use the special: prefix and receive IDs starting from SPECIAL_WORKSPACE_START.
How does Hyprland handle persistent workspaces internally?
Persistent workspaces are flagged with m_persistent = true in their CWorkspaceRule object. The WorkspaceRuleManager stores these IDs in a dedicated persistentWorkspaceIDs vector, ensuring Hyprland does not reuse these IDs for dynamic workspaces and that they survive configuration reloads and session restoration.
Where are workspace rules applied when a new workspace is created?
Rules are applied in src/state/WorkspaceState.cpp within the CWorkspaceStateTracker::createWorkspace() method. This function queries Config::workspaceRuleMgr()->getBoundMonitorForWS() to determine the target monitor and checks persistence flags before finalizing the workspace's initial state and monitor assignment.
Can plugins access workspace rule information at runtime?
Yes. Plugins can include config/shared/workspace/WorkspaceRuleManager.hpp and call Config::workspaceRuleMgr() to access the singleton instance. The getBoundMonitorForWS() method allows reading monitor bindings, while getAllWorkspaceRules() provides iteration over the complete rule set for custom workspace management logic.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →