use log::{debug, warn};
use ncurses;
use hashbrown::HashMap;
use std::cell::{Cell, RefCell};
use std::ffi::CString;
use std::fs::File;
use std::io;
use std::io::Write;
use libc;
use crate::backend;
use crate::event::{Event, Key, MouseButton, MouseEvent};
use crate::theme::{Color, ColorPair, Effect};
use crate::utf8;
use crate::vec::Vec2;
use self::super::split_i32;
use self::ncurses::mmask_t;
pub struct Backend {
current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<(i16, i16), i16>>,
key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>,
input_buffer: Option<Event>,
}
fn find_closest_pair(pair: ColorPair) -> (i16, i16) {
super::find_closest_pair(pair, ncurses::COLORS() as i16)
}
fn write_to_tty(bytes: &[u8]) -> io::Result<()> {
let mut tty_output =
File::create("/dev/tty").expect("cursive can only run with a tty");
tty_output.write_all(bytes)?;
Ok(())
}
impl Backend {
pub fn init() -> io::Result<Box<dyn backend::Backend>> {
if std::env::var("TERM")
.map(|var| var.is_empty())
.unwrap_or(true)
{
return Err(io::Error::new(
io::ErrorKind::Other,
"$TERM is unset. Cannot initialize ncurses interface.",
));
}
ncurses::setlocale(ncurses::LcCategory::all, "");
::std::env::set_var("ESCDELAY", "25");
let tty_path = CString::new("/dev/tty").unwrap();
let mode = CString::new("r+").unwrap();
let tty = unsafe { libc::fopen(tty_path.as_ptr(), mode.as_ptr()) };
ncurses::newterm(None, tty, tty);
ncurses::keypad(ncurses::stdscr(), true);
ncurses::mouseinterval(0);
ncurses::mousemask(
(ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION)
as mmask_t,
None,
);
ncurses::timeout(0);
ncurses::noecho();
ncurses::raw();
ncurses::start_color();
ncurses::use_default_colors();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
write_to_tty(b"\x1B[?1002h")?;
let c = Backend {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
key_codes: initialize_keymap(),
last_mouse_button: None,
input_buffer: None,
};
Ok(Box::new(c))
}
fn insert_color(
&self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16),
) -> i16 {
let n = 1 + pairs.len() as i16;
let target = if ncurses::COLOR_PAIRS() > i32::from(n) {
n
} else {
let target = n - 1;
pairs.retain(|_, &mut v| v != target);
target
};
pairs.insert((front, back), target);
ncurses::init_pair(target, front, back);
target
}
fn get_or_create(&self, pair: ColorPair) -> i16 {
let mut pairs = self.pairs.borrow_mut();
let result = find_closest_pair(pair);
let lookup = pairs.get(&result);
if lookup.is_some() {
*lookup.unwrap()
} else {
self.insert_color(&mut *pairs, result)
}
}
fn set_colors(&self, pair: ColorPair) {
let i = self.get_or_create(pair);
self.current_style.set(pair);
let style = ncurses::COLOR_PAIR(i);
ncurses::attron(style);
}
fn parse_next(&mut self) -> Option<Event> {
if let Some(event) = self.input_buffer.take() {
return Some(event);
}
let ch: i32 = ncurses::getch();
if ch == -1 {
return None;
}
let event = if 32 <= ch && ch <= 255 && ch != 127 {
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
.map(Event::Char)
.unwrap_or_else(|e| {
warn!("Error reading input: {}", e);
Event::Unknown(vec![ch as u8])
})
} else {
self.parse_ncurses_char(ch)
};
Some(event)
}
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
if ch == ncurses::KEY_MOUSE {
self.parse_mouse_event()
} else {
self.key_codes
.get(&ch)
.cloned()
.unwrap_or_else(|| Event::Unknown(split_i32(ch)))
}
}
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = ncurses::MEVENT {
id: 0,
x: 0,
y: 0,
z: 0,
bstate: 0,
};
if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT)
== ncurses::OK
{
let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as mmask_t) != 0;
let _shift =
(mevent.bstate & ncurses::BUTTON_SHIFT as mmask_t) != 0;
let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0;
mevent.bstate &= !(ncurses::BUTTON_SHIFT
| ncurses::BUTTON_ALT
| ncurses::BUTTON_CTRL)
as mmask_t;
let make_event = |event| Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event,
};
if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as mmask_t {
self.last_mouse_button
.map(MouseEvent::Hold)
.or_else(|| {
if mevent.bstate
== ncurses::BUTTON5_DOUBLE_CLICKED as mmask_t
{
Some(MouseEvent::WheelDown)
} else {
None
}
})
.map(&make_event)
.unwrap_or_else(|| Event::Unknown(vec![]))
} else {
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
on_mouse_event(single_event as i32, |e| {
if event.is_none() {
event = Some(e);
} else {
self.input_buffer = Some(make_event(e));
}
});
}
if let Some(event) = event {
match event {
MouseEvent::Press(btn) => {
self.last_mouse_button = Some(btn);
}
MouseEvent::Release(_) => {
self.last_mouse_button = None;
}
_ => (),
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
} else {
debug!("Ncurses event not recognized.");
Event::Unknown(vec![])
}
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"ncurses"
}
fn screen_size(&self) -> Vec2 {
let mut x: i32 = 0;
let mut y: i32 = 0;
ncurses::getmaxyx(ncurses::stdscr(), &mut y, &mut x);
(x, y).into()
}
fn has_colors(&self) -> bool {
ncurses::has_colors()
}
fn poll_event(&mut self) -> Option<Event> {
self.parse_next()
}
fn finish(&mut self) {
write_to_tty(b"\x1B[?1002l").unwrap();
ncurses::endwin();
}
fn set_color(&self, colors: ColorPair) -> ColorPair {
let current = self.current_style.get();
if current != colors {
self.set_colors(colors);
}
current
}
fn set_effect(&self, effect: Effect) {
let style = match effect {
Effect::Reverse => ncurses::A_REVERSE(),
Effect::Simple => ncurses::A_NORMAL(),
Effect::Bold => ncurses::A_BOLD(),
Effect::Italic => ncurses::A_ITALIC(),
Effect::Underline => ncurses::A_UNDERLINE(),
};
ncurses::attron(style);
}
fn unset_effect(&self, effect: Effect) {
let style = match effect {
Effect::Reverse => ncurses::A_REVERSE(),
Effect::Simple => ncurses::A_NORMAL(),
Effect::Bold => ncurses::A_BOLD(),
Effect::Italic => ncurses::A_ITALIC(),
Effect::Underline => ncurses::A_UNDERLINE(),
};
ncurses::attroff(style);
}
fn clear(&self, color: Color) {
let id = self.get_or_create(ColorPair {
front: color,
back: color,
});
ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id));
ncurses::clear();
}
fn refresh(&mut self) {
ncurses::refresh();
}
fn print_at(&self, pos: Vec2, text: &str) {
ncurses::mvaddstr(pos.y as i32, pos.x as i32, text);
}
fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) {
if repetitions > 0 {
ncurses::mvaddstr(pos.y as i32, pos.x as i32, text);
let mut dupes_left = repetitions - 1;
while dupes_left > 0 {
ncurses::addstr(text);
dupes_left -= 1;
}
}
}
}
fn get_mouse_button(bare_event: i32) -> MouseButton {
match bare_event {
ncurses::BUTTON1_RELEASED
| ncurses::BUTTON1_PRESSED
| ncurses::BUTTON1_CLICKED
| ncurses::BUTTON1_DOUBLE_CLICKED
| ncurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left,
ncurses::BUTTON2_RELEASED
| ncurses::BUTTON2_PRESSED
| ncurses::BUTTON2_CLICKED
| ncurses::BUTTON2_DOUBLE_CLICKED
| ncurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle,
ncurses::BUTTON3_RELEASED
| ncurses::BUTTON3_PRESSED
| ncurses::BUTTON3_CLICKED
| ncurses::BUTTON3_DOUBLE_CLICKED
| ncurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right,
ncurses::BUTTON4_RELEASED
| ncurses::BUTTON4_PRESSED
| ncurses::BUTTON4_CLICKED
| ncurses::BUTTON4_DOUBLE_CLICKED
| ncurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4,
ncurses::BUTTON5_RELEASED
| ncurses::BUTTON5_PRESSED
| ncurses::BUTTON5_CLICKED
| ncurses::BUTTON5_DOUBLE_CLICKED
| ncurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5,
_ => MouseButton::Other,
}
}
fn on_mouse_event<F>(bare_event: i32, mut f: F)
where
F: FnMut(MouseEvent),
{
let button = get_mouse_button(bare_event);
match bare_event {
ncurses::BUTTON1_RELEASED
| ncurses::BUTTON2_RELEASED
| ncurses::BUTTON3_RELEASED => f(MouseEvent::Release(button)),
ncurses::BUTTON1_PRESSED
| ncurses::BUTTON2_PRESSED
| ncurses::BUTTON3_PRESSED => f(MouseEvent::Press(button)),
ncurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp),
ncurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown),
_ => debug!("Unknown event: {:032b}", bare_event),
}
}
fn add_fn<F>(start: i32, with_key: F, map: &mut HashMap<i32, Event>)
where
F: Fn(Key) -> Event,
{
for i in 0..12 {
map.insert(start + i, with_key(Key::from_f((i + 1) as u8)));
}
}
fn initialize_keymap() -> HashMap<i32, Event> {
let mut map = HashMap::new();
map.insert(-1, Event::Refresh);
map.insert(9, Event::Key(Key::Tab));
map.insert(10, Event::Key(Key::Enter));
map.insert(ncurses::KEY_ENTER, Event::Key(Key::Enter));
map.insert(27, Event::Key(Key::Esc));
map.insert(127, Event::Key(Key::Backspace));
map.insert(ncurses::KEY_BACKSPACE, Event::Key(Key::Backspace));
map.insert(410, Event::WindowResize);
map.insert(ncurses::KEY_B2, Event::Key(Key::NumpadCenter));
map.insert(ncurses::KEY_DC, Event::Key(Key::Del));
map.insert(ncurses::KEY_IC, Event::Key(Key::Ins));
map.insert(ncurses::KEY_BTAB, Event::Shift(Key::Tab));
map.insert(ncurses::KEY_SLEFT, Event::Shift(Key::Left));
map.insert(ncurses::KEY_SRIGHT, Event::Shift(Key::Right));
map.insert(ncurses::KEY_LEFT, Event::Key(Key::Left));
map.insert(ncurses::KEY_RIGHT, Event::Key(Key::Right));
map.insert(ncurses::KEY_UP, Event::Key(Key::Up));
map.insert(ncurses::KEY_DOWN, Event::Key(Key::Down));
map.insert(ncurses::KEY_SR, Event::Shift(Key::Up));
map.insert(ncurses::KEY_SF, Event::Shift(Key::Down));
map.insert(ncurses::KEY_PPAGE, Event::Key(Key::PageUp));
map.insert(ncurses::KEY_NPAGE, Event::Key(Key::PageDown));
map.insert(ncurses::KEY_HOME, Event::Key(Key::Home));
map.insert(ncurses::KEY_END, Event::Key(Key::End));
map.insert(ncurses::KEY_SHOME, Event::Shift(Key::Home));
map.insert(ncurses::KEY_SEND, Event::Shift(Key::End));
map.insert(ncurses::KEY_SDC, Event::Shift(Key::Del));
map.insert(ncurses::KEY_SNEXT, Event::Shift(Key::PageDown));
map.insert(ncurses::KEY_SPREVIOUS, Event::Shift(Key::PageUp));
for c in 1..26 {
let event = match c {
3 => Event::Exit,
9 => Event::Key(Key::Tab),
10 => Event::Key(Key::Enter),
other => Event::CtrlChar((b'a' - 1 + other as u8) as char),
};
map.insert(c, event);
}
add_fn(ncurses::KEY_F1, Event::Key, &mut map);
add_fn(277, Event::Shift, &mut map);
add_fn(289, Event::Ctrl, &mut map);
add_fn(301, Event::CtrlShift, &mut map);
add_fn(313, Event::Alt, &mut map);
super::fill_key_codes(&mut map, ncurses::keyname);
map
}