feat(pointer): virtio tablet as 2nd input dev (driver per-instance VAs, abs/btn decode), input server serves 2 devices on one notif; display software cursor, bar click switches window, presses/releases to front window in its px; k-ui hit-tests (press focuses, release on same presses, field only focuses)

This commit is contained in:
2026-10-28 16:50:20 +01:00
parent 0de13f124d
commit 1401af3d82
22 changed files with 545 additions and 46 deletions

View File

@@ -2,6 +2,7 @@
#![forbid(unsafe_code)]
mod keymap;
mod pointer;
use core::sync::atomic::{Ordering, fence};
use hw_virtio::mmio::{MmioAccess, Registers};
@@ -11,10 +12,16 @@ use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
/// each), clear of program images and heaps.
const INPUT_MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D1_0000);
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D1_1000);
const EVBUF_VA: VirtualAddress = VirtualAddress::from_raw(0x01D1_3000);
/// each), clear of program images and heaps; an input device takes 16 KiB
/// of it, the `n`th from here (a keyboard, a tablet).
const INPUT_VA: usize = 0x01D1_0000;
const INPUT_SPAN: usize = 0x4000;
/// Its registers, its queue's rings (two pages), its event buffers.
const MMIO_AT: usize = 0;
const VQUEUE_AT: usize = 0x1000;
const EVBUF_AT: usize = 0x3000;
/// How many may be driven at once in one program.
pub const MAX_INPUTS: usize = 4;
const QUEUE_SIZE: usize = 32;
const EVENT_SIZE: usize = 8;
@@ -37,20 +44,27 @@ pub struct VirtioInputDevice {
shift: bool,
ctrl: bool,
alt: bool,
/// Where a tablet's pointer is, between its reports.
pointer: pointer::Pointer,
}
impl VirtioInputDevice {
/// Takes over the virtio input device behind `mmio`. `None` if it is
/// not one or any step fails.
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
let regs = Registers::new(libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?);
/// Takes over the virtio input device behind `mmio`, as this program's
/// `n`th (each has addresses of its own). `None` if it is not one or
/// any step fails.
pub fn try_new(mmio: types::bounded::CapSlot, n: usize) -> Option<Self> {
if n >= MAX_INPUTS {
return None;
}
let at = |offset: usize| VirtualAddress::from_raw(INPUT_VA + n * INPUT_SPAN + offset);
let regs = Registers::new(libkos::device::map_mmio(mmio, at(MMIO_AT)).ok()?);
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
{
return None;
}
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let events = libkos::device::dma(PageCount::new(1)?, EVBUF_VA).ok()?;
let rings = libkos::device::dma(PageCount::new(2)?, at(VQUEUE_AT)).ok()?;
let events = libkos::device::dma(PageCount::new(1)?, at(EVBUF_AT)).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, rings.physical, QUEUE_SIZE as u32);
let mut dev = Self {
@@ -63,6 +77,7 @@ impl VirtioInputDevice {
shift: false,
ctrl: false,
alt: false,
pointer: pointer::Pointer::default(),
};
dev.fill_queue();
Some(dev)
@@ -123,6 +138,9 @@ impl InputDevice for VirtioInputDevice {
let (ev_type, ev_code, ev_value) = self.read_event(desc_idx);
self.resubmit(desc_idx);
if let Some(event) = self.pointer.event(ev_type, ev_code, ev_value) {
return Some(event);
}
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_SHIFT | keymap::RIGHT_SHIFT) {
self.shift = ev_value != 0;
continue;

View File

@@ -0,0 +1,79 @@
//! A tablet's events (Linux codes, as virtio-input reports them) to
//! pointer events: absolute axes collect until the report's sync, then a
//! move; buttons press and let go where the pointer is. Positions are
//! fractions of the screen, 0 to 65535 each way.
use port_input_device::InputEvent;
pub const EV_SYN: u16 = 0;
pub const EV_ABS: u16 = 3;
const ABS_X: u16 = 0;
const ABS_Y: u16 = 1;
const BTN_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111;
/// QEMU's tablet reports 0 to this on each axis.
const ABS_MAX: u32 = 0x7fff;
/// Button ids in pointer events.
pub const LEFT: u8 = 0;
pub const RIGHT: u8 = 1;
#[derive(Default)]
pub struct Pointer {
x: u16,
y: u16,
/// An axis changed since the last sync.
moved: bool,
}
fn scaled(v: u32) -> u16 {
(v.min(ABS_MAX) * 0xffff / ABS_MAX) as u16
}
impl Pointer {
/// What one event of `kind`, `code`, `value` means, if anything yet.
pub fn event(&mut self, kind: u16, code: u16, value: u32) -> Option<InputEvent> {
let (x, y) = (self.x, self.y);
match (kind, code) {
(EV_ABS, ABS_X) => {
self.x = scaled(value);
self.moved = true;
None
}
(EV_ABS, ABS_Y) => {
self.y = scaled(value);
self.moved = true;
None
}
(EV_SYN, _) if self.moved => {
self.moved = false;
Some(InputEvent::PointerMove { id: LEFT, x, y })
}
(super::EV_KEY, BTN_LEFT | BTN_RIGHT) => {
let id = if code == BTN_LEFT { LEFT } else { RIGHT };
match value {
0 => Some(InputEvent::PointerUp { id, x, y }),
1 => Some(InputEvent::PointerDown { id, x, y }),
_ => None,
}
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn axes_move_on_sync_and_buttons_press_where_it_is() {
let mut p = Pointer::default();
assert_eq!(p.event(EV_ABS, ABS_X, 0x7fff), None);
assert_eq!(p.event(EV_ABS, ABS_Y, 0x4000), None);
assert_eq!(p.event(EV_SYN, 0, 0), Some(InputEvent::PointerMove { id: LEFT, x: 0xffff, y: 0x8000 }));
assert_eq!(p.event(EV_SYN, 0, 0), None, "nothing moved since");
assert_eq!(p.event(super::super::EV_KEY, BTN_LEFT, 1), Some(InputEvent::PointerDown { id: LEFT, x: 0xffff, y: 0x8000 }));
assert_eq!(p.event(super::super::EV_KEY, BTN_RIGHT, 0), Some(InputEvent::PointerUp { id: RIGHT, x: 0xffff, y: 0x8000 }));
}
}