diff options
Diffstat (limited to 'src/bin')
| -rw-r--r-- | src/bin/client/main.rs | 4 | ||||
| -rw-r--r-- | src/bin/client/platform.rs | 28 | ||||
| -rw-r--r-- | src/bin/client/repl.rs | 83 | ||||
| -rw-r--r-- | src/bin/windows-test/main.rs | 61 |
4 files changed, 135 insertions, 41 deletions
diff --git a/src/bin/client/main.rs b/src/bin/client/main.rs index 9c15e9e..240ad66 100644 --- a/src/bin/client/main.rs +++ b/src/bin/client/main.rs | |||
| @@ -3,14 +3,10 @@ mod macros; | |||
| 3 | mod repl; | 3 | mod repl; |
| 4 | mod database; | 4 | mod database; |
| 5 | mod connection; | 5 | mod connection; |
| 6 | mod platform; | ||
| 7 | 6 | ||
| 8 | use repl::ClientState; | 7 | use repl::ClientState; |
| 9 | 8 | ||
| 10 | fn main() { | 9 | fn main() { |
| 11 | #[cfg(windows)] | ||
| 12 | platform::win::enable_vt().unwrap(); | ||
| 13 | |||
| 14 | let mut state = ClientState::new(); | 10 | let mut state = ClientState::new(); |
| 15 | if let Some(host) = state.addr.clone() { | 11 | if let Some(host) = state.addr.clone() { |
| 16 | state.connect_to(&host); | 12 | state.connect_to(&host); |
diff --git a/src/bin/client/platform.rs b/src/bin/client/platform.rs deleted file mode 100644 index 817c39d..0000000 --- a/src/bin/client/platform.rs +++ /dev/null | |||
| @@ -1,28 +0,0 @@ | |||
| 1 | #[cfg(windows)] | ||
| 2 | pub mod win { | ||
| 3 | use std::io; | ||
| 4 | use winapi::um::consoleapi::GetConsoleMode; | ||
| 5 | use winapi::um::consoleapi::SetConsoleMode; | ||
| 6 | use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; | ||
| 7 | use winapi::um::wincon::ENABLE_ECHO_INPUT; | ||
| 8 | use winapi::um::winbase::STD_OUTPUT_HANDLE; | ||
| 9 | use winapi::um::processenv::GetStdHandle; | ||
| 10 | use winapi::shared::minwindef::DWORD; | ||
| 11 | |||
| 12 | pub fn enable_vt() -> io::Result<()> { | ||
| 13 | unsafe { | ||
| 14 | let h = GetStdHandle(STD_OUTPUT_HANDLE); | ||
| 15 | if h.is_null() { return Err(io::Error::last_os_error()); } | ||
| 16 | |||
| 17 | let mut mode: DWORD = 0; | ||
| 18 | if GetConsoleMode(h, &mut mode) == 0 { | ||
| 19 | return Err(io::Error::last_os_error()); | ||
| 20 | } | ||
| 21 | let new = (mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) & !ENABLE_ECHO_INPUT; | ||
| 22 | if SetConsoleMode(h, new) == 0 { | ||
| 23 | return Err(io::Error::last_os_error()); | ||
| 24 | } | ||
| 25 | Ok(()) | ||
| 26 | } | ||
| 27 | } | ||
| 28 | } | ||
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index b2f6ecd..4e787cb 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs | |||
| @@ -6,7 +6,7 @@ use std::time::Instant; | |||
| 6 | use crate::connection; | 6 | use crate::connection; |
| 7 | use crate::database; | 7 | use crate::database; |
| 8 | 8 | ||
| 9 | use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show}, execute}; | 9 | use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show, Hide}, execute}; |
| 10 | 10 | ||
| 11 | use sha2::{Sha256, Digest}; | 11 | use sha2::{Sha256, Digest}; |
| 12 | 12 | ||
| @@ -172,7 +172,7 @@ impl ClientState { | |||
| 172 | if username.is_empty() { | 172 | if username.is_empty() { |
| 173 | continue 'outer; | 173 | continue 'outer; |
| 174 | } | 174 | } |
| 175 | let password = read_line("Password: "); | 175 | let password = read_password("Password: "); |
| 176 | if password.is_empty() { | 176 | if password.is_empty() { |
| 177 | continue 'outer; | 177 | continue 'outer; |
| 178 | } | 178 | } |
| @@ -217,7 +217,7 @@ impl ClientState { | |||
| 217 | if username.is_empty() { | 217 | if username.is_empty() { |
| 218 | break; | 218 | break; |
| 219 | } | 219 | } |
| 220 | let password = read_line("Password: "); | 220 | let password = read_password("Password: "); |
| 221 | if password.is_empty() { | 221 | if password.is_empty() { |
| 222 | break; | 222 | break; |
| 223 | } | 223 | } |
| @@ -268,7 +268,7 @@ impl ClientState { | |||
| 268 | if username.is_empty() { | 268 | if username.is_empty() { |
| 269 | continue 'outer; | 269 | continue 'outer; |
| 270 | } | 270 | } |
| 271 | let password = read_line("Password: "); | 271 | let password = read_password("Password: "); |
| 272 | if password.is_empty() { | 272 | if password.is_empty() { |
| 273 | continue 'outer; | 273 | continue 'outer; |
| 274 | } | 274 | } |
| @@ -367,14 +367,13 @@ impl ClientState { | |||
| 367 | *current_input = matches[0].clone(); | 367 | *current_input = matches[0].clone(); |
| 368 | redraw(current_input); | 368 | redraw(current_input); |
| 369 | } else if matches.len() > 1 { | 369 | } else if matches.len() > 1 { |
| 370 | disable_raw_mode().unwrap(); | ||
| 371 | execute!(stdout(), Show).unwrap(); | 370 | execute!(stdout(), Show).unwrap(); |
| 372 | println!("\nPossible completions:"); | 371 | println!("\nPossible completions:"); |
| 373 | for m in &matches { | 372 | for m in &matches { |
| 374 | print!("{} ", m); | 373 | print!("{} ", m); |
| 375 | } | 374 | } |
| 376 | println!(); | 375 | println!(); |
| 377 | enable_raw_mode().unwrap(); | 376 | execute!(stdout(), Hide).unwrap(); |
| 378 | redraw(current_input); | 377 | redraw(current_input); |
| 379 | } | 378 | } |
| 380 | // else do nothing | 379 | // else do nothing |
| @@ -420,12 +419,78 @@ fn redraw(current_input: &str) { | |||
| 420 | } | 419 | } |
| 421 | 420 | ||
| 422 | fn read_line(prompt: &str) -> String { | 421 | fn read_line(prompt: &str) -> String { |
| 423 | disable_raw_mode().unwrap(); | ||
| 424 | execute!(stdout(), Show).unwrap(); | 422 | execute!(stdout(), Show).unwrap(); |
| 425 | print!("{}", prompt); | 423 | print!("{}", prompt); |
| 426 | stdout().flush().unwrap(); | 424 | stdout().flush().unwrap(); |
| 425 | |||
| 427 | let mut input = String::new(); | 426 | let mut input = String::new(); |
| 428 | std::io::stdin().read_line(&mut input).unwrap(); | 427 | let prompt_len = prompt.len(); |
| 429 | enable_raw_mode().unwrap(); | 428 | |
| 429 | loop { | ||
| 430 | match read().unwrap() { | ||
| 431 | Event::Key(key) => match key.code { | ||
| 432 | KeyCode::Enter => { | ||
| 433 | println!(); | ||
| 434 | break; | ||
| 435 | } | ||
| 436 | KeyCode::Backspace => { | ||
| 437 | if !input.is_empty() { | ||
| 438 | input.pop(); | ||
| 439 | // Move cursor back and clear from current position to end of line | ||
| 440 | execute!(stdout(), MoveToColumn((prompt_len + input.len() + 1) as u16), | ||
| 441 | Clear(ClearType::UntilNewLine)).unwrap(); | ||
| 442 | stdout().flush().unwrap(); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | KeyCode::Char(c) => { | ||
| 446 | input.push(c); | ||
| 447 | print!("{}", c); | ||
| 448 | stdout().flush().unwrap(); | ||
| 449 | } | ||
| 450 | _ => {} | ||
| 451 | }, | ||
| 452 | _ => {} | ||
| 453 | } | ||
| 454 | } | ||
| 455 | input.trim().to_string() | ||
| 456 | } | ||
| 457 | |||
| 458 | fn read_password(prompt: &str) -> String { | ||
| 459 | execute!(stdout(), Show).unwrap(); | ||
| 460 | print!("{}", prompt); | ||
| 461 | stdout().flush().unwrap(); | ||
| 462 | |||
| 463 | let mut input = String::new(); | ||
| 464 | let mut display = String::new(); | ||
| 465 | let prompt_len = prompt.len(); | ||
| 466 | |||
| 467 | loop { | ||
| 468 | match read().unwrap() { | ||
| 469 | Event::Key(key) => match key.code { | ||
| 470 | KeyCode::Enter => { | ||
| 471 | println!(); | ||
| 472 | break; | ||
| 473 | } | ||
| 474 | KeyCode::Backspace => { | ||
| 475 | if !input.is_empty() { | ||
| 476 | input.pop(); | ||
| 477 | display.pop(); | ||
| 478 | // Move cursor back and clear from current position to end of line | ||
| 479 | execute!(stdout(), MoveToColumn((prompt_len + display.len() + 1) as u16), | ||
| 480 | Clear(ClearType::UntilNewLine)).unwrap(); | ||
| 481 | stdout().flush().unwrap(); | ||
| 482 | } | ||
| 483 | } | ||
| 484 | KeyCode::Char(c) => { | ||
| 485 | input.push(c); | ||
| 486 | display.push('*'); | ||
| 487 | print!("*"); | ||
| 488 | stdout().flush().unwrap(); | ||
| 489 | } | ||
| 490 | _ => {} | ||
| 491 | }, | ||
| 492 | _ => {} | ||
| 493 | } | ||
| 494 | } | ||
| 430 | input.trim().to_string() | 495 | input.trim().to_string() |
| 431 | } | 496 | } |
diff --git a/src/bin/windows-test/main.rs b/src/bin/windows-test/main.rs new file mode 100644 index 0000000..b8f84fe --- /dev/null +++ b/src/bin/windows-test/main.rs | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | use std::io::{self, Write}; | ||
| 2 | use std::ptr; | ||
| 3 | use windows_sys::Win32::Foundation::HANDLE; | ||
| 4 | use windows_sys::Win32::System::Console::{ | ||
| 5 | GetConsoleMode, ReadConsoleW, SetConsoleMode, GetStdHandle, STD_INPUT_HANDLE, | ||
| 6 | ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, | ||
| 7 | }; | ||
| 8 | |||
| 9 | fn main() -> io::Result<()> { | ||
| 10 | unsafe { | ||
| 11 | let h: HANDLE = GetStdHandle(STD_INPUT_HANDLE); | ||
| 12 | if h == std::ptr::null_mut() || h == -1_i32 as HANDLE { return Err(io::Error::last_os_error()); } | ||
| 13 | |||
| 14 | // save current mode | ||
| 15 | let mut mode: u32 = 0; | ||
| 16 | if GetConsoleMode(h, &mut mode) == 0 { return Err(io::Error::last_os_error()); } | ||
| 17 | |||
| 18 | // disable line input and echo so input is available per-key and not echoed | ||
| 19 | let new_mode = mode & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); | ||
| 20 | if SetConsoleMode(h, new_mode) == 0 { return Err(io::Error::last_os_error()); } | ||
| 21 | |||
| 22 | print!("Press keys (q to exit): "); | ||
| 23 | io::stdout().flush()?; | ||
| 24 | |||
| 25 | // ReadConsoleW reads UTF-16; we'll read one wchar at a time | ||
| 26 | let mut buf: [u16; 1] = [0]; | ||
| 27 | loop { | ||
| 28 | let mut read = 0u32; | ||
| 29 | if ReadConsoleW( | ||
| 30 | h, | ||
| 31 | buf.as_mut_ptr() as *mut _, | ||
| 32 | 1, | ||
| 33 | &mut read, | ||
| 34 | ptr::null_mut(), | ||
| 35 | ) == 0 | ||
| 36 | { | ||
| 37 | // restore mode on error before returning | ||
| 38 | let _ = SetConsoleMode(h, mode); | ||
| 39 | return Err(io::Error::last_os_error()); | ||
| 40 | } | ||
| 41 | |||
| 42 | let ch = std::char::from_u32(buf[0] as u32).unwrap_or('\u{FFFD}'); | ||
| 43 | if ch == 'q' { break; } // q to exit | ||
| 44 | |||
| 45 | // handle Enter (CR) so output is readable | ||
| 46 | if ch == '\r' { | ||
| 47 | println!(); | ||
| 48 | } else { | ||
| 49 | // show code point (no automatic console echo) | ||
| 50 | print!("[{:?}]", ch); | ||
| 51 | io::stdout().flush()?; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | // restore original console mode | ||
| 56 | let _ = SetConsoleMode(h, mode); | ||
| 57 | println!("\nexited"); | ||
| 58 | } | ||
| 59 | |||
| 60 | Ok(()) | ||
| 61 | } | ||
