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
48KB
854 lines
wlx_monitors
A Rust library for detecting and managing display outputs on Wayland using the wlr-output-management protocol.
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 monitorsWlMonitorEvent::Changed(Box<WlMonitor>)- Sent when a monitor's properties changeWlMonitorEvent::Removed { id, name }- Sent when a monitor is disconnectedWlMonitorEvent::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. Themode: Option<(i32, i32, i32)>lets users optionally specify a custom(width, height, refresh_rate)when toggling a monitor back on. IfNone, the smart mode resolution kicks in (last mode > preferred > first available). Theposition: Option<(i32, i32)>let's you specify a custom position(pos_x, pos_y)for your monitor when turning it on, IfNoneit will by default to (0,0).WlMonitorAction::SwitchMode { name, width, height, refresh_rate }- Change a monitor's modeWlMonitorAction::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_v1support:- ✓ 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