diff options
Diffstat (limited to 'src/vtcol.rs')
-rw-r--r-- | src/vtcol.rs | 1204 |
1 files changed, 939 insertions, 265 deletions
diff --git a/src/vtcol.rs b/src/vtcol.rs index 9a78d2f..baf5ae3 100644 --- a/src/vtcol.rs +++ b/src/vtcol.rs @@ -1,6 +1,10 @@ +#![allow(clippy::option_map_unit_fn)] + pub mod lib; -use vtcol::{Console, Fade, Palette, Scheme}; +#[cfg(feature = "gui")] pub mod edit; + +use vtcol::{Console, Fade, KbLedFlags, KbLedState, Palette, Scheme}; use anyhow::{anyhow, Result}; use std::{io::{self, BufWriter}, @@ -18,15 +22,240 @@ macro_rules! vrb { )} } -/* struct Job -- Runtime parameters. - */ +/** Helper for choosing the console. Defaults to the current one if +none is explicitly supplied. */ +#[inline] +fn open_console(path: Option<&str>) -> io::Result<Console> +{ + path.map(Console::from_path).unwrap_or_else(Console::current) +} + +/** Trait for subcommands to implement. */ +trait Run +{ + fn run(self, console: Option<String>) -> Result<()>; +} + #[derive(Debug)] -enum Job +enum LedJob +{ + /** Get keyboard LED state. */ + Get(bool), + /** Revert to normal. */ + Revert, + /** Set keyboard LED state of all leds at once. */ + Set(KbLedState), + /** Set keyboard LED state of individual LEDs. */ + SetIndividual(Option<bool>, Option<bool>, Option<bool>), +} + +impl Run for LedJob +{ + fn run(self, console: Option<String>) -> Result<()> + { + match self { + Self::Get(raw) => Self::get(console, raw), + Self::Revert => Self::revert(console), + Self::Set(st) => Self::set(console, st), + Self::SetIndividual(c, n, s) => + Self::set_individual(console, c, n, s), + } + } +} /* [impl Run for LedJob] */ + +impl LedJob +{ + /** Get the keyboard LED state. */ + fn get(con: Option<String>, raw: bool) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + let leds = KbLedState::get(&fd)?; + + if raw { + println!("{}", u8::from(leds)); + } else { + println!("{}", leds); + } + + Ok(()) + } + + /** Set the keyboard LED state. */ + fn set(con: Option<String>, st: KbLedState) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + st.set(&fd)?; + vrb!("applied"); + + Ok(()) + } + + /** Set the state of the given LEDs using the current state as base. */ + fn set_individual( + con: Option<String>, + cap: Option<bool>, + num: Option<bool>, + scr: Option<bool>, + ) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + let mut st = KbLedState::get(&fd)?; + + cap.map(|b| st.set_cap(b)); + num.map(|b| st.set_num(b)); + scr.map(|b| st.set_scr(b)); + + st.set(&fd)?; + vrb!("applied"); + + Ok(()) + } + + /** Revert the keyboard LED state. */ + fn revert(con: Option<String>) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + KbLedState::revert(&fd)?; + vrb!("reverted"); + + Ok(()) + } +} /* [impl LedJob] */ + +#[derive(Debug)] +enum FlagTarget +{ + Current, + Default, + Both, +} + +#[derive(Debug)] +enum FlagJob +{ + /** Get keyboard flags. */ + Get(bool), + /** Set all keyboard flags at once. */ + Set(KbLedFlags), + /** Set keyboard LED state of individual LEDs. */ + SetIndividual(FlagTarget, Option<bool>, Option<bool>, Option<bool>), +} + +impl FlagJob +{ + /** Get the keyboard flags (modifier locks). */ + fn get(con: Option<String>, raw: bool) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + let leds = KbLedFlags::get(&fd)?; + + if raw { + println!("{}", u8::from(leds)); + } else { + println!("{}", leds); + } + + Ok(()) + } + + /** Set the keyboard flags. */ + fn set(con: Option<String>, st: KbLedFlags) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + st.set(&fd)?; + vrb!("applied"); + + Ok(()) + } + + /** Set / unset keyboard flags using the current as base. */ + fn set_individual( + con: Option<String>, + target: FlagTarget, + cap: Option<bool>, + num: Option<bool>, + scr: Option<bool>, + ) -> Result<()> + { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + + let mut st = KbLedFlags::get(&fd)?; + + if matches!(target, FlagTarget::Default | FlagTarget::Both) { + cap.map(|b| st.set_default_cap(b)); + num.map(|b| st.set_default_num(b)); + scr.map(|b| st.set_default_scr(b)); + } + + if matches!(target, FlagTarget::Current | FlagTarget::Both) { + cap.map(|b| st.set_cap(b)); + num.map(|b| st.set_num(b)); + scr.map(|b| st.set_scr(b)); + } + + st.set(&fd)?; + vrb!("applied"); + + Ok(()) + } +} /*[impl FlagJob] */ + +impl Run for FlagJob +{ + fn run(self, console: Option<String>) -> Result<()> + { + match self { + Self::Get(raw) => Self::get(console, raw), + Self::Set(flags) => Self::set(console, flags), + Self::SetIndividual(dflt, c, n, s) => + Self::set_individual(console, dflt, c, n, s), + } + } +} /* [impl Run for FlagJob] */ + +#[derive(Debug)] +enum KbJob +{ + /** LED state ops. */ + Leds(LedJob), + /** Flags. */ + Flags(FlagJob), +} + +impl Run for KbJob +{ + fn run(self, console: Option<String>) -> Result<()> + { + match self { + Self::Leds(leds) => leds.run(console), + Self::Flags(flags) => flags.run(console), + } + } +} /* [impl Run for KbJob] */ + +#[derive(Debug)] +enum ColorJob { /** List available schemes. */ List, /** Dump a scheme. */ Dump(Scheme), + /** Launch scheme editor. */ + #[cfg(feature = "gui")] + Edit(Option<String>, Scheme), /** Switch to color scheme. */ Set(Scheme), /** Get currently active scheme. */ @@ -37,253 +266,34 @@ enum Job Fade(Option<Scheme>, Scheme, Duration, u8, bool), } -impl<'a> Job +impl Run for ColorJob { - pub fn from_argv() -> Result<Job> + fn run(self, console: Option<String>) -> Result<()> { - use clap::{App, Arg, SubCommand}; - - let app = App::new(clap::crate_name!()) - .version(clap::crate_version!()) - .author(clap::crate_authors!()) - .about(clap::crate_description!()) - .subcommand( - SubCommand::with_name("dump") - .about("dump a color scheme") - .arg( - Arg::with_name("scheme") - .help("name of the scheme") - .required(false) - .value_name("NAME") - .takes_value(true), - ) - .arg( - Arg::with_name("base64") - .short("b") - .long("base64") - .value_name("DATA") - .help("base64 encoded binary input") - .required(false) - .takes_value(true), - ), - ) - .subcommand( - SubCommand::with_name("list").about("list available schemes"), - ) - .subcommand( - SubCommand::with_name("set") - .about("apply color scheme to current terminal") - .arg( - Arg::with_name("scheme") - .value_name("NAME") - .help("predefined color scheme") - .takes_value(true) - .conflicts_with("file"), - ) - .arg( - Arg::with_name("file") - .short("f") - .long("file") - .value_name("PATH") - .help("apply scheme from file") - .takes_value(true), - ) - .arg( - Arg::with_name("base64") - .short("b") - .long("base64") - .help("base64 encoded binary input") - .value_name("DATA") - .required(false) - .takes_value(true), - ), - ) - .subcommand( - SubCommand::with_name("get") - .about("get current color scheme") - .arg( - Arg::with_name("base64") - .short("b") - .long("base64") - .help("base64 encoded binary output") - .required(false) - .takes_value(false), - ), - ) - .subcommand( - SubCommand::with_name("toggle") - .about("toggle between two schemes") - .arg( - Arg::with_name("one") - .value_name("NAME1") - .help("predefined color scheme") - .takes_value(true), - ) - .arg( - Arg::with_name("two") - .value_name("NAME2") - .help("predefined color scheme") - .takes_value(true), - ), - ) - .subcommand( - SubCommand::with_name("fade") - .about("fade from one scheme to another") - .arg( - Arg::with_name("from") - .short("f") - .long("from") - .value_name("NAME1") - .help("initial color scheme (default: current)") - .takes_value(true), - ) - .arg( - Arg::with_name("to") - .short("t") - .long("to") - .value_name("NAME2") - .help("final color scheme") - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name("ms") - .value_name("MS") - .short("m") - .long("ms") - .help("how long (in ms) the fade should take") - .takes_value(true), - ) - .arg( - Arg::with_name("clear") - .short("c") - .long("clear") - .help("clear terminal on each fade step") - .takes_value(false), - ) - .arg( - Arg::with_name("frequency") - .value_name("HZ") - .short("h") - .long("frequency") - .help("rate (HZ/s) of intermediate scheme changes") - .takes_value(true), - ), - ) - .arg( - Arg::with_name("verbose") - .short("v") - .long("verbose") - .help("enable extra diagnostics") - .takes_value(false), - ); - - let matches = app.get_matches(); - - if matches.is_present("verbose") { - VERBOSITY.store(true, Ordering::SeqCst); - } - - match matches.subcommand() { - ("dump", Some(subm)) => { - if let Some(b64) = subm.value_of("base64") { - let scheme = Scheme::from_base64(b64)?; - return Ok(Self::Dump(scheme)); - } - if let Some(name) = subm.value_of("scheme") { - let scm = Scheme::from(name); - return Ok(Self::Dump(scm)); - } - Err(anyhow!("dump requires an argument")) - }, - ("list", _) => Ok(Self::List), - ("set", Some(subm)) => { - if let Some(b64) = subm.value_of("base64") { - let scheme = Scheme::from_base64(&b64)?; - return Ok(Self::Set(scheme)); - } - let scheme = match subm.value_of("scheme") { - Some("-") => Self::read_scheme_from_stdin(), - Some(name) => { - vrb!("pick predefined scheme [{}]", name); - Scheme::from(name) - }, - None => - match subm.value_of("file") { - None | Some("-") => Self::read_scheme_from_stdin(), - Some(fname) => { - vrb!( - "read custom scheme from file [{}]", - fname - ); - Scheme::from_path(fname) - }, - }, - }; - Ok(Self::Set(scheme)) - }, - ("get", Some(subm)) => Ok(Self::Get(subm.is_present("base64"))), - ("toggle", Some(subm)) => { - match (subm.value_of("one"), subm.value_of("two")) { - (Some(one), Some(two)) => { - vrb!("toggle schemes [{}] and [{}]", one, two); - Ok(Self::Toggle(Scheme::from(one), Scheme::from(two))) - }, - _ => - Err(anyhow!( - "please supply two schemes to toggle between" - )), - } - }, - ("fade", Some(subm)) => { - let dur: u64 = if let Some(ms) = subm.value_of("ms") { - ms.parse()? - } else { - DEFAULT_FADE_DURATION_MS - }; - let hz: u8 = if let Some(ms) = subm.value_of("frequency") { - ms.parse()? - } else { - DEFAULT_FADE_UPDATE_HZ - }; - let clear = subm.is_present("clear"); - let dur = Duration::from_millis(dur); - - match (subm.value_of("from"), subm.value_of("to")) { - (_, None) => - Err(anyhow!("please supply color scheme to fade to")), - (from, Some(to)) => - Ok(Self::Fade( - from.map(Scheme::from), - Scheme::from(to), - dur, - hz, - clear, - )), - } - }, - (junk, _) => - Err(anyhow!( - "invalid subcommand [{}]; try ``{} --help``", - junk, - clap::crate_name!() - )), + match self { + Self::Dump(scm) => Self::dump(scm), + #[cfg(feature = "gui")] + Self::Edit(name, scm) => Self::edit(name, scm), + Self::List => Self::list(), + Self::Set(scm) => Self::set(console, scm), + Self::Get(b64) => Self::get(console, b64), + Self::Toggle(one, two) => Self::toggle(console, one, two), + Self::Fade(from, to, ms, hz, clear) => + Self::fade(console, from, to, ms, hz, clear), } } +} /* [impl Run for ColorJob] */ - fn list_schemes() +impl ColorJob +{ + fn list() -> Result<()> { 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() + Ok(()) } fn dump(scm: Scheme) -> Result<()> @@ -318,37 +328,31 @@ impl<'a> Job } } - fn run(self) -> Result<()> + #[cfg(feature = "gui")] + fn edit(name: Option<String>, scm: Scheme) -> Result<()> { - match self { - Self::Dump(scm) => Self::dump(scm)?, - Self::List => Self::list_schemes(), - Self::Set(scm) => Self::set_scheme(scm)?, - Self::Get(b64) => Self::get_scheme(b64)?, - Self::Toggle(one, two) => Self::toggle_scheme(one, two)?, - Self::Fade(from, to, ms, hz, clear) => - Self::fade(from, to, ms, hz, clear)?, - } + vrb!("Launching color scheme editor for scheme {}", scm); + let editor = crate::edit::Edit::new(name, scm); - Ok(()) + editor.run() } - fn set_scheme(scheme: Scheme) -> Result<()> + fn set(con: Option<String>, scheme: Scheme) -> Result<()> { - let con = Console::current()?; - vrb!("console fd: {}", con); + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); - con.apply_scheme(&scheme)?; - con.clear()?; + fd.apply_scheme(&scheme)?; + fd.clear()?; vrb!("successfully enabled scheme {:?}", scheme); /* It’s fine to leak the fd, the kernel will clean up anyways. */ Ok(()) } - fn get_scheme(b64: bool) -> Result<()> + fn get(con: Option<String>, b64: bool) -> Result<()> { - let fd = Console::current()?; + let fd = open_console(con.as_deref())?; vrb!("console fd: {}", fd); let scm = fd.current_scheme()?; @@ -365,15 +369,18 @@ impl<'a> Job /** Toggle between two schemes. Defaults to ``one`` in case neither scheme is active. */ - fn toggle_scheme(one: Scheme, two: Scheme) -> Result<()> + fn toggle(con: Option<String>, one: Scheme, two: Scheme) -> Result<()> { - let fd = Console::current()?; - vrb!("console fd: {}", fd); + let pal = { + let fd = open_console(con.as_deref())?; + vrb!("console fd: {}", fd); + fd.current_palette()? + }; - if fd.current_palette()? == Palette::try_from(&one)? { - Self::set_scheme(two) + if pal == Palette::try_from(&one)? { + Self::set(con, two) } else { - Self::set_scheme(one) + Self::set(con, one) } } @@ -381,6 +388,7 @@ impl<'a> Job If ``from`` is ``None``, the current palette is used as starting point. */ fn fade( + con: Option<String>, from: Option<Scheme>, to: Scheme, dur: Duration, @@ -388,7 +396,7 @@ impl<'a> Job clear: bool, ) -> Result<()> { - let fd = Console::current()?; + let fd = open_console(con.as_deref())?; vrb!("console fd: {}", fd); let from = if let Some(from) = from { @@ -403,6 +411,672 @@ impl<'a> Job fade.commence(&fd)?; Ok(()) } +} /* [impl ColorJob] */ + +/** Subcommand and runtime parameters. */ +#[derive(Debug)] +enum Subcmd +{ + /** Console palette ops. */ + Colors(ColorJob), + /** Keyboard LED ops. */ + Kb(KbJob), +} + +#[derive(Debug)] +struct Job(Option<String>, Subcmd); + +impl<'a> Job +{ + pub fn from_argv() -> Result<Job> + { + use clap::{App, Arg, ArgSettings, SubCommand}; + + let app = App::new(clap::crate_name!()) + .version(clap::crate_version!()) + .author(clap::crate_authors!()) + .about(clap::crate_description!()) + .arg( + Arg::with_name("verbose") + .set(ArgSettings::Global) + .short("v") + .long("verbose") + .help("enable extra diagnostics") + .takes_value(false), + ) + .arg( + Arg::with_name("console") + .set(ArgSettings::Global) + .short("C") + .long("console") + .help( + "path to the console device to operate on [default: \ + current console]", + ) + .takes_value(true), + ) + .subcommand( + SubCommand::with_name("colors") + .about("operations on the console palette") + .subcommand( + SubCommand::with_name("dump") + .about("dump a color scheme") + .arg( + Arg::with_name("scheme") + .help("name of the scheme") + .required(false) + .value_name("NAME") + .takes_value(true), + ) + .arg( + Arg::with_name("base64") + .short("b") + .long("base64") + .value_name("DATA") + .help("base64 encoded binary input") + .required(false) + .takes_value(true), + ), + ) + .subcommand( + SubCommand::with_name("edit") + .about("launch a graphical color scheme editor") + .arg( + Arg::with_name("scheme") + .help("name of the scheme to edit") + .required(false) + .value_name("NAME") + .takes_value(true), + ), + ) + .subcommand( + SubCommand::with_name("list") + .about("list builtin schemes"), + ) + .subcommand( + SubCommand::with_name("set") + .about("apply color scheme to current terminal") + .arg( + Arg::with_name("scheme") + .value_name("NAME") + .help("predefined color scheme") + .takes_value(true) + .conflicts_with("file"), + ) + .arg( + Arg::with_name("file") + .short("f") + .long("file") + .value_name("PATH") + .help("apply scheme from file") + .takes_value(true), + ) + .arg( + Arg::with_name("base64") + .short("b") + .long("base64") + .help("base64 encoded binary input") + .value_name("DATA") + .required(false) + .takes_value(true), + ), + ) + .subcommand( + SubCommand::with_name("get") + .about("get current color scheme") + .arg( + Arg::with_name("base64") + .short("b") + .long("base64") + .help("base64 encoded binary output") + .required(false) + .takes_value(false), + ), + ) + .subcommand( + SubCommand::with_name("toggle") + .about("toggle between two schemes") + .arg( + Arg::with_name("one") + .value_name("NAME1") + .help("predefined color scheme") + .takes_value(true), + ) + .arg( + Arg::with_name("two") + .value_name("NAME2") + .help("predefined color scheme") + .takes_value(true), + ), + ) + .subcommand( + SubCommand::with_name("fade") + .about("fade from one scheme to another") + .arg( + Arg::with_name("from") + .short("f") + .long("from") + .value_name("NAME1") + .help( + "initial color scheme (default: \ + current)", + ) + .takes_value(true), + ) + .arg( + Arg::with_name("to") + .short("t") + .long("to") + .value_name("NAME2") + .help("final color scheme") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("ms") + .value_name("MS") + .short("m") + .long("ms") + .help( + "how long (in ms) the fade should take", + ) + .takes_value(true), + ) + .arg( + Arg::with_name("clear") + .short("c") + .long("clear") + .help("clear terminal on each fade step") + .takes_value(false), + ) + .arg( + Arg::with_name("frequency") + .value_name("HZ") + .short("h") + .long("frequency") + .help( + "rate (HZ/s) of intermediate scheme \ + changes", + ) + .takes_value(true), + ), + ), + ) + .subcommand( + SubCommand::with_name("kb") + .about("keyboard controls") + .subcommand( + SubCommand::with_name("leds") + .about("operations regarding keyboard LEDs") + .subcommand( + SubCommand::with_name("get") + .about("get LED state") + .arg( + Arg::with_name("u8") + .short("8") + .long("u8") + .help("output raw state as integer") + .takes_value(false), + ), + ) + .subcommand( + SubCommand::with_name("set") + .about("set LED state") + .arg( + Arg::with_name("u8") + .short("8") + .long("u8") + .help( + "provide desired state as \ + integer", + ) + .takes_value(true) + .required_unless_one(&[ + "revert", "caps", "num", + "scroll", + ]) + .conflicts_with_all(&[ + "revert", "caps", "num", + "scroll", + ]) + .value_name("STATE"), + ) + .arg( + Arg::with_name("caps") + .short("c") + .long("caps") + .help("[de]activate Caps Lock LED") + .takes_value(true) + .possible_values(&["on", "off"]) + .conflicts_with_all(&[ + "revert", "u8", + ]) + .value_name("STATE"), + ) + .arg( + Arg::with_name("num") + .short("n") + .long("num") + .help("[de]activate Num Lock LED") + .takes_value(true) + .possible_values(&["on", "off"]) + .conflicts_with_all(&[ + "revert", "u8", + ]) + .value_name("STATE"), + ) + .arg( + Arg::with_name("scroll") + .short("s") + .long("scroll") + .help( + "[de]activate Scroll Lock LED", + ) + .takes_value(true) + .possible_values(&["on", "off"]) + .conflicts_with_all(&[ + "revert", "u8", + ]) + .value_name("STATE"), + ) + .arg( + Arg::with_name("revert") + .short("r") + .long("revert") + .help("revert LED state to normal") + .takes_value(false) + .conflicts_with_all(&[ + "u8", "caps", "num", "scroll", + ]), + ), + ), + ) + .subcommand( + SubCommand::with_name("flags") + .about( + "operations regarding keyboard flags \ + (modifier locks)", + ) + .subcommand( + SubCommand::with_name("get") + .about("get flags") + .arg( + Arg::with_name("u8") + .short("8") + .long("u8") + .help("output raw flags as integer") + .takes_value(false), + ), + ) + .subcommand( + SubCommand::with_name("set") + .about("set flags") + .arg( + Arg::with_name("u8") + .short("8") + .long("u8") + .help( + "provide desired flags as + integer", + ) + .takes_value(true) + .required_unless_one(&[ + "caps", "num", "scroll", + ]) + .conflicts_with_all(&[ + "dflt", "caps", "num", "scroll", + ]) + .value_name("STATE"), + ) + .arg( + Arg::with_name("dflt") + .short("d") + .long("default") + .help( + "operate on defaults, not the \ + current state", + ) + .conflicts_with("both") + .takes_value(false), + ) + .arg( + Arg::with_name("both") + .short("b") + .long("both") + .help( + "operate on both defaults and \ + current state", + ) + .takes_value(false), + ) + .arg( + Arg::with_name("caps") + .short("c") + .long("caps") + .help("[de]activate Caps Lock flag") + .takes_value(true) + .possible_values(&["on", "off"]) + .conflicts_with("u8") + .value_name("STATE"), + ) + .arg( + Arg::with_name("num") + .short("n") + .long("num") + .help("[de]activate Num Lock flag") + .takes_value(true) + .possible_values(&["on", "off"]) + .conflicts_with("u8") + .value_name("STATE"), + ) + .arg( + Arg::with_name("scroll") + .short("s") + .long("scroll") + .help( + "[de]activate Scroll Lock flag", + ) + .takes_value(true) + .possible_values(&["on", "off"]) + .conflicts_with("u8") + .value_name("STATE"), + ), + ), + ), + ); + + let matches = app.get_matches(); + + if matches.is_present("verbose") { + VERBOSITY.store(true, Ordering::SeqCst); + } + + let con = matches.value_of("console").map(String::from); + + match matches.subcommand() { + ("colors", Some(subm)) => + match subm.subcommand() { + ("dump", Some(subm)) => { + if let Some(b64) = subm.value_of("base64") { + let scheme = Scheme::from_base64(b64)?; + return Ok(Self( + con, + Subcmd::Colors(ColorJob::Dump(scheme)), + )); + } + if let Some(name) = subm.value_of("scheme") { + let scm = Scheme::from(name); + return Ok(Self( + con, + Subcmd::Colors(ColorJob::Dump(scm)), + )); + } + Err(anyhow!("dump requires an argument")) + }, + ("edit", Some(subm)) => { + #[cfg(feature = "gui")] + if let Some(name) = subm.value_of("scheme") { + let scm = Scheme::from(name); + Ok(Self( + con, + Subcmd::Colors(ColorJob::Edit( + Some(name.to_string()), + scm, + )), + )) + } else { + Ok(Self( + con, + Subcmd::Colors(ColorJob::Edit( + None, + Scheme::from("default"), + )), + )) + } + #[cfg(not(feature = "gui"))] + { + let _ = subm; /* silence warn(unused_variables) */ + Err(anyhow!( + "the ``edit'' subcommand requires vtcol to be \ + built with the the ``gui'' feature" + )) + } + }, + ("list", _) => + Ok(Self(con, Subcmd::Colors(ColorJob::List))), + ("set", Some(subm)) => { + if let Some(b64) = subm.value_of("base64") { + let scheme = Scheme::from_base64(b64)?; + return Ok(Self( + con, + Subcmd::Colors(ColorJob::Set(scheme)), + )); + } + let scheme = match subm.value_of("scheme") { + Some("-") => Self::read_scheme_from_stdin(), + Some(name) => { + vrb!("pick predefined scheme [{}]", name); + Scheme::from(name) + }, + None => + match subm.value_of("file") { + None | Some("-") => + Self::read_scheme_from_stdin(), + Some(fname) => { + vrb!( + "read custom scheme from file [{}]", + fname + ); + Scheme::from_path(fname) + }, + }, + }; + Ok(Self(con, Subcmd::Colors(ColorJob::Set(scheme)))) + }, + ("get", Some(subm)) => + Ok(Self( + con, + Subcmd::Colors(ColorJob::Get( + subm.is_present("base64"), + )), + )), + ("toggle", Some(subm)) => { + match (subm.value_of("one"), subm.value_of("two")) { + (Some(one), Some(two)) => { + vrb!("toggle schemes [{}] and [{}]", one, two); + Ok(Self( + con, + Subcmd::Colors(ColorJob::Toggle( + Scheme::from(one), + Scheme::from(two), + )), + )) + }, + _ => + Err(anyhow!( + "please supply two schemes to toggle \ + between" + )), + } + }, + ("fade", Some(subm)) => { + let dur: u64 = if let Some(ms) = subm.value_of("ms") { + ms.parse()? + } else { + DEFAULT_FADE_DURATION_MS + }; + let hz: u8 = + if let Some(ms) = subm.value_of("frequency") { + ms.parse()? + } else { + DEFAULT_FADE_UPDATE_HZ + }; + let clear = subm.is_present("clear"); + let dur = Duration::from_millis(dur); + + match (subm.value_of("from"), subm.value_of("to")) { + (_, None) => + Err(anyhow!( + "please supply color scheme to fade to" + )), + (from, Some(to)) => + Ok(Self( + con, + Subcmd::Colors(ColorJob::Fade( + from.map(Scheme::from), + Scheme::from(to), + dur, + hz, + clear, + )), + )), + } + }, + (junk, _) => + Err(anyhow!( + "invalid sub-subcommand to colors: [{}]; try ``{} \ + colors --help``", + junk, + clap::crate_name!() + )), + }, + + ("kb", Some(subm)) => + match subm.subcommand() { + ("leds", Some(subm)) => + match subm.subcommand() { + ("get", Some(subm)) => { + let raw = subm.is_present("u8"); + Ok(Self( + con, + Subcmd::Kb(KbJob::Leds(LedJob::Get(raw))), + )) + }, + ("set", Some(subm)) => { + if subm.is_present("revert") { + return Ok(Self( + con, + Subcmd::Kb(KbJob::Leds(LedJob::Revert)), + )); + } + if let Some(st) = subm.value_of("u8") { + let st: u8 = st.parse()?; + let st = KbLedState::try_from(st)?; + return Ok(Self( + con, + Subcmd::Kb(KbJob::Leds(LedJob::Set( + st, + ))), + )); + } + let cap = + subm.value_of("caps").map(|a| a == "on"); + let num = + subm.value_of("num").map(|a| a == "on"); + let scr = + subm.value_of("scroll").map(|a| a == "on"); + Ok(Self( + con, + Subcmd::Kb(KbJob::Leds( + LedJob::SetIndividual(cap, num, scr), + )), + )) + }, + (leds_junk, _) => + Err(anyhow!( + "invalid sub-sub-subcommand to kb leds: \ + [{}]; try ``{} kb leds --help``", + leds_junk, + clap::crate_name!() + )), + }, + ("flags", Some(subm)) => + match subm.subcommand() { + ("get", Some(subm)) => { + let raw = subm.is_present("u8"); + Ok(Self( + con, + Subcmd::Kb(KbJob::Flags(FlagJob::Get(raw))), + )) + }, + ("set", Some(subm)) => { + if let Some(st) = subm.value_of("u8") { + let st: u8 = st.parse()?; + let st = KbLedFlags::try_from(st)?; + return Ok(Self( + con, + Subcmd::Kb(KbJob::Flags(FlagJob::Set( + st, + ))), + )); + } + + let target = if subm.is_present("both") { + FlagTarget::Both + } else if subm.is_present("dflt") { + FlagTarget::Default + } else { + FlagTarget::Current + }; + + let cap = + subm.value_of("caps").map(|a| a == "on"); + let num = + subm.value_of("num").map(|a| a == "on"); + let scr = + subm.value_of("scroll").map(|a| a == "on"); + Ok(Self( + con, + Subcmd::Kb(KbJob::Flags( + FlagJob::SetIndividual( + target, cap, num, scr, + ), + )), + )) + }, + (flags_junk, _) => + Err(anyhow!( + "invalid sub-sub-subcommand to kb flags: \ + [{}]; try ``{} kb flags --help``", + flags_junk, + clap::crate_name!() + )), + }, + + (kb_junk, _) => + Err(anyhow!( + "invalid sub-subcommand to kb [{}]; try ``{} kb \ + --help``", + kb_junk, + clap::crate_name!() + )), + }, + (junk, _) => + Err(anyhow!( + "invalid subcommand [{}]; try ``{} --help``", + junk, + clap::crate_name!() + )), + } + } + + fn read_scheme_from_stdin() -> Scheme + { + vrb!("Go ahead, type your color scheme …"); + vrb!("vtcol>"); + Scheme::from_stdin() + } + + fn run(self) -> Result<()> + { + let Job(con, cmd) = self; + match cmd { + Subcmd::Colors(cols) => cols.run(con)?, + Subcmd::Kb(kb) => kb.run(con)?, + } + + Ok(()) + } } /* [impl Job] */ fn main() -> Result<()> |