mirror of
https://github.com/wez/wezterm.git
synced 2024-12-02 23:55:11 +03:00
refactor ssh connection ui into its own module
I want to re-use this for the mux client
This commit is contained in:
parent
fd8f28960f
commit
1fe9e16f25
169
src/connui.rs
Normal file
169
src/connui.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use crate::termwiztermtab;
|
||||||
|
use anyhow::bail;
|
||||||
|
use crossbeam::channel::{bounded, Receiver, Sender};
|
||||||
|
use promise::Promise;
|
||||||
|
use std::time::Duration;
|
||||||
|
use termwiz::cell::unicode_column_width;
|
||||||
|
use termwiz::lineedit::*;
|
||||||
|
use termwiz::surface::Change;
|
||||||
|
use termwiz::terminal::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PasswordPromptHost {
|
||||||
|
history: BasicHistory,
|
||||||
|
}
|
||||||
|
impl LineEditorHost for PasswordPromptHost {
|
||||||
|
fn history(&mut self) -> &mut dyn History {
|
||||||
|
&mut self.history
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite the input so that we can obscure the password
|
||||||
|
// characters when output to the terminal widget
|
||||||
|
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
||||||
|
let placeholder = "🔑";
|
||||||
|
let grapheme_count = unicode_column_width(line);
|
||||||
|
let mut output = vec![];
|
||||||
|
for _ in 0..grapheme_count {
|
||||||
|
output.push(OutputElement::Text(placeholder.to_string()));
|
||||||
|
}
|
||||||
|
(output, unicode_column_width(placeholder) * cursor_position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum UIRequest {
|
||||||
|
/// Display something
|
||||||
|
Output(Vec<Change>),
|
||||||
|
/// Request input
|
||||||
|
Input {
|
||||||
|
prompt: String,
|
||||||
|
echo: bool,
|
||||||
|
respond: Promise<String>,
|
||||||
|
},
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConnectionUIImpl {
|
||||||
|
term: termwiztermtab::TermWizTerminal,
|
||||||
|
rx: Receiver<UIRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionUIImpl {
|
||||||
|
fn run(&mut self) -> anyhow::Result<()> {
|
||||||
|
loop {
|
||||||
|
match self.rx.recv_timeout(Duration::from_millis(200)) {
|
||||||
|
Ok(UIRequest::Close) => break,
|
||||||
|
Ok(UIRequest::Output(changes)) => self.term.render(&changes)?,
|
||||||
|
Ok(UIRequest::Input {
|
||||||
|
prompt,
|
||||||
|
echo: true,
|
||||||
|
mut respond,
|
||||||
|
}) => {
|
||||||
|
respond.result(self.input_prompt(&prompt));
|
||||||
|
}
|
||||||
|
Ok(UIRequest::Input {
|
||||||
|
prompt,
|
||||||
|
echo: false,
|
||||||
|
mut respond,
|
||||||
|
}) => {
|
||||||
|
respond.result(self.password_prompt(&prompt));
|
||||||
|
}
|
||||||
|
Err(err) if err.is_timeout() => {}
|
||||||
|
Err(err) => bail!("recv_timeout: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::new(2, 0));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn password_prompt(&mut self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut editor = LineEditor::new(&mut self.term);
|
||||||
|
editor.set_prompt(prompt);
|
||||||
|
|
||||||
|
let mut host = PasswordPromptHost::default();
|
||||||
|
if let Some(line) = editor.read_line(&mut host)? {
|
||||||
|
Ok(line)
|
||||||
|
} else {
|
||||||
|
bail!("password entry was cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_prompt(&mut self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut editor = LineEditor::new(&mut self.term);
|
||||||
|
editor.set_prompt(prompt);
|
||||||
|
|
||||||
|
let mut host = NopLineEditorHost::default();
|
||||||
|
if let Some(line) = editor.read_line(&mut host)? {
|
||||||
|
Ok(line)
|
||||||
|
} else {
|
||||||
|
bail!("prompt cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectionUI {
|
||||||
|
tx: Sender<UIRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionUI {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (tx, rx) = bounded(16);
|
||||||
|
promise::spawn::spawn_into_main_thread(termwiztermtab::run(70, 15, move |term| {
|
||||||
|
let mut ui = ConnectionUIImpl { term, rx };
|
||||||
|
ui.run()
|
||||||
|
}));
|
||||||
|
Self { tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self, title: &str) {
|
||||||
|
self.output(vec![Change::Title(title.to_string())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output(&self, changes: Vec<Change>) {
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Output(changes))
|
||||||
|
.expect("send to SShUI failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_str(&self, s: &str) {
|
||||||
|
let s = s.replace("\n", "\r\n");
|
||||||
|
self.output(vec![Change::Text(s)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input(&self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut promise = Promise::new();
|
||||||
|
let future = promise.get_future().unwrap();
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Input {
|
||||||
|
prompt: prompt.replace("\n", "\r\n"),
|
||||||
|
echo: true,
|
||||||
|
respond: promise,
|
||||||
|
})
|
||||||
|
.expect("send to ConnectionUI failed");
|
||||||
|
|
||||||
|
future.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn password(&self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut promise = Promise::new();
|
||||||
|
let future = promise.get_future().unwrap();
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Input {
|
||||||
|
prompt: prompt.replace("\n", "\r\n"),
|
||||||
|
echo: false,
|
||||||
|
respond: promise,
|
||||||
|
})
|
||||||
|
.expect("send to ConnectionUI failed");
|
||||||
|
|
||||||
|
future.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self) {
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Close)
|
||||||
|
.expect("send to ConnectionUI failed");
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ use structopt::StructOpt;
|
|||||||
use tabout::{tabulate_output, Alignment, Column};
|
use tabout::{tabulate_output, Alignment, Column};
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod connui;
|
||||||
mod frontend;
|
mod frontend;
|
||||||
mod keyassignment;
|
mod keyassignment;
|
||||||
mod localtab;
|
mod localtab;
|
||||||
|
174
src/ssh.rs
174
src/ssh.rs
@ -1,12 +1,11 @@
|
|||||||
|
use crate::connui::ConnectionUI;
|
||||||
use crate::localtab::LocalTab;
|
use crate::localtab::LocalTab;
|
||||||
use crate::mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
|
use crate::mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
|
||||||
use crate::mux::tab::Tab;
|
use crate::mux::tab::Tab;
|
||||||
use crate::mux::window::WindowId;
|
use crate::mux::window::WindowId;
|
||||||
use crate::mux::Mux;
|
use crate::mux::Mux;
|
||||||
use crate::termwiztermtab;
|
|
||||||
use anyhow::{anyhow, bail, Context, Error};
|
use anyhow::{anyhow, bail, Context, Error};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crossbeam::channel::{bounded, Receiver, Sender};
|
|
||||||
use portable_pty::cmdbuilder::CommandBuilder;
|
use portable_pty::cmdbuilder::CommandBuilder;
|
||||||
use portable_pty::{PtySize, PtySystem};
|
use portable_pty::{PtySize, PtySystem};
|
||||||
use promise::{Future, Promise};
|
use promise::{Future, Promise};
|
||||||
@ -15,172 +14,8 @@ use std::io::Write;
|
|||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
|
||||||
use termwiz::cell::unicode_column_width;
|
|
||||||
use termwiz::lineedit::*;
|
|
||||||
use termwiz::surface::Change;
|
|
||||||
use termwiz::terminal::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
impl ssh2::KeyboardInteractivePrompt for ConnectionUI {
|
||||||
struct PasswordPromptHost {
|
|
||||||
history: BasicHistory,
|
|
||||||
}
|
|
||||||
impl LineEditorHost for PasswordPromptHost {
|
|
||||||
fn history(&mut self) -> &mut dyn History {
|
|
||||||
&mut self.history
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite the input so that we can obscure the password
|
|
||||||
// characters when output to the terminal widget
|
|
||||||
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
|
||||||
let placeholder = "🔑";
|
|
||||||
let grapheme_count = unicode_column_width(line);
|
|
||||||
let mut output = vec![];
|
|
||||||
for _ in 0..grapheme_count {
|
|
||||||
output.push(OutputElement::Text(placeholder.to_string()));
|
|
||||||
}
|
|
||||||
(output, unicode_column_width(placeholder) * cursor_position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UIRequest {
|
|
||||||
/// Display something
|
|
||||||
Output(Vec<Change>),
|
|
||||||
/// Request input
|
|
||||||
Input {
|
|
||||||
prompt: String,
|
|
||||||
echo: bool,
|
|
||||||
respond: Promise<String>,
|
|
||||||
},
|
|
||||||
Close,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SshUIImpl {
|
|
||||||
term: termwiztermtab::TermWizTerminal,
|
|
||||||
rx: Receiver<UIRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SshUIImpl {
|
|
||||||
fn run(&mut self) -> anyhow::Result<()> {
|
|
||||||
let title = "🔐 wezterm: SSH authentication".to_string();
|
|
||||||
self.term.render(&[Change::Title(title)])?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match self.rx.recv_timeout(Duration::from_millis(200)) {
|
|
||||||
Ok(UIRequest::Close) => break,
|
|
||||||
Ok(UIRequest::Output(changes)) => self.term.render(&changes)?,
|
|
||||||
Ok(UIRequest::Input {
|
|
||||||
prompt,
|
|
||||||
echo: true,
|
|
||||||
mut respond,
|
|
||||||
}) => {
|
|
||||||
respond.result(self.input_prompt(&prompt));
|
|
||||||
}
|
|
||||||
Ok(UIRequest::Input {
|
|
||||||
prompt,
|
|
||||||
echo: false,
|
|
||||||
mut respond,
|
|
||||||
}) => {
|
|
||||||
respond.result(self.password_prompt(&prompt));
|
|
||||||
}
|
|
||||||
Err(err) if err.is_timeout() => {}
|
|
||||||
Err(err) => bail!("recv_timeout: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::new(2, 0));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn password_prompt(&mut self, prompt: &str) -> anyhow::Result<String> {
|
|
||||||
let mut editor = LineEditor::new(&mut self.term);
|
|
||||||
editor.set_prompt(prompt);
|
|
||||||
|
|
||||||
let mut host = PasswordPromptHost::default();
|
|
||||||
if let Some(line) = editor.read_line(&mut host)? {
|
|
||||||
Ok(line)
|
|
||||||
} else {
|
|
||||||
bail!("password entry was cancelled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input_prompt(&mut self, prompt: &str) -> anyhow::Result<String> {
|
|
||||||
let mut editor = LineEditor::new(&mut self.term);
|
|
||||||
editor.set_prompt(prompt);
|
|
||||||
|
|
||||||
let mut host = NopLineEditorHost::default();
|
|
||||||
if let Some(line) = editor.read_line(&mut host)? {
|
|
||||||
Ok(line)
|
|
||||||
} else {
|
|
||||||
bail!("prompt cancelled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SshUI {
|
|
||||||
tx: Sender<UIRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SshUI {
|
|
||||||
fn new() -> Self {
|
|
||||||
let (tx, rx) = bounded(16);
|
|
||||||
promise::spawn::spawn_into_main_thread(termwiztermtab::run(70, 15, move |term| {
|
|
||||||
let mut ui = SshUIImpl { term, rx };
|
|
||||||
ui.run()
|
|
||||||
}));
|
|
||||||
Self { tx }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output(&self, changes: Vec<Change>) {
|
|
||||||
self.tx
|
|
||||||
.send(UIRequest::Output(changes))
|
|
||||||
.expect("send to SShUI failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output_str(&self, s: &str) {
|
|
||||||
let s = s.replace("\n", "\r\n");
|
|
||||||
self.output(vec![Change::Text(s)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input(&self, prompt: &str) -> anyhow::Result<String> {
|
|
||||||
let mut promise = Promise::new();
|
|
||||||
let future = promise.get_future().unwrap();
|
|
||||||
|
|
||||||
self.tx
|
|
||||||
.send(UIRequest::Input {
|
|
||||||
prompt: prompt.replace("\n", "\r\n"),
|
|
||||||
echo: true,
|
|
||||||
respond: promise,
|
|
||||||
})
|
|
||||||
.expect("send to SshUI failed");
|
|
||||||
|
|
||||||
future.wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn password(&self, prompt: &str) -> anyhow::Result<String> {
|
|
||||||
let mut promise = Promise::new();
|
|
||||||
let future = promise.get_future().unwrap();
|
|
||||||
|
|
||||||
self.tx
|
|
||||||
.send(UIRequest::Input {
|
|
||||||
prompt: prompt.replace("\n", "\r\n"),
|
|
||||||
echo: false,
|
|
||||||
respond: promise,
|
|
||||||
})
|
|
||||||
.expect("send to SshUI failed");
|
|
||||||
|
|
||||||
future.wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&self) {
|
|
||||||
self.tx
|
|
||||||
.send(UIRequest::Close)
|
|
||||||
.expect("send to SshUI failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ssh2::KeyboardInteractivePrompt for SshUI {
|
|
||||||
fn prompt<'b>(
|
fn prompt<'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_username: &str,
|
_username: &str,
|
||||||
@ -214,7 +49,7 @@ pub fn async_ssh_connect(remote_address: &str, username: &str) -> Future<ssh2::S
|
|||||||
fn ssh_connect_with_ui(
|
fn ssh_connect_with_ui(
|
||||||
remote_address: &str,
|
remote_address: &str,
|
||||||
username: &str,
|
username: &str,
|
||||||
ui: &mut SshUI,
|
ui: &mut ConnectionUI,
|
||||||
) -> anyhow::Result<ssh2::Session> {
|
) -> anyhow::Result<ssh2::Session> {
|
||||||
let mut sess = ssh2::Session::new()?;
|
let mut sess = ssh2::Session::new()?;
|
||||||
|
|
||||||
@ -371,7 +206,8 @@ fn ssh_connect_with_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2::Session> {
|
pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2::Session> {
|
||||||
let mut ui = SshUI::new();
|
let mut ui = ConnectionUI::new();
|
||||||
|
ui.title("🔐 wezterm: SSH authentication");
|
||||||
let res = ssh_connect_with_ui(remote_address, username, &mut ui);
|
let res = ssh_connect_with_ui(remote_address, username, &mut ui);
|
||||||
match res {
|
match res {
|
||||||
Ok(sess) => {
|
Ok(sess) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user