Skip to main content

input_pipeline_dso/
mouse_binding.rs

1// Copyright 2019 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 crate::input_device::{self, Handled, InputDeviceBinding, InputDeviceStatus, InputEvent};
6use crate::utils::{self, Position};
7use crate::{Transport, metrics};
8use anyhow::{Error, format_err};
9use async_trait::async_trait;
10use fuchsia_inspect::ArrayProperty;
11use fuchsia_inspect::health::Reporter;
12use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
13use metrics_registry::*;
14use sorted_vec_map::SortedVecSet;
15use zx;
16
17pub type MouseButton = u8;
18
19/// Flag to indicate the scroll event is from device reporting precision delta.
20#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum PrecisionScroll {
22    /// Touchpad and some mouse able to report precision delta.
23    Yes,
24    /// Tick based mouse wheel.
25    No,
26}
27
28/// A [`MouseLocation`] represents the mouse pointer location at the time of a pointer event.
29#[derive(Copy, Clone, Debug, PartialEq)]
30pub enum MouseLocation {
31    /// A mouse movement relative to its current position.
32    Relative(RelativeLocation),
33
34    /// An absolute position, in device coordinates.
35    Absolute(Position),
36}
37
38#[derive(Copy, Clone, Debug, PartialEq)]
39pub enum MousePhase {
40    Down,  // One or more buttons were newly pressed.
41    Move,  // The mouse moved with no change in button state.
42    Up,    // One or more buttons were newly released.
43    Wheel, // Mouse wheel is rotating.
44}
45
46/// A [`RelativeLocation`] contains the relative mouse pointer location at the time of a pointer event.
47#[derive(Copy, Clone, Debug, PartialEq)]
48pub struct RelativeLocation {
49    /// A pointer location in counts.
50    pub counts: Position,
51}
52
53impl Default for RelativeLocation {
54    fn default() -> Self {
55        RelativeLocation { counts: Position::zero() }
56    }
57}
58
59/// A [`WheelDelta`] contains raw wheel delta ticks from driver or gesture arena
60/// and scaled wheel delta in physical pixels.
61#[derive(Clone, Debug, PartialEq)]
62pub struct WheelDelta {
63    pub ticks: i64,
64    pub physical_pixel: Option<f32>,
65}
66
67/// A [`MouseEvent`] represents a pointer event with a specified phase, and the buttons
68/// involved in said phase. The supported phases for mice include Up, Down, and Move.
69///
70/// # Example
71/// The following MouseEvent represents a relative movement of 40 units in the x axis
72/// and 20 units in the y axis while holding the primary button (1) down.
73///
74/// ```
75/// let mouse_device_event = input_device::InputDeviceEvent::Mouse(MouseEvent::new(
76///     MouseLocation::Relative(RelativeLocation {
77///       counts: Position { x: 40.0, y: 20.0 },
78///     }),
79///     None, // wheel_delta_v
80///     None, // wheel_delta_h
81///     MousePhase::Move,
82///     SortedVecSet::from(vec![1]),
83///     SortedVecSet::from(vec![1]),
84///     None, // is_precision_scroll
85///     None, // wake_lease
86/// ));
87/// ```
88#[derive(Debug)]
89pub struct MouseEvent {
90    /// The mouse location.
91    pub location: MouseLocation,
92
93    /// The mouse wheel rotated delta in vertical.
94    pub wheel_delta_v: Option<WheelDelta>,
95
96    /// The mouse wheel rotated delta in horizontal.
97    pub wheel_delta_h: Option<WheelDelta>,
98
99    /// The mouse device reports precision scroll delta.
100    pub is_precision_scroll: Option<PrecisionScroll>,
101
102    /// The phase of the [`buttons`] associated with this input event.
103    pub phase: MousePhase,
104
105    /// The buttons relevant to this event.
106    pub affected_buttons: SortedVecSet<MouseButton>,
107
108    /// The complete button state including this event.
109    pub pressed_buttons: SortedVecSet<MouseButton>,
110
111    /// The wake lease for this event.
112    pub wake_lease: Option<zx::EventPair>,
113}
114
115impl Clone for MouseEvent {
116    fn clone(&self) -> Self {
117        log::debug!("MouseEvent cloned without wake lease.");
118        Self {
119            location: self.location,
120            wheel_delta_v: self.wheel_delta_v.clone(),
121            wheel_delta_h: self.wheel_delta_h.clone(),
122            is_precision_scroll: self.is_precision_scroll,
123            phase: self.phase,
124            affected_buttons: self.affected_buttons.clone(),
125            pressed_buttons: self.pressed_buttons.clone(),
126            wake_lease: None,
127        }
128    }
129}
130
131impl PartialEq for MouseEvent {
132    fn eq(&self, other: &Self) -> bool {
133        self.location == other.location
134            && self.wheel_delta_v == other.wheel_delta_v
135            && self.wheel_delta_h == other.wheel_delta_h
136            && self.is_precision_scroll == other.is_precision_scroll
137            && self.phase == other.phase
138            && self.affected_buttons == other.affected_buttons
139            && self.pressed_buttons == other.pressed_buttons
140    }
141}
142
143impl MouseEvent {
144    /// Creates a new [`MouseEvent`].
145    ///
146    /// # Parameters
147    /// - `location`: The mouse location.
148    /// - `phase`: The phase of the [`buttons`] associated with this input event.
149    /// - `buttons`: The buttons relevant to this event.
150    /// - `wake_lease`: The wake lease for this event.
151    pub fn new(
152        location: MouseLocation,
153        wheel_delta_v: Option<WheelDelta>,
154        wheel_delta_h: Option<WheelDelta>,
155        phase: MousePhase,
156        affected_buttons: SortedVecSet<MouseButton>,
157        pressed_buttons: SortedVecSet<MouseButton>,
158        is_precision_scroll: Option<PrecisionScroll>,
159        wake_lease: Option<zx::EventPair>,
160    ) -> MouseEvent {
161        MouseEvent {
162            location,
163            wheel_delta_v,
164            wheel_delta_h,
165            phase,
166            affected_buttons,
167            pressed_buttons,
168            is_precision_scroll,
169            wake_lease,
170        }
171    }
172
173    pub fn record_inspect(&self, node: &fuchsia_inspect::Node) {
174        // Note: location coordinates (`self.location`) are omitted to avoid exposing sensitive user behavior.
175        if let Some(wheel_delta_v) = &self.wheel_delta_v {
176            node.record_child("wheel_delta_v", move |wheel_delta_v_node| {
177                wheel_delta_v_node.record_int("ticks", wheel_delta_v.ticks);
178                if let Some(physical_pixel) = wheel_delta_v.physical_pixel {
179                    wheel_delta_v_node.record_double("physical_pixel", f64::from(physical_pixel));
180                }
181            });
182        }
183
184        if let Some(wheel_delta_h) = &self.wheel_delta_h {
185            node.record_child("wheel_delta_h", move |wheel_delta_h_node| {
186                wheel_delta_h_node.record_int("ticks", wheel_delta_h.ticks);
187                if let Some(physical_pixel) = wheel_delta_h.physical_pixel {
188                    wheel_delta_h_node.record_double("physical_pixel", f64::from(physical_pixel));
189                }
190            });
191        }
192
193        if let Some(is_precision_scroll) = self.is_precision_scroll {
194            match is_precision_scroll {
195                PrecisionScroll::Yes => node.record_string("is_precision_scroll", "yes"),
196                PrecisionScroll::No => node.record_string("is_precision_scroll", "no"),
197            }
198        }
199
200        match self.phase {
201            MousePhase::Down => node.record_string("phase", "down"),
202            MousePhase::Move => node.record_string("phase", "move"),
203            MousePhase::Up => node.record_string("phase", "up"),
204            MousePhase::Wheel => node.record_string("phase", "wheel"),
205        }
206
207        let affected_buttons_node =
208            node.create_uint_array("affected_buttons", self.affected_buttons.len());
209        self.affected_buttons.iter().enumerate().for_each(|(i, button)| {
210            affected_buttons_node.set(i, *button);
211        });
212        node.record(affected_buttons_node);
213
214        let pressed_buttons_node =
215            node.create_uint_array("pressed_buttons", self.pressed_buttons.len());
216        self.pressed_buttons.iter().enumerate().for_each(|(i, button)| {
217            pressed_buttons_node.set(i, *button);
218        });
219        node.record(pressed_buttons_node);
220    }
221}
222
223/// A [`MouseBinding`] represents a connection to a mouse input device.
224///
225/// The [`MouseBinding`] parses and exposes mouse descriptor properties (e.g., the range of
226/// possible x values) for the device it is associated with. It also parses [`InputReport`]s
227/// from the device, and sends them to the device binding owner over `event_sender`.
228pub struct MouseBinding {
229    /// The channel to stream InputEvents to.
230    event_sender: UnboundedSender<Vec<InputEvent>>,
231
232    /// Holds information about this device.
233    device_descriptor: MouseDeviceDescriptor,
234}
235
236#[derive(Clone, Debug, Eq, PartialEq)]
237pub struct MouseDeviceDescriptor {
238    /// The id of the connected mouse input device.
239    pub device_id: u32,
240
241    /// The range of possible x values of absolute mouse positions reported by this device.
242    pub absolute_x_range: Option<fidl_fuchsia_input::Range>,
243
244    /// The range of possible y values of absolute mouse positions reported by this device.
245    pub absolute_y_range: Option<fidl_fuchsia_input::Range>,
246
247    /// The range of possible vertical wheel delta reported by this device.
248    pub wheel_v_range: Option<fidl_fuchsia_input::Axis>,
249
250    /// The range of possible horizontal wheel delta reported by this device.
251    pub wheel_h_range: Option<fidl_fuchsia_input::Axis>,
252
253    /// This is a vector of ids for the mouse buttons.
254    pub buttons: Option<Vec<MouseButton>>,
255}
256
257#[async_trait]
258impl input_device::InputDeviceBinding for MouseBinding {
259    fn input_event_sender(&self) -> UnboundedSender<Vec<InputEvent>> {
260        self.event_sender.clone()
261    }
262
263    fn get_device_descriptor(&self) -> input_device::InputDeviceDescriptor {
264        input_device::InputDeviceDescriptor::Mouse(self.device_descriptor.clone())
265    }
266}
267
268impl MouseBinding {
269    /// Creates a new [`InputDeviceBinding`] from the `device_proxy`.
270    ///
271    /// The binding will start listening for input reports immediately and send new InputEvents
272    /// to the device binding owner over `input_event_sender`.
273    ///
274    /// # Parameters
275    /// - `device_proxy`: The proxy to bind the new [`InputDeviceBinding`] to.
276    /// - `device_id`: The id of the connected mouse device.
277    /// - `input_event_sender`: The channel to send new InputEvents to.
278    /// - `device_node`: The inspect node for this device binding
279    /// - `metrics_logger`: The metrics logger.
280    ///
281    /// # Errors
282    /// If there was an error binding to the proxy.
283    pub async fn new(
284        device_proxy: fidl_next::Client<fidl_next_fuchsia_input_report::InputDevice, Transport>,
285        device_id: u32,
286        input_event_sender: UnboundedSender<Vec<InputEvent>>,
287        device_node: fuchsia_inspect::Node,
288        _feature_flags: input_device::InputPipelineFeatureFlags,
289        metrics_logger: metrics::MetricsLogger,
290    ) -> Result<Self, Error> {
291        let (device_binding, mut inspect_status) =
292            Self::bind_device(&device_proxy, device_id, input_event_sender, device_node).await?;
293        inspect_status.health_node.set_ok();
294        input_device::initialize_report_stream(
295            device_proxy,
296            device_binding.get_device_descriptor(),
297            device_binding.input_event_sender(),
298            inspect_status,
299            metrics_logger,
300            _feature_flags,
301            Self::process_reports,
302        );
303
304        Ok(device_binding)
305    }
306
307    /// Binds the provided input device to a new instance of `Self`.
308    ///
309    /// # Parameters
310    /// - `device`: The device to use to initialize the binding.
311    /// - `device_id`: The id of the connected mouse device.
312    /// - `input_event_sender`: The channel to send new InputEvents to.
313    /// - `device_node`: The inspect node for this device binding
314    ///
315    /// # Errors
316    /// If the device descriptor could not be retrieved, or the descriptor could
317    /// not be parsed correctly.
318    async fn bind_device(
319        device: &fidl_next::Client<fidl_next_fuchsia_input_report::InputDevice, Transport>,
320        device_id: u32,
321        input_event_sender: UnboundedSender<Vec<InputEvent>>,
322        device_node: fuchsia_inspect::Node,
323    ) -> Result<(Self, InputDeviceStatus), Error> {
324        let mut input_device_status = InputDeviceStatus::new(device_node);
325        let device_descriptor: fidl_next_fuchsia_input_report::DeviceDescriptor = match device
326            .get_descriptor()
327            .await
328        {
329            Ok(res) => res.descriptor,
330            Err(_) => {
331                input_device_status.health_node.set_unhealthy("Could not get device descriptor.");
332                return Err(format_err!("Could not get descriptor for device_id: {}", device_id));
333            }
334        };
335
336        let mouse_descriptor = device_descriptor.mouse.ok_or_else(|| {
337            input_device_status
338                .health_node
339                .set_unhealthy("DeviceDescriptor does not have a MouseDescriptor.");
340            format_err!("DeviceDescriptor does not have a MouseDescriptor")
341        })?;
342
343        let mouse_input_descriptor = mouse_descriptor.input.ok_or_else(|| {
344            input_device_status
345                .health_node
346                .set_unhealthy("MouseDescriptor does not have a MouseInputDescriptor.");
347            format_err!("MouseDescriptor does not have a MouseInputDescriptor")
348        })?;
349
350        let device_descriptor: MouseDeviceDescriptor = MouseDeviceDescriptor {
351            device_id,
352            absolute_x_range: mouse_input_descriptor
353                .position_x
354                .as_ref()
355                .map(|axis| utils::range_to_old(&axis.range)),
356            absolute_y_range: mouse_input_descriptor
357                .position_y
358                .as_ref()
359                .map(|axis| utils::range_to_old(&axis.range)),
360            wheel_v_range: utils::axis_to_old(mouse_input_descriptor.scroll_v.as_ref()),
361            wheel_h_range: utils::axis_to_old(mouse_input_descriptor.scroll_h.as_ref()),
362            buttons: mouse_input_descriptor.buttons,
363        };
364
365        Ok((Self { event_sender: input_event_sender, device_descriptor }, input_device_status))
366    }
367
368    /// Parses an [`InputReport`] into one or more [`InputEvent`]s.
369    ///
370    /// The [`InputEvent`]s are sent to the device binding owner via [`input_event_sender`].
371    ///
372    /// # Parameters
373    /// `reports`: The incoming [`InputReport`].
374    /// `previous_report`: The previous [`InputReport`] seen for the same device. This can be
375    ///                    used to determine, for example, which keys are no longer present in
376    ///                    a keyboard report to generate key released events. If `None`, no
377    ///                    previous report was found.
378    /// `device_descriptor`: The descriptor for the input device generating the input reports.
379    /// `input_event_sender`: The sender for the device binding's input event stream.
380    ///
381    /// # Returns
382    /// An [`InputReport`] which will be passed to the next call to [`process_reports`], as
383    /// [`previous_report`]. If `None`, the next call's [`previous_report`] will be `None`.
384    /// A [`UnboundedReceiver<InputEvent>`] which will poll asynchronously generated events to be
385    /// recorded by `inspect_status` in `input_device::initialize_report_stream()`. If device
386    /// binding does not generate InputEvents asynchronously, this will be `None`.
387    ///
388    /// The returned [`InputReport`] is guaranteed to have no `wake_lease`.
389    fn process_reports(
390        reports: &[fidl_next_fuchsia_input_report::wire::InputReport<'_>],
391        mut previous_state: Option<input_device::PreviousDeviceState>,
392        device_descriptor: &input_device::InputDeviceDescriptor,
393        input_event_sender: &mut UnboundedSender<Vec<InputEvent>>,
394        inspect_status: &InputDeviceStatus,
395        metrics_logger: &metrics::MetricsLogger,
396        _feature_flags: &input_device::InputPipelineFeatureFlags,
397    ) -> (Option<input_device::PreviousDeviceState>, Option<UnboundedReceiver<InputEvent>>) {
398        fuchsia_trace::duration!("input", "mouse-binding-process-reports", "num_reports" => reports.len());
399        for report in reports {
400            previous_state = Self::process_report(
401                report,
402                previous_state,
403                device_descriptor,
404                input_event_sender,
405                inspect_status,
406                metrics_logger,
407            );
408        }
409        (previous_state, None)
410    }
411
412    fn process_report(
413        report: &fidl_next_fuchsia_input_report::wire::InputReport<'_>,
414        previous_state: Option<input_device::PreviousDeviceState>,
415        device_descriptor: &input_device::InputDeviceDescriptor,
416        input_event_sender: &mut UnboundedSender<Vec<input_device::InputEvent>>,
417        inspect_status: &InputDeviceStatus,
418        metrics_logger: &metrics::MetricsLogger,
419    ) -> Option<input_device::PreviousDeviceState> {
420        if let Some(trace_id) = report.trace_id() {
421            fuchsia_trace::flow_end!("input", "input_report", trace_id.0.into());
422        }
423
424        // Extract the wake_lease early to prevent it from leaking. If this is moved
425        // below an early return, the lease could accidentally be stored inside
426        // `previous_report`, which would prevent the system from suspending.
427        let wake_lease = utils::duplicate_wake_lease(report.wake_lease());
428
429        inspect_status.count_received_report_wire(report);
430        // Input devices can have multiple types so ensure `report` is a MouseInputReport.
431        let mouse_report = match report.mouse() {
432            Some(mouse) => mouse,
433            None => {
434                inspect_status.count_filtered_report();
435                return previous_state;
436            }
437        };
438
439        let previous_buttons: SortedVecSet<MouseButton> = match previous_state {
440            Some(input_device::PreviousDeviceState::Mouse { pressed_buttons }) => pressed_buttons,
441            _ => SortedVecSet::new(),
442        };
443        let current_buttons: SortedVecSet<MouseButton> =
444            buttons_from_mouse_report_wire(mouse_report);
445
446        // Send a Down event with:
447        // * affected_buttons: the buttons that were pressed since the previous report,
448        //   i.e. that are in the current report, but were not in the previous report.
449        // * pressed_buttons: the full set of currently pressed buttons, including the
450        //   recently pressed ones (affected_buttons).
451        send_mouse_event(
452            MouseLocation::Relative(Default::default()),
453            None, /* wheel_delta_v */
454            None, /* wheel_delta_h */
455            MousePhase::Down,
456            current_buttons.difference(&previous_buttons).cloned().collect(),
457            current_buttons.clone(),
458            device_descriptor,
459            input_event_sender,
460            inspect_status,
461            metrics_logger,
462            wake_lease.as_ref().map(|lease| {
463                lease
464                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
465                    .expect("failed to duplicate event pair")
466            }),
467        );
468
469        // Create a location for the move event. Use the absolute position if available.
470        let location = if let (Some(position_x), Some(position_y)) =
471            (mouse_report.position_x(), mouse_report.position_y())
472        {
473            MouseLocation::Absolute(Position { x: position_x.0 as f32, y: position_y.0 as f32 })
474        } else {
475            let movement_x = mouse_report.movement_x().map(|x| x.0).unwrap_or_default() as f32;
476            let movement_y = mouse_report.movement_y().map(|y| y.0).unwrap_or_default() as f32;
477            MouseLocation::Relative(RelativeLocation {
478                counts: Position { x: movement_x, y: movement_y },
479            })
480        };
481
482        // Send a Move event with buttons from both the current report and the previous report.
483        // * affected_buttons and pressed_buttons are identical in this case, since the full
484        //   set of currently pressed buttons are the same set affected by the event.
485        send_mouse_event(
486            location,
487            None, /* wheel_delta_v */
488            None, /* wheel_delta_h */
489            MousePhase::Move,
490            current_buttons.union(&previous_buttons).cloned().collect(),
491            current_buttons.union(&previous_buttons).cloned().collect(),
492            device_descriptor,
493            input_event_sender,
494            inspect_status,
495            metrics_logger,
496            wake_lease.as_ref().map(|lease| {
497                lease
498                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
499                    .expect("failed to duplicate event pair")
500            }),
501        );
502
503        let wheel_delta_v = mouse_report
504            .scroll_v()
505            .map(|ticks| WheelDelta { ticks: ticks.0, physical_pixel: None });
506
507        let wheel_delta_h = mouse_report
508            .scroll_h()
509            .map(|ticks| WheelDelta { ticks: ticks.0, physical_pixel: None });
510
511        // Send a mouse wheel event.
512        send_mouse_event(
513            MouseLocation::Relative(Default::default()),
514            wheel_delta_v,
515            wheel_delta_h,
516            MousePhase::Wheel,
517            current_buttons.union(&previous_buttons).cloned().collect(),
518            current_buttons.union(&previous_buttons).cloned().collect(),
519            device_descriptor,
520            input_event_sender,
521            inspect_status,
522            metrics_logger,
523            wake_lease.as_ref().map(|lease| {
524                lease
525                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
526                    .expect("failed to duplicate event pair")
527            }),
528        );
529
530        // Send an Up event with:
531        // * affected_buttons: the buttons that were released since the previous report,
532        //   i.e. that were in the previous report, but are not in the current report.
533        // * pressed_buttons: the full set of currently pressed buttons, excluding the
534        //   recently released ones (affected_buttons).
535        send_mouse_event(
536            MouseLocation::Relative(Default::default()),
537            None, /* wheel_delta_v */
538            None, /* wheel_delta_h */
539            MousePhase::Up,
540            previous_buttons.difference(&current_buttons).cloned().collect(),
541            current_buttons.clone(),
542            device_descriptor,
543            input_event_sender,
544            inspect_status,
545            metrics_logger,
546            wake_lease,
547        );
548
549        Some(input_device::PreviousDeviceState::Mouse { pressed_buttons: current_buttons })
550    }
551}
552
553/// Sends an InputEvent over `sender`.
554///
555/// When no buttons are present, only [`MousePhase::Move`] events will
556/// be sent.
557///
558/// # Parameters
559/// - `location`: The mouse location.
560/// - `wheel_delta_v`: The mouse wheel delta in vertical.
561/// - `wheel_delta_h`: The mouse wheel delta in horizontal.
562/// - `phase`: The phase of the [`buttons`] associated with the input event.
563/// - `buttons`: The buttons relevant to the event.
564/// - `device_descriptor`: The descriptor for the input device generating the input reports.
565/// - `sender`: The stream to send the MouseEvent to.
566/// - `wake_lease`: The wake lease to send with the event.
567fn send_mouse_event(
568    location: MouseLocation,
569    wheel_delta_v: Option<WheelDelta>,
570    wheel_delta_h: Option<WheelDelta>,
571    phase: MousePhase,
572    affected_buttons: SortedVecSet<MouseButton>,
573    pressed_buttons: SortedVecSet<MouseButton>,
574    device_descriptor: &input_device::InputDeviceDescriptor,
575    sender: &mut UnboundedSender<Vec<input_device::InputEvent>>,
576    inspect_status: &InputDeviceStatus,
577    metrics_logger: &metrics::MetricsLogger,
578    wake_lease: Option<zx::EventPair>,
579) {
580    // Only send Down/Up events when there are buttons affected.
581    if (phase == MousePhase::Down || phase == MousePhase::Up) && affected_buttons.is_empty() {
582        return;
583    }
584
585    // Don't send Move events when there is no relative movement.
586    // However, absolute movement is always reported.
587    if phase == MousePhase::Move && location == MouseLocation::Relative(Default::default()) {
588        return;
589    }
590
591    // Only send wheel events when the delta has value.
592    if phase == MousePhase::Wheel && wheel_delta_v.is_none() && wheel_delta_h.is_none() {
593        return;
594    }
595
596    let trace_id = fuchsia_trace::Id::new();
597    fuchsia_trace::duration!("input", "mouse-binding-send-event");
598    fuchsia_trace::flow_begin!("input", "event_in_input_pipeline", trace_id);
599
600    let event = input_device::InputEvent {
601        device_event: input_device::InputDeviceEvent::Mouse(MouseEvent::new(
602            location,
603            wheel_delta_v,
604            wheel_delta_h,
605            phase,
606            affected_buttons,
607            pressed_buttons,
608            match phase {
609                MousePhase::Wheel => Some(PrecisionScroll::No),
610                _ => None,
611            },
612            wake_lease,
613        )),
614        device_descriptor: device_descriptor.clone(),
615        event_time: zx::MonotonicInstant::get(),
616        handled: Handled::No,
617        trace_id: Some(trace_id),
618    };
619    let events = vec![event];
620    inspect_status.count_generated_events(&events);
621
622    if let Err(e) = sender.unbounded_send(events) {
623        metrics_logger.log_error(
624            InputPipelineErrorMetricDimensionEvent::MouseFailedToSendEvent,
625            std::format!("Failed to send MouseEvent with error: {:?}", e),
626        );
627    }
628}
629
630fn buttons_from_mouse_report_wire(
631    mouse_report: &fidl_next_fuchsia_input_report::wire::MouseInputReport<'_>,
632) -> SortedVecSet<MouseButton> {
633    mouse_report
634        .pressed_buttons()
635        .map(|buttons| SortedVecSet::from_iter(buttons.iter().copied()))
636        .unwrap_or_default()
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642    use crate::testing_utilities;
643    use futures::StreamExt;
644    use sorted_vec_map::SortedVecSet;
645
646    const DEVICE_ID: u32 = 1;
647
648    fn mouse_device_descriptor(device_id: u32) -> input_device::InputDeviceDescriptor {
649        input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor {
650            device_id,
651            absolute_x_range: None,
652            absolute_y_range: None,
653            wheel_v_range: Some(fidl_fuchsia_input::Axis {
654                range: fidl_fuchsia_input::Range { min: -1, max: 1 },
655                unit: fidl_fuchsia_input::Unit {
656                    type_: fidl_fuchsia_input::UnitType::Other,
657                    exponent: 1,
658                },
659            }),
660            wheel_h_range: Some(fidl_fuchsia_input::Axis {
661                range: fidl_fuchsia_input::Range { min: -1, max: 1 },
662                unit: fidl_fuchsia_input::Unit {
663                    type_: fidl_fuchsia_input::UnitType::Other,
664                    exponent: 1,
665                },
666            }),
667            buttons: None,
668        })
669    }
670
671    fn wheel_delta_ticks(ticks: i64) -> Option<WheelDelta> {
672        Some(WheelDelta { ticks, physical_pixel: None })
673    }
674
675    /// Tests that a report containing no buttons but with movement generates a move event.
676    #[fuchsia::test]
677    async fn movement_without_button() {
678        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
679        let first_report = testing_utilities::create_mouse_input_report_relative(
680            Position { x: 10.0, y: 16.0 },
681            None, /* scroll_v */
682            None, /* scroll_h */
683            vec![],
684            event_time_i64,
685        );
686        let descriptor = mouse_device_descriptor(DEVICE_ID);
687
688        let input_reports = vec![first_report];
689        let expected_events = vec![testing_utilities::create_mouse_event(
690            MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
691            None, /* wheel_delta_v */
692            None, /* wheel_delta_h */
693            None, /* is_precision_scroll */
694            MousePhase::Move,
695            SortedVecSet::new(),
696            SortedVecSet::new(),
697            event_time_u64,
698            &descriptor,
699        )];
700
701        assert_input_report_sequence_generates_events!(
702            input_reports: input_reports,
703            expected_events: expected_events,
704            device_descriptor: descriptor,
705            device_type: MouseBinding,
706        );
707    }
708
709    /// Tests that a report containing a new mouse button generates a down event.
710    #[fuchsia::test]
711    async fn down_without_movement() {
712        let mouse_button: MouseButton = 3;
713        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
714        let first_report = testing_utilities::create_mouse_input_report_relative(
715            Position::zero(),
716            None, /* scroll_v */
717            None, /* scroll_h */
718            vec![mouse_button],
719            event_time_i64,
720        );
721        let descriptor = mouse_device_descriptor(DEVICE_ID);
722
723        let input_reports = vec![first_report];
724        let expected_events = vec![testing_utilities::create_mouse_event(
725            MouseLocation::Relative(Default::default()),
726            None, /* wheel_delta_v */
727            None, /* wheel_delta_h */
728            None, /* is_precision_scroll */
729            MousePhase::Down,
730            SortedVecSet::from(vec![mouse_button]),
731            SortedVecSet::from(vec![mouse_button]),
732            event_time_u64,
733            &descriptor,
734        )];
735
736        assert_input_report_sequence_generates_events!(
737            input_reports: input_reports,
738            expected_events: expected_events,
739            device_descriptor: descriptor,
740            device_type: MouseBinding,
741        );
742    }
743
744    /// Tests that a report containing a new mouse button with movement generates a down event and a
745    /// move event.
746    #[fuchsia::test]
747    async fn down_with_movement() {
748        let mouse_button: MouseButton = 3;
749        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
750        let first_report = testing_utilities::create_mouse_input_report_relative(
751            Position { x: 10.0, y: 16.0 },
752            None, /* scroll_v */
753            None, /* scroll_h */
754            vec![mouse_button],
755            event_time_i64,
756        );
757        let descriptor = mouse_device_descriptor(DEVICE_ID);
758
759        let input_reports = vec![first_report];
760        let expected_events = vec![
761            testing_utilities::create_mouse_event(
762                MouseLocation::Relative(Default::default()),
763                None, /* wheel_delta_v */
764                None, /* wheel_delta_h */
765                None, /* is_precision_scroll */
766                MousePhase::Down,
767                SortedVecSet::from(vec![mouse_button]),
768                SortedVecSet::from(vec![mouse_button]),
769                event_time_u64,
770                &descriptor,
771            ),
772            testing_utilities::create_mouse_event(
773                MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
774                None, /* wheel_delta_v */
775                None, /* wheel_delta_h */
776                None, /* is_precision_scroll */
777                MousePhase::Move,
778                SortedVecSet::from(vec![mouse_button]),
779                SortedVecSet::from(vec![mouse_button]),
780                event_time_u64,
781                &descriptor,
782            ),
783        ];
784
785        assert_input_report_sequence_generates_events!(
786            input_reports: input_reports,
787            expected_events: expected_events,
788            device_descriptor: descriptor,
789            device_type: MouseBinding,
790        );
791    }
792
793    /// Tests that a press and release of a mouse button without movement generates a down and up event.
794    #[fuchsia::test]
795    async fn down_up() {
796        let button = 1;
797        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
798        let first_report = testing_utilities::create_mouse_input_report_relative(
799            Position::zero(),
800            None, /* scroll_v */
801            None, /* scroll_h */
802            vec![button],
803            event_time_i64,
804        );
805        let second_report = testing_utilities::create_mouse_input_report_relative(
806            Position::zero(),
807            None, /* scroll_v */
808            None, /* scroll_h */
809            vec![],
810            event_time_i64,
811        );
812        let descriptor = mouse_device_descriptor(DEVICE_ID);
813
814        let input_reports = vec![first_report, second_report];
815        let expected_events = vec![
816            testing_utilities::create_mouse_event(
817                MouseLocation::Relative(Default::default()),
818                None, /* wheel_delta_v */
819                None, /* wheel_delta_h */
820                None, /* is_precision_scroll */
821                MousePhase::Down,
822                SortedVecSet::from(vec![button]),
823                SortedVecSet::from(vec![button]),
824                event_time_u64,
825                &descriptor,
826            ),
827            testing_utilities::create_mouse_event(
828                MouseLocation::Relative(Default::default()),
829                None, /* wheel_delta_v */
830                None, /* wheel_delta_h */
831                None, /* is_precision_scroll */
832                MousePhase::Up,
833                SortedVecSet::from(vec![button]),
834                SortedVecSet::new(),
835                event_time_u64,
836                &descriptor,
837            ),
838        ];
839
840        assert_input_report_sequence_generates_events!(
841            input_reports: input_reports,
842            expected_events: expected_events,
843            device_descriptor: descriptor,
844            device_type: MouseBinding,
845        );
846    }
847
848    /// Tests that a press and release of a mouse button with movement generates down, move, and up events.
849    #[fuchsia::test]
850    async fn down_up_with_movement() {
851        let button = 1;
852
853        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
854        let first_report = testing_utilities::create_mouse_input_report_relative(
855            Position::zero(),
856            None, /* scroll_v */
857            None, /* scroll_h */
858            vec![button],
859            event_time_i64,
860        );
861        let second_report = testing_utilities::create_mouse_input_report_relative(
862            Position { x: 10.0, y: 16.0 },
863            None, /* scroll_v */
864            None, /* scroll_h */
865            vec![],
866            event_time_i64,
867        );
868        let descriptor = mouse_device_descriptor(DEVICE_ID);
869
870        let input_reports = vec![first_report, second_report];
871        let expected_events = vec![
872            testing_utilities::create_mouse_event(
873                MouseLocation::Relative(Default::default()),
874                None, /* wheel_delta_v */
875                None, /* wheel_delta_h */
876                None, /* is_precision_scroll */
877                MousePhase::Down,
878                SortedVecSet::from(vec![button]),
879                SortedVecSet::from(vec![button]),
880                event_time_u64,
881                &descriptor,
882            ),
883            testing_utilities::create_mouse_event(
884                MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
885                None, /* wheel_delta_v */
886                None, /* wheel_delta_h */
887                None, /* is_precision_scroll */
888                MousePhase::Move,
889                SortedVecSet::from(vec![button]),
890                SortedVecSet::from(vec![button]),
891                event_time_u64,
892                &descriptor,
893            ),
894            testing_utilities::create_mouse_event(
895                MouseLocation::Relative(Default::default()),
896                None, /* wheel_delta_v */
897                None, /* wheel_delta_h */
898                None, /* is_precision_scroll */
899                MousePhase::Up,
900                SortedVecSet::from(vec![button]),
901                SortedVecSet::new(),
902                event_time_u64,
903                &descriptor,
904            ),
905        ];
906
907        assert_input_report_sequence_generates_events!(
908            input_reports: input_reports,
909            expected_events: expected_events,
910            device_descriptor: descriptor,
911            device_type: MouseBinding,
912        );
913    }
914
915    /// Tests that a press, move, and release of a button generates down, move, and up events.
916    /// This specifically tests the separate input report containing the movement, instead of sending
917    /// the movement as part of the down or up events.
918    #[fuchsia::test]
919    async fn down_move_up() {
920        let button = 1;
921
922        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
923        let first_report = testing_utilities::create_mouse_input_report_relative(
924            Position::zero(),
925            None, /* scroll_v */
926            None, /* scroll_h */
927            vec![button],
928            event_time_i64,
929        );
930        let second_report = testing_utilities::create_mouse_input_report_relative(
931            Position { x: 10.0, y: 16.0 },
932            None, /* scroll_v */
933            None, /* scroll_h */
934            vec![button],
935            event_time_i64,
936        );
937        let third_report = testing_utilities::create_mouse_input_report_relative(
938            Position::zero(),
939            None, /* scroll_v */
940            None, /* scroll_h */
941            vec![],
942            event_time_i64,
943        );
944        let descriptor = mouse_device_descriptor(DEVICE_ID);
945
946        let input_reports = vec![first_report, second_report, third_report];
947        let expected_events = vec![
948            testing_utilities::create_mouse_event(
949                MouseLocation::Relative(Default::default()),
950                None, /* wheel_delta_v */
951                None, /* wheel_delta_h */
952                None, /* is_precision_scroll */
953                MousePhase::Down,
954                SortedVecSet::from(vec![button]),
955                SortedVecSet::from(vec![button]),
956                event_time_u64,
957                &descriptor,
958            ),
959            testing_utilities::create_mouse_event(
960                MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
961                None, /* wheel_delta_v */
962                None, /* wheel_delta_h */
963                None, /* is_precision_scroll */
964                MousePhase::Move,
965                SortedVecSet::from(vec![button]),
966                SortedVecSet::from(vec![button]),
967                event_time_u64,
968                &descriptor,
969            ),
970            testing_utilities::create_mouse_event(
971                MouseLocation::Relative(Default::default()),
972                None, /* wheel_delta_v */
973                None, /* wheel_delta_h */
974                None, /* is_precision_scroll */
975                MousePhase::Up,
976                SortedVecSet::from(vec![button]),
977                SortedVecSet::new(),
978                event_time_u64,
979                &descriptor,
980            ),
981        ];
982
983        assert_input_report_sequence_generates_events!(
984            input_reports: input_reports,
985            expected_events: expected_events,
986            device_descriptor: descriptor,
987            device_type: MouseBinding,
988        );
989    }
990
991    /// Tests that a report with absolute movement to {0, 0} generates a move event.
992    #[fuchsia::test(allow_stalls = false)]
993    async fn absolute_movement_to_origin() {
994        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
995        let descriptor = mouse_device_descriptor(DEVICE_ID);
996
997        let input_reports = vec![testing_utilities::create_mouse_input_report_absolute(
998            Position::zero(),
999            None, /* wheel_delta_v */
1000            None, /* wheel_delta_h */
1001            vec![],
1002            event_time_i64,
1003        )];
1004        let expected_events = vec![testing_utilities::create_mouse_event(
1005            MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
1006            None, /* wheel_delta_v */
1007            None, /* wheel_delta_h */
1008            None, /* is_precision_scroll */
1009            MousePhase::Move,
1010            SortedVecSet::new(),
1011            SortedVecSet::new(),
1012            event_time_u64,
1013            &descriptor,
1014        )];
1015
1016        assert_input_report_sequence_generates_events!(
1017            input_reports: input_reports,
1018            expected_events: expected_events,
1019            device_descriptor: descriptor,
1020            device_type: MouseBinding,
1021        );
1022    }
1023
1024    /// Tests that a report that contains both a relative movement and absolute position
1025    /// generates a move event to the absolute position.
1026    #[fuchsia::test(allow_stalls = false)]
1027    async fn report_with_both_movement_and_position() {
1028        let relative_movement = Position { x: 5.0, y: 5.0 };
1029        let absolute_position = Position { x: 10.0, y: 10.0 };
1030        let expected_location = MouseLocation::Absolute(absolute_position);
1031
1032        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1033        let descriptor = mouse_device_descriptor(DEVICE_ID);
1034
1035        let input_reports = vec![fidl_next_fuchsia_input_report::InputReport {
1036            event_time: Some(event_time_i64),
1037            keyboard: None,
1038            mouse: Some(fidl_next_fuchsia_input_report::MouseInputReport {
1039                movement_x: Some(relative_movement.x as i64),
1040                movement_y: Some(relative_movement.y as i64),
1041                position_x: Some(absolute_position.x as i64),
1042                position_y: Some(absolute_position.y as i64),
1043                scroll_h: None,
1044                scroll_v: None,
1045                pressed_buttons: None,
1046                ..Default::default()
1047            }),
1048            touch: None,
1049            sensor: None,
1050            consumer_control: None,
1051            trace_id: None,
1052            ..Default::default()
1053        }];
1054        let expected_events = vec![testing_utilities::create_mouse_event(
1055            expected_location,
1056            None, /* wheel_delta_v */
1057            None, /* wheel_delta_h */
1058            None, /* is_precision_scroll */
1059            MousePhase::Move,
1060            SortedVecSet::new(),
1061            SortedVecSet::new(),
1062            event_time_u64,
1063            &descriptor,
1064        )];
1065
1066        assert_input_report_sequence_generates_events!(
1067            input_reports: input_reports,
1068            expected_events: expected_events,
1069            device_descriptor: descriptor,
1070            device_type: MouseBinding,
1071        );
1072    }
1073
1074    /// Tests that two separate button presses generate two separate down events with differing
1075    /// sets of `affected_buttons` and `pressed_buttons`.
1076    #[fuchsia::test]
1077    async fn down_down() {
1078        const PRIMARY_BUTTON: u8 = 1;
1079        const SECONDARY_BUTTON: u8 = 2;
1080
1081        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1082        let first_report = testing_utilities::create_mouse_input_report_relative(
1083            Position::zero(),
1084            None, /* scroll_v */
1085            None, /* scroll_h */
1086            vec![PRIMARY_BUTTON],
1087            event_time_i64,
1088        );
1089        let second_report = testing_utilities::create_mouse_input_report_relative(
1090            Position::zero(),
1091            None, /* scroll_v */
1092            None, /* scroll_h */
1093            vec![PRIMARY_BUTTON, SECONDARY_BUTTON],
1094            event_time_i64,
1095        );
1096        let descriptor = mouse_device_descriptor(DEVICE_ID);
1097
1098        let input_reports = vec![first_report, second_report];
1099        let expected_events = vec![
1100            testing_utilities::create_mouse_event(
1101                MouseLocation::Relative(Default::default()),
1102                None, /* wheel_delta_v */
1103                None, /* wheel_delta_h */
1104                None, /* is_precision_scroll */
1105                MousePhase::Down,
1106                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1107                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1108                event_time_u64,
1109                &descriptor,
1110            ),
1111            testing_utilities::create_mouse_event(
1112                MouseLocation::Relative(Default::default()),
1113                None, /* wheel_delta_v */
1114                None, /* wheel_delta_h */
1115                None, /* is_precision_scroll */
1116                MousePhase::Down,
1117                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1118                SortedVecSet::from(vec![PRIMARY_BUTTON, SECONDARY_BUTTON]),
1119                event_time_u64,
1120                &descriptor,
1121            ),
1122        ];
1123
1124        assert_input_report_sequence_generates_events!(
1125            input_reports: input_reports,
1126            expected_events: expected_events,
1127            device_descriptor: descriptor,
1128            device_type: MouseBinding,
1129        );
1130    }
1131
1132    /// Tests that two staggered button presses followed by stagged releases generate four mouse
1133    /// events with distinct `affected_buttons` and `pressed_buttons`.
1134    /// Specifically, we test and expect the following in order:
1135    /// | Action           | MousePhase | `affected_buttons` | `pressed_buttons` |
1136    /// | ---------------- | ---------- | ------------------ | ----------------- |
1137    /// | Press button 1   | Down       | [1]                | [1]               |
1138    /// | Press button 2   | Down       | [2]                | [1, 2]            |
1139    /// | Release button 1 | Up         | [1]                | [2]               |
1140    /// | Release button 2 | Up         | [2]                | []                |
1141    #[fuchsia::test]
1142    async fn down_down_up_up() {
1143        const PRIMARY_BUTTON: u8 = 1;
1144        const SECONDARY_BUTTON: u8 = 2;
1145
1146        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1147        let first_report = testing_utilities::create_mouse_input_report_relative(
1148            Position::zero(),
1149            None, /* scroll_v */
1150            None, /* scroll_h */
1151            vec![PRIMARY_BUTTON],
1152            event_time_i64,
1153        );
1154        let second_report = testing_utilities::create_mouse_input_report_relative(
1155            Position::zero(),
1156            None, /* scroll_v */
1157            None, /* scroll_h */
1158            vec![PRIMARY_BUTTON, SECONDARY_BUTTON],
1159            event_time_i64,
1160        );
1161        let third_report = testing_utilities::create_mouse_input_report_relative(
1162            Position::zero(),
1163            None, /* scroll_v */
1164            None, /* scroll_h */
1165            vec![SECONDARY_BUTTON],
1166            event_time_i64,
1167        );
1168        let fourth_report = testing_utilities::create_mouse_input_report_relative(
1169            Position::zero(),
1170            None, /* scroll_v */
1171            None, /* scroll_h */
1172            vec![],
1173            event_time_i64,
1174        );
1175        let descriptor = mouse_device_descriptor(DEVICE_ID);
1176
1177        let input_reports = vec![first_report, second_report, third_report, fourth_report];
1178        let expected_events = vec![
1179            testing_utilities::create_mouse_event(
1180                MouseLocation::Relative(Default::default()),
1181                None, /* wheel_delta_v */
1182                None, /* wheel_delta_h */
1183                None, /* is_precision_scroll */
1184                MousePhase::Down,
1185                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1186                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1187                event_time_u64,
1188                &descriptor,
1189            ),
1190            testing_utilities::create_mouse_event(
1191                MouseLocation::Relative(Default::default()),
1192                None, /* wheel_delta_v */
1193                None, /* wheel_delta_h */
1194                None, /* is_precision_scroll */
1195                MousePhase::Down,
1196                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1197                SortedVecSet::from(vec![PRIMARY_BUTTON, SECONDARY_BUTTON]),
1198                event_time_u64,
1199                &descriptor,
1200            ),
1201            testing_utilities::create_mouse_event(
1202                MouseLocation::Relative(Default::default()),
1203                None, /* wheel_delta_v */
1204                None, /* wheel_delta_h */
1205                None, /* is_precision_scroll */
1206                MousePhase::Up,
1207                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1208                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1209                event_time_u64,
1210                &descriptor,
1211            ),
1212            testing_utilities::create_mouse_event(
1213                MouseLocation::Relative(Default::default()),
1214                None, /* wheel_delta_v */
1215                None, /* wheel_delta_h */
1216                None, /* is_precision_scroll */
1217                MousePhase::Up,
1218                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1219                SortedVecSet::new(),
1220                event_time_u64,
1221                &descriptor,
1222            ),
1223        ];
1224
1225        assert_input_report_sequence_generates_events!(
1226            input_reports: input_reports,
1227            expected_events: expected_events,
1228            device_descriptor: descriptor,
1229            device_type: MouseBinding,
1230        );
1231    }
1232
1233    /// Test simple scroll in vertical and horizontal.
1234    #[fuchsia::test]
1235    async fn scroll() {
1236        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1237        let first_report = testing_utilities::create_mouse_input_report_relative(
1238            Position::zero(),
1239            Some(1),
1240            None,
1241            vec![],
1242            event_time_i64,
1243        );
1244        let second_report = testing_utilities::create_mouse_input_report_relative(
1245            Position::zero(),
1246            None,
1247            Some(1),
1248            vec![],
1249            event_time_i64,
1250        );
1251
1252        let descriptor = mouse_device_descriptor(DEVICE_ID);
1253
1254        let input_reports = vec![first_report, second_report];
1255        let expected_events = vec![
1256            testing_utilities::create_mouse_event(
1257                MouseLocation::Relative(Default::default()),
1258                wheel_delta_ticks(1),
1259                None,
1260                Some(PrecisionScroll::No),
1261                MousePhase::Wheel,
1262                SortedVecSet::new(),
1263                SortedVecSet::new(),
1264                event_time_u64,
1265                &descriptor,
1266            ),
1267            testing_utilities::create_mouse_event(
1268                MouseLocation::Relative(Default::default()),
1269                None,
1270                wheel_delta_ticks(1),
1271                Some(PrecisionScroll::No),
1272                MousePhase::Wheel,
1273                SortedVecSet::new(),
1274                SortedVecSet::new(),
1275                event_time_u64,
1276                &descriptor,
1277            ),
1278        ];
1279
1280        assert_input_report_sequence_generates_events!(
1281            input_reports: input_reports,
1282            expected_events: expected_events,
1283            device_descriptor: descriptor,
1284            device_type: MouseBinding,
1285        );
1286    }
1287
1288    /// Test button down -> scroll -> button up -> continue scroll.
1289    #[fuchsia::test]
1290    async fn down_scroll_up_scroll() {
1291        const PRIMARY_BUTTON: u8 = 1;
1292
1293        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1294        let first_report = testing_utilities::create_mouse_input_report_relative(
1295            Position::zero(),
1296            None, /* scroll_v */
1297            None, /* scroll_h */
1298            vec![PRIMARY_BUTTON],
1299            event_time_i64,
1300        );
1301        let second_report = testing_utilities::create_mouse_input_report_relative(
1302            Position::zero(),
1303            Some(1),
1304            None,
1305            vec![PRIMARY_BUTTON],
1306            event_time_i64,
1307        );
1308        let third_report = testing_utilities::create_mouse_input_report_relative(
1309            Position::zero(),
1310            None, /* scroll_v */
1311            None, /* scroll_h */
1312            vec![],
1313            event_time_i64,
1314        );
1315        let fourth_report = testing_utilities::create_mouse_input_report_relative(
1316            Position::zero(),
1317            Some(1),
1318            None,
1319            vec![],
1320            event_time_i64,
1321        );
1322
1323        let descriptor = mouse_device_descriptor(DEVICE_ID);
1324
1325        let input_reports = vec![first_report, second_report, third_report, fourth_report];
1326        let expected_events = vec![
1327            testing_utilities::create_mouse_event(
1328                MouseLocation::Relative(Default::default()),
1329                None, /* wheel_delta_v */
1330                None, /* wheel_delta_h */
1331                None, /* is_precision_scroll */
1332                MousePhase::Down,
1333                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1334                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1335                event_time_u64,
1336                &descriptor,
1337            ),
1338            testing_utilities::create_mouse_event(
1339                MouseLocation::Relative(Default::default()),
1340                wheel_delta_ticks(1),
1341                None,
1342                Some(PrecisionScroll::No),
1343                MousePhase::Wheel,
1344                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1345                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1346                event_time_u64,
1347                &descriptor,
1348            ),
1349            testing_utilities::create_mouse_event(
1350                MouseLocation::Relative(Default::default()),
1351                None, /* wheel_delta_v */
1352                None, /* wheel_delta_h */
1353                None, /* is_precision_scroll */
1354                MousePhase::Up,
1355                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1356                SortedVecSet::new(),
1357                event_time_u64,
1358                &descriptor,
1359            ),
1360            testing_utilities::create_mouse_event(
1361                MouseLocation::Relative(Default::default()),
1362                wheel_delta_ticks(1),
1363                None,
1364                Some(PrecisionScroll::No),
1365                MousePhase::Wheel,
1366                SortedVecSet::new(),
1367                SortedVecSet::new(),
1368                event_time_u64,
1369                &descriptor,
1370            ),
1371        ];
1372
1373        assert_input_report_sequence_generates_events!(
1374            input_reports: input_reports,
1375            expected_events: expected_events,
1376            device_descriptor: descriptor,
1377            device_type: MouseBinding,
1378        );
1379    }
1380
1381    /// Test button down with scroll -> button up with scroll -> scroll.
1382    #[fuchsia::test]
1383    async fn down_scroll_bundle_up_scroll_bundle() {
1384        const PRIMARY_BUTTON: u8 = 1;
1385
1386        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1387        let first_report = testing_utilities::create_mouse_input_report_relative(
1388            Position::zero(),
1389            Some(1),
1390            None,
1391            vec![PRIMARY_BUTTON],
1392            event_time_i64,
1393        );
1394        let second_report = testing_utilities::create_mouse_input_report_relative(
1395            Position::zero(),
1396            Some(1),
1397            None,
1398            vec![],
1399            event_time_i64,
1400        );
1401        let third_report = testing_utilities::create_mouse_input_report_relative(
1402            Position::zero(),
1403            Some(1),
1404            None,
1405            vec![],
1406            event_time_i64,
1407        );
1408
1409        let descriptor = mouse_device_descriptor(DEVICE_ID);
1410
1411        let input_reports = vec![first_report, second_report, third_report];
1412        let expected_events = vec![
1413            testing_utilities::create_mouse_event(
1414                MouseLocation::Relative(Default::default()),
1415                None, /* wheel_delta_v */
1416                None, /* wheel_delta_h */
1417                None, /* is_precision_scroll */
1418                MousePhase::Down,
1419                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1420                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1421                event_time_u64,
1422                &descriptor,
1423            ),
1424            testing_utilities::create_mouse_event(
1425                MouseLocation::Relative(Default::default()),
1426                wheel_delta_ticks(1),
1427                None,
1428                Some(PrecisionScroll::No),
1429                MousePhase::Wheel,
1430                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1431                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1432                event_time_u64,
1433                &descriptor,
1434            ),
1435            testing_utilities::create_mouse_event(
1436                MouseLocation::Relative(Default::default()),
1437                wheel_delta_ticks(1),
1438                None,
1439                Some(PrecisionScroll::No),
1440                MousePhase::Wheel,
1441                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1442                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1443                event_time_u64,
1444                &descriptor,
1445            ),
1446            testing_utilities::create_mouse_event(
1447                MouseLocation::Relative(Default::default()),
1448                None, /* wheel_delta_v */
1449                None, /* wheel_delta_h */
1450                None, /* is_precision_scroll */
1451                MousePhase::Up,
1452                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1453                SortedVecSet::new(),
1454                event_time_u64,
1455                &descriptor,
1456            ),
1457            testing_utilities::create_mouse_event(
1458                MouseLocation::Relative(Default::default()),
1459                wheel_delta_ticks(1),
1460                None,
1461                Some(PrecisionScroll::No),
1462                MousePhase::Wheel,
1463                SortedVecSet::new(),
1464                SortedVecSet::new(),
1465                event_time_u64,
1466                &descriptor,
1467            ),
1468        ];
1469
1470        assert_input_report_sequence_generates_events!(
1471            input_reports: input_reports,
1472            expected_events: expected_events,
1473            device_descriptor: descriptor,
1474            device_type: MouseBinding,
1475        );
1476    }
1477}