use std::char; use std::io; use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use crate::bindings; use crate::util; pub const KEY_TAB: i32 = bindings::KEY_TAB as i32; pub const KEY_LF: i32 = bindings::KEY_LF as i32; pub const KEY_CR: i32 = bindings::KEY_CR as i32; pub const KEY_ESC: i32 = bindings::KEY_ESC as i32; pub const KEY_BACKSPACE: i32 = bindings::KEY_BACKSPACE as i32; pub const KEY_RIGHT: i32 = bindings::KEY_RIGHT as i32; pub const KEY_UP: i32 = bindings::KEY_UP as i32; pub const KEY_LEFT: i32 = bindings::KEY_LEFT as i32; pub const KEY_DOWN: i32 = bindings::KEY_DOWN as i32; pub const KEY_PAGEUP: i32 = bindings::KEY_PAGEUP as i32; pub const KEY_PAGEDOWN: i32 = bindings::KEY_PAGEDOWN as i32; pub const KEY_DELETE: i32 = bindings::KEY_DELETE as i32; pub const KEY_SHIFTTAB: i32 = bindings::KEY_SHIFTTAB as i32; /// Modifier combinator. pub const KEY_CTRL: i32 = bindings::KEY_CTRL as i32; /// Modifier combinator. pub const KEY_SHIFT: i32 = bindings::KEY_SHIFT as i32; /// Modifier combinator. pub const KEY_ALT: i32 = bindings::KEY_ALT as i32; /// Modifier combinator. pub const KEY_CTRLALT: i32 = bindings::KEY_CTRLALT as i32; /// Modifier combinator. pub const KEY_CTRLSHIFT: i32 = bindings::KEY_CTRLSHIFT as i32; /// A style descriptor for a cell on the screen. #[derive(Debug, Copy, Clone)] pub struct Style { /// The foreground color; valid values are 0, 1, ..., 7 (the eight ANSI /// colors) and 9 (the default color). pub fg: u8, /// The background color; valid values are 0, 1, ..., 7 (the eight ANSI /// colors) and 9 (the default color). pub bg: u8, /// Whether the character in the cell should be printed bold. If this is /// enabled, the terminal generally uses the bright version of the /// foreground color instead of the normal version. Therefore, there are /// effectively 16, not 8, foreground colors (in addition to the default /// color, 9). pub bold: bool, /// Whether to display an underline. pub ul: bool } /// RAII struct for keyboard-input initialization /// /// Only one instance of this struct can be active at a time. Attempting to /// create a second instance will panic. pub struct Keyboard { _phantom: PhantomData<*mut u8>, } static KEYBOARD_REFCOUNT: AtomicBool = AtomicBool::new(false); impl Keyboard { /// Grab the keyboard, i.e. enable raw mode. /// /// This disables automatic printing of typed characters (echo), and allows /// reading a single character even when a newline has not yet been /// entered. /// /// If `nosigkeys` is true, interrupt keycodes like ctrl-C, ctrl-\ and /// ctrl-Z will no longer be handled by the shell but will be available to /// be read using `get_key()` just like the other keys. pub fn grab(nosigkeys: bool) -> Self { let prev = KEYBOARD_REFCOUNT.compare_and_swap( false, true, Ordering::SeqCst); if prev { panic!("Can only hold one Keyboard struct at a time!"); } unsafe { bindings::initkeyboard(nosigkeys); } Self { _phantom: PhantomData } } /// Explicitly release the keyboard. /// /// This prevents further use of the Keyboard functionality and /// simultaneously forces its lifetime to extend until this call, delaying /// deinitialization of the keyboard until this call. pub fn end(self) {} // None on EOF, otherwise a combination of the KEY_* constants pub fn get_key(&self) -> io::Result> { match unsafe { bindings::tgetkey() } { -2 => Err(io::Error::last_os_error()), -1 => Ok(None), value => Ok(Some(value)), } } pub fn get_line(&self) -> Option { let ptr = unsafe { bindings::tgetline() }; if ptr.is_null() { None } else { unsafe { Some(util::string_from_utf8_charp(ptr)) } } } } impl Drop for Keyboard { fn drop(&mut self) { let prev = KEYBOARD_REFCOUNT.compare_and_swap( true, false, Ordering::SeqCst); if !prev { panic!("Internal: No Keyboard was held on Drop of a Keyboard?"); } unsafe { bindings::endkeyboard(); } } } /// RAII struct for screen-drawing initialization /// /// Only one instance of this struct can be active at a time. Attempting to /// create a second instance will panic. pub struct Screen<'a> { _keyboard: &'a Keyboard, } static SCREEN_REFCOUNT: AtomicBool = AtomicBool::new(false); impl<'a> Screen<'a> { /// Grab the screen, i.e. switch to the alternative screen and allocate draw /// buffers. /// /// This function takes and stores a Keyboard reference to ensure that the /// keyboard is initialized while the screen is grabbed. pub fn grab<'b: 'a>(keyboard: &'b Keyboard) -> Self { let prev = SCREEN_REFCOUNT.compare_and_swap( false, true, Ordering::SeqCst); if prev { panic!("Can only hold one Screen struct at a time!"); } unsafe { bindings::initscreen(); } Self { _keyboard: keyboard } } pub fn end(self) {} /// Install ctrl-L redraw handler. /// /// If the argument is true, then upon reading ctrl-L in /// `Keyboard::get_key()`, a full redraw is issued, and the the call blocks /// for the next key. Note that if the user only types ctrl-L, this means /// that `get_key` may block while you don't expect it. pub fn install_cl_handler(&self, install: bool) { unsafe { bindings::installCLhandler(install); } } /// Install redraw handler. /// /// The argument indicates whether the redraw is a full redraw. The handler /// is called before the redraw is performed. Please do not trigger redraws /// from within the handler. pub fn on_redraw(&self, handler: extern "C" fn(full_redraw: bool)) { unsafe { bindings::installredrawhandler(Some(handler)); } } pub fn clear(&self) { unsafe { bindings::clearscreen(); } } /// Update style state. /// /// This modifies the style for the characters printed from now on. Note /// that this does not write anything to the terminal. pub fn set_style(&self, style: Style) { let sty = bindings::Style { fg: style.fg as i32, bg: style.bg as i32, bold: style.bold, ul: style.ul, }; unsafe { bindings::setstyle(&sty as *const bindings::Style); } } /// Update partial style state. see `set_style()`. pub fn set_fg(&self, fg: u8) { unsafe { bindings::setfg(fg as i32); } } /// Update partial style state. see `set_style()`. pub fn set_bg(&self, bg: u8) { unsafe { bindings::setbg(bg as i32); } } /// Update partial style state. see `set_style()`. pub fn set_bold(&self, bold: bool) { unsafe { bindings::setbold(bold); } } /// Update partial style state. see `set_style()`. pub fn set_ul(&self, ul: bool) { unsafe { bindings::setul(ul); } } pub fn putc(&self, c: char) { unsafe { bindings::tputc(c as i32); } } /// Newlines ('\n') return to the column on which printing started. pub fn print(&self, s: &str) { unsafe { bindings::tprint(s.as_bytes().as_ptr() as *const i8); } } pub fn fill_rect(&self, lefttop: (u32, u32), size: (u32, u32), c: char) { unsafe { bindings::fillrect( lefttop.0 as i32, lefttop.1 as i32, size.0 as i32, size.1 as i32, c as i32); } } pub fn redraw(&self) { unsafe { bindings::redraw(); } } pub fn redraw_full(&self) { unsafe { bindings::redrawfull(); } } /// Scrolls a rectangle on the screen by a signed number of lines. pub fn scroll_term(&self, lefttop: (u32, u32), size: (u32, u32), amount: i32) { unsafe { bindings::scrollterm( lefttop.0 as i32, lefttop.1 as i32, size.0 as i32, size.1 as i32, amount); } } pub fn get_buffer_char(&self, pos: (u32, u32)) -> char { unsafe { char::from_u32( bindings::getbufferchar(pos.0 as i32, pos.1 as i32) as u32 ).unwrap() } } /// Updates state; does not actually move the cursor yet (use `redraw()`). pub fn move_to(&self, pos: (u32, u32)) { unsafe { bindings::moveto(pos.0 as i32, pos.1 as i32); } } /// Save the (internal) cursor position on a stack. pub fn push_cursor(&self) { unsafe { bindings::pushcursor(); } } /// Pop the (internal) cursor position from the stack. pub fn pop_cursor(&self) { unsafe { bindings::popcursor(); } } /// Takes effect immediately! pub fn cursor_visible(&self, visible: bool) { unsafe { bindings::cursorvisible(visible); } } } impl<'a> Drop for Screen<'a> { fn drop(&mut self) { let prev = SCREEN_REFCOUNT.compare_and_swap( true, false, Ordering::SeqCst); if !prev { panic!("Internal: No Screen was held on Drop of a Screen?"); } unsafe { bindings::endscreen(); } } } pub fn get_term_size() -> (u32, u32) { let sz = unsafe { bindings::gettermsize() }; (sz.w as u32, sz.h as u32) } pub fn bel() { unsafe { bindings::bel(); } }