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