How Hyprland Handles Monitors and Display Configurations: A Technical Deep-Dive
Hyprland implements monitor management by encapsulating each physical or virtual display as a CMonitor object, coordinating detection, configuration, and lifecycle events through specialized state trackers and rule managers that interface directly with the Aquamarine backend.
The hyprwm/Hyprland repository defines a sophisticated display architecture that transforms raw Wayland outputs into fully configured monitors. At its core, the system treats every screen—physical or virtual—as a discrete CMonitor instance that owns its Aquamarine output, manages current display modes, and emits lifecycle events during connection, configuration changes, and disconnection.
Core Components of the Monitor Ecosystem
The display stack relies on several tightly-coupled components defined across the source tree:
- CMonitor – The runtime representation of a display located in
src/output/Monitor.cpp. It owns the Aquamarine output, maintains current mode and scaling state, handles color management, and drives the per-monitor frame scheduler. - CMonitorStateTracker – A global registry declared in
src/state/MonitorState.hppthat stores all monitors inm_realMonitorsand the logical working listm_monitors. It provides themonitorState()accessor used throughout the codebase to query or modify display sets. - MonitorRuleManager – Parses user-defined
monitor=configuration rules fromsrc/config/shared/monitor/MonitorRuleManager.cpp, resolving which CMonitorRule applies to which output during connection. - MonitorLayoutController – Arranges monitors in a rectangular coordinate space after add/remove operations, triggering window and workspace relayouts.
- WorkspacePlacementController – Ensures workspaces migrate to available monitors when displays appear or disappear.
- MonitorFrameScheduler – Drives the per-monitor frame-callback chain in
src/output/MonitorFrameScheduler.cpp, handling rendering loops and DPMS events.
Monitor Detection and the Connection Lifecycle
When Aquamarine signals a new IOutput, Hyprland instantiates a CMonitor via the constructor in src/output/Monitor.cpp. The object registers listeners for frame, commit, present, destroy, and state events. The full connection sequence executes inside CMonitor::onConnect (lines 84–90 and 108–124):
- Emit pre-add event –
Event::bus()->m_events.monitor.preAdded.emitnotifies listeners before structural changes occur. - Apply the monitor rule – The system fetches the matching rule via
Config::monitorRuleMgr()->get(this)and invokesapplyMonitorRule. - Enable the output –
m_output->state->setEnabled(true)activates the display backend, followed bym_state.commit(). - Create frame scheduler –
m_frameScheduler = makeUnique<CMonitorFrameScheduler>(…)initializes rendering timing. - Configure advanced features – Mirroring, Variable Refresh Rate (VRR), gamma tables, and color-management profiles are applied.
- Place workspaces –
WorkspacePlacementControllerrestores any workspaces previously associated with this monitor ID. - Emit added events –
g_pEventManager->postEvent({ "monitoradded", … })andEvent::bus()->m_events.monitor.added.emitbroadcast the new display to the ecosystem.
Resolution and Mode Selection
The heart of display configuration resides in CMonitor::applyMonitorRule (lines 774–820 and 842–870). Hyprland builds a list of candidate modes based on the user’s rule and evaluates them against hardware capabilities:
- Preferred mode – Used when the rule specifies no resolution.
- Sentinel values – Special tokens like
-1,-1(preferred),-1,-2(highest refresh rate), and-1,-3(highest resolution/maximum width) trigger custom sorting of advertised modes. - Exact specifications – User-defined resolutions or custom DRM modelines (
DRM_MODE_TYPE_USERDEF) are validated directly.
The algorithm stores the three best candidates in requestedModes, then iterates in reverse order:
m_state.applyModeWithSwapchain(mode); // or applyCustomModeWithSwapchain for user-defined modes
if (!m_state.test()) continue; // Reject if the compositor cannot commit
If m_state.test() succeeds, Hyprland updates m_refreshRate, m_size, m_currentMode, and m_pixelSize, schedules a frame, and finalizes with m_state.commit(). Should all candidates fail, the system invokes scheduleModeRetry to reattempt configuration up to three times after short delays.
Scaling, Transforms, and Color Management
After mode selection, Hyprland applies geometric and colorimetric transformations in applyMonitorRuleSoft:
Scaling – If the rule’s scale value is ≤ 0.1, Hyprland enables autoScale and selects the hardware’s getDefaultScale(). For explicit scales, the system validates that the value divides the pixel dimensions cleanly. When a user-supplied scale is invalid, Hyprland searches for the nearest valid divisor in steps of 1/120; if none exists, it falls back to the default scale and optionally notifies the user. This logic appears around line 990 of src/output/Monitor.cpp.
Transform – The rule’s rotation/flip value is applied via m_transform = RULE->m_transform;. Odd-valued transforms (90° or 270°) trigger a swap between width and height dimensions to maintain correct portrait/landscape orientation.
Color Management – The cm field in the monitor rule determines the color space. Based on hardware capabilities (m_enabled10bit, supportsWideColor(), supportsHDR()) and optional ICC profiles, applyCMType constructs a CImageDescription. Changes propagate via PROTO::colorManagement->onMonitorImageDescriptionChanged (lines 420–470 and 520–560).
Mirroring and Workspace Placement
When a configuration specifies mirror_of, CMonitor::applyMonitorRuleSoft invokes setMirror(m_activeMonitorRule.m_mirrorOf). The mirroring implementation:
- Registers the monitor as a clone of the target display.
- Shares the frame scheduler and damage buffers with the primary monitor.
- Automatically clears mirroring relationships if the target monitor disconnects, triggering a configuration reload via
MonitorRuleManager.
Simultaneously, the WorkspacePlacementController ensures workspace persistence by calling workspaceState()->rememberWorkspaceForMonitor during disconnections, allowing workspaces to return to their previous monitor when it reconnects.
Disconnection and Resource Cleanup
When Aquamarine destroys an output, CMonitor::onDisconnect executes the following cleanup sequence:
- Emits the pre-remove event to signal imminent destruction.
- Destroys OpenGL resources associated with the monitor.
- Migrates windows and workspaces to a fallback monitor using the layout controller.
- Persists the last workspace association via
workspaceState()->rememberWorkspaceForMonitorfor future restoration. - Emits removal events and schedules a layout re-check to rebalance remaining displays.
Practical Configuration Examples
Defining Monitor Rules in hyprland.conf
The MonitorRuleManager parses each monitor= line into a CMonitorRule structure:
# Enable HDMI-A-1 at 1080p 144Hz with specific scale and workspace
monitor = HDMI-A-1,1920x1080@144,scale=1.0,transform=0,defaultworkspace=1
# Mirror DP-1 to HDMI-A-1 (shares frame buffer)
monitor = DP-1,2560x1440,scale=1.25,mirror_of=HDMI-A-1
# Disable the internal laptop display
monitor = eDP-1,disable
Runtime Control via hyprctl
Changes applied at runtime flow through the same rule-application pipeline:
# Query current display states
hyprctl monitors
# Set DP-1 to 4K@60 with 10-bit color depth
hyprctl dispatch exec monitorrule DP-1,3840x2160@60,enable10bit=1
# Disable a connected display
hyprctl dispatch exec monitorrule eDP-1,disable
Querying Monitor Geometry Client-Side
Wayland clients can retrieve display metadata through Hyprland’s IPC system, which exposes the internal CMonitor state:
// Example: Querying compositor state via Hyprland IPC (pseudo-API)
auto monitors = hyprctl::getMonitors(); // pseudo-API
for (auto& m : monitors) {
std::cout << "Monitor " << m.name << " size " << m.width << "x" << m.height
<< " @ " << m.refresh << "Hz, scale " << m.scale << "\n";
}
Summary
- CMonitor objects in
src/output/Monitor.cppencapsulate all runtime display state, from Aquamarine output handles to current color profiles. - MonitorRuleManager translates configuration file syntax into structured rules that drive mode selection, scaling, and mirroring decisions.
- Mode selection uses a three-candidate fallback system with
applyModeWithSwapchainandm_state.test()validation, supporting sentinel values like-1,-1for automatic configuration. - Scaling validates against 1/120 granularity steps, falling back to hardware defaults when user values are incompatible.
- Global state trackers (
CMonitorStateTracker,MonitorLayoutController,WorkspacePlacementController) ensure atomic updates across the compositor when monitors connect or disconnect.
Frequently Asked Questions
How does Hyprland store and apply monitor configuration rules?
Hyprland stores user configurations as CMonitorRule objects managed by the MonitorRuleManager class in src/config/shared/monitor/MonitorRuleManager.cpp. When a monitor connects, CMonitor::onConnect fetches the applicable rule via Config::monitorRuleMgr()->get(this) and applies it through applyMonitorRule, which handles resolution, refresh rate, scaling, and positioning.
What happens when Hyprland detects a monitor but cannot apply the requested mode?
If m_state.test() fails for all candidate modes during applyMonitorRule, Hyprland invokes scheduleModeRetry to reattempt configuration up to three times. This retry mechanism accommodates temporary hardware initialization delays or DRM state synchronization issues before falling back to safe defaults.
How does Hyprland calculate monitor scaling when auto-scaling is disabled?
When auto-scaling is disabled or a specific scale is requested, Hyprland validates that the scale divides the pixel dimensions cleanly. If the user-specified scale is invalid, the system searches for the nearest valid divisor in increments of 1/120; if no suitable divisor exists, it reverts to getDefaultScale() and optionally displays a notification to the user.
What is the relationship between CMonitor and the Aquamarine backend?
Each CMonitor instance owns an Aquamarine IOutput handle (m_output), which abstracts the underlying DRM/KMS device. The CMonitor class translates high-level configuration rules (resolution, VRR, color management) into Aquamarine state commits via m_output->state and m_state.commit(), bridging the compositor’s logical display model with kernel-level display hardware control.
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 →