catlock 0.1.8

Defend keyboard against your cat
/*
This is free and unencumbered software released into the public domain.
For more information, please refer to <http://unlicense.org>
*/

/* Make sure that program is safe for your cat */
#![forbid(unsafe_code)]

/** cat definitions */
mod resources;

const CATLOCK_OPTSTRING : &str = "hvp?";

/*   H E L P   T E X T   */
fn print_help() {
   println!("catlock [ -v | -h | -? | -p ]");
   println!("Protects keyboard against side effects caused by your cat interactions.");
   println!("Password for unlocking screen is \"{}\". Keep it secret from your cat.",
      resources::unlock_pw.iter().map(|&c| c as char).collect::<String>()
   );
   println!("You do not need to type spaces or specifically lowercase letters in the password.");
   println!("   -h|? print this help message");
   println!("   -v   print version number and exit");
   println!("   -p   print a cat");
   println!("\nCuddle and play with your cat daily! Cats need your attention.");
   println!("Make sure your cat always has access to fresh water.");
}

/**
Protects your keyboard against accidental cat walking/sleeping
*/
fn main() {
    /* command line parser */
    use getopt3::hideBin;
    let g = getopt3::new(hideBin(std::env::args()), CATLOCK_OPTSTRING).unwrap();
    if g.has('v') {
      const VERSION: &str = env!("CARGO_PKG_VERSION");
      println!("{}", VERSION);
      return
    } else if g.has('h') || g.has('?') {
      print_help();
      return
    } else if g.has('p') {
      let (cat, desc) = random_cat();
      println!("{}\n{}", cat, desc);
      return
    }

    use crossterm::tty::IsTty;
    if std::io::stdin().is_tty() {
       println!("Your keyboard is now CAT PROOF.");
       println!("{}", lockscreen_cat());
       println!("To unlock type: NOT A CAT\n");

       let rc = do_unlock();
       match rc {
          Ok(_)  => println!("You are certainly not a cat!"),
          Err(e) => eprintln!("Error reading password: {}", e)
       }
    } else {
       eprintln!("This program requires a terminal.");
    }
}

/**
Read password from terminal until correct
password is detected or reading fails
*/
fn do_unlock() -> std::io::Result<()> {
   crossterm::terminal::enable_raw_mode()?;
   /* which character we are checking now */
   let mut checkpos : usize = 0;
   loop {
      let maybekey = get_next_key();
      match maybekey {
         Err(r) => {
            /* unlock keyboard on error */
            crossterm::terminal::disable_raw_mode()?;
            return Err(r)
         }
         _ => { }
      }
      let key = maybekey.unwrap();
      if ! is_ignored(key) {
         if resources::unlock_pw[checkpos] == key.to_ascii_lowercase() {
            #[cfg(debug_assertions)]
            println!("tick");
            checkpos += 1;
            if checkpos >= resources::unlock_pw.len() {
               break;
            }
         } else {
            /* wrong input, reset */
            #[cfg(debug_assertions)]
            println!("snap");
            checkpos = 0;
         }
      } else {
         /* ignored key */
         #[cfg(debug_assertions)]
         println!("dup");
      }
   }

   crossterm::terminal::disable_raw_mode()?;
   Ok(())
}

/**
Get next key from stdin.

stdin must be in raw mode to read key without
need to wait for an enter
*/
fn get_next_key() -> std::io::Result<u8> {
   use std::io::Read;
   use std::io::ErrorKind;
   let mut buf: [u8;1] = [0];
   let mut handle = std::io::stdin().lock();
   let keys = handle.read(&mut buf);
   match keys {
      Ok(1) => {
         #[cfg(debug_assertions)]
         println!("You pressed: {:?}\n", buf[0]);
         Ok(buf[0])
      }
      Ok(0) => {
         /* EOF */
         Err(std::io::Error::new(ErrorKind::UnexpectedEof, "EOF reached"))
      }
      Err(e) => Err(e),
      _ => unreachable!()
   }
}

/**
checks if keystroke is ignored
*/
fn is_ignored(keystroke: u8) -> bool {
   resources::ignored_keys.contains(&keystroke)
}

/**
   get random lockscreen cat
*/
fn lockscreen_cat() -> &'static str {
   // Generate a random cat number
   let random_cat : usize = stdrandom::gen_range(0..resources::cats::lockscreen_cats.len() as u64, stdrandom::fast_u64);
   resources::cats::lockscreen_cats[random_cat]
}

/**
   get random cat from all cats with description
*/
fn random_cat() -> (&'static str, &'static str) {
   // Generate a random number
   let random_cat : usize = stdrandom::gen_range(0..resources::cats::all_cats.len() as u64, stdrandom::fast_u64);
   (
      resources::cats::all_cats[random_cat],
      resources::cats::all_cats_desc[random_cat]
   )
}