#monitor-display #wayland #output #wlroots #display

wlx_monitors

Wayland output manager using wlr-output-management protocol

9 releases

Uses new Rust 2024

0.1.8 Feb 23, 2026
0.1.7 Feb 19, 2026

#320 in Hardware support


Used in 2 crates

MIT license

48KB
854 lines

wlx_monitors

A Rust library for detecting and managing display outputs on Wayland using the wlr-output-management protocol.

Crates.io Downloads Documentation License: MIT

What is this?

wlx_monitors provides a safe, idiomatic Rust interface to:

  • Detect connected monitors and their properties (resolution, refresh rate, position, scale)
  • Monitor for display hotplug events (monitor connected/disconnected)
  • Control display outputs (enable/disable, change resolution/refresh rate, scale, and transform/rotation)

Works with wlroots-based Wayland compositors (Sway, Hyprland, River, dwl, etc.) that implement the zwlr_output_manager_v1 protocol.

Quick Start

Add to your Cargo.toml:

[dependencies]
wlx_monitors = "0.1.8"

Basic usage:

use wlx_monitors::{WlMonitorManager, WlMonitorEvent};
use std::sync::mpsc;

fn main() {
    // Create channels for event communication
    let (event_tx, event_rx) = mpsc::sync_channel(16);
    let (action_tx, action_rx) = mpsc::sync_channel(16);

    // Connect to Wayland
    let (manager, event_queue) = WlMonitorManager::new_connection(
        event_tx,
        action_rx
    ).expect("Failed to connect to Wayland");

    // Run the event loop in a separate thread
    std::thread::spawn(move || {
        manager.run(event_queue).expect("Event loop error");
    });

    // Process monitor events
    while let Ok(event) = event_rx.recv() {
        match event {
            WlMonitorEvent::InitialState(monitors) => {
                println!("Detected {} monitors", monitors.len());
                for monitor in monitors {
                    println!("  {} - {}x{}",
                        monitor.name,
                        monitor.resolution.width,
                        monitor.resolution.height
                    );
                }
            }
            WlMonitorEvent::Changed(monitor) => {
                println!("Monitor {} changed", monitor.name);
            }
            WlMonitorEvent::Removed { name, .. } => {
                println!("Monitor {} disconnected", name);
            }
            WlMonitorEvent::ActionFailed { action, reason } => {
                eprintln!("Action {:?} failed: {}", action, reason);
            }
        }
    }
}

Run the included example:

cargo run --example monitor_info

Architecture

This library uses a channel-based event loop pattern:

Events (Wayland → Your App)

The library sends events through an MPSC channel:

  • WlMonitorEvent::InitialState(Vec<WlMonitor>) - Sent once with all currently connected monitors
  • WlMonitorEvent::Changed(Box<WlMonitor>) - Sent when a monitor's properties change
  • WlMonitorEvent::Removed { id, name } - Sent when a monitor is disconnected
  • WlMonitorEvent::ActionFailed { action, reason } - Sent when an action fails (e.g., invalid mode)

Actions (Your App → Wayland)

Send control actions through another MPSC channel:

  • WlMonitorAction::Toggle { name, mode, Position } - Enable/disable a monitor by name. The mode: Option<(i32, i32, i32)> lets users optionally specify a custom (width, height, refresh_rate) when toggling a monitor back on. If None, the smart mode resolution kicks in (last mode > preferred > first available). The position: Option<(i32, i32)> let's you specify a custom position (pos_x, pos_y) for your monitor when turning it on, If None it will by default to (0,0).
  • WlMonitorAction::SwitchMode { name, width, height, refresh_rate } - Change a monitor's mode
  • WlMonitorAction::SetScale { name, scale } - Set a monitor's scale factor (must be > 0, e.g., 1.0, 1.5, 2.0)
  • WlMonitorAction::SetTransform { name, transform } - Set a monitor's rotation/orientation (Normal, Rotate90, Rotate180, Rotate270, Flipped, etc.)
  • WlMonitorAction::SetPosition { name, x, y } - Set a monitor's position in the global coordinate space

Threading Model

┌─────────────────┐     events      ┌──────────────────┐
│  Wayland Server │ ───────────────>│   Your App       │
│  (Compositor)   │                 │  (Main Thread)   │
└─────────────────┘                 └──────────────────┘
         ^                                    │
         │                                   │
         │          actions                   │
         └────────────────────────────────────┘

┌─────────────────┐
│ Event Loop      │
│ (Separate       │
│  Thread)        │
└─────────────────┘

API Overview

Core Types

  • WlMonitorManager - Main entry point. Manages the Wayland connection and event loop.
  • WlMonitor - Represents a connected display with properties (name, resolution, modes, etc.)
  • WlMonitorMode - A display mode (resolution + refresh rate)
  • WlResolution / WlPosition - Basic geometry types

Events

pub enum WlMonitorEvent {
    InitialState(Vec<WlMonitor>),           // All monitors at startup
    Changed(Box<WlMonitor>),                // Monitor properties changed
    Removed { id: ObjectId, name: String }, // Monitor disconnected
    ActionFailed { action: ActionKind, reason: String }, // Action failed
}

Actions

pub enum WlMonitorAction {
    Toggle { name: String, mode: Option<(i32, i32, i32)>, position: Option<(i32, i32)> }, // On/off with optional custom mode and position
    SwitchMode { name: String, width: i32, height: i32, refresh_rate: i32 },
    SetScale { name: String, scale: f64 },                      // Set scale factor
    SetTransform { name: String, transform: WlTransform },       // Set rotation/flip
    SetPosition { name: String, x: i32, y: i32 },               // Set position
}

Monitor Properties

Each WlMonitor provides:

Property Type Description
name String Output name (e.g., "DP-1", "HDMI-A-1")
description String Human-readable description
make String Manufacturer
model String Model name
serial_number String Serial number
enabled bool Currently enabled?
resolution WlResolution Current resolution (width, height)
position WlPosition Position in global coordinate space
scale f64 Scale factor (1.0, 1.5, 2.0, etc.)
modes Vec<WlMonitorMode> Available display modes
transform WlTransform Orientation (normal, rotated, flipped)

Requirements

  • Wayland compositor with zwlr_output_manager_v1 support:

    • ✓ wlroots-based compositors (Sway, Hyprland, River, dwl, Wayfire, etc.)
    • ✓ Some other compositors may support this protocol
    • ✗ GNOME (uses different protocol)
    • ✗ KDE Plasma (uses different protocol)
  • Rust 1.85+ (for Edition 2024)

Building

# Clone the repository
git clone https://github.com/x34-dzt/wlx_monitors
cd wlx_monitors

# Build
cargo build --release

# Run example
cargo run --example monitor_info

Example: Controlling Monitors

use wlx_monitors::{WlMonitorManager, WlMonitorEvent, WlMonitorAction};
use std::sync::mpsc;
use std::thread;

fn main() {
    let (event_tx, event_rx) = mpsc::sync_channel(16);
    let (action_tx, action_rx) = mpsc::sync_channel(16);

    let (manager, event_queue) = WlMonitorManager::new_connection(
        event_tx,
        action_rx
    ).unwrap();

    // Spawn event loop
    thread::spawn(move || {
        manager.run(event_queue).unwrap();
    });

    // Example: Toggle a monitor
    action_tx.send(WlMonitorAction::Toggle {
        name: "DP-1".to_string(),
        mode: None,
        position: None.
    }).unwrap();

    // Example: Switch resolution
    action_tx.send(WlMonitorAction::SwitchMode {
        name: "HDMI-A-1".to_string(),
        width: 1920,
        height: 1080,
        refresh_rate: 60,
    }).unwrap();

    // Example: Set scale factor
    action_tx.send(WlMonitorAction::SetScale {
        name: "DP-1".to_string(),
        scale: 1.5,
    }).unwrap();

    // Example: Rotate a monitor
    use wlx_monitors::WlTransform;
    action_tx.send(WlMonitorAction::SetTransform {
        name: "DP-1".to_string(),
        transform: WlTransform::Rotate90,
    }).unwrap();

    // Example: Move a monitor's position
    action_tx.send(WlMonitorAction::SetPosition {
        name: "HDMI-A-1".to_string(),
        x: 1920,
        y: 0,
    }).unwrap();

    // Process events
    while let Ok(event) = event_rx.recv() {
        match event {
            WlMonitorEvent::Changed(monitor) => {
                println!("Updated: {} - enabled={}",
                    monitor.name,
                    monitor.enabled
                );
            }
            _ => {}
        }
    }
}

Troubleshooting

"Failed to connect to Wayland"

Make sure you're running on a Wayland session:

echo $WAYLAND_DISPLAY
# Should output something like "wayland-1"

"Compositor rejected the configuration"

The compositor may not support the requested mode or the monitor doesn't support the requested resolution/refresh rate.

Protocol References

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Dependencies

~3.5–8.5MB
~178K SLoC