#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
use cmd_lib::*;
use std::io::Read;
use std::{thread, time};
const VERSION: &str = "1.3.0";
const M: i32 = 32768; tls_init!(p, i32, 1); tls_init!(f, i32, 75); tls_init!(s, i32, 13); tls_init!(r, i32, 2000); tls_init!(t, i32, 0); tls_init!(w, i32, 80); tls_init!(h, i32, 24);
#[rustfmt::skip]
tls_init!(sets, Vec<String>, [
r"┃┏ ┓┛━┓ ┗┃┛┗ ┏━",
r"│╭ ╮╯─╮ ╰│╯╰ ╭─",
r"│┌ ┐┘─┐ └│┘└ ┌─",
r"║╔ ╗╝═╗ ╚║╝╚ ╔═",
r"|+ ++-+ +|++ +-",
r"|/ \/-\ \|/\ /-",
r".. .... .... ..",
r".o oo.o o.oo o.",
r"-\ /\|/ /-\/ \|", r"╿┍ ┑┚╼┒ ┕╽┙┖ ┎╾", ].iter().map(|ns| ns.to_string()).collect());
tls_init!(SETS, Vec<char>, vec![]);
tls_init!(x, Vec<i32>, vec![]); tls_init!(y, Vec<i32>, vec![]);
tls_init!(l, Vec<i32>, vec![]); tls_init!(n, Vec<i32>, vec![]); tls_init!(v, Vec<i32>, vec![]); tls_init!(c, Vec<String>, vec![]);
tls_init!(V, Vec<i32>, vec![0]); tls_init!(C, Vec<i32>, vec![1, 2, 3, 4, 5, 6, 7, 0]); tls_init!(VN, i32, 1); tls_init!(CN, i32, 8); tls_init!(E, Vec<String>, vec![]);
tls_init!(RNDSTART, bool, false); tls_init!(BOLD, bool, true);
tls_init!(NOCOLOR, bool, false);
tls_init!(KEEPCT, bool, false);
fn prog_name() -> String {
let arg0 = std::env::args().next().unwrap();
run_fun!(basename $arg0).unwrap()
}
fn print_help() {
let prog = prog_name();
let max_type = tls_get!(sets).len() - 1;
let cgap = " ".repeat(15 - format!("{}", tls_get!(COLORS)).chars().count());
let colors = run_fun!(tput colors).unwrap();
let term = std::env::var("TERM").unwrap();
#[rustfmt::skip]
eprintln!("
Usage: {prog} [OPTION]...
Animated pipes terminal screensaver.
-p [1-] number of pipes (D=1)
-t [0-{max_type}] pipe type (D=0)
-t c[16 chars] custom pipe type
-c [0-{colors}]{cgap}pipe color INDEX (TERM={term}), can be
hexadecimal with '#' prefix
(D=-c 1 -c 2 ... -c 7 -c 0)
-f [20-100] framerate (D=75)
-s [5-15] going straight probability, 1 in (D=13)
-r [0-] reset after (D=2000) characters, 0 if no reset
-R randomize starting position and direction
-B no bold effect
-C no color
-K keep pipe color and type when crossing edges
-h print this help message
-v print version number
Note: -t and -c can be used more than once.");
}
fn parse() -> CmdResult {
fn is_N(arg_opt: Option<String>) -> (bool, i32) {
if let Some(arg) = arg_opt {
if let Ok(vv) = arg.parse::<i32>() {
return (vv >= 0, vv);
}
}
(false, 0)
}
fn is_hex(arg: &str) -> (bool, i32) {
if let Ok(vv) = i32::from_str_radix(&arg, 16) {
return (true, vv);
}
(false, 0)
}
fn pearg(arg: &str, msg: &str) -> ! {
let arg0 = prog_name();
info!("{arg0}: -{arg} invalid argument; {msg}");
print_help();
std::process::exit(1)
}
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"-p" => {
let (is_valid, vv) = is_N(args.next());
if is_valid && vv > 0 {
tls_set!(p, |np| *np = vv);
} else {
pearg(&arg, "must be an integer and greater than 0");
}
}
"-t" => {
let arg_opt = args.next();
let (is_valid, vv) = is_N(arg_opt.clone());
let arg_str = arg_opt.unwrap_or_default();
let len = tls_get!(sets).len() as i32;
if arg_str.chars().count() == 16 {
tls_set!(V, |nv| nv.push(len));
tls_set!(sets, |ns| ns.push(arg_str));
} else if is_valid && vv < len {
tls_set!(V, |nv| nv.push(vv));
} else {
pearg(
&arg,
&format!("must be an integer and from 0 to {}; or a custom type", len),
);
}
}
"-c" => {
let arg_opt = args.next();
let (is_valid, vv) = is_N(arg_opt.clone());
let arg_str = arg_opt.unwrap_or_default();
if arg_str.starts_with("#") {
let (is_valid_hex, hv) = is_hex(&arg_str[1..]);
if !is_valid_hex {
pearg(&arg, "unrecognized hexadecimal string");
}
if hv >= tls_get!(COLORS) {
pearg(
&arg,
&format!("hexadecimal must be from #0 to {:X}", tls_get!(COLORS) - 1),
);
}
tls_set!(C, |nc| nc.push(hv));
} else if is_valid && vv < tls_get!(COLORS) {
tls_set!(C, |nc| nc.push(vv));
} else {
pearg(
&arg,
&format!(
"must be an integer and from 0 to {};
or a hexadecimal string with # prefix",
tls_get!(COLORS) - 1
),
);
}
}
"-f" => {
let (is_valid, vv) = is_N(args.next());
if is_valid && vv >= 20 && vv <= 100 {
tls_set!(f, |nf| *nf = vv);
} else {
pearg(&arg, "must be an integer and from 20 to 100");
}
}
"-s" => {
let (is_valid, vv) = is_N(args.next());
if is_valid && vv >= 5 && vv <= 15 {
tls_set!(r, |nr| *nr = vv);
} else {
pearg(&arg, "must be a non-negative integer");
}
}
"-r" => {
let (is_valid, vv) = is_N(args.next());
if is_valid && vv > 0 {
tls_set!(r, |nr| *nr = vv);
} else {
pearg(&arg, "must be a non-negative integer");
}
}
"-R" => tls_set!(RNDSTART, |nr| *nr = true),
"-B" => tls_set!(BOLD, |nb| *nb = false),
"-C" => tls_set!(NOCOLOR, |nc| *nc = true),
"-K" => tls_set!(KEEPCT, |nk| *nk = true),
"-h" => {
print_help();
std::process::exit(0);
}
"-v" => {
let arg0 = std::env::args().next().unwrap();
let prog = run_fun!(basename $arg0)?;
run_cmd!(echo $prog $VERSION)?;
std::process::exit(0);
}
_ => {
pearg(
&arg,
&format!("illegal arguments -- {}; no arguments allowed", arg),
);
}
}
}
Ok(())
}
fn cleanup() -> CmdResult {
let sgr0 = tls_get!(SGR0);
run_cmd!(
tput reset; tput rmcup;
tput cnorm;
stty echo;
echo $sgr0;
)?;
Ok(())
}
fn resize() -> CmdResult {
let cols = run_fun!(tput cols)?.parse().unwrap();
let lines = run_fun!(tput lines)?.parse().unwrap();
tls_set!(w, |nw| *nw = cols);
tls_set!(h, |nh| *nh = lines);
Ok(())
}
fn init_pipes() {
let mut ci = if tls_get!(KEEPCT) {
0
} else {
tls_get!(CN) * rand() / M
};
let mut vi = if tls_get!(RNDSTART) {
0
} else {
tls_get!(VN) * rand() / M
};
for _ in 0..tls_get!(p) as usize {
tls_set!(n, |nn| nn.push(0));
tls_set!(l, |nl| nl.push(if tls_get!(RNDSTART) {
rand() % 4
} else {
0
}));
tls_set!(x, |nx| nx.push(if tls_get!(RNDSTART) {
tls_get!(w) * rand() / M
} else {
tls_get!(w) / 2
}));
tls_set!(y, |ny| ny.push(if tls_get!(RNDSTART) {
tls_get!(h) * rand() / M
} else {
tls_get!(h) / 2
}));
tls_set!(v, |nv| nv.push(tls_get!(V)[vi as usize]));
tls_set!(c, |nc| nc.push(tls_get!(E)[ci as usize].clone()));
ci = (ci + 1) % tls_get!(CN);
vi = (vi + 1) % tls_get!(VN);
}
}
fn init_screen() -> CmdResult {
run_cmd!(
stty -echo -isig -icanon min 0 time 0;
tput smcup;
tput civis;
tput clear;
)?;
resize()?;
Ok(())
}
tls_init!(SGR0, String, String::new());
tls_init!(SGR_BOLD, String, String::new());
tls_init!(COLORS, i32, 0);
fn rand() -> i32 {
run_fun!(bash -c r"echo $RANDOM").unwrap().parse().unwrap()
}
#[cmd_lib::main]
fn main() -> CmdResult {
let term = std::env::var("TERM").unwrap();
run_cmd!(tput -T $term sgr0 >/dev/null)?;
let colors = run_fun!(tput colors)?.parse().unwrap();
tls_set!(COLORS, |nc| *nc = colors); tls_set!(SGR0, |ns| *ns = run_fun!(tput sgr0).unwrap());
tls_set!(SGR_BOLD, |nb| *nb = run_fun!(tput bold).unwrap());
parse()?;
tls_set!(VN, |vn| *vn = tls_get!(V).len() as i32);
tls_set!(CN, |cn| *cn = tls_get!(C).len() as i32);
for i in 0..(tls_get!(CN) as usize) {
tls_set!(E, |ne| ne.push(tls_get!(SGR0)));
if tls_get!(BOLD) {
tls_set!(E, |ne| ne[i] += &tls_get!(SGR_BOLD));
}
if !tls_get!(NOCOLOR) {
let cc = tls_get!(C)[i];
let setaf = run_fun!(tput setaf $cc)?;
tls_set!(E, |ne| ne[i] += &setaf);
}
}
for i in 0..tls_get!(sets).len() {
for j in 0..16 {
let cc = tls_get!(sets)[i].chars().nth(j).unwrap();
tls_set!(SETS, |ns| ns.push(cc));
}
}
init_screen()?;
init_pipes();
loop {
thread::sleep(time::Duration::from_millis(1000 / tls_get!(f) as u64));
let mut buffer = String::new();
if std::io::stdin().read_to_string(&mut buffer).is_ok() {
match buffer.as_str() {
"q" | "\u{1b}" | "\u{3}" => {
cleanup()?; break;
}
"P" => tls_set!(s, |ns| *ns = if *ns < 15 { *ns + 1 } else { *ns }),
"O" => tls_set!(s, |ns| *ns = if *ns > 3 { *ns - 1 } else { *ns }),
"F" => tls_set!(f, |nf| *nf = if *nf < 100 { *nf + 1 } else { *nf }),
"D" => tls_set!(f, |nf| *nf = if *nf > 20 { *nf - 1 } else { *nf }),
"B" => tls_set!(BOLD, |nb| *nb = !*nb),
"C" => tls_set!(NOCOLOR, |nc| *nc = !*nc),
"K" => tls_set!(KEEPCT, |nk| *nk = !*nk),
_ => (),
}
}
for i in 0..(tls_get!(p) as usize) {
if tls_get!(l)[i] % 2 == 1 {
tls_set!(x, |nx| nx[i] += -tls_get!(l)[i] + 2);
} else {
tls_set!(y, |ny| ny[i] += tls_get!(l)[i] - 1);
}
if !tls_get!(KEEPCT) {
if tls_get!(x)[i] >= tls_get!(w)
|| tls_get!(x)[i] < 0
|| tls_get!(y)[i] >= tls_get!(h)
|| tls_get!(y)[i] < 0
{
tls_set!(c, |nc| nc[i] =
tls_get!(E)[(tls_get!(CN) * rand() / M) as usize].clone());
tls_set!(v, |nv| nv[i] =
tls_get!(V)[(tls_get!(VN) * rand() / M) as usize].clone());
}
}
tls_set!(x, |nx| nx[i] = (nx[i] + tls_get!(w)) % tls_get!(w));
tls_set!(y, |ny| ny[i] = (ny[i] + tls_get!(h)) % tls_get!(h));
tls_set!(n, |nn| nn[i] = tls_get!(s) * rand() / M - 1);
tls_set!(n, |nn| nn[i] = if nn[i] >= 0 {
tls_get!(l)[i]
} else {
tls_get!(l)[i] + (2 * (rand() % 2) - 1)
});
tls_set!(n, |nn| nn[i] = (nn[i] + 4) % 4);
let ii = tls_get!(v)[i] * 16 + tls_get!(l)[i] * 4 + tls_get!(n)[i];
eprint!(
"\u{1b}[{};{}H{}{}",
tls_get!(y)[i] + 1,
tls_get!(x)[i] + 1,
tls_get!(c)[i],
tls_get!(SETS)[ii as usize]
);
tls_set!(l, |nl| nl[i] = tls_get!(n)[i]);
}
if tls_get!(r) > 0 && tls_get!(t) * tls_get!(p) >= tls_get!(r) {
run_cmd!(
tput reset;
tput civis;
stty -echo -isig -icanon min 0 time 0;
)?;
tls_set!(t, |nt| *nt = 0);
} else {
tls_set!(t, |nt| *nt += 1);
}
}
Ok(())
}