Compare commits

..

3 Commits

7 changed files with 344 additions and 4 deletions

64
Cargo.lock generated
View File

@ -441,6 +441,21 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.2" version = "0.2.2"
@ -687,6 +702,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" 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]] [[package]]
name = "fixedbitset" name = "fixedbitset"
version = "0.4.2" version = "0.4.2"
@ -1068,6 +1094,14 @@ dependencies = [
"smex", "smex",
] ]
[[package]]
name = "keyfork-prompt"
version = "0.1.0"
dependencies = [
"crossterm",
"thiserror",
]
[[package]] [[package]]
name = "keyfork-shard" name = "keyfork-shard"
version = "0.1.0" version = "0.1.0"
@ -1281,6 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [ dependencies = [
"libc", "libc",
"log",
"wasi", "wasi",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2033,6 +2068,27 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" 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]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -2203,18 +2259,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.50" version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.50" version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -10,6 +10,7 @@ members = [
"keyfork-frame", "keyfork-frame",
"keyfork-mnemonic-util", "keyfork-mnemonic-util",
"keyfork-pinentry", "keyfork-pinentry",
"keyfork-prompt",
"keyfork-plumbing", "keyfork-plumbing",
"keyfork-shard", "keyfork-shard",
"keyfork-slip10-test-data", "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,80 @@
use std::{
io::Write,
os::fd::AsRawFd,
};
use crossterm::{
cursor::MoveTo,
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use crate::Result;
pub(crate) struct AlternateScreen<'a, W>
where
W: Write + AsRawFd + Sized,
{
write: &'a mut W,
}
impl<'a, W> AlternateScreen<'a, W>
where
W: Write + AsRawFd + Sized,
{
pub(crate) fn new(write_handle: &'a mut W) -> Result<Self> {
write_handle.execute(EnterAlternateScreen)?.execute(MoveTo(0, 0))?;
Ok(Self {
write: write_handle,
})
}
}
impl<W> Write for AlternateScreen<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.flush()
}
}
impl<W> AsRawFd for AlternateScreen<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.write.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) {
self.write.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())?;
mgr.prompt_input("Mnemonic: ")?;
mgr.prompt_passphrase("Passphrase: ")?;
mgr.prompt_message("Please press enter.")?;
mgr.prompt_message("Please press space bar.")?;
Ok(())
}

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

@ -0,0 +1,104 @@
use std::{
io::{BufRead, BufReader, Read, Write},
os::fd::AsRawFd,
};
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: BufReader<R>,
write: 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: BufReader::new(read_handle),
write: write_handle,
})
}
pub fn prompt_input(&mut self, prompt: &str) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
terminal
.execute(terminal::Clear(terminal::ClearType::All))?
.execute(Print(prompt))?;
let mut line = String::new();
self.read.read_line(&mut line)?;
Ok(line)
}
// TODO: return secrecy::Secret<String>
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?;
terminal
.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) => {
terminal.execute(Print("*"))?;
passphrase.push(c);
}
_ => (),
},
_ => (),
}
}
Ok(passphrase)
}
pub fn prompt_message(&mut self, prompt: &str) -> Result<()> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?;
terminal
.execute(terminal::Clear(terminal::ClearType::All))?
.execute(Print(prompt))?;
loop {
match read()? {
Event::Key(k) => match k.code {
KeyCode::Enter | KeyCode::Char(' ') => break,
_ => (),
},
_ => (),
}
}
Ok(())
}
}

View File

@ -0,0 +1,77 @@
use std::{
io::Write,
os::fd::AsRawFd,
};
use crossterm::terminal;
use crate::Result;
pub(crate) struct RawMode<'a, W>
where
W: Write + AsRawFd + Sized,
{
write: &'a mut W,
}
// TODO: fork crossterm to allow using FD from as_raw_fd()
impl<'a, W> RawMode<'a, W>
where
W: Write + AsRawFd + Sized,
{
pub(crate) fn new(write_handle: &'a mut W) -> Result<Self> {
terminal::enable_raw_mode()?;
Ok(Self {
write: write_handle,
})
}
}
impl<W> Write for RawMode<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.flush()
}
}
impl<W> AsRawFd for RawMode<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.write.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();
}
}