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