diff --git a/Cargo.lock b/Cargo.lock index 02e5a67..e2a5db3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,6 +607,7 @@ dependencies = [ "crossterm_winapi", "derive_more", "document-features", + "futures-core", "mio", "parking_lot", "rustix 1.1.2", @@ -998,6 +999,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1005,6 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1013,6 +1030,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1031,10 +1076,16 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -1859,7 +1910,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "ostool" -version = "0.8.7" +version = "0.8.8" dependencies = [ "anyhow", "byte-unit", @@ -1870,6 +1921,7 @@ dependencies = [ "cursive", "env_logger", "fitimage", + "futures", "indicatif", "jkconfig", "log", diff --git a/Cargo.toml b/Cargo.toml index b843912..01eab97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ resolver = "3" [workspace.dependencies] # Cursive TUI framework cursive = {version = "0.21", features = ["crossterm-backend"]} -crossterm = "0.29" +crossterm = {version = "0.29", features = ["event-stream"]} # Data processing anyhow = "1" diff --git a/ostool/Cargo.toml b/ostool/Cargo.toml index f738162..b53c322 100644 --- a/ostool/Cargo.toml +++ b/ostool/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" name = "ostool" readme = "../README.md" repository = "https://github.com/ZR233/ostool" -version = "0.8.7" +version = "0.8.8" [[bin]] name = "ostool" @@ -22,6 +22,7 @@ ui-log = ["jkconfig/logging"] [dependencies] anyhow = {workspace = true, features = ["backtrace"]} +futures = "0.3" byte-unit = "5.1" cargo_metadata = "0.23" clap = {workspace = true, features = ["derive"]} diff --git a/ostool/src/sterm/mod.rs b/ostool/src/sterm/mod.rs index a5e8e59..7cd10e0 100644 --- a/ostool/src/sterm/mod.rs +++ b/ostool/src/sterm/mod.rs @@ -5,9 +5,11 @@ use std::thread; use std::time::Duration; use crossterm::{ - event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}, + event::{Event, EventStream, KeyCode, KeyEventKind, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode}, }; +use futures::stream::StreamExt; +use tokio::task::{AbortHandle, spawn_blocking}; type Tx = Box; type Rx = Box; @@ -83,57 +85,18 @@ impl SerialTerm { is_running: AtomicBool::new(true), }); + // 使用 EventStream 异步处理键盘事件 + let tx_handle = tokio::spawn(Self::tx_work_async(handle.clone(), tx_port)); + + let tx_abort = tx_handle.abort_handle(); // 启动串口接收线程 - let rx_handle = thread::spawn({ + let rx_handle = spawn_blocking({ let handle = handle.clone(); - move || Self::handle_serial_receive(rx_port, handle, on_line) + move || Self::handle_serial_receive(rx_port, handle, tx_abort, on_line) }); - - // 主线程处理键盘输入 - let mut key_state = KeySequenceState::Normal; - - while handle.is_running() { - // 非阻塞读取键盘事件 - if event::poll(Duration::from_millis(10)).is_ok() - && let Ok(Event::Key(key)) = event::read() - && key.kind == KeyEventKind::Press - { - // 检测 Ctrl+A+x 退出序列 - match key_state { - KeySequenceState::Normal => { - if key.code == KeyCode::Char('a') - && key.modifiers.contains(KeyModifiers::CONTROL) - { - key_state = KeySequenceState::CtrlAPressed; - } else { - // 普通按键,发送到串口 - Self::send_key_to_serial(&tx_port, key)?; - } - } - KeySequenceState::CtrlAPressed => { - if key.code == KeyCode::Char('x') { - // 用户请求退出 - eprintln!("\r\nExit by: Ctrl+A+x"); - handle.stop(); - break; - } else { - // 不是x键,发送上一个按键并重置状态 - if let KeyCode::Char('a') = key.code { - // 如果还是 Ctrl+A,保持状态 - } else { - // 发送 Ctrl+A 和当前按键 - Self::send_ctrl_a_to_serial(&tx_port)?; - Self::send_key_to_serial(&tx_port, key)?; - key_state = KeySequenceState::Normal; - } - } - } - } - } - } - // 等待接收线程结束 - let _ = rx_handle.join(); + let _ = rx_handle.await?; + let _ = tx_handle.await; info!("Serial terminal exited"); Ok(()) } @@ -141,6 +104,7 @@ impl SerialTerm { fn handle_serial_receive( rx_port: Arc>, handle: Arc, + tx_abort: AbortHandle, on_line: F, ) -> io::Result<()> where @@ -189,7 +153,7 @@ impl SerialTerm { } } } - + tx_abort.abort(); Ok(()) } @@ -489,4 +453,65 @@ impl SerialTerm { tx_port.lock().unwrap().flush()?; Ok(()) } + + async fn tx_work_async(handle: Arc, tx_port: Arc>) -> anyhow::Result<()> { + // 使用 EventStream 异步处理键盘事件 + let mut reader = EventStream::new(); + let mut key_state = KeySequenceState::Normal; + + while handle.is_running() { + // 使用 EventStream::next() 异步等待事件,不会阻塞 + match reader.next().await { + Some(Ok(Event::Key(key))) if key.kind == KeyEventKind::Press => { + // 检测 Ctrl+A+x 退出序列 + match key_state { + KeySequenceState::Normal => { + if key.code == KeyCode::Char('a') + && key.modifiers.contains(KeyModifiers::CONTROL) + { + key_state = KeySequenceState::CtrlAPressed; + } else { + // 普通按键,发送到串口 + if let Err(e) = Self::send_key_to_serial(&tx_port, key) { + eprintln!("\r\n发送按键失败: {}", e); + } + } + } + KeySequenceState::CtrlAPressed => { + if key.code == KeyCode::Char('x') { + // 用户请求退出 + eprintln!("\r\nExit by: Ctrl+A+x"); + handle.stop(); + break; + } else { + // 不是x键,发送上一个按键并重置状态 + if key.code != KeyCode::Char('a') { + if let Err(e) = Self::send_ctrl_a_to_serial(&tx_port) { + eprintln!("\r\n发送 Ctrl+A 失败: {}", e); + } + if let Err(e) = Self::send_key_to_serial(&tx_port, key) { + eprintln!("\r\n发送按键失败: {}", e); + } + key_state = KeySequenceState::Normal; + } + } + } + } + } + Some(Err(e)) => { + eprintln!("\r\n键盘事件错误: {}", e); + break; + } + None => { + // EventStream 结束 + break; + } + Some(Ok(_)) => { + // 忽略非按键事件(鼠标、调整大小等) + } + } + } + + Ok(()) + } }