mod cursor;
mod types;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
mod bsd;
pub use cursor::CursorTracker;
#[cfg(unix)]
pub use types::RawMouseEvent;
pub use types::{MouseButtons, MouseInputMode};
#[cfg(target_os = "linux")]
pub use linux::RawMouseInput;
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
pub use bsd::RawMouseInput;
use crossterm::event::MouseEvent;
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use crossterm::event::{KeyModifiers, MouseButton, MouseEventKind};
use std::collections::VecDeque;
use std::io;
pub struct MouseInputManager {
mode: MouseInputMode,
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
raw_input: Option<RawMouseInput>,
cursor: CursorTracker,
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
prev_buttons: MouseButtons,
event_queue: VecDeque<MouseEvent>,
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
swap_buttons: bool,
}
impl MouseInputManager {
#[allow(clippy::too_many_arguments)]
#[allow(unused_variables)] pub fn new(
mode: MouseInputMode,
cols: u16,
rows: u16,
device_path: Option<&str>,
invert_x: bool,
invert_y: bool,
swap_buttons: bool,
sensitivity_override: Option<f32>,
) -> io::Result<Self> {
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
let raw_input = if mode.uses_raw_input() {
match RawMouseInput::new(device_path) {
Ok(input) => Some(input),
Err(e) => {
eprintln!("Warning: Could not open mouse device: {}", e);
None
}
}
} else {
None
};
let mut cursor = CursorTracker::new(cols as usize, rows as usize, invert_x, invert_y);
if let Some(sens) = sensitivity_override {
cursor.set_sensitivity(sens);
}
Ok(Self {
mode,
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
raw_input,
cursor,
prev_buttons: MouseButtons::default(),
event_queue: VecDeque::new(),
swap_buttons,
})
}
#[allow(dead_code)]
pub fn mode(&self) -> MouseInputMode {
self.mode
}
pub fn uses_raw_input(&self) -> bool {
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
self.mode.uses_raw_input() && self.raw_input.is_some()
}
#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
{
false
}
}
pub fn cursor_position(&self) -> (u16, u16) {
self.cursor.position_u16()
}
#[allow(dead_code)]
pub fn set_bounds(&mut self, cols: u16, rows: u16) {
self.cursor.set_bounds(cols as usize, rows as usize);
}
pub fn poll_event(&mut self) -> io::Result<Option<MouseEvent>> {
if let Some(event) = self.event_queue.pop_front() {
return Ok(Some(event));
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
let raw_input = match &mut self.raw_input {
Some(input) => input,
None => return Ok(None),
};
let raw_event = match raw_input.read_event()? {
Some(e) => e,
None => return Ok(None),
};
self.cursor.update(raw_event.dx, raw_event.dy);
let (col, row) = self.cursor.position_u16();
self.generate_events(raw_event, col, row);
Ok(self.event_queue.pop_front())
}
#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
{
Ok(None)
}
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
fn generate_events(&mut self, raw: RawMouseEvent, col: u16, row: u16) {
let buttons = if self.swap_buttons {
MouseButtons {
left: raw.buttons.right,
right: raw.buttons.left,
middle: raw.buttons.middle,
}
} else {
raw.buttons
};
if buttons.left && !self.prev_buttons.left {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
if buttons.right && !self.prev_buttons.right {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Right),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
if buttons.middle && !self.prev_buttons.middle {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Middle),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
if !buttons.left && self.prev_buttons.left {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
if !buttons.right && self.prev_buttons.right {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Up(MouseButton::Right),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
if !buttons.middle && self.prev_buttons.middle {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Up(MouseButton::Middle),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
if raw.dx != 0 || raw.dy != 0 {
if buttons.left && self.prev_buttons.left {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Drag(MouseButton::Left),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
} else if buttons.right && self.prev_buttons.right {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Drag(MouseButton::Right),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
} else if buttons.middle && self.prev_buttons.middle {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Drag(MouseButton::Middle),
column: col,
row,
modifiers: KeyModifiers::empty(),
});
} else {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::Moved,
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
}
if raw.scroll > 0 {
for _ in 0..raw.scroll {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::ScrollUp,
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
} else if raw.scroll < 0 {
for _ in 0..(-raw.scroll) {
self.event_queue.push_back(MouseEvent {
kind: MouseEventKind::ScrollDown,
column: col,
row,
modifiers: KeyModifiers::empty(),
});
}
}
self.prev_buttons = buttons;
}
}
#[allow(unused_imports)]
pub use CursorTracker as MouseCursorTracker;
#[cfg(unix)]
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)] pub struct FramebufferMouseEvent {
pub dx: i8,
pub dy: i8,
pub buttons: MouseButtons,
pub scroll: i8,
pub scroll_h: i8,
}
#[cfg(unix)]
impl From<RawMouseEvent> for FramebufferMouseEvent {
fn from(raw: RawMouseEvent) -> Self {
Self {
dx: raw.dx,
dy: raw.dy,
buttons: raw.buttons,
scroll: raw.scroll,
scroll_h: raw.scroll_h,
}
}
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
impl RawMouseInput {
#[allow(dead_code)] pub fn read_event_compat(&mut self) -> io::Result<Option<FramebufferMouseEvent>> {
self.read_event().map(|opt| opt.map(Into::into))
}
}