use std::ops::{Index, IndexMut, Deref, DerefMut};
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::fs::OpenOptions;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::collections::VecDeque;
use std::thread;
use std::time::Duration;
use std::ptr;
use std::mem;
use libc;
use gag::BufferRedirect;
use core::cellbuffer::{CellAccessor, CellBuffer, Cell, Color, Attr};
use core::input::Event;
use core::position::{Cursor, Pos, Size, HasSize};
use core::driver::{DevFn, Driver};
use core::termctl::TermCtl;
static SIGWINCH_STATUS: AtomicBool = ATOMIC_BOOL_INIT;
static RUSTTY_STATUS: AtomicBool = ATOMIC_BOOL_INIT;
type OutBuffer = Vec<u8>;
type EventBuffer = VecDeque<Event>;
pub struct Terminal {
termctl: TermCtl, tty: File, cols: usize, rows: usize, driver: Driver, backbuffer: CellBuffer, frontbuffer: CellBuffer, outbuffer: OutBuffer, eventbuffer: EventBuffer, laststyle: Cell, cursor: Cursor, stderr_handle: BufferRedirect,
}
impl Terminal {
pub fn new() -> Result<Terminal, Error> {
Terminal::with_cell(Cell::default())
}
pub fn with_char(ch: char) -> Result<Terminal, Error> {
Terminal::with_cell(Cell::with_char(ch))
}
pub fn with_cell(cell: Cell) -> Result<Terminal, Error> {
if RUSTTY_STATUS.compare_and_swap(false, true, Ordering::SeqCst) {
return Err(Error::new(ErrorKind::AlreadyExists, "terminal already initialized"));
}
let driver = try!(Driver::new());
let tty = try!(OpenOptions::new()
.write(true)
.read(true)
.open("/dev/tty"));
let rawtty = tty.as_raw_fd();
let handler = sigwinch_handler as libc::size_t;
let mut sa_winch: libc::sigaction = unsafe { mem::zeroed() };
sa_winch.sa_sigaction = handler;
let res = unsafe { libc::sigaction(libc::SIGWINCH, &sa_winch, ptr::null_mut()) };
if res != 0 {
return Err(Error::last_os_error());
}
let termctl = try!(TermCtl::new(rawtty));
try!(termctl.set());
let mut terminal = Terminal {
termctl: termctl,
tty: tty,
cols: 0,
rows: 0,
driver: driver,
backbuffer: CellBuffer::new(0, 0, cell),
frontbuffer: CellBuffer::new(0, 0, cell),
outbuffer: OutBuffer::with_capacity(32 * 1024),
eventbuffer: EventBuffer::with_capacity(128),
laststyle: cell,
cursor: Cursor::new(),
stderr_handle: BufferRedirect::stderr().unwrap(),
};
try!(terminal.outbuffer.write_all(&terminal.driver.get(DevFn::EnterCa)));
try!(terminal.outbuffer.write_all(&terminal.driver.get(DevFn::HideCursor)));
try!(terminal.resize_with_cell(cell));
Ok(terminal)
}
pub fn swap_buffers(&mut self) -> Result<(), Error> {
if SIGWINCH_STATUS.compare_and_swap(true, false, Ordering::SeqCst) {
try!(self.resize());
}
self.cursor.invalidate_last_pos();
for y in 0..self.rows() {
for x in 0..self.cols() {
if self.frontbuffer[(x, y)] == self.backbuffer[(x, y)] {
continue; } else {
let cell = self.backbuffer[(x, y)];
try!(self.send_style(cell));
try!(self.send_char(Some((x, y)), cell.ch()));
self.frontbuffer[(x, y)] = cell;
}
}
}
try!(self.send_cursor());
try!(self.flush());
Ok(())
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn clear(&mut self) -> Result<(), Error> {
if SIGWINCH_STATUS.compare_and_swap(true, false, Ordering::SeqCst) {
try!(self.resize());
}
self.backbuffer.clear(Cell::default());
Ok(())
}
pub fn clear_with_char(&mut self, ch: char) -> Result<(), Error> {
if SIGWINCH_STATUS.compare_and_swap(true, false, Ordering::SeqCst) {
try!(self.resize());
}
self.backbuffer.clear(Cell::with_char(ch));
Ok(())
}
pub fn clear_with_cell(&mut self, cell: Cell) -> Result<(), Error> {
if SIGWINCH_STATUS.compare_and_swap(true, false, Ordering::SeqCst) {
try!(self.resize());
}
self.backbuffer.clear(cell);
Ok(())
}
pub fn check_resize(&self) -> bool {
SIGWINCH_STATUS.load(Ordering::SeqCst)
}
pub fn try_resize(&mut self) -> Result<Option<(usize, usize)>, Error> {
self.try_resize_with_cell(Cell::default())
}
pub fn try_resize_with_char(&mut self, ch: char) -> Result<Option<(usize, usize)>, Error> {
self.try_resize_with_cell(Cell::with_char(ch))
}
pub fn try_resize_with_cell(&mut self, cell: Cell) -> Result<Option<(usize, usize)>, Error> {
if SIGWINCH_STATUS.compare_and_swap(true, false, Ordering::SeqCst) {
try!(self.resize_with_cell(cell));
return Ok(Some((self.cols, self.rows)));
}
Ok(None)
}
pub fn set_cursor(&mut self, x: usize, y: usize) -> Result<(), Error> {
if self.cursor.pos().is_none() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::ShowCursor)));
}
self.cursor.set_pos(Some((x, y)));
try!(self.send_cursor());
Ok(())
}
pub fn hide_cursor(&mut self) -> Result<(), Error> {
if self.cursor.pos().is_some() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::HideCursor)));
}
Ok(())
}
pub fn get_event(&mut self, timeout: Duration) -> Result<Option<Event>, Error> {
if self.eventbuffer.is_empty() {
let nevts = try!(self.read_events(timeout));
if nevts == 0 {
Ok(None)
} else {
Ok(self.eventbuffer.pop_front())
}
} else {
Ok(self.eventbuffer.pop_front())
}
}
fn send_cursor(&mut self) -> Result<(), Error> {
if let Some((cx, cy)) = self.cursor.pos() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::SetCursor(cx, cy))));
}
Ok(())
}
fn send_char(&mut self, coord: Option<Pos>, ch: char) -> Result<(), Error> {
self.cursor.set_pos(coord);
if !self.cursor.is_seq() {
try!(self.send_cursor());
}
try!(write!(self.outbuffer, "{}", ch));
Ok(())
}
fn send_clear(&mut self) -> Result<(), Error> {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::Clear)));
try!(self.send_cursor());
try!(self.flush());
self.cursor.invalidate_last_pos();
Ok(())
}
fn send_style(&mut self, cell: Cell) -> Result<(), Error> {
if cell.fg() != self.laststyle.fg() || cell.bg() != self.laststyle.bg() ||
cell.attrs() != self.laststyle.attrs() {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::Reset)));
match cell.attrs() {
Attr::Bold => try!(self.outbuffer.write_all(&self.driver.get(DevFn::Bold))),
Attr::Underline => {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::Underline)))
}
Attr::Reverse => try!(self.outbuffer.write_all(&self.driver.get(DevFn::Reverse))),
_ => {}
}
try!(self.write_sgr(cell.fg(), cell.bg()));
self.laststyle = cell;
}
Ok(())
}
fn write_sgr(&mut self, fgcol: Color, bgcol: Color) -> Result<(), Error> {
match fgcol {
Color::Default => {}
fgc @ _ => {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::SetFg(fgc.as_byte()))));
}
}
match bgcol {
Color::Default => {}
bgc @ _ => {
try!(self.outbuffer.write_all(&self.driver.get(DevFn::SetBg(bgc.as_byte()))));
}
}
Ok(())
}
fn resize(&mut self) -> Result<(), Error> {
self.resize_with_cell(Cell::default())
}
fn resize_with_cell(&mut self, blank: Cell) -> Result<(), Error> {
let (cols, rows) = try!(self.termctl.window_size());
self.cols = cols;
self.rows = rows;
self.backbuffer.resize(self.cols, self.rows, blank);
self.frontbuffer.resize(self.cols, self.rows, blank);
self.frontbuffer.clear(blank);
try!(self.send_style(blank));
try!(self.send_clear());
Ok(())
}
fn read_events(&mut self, timeout: Duration) -> Result<usize, Error> {
let nevts;
let mut timeout = libc::timeval {
tv_sec: timeout.as_secs() as libc::time_t,
tv_usec: (timeout.subsec_nanos() as libc::suseconds_t) / 1000,
};
let rawfd = self.tty.as_raw_fd();
let nfds = rawfd + 1;
let mut rfds: libc::fd_set = unsafe { mem::zeroed() };
unsafe {
libc::FD_SET(rawfd, &mut rfds);
}
loop {
let res = unsafe {
libc::select(nfds,
&mut rfds,
ptr::null_mut(),
ptr::null_mut(),
&mut timeout)
};
if res == -1 {
let err = Error::last_os_error();
if err.kind() == ErrorKind::Interrupted {
continue;
} else {
return Err(err);
}
} else {
nevts = res;
break;
}
}
if nevts == 0 {
Ok(0)
} else {
let mut buf = String::new();
try!(self.tty.read_to_string(&mut buf));
let mut n = 0;
for ch in buf.chars() {
self.eventbuffer.push_back(Event::Key(ch));
n += 1;
}
Ok(n)
}
}
fn flush(&mut self) -> Result<(), Error> {
try!(self.tty.write_all(&self.outbuffer));
self.outbuffer.clear();
if thread::panicking() {
let mut error = String::new();
self.stderr_handle.read_to_string(&mut error).unwrap();
print!("{}", error);
}
Ok(())
}
}
impl HasSize for Terminal {
fn size(&self) -> Size {
(self.cols, self.rows)
}
}
impl CellAccessor for Terminal {
fn cellvec(&self) -> &Vec<Cell> {
self.backbuffer.cellvec()
}
fn cellvec_mut(&mut self) -> &mut Vec<Cell> {
self.backbuffer.cellvec_mut()
}
}
impl Deref for Terminal {
type Target = [Cell];
fn deref<'a>(&'a self) -> &'a [Cell] {
&self.backbuffer
}
}
impl DerefMut for Terminal {
fn deref_mut<'a>(&'a mut self) -> &'a mut [Cell] {
&mut self.backbuffer
}
}
impl Index<Pos> for Terminal {
type Output = Cell;
fn index<'a>(&'a self, index: Pos) -> &'a Cell {
&self.backbuffer[index]
}
}
impl IndexMut<Pos> for Terminal {
fn index_mut<'a>(&'a mut self, index: Pos) -> &'a mut Cell {
&mut self.backbuffer[index]
}
}
impl Drop for Terminal {
fn drop(&mut self) {
self.outbuffer.write_all(&self.driver.get(DevFn::ShowCursor)).unwrap();
self.outbuffer.write_all(&self.driver.get(DevFn::Reset)).unwrap();
self.outbuffer.write_all(&self.driver.get(DevFn::Clear)).unwrap();
self.outbuffer.write_all(&self.driver.get(DevFn::ExitCa)).unwrap();
self.flush().unwrap();
self.termctl.reset().unwrap();
SIGWINCH_STATUS.store(false, Ordering::SeqCst);
RUSTTY_STATUS.store(false, Ordering::SeqCst);
}
}
extern "C" fn sigwinch_handler(_: i32) {
SIGWINCH_STATUS.store(true, Ordering::SeqCst);
}