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
use std::fs::{File, OpenOptions};
use std::io;
use std::io::{Read, Write};

pub fn query_start(esc: &[u8]) -> io::Result<File> {
	let mut tty = OpenOptions::new().read(true).write(true).open("/dev/tty")?;
	tty.write_all(esc)?;
	tty.flush()?;
	Ok(tty)
}

enum Curp {
	Nil,
	Esc,
	Esc2,
	Y(u16, u16),
	Semi(u16),
	X(u16, u16, u16),
}

fn ascii2digit(x: u8) -> u16 {
	(x - b'0') as u16
}

fn get_cursor_xy_with_esc(esc: &[u8]) -> io::Result<(u16, u16)> {
	let mut state = Curp::Nil;
	for b in query_start(esc)?.bytes() {
		let b = b?;
		state = match (state, b) {
			(Curp::Nil, b'\x1b') => Curp::Esc,
			(Curp::Nil, _) => Curp::Nil,
			(Curp::Esc, b'[') => Curp::Esc2,
			(Curp::Esc, _) => Curp::Nil,
			(Curp::Esc2, b'0'..=b'9') => Curp::Y(10, ascii2digit(b)),
			(Curp::Esc2, _) => Curp::Nil,
			(Curp::Y(_, y), b';') => Curp::Semi(y),
			(Curp::Y(z, y), b'0'..=b'9') => Curp::Y(z * 10, y + ascii2digit(b) * z),
			(Curp::Y(_, _), _) => Curp::Nil,
			(Curp::Semi(y), b'0'..=b'9') => Curp::X(10, ascii2digit(b), y),
			(Curp::Semi(_), _) => Curp::Nil,
			(Curp::X(_, x, y), b'R') => return Ok((x, y)),
			(Curp::X(z, x, y), b'0'..=b'9') => Curp::X(z * 10, x + ascii2digit(b) * z, y),
			(Curp::X(_, _, _), _) => Curp::Nil,
		}
	}
	Err(io::Error::new(
		io::ErrorKind::NotFound,
		"End of tty before cursor pos matched",
	))
}

pub fn get_cursor_xy() -> io::Result<(u16, u16)> {
	get_cursor_xy_with_esc(b"\x1b[6n")
}

pub fn get_tty_wh_dirty() -> io::Result<(u16, u16)> {
	get_cursor_xy_with_esc(b"\x1b[999;999H\x1b[6n")
}

pub fn get_tty_wh() -> io::Result<(u16, u16)> {
	get_cursor_xy_with_esc(b"\x1b7\x1b[999;999H\x1b[6n\x1b8")
}