Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 22 additions & 38 deletions kernel/src/drivers/usb/hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! - Keyboard: 8 bytes (1 modifier + 1 reserved + 6 keycodes)
//! - Mouse: 3-4 bytes (1 buttons + 1 dx + 1 dy + optional wheel)

use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering};

// =============================================================================
// Diagnostic counters (read by heartbeat in timer_interrupt.rs)
Expand Down Expand Up @@ -46,6 +46,11 @@ static MOUSE_X: AtomicU32 = AtomicU32::new(0);
static MOUSE_Y: AtomicU32 = AtomicU32::new(0);
static MOUSE_BUTTONS: AtomicU32 = AtomicU32::new(0);

/// Accumulated scroll wheel delta since last read.
/// Positive = scroll up, negative = scroll down.
/// Consumed (reset to 0) when read by the window input event syscall.
static MOUSE_WHEEL: AtomicI32 = AtomicI32::new(0);

/// Per-endpoint button state for multi-endpoint devices (e.g., VMware dual HID).
/// When multiple USB HID endpoints report button state independently, one endpoint
/// reporting buttons=0 must not cancel the other's press. We track each endpoint's
Expand Down Expand Up @@ -302,19 +307,15 @@ fn screen_dimensions() -> (u32, u32) {
.unwrap_or((1280, 960))
}

/// Process a USB boot protocol mouse report (3-4 bytes).
/// Process a mouse HID report from a specific endpoint.
///
/// Report format:
/// Report format (boot protocol relative mouse):
/// - Byte 0: Button flags (bit 0=left, bit 1=right, bit 2=middle)
/// - Byte 1: X displacement (signed i8)
/// - Byte 2: Y displacement (signed i8)
/// - Byte 3: Wheel displacement (optional, signed i8)
///
/// Updates the global mouse position atomics with clamping to screen bounds.
/// Counter for diagnostic logging of first few mouse reports.
static MOUSE_LOG_COUNT: AtomicU64 = AtomicU64::new(0);

/// Process a mouse HID report from a specific endpoint.
///
/// `ep_idx` identifies the USB endpoint (0-3) so that multi-endpoint devices
/// (like VMware's dual HID mouse) don't race on button state. Each endpoint's
Expand All @@ -324,25 +325,6 @@ pub fn process_mouse_report(report: &[u8], ep_idx: u8) {
return;
}

// Log first few non-zero mouse reports for debugging coordinate mapping
let log_n = MOUSE_LOG_COUNT.load(Ordering::Relaxed);
if log_n < 10 && report.iter().take(8).any(|&b| b != 0 && b != 0xDE) {
MOUSE_LOG_COUNT.fetch_add(1, Ordering::Relaxed);
crate::serial_println!(
"[mouse-report] #{}: [{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}] len={}",
log_n,
report.get(0).copied().unwrap_or(0),
report.get(1).copied().unwrap_or(0),
report.get(2).copied().unwrap_or(0),
report.get(3).copied().unwrap_or(0),
report.get(4).copied().unwrap_or(0),
report.get(5).copied().unwrap_or(0),
report.get(6).copied().unwrap_or(0),
report.get(7).copied().unwrap_or(0),
report.len(),
);
}

let (sw, sh) = screen_dimensions();

// Determine actual report length: the XHCI driver fills the 64-byte buffer
Expand All @@ -364,7 +346,6 @@ pub fn process_mouse_report(report: &[u8], ep_idx: u8) {
let is_tablet = IS_ABSOLUTE_TABLET.load(Ordering::Relaxed);
if !is_tablet && actual_len >= 6 && report[1] == 0 {
IS_ABSOLUTE_TABLET.store(true, Ordering::Relaxed);
crate::serial_println!("[mouse] Latched into absolute tablet mode (first 6-byte report with byte[1]=0)");
}

// Absolute tablet path: coordinates are always at bytes 2-5.
Expand All @@ -375,7 +356,6 @@ pub fn process_mouse_report(report: &[u8], ep_idx: u8) {
if IS_ABSOLUTE_TABLET.load(Ordering::Relaxed) && report.len() >= 6 {
if !TABLET_NO_REPORT_ID.load(Ordering::Relaxed) && report[0] == 0x00 {
TABLET_NO_REPORT_ID.store(true, Ordering::Relaxed);
crate::serial_println!("[mouse] Detected tablet format: no report ID prefix (buttons in byte[0])");
}
let buttons = if TABLET_NO_REPORT_ID.load(Ordering::Relaxed) {
report[0] as u32
Expand All @@ -395,23 +375,13 @@ pub fn process_mouse_report(report: &[u8], ep_idx: u8) {
if pressed != 0 {
MOUSE_BUTTONS_PRESSED.fetch_or(pressed, Ordering::Relaxed);
}
static BTN_LOG: AtomicU64 = AtomicU64::new(0);
if BTN_LOG.fetch_add(1, Ordering::Relaxed) < 50 {
crate::serial_println!("[mouse-click] {} -> {} (ep{})", prev, merged, ep_idx);
}
}

let abs_x = u16::from_le_bytes([report[2], report[3]]) as u32;
let abs_y = u16::from_le_bytes([report[4], report[5]]) as u32;

let new_x = (abs_x * sw / 32768).min(sw - 1);
let new_y = (abs_y * sh / 32768).min(sh - 1);
if log_n < 10 {
crate::serial_println!(
"[mouse-pos] abs=({},{}) btn={} screen={}x{} -> ({},{})",
abs_x, abs_y, buttons, sw, sh, new_x, new_y
);
}
MOUSE_X.store(new_x, Ordering::Relaxed);
MOUSE_Y.store(new_y, Ordering::Relaxed);
crate::syscall::graphics::wake_compositor_if_waiting();
Expand All @@ -434,6 +404,11 @@ pub fn process_mouse_report(report: &[u8], ep_idx: u8) {
}
let dx = report[1] as i8 as i32;
let dy = report[2] as i8 as i32;
let wheel = if report.len() >= 4 {
report[3] as i8 as i32
} else {
0
};

let old_x = MOUSE_X.load(Ordering::Relaxed) as i32;
let new_x = (old_x + dx).clamp(0, sw as i32 - 1) as u32;
Expand All @@ -442,6 +417,10 @@ pub fn process_mouse_report(report: &[u8], ep_idx: u8) {
let old_y = MOUSE_Y.load(Ordering::Relaxed) as i32;
let new_y = (old_y + dy).clamp(0, sh as i32 - 1) as u32;
MOUSE_Y.store(new_y, Ordering::Relaxed);

if wheel != 0 {
MOUSE_WHEEL.fetch_add(wheel, Ordering::Relaxed);
}
crate::syscall::graphics::wake_compositor_if_waiting();
}

Expand Down Expand Up @@ -472,6 +451,11 @@ pub fn mouse_position() -> (u32, u32) {
(MOUSE_X.load(Ordering::Relaxed), MOUSE_Y.load(Ordering::Relaxed))
}

/// Consume accumulated scroll wheel delta (resets to 0 after read).
pub fn mouse_wheel_consume() -> i32 {
MOUSE_WHEEL.swap(0, Ordering::Relaxed)
}

/// Get current mouse position and raw button state (non-consuming peek).
///
/// Returns instantaneous hardware state (no latch). Used by compositor_wait for
Expand Down
19 changes: 17 additions & 2 deletions kernel/src/syscall/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ pub struct WindowInputEvent {
pub mouse_y: i16,
/// Modifier bitmask (bit 0=shift, bit 1=ctrl, bit 2=alt)
pub modifiers: u16,
pub _pad: u16,
/// Scroll wheel delta (positive = scroll up, negative = scroll down)
pub scroll_y: i16,
}

#[cfg(target_arch = "aarch64")]
Expand Down Expand Up @@ -965,7 +966,21 @@ fn handle_virgl_op(cmd: &FbDrawCmd) -> SyscallResult {
if event_ptr == 0 || event_ptr >= USER_SPACE_MAX {
return SyscallResult::Err(super::ErrorCode::Fault as u64);
}
let event: WindowInputEvent = unsafe { core::ptr::read(event_ptr as *const WindowInputEvent) };
let mut event: WindowInputEvent = unsafe { core::ptr::read(event_ptr as *const WindowInputEvent) };

// Inject accumulated scroll wheel delta into mouse events.
// MOUSE_MOVE=3, MOUSE_BUTTON=4, MOUSE_SCROLL=9
if event.event_type == 3 || event.event_type == 4 || event.event_type == 9 {
let wheel = crate::drivers::usb::hid::mouse_wheel_consume();
if wheel != 0 {
event.scroll_y = event.scroll_y.saturating_add(wheel.clamp(-32768, 32767) as i16);
// If this was a plain MOUSE_MOVE (3) with wheel data, upgrade to MOUSE_SCROLL (9)
// so clients that filter on event type receive it correctly.
if event.event_type == 3 && event.scroll_y != 0 {
event.event_type = 9;
}
}
}

let wake_tid = {
let mut reg = WINDOW_REGISTRY.lock();
Expand Down
13 changes: 12 additions & 1 deletion libs/breengel/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ pub enum Event {
MouseMove { x: i32, y: i32 },
/// Mouse button pressed or released.
MouseButton { button: u8, pressed: bool, x: i32, y: i32 },
/// Mouse scroll wheel event.
///
/// `delta_y` > 0 means scroll up (content moves down / offset decreases).
/// `delta_y` < 0 means scroll down (content moves up / offset increases).
Scroll { delta_y: i32 },
/// This window gained keyboard focus.
FocusGained,
/// This window lost keyboard focus.
Expand All @@ -49,6 +54,9 @@ pub enum Event {

impl Event {
/// Convert a raw kernel input event to a high-level Event.
///
/// Unknown event types fall back to a `KeyPress` with ascii=0 and
/// the raw keycode, so they are not silently dropped.
pub fn from_raw(raw: &WindowInputEvent) -> Self {
match raw.event_type {
input_event_type::KEY_PRESS => Event::KeyPress {
Expand All @@ -66,10 +74,13 @@ impl Event {
},
input_event_type::MOUSE_BUTTON => Event::MouseButton {
button: raw.keycode as u8,
pressed: raw._pad != 0,
pressed: raw.scroll_y != 0,
x: raw.mouse_x as i32,
y: raw.mouse_y as i32,
},
input_event_type::MOUSE_SCROLL => Event::Scroll {
delta_y: raw.scroll_y as i32,
},
input_event_type::FOCUS_GAINED => Event::FocusGained,
input_event_type::FOCUS_LOST => Event::FocusLost,
input_event_type::CLOSE_REQUESTED => Event::CloseRequested,
Expand Down
22 changes: 21 additions & 1 deletion libs/libbreenix/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,10 @@ pub struct WindowInputEvent {
pub mouse_y: i16,
/// Modifier bitmask (bit 0=shift, bit 1=ctrl, bit 2=alt)
pub modifiers: u16,
pub _pad: u16,
/// Scroll wheel delta. Positive = scroll up, negative = scroll down.
/// Set on MOUSE_MOVE and MOUSE_BUTTON events when the wheel moved this frame.
/// Also the sole payload for MOUSE_SCROLL events.
pub scroll_y: i16,
}

/// Cursor shape constants for `set_cursor_shape`.
Expand Down Expand Up @@ -696,6 +699,8 @@ pub mod input_event_type {
pub const FOCUS_LOST: u16 = 6;
pub const CLOSE_REQUESTED: u16 = 7;
pub const WINDOW_RESIZED: u16 = 8;
/// Scroll wheel event. `scroll_y` > 0 = scroll up, < 0 = scroll down.
pub const MOUSE_SCROLL: u16 = 9;
}

/// Write an input event to a window's kernel ring buffer.
Expand Down Expand Up @@ -947,6 +952,21 @@ pub fn mouse_state() -> Result<(u32, u32, u32), Error> {
Ok((state[0], state[1], state[2]))
}

/// Get the current mouse cursor position, button state, and scroll wheel delta.
///
/// The scroll delta is an accumulated signed value: positive = scroll up,
/// negative = scroll down. The kernel resets the accumulator on each read.
///
/// # Returns
/// * Ok((x, y, buttons, scroll_y)) - Mouse position, buttons, and wheel delta
/// * Err(Error) - Error (ENODEV if no pointer device)
pub fn mouse_state_with_scroll() -> Result<(u32, u32, u32, i32), Error> {
let mut state: [u32; 4] = [0, 0, 0, 0];
let ret = unsafe { raw::syscall1(nr::GET_MOUSE_POS, &mut state as *mut [u32; 4] as u64) as i64 };
Error::from_syscall(ret)?;
Ok((state[0], state[1], state[2], state[3] as i32))
}

// ============================================================================
// RAII Framebuffer Wrapper
// ============================================================================
Expand Down
1 change: 1 addition & 0 deletions libs/libbui/src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod file_picker;
pub mod label;
pub mod menu_bar;
pub mod panel;
pub mod scroll_bar;
pub mod slider;
pub mod tab_bar;

Expand Down
Loading