From 0758c07ab7ccf3e5b898baaeff170b447a83ce0f Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Sun, 12 Mar 2017 20:18:32 +0000 Subject: [PATCH] added cursor position detection This solves #85 in a similar fashion as the color amount detection: the cursor module now provides a trait that adds a `cursor_pos()` method to an instance of `Write`. It also corrects that previous implementation somewhat by making the `CONTROL_SEQUENCE_TIMEOUT` a member of the raw module and implementing `DetectColors` for any instance of `Write` rather than just `RawTerminal` (`MouseTerminal` for instance works as well). --- examples/mouse.rs | 10 +++++++++- src/color.rs | 9 +++------ src/cursor.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++ src/raw.rs | 3 +++ 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/examples/mouse.rs b/examples/mouse.rs index 52006b1..c8510c8 100644 --- a/examples/mouse.rs +++ b/examples/mouse.rs @@ -1,7 +1,7 @@ extern crate termion; use termion::event::*; -use termion::cursor; +use termion::cursor::{self, DetectCursorPos}; use termion::input::{TermRead, MouseTerminal}; use termion::raw::IntoRawMode; use std::io::{self, Write}; @@ -26,6 +26,14 @@ fn main() { MouseEvent::Release(a, b) | MouseEvent::Hold(a, b) => { write!(stdout, "{}", cursor::Goto(a, b)).unwrap(); + let (x, y) = stdout.cursor_pos().unwrap(); + write!(stdout, + "{}{}Cursor is at: ({},{}){}", + cursor::Goto(5, 5), + termion::clear::UntilNewline, + x, + y, + cursor::Goto(a, b)).unwrap(); } } } diff --git a/src/color.rs b/src/color.rs index 33a5ac2..26c9fd2 100644 --- a/src/color.rs +++ b/src/color.rs @@ -13,7 +13,7 @@ //! ``` use std::fmt; -use raw::RawTerminal; +use raw::CONTROL_SEQUENCE_TIMEOUT; use std::io::{self, Write, Read}; use std::time::{SystemTime, Duration}; use async::async_stdin; @@ -175,7 +175,7 @@ pub trait DetectColors { fn available_colors(&mut self) -> io::Result; } -impl DetectColors for RawTerminal { +impl DetectColors for W { fn available_colors(&mut self) -> io::Result { let mut stdin = async_stdin(); @@ -210,11 +210,8 @@ impl DetectColors for RawTerminal { } } -/// The timeout of an escape code control sequence, in milliseconds. -const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; - /// Detect a color using OSC 4. -fn detect_color(stdout: &mut RawTerminal, +fn detect_color(stdout: &mut W, stdin: &mut Read, color: u16) -> io::Result { diff --git a/src/cursor.rs b/src/cursor.rs index 0335b20..6aef5dd 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,6 +1,10 @@ //! Cursor movement. use std::fmt; +use std::io::{self, Write, Error, ErrorKind, Read}; +use async::async_stdin; +use std::time::{SystemTime, Duration}; +use raw::CONTROL_SEQUENCE_TIMEOUT; derive_csi_sequence!("Hide the cursor.", Hide, "?25l"); derive_csi_sequence!("Show the cursor.", Show, "?25h"); @@ -76,3 +80,50 @@ impl fmt::Display for Down { write!(f, csi!("{}B"), self.0) } } + +/// Types that allow detection of the cursor position. +pub trait DetectCursorPos { + /// Get the (1,1)-based cursor position from the terminal. + fn cursor_pos(&mut self) -> io::Result<(u16, u16)>; +} + +impl DetectCursorPos for W { + fn cursor_pos(&mut self) -> io::Result<(u16, u16)> { + let mut stdin = async_stdin(); + + // Where is the cursor? + // Use `ESC [ 6 n`. + write!(self, "\x1B[6n")?; + self.flush()?; + + let mut buf: [u8; 1] = [0]; + let mut read_chars = Vec::new(); + + let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT); + let now = SystemTime::now(); + + // Either consume all data up to R or wait for a timeout. + while buf[0] != b'R' && now.elapsed().unwrap() < timeout { + if stdin.read(&mut buf)? > 0 { + read_chars.push(buf[0]); + } + } + + if read_chars.len() == 0 { + return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out.")); + } + + // The answer will look like `ESC [ Cy ; Cx R`. + + read_chars.pop(); // remove trailing R. + let read_str = String::from_utf8(read_chars).unwrap(); + let beg = read_str.rfind('[').unwrap(); + let coords: String = read_str.chars().skip(beg+1).collect(); + let mut nums = coords.split(';'); + + let cy = nums.next().unwrap().parse::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap(); + + Ok((cx, cy)) + } +} diff --git a/src/raw.rs b/src/raw.rs index 6e5fd27..952ab20 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -25,6 +25,9 @@ use std::io::{self, Write}; use std::ops; +/// The timeout of an escape code control sequence, in milliseconds. +pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; + /// A terminal restorer, which keeps the previous state of the terminal, and restores it, when /// dropped. ///