1use linux_drm::{
2 event::{DrmEvent, GenericDrmEvent},
3 modeset::{
4 AtomicCommitFlags, CardResources, ConnectionState, ConnectorId, ConnectorState, CrtcId,
5 DumbBuffer, DumbBufferRequest, ModeInfo, ObjectId, PlaneId, PropertyId,
6 },
7 result::Error,
8 Card, ClientCap, DeviceCap,
9};
10use std::{
11 borrow::Cow,
12 env,
13 ffi::{CStr, CString},
14};
15
16fn main() -> std::io::Result<()> {
17 let card_path = card_path();
18 let mut card = Card::open(card_path).map_err(map_init_err)?;
19 card.become_master().map_err(map_err)?;
20
21 {
22 let name = card.driver_name().map_err(map_err)?;
23 let name = String::from_utf8_lossy(&name);
24 println!("Driver name: {name}");
25 }
26
27 if card
28 .get_device_cap(DeviceCap::DumbBuffer)
29 .map_err(map_err)?
30 == 0
31 {
32 return Err(std::io::Error::other(
33 "device does not support 'dumb buffers'",
34 ));
35 } else {
36 println!("Device supports 'dumb buffers'");
37 }
38
39 card.set_client_cap(ClientCap::UniversalPlanes, 1)
40 .map_err(map_err)?;
41 card.set_client_cap(ClientCap::Atomic, 1).map_err(map_err)?;
42
43 display_demo(&mut card).map_err(map_err)
44}
45
46fn card_path<'a>() -> Cow<'a, CStr> {
47 static DEFAULT_PATH: &'static CStr = c"/dev/dri/card0";
48
49 let mut args = env::args();
50 if !args.next().is_some() {
51 return Cow::Borrowed(DEFAULT_PATH);
53 }
54
55 args.next().map_or(Cow::Borrowed(DEFAULT_PATH), |s| {
56 Cow::Owned(CString::new(s).unwrap())
57 })
58}
59
60fn display_demo(card: &mut Card) -> Result<(), Error> {
61 let mut outputs = prepare_outputs(&card)?;
62 let mut req = linux_drm::modeset::AtomicRequest::new();
63
64 for output in &mut outputs {
65 println!("preparing output {output:#?}");
66 let conn = card.connector_state(output.conn_id)?;
67
68 let mode = &output.mode;
69 let mode_name = String::from_utf8_lossy(&mode.name);
70 println!(
71 "{:?} connector uses {mode_name} ({}x{}@{}Hz)",
72 conn.connector_type, mode.hdisplay, mode.vdisplay, mode.vrefresh,
73 );
74
75 let rows = output.db.height() as usize;
76 let pitch = output.db.pitch() as usize;
77 let data = output.db.buffer_mut();
78 for i in 0..rows {
79 if (i % 8) > 3 {
80 let row = &mut data[(i * pitch)..(i * pitch) + pitch];
81 row.fill(0xff);
82 }
83 }
84
85 println!(
86 "configuring CRTC {:?} for framebuffer {:?} and mode {mode_name} on connector {:?}",
87 output.crtc_id,
88 output.db.framebuffer_id(),
89 conn.id
90 );
91
92 req.set_property(
93 ObjectId::Connector(output.conn_id),
94 output.conn_prop_ids.crtc_id,
95 output.crtc_id,
96 );
97 req.set_property(
98 ObjectId::Crtc(output.crtc_id),
99 output.crtc_prop_ids.active,
100 true,
101 );
102 req.set_property(
103 ObjectId::Plane(output.plane_id),
104 output.plane_prop_ids.fb_id,
105 output.db.framebuffer_id(),
106 );
107 req.set_property(
108 ObjectId::Plane(output.plane_id),
109 output.plane_prop_ids.crtc_id,
110 output.crtc_id,
111 );
112 req.set_property(
113 ObjectId::Plane(output.plane_id),
114 output.plane_prop_ids.crtc_x,
115 0,
116 );
117 req.set_property(
118 ObjectId::Plane(output.plane_id),
119 output.plane_prop_ids.crtc_y,
120 0,
121 );
122 req.set_property(
123 ObjectId::Plane(output.plane_id),
124 output.plane_prop_ids.crtc_w,
125 output.db.width(),
126 );
127 req.set_property(
128 ObjectId::Plane(output.plane_id),
129 output.plane_prop_ids.crtc_h,
130 output.db.height(),
131 );
132 req.set_property(
133 ObjectId::Plane(output.plane_id),
134 output.plane_prop_ids.src_x,
135 0,
136 );
137 req.set_property(
138 ObjectId::Plane(output.plane_id),
139 output.plane_prop_ids.src_y,
140 0,
141 );
142 req.set_property(
143 ObjectId::Plane(output.plane_id),
144 output.plane_prop_ids.src_w,
145 (output.db.width() as u64) << 16,
146 );
147 req.set_property(
148 ObjectId::Plane(output.plane_id),
149 output.plane_prop_ids.src_h,
150 (output.db.height() as u64) << 16,
151 );
152 }
153
154 println!("atomic commit {req:#?}");
155 card.atomic_commit(
156 &req,
157 AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::PAGE_FLIP_EVENT,
158 0,
159 )?;
160
161 let mut evt_buf = vec![0_u8; 1024];
162 loop {
163 println!("waiting for events (send SIGINT to exit)");
164 for evt in card.read_events(&mut evt_buf)? {
165 println!("event {evt:?}");
166 match evt {
167 DrmEvent::Generic(GenericDrmEvent::FlipComplete(_)) => {
168 }
171 _ => {
172 }
174 }
175 }
176 }
177}
178
179fn prepare_outputs(card: &Card) -> Result<Vec<Output>, Error> {
180 println!("preparing outputs");
181
182 let resources = card.resources()?;
183 let mut outputs = Vec::<Output>::new();
184
185 for id in resources.connector_ids.iter().copied() {
186 println!("preparing output for connector #{id:?}");
187
188 let conn = card.connector_state(id)?;
189 if conn.connection_state != ConnectionState::Connected {
190 println!("ignoring unconnected connector {id:?}");
191 continue;
192 }
193 if conn.current_encoder_id.0 == 0 {
194 println!("ignoring encoderless connector {id:?}");
195 continue;
196 }
197 if conn.modes.len() == 0 {
198 println!("ignoring modeless connector {id:?}");
199 continue;
200 }
201
202 let output = prepare_output(card, conn, &resources)?;
203 outputs.push(output);
204 }
205
206 Ok(outputs)
207}
208
209fn prepare_output(
210 card: &Card,
211 conn: ConnectorState,
212 resources: &CardResources,
213) -> Result<Output, Error> {
214 if conn.current_encoder_id.0 == 0 {
215 return Err(Error::NotSupported);
220 }
221 let _ = resources; let enc = card.encoder_state(conn.current_encoder_id)?;
224 let crtc_id = enc.current_crtc_id;
225 let crtc = card.crtc_state(crtc_id)?;
226 let mode = crtc.mode;
227 let db = card.create_dumb_buffer(DumbBufferRequest {
228 width: mode.hdisplay as u32,
229 height: mode.vdisplay as u32,
230 depth: 24,
231 bpp: 32,
232 })?;
233
234 let mut chosen_plane_id: Option<PlaneId> = None;
240 for plane_id in resources.plane_ids.iter().copied() {
241 let plane = card.plane_state(plane_id)?;
242 if plane.crtc_id == crtc_id {
243 chosen_plane_id = Some(plane_id);
244 break;
245 }
246 }
247 let Some(chosen_plane_id) = chosen_plane_id else {
248 return Err(Error::NonExist);
249 };
250
251 println!("collecting properties");
252 let conn_prop_ids = ConnectorPropIds::new(conn.id, card)?;
253 let crtc_prop_ids = CrtcPropIds::new(crtc_id, card)?;
254 let plane_prop_ids = PlanePropIds::new(chosen_plane_id, card)?;
255
256 println!("collected properties");
257 Ok(Output {
258 conn_id: conn.id,
259 conn_prop_ids,
260 crtc_id,
261 crtc_prop_ids,
262 plane_id: chosen_plane_id,
263 plane_prop_ids,
264 mode,
265 db,
266 })
267}
268
269#[derive(Debug)]
270struct Output {
271 db: DumbBuffer,
272 mode: ModeInfo,
273 conn_id: ConnectorId,
274 conn_prop_ids: ConnectorPropIds,
275 crtc_id: CrtcId,
276 crtc_prop_ids: CrtcPropIds,
277 plane_id: PlaneId,
278 plane_prop_ids: PlanePropIds,
279}
280
281#[derive(Debug)]
282struct ConnectorPropIds {
283 crtc_id: PropertyId,
284}
285
286impl ConnectorPropIds {
287 pub fn new(conn_id: ConnectorId, card: &linux_drm::Card) -> Result<Self, Error> {
288 let mut ret: Self = unsafe { core::mem::zeroed() };
289 card.each_object_property_meta(conn_id, |meta, _| ret.populate_from(meta))?;
290 Ok(ret)
291 }
292
293 pub fn populate_from<'card>(&mut self, from: linux_drm::modeset::ObjectPropMeta<'card>) {
294 match from.name() {
295 "CRTC_ID" => self.crtc_id = from.property_id(),
296 _ => {}
297 }
298 }
299}
300
301#[derive(Debug)]
302struct CrtcPropIds {
303 active: PropertyId,
304}
305
306impl CrtcPropIds {
307 pub fn new(crtc_id: CrtcId, card: &linux_drm::Card) -> Result<Self, Error> {
308 let mut ret: Self = unsafe { core::mem::zeroed() };
309 card.each_object_property_meta(linux_drm::modeset::ObjectId::Crtc(crtc_id), |meta, _| {
310 ret.populate_from(meta)
311 })?;
312 Ok(ret)
313 }
314
315 pub fn populate_from<'card>(&mut self, from: linux_drm::modeset::ObjectPropMeta<'card>) {
316 match from.name() {
317 "ACTIVE" => self.active = from.property_id(),
318 _ => {}
319 }
320 }
321}
322
323#[derive(Debug)]
324struct PlanePropIds {
325 typ: PropertyId,
326 fb_id: PropertyId,
327 crtc_id: PropertyId,
328 crtc_x: PropertyId,
329 crtc_y: PropertyId,
330 crtc_w: PropertyId,
331 crtc_h: PropertyId,
332 src_x: PropertyId,
333 src_y: PropertyId,
334 src_w: PropertyId,
335 src_h: PropertyId,
336}
337
338impl PlanePropIds {
339 pub fn new(plane_id: PlaneId, card: &linux_drm::Card) -> Result<Self, Error> {
340 let mut ret: Self = unsafe { core::mem::zeroed() };
341 card.each_object_property_meta(plane_id, |meta, _| ret.populate_from(meta))?;
342 Ok(ret)
343 }
344
345 pub fn populate_from<'card>(&mut self, from: linux_drm::modeset::ObjectPropMeta<'card>) {
346 let field: &mut PropertyId = match from.name() {
347 "type" => &mut self.typ,
348 "FB_ID" => &mut self.fb_id,
349 "CRTC_ID" => &mut self.crtc_id,
350 "CRTC_X" => &mut self.crtc_x,
351 "CRTC_Y" => &mut self.crtc_y,
352 "CRTC_W" => &mut self.crtc_w,
353 "CRTC_H" => &mut self.crtc_h,
354 "SRC_X" => &mut self.src_x,
355 "SRC_Y" => &mut self.src_y,
356 "SRC_W" => &mut self.src_w,
357 "SRC_H" => &mut self.src_h,
358 _ => return,
359 };
360 *field = from.property_id()
361 }
362}
363
364fn map_init_err(e: linux_drm::result::InitError) -> std::io::Error {
365 let e: linux_io::result::Error = e.into();
366 e.into_std_io_error()
367}
368
369fn map_err(e: linux_drm::result::Error) -> std::io::Error {
370 let e: linux_io::result::Error = e.into();
371 e.into_std_io_error()
372}