1use crate::input_handler::{Handler, InputHandlerStatus, UnhandledInputHandler};
6use crate::utils::Position;
7use crate::{input_device, metrics, mouse_binding};
8use anyhow::{Error, format_err};
9use async_trait::async_trait;
10use derivative::Derivative;
11use fuchsia_inspect::health::Reporter;
12use metrics_registry::*;
13use std::rc::Rc;
14
15#[derive(Derivative)]
17#[derivative(Debug, PartialEq)]
18pub struct PointerDisplayScaleHandler {
19 scale_factor: f32,
22
23 pub inspect_status: InputHandlerStatus,
25
26 #[derivative(Debug = "ignore", PartialEq = "ignore")]
28 metrics_logger: metrics::MetricsLogger,
29}
30
31impl Handler for PointerDisplayScaleHandler {
32 fn set_handler_healthy(self: std::rc::Rc<Self>) {
33 self.inspect_status.health_node.borrow_mut().set_ok();
34 }
35
36 fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
37 self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
38 }
39
40 fn get_name(&self) -> &'static str {
41 "PointerDisplayScaleHandler"
42 }
43
44 fn interest(&self) -> Vec<input_device::InputEventType> {
45 vec![input_device::InputEventType::Mouse]
46 }
47}
48
49#[async_trait(?Send)]
50impl UnhandledInputHandler for PointerDisplayScaleHandler {
51 async fn handle_unhandled_input_event(
52 self: Rc<Self>,
53 unhandled_input_event: input_device::UnhandledInputEvent,
54 ) -> Vec<input_device::InputEvent> {
55 fuchsia_trace::duration!("input", "pointer_display_scale_handler");
56 match unhandled_input_event {
57 input_device::UnhandledInputEvent {
58 device_event:
59 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
60 location:
61 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
62 millimeters: raw_mm,
63 }),
64 wheel_delta_v,
65 wheel_delta_h,
66 phase: phase @ mouse_binding::MousePhase::Move,
68 affected_buttons,
69 pressed_buttons,
70 is_precision_scroll,
71 wake_lease,
72 }),
73 device_descriptor: device_descriptor @ input_device::InputDeviceDescriptor::Mouse(_),
74 event_time,
75 trace_id,
76 } => {
77 fuchsia_trace::duration!("input", "pointer_display_scale_handler[processing]");
78 let tracing_id = trace_id.unwrap_or_else(|| 0.into());
79 fuchsia_trace::flow_step!("input", "event_in_input_pipeline", tracing_id);
80
81 self.inspect_status.count_received_event(&event_time);
82 let scaled_mm = self.scale_motion(raw_mm);
83 let input_event = input_device::InputEvent {
84 device_event: input_device::InputDeviceEvent::Mouse(
85 mouse_binding::MouseEvent {
86 location: mouse_binding::MouseLocation::Relative(
87 mouse_binding::RelativeLocation { millimeters: scaled_mm },
88 ),
89 wheel_delta_v,
90 wheel_delta_h,
91 phase,
92 affected_buttons,
93 pressed_buttons,
94 is_precision_scroll,
95 wake_lease,
96 },
97 ),
98 device_descriptor,
99 event_time,
100 handled: input_device::Handled::No,
101 trace_id,
102 };
103 vec![input_event]
104 }
105 input_device::UnhandledInputEvent {
106 device_event:
107 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
108 location,
109 wheel_delta_v,
110 wheel_delta_h,
111 phase: phase @ mouse_binding::MousePhase::Wheel,
112 affected_buttons,
113 pressed_buttons,
114 is_precision_scroll,
115 wake_lease,
116 }),
117 device_descriptor: device_descriptor @ input_device::InputDeviceDescriptor::Mouse(_),
118 event_time,
119 trace_id,
120 } => {
121 fuchsia_trace::duration!("input", "pointer_display_scale_handler[processing]");
122 if let Some(trace_id) = trace_id {
123 fuchsia_trace::flow_step!(
124 c"input",
125 c"event_in_input_pipeline",
126 trace_id.into()
127 );
128 }
129
130 self.inspect_status.count_received_event(&event_time);
131 let scaled_wheel_delta_v = self.scale_wheel_delta(wheel_delta_v);
132 let scaled_wheel_delta_h = self.scale_wheel_delta(wheel_delta_h);
133 let input_event = input_device::InputEvent {
134 device_event: input_device::InputDeviceEvent::Mouse(
135 mouse_binding::MouseEvent {
136 location,
137 wheel_delta_v: scaled_wheel_delta_v,
138 wheel_delta_h: scaled_wheel_delta_h,
139 phase,
140 affected_buttons,
141 pressed_buttons,
142 is_precision_scroll,
143 wake_lease,
144 },
145 ),
146 device_descriptor,
147 event_time,
148 handled: input_device::Handled::No,
149 trace_id,
150 };
151 vec![input_event]
152 }
153 _ => {
154 log::warn!("Unhandled input event: {:?}", unhandled_input_event.get_event_type());
156 vec![input_device::InputEvent::from(unhandled_input_event)]
157 }
158 }
159 }
160}
161
162impl PointerDisplayScaleHandler {
163 pub fn new(
169 scale_factor: f32,
170 input_handlers_node: &fuchsia_inspect::Node,
171 metrics_logger: metrics::MetricsLogger,
172 ) -> Result<Rc<Self>, Error> {
173 log::debug!("scale_factor={}", scale_factor);
174 use std::num::FpCategory;
175 let inspect_status = InputHandlerStatus::new(
176 input_handlers_node,
177 "pointer_display_scale_handler",
178 false,
179 );
180 match scale_factor.classify() {
181 FpCategory::Nan | FpCategory::Infinite | FpCategory::Zero | FpCategory::Subnormal => {
182 Err(format_err!(
183 "scale_factor {} is not a `Normal` floating-point value",
184 scale_factor
185 ))
186 }
187 FpCategory::Normal => {
188 if scale_factor < 0.0 {
189 Err(format_err!("Inverting motion is not supported"))
190 } else if scale_factor < 1.0 {
191 Err(format_err!("Down-scaling motion is not supported"))
192 } else {
193 Ok(Rc::new(Self { scale_factor, inspect_status, metrics_logger }))
194 }
195 }
196 }
197 }
198
199 fn scale_motion(self: &Rc<Self>, motion: Position) -> Position {
201 motion * self.scale_factor
202 }
203
204 fn scale_wheel_delta(
206 self: &Rc<Self>,
207 wheel_delta: Option<mouse_binding::WheelDelta>,
208 ) -> Option<mouse_binding::WheelDelta> {
209 match wheel_delta {
210 None => None,
211 Some(delta) => Some(mouse_binding::WheelDelta {
212 raw_data: delta.raw_data,
213 physical_pixel: match delta.physical_pixel {
214 None => {
215 self.metrics_logger.log_error(
218 InputPipelineErrorMetricDimensionEvent::PointerDisplayScaleNoPhysicalPixel,
219 "physical_pixel is none",
220 );
221 None
222 }
223 Some(pixel) => Some(self.scale_factor * pixel),
224 },
225 }),
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::input_handler::InputHandler;
234 use crate::testing_utilities;
235 use assert_matches::assert_matches;
236 use fuchsia_async as fasync;
237 use maplit::hashset;
238 use std::cell::Cell;
239 use std::collections::HashSet;
240 use std::ops::Add;
241 use test_case::test_case;
242
243 const COUNTS_PER_MM: f32 = 12.0;
244 const DEVICE_DESCRIPTOR: input_device::InputDeviceDescriptor =
245 input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
246 device_id: 0,
247 absolute_x_range: None,
248 absolute_y_range: None,
249 wheel_v_range: None,
250 wheel_h_range: None,
251 buttons: None,
252 counts_per_mm: COUNTS_PER_MM as u32,
253 });
254
255 std::thread_local! {static NEXT_EVENT_TIME: Cell<i64> = Cell::new(0)}
256
257 fn make_unhandled_input_event(
258 mouse_event: mouse_binding::MouseEvent,
259 ) -> input_device::UnhandledInputEvent {
260 let event_time = NEXT_EVENT_TIME.with(|t| {
261 let old = t.get();
262 t.set(old + 1);
263 old
264 });
265 input_device::UnhandledInputEvent {
266 device_event: input_device::InputDeviceEvent::Mouse(mouse_event),
267 device_descriptor: DEVICE_DESCRIPTOR.clone(),
268 event_time: zx::MonotonicInstant::from_nanos(event_time),
269 trace_id: None,
270 }
271 }
272
273 #[test_case(f32::NAN => matches Err(_); "yields err for NaN scale")]
274 #[test_case(f32::INFINITY => matches Err(_); "yields err for pos infinite scale")]
275 #[test_case(f32::NEG_INFINITY => matches Err(_); "yields err for neg infinite scale")]
276 #[test_case( -1.0 => matches Err(_); "yields err for neg scale")]
277 #[test_case( 0.0 => matches Err(_); "yields err for pos zero scale")]
278 #[test_case( -0.0 => matches Err(_); "yields err for neg zero scale")]
279 #[test_case( 0.5 => matches Err(_); "yields err for downscale")]
280 #[test_case( 1.0 => matches Ok(_); "yields handler for unit scale")]
281 #[test_case( 1.5 => matches Ok(_); "yields handler for upscale")]
282 fn new(scale_factor: f32) -> Result<Rc<PointerDisplayScaleHandler>, Error> {
283 let inspector = fuchsia_inspect::Inspector::default();
284 let test_node = inspector.root().create_child("test_node");
285 PointerDisplayScaleHandler::new(scale_factor, &test_node, metrics::MetricsLogger::default())
286 }
287
288 #[fuchsia::test(allow_stalls = false)]
289 async fn applies_scale_mm() {
290 let inspector = fuchsia_inspect::Inspector::default();
291 let test_node = inspector.root().create_child("test_node");
292 let handler =
293 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
294 .expect("failed to make handler");
295 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
296 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
297 millimeters: Position { x: 1.5, y: 4.5 },
298 }),
299 wheel_delta_v: None,
300 wheel_delta_h: None,
301 phase: mouse_binding::MousePhase::Move,
302 affected_buttons: hashset! {},
303 pressed_buttons: hashset! {},
304 is_precision_scroll: None,
305 wake_lease: None.into(),
306 });
307 assert_matches!(
308 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
309 [input_device::InputEvent {
310 device_event:
311 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
312 location:
313 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {millimeters: Position { x, y }}),
314 ..
315 }),
316 ..
317 }] if *x == 3.0 && *y == 9.0
318 );
319 }
320
321 #[test_case(
322 mouse_binding::MouseEvent {
323 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
324 millimeters: Position {
325 x: 1.5 / COUNTS_PER_MM,
326 y: 4.5 / COUNTS_PER_MM },
327 }),
328 wheel_delta_v: None,
329 wheel_delta_h: None,
330 phase: mouse_binding::MousePhase::Move,
331 affected_buttons: hashset! {},
332 pressed_buttons: hashset! {},
333 is_precision_scroll: None,
334 wake_lease: None.into(),
335 }; "move event")]
336 #[test_case(
337 mouse_binding::MouseEvent {
338 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
339 millimeters: Position::zero(),
340 }),
341 wheel_delta_v: Some(mouse_binding::WheelDelta {
342 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
343 physical_pixel: Some(1.0),
344 }),
345 wheel_delta_h: None,
346 phase: mouse_binding::MousePhase::Wheel,
347 affected_buttons: hashset! {},
348 pressed_buttons: hashset! {},
349 is_precision_scroll: None,
350 wake_lease: None.into(),
351 }; "wheel event")]
352 #[fuchsia::test(allow_stalls = false)]
353 async fn does_not_consume(event: mouse_binding::MouseEvent) {
354 let inspector = fuchsia_inspect::Inspector::default();
355 let test_node = inspector.root().create_child("test_node");
356 let handler =
357 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
358 .expect("failed to make handler");
359 let input_event = make_unhandled_input_event(event);
360 assert_matches!(
361 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
362 [input_device::InputEvent { handled: input_device::Handled::No, .. }]
363 );
364 }
365
366 #[test_case(hashset! { }; "empty buttons")]
367 #[test_case(hashset! { 1}; "one button")]
368 #[test_case(hashset! {1, 2, 3}; "multiple buttons")]
369 #[fuchsia::test(allow_stalls = false)]
370 async fn preserves_buttons_move_event(input_buttons: HashSet<u8>) {
371 let inspector = fuchsia_inspect::Inspector::default();
372 let test_node = inspector.root().create_child("test_node");
373 let handler =
374 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
375 .expect("failed to make handler");
376 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
377 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
378 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
379 }),
380 wheel_delta_v: None,
381 wheel_delta_h: None,
382 phase: mouse_binding::MousePhase::Move,
383 affected_buttons: input_buttons.clone(),
384 pressed_buttons: input_buttons.clone(),
385 is_precision_scroll: None,
386 wake_lease: None.into(),
387 });
388 assert_matches!(
389 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
390 [input_device::InputEvent {
391 device_event:
392 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent { affected_buttons, pressed_buttons, ..}),
393 ..
394 }] if *affected_buttons == input_buttons && *pressed_buttons == input_buttons
395 );
396 }
397
398 #[test_case(hashset! { }; "empty buttons")]
399 #[test_case(hashset! { 1}; "one button")]
400 #[test_case(hashset! {1, 2, 3}; "multiple buttons")]
401 #[fuchsia::test(allow_stalls = false)]
402 async fn preserves_buttons_wheel_event(input_buttons: HashSet<u8>) {
403 let inspector = fuchsia_inspect::Inspector::default();
404 let test_node = inspector.root().create_child("test_node");
405 let handler =
406 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
407 .expect("failed to make handler");
408 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
409 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
410 millimeters: Position::zero(),
411 }),
412 wheel_delta_v: Some(mouse_binding::WheelDelta {
413 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
414 physical_pixel: Some(1.0),
415 }),
416 wheel_delta_h: None,
417 phase: mouse_binding::MousePhase::Wheel,
418 affected_buttons: input_buttons.clone(),
419 pressed_buttons: input_buttons.clone(),
420 is_precision_scroll: None,
421 wake_lease: None.into(),
422 });
423 assert_matches!(
424 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
425 [input_device::InputEvent {
426 device_event:
427 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent { affected_buttons, pressed_buttons, ..}),
428 ..
429 }] if *affected_buttons == input_buttons && *pressed_buttons == input_buttons
430 );
431 }
432
433 #[test_case(
434 mouse_binding::MouseEvent {
435 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
436 millimeters: Position {
437 x: 1.5 / COUNTS_PER_MM,
438 y: 4.5 / COUNTS_PER_MM },
439 }),
440 wheel_delta_v: None,
441 wheel_delta_h: None,
442 phase: mouse_binding::MousePhase::Move,
443 affected_buttons: hashset! {},
444 pressed_buttons: hashset! {},
445 is_precision_scroll: None,
446 wake_lease: None.into(),
447 }; "move event")]
448 #[test_case(
449 mouse_binding::MouseEvent {
450 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
451 millimeters: Position::zero(),
452 }),
453 wheel_delta_v: Some(mouse_binding::WheelDelta {
454 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
455 physical_pixel: Some(1.0),
456 }),
457 wheel_delta_h: None,
458 phase: mouse_binding::MousePhase::Wheel,
459 affected_buttons: hashset! {},
460 pressed_buttons: hashset! {},
461 is_precision_scroll: None,
462 wake_lease: None.into(),
463 }; "wheel event")]
464 #[fuchsia::test(allow_stalls = false)]
465 async fn preserves_descriptor(event: mouse_binding::MouseEvent) {
466 let inspector = fuchsia_inspect::Inspector::default();
467 let test_node = inspector.root().create_child("test_node");
468 let handler =
469 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
470 .expect("failed to make handler");
471 let input_event = make_unhandled_input_event(event);
472 assert_matches!(
473 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
474 [input_device::InputEvent { device_descriptor: DEVICE_DESCRIPTOR, .. }]
475 );
476 }
477
478 #[test_case(
479 mouse_binding::MouseEvent {
480 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
481 millimeters: Position {
482 x: 1.5 / COUNTS_PER_MM,
483 y: 4.5 / COUNTS_PER_MM },
484 }),
485 wheel_delta_v: None,
486 wheel_delta_h: None,
487 phase: mouse_binding::MousePhase::Move,
488 affected_buttons: hashset! {},
489 pressed_buttons: hashset! {},
490 is_precision_scroll: None,
491 wake_lease: None.into(),
492 }; "move event")]
493 #[test_case(
494 mouse_binding::MouseEvent {
495 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
496 millimeters: Position::zero(),
497 }),
498 wheel_delta_v: Some(mouse_binding::WheelDelta {
499 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
500 physical_pixel: Some(1.0),
501 }),
502 wheel_delta_h: None,
503 phase: mouse_binding::MousePhase::Wheel,
504 affected_buttons: hashset! {},
505 pressed_buttons: hashset! {},
506 is_precision_scroll: None,
507 wake_lease: None.into(),
508 }; "wheel event")]
509 #[fuchsia::test(allow_stalls = false)]
510 async fn preserves_event_time(event: mouse_binding::MouseEvent) {
511 let inspector = fuchsia_inspect::Inspector::default();
512 let test_node = inspector.root().create_child("test_node");
513 let handler =
514 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
515 .expect("failed to make handler");
516 let mut input_event = make_unhandled_input_event(event);
517 const EVENT_TIME: zx::MonotonicInstant = zx::MonotonicInstant::from_nanos(42);
518 input_event.event_time = EVENT_TIME;
519
520 let events = handler.clone().handle_unhandled_input_event(input_event).await;
521 assert_eq!(events.len(), 1, "{events:?} should be 1 element");
522 assert_eq!(events[0].event_time, EVENT_TIME);
523 }
524
525 #[test_case(
526 mouse_binding::MouseEvent {
527 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
528 millimeters: Position::zero(),
529 }),
530 wheel_delta_v: Some(mouse_binding::WheelDelta {
531 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
532 physical_pixel: Some(1.0),
533 }),
534 wheel_delta_h: None,
535 phase: mouse_binding::MousePhase::Wheel,
536 affected_buttons: hashset! {},
537 pressed_buttons: hashset! {},
538 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
539 wake_lease: None.into(),
540 } => matches input_device::InputEvent {
541 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
542 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
543 ..
544 }),
545 ..
546 }; "no")]
547 #[test_case(
548 mouse_binding::MouseEvent {
549 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
550 millimeters: Position::zero(),
551 }),
552 wheel_delta_v: Some(mouse_binding::WheelDelta {
553 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
554 physical_pixel: Some(1.0),
555 }),
556 wheel_delta_h: None,
557 phase: mouse_binding::MousePhase::Wheel,
558 affected_buttons: hashset! {},
559 pressed_buttons: hashset! {},
560 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
561 wake_lease: None.into(),
562 } => matches input_device::InputEvent {
563 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
564 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
565 ..
566 }),
567 ..
568 }; "yes")]
569 #[fuchsia::test(allow_stalls = false)]
570 async fn preserves_is_precision_scroll(
571 event: mouse_binding::MouseEvent,
572 ) -> input_device::InputEvent {
573 let inspector = fuchsia_inspect::Inspector::default();
574 let test_node = inspector.root().create_child("test_node");
575 let handler =
576 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
577 .expect("failed to make handler");
578 let input_event = make_unhandled_input_event(event);
579
580 handler.clone().handle_unhandled_input_event(input_event).await[0].clone()
581 }
582
583 #[test_case(
584 Some(mouse_binding::WheelDelta {
585 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
586 physical_pixel: Some(1.0),
587 }),
588 None => (Some(2.0), None); "v tick h none"
589 )]
590 #[test_case(
591 None, Some(mouse_binding::WheelDelta {
592 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
593 physical_pixel: Some(1.0),
594 }) => (None, Some(2.0)); "v none h tick"
595 )]
596 #[test_case(
597 Some(mouse_binding::WheelDelta {
598 raw_data: mouse_binding::RawWheelDelta::Millimeters(1.0),
599 physical_pixel: Some(1.0),
600 }),
601 None => (Some(2.0), None); "v mm h none"
602 )]
603 #[test_case(
604 None, Some(mouse_binding::WheelDelta {
605 raw_data: mouse_binding::RawWheelDelta::Millimeters(1.0),
606 physical_pixel: Some(1.0),
607 }) => (None, Some(2.0)); "v none h mm"
608 )]
609 #[fuchsia::test(allow_stalls = false)]
610 async fn applied_scale_scroll_event(
611 wheel_delta_v: Option<mouse_binding::WheelDelta>,
612 wheel_delta_h: Option<mouse_binding::WheelDelta>,
613 ) -> (Option<f32>, Option<f32>) {
614 let inspector = fuchsia_inspect::Inspector::default();
615 let test_node = inspector.root().create_child("test_node");
616 let handler =
617 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
618 .expect("failed to make handler");
619 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
620 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
621 millimeters: Position::zero(),
622 }),
623 wheel_delta_v,
624 wheel_delta_h,
625 phase: mouse_binding::MousePhase::Wheel,
626 affected_buttons: hashset! {},
627 pressed_buttons: hashset! {},
628 is_precision_scroll: None,
629 wake_lease: None.into(),
630 });
631 let events = handler.clone().handle_unhandled_input_event(input_event).await;
632 assert_matches!(
633 events.as_slice(),
634 [input_device::InputEvent {
635 device_event: input_device::InputDeviceEvent::Mouse(
636 mouse_binding::MouseEvent { .. }
637 ),
638 ..
639 }]
640 );
641 if let input_device::InputEvent {
642 device_event:
643 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
644 wheel_delta_v,
645 wheel_delta_h,
646 ..
647 }),
648 ..
649 } = events[0].clone()
650 {
651 match (wheel_delta_v, wheel_delta_h) {
652 (None, None) => return (None, None),
653 (None, Some(delta_h)) => return (None, delta_h.physical_pixel),
654 (Some(delta_v), None) => return (delta_v.physical_pixel, None),
655 (Some(delta_v), Some(delta_h)) => {
656 return (delta_v.physical_pixel, delta_h.physical_pixel);
657 }
658 }
659 } else {
660 unreachable!();
661 }
662 }
663
664 #[fuchsia::test]
665 async fn pointer_display_scale_handler_initialized_with_inspect_node() {
666 let inspector = fuchsia_inspect::Inspector::default();
667 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
668 let _handler = PointerDisplayScaleHandler::new(
669 1.0,
670 &fake_handlers_node,
671 metrics::MetricsLogger::default(),
672 );
673 diagnostics_assertions::assert_data_tree!(inspector, root: {
674 input_handlers_node: {
675 pointer_display_scale_handler: {
676 events_received_count: 0u64,
677 events_handled_count: 0u64,
678 last_received_timestamp_ns: 0u64,
679 "fuchsia.inspect.Health": {
680 status: "STARTING_UP",
681 start_timestamp_nanos: diagnostics_assertions::AnyProperty
684 },
685 }
686 }
687 });
688 }
689
690 #[fasync::run_singlethreaded(test)]
691 async fn pointer_display_scale_handler_inspect_counts_events() {
692 let inspector = fuchsia_inspect::Inspector::default();
693 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
694 let handler = PointerDisplayScaleHandler::new(
695 1.0,
696 &fake_handlers_node,
697 metrics::MetricsLogger::default(),
698 )
699 .expect("failed to make handler");
700
701 let event_time1 = zx::MonotonicInstant::get();
702 let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
703 let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
704
705 let input_events = vec![
706 testing_utilities::create_mouse_event(
707 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
708 None, None, None, mouse_binding::MousePhase::Wheel,
712 hashset! {},
713 hashset! {},
714 event_time1,
715 &DEVICE_DESCRIPTOR,
716 ),
717 testing_utilities::create_mouse_event(
718 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
719 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
720 }),
721 None, None, None, mouse_binding::MousePhase::Move,
725 hashset! {},
726 hashset! {},
727 event_time2,
728 &DEVICE_DESCRIPTOR,
729 ),
730 testing_utilities::create_fake_input_event(event_time2),
732 testing_utilities::create_mouse_event_with_handled(
734 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
735 None, None, None, mouse_binding::MousePhase::Wheel,
739 hashset! {},
740 hashset! {},
741 event_time3,
742 &DEVICE_DESCRIPTOR,
743 input_device::Handled::Yes,
744 ),
745 ];
746
747 for input_event in input_events {
748 let _ = handler.clone().handle_input_event(input_event).await;
749 }
750
751 let last_received_event_time: u64 = event_time2.into_nanos().try_into().unwrap();
752
753 diagnostics_assertions::assert_data_tree!(inspector, root: {
754 input_handlers_node: {
755 pointer_display_scale_handler: {
756 events_received_count: 2u64,
757 events_handled_count: 0u64,
758 last_received_timestamp_ns: last_received_event_time,
759 "fuchsia.inspect.Health": {
760 status: "STARTING_UP",
761 start_timestamp_nanos: diagnostics_assertions::AnyProperty
764 },
765 }
766 }
767 });
768 }
769}