blob: 480efbefd59841447f74f32608a3efb51f31efd0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{input_device, input_handler::UnhandledInputHandler, mouse_binding, utils::Position},
anyhow::{format_err, Error},
async_trait::async_trait,
fuchsia_syslog::fx_log_debug,
std::{convert::From, rc::Rc},
};
// TODO(fxbug.dev/91272) Add trackpad support
#[derive(Debug, PartialEq)]
pub struct PointerMotionDisplayScaleHandler {
/// The amount by which motion will be scaled up. E.g., a `scale_factor`
/// of 2 means that all motion will be multiplied by 2.
scale_factor: f32,
}
#[async_trait(?Send)]
impl UnhandledInputHandler for PointerMotionDisplayScaleHandler {
async fn handle_unhandled_input_event(
self: Rc<Self>,
unhandled_input_event: input_device::UnhandledInputEvent,
) -> Vec<input_device::InputEvent> {
match unhandled_input_event {
input_device::UnhandledInputEvent {
device_event:
input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
location:
mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
counts: raw_counts,
millimeters: _,
}),
wheel_delta_v,
wheel_delta_h,
// Only the `Move` phase carries non-zero motion.
phase: phase @ mouse_binding::MousePhase::Move,
affected_buttons,
pressed_buttons,
}),
device_descriptor: device_descriptor @ input_device::InputDeviceDescriptor::Mouse(_),
event_time,
trace_id: _,
} => {
let scaled_counts = self.scale_motion(raw_counts);
let input_event = input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Mouse(
mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(
mouse_binding::RelativeLocation {
counts: scaled_counts,
// TODO(https://fxbug.dev/102569): Implement millimeters.
millimeters: Position::zero(),
},
),
wheel_delta_v,
wheel_delta_h,
phase,
affected_buttons,
pressed_buttons,
},
),
device_descriptor,
event_time,
handled: input_device::Handled::No,
trace_id: None,
};
vec![input_event]
}
_ => vec![input_device::InputEvent::from(unhandled_input_event)],
}
}
}
impl PointerMotionDisplayScaleHandler {
/// Creates a new [`PointerMotionDisplayScaleHandler`].
///
/// Returns
/// * `Ok(Rc<Self>)` if `scale_factor` is finite and >= 1.0, and
/// * `Err(Error)` otherwise.
pub fn new(scale_factor: f32) -> Result<Rc<Self>, Error> {
fx_log_debug!("scale_factor={}", scale_factor);
use std::num::FpCategory;
match scale_factor.classify() {
FpCategory::Nan | FpCategory::Infinite | FpCategory::Zero | FpCategory::Subnormal => {
Err(format_err!(
"scale_factor {} is not a `Normal` floating-point value",
scale_factor
))
}
FpCategory::Normal => {
if scale_factor < 0.0 {
Err(format_err!("Inverting motion is not supported"))
} else if scale_factor < 1.0 {
Err(format_err!("Down-scaling motion is not supported"))
} else {
Ok(Rc::new(Self { scale_factor }))
}
}
}
}
/// Scales `motion`, using the configuration in `self`.
fn scale_motion(self: &Rc<Self>, motion: Position) -> Position {
motion * self.scale_factor
}
}
#[cfg(test)]
mod tests {
use {
super::*,
assert_matches::assert_matches,
fuchsia_zircon as zx,
maplit::hashset,
std::{cell::Cell, collections::HashSet},
test_case::test_case,
};
const DEVICE_DESCRIPTOR: input_device::InputDeviceDescriptor =
input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
device_id: 0,
absolute_x_range: None,
absolute_y_range: None,
wheel_v_range: None,
wheel_h_range: None,
buttons: None,
});
std::thread_local! {static NEXT_EVENT_TIME: Cell<i64> = Cell::new(0)}
fn make_unhandled_input_event(
mouse_event: mouse_binding::MouseEvent,
) -> input_device::UnhandledInputEvent {
let event_time = NEXT_EVENT_TIME.with(|t| {
let old = t.get();
t.set(old + 1);
old
});
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Mouse(mouse_event),
device_descriptor: DEVICE_DESCRIPTOR.clone(),
event_time: zx::Time::from_nanos(event_time),
trace_id: None,
}
}
#[test_case(f32::NAN => matches Err(_); "yields err for NaN scale")]
#[test_case(f32::INFINITY => matches Err(_); "yields err for pos infinite scale")]
#[test_case(f32::NEG_INFINITY => matches Err(_); "yields err for neg infinite scale")]
#[test_case( -1.0 => matches Err(_); "yields err for neg scale")]
#[test_case( 0.0 => matches Err(_); "yields err for pos zero scale")]
#[test_case( -0.0 => matches Err(_); "yields err for neg zero scale")]
#[test_case( 0.5 => matches Err(_); "yields err for downscale")]
#[test_case( 1.0 => matches Ok(_); "yields handler for unit scale")]
#[test_case( 1.5 => matches Ok(_); "yields handler for upscale")]
fn new(scale_factor: f32) -> Result<Rc<PointerMotionDisplayScaleHandler>, Error> {
PointerMotionDisplayScaleHandler::new(scale_factor)
}
#[fuchsia::test(allow_stalls = false)]
async fn applies_scale() {
let handler = PointerMotionDisplayScaleHandler::new(2.0).expect("failed to make handler");
let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
counts: Position { x: 1.5, y: 4.5 },
millimeters: Position::zero(),
}),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
});
assert_matches!(
handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
[input_device::InputEvent {
device_event:
input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
location:
mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {counts: Position { x, y }, millimeters: _
}),
..
}),
..
}] if *x == 3.0 && *y == 9.0
);
}
#[fuchsia::test(allow_stalls = false)]
async fn does_not_consume_event() {
let handler = PointerMotionDisplayScaleHandler::new(2.0).expect("failed to make handler");
let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
counts: Position { x: 1.5, y: 4.5 },
millimeters: Position::zero(),
}),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
});
assert_matches!(
handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::No, .. }]
);
}
#[test_case(hashset! { }; "empty buttons")]
#[test_case(hashset! { 1}; "one button")]
#[test_case(hashset! {1, 2, 3}; "multiple buttons")]
#[fuchsia::test(allow_stalls = false)]
async fn preserves_buttons(input_buttons: HashSet<u8>) {
let handler = PointerMotionDisplayScaleHandler::new(2.0).expect("failed to make handler");
let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
counts: Position { x: 1.5, y: 4.5 },
millimeters: Position::zero(),
}),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: input_buttons.clone(),
pressed_buttons: input_buttons.clone(),
});
assert_matches!(
handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
[input_device::InputEvent {
device_event:
input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent { affected_buttons, pressed_buttons, .. }),
..
}] if *affected_buttons == input_buttons && *pressed_buttons == input_buttons
);
}
#[fuchsia::test(allow_stalls = false)]
async fn preserves_descriptor() {
let handler = PointerMotionDisplayScaleHandler::new(2.0).expect("failed to make handler");
let input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
counts: Position { x: 1.5, y: 4.5 },
millimeters: Position::zero(),
}),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
});
assert_matches!(
handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
[input_device::InputEvent { device_descriptor: DEVICE_DESCRIPTOR, .. }]
);
}
#[fuchsia::test(allow_stalls = false)]
async fn preserves_event_time() {
let handler = PointerMotionDisplayScaleHandler::new(2.0).expect("failed to make handler");
let mut input_event = make_unhandled_input_event(mouse_binding::MouseEvent {
location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
counts: Position { x: 1.5, y: 4.5 },
millimeters: Position::zero(),
}),
wheel_delta_v: None,
wheel_delta_h: None,
phase: mouse_binding::MousePhase::Move,
affected_buttons: hashset! {},
pressed_buttons: hashset! {},
});
const EVENT_TIME: zx::Time = zx::Time::from_nanos(42);
input_event.event_time = EVENT_TIME;
assert_matches!(
handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
[input_device::InputEvent { event_time: EVENT_TIME, .. }]
);
}
}