modesetting/
modesetting.rs

1use std::{
2    borrow::Cow,
3    env,
4    ffi::{CStr, CString},
5};
6
7use linux_drm::{
8    event::{DrmEvent, GenericDrmEvent},
9    modeset::{
10        CardResources, ConnectionState, ConnectorId, ConnectorState, CrtcId, DumbBuffer,
11        DumbBufferRequest, ModeInfo, PageFlipFlags,
12    },
13    result::Error,
14    Card, DeviceCap,
15};
16
17fn main() -> std::io::Result<()> {
18    let card_path = card_path();
19    let mut card = Card::open(card_path).map_err(map_init_err)?;
20    card.become_master().map_err(map_err)?;
21
22    {
23        let name = card.driver_name().map_err(map_err)?;
24        let name = String::from_utf8_lossy(&name);
25        println!("Driver name: {name}");
26    }
27
28    if card
29        .get_device_cap(DeviceCap::DumbBuffer)
30        .map_err(map_err)?
31        == 0
32    {
33        return Err(std::io::Error::other(
34            "device does not support 'dumb buffers'",
35        ));
36    } else {
37        println!("Device supports 'dumb buffers'");
38    }
39
40    display_demo(&mut card).map_err(map_err)
41}
42
43fn card_path<'a>() -> Cow<'a, CStr> {
44    static DEFAULT_PATH: &'static CStr = c"/dev/dri/card0";
45
46    let mut args = env::args();
47    if !args.next().is_some() {
48        // skip the executable name
49        return Cow::Borrowed(DEFAULT_PATH);
50    }
51
52    args.next().map_or(Cow::Borrowed(DEFAULT_PATH), |s| {
53        Cow::Owned(CString::new(s).unwrap())
54    })
55}
56
57fn display_demo(card: &mut Card) -> Result<(), Error> {
58    let mut outputs = prepare_outputs(&card)?;
59    for output in &mut outputs {
60        println!("preparing output {output:#?}");
61        let conn = card.connector_state(output.conn_id)?;
62
63        let mode = &output.mode;
64        let mode_name = String::from_utf8_lossy(&mode.name);
65        println!(
66            "{:?} connector uses {mode_name} ({}x{}@{}Hz)",
67            conn.connector_type, mode.hdisplay, mode.vdisplay, mode.vrefresh,
68        );
69
70        let rows = output.db.height() as usize;
71        let pitch = output.db.pitch() as usize;
72        let data = output.db.buffer_mut();
73        for i in 0..rows {
74            if (i % 8) > 3 {
75                let row = &mut data[(i * pitch)..(i * pitch) + pitch];
76                row.fill(0xff);
77            }
78        }
79
80        println!(
81            "configuring CRTC {:?} for framebuffer {:?} and mode {mode_name} on connection {:?}",
82            output.crtc_id,
83            output.db.framebuffer_id(),
84            conn.id
85        );
86        card.set_crtc_dumb_buffer(output.crtc_id, &output.db, mode, &[output.conn_id])?;
87        card.crtc_page_flip_dumb_buffer(output.crtc_id, &output.db, PageFlipFlags::EVENT)?;
88    }
89
90    let mut evt_buf = vec![0_u8; 1024];
91    loop {
92        println!("waiting for events (send SIGINT to exit)");
93        for evt in card.read_events(&mut evt_buf)? {
94            println!("event {evt:?}");
95            match evt {
96                DrmEvent::Generic(GenericDrmEvent::FlipComplete(_)) => {
97                    // In a real program this would be a time place to draw the next frame
98                    // for the reported crtc.
99                }
100                _ => {
101                    // Ignore any unrecognized event types.
102                }
103            }
104        }
105    }
106}
107
108fn prepare_outputs(card: &Card) -> Result<Vec<Output>, Error> {
109    let resources = card.resources()?;
110    let mut outputs = Vec::<Output>::new();
111
112    for id in resources.connector_ids.iter().copied() {
113        let conn = card.connector_state(id)?;
114        if conn.connection_state != ConnectionState::Connected {
115            println!("ignoring unconnected connector {id:?}");
116            continue;
117        }
118        if conn.current_encoder_id.0 == 0 {
119            println!("ignoring encoderless connector {id:?}");
120            continue;
121        }
122        if conn.modes.len() == 0 {
123            println!("ignoring modeless connector {id:?}");
124            continue;
125        }
126
127        let output = prepare_output(card, conn, &resources)?;
128        outputs.push(output);
129    }
130
131    Ok(outputs)
132}
133
134fn prepare_output(
135    card: &Card,
136    conn: ConnectorState,
137    resources: &CardResources,
138) -> Result<Output, Error> {
139    if conn.current_encoder_id.0 == 0 {
140        // It could be reasonable to go hunting for a suitable encoder and
141        // CRTC to activate this connector, but for this simple example
142        // we'll just use whatever connectors are already producing some
143        // output and keep using whatever modes they are currently in.
144        return Err(Error::NotSupported);
145    }
146    let _ = resources; // (don't actually need this when we're just using the already-selected encoder/crtc)
147
148    let enc = card.encoder_state(conn.current_encoder_id)?;
149    let crtc_id = enc.current_crtc_id;
150    let crtc = card.crtc_state(crtc_id)?;
151    let mode = crtc.mode;
152    let db = card.create_dumb_buffer(DumbBufferRequest {
153        width: mode.hdisplay as u32,
154        height: mode.vdisplay as u32,
155        depth: 24,
156        bpp: 32,
157    })?;
158    Ok(Output {
159        conn_id: conn.id,
160        crtc_id,
161        mode,
162        db,
163    })
164}
165
166#[derive(Debug)]
167struct Output {
168    db: DumbBuffer,
169    mode: ModeInfo,
170    conn_id: ConnectorId,
171    crtc_id: CrtcId,
172}
173
174fn map_init_err(e: linux_drm::result::InitError) -> std::io::Error {
175    let e: linux_io::result::Error = e.into();
176    e.into_std_io_error()
177}
178
179fn map_err(e: linux_drm::result::Error) -> std::io::Error {
180    let e: linux_io::result::Error = e.into();
181    e.into_std_io_error()
182}