use std::io::Write as _;
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
pub type ByteCb = Arc<Mutex<dyn FnMut(u8) + Send>>;
pub trait Chardev: Send {
fn read(&mut self) -> Option<u8>;
fn write(&mut self, data: u8);
fn can_read(&self) -> bool;
fn start_input(&mut self, _cb: ByteCb) {}
}
pub struct CharFrontend {
backend: Box<dyn Chardev>,
}
impl CharFrontend {
pub fn new(backend: Box<dyn Chardev>) -> Self {
Self { backend }
}
pub fn write(&mut self, data: &[u8]) {
for &b in data {
self.backend.write(b);
}
}
pub fn start_input(&mut self, cb: ByteCb) {
self.backend.start_input(cb);
}
}
pub struct NullChardev;
impl Chardev for NullChardev {
fn read(&mut self) -> Option<u8> {
None
}
fn write(&mut self, _data: u8) {}
fn can_read(&self) -> bool {
false
}
}
pub struct StdioChardev {
_thread: Option<std::thread::JoinHandle<()>>,
saved_termios: Option<libc::termios>,
monitor_cb: Option<ByteCb>,
quit_cb: Option<Arc<dyn Fn() + Send + Sync>>,
}
const ESCAPE_CHAR: u8 = 0x01;
impl StdioChardev {
pub fn new() -> Self {
let saved = enable_raw_mode();
if saved.is_some() {
eprintln!("machina: Ctrl+A H for help");
}
Self {
_thread: None,
saved_termios: saved,
monitor_cb: None,
quit_cb: None,
}
}
pub fn set_quit_cb(&mut self, cb: Arc<dyn Fn() + Send + Sync>) {
self.quit_cb = Some(cb);
}
pub fn set_monitor_cb(&mut self, cb: ByteCb) {
self.monitor_cb = Some(cb);
}
}
impl Default for StdioChardev {
fn default() -> Self {
Self::new()
}
}
impl Drop for StdioChardev {
fn drop(&mut self) {
if let Some(ref t) = self.saved_termios {
restore_termios(t);
}
}
}
impl Chardev for StdioChardev {
fn read(&mut self) -> Option<u8> {
None
}
fn write(&mut self, data: u8) {
let mut out = std::io::stdout().lock();
let _ = out.write_all(&[data]);
let _ = out.flush();
}
fn can_read(&self) -> bool {
false
}
fn start_input(&mut self, cb: ByteCb) {
let quit_cb = self.quit_cb.clone();
let mon_cb = self.monitor_cb.clone();
let handle = std::thread::spawn(move || {
use std::io::Read;
let stdin = std::io::stdin();
let mut buf = [0u8; 1];
let mut escape = false;
let mut in_monitor = false;
loop {
match stdin.lock().read(&mut buf) {
Ok(0) => break,
Ok(_) => {
let ch = buf[0];
if escape {
escape = false;
match ch {
b'x' | b'X' => {
if let Some(ref q) = quit_cb {
q();
} else {
std::process::exit(0);
}
break;
}
b'c' | b'C' => {
in_monitor = !in_monitor;
if in_monitor {
eprint!(
"\r\n\
(machina) "
);
} else {
eprint!("\r\n");
}
}
b'h' | b'H' => {
eprintln!(
"\nCtrl+A H \
help\n\
Ctrl+A X \
exit\n\
Ctrl+A C \
monitor\n\
Ctrl+A \
Ctrl+A \
send Ctrl+A"
);
}
ESCAPE_CHAR => {
send_to(
&cb,
&mon_cb,
in_monitor,
ESCAPE_CHAR,
);
}
_ => {
send_to(&cb, &mon_cb, in_monitor, ch);
}
}
} else if ch == ESCAPE_CHAR {
escape = true;
} else {
send_to(&cb, &mon_cb, in_monitor, ch);
}
}
Err(_) => break,
}
}
});
self._thread = Some(handle);
}
}
fn send_to(
serial_cb: &ByteCb,
monitor_cb: &Option<ByteCb>,
in_monitor: bool,
ch: u8,
) {
if in_monitor {
if let Some(ref m) = monitor_cb {
if let Ok(mut f) = m.lock() {
f(ch);
}
}
} else {
if let Ok(mut f) = serial_cb.lock() {
f(ch);
}
}
}
static SAVED_TERMIOS: std::sync::Mutex<Option<libc::termios>> =
std::sync::Mutex::new(None);
pub fn restore_terminal() {
if let Ok(guard) = SAVED_TERMIOS.lock() {
if let Some(ref t) = *guard {
unsafe {
libc::tcsetattr(0, libc::TCSANOW, t);
}
}
}
}
fn enable_raw_mode() -> Option<libc::termios> {
unsafe {
let mut orig: libc::termios = std::mem::zeroed();
if libc::tcgetattr(0, &mut orig) != 0 {
return None;
}
if let Ok(mut g) = SAVED_TERMIOS.lock() {
*g = Some(orig);
}
let mut raw = orig;
raw.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG);
raw.c_iflag &= !(libc::IXON | libc::ICRNL | libc::INLCR | libc::IGNCR);
raw.c_cc[libc::VMIN] = 1;
raw.c_cc[libc::VTIME] = 0;
if libc::tcsetattr(0, libc::TCSANOW, &raw) != 0 {
return None;
}
Some(orig)
}
}
fn restore_termios(orig: &libc::termios) {
unsafe {
libc::tcsetattr(0, libc::TCSANOW, orig);
}
}
pub struct SocketChardev {
stream: Option<UnixStream>,
}
impl SocketChardev {
pub fn new() -> Self {
Self { stream: None }
}
pub fn connect(&mut self, path: &str) -> std::io::Result<()> {
let s = UnixStream::connect(path)?;
s.set_nonblocking(true)?;
self.stream = Some(s);
Ok(())
}
}
impl Default for SocketChardev {
fn default() -> Self {
Self::new()
}
}
impl Chardev for SocketChardev {
fn read(&mut self) -> Option<u8> {
use std::io::Read;
let stream = self.stream.as_mut()?;
let mut buf = [0u8; 1];
match stream.read(&mut buf) {
Ok(1) => Some(buf[0]),
_ => None,
}
}
fn write(&mut self, data: u8) {
if let Some(ref mut stream) = self.stream {
let _ = stream.write_all(&[data]);
}
}
fn can_read(&self) -> bool {
self.stream.is_some()
}
}