Add runtime tools and Red Bear service wiring
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -8,3 +8,7 @@ redox-scheme = "0.1"
|
||||
syscall = { package = "redox_syscall", version = "0.4" }
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
thiserror = "2"
|
||||
orbclient = { version = "=0.3.47", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox_event = "0.4"
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::types::{InputEvent, InputId, BUS_VIRTUAL};
|
||||
use crate::translate::{KEYBOARD_KEY_CODES, MOUSE_BUTTON_CODES, TOUCHPAD_KEY_CODES};
|
||||
use crate::types::{
|
||||
AbsInfo, InputId, ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_SLOT, ABS_MT_TOUCH_MAJOR,
|
||||
ABS_MT_TRACKING_ID, ABS_PRESSURE, ABS_X, ABS_Y, BUS_VIRTUAL, EV_ABS, EV_KEY, EV_LED, EV_MSC,
|
||||
EV_REL, EV_REP, EV_SYN, INPUT_PROP_POINTER, KEY_MAX, LED_CAPSL, LED_MAX, LED_NUML, LED_SCROLLL,
|
||||
MSC_SCAN, REL_HWHEEL, REL_WHEEL, REL_X, REL_Y, REP_DELAY, REP_PERIOD,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum DeviceKind {
|
||||
@@ -10,17 +16,17 @@ pub enum DeviceKind {
|
||||
}
|
||||
|
||||
pub struct InputDevice {
|
||||
pub id: usize,
|
||||
pub kind: DeviceKind,
|
||||
pub name: String,
|
||||
pub input_id: InputId,
|
||||
pub event_buf: VecDeque<InputEvent>,
|
||||
pub key_state: [u8; KEY_MAX / 8 + 1],
|
||||
pub led_state: [u8; LED_MAX / 8 + 1],
|
||||
pub custom_abs: BTreeMap<u16, AbsInfo>,
|
||||
}
|
||||
|
||||
impl InputDevice {
|
||||
pub fn new_keyboard(id: usize) -> Self {
|
||||
InputDevice {
|
||||
id,
|
||||
kind: DeviceKind::Keyboard,
|
||||
name: format!("Redox Keyboard {}", id),
|
||||
input_id: InputId {
|
||||
@@ -29,13 +35,14 @@ impl InputDevice {
|
||||
product: id as u16,
|
||||
version: 1,
|
||||
},
|
||||
event_buf: VecDeque::new(),
|
||||
key_state: [0u8; KEY_MAX / 8 + 1],
|
||||
led_state: [0u8; LED_MAX / 8 + 1],
|
||||
custom_abs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mouse(id: usize) -> Self {
|
||||
InputDevice {
|
||||
id,
|
||||
kind: DeviceKind::Mouse,
|
||||
name: format!("Redox Mouse {}", id),
|
||||
input_id: InputId {
|
||||
@@ -44,13 +51,14 @@ impl InputDevice {
|
||||
product: (id + 0x10) as u16,
|
||||
version: 1,
|
||||
},
|
||||
event_buf: VecDeque::new(),
|
||||
key_state: [0u8; KEY_MAX / 8 + 1],
|
||||
led_state: [0u8; LED_MAX / 8 + 1],
|
||||
custom_abs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_touchpad(id: usize) -> Self {
|
||||
InputDevice {
|
||||
id,
|
||||
kind: DeviceKind::Touchpad,
|
||||
name: format!("Redox Touchpad {}", id),
|
||||
input_id: InputId {
|
||||
@@ -59,37 +67,166 @@ impl InputDevice {
|
||||
product: (id + 0x20) as u16,
|
||||
version: 1,
|
||||
},
|
||||
event_buf: VecDeque::new(),
|
||||
key_state: [0u8; KEY_MAX / 8 + 1],
|
||||
led_state: [0u8; LED_MAX / 8 + 1],
|
||||
custom_abs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_event(&mut self, event: InputEvent) {
|
||||
self.event_buf.push_back(event);
|
||||
}
|
||||
|
||||
pub fn push_events(&mut self, events: &[InputEvent]) {
|
||||
for &ev in events {
|
||||
self.event_buf.push_back(ev);
|
||||
pub fn supported_event_types(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Keyboard => bitmap_from_codes(&[EV_SYN, EV_KEY, EV_MSC, EV_LED, EV_REP]),
|
||||
DeviceKind::Mouse => bitmap_from_codes(&[EV_SYN, EV_KEY, EV_REL]),
|
||||
DeviceKind::Touchpad => bitmap_from_codes(&[EV_SYN, EV_KEY, EV_ABS]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_bytes(&mut self, buf: &mut [u8]) -> usize {
|
||||
let event_count = buf.len() / InputEvent::SIZE;
|
||||
let mut written = 0;
|
||||
for _ in 0..event_count {
|
||||
match self.event_buf.pop_front() {
|
||||
Some(ev) => {
|
||||
let bytes = ev.to_bytes();
|
||||
buf[written..written + InputEvent::SIZE].copy_from_slice(&bytes);
|
||||
written += InputEvent::SIZE;
|
||||
}
|
||||
None => break,
|
||||
pub fn supported_keys(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Keyboard => bitmap_from_codes(KEYBOARD_KEY_CODES),
|
||||
DeviceKind::Mouse => bitmap_from_codes(MOUSE_BUTTON_CODES),
|
||||
DeviceKind::Touchpad => bitmap_from_codes(TOUCHPAD_KEY_CODES),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_rel(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Mouse => bitmap_from_codes(&[REL_X, REL_Y, REL_WHEEL, REL_HWHEEL]),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_abs(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Touchpad => bitmap_from_codes(&[
|
||||
ABS_X,
|
||||
ABS_Y,
|
||||
ABS_PRESSURE,
|
||||
ABS_MT_SLOT,
|
||||
ABS_MT_TOUCH_MAJOR,
|
||||
ABS_MT_POSITION_X,
|
||||
ABS_MT_POSITION_Y,
|
||||
ABS_MT_TRACKING_ID,
|
||||
]),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_msc(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Keyboard => bitmap_from_codes(&[MSC_SCAN]),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_leds(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Keyboard => bitmap_from_codes(&[LED_NUML, LED_CAPSL, LED_SCROLLL]),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_rep(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Keyboard => bitmap_from_codes(&[REP_DELAY, REP_PERIOD]),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_props(&self) -> Vec<u8> {
|
||||
match self.kind {
|
||||
DeviceKind::Mouse | DeviceKind::Touchpad => bitmap_from_codes(&[INPUT_PROP_POINTER]),
|
||||
DeviceKind::Keyboard => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn abs_info(&self, axis: u16) -> AbsInfo {
|
||||
if let Some(abs_info) = self.custom_abs.get(&axis) {
|
||||
return *abs_info;
|
||||
}
|
||||
|
||||
if self.kind != DeviceKind::Touchpad {
|
||||
return AbsInfo::default();
|
||||
}
|
||||
|
||||
match axis {
|
||||
ABS_X | ABS_MT_POSITION_X => AbsInfo {
|
||||
minimum: 0,
|
||||
maximum: 65_535,
|
||||
resolution: 1,
|
||||
..AbsInfo::default()
|
||||
},
|
||||
ABS_Y | ABS_MT_POSITION_Y => AbsInfo {
|
||||
minimum: 0,
|
||||
maximum: 65_535,
|
||||
resolution: 1,
|
||||
..AbsInfo::default()
|
||||
},
|
||||
ABS_PRESSURE => AbsInfo {
|
||||
minimum: 0,
|
||||
maximum: 255,
|
||||
resolution: 1,
|
||||
..AbsInfo::default()
|
||||
},
|
||||
ABS_MT_TOUCH_MAJOR => AbsInfo {
|
||||
minimum: 0,
|
||||
maximum: 255,
|
||||
resolution: 1,
|
||||
..AbsInfo::default()
|
||||
},
|
||||
ABS_MT_SLOT => AbsInfo {
|
||||
minimum: 0,
|
||||
maximum: 9,
|
||||
..AbsInfo::default()
|
||||
},
|
||||
ABS_MT_TRACKING_ID => AbsInfo {
|
||||
minimum: 0,
|
||||
maximum: i32::MAX,
|
||||
..AbsInfo::default()
|
||||
},
|
||||
_ => AbsInfo::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_abs_info(&mut self, axis: u16, abs_info: AbsInfo) {
|
||||
self.custom_abs.insert(axis, abs_info);
|
||||
}
|
||||
|
||||
pub fn update_key_state(&mut self, code: u16, pressed: bool) {
|
||||
let byte = (code / 8) as usize;
|
||||
let bit = code % 8;
|
||||
if byte < self.key_state.len() {
|
||||
if pressed {
|
||||
self.key_state[byte] |= 1 << bit;
|
||||
} else {
|
||||
self.key_state[byte] &= !(1 << bit);
|
||||
}
|
||||
}
|
||||
written
|
||||
}
|
||||
|
||||
pub fn has_events(&self) -> bool {
|
||||
!self.event_buf.is_empty()
|
||||
pub fn update_led_state(&mut self, code: u16, lit: bool) {
|
||||
let byte = (code / 8) as usize;
|
||||
let bit = code % 8;
|
||||
if byte < self.led_state.len() {
|
||||
if lit {
|
||||
self.led_state[byte] |= 1 << bit;
|
||||
} else {
|
||||
self.led_state[byte] &= !(1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bitmap_from_codes(codes: &[u16]) -> Vec<u8> {
|
||||
let Some(max) = codes.iter().copied().max() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut bitmap = vec![0u8; (usize::from(max) / 8) + 1];
|
||||
for &code in codes {
|
||||
let index = usize::from(code / 8);
|
||||
let bit = 1u8 << (code % 8);
|
||||
bitmap[index] |= bit;
|
||||
}
|
||||
bitmap
|
||||
}
|
||||
|
||||
@@ -3,16 +3,37 @@ mod scheme;
|
||||
mod translate;
|
||||
mod types;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{ErrorKind, Read};
|
||||
use std::mem::{size_of, MaybeUninit};
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::process;
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use std::thread;
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{error, info, LevelFilter, Metadata, Record};
|
||||
use redox_scheme::{SignalBehavior, Socket};
|
||||
use orbclient::{Event, EventOption};
|
||||
use redox_scheme::{Request, SignalBehavior, Socket};
|
||||
use syscall::error::EAGAIN;
|
||||
use syscall::flag::O_NONBLOCK;
|
||||
|
||||
use scheme::EvdevScheme;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
use event::{EventFlags as QueueEventFlags, RawEventQueue};
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
const SCHEME_QUEUE_TOKEN: usize = 1;
|
||||
#[cfg(target_os = "redox")]
|
||||
const INPUT_QUEUE_TOKEN: usize = 2;
|
||||
|
||||
struct StderrLogger {
|
||||
level: LevelFilter,
|
||||
}
|
||||
@@ -29,64 +50,256 @@ impl log::Log for StderrLogger {
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
fn read_input_events(scheme: &mut EvdevScheme) -> Result<(), String> {
|
||||
let mut input_file =
|
||||
File::open("/scheme/input").map_err(|e| format!("failed to open /scheme/input: {}", e))?;
|
||||
struct InputConsumer {
|
||||
file: File,
|
||||
partial: Vec<u8>,
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 256];
|
||||
match input_file.read(&mut buf) {
|
||||
Ok(n) if n > 0 => {
|
||||
let data = &buf[..n];
|
||||
for &byte in data {
|
||||
let pressed = (byte & 0x80) == 0;
|
||||
let key = byte & 0x7F;
|
||||
scheme.feed_keyboard_event(key, pressed);
|
||||
impl InputConsumer {
|
||||
fn open() -> Result<Self, String> {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(O_NONBLOCK as i32)
|
||||
.open("/scheme/input/consumer")
|
||||
.map_err(|e| format!("failed to open /scheme/input/consumer: {e}"))?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
partial: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn fd(&self) -> usize {
|
||||
self.file.as_raw_fd() as usize
|
||||
}
|
||||
|
||||
fn read_available(&mut self, scheme: &mut EvdevScheme) -> Result<bool, String> {
|
||||
let event_size = size_of::<Event>();
|
||||
let mut buf = vec![0u8; event_size * 32];
|
||||
let mut progress = false;
|
||||
|
||||
loop {
|
||||
match self.file.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(count) => {
|
||||
progress = true;
|
||||
self.partial.extend_from_slice(&buf[..count]);
|
||||
|
||||
while self.partial.len() >= event_size {
|
||||
let event = read_event_from_bytes(&self.partial[..event_size]);
|
||||
self.partial.drain(..event_size);
|
||||
dispatch_input_event(event, scheme);
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
|
||||
Err(err) => return Err(format!("failed to read /scheme/input/consumer: {err}")),
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("evdevd: failed to read input: {}", e);
|
||||
|
||||
Ok(progress)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
struct SchemePoll {
|
||||
mounted: bool,
|
||||
progress: bool,
|
||||
}
|
||||
|
||||
fn read_event_from_bytes(bytes: &[u8]) -> Event {
|
||||
let mut event = MaybeUninit::<Event>::uninit();
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), event.as_mut_ptr() as *mut u8, bytes.len());
|
||||
event.assume_init()
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_input_event(event: Event, scheme: &mut EvdevScheme) {
|
||||
match event.to_option() {
|
||||
EventOption::Key(key) => scheme.feed_keyboard_event(key.scancode, key.pressed),
|
||||
EventOption::Mouse(mouse) => scheme.feed_touchpad_position(mouse.x, mouse.y),
|
||||
EventOption::MouseRelative(mouse) => scheme.feed_mouse_move(mouse.dx, mouse.dy),
|
||||
EventOption::Button(button) => {
|
||||
scheme.feed_mouse_buttons(button.left, button.middle, button.right)
|
||||
}
|
||||
EventOption::Scroll(scroll) => scheme.feed_mouse_scroll(scroll.x, scroll.y),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_would_block_socket(err: &syscall::Error) -> bool {
|
||||
err.errno == EAGAIN
|
||||
}
|
||||
|
||||
fn write_scheme_response(socket: &Socket, response: redox_scheme::Response) -> Result<(), String> {
|
||||
socket
|
||||
.write_response(response, SignalBehavior::Restart)
|
||||
.map_err(|e| format!("failed to write response: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
request: Request,
|
||||
scheme: &mut EvdevScheme,
|
||||
pending_requests: &mut VecDeque<Request>,
|
||||
socket: &Socket,
|
||||
) -> Result<bool, String> {
|
||||
match request.handle_scheme_block_mut(scheme) {
|
||||
Ok(response) => {
|
||||
write_scheme_response(socket, response)?;
|
||||
Ok(true)
|
||||
}
|
||||
Err(request) => {
|
||||
pending_requests.push_back(request);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_pending_requests(
|
||||
scheme: &mut EvdevScheme,
|
||||
pending_requests: &mut VecDeque<Request>,
|
||||
socket: &Socket,
|
||||
) -> Result<bool, String> {
|
||||
let mut progress = false;
|
||||
let pending_len = pending_requests.len();
|
||||
|
||||
for _ in 0..pending_len {
|
||||
let Some(request) = pending_requests.pop_front() else {
|
||||
break;
|
||||
};
|
||||
|
||||
match request.handle_scheme_block_mut(scheme) {
|
||||
Ok(response) => {
|
||||
write_scheme_response(socket, response)?;
|
||||
progress = true;
|
||||
}
|
||||
Err(request) => pending_requests.push_back(request),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(progress)
|
||||
}
|
||||
|
||||
fn read_scheme_requests(
|
||||
socket: &Socket,
|
||||
scheme: &mut EvdevScheme,
|
||||
pending_requests: &mut VecDeque<Request>,
|
||||
) -> Result<SchemePoll, String> {
|
||||
let mut poll = SchemePoll {
|
||||
mounted: true,
|
||||
progress: false,
|
||||
};
|
||||
|
||||
loop {
|
||||
match socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(request)) => {
|
||||
poll.progress =
|
||||
handle_request(request, scheme, pending_requests, socket)? || poll.progress;
|
||||
}
|
||||
Ok(None) => {
|
||||
poll.mounted = false;
|
||||
break;
|
||||
}
|
||||
Err(err) if is_would_block_socket(&err) => break,
|
||||
Err(err) => return Err(format!("failed to read scheme request: {err}")),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(poll)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn run_redox_event_loop(
|
||||
socket: &Socket,
|
||||
scheme: &mut EvdevScheme,
|
||||
input: &mut InputConsumer,
|
||||
pending_requests: &mut VecDeque<Request>,
|
||||
) -> Result<(), String> {
|
||||
let event_queue =
|
||||
RawEventQueue::new().map_err(|e| format!("failed to create event queue: {e}"))?;
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
SCHEME_QUEUE_TOKEN,
|
||||
QueueEventFlags::READ,
|
||||
)
|
||||
.map_err(|e| format!("failed to subscribe scheme socket: {e}"))?;
|
||||
event_queue
|
||||
.subscribe(input.fd(), INPUT_QUEUE_TOKEN, QueueEventFlags::READ)
|
||||
.map_err(|e| format!("failed to subscribe input consumer: {e}"))?;
|
||||
|
||||
loop {
|
||||
let raw_event = event_queue
|
||||
.next_event()
|
||||
.map_err(|e| format!("failed to wait for events: {e}"))?;
|
||||
|
||||
match raw_event.user_data {
|
||||
SCHEME_QUEUE_TOKEN => {
|
||||
let poll = read_scheme_requests(socket, scheme, pending_requests)?;
|
||||
if !poll.mounted {
|
||||
info!("evdevd: scheme unmounted, exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
INPUT_QUEUE_TOKEN => {
|
||||
let _ = input.read_available(scheme)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let _ = flush_pending_requests(scheme, pending_requests, socket)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
fn run_host_event_loop(
|
||||
socket: &Socket,
|
||||
scheme: &mut EvdevScheme,
|
||||
input: &mut InputConsumer,
|
||||
pending_requests: &mut VecDeque<Request>,
|
||||
) -> Result<(), String> {
|
||||
loop {
|
||||
let mut progress = input.read_available(scheme)?;
|
||||
|
||||
let poll = read_scheme_requests(socket, scheme, pending_requests)?;
|
||||
if !poll.mounted {
|
||||
info!("evdevd: scheme unmounted, exiting");
|
||||
break;
|
||||
}
|
||||
progress |= poll.progress;
|
||||
progress |= flush_pending_requests(scheme, pending_requests, socket)?;
|
||||
|
||||
if !progress {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let mut input = InputConsumer::open()?;
|
||||
let mut pending_requests = VecDeque::new();
|
||||
|
||||
let socket =
|
||||
Socket::create("evdev").map_err(|e| format!("failed to register evdev scheme: {}", e))?;
|
||||
Socket::nonblock("evdev").map_err(|e| format!("failed to register evdev scheme: {}", e))?;
|
||||
info!("evdevd: registered scheme:evdev");
|
||||
info!("evdevd: consuming orbclient::Event from /scheme/input/consumer");
|
||||
|
||||
loop {
|
||||
let request = match socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(r)) => r,
|
||||
Ok(None) => {
|
||||
info!("evdevd: scheme unmounted, exiting");
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("evdevd: failed to read scheme request: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let response = match request.handle_scheme_block_mut(&mut scheme) {
|
||||
Ok(r) => r,
|
||||
Err(_req) => {
|
||||
error!("evdevd: failed to handle request");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = socket.write_response(response, SignalBehavior::Restart) {
|
||||
error!("evdevd: failed to write response: {}", e);
|
||||
}
|
||||
|
||||
let _ = read_input_events(&mut scheme);
|
||||
#[cfg(target_os = "redox")]
|
||||
{
|
||||
run_redox_event_loop(&socket, &mut scheme, &mut input, &mut pending_requests)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
{
|
||||
run_host_event_loop(&socket, &mut scheme, &mut input, &mut pending_requests)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::mem::size_of;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr;
|
||||
|
||||
use syscall::data::Stat;
|
||||
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT, EROFS};
|
||||
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, SEEK_CUR, SEEK_END, SEEK_SET};
|
||||
use syscall::error::{Error, Result, EBADF, EBUSY, EFAULT, EINVAL, ENOENT, ENOTTY, EROFS};
|
||||
use syscall::flag::{
|
||||
EventFlags, F_GETFD, F_GETFL, F_SETFD, F_SETFL, MODE_DIR, MODE_FILE, O_RDONLY, SEEK_CUR,
|
||||
SEEK_END, SEEK_SET,
|
||||
};
|
||||
|
||||
use crate::device::{DeviceKind, InputDevice};
|
||||
use crate::translate;
|
||||
use crate::types::{
|
||||
ioc_dir, ioc_nr, ioc_size, ioc_type, is_evdev_ioctl, AbsInfo, InputEvent, InputId,
|
||||
EVDEV_IOCTL_TYPE, EVIOCGABS, EVIOCGEFFECTS, EVIOCGID, EVIOCGKEY, EVIOCGLED, EVIOCGNAME,
|
||||
EVIOCGPROP, EVIOCGRAB, EVIOCGVERSION, EVIOCRMFF, EVIOCSABS, EVIOCSCLOCKID, EVIOCSFF, EV_ABS,
|
||||
EV_KEY, EV_LED, EV_MSC, EV_REL, EV_REP, EV_VERSION, IOC_READ,
|
||||
};
|
||||
|
||||
struct Handle {
|
||||
kind: HandleKind,
|
||||
@@ -14,13 +26,22 @@ struct Handle {
|
||||
|
||||
enum HandleKind {
|
||||
Root,
|
||||
Device(usize),
|
||||
Device {
|
||||
device_idx: usize,
|
||||
events: VecDeque<InputEvent>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EvdevScheme {
|
||||
next_id: usize,
|
||||
handles: BTreeMap<usize, Handle>,
|
||||
devices: Vec<InputDevice>,
|
||||
grabbed_by: BTreeMap<usize, usize>,
|
||||
mouse_buttons: [bool; 3],
|
||||
touchpad_position: (i32, i32),
|
||||
touchpad_touching: bool,
|
||||
next_tracking_id: i32,
|
||||
current_tracking_id: i32,
|
||||
}
|
||||
|
||||
impl EvdevScheme {
|
||||
@@ -29,54 +50,241 @@ impl EvdevScheme {
|
||||
next_id: 0,
|
||||
handles: BTreeMap::new(),
|
||||
devices: Vec::new(),
|
||||
grabbed_by: BTreeMap::new(),
|
||||
mouse_buttons: [false; 3],
|
||||
touchpad_position: (0, 0),
|
||||
touchpad_touching: false,
|
||||
next_tracking_id: 1,
|
||||
current_tracking_id: -1,
|
||||
};
|
||||
scheme.devices.push(InputDevice::new_keyboard(0));
|
||||
scheme.devices.push(InputDevice::new_mouse(0));
|
||||
scheme.devices.push(InputDevice::new_mouse(1));
|
||||
scheme.devices.push(InputDevice::new_touchpad(2));
|
||||
scheme
|
||||
}
|
||||
|
||||
pub fn feed_keyboard_event(&mut self, key: u8, pressed: bool) {
|
||||
let events = translate::translate_keyboard(key, pressed);
|
||||
if !events.is_empty() {
|
||||
if let Some(dev) = self
|
||||
.devices
|
||||
.iter_mut()
|
||||
.find(|d| d.kind == DeviceKind::Keyboard)
|
||||
fn device_index(&self, kind: DeviceKind) -> Option<usize> {
|
||||
self.devices.iter().position(|d| d.kind == kind)
|
||||
}
|
||||
|
||||
fn current_tracking_id(&self) -> i32 {
|
||||
if self.touchpad_touching {
|
||||
self.current_tracking_id
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_device_events(&mut self, kind: DeviceKind, events: &[InputEvent]) {
|
||||
if events.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(device_idx) = self.device_index(kind) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in events {
|
||||
if event.event_type == EV_KEY {
|
||||
self.devices[device_idx].update_key_state(event.code, event.value != 0);
|
||||
} else if event.event_type == EV_LED {
|
||||
self.devices[device_idx].update_led_state(event.code, event.value != 0);
|
||||
}
|
||||
}
|
||||
|
||||
let grabbed_handle = self.grabbed_by.get(&device_idx).copied();
|
||||
|
||||
for (handle_id, handle) in self.handles.iter_mut() {
|
||||
if let HandleKind::Device {
|
||||
device_idx: handle_device_idx,
|
||||
events: handle_events,
|
||||
} = &mut handle.kind
|
||||
{
|
||||
dev.push_events(&events);
|
||||
if *handle_device_idx == device_idx {
|
||||
if let Some(grabbed_id) = grabbed_handle {
|
||||
if *handle_id != grabbed_id {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
handle_events.extend(events.iter().copied());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_handle_bytes(events: &mut VecDeque<InputEvent>, buf: &mut [u8]) -> usize {
|
||||
let event_count = buf.len() / InputEvent::SIZE;
|
||||
let mut written = 0;
|
||||
|
||||
for _ in 0..event_count {
|
||||
let Some(event) = events.pop_front() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let bytes = event.to_bytes();
|
||||
buf[written..written + InputEvent::SIZE].copy_from_slice(&bytes);
|
||||
written += InputEvent::SIZE;
|
||||
}
|
||||
|
||||
written
|
||||
}
|
||||
|
||||
pub fn feed_keyboard_event(&mut self, scancode: u8, pressed: bool) {
|
||||
let events = translate::translate_keyboard(scancode, pressed);
|
||||
self.queue_device_events(DeviceKind::Keyboard, &events);
|
||||
}
|
||||
|
||||
pub fn feed_mouse_move(&mut self, dx: i32, dy: i32) {
|
||||
if let Some(dev) = self
|
||||
.devices
|
||||
.iter_mut()
|
||||
.find(|d| d.kind == DeviceKind::Mouse)
|
||||
{
|
||||
dev.push_events(&translate::translate_mouse_dx(dx));
|
||||
dev.push_events(&translate::translate_mouse_dy(dy));
|
||||
let events = translate::translate_mouse_motion(dx, dy);
|
||||
self.queue_device_events(DeviceKind::Mouse, &events);
|
||||
}
|
||||
|
||||
pub fn feed_mouse_scroll(&mut self, x: i32, y: i32) {
|
||||
let events = translate::translate_mouse_scroll(x, y);
|
||||
self.queue_device_events(DeviceKind::Mouse, &events);
|
||||
}
|
||||
|
||||
pub fn feed_mouse_buttons(&mut self, left: bool, middle: bool, right: bool) {
|
||||
let old_buttons = self.mouse_buttons;
|
||||
let new_buttons = [left, middle, right];
|
||||
for (index, (&old, &new)) in old_buttons.iter().zip(new_buttons.iter()).enumerate() {
|
||||
if old != new {
|
||||
let events = translate::translate_mouse_button(index, new);
|
||||
self.queue_device_events(DeviceKind::Mouse, &events);
|
||||
}
|
||||
}
|
||||
self.mouse_buttons = new_buttons;
|
||||
|
||||
let touching = left;
|
||||
if touching != self.touchpad_touching {
|
||||
if touching {
|
||||
self.current_tracking_id = self.next_tracking_id;
|
||||
self.next_tracking_id = self.next_tracking_id.saturating_add(1);
|
||||
}
|
||||
|
||||
self.touchpad_touching = touching;
|
||||
let (x, y) = self.touchpad_position;
|
||||
let tracking_id = self.current_tracking_id();
|
||||
let events = translate::translate_touchpad_contact(x, y, touching, tracking_id);
|
||||
self.queue_device_events(DeviceKind::Touchpad, &events);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed_mouse_scroll(&mut self, y: i32) {
|
||||
if let Some(dev) = self
|
||||
.devices
|
||||
.iter_mut()
|
||||
.find(|d| d.kind == DeviceKind::Mouse)
|
||||
{
|
||||
dev.push_events(&translate::translate_mouse_scroll(y));
|
||||
pub fn feed_touchpad_position(&mut self, x: i32, y: i32) {
|
||||
self.touchpad_position = (x, y);
|
||||
let touching = self.touchpad_touching;
|
||||
let tracking_id = self.current_tracking_id();
|
||||
let events = translate::translate_touchpad_motion(x, y, touching, tracking_id);
|
||||
self.queue_device_events(DeviceKind::Touchpad, &events);
|
||||
}
|
||||
|
||||
fn ioctl_name_len(cmd: u64) -> Option<usize> {
|
||||
if cmd == EVIOCGNAME || (ioc_type(cmd) == EVDEV_IOCTL_TYPE && ioc_nr(cmd) == 0x06) {
|
||||
let size = ioc_size(cmd);
|
||||
return Some(if size == 0 { 256 } else { size });
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn ioctl_bit_ev_and_len(cmd: u64) -> Option<(u8, usize)> {
|
||||
if !is_evdev_ioctl(cmd) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let nr = ioc_nr(cmd);
|
||||
if !(0x20..0x40).contains(&nr) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size = ioc_size(cmd);
|
||||
Some(((nr - 0x20) as u8, size))
|
||||
}
|
||||
|
||||
fn ioctl_abs_axis(cmd: u64) -> Option<u16> {
|
||||
if !is_evdev_ioctl(cmd) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let nr = ioc_nr(cmd);
|
||||
if !(0x40..0x80).contains(&nr) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((nr - 0x40) as u16)
|
||||
}
|
||||
|
||||
fn device_bitmap(device: &InputDevice, ev: u8) -> Vec<u8> {
|
||||
match u16::from(ev) {
|
||||
0 => device.supported_event_types(),
|
||||
EV_KEY => device.supported_keys(),
|
||||
EV_REL => device.supported_rel(),
|
||||
EV_ABS => device.supported_abs(),
|
||||
EV_MSC => device.supported_msc(),
|
||||
EV_LED => device.supported_leds(),
|
||||
EV_REP => device.supported_rep(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed_mouse_button(&mut self, button: usize, pressed: bool) {
|
||||
if let Some(dev) = self
|
||||
.devices
|
||||
.iter_mut()
|
||||
.find(|d| d.kind == DeviceKind::Mouse)
|
||||
{
|
||||
dev.push_events(&translate::translate_mouse_button(button, pressed));
|
||||
unsafe fn write_value_to_user<T: Copy>(arg: usize, value: &T) -> Result<usize> {
|
||||
if arg == 0 {
|
||||
return Err(Error::new(EFAULT));
|
||||
}
|
||||
|
||||
ptr::copy_nonoverlapping(
|
||||
value as *const T as *const u8,
|
||||
arg as *mut u8,
|
||||
size_of::<T>(),
|
||||
);
|
||||
Ok(size_of::<T>())
|
||||
}
|
||||
|
||||
unsafe fn write_bytes_to_user(arg: usize, bytes: &[u8]) -> Result<usize> {
|
||||
if arg == 0 {
|
||||
return Err(Error::new(EFAULT));
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr(), arg as *mut u8, bytes.len());
|
||||
}
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
unsafe fn read_value_from_user<T: Copy>(arg: usize) -> Result<T> {
|
||||
if arg == 0 {
|
||||
return Err(Error::new(EFAULT));
|
||||
}
|
||||
|
||||
let mut value = MaybeUninit::<T>::uninit();
|
||||
ptr::copy_nonoverlapping(
|
||||
arg as *const u8,
|
||||
value.as_mut_ptr() as *mut u8,
|
||||
size_of::<T>(),
|
||||
);
|
||||
Ok(value.assume_init())
|
||||
}
|
||||
|
||||
fn ioctl_abs_set_axis(cmd: u64) -> Option<u16> {
|
||||
if !is_evdev_ioctl(cmd) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let nr = ioc_nr(cmd);
|
||||
if !(0xc0..0x100).contains(&nr) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((nr - 0xc0) as u16)
|
||||
}
|
||||
|
||||
fn device_prop_bitmap(device: &InputDevice) -> [u8; 64] {
|
||||
let bitmap = device.supported_props();
|
||||
let mut bytes = [0u8; 64];
|
||||
let copy_len = bitmap.len().min(bytes.len());
|
||||
if copy_len > 0 {
|
||||
bytes[..copy_len].copy_from_slice(&bitmap[..copy_len]);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +302,10 @@ impl redox_scheme::SchemeBlockMut for EvdevScheme {
|
||||
if idx >= self.devices.len() {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
HandleKind::Device(idx)
|
||||
HandleKind::Device {
|
||||
device_idx: idx,
|
||||
events: VecDeque::new(),
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
};
|
||||
@@ -108,22 +319,28 @@ impl redox_scheme::SchemeBlockMut for EvdevScheme {
|
||||
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
match &handle.kind {
|
||||
match &mut handle.kind {
|
||||
HandleKind::Root => {
|
||||
let mut listing = String::new();
|
||||
for (i, _dev) in self.devices.iter().enumerate() {
|
||||
listing.push_str(&format!("event{}\n", i));
|
||||
}
|
||||
let bytes = listing.as_bytes();
|
||||
if handle.offset >= bytes.len() {
|
||||
return Ok(Some(0));
|
||||
}
|
||||
let remaining = &bytes[handle.offset..];
|
||||
let to_copy = remaining.len().min(buf.len());
|
||||
buf[..to_copy].copy_from_slice(&remaining[..to_copy]);
|
||||
handle.offset += to_copy;
|
||||
Ok(Some(to_copy))
|
||||
}
|
||||
HandleKind::Device(idx) => {
|
||||
let dev = &mut self.devices[*idx];
|
||||
let written = dev.pop_bytes(buf);
|
||||
HandleKind::Device { events, .. } => {
|
||||
if !events.is_empty() && buf.len() < InputEvent::SIZE {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let written = Self::pop_handle_bytes(events, buf);
|
||||
handle.offset += written;
|
||||
Ok(if written == 0 { None } else { Some(written) })
|
||||
}
|
||||
@@ -156,7 +373,7 @@ impl redox_scheme::SchemeBlockMut for EvdevScheme {
|
||||
HandleKind::Root => {
|
||||
stat.st_mode = MODE_DIR | 0o555;
|
||||
}
|
||||
HandleKind::Device(_) => {
|
||||
HandleKind::Device { .. } => {
|
||||
stat.st_mode = MODE_FILE | 0o444;
|
||||
}
|
||||
}
|
||||
@@ -164,6 +381,7 @@ impl redox_scheme::SchemeBlockMut for EvdevScheme {
|
||||
}
|
||||
|
||||
fn close(&mut self, id: usize) -> Result<Option<usize>> {
|
||||
self.grabbed_by.retain(|_, grabbed_id| *grabbed_id != id);
|
||||
self.handles.remove(&id);
|
||||
Ok(Some(0))
|
||||
}
|
||||
@@ -172,7 +390,7 @@ impl redox_scheme::SchemeBlockMut for EvdevScheme {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
let path = match &handle.kind {
|
||||
HandleKind::Root => "evdev:".to_string(),
|
||||
HandleKind::Device(idx) => format!("evdev:event{}", idx),
|
||||
HandleKind::Device { device_idx, .. } => format!("evdev:event{}", device_idx),
|
||||
};
|
||||
let bytes = path.as_bytes();
|
||||
let to_copy = bytes.len().min(buf.len());
|
||||
@@ -180,13 +398,343 @@ impl redox_scheme::SchemeBlockMut for EvdevScheme {
|
||||
Ok(Some(to_copy))
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize) -> Result<Option<usize>> {
|
||||
let _ = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
Ok(Some(0))
|
||||
fn fcntl(&mut self, id: usize, cmd_raw: usize, arg: usize) -> Result<Option<usize>> {
|
||||
let device_idx = match &self.handles.get(&id).ok_or(Error::new(EBADF))?.kind {
|
||||
HandleKind::Root => None,
|
||||
HandleKind::Device { device_idx, .. } => Some(*device_idx),
|
||||
};
|
||||
|
||||
match cmd_raw {
|
||||
F_GETFL => return Ok(Some(O_RDONLY)),
|
||||
F_GETFD => return Ok(Some(0)),
|
||||
F_SETFL | F_SETFD => {
|
||||
return Ok(Some(0));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let Some(idx) = device_idx else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
if cmd_raw == EVIOCGRAB as usize {
|
||||
let grab = unsafe { Self::read_value_from_user::<i32>(arg)? };
|
||||
return match grab {
|
||||
0 => {
|
||||
if self.grabbed_by.get(&idx) == Some(&id) {
|
||||
self.grabbed_by.remove(&idx);
|
||||
}
|
||||
Ok(Some(0))
|
||||
}
|
||||
1 => match self.grabbed_by.get(&idx).copied() {
|
||||
Some(grabbed_id) if grabbed_id != id => Err(Error::new(EBUSY)),
|
||||
_ => {
|
||||
self.grabbed_by.insert(idx, id);
|
||||
Ok(Some(0))
|
||||
}
|
||||
},
|
||||
_ => Err(Error::new(EINVAL)),
|
||||
};
|
||||
}
|
||||
|
||||
if cmd_raw == EVIOCSCLOCKID as usize {
|
||||
return Ok(Some(0));
|
||||
}
|
||||
let cmd = cmd_raw as u64;
|
||||
|
||||
if matches!(cmd, EVIOCSFF | EVIOCRMFF | EVIOCGEFFECTS) {
|
||||
return Err(Error::new(ENOTTY));
|
||||
}
|
||||
|
||||
if cmd == EVIOCSABS || Self::ioctl_abs_set_axis(cmd).is_some() {
|
||||
let axis = Self::ioctl_abs_set_axis(cmd).unwrap_or(0);
|
||||
let abs_info = unsafe { Self::read_value_from_user::<AbsInfo>(arg)? };
|
||||
self.devices[idx].set_abs_info(axis, abs_info);
|
||||
return Ok(Some(0));
|
||||
}
|
||||
|
||||
let device = &self.devices[idx];
|
||||
|
||||
if cmd == EVIOCGVERSION {
|
||||
let version = EV_VERSION;
|
||||
return unsafe { Self::write_value_to_user(arg, &version).map(Some) };
|
||||
}
|
||||
|
||||
if cmd == EVIOCGID {
|
||||
let input_id: InputId = device.input_id;
|
||||
return unsafe { Self::write_value_to_user(arg, &input_id).map(Some) };
|
||||
}
|
||||
|
||||
if cmd == EVIOCGKEY {
|
||||
let key_state = device.key_state;
|
||||
return unsafe { Self::write_bytes_to_user(arg, &key_state).map(Some) };
|
||||
}
|
||||
|
||||
if cmd == EVIOCGLED {
|
||||
let led_state = device.led_state;
|
||||
return unsafe { Self::write_bytes_to_user(arg, &led_state).map(Some) };
|
||||
}
|
||||
|
||||
if cmd == EVIOCGPROP {
|
||||
let props = Self::device_prop_bitmap(device);
|
||||
return unsafe { Self::write_bytes_to_user(arg, &props).map(Some) };
|
||||
}
|
||||
|
||||
if let Some(name_len) = Self::ioctl_name_len(cmd) {
|
||||
let mut bytes = vec![0u8; name_len];
|
||||
let name = device.name.as_bytes();
|
||||
let copy_len = name.len().min(bytes.len().saturating_sub(1));
|
||||
if copy_len > 0 {
|
||||
bytes[..copy_len].copy_from_slice(&name[..copy_len]);
|
||||
}
|
||||
return unsafe { Self::write_bytes_to_user(arg, &bytes).map(Some) };
|
||||
}
|
||||
|
||||
if let Some((ev, len)) = Self::ioctl_bit_ev_and_len(cmd) {
|
||||
let bitmap = Self::device_bitmap(device, ev);
|
||||
let out_len = if len == 0 {
|
||||
bitmap.len()
|
||||
} else {
|
||||
len.max(bitmap.len()).min(len)
|
||||
};
|
||||
let mut bytes = vec![0u8; out_len];
|
||||
let copy_len = bitmap.len().min(bytes.len());
|
||||
if copy_len > 0 {
|
||||
bytes[..copy_len].copy_from_slice(&bitmap[..copy_len]);
|
||||
}
|
||||
return unsafe { Self::write_bytes_to_user(arg, &bytes).map(Some) };
|
||||
}
|
||||
|
||||
if cmd == EVIOCGABS || Self::ioctl_abs_axis(cmd).is_some() {
|
||||
let axis = Self::ioctl_abs_axis(cmd).unwrap_or(0);
|
||||
let abs_info: AbsInfo = device.abs_info(axis);
|
||||
return unsafe { Self::write_value_to_user(arg, &abs_info).map(Some) };
|
||||
}
|
||||
|
||||
if is_evdev_ioctl(cmd) && ioc_dir(cmd) == IOC_READ {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
|
||||
fn fevent(&mut self, id: usize, flags: EventFlags) -> Result<Option<EventFlags>> {
|
||||
let _ = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
Ok(Some(flags))
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let readiness = match &handle.kind {
|
||||
HandleKind::Root => flags,
|
||||
HandleKind::Device { events, .. } if !events.is_empty() => {
|
||||
flags & EventFlags::EVENT_READ
|
||||
}
|
||||
HandleKind::Device { .. } => EventFlags::empty(),
|
||||
};
|
||||
|
||||
Ok(Some(readiness))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use redox_scheme::SchemeBlockMut;
|
||||
|
||||
use super::EvdevScheme;
|
||||
use crate::types::{
|
||||
AbsInfo, InputEvent, ABS_MT_SLOT, EVIOCGEFFECTS, EVIOCGPROP, EVIOCGRAB, EVIOCRMFF,
|
||||
EVIOCSABS, EVIOCSFF, INPUT_PROP_POINTER,
|
||||
};
|
||||
|
||||
fn open_device(scheme: &mut EvdevScheme, index: usize) -> usize {
|
||||
scheme
|
||||
.open(&format!("event{index}"), 0, 0, 0)
|
||||
.expect("open should succeed")
|
||||
.expect("device handle id")
|
||||
}
|
||||
|
||||
fn read_events(scheme: &mut EvdevScheme, id: usize) -> Option<usize> {
|
||||
let mut buf = vec![0u8; InputEvent::SIZE * 8];
|
||||
scheme.read(id, &mut buf).expect("read should succeed")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eviocgrab_routes_events_only_to_grabbing_handle() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let first = open_device(&mut scheme, 0);
|
||||
let second = open_device(&mut scheme, 0);
|
||||
|
||||
let grab = 1i32;
|
||||
scheme
|
||||
.fcntl(first, EVIOCGRAB as usize, (&grab as *const i32) as usize)
|
||||
.expect("grab should succeed");
|
||||
|
||||
let err = scheme
|
||||
.fcntl(second, EVIOCGRAB as usize, (&grab as *const i32) as usize)
|
||||
.expect_err("second grab should fail");
|
||||
assert_eq!(err.errno, syscall::error::EBUSY);
|
||||
|
||||
scheme.feed_keyboard_event(0x1E, true);
|
||||
|
||||
assert!(read_events(&mut scheme, first).is_some());
|
||||
assert_eq!(read_events(&mut scheme, second), None);
|
||||
|
||||
let release = 0i32;
|
||||
scheme
|
||||
.fcntl(first, EVIOCGRAB as usize, (&release as *const i32) as usize)
|
||||
.expect("release should succeed");
|
||||
|
||||
scheme.feed_keyboard_event(0x30, true);
|
||||
|
||||
assert!(read_events(&mut scheme, first).is_some());
|
||||
assert!(read_events(&mut scheme, second).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_grabbed_handle_releases_grab() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let first = open_device(&mut scheme, 0);
|
||||
let second = open_device(&mut scheme, 0);
|
||||
|
||||
let grab = 1i32;
|
||||
scheme
|
||||
.fcntl(first, EVIOCGRAB as usize, (&grab as *const i32) as usize)
|
||||
.expect("grab should succeed");
|
||||
scheme.close(first).expect("close should succeed");
|
||||
|
||||
scheme.feed_keyboard_event(0x1E, true);
|
||||
|
||||
assert!(read_events(&mut scheme, second).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eviocgrab_is_scoped_to_each_device() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let keyboard = open_device(&mut scheme, 0);
|
||||
let mouse = open_device(&mut scheme, 1);
|
||||
|
||||
let grab = 1i32;
|
||||
|
||||
scheme
|
||||
.fcntl(keyboard, EVIOCGRAB as usize, (&grab as *const i32) as usize)
|
||||
.expect("keyboard grab should succeed");
|
||||
scheme
|
||||
.fcntl(mouse, EVIOCGRAB as usize, (&grab as *const i32) as usize)
|
||||
.expect("mouse grab should also succeed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eviocgprop_reports_pointer_capability_for_pointer_devices() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let keyboard = open_device(&mut scheme, 0);
|
||||
let mouse = open_device(&mut scheme, 1);
|
||||
let touchpad = open_device(&mut scheme, 2);
|
||||
|
||||
let mut keyboard_props = [0u8; 64];
|
||||
let mut mouse_props = [0u8; 64];
|
||||
let mut touchpad_props = [0u8; 64];
|
||||
|
||||
let keyboard_len = scheme
|
||||
.fcntl(
|
||||
keyboard,
|
||||
EVIOCGPROP as usize,
|
||||
keyboard_props.as_mut_ptr() as usize,
|
||||
)
|
||||
.expect("keyboard props ioctl should succeed")
|
||||
.expect("keyboard props length");
|
||||
let mouse_len = scheme
|
||||
.fcntl(
|
||||
mouse,
|
||||
EVIOCGPROP as usize,
|
||||
mouse_props.as_mut_ptr() as usize,
|
||||
)
|
||||
.expect("mouse props ioctl should succeed")
|
||||
.expect("mouse props length");
|
||||
let touchpad_len = scheme
|
||||
.fcntl(
|
||||
touchpad,
|
||||
EVIOCGPROP as usize,
|
||||
touchpad_props.as_mut_ptr() as usize,
|
||||
)
|
||||
.expect("touchpad props ioctl should succeed")
|
||||
.expect("touchpad props length");
|
||||
|
||||
let pointer_mask = 1u8 << INPUT_PROP_POINTER;
|
||||
assert_eq!(keyboard_len, 64);
|
||||
assert_eq!(mouse_len, 64);
|
||||
assert_eq!(touchpad_len, 64);
|
||||
assert_eq!(keyboard_props[0] & pointer_mask, 0);
|
||||
assert_ne!(mouse_props[0] & pointer_mask, 0);
|
||||
assert_ne!(touchpad_props[0] & pointer_mask, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eviocsabs_overrides_default_abs_info() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let touchpad = open_device(&mut scheme, 2);
|
||||
|
||||
let abs_info = AbsInfo {
|
||||
value: 7,
|
||||
minimum: -10,
|
||||
maximum: 1234,
|
||||
fuzz: 2,
|
||||
flat: 3,
|
||||
resolution: 4,
|
||||
};
|
||||
let mut reported = AbsInfo::default();
|
||||
|
||||
scheme
|
||||
.fcntl(
|
||||
touchpad,
|
||||
EVIOCSABS as usize,
|
||||
(&abs_info as *const AbsInfo) as usize,
|
||||
)
|
||||
.expect("set abs info should succeed");
|
||||
scheme
|
||||
.fcntl(
|
||||
touchpad,
|
||||
crate::types::eviocgabs(crate::types::ABS_X as u8) as usize,
|
||||
(&mut reported as *mut AbsInfo) as usize,
|
||||
)
|
||||
.expect("get abs info should succeed");
|
||||
|
||||
assert_eq!(reported.value, abs_info.value);
|
||||
assert_eq!(reported.minimum, abs_info.minimum);
|
||||
assert_eq!(reported.maximum, abs_info.maximum);
|
||||
assert_eq!(reported.fuzz, abs_info.fuzz);
|
||||
assert_eq!(reported.flat, abs_info.flat);
|
||||
assert_eq!(reported.resolution, abs_info.resolution);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multitouch_slot_abs_info_reports_nine_slots() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let touchpad = open_device(&mut scheme, 2);
|
||||
let mut reported = AbsInfo::default();
|
||||
|
||||
scheme
|
||||
.fcntl(
|
||||
touchpad,
|
||||
crate::types::eviocgabs(ABS_MT_SLOT as u8) as usize,
|
||||
(&mut reported as *mut AbsInfo) as usize,
|
||||
)
|
||||
.expect("get mt slot abs info should succeed");
|
||||
|
||||
assert_eq!(reported.minimum, 0);
|
||||
assert_eq!(reported.maximum, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_feedback_ioctls_return_enotty() {
|
||||
let mut scheme = EvdevScheme::new();
|
||||
let mouse = open_device(&mut scheme, 1);
|
||||
|
||||
for cmd in [
|
||||
EVIOCSFF as usize,
|
||||
EVIOCRMFF as usize,
|
||||
EVIOCGEFFECTS as usize,
|
||||
] {
|
||||
let err = scheme
|
||||
.fcntl(mouse, cmd, 0)
|
||||
.expect_err("force feedback ioctl should fail");
|
||||
assert_eq!(err.errno, syscall::error::ENOTTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,226 @@
|
||||
use crate::types::*;
|
||||
|
||||
fn orb_key_to_evdev(orb_key: u8) -> Option<u16> {
|
||||
let mapped = match orb_key {
|
||||
b'1'..=b'9' => KEY_1 + (orb_key - b'1') as u16,
|
||||
b'0' => KEY_0,
|
||||
b'a'..=b'z' => KEY_A + (orb_key - b'a') as u16,
|
||||
b'\n' | b'\r' => KEY_ENTER,
|
||||
b'\t' => KEY_TAB,
|
||||
b' ' => KEY_SPACE,
|
||||
b'\x08' => KEY_BACKSPACE,
|
||||
b'\x1b' => KEY_ESC,
|
||||
b'-' => KEY_MINUS,
|
||||
b'=' => KEY_EQUAL,
|
||||
b'[' => KEY_LEFTBRACE,
|
||||
b']' => KEY_RIGHTBRACE,
|
||||
b'\\' => KEY_BACKSLASH,
|
||||
b';' => KEY_SEMICOLON,
|
||||
b'\'' => KEY_APOSTROPHE,
|
||||
b'`' => KEY_GRAVE,
|
||||
b',' => KEY_COMMA,
|
||||
b'.' => KEY_DOT,
|
||||
b'/' => KEY_SLASH,
|
||||
pub const KEYBOARD_KEY_CODES: &[u16] = &[
|
||||
KEY_ESC,
|
||||
KEY_1,
|
||||
KEY_2,
|
||||
KEY_3,
|
||||
KEY_4,
|
||||
KEY_5,
|
||||
KEY_6,
|
||||
KEY_7,
|
||||
KEY_8,
|
||||
KEY_9,
|
||||
KEY_0,
|
||||
KEY_MINUS,
|
||||
KEY_EQUAL,
|
||||
KEY_BACKSPACE,
|
||||
KEY_TAB,
|
||||
KEY_Q,
|
||||
KEY_W,
|
||||
KEY_E,
|
||||
KEY_R,
|
||||
KEY_T,
|
||||
KEY_Y,
|
||||
KEY_U,
|
||||
KEY_I,
|
||||
KEY_O,
|
||||
KEY_P,
|
||||
KEY_LEFTBRACE,
|
||||
KEY_RIGHTBRACE,
|
||||
KEY_ENTER,
|
||||
KEY_LEFTCTRL,
|
||||
KEY_A,
|
||||
KEY_S,
|
||||
KEY_D,
|
||||
KEY_F,
|
||||
KEY_G,
|
||||
KEY_H,
|
||||
KEY_J,
|
||||
KEY_K,
|
||||
KEY_L,
|
||||
KEY_SEMICOLON,
|
||||
KEY_APOSTROPHE,
|
||||
KEY_GRAVE,
|
||||
KEY_LEFTSHIFT,
|
||||
KEY_BACKSLASH,
|
||||
KEY_Z,
|
||||
KEY_X,
|
||||
KEY_C,
|
||||
KEY_V,
|
||||
KEY_B,
|
||||
KEY_N,
|
||||
KEY_M,
|
||||
KEY_COMMA,
|
||||
KEY_DOT,
|
||||
KEY_SLASH,
|
||||
KEY_RIGHTSHIFT,
|
||||
KEY_KPASTERISK,
|
||||
KEY_LEFTALT,
|
||||
KEY_SPACE,
|
||||
KEY_CAPSLOCK,
|
||||
KEY_F1,
|
||||
KEY_F2,
|
||||
KEY_F3,
|
||||
KEY_F4,
|
||||
KEY_F5,
|
||||
KEY_F6,
|
||||
KEY_F7,
|
||||
KEY_F8,
|
||||
KEY_F9,
|
||||
KEY_F10,
|
||||
KEY_NUMLOCK,
|
||||
KEY_SCROLLLOCK,
|
||||
KEY_KP7,
|
||||
KEY_KP8,
|
||||
KEY_KP9,
|
||||
KEY_KPMINUS,
|
||||
KEY_KP4,
|
||||
KEY_KP5,
|
||||
KEY_KP6,
|
||||
KEY_KPPLUS,
|
||||
KEY_KP1,
|
||||
KEY_KP2,
|
||||
KEY_KP3,
|
||||
KEY_KP0,
|
||||
KEY_KPDOT,
|
||||
KEY_F11,
|
||||
KEY_F12,
|
||||
KEY_KPENTER,
|
||||
KEY_RIGHTCTRL,
|
||||
KEY_KPSLASH,
|
||||
KEY_RIGHTALT,
|
||||
KEY_HOME,
|
||||
KEY_UP,
|
||||
KEY_PAGEUP,
|
||||
KEY_LEFT,
|
||||
KEY_RIGHT,
|
||||
KEY_END,
|
||||
KEY_DOWN,
|
||||
KEY_PAGEDOWN,
|
||||
KEY_INSERT,
|
||||
KEY_DELETE,
|
||||
KEY_LEFTMETA,
|
||||
KEY_RIGHTMETA,
|
||||
KEY_MENU,
|
||||
];
|
||||
|
||||
pub const MOUSE_BUTTON_CODES: &[u16] = &[BTN_LEFT, BTN_RIGHT, BTN_MIDDLE];
|
||||
pub const TOUCHPAD_KEY_CODES: &[u16] = &[BTN_TOUCH, BTN_TOOL_FINGER];
|
||||
|
||||
fn orb_key_to_evdev(scancode: u8) -> Option<u16> {
|
||||
Some(match scancode {
|
||||
0x01 => KEY_ESC,
|
||||
0x02 => KEY_1,
|
||||
0x03 => KEY_2,
|
||||
0x04 => KEY_3,
|
||||
0x05 => KEY_4,
|
||||
0x06 => KEY_5,
|
||||
0x07 => KEY_6,
|
||||
0x08 => KEY_7,
|
||||
0x09 => KEY_8,
|
||||
0x0A => KEY_9,
|
||||
0x0B => KEY_0,
|
||||
0x0C => KEY_MINUS,
|
||||
0x0D => KEY_EQUAL,
|
||||
0x0E => KEY_BACKSPACE,
|
||||
0x0F => KEY_TAB,
|
||||
0x10 => KEY_Q,
|
||||
0x11 => KEY_W,
|
||||
0x12 => KEY_E,
|
||||
0x13 => KEY_R,
|
||||
0x14 => KEY_T,
|
||||
0x15 => KEY_Y,
|
||||
0x16 => KEY_U,
|
||||
0x17 => KEY_I,
|
||||
0x18 => KEY_O,
|
||||
0x19 => KEY_P,
|
||||
0x1A => KEY_LEFTBRACE,
|
||||
0x1B => KEY_RIGHTBRACE,
|
||||
0x1C => KEY_ENTER,
|
||||
0x1D => KEY_LEFTCTRL,
|
||||
0x1E => KEY_A,
|
||||
0x1F => KEY_S,
|
||||
0x20 => KEY_D,
|
||||
0x21 => KEY_F,
|
||||
0x22 => KEY_G,
|
||||
0x23 => KEY_H,
|
||||
0x24 => KEY_J,
|
||||
0x25 => KEY_K,
|
||||
0x26 => KEY_L,
|
||||
0x27 => KEY_SEMICOLON,
|
||||
0x28 => KEY_APOSTROPHE,
|
||||
0x29 => KEY_GRAVE,
|
||||
0x2A => KEY_LEFTSHIFT,
|
||||
0x2B => KEY_BACKSLASH,
|
||||
0x2C => KEY_Z,
|
||||
0x2D => KEY_X,
|
||||
0x2E => KEY_C,
|
||||
0x2F => KEY_V,
|
||||
0x30 => KEY_B,
|
||||
0x31 => KEY_N,
|
||||
0x32 => KEY_M,
|
||||
0x33 => KEY_COMMA,
|
||||
0x34 => KEY_DOT,
|
||||
0x35 => KEY_SLASH,
|
||||
0x36 => KEY_RIGHTSHIFT,
|
||||
0x37 => KEY_KPASTERISK,
|
||||
0x38 => KEY_LEFTALT,
|
||||
0x39 => KEY_SPACE,
|
||||
0x3A => KEY_CAPSLOCK,
|
||||
0x3B => KEY_F1,
|
||||
0x3C => KEY_F2,
|
||||
0x3D => KEY_F3,
|
||||
0x3E => KEY_F4,
|
||||
0x3F => KEY_F5,
|
||||
0x40 => KEY_F6,
|
||||
0x41 => KEY_F7,
|
||||
0x42 => KEY_F8,
|
||||
0x43 => KEY_F9,
|
||||
0x44 => KEY_F10,
|
||||
0x45 => KEY_NUMLOCK,
|
||||
0x46 => KEY_SCROLLLOCK,
|
||||
0x47 => KEY_HOME,
|
||||
0x48 => KEY_UP,
|
||||
0x49 => KEY_PAGEUP,
|
||||
0x4B => KEY_LEFT,
|
||||
0x4D => KEY_RIGHT,
|
||||
0x4F => KEY_END,
|
||||
0x50 => KEY_DOWN,
|
||||
0x51 => KEY_PAGEDOWN,
|
||||
0x52 => KEY_INSERT,
|
||||
0x53 => KEY_DELETE,
|
||||
0x57 => KEY_F11,
|
||||
0x58 => KEY_F12,
|
||||
0x5B => KEY_LEFTMETA,
|
||||
0x5C => KEY_RIGHTMETA,
|
||||
0x5D => KEY_MENU,
|
||||
0x64 => KEY_RIGHTCTRL,
|
||||
0x70 => KEY_KP0,
|
||||
0x71 => KEY_KP1,
|
||||
0x72 => KEY_KP2,
|
||||
0x73 => KEY_KP3,
|
||||
0x74 => KEY_KP4,
|
||||
0x75 => KEY_KP5,
|
||||
0x76 => KEY_KP6,
|
||||
0x77 => KEY_KP7,
|
||||
0x78 => KEY_KP8,
|
||||
0x79 => KEY_KP9,
|
||||
0x7A => KEY_KPDOT,
|
||||
0x7B => KEY_KPMINUS,
|
||||
0x7C => KEY_KPPLUS,
|
||||
0x7D => KEY_KPASTERISK,
|
||||
0x7E => KEY_KPSLASH,
|
||||
0x7F => KEY_KPENTER,
|
||||
_ => return None,
|
||||
};
|
||||
Some(mapped)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn translate_keyboard(orb_key: u8, pressed: bool) -> Vec<InputEvent> {
|
||||
pub fn translate_keyboard(scancode: u8, pressed: bool) -> Vec<InputEvent> {
|
||||
let value = if pressed { 1 } else { 0 };
|
||||
match orb_key_to_evdev(orb_key) {
|
||||
match orb_key_to_evdev(scancode) {
|
||||
Some(code) => vec![
|
||||
InputEvent::new(EV_MSC, MSC_SCAN, i32::from(scancode)),
|
||||
InputEvent::new(EV_KEY, code, value),
|
||||
InputEvent::syn_report(),
|
||||
],
|
||||
@@ -37,19 +228,32 @@ pub fn translate_keyboard(orb_key: u8, pressed: bool) -> Vec<InputEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate_mouse_dx(dx: i32) -> Vec<InputEvent> {
|
||||
vec![InputEvent::new(EV_REL, REL_X, dx), InputEvent::syn_report()]
|
||||
pub fn translate_mouse_motion(dx: i32, dy: i32) -> Vec<InputEvent> {
|
||||
let mut events = Vec::new();
|
||||
if dx != 0 {
|
||||
events.push(InputEvent::new(EV_REL, REL_X, dx));
|
||||
}
|
||||
if dy != 0 {
|
||||
events.push(InputEvent::new(EV_REL, REL_Y, dy));
|
||||
}
|
||||
if !events.is_empty() {
|
||||
events.push(InputEvent::syn_report());
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
pub fn translate_mouse_dy(dy: i32) -> Vec<InputEvent> {
|
||||
vec![InputEvent::new(EV_REL, REL_Y, dy), InputEvent::syn_report()]
|
||||
}
|
||||
|
||||
pub fn translate_mouse_scroll(y: i32) -> Vec<InputEvent> {
|
||||
vec![
|
||||
InputEvent::new(EV_REL, REL_WHEEL, y),
|
||||
InputEvent::syn_report(),
|
||||
]
|
||||
pub fn translate_mouse_scroll(x: i32, y: i32) -> Vec<InputEvent> {
|
||||
let mut events = Vec::new();
|
||||
if x != 0 {
|
||||
events.push(InputEvent::new(EV_REL, REL_HWHEEL, x));
|
||||
}
|
||||
if y != 0 {
|
||||
events.push(InputEvent::new(EV_REL, REL_WHEEL, y));
|
||||
}
|
||||
if !events.is_empty() {
|
||||
events.push(InputEvent::syn_report());
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
pub fn translate_mouse_button(button: usize, pressed: bool) -> Vec<InputEvent> {
|
||||
@@ -68,10 +272,206 @@ pub fn translate_mouse_button(button: usize, pressed: bool) -> Vec<InputEvent> {
|
||||
]
|
||||
}
|
||||
|
||||
pub fn translate_touch(x: i32, y: i32, touching: bool) -> Vec<InputEvent> {
|
||||
let btn = InputEvent::new(EV_KEY, BTN_TOUCH, if touching { 1 } else { 0 });
|
||||
let abs_x = InputEvent::new(EV_ABS, ABS_X, x);
|
||||
let abs_y = InputEvent::new(EV_ABS, ABS_Y, y);
|
||||
let syn = InputEvent::syn_report();
|
||||
vec![btn, abs_x, abs_y, syn]
|
||||
pub fn translate_touchpad_motion(
|
||||
x: i32,
|
||||
y: i32,
|
||||
touching: bool,
|
||||
tracking_id: i32,
|
||||
) -> Vec<InputEvent> {
|
||||
let mut events = vec![
|
||||
InputEvent::new(EV_ABS, ABS_X, x),
|
||||
InputEvent::new(EV_ABS, ABS_Y, y),
|
||||
];
|
||||
|
||||
if touching {
|
||||
events.extend_from_slice(&[
|
||||
InputEvent::new(EV_ABS, ABS_MT_SLOT, 0),
|
||||
InputEvent::new(EV_ABS, ABS_MT_TRACKING_ID, tracking_id),
|
||||
InputEvent::new(EV_ABS, ABS_MT_POSITION_X, x),
|
||||
InputEvent::new(EV_ABS, ABS_MT_POSITION_Y, y),
|
||||
InputEvent::new(EV_ABS, ABS_PRESSURE, 255),
|
||||
InputEvent::new(EV_ABS, ABS_MT_TOUCH_MAJOR, 1),
|
||||
]);
|
||||
}
|
||||
|
||||
events.push(InputEvent::syn_report());
|
||||
events
|
||||
}
|
||||
|
||||
pub fn translate_touchpad_contact(
|
||||
x: i32,
|
||||
y: i32,
|
||||
touching: bool,
|
||||
tracking_id: i32,
|
||||
) -> Vec<InputEvent> {
|
||||
let mut events = vec![
|
||||
InputEvent::new(EV_ABS, ABS_X, x),
|
||||
InputEvent::new(EV_ABS, ABS_Y, y),
|
||||
InputEvent::new(EV_ABS, ABS_MT_SLOT, 0),
|
||||
];
|
||||
|
||||
if touching {
|
||||
events.extend_from_slice(&[
|
||||
InputEvent::new(EV_KEY, BTN_TOUCH, 1),
|
||||
InputEvent::new(EV_KEY, BTN_TOOL_FINGER, 1),
|
||||
InputEvent::new(EV_ABS, ABS_MT_TRACKING_ID, tracking_id),
|
||||
InputEvent::new(EV_ABS, ABS_MT_POSITION_X, x),
|
||||
InputEvent::new(EV_ABS, ABS_MT_POSITION_Y, y),
|
||||
InputEvent::new(EV_ABS, ABS_PRESSURE, 255),
|
||||
InputEvent::new(EV_ABS, ABS_MT_TOUCH_MAJOR, 1),
|
||||
]);
|
||||
} else {
|
||||
events.extend_from_slice(&[
|
||||
InputEvent::new(EV_ABS, ABS_MT_TRACKING_ID, -1),
|
||||
InputEvent::new(EV_ABS, ABS_PRESSURE, 0),
|
||||
InputEvent::new(EV_ABS, ABS_MT_TOUCH_MAJOR, 0),
|
||||
InputEvent::new(EV_KEY, BTN_TOUCH, 0),
|
||||
InputEvent::new(EV_KEY, BTN_TOOL_FINGER, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
events.push(InputEvent::syn_report());
|
||||
events
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
translate_keyboard, translate_mouse_button, translate_mouse_motion, translate_mouse_scroll,
|
||||
translate_touchpad_motion,
|
||||
};
|
||||
use crate::types::*;
|
||||
|
||||
fn has_event(events: &[InputEvent], event_type: u16, code: u16, value: i32) -> bool {
|
||||
events.iter().any(|event| {
|
||||
event.event_type == event_type && event.code == code && event.value == value
|
||||
})
|
||||
}
|
||||
|
||||
fn has_event_code(events: &[InputEvent], event_type: u16, code: u16) -> bool {
|
||||
events
|
||||
.iter()
|
||||
.any(|event| event.event_type == event_type && event.code == code)
|
||||
}
|
||||
|
||||
fn event_index(events: &[InputEvent], event_type: u16, code: u16, value: i32) -> Option<usize> {
|
||||
events.iter().position(|event| {
|
||||
event.event_type == event_type && event.code == code && event.value == value
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyboard_press_translates_to_key_a_down() {
|
||||
let events = translate_keyboard(0x1E, true);
|
||||
|
||||
assert!(has_event(&events, EV_KEY, KEY_A, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyboard_release_translates_to_key_a_up() {
|
||||
let events = translate_keyboard(0x1E, false);
|
||||
|
||||
assert!(has_event(&events, EV_KEY, KEY_A, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyboard_events_include_scan_before_key() {
|
||||
let events = translate_keyboard(0x1E, true);
|
||||
let scan_index = event_index(&events, EV_MSC, MSC_SCAN, 0x1E).unwrap();
|
||||
let key_index = event_index(&events, EV_KEY, KEY_A, 1).unwrap();
|
||||
|
||||
assert!(scan_index < key_index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyboard_events_end_with_syn_report() {
|
||||
let events = translate_keyboard(0x1E, true);
|
||||
|
||||
let last = events
|
||||
.last()
|
||||
.expect("keyboard translation should emit events");
|
||||
assert_eq!(last.event_type, EV_SYN);
|
||||
assert_eq!(last.code, SYN_REPORT);
|
||||
assert_eq!(last.value, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_keyboard_scancode_returns_empty_events() {
|
||||
let events = translate_keyboard(0xFF, true);
|
||||
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_motion_x_only_emits_x_and_syn() {
|
||||
let events = translate_mouse_motion(10, 0);
|
||||
|
||||
assert!(has_event(&events, EV_REL, REL_X, 10));
|
||||
assert!(!has_event_code(&events, EV_REL, REL_Y));
|
||||
assert_eq!(
|
||||
events
|
||||
.last()
|
||||
.map(|event| (event.event_type, event.code, event.value)),
|
||||
Some((EV_SYN, SYN_REPORT, 0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_motion_x_and_y_emits_both_axes() {
|
||||
let events = translate_mouse_motion(5, -3);
|
||||
|
||||
assert!(has_event(&events, EV_REL, REL_X, 5));
|
||||
assert!(has_event(&events, EV_REL, REL_Y, -3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_motion_zero_returns_empty_events() {
|
||||
let events = translate_mouse_motion(0, 0);
|
||||
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_scroll_up_emits_vertical_wheel() {
|
||||
let events = translate_mouse_scroll(0, 1);
|
||||
|
||||
assert!(has_event(&events, EV_REL, REL_WHEEL, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_scroll_horizontal_emits_horizontal_wheel() {
|
||||
let events = translate_mouse_scroll(2, 0);
|
||||
|
||||
assert!(has_event(&events, EV_REL, REL_HWHEEL, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_button_left_press_emits_btn_left_down() {
|
||||
let events = translate_mouse_button(0, true);
|
||||
|
||||
assert!(has_event(&events, EV_KEY, BTN_LEFT, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_button_right_release_emits_btn_right_up() {
|
||||
let events = translate_mouse_button(2, false);
|
||||
|
||||
assert!(has_event(&events, EV_KEY, BTN_RIGHT, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_mouse_button_returns_empty_events() {
|
||||
let events = translate_mouse_button(10, true);
|
||||
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touchpad_motion_emits_absolute_contact_details() {
|
||||
let events = translate_touchpad_motion(100, 200, true, 1);
|
||||
|
||||
assert!(has_event(&events, EV_ABS, ABS_X, 100));
|
||||
assert!(has_event(&events, EV_ABS, ABS_Y, 200));
|
||||
assert!(has_event(&events, EV_ABS, ABS_MT_TRACKING_ID, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
/// Linux-compatible evdev event types and constants.
|
||||
///
|
||||
/// These mirror the Linux kernel's `include/uapi/linux/input.h` definitions
|
||||
/// so that clients expecting evdev semantics can work on Redox.
|
||||
use std::mem::size_of;
|
||||
|
||||
// Event types
|
||||
pub const EV_SYN: u16 = 0x00;
|
||||
@@ -9,14 +12,51 @@ pub const EV_KEY: u16 = 0x01;
|
||||
pub const EV_REL: u16 = 0x02;
|
||||
pub const EV_ABS: u16 = 0x03;
|
||||
pub const EV_MSC: u16 = 0x04;
|
||||
pub const EV_SW: u16 = 0x05;
|
||||
pub const EV_LED: u16 = 0x11;
|
||||
pub const EV_SND: u16 = 0x12;
|
||||
pub const EV_REP: u16 = 0x14;
|
||||
pub const EV_FF: u16 = 0x15;
|
||||
|
||||
// Synchronization events
|
||||
pub const SYN_REPORT: u16 = 0;
|
||||
pub const SYN_CONFIG: u16 = 1;
|
||||
|
||||
// Misc events
|
||||
pub const MSC_SCAN: u16 = 0x04;
|
||||
|
||||
// Switch events
|
||||
pub const SW_LID: u16 = 0x00;
|
||||
pub const SW_TABLET_MODE: u16 = 0x01;
|
||||
pub const SW_HEADPHONE_INSERT: u16 = 0x02;
|
||||
pub const SW_RFKILL_ALL: u16 = 0x03;
|
||||
pub const SW_MICROPHONE_INSERT: u16 = 0x04;
|
||||
pub const SW_DOCK: u16 = 0x05;
|
||||
pub const SW_LINEOUT_INSERT: u16 = 0x06;
|
||||
pub const SW_JACK_PHYSICAL_INSERT: u16 = 0x07;
|
||||
pub const SW_VIDEOOUT_INSERT: u16 = 0x08;
|
||||
pub const SW_CAMERA_LENS_COVER: u16 = 0x09;
|
||||
pub const SW_KEYPAD_SLIDE: u16 = 0x0a;
|
||||
pub const SW_FRONT_PROXIMITY: u16 = 0x0b;
|
||||
pub const SW_ROTATE_LOCK: u16 = 0x0c;
|
||||
pub const SW_LINEIN_INSERT: u16 = 0x0d;
|
||||
pub const SW_MUTE_DEVICE: u16 = 0x0e;
|
||||
pub const SW_PEN_INSERTED: u16 = 0x0f;
|
||||
pub const SW_MACHINE_COVER: u16 = 0x10;
|
||||
|
||||
// Input properties
|
||||
pub const INPUT_PROP_POINTER: u16 = 0x00;
|
||||
pub const INPUT_PROP_DIRECT: u16 = 0x01;
|
||||
|
||||
// LEDs
|
||||
pub const LED_NUML: u16 = 0x00;
|
||||
pub const LED_CAPSL: u16 = 0x01;
|
||||
pub const LED_SCROLLL: u16 = 0x02;
|
||||
|
||||
// Repeat settings
|
||||
pub const REP_DELAY: u16 = 0x00;
|
||||
pub const REP_PERIOD: u16 = 0x01;
|
||||
|
||||
// Relative axes
|
||||
pub const REL_X: u16 = 0x00;
|
||||
pub const REL_Y: u16 = 0x01;
|
||||
@@ -107,8 +147,22 @@ pub const KEY_F9: u16 = 67;
|
||||
pub const KEY_F10: u16 = 68;
|
||||
pub const KEY_NUMLOCK: u16 = 69;
|
||||
pub const KEY_SCROLLLOCK: u16 = 70;
|
||||
pub const KEY_KP7: u16 = 71;
|
||||
pub const KEY_KP8: u16 = 72;
|
||||
pub const KEY_KP9: u16 = 73;
|
||||
pub const KEY_KPMINUS: u16 = 74;
|
||||
pub const KEY_KP4: u16 = 75;
|
||||
pub const KEY_KP5: u16 = 76;
|
||||
pub const KEY_KP6: u16 = 77;
|
||||
pub const KEY_KPPLUS: u16 = 78;
|
||||
pub const KEY_KP1: u16 = 79;
|
||||
pub const KEY_KP2: u16 = 80;
|
||||
pub const KEY_KP3: u16 = 81;
|
||||
pub const KEY_KP0: u16 = 82;
|
||||
pub const KEY_KPDOT: u16 = 83;
|
||||
pub const KEY_F11: u16 = 87;
|
||||
pub const KEY_F12: u16 = 88;
|
||||
pub const KEY_KPENTER: u16 = 96;
|
||||
|
||||
pub const KEY_HOME: u16 = 102;
|
||||
pub const KEY_UP: u16 = 103;
|
||||
@@ -120,6 +174,8 @@ pub const KEY_DOWN: u16 = 108;
|
||||
pub const KEY_PAGEDOWN: u16 = 109;
|
||||
pub const KEY_INSERT: u16 = 110;
|
||||
pub const KEY_DELETE: u16 = 111;
|
||||
pub const KEY_KPSLASH: u16 = 98;
|
||||
pub const KEY_MENU: u16 = 139;
|
||||
|
||||
pub const KEY_LEFTMETA: u16 = 125;
|
||||
pub const KEY_RIGHTMETA: u16 = 126;
|
||||
@@ -145,6 +201,95 @@ pub const BUS_VIRTUAL: u16 = 0x06;
|
||||
// Evdev version
|
||||
pub const EV_VERSION: i32 = 0x010001;
|
||||
|
||||
// ioctl constants
|
||||
pub const EVIOCGVERSION: u64 = 0x80044501;
|
||||
pub const EVIOCGID: u64 = 0x80084502;
|
||||
pub const EVIOCGNAME: u64 = 0x80000000 | 0x45 << 8;
|
||||
pub const EVIOCGBIT: u64 = 0x80000000 | 0x45 << 8;
|
||||
pub const EVIOCGABS: u64 = 0x80184540;
|
||||
pub const EVIOCSABS: u64 = 0x401845c0;
|
||||
pub const EVIOCGRAB: u64 = 0x40044590;
|
||||
pub const EVIOCSCLOCKID: u64 = 0x400445a0;
|
||||
pub const EVIOCGPROP: u64 = 0x804045a0;
|
||||
pub const EVIOCSFF: u64 = 0x402c4580;
|
||||
pub const EVIOCRMFF: u64 = 0x40044581;
|
||||
pub const EVIOCGEFFECTS: u64 = 0x80044584;
|
||||
|
||||
// EVIOCGKEY returns the current state of all keys (bitmask of pressed keys)
|
||||
pub const EVIOCGKEY: u64 = (IOC_READ << IOC_DIRSHIFT)
|
||||
| ((KEY_MAX / 8 + 1) as u64) << IOC_SIZESHIFT
|
||||
| (EVDEV_IOCTL_TYPE << IOC_TYPESHIFT)
|
||||
| 0x18;
|
||||
|
||||
// EVIOCGLED returns the current state of all LEDs (bitmask of lit LEDs)
|
||||
pub const EVIOCGLED: u64 = (IOC_READ << IOC_DIRSHIFT)
|
||||
| ((LED_MAX / 8 + 1) as u64) << IOC_SIZESHIFT
|
||||
| (EVDEV_IOCTL_TYPE << IOC_TYPESHIFT)
|
||||
| 0x19;
|
||||
|
||||
// Key and LED state bitmaps
|
||||
pub const KEY_MAX: usize = 0x2FF; // 767 bits = 96 bytes
|
||||
pub const LED_MAX: usize = 0x0F; // 15 bits = 2 bytes
|
||||
|
||||
pub const IOC_NRBITS: u64 = 8;
|
||||
pub const IOC_TYPEBITS: u64 = 8;
|
||||
pub const IOC_SIZEBITS: u64 = 14;
|
||||
|
||||
pub const IOC_NRMASK: u64 = (1 << IOC_NRBITS) - 1;
|
||||
pub const IOC_TYPEMASK: u64 = (1 << IOC_TYPEBITS) - 1;
|
||||
pub const IOC_SIZEMASK: u64 = (1 << IOC_SIZEBITS) - 1;
|
||||
|
||||
pub const IOC_NRSHIFT: u64 = 0;
|
||||
pub const IOC_TYPESHIFT: u64 = IOC_NRSHIFT + IOC_NRBITS;
|
||||
pub const IOC_SIZESHIFT: u64 = IOC_TYPESHIFT + IOC_TYPEBITS;
|
||||
pub const IOC_DIRSHIFT: u64 = IOC_SIZESHIFT + IOC_SIZEBITS;
|
||||
|
||||
pub const IOC_NONE: u64 = 0;
|
||||
pub const IOC_WRITE: u64 = 1;
|
||||
pub const IOC_READ: u64 = 2;
|
||||
pub const EVDEV_IOCTL_TYPE: u64 = 0x45;
|
||||
|
||||
pub const fn ioc_dir(cmd: u64) -> u64 {
|
||||
cmd >> IOC_DIRSHIFT
|
||||
}
|
||||
|
||||
pub const fn ioc_type(cmd: u64) -> u64 {
|
||||
(cmd >> IOC_TYPESHIFT) & IOC_TYPEMASK
|
||||
}
|
||||
|
||||
pub const fn ioc_nr(cmd: u64) -> u64 {
|
||||
(cmd >> IOC_NRSHIFT) & IOC_NRMASK
|
||||
}
|
||||
|
||||
pub const fn ioc_size(cmd: u64) -> usize {
|
||||
((cmd >> IOC_SIZESHIFT) & IOC_SIZEMASK) as usize
|
||||
}
|
||||
|
||||
pub const fn is_evdev_ioctl(cmd: u64) -> bool {
|
||||
ioc_type(cmd) == EVDEV_IOCTL_TYPE
|
||||
}
|
||||
|
||||
pub const fn eviocgname(len: usize) -> u64 {
|
||||
(IOC_READ << IOC_DIRSHIFT)
|
||||
| ((len as u64) << IOC_SIZESHIFT)
|
||||
| (EVDEV_IOCTL_TYPE << IOC_TYPESHIFT)
|
||||
| 0x06
|
||||
}
|
||||
|
||||
pub const fn eviocgbit(ev: u8, len: usize) -> u64 {
|
||||
(IOC_READ << IOC_DIRSHIFT)
|
||||
| ((len as u64) << IOC_SIZESHIFT)
|
||||
| (EVDEV_IOCTL_TYPE << IOC_TYPESHIFT)
|
||||
| ((0x20 + ev as u64) << IOC_NRSHIFT)
|
||||
}
|
||||
|
||||
pub const fn eviocgabs(axis: u8) -> u64 {
|
||||
(IOC_READ << IOC_DIRSHIFT)
|
||||
| ((size_of::<AbsInfo>() as u64) << IOC_SIZESHIFT)
|
||||
| (EVDEV_IOCTL_TYPE << IOC_TYPESHIFT)
|
||||
| ((0x40 + axis as u64) << IOC_NRSHIFT)
|
||||
}
|
||||
|
||||
/// Linux `struct input_event` layout (24 bytes).
|
||||
///
|
||||
/// Matches the kernel binary layout:
|
||||
@@ -203,6 +348,17 @@ pub struct InputId {
|
||||
pub version: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct AbsInfo {
|
||||
pub value: i32,
|
||||
pub minimum: i32,
|
||||
pub maximum: i32,
|
||||
pub fuzz: i32,
|
||||
pub flat: i32,
|
||||
pub resolution: i32,
|
||||
}
|
||||
|
||||
fn now_timestamp() -> (u64, u64) {
|
||||
use std::time::SystemTime;
|
||||
let dur = SystemTime::now()
|
||||
|
||||
Reference in New Issue
Block a user