# How Hyprland's Configuration Hot-Reload System Works with inotify

> Discover how Hyprland's configuration hot-reload uses inotify to automatically update your settings without restarting. Learn about the CConfigWatcher class.

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

---

**Hyprland uses a singleton `CConfigWatcher` class that wraps Linux's inotify API to monitor configuration files and trigger automatic reloads when they change on disk.**

Hyprland, the dynamic tiling Wayland compositor, implements a robust configuration hot-reload system that allows users to modify settings in real-time without restarting the session. This functionality is built upon a thin abstraction around Linux's **inotify** file system notification mechanism, encapsulated in the `CConfigWatcher` class located in [`src/config/shared/inotify/ConfigWatcher.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/config/shared/inotify/ConfigWatcher.cpp).

## The CConfigWatcher Architecture

The hot-reload system centers on a lazily instantiated singleton accessed via `Config::watcher()`. This design pattern ensures that only one inotify instance exists per compositor session, preventing resource exhaustion and simplifying event coordination. The singleton is initialized on first access and persists for the lifetime of the Hyprland process.

When constructed, `CConfigWatcher` attempts to open an inotify file descriptor using `inotify_init1(IN_NONBLOCK | IN_CLOEXEC)`. The **IN_NONBLOCK** flag ensures that read operations return immediately if no events are available, while **IN_CLOEXEC** prevents the descriptor from leaking to child processes. If initialization fails, the compositor logs a warning and silently disables hot-reload capabilities rather than crashing.

## Building the Dynamic Watch List

The watcher dynamically constructs its monitoring list through the `update()` method, which respects the user's `misc:disable_autoreload` setting. When auto-reload is enabled, `update()` invokes `setWatchList()` with all configuration paths returned by `Config::mgr()->getConfigPaths()`.

For each path, the system calls `inotify_add_watch()` with event masks tailored to the file type:

- **Regular files** receive `IN_CLOSE_WRITE` to trigger only after the file handle is closed, ensuring complete writes
- **Directories** receive a broader mask including `IN_CREATE`, `IN_DELETE`, and `IN_MOVED_*` events to detect new or removed config files
- **Symlinks** are resolved to their canonical targets before watching, ensuring that changes to the actual file trigger events regardless of how the path was accessed

This differentiation prevents premature reloads during incomplete writes while capturing structural changes to configuration directories.

## Event Handling and Callback Propagation

When the compositor's main poll loop detects activity on the inotify file descriptor, it invokes `CConfigWatcher::onInotifyEvent()`. This method reads the raw event buffer from the kernel, validates each `inotify_event` structure, and matches the watch descriptor against internal tracking entries.

Upon validation, the system constructs an `SConfigWatchEvent` structure containing the affected file path and forwards it to a user-defined callback function. The Lua configuration manager registers this handler in [`src/config/lua/ConfigManager.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/config/lua/ConfigManager.cpp):

```cpp
Config::watcher()->setOnChange([this](const CConfigWatcher::SConfigWatchEvent& e) {
    Log::logger->log(Log::DEBUG, "[lua] file {} modified, reloading", e.file);
    reload();
});

```

This callback triggers `reload()`, which reparses the Lua configuration and updates compositor state without requiring a restart.

## Integration with the Event Loop

The inotify descriptor integrates into Hyprland's asynchronous I/O architecture through [`src/managers/eventLoop/EventLoopManager.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/managers/eventLoop/EventLoopManager.cpp). The event loop manager adds the inotify file descriptor to its poll set alongside display server sockets and input devices.

When `poll()` reports the descriptor as readable, the event loop immediately calls `Config::watcher()->onInotifyEvent()`, ensuring that configuration changes are processed with minimal latency. This integration allows Hyprland to remain responsive while maintaining zero-cost monitoring when no file changes occur.

## Practical Implementation Examples

You can register custom hot-reload handlers in plugin code or custom builds by accessing the watcher singleton:

```cpp
#include "config/shared/inotify/ConfigWatcher.hpp"

void myReloadHandler(const Config::CConfigWatcher::SConfigWatchEvent& ev) {
    std::cout << "Config file changed: " << ev.file << "\n";
    // Custom reload logic here
}

int main() {
    // Ensure the watcher is initialized
    Config::watcher()->update();
    Config::watcher()->setOnChange(myReloadHandler);
    // Proceed to main event loop
}

```

To manually refresh the watch list after programmatically adding new configuration files:

```cpp
Config::watcher()->update();   // Re-reads Config::mgr()->getConfigPaths()

```

## Summary

- **CConfigWatcher** provides a singleton wrapper around the Linux inotify API for thread-safe file monitoring
- The system uses `inotify_init1(IN_NONBLOCK | IN_CLOEXEC)` to create a non-blocking, leak-proof file descriptor
- Watch lists are dynamically built from `Config::mgr()->getConfigPaths()`, with separate event masks for files (`IN_CLOSE_WRITE`) and directories (`IN_CREATE`, `IN_DELETE`, `IN_MOVED_*`)
- Changes propagate through callbacks registered via `setOnChange()`, enabling the Lua manager to invoke `reload()`
- `EventLoopManager` integrates the inotify descriptor into the main poll loop for efficient, event-driven architecture

## Frequently Asked Questions

### Can I disable configuration hot-reload in Hyprland?

Yes. Set the configuration option `misc:disable_autoreload` to true in your Hyprland configuration. When this option is enabled, `CConfigWatcher::update()` skips the watch list construction, and the compositor will not monitor files for changes or trigger automatic reloads.

### Does Hyprland follow symlinks when watching configuration files?

Yes. According to the implementation in [`src/config/shared/inotify/ConfigWatcher.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/config/shared/inotify/ConfigWatcher.cpp), the system resolves symlinks to their canonical targets before calling `inotify_add_watch()`. This ensures that changes to the actual file trigger reload events regardless of whether you access the configuration through a symlink or the direct path.

### What specific file events trigger a configuration reload?

Regular files trigger reloads only on `IN_CLOSE_WRITE` events, ensuring the compositor waits until the file handle is closed and the write is complete. Directories use a broader mask including `IN_CREATE`, `IN_DELETE`, and `IN_MOVED_*` events to detect when configuration files are added, removed, or renamed within watched directories.

### How does the compositor detect file changes without polling?

Hyprland integrates the inotify file descriptor into its main event loop via [`src/managers/eventLoop/EventLoopManager.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/managers/eventLoop/EventLoopManager.cpp). The descriptor is added to a `poll()` call alongside other I/O sources. When the kernel reports a file system event, `poll()` returns immediately and the event loop invokes `Config::watcher()->onInotifyEvent()`, providing efficient, kernel-driven notifications without CPU-intensive polling.