use std::fs::File;
use std::io::{self, Read};
use std::os::unix::io::AsRawFd;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseButtons {
pub left: bool,
pub right: bool,
pub middle: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct MouseEvent {
pub dx: i8,
pub dy: i8,
pub buttons: MouseButtons,
#[allow(dead_code)]
pub scroll: i8,
#[allow(dead_code)]
pub scroll_h: i8,
}
const EV_REL: u16 = 0x02; const EV_KEY: u16 = 0x01;
const REL_X: u16 = 0x00;
const REL_Y: u16 = 0x01;
const REL_WHEEL: u16 = 0x08; const REL_HWHEEL: u16 = 0x06;
const BTN_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111;
const BTN_MIDDLE: u16 = 0x112;
#[cfg(target_pointer_width = "64")]
const INPUT_EVENT_SIZE: usize = 24;
#[cfg(target_pointer_width = "32")]
const INPUT_EVENT_SIZE: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Protocol {
Ps2, InputEvent, }
pub struct MouseInput {
file: File,
protocol: Protocol,
dx_accumulator: i32,
dy_accumulator: i32,
scroll_accumulator: i32, scroll_h_accumulator: i32, buttons: MouseButtons,
button_changed: bool, }
impl MouseInput {
pub fn new(device_path: Option<&str>) -> io::Result<Self> {
if let Some(path) = device_path {
let file = File::open(path)?;
let protocol = if path.contains("mice") {
Protocol::Ps2
} else {
Protocol::InputEvent
};
Self::setup_device(file, protocol)
} else {
if let Ok(file) = File::open("/dev/input/mice") {
Self::setup_device(file, Protocol::Ps2)
} else {
Self::find_event_device()
}
}
}
fn find_event_device() -> io::Result<Self> {
for i in 0..16 {
let path = format!("/dev/input/event{}", i);
if Path::new(&path).exists() {
if let Ok(file) = File::open(&path) {
if let Ok(input) = Self::setup_device(file, Protocol::InputEvent) {
return Ok(input);
}
}
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"No mouse input device found (/dev/input/mice or /dev/input/event*)",
))
}
fn setup_device(file: File, protocol: Protocol) -> io::Result<Self> {
unsafe {
let flags = libc::fcntl(file.as_raw_fd(), libc::F_GETFL, 0);
if flags < 0 {
return Err(io::Error::last_os_error());
}
if libc::fcntl(file.as_raw_fd(), libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(Self {
file,
protocol,
dx_accumulator: 0,
dy_accumulator: 0,
scroll_accumulator: 0,
scroll_h_accumulator: 0,
buttons: MouseButtons {
left: false,
right: false,
middle: false,
},
button_changed: false,
})
}
pub fn has_event(&self) -> bool {
let fd = self.file.as_raw_fd();
let mut fds = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
unsafe {
libc::poll(&mut fds as *mut libc::pollfd, 1, 0) > 0 && (fds.revents & libc::POLLIN) != 0
}
}
pub fn read_event(&mut self) -> io::Result<Option<MouseEvent>> {
if !self.has_event() {
return Ok(None);
}
match self.protocol {
Protocol::Ps2 => self.read_ps2_event(),
Protocol::InputEvent => self.read_input_event(),
}
}
fn read_ps2_event(&mut self) -> io::Result<Option<MouseEvent>> {
let mut buf = [0u8; 4];
match self.file.read(&mut buf) {
Ok(n) if n >= 3 => {
let buttons = MouseButtons {
left: (buf[0] & 0x01) != 0,
right: (buf[0] & 0x02) != 0,
middle: (buf[0] & 0x04) != 0,
};
let dx = buf[1] as i8;
let dy = buf[2] as i8;
let scroll = if n == 4 {
let wheel_byte = buf[3] as i8;
-wheel_byte
} else {
0
};
Ok(Some(MouseEvent {
dx,
dy,
buttons,
scroll,
scroll_h: 0,
}))
}
Ok(_) => {
Ok(None)
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(e),
}
}
fn read_input_event(&mut self) -> io::Result<Option<MouseEvent>> {
let mut buf = [0u8; INPUT_EVENT_SIZE];
#[cfg(target_pointer_width = "64")]
const TYPE_OFFSET: usize = 16;
#[cfg(target_pointer_width = "32")]
const TYPE_OFFSET: usize = 8;
loop {
match self.file.read(&mut buf) {
Ok(n) if n == INPUT_EVENT_SIZE => {
let type_ = u16::from_ne_bytes([buf[TYPE_OFFSET], buf[TYPE_OFFSET + 1]]);
let code = u16::from_ne_bytes([buf[TYPE_OFFSET + 2], buf[TYPE_OFFSET + 3]]);
let value = i32::from_ne_bytes([
buf[TYPE_OFFSET + 4],
buf[TYPE_OFFSET + 5],
buf[TYPE_OFFSET + 6],
buf[TYPE_OFFSET + 7],
]);
match type_ {
EV_REL => {
match code {
REL_X => self.dx_accumulator += value,
REL_Y => self.dy_accumulator += value,
REL_WHEEL => self.scroll_accumulator += value,
REL_HWHEEL => self.scroll_h_accumulator += value,
_ => {}
}
}
EV_KEY => {
let old_buttons = self.buttons;
match code {
BTN_LEFT => self.buttons.left = value != 0,
BTN_RIGHT => self.buttons.right = value != 0,
BTN_MIDDLE => self.buttons.middle = value != 0,
_ => {}
}
if self.buttons.left != old_buttons.left
|| self.buttons.right != old_buttons.right
|| self.buttons.middle != old_buttons.middle
{
self.button_changed = true;
}
}
_ => {}
}
let has_data = self.dx_accumulator != 0
|| self.dy_accumulator != 0
|| self.scroll_accumulator != 0
|| self.scroll_h_accumulator != 0
|| self.button_changed;
if has_data {
let dx = self.dx_accumulator.clamp(-127, 127) as i8;
let dy = self.dy_accumulator.clamp(-127, 127) as i8;
let scroll = self.scroll_accumulator.clamp(-127, 127) as i8;
let scroll_h = self.scroll_h_accumulator.clamp(-127, 127) as i8;
self.dx_accumulator = 0;
self.dy_accumulator = 0;
self.scroll_accumulator = 0;
self.scroll_h_accumulator = 0;
self.button_changed = false;
return Ok(Some(MouseEvent {
dx,
dy,
buttons: self.buttons,
scroll,
scroll_h,
}));
}
}
Ok(_) => {
return Ok(None);
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
let has_data = self.dx_accumulator != 0
|| self.dy_accumulator != 0
|| self.scroll_accumulator != 0
|| self.scroll_h_accumulator != 0
|| self.button_changed;
if has_data {
let dx = self.dx_accumulator.clamp(-127, 127) as i8;
let dy = self.dy_accumulator.clamp(-127, 127) as i8;
let scroll = self.scroll_accumulator.clamp(-127, 127) as i8;
let scroll_h = self.scroll_h_accumulator.clamp(-127, 127) as i8;
self.dx_accumulator = 0;
self.dy_accumulator = 0;
self.scroll_accumulator = 0;
self.scroll_h_accumulator = 0;
self.button_changed = false;
return Ok(Some(MouseEvent {
dx,
dy,
buttons: self.buttons,
scroll,
scroll_h,
}));
}
return Ok(None);
}
Err(e) => return Err(e),
}
}
}
}
pub struct CursorTracker {
pub x: usize,
pub y: usize,
max_x: usize,
max_y: usize,
sensitivity: f32,
invert_x: bool,
invert_y: bool,
}
impl CursorTracker {
pub fn new(max_x: usize, max_y: usize, invert_x: bool, invert_y: bool) -> Self {
Self {
x: max_x / 2,
y: max_y / 2,
max_x,
max_y,
sensitivity: 1.0,
invert_x,
invert_y,
}
}
pub fn update(&mut self, dx: i8, dy: i8) {
let dx = if self.invert_x { -dx } else { dx };
let dy = if self.invert_y { -dy } else { dy };
let new_x = (self.x as i32 + (dx as f32 * self.sensitivity) as i32)
.max(0)
.min(self.max_x as i32 - 1);
let new_y = (self.y as i32 + (dy as f32 * self.sensitivity) as i32)
.max(0)
.min(self.max_y as i32 - 1);
self.x = new_x as usize;
self.y = new_y as usize;
}
#[allow(dead_code)]
pub fn set_position(&mut self, x: usize, y: usize) {
self.x = x.min(self.max_x - 1);
self.y = y.min(self.max_y - 1);
}
#[allow(dead_code)]
pub fn set_bounds(&mut self, max_x: usize, max_y: usize) {
self.max_x = max_x;
self.max_y = max_y;
self.x = self.x.min(max_x - 1);
self.y = self.y.min(max_y - 1);
}
#[allow(dead_code)]
pub fn set_sensitivity(&mut self, sensitivity: f32) {
self.sensitivity = sensitivity.clamp(0.1, 10.0);
}
}