use std::io;
use std::sync::Arc;
use std::time::Duration;
use parking_lot::Mutex;
use tokio::sync::mpsc;
use super::backend::{Backend, Capabilities};
use super::input_parser::InputParser;
use crate::core::event::Event;
pub struct SshBackend {
output_buffer: Vec<u8>,
output_tx: mpsc::UnboundedSender<Vec<u8>>,
event_rx: mpsc::UnboundedReceiver<Event>,
event_queue: Vec<Event>,
size: Arc<Mutex<(u16, u16)>>,
capabilities: Capabilities,
initialized: bool,
}
impl SshBackend {
pub fn new(
output_tx: mpsc::UnboundedSender<Vec<u8>>,
event_rx: mpsc::UnboundedReceiver<Event>,
size: Arc<Mutex<(u16, u16)>>,
) -> Self {
Self {
output_buffer: Vec::with_capacity(8192),
output_tx,
event_rx,
event_queue: Vec::new(),
size,
capabilities: Capabilities {
mouse: true,
colors_256: true,
true_color: false, bracketed_paste: false,
focus_events: false,
kitty_keyboard: false,
},
initialized: false,
}
}
pub fn size_handle(&self) -> Arc<Mutex<(u16, u16)>> {
Arc::clone(&self.size)
}
pub fn set_capabilities(&mut self, caps: Capabilities) {
self.capabilities = caps;
}
fn send_output(&mut self) -> io::Result<()> {
if !self.output_buffer.is_empty() {
let data = std::mem::take(&mut self.output_buffer);
self.output_tx
.send(data)
.map_err(|_| io::Error::new(
io::ErrorKind::BrokenPipe,
"SSH channel closed"
))?;
}
Ok(())
}
}
impl Backend for SshBackend {
fn init(&mut self) -> io::Result<()> {
if self.initialized {
return Ok(());
}
self.output_buffer.extend_from_slice(b"\x1b[?1049h");
self.output_buffer.extend_from_slice(b"\x1b[?1000h");
self.output_buffer.extend_from_slice(b"\x1b[?1006h");
self.output_buffer.extend_from_slice(b"\x1b[?1002h");
self.output_buffer.extend_from_slice(b"\x1b[?25l");
self.output_buffer.extend_from_slice(b"\x1b[?7l");
self.send_output()?;
self.initialized = true;
Ok(())
}
fn cleanup(&mut self) -> io::Result<()> {
if !self.initialized {
return Ok(());
}
self.output_buffer.extend_from_slice(b"\x1b[?25h");
self.output_buffer.extend_from_slice(b"\x1b[?7h");
self.output_buffer.extend_from_slice(b"\x1b[?1002l");
self.output_buffer.extend_from_slice(b"\x1b[?1006l");
self.output_buffer.extend_from_slice(b"\x1b[?1000l");
self.output_buffer.extend_from_slice(b"\x1b[?1049l");
self.output_buffer.extend_from_slice(b"\x1b[0m");
self.send_output()?;
self.initialized = false;
Ok(())
}
fn size(&self) -> io::Result<(u16, u16)> {
Ok(*self.size.lock())
}
fn poll_event(&mut self, _timeout: Duration) -> io::Result<Option<Event>> {
if let Some(ev) = self.event_queue.pop() {
return Ok(Some(ev));
}
match self.event_rx.try_recv() {
Ok(ev) => Ok(Some(ev)),
Err(mpsc::error::TryRecvError::Empty) => Ok(None),
Err(mpsc::error::TryRecvError::Disconnected) => {
Err(io::Error::new(io::ErrorKind::BrokenPipe, "SSH channel disconnected"))
}
}
}
fn write_raw(&mut self, data: &[u8]) -> io::Result<()> {
self.output_buffer.extend_from_slice(data);
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
self.send_output()
}
fn show_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
use std::io::Write;
write!(self.output_buffer, "\x1b[{};{}H\x1b[?25h", y + 1, x + 1)?;
Ok(())
}
fn hide_cursor(&mut self) -> io::Result<()> {
self.output_buffer.extend_from_slice(b"\x1b[?25l");
Ok(())
}
fn capabilities(&self) -> Capabilities {
self.capabilities
}
fn suspend(&mut self) -> io::Result<()> {
Ok(())
}
fn resume(&mut self) -> io::Result<()> {
Ok(())
}
fn cell_aspect_ratio(&self) -> (i16, i16) {
(2, 1)
}
fn bell(&mut self) -> io::Result<()> {
self.output_buffer.push(0x07);
self.send_output()
}
fn clear_screen(&mut self) -> io::Result<()> {
self.output_buffer.extend_from_slice(b"\x1b[0m\x1b[2J\x1b[H");
self.send_output()
}
}
pub struct SshSessionBuilder {
width: u16,
height: u16,
}
impl SshSessionBuilder {
pub fn new() -> Self {
Self {
width: 80,
height: 24,
}
}
pub fn size(mut self, width: u16, height: u16) -> Self {
self.width = width;
self.height = height;
self
}
pub fn build(self) -> (SshBackend, SshSessionHandle) {
let (event_tx, event_rx) = mpsc::unbounded_channel();
let (output_tx, output_rx) = mpsc::unbounded_channel();
let size = Arc::new(Mutex::new((self.width, self.height)));
let backend = SshBackend::new(output_tx, event_rx, Arc::clone(&size));
let handle = SshSessionHandle {
event_tx,
output_rx,
size,
input_parser: InputParser::new(),
};
(backend, handle)
}
}
impl Default for SshSessionBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct SshSessionHandle {
pub event_tx: mpsc::UnboundedSender<Event>,
pub output_rx: mpsc::UnboundedReceiver<Vec<u8>>,
pub size: Arc<Mutex<(u16, u16)>>,
pub input_parser: InputParser,
}
impl SshSessionHandle {
pub fn resize(&mut self, width: u16, height: u16) {
*self.size.lock() = (width, height);
let event = Event::mouse(
crate::core::event::EventType::Nothing,
crate::core::geometry::Point::zero(),
0,
false,
);
let _ = self.event_tx.send(event);
}
pub fn process_input(&mut self, data: &[u8]) {
let events = self.input_parser.parse(data);
for event in events {
let _ = self.event_tx.send(event);
}
}
pub fn try_recv_output(&mut self) -> Option<Vec<u8>> {
self.output_rx.try_recv().ok()
}
pub fn is_disconnected(&self) -> bool {
self.event_tx.is_closed()
}
}