mod command;
use std::io::prelude::*;
use std::println;
use std::string::ToString;
use crate::guest_console::ConsoleInputEvent;
use crate::shell::command::{
CommandHistory, clear_line_and_redraw, handle_builtin_commands, print_prompt, prompt_string,
run_cmd_bytes,
};
const LF: u8 = b'\n';
const CR: u8 = b'\r';
const DL: u8 = b'\x7f';
const BS: u8 = b'\x08';
const ESC: u8 = 0x1b;
const MAX_LINE_LEN: usize = 256;
enum InputState {
Normal,
Escape,
EscapeSeq,
}
fn print_shell_intro() {
println!("Welcome to AxVisor Shell!");
println!("Type 'help' to see available commands");
println!("Use UP/DOWN arrows to navigate command history");
print_console_shortcuts();
#[cfg(not(feature = "fs"))]
println!("Note: Running with limited features (filesystem support disabled).");
println!();
}
fn print_console_shortcuts() {
println!("Console shortcuts:");
println!(" Ctrl+Alt+H return to the Axvisor shell");
println!(" Ctrl+Alt+[ attach the previous running guest");
println!(" Ctrl+Alt+] attach the next running guest");
}
pub fn console_init() {
let mut stdout = std::io::stdout();
let mut history = CommandHistory::new(100);
let mut buf = [0; MAX_LINE_LEN];
let mut cursor = 0; let mut line_len = 0;
let mut input_state = InputState::Normal;
let mut pending_shell_byte = None;
let mut shell_announced = false;
if crate::guest_console::attached_vm().is_none() {
print_shell_intro();
shell_announced = true;
print_prompt();
}
loop {
if let Some(vm_id) = crate::guest_console::reconcile_vm_states() {
println!();
println!("[Axvisor] VM[{vm_id}] stopped; returning to the management shell");
if !shell_announced {
print_shell_intro();
shell_announced = true;
}
let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or("");
clear_line_and_redraw(&mut stdout, &prompt_string(), current_content, cursor);
}
let ch = match pending_shell_byte.take() {
Some(ch) => ch,
None => {
let Some(host_byte) = crate::guest_console::read_host_byte() else {
crate::guest_console::wait_for_host_input();
continue;
};
match crate::guest_console::route_host_byte(host_byte) {
ConsoleInputEvent::ShellByte(ch) => ch,
ConsoleInputEvent::ShellSequence(first, second) => {
pending_shell_byte = Some(second);
first
}
ConsoleInputEvent::Consumed => continue,
ConsoleInputEvent::Attached(vm_id) => {
println!();
println!(
"[Axvisor] attached VM[{vm_id}] console; use Ctrl+Alt+H to return to \
the shell"
);
continue;
}
ConsoleInputEvent::Detached(vm_id) => {
println!();
println!("[Axvisor] detached VM[{vm_id}] console");
if !shell_announced {
print_shell_intro();
shell_announced = true;
}
let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or("");
clear_line_and_redraw(
&mut stdout,
&prompt_string(),
current_content,
cursor,
);
continue;
}
ConsoleInputEvent::NoRunningGuest => {
println!();
println!("[Axvisor] no running VM is available for console attachment");
let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or("");
clear_line_and_redraw(
&mut stdout,
&prompt_string(),
current_content,
cursor,
);
continue;
}
}
}
};
match input_state {
InputState::Normal => {
match ch {
CR | LF => {
println!();
if line_len > 0 {
let cmd_str = std::str::from_utf8(&buf[..line_len]).unwrap_or("");
history.add_command(cmd_str.to_string());
if !handle_builtin_commands(cmd_str) {
run_cmd_bytes(&buf[..line_len]);
}
buf[..line_len].fill(0);
cursor = 0;
line_len = 0;
}
if crate::guest_console::attached_vm().is_none() {
print_prompt();
}
}
BS | DL => {
if cursor > 0 {
for i in cursor..line_len {
buf[i - 1] = buf[i];
}
cursor -= 1;
line_len -= 1;
if line_len < buf.len() {
buf[line_len] = 0;
}
let current_content =
std::str::from_utf8(&buf[..line_len]).unwrap_or("");
let prompt = prompt_string();
clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor);
}
}
ESC => {
input_state = InputState::Escape;
}
0..=31 => {
}
c => {
if line_len < MAX_LINE_LEN - 1 {
for i in (cursor..line_len).rev() {
buf[i + 1] = buf[i];
}
buf[cursor] = c;
cursor += 1;
line_len += 1;
let current_content =
std::str::from_utf8(&buf[..line_len]).unwrap_or("");
let prompt = prompt_string();
clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor);
}
}
}
}
InputState::Escape => match ch {
b'[' => {
input_state = InputState::EscapeSeq;
}
_ => {
input_state = InputState::Normal;
}
},
InputState::EscapeSeq => {
match ch {
b'A' => {
if let Some(prev_cmd) = history.previous() {
buf[..line_len].fill(0);
let cmd_bytes = prev_cmd.as_bytes();
let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1);
buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]);
cursor = copy_len;
line_len = copy_len;
let prompt = prompt_string();
clear_line_and_redraw(&mut stdout, &prompt, prev_cmd, cursor);
}
input_state = InputState::Normal;
}
b'B' => {
match history.next() {
Some(next_cmd) => {
buf[..line_len].fill(0);
let cmd_bytes = next_cmd.as_bytes();
let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1);
buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]);
cursor = copy_len;
line_len = copy_len;
let prompt = prompt_string();
clear_line_and_redraw(&mut stdout, &prompt, next_cmd, cursor);
}
None => {
buf[..line_len].fill(0);
cursor = 0;
line_len = 0;
let prompt = prompt_string();
clear_line_and_redraw(&mut stdout, &prompt, "", cursor);
}
}
input_state = InputState::Normal;
}
b'C' => {
if cursor < line_len {
cursor += 1;
stdout.write_all(b"\x1b[C").ok();
stdout.flush().ok();
}
input_state = InputState::Normal;
}
b'D' => {
if cursor > 0 {
cursor -= 1;
stdout.write_all(b"\x1b[D").ok();
stdout.flush().ok();
}
input_state = InputState::Normal;
}
b'3' => {
input_state = InputState::Normal;
}
_ => {
input_state = InputState::Normal;
}
}
}
}
}
}