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