keyfork-prompt: basic working version, committing before refactor
This commit is contained in:
parent
e42e362aea
commit
d8f9fc216f
|
@ -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",
|
||||
|
|
|
@ -10,6 +10,7 @@ members = [
|
|||
"keyfork-frame",
|
||||
"keyfork-mnemonic-util",
|
||||
"keyfork-pinentry",
|
||||
"keyfork-prompt",
|
||||
"keyfork-plumbing",
|
||||
"keyfork-shard",
|
||||
"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,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();
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue