use crate::cursive::backends::termion::termion;
use crate::cursive::backends::termion::termion::color as tcolor;
use crate::cursive::backends::termion::termion::event::Event as TEvent;
use crate::cursive::backends::termion::termion::event::Key as TKey;
use crate::cursive::backends::termion::termion::event::MouseButton as TMouseButton;
use crate::cursive::backends::termion::termion::event::MouseEvent as TMouseEvent;
use crate::cursive::backends::termion::termion::input::{Events, TermRead};
use crate::cursive::backends::termion::termion::style as tstyle;
use crate::cursive::backend;
use crate::cursive::event::{Event, Key, MouseButton, MouseEvent};
use crate::cursive::theme;
use crate::cursive::Vec2;
use tokio::sync::mpsc::Receiver;
use tokio::sync::mpsc::Sender;
use std::cell::Cell;
use std::cell::RefCell;
use std::fs::File;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CursiveOutput {
Data(Vec<u8>),
Close,
}
pub struct Backend {
current_style: Cell<theme::ColorPair>,
last_button: Option<MouseButton>,
events: Events<File>,
#[cfg(unix)]
input_fd: std::os::unix::io::RawFd,
running: Arc<AtomicBool>,
output_sender: Sender<CursiveOutput>,
resize_receiver: Receiver<Vec2>,
relayout_sender: Sender<()>,
size: Vec2,
data: RefCell<Vec<u8>>,
}
#[cfg(unix)]
fn set_blocking(fd: std::os::unix::io::RawFd, blocking: bool) -> std::io::Result<()> {
use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
let flags = unsafe { fcntl(fd, F_GETFL, 0) };
if flags < 0 {
return Err(std::io::Error::last_os_error());
}
let flags = if blocking {
flags & !O_NONBLOCK
} else {
flags | O_NONBLOCK
};
let res = unsafe { fcntl(fd, F_SETFL, flags) };
if res != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
impl Backend {
pub fn init_ssh(
input_file: File,
output_sender: Sender<CursiveOutput>,
resize_receiver: Receiver<Vec2>,
relayout_sender: Sender<()>,
) -> std::io::Result<Box<dyn backend::Backend>> {
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(unix)]
let input_fd = input_file.as_raw_fd();
#[cfg(unix)]
set_blocking(input_fd, false)?;
let running = Arc::new(AtomicBool::new(true));
let c = Backend {
current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
last_button: None,
events: input_file.events(),
#[cfg(unix)]
input_fd,
running,
output_sender,
resize_receiver,
relayout_sender,
size: Vec2::new(1, 1),
data: RefCell::new(Vec::new()),
};
c.write(format!("{}", termion::cursor::Hide));
Ok(Box::new(c))
}
fn apply_colors(&self, colors: theme::ColorPair) {
with_color(colors.front, |c| self.write(tcolor::Fg(c)));
with_color(colors.back, |c| self.write(tcolor::Bg(c)));
}
fn map_key(&mut self, event: TEvent) -> Event {
match event {
TEvent::Unsupported(bytes) => Event::Unknown(bytes),
TEvent::Key(TKey::Esc) => Event::Key(Key::Esc),
TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace),
TEvent::Key(TKey::Left) => Event::Key(Key::Left),
TEvent::Key(TKey::Right) => Event::Key(Key::Right),
TEvent::Key(TKey::Up) => Event::Key(Key::Up),
TEvent::Key(TKey::Down) => Event::Key(Key::Down),
TEvent::Key(TKey::Home) => Event::Key(Key::Home),
TEvent::Key(TKey::End) => Event::Key(Key::End),
TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp),
TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown),
TEvent::Key(TKey::Delete) => Event::Key(Key::Del),
TEvent::Key(TKey::Insert) => Event::Key(Key::Ins),
TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)),
TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]),
TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter),
TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab),
TEvent::Key(TKey::Char(c)) => Event::Char(c),
TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c),
TEvent::Key(TKey::Alt(c)) => Event::AltChar(c),
TEvent::Mouse(TMouseEvent::Press(btn, x, y)) => {
let position = (x - 1, y - 1).into();
let event = match btn {
TMouseButton::Left => MouseEvent::Press(MouseButton::Left),
TMouseButton::Middle => MouseEvent::Press(MouseButton::Middle),
TMouseButton::Right => MouseEvent::Press(MouseButton::Right),
TMouseButton::WheelUp => MouseEvent::WheelUp,
TMouseButton::WheelDown => MouseEvent::WheelDown,
};
if let MouseEvent::Press(btn) = event {
self.last_button = Some(btn);
}
Event::Mouse {
event,
position,
offset: Vec2::zero(),
}
}
TEvent::Mouse(TMouseEvent::Release(x, y)) if self.last_button.is_some() => {
let event = MouseEvent::Release(self.last_button.unwrap());
let position = (x - 1, y - 1).into();
Event::Mouse {
event,
position,
offset: Vec2::zero(),
}
}
TEvent::Mouse(TMouseEvent::Hold(x, y)) if self.last_button.is_some() => {
let event = MouseEvent::Hold(self.last_button.unwrap());
let position = (x - 1, y - 1).into();
Event::Mouse {
event,
position,
offset: Vec2::zero(),
}
}
_ => Event::Unknown(vec![]),
}
}
fn write<T>(&self, content: T)
where
T: std::fmt::Display,
{
self.data
.borrow_mut()
.extend(format!("{}", content).as_bytes().to_vec());
}
fn close(&self) {
{
let mut data = self.data.borrow_mut();
if !data.is_empty() {
self.output_sender
.blocking_send(CursiveOutput::Data(data.clone()))
.unwrap();
data.clear();
}
}
self.output_sender
.blocking_send(CursiveOutput::Close)
.unwrap();
}
}
impl Drop for Backend {
fn drop(&mut self) {
self.running.store(false, Ordering::Relaxed);
#[cfg(unix)]
set_blocking(self.input_fd, true).unwrap();
self.write(format!(
"{}{}",
termion::cursor::Show,
termion::cursor::Goto(1, 1)
));
self.write(format!(
"{}[49m{}[39m{}",
27 as char,
27 as char,
termion::clear::All
));
self.close();
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"termion"
}
fn set_title(&mut self, title: String) {
self.write(format!("\x1B]0;{}\x07", title));
}
fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair {
let current_style = self.current_style.get();
if current_style != color {
self.apply_colors(color);
self.current_style.set(color);
}
current_style
}
fn set_effect(&self, effect: theme::Effect) {
match effect {
theme::Effect::Simple => (),
theme::Effect::Reverse => self.write(tstyle::Invert),
theme::Effect::Dim => self.write(tstyle::Faint),
theme::Effect::Bold => self.write(tstyle::Bold),
theme::Effect::Blink => self.write(tstyle::Blink),
theme::Effect::Italic => self.write(tstyle::Italic),
theme::Effect::Strikethrough => self.write(tstyle::CrossedOut),
theme::Effect::Underline => self.write(tstyle::Underline),
}
}
fn unset_effect(&self, effect: theme::Effect) {
match effect {
theme::Effect::Simple => (),
theme::Effect::Reverse => self.write(tstyle::NoInvert),
theme::Effect::Dim | theme::Effect::Bold => self.write(tstyle::NoFaint),
theme::Effect::Blink => self.write(tstyle::NoBlink),
theme::Effect::Italic => self.write(tstyle::NoItalic),
theme::Effect::Strikethrough => self.write(tstyle::NoCrossedOut),
theme::Effect::Underline => self.write(tstyle::NoUnderline),
}
}
fn has_colors(&self) -> bool {
true
}
fn screen_size(&self) -> Vec2 {
self.size.clone()
}
fn clear(&self, color: theme::Color) {
self.apply_colors(theme::ColorPair {
front: color,
back: color,
});
self.write(termion::clear::All);
}
fn refresh(&mut self) {
}
fn print_at(&self, pos: Vec2, text: &str) {
self.write(format!(
"{}{}",
termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16),
text
));
}
fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) {
if repetitions > 0 {
self.write(format!(
"{}{}",
termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16),
text
));
let mut dupes_left = repetitions - 1;
while dupes_left > 0 {
self.write(format!("{}", text));
dupes_left -= 1;
}
}
}
fn poll_event(&mut self) -> Option<Event> {
{
let mut data = self.data.borrow_mut();
if !data.is_empty() {
self.output_sender
.blocking_send(CursiveOutput::Data(data.clone()))
.unwrap();
data.clear();
}
}
if let Ok(size) = self.resize_receiver.try_recv() {
self.size = size;
self.relayout_sender.blocking_send(()).unwrap();
}
if let Some(Ok(event)) = self.events.next() {
Some(self.map_key(event))
} else {
None
}
}
}
fn with_color<F, R>(clr: theme::Color, f: F) -> R
where
F: FnOnce(&dyn tcolor::Color) -> R,
{
match clr {
theme::Color::TerminalDefault => f(&tcolor::Reset),
theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black),
theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red),
theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green),
theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow),
theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue),
theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta),
theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan),
theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White),
theme::Color::Light(theme::BaseColor::Black) => f(&tcolor::LightBlack),
theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed),
theme::Color::Light(theme::BaseColor::Green) => f(&tcolor::LightGreen),
theme::Color::Light(theme::BaseColor::Yellow) => f(&tcolor::LightYellow),
theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue),
theme::Color::Light(theme::BaseColor::Magenta) => f(&tcolor::LightMagenta),
theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan),
theme::Color::Light(theme::BaseColor::White) => f(&tcolor::LightWhite),
theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)),
theme::Color::RgbLowRes(r, g, b) => f(&tcolor::AnsiValue::rgb(r, g, b)),
}
}