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::*;