Skip to content

In short: Autorandr for Hyprland. Manage Hyprland configuration based on connected displays and power state.

License

Notifications You must be signed in to change notification settings

fiffeek/hyprdynamicmonitors

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hyprdynamicmonitors logo

HyprDynamicMonitors


An event-driven service that automatically manages Hyprland monitor configurations based on connected displays and power state.

Preview

demo

Documentation

Features

  • Event-driven architecture responding to monitor and power state changes in real-time
  • Interactive TUI for visual monitor configuration and profile management
  • Profile-based configuration with different settings for different monitor setups
  • Template support for dynamic configuration generation
  • Hot reloading: automatically detects and applies configuration changes without restart by watching config files (optional)
  • Configurable UPower queries for custom power management systems
  • Desktop notifications for configuration changes (optional)

Design Philosophy

HyprDynamicMonitors follows a fail-fast architecture designed for reliability and simplicity.

Reliability Through Restarts

The service intentionally fails quickly on critical issues rather than attempting complex recovery. This design expects the service to run under systemd or a wrapper script that provides automatic restarts. Since configuration is applied on startup, restarts ensure the service remains operational even after encountering errors.

Hot Reloading With Graceful Restart

For configuration changes, the service provides automatic hot reloading by watching configuration files. When hot reloading encounters issues, it gracefully falls back to the fail-fast behavior, prioritizing reliability over attempting risky recovery scenarios.

Hyprland-Native Integration

The service leverages Hyprland's native abstractions rather than working directly with Wayland protocols. It detects the desired configuration based on current monitor state and power supply, then either:

  • Generates a templated Hyprland config file at the specified destination
  • Or creates a symlink to a user-provided static configuration file

Hyprland automatically detects and applies these configuration changes (granted it's not explicitly turned off, if so you have to use the callbacks to hyprctl reload), ensuring seamless integration with the compositor's built-in configuration system.

Installation

Binary Release

Download the latest binary from GitHub releases:

# optionally override the destination directory, defaults to ~/.local/bin/
export DESTDIR="$HOME/.bin"
curl -o- https://raw.githubusercontent.com/fiffeek/hyprdynamicmonitors/refs/heads/main/scripts/install.sh | bash

AUR

For Arch Linux users, install from the AUR:

# Using your preferred AUR helper (replace 'aurHelper' with your choice)
aurHelper="yay"  # or paru, trizen, etc.
$aurHelper -S hyprdynamicmonitors-bin

# Or using makepkg:
git clone https://aur.archlinux.org/hyprdynamicmonitors-bin.git
cd hyprdynamicmonitors-bin
makepkg -si

Nix

For Nix and NixOS users:

# Run directly from GitHub
nix run github:fiffeek/hyprdynamicmonitors

# Or from specific tag/version (recommended)
nix run github:fiffeek/hyprdynamicmonitors/v1.0.0

# Install to profile
nix profile install github:fiffeek/hyprdynamicmonitors

Build from Source

Requires asdf to manage the Go toolchain:

# Build the binary (output goes to ./dest/)
make

# Install to custom location
make DESTDIR=$HOME/binaries install

# Uninstall from custom location
make DESTDIR=$HOME/binaries uninstall

# Install system-wide (may require sudo)
sudo make DESTDIR=/usr/bin install

Usage

Command Line

HyprDynamicMonitors is a service that automatically switches between predefined Hyprland monitor configuration profiles based on connected monitors and power state.

Usage:
  hyprdynamicmonitors [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  freeze      Freeze current monitor configuration as a new profile template
  help        Help about any command
  run         Run the monitor configuration service
  tui         Launch interactive TUI for monitor configuration
  validate    Validate configuration file

Flags:
      --config string             Path to configuration file (default "$HOME/.config/hyprdynamicmonitors/config.toml")
      --debug                     Enable debug logging
      --enable-json-logs-format   Enable structured logging
  -h, --help                      help for hyprdynamicmonitors
      --verbose                   Enable verbose logging
  -v, --version                   version for hyprdynamicmonitors

Use "hyprdynamicmonitors [command] --help" for more information about a command.

Run command

Run the HyprDynamicMonitors service to continuously monitor for display changes and automatically apply matching configuration profiles.

Usage:
  hyprdynamicmonitors run [flags]

Flags:
      --connect-to-session-bus    Connect to session bus instead of system bus for power events: https://wiki.archlinux.org/title/D-Bus. You can switch as long as you expose power line events in your user session bus.
      --disable-auto-hot-reload   Disable automatic hot reload (no file watchers)
      --disable-power-events      Disable power events (dbus)
      --dry-run                   Show what would be done without making changes
  -h, --help                      help for run
      --run-once                  Run once and exit immediately

Global Flags:
      --config string             Path to configuration file (default "$HOME/.config/hyprdynamicmonitors/config.toml")
      --debug                     Enable debug logging
      --enable-json-logs-format   Enable structured logging
      --verbose                   Enable verbose logging

Validate command

Validate the configuration file for syntax errors and logical consistency.

Usage:
  hyprdynamicmonitors validate [flags]

Flags:
  -h, --help   help for validate

Global Flags:
      --config string             Path to configuration file (default "$HOME/.config/hyprdynamicmonitors/config.toml")
      --debug                     Enable debug logging
      --enable-json-logs-format   Enable structured logging
      --verbose                   Enable verbose logging

Validate configuration:

# Validate default config file
hyprdynamicmonitors validate

# Validate specific config file
hyprdynamicmonitors --config /path/to/config.toml validate

# Validate with debug output
hyprdynamicmonitors --debug validate

Freeze command

Freeze the current Hyprland monitor configuration and save it as a new profile template.

This command captures your current monitor setup and creates two artifacts:
1. A Go template file containing the Hyprland configuration
2. A new profile entry in your configuration file that references this template

TEMPLATE FILE:
The Go template will be saved to hyprconfigs/{profile-name}.go.tmpl by default, or to a
custom location specified with --config-file-location. This template can be edited after
creation to customize the configuration.

PROFILE ENTRY:
A new profile with the specified name will be appended to your configuration file. The
profile will automatically require monitors by description (not name) to ensure better
portability across different systems.

PREREQUISITES:
- The profile name must not already exist in your configuration (it will be checked)
- The template file location must not exist (it will be created)
- Hyprland must be running with a valid monitor configuration

This is useful for quickly creating new profiles based on your current working setup.

Usage:
  hyprdynamicmonitors freeze [flags]

Flags:
      --config-file-location string   Where to put the generated config file template (defaults to hyprconfigs/$PROFILE_NAME.go.tmpl)
  -h, --help                          help for freeze
      --profile-name string           What profile name to set the frozen profile to.

Global Flags:
      --config string             Path to configuration file (default "$HOME/.config/hyprdynamicmonitors/config.toml")
      --debug                     Enable debug logging
      --enable-json-logs-format   Enable structured logging
      --verbose                   Enable verbose logging

TUI command

Launch an interactive terminal-based TUI for managing monitor configurations.

Usage:
  hyprdynamicmonitors tui [flags]

Flags:
      --connect-to-session-bus          Connect to session bus instead of system bus for power events: https://wiki.archlinux.org/title/D-Bus. You can switch as long as you expose power line events in your user session bus.
      --disable-power-events            Disable power events (dbus)
  -h, --help                            help for tui
      --hypr-monitors-override string   When used it fill parse the given file as hyprland monitors spec, used for testing.

Global Flags:
      --config string             Path to configuration file (default "$HOME/.config/hyprdynamicmonitors/config.toml")
      --debug                     Enable debug logging
      --enable-json-logs-format   Enable structured logging
      --verbose                   Enable verbose logging

TUI has the same flags as run. It can be used without the running daemon for ad-hoc changes. When the config is not passed or invalid you will be unable to persist the configuration in hyprdynamicmonitors config. You can however experiment with the monitors and apply the hypr configuration. Refer to the TUI docs for more details.

Minimal Example

This example sets up basic laptop-only monitor configuration. First, check your display name with hyprctl monitors.

Configuration file (~/.config/hyprdynamicmonitors/config.toml):

[general]
destination = "$HOME/.config/hypr/monitors.conf"

[power_events]
[power_events.dbus_query_object]
path = "/org/freedesktop/UPower/devices/line_power_ACAD"

[[power_events.dbus_signal_match_rules]]
object_path = "/org/freedesktop/UPower/devices/line_power_ACAD"

[profiles.laptop_only]
config_file = "hyprconfigs/laptop.conf"
config_file_type = "static"

[[profiles.laptop_only.conditions.required_monitors]]
name = "eDP-1"  # Replace with your display name from hyprctl monitors

Monitor configuration (~/.config/hyprdynamicmonitors/hyprconfigs/laptop.conf):

monitor=eDP-1,[email protected],0x0,2.0,vrr,1

Run the service using systemd (recommended - see Running with systemd) or add to Hyprland config:

exec-once = hyprdynamicmonitors

Ensure you source the linked destination config file (in ~/.config/hypr/hyprland.conf):

source = ~/.config/hypr/monitors.conf

How it works: When only the eDP-1 monitor is detected, the service symlinks hyprconfigs/laptop.conf to $HOME/.config/hypr/monitors.conf, and Hyprland automatically applies the new configuration.

Examples

See examples/ directory for complete configuration examples including basic setups and comprehensive configurations with all features. Most notably, examples/full/config.toml contains all available configuration options reference. An example for disabling a monitor depending on the detected setup is in examples/disable-monitors.

Runtime requirements

  • Hyprland with IPC support
  • UPower (optional, for power state monitoring)
  • Read-only access to system D-Bus (optional for power state monitoring; should already be your default)
  • Write access to system D-Bus for notifications (optional; should already be your default)

Configuration

Monitor Matching

Profiles match monitors based on their properties. You can match by:

  • Name: The monitor's connector name (e.g., eDP-1, DP-1)
  • Description: The monitor's model/manufacturer string

You also optionally set (required for templating):

  • Tags: Custom labels you assign to monitors for easier reference
[[profiles.laptop_only.conditions.required_monitors]]
name = "eDP-1"  # Match by connector name

[[profiles.external_4k.conditions.required_monitors]]
description = "Dell U2720Q"  # Match by monitor model

[[profiles.dual_setup.conditions.required_monitors]]
name = "eDP-1"
monitor_tag = "laptop"  # Assign a tag for template use

Use hyprctl monitors to see available monitors and their properties. To understand scoring and profile matching more see examples/scoring.

Profile Selection: When multiple profiles have equal scores, the last profile defined in the TOML configuration file is selected.

Configuration File Types

  • Static: Creates symlinks to existing configuration files
  • Template: Processes Go templates with dynamic monitor and power state data

Template Variables

.PowerState         // "AC" or "BAT"
.Monitors           // Array of all connected monitors returned by `hyprctl monitors`
.ExtraMonitors      // Array of connected monitors not defined in the profile
.RequiredMonitors   // Array of connected monitors defined in the profile
.MonitorsByTag      // Map of tagged monitors (monitor_tag -> monitor)

Static Template Values

You can define custom static values that are available in templates. These can be defined globally or per-profile:

Global static values (available in all templates):

[static_template_values]
default_vrr = "1"
default_res = "2880x1920"
refresh_rate_high = "120.00000"
refresh_rate_low = "60.00000"

Per-profile static values (override global values):

[profiles.laptop_only.static_template_values]
default_vrr = "0"        # Override global value
battery_scaling = "1.5"  # Profile-specific value

Template usage:

# Use static values in templates
monitor=eDP-1,{{.default_res}}@{{if isOnAC}}{{.refresh_rate_high}}{{else}}{{.refresh_rate_low}}{{end}},0x0,1,vrr,{{.default_vrr}}

To understand template variables more see examples/template-variables.

Template Functions

isOnBattery         // Returns true if on battery power
isOnAC              // Returns true if on AC power
powerState          // Returns current power state string

Fallback Profile

When no regular profile matches the current monitor setup and power state, you can define a fallback profile that will be used as a last resort. This is particularly useful for handling unexpected monitor configurations or providing a safe default configuration.

# Regular profiles with specific conditions
[profiles.laptop_only]
config_file = "hyprconfigs/laptop.conf"

[[profiles.laptop_only.conditions.required_monitors]]
name = "eDP-1"

[profiles.dual_monitor]
config_file = "hyprconfigs/dual.conf"

[[profiles.dual_monitor.conditions.required_monitors]]
name = "eDP-1"

[[profiles.dual_monitor.conditions.required_monitors]]
description = "External Monitor"

# Fallback profile - used when no other profile matches
[fallback_profile]
config_file = "hyprconfigs/fallback.conf"
config_file_type = "static"

The fallback profile in hyprconfigs/fallback.conf might contain a safe default configuration:

# Generic fallback: configure all connected monitors with preferred settings
monitor=,preferred,auto,1

Key characteristics of fallback profiles:

  • Cannot define conditions (since they're used when no conditions match)
  • Supports both static and template configuration types
  • Only used when no regular profile matches the current setup
  • Regular matching profiles always take precedence over the fallback

To understand fallback profiles more see examples/fallback.

User Callbacks (Exec Commands)

HyprDynamicMonitors supports custom user commands that are executed before and after profile configuration changes. These commands can be defined globally or per-profile, allowing for custom actions like notifications, script execution, or system adjustments.

[general]
# Global exec commands - run for all profile changes
pre_apply_exec = "notify-send 'HyprDynamicMonitors' 'Switching monitor profile...'"
post_apply_exec = "notify-send 'HyprDynamicMonitors' 'Profile applied successfully'"

# Profile-specific exec commands override global settings
[profiles.gaming_setup]
config_file = "hyprconfigs/gaming.conf"
pre_apply_exec = "notify-send 'Gaming Mode' 'Activating high-performance profile'"
post_apply_exec = "/usr/local/bin/gaming-mode-on.sh"

Key characteristics:

  • pre_apply_exec: Executed before the new monitor configuration is applied
  • post_apply_exec: Executed after the new monitor configuration is successfully applied
  • Profile-specific commands override global commands for that profile
  • Failure handling: If exec commands fail, the service continues operating normally (no interruption to monitor configuration)
  • Shell execution: Commands are executed through bash -c, supporting shell features like pipes and environment variables

To understand callbacks more see examples/callbacks.

Notifications

HyprDynamicMonitors can show desktop notifications when configuration changes occur. Notifications are sent via D-Bus using the standard org.freedesktop.Notifications interface.

[notifications]
disabled = false      # Enable/disable notifications (default: false)
timeout_ms = 10000   # Notification timeout in milliseconds (default: 10000)

To disable notifications completely:

[notifications]
disabled = true

To show brief notifications:

[notifications]
timeout_ms = 3000    # 3 seconds

Hyprland Integration

Add to your Hyprland config (assuming ~/.config/hypr/monitors.conf is your destination):

source = ~/.config/hypr/monitors.conf

Important: Do not set disable_autoreload = true in Hyprland settings, or you'll have to reload Hyprland manually after configuration changes.

Power Events

Power state monitoring uses D-Bus to listen for UPower events. This feature is optional and can be completely disabled.

Disabling power events

To disable power state monitoring entirely, start with the flag:

hyprdynamicmonitors --disable-power-events

When disabled, the system defaults to AC power state. No power events will be delivered, no dbus connection will be made.

Default power event configuration

By default, the service listens for D-Bus signals:

  • Signal: org.freedesktop.DBus.Properties.PropertiesChanged
  • Interface: org.freedesktop.DBus.Properties
  • Member: PropertiesChanged
  • Path: /org/freedesktop/UPower/devices/line_power_ACAD

These defaults can be overridden in the configuration (or left empty).

You can monitor these events with:

gdbus monitor -y -d org.freedesktop.UPower | grep -E "PropertiesChanged|Device(Added|Removed)"

Example output:

/org/freedesktop/UPower/devices/line_power_ACAD: org.freedesktop.DBus.Properties.PropertiesChanged ('org.freedesktop.UPower.Device', {'UpdateTime': <uint64 1756242314>, 'Online': <true>}, @as [])
# Format: PATH INTERFACE.MEMBER (INFO)

Querying

On each event, the current power status is queried. Here's the equivalent command:

dbus-send --system --print-reply --dest=org.freedesktop.UPower \
  /org/freedesktop/UPower/devices/line_power_ACAD \
  org.freedesktop.DBus.Properties.Get string:org.freedesktop.UPower.Device string:Online

Receive Filters

You can filter received events by name. By default, only org.freedesktop.DBus.Properties.PropertiesChanged is matched. This prevents noisy signals. Additionally, power status changes are only propagated when the state actually changes, and template/link replacement only occurs when file contents differ.

Custom D-Bus Configuration

You can customize which D-Bus signals to monitor:

[power_events]
# Custom D-Bus signal match rules
[[power_events.dbus_signal_match_rules]]
interface = "org.freedesktop.DBus.Properties"
member = "PropertiesChanged"
object_path = "/org/freedesktop/UPower/devices/line_power_ACAD"

# Custom signal filters
[[power_events.dbus_signal_receive_filters]]
name = "org.freedesktop.DBus.Properties.PropertiesChanged"

# Custom UPower query for non-standard power managers
[power_events.dbus_query_object]
destination = "org.freedesktop.UPower"
path = "/org/freedesktop/UPower"
method = "org.freedesktop.DBus.Properties.Get"
expected_discharging_value = "true"

[[power_events.dbus_query_object.args]]
arg = "org.freedesktop.UPower"

[[power_events.dbus_query_object.args]]
arg = "OnBattery"

The above query settings are equivalent to:

dbus-send --system --print-reply --dest=org.freedesktop.UPower \
  /org/freedesktop/UPower org.freedesktop.DBus.Properties.Get string:org.freedesktop.UPower string:OnBattery

Note: This particular query is not recommended for production use!

Leave Empty Token

To explicitly remove default values from D-Bus match rules, use the leaveEmptyToken:

[[power_events.dbus_signal_match_rules]]
interface = "leaveEmptyToken"  # Removes interface match
member = "PropertiesChanged"
object_path = "/custom/path"

Signals

  • SIGHUP: Instantly reloads configuration and reapplies monitor setup
  • SIGUSR1: Reapplies the monitor setup without reloading the service configuration
  • SIGTERM/SIGINT: Graceful shutdown

You can trigger an instant reload with:

kill -SIGHUP $(pidof hyprdynamicmonitors)
# or just restart if running as systemd service
# since the configuration is applied on the startup
systemctl --user reload hyprdynamicmonitors

Hot Reloading

HyprDynamicMonitors automatically watches for changes to configuration files and applies them without requiring a restart. This includes:

  • Configuration file changes: Modifications to config.toml are detected and applied automatically
  • Profile config changes: Updates to individual profile configuration files (both static and template files)
  • New profile files: Adding new configuration files referenced by profiles

The service uses file system watching with debounced updates to avoid excessive reloading during rapid changes (e.g., when editors create temporary files).

Hot reload behavior:

  • Configuration changes are debounced by default (1000ms delay; configurable)
  • Only actual content changes trigger reloads (identical content is ignored)
  • Service continues running normally during hot reloads

Disabling hot reload:

hyprdynamicmonitors --disable-auto-hot-reload

When disabled, you can still use SIGHUP signal for manual reloading.

TUI

A tui is available for ad-hoc changes as well as persisting the configuration under hyprdynamicmonitors. Refer to the tui docs for more details.

Tests

Live Testing

Live tested on:

  • Hyprland v0.50.1
  • UPower v1.90.9

You can see my configuration here.

Integration Testing

All features should be covered by integration tests. Run make test/integration locally to execute end-to-end CLI tests that build the binary and verify expected outputs. Test fixtures can be regenerated using make test/integration/regenerate.

Running with systemd

For production use, it's recommended to run HyprDynamicMonitors as a systemd user service. This ensures automatic restart on failures and proper integration with session management.

Important: Ensure you're properly pushing environment variables to systemd.

Hyprland under systemd

If you run Hyprland under systemd, setup is straightforward. Create ~/.config/systemd/user/hyprdynamicmonitors.service:

[Unit]
Description=HyprDynamicMonitors - Dynamic monitor configuration for Hyprland
After=graphical-session.target
Wants=graphical-session.target
PartOf=hyprland-session.target

[Service]
Type=exec
ExecStart=/usr/bin/hyprdynamicmonitors
Restart=on-failure
RestartSec=5

[Install]
WantedBy=hyprland-session.target

Enable and start the service:

systemctl --user daemon-reload
systemctl --user enable hyprdynamicmonitors
systemctl --user start hyprdynamicmonitors

Run on boot and let restarts do the job

You can essentially just run it on boot and add restarts, e.g.:

[Unit]
Description=HyprDynamicMonitors - Dynamic monitor configuration for Hyprland
After=default.target

[Service]
Type=exec
ExecStart=/usr/bin/hyprdynamicmonitors
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target

It will keep failing until Hyprland is ready/launched and environment variables are propagated.

Custom systemd target

You can also add a custom systemd target that would be started by Hyprland, e.g.

exec-once = systemctl --user start hyprland-custom-session.target
bind = $mainMod, X, exec, systemctl --user stop hyprland-session.target

Then:

❯ cat ~/.config/systemd/user/hyprland-custom-session.target
[Unit]
Description=A target for other services when hyprland becomes ready
After=graphical-session-pre.target
Wants=graphical-session-pre.target
BindsTo=graphical-session.target

And:

❯ cat ~/.config/systemd/user/hyprdynamicmonitors.service
[Unit]
Description=Run hyprdynamicmonitors daemon
After=hyprland-custom-session.target
After=dbus.socket
Requires=dbus.socket
PartOf=hyprland-custom-session.target

[Service]
Type=exec
ExecStart=/usr/bin/hyprdynamicmonitors
Restart=on-failure
RestartSec=5


[Install]
WantedBy=hyprland-custom-session.target

Alternative: Wrapper script

If you prefer a wrapper script approach, create a simple restart loop:

#!/bin/bash
while true; do
    /usr/bin/hyprdynamicmonitors
    echo "HyprDynamicMonitors exited with code $?, restarting in 5 seconds..."
    sleep 5
done

Then execute it from Hyprland:

exec-once = /path/to/the/script.sh

Development

Setup Development Environment

Set up the complete development environment with all dependencies:

make dev

This installs:

  • asdf version manager with required tool versions
  • Go toolchain and dependencies
  • Python virtual environment for pre-commit hooks
  • Node.js dependencies for commit linting
  • Pre-commit hooks configuration
  • Documentation generation tools

Development Commands

Code quality and testing:

make fmt          # Format code and tidy modules
make lint         # Run linting checks
make test         # Run all tests (unit + integration)
make pre-push     # Run complete CI pipeline (fmt + lint + test)

Testing specific areas:

make test/unit                    # Run only unit tests
make test/integration             # Run only integration tests
make test/integration/regenerate  # Regenerate test fixtures

Running selected tests (runs with -debug for log output):

# Run subset of integration tests
make TEST_SELECTOR=Test__Run_Binary/power_events_triggers test/integration/selected

# Run subset of unit tests
make TEST_SELECTOR="TestIPC_Run/happy_path$" PACKAGE_SELECTOR=hypr/... test/unit/selected

Building:

make release/local    # Build release binaries for all platforms
make build/test       # Build test binary for integration tests

Documentation:

make help/generate    # Generate help documentation from binary

Development Workflow

  1. Initial setup: make dev (one-time setup)
  2. Development cycle: Make changes, then run make pre-push before committing
  3. Testing: Use make test for full test suite, or specific test targets for focused testing
  4. Pre-commit hooks: Automatically run on commit (installed by make dev)

Release Candidates

Release candidates are published for testing new features before stable releases:

  • GitHub Releases: RC versions are marked as pre-releases on GitHub (e.g., v0.2.0-rc1)
  • AUR Package: Available as separate hyprdynamicmonitors-rc-bin package alongside the stable hyprdynamicmonitors-bin
  • Binary Name: RC builds use hyprdynamicmonitors-rc to avoid conflicts with stable installations
  • Parallel Installation: Both stable and RC versions can be installed simultaneously for testing

To install the RC version from AUR:

yay -S hyprdynamicmonitors-rc-bin

FAQ

How do I assign workspaces to specific monitors?

You can include workspace assignments directly in your profile configuration files. HyprDynamicMonitors generates or links Hyprland configuration files, so any valid Hyprland configuration syntax works.

Example with static configuration:

In your profile configuration file (e.g., hyprconfigs/triple-monitor.conf):

# Monitor configuration
monitor=desc:BOE 0x0C6B,preferred,0x0,1
monitor=desc:LG Electronics LG ULTRAWIDE 408NTVSDT319,preferred,1920x0,1
monitor=desc:Dell Inc. DELL P2222H B2RY1H3,preferred,3840x0,1

# Workspace assignments
workspace=1,monitor:desc:BOE 0x0C6B,default:true
workspace=2,monitor:desc:LG Electronics LG ULTRAWIDE 408NTVSDT319,default:true
workspace=3,monitor:desc:Dell Inc. DELL P2222H B2RY1H3,default:true

Example with templates:

In hyprconfigs/dual-monitor.go.tmpl:

{{- $laptop := index .MonitorsByTag "laptop" -}}
{{- $external := index .MonitorsByTag "external" -}}
monitor={{$laptop.Name}},preferred,0x0,1
monitor={{$external.Name}},preferred,1920x0,1

workspace=1,monitor:{{$laptop.Name}},default:true
workspace=2,monitor:{{$external.Name}},default:true
workspace=3,monitor:{{$external.Name}}

You can also use the TUI (hyprdynamicmonitors tui) to create and edit profiles interactively.

How do I use the TUI to create or edit profiles?

The TUI provides an interactive way to configure monitors and manage profiles. The workflow involves two main views:

Step 1: Configure monitors in the Monitors view

  • Adjust your monitor settings (resolution, position, scale, etc.)
  • Apply the configuration with a to test it in Hyprland
  • Once satisfied with the layout, proceed to save it as a profile

Step 2: Switch to the HDM Profile view

  • Press Tab to switch between Monitors view and HDM Profile view
  • Choose to either:
    • Edit existing profile: Press a to apply the current monitor configuration to the selected profile
    • Create new profile: Press n to create a new profile with the current monitor configuration

Understanding the generated configuration:

The TUI places generated configuration between comment markers in your profile config files:

# <<<<< TUI AUTO START
monitor=eDP-1,2880x1920@120,0x0,1.5
monitor=DP-1,3840x2160@60,2880x0,1
# <<<<< TUI AUTO END

You can add additional settings outside these markers, and they will be preserved across TUI updates. This is useful for:

  • Overriding specific settings
  • Adding workspace assignments
  • Including additional Hyprland configuration

Manual editing:

From the HDM Profile view, you can manually edit files using your $EDITOR:

  • Edit the profile configuration file (the Hyprland config)
  • Edit the HDM configuration file (the TOML config)

This allows you to fine-tune settings or add advanced configuration that the TUI doesn't directly support.

For more details, see the TUI documentation.

How to disable all monitors not defined in the profile required monitors?

See this example, you can use .ExtraMonitors variable in the template:

{{- range .ExtraMonitors}}
monitor={{.Name}},disable
{{- end }}

Can I use the TUI without running the hyprdynamicmonitors daemon?

Yes, the TUI can be used standalone without the daemon running. However, functionality is limited:

What works without the daemon:

  • Experimenting with monitor configurations in the Monitors view
  • Applying configurations manually and ephemerally with a (changes are temporary until Hyprland restarts)
  • Testing different layouts and settings interactively

What requires a valid configuration:

  • Saving profiles to HyprDynamicMonitors configuration (HDM Profile view features)
  • Persisting monitor configurations that automatically apply on monitor connect/disconnect
  • Power state-based profile switching

Typical standalone usage:

# Use TUI to experiment with monitor layouts
hyprdynamicmonitors tui

# Configure monitors interactively, apply with 'a' to test
# Changes are applied to Hyprland but not persisted

If you want to persist configurations created in the TUI, you need a valid HyprDynamicMonitors config file. You can start with a minimal config and build from there using the TUI's profile creation features.

Why are my comments becoming part of the configuration in templates?

This is due to Go template whitespace trimming behavior. When you use -}}, it removes all whitespace (including newlines) after the template action.

Problem example:

# auto generated by hyprdynamicmonitors

{{- $laptop := index .MonitorsByTag "LaptopMonitor" -}}
{{- $external := index .MonitorsByTag "ExternalMonitor" -}}

monitor={{$laptop.Name}}, disable
# change to your preferred external monitor config
monitor={{$external.Name}},preferred,0x0,1

The -}} after the variable definitions removes the newline, causing monitor={{$laptop.Name}}, disable to be pulled up into the comment above it, creating one long comment line.

Solution 1: Remove trailing dash from the last template action

# auto generated by hyprdynamicmonitors

{{- $laptop := index .MonitorsByTag "LaptopMonitor" -}}
{{- $external := index .MonitorsByTag "ExternalMonitor" }}

monitor={{$laptop.Name}}, disable
# change to your preferred external monitor config
monitor={{$external.Name}},preferred,0x0,1

Solution 2: Define all variables at the start, then add content

{{- $laptop := index .MonitorsByTag "LaptopMonitor" -}}
{{- $external := index .MonitorsByTag "ExternalMonitor" -}}


# auto generated by hyprdynamicmonitors
# change to your preferred external monitor config
monitor={{$laptop.Name}}, disable
monitor={{$external.Name}},preferred,0x0,1

Key points:

  • -}} trims all whitespace after the action, including newlines
  • This can cause the next line to merge with whatever comes before the template action
  • Use }} (without trailing dash) on the last variable definition before your config
  • Or group all variable definitions at the top, separated from config with blank lines

Alternative software

Most similar tools are more generic, working with any Wayland compositor. In contrast, hyprdynamicmonitors is specifically designed for Hyprland (using its IPC) but provides several advantages:

Advantages of HyprDynamicMonitors:

  • Interactive TUI: Built-in terminal interface for visual monitor configuration and profile management
  • Full configuration control: Instead of introducing another configuration format, you work directly with Hyprland's native config syntax
  • Template system: Dynamic configuration generation based on connected monitors and power state
  • Power state awareness: Built-in AC/battery detection for laptop users
  • Event-driven automation: Automatically responds to monitor connect/disconnect events

Trade-offs:

  • Hyprland-specific (not generic Wayland)
  • Requires systemd or wrapper script for production use (fail-fast design)

Similar Tools:

  • kanshi - Generic Wayland output management
  • shikane - Another Wayland output manager
  • nwg-displays - GUI-based display configuration tool for Sway/Hyprland
  • hyprmon - TUI-based display configuration tool for Hyprland

hyprdynamicmonitors can be used side-by-side with nwg-displays:

  • Tweak the configuration in nwg-displays
  • Let hyprdynamicmonitors automatically write/link it to your Hyprland configuration directory

About

In short: Autorandr for Hyprland. Manage Hyprland configuration based on connected displays and power state.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published