# How Hyprland Handles Monitors and Display Configurations: A Technical Deep-Dive

> Discover how Hyprland handles monitors and display configurations. Learn about its CMonitor object, state trackers, and Aquamarine backend integration for seamless display management.

- Repository: [Hypr Development/Hyprland](https://github.com/hyprwm/Hyprland)
- Tags: deep-dive
- Published: 2026-07-23

---

**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`](https://github.com/hyprwm/Hyprland/blob/main/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.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/state/MonitorState.hpp) that stores all monitors in `m_realMonitors` and the logical working list `m_monitors`. It provides the `monitorState()` accessor used throughout the codebase to query or modify display sets.
- **MonitorRuleManager** – Parses user-defined `monitor=` configuration rules from [`src/config/shared/monitor/MonitorRuleManager.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/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`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/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):

1. **Emit pre-add event** – `Event::bus()->m_events.monitor.preAdded.emit` notifies listeners before structural changes occur.
2. **Apply the monitor rule** – The system fetches the matching rule via `Config::monitorRuleMgr()->get(this)` and invokes `applyMonitorRule`.
3. **Enable the output** – `m_output->state->setEnabled(true)` activates the display backend, followed by `m_state.commit()`.
4. **Create frame scheduler** – `m_frameScheduler = makeUnique<CMonitorFrameScheduler>(…)` initializes rendering timing.
5. **Configure advanced features** – Mirroring, Variable Refresh Rate (VRR), gamma tables, and color-management profiles are applied.
6. **Place workspaces** – `WorkspacePlacementController` restores any workspaces previously associated with this monitor ID.
7. **Emit added events** – `g_pEventManager->postEvent({ "monitoradded", … })` and `Event::bus()->m_events.monitor.added.emit` broadcast 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:

```cpp
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`](https://github.com/hyprwm/Hyprland/blob/main/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:

1. Emits the pre-remove event to signal imminent destruction.
2. Destroys OpenGL resources associated with the monitor.
3. Migrates windows and workspaces to a fallback monitor using the layout controller.
4. Persists the last workspace association via `workspaceState()->rememberWorkspaceForMonitor` for future restoration.
5. 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:

```ini

# 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:

```bash

# 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:

```cpp
// 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.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/output/Monitor.cpp) encapsulate 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 `applyModeWithSwapchain` and `m_state.test()` validation, supporting sentinel values like `-1,-1` for 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`](https://github.com/hyprwm/Hyprland/blob/main/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.