/* * 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>, hovering_pointer_ids_by_device: HashMap>, button_verifier_by_device: HashMap, } 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()); } }