diff --git a/.gitignore b/.gitignore index 5a4f6354..2622eacf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ target *.gz *.out .direnv +*.jpg +*.jpeg +*.png +*.ppm +*.qoi diff --git a/wayshot/src/cli.rs b/wayshot/src/cli.rs index 182737b2..29dd64fe 100644 --- a/wayshot/src/cli.rs +++ b/wayshot/src/cli.rs @@ -1,43 +1,44 @@ +use std::path::PathBuf; + use clap::arg; use clap::Parser; +use crate::utils::EncodingFormat; +use clap::builder::TypedValueParser; + #[derive(Parser)] #[command(version, about)] pub struct Cli { - /// Enable debug mode - #[arg(short, long, action = clap::ArgAction::SetTrue)] - pub debug: bool, + /// Where to save the screenshot, "-" for stdout. Defaults to "$UNIX_TIMESTAMP-wayshot.$EXTENSION". + #[arg(value_name = "OUTPUT")] + pub file: Option, + + /// Log level to be used for printing to stderr + #[arg(long, default_value = "info", value_parser = clap::builder::PossibleValuesParser::new(["trace", "debug", "info", "warn", "error"]).map(|s| -> tracing::Level{ s.parse().unwrap()}))] + pub log_level: tracing::Level, /// Arguments to call slurp with for selecting a region #[arg(short, long, value_name = "SLURP_ARGS")] pub slurp: Option, - /// Mention a custom file path - #[arg(short, long, conflicts_with = "stdout", value_name = "FILE_PATH")] - pub file: Option, - - /// Output the image data to standard out - #[arg(long, conflicts_with = "file", action = clap::ArgAction::SetTrue)] - pub stdout: bool, - /// Enable cursor in screenshots - #[arg(short, long, action = clap::ArgAction::SetTrue)] + #[arg(short, long)] pub cursor: bool, - /// Set image encoder (Png is default) - #[arg(short, long, value_name = "FILE_EXTENSION")] - pub extension: Option, + /// Set image encoder, if output file contains an extension, that will be used instead. + #[arg(long, visible_aliases = ["extension", "format", "output-format"], value_name = "FILE_EXTENSION", default_value = "png")] + pub encoding: EncodingFormat, /// List all valid outputs - #[arg(short, long, alias = "listoutputs", action = clap::ArgAction::SetTrue)] + #[arg(short, long, alias = "listoutputs")] pub list_outputs: bool, - /// Choose a particular display to screenshot + /// Choose a particular output/display to screenshot #[arg(short, long, conflicts_with = "slurp")] pub output: Option, - /// Present a fuzzy selector for outputs - #[arg(long, alias = "chooseoutput", conflicts_with_all = ["slurp", "output"], action = clap::ArgAction::SetTrue)] + /// Present a fuzzy selector for output/display selection + #[arg(long, alias = "chooseoutput", conflicts_with_all = ["slurp", "output"])] pub choose_output: bool, } diff --git a/wayshot/src/utils.rs b/wayshot/src/utils.rs index cea4217d..407f4060 100644 --- a/wayshot/src/utils.rs +++ b/wayshot/src/utils.rs @@ -1,6 +1,10 @@ -use eyre::{ContextCompat, Result}; +use clap::ValueEnum; +use eyre::{bail, ContextCompat, Error, Result}; + use std::{ + path::PathBuf, process::exit, + str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; @@ -47,15 +51,15 @@ pub fn parse_geometry(g: &str) -> Result { } /// Supported image encoding formats. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] pub enum EncodingFormat { - /// Jpeg / jpg encoder. + /// JPG/JPEG encoder. Jpg, - /// Png encoder. + /// PNG encoder. Png, - /// Ppm encoder. + /// PPM encoder. Ppm, - /// Qoi encoder. + /// Qut encoder. Qoi, } @@ -70,6 +74,27 @@ impl From for image::ImageOutputFormat { } } +impl TryFrom<&PathBuf> for EncodingFormat { + type Error = Error; + + fn try_from(value: &PathBuf) -> std::result::Result { + value + .extension() + .wrap_err_with(|| { + format!( + "no extension in {} to deduce encoding format", + value.display() + ) + }) + .and_then(|ext| { + ext.to_str().wrap_err_with(|| { + format!("extension in {} is not valid unicode", value.display()) + }) + }) + .and_then(|ext| ext.parse()) + } +} + impl From for &str { fn from(format: EncodingFormat) -> Self { match format { @@ -81,7 +106,21 @@ impl From for &str { } } -pub fn get_default_file_name(extension: EncodingFormat) -> String { +impl FromStr for EncodingFormat { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "jpg" | "jpeg" => Self::Jpg, + "png" => Self::Png, + "ppm" => Self::Ppm, + "qoi" => Self::Qoi, + _ => bail!("unsupported extension '{s}'"), + }) + } +} + +pub fn get_default_file_name(extension: EncodingFormat) -> PathBuf { let time = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => n.as_secs().to_string(), Err(_) => { @@ -90,5 +129,5 @@ pub fn get_default_file_name(extension: EncodingFormat) -> String { } }; - time + "-wayshot." + extension.into() + (time + "-wayshot." + extension.into()).into() } diff --git a/wayshot/src/wayshot.rs b/wayshot/src/wayshot.rs index 8e3bc8af..f274b376 100644 --- a/wayshot/src/wayshot.rs +++ b/wayshot/src/wayshot.rs @@ -11,9 +11,6 @@ mod cli; mod utils; use dialoguer::{theme::ColorfulTheme, FuzzySelect}; -use tracing::Level; - -use crate::utils::EncodingFormat; fn select_ouput(ouputs: &[T]) -> Option where @@ -32,41 +29,26 @@ where fn main() -> Result<()> { let cli = cli::Cli::parse(); - let level = if cli.debug { Level::TRACE } else { Level::INFO }; tracing_subscriber::fmt() - .with_max_level(level) + .with_max_level(cli.log_level) .with_writer(std::io::stderr) .init(); - let extension = if let Some(extension) = cli.extension { - let ext = extension.trim().to_lowercase(); - tracing::debug!("Using custom extension: {:#?}", ext); - - match ext.as_str() { - "jpeg" | "jpg" => EncodingFormat::Jpg, - "png" => EncodingFormat::Png, - "ppm" => EncodingFormat::Ppm, - "qoi" => EncodingFormat::Qoi, - _ => { - tracing::error!("Invalid extension provided.\nValid extensions:\n1) jpeg\n2) jpg\n3) png\n4) ppm\n5) qoi"); - exit(1); + let (file, _format) = match cli.file { + Some(pathbuf) => { + if pathbuf.to_string_lossy() == "-" { + (None, cli.encoding) + } else { + let extension = (&pathbuf).try_into().unwrap_or_else(|_| cli.encoding); + (Some(pathbuf), extension) } } - } else { - EncodingFormat::Png + None => ( + Some(utils::get_default_file_name(cli.encoding)), + cli.encoding, + ), }; - let mut file_is_stdout: bool = false; - let mut file_path: Option = None; - - if cli.stdout { - file_is_stdout = true; - } else if let Some(filepath) = cli.file { - file_path = Some(filepath.trim().to_string()); - } else { - file_path = Some(utils::get_default_file_name(extension)); - } - let wayshot_conn = WayshotConnection::new()?; if cli.list_outputs { @@ -117,16 +99,16 @@ fn main() -> Result<()> { wayshot_conn.screenshot_all(cli.cursor)? }; - if file_is_stdout { + if let Some(file) = file { + image_buffer.save(file)?; + } else { let stdout = stdout(); let mut buffer = Cursor::new(Vec::new()); let mut writer = BufWriter::new(stdout.lock()); - image_buffer.write_to(&mut buffer, extension)?; + image_buffer.write_to(&mut buffer, cli.encoding)?; writer.write_all(buffer.get_ref())?; - } else { - image_buffer.save(file_path.unwrap())?; } Ok(())