From 10f04a80a878fd164ec01d4832f11c3cd63712eb Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Mon, 15 Nov 2021 20:43:27 +0100 Subject: lib: split out library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not really a satisfactorily clean split as of yet but it’s a start. Error types have been changed from anyhow to io::Error in ``lib.rs`` so as to not force an error handling crate on downstream users. --- Cargo.toml | 4 + src/lib.rs | 528 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/vtcol.rs | 581 +++++------------------------------------------------------ 3 files changed, 580 insertions(+), 533 deletions(-) create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 82eae4e..63c199b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,10 @@ path = "src/vtcol.rs" test = false doc = false +[lib] +# doctests suck +doctest = false + [dependencies] clap = "2.33" libc = "0.2" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..47f650d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,528 @@ +use libc::ioctl; +use std::{convert::TryFrom, + fmt, + io::{self, BufWriter, Error, Write}, + path::{Path, PathBuf}}; + +pub type Fd = libc::c_int; + +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 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 RAW_COLEXPR_SIZE: usize = 6; /* e. g. 0xBADF00 */ +pub type RawPalette<'a> = [&'a str; PALETTE_SIZE]; + +#[derive(Debug)] +pub enum Color +{ + Black(bool), + Red(bool), + Green(bool), + Yellow(bool), + Blue(bool), + Magenta(bool), + Cyan(bool), + White(bool), +} + +impl TryFrom for Color +{ + type Error = io::Error; + + fn try_from(val: u8) -> io::Result + { + match val { + 0x00 => Ok(Color::Black(false)), + 0x01 => Ok(Color::Red(false)), + 0x02 => Ok(Color::Green(false)), + 0x03 => Ok(Color::Yellow(false)), + 0x04 => Ok(Color::Blue(false)), + 0x05 => Ok(Color::Magenta(false)), + 0x06 => Ok(Color::Cyan(false)), + 0x07 => Ok(Color::White(false)), + + 0x08 => Ok(Color::Black(true)), + 0x09 => Ok(Color::Red(true)), + 0x0a => Ok(Color::Green(true)), + 0x0b => Ok(Color::Yellow(true)), + 0x0c => Ok(Color::Blue(true)), + 0x0d => Ok(Color::Magenta(true)), + 0x0e => Ok(Color::Cyan(true)), + 0x0f => Ok(Color::White(true)), + + _ => + Err(io::Error::new( + io::ErrorKind::Other, + format!("invalid color value: {}", val), + )), + } + } +} /* [impl TryFrom for Color] */ + +impl Color +{ + fn format_brightness(b: bool, s: &str) -> String + { + if b { + "bright ".to_string() + s + } else { + s.to_string() + } + } +} /* [impl Color] */ + +impl fmt::Display for Color +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + let c = match *self { + Color::Black(b) => Color::format_brightness(b, "black"), + Color::Red(b) => Color::format_brightness(b, "red"), + Color::Green(b) => Color::format_brightness(b, "green"), + Color::Yellow(b) => Color::format_brightness(b, "yellow"), + Color::Blue(b) => Color::format_brightness(b, "blue"), + Color::Magenta(b) => Color::format_brightness(b, "magenta"), + Color::Cyan(b) => Color::format_brightness(b, "cyan"), + Color::White(b) => Color::format_brightness(b, "white"), + }; + + write!(f, "{}", c) + } +} /* [impl fmt::Display for Color] */ + +#[derive(Debug, Clone)] +pub struct Builtin +{ + names: &'static [&'static str], + palette: &'static RawPalette<'static>, +} + +/** Vanilla Linux colors. */ +const DEFAULT_COLORS: RawPalette = [ + "000000", "aa0000", "00aa00", "aa5500", "0000aa", "aa00aa", "00aaaa", + "aaaaaa", "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", + "55ffff", "ffffff", +]; + +/** The dark (default) version of the Solarized scheme. */ +const SOLARIZED_COLORS_DARK: RawPalette = [ + "002b36", "dc322f", "859900", "b58900", "268bd2", "d33682", "2aa198", + "eee8d5", "002b36", "cb4b16", "586e75", "657b83", "839496", "6c71c4", + "93a1a1", "fdf6e3", +]; + +/** The light version of the Solarized theme. */ +const SOLARIZED_COLORS_LIGHT: RawPalette = [ + "eee8d5", "dc322f", "859900", "b58900", "268bd2", "d33682", "2aa198", + "073642", "fdf6e3", "cb4b16", "93a1a1", "839496", "657b83", "6c71c4", + "586e75", "002b36", +]; + +/** Bright green monochrome terminal. */ +const MONOCHROME_PHOSPHOR: RawPalette = [ + "000000", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", + "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", + "68fc68", "68fc68", +]; + +const DUMMY_COLORS: RawPalette = [ + "000000", "ffffff", "000000", "ffffff", "000000", "ffffff", "000000", + "ffffff", "000000", "ffffff", "000000", "ffffff", "000000", "ffffff", + "000000", "ffffff", +]; + +pub const BUILTIN_SCHEMES: &[Builtin] = &[ + Builtin::solarized(), + Builtin::solarized_light(), + Builtin::default(), + Builtin::phosphor(), +]; + +impl Builtin +{ + pub fn name(&self) -> &'static str { self.names.iter().next().unwrap() } + + pub fn palette(&self) -> &RawPalette { self.palette } + + const fn solarized() -> Self + { + Self { + names: &["solarized", "solarized_dark", "sd"], + palette: &SOLARIZED_COLORS_DARK, + } + } + + const fn solarized_light() -> Self + { + Self { + names: &["solarized_light", "sl"], + palette: &SOLARIZED_COLORS_LIGHT, + } + } + + const fn default() -> Self + { + Self { + names: &["default", "normal", "linux"], + palette: &DEFAULT_COLORS, + } + } + + const fn phosphor() -> Self + { + Self { names: &["phosphor", "matrix"], palette: &MONOCHROME_PHOSPHOR } + } +} + +impl TryFrom<&str> for Builtin +{ + type Error = io::Error; + + fn try_from(name: &str) -> Result + { + for b in BUILTIN_SCHEMES { + if b.names.contains(&name) { + return Ok(b.clone()); + } + } + + Err(io::Error::new( + io::ErrorKind::Other, + format!("no such builtin scheme: {}", name), + )) + } +} + +impl<'a> fmt::Display for Builtin +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + write!(f, "{}", self.name()) + } +} + +#[derive(Debug)] +pub enum Scheme +{ + /** One of the predefined schemes. */ + Builtin(Builtin), + /** Custom ``Palette``. */ + Palette(Palette), + /** Load from file. */ + Custom(Option), +} + +impl<'a> fmt::Display for Scheme +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + match self { + Self::Builtin(b) => write!(f, "{}", b), + Self::Custom(None) => write!(f, ""), + Self::Custom(Some(fname)) => write!(f, "{}", fname.display()), + Self::Palette(pal) => write!(f, "palette: {}", pal), + } + } +} /* [impl fmt::String for Scheme] */ + +impl Scheme +{ + pub fn from_stdin() -> Self { Self::Custom(None) } + + pub fn from_path>(path: P) -> Self + { + Self::Custom(Some(path.as_ref().into())) + } +} /* [impl Scheme] */ + +/** Try to select one of the predefined schemes; if that fails, +interpret the argument as a path. */ +impl From<&str> for Scheme +{ + fn from(name: &str) -> Scheme + { + Builtin::try_from(name) + .map(Self::Builtin) + .unwrap_or_else(|_| Self::from_path(name)) + } +} + +/** Try to match the palette against one of the predefined schemes. */ +impl From for Scheme +{ + fn from(pal: Palette) -> Scheme + { + if pal == Palette::from(&DEFAULT_COLORS) { + return Self::Builtin(Builtin::default()); + } + if pal == Palette::from(&SOLARIZED_COLORS_DARK) { + return Self::Builtin(Builtin::solarized()); + } + if pal == Palette::from(&SOLARIZED_COLORS_LIGHT) { + return Self::Builtin(Builtin::solarized_light()); + } + if pal == Palette::from(&MONOCHROME_PHOSPHOR) { + return Self::Builtin(Builtin::phosphor()); + } + + Self::Palette(pal) + } +} + +fn nibble_of_char(chr: u8) -> u8 +{ + match chr { + b'0'..=b'9' => chr - b'0', + b'a'..=b'f' => chr - b'a' + 10, + b'A'..=b'F' => chr - b'A' + 10, + _ => 0, + } +} + +macro_rules! byte_of_hex { + ($ar:ident, $off:expr) => { + (nibble_of_char($ar[$off])) << 4 | nibble_of_char($ar[$off + 1]) as u8 + }; +} + +fn rgb_of_hex_triplet(def: &str) -> (u8, u8, u8) +{ + let bytes = def.as_bytes(); + let r: u8 = byte_of_hex!(bytes, 0); + let g: u8 = byte_of_hex!(bytes, 2); + let b: u8 = byte_of_hex!(bytes, 4); + (r, g, b) +} + +#[derive(Eq, PartialEq, Clone)] +pub struct Palette([u8; PALETTE_BYTES]); + +impl Palette +{ + /** Construct an all-zero ``Palette``. */ + pub fn new() -> Self { Self([0u8; PALETTE_BYTES]) } + + pub fn dummy() -> Self { Self::from(&DUMMY_COLORS) } + + pub fn from_buffered_reader(reader: &mut dyn std::io::BufRead) -> Self + { + let mut pal_idx: usize = 0; + let mut pal: [u8; PALETTE_BYTES] = [0; PALETTE_BYTES]; + let mut line: String = String::new(); + + while reader.read_line(&mut line).is_ok() { + let len = line.len(); + if len == 0 { + break; + } else if len >= 8 { + if let Some(off) = line.find('#') { + if off != 0 { + /* Palette index specified, number prepended */ + let parse_res: Result = + std::str::FromStr::from_str(&line[0..off]); + if let Ok(new_idx) = parse_res { + if new_idx < PALETTE_SIZE { + pal_idx = new_idx * 3; + } + } + } + let off = off + 1; + if off > len - 6 { + /* no room left for color definition after '#' char */ + panic!("invalid color definition: {}", line); + } + let col = &line[off..(off + RAW_COLEXPR_SIZE)]; + + let (r, g, b) = rgb_of_hex_triplet(col); + pal[pal_idx] = r; + pal[pal_idx + 1] = g; + pal[pal_idx + 2] = b; + pal_idx = (pal_idx + 3) % PALETTE_BYTES; + } + } + line.truncate(0); + } + + Self(pal) + } + + #[allow(rustdoc::invalid_rust_codeblocks)] + /** Print palette in a text format that can be re-read back in. + Basically we print one hex rgb code per line prefixed with color + indices and the canonical names on the right: + + 00 #002B36 black + 01 #DC322F red + 02 #859900 green + 03 #B58900 yellow + 04 #268BD2 blue + … + */ + pub fn dump(&self, out: &mut BufWriter) -> io::Result<()> + { + let mut buf: [u8; 3] = [0u8, 0u8, 0u8]; + for (i, col) in self.0.iter().enumerate() { + let idx: usize = i % 3; + buf[idx] = *col; + if idx == 2 { + let col = Color::try_from((i / 3) as u8)?; + out.write_all( + format!( + "{:02} #{:02.X}{:02.X}{:02.X} {}\n", + i / 3, + buf[0], + buf[1], + buf[2], + col, + ) + .as_bytes(), + )?; + } + } + + Ok(()) + } + + pub fn from_file(fname: &Path) -> Self + { + /* Check if file exists + */ + let file = match std::fs::File::open(&fname) { + Err(e) => { + panic!("failed to open {} as file ({})", fname.display(), e) + }, + Ok(f) => f, + }; + let mut reader = std::io::BufReader::new(file); + + /* Parse scheme file + */ + Self::from_buffered_reader(&mut reader) + } + + /* [Palette::from_file] */ + + pub fn from_stdin() -> Self + { + let mut reader = std::io::BufReader::new(std::io::stdin()); + + /* Parse scheme file + */ + Self::from_buffered_reader(&mut reader) + } /* [Palette::from_stdin] */ +} /* [impl Palette] */ + +impl fmt::Display for Palette +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + let mut i = 0; + while i < PALETTE_BYTES { + let _ = write!(f, "{}", if i == 0 { "(" } else { "\n " }); + let r = self.0[i]; + let g = self.0[i + 1]; + let b = self.0[i + 2]; + let _ = write!( + f, + "((r 0x{:02.X}) (g 0x{:02.X}) (b 0x{:02.x}))", + r, g, b + ); + i += 3; + } + writeln!(f, ")") + } +} /* [impl fmt::Display for Palette] */ + +impl fmt::Debug for Palette +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + let mut i: u8 = 0_u8; + while (i as usize) < PALETTE_BYTES { + let r = self.0[i as usize]; + let g = self.0[i as usize + 1]; + let b = self.0[i as usize + 2]; + let col = Color::try_from((i / 3) as u8) + .map(|c| format!("{}", c)) + .unwrap_or_else(|_| "??".into()); + let _ = writeln!(f, "{} => 0x{:02.X}{:02.X}{:02.X}", col, r, g, b); + i += 3_u8; + } + std::result::Result::Ok(()) + } +} /* [impl fmt::Debug for Palette] */ + +/** Obtain a ``Palette`` from a ``Scheme``. */ +impl From<&Scheme> for Palette +{ + fn from(scm: &Scheme) -> Self + { + match scm { + Scheme::Builtin(Builtin { palette, .. }) => Self::from(*palette), + Scheme::Custom(None) => Self::from_stdin(), + Scheme::Custom(Some(ref fname)) => Self::from_file(fname), + Scheme::Palette(pal) => pal.clone(), + } + } +} + +/** Obtain a ``Palette`` from a ``RawPalette``. */ +impl From<&RawPalette<'_>> for Palette +{ + fn from(colors: &RawPalette<'_>) -> Self + { + let mut idx: usize = 0; + let mut pal: [u8; PALETTE_BYTES] = [0; PALETTE_BYTES]; + + for def in colors.iter() { + let (r, g, b) = rgb_of_hex_triplet(*def); + pal[idx] = r; + pal[idx + 1] = g; + pal[idx + 2] = b; + //println!(">> {} -> {:X} {:X} {:X}", def, r, g, b); + idx += 3; + } + + Self(pal) + } +} + +pub fn ioctl_pio_cmap(fd: Fd, pal: &Palette) -> io::Result<()> +{ + if unsafe { + ioctl( + fd, + PIO_CMAP, + std::mem::transmute::<&Palette, *const libc::c_void>(&pal), + ) + } < 0 + { + Err(Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn ioctl_gio_cmap(fd: Fd) -> io::Result +{ + let mut pal = Palette::new(); + + if unsafe { + ioctl( + fd, + GIO_CMAP, + std::mem::transmute::<&mut Palette, *mut libc::c_void>(&mut pal), + ) + } < 0 + { + Err(Error::last_os_error()) + } else { + Ok(pal) + } +} diff --git a/src/vtcol.rs b/src/vtcol.rs index 5d073e7..707f96d 100644 --- a/src/vtcol.rs +++ b/src/vtcol.rs @@ -1,13 +1,11 @@ -#![allow(rustdoc::invalid_rust_codeblocks)] +pub mod lib; + +use vtcol::{Palette, Scheme}; + use anyhow::{anyhow, Result}; -use std::{convert::TryFrom, - fmt, - io::{self, BufWriter, Error, Write}, - path::{Path, PathBuf}, +use std::{io::{self, BufWriter, Error}, sync::atomic::{AtomicBool, Ordering}}; -type Fd = libc::c_int; - static VERBOSITY: AtomicBool = AtomicBool::new(false); macro_rules! vrb { @@ -16,234 +14,6 @@ macro_rules! vrb { )} } -const PALETTE_SIZE: usize = 16; -const PALETTE_BYTES: usize = PALETTE_SIZE * 3; /* 16 * sizeof(int) */ - -const RAW_COLEXPR_SIZE: usize = 6; /* e. g. 0xBADF00 */ - -type RawPalette<'a> = [&'a str; PALETTE_SIZE]; - -/* XXX: can we get these into ``libc``? */ -const KDGKBTYPE: libc::c_int = 0x4b33; /* kd.h */ -const GIO_CMAP: libc::c_int = 0x00004B70; /* kd.h */ -const PIO_CMAP: libc::c_int = 0x00004B71; /* kd.h */ -const KB_101: libc::c_char = 0x0002; /* kd.h */ -const O_NOCTTY: libc::c_int = 0o0400; /* fcntl.h */ - -#[derive(Debug)] -enum Color -{ - Black(bool), - Red(bool), - Green(bool), - Yellow(bool), - Blue(bool), - Magenta(bool), - Cyan(bool), - White(bool), -} - -impl TryFrom for Color -{ - type Error = anyhow::Error; - - fn try_from(val: u8) -> Result - { - match val { - 0x00 => Ok(Color::Black(false)), - 0x01 => Ok(Color::Red(false)), - 0x02 => Ok(Color::Green(false)), - 0x03 => Ok(Color::Yellow(false)), - 0x04 => Ok(Color::Blue(false)), - 0x05 => Ok(Color::Magenta(false)), - 0x06 => Ok(Color::Cyan(false)), - 0x07 => Ok(Color::White(false)), - - 0x08 => Ok(Color::Black(true)), - 0x09 => Ok(Color::Red(true)), - 0x0a => Ok(Color::Green(true)), - 0x0b => Ok(Color::Yellow(true)), - 0x0c => Ok(Color::Blue(true)), - 0x0d => Ok(Color::Magenta(true)), - 0x0e => Ok(Color::Cyan(true)), - 0x0f => Ok(Color::White(true)), - - _ => Err(anyhow!("invalid color value: {}", val)), - } - } -} /* [impl TryFrom for Color] */ - -impl Color -{ - fn format_brightness(b: bool, s: &str) -> String - { - if b { - "bright ".to_string() + s - } else { - s.to_string() - } - } -} /* [impl Color] */ - -impl fmt::Display for Color -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result - { - let c = match *self { - Color::Black(b) => Color::format_brightness(b, "black"), - Color::Red(b) => Color::format_brightness(b, "red"), - Color::Green(b) => Color::format_brightness(b, "green"), - Color::Yellow(b) => Color::format_brightness(b, "yellow"), - Color::Blue(b) => Color::format_brightness(b, "blue"), - Color::Magenta(b) => Color::format_brightness(b, "magenta"), - Color::Cyan(b) => Color::format_brightness(b, "cyan"), - Color::White(b) => Color::format_brightness(b, "white"), - }; - - write!(f, "{}", c) - } -} /* [impl fmt::Display for Color] */ - -#[derive(Debug, Clone)] -struct Builtin -{ - names: &'static [&'static str], - palette: &'static RawPalette<'static>, -} - -const BUILTIN_SCHEMES: &[Builtin] = &[ - Builtin::solarized(), - Builtin::solarized_light(), - Builtin::default(), - Builtin::phosphor(), -]; - -impl Builtin -{ - fn name(&self) -> &'static str { self.names.iter().next().unwrap() } - - const fn solarized() -> Self - { - Self { - names: &["solarized", "solarized_dark", "sd"], - palette: &SOLARIZED_COLORS_DARK, - } - } - - const fn solarized_light() -> Self - { - Self { - names: &["solarized_light", "sl"], - palette: &SOLARIZED_COLORS_LIGHT, - } - } - - const fn default() -> Self - { - Self { - names: &["default", "normal", "linux"], - palette: &DEFAULT_COLORS, - } - } - - const fn phosphor() -> Self - { - Self { names: &["phosphor", "matrix"], palette: &MONOCHROME_PHOSPHOR } - } -} - -impl TryFrom<&str> for Builtin -{ - type Error = anyhow::Error; - - fn try_from(name: &str) -> Result - { - for b in BUILTIN_SCHEMES { - if b.names.contains(&name) { - return Ok(b.clone()); - } - } - - Err(anyhow!("no such builtin: {}", name)) - } -} - -impl<'a> fmt::Display for Builtin -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result - { - write!(f, "{}", self.name()) - } -} - -#[derive(Debug)] -enum Scheme -{ - /** One of the predefined schemes. */ - Builtin(Builtin), - /** Custom ``Palette``. */ - Palette(Palette), - /** Load from file. */ - Custom(Option), -} - -impl<'a> fmt::Display for Scheme -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result - { - match self { - Self::Builtin(b) => write!(f, "{}", b), - Self::Custom(None) => write!(f, ""), - Self::Custom(Some(fname)) => write!(f, "{}", fname.display()), - Self::Palette(pal) => write!(f, "palette: {}", pal), - } - } -} /* [impl fmt::String for Scheme] */ - -impl Scheme -{ - fn from_stdin() -> Self { Self::Custom(None) } - - fn from_path>(path: P) -> Self - { - Self::Custom(Some(path.as_ref().into())) - } -} /* [impl Scheme] */ - -/** Try to select one of the predefined schemes; if that fails, -interpret the argument as a path. */ -impl From<&str> for Scheme -{ - fn from(name: &str) -> Scheme - { - Builtin::try_from(name) - .map(Self::Builtin) - .unwrap_or_else(|_| Self::from_path(name)) - } -} - -/** Try to match the palette against one of the predefined schemes. */ -impl From for Scheme -{ - fn from(pal: Palette) -> Scheme - { - if pal == Palette::from(&DEFAULT_COLORS) { - return Self::Builtin(Builtin::default()); - } - if pal == Palette::from(&SOLARIZED_COLORS_DARK) { - return Self::Builtin(Builtin::solarized()); - } - if pal == Palette::from(&SOLARIZED_COLORS_LIGHT) { - return Self::Builtin(Builtin::solarized_light()); - } - if pal == Palette::from(&MONOCHROME_PHOSPHOR) { - return Self::Builtin(Builtin::phosphor()); - } - - Self::Palette(pal) - } -} - /* struct Job -- Runtime parameters. */ #[derive(Debug)] @@ -346,14 +116,14 @@ impl<'a> Job ("list", _) => Ok(Self::List), ("set", Some(subm)) => { let scheme = match subm.value_of("scheme") { - Some("-") => Scheme::from_stdin(), + Some("-") => Self::read_scheme_from_stdin(), Some(name) => { vrb!("pick predefined scheme [{}]", name); Scheme::from(name) }, None => match subm.value_of("file") { - None | Some("-") => Scheme::from_stdin(), + None | Some("-") => Self::read_scheme_from_stdin(), Some(fname) => { vrb!( "read custom scheme from file [{}]", @@ -389,24 +159,48 @@ impl<'a> Job fn list_schemes() { - println!("{} color schemes available:", BUILTIN_SCHEMES.len()); - for s in BUILTIN_SCHEMES { + println!("{} color schemes available:", vtcol::BUILTIN_SCHEMES.len()); + for s in vtcol::BUILTIN_SCHEMES { println!(" * {}", s.name()); } } + fn read_scheme_from_stdin() -> Scheme + { + vrb!("Go ahead, type your color scheme …"); + vrb!("vtcol>"); + Scheme::from_stdin() + } + fn dump(scm: Scheme) -> Result<()> { vrb!("Dumping color scheme {}", scm); let mut out = BufWriter::new(io::stdout()); match scm { - Scheme::Builtin(Builtin { palette, .. }) => - Palette::from(palette).dump(&mut out), - Scheme::Custom(None) => Palette::from_stdin().dump(&mut out), + Scheme::Builtin(bltn) => + Palette::from(bltn.palette()).dump(&mut out).map_err(|e| { + anyhow!( + "error loading builtin scheme {}: {}", + bltn.name(), + e + ) + }), + Scheme::Custom(None) => + Palette::from_stdin().dump(&mut out).map_err(|e| { + anyhow!("error loading palette from stdin: {}", e) + }), Scheme::Custom(Some(fname)) => - Palette::from_file(&fname).dump(&mut out), - Scheme::Palette(pal) => pal.dump(&mut out), + Palette::from_file(&fname).dump(&mut out).map_err(|e| { + anyhow!( + "error loading palette from file [{}]: {}", + fname.display(), + e + ) + }), + Scheme::Palette(pal) => + pal.dump(&mut out) + .map_err(|e| anyhow!("error dumping palette: {}", e)), } } @@ -431,7 +225,7 @@ impl<'a> Job let fd = get_console_fd()?; vrb!("console fd: {}", fd); - ioctl_pio_cmap(fd, &pal)?; + vtcol::ioctl_pio_cmap(fd, &pal)?; clear_term(fd)?; vrb!("successfully enabled scheme {:?}", scheme); @@ -444,7 +238,7 @@ impl<'a> Job let fd = get_console_fd()?; vrb!("console fd: {}", fd); - let pal = ioctl_gio_cmap(fd)?; + let pal = vtcol::ioctl_gio_cmap(fd)?; let scm = Scheme::from(pal); vrb!("active scheme:"); @@ -461,7 +255,7 @@ impl<'a> Job let fd = get_console_fd()?; vrb!("console fd: {}", fd); - let pal = ioctl_gio_cmap(fd)?; + let pal = vtcol::ioctl_gio_cmap(fd)?; if pal == Palette::from(&one) { Self::set_scheme(two) @@ -493,263 +287,10 @@ const CONSOLE_PATHS: [&str; 6] = [ "/dev/console", ]; -/** Vanilla Linux colors. */ -const DEFAULT_COLORS: RawPalette = [ - "000000", "aa0000", "00aa00", "aa5500", "0000aa", "aa00aa", "00aaaa", - "aaaaaa", "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", - "55ffff", "ffffff", -]; - -/** The dark (default) version of the Solarized scheme. */ -const SOLARIZED_COLORS_DARK: RawPalette = [ - "002b36", "dc322f", "859900", "b58900", "268bd2", "d33682", "2aa198", - "eee8d5", "002b36", "cb4b16", "586e75", "657b83", "839496", "6c71c4", - "93a1a1", "fdf6e3", -]; - -/** The light version of the Solarized theme. */ -const SOLARIZED_COLORS_LIGHT: RawPalette = [ - "eee8d5", "dc322f", "859900", "b58900", "268bd2", "d33682", "2aa198", - "073642", "fdf6e3", "cb4b16", "93a1a1", "839496", "657b83", "6c71c4", - "586e75", "002b36", -]; - -/** Bright green monochrome terminal. */ -const MONOCHROME_PHOSPHOR: RawPalette = [ - "000000", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", - "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", "68fc68", - "68fc68", "68fc68", -]; - -const DUMMY_COLORS: RawPalette = [ - "000000", "ffffff", "000000", "ffffff", "000000", "ffffff", "000000", - "ffffff", "000000", "ffffff", "000000", "ffffff", "000000", "ffffff", - "000000", "ffffff", -]; - -fn nibble_of_char(chr: u8) -> u8 -{ - match chr { - b'0'..=b'9' => chr - b'0', - b'a'..=b'f' => chr - b'a' + 10, - b'A'..=b'F' => chr - b'A' + 10, - _ => 0, - } -} - -macro_rules! byte_of_hex { - ($ar:ident, $off:expr) => { - (nibble_of_char($ar[$off])) << 4 | nibble_of_char($ar[$off + 1]) as u8 - }; -} - -fn rgb_of_hex_triplet(def: &str) -> (u8, u8, u8) -{ - let bytes = def.as_bytes(); - let r: u8 = byte_of_hex!(bytes, 0); - let g: u8 = byte_of_hex!(bytes, 2); - let b: u8 = byte_of_hex!(bytes, 4); - (r, g, b) -} - -#[derive(Eq, PartialEq, Clone)] -pub struct Palette([u8; PALETTE_BYTES]); - -impl Palette -{ - /** Construct an all-zero ``Palette``. */ - pub fn new() -> Self { Self([0u8; PALETTE_BYTES]) } - - pub fn dummy() -> Self { Self::from(&DUMMY_COLORS) } - - pub fn from_buffered_reader(reader: &mut dyn std::io::BufRead) -> Self - { - let mut pal_idx: usize = 0; - let mut pal: [u8; PALETTE_BYTES] = [0; PALETTE_BYTES]; - let mut line: String = String::new(); - - while reader.read_line(&mut line).is_ok() { - let len = line.len(); - if len == 0 { - break; - } else if len >= 8 { - if let Some(off) = line.find('#') { - if off != 0 { - /* Palette index specified, number prepended */ - let parse_res: Result = - std::str::FromStr::from_str(&line[0..off]); - if let Ok(new_idx) = parse_res { - if new_idx < PALETTE_SIZE { - pal_idx = new_idx * 3; - } - } - } - let off = off + 1; - if off > len - 6 { - /* no room left for color definition after '#' char */ - panic!("invalid color definition: {}", line); - } - let col = &line[off..(off + RAW_COLEXPR_SIZE)]; - - let (r, g, b) = rgb_of_hex_triplet(col); - pal[pal_idx] = r; - pal[pal_idx + 1] = g; - pal[pal_idx + 2] = b; - pal_idx = (pal_idx + 3) % PALETTE_BYTES; - } - } - line.truncate(0); - } - - Self(pal) - } - - /** Print palette in a text format that can be re-read back in. - Basically we print one hex rgb code per line prefixed with color - indices and the canonical names on the right: - - 00 #002B36 black - 01 #DC322F red - 02 #859900 green - 03 #B58900 yellow - 04 #268BD2 blue - … - */ - fn dump(&self, out: &mut BufWriter) -> Result<()> - { - let mut buf: [u8; 3] = [0u8, 0u8, 0u8]; - for (i, col) in self.0.iter().enumerate() { - let idx: usize = i % 3; - buf[idx] = *col; - if idx == 2 { - let col = Color::try_from((i / 3) as u8)?; - out.write_all( - format!( - "{:02} #{:02.X}{:02.X}{:02.X} {}\n", - i / 3, - buf[0], - buf[1], - buf[2], - col, - ) - .as_bytes(), - )?; - } - } - - Ok(()) - } - - pub fn from_file(fname: &Path) -> Self - { - /* Check if file exists - */ - let file = match std::fs::File::open(&fname) { - Err(e) => { - panic!("failed to open {} as file ({})", fname.display(), e) - }, - Ok(f) => f, - }; - let mut reader = std::io::BufReader::new(file); - - /* Parse scheme file - */ - Self::from_buffered_reader(&mut reader) - } - - /* [Palette::from_file] */ - - pub fn from_stdin() -> Self - { - vrb!("Go ahead, type your color scheme …"); - vrb!("vtcol>"); - let mut reader = std::io::BufReader::new(std::io::stdin()); - - /* Parse scheme file - */ - Self::from_buffered_reader(&mut reader) - } /* [Palette::from_stdin] */ -} /* [impl Palette] */ - -impl fmt::Display for Palette -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result - { - let mut i = 0; - while i < PALETTE_BYTES { - let _ = write!(f, "{}", if i == 0 { "(" } else { "\n " }); - let r = self.0[i]; - let g = self.0[i + 1]; - let b = self.0[i + 2]; - let _ = write!( - f, - "((r 0x{:02.X}) (g 0x{:02.X}) (b 0x{:02.x}))", - r, g, b - ); - i += 3; - } - writeln!(f, ")") - } -} /* [impl fmt::Display for Palette] */ - -impl fmt::Debug for Palette -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result - { - let mut i: u8 = 0_u8; - while (i as usize) < PALETTE_BYTES { - let r = self.0[i as usize]; - let g = self.0[i as usize + 1]; - let b = self.0[i as usize + 2]; - let col = Color::try_from((i / 3) as u8) - .map(|c| format!("{}", c)) - .unwrap_or_else(|_| "??".into()); - let _ = writeln!(f, "{} => 0x{:02.X}{:02.X}{:02.X}", col, r, g, b); - i += 3_u8; - } - std::result::Result::Ok(()) - } -} /* [impl fmt::Debug for Palette] */ - -/** Obtain a ``Palette`` from a ``Scheme``. */ -impl From<&Scheme> for Palette -{ - fn from(scm: &Scheme) -> Self - { - match scm { - Scheme::Builtin(Builtin { palette, .. }) => Self::from(*palette), - Scheme::Custom(None) => Self::from_stdin(), - Scheme::Custom(Some(ref fname)) => Self::from_file(fname), - Scheme::Palette(pal) => pal.clone(), - } - } -} - -/** Obtain a ``Palette`` from a ``RawPalette``. */ -impl From<&RawPalette<'_>> for Palette -{ - fn from(colors: &RawPalette<'_>) -> Self - { - let mut idx: usize = 0; - let mut pal: [u8; PALETTE_BYTES] = [0; PALETTE_BYTES]; - - for def in colors.iter() { - let (r, g, b) = rgb_of_hex_triplet(*def); - pal[idx] = r; - pal[idx + 1] = g; - pal[idx + 2] = b; - //println!(">> {} -> {:X} {:X} {:X}", def, r, g, b); - idx += 3; - } - - Self(pal) - } -} - -fn fd_of_path(path: &std::path::Path) -> Option +fn fd_of_path(path: &std::path::Path) -> Option { let p = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); - match unsafe { libc::open(p.as_ptr(), libc::O_RDWR | O_NOCTTY, 0) } { + match unsafe { libc::open(p.as_ptr(), libc::O_RDWR | libc::O_NOCTTY, 0) } { -1 => None, fd => { vrb!(" *> got fd"); @@ -763,7 +304,7 @@ fn fd_of_path(path: &std::path::Path) -> Option let res = unsafe { ioctl( fd, - KDGKBTYPE as libc::c_int, + vtcol::KDGKBTYPE as libc::c_int, std::mem::transmute(&mut tty_type), ) }; @@ -772,7 +313,7 @@ fn fd_of_path(path: &std::path::Path) -> Option return None; } - if tty_type != KB_101 { + if tty_type != vtcol::KB_101 { return None; } @@ -781,7 +322,7 @@ fn fd_of_path(path: &std::path::Path) -> Option } } -fn get_console_fd() -> Result +fn get_console_fd() -> Result { for path in CONSOLE_PATHS.iter() { vrb!("trying path: {:?}", path); @@ -794,7 +335,7 @@ fn get_console_fd() -> Result Err(anyhow!("could not retrieve fd for any of the search paths")) } -fn write_to_term(fd: Fd, buf: &str) -> Result<()> +fn write_to_term(fd: vtcol::Fd, buf: &str) -> Result<()> { let len = buf.len() as libc::size_t; @@ -812,7 +353,7 @@ fn write_to_term(fd: Fd, buf: &str) -> Result<()> } } -fn clear_term(fd: Fd) -> Result<()> +fn clear_term(fd: vtcol::Fd) -> Result<()> { let clear = "\x1b[2J"; let cursor = "\x1b[1;1H"; @@ -822,32 +363,6 @@ fn clear_term(fd: Fd) -> Result<()> Ok(()) } -fn ioctl_pio_cmap(fd: Fd, pal: &Palette) -> Result<()> -{ - if unsafe { ioctl(fd, PIO_CMAP, std::mem::transmute(pal)) } < 0 { - Err(anyhow!( - "ioctl(PIO_CMAP) failed to insert new palette: {}", - Error::last_os_error() - )) - } else { - Ok(()) - } -} - -fn ioctl_gio_cmap(fd: Fd) -> Result -{ - let mut pal = Palette::new(); - - if unsafe { ioctl(fd, GIO_CMAP, std::mem::transmute(&mut pal)) } < 0 { - Err(anyhow!( - "ioctl(GIO_CMAP) failed to get current palette: {}", - Error::last_os_error() - )) - } else { - Ok(pal) - } -} - fn main() -> Result<()> { let job = Job::from_argv()?; -- cgit v1.2.3