diff options
-rw-r--r-- | src/lib.rs | 157 | ||||
-rw-r--r-- | src/vtcol.rs | 109 |
2 files changed, 162 insertions, 104 deletions
@@ -5,11 +5,26 @@ use std::{convert::TryFrom, os::unix::io::{AsRawFd, RawFd}, path::{Path, PathBuf}}; +trait IsMinusOne +{ + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) +} + +impl_is_minus_one! { i8 i16 i32 i64 isize } + /** Convenience syscall wrapper based on its namesake found in the sadly private ``std::sys::unix`` library. */ -fn cvt(t: libc::c_int) -> io::Result<libc::c_int> +fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> { - if t == -1 { + if t.is_minus_one() { Err(Error::last_os_error()) } else { Ok(t) @@ -18,7 +33,7 @@ fn cvt(t: libc::c_int) -> io::Result<libc::c_int> /** Convenience syscall wrapper based on its namesake found in the sadly private ``std::sys::unix`` library. */ -fn cvt_r(f: &mut dyn FnMut() -> libc::c_int) -> io::Result<libc::c_int> +fn cvt_r<T: IsMinusOne>(f: &mut dyn FnMut() -> T) -> io::Result<T> { loop { match cvt((*f)()) { @@ -53,10 +68,10 @@ const PALETTE_SIZE: usize = 16; const PALETTE_BYTES: usize = PALETTE_SIZE * 3; /* 16 * sizeof(int) */ /* XXX: can we get these into ``libc``? */ -pub const KDGKBTYPE: libc::c_ulong = 0x4b33; /* kd.h */ +const KDGKBTYPE: libc::c_ulong = 0x4b33; /* kd.h */ const GIO_CMAP: libc::c_ulong = 0x00004B70; /* kd.h */ const PIO_CMAP: libc::c_ulong = 0x00004B71; /* kd.h */ -pub const KB_101: libc::c_char = 0x0002; /* kd.h */ +const KB_101: libc::c_char = 0x0002; /* kd.h */ const RAW_COLEXPR_SIZE: usize = 6; /* e. g. 0xBADF00 */ pub type RawPalette<'a> = [&'a str; PALETTE_SIZE]; @@ -570,3 +585,135 @@ pub fn ioctl_gio_cmap<F: AsRawFd>(fd: &F) -> io::Result<Palette> .map(|_| ())?; Ok(pal) } + +const CONSOLE_PATHS: [&str; 6] = [ + "/proc/self/fd/0", + "/dev/tty", + "/dev/tty0", + "/dev/vc/0", + "/dev/systty", + "/dev/console", +]; + +const CONTROL_CLEAR: &[u8] = b"\x1b[2J"; +const CONTROL_CURSOR: &[u8] = b"\x1b[1;1H"; + +pub struct Console(libc::c_int); + +impl Console +{ + fn from_fd(fd: libc::c_int) -> io::Result<Self> + { + if unsafe { libc::isatty(fd) } == 0 { + return Err(Error::last_os_error()); + } + + let mut tty_type: libc::c_char = 0; + + let _ = cvt_r(&mut || unsafe { + ioctl(fd, KDGKBTYPE, &mut tty_type as *mut _) + })?; + + /* Sanity check. */ + if tty_type != KB_101 { + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "console {} exhibiting weird behavior; bailing out", + fd + ), + )); + } + + Ok(Self(fd)) + } + + fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> + { + let p = + std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap(); + let fd = cvt_r(&mut || unsafe { + libc::open(p.as_ptr(), libc::O_RDWR | libc::O_NOCTTY, 0) + })?; + Self::from_fd(fd) + } + + /** Try and obtain a handle referring to the console we’re running in. */ + pub fn current() -> io::Result<Self> + { + for path in CONSOLE_PATHS.iter() { + let path = std::path::Path::new(path); + if let Ok(con) = Self::from_path(path) { + return Ok(con); + } + } + + Err(io::Error::new( + io::ErrorKind::Other, + format!("could not retrieve fd for any of the search paths"), + )) + } + + fn write(&self, buf: &[u8]) -> io::Result<()> + { + let len = buf.len() as libc::size_t; + + if cvt_r(&mut || unsafe { + libc::write(self.0, buf.as_ptr() as *const libc::c_void, len) + })? != len as isize + { + Err(Error::last_os_error()) + } else { + Ok(()) + } + } + + pub fn clear(&self) -> io::Result<()> + { + self.write(CONTROL_CLEAR)?; + self.write(CONTROL_CURSOR)?; + + Ok(()) + } + + /** Read the current palette. */ + #[inline] + pub fn current_palette(&self) -> io::Result<Palette> + { + ioctl_gio_cmap(&self.as_raw_fd()) + } + + /** Tell the kernel to use the specified palette on the console. */ + #[inline] + pub fn apply_palette(&self, pal: &Palette) -> io::Result<()> + { + ioctl_pio_cmap(&self.as_raw_fd(), pal) + } + + /** Read the current palette and determine the scheme. */ + #[inline] + pub fn current_scheme(&self) -> io::Result<Scheme> + { + Ok(Scheme::from(self.current_palette()?)) + } + + /** Convert ``scm`` into a ``Palette`` and tell the kernel to use it. */ + #[inline] + pub fn apply_scheme(&self, scm: &Scheme) -> io::Result<()> + { + self.apply_palette(&Palette::from(scm)) + } +} + +impl AsRawFd for Console +{ + fn as_raw_fd(&self) -> RawFd { self.0 } +} + +impl fmt::Display for Console +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + write!(f, "Console(fd={})", self.0) + } +} diff --git a/src/vtcol.rs b/src/vtcol.rs index 0cc713c..d3399fc 100644 --- a/src/vtcol.rs +++ b/src/vtcol.rs @@ -1,10 +1,9 @@ pub mod lib; -use vtcol::{Fd, Palette, Scheme}; +use vtcol::{Console, Palette, Scheme}; use anyhow::{anyhow, Result}; -use std::{io::{self, BufWriter, Error}, - os::unix::io::AsRawFd, +use std::{io::{self, BufWriter}, sync::atomic::{AtomicBool, Ordering}}; static VERBOSITY: AtomicBool = AtomicBool::new(false); @@ -220,15 +219,12 @@ impl<'a> Job fn set_scheme(scheme: Scheme) -> Result<()> { - let pal = Palette::from(&scheme); - vrb!("Using palette:"); - vrb!("{}", pal); - let fd = get_console_fd()?; - vrb!("console fd: {}", fd); + let con = Console::current()?; + vrb!("console fd: {}", con); - vtcol::ioctl_pio_cmap(&fd, &pal)?; + con.apply_scheme(&scheme)?; + con.clear()?; - clear_term(&fd)?; vrb!("successfully enabled scheme {:?}", scheme); /* It’s fine to leak the fd, the kernel will clean up anyways. */ Ok(()) @@ -236,11 +232,10 @@ impl<'a> Job fn get_scheme() -> Result<()> { - let fd = get_console_fd()?; + let fd = Console::current()?; vrb!("console fd: {}", fd); - let pal = vtcol::ioctl_gio_cmap(&fd)?; - let scm = Scheme::from(pal); + let scm = fd.current_scheme()?; vrb!("active scheme:"); println!("{}", scm); @@ -253,12 +248,10 @@ impl<'a> Job */ fn toggle_scheme(one: Scheme, two: Scheme) -> Result<()> { - let fd = get_console_fd()?; + let fd = Console::current()?; vrb!("console fd: {}", fd); - let pal = vtcol::ioctl_gio_cmap(&fd)?; - - if pal == Palette::from(&one) { + if fd.current_palette()? == Palette::from(&one) { Self::set_scheme(two) } else { Self::set_scheme(one) @@ -266,88 +259,6 @@ impl<'a> Job } } /* [impl Job] */ -const CONSOLE_PATHS: [&str; 6] = [ - "/proc/self/fd/0", - "/dev/tty", - "/dev/tty0", - "/dev/vc/0", - "/dev/systty", - "/dev/console", -]; - -fn fd_of_path(path: &std::path::Path) -> Option<vtcol::Fd> -{ - let p = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); - match unsafe { libc::open(p.as_ptr(), libc::O_RDWR | libc::O_NOCTTY, 0) } { - -1 => None, - fd => { - vrb!(" *> got fd"); - if unsafe { libc::isatty(fd) } == 0 { - vrb!(" *> not a tty"); - return None; - } - - let mut tty_type: libc::c_char = 0; - - let res = unsafe { - libc::ioctl(fd, vtcol::KDGKBTYPE, &mut tty_type as *mut _) - }; - if res < 0 { - vrb!(" *> ioctl failed"); - return None; - } - - if tty_type != vtcol::KB_101 { - return None; - } - - Some(Fd::from(fd)) - }, - } -} - -fn get_console_fd() -> Result<Fd> -{ - for path in CONSOLE_PATHS.iter() { - vrb!("trying path: {:?}", path); - let path = std::path::Path::new(path); - if let Some(fd) = fd_of_path(path) { - vrb!(" * Success!"); - return Ok(fd); - } - } - Err(anyhow!("could not retrieve fd for any of the search paths")) -} - -fn write_to_term(fd: &Fd, buf: &str) -> Result<()> -{ - let len = buf.len() as libc::size_t; - - if unsafe { - libc::write(fd.as_raw_fd(), buf.as_ptr() as *const libc::c_void, len) - } != len as isize - { - Err(anyhow!( - "failed to write {} B to fd {}: {}", - len, - fd, - Error::last_os_error() - )) - } else { - Ok(()) - } -} - -fn clear_term(fd: &Fd) -> Result<()> -{ - let clear = "\x1b[2J"; - let cursor = "\x1b[1;1H"; - write_to_term(fd, clear)?; - write_to_term(fd, cursor)?; - - Ok(()) -} - fn main() -> Result<()> { let job = Job::from_argv()?; |