Hey there,
I've been working on a little project which essentially emulates a Windows terminal (either cmd or powershell) from a Rust program.
I am using crossterm to handle input events. I've enabled raw mode as I would prefer to have more control over stdin/stdout.
Below is a small example of what I am trying to achieve.
fn main() {
crossterm::terminal::enable_raw_mode().unwrap();
let command = "python"; // can be anything - echo/dir etc...
let mut child = std::process::Command::new("powershell") // or cmd /c
.arg("-Command")
.arg(command)
// .stdin(std::process::Stdio::piped())
// .stdout(std::process::Stdio::piped())
// .stderr(std::process::Stdio::piped())
.spawn()
.unwrap();
// User should now be able to interact with this process until it is complete.
child.wait.unwrap();
}
Using this particular snippet of code, I found that commands such as echo
work as intended.
In contrast, programs such as python
in interactive mode, had strange behaviour where stdout
would be inherited and display output if stdin
was disregarded. When adding an stdin
pipe, however, the output of Python could not be seen.
I was able to get glimpses of outputs when using the following snippet (and uncommenting the lines in the child command).
let mut child_stdin = child.stdin.take().unwrap();
let mut child_stdout = child.stdout.take().unwrap();
let mut child_stderr = child.stderr.take().unwrap();
// STDIN
std::thread::spawn(move || {
let mut buffer = [0; 1024];
loop {
let n = std::io::stdin().read(&mut buffer).unwrap();
std::io::stdout().write(&mut buffer[..n]).unwrap();
std::io::stdout().flush().unwrap();
let n = child_stdin.write(&mut buffer[..n]).unwrap();
if n > 0 {
child_stdin.flush().unwrap();
}
}
});
// STDOUT
std::thread::spawn(move || {
let mut buffer = [0; 1024];
loop {
let n = child_stdout.read(&mut buffer).unwrap();
if n > 0 {
std::io::stdout().write(&buffer[..n]).unwrap();
std::io::stdout().flush().unwrap();
}
}
});
// STDERR
std::thread::spawn(move || {
let mut buffer = [0; 1024];
loop {
let n = child_stderr.read(&mut buffer).unwrap();
if n > 0 {
print!("{}", String::from_utf8_lossy(&buffer[..n])); // trying print out of desperation :/
std::io::stdout().flush().unwrap();
}
}
});
Unfortunately, even though I was able to see glimpses of the Python shell, I would see errors such as Unable to Initialize Device Prn
.
Ultimately, it did not work in any intended way.
There were many more things I tried, however, all followed the same sort of style and I simply do not know enough about what is going on in the background.
My last resort is to disable raw input and suppress SIGINT, however, I believe it is doable another way.
Any advice/guidance to useful resources regarding Windows terminals/Rust child processes and emulating shells would be greatly appreciated.