Compare commits
3 Commits
e42e362aea
...
1ac99e16f8
Author | SHA1 | Date |
---|---|---|
Ryan Heywood | 1ac99e16f8 | |
Ryan Heywood | 0ea49109d1 | |
Ryan Heywood | d8f9fc216f |
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue