Files
edit/src/input.rs
2025-03-20 15:26:00 +01:00

512 lines
18 KiB
Rust

use crate::helpers::{Point, Size};
use crate::vt;
// TODO: Is this a good idea? I did it to allow typing `kbmod::CTRL | vk::A`.
// The reason it's an awkard u32 and not a struct is to hopefully make ABIs easier later.
// Of course you could just translate on the ABI boundary, but my hope is that this
// design lets me realize some restrictions early on that I can't foresee yet.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct InputKey(u32);
impl InputKey {
pub const fn new(v: u32) -> Self {
Self(v)
}
pub const fn from_ascii(ch: char) -> Option<Self> {
if ch == ' ' || (ch >= '0' && ch <= '9') {
Some(Self(ch as u32))
} else if ch >= 'a' && ch <= 'z' {
Some(Self(ch as u32 & !0x20)) // Shift a-z to A-Z
} else if ch >= 'A' && ch <= 'Z' {
Some(Self(kbmod::SHIFT.0 | ch as u32))
} else {
None
}
}
pub const fn value(&self) -> u32 {
self.0
}
pub const fn key(&self) -> InputKey {
InputKey(self.0 & 0x00FFFFFF)
}
pub const fn modifiers(&self) -> InputKeyMod {
InputKeyMod(self.0 & 0xFF000000)
}
pub const fn modifiers_contains(&self, modifier: InputKeyMod) -> bool {
(self.0 & modifier.0) != 0
}
pub const fn with_modifiers(&self, modifiers: InputKeyMod) -> InputKey {
InputKey(self.0 | modifiers.0)
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct InputKeyMod(u32);
impl InputKeyMod {
const fn new(v: u32) -> Self {
Self(v)
}
pub const fn contains(&self, modifier: InputKeyMod) -> bool {
(self.0 & modifier.0) != 0
}
}
impl std::ops::BitOr<InputKeyMod> for InputKey {
type Output = InputKey;
fn bitor(self, rhs: InputKeyMod) -> InputKey {
InputKey(self.0 | rhs.0)
}
}
impl std::ops::BitOr<InputKey> for InputKeyMod {
type Output = InputKey;
fn bitor(self, rhs: InputKey) -> InputKey {
InputKey(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for InputKeyMod {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
// The codes defined here match the VK_* constants on Windows.
// It's a convenient way to handle keyboard input, even on other platforms.
pub mod vk {
use super::InputKey;
pub const NULL: InputKey = InputKey::new(0x00);
pub const BACK: InputKey = InputKey::new(0x08);
pub const TAB: InputKey = InputKey::new(0x09);
pub const RETURN: InputKey = InputKey::new(0x0D);
pub const ESCAPE: InputKey = InputKey::new(0x1B);
pub const SPACE: InputKey = InputKey::new(' ' as u32);
pub const PRIOR: InputKey = InputKey::new(0x21);
pub const NEXT: InputKey = InputKey::new(0x22);
pub const END: InputKey = InputKey::new(0x23);
pub const HOME: InputKey = InputKey::new(0x24);
pub const LEFT: InputKey = InputKey::new(0x25);
pub const UP: InputKey = InputKey::new(0x26);
pub const RIGHT: InputKey = InputKey::new(0x27);
pub const DOWN: InputKey = InputKey::new(0x28);
pub const INSERT: InputKey = InputKey::new(0x2D);
pub const DELETE: InputKey = InputKey::new(0x2E);
pub const N0: InputKey = InputKey::new('0' as u32);
pub const N1: InputKey = InputKey::new('1' as u32);
pub const N2: InputKey = InputKey::new('2' as u32);
pub const N3: InputKey = InputKey::new('3' as u32);
pub const N4: InputKey = InputKey::new('4' as u32);
pub const N5: InputKey = InputKey::new('5' as u32);
pub const N6: InputKey = InputKey::new('6' as u32);
pub const N7: InputKey = InputKey::new('7' as u32);
pub const N8: InputKey = InputKey::new('8' as u32);
pub const N9: InputKey = InputKey::new('9' as u32);
pub const A: InputKey = InputKey::new('A' as u32);
pub const B: InputKey = InputKey::new('B' as u32);
pub const C: InputKey = InputKey::new('C' as u32);
pub const D: InputKey = InputKey::new('D' as u32);
pub const E: InputKey = InputKey::new('E' as u32);
pub const F: InputKey = InputKey::new('F' as u32);
pub const G: InputKey = InputKey::new('G' as u32);
pub const H: InputKey = InputKey::new('H' as u32);
pub const I: InputKey = InputKey::new('I' as u32);
pub const J: InputKey = InputKey::new('J' as u32);
pub const K: InputKey = InputKey::new('K' as u32);
pub const L: InputKey = InputKey::new('L' as u32);
pub const M: InputKey = InputKey::new('M' as u32);
pub const N: InputKey = InputKey::new('N' as u32);
pub const O: InputKey = InputKey::new('O' as u32);
pub const P: InputKey = InputKey::new('P' as u32);
pub const Q: InputKey = InputKey::new('Q' as u32);
pub const R: InputKey = InputKey::new('R' as u32);
pub const S: InputKey = InputKey::new('S' as u32);
pub const T: InputKey = InputKey::new('T' as u32);
pub const U: InputKey = InputKey::new('U' as u32);
pub const V: InputKey = InputKey::new('V' as u32);
pub const W: InputKey = InputKey::new('W' as u32);
pub const X: InputKey = InputKey::new('X' as u32);
pub const Y: InputKey = InputKey::new('Y' as u32);
pub const Z: InputKey = InputKey::new('Z' as u32);
pub const NUMPAD0: InputKey = InputKey::new(0x60);
pub const NUMPAD1: InputKey = InputKey::new(0x61);
pub const NUMPAD2: InputKey = InputKey::new(0x62);
pub const NUMPAD3: InputKey = InputKey::new(0x63);
pub const NUMPAD4: InputKey = InputKey::new(0x64);
pub const NUMPAD5: InputKey = InputKey::new(0x65);
pub const NUMPAD6: InputKey = InputKey::new(0x66);
pub const NUMPAD7: InputKey = InputKey::new(0x67);
pub const NUMPAD8: InputKey = InputKey::new(0x68);
pub const NUMPAD9: InputKey = InputKey::new(0x69);
pub const MULTIPLY: InputKey = InputKey::new(0x6A);
pub const ADD: InputKey = InputKey::new(0x6B);
pub const SEPARATOR: InputKey = InputKey::new(0x6C);
pub const SUBTRACT: InputKey = InputKey::new(0x6D);
pub const DECIMAL: InputKey = InputKey::new(0x6E);
pub const DIVIDE: InputKey = InputKey::new(0x6F);
pub const F1: InputKey = InputKey::new(0x70);
pub const F2: InputKey = InputKey::new(0x71);
pub const F3: InputKey = InputKey::new(0x72);
pub const F4: InputKey = InputKey::new(0x73);
pub const F5: InputKey = InputKey::new(0x74);
pub const F6: InputKey = InputKey::new(0x75);
pub const F7: InputKey = InputKey::new(0x76);
pub const F8: InputKey = InputKey::new(0x77);
pub const F9: InputKey = InputKey::new(0x78);
pub const F10: InputKey = InputKey::new(0x79);
pub const F11: InputKey = InputKey::new(0x7A);
pub const F12: InputKey = InputKey::new(0x7B);
pub const F13: InputKey = InputKey::new(0x7C);
pub const F14: InputKey = InputKey::new(0x7D);
pub const F15: InputKey = InputKey::new(0x7E);
pub const F16: InputKey = InputKey::new(0x7F);
pub const F17: InputKey = InputKey::new(0x80);
pub const F18: InputKey = InputKey::new(0x81);
pub const F19: InputKey = InputKey::new(0x82);
pub const F20: InputKey = InputKey::new(0x83);
pub const F21: InputKey = InputKey::new(0x84);
pub const F22: InputKey = InputKey::new(0x85);
pub const F23: InputKey = InputKey::new(0x86);
pub const F24: InputKey = InputKey::new(0x87);
}
pub mod kbmod {
use super::InputKeyMod;
pub const NONE: InputKeyMod = InputKeyMod::new(0x00000000);
pub const CTRL: InputKeyMod = InputKeyMod::new(0x01000000);
pub const ALT: InputKeyMod = InputKeyMod::new(0x02000000);
pub const SHIFT: InputKeyMod = InputKeyMod::new(0x04000000);
pub const CTRL_ALT: InputKeyMod = InputKeyMod::new(0x03000000);
pub const CTRL_SHIFT: InputKeyMod = InputKeyMod::new(0x05000000);
pub const ALT_SHIFT: InputKeyMod = InputKeyMod::new(0x06000000);
pub const CTRL_ALT_SHIFT: InputKeyMod = InputKeyMod::new(0x07000000);
}
#[derive(Clone, Copy)]
pub struct InputText<'a> {
pub text: &'a str,
pub bracketed: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum InputMouseState {
#[default]
None,
// These 3 carry their state between frames.
Left,
Middle,
Right,
// These 2 get reset to None on the next frame.
Release,
Scroll,
}
#[derive(Clone, Copy)]
pub struct InputMouse {
pub state: InputMouseState,
pub modifiers: InputKeyMod,
pub position: Point,
pub scroll: Point,
}
pub enum Input<'input> {
Resize(Size),
Text(InputText<'input>),
Keyboard(InputKey),
Mouse(InputMouse),
}
pub struct Parser {
want: bool,
buf: [u8; 3],
len: usize,
}
impl Parser {
pub fn new() -> Self {
Self {
want: false,
buf: [0; 3],
len: 0,
}
}
/// Turns VT sequences into keyboard, mouse, etc., inputs.
pub fn parse<'parser, 'vt, 'input>(
&'parser mut self,
stream: vt::Stream<'vt, 'input>,
) -> Stream<'parser, 'vt, 'input> {
Stream {
parser: self,
stream,
}
}
}
pub struct Stream<'parser, 'vt, 'input> {
parser: &'parser mut Parser,
stream: vt::Stream<'vt, 'input>,
}
impl Stream<'_, '_, '_> {
/// Parses the next input action from the previously given input.
///
/// Can't implement Iterator, because this is a "lending iterator".
pub fn next(&mut self) -> Option<Input> {
if self.parser.want {
return self.parse_x10_mouse_coordinates();
}
let token = self.stream.next()?;
match token {
vt::Token::Text(text) => Some(Input::Text(InputText {
text,
bracketed: false,
})),
vt::Token::Ctrl(ch) => match ch {
'\0' | '\t' | '\r' => Some(Input::Keyboard(InputKey::new(ch as u32))),
..='\x1a' => {
// Shift control code to A-Z
let key = ch as u32 | 0x40;
Some(Input::Keyboard(kbmod::CTRL | InputKey::new(key)))
}
'\x7f' => Some(Input::Keyboard(vk::BACK)),
_ => None,
},
vt::Token::Esc(ch) => {
match ch {
'\0' => Some(Input::Keyboard(vk::ESCAPE)),
' '..='~' => {
let ch = ch as u32;
let key = ch & !0x20; // Shift a-z to A-Z
let modifiers = if (ch & 0x20) != 0 {
kbmod::ALT
} else {
kbmod::ALT_SHIFT
};
Some(Input::Keyboard(modifiers | InputKey::new(key)))
}
_ => None,
}
}
vt::Token::SS3(ch) => {
if ('P'..='S').contains(&ch) {
let key = vk::F1.value() + ch as u32 - 'P' as u32;
Some(Input::Keyboard(InputKey::new(key)))
} else {
None
}
}
vt::Token::Csi(csi) => {
match csi.final_byte {
'A'..='H' => {
const LUT: [u8; 8] = [
vk::UP.value() as u8, // A
vk::DOWN.value() as u8, // B
vk::RIGHT.value() as u8, // C
vk::LEFT.value() as u8, // D
0, // E
vk::END.value() as u8, // F
0, // G
vk::HOME.value() as u8, // H
];
let vk = LUT[csi.final_byte as usize - 'A' as usize];
if vk != 0 {
return Some(Input::Keyboard(
InputKey::new(vk as u32) | Self::parse_modifiers(csi),
));
}
None
}
'Z' => return Some(Input::Keyboard(kbmod::SHIFT | vk::TAB)),
'~' => {
const LUT: [u8; 35] = [
0,
vk::HOME.value() as u8, // 1
vk::INSERT.value() as u8, // 2
vk::DELETE.value() as u8, // 3
vk::END.value() as u8, // 4
vk::PRIOR.value() as u8, // 5
vk::NEXT.value() as u8, // 6
0,
0,
0,
0,
0,
0,
0,
0,
vk::F5.value() as u8, // 15
0,
vk::F6.value() as u8, // 17
vk::F7.value() as u8, // 18
vk::F8.value() as u8, // 19
vk::F9.value() as u8, // 20
vk::F10.value() as u8, // 21
0,
vk::F11.value() as u8, // 23
vk::F12.value() as u8, // 24
vk::F13.value() as u8, // 25
vk::F14.value() as u8, // 26
0,
vk::F15.value() as u8, // 28
vk::F16.value() as u8, // 29
0,
vk::F17.value() as u8, // 31
vk::F18.value() as u8, // 32
vk::F19.value() as u8, // 33
vk::F20.value() as u8, // 34
];
let p0 = csi.params[0];
if p0 >= 0 && p0 <= LUT.len() as i32 {
let vk = LUT[p0 as usize];
if vk != 0 {
return Some(Input::Keyboard(
InputKey::new(vk as u32) | Self::parse_modifiers(csi),
));
}
}
None
}
'm' | 'M' if csi.private_byte == '<' => {
let btn = csi.params[0];
let mut mouse = InputMouse {
state: InputMouseState::None,
modifiers: kbmod::NONE,
position: Point::default(),
scroll: Point::default(),
};
mouse.state = InputMouseState::None;
if (btn & 0x40) != 0 {
mouse.state = InputMouseState::Scroll;
mouse.scroll.y += if (btn & 0x01) != 0 { 3 } else { -3 };
} else if csi.final_byte == 'M' {
const STATES: [InputMouseState; 4] = [
InputMouseState::Left,
InputMouseState::Middle,
InputMouseState::Right,
InputMouseState::None,
];
mouse.state = STATES[(btn as usize) & 0x03];
}
mouse.modifiers = kbmod::NONE;
mouse.modifiers |= if (btn & 0x04) != 0 {
kbmod::SHIFT
} else {
kbmod::NONE
};
mouse.modifiers |= if (btn & 0x08) != 0 {
kbmod::ALT
} else {
kbmod::NONE
};
mouse.modifiers |= if (btn & 0x10f) != 0 {
kbmod::CTRL
} else {
kbmod::NONE
};
mouse.position.x = csi.params[1] - 1;
mouse.position.y = csi.params[2] - 1;
Some(Input::Mouse(mouse))
}
'M' if csi.param_count == 0 => {
self.parser.want = true;
None
}
't' if csi.params[0] == 8 => {
// Window Size
let width = csi.params[2].clamp(1, 32767);
let height = csi.params[1].clamp(1, 32767);
Some(Input::Resize(Size { width, height }))
}
_ => None,
}
}
_ => None,
}
}
/// Implements the X10 mouse protocol via `CSI M CbCxCy`.
///
/// You want to send numeric mouse coordinates.
/// You have CSI sequences with numeric parameters.
/// So, of course you put the coordinates as shifted ASCII characters after
/// the end of the sequence. Limited coordinate range and complicated parsing!
/// This is so puzzling to me. The existence of this function makes me unhappy.
#[cold]
fn parse_x10_mouse_coordinates(&mut self) -> Option<Input> {
self.parser.len += self.stream.read(&mut self.parser.buf[self.parser.len..]);
if self.parser.len < 3 {
return None;
}
let button = self.parser.buf[0] & 0b11;
let modifier = self.parser.buf[0] & 0b11100;
let x = self.parser.buf[1] as i32 - 0x21;
let y = self.parser.buf[2] as i32 - 0x21;
let action = match button {
0 => InputMouseState::Left,
1 => InputMouseState::Middle,
2 => InputMouseState::Right,
_ => InputMouseState::None,
};
let modifiers = match modifier {
4 => kbmod::SHIFT,
8 => kbmod::ALT,
16 => kbmod::CTRL,
_ => kbmod::NONE,
};
self.parser.want = false;
self.parser.len = 0;
Some(Input::Mouse(InputMouse {
state: action,
modifiers,
position: Point { x, y },
scroll: Point::default(),
}))
}
fn parse_modifiers(csi: &vt::Csi) -> InputKeyMod {
let mut modifiers = kbmod::NONE;
let p1 = (csi.params[1] - 1).max(0);
if (p1 & 0x01) != 0 {
modifiers |= kbmod::SHIFT;
}
if (p1 & 0x02) != 0 {
modifiers |= kbmod::ALT;
}
if (p1 & 0x04) != 0 {
modifiers |= kbmod::CTRL;
}
modifiers
}
}