use super::types::{MouseButtons, RawMouseEvent};
use std::fs::File;
use std::io::{self, Read};
use std::os::unix::io::AsRawFd;
use std::path::Path;
pub struct RawMouseInput {
file: File,
#[cfg(target_os = "freebsd")]
_protocol: FreeBsdProtocol,
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
_protocol: WsconsProtocol,
#[allow(dead_code)] buttons: MouseButtons,
}
#[cfg(target_os = "freebsd")]
#[derive(Debug, Clone, Copy)]
enum FreeBsdProtocol {
Sysmouse,
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
#[derive(Debug, Clone, Copy)]
enum WsconsProtocol {
Wscons,
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
mod wscons {
pub const WSCONS_EVENT_MOUSE_DELTA_X: u32 = 0;
pub const WSCONS_EVENT_MOUSE_DELTA_Y: u32 = 1;
pub const WSCONS_EVENT_MOUSE_DELTA_Z: u32 = 2; pub const WSCONS_EVENT_MOUSE_DELTA_W: u32 = 3; pub const WSCONS_EVENT_MOUSE_ABSOLUTE_X: u32 = 4;
pub const WSCONS_EVENT_MOUSE_ABSOLUTE_Y: u32 = 5;
pub const WSCONS_EVENT_MOUSE_ABSOLUTE_Z: u32 = 6;
pub const WSCONS_EVENT_MOUSE_ABSOLUTE_W: u32 = 7;
pub const WSCONS_EVENT_MOUSE_UP: u32 = 8;
pub const WSCONS_EVENT_MOUSE_DOWN: u32 = 9;
#[cfg(target_pointer_width = "64")]
pub const WSCONS_EVENT_SIZE: usize = 24;
#[cfg(target_pointer_width = "32")]
pub const WSCONS_EVENT_SIZE: usize = 16;
}
impl RawMouseInput {
#[cfg(target_os = "freebsd")]
pub fn new(device_path: Option<&str>) -> io::Result<Self> {
let path = device_path.unwrap_or("/dev/sysmouse");
if !Path::new(path).exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Mouse device not found: {}", path),
));
}
let file = File::open(path)?;
Self::setup_device(file, FreeBsdProtocol::Sysmouse)
}
#[cfg(target_os = "netbsd")]
pub fn new(device_path: Option<&str>) -> io::Result<Self> {
let path = device_path.unwrap_or("/dev/wsmouse0");
if !Path::new(path).exists() {
let alternatives = ["/dev/wsmouse", "/dev/wsmouse1"];
let found_path = alternatives
.iter()
.find(|p| Path::new(p).exists())
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"No wscons mouse device found (/dev/wsmouse*)",
)
})?;
let file = File::open(found_path)?;
return Self::setup_device(file, WsconsProtocol::Wscons);
}
let file = File::open(path)?;
Self::setup_device(file, WsconsProtocol::Wscons)
}
#[cfg(target_os = "openbsd")]
pub fn new(device_path: Option<&str>) -> io::Result<Self> {
let path = device_path.unwrap_or("/dev/wsmouse");
if !Path::new(path).exists() {
let alternatives = ["/dev/wsmouse0", "/dev/wsmouse1"];
let found_path = alternatives
.iter()
.find(|p| Path::new(p).exists())
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"No wscons mouse device found (/dev/wsmouse*)",
)
})?;
let file = File::open(found_path)?;
return Self::setup_device(file, WsconsProtocol::Wscons);
}
let file = File::open(path)?;
Self::setup_device(file, WsconsProtocol::Wscons)
}
#[cfg(target_os = "freebsd")]
fn setup_device(file: File, protocol: FreeBsdProtocol) -> 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: protocol,
buttons: MouseButtons::default(),
})
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
fn setup_device(file: File, protocol: WsconsProtocol) -> 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: protocol,
buttons: MouseButtons::default(),
})
}
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<RawMouseEvent>> {
if !self.has_event() {
return Ok(None);
}
#[cfg(target_os = "freebsd")]
{
self.read_sysmouse_event()
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
{
self.read_wscons_event()
}
}
#[cfg(target_os = "freebsd")]
fn read_sysmouse_event(&mut self) -> io::Result<Option<RawMouseEvent>> {
let mut buf = [0u8; 8]; match self.file.read(&mut buf) {
Ok(n) if n >= 5 => {
let btn_byte = buf[0];
let buttons = MouseButtons {
left: (btn_byte & 0x04) == 0, middle: (btn_byte & 0x02) == 0, right: (btn_byte & 0x01) == 0, };
let dx = buf[1] as i8;
let dy = buf[2] as i8;
let scroll = if n >= 5 {
let scroll_raw = i16::from_le_bytes([buf[3], buf[4]]);
scroll_raw.clamp(-127, 127) as i8
} else {
0
};
Ok(Some(RawMouseEvent {
dx,
dy,
buttons,
scroll,
scroll_h: 0,
}))
}
Ok(_) => Ok(None),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(e),
}
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
fn read_wscons_event(&mut self) -> io::Result<Option<RawMouseEvent>> {
use wscons::*;
let mut buf = [0u8; WSCONS_EVENT_SIZE];
let mut dx_accum: i32 = 0;
let mut dy_accum: i32 = 0;
let mut scroll_accum: i32 = 0;
let mut scroll_h_accum: i32 = 0;
let mut had_event = false;
loop {
match self.file.read(&mut buf) {
Ok(n) if n == WSCONS_EVENT_SIZE => {
let event_type = u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]);
let value = i32::from_ne_bytes([buf[4], buf[5], buf[6], buf[7]]);
match event_type {
WSCONS_EVENT_MOUSE_DELTA_X => {
dx_accum += value;
had_event = true;
}
WSCONS_EVENT_MOUSE_DELTA_Y => {
dy_accum -= value;
had_event = true;
}
WSCONS_EVENT_MOUSE_DELTA_Z => {
scroll_accum += value;
had_event = true;
}
WSCONS_EVENT_MOUSE_DELTA_W => {
scroll_h_accum += value;
had_event = true;
}
WSCONS_EVENT_MOUSE_DOWN => {
match value {
0 => self.buttons.left = true,
1 => self.buttons.middle = true,
2 => self.buttons.right = true,
_ => {}
}
had_event = true;
}
WSCONS_EVENT_MOUSE_UP => {
match value {
0 => self.buttons.left = false,
1 => self.buttons.middle = false,
2 => self.buttons.right = false,
_ => {}
}
had_event = true;
}
_ => {}
}
}
Ok(_) => break,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(e) => return Err(e),
}
}
if had_event {
Ok(Some(RawMouseEvent {
dx: dx_accum.clamp(-127, 127) as i8,
dy: dy_accum.clamp(-127, 127) as i8,
buttons: self.buttons,
scroll: scroll_accum.clamp(-127, 127) as i8,
scroll_h: scroll_h_accum.clamp(-127, 127) as i8,
}))
} else {
Ok(None)
}
}
}