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 self.metrics_logger.log_error(
155 InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
156 std::format!(
157 "{} uninterested input event: {:?}",
158 self.get_name(),
159 unhandled_input_event.get_event_type()
160 ),
161 );
162 vec![input_device::InputEvent::from(unhandled_input_event)]
163 }
164 }
165 }
166}
167
168impl PointerDisplayScaleHandler {
169 pub fn new(
175 scale_factor: f32,
176 input_handlers_node: &fuchsia_inspect::Node,
177 metrics_logger: metrics::MetricsLogger,
178 ) -> Result<Rc<Self>, Error> {
179 log::debug!("scale_factor={}", scale_factor);
180 use std::num::FpCategory;
181 let inspect_status = InputHandlerStatus::new(
182 input_handlers_node,
183 "pointer_display_scale_handler",
184 false,
185 );
186 match scale_factor.classify() {
187 FpCategory::Nan | FpCategory::Infinite | FpCategory::Zero | FpCategory::Subnormal => {
188 Err(format_err!(
189 "scale_factor {} is not a `Normal` floating-point value",
190 scale_factor
191 ))
192 }
193 FpCategory::Normal => {
194 if scale_factor < 0.0 {
195 Err(format_err!("Inverting motion is not supported"))
196 } else if scale_factor < 1.0 {
197 Err(format_err!("Down-scaling motion is not supported"))
198 } else {
199 Ok(Rc::new(Self { scale_factor, inspect_status, metrics_logger }))
200 }
201 }
202 }
203 }
204
205 fn scale_motion(self: &Rc<Self>, motion: Position) -> Position {
207 motion * self.scale_factor
208 }
209
210 fn scale_wheel_delta(
212 self: &Rc<Self>,
213 wheel_delta: Option<mouse_binding::WheelDelta>,
214 ) -> Option<mouse_binding::WheelDelta> {
215 match wheel_delta {
216 None => None,
217 Some(delta) => Some(mouse_binding::WheelDelta {
218 raw_data: delta.raw_data,
219 physical_pixel: match delta.physical_pixel {
220 None => {
221 self.metrics_logger.log_error(
224 InputPipelineErrorMetricDimensionEvent::PointerDisplayScaleNoPhysicalPixel,
225 "physical_pixel is none",
226 );
227 None
228 }
229 Some(pixel) => Some(self.scale_factor * pixel),
230 },
231 }),
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::input_handler::InputHandler;
240 use crate::testing_utilities;
241 use assert_matches::assert_matches;
242 use fuchsia_async as fasync;
243 use maplit::hashset;
244 use std::cell::Cell;
245 use std::collections::HashSet;
246 use std::ops::Add;
247 use test_case::test_case;
248
249 const COUNTS_PER_MM: f32 = 12.0;
250 const DEVICE_DESCRIPTOR: input_device::InputDeviceDescriptor =
251 input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
252 device_id: 0,
253 absolute_x_range: None,
254 absolute_y_range: None,
255 wheel_v_range: None,
256 wheel_h_range: None,
257 buttons: None,
258 counts_per_mm: COUNTS_PER_MM as u32,
259 });
260
261 std::thread_local! {static NEXT_EVENT_TIME: Cell<i64> = Cell::new(0)}
262
263 fn make_unhandled_input_event(
264 mouse_event: mouse_binding::MouseEvent,
265 ) -> input_device::UnhandledInputEvent {
266 let event_time = NEXT_EVENT_TIME.with(|t| {
267 let old = t.get();
268 t.set(old + 1);
269 old
270 });
271 input_device::UnhandledInputEvent {
272 device_event: input_device::InputDeviceEvent::Mouse(mouse_event),
273 device_descriptor: DEVICE_DESCRIPTOR.clone(),
274 event_time: zx::MonotonicInstant::from_nanos(event_time),
275 trace_id: None,
276 }
277 }
278
279 #[test_case(f32::NAN => matches Err(_); "yields err for NaN scale")]
280 #[test_case(f32::INFINITY => matches Err(_); "yields err for pos infinite scale")]
281 #[test_case(f32::NEG_INFINITY => matches Err(_); "yields err for neg infinite scale")]
282 #[test_case( -1.0 => matches Err(_); "yields err for neg scale")]
283 #[test_case( 0.0 => matches Err(_); "yields err for pos zero scale")]
284 #[test_case( -0.0 => matches Err(_); "yields err for neg zero scale")]
285 #[test_case( 0.5 => matches Err(_); "yields err for downscale")]
286 #[test_case( 1.0 => matches Ok(_); "yields handler for unit scale")]
287 #[test_case( 1.5 => matches Ok(_); "yields handler for upscale")]
288 fn new(scale_factor: f32) -> Result<Rc<PointerDisplayScaleHandler>, Error> {
289 let inspector = fuchsia_inspect::Inspector::default();
290 let test_node = inspector.root().create_child("test_node");
291 PointerDisplayScaleHandler::new(scale_factor, &test_node, metrics::MetricsLogger::default())
292 }
293
294 #[fuchsia::test(allow_stalls = false)]
295 async fn applies_scale_mm() {
296 let inspector = fuchsia_inspect::Inspector::default();
297 let test_node = inspector.root().create_child("test_node");
298 let handler =
299 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
300 .expect("failed to make handler");
301 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
302 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
303 millimeters: Position { x: 1.5, y: 4.5 },
304 }),
305 wheel_delta_v: None,
306 wheel_delta_h: None,
307 phase: mouse_binding::MousePhase::Move,
308 affected_buttons: hashset! {},
309 pressed_buttons: hashset! {},
310 is_precision_scroll: None,
311 wake_lease: None.into(),
312 });
313 assert_matches!(
314 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
315 [input_device::InputEvent {
316 device_event:
317 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
318 location:
319 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {millimeters: Position { x, y }}),
320 ..
321 }),
322 ..
323 }] if *x == 3.0 && *y == 9.0
324 );
325 }
326
327 #[test_case(
328 mouse_binding::MouseEvent {
329 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
330 millimeters: Position {
331 x: 1.5 / COUNTS_PER_MM,
332 y: 4.5 / COUNTS_PER_MM },
333 }),
334 wheel_delta_v: None,
335 wheel_delta_h: None,
336 phase: mouse_binding::MousePhase::Move,
337 affected_buttons: hashset! {},
338 pressed_buttons: hashset! {},
339 is_precision_scroll: None,
340 wake_lease: None.into(),
341 }; "move event")]
342 #[test_case(
343 mouse_binding::MouseEvent {
344 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
345 millimeters: Position::zero(),
346 }),
347 wheel_delta_v: Some(mouse_binding::WheelDelta {
348 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
349 physical_pixel: Some(1.0),
350 }),
351 wheel_delta_h: None,
352 phase: mouse_binding::MousePhase::Wheel,
353 affected_buttons: hashset! {},
354 pressed_buttons: hashset! {},
355 is_precision_scroll: None,
356 wake_lease: None.into(),
357 }; "wheel event")]
358 #[fuchsia::test(allow_stalls = false)]
359 async fn does_not_consume(event: mouse_binding::MouseEvent) {
360 let inspector = fuchsia_inspect::Inspector::default();
361 let test_node = inspector.root().create_child("test_node");
362 let handler =
363 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
364 .expect("failed to make handler");
365 let input_event = make_unhandled_input_event(event);
366 assert_matches!(
367 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
368 [input_device::InputEvent { handled: input_device::Handled::No, .. }]
369 );
370 }
371
372 #[test_case(hashset! { }; "empty buttons")]
373 #[test_case(hashset! { 1}; "one button")]
374 #[test_case(hashset! {1, 2, 3}; "multiple buttons")]
375 #[fuchsia::test(allow_stalls = false)]
376 async fn preserves_buttons_move_event(input_buttons: HashSet<u8>) {
377 let inspector = fuchsia_inspect::Inspector::default();
378 let test_node = inspector.root().create_child("test_node");
379 let handler =
380 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
381 .expect("failed to make handler");
382 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
383 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
384 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
385 }),
386 wheel_delta_v: None,
387 wheel_delta_h: None,
388 phase: mouse_binding::MousePhase::Move,
389 affected_buttons: input_buttons.clone(),
390 pressed_buttons: input_buttons.clone(),
391 is_precision_scroll: None,
392 wake_lease: None.into(),
393 });
394 assert_matches!(
395 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
396 [input_device::InputEvent {
397 device_event:
398 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent { affected_buttons, pressed_buttons, ..}),
399 ..
400 }] if *affected_buttons == input_buttons && *pressed_buttons == input_buttons
401 );
402 }
403
404 #[test_case(hashset! { }; "empty buttons")]
405 #[test_case(hashset! { 1}; "one button")]
406 #[test_case(hashset! {1, 2, 3}; "multiple buttons")]
407 #[fuchsia::test(allow_stalls = false)]
408 async fn preserves_buttons_wheel_event(input_buttons: HashSet<u8>) {
409 let inspector = fuchsia_inspect::Inspector::default();
410 let test_node = inspector.root().create_child("test_node");
411 let handler =
412 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
413 .expect("failed to make handler");
414 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
415 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
416 millimeters: Position::zero(),
417 }),
418 wheel_delta_v: Some(mouse_binding::WheelDelta {
419 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
420 physical_pixel: Some(1.0),
421 }),
422 wheel_delta_h: None,
423 phase: mouse_binding::MousePhase::Wheel,
424 affected_buttons: input_buttons.clone(),
425 pressed_buttons: input_buttons.clone(),
426 is_precision_scroll: None,
427 wake_lease: None.into(),
428 });
429 assert_matches!(
430 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
431 [input_device::InputEvent {
432 device_event:
433 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent { affected_buttons, pressed_buttons, ..}),
434 ..
435 }] if *affected_buttons == input_buttons && *pressed_buttons == input_buttons
436 );
437 }
438
439 #[test_case(
440 mouse_binding::MouseEvent {
441 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
442 millimeters: Position {
443 x: 1.5 / COUNTS_PER_MM,
444 y: 4.5 / COUNTS_PER_MM },
445 }),
446 wheel_delta_v: None,
447 wheel_delta_h: None,
448 phase: mouse_binding::MousePhase::Move,
449 affected_buttons: hashset! {},
450 pressed_buttons: hashset! {},
451 is_precision_scroll: None,
452 wake_lease: None.into(),
453 }; "move event")]
454 #[test_case(
455 mouse_binding::MouseEvent {
456 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
457 millimeters: Position::zero(),
458 }),
459 wheel_delta_v: Some(mouse_binding::WheelDelta {
460 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
461 physical_pixel: Some(1.0),
462 }),
463 wheel_delta_h: None,
464 phase: mouse_binding::MousePhase::Wheel,
465 affected_buttons: hashset! {},
466 pressed_buttons: hashset! {},
467 is_precision_scroll: None,
468 wake_lease: None.into(),
469 }; "wheel event")]
470 #[fuchsia::test(allow_stalls = false)]
471 async fn preserves_descriptor(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 input_event = make_unhandled_input_event(event);
478 assert_matches!(
479 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
480 [input_device::InputEvent { device_descriptor: DEVICE_DESCRIPTOR, .. }]
481 );
482 }
483
484 #[test_case(
485 mouse_binding::MouseEvent {
486 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
487 millimeters: Position {
488 x: 1.5 / COUNTS_PER_MM,
489 y: 4.5 / COUNTS_PER_MM },
490 }),
491 wheel_delta_v: None,
492 wheel_delta_h: None,
493 phase: mouse_binding::MousePhase::Move,
494 affected_buttons: hashset! {},
495 pressed_buttons: hashset! {},
496 is_precision_scroll: None,
497 wake_lease: None.into(),
498 }; "move event")]
499 #[test_case(
500 mouse_binding::MouseEvent {
501 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
502 millimeters: Position::zero(),
503 }),
504 wheel_delta_v: Some(mouse_binding::WheelDelta {
505 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
506 physical_pixel: Some(1.0),
507 }),
508 wheel_delta_h: None,
509 phase: mouse_binding::MousePhase::Wheel,
510 affected_buttons: hashset! {},
511 pressed_buttons: hashset! {},
512 is_precision_scroll: None,
513 wake_lease: None.into(),
514 }; "wheel event")]
515 #[fuchsia::test(allow_stalls = false)]
516 async fn preserves_event_time(event: mouse_binding::MouseEvent) {
517 let inspector = fuchsia_inspect::Inspector::default();
518 let test_node = inspector.root().create_child("test_node");
519 let handler =
520 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
521 .expect("failed to make handler");
522 let mut input_event = make_unhandled_input_event(event);
523 const EVENT_TIME: zx::MonotonicInstant = zx::MonotonicInstant::from_nanos(42);
524 input_event.event_time = EVENT_TIME;
525
526 let events = handler.clone().handle_unhandled_input_event(input_event).await;
527 assert_eq!(events.len(), 1, "{events:?} should be 1 element");
528 assert_eq!(events[0].event_time, EVENT_TIME);
529 }
530
531 #[test_case(
532 mouse_binding::MouseEvent {
533 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
534 millimeters: Position::zero(),
535 }),
536 wheel_delta_v: Some(mouse_binding::WheelDelta {
537 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
538 physical_pixel: Some(1.0),
539 }),
540 wheel_delta_h: None,
541 phase: mouse_binding::MousePhase::Wheel,
542 affected_buttons: hashset! {},
543 pressed_buttons: hashset! {},
544 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
545 wake_lease: None.into(),
546 } => matches input_device::InputEvent {
547 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
548 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
549 ..
550 }),
551 ..
552 }; "no")]
553 #[test_case(
554 mouse_binding::MouseEvent {
555 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
556 millimeters: Position::zero(),
557 }),
558 wheel_delta_v: Some(mouse_binding::WheelDelta {
559 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
560 physical_pixel: Some(1.0),
561 }),
562 wheel_delta_h: None,
563 phase: mouse_binding::MousePhase::Wheel,
564 affected_buttons: hashset! {},
565 pressed_buttons: hashset! {},
566 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
567 wake_lease: None.into(),
568 } => matches input_device::InputEvent {
569 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
570 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
571 ..
572 }),
573 ..
574 }; "yes")]
575 #[fuchsia::test(allow_stalls = false)]
576 async fn preserves_is_precision_scroll(
577 event: mouse_binding::MouseEvent,
578 ) -> input_device::InputEvent {
579 let inspector = fuchsia_inspect::Inspector::default();
580 let test_node = inspector.root().create_child("test_node");
581 let handler =
582 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
583 .expect("failed to make handler");
584 let input_event = make_unhandled_input_event(event);
585
586 handler.clone().handle_unhandled_input_event(input_event).await[0].clone()
587 }
588
589 #[test_case(
590 Some(mouse_binding::WheelDelta {
591 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
592 physical_pixel: Some(1.0),
593 }),
594 None => (Some(2.0), None); "v tick h none"
595 )]
596 #[test_case(
597 None, Some(mouse_binding::WheelDelta {
598 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
599 physical_pixel: Some(1.0),
600 }) => (None, Some(2.0)); "v none h tick"
601 )]
602 #[test_case(
603 Some(mouse_binding::WheelDelta {
604 raw_data: mouse_binding::RawWheelDelta::Millimeters(1.0),
605 physical_pixel: Some(1.0),
606 }),
607 None => (Some(2.0), None); "v mm h none"
608 )]
609 #[test_case(
610 None, Some(mouse_binding::WheelDelta {
611 raw_data: mouse_binding::RawWheelDelta::Millimeters(1.0),
612 physical_pixel: Some(1.0),
613 }) => (None, Some(2.0)); "v none h mm"
614 )]
615 #[fuchsia::test(allow_stalls = false)]
616 async fn applied_scale_scroll_event(
617 wheel_delta_v: Option<mouse_binding::WheelDelta>,
618 wheel_delta_h: Option<mouse_binding::WheelDelta>,
619 ) -> (Option<f32>, Option<f32>) {
620 let inspector = fuchsia_inspect::Inspector::default();
621 let test_node = inspector.root().create_child("test_node");
622 let handler =
623 PointerDisplayScaleHandler::new(2.0, &test_node, metrics::MetricsLogger::default())
624 .expect("failed to make handler");
625 let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
626 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
627 millimeters: Position::zero(),
628 }),
629 wheel_delta_v,
630 wheel_delta_h,
631 phase: mouse_binding::MousePhase::Wheel,
632 affected_buttons: hashset! {},
633 pressed_buttons: hashset! {},
634 is_precision_scroll: None,
635 wake_lease: None.into(),
636 });
637 let events = handler.clone().handle_unhandled_input_event(input_event).await;
638 assert_matches!(
639 events.as_slice(),
640 [input_device::InputEvent {
641 device_event: input_device::InputDeviceEvent::Mouse(
642 mouse_binding::MouseEvent { .. }
643 ),
644 ..
645 }]
646 );
647 if let input_device::InputEvent {
648 device_event:
649 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
650 wheel_delta_v,
651 wheel_delta_h,
652 ..
653 }),
654 ..
655 } = events[0].clone()
656 {
657 match (wheel_delta_v, wheel_delta_h) {
658 (None, None) => return (None, None),
659 (None, Some(delta_h)) => return (None, delta_h.physical_pixel),
660 (Some(delta_v), None) => return (delta_v.physical_pixel, None),
661 (Some(delta_v), Some(delta_h)) => {
662 return (delta_v.physical_pixel, delta_h.physical_pixel);
663 }
664 }
665 } else {
666 unreachable!();
667 }
668 }
669
670 #[fuchsia::test]
671 async fn pointer_display_scale_handler_initialized_with_inspect_node() {
672 let inspector = fuchsia_inspect::Inspector::default();
673 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
674 let _handler = PointerDisplayScaleHandler::new(
675 1.0,
676 &fake_handlers_node,
677 metrics::MetricsLogger::default(),
678 );
679 diagnostics_assertions::assert_data_tree!(inspector, root: {
680 input_handlers_node: {
681 pointer_display_scale_handler: {
682 events_received_count: 0u64,
683 events_handled_count: 0u64,
684 last_received_timestamp_ns: 0u64,
685 "fuchsia.inspect.Health": {
686 status: "STARTING_UP",
687 start_timestamp_nanos: diagnostics_assertions::AnyProperty
690 },
691 }
692 }
693 });
694 }
695
696 #[fasync::run_singlethreaded(test)]
697 async fn pointer_display_scale_handler_inspect_counts_events() {
698 let inspector = fuchsia_inspect::Inspector::default();
699 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
700 let handler = PointerDisplayScaleHandler::new(
701 1.0,
702 &fake_handlers_node,
703 metrics::MetricsLogger::default(),
704 )
705 .expect("failed to make handler");
706
707 let event_time1 = zx::MonotonicInstant::get();
708 let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
709 let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
710
711 let input_events = vec![
712 testing_utilities::create_mouse_event(
713 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
714 None, None, None, mouse_binding::MousePhase::Wheel,
718 hashset! {},
719 hashset! {},
720 event_time1,
721 &DEVICE_DESCRIPTOR,
722 ),
723 testing_utilities::create_mouse_event(
724 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
725 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
726 }),
727 None, None, None, mouse_binding::MousePhase::Move,
731 hashset! {},
732 hashset! {},
733 event_time2,
734 &DEVICE_DESCRIPTOR,
735 ),
736 testing_utilities::create_fake_input_event(event_time2),
738 testing_utilities::create_mouse_event_with_handled(
740 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
741 None, None, None, mouse_binding::MousePhase::Wheel,
745 hashset! {},
746 hashset! {},
747 event_time3,
748 &DEVICE_DESCRIPTOR,
749 input_device::Handled::Yes,
750 ),
751 ];
752
753 for input_event in input_events {
754 let _ = handler.clone().handle_input_event(input_event).await;
755 }
756
757 let last_received_event_time: u64 = event_time2.into_nanos().try_into().unwrap();
758
759 diagnostics_assertions::assert_data_tree!(inspector, root: {
760 input_handlers_node: {
761 pointer_display_scale_handler: {
762 events_received_count: 2u64,
763 events_handled_count: 0u64,
764 last_received_timestamp_ns: last_received_event_time,
765 "fuchsia.inspect.Health": {
766 status: "STARTING_UP",
767 start_timestamp_nanos: diagnostics_assertions::AnyProperty
770 },
771 }
772 }
773 });
774 }
775}