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