made mouse optional and fixed small issues

This commit is contained in:
IGI-111 2016-07-20 01:00:22 +02:00
parent cc9c32b981
commit 206c61de9e
6 changed files with 169 additions and 112 deletions

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

View File

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

View File

@ -6,20 +6,20 @@ use std::str;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Event { pub enum Event {
/// A key press. /// A key press.
KeyEvent(Key), Key(Key),
/// A mouse button press, release or wheel use at specific coordinates. /// A mouse button press, release or wheel use at specific coordinates.
MouseEvent(Mouse, u16, u16), Mouse(MouseEvent),
/// An event that cannot currently be evaluated. /// An event that cannot currently be evaluated.
Unsupported, Unsupported,
} }
/// A mouse related event. /// A mouse related event.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mouse { pub enum MouseEvent {
/// A mouse button was pressed. /// A mouse button was pressed.
Press(MouseButton), Press(MouseButton, u16, u16),
/// A mouse button was released. /// A mouse button was released.
Release, Release(u16, u16),
} }
/// A mouse button. /// A mouse button.
@ -86,6 +86,7 @@ pub enum Key {
__IsNotComplete __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> pub fn parse_event<I>(item: Result<u8, Error>, iter: &mut I) -> Result<Event, Error>
where I: Iterator<Item = Result<u8, Error>> where I: Iterator<Item = Result<u8, Error>>
{ {
@ -95,51 +96,47 @@ where I: Iterator<Item = Result<u8, Error>>
Ok(match iter.next() { Ok(match iter.next() {
Some(Ok(b'O')) => { Some(Ok(b'O')) => {
match iter.next() { match iter.next() {
Some(Ok(b'P')) => Event::KeyEvent(Key::F(1)), Some(Ok(val @ b'P' ... b'S')) => Event::Key(Key::F(1 + val - b'P')),
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, _ => return error,
} }
} }
Some(Ok(b'[')) => { Some(Ok(b'[')) => {
match iter.next() { match iter.next() {
Some(Ok(b'D')) => Event::KeyEvent(Key::Left), Some(Ok(b'D')) => Event::Key(Key::Left),
Some(Ok(b'C')) => Event::KeyEvent(Key::Right), Some(Ok(b'C')) => Event::Key(Key::Right),
Some(Ok(b'A')) => Event::KeyEvent(Key::Up), Some(Ok(b'A')) => Event::Key(Key::Up),
Some(Ok(b'B')) => Event::KeyEvent(Key::Down), Some(Ok(b'B')) => Event::Key(Key::Down),
Some(Ok(b'H')) => Event::KeyEvent(Key::Home), Some(Ok(b'H')) => Event::Key(Key::Home),
Some(Ok(b'F')) => Event::KeyEvent(Key::End), Some(Ok(b'F')) => Event::Key(Key::End),
Some(Ok(b'M')) => { 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; let cb = iter.next().unwrap().unwrap() as i8 - 32;
// (1, 1) are the coords for upper left // (1, 1) are the coords for upper left.
let cx = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32); 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); let cy = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32) as u16;
Event::MouseEvent(match cb & 0b11 { Event::Mouse(match cb & 0b11 {
0 => { 0 => {
if cb & 64 != 0 { if cb & 0x40 != 0 {
Mouse::Press(MouseButton::WheelUp) MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else { } else {
Mouse::Press(MouseButton::Left) MouseEvent::Press(MouseButton::Left, cx, cy)
} }
} }
1 => { 1 => {
if cb & 64 != 0 { if cb & 0x40 != 0 {
Mouse::Press(MouseButton::WheelDown) MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else { } else {
Mouse::Press(MouseButton::Middle) MouseEvent::Press(MouseButton::Middle, cx, cy)
} }
} }
2 => Mouse::Press(MouseButton::Right), 2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => Mouse::Release, 3 => MouseEvent::Release(cx, cy),
_ => return error, _ => return error,
}, })
cx as u16,
cy as u16)
} }
Some(Ok(b'<')) => { 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 buf = Vec::new();
let mut c = iter.next().unwrap().unwrap(); let mut c = iter.next().unwrap().unwrap();
while match c { while match c {
@ -164,17 +161,15 @@ where I: Iterator<Item = Result<u8, Error>>
65 => MouseButton::WheelDown, 65 => MouseButton::WheelDown,
_ => return error, _ => return error,
}; };
Event::MouseEvent(match c { Event::Mouse(match c {
b'M' => Mouse::Press(button), b'M' => MouseEvent::Press(button, cx, cy),
b'm' => Mouse::Release, b'm' => MouseEvent::Release(cx, cy),
_ => return error, _ => return error,
}, })
cx,
cy)
} }
Some(Ok(c @ b'0'...b'9')) => { Some(Ok(c @ b'0'...b'9')) => {
// numbered escape code // Numbered escape code.
let mut buf = Vec::new(); let mut buf = Vec::new();
buf.push(c); buf.push(c);
let mut c = iter.next().unwrap().unwrap(); let mut c = iter.next().unwrap().unwrap();
@ -187,7 +182,8 @@ where I: Iterator<Item = Result<u8, Error>>
} }
match c { match c {
// rxvt mouse encoding: ESC [ Cb ; Cx ; Cy ; M // rxvt mouse encoding:
// ESC [ Cb ; Cx ; Cy ; M
b'M' => { b'M' => {
let str_buf = String::from_utf8(buf).unwrap(); let str_buf = String::from_utf8(buf).unwrap();
let ref mut nums = str_buf.split(';'); let ref mut nums = str_buf.split(';');
@ -197,30 +193,30 @@ where I: Iterator<Item = Result<u8, Error>>
let cy = nums.next().unwrap().parse::<u16>().unwrap() - 1; let cy = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let event = match cb { let event = match cb {
32 => Mouse::Press(MouseButton::Left), 32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => Mouse::Press(MouseButton::Middle), 33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => Mouse::Press(MouseButton::Right), 34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => Mouse::Release, 35 => MouseEvent::Release(cx, cy),
96 => Mouse::Press(MouseButton::WheelUp), 96 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
97 => Mouse::Press(MouseButton::WheelUp), 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => return error, _ => return error,
}; };
Event::MouseEvent(event, cx, cy) Event::Mouse(event)
}, },
// special key code // Special key code.
b'~' => { b'~' => {
let num: u8 = String::from_utf8(buf).unwrap().parse().unwrap(); let num: u8 = String::from_utf8(buf).unwrap().parse().unwrap();
match num { match num {
1 | 7 => Event::KeyEvent(Key::Home), 1 | 7 => Event::Key(Key::Home),
2 => Event::KeyEvent(Key::Insert), 2 => Event::Key(Key::Insert),
3 => Event::KeyEvent(Key::Delete), 3 => Event::Key(Key::Delete),
4 | 8 => Event::KeyEvent(Key::End), 4 | 8 => Event::Key(Key::End),
5 => Event::KeyEvent(Key::PageUp), 5 => Event::Key(Key::PageUp),
6 => Event::KeyEvent(Key::PageDown), 6 => Event::Key(Key::PageDown),
v @ 11...15 => Event::KeyEvent(Key::F(v - 10)), v @ 11...15 => Event::Key(Key::F(v - 10)),
v @ 17...21 => Event::KeyEvent(Key::F(v - 11)), v @ 17...21 => Event::Key(Key::F(v - 11)),
v @ 23...24 => Event::KeyEvent(Key::F(v - 12)), v @ 23...24 => Event::Key(Key::F(v - 12)),
_ => return error, _ => return error,
} }
} }
@ -232,29 +228,30 @@ where I: Iterator<Item = Result<u8, Error>>
} }
Some(Ok(c)) => { Some(Ok(c)) => {
let ch = parse_utf8_char(c, iter); let ch = parse_utf8_char(c, iter);
Event::KeyEvent(Key::Alt(try!(ch))) Event::Key(Key::Alt(try!(ch)))
} }
Some(Err(_)) | None => return error, Some(Err(_)) | None => return error,
}) })
} }
Ok(b'\n') | Ok(b'\r') => Ok(Event::KeyEvent(Key::Char('\n'))), Ok(b'\n') | Ok(b'\r') => Ok(Event::Key(Key::Char('\n'))),
Ok(b'\t') => Ok(Event::KeyEvent(Key::Char('\t'))), Ok(b'\t') => Ok(Event::Key(Key::Char('\t'))),
Ok(b'\x7F') => Ok(Event::KeyEvent(Key::Backspace)), Ok(b'\x7F') => Ok(Event::Key(Key::Backspace)),
Ok(c @ b'\x01'...b'\x1A') => Ok(Event::KeyEvent(Key::Ctrl((c as u8 - 0x1 + b'a') as char))), 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(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(c) => {
Ok({ Ok({
let ch = parse_utf8_char(c, iter); let ch = parse_utf8_char(c, iter);
Event::KeyEvent(Key::Char(try!(ch))) Event::Key(Key::Char(try!(ch)))
}) })
} }
Err(e) => Err(e), 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> fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char, Error>
where I: Iterator<Item = Result<u8, Error>> where I: Iterator<Item = Result<u8, Error>>
{ {

View File

@ -15,7 +15,7 @@ impl<I: Iterator<Item = Result<u8, io::Error>>> Iterator for Keys<I> {
fn next(&mut self) -> Option<Result<Key, io::Error>> { fn next(&mut self) -> Option<Result<Key, io::Error>> {
loop { loop {
match self.iter.next() { 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, Some(Ok(_)) => continue,
e @ Some(Err(_)) => e, e @ Some(Err(_)) => e,
None => return None, None => return None,

View File

@ -27,10 +27,10 @@ mod input;
pub use input::{TermRead, Events, Keys}; pub use input::{TermRead, Events, Keys};
mod event; mod event;
pub use event::{Key, Mouse, MouseButton, 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

@ -1,9 +1,6 @@
use std::io::{self, Write}; use std::io::{self, Write};
use std::ops::{Deref, DerefMut}; 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 /// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped. /// dropped.
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
@ -15,7 +12,6 @@ pub struct RawTerminal<W: Write> {
impl<W: Write> Drop for RawTerminal<W> { impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) { fn drop(&mut self) {
use control::TermWrite; use control::TermWrite;
try!(self.write(EXIT_MOUSE_SEQUENCE));
self.csi(b"R").unwrap(); self.csi(b"R").unwrap();
} }
} }
@ -30,11 +26,21 @@ 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) {
use termios::set_terminal_attr; use termios::set_terminal_attr;
self.write(EXIT_MOUSE_SEQUENCE).unwrap();
set_terminal_attr(&mut self.prev_ios as *mut _); set_terminal_attr(&mut self.prev_ios as *mut _);
} }
} }
@ -91,11 +97,10 @@ 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 {
let mut res = RawTerminal { let res = RawTerminal {
prev_ios: prev_ios, prev_ios: prev_ios,
output: self, output: self,
}; };
try!(res.write(ENTER_MOUSE_SEQUENCE));
Ok(res) Ok(res)
} }
} }
@ -106,14 +111,63 @@ impl<W: Write> IntoRawMode for W {
self.csi(b"r").map(|_| { self.csi(b"r").map(|_| {
let mut res = RawTerminal { let mut res = RawTerminal {
output: self, output: self,
}; };
try!(res.write(ENTER_MOUSE_SEQUENCE));
res 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::*;