Merge
This commit is contained in:
commit
5b94db9663
|
@ -5,7 +5,3 @@ authors = ["Ticki <Ticki@users.noreply.github.com>"]
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "redox"))'.dependencies]
|
[target.'cfg(not(target_os = "redox"))'.dependencies]
|
||||||
libc = "0.2.8"
|
libc = "0.2.8"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["nightly"]
|
|
||||||
nightly = []
|
|
||||||
|
|
15
README.md
15
README.md
|
@ -19,21 +19,11 @@ and this crate can generally be considered stable.
|
||||||
|
|
||||||
## Cargo.toml
|
## Cargo.toml
|
||||||
|
|
||||||
For nightly, add
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies.termion]
|
[dependencies.termion]
|
||||||
git = "https://github.com/ticki/termion.git"
|
git = "https://github.com/ticki/termion.git"
|
||||||
```
|
```
|
||||||
|
|
||||||
For stable,
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies.termion]
|
|
||||||
git = "https://github.com/ticki/termion.git"
|
|
||||||
default-features = false
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Raw mode.
|
- Raw mode.
|
||||||
|
@ -53,6 +43,7 @@ default-features = false
|
||||||
- Special keys events (modifiers, special keys, etc.).
|
- Special keys events (modifiers, special keys, etc.).
|
||||||
- Allocation-free.
|
- Allocation-free.
|
||||||
- Asynchronous key events.
|
- Asynchronous key events.
|
||||||
|
- Mouse input
|
||||||
- Carefully tested.
|
- Carefully tested.
|
||||||
|
|
||||||
and much more.
|
and much more.
|
||||||
|
@ -86,10 +77,6 @@ For a more complete example, see [a minesweeper implementation](https://github.c
|
||||||
<img src="image.png" width="200">
|
<img src="image.png" width="200">
|
||||||
|
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- Mouse input
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT/X11.
|
MIT/X11.
|
||||||
|
|
|
@ -18,15 +18,14 @@ fn main() {
|
||||||
|
|
||||||
match c.unwrap() {
|
match c.unwrap() {
|
||||||
Key::Char('q') => break,
|
Key::Char('q') => break,
|
||||||
Key::Char(c) => writeln!(stdout, "{}", c).unwrap(),
|
Key::Char(c) => println!("{}", c),
|
||||||
Key::Alt(c) => writeln!(stdout, "^{}", c).unwrap(),
|
Key::Alt(c) => println!("^{}", c),
|
||||||
Key::Ctrl(c) => writeln!(stdout, "*{}", c).unwrap(),
|
Key::Ctrl(c) => println!("*{}", c),
|
||||||
Key::Left => writeln!(stdout, "←").unwrap(),
|
Key::Left => println!("←"),
|
||||||
Key::Right => writeln!(stdout, "→").unwrap(),
|
Key::Right => println!("→"),
|
||||||
Key::Up => writeln!(stdout, "↑").unwrap(),
|
Key::Up => println!("↑"),
|
||||||
Key::Down => writeln!(stdout, "↓").unwrap(),
|
Key::Down => println!("↓"),
|
||||||
Key::Backspace => writeln!(stdout, "×").unwrap(),
|
Key::Backspace => println!("×"),
|
||||||
Key::Invalid => writeln!(stdout, "???").unwrap(),
|
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
stdout.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
extern crate termion;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
use termion::{TermRead, TermWrite, IntoRawMode, Key, Event, MouseEvent};
|
||||||
|
use std::io::{Write, stdout, stdin};
|
||||||
|
|
||||||
|
let stdin = stdin();
|
||||||
|
let mut stdout = stdout().into_raw_mode().unwrap().with_mouse().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();
|
||||||
|
let evt = c.unwrap();
|
||||||
|
match evt {
|
||||||
|
Event::Key(Key::Char('q')) => break,
|
||||||
|
Event::Mouse(me) => {
|
||||||
|
match me {
|
||||||
|
MouseEvent::Press(_, a, b) |
|
||||||
|
MouseEvent::Release(a, b) => {
|
||||||
|
x = a;
|
||||||
|
y = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
println!("{:?}", evt);
|
||||||
|
stdout.goto(x, y).unwrap();
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.show_cursor().unwrap();
|
||||||
|
}
|
|
@ -46,3 +46,23 @@ impl fmt::Display for Right {
|
||||||
write!(f, csi!("{}C"), self.0)
|
write!(f, csi!("{}C"), self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Move cursor up.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Up(pub u16);
|
||||||
|
|
||||||
|
impl fmt::Display for Up {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, csi!("{}A"), self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move cursor down.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Down(pub u16);
|
||||||
|
|
||||||
|
impl fmt::Display for Down {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, csi!("{}B"), self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
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.
|
||||||
|
Key(Key),
|
||||||
|
/// A mouse button press, release or wheel use at specific coordinates.
|
||||||
|
Mouse(MouseEvent),
|
||||||
|
/// An event that cannot currently be evaluated.
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mouse related event.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MouseEvent {
|
||||||
|
/// A mouse button was pressed.
|
||||||
|
Press(MouseButton, u16, u16),
|
||||||
|
/// A mouse button was released.
|
||||||
|
Release(u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
|
||||||
|
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(val @ b'P' ... b'S')) => Event::Key(Key::F(1 + val - b'P')),
|
||||||
|
_ => return error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Ok(b'[')) => {
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ok(b'D')) => Event::Key(Key::Left),
|
||||||
|
Some(Ok(b'C')) => Event::Key(Key::Right),
|
||||||
|
Some(Ok(b'A')) => Event::Key(Key::Up),
|
||||||
|
Some(Ok(b'B')) => Event::Key(Key::Down),
|
||||||
|
Some(Ok(b'H')) => Event::Key(Key::Home),
|
||||||
|
Some(Ok(b'F')) => Event::Key(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) as u16;
|
||||||
|
let cy = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32) as u16;
|
||||||
|
Event::Mouse(match cb & 0b11 {
|
||||||
|
0 => {
|
||||||
|
if cb & 0x40 != 0 {
|
||||||
|
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
|
||||||
|
} else {
|
||||||
|
MouseEvent::Press(MouseButton::Left, cx, cy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
if cb & 0x40 != 0 {
|
||||||
|
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
|
||||||
|
} else {
|
||||||
|
MouseEvent::Press(MouseButton::Middle, cx, cy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
|
||||||
|
3 => MouseEvent::Release(cx, cy),
|
||||||
|
_ => return error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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::Mouse(match c {
|
||||||
|
b'M' => MouseEvent::Press(button, cx, cy),
|
||||||
|
b'm' => MouseEvent::Release(cx, cy),
|
||||||
|
_ => return error,
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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 => MouseEvent::Press(MouseButton::Left, cx, cy),
|
||||||
|
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
|
||||||
|
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
|
||||||
|
35 => MouseEvent::Release(cx, cy),
|
||||||
|
96 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
|
||||||
|
97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
|
||||||
|
_ => return error,
|
||||||
|
};
|
||||||
|
|
||||||
|
Event::Mouse(event)
|
||||||
|
},
|
||||||
|
// Special key code.
|
||||||
|
b'~' => {
|
||||||
|
let num: u8 = String::from_utf8(buf).unwrap().parse().unwrap();
|
||||||
|
match num {
|
||||||
|
1 | 7 => Event::Key(Key::Home),
|
||||||
|
2 => Event::Key(Key::Insert),
|
||||||
|
3 => Event::Key(Key::Delete),
|
||||||
|
4 | 8 => Event::Key(Key::End),
|
||||||
|
5 => Event::Key(Key::PageUp),
|
||||||
|
6 => Event::Key(Key::PageDown),
|
||||||
|
v @ 11...15 => Event::Key(Key::F(v - 10)),
|
||||||
|
v @ 17...21 => Event::Key(Key::F(v - 11)),
|
||||||
|
v @ 23...24 => Event::Key(Key::F(v - 12)),
|
||||||
|
_ => return error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Ok(c)) => {
|
||||||
|
let ch = parse_utf8_char(c, iter);
|
||||||
|
Event::Key(Key::Alt(try!(ch)))
|
||||||
|
}
|
||||||
|
Some(Err(_)) | None => return error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(b'\n') | Ok(b'\r') => Ok(Event::Key(Key::Char('\n'))),
|
||||||
|
Ok(b'\t') => Ok(Event::Key(Key::Char('\t'))),
|
||||||
|
Ok(b'\x7F') => Ok(Event::Key(Key::Backspace)),
|
||||||
|
Ok(c @ b'\x01'...b'\x1A') => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
|
||||||
|
Ok(c @ b'\x1C'...b'\x1F') => {
|
||||||
|
Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char)))
|
||||||
|
}
|
||||||
|
Ok(b'\0') => Ok(Event::Key(Key::Null)),
|
||||||
|
Ok(c) => {
|
||||||
|
Ok({
|
||||||
|
let ch = parse_utf8_char(c, iter);
|
||||||
|
Event::Key(Key::Char(try!(ch)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_utf8() {
|
||||||
|
let st = "abcéŷ¤£€ù%323";
|
||||||
|
let ref mut bytes = st.bytes().map(|x| Ok(x));
|
||||||
|
let chars = st.chars();
|
||||||
|
for c in chars {
|
||||||
|
let b = bytes.next().unwrap().unwrap();
|
||||||
|
assert!(c == parse_utf8_char(b, bytes).unwrap());
|
||||||
|
}
|
||||||
|
}
|
152
src/input.rs
152
src/input.rs
|
@ -1,81 +1,55 @@
|
||||||
//! Input.
|
//! Input.
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
use event::{parse_event, Event, Key};
|
||||||
|
|
||||||
use raw::IntoRawMode;
|
use raw::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,
|
|
||||||
/// 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,
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[doc(hidden)]
|
|
||||||
__IsNotComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over input keys.
|
/// An iterator over input keys.
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub struct Keys<I> {
|
pub struct Keys<I> {
|
||||||
chars: I,
|
iter: Events<I>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Keys<I> {
|
||||||
impl<I: Iterator<Item = Result<char, io::CharsError>>> Iterator for Keys<I> {
|
type Item = Result<Key, io::Error>;
|
||||||
type Item = Result<Key, io::CharsError>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Result<Key, io::CharsError>> {
|
fn next(&mut self) -> Option<Result<Key, io::Error>> {
|
||||||
Some(match self.chars.next() {
|
loop {
|
||||||
Some(Ok('\x1B')) => Ok(match self.chars.next() {
|
match self.iter.next() {
|
||||||
Some(Ok('[')) => match self.chars.next() {
|
Some(Ok(Event::Key(k))) => return Some(Ok(k)),
|
||||||
Some(Ok('D')) => Key::Left,
|
Some(Ok(_)) => continue,
|
||||||
Some(Ok('C')) => Key::Right,
|
e @ Some(Err(_)) => e,
|
||||||
Some(Ok('A')) => Key::Up,
|
|
||||||
Some(Ok('B')) => Key::Down,
|
|
||||||
_ => 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,
|
None => return None,
|
||||||
})
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over input events.
|
||||||
|
pub struct Events<I> {
|
||||||
|
bytes: I,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
/// Extension to `Read` trait.
|
||||||
pub trait TermRead {
|
pub trait TermRead {
|
||||||
|
/// An iterator over input events.
|
||||||
|
fn events(self) -> Events<io::Bytes<Self>> where Self: Sized;
|
||||||
|
|
||||||
/// An iterator over key inputs.
|
/// An iterator over key inputs.
|
||||||
#[cfg(feature = "nightly")]
|
fn keys(self) -> Keys<io::Bytes<Self>> where Self: Sized;
|
||||||
fn keys(self) -> Keys<io::Chars<Self>> where Self: Sized;
|
|
||||||
|
|
||||||
/// Read a line.
|
/// Read a line.
|
||||||
///
|
///
|
||||||
|
@ -95,10 +69,14 @@ pub trait TermRead {
|
||||||
|
|
||||||
|
|
||||||
impl<R: Read> TermRead for R {
|
impl<R: Read> TermRead for R {
|
||||||
#[cfg(feature = "nightly")]
|
fn events(self) -> Events<io::Bytes<R>> {
|
||||||
fn keys(self) -> Keys<io::Chars<R>> {
|
Events {
|
||||||
|
bytes: self.bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn keys(self) -> Keys<io::Bytes<R>> {
|
||||||
Keys {
|
Keys {
|
||||||
chars: self.chars(),
|
iter: self.events(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +102,8 @@ impl<R: Read> TermRead for R {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use event::{Key, Event, MouseEvent, MouseButton};
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys() {
|
fn test_keys() {
|
||||||
let mut i = b"\x1Bayo\x7F\x1B[D".keys();
|
let mut i = b"\x1Bayo\x7F\x1B[D".keys();
|
||||||
|
@ -138,6 +116,53 @@ mod test {
|
||||||
assert!(i.next().is_none());
|
assert!(i.next().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_events() {
|
||||||
|
let mut i = b"\x1B[\x00bc\x7F\x1B[D\
|
||||||
|
\x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb".events();
|
||||||
|
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Unsupported);
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('c')));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Backspace));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Left));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 1, 3)));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 1, 3)));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 1, 3)));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3)));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3)));
|
||||||
|
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
|
||||||
|
assert!(i.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_keys() {
|
||||||
|
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
|
||||||
|
for i in 1 .. 5 {
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut st = b"\x1B[11~\x1B[12~\x1B[13~\x1B[14~\x1B[15~\
|
||||||
|
\x1B[17~\x1B[18~\x1B[19~\x1B[20~\x1B[21~\x1B[23~\x1B[24~".keys();
|
||||||
|
for i in 1 .. 13 {
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::Insert);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::Home);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::Home);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::PageUp);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::Delete);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::End);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::End);
|
||||||
|
assert_eq!(st.next().unwrap().unwrap(), Key::PageDown);
|
||||||
|
assert!(st.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
fn line_match(a: &str, b: Option<&str>) {
|
fn line_match(a: &str, b: Option<&str>) {
|
||||||
let mut sink = io::sink();
|
let mut sink = io::sink();
|
||||||
|
|
||||||
|
@ -181,4 +206,5 @@ mod test {
|
||||||
line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None);
|
line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None);
|
||||||
line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None);
|
line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,6 @@
|
||||||
//! For more information refer to the [README](https://github.com/ticki/termion).
|
//! For more information refer to the [README](https://github.com/ticki/termion).
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
#![cfg_attr(feature = "nightly", feature(io))]
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
|
@ -34,6 +31,8 @@ mod macros;
|
||||||
pub mod clear;
|
pub mod clear;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
|
pub mod event;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
|
pub mod scroll;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
83
src/raw.rs
83
src/raw.rs
|
@ -28,6 +28,17 @@ pub struct RawTerminal<W: Write> {
|
||||||
output: W,
|
output: W,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
impl<W> RawTerminal<W>
|
||||||
|
where W: Write
|
||||||
|
{
|
||||||
|
/// Enable mouse support.
|
||||||
|
pub fn with_mouse(mut self) -> io::Result<MouseTerminal<W>> {
|
||||||
|
try!(self.write(ENTER_MOUSE_SEQUENCE));
|
||||||
|
Ok(MouseTerminal { term: self })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
impl<W: Write> Drop for RawTerminal<W> {
|
impl<W: Write> Drop for RawTerminal<W> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
@ -64,9 +75,9 @@ impl<W: Write> Write for RawTerminal<W> {
|
||||||
pub trait IntoRawMode: Write + Sized {
|
pub trait IntoRawMode: Write + Sized {
|
||||||
/// Switch to raw mode.
|
/// Switch to raw mode.
|
||||||
///
|
///
|
||||||
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by the
|
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by
|
||||||
/// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from
|
/// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can
|
||||||
/// stdin one byte of a time). The output is neither modified in any way.
|
/// read from stdin one byte of a time). The output is neither modified in any way.
|
||||||
fn into_raw_mode(self) -> io::Result<RawTerminal<Self>>;
|
fn into_raw_mode(self) -> io::Result<RawTerminal<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,10 +99,11 @@ impl<W: Write> IntoRawMode for W {
|
||||||
if set_terminal_attr(&mut ios as *mut _) != 0 {
|
if set_terminal_attr(&mut ios as *mut _) != 0 {
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute."))
|
Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute."))
|
||||||
} else {
|
} else {
|
||||||
Ok(RawTerminal {
|
let res = RawTerminal {
|
||||||
prev_ios: prev_ios,
|
prev_ios: prev_ios,
|
||||||
output: self,
|
output: self,
|
||||||
})
|
};
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,12 +111,63 @@ impl<W: Write> IntoRawMode for W {
|
||||||
fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> {
|
fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> {
|
||||||
use control::TermWrite;
|
use control::TermWrite;
|
||||||
|
|
||||||
self.csi(b"r").map(|_| RawTerminal {
|
self.csi(b"r").map(|_| {
|
||||||
output: self,
|
let mut res = RawTerminal { output: self };
|
||||||
|
res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A sequence of escape codes to enable terminal mouse support.
|
||||||
|
const ENTER_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h";
|
||||||
|
|
||||||
|
/// A sequence of escape codes to disable terminal mouse support.
|
||||||
|
const EXIT_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l";
|
||||||
|
|
||||||
|
/// A `RawTerminal` with added mouse support.
|
||||||
|
///
|
||||||
|
/// To get such a terminal handle use `RawTerminal`'s
|
||||||
|
/// [`with_mouse()`](../termion/struct.RawTerminal.html#method.with_mouse) method.
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
pub struct MouseTerminal<W: Write> {
|
||||||
|
term: RawTerminal<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
impl<W: Write> Drop for MouseTerminal<W> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.term.write(EXIT_MOUSE_SEQUENCE).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
impl<W: Write> Deref for MouseTerminal<W> {
|
||||||
|
type Target = W;
|
||||||
|
|
||||||
|
fn deref(&self) -> &W {
|
||||||
|
self.term.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
impl<W: Write> DerefMut for MouseTerminal<W> {
|
||||||
|
fn deref_mut(&mut self) -> &mut W {
|
||||||
|
self.term.deref_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
impl<W: Write> Write for MouseTerminal<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.term.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.term.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -116,4 +179,10 @@ mod test {
|
||||||
|
|
||||||
out.write(b"this is a test, muahhahahah").unwrap();
|
out.write(b"this is a test, muahhahahah").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enable_mouse() {
|
||||||
|
let mut out = stdout().into_raw_mode().unwrap().with_mouse().unwrap();
|
||||||
|
out.write(b"abcde\x1B[<1;1;0;Mfgh").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/// Scroll up.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Up(pub u16);
|
||||||
|
|
||||||
|
impl fmt::Display for Up {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, csi!("{}S"), self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll down.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Down(pub u16);
|
||||||
|
|
||||||
|
impl fmt::Display for Down {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, csi!("{}T"), self.0)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue