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