keyfork-prompt: basic working version, committing before refactor

This commit is contained in:
Ryan Heywood 2023-12-21 12:04:35 -05:00
parent e42e362aea
commit d8f9fc216f
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
7 changed files with 345 additions and 4 deletions

64
Cargo.lock generated
View File

@ -441,6 +441,21 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.4.0",
"filedescriptor",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
]
[[package]]
name = "crunchy"
version = "0.2.2"
@ -687,6 +702,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d"
[[package]]
name = "filedescriptor"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
dependencies = [
"libc",
"thiserror",
"winapi",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -1068,6 +1094,14 @@ dependencies = [
"smex",
]
[[package]]
name = "keyfork-prompt"
version = "0.1.0"
dependencies = [
"crossterm",
"thiserror",
]
[[package]]
name = "keyfork-shard"
version = "0.1.0"
@ -1281,6 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
@ -2033,6 +2068,27 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@ -2203,18 +2259,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,6 +10,7 @@ members = [
"keyfork-frame",
"keyfork-mnemonic-util",
"keyfork-pinentry",
"keyfork-prompt",
"keyfork-plumbing",
"keyfork-shard",
"keyfork-slip10-test-data",

10
keyfork-prompt/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "keyfork-prompt"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
crossterm = { version = "0.27.0", default-features = false, features = ["use-dev-tty", "events"] }
thiserror = "1.0.51"

View File

@ -0,0 +1,88 @@
use std::{
io::Write,
os::fd::AsRawFd,
sync::{Arc, Mutex},
};
use crossterm::{
cursor::MoveTo,
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use crate::Result;
pub(crate) struct AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
write: Arc<Mutex<W>>,
}
impl<W> AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
pub(crate) fn new(write_handle: Arc<Mutex<W>>) -> Result<Self> {
let mut write = write_handle.lock().unwrap();
write.execute(EnterAlternateScreen)?.execute(MoveTo(0, 0))?;
drop(write);
Ok(Self {
write: write_handle,
})
}
pub(crate) fn arc_mutex(self) -> Arc<Mutex<Self>> {
Arc::new(Mutex::new(self))
}
}
impl<W> Write for AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.lock().unwrap().write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.lock().unwrap().flush()
}
}
impl<W> AsRawFd for AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.write.lock().unwrap().as_raw_fd()
}
}
/*
impl<W> ExecutableCommand for AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
fn execute(&mut self, command: impl crossterm::Command) -> std::io::Result<&mut Self> {
let mut write = self.write.lock().unwrap();
match write.execute(command) {
Ok(_) => {
drop(write);
Ok(self)
}
Err(e) => Err(e),
}
}
}
*/
impl<W> Drop for AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
fn drop(&mut self) {
let mut write_handle = self.write.lock().unwrap();
write_handle.execute(LeaveAlternateScreen).unwrap();
}
}

View File

@ -0,0 +1,12 @@
use std::io::{stdin, stdout};
use keyfork_prompt::*;
pub fn main() -> Result<()> {
let mut mgr = PromptManager::new(stdin(), stdout())?;
let line = mgr.prompt_input("Mnemonic: ")?;
dbg!(&line);
let line = mgr.prompt_passphrase("Passphrase: ")?;
dbg!(&line);
Ok(())
}

92
keyfork-prompt/src/lib.rs Normal file
View File

@ -0,0 +1,92 @@
use std::{
io::{BufRead, BufReader, Read, Write},
os::fd::AsRawFd,
sync::{Arc, Mutex},
};
use crossterm::{
event::{read, Event, KeyCode},
style::Print,
terminal,
tty::IsTty,
ExecutableCommand,
};
mod alternate_screen;
mod raw_mode;
use alternate_screen::*;
use raw_mode::*;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("The given handler is not a TTY")]
NotATTY,
#[error("IO Error: {0}")]
IO(#[from] std::io::Error),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct PromptManager<R, W> {
read: Arc<Mutex<BufReader<R>>>,
write: Arc<Mutex<W>>,
}
impl<R, W> PromptManager<R, W>
where
R: Read + Sized,
W: Write + AsRawFd + Sized,
{
pub fn new(read_handle: R, write_handle: W) -> Result<Self> {
if !write_handle.is_tty() {
return Err(Error::NotATTY);
}
Ok(Self {
read: Arc::new(Mutex::new(BufReader::new(read_handle))),
write: Arc::new(Mutex::new(write_handle)),
})
}
fn alt_screen(&self) -> Result<AlternateScreen<W>> {
AlternateScreen::new(self.write.clone())
}
pub fn prompt_input(&mut self, prompt: &str) -> Result<String> {
let mut alt_screen = self.alt_screen()?;
alt_screen
.execute(terminal::Clear(terminal::ClearType::All))?
.execute(Print(prompt))?;
let mut line = String::new();
self.read.lock().unwrap().read_line(&mut line)?;
Ok(line)
}
// TODO: return secrecy::Secret<String>
// TODO: write a guard drop system for raw mode
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
let write = AlternateScreen::new(self.write.clone())?;
let mut write = RawMode::new(write.arc_mutex())?;
write
.execute(terminal::Clear(terminal::ClearType::All))?
.execute(Print(prompt))?;
let mut passphrase = String::new();
loop {
match read()? {
Event::Key(k) => match k.code {
KeyCode::Enter => {
passphrase.push('\n');
break;
}
KeyCode::Char(c) => {
write.execute(Print("*"))?;
passphrase.push(c);
}
_ => (),
},
_ => (),
}
}
Ok(passphrase)
}
}

View File

@ -0,0 +1,82 @@
use std::{
io::Write,
os::fd::AsRawFd,
sync::{Arc, Mutex},
};
use crossterm::{terminal, ExecutableCommand};
use crate::Result;
pub(crate) struct RawMode<W>
where
W: Write + AsRawFd + Sized,
{
write: Arc<Mutex<W>>,
}
// TODO: fork crossterm to allow using FD from as_raw_fd()
impl<W> RawMode<W>
where
W: Write + AsRawFd + Sized,
{
pub(crate) fn new(write_handle: Arc<Mutex<W>>) -> Result<Self> {
terminal::enable_raw_mode()?;
Ok(Self {
write: write_handle,
})
}
pub(crate) fn arc_mutex(self) -> Arc<Mutex<Self>> {
Arc::new(Mutex::new(self))
}
}
impl<W> Write for RawMode<W>
where
W: Write + AsRawFd + Sized,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.lock().unwrap().write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.lock().unwrap().flush()
}
}
impl<W> AsRawFd for RawMode<W>
where
W: Write + AsRawFd + Sized,
{
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.write.lock().unwrap().as_raw_fd()
}
}
/*
impl<W> ExecutableCommand for RawMode<W>
where
W: Write + AsRawFd + Sized,
{
fn execute(&mut self, command: impl crossterm::Command) -> std::io::Result<&mut Self> {
let mut write = self.write.lock().unwrap();
match write.execute(command) {
Ok(_) => {
drop(write);
Ok(self)
}
Err(e) => Err(e),
}
}
}
*/
impl<W> Drop for RawMode<W>
where
W: Write + AsRawFd + Sized,
{
fn drop(&mut self) {
terminal::disable_raw_mode().unwrap();
}
}