#![feature(hash)]
#![feature(io)]
#![feature(libc)]
#![feature(std_misc)]
#![feature(core)]
#![feature(optin_builtin_traits)]
extern crate libc;
extern crate "termbox-sys" as termbox;
#[macro_use] extern crate bitflags;
pub use self::running::running;
pub use self::style::{Style, RB_BOLD, RB_UNDERLINE, RB_REVERSE, RB_NORMAL};
use std::error::Error;
use std::fmt;
use std::time::duration::Duration;
use std::num::FromPrimitive;
use std::old_io::IoError;
use std::default::Default;
use termbox::RawEvent;
use libc::c_int;
#[derive(Copy)]
pub enum Event {
KeyEvent(u8, u16, u32),
ResizeEvent(i32, i32),
NoEvent
}
#[derive(Copy, Debug)]
pub enum InputMode {
Current = 0x00,
Esc = 0x01,
Alt = 0x02,
}
#[derive(Copy, PartialEq)]
#[repr(C,u16)]
pub enum Color {
Default = 0x00,
Black = 0x01,
Red = 0x02,
Green = 0x03,
Yellow = 0x04,
Blue = 0x05,
Magenta = 0x06,
Cyan = 0x07,
White = 0x08,
}
mod style {
bitflags! {
#[repr(C)]
flags Style: u16 {
const TB_NORMAL_COLOR = 0x000F,
const RB_BOLD = 0x0100,
const RB_UNDERLINE = 0x0200,
const RB_REVERSE = 0x0400,
const RB_NORMAL = 0x0000,
const TB_ATTRIB = RB_BOLD.bits | RB_UNDERLINE.bits | RB_REVERSE.bits,
}
}
impl Style {
pub fn from_color(color: super::Color) -> Style {
Style { bits: color as u16 & TB_NORMAL_COLOR.bits }
}
}
}
const NIL_RAW_EVENT: RawEvent = RawEvent { etype: 0, emod: 0, key: 0, ch: 0, w: 0, h: 0, x: 0, y: 0 };
#[allow(non_snake_case)]
pub mod EventErrorKind {
#[derive(Copy,Debug)]
pub struct Error;
}
pub type EventError = Option<EventErrorKind::Error>;
pub type EventResult<T> = Result<T, EventError>;
impl Error for EventError {
fn description(&self) -> &str {
match *self {
Some(EventErrorKind::Error) => "Unknown error.",
None => "Unexpected return code."
}
}
}
impl fmt::Display for EventError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", *self)
}
}
fn unpack_event(ev_type: c_int, ev: &RawEvent) -> EventResult<Event> {
match ev_type {
0 => Ok(Event::NoEvent),
1 => Ok(Event::KeyEvent(ev.emod, ev.key, ev.ch)),
2 => Ok(Event::ResizeEvent(ev.w, ev.h)),
-1 => Err(Some(EventErrorKind::Error)),
_ => Err(None)
}
}
#[derive(Copy,FromPrimitive,Debug)]
#[repr(C,isize)]
pub enum InitErrorKind {
UnsupportedTerminal = -1,
FailedToOpenTty = -2,
PipeTrapError = -3,
}
pub enum InitError {
BufferStderrFailed(IoError),
AlreadyOpen,
TermBox(Option<InitErrorKind>),
}
impl fmt::Display for InitError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.description())
}
}
impl Error for InitError {
fn description(&self) -> &str {
match *self {
InitError::BufferStderrFailed(_) => "Could not redirect stderr.",
InitError::AlreadyOpen => "RustBox is already open.",
InitError::TermBox(e) => e.map_or("Unexpected TermBox return code.", |e| match e {
InitErrorKind::UnsupportedTerminal => "Unsupported terminal.",
InitErrorKind::FailedToOpenTty => "Failed to open TTY.",
InitErrorKind::PipeTrapError => "Pipe trap error.",
}),
}
}
fn cause(&self) -> Option<&Error> {
match *self {
InitError::BufferStderrFailed(ref e) => Some(e),
_ => None
}
}
}
mod running {
use std::sync::atomic::{self, AtomicBool};
static RUSTBOX_RUNNING: AtomicBool = atomic::ATOMIC_BOOL_INIT;
pub fn running() -> bool {
RUSTBOX_RUNNING.load(atomic::Ordering::SeqCst)
}
#[allow(missing_copy_implementations)]
pub struct RunningGuard(());
pub fn run() -> Option<RunningGuard> {
if RUSTBOX_RUNNING.swap(true, atomic::Ordering::SeqCst) {
None
} else {
Some(RunningGuard(()))
}
}
impl Drop for RunningGuard {
fn drop(&mut self) {
RUSTBOX_RUNNING.store(false, atomic::Ordering::SeqCst);
}
}
}
#[cfg(unix)]
mod redirect {
use libc;
use std::old_io::{util, IoError, IoErrorKind, PipeStream, standard_error};
use std::old_io::pipe::PipePair;
use std::os::unix::AsRawFd;
use super::running::RunningGuard;
pub struct Redirect {
pair: PipePair,
fd: PipeStream,
}
impl Drop for Redirect {
fn drop(&mut self) {
unsafe {
let old_fd = self.pair.writer.as_raw_fd();
let new_fd = self.fd.as_raw_fd();
if libc::dup2(old_fd, new_fd) != new_fd { return }
}
drop(util::copy(&mut self.pair.reader, &mut self.pair.writer));
}
}
fn redirect(new: PipeStream, _: &RunningGuard) -> Result<Redirect, IoError> {
let mut pair = try!(PipeStream::pair());
unsafe {
let new_fd = new.as_raw_fd();
let dup_fd = match libc::dup(new_fd) {
-1 => return Err(IoError::last_error()),
fd => try!(PipeStream::open(fd)),
};
let old_fd = pair.writer.as_raw_fd();
let res = libc::fcntl(old_fd, libc::F_SETFL, libc::O_NONBLOCK);
if res != 0 {
return Err(if res == -1 {
IoError::last_error()
} else {
standard_error(IoErrorKind::OtherIoError)
});
}
let fd = libc::dup2(old_fd, new_fd);
if fd == new_fd {
pair.writer = dup_fd;
Ok(Redirect {
pair: pair,
fd: new,
})
} else {
Err(if fd == -1 {
IoError::last_error()
} else {
standard_error(IoErrorKind::OtherIoError)
})
}
}
}
pub fn redirect_stderr(rg: &RunningGuard) -> Result<Redirect, IoError> {
Ok(try!(redirect(try!(PipeStream::open(libc::STDERR_FILENO)), rg)))
}
}
#[cfg(not(unix))]
mod redirect {
pub enum Redirect { }
pub fn redirect_stderr(_: &super::RunningGuard) -> Result<Redirect, IoError> {
Err(standard_error(IoErrorKind::OtherIoError))
}
}
#[allow(missing_copy_implementations)]
pub struct RustBox {
_stderr: Option<redirect::Redirect>,
_running: running::RunningGuard,
}
impl !Send for RustBox {}
#[derive(Copy,Debug)]
pub struct InitOptions {
pub buffer_stderr: bool,
pub input_mode: InputMode,
}
impl Default for InitOptions {
fn default() -> Self {
InitOptions {
buffer_stderr: false,
input_mode: InputMode::Current,
}
}
}
impl RustBox {
pub fn init(opts: InitOptions) -> Result<RustBox, InitError> {
let running = match running::run() {
Some(r) => r,
None => return Err(InitError::AlreadyOpen)
};
let stderr = if opts.buffer_stderr {
Some(try!(redirect::redirect_stderr(&running).map_err(|e| InitError::BufferStderrFailed(e))))
} else {
None
};
let rb = unsafe { match termbox::tb_init() {
0 => RustBox {
_stderr: stderr,
_running: running,
},
res => {
return Err(InitError::TermBox(FromPrimitive::from_int(res as isize)))
}
}};
match opts.input_mode {
InputMode::Current => (),
_ => rb.set_input_mode(opts.input_mode),
}
Ok(rb)
}
pub fn width(&self) -> usize {
unsafe { termbox::tb_width() as usize }
}
pub fn height(&self) -> usize {
unsafe { termbox::tb_height() as usize }
}
pub fn clear(&self) {
unsafe { termbox::tb_clear() }
}
pub fn present(&self) {
unsafe { termbox::tb_present() }
}
pub fn set_cursor(&self, x: isize, y: isize) {
unsafe { termbox::tb_set_cursor(x as c_int, y as c_int) }
}
pub unsafe fn change_cell(&self, x: usize, y: usize, ch: u32, fg: u16, bg: u16) {
termbox::tb_change_cell(x as c_int, y as c_int, ch, fg, bg)
}
pub fn print(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, s: &str) {
let fg = Style::from_color(fg) | (sty & style::TB_ATTRIB);
let bg = Style::from_color(bg);
for (i, ch) in s.chars().enumerate() {
unsafe {
self.change_cell(x+i, y, ch as u32, fg.bits(), bg.bits());
}
}
}
pub fn print_char(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, ch: char) {
let fg = Style::from_color(fg) | (sty & style::TB_ATTRIB);
let bg = Style::from_color(bg);
unsafe {
self.change_cell(x, y, ch as u32, fg.bits(), bg.bits());
}
}
pub fn poll_event(&self) -> EventResult<Event> {
let ev = NIL_RAW_EVENT;
let rc = unsafe {
termbox::tb_poll_event(&ev as *const RawEvent)
};
unpack_event(rc, &ev)
}
pub fn peek_event(&self, timeout: Duration) -> EventResult<Event> {
let ev = NIL_RAW_EVENT;
let rc = unsafe {
termbox::tb_peek_event(&ev as *const RawEvent, timeout.num_milliseconds() as c_int)
};
unpack_event(rc, &ev)
}
pub fn set_input_mode(&self, mode: InputMode) {
unsafe {
termbox::tb_select_input_mode(mode as c_int);
}
}
}
impl Drop for RustBox {
fn drop(&mut self) {
unsafe {
termbox::tb_shutdown();
}
}
}