Commit cc9c32b9 authored by IGI-111's avatar IGI-111

added mouse input

The event system has been reworked to allow the detection of mouse
events as well as key presses.
Xterm, rxvt and X10 emulated escape codes are supported, they are
enabled and disabled by sending the right escape codes when creating a
RawTerminal.

To allow for byte manipulation, which was necessary to implement those
features, the backend iterator has been changed from chars() to bytes()
(with specific treatment of unicode sequences), making the whole crate
not require nightly rustc.
parent 12c0ad04
......@@ -5,7 +5,3 @@ authors = ["Ticki <Ticki@users.noreply.github.com>"]
[target.'cfg(not(target_os = "redox"))'.dependencies]
libc = "0.2.8"
[features]
default = ["nightly"]
nightly = []
......@@ -19,21 +19,11 @@ and this crate can generally be considered stable.
## Cargo.toml
For nightly, add
```toml
[dependencies.termion]
git = "https://github.com/ticki/termion.git"
```
For stable,
```toml
[dependencies.termion]
git = "https://github.com/ticki/termion.git"
default-features = false
```
## Features
- Raw mode.
......@@ -51,6 +41,7 @@ default-features = false
- Special keys events (modifiers, special keys, etc.).
- Allocation-free.
- Asynchronous key events.
- Mouse input
- Carefully tested.
and much more.
......@@ -92,10 +83,6 @@ For a more complete example, see [a minesweeper implementation](https://github.c
<img src="image.png" width="200">
## TODO
- Mouse input
## License
MIT/X11.
extern crate termion;
#[cfg(feature = "nightly")]
fn main() {
use termion::{TermRead, TermWrite, IntoRawMode, Key};
use std::io::{Write, stdout, stdin};
......@@ -27,7 +26,6 @@ fn main() {
Key::Up => println!("↑"),
Key::Down => println!("↓"),
Key::Backspace => println!("×"),
Key::Invalid => println!("???"),
_ => {},
}
stdout.flush().unwrap();
......@@ -35,8 +33,3 @@ fn main() {
stdout.show_cursor().unwrap();
}
#[cfg(not(feature = "nightly"))]
fn main() {
println!("To run this example, you need to enable the `nightly` feature. Use Rust nightly and compile with `--features nightly`.")
}
extern crate termion;
fn main() {
use termion::{TermRead, TermWrite, IntoRawMode, Key, Event};
use std::io::{Write, stdout, stdin};
let stdin = stdin();
let mut stdout = stdout().into_raw_mode().unwrap();
stdout.clear().unwrap();
stdout.goto(0, 0).unwrap();
stdout.write(b"q to exit. Type stuff, use alt, click around...").unwrap();
stdout.flush().unwrap();
let mut x = 0;
let mut y = 0;
for c in stdin.events() {
stdout.goto(5, 5).unwrap();
stdout.clear_line().unwrap();
match c.unwrap() {
Event::KeyEvent(Key::Char('q')) => break,
Event::MouseEvent(val, a, b) => {
x = a;
y = b;
println!("{:?}", Event::MouseEvent(val, a, b));
},
val => println!("{:?}", val),
}
stdout.goto(x, y).unwrap();
stdout.flush().unwrap();
}
stdout.show_cursor().unwrap();
}
use std::io::{Error, ErrorKind};
use std::ascii::AsciiExt;
use std::str;
/// An event reported by the terminal.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Event {
/// A key press.
KeyEvent(Key),
/// A mouse button press, release or wheel use at specific coordinates.
MouseEvent(Mouse, u16, u16),
/// An event that cannot currently be evaluated.
Unsupported,
}
/// A mouse related event.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mouse {
/// A mouse button was pressed.
Press(MouseButton),
/// A mouse button was released.
Release,
}
/// A mouse button.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum MouseButton {
/// The left mouse button.
Left,
/// The right mouse button.
Right,
/// The middle mouse button.
Middle,
/// Mouse wheel is going up.
///
/// This event is typically only used with Mouse::Press.
WheelUp,
/// Mouse wheel is going down.
///
/// This event is typically only used with Mouse::Press.
WheelDown,
}
/// A key.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Key {
/// Backspace.
Backspace,
/// Left arrow.
Left,
/// Right arrow.
Right,
/// Up arrow.
Up,
/// Down arrow.
Down,
/// Home key.
Home,
/// End key.
End,
/// Page Up key.
PageUp,
/// Page Down key.
PageDown,
/// Delete key.
Delete,
/// Insert key.
Insert,
/// Function keys.
///
/// Only function keys 1 through 12 are supported.
F(u8),
/// Normal character.
Char(char),
/// Alt modified character.
Alt(char),
/// Ctrl modified character.
///
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
Ctrl(char),
/// Null byte.
Null,
#[allow(missing_docs)]
#[doc(hidden)]
__IsNotComplete
}
pub fn parse_event<I>(item: Result<u8, Error>, iter: &mut I) -> Result<Event, Error>
where I: Iterator<Item = Result<u8, Error>>
{
let error = Err(Error::new(ErrorKind::Other, "Could not parse an event"));
match item {
Ok(b'\x1B') => {
Ok(match iter.next() {
Some(Ok(b'O')) => {
match iter.next() {
Some(Ok(b'P')) => Event::KeyEvent(Key::F(1)),
Some(Ok(b'Q')) => Event::KeyEvent(Key::F(2)),
Some(Ok(b'R')) => Event::KeyEvent(Key::F(3)),
Some(Ok(b'S')) => Event::KeyEvent(Key::F(4)),
_ => return error,
}
}
Some(Ok(b'[')) => {
match iter.next() {
Some(Ok(b'D')) => Event::KeyEvent(Key::Left),
Some(Ok(b'C')) => Event::KeyEvent(Key::Right),
Some(Ok(b'A')) => Event::KeyEvent(Key::Up),
Some(Ok(b'B')) => Event::KeyEvent(Key::Down),
Some(Ok(b'H')) => Event::KeyEvent(Key::Home),
Some(Ok(b'F')) => Event::KeyEvent(Key::End),
Some(Ok(b'M')) => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only)
let cb = iter.next().unwrap().unwrap() as i8 - 32;
// (1, 1) are the coords for upper left
let cx = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32);
let cy = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32);
Event::MouseEvent(match cb & 0b11 {
0 => {
if cb & 64 != 0 {
Mouse::Press(MouseButton::WheelUp)
} else {
Mouse::Press(MouseButton::Left)
}
}
1 => {
if cb & 64 != 0 {
Mouse::Press(MouseButton::WheelDown)
} else {
Mouse::Press(MouseButton::Middle)
}
}
2 => Mouse::Press(MouseButton::Right),
3 => Mouse::Release,
_ => return error,
},
cx as u16,
cy as u16)
}
Some(Ok(b'<')) => {
// xterm mouse encoding: ESC [ < Cb ; Cx ; Cy ; (M or m)
let mut buf = Vec::new();
let mut c = iter.next().unwrap().unwrap();
while match c {
b'm' | b'M' => false,
_ => true,
} {
buf.push(c);
c = iter.next().unwrap().unwrap();
}
let str_buf = String::from_utf8(buf).unwrap();
let ref mut nums = str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let cy = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
_ => return error,
};
Event::MouseEvent(match c {
b'M' => Mouse::Press(button),
b'm' => Mouse::Release,
_ => return error,
},
cx,
cy)
}
Some(Ok(c @ b'0'...b'9')) => {
// numbered escape code
let mut buf = Vec::new();
buf.push(c);
let mut c = iter.next().unwrap().unwrap();
while match c {
b'M' | b'~' => false,
_ => true,
} {
buf.push(c);
c = iter.next().unwrap().unwrap();
}
match c {
// rxvt mouse encoding: ESC [ Cb ; Cx ; Cy ; M
b'M' => {
let str_buf = String::from_utf8(buf).unwrap();
let ref mut nums = str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let cy = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let event = match cb {
32 => Mouse::Press(MouseButton::Left),
33 => Mouse::Press(MouseButton::Middle),
34 => Mouse::Press(MouseButton::Right),
35 => Mouse::Release,
96 => Mouse::Press(MouseButton::WheelUp),
97 => Mouse::Press(MouseButton::WheelUp),
_ => return error,
};
Event::MouseEvent(event, cx, cy)
},
// special key code
b'~' => {
let num: u8 = String::from_utf8(buf).unwrap().parse().unwrap();
match num {
1 | 7 => Event::KeyEvent(Key::Home),
2 => Event::KeyEvent(Key::Insert),
3 => Event::KeyEvent(Key::Delete),
4 | 8 => Event::KeyEvent(Key::End),
5 => Event::KeyEvent(Key::PageUp),
6 => Event::KeyEvent(Key::PageDown),
v @ 11...15 => Event::KeyEvent(Key::F(v - 10)),
v @ 17...21 => Event::KeyEvent(Key::F(v - 11)),
v @ 23...24 => Event::KeyEvent(Key::F(v - 12)),
_ => return error,
}
}
_ => return error,
}
}
_ => return error,
}
}
Some(Ok(c)) => {
let ch = parse_utf8_char(c, iter);
Event::KeyEvent(Key::Alt(try!(ch)))
}
Some(Err(_)) | None => return error,
})
}
Ok(b'\n') | Ok(b'\r') => Ok(Event::KeyEvent(Key::Char('\n'))),
Ok(b'\t') => Ok(Event::KeyEvent(Key::Char('\t'))),
Ok(b'\x7F') => Ok(Event::KeyEvent(Key::Backspace)),
Ok(c @ b'\x01'...b'\x1A') => Ok(Event::KeyEvent(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
Ok(c @ b'\x1C'...b'\x1F') => {
Ok(Event::KeyEvent(Key::Ctrl((c as u8 - 0x1C + b'4') as char)))
}
Ok(b'\0') => Ok(Event::KeyEvent(Key::Null)),
Ok(c) => {
Ok({
let ch = parse_utf8_char(c, iter);
Event::KeyEvent(Key::Char(try!(ch)))
})
}
Err(e) => Err(e),
}
}
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char, Error>
where I: Iterator<Item = Result<u8, Error>>
{
let error = Err(Error::new(ErrorKind::Other, "Input character is not valid UTF-8"));
if c.is_ascii() {
Ok(c as char)
} else {
let ref mut bytes = Vec::new();
bytes.push(c);
loop {
bytes.push(iter.next().unwrap().unwrap());
match str::from_utf8(bytes) {
Ok(st) => return Ok(st.chars().next().unwrap()),
Err(_) => {},
}
if bytes.len() >= 4 { return error; }
}
}
}
use std::io::{self, Read, Write};
use event::{parse_event, Event, Key};
use IntoRawMode;
/// A key.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Key {
/// Backspace.
Backspace,
/// Left arrow.
Left,
/// Right arrow.
Right,
/// Up arrow.
Up,
/// Down arrow.
Down,
/// Home key.
Home,
/// End key.
End,
/// Page Up key.
PageUp,
/// Page Down key.
PageDown,
/// Delete key.
Delete,
/// Insert key.
Insert,
/// Function keys.
///
/// Only function keys 1 through 12 are supported.
F(u8),
/// Normal character.
Char(char),
/// Alt modified character.
Alt(char),
/// Ctrl modified character.
///
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
Ctrl(char),
/// Invalid character code.
Invalid,
/// Null byte.
Null,
/// An iterator over input keys.
pub struct Keys<I> {
iter: Events<I>,
}
#[allow(missing_docs)]
#[doc(hidden)]
__IsNotComplete
impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Keys<I> {
type Item = Result<Key, io::Error>;
fn next(&mut self) -> Option<Result<Key, io::Error>> {
loop {
match self.iter.next() {
Some(Ok(Event::KeyEvent(k))) => return Some(Ok(k)),
Some(Ok(_)) => continue,
e @ Some(Err(_)) => e,
None => return None,
};
}
}
}
/// An iterator over input keys.
#[cfg(feature = "nightly")]
pub struct Keys<I> {
chars: I,
/// An iterator over input events.
pub struct Events<I> {
bytes: I,
}
#[cfg(feature = "nightly")]
impl<I: Iterator<Item = Result<char, io::CharsError>>> Iterator for Keys<I> {
type Item = Result<Key, io::CharsError>;
fn next(&mut self) -> Option<Result<Key, io::CharsError>> {
Some(match self.chars.next() {
Some(Ok('\x1B')) => Ok(match self.chars.next() {
Some(Ok('O')) => match self.chars.next() {
Some(Ok('P')) => Key::F(1),
Some(Ok('Q')) => Key::F(2),
Some(Ok('R')) => Key::F(3),
Some(Ok('S')) => Key::F(4),
_ => Key::Invalid,
},
Some(Ok('[')) => match self.chars.next() {
Some(Ok('D')) => Key::Left,
Some(Ok('C')) => Key::Right,
Some(Ok('A')) => Key::Up,
Some(Ok('B')) => Key::Down,
Some(Ok('H')) => Key::Home,
Some(Ok('F')) => Key::End,
Some(Ok(c @ '1' ... '8')) => match self.chars.next() {
Some(Ok('~')) => match c {
'1' | '7' => Key::Home,
'2'=> Key::Insert,
'3' => Key::Delete,
'4' | '8' => Key::End,
'5' => Key::PageUp,
'6' => Key::PageDown,
_ => Key::Invalid,
},
Some(Ok(k @ '0' ... '9')) => match self.chars.next() {
Some(Ok('~')) => match 10 * (c as u8 - b'0') + (k as u8 - b'0') {
v @ 11 ... 15 => Key::F(v - 10),
v @ 17 ... 21 => Key::F(v - 11),
v @ 23 ... 24 => Key::F(v - 12),
_ => Key::Invalid,
},
_ => Key::Invalid,
},
_ => Key::Invalid,
},
_ => Key::Invalid,
},
Some(Ok(c)) => Key::Alt(c),
Some(Err(_)) | None => Key::Invalid,
}),
Some(Ok('\n')) | Some(Ok('\r')) => Ok(Key::Char('\n')),
Some(Ok('\t')) => Ok(Key::Char('\t')),
Some(Ok('\x7F')) => Ok(Key::Backspace),
Some(Ok(c @ '\x01' ... '\x1A')) => Ok(Key::Ctrl((c as u8 - 0x1 + b'a') as char)),
Some(Ok(c @ '\x1C' ... '\x1F')) => Ok(Key::Ctrl((c as u8 - 0x1C + b'4') as char)),
Some(Ok('\0')) => Ok(Key::Null),
Some(Ok(c)) => Ok(Key::Char(c)),
Some(Err(e)) => Err(e),
None => return None,
})
impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Events<I> {
type Item = Result<Event, io::Error>;
fn next(&mut self) -> Option<Result<Event, io::Error>> {
let ref mut iter = self.bytes;
match iter.next() {
Some(item) => Some(parse_event(item, iter).or(Ok(Event::Unsupported))),
None => None,
}
}
}
/// Extension to `Read` trait.
pub trait TermRead {
/// An iterator over input events.
fn events(self) -> Events<io::Bytes<Self>> where Self: Sized;
/// An iterator over key inputs.
#[cfg(feature = "nightly")]
fn keys(self) -> Keys<io::Chars<Self>> where Self: Sized;
fn keys(self) -> Keys<io::Bytes<Self>> where Self: Sized;
/// Read a line.
///
......@@ -140,10 +67,14 @@ pub trait TermRead {
impl<R: Read> TermRead for R {
#[cfg(feature = "nightly")]
fn keys(self) -> Keys<io::Chars<R>> {
fn events(self) -> Events<io::Bytes<R>> {
Events {
bytes: self.bytes(),
}
}
fn keys(self) -> Keys<io::Bytes<R>> {
Keys {
chars: self.chars(),
iter: self.events(),
}
}
......@@ -170,7 +101,6 @@ mod test {
use super::*;
use std::io;
#[cfg(feature = "nightly")]
#[test]
fn test_keys() {
let mut i = b"\x1Bayo\x7F\x1B[D".keys();
......@@ -183,7 +113,6 @@ mod test {
assert!(i.next().is_none());
}
#[cfg(feature = "nightly")]
#[test]
fn test_function_keys() {
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
......@@ -198,7 +127,6 @@ mod test {
}
}
#[cfg(feature = "nightly")]
#[test]
fn test_special_keys() {
let mut st = b"\x1B[2~\x1B[H\x1B[7~\x1B[5~\x1B[3~\x1B[F\x1B[8~\x1B[6~".keys();
......
......@@ -11,9 +11,6 @@
//! For more information refer to the [README](https://github.com/ticki/termion).
#![warn(missing_docs)]
#![cfg_attr(feature = "nightly", feature(io))]
#[cfg(not(target_os = "redox"))]
extern crate libc;
......@@ -27,9 +24,10 @@ mod async;
pub use async::{AsyncReader, async_stdin};
mod input;
pub use input::{TermRead, Key};
#[cfg(feature = "nightly")]
pub use input::Keys;
pub use input::{TermRead, Events, Keys};
mod event;
pub use event::{Key, Mouse, MouseButton, Event};
mod raw;
pub use raw::{IntoRawMode, RawTerminal};
......
use std::io::{self, Write};
use std::ops::{Deref, DerefMut};
const ENTER_MOUSE_SEQUENCE: &'static[u8] = b"\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h";
const EXIT_MOUSE_SEQUENCE: &'static[u8] = b"\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l";
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when