blob: f87dd413c61fc5af2949f363f04e5e0c276a629b [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Contains the InputVerifier, used to validate a stream of input events.
use crate::ffi::RustPointerProperties;
use crate::input::{DeviceId, MotionAction, MotionButton, MotionFlags, Source, SourceClass};
use log::info;
use std::collections::HashMap;
use std::collections::HashSet;
/// Represents a movement or state change event from a pointer device. The Rust equivalent of the
/// C++ NotifyMotionArgs struct.
#[derive(Clone, Copy)]
pub struct NotifyMotionArgs<'a> {
/// The ID of the device that emitted the event.
pub device_id: DeviceId,
/// The type of device that emitted the event.
pub source: Source,
/// The type of event that took place.
pub action: MotionAction,
/// The properties of each of the pointers involved in the event.
pub pointer_properties: &'a [RustPointerProperties],
/// Flags applied to the event.
pub flags: MotionFlags,
/// The set of buttons that were pressed at the time of the event.
///
/// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
/// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
/// for those buttons, building up to a button state matching that of the DOWN. For example, if
/// the user presses the primary and secondary buttons at exactly the same time, we'd expect
/// this sequence:
///
/// | Action | Action button | Button state |
/// |----------------|---------------|------------------------|
/// | `HOVER_EXIT` | - | - |
/// | `DOWN` | - | `PRIMARY`, `SECONDARY` |
/// | `BUTTON_PRESS` | `PRIMARY` | `PRIMARY` |
/// | `BUTTON_PRESS` | `SECONDARY` | `PRIMARY`, `SECONDARY` |
/// | `MOVE` | - | `PRIMARY`, `SECONDARY` |
pub button_state: MotionButton,
}
/// Verifies the properties of an event that should always be true, regardless of the current state.
fn verify_event(event: NotifyMotionArgs<'_>, verify_buttons: bool) -> Result<(), String> {
let pointer_count = event.pointer_properties.len();
if pointer_count < 1 {
return Err(format!("Invalid {} event: no pointers", event.action));
}
match event.action {
MotionAction::Down
| MotionAction::HoverEnter
| MotionAction::HoverExit
| MotionAction::HoverMove
| MotionAction::Up => {
if pointer_count != 1 {
return Err(format!(
"Invalid {} event: there are {} pointers in the event",
event.action, pointer_count
));
}
}
MotionAction::Cancel => {
if !event.flags.contains(MotionFlags::CANCELED) {
return Err(format!(
"For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
event.flags
));
}
}
MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
if action_index >= pointer_count {
return Err(format!(
"Got {}, but event has {} pointer(s)",
event.action, pointer_count
));
}
}
MotionAction::ButtonPress { action_button }
| MotionAction::ButtonRelease { action_button } => {
if verify_buttons {
let button_count = action_button.iter().count();
if button_count != 1 {
return Err(format!(
"Invalid {} event: must specify a single action button, not {} action \
buttons",
event.action, button_count
));
}
}
}
_ => {}
}
Ok(())
}
/// Keeps track of the button state for a single device and verifies events against it.
#[derive(Default)]
struct ButtonVerifier {
/// The current button state of the device.
button_state: MotionButton,
/// The set of "pending buttons", which were seen in the last DOWN event's button state but
/// for which we haven't seen BUTTON_PRESS events yet (see [`NotifyMotionArgs::button_state`]).
pending_buttons: MotionButton,
}
impl ButtonVerifier {
pub fn process_event(&mut self, event: NotifyMotionArgs<'_>) -> Result<(), String> {
if !self.pending_buttons.is_empty() {
// We just saw a DOWN with some additional buttons in its state, so it should be
// immediately followed by ButtonPress events for those buttons.
match event.action {
MotionAction::ButtonPress { action_button }
if self.pending_buttons.contains(action_button) =>
{
self.pending_buttons -= action_button;
}
_ => {
return Err(format!(
"After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {}",
self.pending_buttons, event.action
));
}
}
}
let expected_state = match event.action {
MotionAction::Down => {
if self.button_state - event.button_state != MotionButton::empty() {
return Err(format!(
"DOWN event button state is missing {:?}",
self.button_state - event.button_state
));
}
self.pending_buttons = event.button_state - self.button_state;
// We've already checked that the state isn't missing any already-down buttons, and
// extra buttons are valid on DOWN actions, so bypass the expected state check.
event.button_state
}
MotionAction::ButtonPress { action_button } => {
if self.button_state.contains(action_button) {
return Err(format!(
"Duplicate BUTTON_PRESS; button state already contains {action_button:?}"
));
}
self.button_state | action_button
}
MotionAction::ButtonRelease { action_button } => {
if !self.button_state.contains(action_button) {
return Err(format!(
"Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}",
));
}
self.button_state - action_button
}
_ => self.button_state,
};
if event.button_state != expected_state {
return Err(format!(
"Expected {} button state to be {:?}, but was {:?}",
event.action, expected_state, event.button_state
));
}
// DOWN events can have pending buttons, so don't update the state for them.
if event.action != MotionAction::Down {
self.button_state = event.button_state;
}
Ok(())
}
}
/// The InputVerifier is used to validate a stream of input events.
pub struct InputVerifier {
name: String,
should_log: bool,
verify_buttons: bool,
touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
button_verifier_by_device: HashMap<DeviceId, ButtonVerifier>,
}
impl InputVerifier {
/// Create a new InputVerifier.
pub fn new(name: &str, should_log: bool, verify_buttons: bool) -> Self {
logger::init(
logger::Config::default()
.with_tag_on_device("InputVerifier")
.with_max_level(log::LevelFilter::Trace),
);
Self {
name: name.to_owned(),
should_log,
verify_buttons,
touching_pointer_ids_by_device: HashMap::new(),
hovering_pointer_ids_by_device: HashMap::new(),
button_verifier_by_device: HashMap::new(),
}
}
/// Process a pointer movement event from an InputDevice.
/// If the event is not valid, we return an error string that describes the issue.
pub fn process_movement(&mut self, event: NotifyMotionArgs<'_>) -> Result<(), String> {
if !event.source.is_from_class(SourceClass::Pointer) {
// Skip non-pointer sources like MOUSE_RELATIVE for now
return Ok(());
}
if self.should_log {
info!(
"Processing {} for device {:?} ({} pointer{}) on {}",
event.action,
event.device_id,
event.pointer_properties.len(),
if event.pointer_properties.len() == 1 { "" } else { "s" },
self.name
);
}
verify_event(event, self.verify_buttons)?;
if self.verify_buttons {
self.button_verifier_by_device
.entry(event.device_id)
.or_default()
.process_event(event)?;
}
match event.action {
MotionAction::Down => {
if self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
self.name, event.device_id, self.touching_pointer_ids_by_device
));
}
let it = self.touching_pointer_ids_by_device.entry(event.device_id).or_default();
it.insert(event.pointer_properties[0].id);
}
MotionAction::PointerDown { action_index } => {
if !self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Received POINTER_DOWN but no pointers are currently down \
for device {:?}",
self.name, event.device_id
));
}
let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
if it.len() != event.pointer_properties.len() - 1 {
return Err(format!(
"{}: There are currently {} touching pointers, but the incoming \
POINTER_DOWN event has {}",
self.name,
it.len(),
event.pointer_properties.len()
));
}
let pointer_id = event.pointer_properties[action_index].id;
if it.contains(&pointer_id) {
return Err(format!(
"{}: Pointer with id={} already present found in the properties",
self.name, pointer_id
));
}
it.insert(pointer_id);
}
MotionAction::Move => {
if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
return Err(format!(
"{}: ACTION_MOVE touching pointers don't match",
self.name
));
}
}
MotionAction::PointerUp { action_index } => {
if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
return Err(format!(
"{}: ACTION_POINTER_UP touching pointers don't match",
self.name
));
}
let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
let pointer_id = event.pointer_properties[action_index].id;
it.remove(&pointer_id);
}
MotionAction::Up => {
if !self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{} Received ACTION_UP but no pointers are currently down for device {:?}",
self.name, event.device_id
));
}
let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
if it.len() != 1 {
return Err(format!(
"{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
self.name, it, event.device_id
));
}
let pointer_id = event.pointer_properties[0].id;
if !it.contains(&pointer_id) {
return Err(format!(
"{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
{:?} for device {:?}",
self.name, pointer_id, it, event.device_id
));
}
self.touching_pointer_ids_by_device.remove(&event.device_id);
}
MotionAction::Cancel => {
if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
return Err(format!(
"{}: Got ACTION_CANCEL, but the pointers don't match. \
Existing pointers: {:?}",
self.name, self.touching_pointer_ids_by_device
));
}
self.touching_pointer_ids_by_device.remove(&event.device_id);
}
/*
* The hovering protocol currently supports a single pointer only, because we do not
* have ACTION_HOVER_POINTER_ENTER or ACTION_HOVER_POINTER_EXIT.
* Still, we are keeping the infrastructure here pretty general in case that is
* eventually supported.
*/
MotionAction::HoverEnter => {
if self.hovering_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\
{:?}",
self.name, event.device_id, self.hovering_pointer_ids_by_device
));
}
let it = self.hovering_pointer_ids_by_device.entry(event.device_id).or_default();
it.insert(event.pointer_properties[0].id);
}
MotionAction::HoverMove => {
// For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
// If there was no prior HOVER_ENTER, just start a new hovering pointer.
let it = self.hovering_pointer_ids_by_device.entry(event.device_id).or_default();
it.insert(event.pointer_properties[0].id);
}
MotionAction::HoverExit => {
if !self.hovering_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}",
self.name, event.device_id
));
}
let pointer_id = event.pointer_properties[0].id;
let it = self.hovering_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
it.remove(&pointer_id);
if !it.is_empty() {
return Err(format!(
"{}: Removed hovering pointer {}, but pointers are still\
hovering for device {:?}: {:?}",
self.name, pointer_id, event.device_id, it
));
}
self.hovering_pointer_ids_by_device.remove(&event.device_id);
}
_ => return Ok(()),
}
Ok(())
}
/// Notify the verifier that the device has been reset, which will cause the verifier to erase
/// the current internal state for this device. Subsequent events from this device are expected
//// to start a new gesture.
pub fn reset_device(&mut self, device_id: DeviceId) {
self.touching_pointer_ids_by_device.remove(&device_id);
self.hovering_pointer_ids_by_device.remove(&device_id);
}
fn ensure_touching_pointers_match(
&self,
device_id: DeviceId,
pointer_properties: &[RustPointerProperties],
) -> bool {
let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
return false;
};
if pointers.len() != pointer_properties.len() {
return false;
}
for pointer_property in pointer_properties.iter() {
let pointer_id = pointer_property.id;
if !pointers.contains(&pointer_id) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use crate::input::MotionButton;
use crate::input_verifier::InputVerifier;
use crate::input_verifier::NotifyMotionArgs;
use crate::DeviceId;
use crate::MotionAction;
use crate::MotionFlags;
use crate::RustPointerProperties;
use crate::Source;
const BASE_POINTER_PROPERTIES: [RustPointerProperties; 1] = [RustPointerProperties { id: 0 }];
const BASE_EVENT: NotifyMotionArgs = NotifyMotionArgs {
device_id: DeviceId(1),
source: Source::Touchscreen,
action: MotionAction::Down,
pointer_properties: &BASE_POINTER_PROPERTIES,
flags: MotionFlags::empty(),
button_state: MotionButton::empty(),
};
const BASE_MOUSE_EVENT: NotifyMotionArgs =
NotifyMotionArgs { source: Source::Mouse, ..BASE_EVENT };
#[test]
/**
* Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
*/
fn bad_down_event() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ true, /*verify_buttons*/ true);
let pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_err());
}
#[test]
fn single_pointer_stream() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Move,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Up,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_ok());
}
#[test]
fn two_pointer_stream() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_ok());
// POINTER 1 DOWN
let two_pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::PointerDown { action_index: 1 },
pointer_properties: &two_pointer_properties,
..BASE_EVENT
})
.is_ok());
// POINTER 0 UP
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::PointerUp { action_index: 0 },
pointer_properties: &two_pointer_properties,
..BASE_EVENT
})
.is_ok());
// ACTION_UP for pointer id=1
let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Up,
pointer_properties: &pointer_1_properties,
..BASE_EVENT
})
.is_ok());
}
#[test]
fn multi_device_stream() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
device_id: DeviceId(1),
action: MotionAction::Down,
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
device_id: DeviceId(1),
action: MotionAction::Move,
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
device_id: DeviceId(2),
action: MotionAction::Down,
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
device_id: DeviceId(2),
action: MotionAction::Move,
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
device_id: DeviceId(1),
action: MotionAction::Up,
..BASE_EVENT
})
.is_ok());
}
#[test]
fn action_cancel() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
flags: MotionFlags::empty(),
..BASE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Cancel,
flags: MotionFlags::CANCELED,
..BASE_EVENT
})
.is_ok());
}
#[test]
fn invalid_action_cancel() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::Down, ..BASE_EVENT })
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::Cancel, ..BASE_EVENT })
.is_err());
}
#[test]
fn invalid_up() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::Up, ..BASE_EVENT })
.is_err());
}
#[test]
fn correct_hover_sequence() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::HoverMove, ..BASE_EVENT })
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::HoverExit, ..BASE_EVENT })
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_ok());
}
#[test]
fn double_hover_enter() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_err());
}
// Send a MOVE without a preceding DOWN event. This is OK because it's from source
// MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
#[test]
fn relative_mouse_move() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
device_id: DeviceId(2),
source: Source::MouseRelative,
action: MotionAction::Move,
..BASE_EVENT
})
.is_ok());
}
// Send a MOVE event with incorrect number of pointers (one of the pointers is missing).
#[test]
fn move_with_wrong_number_of_pointers() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_ok());
// POINTER 1 DOWN
let two_pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::PointerDown { action_index: 1 },
pointer_properties: &two_pointer_properties,
..BASE_EVENT
})
.is_ok());
// MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Move,
pointer_properties: &pointer_properties,
..BASE_EVENT
})
.is_err());
}
#[test]
fn correct_button_press() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
}
#[test]
fn button_press_without_action_button() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::empty() },
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn button_press_with_multiple_action_buttons() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress {
action_button: MotionButton::Back | MotionButton::Forward
},
button_state: MotionButton::Back | MotionButton::Forward,
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn button_press_without_action_button_in_state() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn button_release_with_action_button_in_state() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonRelease { action_button: MotionButton::Primary },
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn nonbutton_action_with_button_state_change() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::HoverEnter,
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::HoverMove,
button_state: MotionButton::Back,
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn nonbutton_action_missing_button_state() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::HoverEnter,
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Back },
button_state: MotionButton::Back,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::HoverMove,
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn up_without_button_release() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
// This UP event shouldn't change the button state; a BUTTON_RELEASE before it should.
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Up,
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn button_press_for_already_pressed_button() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Back },
button_state: MotionButton::Back,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Back },
button_state: MotionButton::Back,
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn button_release_for_unpressed_button() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonRelease { action_button: MotionButton::Back },
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn correct_multiple_button_presses_without_down() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Back },
button_state: MotionButton::Back,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Forward },
button_state: MotionButton::Back | MotionButton::Forward,
..BASE_MOUSE_EVENT
})
.is_ok());
}
#[test]
fn correct_down_with_button_press() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
button_state: MotionButton::Primary | MotionButton::Secondary,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Secondary },
button_state: MotionButton::Primary | MotionButton::Secondary,
..BASE_MOUSE_EVENT
})
.is_ok());
// Also check that the MOVE afterwards is OK, as that's where errors would be raised if not
// enough BUTTON_PRESSes were sent.
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Move,
button_state: MotionButton::Primary | MotionButton::Secondary,
..BASE_MOUSE_EVENT
})
.is_ok());
}
#[test]
fn down_with_button_state_change_not_followed_by_button_press() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
// The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS.
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Move,
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn down_with_button_state_change_not_followed_by_enough_button_presses() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
button_state: MotionButton::Primary | MotionButton::Secondary,
..BASE_MOUSE_EVENT
})
.is_ok());
// The DOWN event itself is OK, but it needs to be immediately followed by two
// BUTTON_PRESSes, one for each button.
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Move,
button_state: MotionButton::Primary,
..BASE_MOUSE_EVENT
})
.is_err());
}
#[test]
fn down_missing_already_pressed_button() {
let mut verifier =
InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::ButtonPress { action_button: MotionButton::Back },
button_state: MotionButton::Back,
..BASE_MOUSE_EVENT
})
.is_ok());
assert!(verifier
.process_movement(NotifyMotionArgs {
action: MotionAction::Down,
button_state: MotionButton::empty(),
..BASE_MOUSE_EVENT
})
.is_err());
}
}