The problem
After switching to Arch Linux, I decided to give Wayland another try (my last setup was on Ubuntu with X11). For the compositor, I landed on Hyprland. Its excellent documentation and the sheer amount of public configs made it an easy choice.
But I quickly hit a roadblock: I missed one of my most essential X11 tools — autorandr. Since I regularly switch between laptop-only, home docking, and office setups — and I care a lot about battery life — I needed something that:
- Automatically applies the right monitor layout when displays change
- Is aware of power state (AC vs battery)
- Lets me keep using Hyprland’s native monitor syntax
Existing software
Before deciding to write my own tool, I experimented with several existing options:
- [1] kanshi - Generic Wayland output management
- [2] shikane - Another Wayland output manager
- [3] nwg-displays - GUI-based display configuration tool for Sway/Hyprland
- [4] hyprmon - TUI-based display configuration tool for Hyprland
- [5] pyprland's monitors plugin - Hyprland monitor management via IPC
The first two ([1], [2]) are essentially autorandr
replacements — but they require you to define a completely separate configuration format that's then translated to Wayland protocols. Since Hyprland already supports a powerful native monitor syntax, I wasn't keen on losing that flexibility.
The next two ([3], [4]) provide a way to visually configure and save monitor profiles — great for one-time setups, but not automatic. I wanted something that reacts instantly when monitors are plugged in or removed, without me having to run commands.
pyprland
’s monitors plugin ([5]) came the closest to what I wanted. It listens to Hyprland events and issues hyprctl
commands. But it had three drawbacks for my use case:
- Its configuration format is transformed into
hyprctl
commands, meaning upstream code changes are required for new options. - There’s no simple, inspectable config file to tweak, just plugin settings.
- No power state awareness.
After testing each option, here’s how they stacked up against what I actually needed:
Feature | kanshi | shikane | nwg-displays | hyprmon | pyprland | needed |
---|---|---|---|---|---|---|
Visual way to adjust monitors | ❌ | ❌ | ✅ | ✅ | ❌ | ❓ |
Automatic config application | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
Power state awareness | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Outputs an editable file | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
User control over generated file | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Supports Hyprland | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Supports Wayland (generic) | ✅ | ✅ | ✅ | ❌ | ❌ | ❓ |
Why I Built My Own
Extending pyprland
would have meant dealing with power-awareness, rewriting its config parser, and restructuring event handling — essentially a partial rewrite.
Since I wanted full control over configuration, a minimal architecture, and something that gracefully survives restarts, suspend/resume cycles, and enabling/disabling a monitor, I decided to build a dedicated tool instead.
The Solution: HyprDynamicMonitors
The result is HyprDynamicMonitors: a power-aware, event-driven monitor configuration manager that embraces Hyprland's native config syntax instead of abstracting it away. Check out the GitHub repository for more examples and detailed usage instructions.
Core Design Principles
Event-driven, not polling
Listens to Hyprland IPC and D-Bus events; near-zero CPU usage when idle.
Native Hyprland syntax
No new DSL to learn; just write normal Hyprland config with Go template logic for dynamic behavior:
# Adjust refresh rate based on power state
monitor=eDP-1,2880x1920@{{if isOnAC}}120.00000{{else}}60.00000{{end}},0x0,2.0,vrr,1
{{if isOnBattery}}
# Disable animations when on battery
animations {
enabled = false
}
{{end}}
Profile-based matching
Each monitor setup gets its own profile with clear matching rules. When a profile matches the current environment, its config is applied automatically:
[profiles.laptop_only]
config_file = "laptop.conf"
[[profiles.laptop_only.conditions.required_monitors]]
name = "eDP-1"
[profiles.dual_4k]
config_file = "dual-4k.conf"
[[profiles.dual_4k.conditions.required_monitors]]
name = "eDP-1"
[[profiles.dual_4k.conditions.required_monitors]]
description = "Dell U2720Q"
Fail-fast reliability
If something goes wrong, the service exits immediately rather than continuing in a broken state. Systemd
then restarts it and re-applies the correct configuration; no silent failures.
Design decisions
- Go templates were chosen for their simplicity and familiarity to anyone who's used
Hugo
,Helm
, orKubernetes
manifests. - File generation over
hyprctl
commands gives you visibility and control. You can inspect, tweak, and version your configs. - No GUI/TUI by design tools like
hyprmon
andnwg-displays
already very good for visual configuration. You can use them alongsideHyprDynamicMonitors
to define the setup visually, then have it applied automatically.
What makes it different
- Full Hyprland integration: Uses native syntax, so you can inspect, debug, and even version-control the generated configs.
- Power state awareness: Automatically adapts refresh rates, animations, and layouts based on whether you're on AC power or battery.
- Hot reloading: Configuration changes are applied automatically without restarting the service.
- Minimal resource usage: No polling, no background loops — it's entirely event-driven.
Results and reflection
After using HyprDynamicMonitors for several weeks in my daily workflow, it's solved the core problems I had with existing solutions:
- Laptop mobility: Plugging into my desk setup at home or work automatically switches to the appropriate multi-monitor configuration. Unplugging immediately falls back to laptop-only mode with battery-optimized settings.
- Power efficiency: Battery profiles automatically reduce refresh rates from 120Hz to 60Hz and disable resource-intensive visual effects, extending battery life without any manual intervention.
- Maintainable configuration: Since it uses standard Hyprland syntax, configuration changes don't require learning tool-specific formats. Templates make it easy to share common patterns across profiles while keeping each one readable.
- Reliability: The fail-fast design with systemd restarts has proven robust over weeks of daily use (mostly due to user errors on the configuration side). The service handles Hyprland restarts, suspend/resume cycles, and various edge cases gracefully.
Was it worth building from scratch?
The combination of power state awareness, native Hyprland integration, and template-based configuration would have been difficult to achieve by extending existing tools without fundamental architectural changes.
This approach does come with trade-offs, though. It's Hyprland-specific (unlike kanshi
or shikane
which work with any Wayland compositor), and the fail-fast design requires proper service management setup.
For users with simpler needs or those using other compositors, existing tools are likely sufficient. But for complex laptop setups requiring power-aware, dynamic monitor configuration with full control over the resulting Hyprland config, building a purpose-built solution proved to be the right choice.
Plus, it gave me a great excuse to dive deeper into Go
, D-Bus
, AUR
, and Hyprland
's internals — which was fun!
Feedback
HyprDynamicMonitors ended up solving every pain point I had: seamless profile switching, battery-optimized refresh rates, and solid reliability. All while letting me keep full control over my Hyprland config.
If you have a laptop setup that moves between multiple monitor configurations, give it a try!
And if you run into issues or have ideas for improvement, open an issue or email me — I’d love to hear how it works on your setup.