1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
use libc::{termios, winsize};
use std::error::Error;
use std::io::{stdout, BufRead, BufReader, Read, Stdin, Write};
const ESC: u8 = 27;
pub fn size() -> Result<winsize, Box<dyn Error>> {
unsafe {
let mut size = std::mem::zeroed();
if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 {
return Err(Box::from("ioctl failed"));
}
Ok(size)
}
}
pub struct RawHandle {
old_attr: termios,
}
impl Drop for RawHandle {
fn drop(&mut self) {
unsafe {
libc::tcsetattr(1, libc::TCSANOW, &self.old_attr);
}
let _ = write!(stdout(), "\x1b[?7h\x1b[2J");
let _ = stdout().flush();
}
}
pub fn raw_mode() -> Result<RawHandle, Box<dyn Error>> {
unsafe {
let mut attr = std::mem::zeroed();
if libc::tcgetattr(1, &mut attr) < 0 {
return Err(Box::from("tcgetattr failed"));
}
let mut raw = std::mem::zeroed();
libc::cfmakeraw(&mut raw);
raw.c_cc[libc::VTIME] = 1;
raw.c_cc[libc::VMIN] = 0;
if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 {
return Err(Box::from("tcsetattr failed"));
}
write!(stdout(), "\x1b[?7l")?;
stdout().flush()?;
Ok(RawHandle { old_attr: attr })
}
}
pub enum Key {
Timeout,
Up,
Down,
Left,
Right,
Byte(u8),
}
pub fn read_key(stdin: &mut BufReader<Stdin>) -> Result<Key, Box<dyn Error>> {
loop {
let mut buf = vec![0; 1];
let n = stdin.read(&mut buf)?;
if n == 0 {
return Ok(Key::Timeout);
}
if buf[0] != ESC {
return Ok(Key::Byte(buf[0]));
}
let buf = stdin.fill_buf()?;
if buf.is_empty() {
continue;
}
if buf[0] != b'[' || buf.len() == 1 {
stdin.consume(1);
continue;
}
let mut n = buf.len();
for (i, &c) in buf.iter().enumerate() {
match c {
b'A'..=b'Z' | b'a'..=b'z' => {
n = i + 1;
break;
}
_ => (),
}
}
let c = buf[1];
stdin.consume(n);
match c {
b'A' => {
return Ok(Key::Up);
}
b'B' => {
return Ok(Key::Down);
}
b'C' => {
return Ok(Key::Right);
}
b'D' => {
return Ok(Key::Left);
}
_ => {
continue;
}
}
}
}
// cursor_pos *WILL* fail if the user is typing on the keyboard. Be sure to handle
// errors appropriately.
pub fn cursor_pos(stdin: &mut BufReader<Stdin>) -> Result<(u16, u16), Box<dyn Error>> {
if !stdin.buffer().is_empty() {
// If there is any buffered input, the below call to fill_buf will just return the buffered
// data and not actually read the terminal response.
return Err(Box::from("interrupted"));
}
write!(stdout(), "\x1b[6n")?;
stdout().flush()?;
let buf = stdin.fill_buf()?;
let mut semicolon = buf.len();
let mut r = buf.len();
for (i, &c) in buf.iter().enumerate() {
if c == b';' {
semicolon = i;
}
if c == b'R' {
r = i;
break;
}
}
if buf.len() < 6
|| buf[0] != ESC
|| buf[1] != b'['
|| semicolon < 2
|| semicolon + 1 > r
|| r == buf.len()
{
return Err(Box::from("invalid response"));
}
let row = std::str::from_utf8(&buf[2..semicolon])?.parse::<u16>()?;
let col = std::str::from_utf8(&buf[semicolon + 1..r])?.parse::<u16>()?;
stdin.consume(r + 1);
Ok((row - 1, col - 1))
}
|