Skip to main content

display_utils/
controller.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::Context;
6use display_types::IMAGE_TILING_TYPE_LINEAR;
7
8use fidl::endpoints::ClientEnd;
9use fidl_fuchsia_hardware_display::{
10    self as display, CoordinatorListenerRequest, LayerId as FidlLayerId,
11};
12use fidl_fuchsia_hardware_display_types::{self as display_types};
13use fuchsia_async::{DurationExt as _, TimeoutExt as _};
14use fuchsia_component::client::Service;
15use fuchsia_sync::RwLock;
16use futures::channel::mpsc;
17use futures::{TryFutureExt, TryStreamExt, future};
18use std::fmt;
19use std::sync::Arc;
20
21use crate::INVALID_EVENT_ID;
22use crate::config::{DisplayConfig, LayerConfig};
23use crate::error::{ConfigError, Error, Result};
24use crate::types::{
25    BufferCollectionId, ClientPriority, DisplayId, DisplayInfo, Event, EventId, ImageId, LayerId,
26};
27
28const TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
29
30/// Client abstraction for the `fuchsia.hardware.display.Coordinator` protocol. Instances can be
31/// safely cloned and passed across threads.
32#[derive(Clone)]
33pub struct Coordinator {
34    inner: Arc<RwLock<CoordinatorInner>>,
35}
36
37struct CoordinatorInner {
38    displays: Vec<DisplayInfo>,
39    proxy: display::CoordinatorProxy,
40    listener_requests: Option<display::CoordinatorListenerRequestStream>,
41
42    // All subscribed vsync listeners and their optional ID filters.
43    vsync_listeners: Vec<(mpsc::UnboundedSender<VsyncEvent>, Option<DisplayId>)>,
44
45    // Simple counter to generate client-assigned integer identifiers.
46    id_counter: u64,
47
48    // Generate stamps for `commit_config()`.
49    stamp_counter: u64,
50}
51
52/// A vsync event payload.
53#[derive(Debug)]
54pub struct VsyncEvent {
55    /// The ID of the display that generated the vsync event.
56    pub id: DisplayId,
57
58    /// The monotonic timestamp of the vsync event.
59    pub timestamp: zx::MonotonicInstant,
60
61    /// The stamp of the latest fully applied display configuration.
62    pub config: display::ConfigStamp,
63}
64
65impl Coordinator {
66    /// Establishes a connection to the display-coordinator device and initialize a `Coordinator`
67    /// instance with the initial set of available displays. The returned `Coordinator` will
68    /// maintain FIDL connection to the underlying device as long as it is alive or the connection
69    /// is closed by the peer.
70    ///
71    /// Returns an error if
72    /// - No display-coordinator device is found within `TIMEOUT`.
73    /// - An initial OnDisplaysChanged event is not received from the display driver within
74    ///   `TIMEOUT` seconds.
75    ///
76    /// Current limitations:
77    ///   - This function connects to the first display-coordinator device that it observes. It
78    ///   currently does not support selection of a specific device if multiple display-coordinator
79    ///   devices are present.
80    // TODO(https://fxbug.dev/42168593): This will currently result in an error if no displays are present on
81    // the system (or if one is not attached within `TIMEOUT`). It wouldn't be neceesary to rely on
82    // a timeout if the display driver sent en event with no displays.
83    pub async fn init(client_priority: ClientPriority) -> Result<Coordinator> {
84        let service_proxy = Service::open(display::ServiceMarker)
85            .context("failed to open display Service")
86            .map_err(Error::DeviceConnectionError)?
87            .watch_for_any()
88            .map_err(Error::DeviceConnectionError)
89            .on_timeout(TIMEOUT.after_now(), || Err(Error::DeviceNotFound))
90            .await?;
91
92        let provider_proxy = service_proxy
93            .connect_to_provider()
94            .context("failed to connect to FIDL provider")
95            .map_err(|x| Error::DeviceConnectionError(x.into()))?;
96
97        let (coordinator_proxy, coordinator_server_end) =
98            fidl::endpoints::create_proxy::<display::CoordinatorMarker>();
99        let (coordinator_listener_client_end, coordinator_listener_requests) =
100            fidl::endpoints::create_request_stream::<display::CoordinatorListenerMarker>();
101
102        // TODO(https://fxbug.dev/42075865): Consider supporting virtcon client
103        // connections.
104        let payload = display::ProviderOpenCoordinatorRequest {
105            coordinator: Some(coordinator_server_end),
106            coordinator_listener: Some(coordinator_listener_client_end),
107            priority: Some(client_priority.into()),
108            __source_breaking: fidl::marker::SourceBreaking,
109        };
110        let () = provider_proxy.open_coordinator(payload).await?.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    /// Initialize a `Coordinator` instance from pre-established Coordinator and
120    /// CoordinatorListener channels.
121    ///
122    /// Returns an error if
123    /// - An initial OnDisplaysChanged event is not received from the display driver within
124    ///   `TIMEOUT` seconds.
125    // TODO(https://fxbug.dev/42168593): This will currently result in an error if no displays are
126    // present on the system (or if one is not attached within `TIMEOUT`). It wouldn't be neceesary
127    // to rely on a timeout if the display driver sent en event with no displays.
128    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    /// Returns a copy of the list of displays that are currently known to be present on the system.
151    pub fn displays(&self) -> Vec<DisplayInfo> {
152        self.inner.read().displays.clone()
153    }
154
155    /// Returns a clone of the underlying FIDL client proxy.
156    ///
157    /// Note: This can be helpful to prevent holding the inner RwLock when awaiting a chained FIDL
158    /// call over a proxy.
159    pub fn proxy(&self) -> display::CoordinatorProxy {
160        self.inner.read().proxy.clone()
161    }
162
163    /// Registers a channel to listen to vsync events.
164    pub fn add_vsync_listener(
165        &self,
166        id: Option<DisplayId>,
167    ) -> Result<mpsc::UnboundedReceiver<VsyncEvent>> {
168        // TODO(armansito): Switch to a bounded channel instead.
169        let (sender, receiver) = mpsc::unbounded::<VsyncEvent>();
170        self.inner.write().vsync_listeners.push((sender, id));
171        Ok(receiver)
172    }
173
174    /// Returns a Future that represents the FIDL event handling task. Once scheduled on an
175    /// executor, this task will continuously handle incoming FIDL events from the display stack
176    /// and the returned Future will not terminate until the FIDL channel is closed.
177    ///
178    /// This task can be scheduled safely on any thread.
179    pub async fn handle_events(&self) -> Result<()> {
180        let inner = self.inner.clone();
181        let mut events = inner.write().listener_requests.take().ok_or(Error::AlreadyRequested)?;
182        while let Some(msg) = events.try_next().await? {
183            match msg {
184                CoordinatorListenerRequest::OnDisplaysChanged {
185                    added,
186                    removed,
187                    control_handle: _,
188                } => {
189                    let removed =
190                        removed.into_iter().map(|id| id.into()).collect::<Vec<DisplayId>>();
191                    inner.read().handle_displays_changed(added, removed);
192                }
193                CoordinatorListenerRequest::OnVsync {
194                    display_id,
195                    timestamp,
196                    displayed_config_stamp,
197                    cookie,
198                    control_handle: _,
199                } => {
200                    inner.write().handle_vsync(
201                        display_id.into(),
202                        timestamp,
203                        displayed_config_stamp,
204                        cookie,
205                    )?;
206                }
207                _ => continue,
208            }
209        }
210        Ok(())
211    }
212
213    /// Allocates a new virtual hardware layer that is not associated with any display and has no
214    /// configuration.
215    pub async fn create_layer(&self) -> Result<LayerId> {
216        let layer_id = self.inner.write().next_free_layer_id()?;
217        self.proxy().create_layer(&layer_id.into()).await?.map_err(zx::Status::from_raw)?;
218        Ok(layer_id)
219    }
220
221    /// Creates and registers a zircon event with the display driver. The returned event can be
222    /// used as a fence in a display configuration.
223    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    /// Apply a display configuration. The client is expected to receive a vsync event once the
233    /// configuration is successfully applied. Returns an error if the FIDL message cannot be sent.
234    pub async fn commit_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, display_destination } => {
247                        let fidl_color = fidl_fuchsia_hardware_display_types::Color::from(color);
248                        proxy.set_layer_color_config(
249                            &layer.id.into(),
250                            &fidl_color,
251                            display_destination,
252                        )?;
253                    }
254                    LayerConfig::Primary { image_id, image_metadata, unblock_event, alpha } => {
255                        proxy.set_layer_primary_config(&layer.id.into(), &image_metadata)?;
256                        if let Some(alpha_config) = alpha {
257                            proxy.set_layer_primary_alpha(
258                                &layer.id.into(),
259                                alpha_config.mode,
260                                alpha_config.val,
261                            )?;
262                        }
263                        proxy.set_layer_image2(
264                            &layer.id.into(),
265                            &(*image_id).into(),
266                            &unblock_event.unwrap_or(INVALID_EVENT_ID).into(),
267                        )?;
268                    }
269                }
270            }
271        }
272
273        let result = proxy.check_config().await?;
274        if result != display_types::ConfigResult::Ok {
275            return Err(ConfigError::invalid(result));
276        }
277
278        let config_stamp = self.inner.write().next_config_stamp().unwrap();
279        let payload = fidl_fuchsia_hardware_display::CoordinatorCommitConfigRequest {
280            stamp: Some(fidl_fuchsia_hardware_display::ConfigStamp { value: config_stamp }),
281            ..Default::default()
282        };
283        match proxy.commit_config(payload) {
284            Ok(()) => Ok(config_stamp),
285            Err(err) => Err(ConfigError::from(err)),
286        }
287    }
288
289    /// Get the config stamp value of the most recent applied config in
290    /// `commit_config`. Returns an error if the FIDL message cannot be sent.
291    pub async fn get_recent_committed_config_stamp(&self) -> std::result::Result<u64, Error> {
292        let proxy = self.proxy();
293        let response = proxy.get_latest_committed_config_stamp().await?;
294        Ok(response.value)
295    }
296
297    /// Import a sysmem buffer collection. The returned `BufferCollectionId` can be used in future
298    /// API calls to refer to the imported collection.
299    pub(crate) async fn import_buffer_collection(
300        &self,
301        token: ClientEnd<fidl_fuchsia_sysmem2::BufferCollectionTokenMarker>,
302    ) -> Result<BufferCollectionId> {
303        let id = self.inner.write().next_free_collection_id()?;
304        let proxy = self.proxy();
305
306        // First import the token.
307        proxy.import_buffer_collection(&id.into(), token).await?.map_err(zx::Status::from_raw)?;
308
309        // Tell the driver to assign any device-specific constraints.
310        // TODO(https://fxbug.dev/42166207): These fields are effectively unused except for `type` in the case
311        // of IMAGE_TYPE_CAPTURE.
312        proxy
313            .set_buffer_collection_constraints(
314                &id.into(),
315                &display_types::ImageBufferUsage { tiling_type: IMAGE_TILING_TYPE_LINEAR },
316            )
317            .await?
318            .map_err(zx::Status::from_raw)?;
319        Ok(id)
320    }
321
322    /// Notify the display driver to release its handle on a previously imported buffer collection.
323    pub(crate) fn release_buffer_collection(&self, id: BufferCollectionId) -> Result<()> {
324        self.inner.read().proxy.release_buffer_collection(&id.into()).map_err(Error::from)
325    }
326
327    /// Register a sysmem buffer collection backed image to the display driver.
328    pub(crate) async fn import_image(
329        &self,
330        collection_id: BufferCollectionId,
331        image_id: ImageId,
332        image_metadata: display_types::ImageMetadata,
333    ) -> Result<()> {
334        self.proxy()
335            .import_image(
336                &image_metadata,
337                &collection_id.into(),
338                0, // buffer_index
339                &image_id.into(),
340            )
341            .await?
342            .map_err(zx::Status::from_raw)?;
343        Ok(())
344    }
345}
346
347// fmt::Debug implementation to allow a `Coordinator` instance to be used with a debug format
348// specifier. We use a custom implementation as not all `Coordinator` members derive fmt::Debug.
349impl fmt::Debug for Coordinator {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        f.debug_struct("Coordinator").field("displays", &self.displays()).finish()
352    }
353}
354
355impl CoordinatorInner {
356    fn next_free_collection_id(&mut self) -> Result<BufferCollectionId> {
357        self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
358        Ok(BufferCollectionId(self.id_counter))
359    }
360
361    fn next_free_event_id(&mut self) -> Result<EventId> {
362        self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
363        Ok(EventId(self.id_counter))
364    }
365
366    fn next_free_layer_id(&mut self) -> Result<LayerId> {
367        self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
368        Ok(LayerId(self.id_counter))
369    }
370
371    fn next_config_stamp(&mut self) -> Result<u64> {
372        self.stamp_counter = self.stamp_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
373        Ok(self.stamp_counter)
374    }
375
376    fn handle_displays_changed(&self, _added: Vec<display::Info>, _removed: Vec<DisplayId>) {
377        // TODO(armansito): update the displays list and notify clients. Terminate vsync listeners
378        // that are attached to a removed display.
379    }
380
381    fn handle_vsync(
382        &mut self,
383        display_id: DisplayId,
384        timestamp: zx::MonotonicInstant,
385        displayed_config_stamp: display::ConfigStamp,
386        cookie: display::VsyncAckCookie,
387    ) -> Result<()> {
388        if cookie.value != 0 {
389            self.proxy.acknowledge_vsync(cookie.value)?;
390        }
391
392        let mut listeners_to_remove = Vec::new();
393        for (pos, (sender, filter)) in self.vsync_listeners.iter().enumerate() {
394            // Skip the listener if it has a filter that does not match `display_id`.
395            if filter.as_ref().map_or(false, |id| *id != display_id) {
396                continue;
397            }
398            let payload = VsyncEvent { id: display_id, timestamp, config: displayed_config_stamp };
399            if let Err(e) = sender.unbounded_send(payload) {
400                if e.is_disconnected() {
401                    listeners_to_remove.push(pos);
402                } else {
403                    return Err(e.into());
404                }
405            }
406        }
407
408        // Clean up disconnected listeners.
409        listeners_to_remove.into_iter().for_each(|pos| {
410            self.vsync_listeners.swap_remove(pos);
411        });
412
413        Ok(())
414    }
415}
416
417// Waits for a single fuchsia.hardware.display.Coordinator.OnDisplaysChanged event and returns the
418// reported displays. By API contract, this event will fire at least once upon initial channel
419// connection if any displays are present. If no displays are present, then the returned Future
420// will not resolve until a display is plugged in.
421async fn wait_for_initial_displays(
422    listener_requests: &mut display::CoordinatorListenerRequestStream,
423) -> Result<Vec<display::Info>> {
424    let mut stream = listener_requests.try_filter_map(|event| match event {
425        CoordinatorListenerRequest::OnDisplaysChanged { added, removed: _, control_handle: _ } => {
426            future::ok(Some(added))
427        }
428        _ => future::ok(None),
429    });
430    stream.try_next().await?.ok_or(Error::NoDisplays)
431}
432
433#[cfg(test)]
434mod tests {
435    use super::{Coordinator, DisplayId, VsyncEvent};
436    use anyhow::{Context, Result, format_err};
437    use display_mocks::{MockCoordinator, create_proxy_and_mock};
438    use fidl_fuchsia_hardware_display as display;
439    use fidl_fuchsia_hardware_display_types as display_types;
440    use fuchsia_async::TestExecutor;
441    use futures::task::Poll;
442    use futures::{FutureExt, StreamExt, pin_mut, select};
443    use googletest::{assert_that, expect_eq, expect_that, gtest, matchers};
444
445    async fn init_with_proxy_and_listener_requests(
446        coordinator_proxy: display::CoordinatorProxy,
447        listener_requests: display::CoordinatorListenerRequestStream,
448    ) -> Result<Coordinator> {
449        Coordinator::init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests)
450            .await
451            .context("failed to initialize Coordinator")
452    }
453
454    // Returns a Coordinator and a connected mock FIDL server. This function sets up the initial
455    // "OnDisplaysChanged" event with the given list of `displays`, which `Coordinator` requires
456    // before it can resolve its initialization Future.
457    async fn init_with_displays(
458        displays: &[display::Info],
459    ) -> Result<(Coordinator, MockCoordinator)> {
460        let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
461        mock.assign_displays(displays.to_vec())?;
462
463        Ok((
464            init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?,
465            mock,
466        ))
467    }
468
469    #[gtest]
470    #[fuchsia::test]
471    async fn test_init_fails_with_no_device_dir() {
472        let result = Coordinator::init(crate::types::ClientPriority(300)).await;
473        expect_that!(&result, matchers::err(matchers::anything()));
474    }
475
476    #[gtest]
477    #[fuchsia::test]
478    async fn test_init_with_no_displays() -> Result<()> {
479        let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
480        mock.assign_displays([].to_vec())?;
481
482        let coordinator =
483            init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?;
484        expect_that!(&coordinator.displays(), matchers::is_empty());
485
486        Ok(())
487    }
488
489    // TODO(https://fxbug.dev/42075852): We should have an automated test verifying that
490    // the service provided by driver framework can be opened correctly.
491
492    #[gtest]
493    #[fuchsia::test]
494    async fn test_init_with_displays() -> Result<()> {
495        let displays = [
496            display::Info {
497                id: display_types::DisplayId { value: 1 },
498                modes: Vec::new(),
499                pixel_format: Vec::new(),
500                manufacturer_name: "Foo".to_string(),
501                monitor_name: "what".to_string(),
502                monitor_serial: "".to_string(),
503                horizontal_size_mm: 0,
504                vertical_size_mm: 0,
505                using_fallback_size: false,
506                max_layer_count: 1,
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                max_layer_count: 1,
519            },
520        ]
521        .to_vec();
522        let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
523        mock.assign_displays(displays.clone())?;
524
525        let coordinator =
526            init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?;
527        expect_eq!(coordinator.displays().len(), 2);
528        expect_eq!(coordinator.displays()[0].0, displays[0]);
529        expect_eq!(coordinator.displays()[1].0, displays[1]);
530
531        Ok(())
532    }
533
534    #[gtest]
535    #[test]
536    fn test_vsync_listener_single() -> Result<()> {
537        // Drive an executor directly for this test to avoid having to rely on timeouts for cases
538        // in which no events are received.
539        let mut executor = TestExecutor::new();
540        let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
541        let mut vsync = coordinator.add_vsync_listener(None)?;
542
543        const ID: DisplayId = DisplayId(1);
544        const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
545        let event_handlers = async {
546            select! {
547                event = vsync.next() => event.ok_or_else(|| format_err!("did not receive vsync event")),
548                result = coordinator.handle_events().fuse() => {
549                    result.context("FIDL event handler failed")?;
550                    Err(format_err!("FIDL event handler completed before client vsync event"))
551                },
552            }
553        };
554        pin_mut!(event_handlers);
555
556        // Send a single event.
557        mock.emit_vsync_event(ID.0, STAMP)?;
558        let vsync_event = executor.run_until_stalled(&mut event_handlers);
559        assert_that!(&vsync_event, matchers::matches_pattern!(Poll::Ready(matchers::anything())));
560        let Poll::Ready(result) = vsync_event else { unreachable!() };
561        let VsyncEvent { id, config, .. } = result.unwrap();
562        expect_eq!(id, ID);
563        expect_eq!(config, STAMP);
564
565        Ok(())
566    }
567
568    #[gtest]
569    #[test]
570    fn test_vsync_listener_multiple() -> Result<()> {
571        // Drive an executor directly for this test to avoid having to rely on timeouts for cases
572        // in which no events are received.
573        let mut executor = TestExecutor::new();
574        let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
575        let mut vsync = coordinator.add_vsync_listener(None)?;
576
577        let fidl_server = coordinator.handle_events().fuse();
578        pin_mut!(fidl_server);
579
580        const ID1: DisplayId = DisplayId(1);
581        const ID2: DisplayId = DisplayId(2);
582        const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
583
584        // Queue multiple events.
585        mock.emit_vsync_event(ID1.0, STAMP)?;
586        mock.emit_vsync_event(ID2.0, STAMP)?;
587        mock.emit_vsync_event(ID1.0, STAMP)?;
588
589        // Process the FIDL events. The FIDL server Future should not complete as it runs
590        // indefinitely.
591        let fidl_server_result = executor.run_until_stalled(&mut fidl_server);
592        assert_that!(fidl_server_result, matchers::matches_pattern!(Poll::Pending));
593
594        // Process the vsync listener.
595        let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
596        assert_that!(&vsync_event, matchers::matches_pattern!(Poll::Ready(matchers::anything())));
597        let Poll::Ready(Some(VsyncEvent { id, config, .. })) = vsync_event else { unreachable!() };
598        expect_eq!(id, ID1);
599        expect_eq!(config, STAMP);
600
601        let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
602        assert_that!(&vsync_event, matchers::matches_pattern!(Poll::Ready(matchers::anything())));
603        let Poll::Ready(Some(VsyncEvent { id, config, .. })) = vsync_event else { unreachable!() };
604        expect_eq!(id, ID2);
605        expect_eq!(config, STAMP);
606
607        let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
608        assert_that!(&vsync_event, matchers::matches_pattern!(Poll::Ready(matchers::anything())));
609        let Poll::Ready(Some(VsyncEvent { id, config, .. })) = vsync_event else { unreachable!() };
610        expect_eq!(id, ID1);
611        expect_eq!(config, STAMP);
612
613        Ok(())
614    }
615
616    #[gtest]
617    #[test]
618    fn test_vsync_listener_display_id_filter() -> Result<()> {
619        // Drive an executor directly for this test to avoid having to rely on timeouts for cases
620        // in which no events are received.
621        let mut executor = TestExecutor::new();
622        let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
623
624        const ID1: DisplayId = DisplayId(1);
625        const ID2: DisplayId = DisplayId(2);
626        const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
627
628        // Listen to events from ID2.
629        let mut vsync = coordinator.add_vsync_listener(Some(ID2))?;
630        let event_handlers = async {
631            select! {
632                event = vsync.next() => event.ok_or_else(|| format_err!("did not receive vsync event")),
633                result = coordinator.handle_events().fuse() => {
634                    result.context("FIDL event handler failed")?;
635                    Err(format_err!("FIDL event handler completed before client vsync event"))
636                },
637            }
638        };
639        pin_mut!(event_handlers);
640
641        // Event from ID1 should get filtered out and the client should not receive any events.
642        mock.emit_vsync_event(ID1.0, STAMP)?;
643        let vsync_event = executor.run_until_stalled(&mut event_handlers);
644        assert_that!(vsync_event, matchers::matches_pattern!(Poll::Pending));
645
646        // Event from ID2 should be received.
647        mock.emit_vsync_event(ID2.0, STAMP)?;
648        let vsync_event = executor.run_until_stalled(&mut event_handlers);
649        assert_that!(&vsync_event, matchers::matches_pattern!(Poll::Ready(matchers::anything())));
650        let Poll::Ready(result) = vsync_event else { unreachable!() };
651        let VsyncEvent { id, config, .. } = result.unwrap();
652        expect_eq!(id, ID2);
653        expect_eq!(config, STAMP);
654
655        Ok(())
656    }
657}