How Hyprland's Configuration Hot-Reload System Works with inotify
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.
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_WRITEto trigger only after the file handle is closed, ensuring complete writes - Directories receive a broader mask including
IN_CREATE,IN_DELETE, andIN_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:
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. 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:
#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:
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 invokereload() EventLoopManagerintegrates 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, 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. 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.
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 →