use std::io::{self, Write, Read, IoSlice, IoSliceMut};
use std::fmt;
use std::fs::{File, OpenOptions};
use std::os::unix::io::{RawFd, AsRawFd};
use nix::libc::*;
use nix::sys::termios::{
Termios, InputFlags, LocalFlags, FlushArg, SetArg, SpecialCharacterIndices,
tcgetattr, tcsetattr, tcflush, cfmakeraw
};
use crate::ffi;
use crate::console::Console;
pub trait AsVtNumber {
fn as_vt_number(&self) -> VtNumber;
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct VtNumber(i32);
impl VtNumber {
pub fn new(number: i32) -> VtNumber {
if number < 0 {
panic!("Invalid virtual terminal number.");
}
VtNumber(number)
}
pub(crate) fn as_native(self) -> c_int {
self.0
}
}
impl From<i32> for VtNumber {
fn from(number: i32) -> VtNumber {
VtNumber::new(number)
}
}
impl AsVtNumber for VtNumber {
fn as_vt_number(&self) -> VtNumber {
*self
}
}
impl fmt::Display for VtNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
bitflags! {
pub struct VtSignals: u8 {
const SIGINT = 1;
const SIGQUIT = 1 << 1;
const SIGTSTP = 1 << 2;
}
}
pub enum VtFlushType {
Incoming,
Outgoing,
Both
}
pub struct Vt<'a> {
console: &'a Console,
number: VtNumber,
file: File,
termios: Termios
}
impl<'a> Vt<'a> {
pub(crate) fn with_number(console: &'a Console, number: VtNumber) -> io::Result<Vt<'a>> {
let path = format!("/dev/tty{}", number);
let file = OpenOptions::new().read(true).write(true).open(path)?;
Vt::with_number_and_file(console, number, file)
}
pub(crate) fn with_number_and_file(console: &'a Console, number: VtNumber, file: File) -> io::Result<Vt<'a>> {
let mut termios = tcgetattr(file.as_raw_fd())
.map_err(|e| io::Error::from_raw_os_error(e.as_errno().unwrap_or(nix::errno::Errno::UnknownErrno) as i32))?;
termios.input_flags |= InputFlags::IGNBRK;
termios.local_flags &= !(LocalFlags::ECHO | LocalFlags::ISIG);
termios.control_chars[SpecialCharacterIndices::VEOF as usize] = 0;
let vt = Vt {
console,
number,
file,
termios
};
vt.update_termios()?;
Ok(vt)
}
fn update_termios(&self) -> io::Result<()> {
tcsetattr(
self.file.as_raw_fd(),
SetArg::TCSANOW,
&self.termios
)
.map_err(|e| io::Error::from_raw_os_error(e.as_errno().unwrap_or(nix::errno::Errno::UnknownErrno) as i32))
}
pub fn number(&self) -> VtNumber {
self.number
}
pub fn switch(&self) -> io::Result<&Self> {
self.console.switch_to(self.number)?;
Ok(self)
}
pub fn clear(&mut self) -> io::Result<&mut Self> {
write!(self, "\x1b[H\x1b[J")?;
Ok(self)
}
pub fn set_blank_timer(&mut self, timer: u32) -> io::Result<&mut Self> {
write!(self, "\x1b[9;{}]", timer)?;
Ok(self)
}
pub fn blank(&mut self, blank: bool) -> io::Result<&mut Self> {
let needs_timer_reset = if blank && self.console.blank_timer()? == 0 {
self.set_blank_timer(1)?;
true
} else {
false
};
let mut arg = if blank { ffi::TIOCL_BLANKSCREEN } else { ffi::TIOCL_UNBLANKSCREEN };
ffi::tioclinux(self.file.as_raw_fd(), &mut arg)?;
if needs_timer_reset {
self.set_blank_timer(0)?;
}
Ok(self)
}
pub fn set_echo(&mut self, echo: bool) -> io::Result<&mut Self> {
if echo {
self.termios.local_flags |= LocalFlags::ECHO;
} else {
self.termios.local_flags &= !LocalFlags::ECHO;
}
self.update_termios()?;
Ok(self)
}
pub fn is_echo_enabled(&self) -> bool {
self.termios.local_flags.contains(LocalFlags::ECHO)
}
pub fn signals(&mut self, signals: VtSignals) -> io::Result<&mut Self> {
self.termios.local_flags |= LocalFlags::ISIG;
if !signals.contains(VtSignals::SIGINT) {
self.termios.control_chars[SpecialCharacterIndices::VINTR as usize] = 0;
} else {
self.termios.control_chars[SpecialCharacterIndices::VINTR as usize] = 3;
}
if !signals.contains(VtSignals::SIGQUIT) {
self.termios.control_chars[SpecialCharacterIndices::VQUIT as usize] = 0;
} else {
self.termios.control_chars[SpecialCharacterIndices::VQUIT as usize] = 34;
}
if !signals.contains(VtSignals::SIGTSTP) {
self.termios.control_chars[SpecialCharacterIndices::VSUSP as usize] = 0;
} else {
self.termios.control_chars[SpecialCharacterIndices::VSUSP as usize] = 32;
}
self.update_termios()?;
Ok(self)
}
pub fn flush_buffers(&mut self, t: VtFlushType) -> io::Result<&mut Self> {
let action = match t {
VtFlushType::Incoming => FlushArg::TCIFLUSH,
VtFlushType::Outgoing => FlushArg::TCOFLUSH,
VtFlushType::Both => FlushArg::TCIOFLUSH
};
tcflush(self.file.as_raw_fd(), action)
.map_err(|e| io::Error::from_raw_os_error(e.as_errno().unwrap_or(nix::errno::Errno::UnknownErrno) as i32))?;
Ok(self)
}
pub fn raw(&mut self) -> io::Result<&mut Self> {
cfmakeraw(&mut self.termios);
self.update_termios()?;
Ok(self)
}
}
impl<'a> Drop for Vt<'a> {
fn drop(&mut self) {
let _ = self.console.disallocate_vt(self.number);
}
}
impl<'a> AsVtNumber for Vt<'a> {
fn as_vt_number(&self) -> VtNumber {
self.number
}
}
impl<'a> AsRawFd for Vt<'a> {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl<'a> Read for Vt<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> io::Result<usize> {
self.file.read_vectored(bufs)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
self.file.read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
self.file.read_to_string(buf)
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
self.file.read_exact(buf)
}
}
impl<'a> Write for Vt<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
self.file.write_vectored(bufs)
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.file.write_all(buf)
}
fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
self.file.write_fmt(fmt)
}
}