Sprint 8: Multi-Monitor Support
Goal: Properly handle multi-monitor setups by detecting all monitors via RandR and positioning the UI appropriately.
Objectives
- Detect all connected monitors using XRandR
- Create window spanning entire virtual screen
- Render background on all monitors
- Center login UI on primary monitor
- Handle monitor hotplug events (optional)
Implementation
Monitor Detection
// Using x11rb's randr extension
use x11rb::protocol::randr::{self, ConnectionExt as _};
struct Monitor {
x: i16,
y: i16,
width: u16,
height: u16,
primary: bool,
name: String,
}
fn get_monitors(conn: &RustConnection, root: Window) -> Result<Vec<Monitor>> {
let resources = conn.randr_get_screen_resources(root)?.reply()?;
let mut monitors = Vec::new();
for output in resources.outputs {
let output_info = conn.randr_get_output_info(output, 0)?.reply()?;
if output_info.connection == randr::Connection::CONNECTED {
if let Some(crtc) = output_info.crtc {
let crtc_info = conn.randr_get_crtc_info(crtc, 0)?.reply()?;
monitors.push(Monitor {
x: crtc_info.x,
y: crtc_info.y,
width: crtc_info.width,
height: crtc_info.height,
primary: false, // Check via randr_get_output_primary
name: String::from_utf8_lossy(&output_info.name).to_string(),
});
}
}
}
// Mark primary
let primary = conn.randr_get_output_primary(root)?.reply()?;
// ... mark the matching monitor as primary
Ok(monitors)
}
Window Spanning
- Window covers entire virtual screen (union of all monitors)
- Use root window dimensions for total size
- Background rendered per-monitor with proper offsets
UI Centering
- Find primary monitor (or largest if no primary set)
- Calculate center point of primary monitor
- Offset all UI element positions relative to primary center
- Login form, user list, session selector all centered on primary
Background Rendering
- Load background image once
- For each monitor:
- Scale/crop background to monitor dimensions
- Apply blur and brightness
- Render at monitor's x,y offset in the virtual screen
Example Layout
┌─────────────────┬─────────────────────────┐
│ │ │
│ Monitor 2 │ Monitor 1 │
│ 1920x1080 │ 2560x1440 │
│ (secondary) │ (primary) │
│ │ │
│ [background] │ [background + UI] │
│ │ │
└─────────────────┴─────────────────────────┘
Testing
# Test with Xephyr multi-head (limited)
# Better: test on actual multi-monitor setup
# Check detected monitors
xrandr --query
# Verify greeter spans all monitors
DISPLAY=:0 ./target/release/gardm-greeter
Files to Modify
gardm-greeter/src/window.rs- Add RandR detectiongardm-greeter/src/main.rs- Pass monitor info to widgetsgardm-greeter/src/background.rs- Per-monitor rendering- Widget files - Accept center offset parameter
View source
| 1 | # Sprint 8: Multi-Monitor Support |
| 2 | |
| 3 | **Goal:** Properly handle multi-monitor setups by detecting all monitors via RandR and positioning the UI appropriately. |
| 4 | |
| 5 | ## Objectives |
| 6 | |
| 7 | 1. Detect all connected monitors using XRandR |
| 8 | 2. Create window spanning entire virtual screen |
| 9 | 3. Render background on all monitors |
| 10 | 4. Center login UI on primary monitor |
| 11 | 5. Handle monitor hotplug events (optional) |
| 12 | |
| 13 | ## Implementation |
| 14 | |
| 15 | ### Monitor Detection |
| 16 | ```rust |
| 17 | // Using x11rb's randr extension |
| 18 | use x11rb::protocol::randr::{self, ConnectionExt as _}; |
| 19 | |
| 20 | struct Monitor { |
| 21 | x: i16, |
| 22 | y: i16, |
| 23 | width: u16, |
| 24 | height: u16, |
| 25 | primary: bool, |
| 26 | name: String, |
| 27 | } |
| 28 | |
| 29 | fn get_monitors(conn: &RustConnection, root: Window) -> Result<Vec<Monitor>> { |
| 30 | let resources = conn.randr_get_screen_resources(root)?.reply()?; |
| 31 | let mut monitors = Vec::new(); |
| 32 | |
| 33 | for output in resources.outputs { |
| 34 | let output_info = conn.randr_get_output_info(output, 0)?.reply()?; |
| 35 | if output_info.connection == randr::Connection::CONNECTED { |
| 36 | if let Some(crtc) = output_info.crtc { |
| 37 | let crtc_info = conn.randr_get_crtc_info(crtc, 0)?.reply()?; |
| 38 | monitors.push(Monitor { |
| 39 | x: crtc_info.x, |
| 40 | y: crtc_info.y, |
| 41 | width: crtc_info.width, |
| 42 | height: crtc_info.height, |
| 43 | primary: false, // Check via randr_get_output_primary |
| 44 | name: String::from_utf8_lossy(&output_info.name).to_string(), |
| 45 | }); |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | // Mark primary |
| 51 | let primary = conn.randr_get_output_primary(root)?.reply()?; |
| 52 | // ... mark the matching monitor as primary |
| 53 | |
| 54 | Ok(monitors) |
| 55 | } |
| 56 | ``` |
| 57 | |
| 58 | ### Window Spanning |
| 59 | - Window covers entire virtual screen (union of all monitors) |
| 60 | - Use root window dimensions for total size |
| 61 | - Background rendered per-monitor with proper offsets |
| 62 | |
| 63 | ### UI Centering |
| 64 | - Find primary monitor (or largest if no primary set) |
| 65 | - Calculate center point of primary monitor |
| 66 | - Offset all UI element positions relative to primary center |
| 67 | - Login form, user list, session selector all centered on primary |
| 68 | |
| 69 | ### Background Rendering |
| 70 | - Load background image once |
| 71 | - For each monitor: |
| 72 | - Scale/crop background to monitor dimensions |
| 73 | - Apply blur and brightness |
| 74 | - Render at monitor's x,y offset in the virtual screen |
| 75 | |
| 76 | ### Example Layout |
| 77 | ``` |
| 78 | ┌─────────────────┬─────────────────────────┐ |
| 79 | │ │ │ |
| 80 | │ Monitor 2 │ Monitor 1 │ |
| 81 | │ 1920x1080 │ 2560x1440 │ |
| 82 | │ (secondary) │ (primary) │ |
| 83 | │ │ │ |
| 84 | │ [background] │ [background + UI] │ |
| 85 | │ │ │ |
| 86 | └─────────────────┴─────────────────────────┘ |
| 87 | ``` |
| 88 | |
| 89 | ## Testing |
| 90 | ```bash |
| 91 | # Test with Xephyr multi-head (limited) |
| 92 | # Better: test on actual multi-monitor setup |
| 93 | |
| 94 | # Check detected monitors |
| 95 | xrandr --query |
| 96 | |
| 97 | # Verify greeter spans all monitors |
| 98 | DISPLAY=:0 ./target/release/gardm-greeter |
| 99 | ``` |
| 100 | |
| 101 | ## Files to Modify |
| 102 | - `gardm-greeter/src/window.rs` - Add RandR detection |
| 103 | - `gardm-greeter/src/main.rs` - Pass monitor info to widgets |
| 104 | - `gardm-greeter/src/background.rs` - Per-monitor rendering |
| 105 | - Widget files - Accept center offset parameter |