Device Status Reports allow a user of a terminal to use an ansi escape code sequence to query for data. One example is identifying the terminal width and height in colums and rows (respectively). This is accomplished in a simple terminal window using: printf "\x1b[19t" which can be semantically represented as: ESC[CMDt. The response for a standard 80x25 terminal would be: \x1b[9;25;80t or semantically: ESC[STATUS;ROWS; COLSt`.
In rust, I can send this request with a simple print macro: print!("\x1b[19t"), but I'm struggling with capturing the response.
Here's some code that I've put together which is probably close, but doesn't give me the answer I need:
use std::sync::mpsc::{channel, Receiver, TryRecvError};
use std::{
io::{self, Read},
thread, time,
};
fn main() {
let timeout = time::Duration::from_micros(50);
let rx = spawn_read();
print!("\x1b[19t");
let mut data = Vec::new();
for _ in 0..200 {
thread::sleep(timeout);
match rx.try_recv() {
Ok(value) => data.push(value),
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => break,
}
}
let string = match std::str::from_utf8(&data) {
Ok(value) => value,
Err(_why) => "",
};
println!("Captured: {:?}", string);
}
fn spawn_read() -> Receiver<u8> {
let (tx, rx) = channel::<u8>();
thread::spawn(move || loop {
let mut buf = [0u8];
io::stdin().read_exact(&mut buf).unwrap();
if buf != [0u8] {
tx.send(buf[0]).ok();
}
});
rx
}
Using this code, I actually get this as an output when it's run:
Stdin is buffered (and cooked and may not be a TTY). I suggest using the termion crate. It can handle your example, some other queries, and get you raw mode if you need that directly.
I should have probably added some more qualifiers...
a) I'm looking for something that doesn't require a crate that explicitly uses the libc library. From a library perspective, it makes the crate susceptible to dependency problems if two crates in your dependency tree require different libc versions. Rust, unfortunately, doesn't let multiple c-library dependencies live side-by-side like it would with pure rust packages. This actually bit me recently, and I've been avoiding libc where I can.
b) I want something cross platform. Termion appears to only work for posix-based systems, and I'm going to be running on Windows as well. I am okay with adding cfg code, but ultimately, I want something that works for both a posix environment and a windows environment. In my first iteration, I was using Termion. I went to crossterm, but I found that it was about twice as slow, so I started digging more. I know there's a bunch of wrapper code in Termion's raw.rs file, but I also believe I can get a raw mode file descriptor from a single line that uses unsafe to access the raw file descriptor with just the standard library.
From a higher level, it also feels like Rust should support this out of the box somewhere, which is why I'm poking the forums here with my question.
I see. I don't know how to set raw mode without ultimately calling tcsetattr on POSIX systems (and know nothing about Windows support of terminal escapes), sorry. But you're definitely going to need something lower-level than Stdin.
Tracking libc in the backwards-compatibility-locked stdlib was deemed too messy basically; the deprecated leftovers say to use the libc crate specifically. (It's owned by rust-lang too.)
While you can easily get a raw file descriptor for stdin you still need tcsetattr or an ioctl to switch the terminal from cooked to raw mode. Otherwise the terminal itself buffers input including the response to your CSI 1 9 t sequence, so that the process won't see the response at all until the user presses enter.
BTW: You get no response from the terminal at all because Rust stdout is line-buffered, so you need something like io::stdout().flush().unwrap(); after print!(...)