use portable_pty::PtySize;
use r3bl_tui::{AnsiSequenceGenerator, InputEvent, Key, KeyPress, KeyState,
ModifierKeysMask, RawMode, col,
core::{get_size,
pty::{ControlSequence, CursorKeyMode, PtyCommandBuilder,
PtyInputEvent, PtyReadWriteOutputEvent,
PtyReadWriteSession},
terminal_io::{InputDevice, OutputDevice},
try_initialize_logging_global},
lock_output_device_as_mut, row, set_mimalloc_in_main};
#[tokio::main]
async fn main() -> miette::Result<()> {
set_mimalloc_in_main!();
try_initialize_logging_global(tracing_core::LevelFilter::DEBUG).ok();
tracing::debug!("Starting Simple PTY Example");
println!("🚀 Starting Simple PTY Example");
println!("📋 Running htop in a PTY");
println!("⌨️ Use htop normally, Ctrl+Q to quit");
println!("📝 Debug output will be written to log.txt");
println!();
let terminal_size = get_size()?;
let mut output_device = OutputDevice::new_stdout();
let mut input_device = InputDevice::default();
RawMode::start(
terminal_size,
lock_output_device_as_mut!(&output_device),
false,
);
tracing::debug!("Raw mode started");
{
let out = lock_output_device_as_mut!(&output_device);
let _unused = out.write_all(AnsiSequenceGenerator::clear_screen().as_bytes());
let _unused = out
.write_all(AnsiSequenceGenerator::cursor_position(row(0), col(0)).as_bytes());
let _unused = out.flush();
}
tracing::debug!("Screen cleared");
let pty_size = PtySize {
rows: terminal_size.row_height.into(),
cols: terminal_size.col_width.into(),
pixel_width: 0,
pixel_height: 0,
};
tracing::debug!("Spawning htop with PTY size: {:?}", pty_size);
let session = PtyCommandBuilder::new("htop").spawn_read_write(pty_size)?;
tracing::debug!("htop process started successfully");
let result = run_event_loop(session, &mut input_device, &mut output_device).await;
tracing::debug!("Starting cleanup");
RawMode::end(
terminal_size,
lock_output_device_as_mut!(&output_device),
false,
);
tracing::debug!("Raw mode ended, cleanup complete");
println!("👋 Goodbye!");
result
}
async fn run_event_loop(
mut session: PtyReadWriteSession,
input_device: &mut InputDevice,
output_device: &mut OutputDevice,
) -> miette::Result<()> {
let mut output_count = 0u64;
let mut input_count = 0u64;
tracing::debug!("Starting event loop");
loop {
tokio::select! {
Some(event) = session.output_event_receiver_half.recv() => {
match event {
PtyReadWriteOutputEvent::Output(data) => {
output_count += 1;
if output_count <= 10 || output_count.is_multiple_of(100) {
tracing::debug!("PTY output #{}: {} bytes", output_count, data.len());
}
if data.len() < 1000 {
tracing::debug!("PTY output #{} content ({} bytes): {:?}",
output_count, data.len(), String::from_utf8_lossy(&data));
tracing::debug!("PTY output #{} raw bytes: {:02x?}", output_count, &data[..data.len().min(100)]);
}
let out = lock_output_device_as_mut!(output_device);
if let Err(e) = out.write_all(&data) {
tracing::error!("Failed to write to output device: {}", e);
}
if let Err(e) = out.flush() {
tracing::error!("Failed to flush output device: {}", e);
}
}
PtyReadWriteOutputEvent::Exit(status) => {
tracing::debug!("PTY exited with status: {:?}", status);
return Ok(());
}
_ => {}
}
}
Some(input_event) = input_device.next() => {
match input_event {
InputEvent::Keyboard(key) => {
if let KeyPress::WithModifiers {
key: Key::Character('q'),
mask: ModifierKeysMask {
ctrl_key_state: KeyState::Pressed,
shift_key_state: KeyState::NotPressed,
alt_key_state: KeyState::NotPressed,
},
} = key {
tracing::debug!("Ctrl+Q pressed, starting shutdown");
tracing::debug!("Sending Ctrl+C to htop");
let _unused = session.input_event_ch_tx_half.send(PtyInputEvent::SendControl(ControlSequence::CtrlC, CursorKeyMode::default()));
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
tracing::debug!("Sending Close event to PTY");
let _unused = session.input_event_ch_tx_half.send(PtyInputEvent::Close);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
tracing::debug!("Exiting event loop");
return Ok(());
}
if let Some(event) = Option::<PtyInputEvent>::from(key) {
input_count += 1;
tracing::debug!("Input #{}: key={:?}, event={:?}", input_count, key, event);
if let PtyInputEvent::SendControl(ref ctrl, ref mode) = event {
tracing::debug!("Sending control bytes: {:02x?}", ctrl.to_bytes(*mode).as_ref());
}
if let Err(e) = session.input_event_ch_tx_half.send(event.clone()) {
tracing::error!("Failed to send input to PTY: {}", e);
} else {
tracing::debug!("Successfully sent input event: {:?}", event);
}
} else {
tracing::debug!("Unhandled key: {:?}", key);
}
}
InputEvent::Resize(new_size) => {
let pty_size = PtySize {
rows: new_size.row_height.0.value,
cols: new_size.col_width.0.value,
pixel_width: 0,
pixel_height: 0,
};
let _unused = session.input_event_ch_tx_half.send(PtyInputEvent::Resize(pty_size));
}
_ => {}
}
}
}
}
}