Skip to main content

input_pipeline/
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, 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, crate::dispatcher::TaskHandle<()>), Error> {
291        let (device_descriptor, mut inspect_status) =
292            Self::bind_device(&device_proxy, device_id, device_node).await?;
293        inspect_status.health_node.set_ok();
294        let task = input_device::initialize_report_stream(
295            device_proxy,
296            input_device::InputDeviceDescriptor::Mouse(device_descriptor.clone()),
297            input_event_sender.clone(),
298            inspect_status,
299            metrics_logger,
300            _feature_flags,
301            Self::process_reports,
302        );
303
304        Ok((MouseBinding { event_sender: input_event_sender, device_descriptor }, task))
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        device_node: fuchsia_inspect::Node,
322    ) -> Result<(MouseDeviceDescriptor, InputDeviceStatus), Error> {
323        let mut input_device_status = InputDeviceStatus::new(device_node);
324        let device_descriptor: fidl_next_fuchsia_input_report::DeviceDescriptor = match device
325            .get_descriptor()
326            .await
327        {
328            Ok(res) => res.descriptor,
329            Err(_) => {
330                input_device_status.health_node.set_unhealthy("Could not get device descriptor.");
331                return Err(format_err!("Could not get descriptor for device_id: {}", device_id));
332            }
333        };
334
335        let mouse_descriptor = device_descriptor.mouse.ok_or_else(|| {
336            input_device_status
337                .health_node
338                .set_unhealthy("DeviceDescriptor does not have a MouseDescriptor.");
339            format_err!("DeviceDescriptor does not have a MouseDescriptor")
340        })?;
341
342        let mouse_input_descriptor = mouse_descriptor.input.ok_or_else(|| {
343            input_device_status
344                .health_node
345                .set_unhealthy("MouseDescriptor does not have a MouseInputDescriptor.");
346            format_err!("MouseDescriptor does not have a MouseInputDescriptor")
347        })?;
348
349        let device_descriptor: MouseDeviceDescriptor = MouseDeviceDescriptor {
350            device_id,
351            absolute_x_range: mouse_input_descriptor
352                .position_x
353                .as_ref()
354                .map(|axis| utils::range_to_old(&axis.range)),
355            absolute_y_range: mouse_input_descriptor
356                .position_y
357                .as_ref()
358                .map(|axis| utils::range_to_old(&axis.range)),
359            wheel_v_range: utils::axis_to_old(mouse_input_descriptor.scroll_v.as_ref()),
360            wheel_h_range: utils::axis_to_old(mouse_input_descriptor.scroll_h.as_ref()),
361            buttons: mouse_input_descriptor.buttons,
362        };
363
364        Ok((device_descriptor, input_device_status))
365    }
366
367    /// Parses an [`InputReport`] into one or more [`InputEvent`]s.
368    ///
369    /// The [`InputEvent`]s are sent to the device binding owner via [`input_event_sender`].
370    ///
371    /// # Parameters
372    /// `reports`: The incoming [`InputReport`].
373    /// `previous_report`: The previous [`InputReport`] seen for the same device. This can be
374    ///                    used to determine, for example, which keys are no longer present in
375    ///                    a keyboard report to generate key released events. If `None`, no
376    ///                    previous report was found.
377    /// `device_descriptor`: The descriptor for the input device generating the input reports.
378    /// `input_event_sender`: The sender for the device binding's input event stream.
379    ///
380    /// # Returns
381    /// An [`InputReport`] which will be passed to the next call to [`process_reports`], as
382    /// [`previous_report`]. If `None`, the next call's [`previous_report`] will be `None`.
383    /// A [`UnboundedReceiver<InputEvent>`] which will poll asynchronously generated events to be
384    /// recorded by `inspect_status` in `input_device::initialize_report_stream()`. If device
385    /// binding does not generate InputEvents asynchronously, this will be `None`.
386    ///
387    /// The returned [`InputReport`] is guaranteed to have no `wake_lease`.
388    fn process_reports(
389        reports: &[fidl_next_fuchsia_input_report::wire::InputReport<'_>],
390        mut previous_state: Option<input_device::PreviousDeviceState>,
391        device_descriptor: &input_device::InputDeviceDescriptor,
392        input_event_sender: &mut UnboundedSender<Vec<InputEvent>>,
393        inspect_status: &InputDeviceStatus,
394        metrics_logger: &metrics::MetricsLogger,
395        _feature_flags: &input_device::InputPipelineFeatureFlags,
396    ) -> (Option<input_device::PreviousDeviceState>, Option<UnboundedReceiver<InputEvent>>) {
397        fuchsia_trace::duration!("input", "mouse-binding-process-reports", "num_reports" => reports.len());
398        for report in reports {
399            previous_state = Self::process_report(
400                report,
401                previous_state,
402                device_descriptor,
403                input_event_sender,
404                inspect_status,
405                metrics_logger,
406            );
407        }
408        (previous_state, None)
409    }
410
411    fn process_report(
412        report: &fidl_next_fuchsia_input_report::wire::InputReport<'_>,
413        previous_state: Option<input_device::PreviousDeviceState>,
414        device_descriptor: &input_device::InputDeviceDescriptor,
415        input_event_sender: &mut UnboundedSender<Vec<input_device::InputEvent>>,
416        inspect_status: &InputDeviceStatus,
417        metrics_logger: &metrics::MetricsLogger,
418    ) -> Option<input_device::PreviousDeviceState> {
419        if let Some(trace_id) = report.trace_id() {
420            fuchsia_trace::flow_end!("input", "input_report", trace_id.0.into());
421        }
422
423        // Extract the wake_lease early to prevent it from leaking. If this is moved
424        // below an early return, the lease could accidentally be stored inside
425        // `previous_report`, which would prevent the system from suspending.
426        let wake_lease = utils::duplicate_wake_lease(report.wake_lease());
427
428        inspect_status.count_received_report_wire(report);
429        // Input devices can have multiple types so ensure `report` is a MouseInputReport.
430        let mouse_report = match report.mouse() {
431            Some(mouse) => mouse,
432            None => {
433                inspect_status.count_filtered_report();
434                return previous_state;
435            }
436        };
437
438        let previous_buttons: SortedVecSet<MouseButton> = match previous_state {
439            Some(input_device::PreviousDeviceState::Mouse { pressed_buttons }) => pressed_buttons,
440            _ => SortedVecSet::new(),
441        };
442        let current_buttons: SortedVecSet<MouseButton> =
443            buttons_from_mouse_report_wire(mouse_report);
444
445        // Send a Down event with:
446        // * affected_buttons: the buttons that were pressed since the previous report,
447        //   i.e. that are in the current report, but were not in the previous report.
448        // * pressed_buttons: the full set of currently pressed buttons, including the
449        //   recently pressed ones (affected_buttons).
450        send_mouse_event(
451            MouseLocation::Relative(Default::default()),
452            None, /* wheel_delta_v */
453            None, /* wheel_delta_h */
454            MousePhase::Down,
455            current_buttons.difference(&previous_buttons).cloned().collect(),
456            current_buttons.clone(),
457            device_descriptor,
458            input_event_sender,
459            inspect_status,
460            metrics_logger,
461            wake_lease.as_ref().map(|lease| {
462                lease
463                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
464                    .expect("failed to duplicate event pair")
465            }),
466        );
467
468        // Create a location for the move event. Use the absolute position if available.
469        let location = if let (Some(position_x), Some(position_y)) =
470            (mouse_report.position_x(), mouse_report.position_y())
471        {
472            MouseLocation::Absolute(Position { x: position_x.0 as f32, y: position_y.0 as f32 })
473        } else {
474            let movement_x = mouse_report.movement_x().map(|x| x.0).unwrap_or_default() as f32;
475            let movement_y = mouse_report.movement_y().map(|y| y.0).unwrap_or_default() as f32;
476            MouseLocation::Relative(RelativeLocation {
477                counts: Position { x: movement_x, y: movement_y },
478            })
479        };
480
481        // Send a Move event with buttons from both the current report and the previous report.
482        // * affected_buttons and pressed_buttons are identical in this case, since the full
483        //   set of currently pressed buttons are the same set affected by the event.
484        send_mouse_event(
485            location,
486            None, /* wheel_delta_v */
487            None, /* wheel_delta_h */
488            MousePhase::Move,
489            current_buttons.union(&previous_buttons).cloned().collect(),
490            current_buttons.union(&previous_buttons).cloned().collect(),
491            device_descriptor,
492            input_event_sender,
493            inspect_status,
494            metrics_logger,
495            wake_lease.as_ref().map(|lease| {
496                lease
497                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
498                    .expect("failed to duplicate event pair")
499            }),
500        );
501
502        let wheel_delta_v = mouse_report
503            .scroll_v()
504            .map(|ticks| WheelDelta { ticks: ticks.0, physical_pixel: None });
505
506        let wheel_delta_h = mouse_report
507            .scroll_h()
508            .map(|ticks| WheelDelta { ticks: ticks.0, physical_pixel: None });
509
510        // Send a mouse wheel event.
511        send_mouse_event(
512            MouseLocation::Relative(Default::default()),
513            wheel_delta_v,
514            wheel_delta_h,
515            MousePhase::Wheel,
516            current_buttons.union(&previous_buttons).cloned().collect(),
517            current_buttons.union(&previous_buttons).cloned().collect(),
518            device_descriptor,
519            input_event_sender,
520            inspect_status,
521            metrics_logger,
522            wake_lease.as_ref().map(|lease| {
523                lease
524                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
525                    .expect("failed to duplicate event pair")
526            }),
527        );
528
529        // Send an Up event with:
530        // * affected_buttons: the buttons that were released since the previous report,
531        //   i.e. that were in the previous report, but are not in the current report.
532        // * pressed_buttons: the full set of currently pressed buttons, excluding the
533        //   recently released ones (affected_buttons).
534        send_mouse_event(
535            MouseLocation::Relative(Default::default()),
536            None, /* wheel_delta_v */
537            None, /* wheel_delta_h */
538            MousePhase::Up,
539            previous_buttons.difference(&current_buttons).cloned().collect(),
540            current_buttons.clone(),
541            device_descriptor,
542            input_event_sender,
543            inspect_status,
544            metrics_logger,
545            wake_lease,
546        );
547
548        Some(input_device::PreviousDeviceState::Mouse { pressed_buttons: current_buttons })
549    }
550}
551
552/// Sends an InputEvent over `sender`.
553///
554/// When no buttons are present, only [`MousePhase::Move`] events will
555/// be sent.
556///
557/// # Parameters
558/// - `location`: The mouse location.
559/// - `wheel_delta_v`: The mouse wheel delta in vertical.
560/// - `wheel_delta_h`: The mouse wheel delta in horizontal.
561/// - `phase`: The phase of the [`buttons`] associated with the input event.
562/// - `buttons`: The buttons relevant to the event.
563/// - `device_descriptor`: The descriptor for the input device generating the input reports.
564/// - `sender`: The stream to send the MouseEvent to.
565/// - `wake_lease`: The wake lease to send with the event.
566fn send_mouse_event(
567    location: MouseLocation,
568    wheel_delta_v: Option<WheelDelta>,
569    wheel_delta_h: Option<WheelDelta>,
570    phase: MousePhase,
571    affected_buttons: SortedVecSet<MouseButton>,
572    pressed_buttons: SortedVecSet<MouseButton>,
573    device_descriptor: &input_device::InputDeviceDescriptor,
574    sender: &mut UnboundedSender<Vec<input_device::InputEvent>>,
575    inspect_status: &InputDeviceStatus,
576    metrics_logger: &metrics::MetricsLogger,
577    wake_lease: Option<zx::EventPair>,
578) {
579    // Only send Down/Up events when there are buttons affected.
580    if (phase == MousePhase::Down || phase == MousePhase::Up) && affected_buttons.is_empty() {
581        return;
582    }
583
584    // Don't send Move events when there is no relative movement.
585    // However, absolute movement is always reported.
586    if phase == MousePhase::Move && location == MouseLocation::Relative(Default::default()) {
587        return;
588    }
589
590    // Only send wheel events when the delta has value.
591    if phase == MousePhase::Wheel && wheel_delta_v.is_none() && wheel_delta_h.is_none() {
592        return;
593    }
594
595    let trace_id = fuchsia_trace::Id::new();
596    fuchsia_trace::duration!("input", "mouse-binding-send-event");
597    fuchsia_trace::flow_begin!("input", "event_in_input_pipeline", trace_id);
598
599    let event = input_device::InputEvent {
600        device_event: input_device::InputDeviceEvent::Mouse(MouseEvent::new(
601            location,
602            wheel_delta_v,
603            wheel_delta_h,
604            phase,
605            affected_buttons,
606            pressed_buttons,
607            match phase {
608                MousePhase::Wheel => Some(PrecisionScroll::No),
609                _ => None,
610            },
611            wake_lease,
612        )),
613        device_descriptor: device_descriptor.clone(),
614        event_time: zx::MonotonicInstant::get(),
615        handled: Handled::No,
616        trace_id: Some(trace_id),
617    };
618    let events = vec![event];
619    inspect_status.count_generated_events(&events);
620
621    if let Err(e) = sender.unbounded_send(events) {
622        metrics_logger.log_error(
623            InputPipelineErrorMetricDimensionEvent::MouseFailedToSendEvent,
624            std::format!("Failed to send MouseEvent with error: {:?}", e),
625        );
626    }
627}
628
629fn buttons_from_mouse_report_wire(
630    mouse_report: &fidl_next_fuchsia_input_report::wire::MouseInputReport<'_>,
631) -> SortedVecSet<MouseButton> {
632    mouse_report
633        .pressed_buttons()
634        .map(|buttons| SortedVecSet::from_iter(buttons.iter().copied()))
635        .unwrap_or_default()
636}
637
638#[cfg(test)]
639mod tests {
640    use super::*;
641    use crate::testing_utilities;
642    use futures::StreamExt;
643    use sorted_vec_map::SortedVecSet;
644
645    const DEVICE_ID: u32 = 1;
646
647    fn mouse_device_descriptor(device_id: u32) -> input_device::InputDeviceDescriptor {
648        input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor {
649            device_id,
650            absolute_x_range: None,
651            absolute_y_range: None,
652            wheel_v_range: Some(fidl_fuchsia_input::Axis {
653                range: fidl_fuchsia_input::Range { min: -1, max: 1 },
654                unit: fidl_fuchsia_input::Unit {
655                    type_: fidl_fuchsia_input::UnitType::Other,
656                    exponent: 1,
657                },
658            }),
659            wheel_h_range: Some(fidl_fuchsia_input::Axis {
660                range: fidl_fuchsia_input::Range { min: -1, max: 1 },
661                unit: fidl_fuchsia_input::Unit {
662                    type_: fidl_fuchsia_input::UnitType::Other,
663                    exponent: 1,
664                },
665            }),
666            buttons: None,
667        })
668    }
669
670    fn wheel_delta_ticks(ticks: i64) -> Option<WheelDelta> {
671        Some(WheelDelta { ticks, physical_pixel: None })
672    }
673
674    /// Tests that a report containing no buttons but with movement generates a move event.
675    #[fuchsia::test]
676    async fn movement_without_button() {
677        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
678        let first_report = testing_utilities::create_mouse_input_report_relative(
679            Position { x: 10.0, y: 16.0 },
680            None, /* scroll_v */
681            None, /* scroll_h */
682            vec![],
683            event_time_i64,
684        );
685        let descriptor = mouse_device_descriptor(DEVICE_ID);
686
687        let input_reports = vec![first_report];
688        let expected_events = vec![testing_utilities::create_mouse_event(
689            MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
690            None, /* wheel_delta_v */
691            None, /* wheel_delta_h */
692            None, /* is_precision_scroll */
693            MousePhase::Move,
694            SortedVecSet::new(),
695            SortedVecSet::new(),
696            event_time_u64,
697            &descriptor,
698        )];
699
700        assert_input_report_sequence_generates_events!(
701            input_reports: input_reports,
702            expected_events: expected_events,
703            device_descriptor: descriptor,
704            device_type: MouseBinding,
705        );
706    }
707
708    /// Tests that a report containing a new mouse button generates a down event.
709    #[fuchsia::test]
710    async fn down_without_movement() {
711        let mouse_button: MouseButton = 3;
712        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
713        let first_report = testing_utilities::create_mouse_input_report_relative(
714            Position::zero(),
715            None, /* scroll_v */
716            None, /* scroll_h */
717            vec![mouse_button],
718            event_time_i64,
719        );
720        let descriptor = mouse_device_descriptor(DEVICE_ID);
721
722        let input_reports = vec![first_report];
723        let expected_events = vec![testing_utilities::create_mouse_event(
724            MouseLocation::Relative(Default::default()),
725            None, /* wheel_delta_v */
726            None, /* wheel_delta_h */
727            None, /* is_precision_scroll */
728            MousePhase::Down,
729            SortedVecSet::from(vec![mouse_button]),
730            SortedVecSet::from(vec![mouse_button]),
731            event_time_u64,
732            &descriptor,
733        )];
734
735        assert_input_report_sequence_generates_events!(
736            input_reports: input_reports,
737            expected_events: expected_events,
738            device_descriptor: descriptor,
739            device_type: MouseBinding,
740        );
741    }
742
743    /// Tests that a report containing a new mouse button with movement generates a down event and a
744    /// move event.
745    #[fuchsia::test]
746    async fn down_with_movement() {
747        let mouse_button: MouseButton = 3;
748        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
749        let first_report = testing_utilities::create_mouse_input_report_relative(
750            Position { x: 10.0, y: 16.0 },
751            None, /* scroll_v */
752            None, /* scroll_h */
753            vec![mouse_button],
754            event_time_i64,
755        );
756        let descriptor = mouse_device_descriptor(DEVICE_ID);
757
758        let input_reports = vec![first_report];
759        let expected_events = vec![
760            testing_utilities::create_mouse_event(
761                MouseLocation::Relative(Default::default()),
762                None, /* wheel_delta_v */
763                None, /* wheel_delta_h */
764                None, /* is_precision_scroll */
765                MousePhase::Down,
766                SortedVecSet::from(vec![mouse_button]),
767                SortedVecSet::from(vec![mouse_button]),
768                event_time_u64,
769                &descriptor,
770            ),
771            testing_utilities::create_mouse_event(
772                MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
773                None, /* wheel_delta_v */
774                None, /* wheel_delta_h */
775                None, /* is_precision_scroll */
776                MousePhase::Move,
777                SortedVecSet::from(vec![mouse_button]),
778                SortedVecSet::from(vec![mouse_button]),
779                event_time_u64,
780                &descriptor,
781            ),
782        ];
783
784        assert_input_report_sequence_generates_events!(
785            input_reports: input_reports,
786            expected_events: expected_events,
787            device_descriptor: descriptor,
788            device_type: MouseBinding,
789        );
790    }
791
792    /// Tests that a press and release of a mouse button without movement generates a down and up event.
793    #[fuchsia::test]
794    async fn down_up() {
795        let button = 1;
796        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
797        let first_report = testing_utilities::create_mouse_input_report_relative(
798            Position::zero(),
799            None, /* scroll_v */
800            None, /* scroll_h */
801            vec![button],
802            event_time_i64,
803        );
804        let second_report = testing_utilities::create_mouse_input_report_relative(
805            Position::zero(),
806            None, /* scroll_v */
807            None, /* scroll_h */
808            vec![],
809            event_time_i64,
810        );
811        let descriptor = mouse_device_descriptor(DEVICE_ID);
812
813        let input_reports = vec![first_report, second_report];
814        let expected_events = vec![
815            testing_utilities::create_mouse_event(
816                MouseLocation::Relative(Default::default()),
817                None, /* wheel_delta_v */
818                None, /* wheel_delta_h */
819                None, /* is_precision_scroll */
820                MousePhase::Down,
821                SortedVecSet::from(vec![button]),
822                SortedVecSet::from(vec![button]),
823                event_time_u64,
824                &descriptor,
825            ),
826            testing_utilities::create_mouse_event(
827                MouseLocation::Relative(Default::default()),
828                None, /* wheel_delta_v */
829                None, /* wheel_delta_h */
830                None, /* is_precision_scroll */
831                MousePhase::Up,
832                SortedVecSet::from(vec![button]),
833                SortedVecSet::new(),
834                event_time_u64,
835                &descriptor,
836            ),
837        ];
838
839        assert_input_report_sequence_generates_events!(
840            input_reports: input_reports,
841            expected_events: expected_events,
842            device_descriptor: descriptor,
843            device_type: MouseBinding,
844        );
845    }
846
847    /// Tests that a press and release of a mouse button with movement generates down, move, and up events.
848    #[fuchsia::test]
849    async fn down_up_with_movement() {
850        let button = 1;
851
852        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
853        let first_report = testing_utilities::create_mouse_input_report_relative(
854            Position::zero(),
855            None, /* scroll_v */
856            None, /* scroll_h */
857            vec![button],
858            event_time_i64,
859        );
860        let second_report = testing_utilities::create_mouse_input_report_relative(
861            Position { x: 10.0, y: 16.0 },
862            None, /* scroll_v */
863            None, /* scroll_h */
864            vec![],
865            event_time_i64,
866        );
867        let descriptor = mouse_device_descriptor(DEVICE_ID);
868
869        let input_reports = vec![first_report, second_report];
870        let expected_events = vec![
871            testing_utilities::create_mouse_event(
872                MouseLocation::Relative(Default::default()),
873                None, /* wheel_delta_v */
874                None, /* wheel_delta_h */
875                None, /* is_precision_scroll */
876                MousePhase::Down,
877                SortedVecSet::from(vec![button]),
878                SortedVecSet::from(vec![button]),
879                event_time_u64,
880                &descriptor,
881            ),
882            testing_utilities::create_mouse_event(
883                MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
884                None, /* wheel_delta_v */
885                None, /* wheel_delta_h */
886                None, /* is_precision_scroll */
887                MousePhase::Move,
888                SortedVecSet::from(vec![button]),
889                SortedVecSet::from(vec![button]),
890                event_time_u64,
891                &descriptor,
892            ),
893            testing_utilities::create_mouse_event(
894                MouseLocation::Relative(Default::default()),
895                None, /* wheel_delta_v */
896                None, /* wheel_delta_h */
897                None, /* is_precision_scroll */
898                MousePhase::Up,
899                SortedVecSet::from(vec![button]),
900                SortedVecSet::new(),
901                event_time_u64,
902                &descriptor,
903            ),
904        ];
905
906        assert_input_report_sequence_generates_events!(
907            input_reports: input_reports,
908            expected_events: expected_events,
909            device_descriptor: descriptor,
910            device_type: MouseBinding,
911        );
912    }
913
914    /// Tests that a press, move, and release of a button generates down, move, and up events.
915    /// This specifically tests the separate input report containing the movement, instead of sending
916    /// the movement as part of the down or up events.
917    #[fuchsia::test]
918    async fn down_move_up() {
919        let button = 1;
920
921        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
922        let first_report = testing_utilities::create_mouse_input_report_relative(
923            Position::zero(),
924            None, /* scroll_v */
925            None, /* scroll_h */
926            vec![button],
927            event_time_i64,
928        );
929        let second_report = testing_utilities::create_mouse_input_report_relative(
930            Position { x: 10.0, y: 16.0 },
931            None, /* scroll_v */
932            None, /* scroll_h */
933            vec![button],
934            event_time_i64,
935        );
936        let third_report = testing_utilities::create_mouse_input_report_relative(
937            Position::zero(),
938            None, /* scroll_v */
939            None, /* scroll_h */
940            vec![],
941            event_time_i64,
942        );
943        let descriptor = mouse_device_descriptor(DEVICE_ID);
944
945        let input_reports = vec![first_report, second_report, third_report];
946        let expected_events = vec![
947            testing_utilities::create_mouse_event(
948                MouseLocation::Relative(Default::default()),
949                None, /* wheel_delta_v */
950                None, /* wheel_delta_h */
951                None, /* is_precision_scroll */
952                MousePhase::Down,
953                SortedVecSet::from(vec![button]),
954                SortedVecSet::from(vec![button]),
955                event_time_u64,
956                &descriptor,
957            ),
958            testing_utilities::create_mouse_event(
959                MouseLocation::Relative(RelativeLocation { counts: Position { x: 10.0, y: 16.0 } }),
960                None, /* wheel_delta_v */
961                None, /* wheel_delta_h */
962                None, /* is_precision_scroll */
963                MousePhase::Move,
964                SortedVecSet::from(vec![button]),
965                SortedVecSet::from(vec![button]),
966                event_time_u64,
967                &descriptor,
968            ),
969            testing_utilities::create_mouse_event(
970                MouseLocation::Relative(Default::default()),
971                None, /* wheel_delta_v */
972                None, /* wheel_delta_h */
973                None, /* is_precision_scroll */
974                MousePhase::Up,
975                SortedVecSet::from(vec![button]),
976                SortedVecSet::new(),
977                event_time_u64,
978                &descriptor,
979            ),
980        ];
981
982        assert_input_report_sequence_generates_events!(
983            input_reports: input_reports,
984            expected_events: expected_events,
985            device_descriptor: descriptor,
986            device_type: MouseBinding,
987        );
988    }
989
990    /// Tests that a report with absolute movement to {0, 0} generates a move event.
991    #[fuchsia::test(allow_stalls = false)]
992    async fn absolute_movement_to_origin() {
993        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
994        let descriptor = mouse_device_descriptor(DEVICE_ID);
995
996        let input_reports = vec![testing_utilities::create_mouse_input_report_absolute(
997            Position::zero(),
998            None, /* wheel_delta_v */
999            None, /* wheel_delta_h */
1000            vec![],
1001            event_time_i64,
1002        )];
1003        let expected_events = vec![testing_utilities::create_mouse_event(
1004            MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
1005            None, /* wheel_delta_v */
1006            None, /* wheel_delta_h */
1007            None, /* is_precision_scroll */
1008            MousePhase::Move,
1009            SortedVecSet::new(),
1010            SortedVecSet::new(),
1011            event_time_u64,
1012            &descriptor,
1013        )];
1014
1015        assert_input_report_sequence_generates_events!(
1016            input_reports: input_reports,
1017            expected_events: expected_events,
1018            device_descriptor: descriptor,
1019            device_type: MouseBinding,
1020        );
1021    }
1022
1023    /// Tests that a report that contains both a relative movement and absolute position
1024    /// generates a move event to the absolute position.
1025    #[fuchsia::test(allow_stalls = false)]
1026    async fn report_with_both_movement_and_position() {
1027        let relative_movement = Position { x: 5.0, y: 5.0 };
1028        let absolute_position = Position { x: 10.0, y: 10.0 };
1029        let expected_location = MouseLocation::Absolute(absolute_position);
1030
1031        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1032        let descriptor = mouse_device_descriptor(DEVICE_ID);
1033
1034        let input_reports = vec![fidl_next_fuchsia_input_report::InputReport {
1035            event_time: Some(event_time_i64),
1036            keyboard: None,
1037            mouse: Some(fidl_next_fuchsia_input_report::MouseInputReport {
1038                movement_x: Some(relative_movement.x as i64),
1039                movement_y: Some(relative_movement.y as i64),
1040                position_x: Some(absolute_position.x as i64),
1041                position_y: Some(absolute_position.y as i64),
1042                scroll_h: None,
1043                scroll_v: None,
1044                pressed_buttons: None,
1045                ..Default::default()
1046            }),
1047            touch: None,
1048            sensor: None,
1049            consumer_control: None,
1050            trace_id: None,
1051            ..Default::default()
1052        }];
1053        let expected_events = vec![testing_utilities::create_mouse_event(
1054            expected_location,
1055            None, /* wheel_delta_v */
1056            None, /* wheel_delta_h */
1057            None, /* is_precision_scroll */
1058            MousePhase::Move,
1059            SortedVecSet::new(),
1060            SortedVecSet::new(),
1061            event_time_u64,
1062            &descriptor,
1063        )];
1064
1065        assert_input_report_sequence_generates_events!(
1066            input_reports: input_reports,
1067            expected_events: expected_events,
1068            device_descriptor: descriptor,
1069            device_type: MouseBinding,
1070        );
1071    }
1072
1073    /// Tests that two separate button presses generate two separate down events with differing
1074    /// sets of `affected_buttons` and `pressed_buttons`.
1075    #[fuchsia::test]
1076    async fn down_down() {
1077        const PRIMARY_BUTTON: u8 = 1;
1078        const SECONDARY_BUTTON: u8 = 2;
1079
1080        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1081        let first_report = testing_utilities::create_mouse_input_report_relative(
1082            Position::zero(),
1083            None, /* scroll_v */
1084            None, /* scroll_h */
1085            vec![PRIMARY_BUTTON],
1086            event_time_i64,
1087        );
1088        let second_report = testing_utilities::create_mouse_input_report_relative(
1089            Position::zero(),
1090            None, /* scroll_v */
1091            None, /* scroll_h */
1092            vec![PRIMARY_BUTTON, SECONDARY_BUTTON],
1093            event_time_i64,
1094        );
1095        let descriptor = mouse_device_descriptor(DEVICE_ID);
1096
1097        let input_reports = vec![first_report, second_report];
1098        let expected_events = vec![
1099            testing_utilities::create_mouse_event(
1100                MouseLocation::Relative(Default::default()),
1101                None, /* wheel_delta_v */
1102                None, /* wheel_delta_h */
1103                None, /* is_precision_scroll */
1104                MousePhase::Down,
1105                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1106                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1107                event_time_u64,
1108                &descriptor,
1109            ),
1110            testing_utilities::create_mouse_event(
1111                MouseLocation::Relative(Default::default()),
1112                None, /* wheel_delta_v */
1113                None, /* wheel_delta_h */
1114                None, /* is_precision_scroll */
1115                MousePhase::Down,
1116                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1117                SortedVecSet::from(vec![PRIMARY_BUTTON, SECONDARY_BUTTON]),
1118                event_time_u64,
1119                &descriptor,
1120            ),
1121        ];
1122
1123        assert_input_report_sequence_generates_events!(
1124            input_reports: input_reports,
1125            expected_events: expected_events,
1126            device_descriptor: descriptor,
1127            device_type: MouseBinding,
1128        );
1129    }
1130
1131    /// Tests that two staggered button presses followed by stagged releases generate four mouse
1132    /// events with distinct `affected_buttons` and `pressed_buttons`.
1133    /// Specifically, we test and expect the following in order:
1134    /// | Action           | MousePhase | `affected_buttons` | `pressed_buttons` |
1135    /// | ---------------- | ---------- | ------------------ | ----------------- |
1136    /// | Press button 1   | Down       | [1]                | [1]               |
1137    /// | Press button 2   | Down       | [2]                | [1, 2]            |
1138    /// | Release button 1 | Up         | [1]                | [2]               |
1139    /// | Release button 2 | Up         | [2]                | []                |
1140    #[fuchsia::test]
1141    async fn down_down_up_up() {
1142        const PRIMARY_BUTTON: u8 = 1;
1143        const SECONDARY_BUTTON: u8 = 2;
1144
1145        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1146        let first_report = testing_utilities::create_mouse_input_report_relative(
1147            Position::zero(),
1148            None, /* scroll_v */
1149            None, /* scroll_h */
1150            vec![PRIMARY_BUTTON],
1151            event_time_i64,
1152        );
1153        let second_report = testing_utilities::create_mouse_input_report_relative(
1154            Position::zero(),
1155            None, /* scroll_v */
1156            None, /* scroll_h */
1157            vec![PRIMARY_BUTTON, SECONDARY_BUTTON],
1158            event_time_i64,
1159        );
1160        let third_report = testing_utilities::create_mouse_input_report_relative(
1161            Position::zero(),
1162            None, /* scroll_v */
1163            None, /* scroll_h */
1164            vec![SECONDARY_BUTTON],
1165            event_time_i64,
1166        );
1167        let fourth_report = testing_utilities::create_mouse_input_report_relative(
1168            Position::zero(),
1169            None, /* scroll_v */
1170            None, /* scroll_h */
1171            vec![],
1172            event_time_i64,
1173        );
1174        let descriptor = mouse_device_descriptor(DEVICE_ID);
1175
1176        let input_reports = vec![first_report, second_report, third_report, fourth_report];
1177        let expected_events = vec![
1178            testing_utilities::create_mouse_event(
1179                MouseLocation::Relative(Default::default()),
1180                None, /* wheel_delta_v */
1181                None, /* wheel_delta_h */
1182                None, /* is_precision_scroll */
1183                MousePhase::Down,
1184                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1185                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1186                event_time_u64,
1187                &descriptor,
1188            ),
1189            testing_utilities::create_mouse_event(
1190                MouseLocation::Relative(Default::default()),
1191                None, /* wheel_delta_v */
1192                None, /* wheel_delta_h */
1193                None, /* is_precision_scroll */
1194                MousePhase::Down,
1195                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1196                SortedVecSet::from(vec![PRIMARY_BUTTON, SECONDARY_BUTTON]),
1197                event_time_u64,
1198                &descriptor,
1199            ),
1200            testing_utilities::create_mouse_event(
1201                MouseLocation::Relative(Default::default()),
1202                None, /* wheel_delta_v */
1203                None, /* wheel_delta_h */
1204                None, /* is_precision_scroll */
1205                MousePhase::Up,
1206                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1207                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1208                event_time_u64,
1209                &descriptor,
1210            ),
1211            testing_utilities::create_mouse_event(
1212                MouseLocation::Relative(Default::default()),
1213                None, /* wheel_delta_v */
1214                None, /* wheel_delta_h */
1215                None, /* is_precision_scroll */
1216                MousePhase::Up,
1217                SortedVecSet::from(vec![SECONDARY_BUTTON]),
1218                SortedVecSet::new(),
1219                event_time_u64,
1220                &descriptor,
1221            ),
1222        ];
1223
1224        assert_input_report_sequence_generates_events!(
1225            input_reports: input_reports,
1226            expected_events: expected_events,
1227            device_descriptor: descriptor,
1228            device_type: MouseBinding,
1229        );
1230    }
1231
1232    /// Test simple scroll in vertical and horizontal.
1233    #[fuchsia::test]
1234    async fn scroll() {
1235        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1236        let first_report = testing_utilities::create_mouse_input_report_relative(
1237            Position::zero(),
1238            Some(1),
1239            None,
1240            vec![],
1241            event_time_i64,
1242        );
1243        let second_report = testing_utilities::create_mouse_input_report_relative(
1244            Position::zero(),
1245            None,
1246            Some(1),
1247            vec![],
1248            event_time_i64,
1249        );
1250
1251        let descriptor = mouse_device_descriptor(DEVICE_ID);
1252
1253        let input_reports = vec![first_report, second_report];
1254        let expected_events = vec![
1255            testing_utilities::create_mouse_event(
1256                MouseLocation::Relative(Default::default()),
1257                wheel_delta_ticks(1),
1258                None,
1259                Some(PrecisionScroll::No),
1260                MousePhase::Wheel,
1261                SortedVecSet::new(),
1262                SortedVecSet::new(),
1263                event_time_u64,
1264                &descriptor,
1265            ),
1266            testing_utilities::create_mouse_event(
1267                MouseLocation::Relative(Default::default()),
1268                None,
1269                wheel_delta_ticks(1),
1270                Some(PrecisionScroll::No),
1271                MousePhase::Wheel,
1272                SortedVecSet::new(),
1273                SortedVecSet::new(),
1274                event_time_u64,
1275                &descriptor,
1276            ),
1277        ];
1278
1279        assert_input_report_sequence_generates_events!(
1280            input_reports: input_reports,
1281            expected_events: expected_events,
1282            device_descriptor: descriptor,
1283            device_type: MouseBinding,
1284        );
1285    }
1286
1287    /// Test button down -> scroll -> button up -> continue scroll.
1288    #[fuchsia::test]
1289    async fn down_scroll_up_scroll() {
1290        const PRIMARY_BUTTON: u8 = 1;
1291
1292        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1293        let first_report = testing_utilities::create_mouse_input_report_relative(
1294            Position::zero(),
1295            None, /* scroll_v */
1296            None, /* scroll_h */
1297            vec![PRIMARY_BUTTON],
1298            event_time_i64,
1299        );
1300        let second_report = testing_utilities::create_mouse_input_report_relative(
1301            Position::zero(),
1302            Some(1),
1303            None,
1304            vec![PRIMARY_BUTTON],
1305            event_time_i64,
1306        );
1307        let third_report = testing_utilities::create_mouse_input_report_relative(
1308            Position::zero(),
1309            None, /* scroll_v */
1310            None, /* scroll_h */
1311            vec![],
1312            event_time_i64,
1313        );
1314        let fourth_report = testing_utilities::create_mouse_input_report_relative(
1315            Position::zero(),
1316            Some(1),
1317            None,
1318            vec![],
1319            event_time_i64,
1320        );
1321
1322        let descriptor = mouse_device_descriptor(DEVICE_ID);
1323
1324        let input_reports = vec![first_report, second_report, third_report, fourth_report];
1325        let expected_events = vec![
1326            testing_utilities::create_mouse_event(
1327                MouseLocation::Relative(Default::default()),
1328                None, /* wheel_delta_v */
1329                None, /* wheel_delta_h */
1330                None, /* is_precision_scroll */
1331                MousePhase::Down,
1332                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1333                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1334                event_time_u64,
1335                &descriptor,
1336            ),
1337            testing_utilities::create_mouse_event(
1338                MouseLocation::Relative(Default::default()),
1339                wheel_delta_ticks(1),
1340                None,
1341                Some(PrecisionScroll::No),
1342                MousePhase::Wheel,
1343                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1344                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1345                event_time_u64,
1346                &descriptor,
1347            ),
1348            testing_utilities::create_mouse_event(
1349                MouseLocation::Relative(Default::default()),
1350                None, /* wheel_delta_v */
1351                None, /* wheel_delta_h */
1352                None, /* is_precision_scroll */
1353                MousePhase::Up,
1354                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1355                SortedVecSet::new(),
1356                event_time_u64,
1357                &descriptor,
1358            ),
1359            testing_utilities::create_mouse_event(
1360                MouseLocation::Relative(Default::default()),
1361                wheel_delta_ticks(1),
1362                None,
1363                Some(PrecisionScroll::No),
1364                MousePhase::Wheel,
1365                SortedVecSet::new(),
1366                SortedVecSet::new(),
1367                event_time_u64,
1368                &descriptor,
1369            ),
1370        ];
1371
1372        assert_input_report_sequence_generates_events!(
1373            input_reports: input_reports,
1374            expected_events: expected_events,
1375            device_descriptor: descriptor,
1376            device_type: MouseBinding,
1377        );
1378    }
1379
1380    /// Test button down with scroll -> button up with scroll -> scroll.
1381    #[fuchsia::test]
1382    async fn down_scroll_bundle_up_scroll_bundle() {
1383        const PRIMARY_BUTTON: u8 = 1;
1384
1385        let (event_time_i64, event_time_u64) = testing_utilities::event_times();
1386        let first_report = testing_utilities::create_mouse_input_report_relative(
1387            Position::zero(),
1388            Some(1),
1389            None,
1390            vec![PRIMARY_BUTTON],
1391            event_time_i64,
1392        );
1393        let second_report = testing_utilities::create_mouse_input_report_relative(
1394            Position::zero(),
1395            Some(1),
1396            None,
1397            vec![],
1398            event_time_i64,
1399        );
1400        let third_report = testing_utilities::create_mouse_input_report_relative(
1401            Position::zero(),
1402            Some(1),
1403            None,
1404            vec![],
1405            event_time_i64,
1406        );
1407
1408        let descriptor = mouse_device_descriptor(DEVICE_ID);
1409
1410        let input_reports = vec![first_report, second_report, third_report];
1411        let expected_events = vec![
1412            testing_utilities::create_mouse_event(
1413                MouseLocation::Relative(Default::default()),
1414                None, /* wheel_delta_v */
1415                None, /* wheel_delta_h */
1416                None, /* is_precision_scroll */
1417                MousePhase::Down,
1418                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1419                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1420                event_time_u64,
1421                &descriptor,
1422            ),
1423            testing_utilities::create_mouse_event(
1424                MouseLocation::Relative(Default::default()),
1425                wheel_delta_ticks(1),
1426                None,
1427                Some(PrecisionScroll::No),
1428                MousePhase::Wheel,
1429                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1430                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1431                event_time_u64,
1432                &descriptor,
1433            ),
1434            testing_utilities::create_mouse_event(
1435                MouseLocation::Relative(Default::default()),
1436                wheel_delta_ticks(1),
1437                None,
1438                Some(PrecisionScroll::No),
1439                MousePhase::Wheel,
1440                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1441                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1442                event_time_u64,
1443                &descriptor,
1444            ),
1445            testing_utilities::create_mouse_event(
1446                MouseLocation::Relative(Default::default()),
1447                None, /* wheel_delta_v */
1448                None, /* wheel_delta_h */
1449                None, /* is_precision_scroll */
1450                MousePhase::Up,
1451                SortedVecSet::from(vec![PRIMARY_BUTTON]),
1452                SortedVecSet::new(),
1453                event_time_u64,
1454                &descriptor,
1455            ),
1456            testing_utilities::create_mouse_event(
1457                MouseLocation::Relative(Default::default()),
1458                wheel_delta_ticks(1),
1459                None,
1460                Some(PrecisionScroll::No),
1461                MousePhase::Wheel,
1462                SortedVecSet::new(),
1463                SortedVecSet::new(),
1464                event_time_u64,
1465                &descriptor,
1466            ),
1467        ];
1468
1469        assert_input_report_sequence_generates_events!(
1470            input_reports: input_reports,
1471            expected_events: expected_events,
1472            device_descriptor: descriptor,
1473            device_type: MouseBinding,
1474        );
1475    }
1476}