use clap::builder::{PossibleValue, RangedU64ValueParser, TypedValueParser, ValueParserFactory};
use clap::error::ErrorKind;
use clap::{Arg, ArgAction, Args, Error, Parser, Subcommand};
use pixeldike::pixmap::Color;
use std::ffi::OsStr;
use std::net::SocketAddr;
use std::path::PathBuf;
#[derive(Parser, Debug, Clone)]
#[command(author, version, about)]
pub(crate) struct CliOpts {
#[command(subcommand)]
pub command: Command,
#[arg(short = 'v', long = "verbose", action = ArgAction::Count, default_value = "0")]
pub verbose: u8,
#[arg(short = 'q', long = "quiet", action = ArgAction::Count, default_value = "0")]
pub quiet: u8,
}
#[derive(Subcommand, Debug, Clone)]
pub(crate) enum Command {
Server(ServerOpts),
PutRectangle(PutRectangleData),
PutImage(PutImageData),
}
#[derive(Args, Debug, Clone)]
pub(crate) struct ServerOpts {
#[arg(long = "tcp")]
pub tcp_bind_addr: Option<SocketAddr>,
#[cfg(feature = "udp")]
#[arg(long = "udp")]
pub udp_bind_addr: Option<SocketAddr>,
#[cfg(feature = "ws")]
#[arg(long = "ws")]
pub ws_bind_addr: Option<SocketAddr>,
#[arg(short = 'x', long = "width", default_value = "800")]
pub width: usize,
#[arg(short = 'y', long = "height", default_value = "600")]
pub height: usize,
#[command(flatten)]
pub stream_opts: StreamOpts,
#[command(flatten)]
pub file_opts: FileOpts,
#[command(flatten)]
pub fb_opts: FramebufferOpts,
#[cfg(feature = "windowing")]
#[arg(long = "open-window")]
pub open_window: bool,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct StreamOpts {
#[arg(long = "rtmp-stream")]
pub rtmp_dst_addr: Option<String>,
#[arg(long = "rtsp-stream")]
pub rtsp_dst_addr: Option<String>,
#[arg(long = "stream-framerate", default_value = "30")]
pub framerate: usize,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct FileOpts {
#[arg(long = "load-snapshot")]
pub load_snapshot: Option<PathBuf>,
#[arg(long = "snapshot")]
pub snapshot_file: Option<PathBuf>,
#[arg(long = "snapshot-interval", default_value = "5")]
pub snapshot_interval_secs: usize,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct FramebufferOpts {
#[arg(long = "fb-device")]
pub fb_device: Option<PathBuf>,
#[arg(long = "fb-framerate", default_value = "30")]
pub fb_framerate: usize,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct CommonClientOps {
#[arg(long = "server")]
pub server: SocketAddr,
#[arg(long = "width", default_value = "fill")]
pub width: TargetDimension,
#[arg(long = "height", default_value = "fill")]
pub height: TargetDimension,
#[arg(short = 'x', default_value = "0")]
pub x_offset: usize,
#[arg(short = 'y', default_value = "0")]
pub y_offset: usize,
#[arg(long = "once", action = ArgAction::SetFalse)]
pub do_loop: bool,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct PutRectangleData {
#[command(flatten)]
pub common: CommonClientOps,
#[arg(long = "color", default_value = "random")]
pub color: TargetColor,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct PutImageData {
#[command(flatten)]
pub common: CommonClientOps,
#[arg(short = 'f', long = "file")]
pub path: PathBuf,
}
#[derive(Debug, Clone)]
pub(crate) enum TargetDimension {
Fill,
Specific(usize),
}
impl ValueParserFactory for TargetDimension {
type Parser = TargetDimensionParser;
fn value_parser() -> Self::Parser {
TargetDimensionParser
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct TargetDimensionParser;
impl TypedValueParser for TargetDimensionParser {
type Value = TargetDimension;
fn parse_ref(&self, cmd: &clap::Command, arg: Option<&Arg>, value: &OsStr) -> Result<Self::Value, Error> {
let str_value = value.to_str().ok_or(cmd.clone().error(
ErrorKind::InvalidValue,
format!(
"{} argument is neither 'fill' nor a number",
arg.unwrap().get_id()
),
))?;
if str_value.eq_ignore_ascii_case("fill") {
Ok(TargetDimension::Fill)
} else {
RangedU64ValueParser::new()
.parse_ref(cmd, arg, value)
.map(|int_value: usize| TargetDimension::Specific(int_value))
}
}
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(
[PossibleValue::new("fill"), PossibleValue::new("<number>")].into_iter(),
))
}
}
#[derive(Debug, Clone)]
pub(crate) enum TargetColor {
RandomPerIteration,
RandomOnce,
Specific(Color),
}
impl ValueParserFactory for TargetColor {
type Parser = TargetColorParser;
fn value_parser() -> Self::Parser {
TargetColorParser
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct TargetColorParser;
impl TypedValueParser for TargetColorParser {
type Value = TargetColor;
fn parse_ref(&self, cmd: &clap::Command, arg: Option<&Arg>, value: &OsStr) -> Result<Self::Value, Error> {
let make_error = || {
cmd.clone().error(
ErrorKind::InvalidValue,
format!(
"{} argument is neither 'random', 'random-per-iteration' nor a valid hex color",
arg.unwrap().get_id()
),
)
};
let str_value = value.to_str().ok_or(make_error())?;
if str_value.eq_ignore_ascii_case("random") {
Ok(TargetColor::RandomOnce)
} else if str_value.eq_ignore_ascii_case("random-per-iteration") {
Ok(TargetColor::RandomPerIteration)
} else {
let color = u32::from_str_radix(str_value, 16).map_err(|_| make_error())?;
Ok(TargetColor::Specific(color.into()))
}
}
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(
[
PossibleValue::new("random"),
PossibleValue::new("random-per-iteration"),
PossibleValue::new("<hex-color>"),
]
.into_iter(),
))
}
}