blob: dd6fdf06d6c927cb88fdda2fb0e962b618e65359 [file] [log] [blame]
// Copyright 2024 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::{
FuchsiaTouchEventToLinuxTouchEventConverter, InputDeviceStatus, InputFile,
LinuxEventWithTraceId, parse_fidl_keyboard_event_to_linux_input_event,
parse_fidl_media_button_event, parse_fidl_touch_button_event,
};
use fidl::endpoints::{ClientEnd, RequestStream};
use fidl_fuchsia_ui_input::TouchDeviceInfo;
use fidl_fuchsia_ui_input3::{
KeyEventStatus, KeyboardListenerMarker, KeyboardListenerRequest, KeyboardListenerRequestStream,
KeyboardSynchronousProxy,
};
use fidl_fuchsia_ui_pointer::{
MouseEvent as FidlMouseEvent, MousePointerSample, TouchEvent as FidlTouchEvent,
TouchPointerSample, TouchResponse as FidlTouchResponse, TouchResponseType,
{self as fuipointer},
};
use futures::StreamExt as _;
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};
use futures::channel::oneshot::{self, Sender};
use futures::executor::block_on;
use starnix_core::power::{create_proxy_for_wake_events_counter, mark_proxy_message_handled};
use starnix_core::task::{Kernel, LockedAndTask};
use starnix_logging::{
log_warn, trace_duration, trace_duration_begin, trace_duration_end, trace_flow_step,
};
use starnix_sync::Mutex;
use starnix_types::time::timeval_from_time;
use starnix_uapi::uapi;
use starnix_uapi::vfs::FdEvents;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Weak};
use {fidl_fuchsia_ui_policy as fuipolicy, fidl_fuchsia_ui_views as fuiviews};
const INPUT_RELAY_ROLE_NAME: &str = "fuchsia.starnix.kthread.input_relay";
#[derive(Clone, Copy)]
pub enum EventProxyMode {
/// Don't proxy input events at all.
None,
/// Have the Starnix runner proxy events such that the container
/// will wake up if events are received while the container is
/// suspended.
WakeContainer,
}
pub type OpenedFiles = Arc<Mutex<Vec<Weak<InputFile>>>>;
pub enum InputDeviceType {
Touch(FuchsiaTouchEventToLinuxTouchEventConverter),
Keyboard,
Mouse,
}
impl std::fmt::Display for InputDeviceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InputDeviceType::Touch(_) => write!(f, "touch"),
InputDeviceType::Keyboard => write!(f, "keyboard"),
InputDeviceType::Mouse => write!(f, "mouse"),
}
}
}
pub struct DeviceState {
device_type: InputDeviceType,
open_files: OpenedFiles,
inspect_status: Option<Arc<InputDeviceStatus>>,
}
pub type DeviceId = u32;
pub const DEFAULT_TOUCH_DEVICE_ID: DeviceId = 0;
pub const DEFAULT_KEYBOARD_DEVICE_ID: DeviceId = 1;
pub const DEFAULT_MOUSE_DEVICE_ID: DeviceId = 2;
enum DeviceStateChange {
Add(DeviceId, DeviceState, Sender<()>),
Remove(DeviceId, Sender<()>),
}
pub fn new_input_relay() -> (InputEventsRelay, Arc<InputEventsRelayHandle>) {
let (sender, receiver) = unbounded();
(
InputEventsRelay { devices: HashMap::new(), receiver },
Arc::new(InputEventsRelayHandle { sender }),
)
}
pub struct InputEventsRelayHandle {
sender: UnboundedSender<DeviceStateChange>,
}
impl InputEventsRelayHandle {
pub fn add_touch_device(
self: &Arc<Self>,
device_id: DeviceId,
open_files: OpenedFiles,
inspect_status: Option<Arc<InputDeviceStatus>>,
) {
let (sender, receiver) = oneshot::channel();
let _ = self.sender.unbounded_send(DeviceStateChange::Add(
device_id,
DeviceState {
device_type: InputDeviceType::Touch(
FuchsiaTouchEventToLinuxTouchEventConverter::create(),
),
open_files,
inspect_status,
},
sender,
));
let _ = block_on(receiver);
}
pub fn add_keyboard_device(
&self,
device_id: DeviceId,
open_files: OpenedFiles,
inspect_status: Option<Arc<InputDeviceStatus>>,
) {
let (sender, receiver) = oneshot::channel();
let _ = self.sender.unbounded_send(DeviceStateChange::Add(
device_id,
DeviceState { device_type: InputDeviceType::Keyboard, open_files, inspect_status },
sender,
));
let _ = block_on(receiver);
}
pub fn remove_device(&self, device_id: DeviceId) {
let (sender, receiver) = oneshot::channel();
let _ = self.sender.unbounded_send(DeviceStateChange::Remove(device_id, sender));
let _ = block_on(receiver);
}
}
pub struct InputEventsRelay {
devices: HashMap<DeviceId, DeviceState>,
receiver: UnboundedReceiver<DeviceStateChange>,
}
impl InputEventsRelay {
// TODO(https://fxbug.dev/371602479): Use `fuchsia.ui.SupportedInputDevices` to create
// relays.
// start_relays will take over the ownership of InputEventsRelay.
pub fn start_relays(
mut self: Self,
kernel: &Kernel,
event_proxy_mode: EventProxyMode,
touch_source_client_end: ClientEnd<fuipointer::TouchSourceMarker>,
keyboard: KeyboardSynchronousProxy,
mouse_source_client_end: ClientEnd<fuipointer::MouseSourceMarker>,
view_ref: fuiviews::ViewRef,
registry_proxy: fuipolicy::DeviceListenerRegistrySynchronousProxy,
default_touch_device_opened_files: OpenedFiles,
default_keyboard_device_opened_files: OpenedFiles,
default_mouse_device_opened_files: OpenedFiles,
default_touch_device_inspect: Option<Arc<InputDeviceStatus>>,
default_keyboard_device_inspect: Option<Arc<InputDeviceStatus>>,
default_mouse_device_inspect: Option<Arc<InputDeviceStatus>>,
) {
kernel.kthreads.spawn_async_with_role(INPUT_RELAY_ROLE_NAME, async move |_: LockedAndTask<'_>| {
// For event types (touch, mouse, or button streams), the sequence for handling events
// MUST be:
//
// 1. create future
// 2. decrease counter
// 3. await future
//
// for allowing suspend - wake.
// touch
let (mut default_touch_device, touch_source_proxy, touch_message_counter) =
setup_touch_relay(
event_proxy_mode,
touch_source_client_end,
default_touch_device_opened_files,
default_touch_device_inspect,
);
let mut previous_touch_event_disposition: Vec<FidlTouchResponse> = vec![];
// Create the touch future to watch for the the next input events, but don't execute
// it...
let mut touch_future = touch_source_proxy.watch(&previous_touch_event_disposition);
// .. until the counter that we passed to the runner has been decremented. This prevents
// the container from suspending between calls to `watch`.
touch_message_counter.as_ref().map(mark_proxy_message_handled);
// mouse
let (mut default_mouse_device, mouse_source_proxy, mouse_message_counter) =
setup_mouse_relay(
event_proxy_mode,
mouse_source_client_end,
default_mouse_device_opened_files,
default_mouse_device_inspect,
);
// Create the mouse future to watch for the the next input events, but don't execute
// it...
let mut mouse_future = mouse_source_proxy.watch();
// .. until the message counter has been decremented. This prevents
// the container from suspending between calls to `watch`.
mouse_message_counter.as_ref().map(mark_proxy_message_handled);
// keyboard
let (mut default_keyboard_device, mut keyboard_event_stream) = setup_keyboard_relay(
keyboard,
view_ref,
default_keyboard_device_opened_files.clone(),
default_keyboard_device_inspect.clone(),
);
// button
let (mut default_button_device, mut media_button_event_stream, media_button_message_counter, mut touch_button_event_stream, touch_button_message_counter) =
setup_button_relay(
registry_proxy,
event_proxy_mode,
default_keyboard_device_opened_files,
default_keyboard_device_inspect,
);
media_button_message_counter.as_ref().map(mark_proxy_message_handled);
touch_button_message_counter.as_ref().map(mark_proxy_message_handled);
let mut power_was_pressed = false;
let mut function_was_pressed = false;
let mut palm_was_pressed = false;
loop {
futures::select! {
touch_future_res = touch_future => {
match touch_future_res {
Ok(touch_events) => {
previous_touch_event_disposition = self.process_touch_event(&mut default_touch_device, touch_events);
// Create the touch future to watch for the the next input events, but don't execute
// it...
touch_future = touch_source_proxy.watch(&previous_touch_event_disposition);
// .. until the counter that we passed to the runner has been decremented. This prevents
// the container from suspending between calls to `watch`.
touch_message_counter.as_ref().map(mark_proxy_message_handled);
}
Err(e) => {
log_warn!("error {:?} reading from TouchSourceProxy; input is stopped", e);
}
};
}
mouse_future_res = mouse_future => {
match mouse_future_res {
Ok(mouse_events) => {
self.process_mouse_event(&mut default_mouse_device, mouse_events);
// Create the mouse future to watch for the the next input events, but don't execute
// it...
mouse_future = mouse_source_proxy.watch();
// .. until the counter that we passed to the runner has been decremented. This prevents
// the container from suspending between calls to `watch`.
mouse_message_counter.as_ref().map(mark_proxy_message_handled);
}
Err(e) => {
log_warn!("error {:?} reading from MouseSourceProxy; input is stopped", e);
}
}
}
e = keyboard_event_stream.next() => {
match e {
Some(Ok(request)) => {
self.process_keyboard(&mut default_keyboard_device, request);
}
_ => {}
}
}
e = media_button_event_stream.next() => {
match e {
Some(Ok(event)) => {
(power_was_pressed, function_was_pressed) = self.process_media_button_event(&mut default_button_device, event, power_was_pressed, function_was_pressed);
}
_ => {}
}
media_button_message_counter.as_ref().map(mark_proxy_message_handled);
}
e = touch_button_event_stream.next() => {
match e {
Some(Ok(event)) => {
palm_was_pressed = self.process_touch_button_event(&mut default_touch_device, event, palm_was_pressed);
}
_ => {}
}
touch_button_message_counter.as_ref().map(mark_proxy_message_handled);
}
e = self.receiver.next() => {
match e {
Some(event) => {
match event {
DeviceStateChange::Add(id, device_state, sender) => {
self.devices.insert(id, device_state);
let _ = sender.send(());
}
DeviceStateChange::Remove(id, sender) => {
self.devices.remove(&id);
let _ = sender.send(());
}
}
}
_ => {}
}
}
complete => break,
}
}
});
}
fn process_touch_event(
self: &mut Self,
default_touch_device: &mut DeviceState,
touch_events: Vec<FidlTouchEvent>,
) -> Vec<FidlTouchResponse> {
trace_duration!(c"input", c"starnix_process_touch_event");
for e in &touch_events {
match e.trace_flow_id {
Some(trace_flow_id) => {
trace_flow_step!(c"input", c"dispatch_event_to_client", trace_flow_id.into());
}
None => {
log_warn!("touch event has not tracing id");
}
}
}
let num_received_events: u64 = touch_events.len().try_into().unwrap();
let previous_event_disposition =
touch_events.iter().map(make_response_for_fidl_event).collect();
let mut num_ignored_events: u64 = 0;
// 1 vec may contains events from different device.
let (events_by_device, ignored_events) = group_touch_events_by_device_id(touch_events);
num_ignored_events += ignored_events;
for (device_id, events) in events_by_device {
trace_duration_begin!(c"input", c"starnix_process_per_device_touch_event");
let dev = self.devices.get_mut(&device_id).unwrap_or(default_touch_device);
let mut num_converted_events: u64 = 0;
let mut num_unexpected_events: u64 = 0;
let mut new_events: VecDeque<uapi::input_event> = VecDeque::new();
let last_event_time_ns: i64;
if let InputDeviceType::Touch(ref mut converter) = dev.device_type {
let mut batch = converter.handle(events);
new_events.append(&mut batch.events);
num_converted_events += batch.count_converted_fidl_events;
num_ignored_events += batch.count_ignored_fidl_events;
num_unexpected_events += batch.count_unexpected_fidl_events;
last_event_time_ns = batch.last_event_time_ns;
} else {
trace_duration_end!(c"input", c"starnix_process_per_device_touch_event");
log_warn!(
"Non touch device received touch events: device_id = {}, device_type = {}",
device_id,
dev.device_type
);
continue;
}
if let Some(dev_inspect_status) = &dev.inspect_status {
dev_inspect_status.count_total_received_events(num_received_events);
dev_inspect_status.count_total_ignored_events(num_ignored_events);
dev_inspect_status.count_total_unexpected_events(num_unexpected_events);
dev_inspect_status.count_total_converted_events(num_converted_events);
dev_inspect_status.count_total_generated_events(
new_events.len().try_into().unwrap(),
last_event_time_ns,
);
} else {
log_warn!(
"unable to record inspect for device_id: {}, device_type: {}",
device_id,
dev.device_type
);
}
trace_duration_end!(c"input", c"starnix_process_per_device_touch_event");
dev.open_files.lock().retain(|f| {
let Some(file) = f.upgrade() else {
log_warn!("Dropping input file for touch that failed to upgrade");
return false;
};
match &file.inspect_status {
Some(file_inspect_status) => {
file_inspect_status.count_received_events(num_received_events);
file_inspect_status.count_ignored_events(num_ignored_events);
file_inspect_status.count_unexpected_events(num_unexpected_events);
file_inspect_status.count_converted_events(num_converted_events);
}
None => {
log_warn!("unable to record inspect within the input file")
}
}
if !new_events.is_empty() {
// TODO(https://fxbug.dev/42075438): Reading from an `InputFile` should
// not provide access to events that occurred before the file was
// opened.
if let Some(file_inspect_status) = &file.inspect_status {
file_inspect_status.count_generated_events(
new_events.len().try_into().unwrap(),
last_event_time_ns,
);
}
let mut inner = file.inner.lock();
inner
.events
.extend(new_events.clone().into_iter().map(LinuxEventWithTraceId::new));
inner.waiters.notify_fd_events(FdEvents::POLLIN);
}
true
});
}
previous_event_disposition
}
fn process_keyboard(
self: &mut Self,
default_keyboard_device: &mut DeviceState,
request: KeyboardListenerRequest,
) {
match request {
KeyboardListenerRequest::OnKeyEvent { event, responder } => {
trace_duration!(c"input", c"starnix_process_keyboard_event");
let new_events = parse_fidl_keyboard_event_to_linux_input_event(&event);
let dev = match event.device_id {
Some(device_id) => {
self.devices.get_mut(&device_id).unwrap_or(default_keyboard_device)
}
None => default_keyboard_device,
};
dev.open_files.lock().retain(|f| {
let Some(file) = f.upgrade() else {
log_warn!("Dropping input file for keyboard that failed to upgrade");
return false;
};
let mut inner = file.inner.lock();
if !new_events.is_empty() {
inner
.events
.extend(new_events.clone().into_iter().map(LinuxEventWithTraceId::new));
inner.waiters.notify_fd_events(FdEvents::POLLIN);
}
true
});
responder.send(KeyEventStatus::Handled).expect("");
}
}
}
fn process_media_button_event(
&mut self,
default_button_device: &mut DeviceState,
button_event: fuipolicy::MediaButtonsListenerRequest,
power_was_pressed: bool,
function_was_pressed: bool,
) -> (bool, bool) {
let mut power_was_pressed_after = false;
let mut function_was_pressed_after = false;
match button_event {
fuipolicy::MediaButtonsListenerRequest::OnEvent { event, responder } => {
trace_duration!(c"input", c"starnix_process_media_button_event");
let batch =
parse_fidl_media_button_event(&event, power_was_pressed, function_was_pressed);
power_was_pressed_after = batch.power_is_pressed;
function_was_pressed_after = batch.function_is_pressed;
let (converted_events, ignored_events, generated_events) = match batch.events.len()
{
0 => (0u64, 1u64, 0u64),
len => {
if len % 2 == 1 {
log_warn!(
"unexpectedly received {} events: there should always be an even number of non-empty events.",
len
);
}
(1u64, 0u64, len as u64)
}
};
let dev = match event.device_id {
Some(device_id) => {
self.devices.get_mut(&device_id).unwrap_or(default_button_device)
}
None => default_button_device,
};
if let Some(dev_inspect_status) = &dev.inspect_status {
dev_inspect_status.count_total_received_events(1);
dev_inspect_status.count_total_ignored_events(ignored_events);
dev_inspect_status.count_total_converted_events(converted_events);
dev_inspect_status.count_total_generated_events(
generated_events,
batch.event_time.into_nanos().try_into().unwrap(),
);
} else {
log_warn!("unable to record inspect for button device");
}
dev.open_files.lock().retain(|f| {
let Some(file) = f.upgrade() else {
log_warn!("Dropping input file for buttons that failed to upgrade");
return false;
};
match &file.inspect_status {
Some(file_inspect_status) => {
file_inspect_status.count_received_events(1);
file_inspect_status.count_ignored_events(ignored_events);
file_inspect_status.count_converted_events(converted_events);
}
None => {
log_warn!("unable to record inspect within the input file")
}
}
if !batch.events.is_empty() {
if let Some(file_inspect_status) = &file.inspect_status {
file_inspect_status.count_generated_events(
generated_events,
batch.event_time.into_nanos().try_into().unwrap(),
);
}
let mut inner = file.inner.lock();
inner.events.extend(
batch.events.clone().into_iter().map(LinuxEventWithTraceId::new),
);
inner.waiters.notify_fd_events(FdEvents::POLLIN);
}
true
});
responder.send().expect("media buttons responder failed to respond");
}
_ => { /* Ignore deprecated OnMediaButtonsEvent */ }
}
(power_was_pressed_after, function_was_pressed_after)
}
fn process_touch_button_event(
&mut self,
default_touch_device: &mut DeviceState,
button_event: fuipolicy::TouchButtonsListenerRequest,
palm_was_pressed: bool,
) -> bool {
trace_duration!(c"input", c"starnix_process_touch_button_event");
let fuipolicy::TouchButtonsListenerRequest::OnEvent { event, responder } = button_event;
let batch = parse_fidl_touch_button_event(&event, palm_was_pressed);
let (converted_events, ignored_events, generated_events) = match batch.events.len() {
0 => (0u64, 1u64, 0u64),
len => {
if len % 2 == 1 {
log_warn!(
"unexpectedly received {} events: there should always be an even number of non-empty events.",
len
);
}
(1u64, 0u64, len as u64)
}
};
let dev = match event.device_info {
Some(TouchDeviceInfo { id: Some(device_id), .. }) => {
self.devices.get_mut(&device_id).unwrap_or(default_touch_device)
}
_ => default_touch_device,
};
if let Some(dev_inspect_status) = &dev.inspect_status {
dev_inspect_status.count_total_received_events(1);
dev_inspect_status.count_total_ignored_events(ignored_events);
dev_inspect_status.count_total_converted_events(converted_events);
dev_inspect_status.count_total_generated_events(
generated_events,
batch.event_time.into_nanos().try_into().unwrap(),
);
} else {
log_warn!("unable to record inspect for touch device");
}
dev.open_files.lock().retain(|f| {
let Some(file) = f.upgrade() else {
log_warn!("Dropping input file for touch that failed to upgrade");
return false;
};
match &file.inspect_status {
Some(file_inspect_status) => {
file_inspect_status.count_received_events(1);
file_inspect_status.count_ignored_events(ignored_events);
file_inspect_status.count_converted_events(converted_events);
}
None => {
log_warn!("unable to record inspect within the input file")
}
}
if !batch.events.is_empty() {
if let Some(file_inspect_status) = &file.inspect_status {
file_inspect_status.count_generated_events(
generated_events,
batch.event_time.into_nanos().try_into().unwrap(),
);
}
let mut inner = file.inner.lock();
inner
.events
.extend(batch.events.clone().into_iter().map(LinuxEventWithTraceId::new));
inner.waiters.notify_fd_events(FdEvents::POLLIN);
}
true
});
responder.send().expect("touch buttons responder failed to respond");
batch.palm_is_pressed
}
fn process_mouse_event(
self: &Self,
default_mouse_device: &mut DeviceState,
mouse_events: Vec<FidlMouseEvent>,
) {
let num_received_events: u64 = mouse_events.len().try_into().unwrap();
let mut num_ignored_events: u64 = 0;
let mut num_converted_events: u64 = 0;
let mut num_unexpected_events: u64 = 0;
let mut new_events: VecDeque<uapi::input_event> = VecDeque::new();
let mut last_event_time_ns = zx::MonotonicInstant::get();
for event in mouse_events {
match event {
FidlMouseEvent {
timestamp: Some(time),
pointer_sample: Some(MousePointerSample { scroll_v: Some(ticks), .. }),
..
} => {
last_event_time_ns = zx::MonotonicInstant::from_nanos(time);
// Ensure this is a mouse wheel event with delta, otherwise ignore.
if ticks != 0 {
new_events.push_back(uapi::input_event {
time: timeval_from_time(last_event_time_ns),
type_: uapi::EV_REL as u16,
code: uapi::REL_WHEEL as u16,
value: ticks as i32,
});
num_converted_events += 1;
} else {
num_ignored_events += 1;
}
}
_ => {
num_unexpected_events += 1;
}
}
}
if new_events.len() > 0 {
new_events.push_back(uapi::input_event {
// See https://www.kernel.org/doc/Documentation/input/event-codes.rst.
time: timeval_from_time(last_event_time_ns),
type_: uapi::EV_SYN as u16,
code: uapi::SYN_REPORT as u16,
value: 0,
});
}
if let Some(dev_inspect_status) = &default_mouse_device.inspect_status {
dev_inspect_status.count_total_received_events(num_received_events);
dev_inspect_status.count_total_ignored_events(num_ignored_events);
dev_inspect_status.count_total_unexpected_events(num_unexpected_events);
dev_inspect_status.count_total_converted_events(num_converted_events);
if !new_events.is_empty() {
dev_inspect_status.count_total_generated_events(
new_events.len().try_into().unwrap(),
last_event_time_ns.into_nanos().try_into().unwrap(),
);
}
} else {
log_warn!("unable to record inspect for mouse device");
}
default_mouse_device.open_files.lock().retain(|f| {
let Some(file) = f.upgrade() else {
log_warn!("Dropping input file for mouse that failed to upgrade");
return false;
};
match &file.inspect_status {
Some(file_inspect_status) => {
file_inspect_status.count_received_events(num_received_events);
file_inspect_status.count_ignored_events(num_ignored_events);
file_inspect_status.count_unexpected_events(num_unexpected_events);
file_inspect_status.count_converted_events(num_converted_events);
}
None => {
log_warn!("unable to record inspect within the input file")
}
}
if !new_events.is_empty() {
if let Some(file_inspect_status) = &file.inspect_status {
file_inspect_status.count_generated_events(
new_events.len().try_into().unwrap(),
last_event_time_ns.into_nanos().try_into().unwrap(),
);
}
let mut inner = file.inner.lock();
inner.events.extend(new_events.clone().into_iter().map(LinuxEventWithTraceId::new));
inner.waiters.notify_fd_events(FdEvents::POLLIN);
}
true
});
}
}
fn setup_touch_relay(
event_proxy_mode: EventProxyMode,
touch_source_client_end: ClientEnd<fuipointer::TouchSourceMarker>,
default_touch_device_opened_files: OpenedFiles,
device_inspect_status: Option<Arc<InputDeviceStatus>>,
) -> (DeviceState, fuipointer::TouchSourceProxy, Option<zx::Counter>) {
let default_touch_device = DeviceState {
device_type: InputDeviceType::Touch(FuchsiaTouchEventToLinuxTouchEventConverter::create()),
open_files: default_touch_device_opened_files,
inspect_status: device_inspect_status,
};
let (touch_source_proxy, message_counter) = match event_proxy_mode {
EventProxyMode::WakeContainer => {
// Proxy the touch events through the Starnix runner. This allows touch events to
// wake the container when it is suspended.
let (touch_source_channel, message_counter) = create_proxy_for_wake_events_counter(
touch_source_client_end.into_channel(),
"touch".to_string(),
);
(
fuipointer::TouchSourceProxy::new(fidl::AsyncChannel::from_channel(
touch_source_channel,
)),
Some(message_counter),
)
}
EventProxyMode::None => (touch_source_client_end.into_proxy(), None),
};
(default_touch_device, touch_source_proxy, message_counter)
}
fn setup_keyboard_relay(
keyboard: KeyboardSynchronousProxy,
view_ref: fuiviews::ViewRef,
default_keyboard_device_opened_files: OpenedFiles,
device_inspect_status: Option<Arc<InputDeviceStatus>>,
) -> (DeviceState, KeyboardListenerRequestStream) {
let default_keyboard_device = DeviceState {
device_type: InputDeviceType::Keyboard,
open_files: default_keyboard_device_opened_files,
inspect_status: device_inspect_status,
};
let (keyboard_listener, event_stream) =
fidl::endpoints::create_request_stream::<KeyboardListenerMarker>();
if keyboard.add_listener(view_ref, keyboard_listener, zx::MonotonicInstant::INFINITE).is_err() {
log_warn!("Could not register keyboard listener");
}
(default_keyboard_device, event_stream)
}
fn setup_button_relay(
registry_proxy: fuipolicy::DeviceListenerRegistrySynchronousProxy,
event_proxy_mode: EventProxyMode,
default_keyboard_device_opened_files: OpenedFiles,
device_inspect_status: Option<Arc<InputDeviceStatus>>,
) -> (
DeviceState,
fuipolicy::MediaButtonsListenerRequestStream,
Option<zx::Counter>,
fuipolicy::TouchButtonsListenerRequestStream,
Option<zx::Counter>,
) {
let default_keyboard_device = DeviceState {
device_type: InputDeviceType::Keyboard,
open_files: default_keyboard_device_opened_files,
inspect_status: device_inspect_status,
};
let (remote_media_button_client, remote_media_button_server) =
fidl::endpoints::create_endpoints::<fuipolicy::MediaButtonsListenerMarker>();
if let Err(e) =
registry_proxy.register_listener(remote_media_button_client, zx::MonotonicInstant::INFINITE)
{
log_warn!("Failed to register media buttons listener: {:?}", e);
}
let (remote_touch_button_client, remote_touch_button_server) =
fidl::endpoints::create_endpoints::<fuipolicy::TouchButtonsListenerMarker>();
if let Err(e) = registry_proxy
.register_touch_buttons_listener(remote_touch_button_client, zx::MonotonicInstant::INFINITE)
{
log_warn!("Failed to register touch buttons listener: {:?}", e);
}
let (
local_media_buttons_listener_stream,
media_buttons_message_counter,
local_touch_buttons_listener_stream,
touch_buttons_message_counter,
) = match event_proxy_mode {
EventProxyMode::WakeContainer => {
let (local_media_buttons_channel, media_buttons_message_counter) =
create_proxy_for_wake_events_counter(
remote_media_button_server.into_channel(),
"media buttons".to_string(),
);
let local_media_buttons_listener_stream =
fuipolicy::MediaButtonsListenerRequestStream::from_channel(
fidl::AsyncChannel::from_channel(local_media_buttons_channel),
);
let (local_touch_buttons_channel, touch_buttons_message_counter) =
create_proxy_for_wake_events_counter(
remote_touch_button_server.into_channel(),
"touch buttons".to_string(),
);
let local_touch_buttons_listener_stream =
fuipolicy::TouchButtonsListenerRequestStream::from_channel(
fidl::AsyncChannel::from_channel(local_touch_buttons_channel),
);
(
local_media_buttons_listener_stream,
Some(media_buttons_message_counter),
local_touch_buttons_listener_stream,
Some(touch_buttons_message_counter),
)
}
EventProxyMode::None => (
remote_media_button_server.into_stream(),
None,
remote_touch_button_server.into_stream(),
None,
),
};
(
default_keyboard_device,
local_media_buttons_listener_stream,
media_buttons_message_counter,
local_touch_buttons_listener_stream,
touch_buttons_message_counter,
)
}
fn setup_mouse_relay(
event_proxy_mode: EventProxyMode,
mouse_source_client_end: ClientEnd<fuipointer::MouseSourceMarker>,
default_mouse_device_opened_files: OpenedFiles,
device_inspect_status: Option<Arc<InputDeviceStatus>>,
) -> (DeviceState, fuipointer::MouseSourceProxy, Option<zx::Counter>) {
let default_mouse_device = DeviceState {
device_type: InputDeviceType::Mouse,
open_files: default_mouse_device_opened_files,
inspect_status: device_inspect_status,
};
let (mouse_source_proxy, message_counter) = match event_proxy_mode {
EventProxyMode::WakeContainer => {
// Proxy the mouse events through the Starnix runner. This allows mouse events to
// wake the container when it is suspended.
let (mouse_source_channel, resume_event) = create_proxy_for_wake_events_counter(
mouse_source_client_end.into_channel(),
"mouse".to_string(),
);
(
fuipointer::MouseSourceProxy::new(fidl::AsyncChannel::from_channel(
mouse_source_channel,
)),
Some(resume_event),
)
}
EventProxyMode::None => (mouse_source_client_end.into_proxy(), None),
};
(default_mouse_device, mouse_source_proxy, message_counter)
}
/// Returns a FIDL response for `fidl_event`.
fn make_response_for_fidl_event(fidl_event: &FidlTouchEvent) -> FidlTouchResponse {
match fidl_event {
FidlTouchEvent { pointer_sample: Some(_), .. } => FidlTouchResponse {
response_type: Some(TouchResponseType::Yes), // Event consumed by Starnix.
trace_flow_id: fidl_event.trace_flow_id,
..Default::default()
},
_ => FidlTouchResponse::default(),
}
}
fn group_touch_events_by_device_id(
events: Vec<FidlTouchEvent>,
) -> (HashMap<DeviceId, Vec<FidlTouchEvent>>, u64) {
let mut events_by_device: HashMap<u32, Vec<FidlTouchEvent>> = HashMap::new();
let mut ignored_events: u64 = 0;
for e in events {
match e {
FidlTouchEvent {
pointer_sample: Some(TouchPointerSample { interaction: Some(id), .. }),
..
} => {
events_by_device.entry(id.device_id).or_default().push(e);
}
_ => {
ignored_events += 1;
}
}
}
(events_by_device, ignored_events)
}
#[cfg(test)]
pub async fn start_input_relays_for_test(
locked: &mut starnix_sync::Locked<starnix_sync::Unlocked>,
current_task: &starnix_core::task::CurrentTask,
) -> (
Arc<InputEventsRelayHandle>,
crate::InputDevice,
crate::InputDevice,
crate::InputDevice,
starnix_core::vfs::FileHandle,
starnix_core::vfs::FileHandle,
starnix_core::vfs::FileHandle,
fuipointer::TouchSourceRequestStream,
fuipointer::MouseSourceRequestStream,
fidl_fuchsia_ui_input3::KeyboardListenerProxy,
fuipolicy::MediaButtonsListenerProxy,
fuipolicy::TouchButtonsListenerProxy,
) {
let inspector = fuchsia_inspect::Inspector::default();
let touch_device = crate::InputDevice::new_touch(700, 1200, inspector.root());
let touch_file =
touch_device.open_test(locked, current_task).expect("Failed to create input file");
let keyboard_device = crate::InputDevice::new_keyboard(inspector.root());
let keyboard_file =
keyboard_device.open_test(locked, current_task).expect("Failed to create input file");
let mouse_device = crate::InputDevice::new_mouse(inspector.root());
let mouse_file =
mouse_device.open_test(locked, current_task).expect("Failed to create input file");
let (touch_source_client_end, touch_source_stream) =
fidl::endpoints::create_request_stream::<fuipointer::TouchSourceMarker>();
let (mouse_source_client_end, mouse_stream) =
fidl::endpoints::create_request_stream::<fuipointer::MouseSourceMarker>();
let (keyboard_proxy, mut keyboard_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fidl_fuchsia_ui_input3::KeyboardMarker>();
let view_ref_pair = fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair");
let (device_registry_proxy, mut device_listener_stream) =
fidl::endpoints::create_sync_proxy_and_stream::<fuipolicy::DeviceListenerRegistryMarker>();
let (relay, relay_handle) = new_input_relay();
relay.start_relays(
&current_task.kernel(),
EventProxyMode::None,
touch_source_client_end,
keyboard_proxy,
mouse_source_client_end,
view_ref_pair.view_ref,
device_registry_proxy,
touch_device.open_files.clone(),
keyboard_device.open_files.clone(),
mouse_device.open_files.clone(),
Some(touch_device.inspect_status.clone()),
Some(keyboard_device.inspect_status.clone()),
Some(mouse_device.inspect_status.clone()),
);
let keyboard_listener = match keyboard_stream.next().await {
Some(Ok(fidl_fuchsia_ui_input3::KeyboardRequest::AddListener {
view_ref: _,
listener,
responder,
})) => {
let _ = responder.send();
listener.into_proxy()
}
_ => {
panic!("Failed to get event");
}
};
let media_buttons_listener = match device_listener_stream.next().await {
Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterListener {
listener,
responder,
})) => {
let _ = responder.send();
listener.into_proxy()
}
_ => {
panic!("Failed to get event");
}
};
let touch_buttons_listener = match device_listener_stream.next().await {
Some(Ok(fuipolicy::DeviceListenerRegistryRequest::RegisterTouchButtonsListener {
listener,
responder,
})) => {
let _ = responder.send();
listener.into_proxy()
}
_ => {
panic!("Failed to get event");
}
};
(
relay_handle,
touch_device,
keyboard_device,
mouse_device,
touch_file,
keyboard_file,
mouse_file,
touch_source_stream,
mouse_stream,
keyboard_listener,
media_buttons_listener,
touch_buttons_listener,
)
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::anyhow;
use fidl_fuchsia_ui_input::{
MediaButtonsEvent, TouchButton, TouchButtonsEvent, TouchDeviceInfo,
};
use fidl_fuchsia_ui_input3 as fuiinput;
use fuipointer::{
EventPhase, MouseEvent, TouchEvent, TouchInteractionId, TouchPointerSample, TouchResponse,
TouchSourceRequest, TouchSourceRequestStream,
};
use starnix_core::task::CurrentTask;
use starnix_core::testing::create_kernel_task_and_unlocked;
use starnix_core::vfs::{FileHandle, FileObject, VecOutputBuffer};
use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
use starnix_types::time::timeval_from_time;
use starnix_uapi::errors::{EAGAIN, Errno};
use starnix_uapi::input_id;
use starnix_uapi::open_flags::OpenFlags;
use zerocopy::FromBytes as _;
const INPUT_EVENT_SIZE: usize = std::mem::size_of::<uapi::input_event>();
// Waits for a `Watch()` request to arrive on `request_stream`, and responds with
// `touch_event`. Returns the arguments to the `Watch()` call.
async fn answer_next_touch_watch_request(
request_stream: &mut TouchSourceRequestStream,
touch_events: Vec<TouchEvent>,
) -> Vec<TouchResponse> {
match request_stream.next().await {
Some(Ok(TouchSourceRequest::Watch { responses, responder })) => {
responder.send(&touch_events).expect("failure sending Watch reply");
responses
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
}
// Waits for a `Watch()` request to arrive on `request_stream`, and responds with
// `mouse_events`.
async fn answer_next_mouse_watch_request(
request_stream: &mut fuipointer::MouseSourceRequestStream,
mouse_events: Vec<MouseEvent>,
) {
match request_stream.next().await {
Some(Ok(fuipointer::MouseSourceRequest::Watch { responder })) => {
responder.send(&mouse_events).expect("failure sending Watch reply");
}
unexpected_request => panic!("unexpected request {:?}", unexpected_request),
}
}
fn make_empty_touch_event(device_id: u32) -> TouchEvent {
TouchEvent {
pointer_sample: Some(TouchPointerSample {
interaction: Some(TouchInteractionId {
pointer_id: 0,
device_id,
interaction_id: 0,
}),
..Default::default()
}),
..Default::default()
}
}
fn make_touch_event_with_phase_device_id(
phase: EventPhase,
pointer_id: u32,
device_id: u32,
) -> TouchEvent {
make_touch_event_with_phase_device_id_position(phase, pointer_id, device_id, 0.0, 0.0)
}
fn make_touch_event_with_phase_device_id_position(
phase: EventPhase,
pointer_id: u32,
device_id: u32,
x: f32,
y: f32,
) -> TouchEvent {
TouchEvent {
timestamp: Some(0),
pointer_sample: Some(TouchPointerSample {
position_in_viewport: Some([x, y]),
phase: Some(phase),
interaction: Some(TouchInteractionId { pointer_id, device_id, interaction_id: 0 }),
..Default::default()
}),
..Default::default()
}
}
fn make_mouse_wheel_event(scroll_v_ticks: i64, device_id: u32) -> MouseEvent {
MouseEvent {
timestamp: Some(0),
pointer_sample: Some(MousePointerSample {
device_id: Some(device_id),
scroll_v: Some(scroll_v_ticks),
..Default::default()
}),
..Default::default()
}
}
fn read_uapi_events<L>(
locked: &mut Locked<L>,
file: &FileHandle,
current_task: &CurrentTask,
) -> Vec<uapi::input_event>
where
L: LockEqualOrBefore<FileOpsCore>,
{
std::iter::from_fn(|| {
let locked = locked.cast_locked::<FileOpsCore>();
let mut event_bytes = VecOutputBuffer::new(INPUT_EVENT_SIZE);
match file.read(locked, current_task, &mut event_bytes) {
Ok(INPUT_EVENT_SIZE) => Some(
uapi::input_event::read_from_bytes(Vec::from(event_bytes).as_slice())
.map_err(|_| anyhow!("failed to read input_event from buffer")),
),
Ok(other_size) => {
Some(Err(anyhow!("got {} bytes (expected {})", other_size, INPUT_EVENT_SIZE)))
}
Err(Errno { code: EAGAIN, .. }) => None,
Err(other_error) => Some(Err(anyhow!("read failed: {:?}", other_error))),
}
})
.enumerate()
.map(|(i, read_res)| match read_res {
Ok(event) => event,
Err(e) => panic!("unexpected result {:?} on iteration {}", e, i),
})
.collect()
}
fn create_test_touch_device(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
input_relay: Arc<InputEventsRelayHandle>,
device_id: u32,
) -> FileHandle {
let open_files: OpenedFiles = Arc::new(Mutex::new(vec![]));
input_relay.add_touch_device(device_id, open_files.clone(), None);
let device_file = Arc::new(InputFile::new_touch(
input_id { bustype: 0, vendor: 0, product: 0, version: 0 },
1000,
1000,
None,
));
open_files.lock().push(Arc::downgrade(&device_file));
FileObject::new(
&current_task,
Box::new(device_file),
current_task
.lookup_path_from_root(locked, ".".into())
.expect("failed to get namespace node for root"),
OpenFlags::empty(),
)
.expect("FileObject::new failed")
}
fn make_uapi_input_event(ty: u32, code: u32, value: i32) -> uapi::input_event {
uapi::input_event {
time: timeval_from_time(zx::MonotonicInstant::from_nanos(0)),
type_: ty as u16,
code: code as u16,
value,
}
}
#[::fuchsia::test]
async fn route_touch_event_by_device_id() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
input_relay,
_touch_device,
_keyboard_device,
_mouse_device,
input_file,
_keyboard_file,
_mouse_file,
mut touch_source_stream,
_mouse_source_stream,
_keyboard_listener,
_media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
const DEVICE_ID: u32 = 10;
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase_device_id(EventPhase::Add, 1, DEVICE_ID)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
// `uapi::input_event`s.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_empty_touch_event(DEVICE_ID)],
)
.await;
// Consume all of the `uapi::input_event`s that are available.
let events = read_uapi_events(locked, &input_file, &current_task);
// Default device receive events because no matched device.
assert_ne!(events.len(), 0);
// add a device, mock uinput.
let device_id_10_file =
create_test_touch_device(locked, &current_task, input_relay.clone(), DEVICE_ID);
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase_device_id(EventPhase::Add, 1, DEVICE_ID)],
)
.await;
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_empty_touch_event(DEVICE_ID)],
)
.await;
let events = read_uapi_events(locked, &input_file, &current_task);
// Default device should not receive events because they matched device id 10.
assert_eq!(events.len(), 0);
let events = read_uapi_events(locked, &device_id_10_file, &current_task);
// file of device id 10 should receive events.
assert_ne!(events.len(), 0);
}
#[::fuchsia::test]
async fn route_touch_event_by_device_id_multi_device_events_in_one_sequence() {
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
input_relay,
_touch_device,
_keyboard_device,
_mouse_device,
_input_file,
_keyboard_file,
_mouse_file,
mut touch_source_stream,
_mouse_source_stream,
_keyboard_listener,
_media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
const DEVICE_ID_10: u32 = 10;
const DEVICE_ID_11: u32 = 11;
let device_id_10_file =
create_test_touch_device(locked, &current_task, input_relay.clone(), DEVICE_ID_10);
let device_id_11_file =
create_test_touch_device(locked, &current_task, input_relay.clone(), DEVICE_ID_11);
// 2 pointer down on different touch device.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![
make_touch_event_with_phase_device_id_position(
EventPhase::Add,
1,
DEVICE_ID_10,
10.0,
20.0,
),
make_touch_event_with_phase_device_id_position(
EventPhase::Add,
2,
DEVICE_ID_11,
30.0,
40.0,
),
],
)
.await;
answer_next_touch_watch_request(&mut touch_source_stream, vec![]).await;
let events_10 = read_uapi_events(locked, &device_id_10_file, &current_task);
let events_11 = read_uapi_events(locked, &device_id_11_file, &current_task);
assert_eq!(events_10.len(), events_11.len());
assert_eq!(
events_10,
vec![
make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
assert_eq!(
events_11,
vec![
make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 2),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 30),
make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 40),
make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
]
);
}
#[::fuchsia::test]
async fn route_key_event_by_device_id() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
input_relay,
_touch_device,
_keyboard_device,
_mouse_device,
_touch_file,
keyboard_file,
_mouse_file,
_touch_source_stream,
_mouse_source_stream,
keyboard_listener,
_media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
const DEVICE_ID: u32 = 10;
let key_event = fuiinput::KeyEvent {
timestamp: Some(0),
type_: Some(fuiinput::KeyEventType::Pressed),
key: Some(fidl_fuchsia_input::Key::A),
device_id: Some(DEVICE_ID),
..Default::default()
};
let _ = keyboard_listener.on_key_event(&key_event).await;
let events = read_uapi_events(locked, &keyboard_file, &current_task);
// Default device should receive events because no device device id is 10.
assert_ne!(events.len(), 0);
// add a device, mock uinput.
let open_files: OpenedFiles = Arc::new(Mutex::new(vec![]));
input_relay.add_keyboard_device(DEVICE_ID, open_files.clone(), None);
let device_id_10_file = Arc::new(InputFile::new_keyboard(
input_id { bustype: 0, vendor: 0, product: 0, version: 0 },
None,
));
open_files.lock().push(Arc::downgrade(&device_id_10_file));
let device_id_10_file_object = FileObject::new(
&current_task,
Box::new(device_id_10_file),
current_task
.lookup_path_from_root(locked, ".".into())
.expect("failed to get namespace node for root"),
OpenFlags::empty(),
)
.expect("FileObject::new failed");
let _ = keyboard_listener.on_key_event(&key_event).await;
let events = read_uapi_events(locked, &keyboard_file, &current_task);
// Default device should not receive events because they matched device id 10.
assert_eq!(events.len(), 0);
let events = read_uapi_events(locked, &device_id_10_file_object, &current_task);
// file of device id 10 should receive events.
assert_ne!(events.len(), 0);
std::mem::drop(keyboard_listener); // Close Zircon channel.
}
#[::fuchsia::test]
async fn route_media_button_event_by_device_id() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
input_relay,
_touch_device,
_keyboard_device,
_mouse_device,
_touch_file,
keyboard_file,
_mouse_file,
_touch_source_stream,
_mouse_source_stream,
_keyboard_listener,
media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
const DEVICE_ID: u32 = 10;
let power_pressed_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(false),
device_id: Some(DEVICE_ID),
..Default::default()
};
let _ = media_buttons_listener.on_event(&power_pressed_event).await;
let events = read_uapi_events(locked, &keyboard_file, &current_task);
// Default device should receive events because no device device id is 10.
assert_ne!(events.len(), 0);
// add a device, mock uinput.
let open_files: OpenedFiles = Arc::new(Mutex::new(vec![]));
input_relay.add_keyboard_device(DEVICE_ID, open_files.clone(), None);
let device_id_10_file = Arc::new(InputFile::new_keyboard(
input_id { bustype: 0, vendor: 0, product: 0, version: 0 },
None,
));
open_files.lock().push(Arc::downgrade(&device_id_10_file));
let device_id_10_file_object = FileObject::new(
&current_task,
Box::new(device_id_10_file),
current_task
.lookup_path_from_root(locked, ".".into())
.expect("failed to get namespace node for root"),
OpenFlags::empty(),
)
.expect("FileObject::new failed");
let power_released_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(false),
function: Some(false),
device_id: Some(DEVICE_ID),
..Default::default()
};
let _ = media_buttons_listener.on_event(&power_released_event).await;
let events = read_uapi_events(locked, &keyboard_file, &current_task);
// Default device should not receive events because they matched device id 10.
assert_eq!(events.len(), 0);
let events = read_uapi_events(locked, &device_id_10_file_object, &current_task);
// file of device id 10 should receive events.
assert_ne!(events.len(), 0);
std::mem::drop(media_buttons_listener); // Close Zircon channel.
}
#[::fuchsia::test]
async fn route_touch_button_event_by_device_id() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
input_relay,
_touch_device,
_keyboard_device,
_mouse_device,
touch_file,
_keyboard_file,
_mouse_file,
_touch_source_stream,
_mouse_source_stream,
_keyboard_listener,
_media_buttons_listener,
touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
const DEVICE_ID: u32 = 10;
let palm_pressed_event: TouchButtonsEvent = TouchButtonsEvent {
pressed_buttons: Some(vec![TouchButton::Palm]),
device_info: Some(TouchDeviceInfo { id: Some(DEVICE_ID), ..Default::default() }),
..Default::default()
};
let _ = touch_buttons_listener.on_event(&palm_pressed_event).await;
let events = read_uapi_events(locked, &touch_file, &current_task);
// Default device should receive events because no device device id is 10.
assert_ne!(events.len(), 0);
// add a device, mock uinput.
let open_files: OpenedFiles = Arc::new(Mutex::new(vec![]));
input_relay.add_touch_device(DEVICE_ID, open_files.clone(), None);
let device_id_10_file =
create_test_touch_device(locked, &current_task, input_relay.clone(), DEVICE_ID);
let power_released_event: TouchButtonsEvent = TouchButtonsEvent {
pressed_buttons: Some(vec![]),
device_info: Some(TouchDeviceInfo { id: Some(DEVICE_ID), ..Default::default() }),
..Default::default()
};
let _ = touch_buttons_listener.on_event(&power_released_event).await;
let events = read_uapi_events(locked, &touch_file, &current_task);
// Default device should not receive events because they matched device id 10.
assert_eq!(events.len(), 0);
let events = read_uapi_events(locked, &device_id_10_file, &current_task);
// file of device id 10 should receive events.
assert_ne!(events.len(), 0);
std::mem::drop(touch_buttons_listener); // Close Zircon channel.
}
#[::fuchsia::test]
async fn touch_device_multi_reader() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
_input_relay,
touch_device,
_keyboard_device,
_mouse_device,
touch_reader1,
_keyboard_file,
_mouse_file,
mut touch_source_stream,
_mouse_source_stream,
_keyboard_listener,
_media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
let touch_reader2 =
touch_device.open_test(locked, &current_task).expect("Failed to create input file");
const DEVICE_ID: u32 = 10;
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_touch_event_with_phase_device_id(EventPhase::Add, 1, DEVICE_ID)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `TouchEvent`, to minimize the chance that this event creates unexpected
// `uapi::input_event`s.
answer_next_touch_watch_request(
&mut touch_source_stream,
vec![make_empty_touch_event(DEVICE_ID)],
)
.await;
// Consume all of the `uapi::input_event`s that are available.
let events_from_reader1 = read_uapi_events(locked, &touch_reader1, &current_task);
let events_from_reader2 = read_uapi_events(locked, &touch_reader2, &current_task);
assert_ne!(events_from_reader1.len(), 0);
assert_eq!(events_from_reader1.len(), events_from_reader2.len());
}
#[::fuchsia::test]
async fn keyboard_device_multi_reader() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
_input_relay,
_touch_device,
keyboard_device,
_mouse_device,
_touch_file,
keyboard_reader1,
_mouse_file,
_touch_source_stream,
_mouse_source_stream,
keyboard_listener,
_media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
let keyboard_reader2 =
keyboard_device.open_test(locked, &current_task).expect("Failed to create input file");
const DEVICE_ID: u32 = 10;
let key_event = fuiinput::KeyEvent {
timestamp: Some(0),
type_: Some(fuiinput::KeyEventType::Pressed),
key: Some(fidl_fuchsia_input::Key::A),
device_id: Some(DEVICE_ID),
..Default::default()
};
let _ = keyboard_listener.on_key_event(&key_event).await;
// Consume all of the `uapi::input_event`s that are available.
let events_from_reader1 = read_uapi_events(locked, &keyboard_reader1, &current_task);
let events_from_reader2 = read_uapi_events(locked, &keyboard_reader2, &current_task);
assert_ne!(events_from_reader1.len(), 0);
assert_eq!(events_from_reader1.len(), events_from_reader2.len());
}
#[::fuchsia::test]
async fn button_device_multi_reader() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
_input_relay,
_touch_device,
keyboard_device,
_mouse_device,
_touch_file,
keyboard_reader1,
_mouse_file,
_touch_source_stream,
_mouse_source_stream,
_keyboard_listener,
media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
let keyboard_reader2 =
keyboard_device.open_test(locked, &current_task).expect("Failed to create input file");
const DEVICE_ID: u32 = 10;
let power_pressed_event = MediaButtonsEvent {
volume: Some(0),
mic_mute: Some(false),
pause: Some(false),
camera_disable: Some(false),
power: Some(true),
function: Some(false),
device_id: Some(DEVICE_ID),
..Default::default()
};
let _ = media_buttons_listener.on_event(&power_pressed_event).await;
// Consume all of the `uapi::input_event`s that are available.
let events_from_reader1 = read_uapi_events(locked, &keyboard_reader1, &current_task);
let events_from_reader2 = read_uapi_events(locked, &keyboard_reader2, &current_task);
assert_ne!(events_from_reader1.len(), 0);
assert_eq!(events_from_reader1.len(), events_from_reader2.len());
}
#[::fuchsia::test]
async fn mouse_device_multi_reader() {
// Set up resources.
let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
let (
_input_relay,
_touch_device,
_keyboard_device,
mouse_device,
_touch_file,
_keyboard_file,
mouse_reader1,
_touch_stream,
mut mouse_stream,
_keyboard_listener,
_media_buttons_listener,
_touch_buttons_listener,
) = start_input_relays_for_test(locked, &current_task).await;
let mouse_reader2 =
mouse_device.open_test(locked, &current_task).expect("Failed to create input file");
const DEVICE_ID: u32 = 10;
answer_next_mouse_watch_request(
&mut mouse_stream,
vec![make_mouse_wheel_event(1, DEVICE_ID)],
)
.await;
// Wait for another `Watch` to ensure input_file done processing the first reply.
// Use an empty `MouseEvent`, to minimize the chance that this event creates unexpected
// `uapi::input_event`s.
answer_next_mouse_watch_request(
&mut mouse_stream,
vec![make_mouse_wheel_event(0, DEVICE_ID)],
)
.await;
// Consume all of the `uapi::input_event`s that are available.
let events_from_reader1 = read_uapi_events(locked, &mouse_reader1, &current_task);
let events_from_reader2 = read_uapi_events(locked, &mouse_reader2, &current_task);
assert_ne!(events_from_reader1.len(), 0);
assert_eq!(events_from_reader1.len(), events_from_reader2.len());
}
}