Merge pull request #29 from IGI-111/master

Implemented mouse input
This commit is contained in:
ticki 2016-07-20 13:54:56 +02:00 committed by GitHub
commit fb1d702b13
8 changed files with 469 additions and 152 deletions

View File

@ -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 = []

View File

@ -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.
@ -51,6 +41,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.
@ -92,10 +83,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.

View File

@ -1,6 +1,5 @@
extern crate termion; extern crate termion;
#[cfg(feature = "nightly")]
fn main() { fn main() {
use termion::{TermRead, TermWrite, IntoRawMode, Key}; use termion::{TermRead, TermWrite, IntoRawMode, Key};
use std::io::{Write, stdout, stdin}; use std::io::{Write, stdout, stdin};
@ -27,7 +26,6 @@ fn main() {
Key::Up => println!(""), Key::Up => println!(""),
Key::Down => println!(""), Key::Down => println!(""),
Key::Backspace => println!("×"), Key::Backspace => println!("×"),
Key::Invalid => println!("???"),
_ => {}, _ => {},
} }
stdout.flush().unwrap(); stdout.flush().unwrap();
@ -35,8 +33,3 @@ fn main() {
stdout.show_cursor().unwrap(); 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`.")
}

41
examples/mouse.rs Normal file
View File

@ -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();
}

285
src/event.rs Normal file
View File

@ -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());
}
}

View File

@ -1,126 +1,53 @@
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use event::{parse_event, Event, Key};
use IntoRawMode; 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,
#[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('O')) => match self.chars.next() { Some(Ok(Event::Key(k))) => return Some(Ok(k)),
Some(Ok('P')) => Key::F(1), Some(Ok(_)) => continue,
Some(Ok('Q')) => Key::F(2), e @ Some(Err(_)) => e,
Some(Ok('R')) => Key::F(3), None => return None,
Some(Ok('S')) => Key::F(4), };
_ => Key::Invalid, }
}, }
Some(Ok('[')) => match self.chars.next() { }
Some(Ok('D')) => Key::Left,
Some(Ok('C')) => Key::Right, /// An iterator over input events.
Some(Ok('A')) => Key::Up, pub struct Events<I> {
Some(Ok('B')) => Key::Down, bytes: I,
Some(Ok('H')) => Key::Home, }
Some(Ok('F')) => Key::End,
Some(Ok(c @ '1' ... '8')) => match self.chars.next() { impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Events<I> {
Some(Ok('~')) => match c { type Item = Result<Event, io::Error>;
'1' | '7' => Key::Home,
'2'=> Key::Insert, fn next(&mut self) -> Option<Result<Event, io::Error>> {
'3' => Key::Delete, let ref mut iter = self.bytes;
'4' | '8' => Key::End, match iter.next() {
'5' => Key::PageUp, Some(item) => Some(parse_event(item, iter).or(Ok(Event::Unsupported))),
'6' => Key::PageDown, None => None,
_ => 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,
})
} }
} }
/// 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.
/// ///
@ -140,10 +67,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(),
} }
} }
@ -169,8 +100,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();
@ -183,7 +114,25 @@ mod test {
assert!(i.next().is_none()); assert!(i.next().is_none());
} }
#[cfg(feature = "nightly")] #[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] #[test]
fn test_function_keys() { fn test_function_keys() {
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys(); let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
@ -198,7 +147,6 @@ mod test {
} }
} }
#[cfg(feature = "nightly")]
#[test] #[test]
fn test_special_keys() { 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(); let mut st = b"\x1B[2~\x1B[H\x1B[7~\x1B[5~\x1B[3~\x1B[F\x1B[8~\x1B[6~".keys();

View File

@ -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;
@ -27,12 +24,13 @@ mod async;
pub use async::{AsyncReader, async_stdin}; pub use async::{AsyncReader, async_stdin};
mod input; mod input;
pub use input::{TermRead, Key}; pub use input::{TermRead, Events, Keys};
#[cfg(feature = "nightly")]
pub use input::Keys; mod event;
pub use event::{Key, MouseEvent, MouseButton, Event};
mod raw; mod raw;
pub use raw::{IntoRawMode, RawTerminal}; pub use raw::{IntoRawMode, RawTerminal, MouseTerminal};
mod size; mod size;
pub use size::terminal_size; pub use size::terminal_size;

View File

@ -26,6 +26,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) {
@ -62,9 +73,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>>;
} }
@ -86,10 +97,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)
} }
} }
@ -97,12 +109,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::*;
@ -114,4 +177,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();
}
} }