1use display_types::IMAGE_TILING_TYPE_LINEAR;
6
7use fidl::endpoints::ClientEnd;
8use fidl_fuchsia_hardware_display::{
9 self as display, CoordinatorListenerRequest, LayerId as FidlLayerId,
10};
11use fidl_fuchsia_hardware_display_types::{self as display_types};
12use fidl_fuchsia_io as fio;
13use fuchsia_async::{DurationExt as _, TimeoutExt as _};
14use fuchsia_component::client::connect_to_protocol_at_path;
15use fuchsia_fs::directory::{WatchEvent, Watcher};
16use fuchsia_sync::RwLock;
17use futures::channel::mpsc;
18use futures::{future, TryStreamExt};
19use std::fmt;
20use std::path::{Path, PathBuf};
21use std::sync::Arc;
22use zx::{self as zx, HandleBased};
23
24use crate::config::{DisplayConfig, LayerConfig};
25use crate::error::{ConfigError, Error, Result};
26use crate::types::{
27 BufferCollectionId, BufferId, DisplayId, DisplayInfo, Event, EventId, ImageId, LayerId,
28};
29use crate::INVALID_EVENT_ID;
30
31const DEV_DIR_PATH: &str = "/dev/class/display-coordinator";
32const TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
33
34#[derive(Clone)]
37pub struct Coordinator {
38 inner: Arc<RwLock<CoordinatorInner>>,
39}
40
41struct CoordinatorInner {
42 displays: Vec<DisplayInfo>,
43 proxy: display::CoordinatorProxy,
44 listener_requests: Option<display::CoordinatorListenerRequestStream>,
45
46 vsync_listeners: Vec<(mpsc::UnboundedSender<VsyncEvent>, Option<DisplayId>)>,
48
49 id_counter: u64,
51
52 stamp_counter: u64,
54}
55
56#[derive(Debug)]
58pub struct VsyncEvent {
59 pub id: DisplayId,
61
62 pub timestamp: zx::MonotonicInstant,
64
65 pub config: display::ConfigStamp,
67}
68
69impl Coordinator {
70 pub async fn init() -> Result<Coordinator> {
88 let path = watch_first_file(DEV_DIR_PATH)
89 .on_timeout(TIMEOUT.after_now(), || Err(Error::DeviceNotFound))
90 .await?;
91 let path = path.to_str().ok_or(Error::DevicePathInvalid)?;
92 let provider_proxy = connect_to_protocol_at_path::<display::ProviderMarker>(path)
93 .map_err(Error::DeviceConnectionError)?;
94
95 let (coordinator_proxy, coordinator_server_end) =
96 fidl::endpoints::create_proxy::<display::CoordinatorMarker>();
97 let (coordinator_listener_client_end, coordinator_listener_requests) =
98 fidl::endpoints::create_request_stream::<display::CoordinatorListenerMarker>();
99
100 let payload = display::ProviderOpenCoordinatorWithListenerForPrimaryRequest {
103 coordinator: Some(coordinator_server_end),
104 coordinator_listener: Some(coordinator_listener_client_end),
105 __source_breaking: fidl::marker::SourceBreaking,
106 };
107 let () = provider_proxy
108 .open_coordinator_with_listener_for_primary(payload)
109 .await?
110 .map_err(zx::Status::from_raw)?;
111
112 Self::init_with_proxy_and_listener_requests(
113 coordinator_proxy,
114 coordinator_listener_requests,
115 )
116 .await
117 }
118
119 pub async fn init_with_proxy_and_listener_requests(
129 coordinator_proxy: display::CoordinatorProxy,
130 mut listener_requests: display::CoordinatorListenerRequestStream,
131 ) -> Result<Coordinator> {
132 let displays = wait_for_initial_displays(&mut listener_requests)
133 .on_timeout(TIMEOUT.after_now(), || Err(Error::NoDisplays))
134 .await?
135 .into_iter()
136 .map(DisplayInfo)
137 .collect::<Vec<_>>();
138 Ok(Coordinator {
139 inner: Arc::new(RwLock::new(CoordinatorInner {
140 proxy: coordinator_proxy,
141 listener_requests: Some(listener_requests),
142 displays,
143 vsync_listeners: Vec::new(),
144 id_counter: 0,
145 stamp_counter: 0,
146 })),
147 })
148 }
149
150 pub fn displays(&self) -> Vec<DisplayInfo> {
152 self.inner.read().displays.clone()
153 }
154
155 pub fn proxy(&self) -> display::CoordinatorProxy {
160 self.inner.read().proxy.clone()
161 }
162
163 pub fn add_vsync_listener(
165 &self,
166 id: Option<DisplayId>,
167 ) -> Result<mpsc::UnboundedReceiver<VsyncEvent>> {
168 self.inner.read().proxy.set_vsync_event_delivery(true)?;
169
170 let (sender, receiver) = mpsc::unbounded::<VsyncEvent>();
172 self.inner.write().vsync_listeners.push((sender, id));
173 Ok(receiver)
174 }
175
176 pub async fn handle_events(&self) -> Result<()> {
182 let inner = self.inner.clone();
183 let mut events = inner.write().listener_requests.take().ok_or(Error::AlreadyRequested)?;
184 while let Some(msg) = events.try_next().await? {
185 match msg {
186 CoordinatorListenerRequest::OnDisplaysChanged {
187 added,
188 removed,
189 control_handle: _,
190 } => {
191 let removed =
192 removed.into_iter().map(|id| id.into()).collect::<Vec<DisplayId>>();
193 inner.read().handle_displays_changed(added, removed);
194 }
195 CoordinatorListenerRequest::OnVsync {
196 display_id,
197 timestamp,
198 applied_config_stamp,
199 cookie,
200 control_handle: _,
201 } => {
202 inner.write().handle_vsync(
203 display_id.into(),
204 zx::MonotonicInstant::from_nanos(timestamp),
205 applied_config_stamp,
206 cookie,
207 )?;
208 }
209 _ => continue,
210 }
211 }
212 Ok(())
213 }
214
215 pub async fn create_layer(&self) -> Result<LayerId> {
218 Ok(self.proxy().create_layer().await?.map_err(zx::Status::from_raw)?.into())
219 }
220
221 pub fn create_event(&self) -> Result<Event> {
224 let event = zx::Event::create();
225 let remote = event.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
226 let id = self.inner.write().next_free_event_id()?;
227
228 self.inner.read().proxy.import_event(zx::Event::from(remote), &id.into())?;
229 Ok(Event::new(id, event))
230 }
231
232 pub async fn apply_config(
235 &self,
236 configs: &[DisplayConfig],
237 ) -> std::result::Result<u64, ConfigError> {
238 let proxy = self.proxy();
239 for config in configs {
240 proxy.set_display_layers(
241 &config.id.into(),
242 &config.layers.iter().map(|l| l.id.into()).collect::<Vec<FidlLayerId>>(),
243 )?;
244 for layer in &config.layers {
245 match &layer.config {
246 LayerConfig::Color { color } => {
247 let fidl_color = fidl_fuchsia_hardware_display_types::Color::from(color);
248 proxy.set_layer_color_config(&layer.id.into(), &fidl_color)?;
249 }
250 LayerConfig::Primary { image_id, image_metadata, unblock_event } => {
251 proxy.set_layer_primary_config(&layer.id.into(), &image_metadata)?;
252 proxy.set_layer_image2(
253 &layer.id.into(),
254 &(*image_id).into(),
255 &unblock_event.unwrap_or(INVALID_EVENT_ID).into(),
256 )?;
257 }
258 }
259 }
260 }
261
262 let (result, ops) = proxy.check_config(false).await?;
263 if result != display_types::ConfigResult::Ok {
264 return Err(ConfigError::invalid(result, ops));
265 }
266
267 let config_stamp = self.inner.write().next_config_stamp().unwrap();
268 let payload = fidl_fuchsia_hardware_display::CoordinatorApplyConfig3Request {
269 stamp: Some(fidl_fuchsia_hardware_display::ConfigStamp { value: config_stamp }),
270 ..Default::default()
271 };
272 match proxy.apply_config3(payload) {
273 Ok(()) => Ok(config_stamp),
274 Err(err) => Err(ConfigError::from(err)),
275 }
276 }
277
278 pub async fn get_recent_applied_config_stamp(&self) -> std::result::Result<u64, Error> {
281 let proxy = self.proxy();
282 let response = proxy.get_latest_applied_config_stamp().await?;
283 Ok(response.value)
284 }
285
286 pub(crate) async fn import_buffer_collection(
289 &self,
290 token: ClientEnd<fidl_fuchsia_sysmem2::BufferCollectionTokenMarker>,
291 ) -> Result<BufferCollectionId> {
292 let id = self.inner.write().next_free_collection_id()?;
293 let proxy = self.proxy();
294
295 proxy.import_buffer_collection(&id.into(), token).await?.map_err(zx::Status::from_raw)?;
297
298 proxy
302 .set_buffer_collection_constraints(
303 &id.into(),
304 &display_types::ImageBufferUsage { tiling_type: IMAGE_TILING_TYPE_LINEAR },
305 )
306 .await?
307 .map_err(zx::Status::from_raw)?;
308 Ok(id)
309 }
310
311 pub(crate) fn release_buffer_collection(&self, id: BufferCollectionId) -> Result<()> {
313 self.inner.read().proxy.release_buffer_collection(&id.into()).map_err(Error::from)
314 }
315
316 pub(crate) async fn import_image(
318 &self,
319 collection_id: BufferCollectionId,
320 image_id: ImageId,
321 image_metadata: display_types::ImageMetadata,
322 ) -> Result<()> {
323 self.proxy()
324 .import_image(
325 &image_metadata,
326 &BufferId::new(collection_id, 0).into(),
327 &image_id.into(),
328 )
329 .await?
330 .map_err(zx::Status::from_raw)?;
331 Ok(())
332 }
333}
334
335impl fmt::Debug for Coordinator {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 f.debug_struct("Coordinator").field("displays", &self.displays()).finish()
340 }
341}
342
343impl CoordinatorInner {
344 fn next_free_collection_id(&mut self) -> Result<BufferCollectionId> {
345 self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
346 Ok(BufferCollectionId(self.id_counter))
347 }
348
349 fn next_free_event_id(&mut self) -> Result<EventId> {
350 self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
351 Ok(EventId(self.id_counter))
352 }
353
354 fn next_config_stamp(&mut self) -> Result<u64> {
355 self.stamp_counter = self.stamp_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
356 Ok(self.stamp_counter)
357 }
358
359 fn handle_displays_changed(&self, _added: Vec<display::Info>, _removed: Vec<DisplayId>) {
360 }
363
364 fn handle_vsync(
365 &mut self,
366 display_id: DisplayId,
367 timestamp: zx::MonotonicInstant,
368 applied_config_stamp: display::ConfigStamp,
369 cookie: display::VsyncAckCookie,
370 ) -> Result<()> {
371 self.proxy.acknowledge_vsync(cookie.value)?;
372
373 let mut listeners_to_remove = Vec::new();
374 for (pos, (sender, filter)) in self.vsync_listeners.iter().enumerate() {
375 if filter.as_ref().map_or(false, |id| *id != display_id) {
377 continue;
378 }
379 let payload = VsyncEvent { id: display_id, timestamp, config: applied_config_stamp };
380 if let Err(e) = sender.unbounded_send(payload) {
381 if e.is_disconnected() {
382 listeners_to_remove.push(pos);
383 } else {
384 return Err(e.into());
385 }
386 }
387 }
388
389 listeners_to_remove.into_iter().for_each(|pos| {
391 self.vsync_listeners.swap_remove(pos);
392 });
393
394 Ok(())
395 }
396}
397
398async fn watch_first_file(path: &str) -> Result<PathBuf> {
402 let dir = fuchsia_fs::directory::open_in_namespace(path, fio::PERM_READABLE)?;
403
404 let mut watcher = Watcher::new(&dir).await?;
405 while let Some(msg) = watcher.try_next().await? {
406 match msg.event {
407 WatchEvent::EXISTING | WatchEvent::ADD_FILE => {
408 if msg.filename == Path::new(".") {
409 continue;
410 }
411 return Ok(Path::new(path).join(msg.filename));
412 }
413 _ => continue,
414 }
415 }
416 Err(Error::DeviceNotFound)
417}
418
419async fn wait_for_initial_displays(
424 listener_requests: &mut display::CoordinatorListenerRequestStream,
425) -> Result<Vec<display::Info>> {
426 let mut stream = listener_requests.try_filter_map(|event| match event {
427 CoordinatorListenerRequest::OnDisplaysChanged { added, removed: _, control_handle: _ } => {
428 future::ok(Some(added))
429 }
430 _ => future::ok(None),
431 });
432 stream.try_next().await?.ok_or(Error::NoDisplays)
433}
434
435#[cfg(test)]
436mod tests {
437 use super::{Coordinator, DisplayId, VsyncEvent};
438 use anyhow::{format_err, Context, Result};
439 use assert_matches::assert_matches;
440 use display_mocks::{create_proxy_and_mock, MockCoordinator};
441 use fuchsia_async::TestExecutor;
442 use futures::task::Poll;
443 use futures::{pin_mut, select, FutureExt, StreamExt};
444 use {
445 fidl_fuchsia_hardware_display as display,
446 fidl_fuchsia_hardware_display_types as display_types,
447 };
448
449 async fn init_with_proxy_and_listener_requests(
450 coordinator_proxy: display::CoordinatorProxy,
451 listener_requests: display::CoordinatorListenerRequestStream,
452 ) -> Result<Coordinator> {
453 Coordinator::init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests)
454 .await
455 .context("failed to initialize Coordinator")
456 }
457
458 async fn init_with_displays(
462 displays: &[display::Info],
463 ) -> Result<(Coordinator, MockCoordinator)> {
464 let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
465 mock.assign_displays(displays.to_vec())?;
466
467 Ok((
468 init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?,
469 mock,
470 ))
471 }
472
473 #[fuchsia::test]
474 async fn test_init_fails_with_no_device_dir() {
475 let result = Coordinator::init().await;
476 assert_matches!(result, Err(_));
477 }
478
479 #[fuchsia::test]
480 async fn test_init_with_no_displays() -> Result<()> {
481 let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
482 mock.assign_displays([].to_vec())?;
483
484 let coordinator =
485 init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?;
486 assert!(coordinator.displays().is_empty());
487
488 Ok(())
489 }
490
491 #[fuchsia::test]
495 async fn test_init_with_displays() -> Result<()> {
496 let displays = [
497 display::Info {
498 id: display_types::DisplayId { value: 1 },
499 modes: Vec::new(),
500 pixel_format: Vec::new(),
501 manufacturer_name: "Foo".to_string(),
502 monitor_name: "what".to_string(),
503 monitor_serial: "".to_string(),
504 horizontal_size_mm: 0,
505 vertical_size_mm: 0,
506 using_fallback_size: false,
507 },
508 display::Info {
509 id: display_types::DisplayId { value: 2 },
510 modes: Vec::new(),
511 pixel_format: Vec::new(),
512 manufacturer_name: "Bar".to_string(),
513 monitor_name: "who".to_string(),
514 monitor_serial: "".to_string(),
515 horizontal_size_mm: 0,
516 vertical_size_mm: 0,
517 using_fallback_size: false,
518 },
519 ]
520 .to_vec();
521 let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
522 mock.assign_displays(displays.clone())?;
523
524 let coordinator =
525 init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?;
526 assert_eq!(coordinator.displays().len(), 2);
527 assert_eq!(coordinator.displays()[0].0, displays[0]);
528 assert_eq!(coordinator.displays()[1].0, displays[1]);
529
530 Ok(())
531 }
532
533 #[test]
534 fn test_vsync_listener_single() -> Result<()> {
535 let mut executor = TestExecutor::new();
538 let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
539 let mut vsync = coordinator.add_vsync_listener(None)?;
540
541 const ID: DisplayId = DisplayId(1);
542 const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
543 let event_handlers = async {
544 select! {
545 event = vsync.next() => event.ok_or(format_err!("did not receive vsync event")),
546 result = coordinator.handle_events().fuse() => {
547 result.context("FIDL event handler failed")?;
548 Err(format_err!("FIDL event handler completed before client vsync event"))
549 },
550 }
551 };
552 pin_mut!(event_handlers);
553
554 mock.emit_vsync_event(ID.0, STAMP)?;
556 let vsync_event = executor.run_until_stalled(&mut event_handlers);
557 assert_matches!(
558 vsync_event,
559 Poll::Ready(Ok(VsyncEvent { id: ID, timestamp: _, config: STAMP }))
560 );
561
562 Ok(())
563 }
564
565 #[test]
566 fn test_vsync_listener_multiple() -> Result<()> {
567 let mut executor = TestExecutor::new();
570 let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
571 let mut vsync = coordinator.add_vsync_listener(None)?;
572
573 let fidl_server = coordinator.handle_events().fuse();
574 pin_mut!(fidl_server);
575
576 const ID1: DisplayId = DisplayId(1);
577 const ID2: DisplayId = DisplayId(2);
578 const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
579
580 mock.emit_vsync_event(ID1.0, STAMP)?;
582 mock.emit_vsync_event(ID2.0, STAMP)?;
583 mock.emit_vsync_event(ID1.0, STAMP)?;
584
585 let fidl_server_result = executor.run_until_stalled(&mut fidl_server);
588 assert_matches!(fidl_server_result, Poll::Pending);
589
590 let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
592 assert_matches!(
593 vsync_event,
594 Poll::Ready(Some(VsyncEvent { id: ID1, timestamp: _, config: STAMP }))
595 );
596
597 let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
598 assert_matches!(
599 vsync_event,
600 Poll::Ready(Some(VsyncEvent { id: ID2, timestamp: _, config: STAMP }))
601 );
602
603 let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
604 assert_matches!(
605 vsync_event,
606 Poll::Ready(Some(VsyncEvent { id: ID1, timestamp: _, config: STAMP }))
607 );
608
609 Ok(())
610 }
611
612 #[test]
613 fn test_vsync_listener_display_id_filter() -> Result<()> {
614 let mut executor = TestExecutor::new();
617 let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
618
619 const ID1: DisplayId = DisplayId(1);
620 const ID2: DisplayId = DisplayId(2);
621 const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
622
623 let mut vsync = coordinator.add_vsync_listener(Some(ID2))?;
625 let event_handlers = async {
626 select! {
627 event = vsync.next() => event.ok_or(format_err!("did not receive vsync event")),
628 result = coordinator.handle_events().fuse() => {
629 result.context("FIDL event handler failed")?;
630 Err(format_err!("FIDL event handler completed before client vsync event"))
631 },
632 }
633 };
634 pin_mut!(event_handlers);
635
636 mock.emit_vsync_event(ID1.0, STAMP)?;
638 let vsync_event = executor.run_until_stalled(&mut event_handlers);
639 assert_matches!(vsync_event, Poll::Pending);
640
641 mock.emit_vsync_event(ID2.0, STAMP)?;
643 let vsync_event = executor.run_until_stalled(&mut event_handlers);
644 assert_matches!(
645 vsync_event,
646 Poll::Ready(Ok(VsyncEvent { id: ID2, timestamp: _, config: STAMP }))
647 );
648
649 Ok(())
650 }
651}