From 7a723b0ef53db242b95fd73739f8b18a4343e2bf Mon Sep 17 00:00:00 2001 From: Philip Munksgaard Date: Wed, 13 Jul 2016 23:43:37 +0200 Subject: [PATCH 01/12] Add more controls Add support for scrolling up and down, and moving the cursor up and down. --- src/control.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/control.rs b/src/control.rs index 810a087..7d0bd74 100644 --- a/src/control.rs +++ b/src/control.rs @@ -91,6 +91,66 @@ pub trait TermWrite { } } + /// Move the cursor `num` spaces up. + #[inline] + fn move_cursor_up(&mut self, num: u32) -> io::Result { + if num > 0 { + self.csi(&[b'0' + (num / 10000) as u8, + b'0' + (num / 1000) as u8 % 10, + b'0' + (num / 100) as u8 % 10, + b'0' + (num / 10) as u8 % 10, + b'0' + num as u8 % 10, + b'A']) + } else { + Ok(0) + } + } + + /// Move the cursor `num` spaces down. + #[inline] + fn move_cursor_down(&mut self, num: u32) -> io::Result { + if num > 0 { + self.csi(&[b'0' + (num / 10000) as u8, + b'0' + (num / 1000) as u8 % 10, + b'0' + (num / 100) as u8 % 10, + b'0' + (num / 10) as u8 % 10, + b'0' + num as u8 % 10, + b'B']) + } else { + Ok(0) + } + } + + /// Scroll the window up. + #[inline] + fn scroll_up(&mut self, num: u32) -> io::Result { + if num > 0 { + self.csi(&[b'0' + (num / 10000) as u8, + b'0' + (num / 1000) as u8 % 10, + b'0' + (num / 100) as u8 % 10, + b'0' + (num / 10) as u8 % 10, + b'0' + num as u8 % 10, + b'S']) + } else { + Ok(0) + } + } + + /// Scroll the window down. + #[inline] + fn scroll_down(&mut self, num: u32) -> io::Result { + if num > 0 { + self.csi(&[b'0' + (num / 10000) as u8, + b'0' + (num / 1000) as u8 % 10, + b'0' + (num / 100) as u8 % 10, + b'0' + (num / 10) as u8 % 10, + b'0' + num as u8 % 10, + b'T']) + } else { + Ok(0) + } + } + /// Reset the rendition mode. /// /// This will reset both the current style and color. From fa75b334d0efd13184d8b71fcd5738b924f15601 Mon Sep 17 00:00:00 2001 From: Philip Munksgaard Date: Thu, 14 Jul 2016 21:51:00 +0200 Subject: [PATCH 02/12] Improve function documentation --- src/control.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/control.rs b/src/control.rs index 7d0bd74..3d61109 100644 --- a/src/control.rs +++ b/src/control.rs @@ -121,7 +121,7 @@ pub trait TermWrite { } } - /// Scroll the window up. + /// Scroll the window `num` spaces up. #[inline] fn scroll_up(&mut self, num: u32) -> io::Result { if num > 0 { @@ -136,7 +136,7 @@ pub trait TermWrite { } } - /// Scroll the window down. + /// Scroll the window `num` spaces down. #[inline] fn scroll_down(&mut self, num: u32) -> io::Result { if num > 0 { From 0e74a7672fd8528b1f62e2c6be151000a5948eec Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Fri, 15 Jul 2016 07:41:31 +0200 Subject: [PATCH 03/12] added more special keys Key now supports Home, End, PageUp, PageDown, Delete, Insert and Function keys. All this is done through the detection of both VT100 escape codes and more modern standard counterparts. For instance, F2 can be both ESC OQ on VT100, screen, and some versions of xterm and ESC [12~ on rxvt and other xterm versions depending on your terminal --- src/input.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/input.rs b/src/input.rs index 7258d34..921ccb1 100644 --- a/src/input.rs +++ b/src/input.rs @@ -15,6 +15,20 @@ pub enum Key { 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. + F(u8), /// Normal character. Char(char), /// Alt modified character. @@ -28,6 +42,7 @@ pub enum Key { /// Null byte. Null, + #[allow(missing_docs)] #[doc(hidden)] __IsNotComplete @@ -46,11 +61,50 @@ impl>> Iterator for Keys { fn next(&mut self) -> Option> { 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' ... '6')) => match self.chars.next() { + Some(Ok('~')) => match c { + '1' => Key::Home, + '2' => Key::Insert, + '3' => Key::Delete, + '4' => Key::End, + '5' => Key::PageUp, + '6' => Key::PageDown, + _ => Key::Invalid, + }, + Some(Ok(k @ '0' ... '9')) => match self.chars.next() { + Some(Ok('~')) => match (c, k) { + ('1', '1') => Key::F(1), + ('1', '2') => Key::F(2), + ('1', '3') => Key::F(3), + ('1', '4') => Key::F(4), + ('1', '5') => Key::F(5), + ('1', '7') => Key::F(6), + ('1', '8') => Key::F(7), + ('1', '9') => Key::F(8), + ('2', '0') => Key::F(9), + ('2', '1') => Key::F(10), + ('2', '3') => Key::F(11), + ('2', '4') => Key::F(12), + _ => Key::Invalid, + }, + _ => Key::Invalid, + }, + _ => Key::Invalid, + }, _ => Key::Invalid, }, Some(Ok(c)) => Key::Alt(c), From 3552c6eae0e6abf97a969b5785fcf2deacd239b1 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Fri, 15 Jul 2016 12:19:02 +0200 Subject: [PATCH 04/12] added rxvt Home and End escape codes --- src/input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input.rs b/src/input.rs index 921ccb1..46ff6f3 100644 --- a/src/input.rs +++ b/src/input.rs @@ -77,8 +77,8 @@ impl>> Iterator for Keys { Some(Ok('F')) => Key::End, Some(Ok(c @ '1' ... '6')) => match self.chars.next() { Some(Ok('~')) => match c { - '1' => Key::Home, - '2' => Key::Insert, + '1' | '7' => Key::Home, + '2' | '8' => Key::Insert, '3' => Key::Delete, '4' => Key::End, '5' => Key::PageUp, From 70c12b20d6a772084450ae3b1ed2b6435670f9ca Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Sat, 16 Jul 2016 18:48:00 +0200 Subject: [PATCH 05/12] added doc for function keys --- src/input.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/input.rs b/src/input.rs index 46ff6f3..d89893f 100644 --- a/src/input.rs +++ b/src/input.rs @@ -28,6 +28,8 @@ pub enum Key { /// Insert key. Insert, /// Function keys. + /// + /// Only function keys 1 through 12 are supported. F(u8), /// Normal character. Char(char), From 5eae7cf7324c080611af4e6fd3bc3a03b3897f58 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Sat, 16 Jul 2016 19:10:04 +0200 Subject: [PATCH 06/12] function keys now use ranges for detection --- src/input.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/input.rs b/src/input.rs index d89893f..21a80dd 100644 --- a/src/input.rs +++ b/src/input.rs @@ -88,19 +88,10 @@ impl>> Iterator for Keys { _ => Key::Invalid, }, Some(Ok(k @ '0' ... '9')) => match self.chars.next() { - Some(Ok('~')) => match (c, k) { - ('1', '1') => Key::F(1), - ('1', '2') => Key::F(2), - ('1', '3') => Key::F(3), - ('1', '4') => Key::F(4), - ('1', '5') => Key::F(5), - ('1', '7') => Key::F(6), - ('1', '8') => Key::F(7), - ('1', '9') => Key::F(8), - ('2', '0') => Key::F(9), - ('2', '1') => Key::F(10), - ('2', '3') => Key::F(11), - ('2', '4') => Key::F(12), + 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, From 4402ebd8b38baed0fc3e4f0e8eda8f6c800b40ed Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Sat, 16 Jul 2016 19:21:51 +0200 Subject: [PATCH 07/12] fix wrong range used --- src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input.rs b/src/input.rs index 21a80dd..fdb8da2 100644 --- a/src/input.rs +++ b/src/input.rs @@ -77,7 +77,7 @@ impl>> Iterator for Keys { Some(Ok('B')) => Key::Down, Some(Ok('H')) => Key::Home, Some(Ok('F')) => Key::End, - Some(Ok(c @ '1' ... '6')) => match self.chars.next() { + Some(Ok(c @ '1' ... '8')) => match self.chars.next() { Some(Ok('~')) => match c { '1' | '7' => Key::Home, '2' | '8' => Key::Insert, From 2f6ebb8669c72941bb21cf88a7a4d56de01d54e4 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Sat, 16 Jul 2016 22:46:29 +0200 Subject: [PATCH 08/12] added tests --- src/input.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/input.rs b/src/input.rs index fdb8da2..824bb49 100644 --- a/src/input.rs +++ b/src/input.rs @@ -80,9 +80,9 @@ impl>> Iterator for Keys { Some(Ok(c @ '1' ... '8')) => match self.chars.next() { Some(Ok('~')) => match c { '1' | '7' => Key::Home, - '2' | '8' => Key::Insert, + '2'=> Key::Insert, '3' => Key::Delete, - '4' => Key::End, + '4' | '8' => Key::End, '5' => Key::PageUp, '6' => Key::PageDown, _ => Key::Invalid, @@ -183,6 +183,36 @@ mod test { assert!(i.next().is_none()); } + #[cfg(feature = "nightly")] + #[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)); + } + } + + #[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(); + 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>) { let mut sink = io::sink(); @@ -226,4 +256,5 @@ mod test { line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None); line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None); } + } From cc9c32b981363771c7a6e011aa103255277d157b Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Tue, 19 Jul 2016 02:31:34 +0200 Subject: [PATCH 09/12] 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. --- Cargo.toml | 4 - README.md | 15 +-- examples/keys.rs | 7 -- examples/test.rs | 35 ++++++ src/event.rs | 277 +++++++++++++++++++++++++++++++++++++++++++++++ src/input.rs | 154 +++++++------------------- src/lib.rs | 10 +- src/raw.rs | 17 ++- 8 files changed, 372 insertions(+), 147 deletions(-) create mode 100644 examples/test.rs create mode 100644 src/event.rs diff --git a/Cargo.toml b/Cargo.toml index 4f0f119..aa8505a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,3 @@ authors = ["Ticki "] [target.'cfg(not(target_os = "redox"))'.dependencies] libc = "0.2.8" - -[features] -default = ["nightly"] -nightly = [] diff --git a/README.md b/README.md index 69560de..cd8896d 100644 --- a/README.md +++ b/README.md @@ -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 -## TODO - -- Mouse input - ## License MIT/X11. diff --git a/examples/keys.rs b/examples/keys.rs index 55d2f83..4427d7f 100644 --- a/examples/keys.rs +++ b/examples/keys.rs @@ -1,6 +1,5 @@ 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`.") -} diff --git a/examples/test.rs b/examples/test.rs new file mode 100644 index 0000000..48ba1f5 --- /dev/null +++ b/examples/test.rs @@ -0,0 +1,35 @@ +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(); +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..cd98b3c --- /dev/null +++ b/src/event.rs @@ -0,0 +1,277 @@ +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(item: Result, iter: &mut I) -> Result +where I: Iterator> +{ + 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::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap() - 1; + let cy = nums.next().unwrap().parse::().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::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap() - 1; + let cy = nums.next().unwrap().parse::().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(c: u8, iter: &mut I) -> Result +where I: Iterator> +{ + 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; } + } + } +} diff --git a/src/input.rs b/src/input.rs index 824bb49..32c51ed 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,126 +1,53 @@ 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, - - - #[allow(missing_docs)] - #[doc(hidden)] - __IsNotComplete -} /// An iterator over input keys. -#[cfg(feature = "nightly")] pub struct Keys { - chars: I, + iter: Events, } -#[cfg(feature = "nightly")] -impl>> Iterator for Keys { - type Item = Result; +impl>> Iterator for Keys { + type Item = Result; - fn next(&mut self) -> Option> { - 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, - }) + fn next(&mut self) -> Option> { + 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 events. +pub struct Events { + bytes: I, +} + +impl>> Iterator for Events { + type Item = Result; + + fn next(&mut self) -> Option> { + 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> where Self: Sized; + /// An iterator over key inputs. - #[cfg(feature = "nightly")] - fn keys(self) -> Keys> where Self: Sized; + fn keys(self) -> Keys> where Self: Sized; /// Read a line. /// @@ -140,10 +67,14 @@ pub trait TermRead { impl TermRead for R { - #[cfg(feature = "nightly")] - fn keys(self) -> Keys> { + fn events(self) -> Events> { + Events { + bytes: self.bytes(), + } + } + fn keys(self) -> Keys> { 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(); diff --git a/src/lib.rs b/src/lib.rs index 8a78981..c1d84b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/raw.rs b/src/raw.rs index ca5215d..c861006 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -1,6 +1,9 @@ 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 /// dropped. #[cfg(target_os = "redox")] @@ -12,6 +15,7 @@ pub struct RawTerminal { impl Drop for RawTerminal { fn drop(&mut self) { use control::TermWrite; + try!(self.write(EXIT_MOUSE_SEQUENCE)); self.csi(b"R").unwrap(); } } @@ -30,6 +34,7 @@ pub struct RawTerminal { impl Drop for RawTerminal { fn drop(&mut self) { use termios::set_terminal_attr; + self.write(EXIT_MOUSE_SEQUENCE).unwrap(); set_terminal_attr(&mut self.prev_ios as *mut _); } } @@ -86,10 +91,12 @@ impl IntoRawMode for W { if set_terminal_attr(&mut ios as *mut _) != 0 { Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute.")) } else { - Ok(RawTerminal { + let mut res = RawTerminal { prev_ios: prev_ios, output: self, - }) + }; + try!(res.write(ENTER_MOUSE_SEQUENCE)); + Ok(res) } } @@ -97,8 +104,12 @@ impl IntoRawMode for W { fn into_raw_mode(mut self) -> io::Result> { use control::TermWrite; - self.csi(b"r").map(|_| RawTerminal { + self.csi(b"r").map(|_| { + let mut res = RawTerminal { output: self, + }; + try!(res.write(ENTER_MOUSE_SEQUENCE)); + res }) } } From 206c61de9efded63e218f81a7ac3ae0323f761e7 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Wed, 20 Jul 2016 01:00:22 +0200 Subject: [PATCH 10/12] made mouse optional and fixed small issues --- examples/mouse.rs | 41 +++++++++++++++ examples/test.rs | 35 ------------- src/event.rs | 127 ++++++++++++++++++++++------------------------ src/input.rs | 2 +- src/lib.rs | 4 +- src/raw.rs | 72 ++++++++++++++++++++++---- 6 files changed, 169 insertions(+), 112 deletions(-) create mode 100644 examples/mouse.rs delete mode 100644 examples/test.rs diff --git a/examples/mouse.rs b/examples/mouse.rs new file mode 100644 index 0000000..9d8a598 --- /dev/null +++ b/examples/mouse.rs @@ -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(); +} diff --git a/examples/test.rs b/examples/test.rs deleted file mode 100644 index 48ba1f5..0000000 --- a/examples/test.rs +++ /dev/null @@ -1,35 +0,0 @@ -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(); -} diff --git a/src/event.rs b/src/event.rs index cd98b3c..852196e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -6,20 +6,20 @@ use std::str; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Event { /// A key press. - KeyEvent(Key), + Key(Key), /// A mouse button press, release or wheel use at specific coordinates. - MouseEvent(Mouse, u16, u16), + Mouse(MouseEvent), /// An event that cannot currently be evaluated. Unsupported, } /// A mouse related event. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Mouse { +pub enum MouseEvent { /// A mouse button was pressed. - Press(MouseButton), + Press(MouseButton, u16, u16), /// A mouse button was released. - Release, + Release(u16, u16), } /// A mouse button. @@ -86,6 +86,7 @@ pub enum Key { __IsNotComplete } +/// Parse an Event from `item` and possibly subsequent bytes through `iter`. pub fn parse_event(item: Result, iter: &mut I) -> Result where I: Iterator> { @@ -95,51 +96,47 @@ where I: Iterator> 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)), + 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::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'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) + // 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 { + // (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 & 64 != 0 { - Mouse::Press(MouseButton::WheelUp) + if cb & 0x40 != 0 { + MouseEvent::Press(MouseButton::WheelUp, cx, cy) } else { - Mouse::Press(MouseButton::Left) + MouseEvent::Press(MouseButton::Left, cx, cy) } } 1 => { - if cb & 64 != 0 { - Mouse::Press(MouseButton::WheelDown) + if cb & 0x40 != 0 { + MouseEvent::Press(MouseButton::WheelDown, cx, cy) } else { - Mouse::Press(MouseButton::Middle) + MouseEvent::Press(MouseButton::Middle, cx, cy) } } - 2 => Mouse::Press(MouseButton::Right), - 3 => Mouse::Release, + 2 => MouseEvent::Press(MouseButton::Right, cx, cy), + 3 => MouseEvent::Release(cx, cy), _ => return error, - }, - cx as u16, - cy as u16) + }) } Some(Ok(b'<')) => { - // xterm mouse encoding: ESC [ < Cb ; Cx ; Cy ; (M or m) + // 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 { @@ -164,17 +161,15 @@ where I: Iterator> 65 => MouseButton::WheelDown, _ => return error, }; - Event::MouseEvent(match c { - b'M' => Mouse::Press(button), - b'm' => Mouse::Release, + Event::Mouse(match c { + b'M' => MouseEvent::Press(button, cx, cy), + b'm' => MouseEvent::Release(cx, cy), _ => return error, - }, - cx, - cy) + }) } Some(Ok(c @ b'0'...b'9')) => { - // numbered escape code + // Numbered escape code. let mut buf = Vec::new(); buf.push(c); let mut c = iter.next().unwrap().unwrap(); @@ -187,7 +182,8 @@ where I: Iterator> } match c { - // rxvt mouse encoding: ESC [ Cb ; Cx ; Cy ; M + // 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(';'); @@ -197,30 +193,30 @@ where I: Iterator> let cy = nums.next().unwrap().parse::().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), + 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::MouseEvent(event, cx, cy) + Event::Mouse(event) }, - // special key code + // 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)), + 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, } } @@ -232,29 +228,30 @@ where I: Iterator> } Some(Ok(c)) => { let ch = parse_utf8_char(c, iter); - Event::KeyEvent(Key::Alt(try!(ch))) + Event::Key(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(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::KeyEvent(Key::Ctrl((c as u8 - 0x1C + b'4') as char))) + Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char))) } - Ok(b'\0') => Ok(Event::KeyEvent(Key::Null)), + Ok(b'\0') => Ok(Event::Key(Key::Null)), Ok(c) => { Ok({ let ch = parse_utf8_char(c, iter); - Event::KeyEvent(Key::Char(try!(ch))) + 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(c: u8, iter: &mut I) -> Result where I: Iterator> { diff --git a/src/input.rs b/src/input.rs index 32c51ed..060226f 100644 --- a/src/input.rs +++ b/src/input.rs @@ -15,7 +15,7 @@ impl>> Iterator for Keys { fn next(&mut self) -> Option> { loop { match self.iter.next() { - Some(Ok(Event::KeyEvent(k))) => return Some(Ok(k)), + Some(Ok(Event::Key(k))) => return Some(Ok(k)), Some(Ok(_)) => continue, e @ Some(Err(_)) => e, None => return None, diff --git a/src/lib.rs b/src/lib.rs index c1d84b3..165cd15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,10 @@ mod input; pub use input::{TermRead, Events, Keys}; mod event; -pub use event::{Key, Mouse, MouseButton, Event}; +pub use event::{Key, MouseEvent, MouseButton, Event}; mod raw; -pub use raw::{IntoRawMode, RawTerminal}; +pub use raw::{IntoRawMode, RawTerminal, MouseTerminal}; mod size; pub use size::terminal_size; diff --git a/src/raw.rs b/src/raw.rs index c861006..ec836a1 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -1,9 +1,6 @@ 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 /// dropped. #[cfg(target_os = "redox")] @@ -15,7 +12,6 @@ pub struct RawTerminal { impl Drop for RawTerminal { fn drop(&mut self) { use control::TermWrite; - try!(self.write(EXIT_MOUSE_SEQUENCE)); self.csi(b"R").unwrap(); } } @@ -30,11 +26,21 @@ pub struct RawTerminal { output: W, } +#[cfg(not(target_os = "redox"))] +impl RawTerminal +where W: Write +{ + /// Enable mouse support. + pub fn with_mouse(mut self) -> io::Result> { + try!(self.write(ENTER_MOUSE_SEQUENCE)); + Ok(MouseTerminal { term: self }) + } +} + #[cfg(not(target_os = "redox"))] impl Drop for RawTerminal { fn drop(&mut self) { use termios::set_terminal_attr; - self.write(EXIT_MOUSE_SEQUENCE).unwrap(); set_terminal_attr(&mut self.prev_ios as *mut _); } } @@ -91,11 +97,10 @@ impl IntoRawMode for W { if set_terminal_attr(&mut ios as *mut _) != 0 { Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute.")) } else { - let mut res = RawTerminal { + let res = RawTerminal { prev_ios: prev_ios, output: self, }; - try!(res.write(ENTER_MOUSE_SEQUENCE)); Ok(res) } } @@ -106,14 +111,63 @@ impl IntoRawMode for W { self.csi(b"r").map(|_| { let mut res = RawTerminal { - output: self, + output: self, }; - try!(res.write(ENTER_MOUSE_SEQUENCE)); 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 { + term: RawTerminal, +} + +#[cfg(not(target_os = "redox"))] +impl Drop for MouseTerminal { + fn drop(&mut self) { + self.term.write(EXIT_MOUSE_SEQUENCE).unwrap(); + } +} + +#[cfg(not(target_os = "redox"))] +impl Deref for MouseTerminal { + type Target = W; + + fn deref(&self) -> &W { + self.term.deref() + } +} + +#[cfg(not(target_os = "redox"))] +impl DerefMut for MouseTerminal { + fn deref_mut(&mut self) -> &mut W { + self.term.deref_mut() + } +} + +#[cfg(not(target_os = "redox"))] +impl Write for MouseTerminal { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.term.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.term.flush() + } +} + + #[cfg(test)] mod test { use super::*; From ab12a8f8a637dbc49c7b0b9796096be058858663 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Wed, 20 Jul 2016 11:03:30 +0200 Subject: [PATCH 11/12] fixed formatting --- src/raw.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/raw.rs b/src/raw.rs index ec836a1..b8c6c89 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -28,7 +28,7 @@ pub struct RawTerminal { #[cfg(not(target_os = "redox"))] impl RawTerminal -where W: Write + where W: Write { /// Enable mouse support. pub fn with_mouse(mut self) -> io::Result> { @@ -73,9 +73,9 @@ impl Write for RawTerminal { pub trait IntoRawMode: Write + Sized { /// Switch to raw mode. /// - /// Raw mode means that stdin won't be printed (it will instead have to be written manually by the - /// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from - /// stdin one byte of a time). The output is neither modified in any way. + /// Raw mode means that stdin won't be printed (it will instead have to be written manually by + /// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can + /// read from stdin one byte of a time). The output is neither modified in any way. fn into_raw_mode(self) -> io::Result>; } @@ -110,19 +110,17 @@ impl IntoRawMode for W { use control::TermWrite; self.csi(b"r").map(|_| { - let mut res = RawTerminal { - 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"; +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"; +const EXIT_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"; /// A `RawTerminal` with added mouse support. /// From 1c50a795f86fef37481a70c382260e08cab917c1 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Wed, 20 Jul 2016 13:06:04 +0200 Subject: [PATCH 12/12] added some tests --- src/event.rs | 11 +++++++++++ src/input.rs | 20 ++++++++++++++++++++ src/raw.rs | 6 ++++++ 3 files changed, 37 insertions(+) diff --git a/src/event.rs b/src/event.rs index 852196e..fde57f4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -272,3 +272,14 @@ where I: Iterator> } } } + +#[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()); + } +} diff --git a/src/input.rs b/src/input.rs index 060226f..e2f2ea0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -100,6 +100,7 @@ impl TermRead for R { mod test { use super::*; use std::io; + use event::{Key, Event, MouseEvent, MouseButton}; #[test] fn test_keys() { @@ -113,6 +114,25 @@ mod test { 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(); diff --git a/src/raw.rs b/src/raw.rs index b8c6c89..2bb9991 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -177,4 +177,10 @@ mod test { 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(); + } }