use anyhow::Result;
use crossterm::{
cursor,
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
execute, queue,
style::{self, Color},
terminal,
};
use std::io::{stderr, Write as _};
use std::sync::Arc;
use yb_core::{piv::FlashHandle, DeviceInfo, PivBackend};
pub type PickerResult = Option<(DeviceInfo, Option<Box<dyn FlashHandle>>)>;
const FLASH_ON_MS: u64 = 100;
const FLASH_OFF_MS: u64 = 100;
pub fn run_picker(piv: &Arc<dyn PivBackend>, devices: &[DeviceInfo]) -> Result<PickerResult> {
assert!(
!devices.is_empty(),
"run_picker called with empty device list"
);
let mut idx = 0usize;
let mut flash = piv.start_flash(&devices[idx].reader, FLASH_ON_MS, FLASH_OFF_MS);
terminal::enable_raw_mode()?;
let mut out = stderr();
execute!(out, cursor::Hide)?;
render(&mut out, devices, idx, true)?;
let result = loop {
if let Ok(Event::Key(KeyEvent {
code, modifiers, ..
})) = event::read()
{
match (code, modifiers) {
(KeyCode::Left, _)
| (KeyCode::Char('h'), KeyModifiers::NONE)
| (KeyCode::Char('k'), KeyModifiers::NONE) => {
drop(flash);
idx = if idx == 0 { devices.len() - 1 } else { idx - 1 };
flash = piv.start_flash(&devices[idx].reader, FLASH_ON_MS, FLASH_OFF_MS);
render(&mut out, devices, idx, false)?;
}
(KeyCode::Right, _)
| (KeyCode::Char('l'), KeyModifiers::NONE)
| (KeyCode::Char('j'), KeyModifiers::NONE) => {
drop(flash);
idx = (idx + 1) % devices.len();
flash = piv.start_flash(&devices[idx].reader, FLASH_ON_MS, FLASH_OFF_MS);
render(&mut out, devices, idx, false)?;
}
(KeyCode::Enter, _) => {
break Some((devices[idx].clone(), Some(flash)));
}
(KeyCode::Esc, _)
| (KeyCode::Char('q'), KeyModifiers::NONE)
| (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
drop(flash);
break None;
}
_ => {}
}
}
};
queue!(
out,
cursor::MoveToColumn(0),
terminal::Clear(terminal::ClearType::CurrentLine),
cursor::Show,
)?;
out.flush()?;
terminal::disable_raw_mode()?;
Ok(result)
}
fn render(
out: &mut impl std::io::Write,
devices: &[DeviceInfo],
idx: usize,
first: bool,
) -> Result<()> {
let dev = &devices[idx];
let n = devices.len();
if !first {
queue!(out, cursor::MoveToColumn(0))?;
}
queue!(
out,
terminal::Clear(terminal::ClearType::CurrentLine),
style::SetForegroundColor(Color::DarkYellow),
style::Print("\u{2190} "), style::ResetColor,
style::SetBackgroundColor(Color::DarkGreen),
style::SetForegroundColor(Color::White),
style::Print(format!(" YubiKey {} (v{}) ", dev.serial, dev.version)),
style::ResetColor,
style::SetForegroundColor(Color::DarkYellow),
style::Print(format!(" \u{2192} [{}/{}]", idx + 1, n)), style::ResetColor,
style::Print(" \u{2190}/\u{2192} navigate \u{00B7} Enter confirm \u{00B7} Esc cancel"),
)?;
out.flush()?;
Ok(())
}