Skip to content

Commit 67f0ff6

Browse files
committed
fix(sterm): 修复 event::poll 阻塞问题并改用 EventStream 异步处理
- 启用 crossterm 的 event-stream feature 并添加 futures 依赖 - 将键盘事件处理从阻塞式轮询改为 EventStream 异步流 - 使用 tokio::spawn 替代 thread::spawn 以正确支持异步上下文 - 修复因线程上下文不匹配导致的 stdin 阻塞问题 - 移除不可靠的超时轮询,采用真正的事件驱动模型
1 parent 1440f7c commit 67f0ff6

File tree

4 files changed

+130
-52
lines changed

4 files changed

+130
-52
lines changed

Cargo.lock

Lines changed: 53 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ resolver = "3"
66
[workspace.dependencies]
77
# Cursive TUI framework
88
cursive = {version = "0.21", features = ["crossterm-backend"]}
9-
crossterm = "0.29"
9+
crossterm = {version = "0.29", features = ["event-stream"]}
1010

1111
# Data processing
1212
anyhow = "1"

ostool/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
77
name = "ostool"
88
readme = "../README.md"
99
repository = "https://github.com/ZR233/ostool"
10-
version = "0.8.7"
10+
version = "0.8.8"
1111

1212
[[bin]]
1313
name = "ostool"
@@ -22,6 +22,7 @@ ui-log = ["jkconfig/logging"]
2222

2323
[dependencies]
2424
anyhow = {workspace = true, features = ["backtrace"]}
25+
futures = "0.3"
2526
byte-unit = "5.1"
2627
cargo_metadata = "0.23"
2728
clap = {workspace = true, features = ["derive"]}

ostool/src/sterm/mod.rs

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use std::thread;
55
use std::time::Duration;
66

77
use crossterm::{
8-
event::{self, Event, KeyCode, KeyEventKind, KeyModifiers},
8+
event::{Event, KeyCode, KeyEventKind, KeyModifiers, EventStream},
99
terminal::{disable_raw_mode, enable_raw_mode},
1010
};
11+
use futures::{stream::StreamExt};
12+
use tokio::task::{AbortHandle, spawn_blocking};
1113

1214
type Tx = Box<dyn Write + Send>;
1315
type Rx = Box<dyn Read + Send>;
@@ -83,64 +85,26 @@ impl SerialTerm {
8385
is_running: AtomicBool::new(true),
8486
});
8587

88+
// 使用 EventStream 异步处理键盘事件
89+
let tx_handle = tokio::spawn(Self::tx_work_async(handle.clone(), tx_port));
90+
91+
let tx_abort = tx_handle.abort_handle();
8692
// 启动串口接收线程
87-
let rx_handle = thread::spawn({
93+
let rx_handle = spawn_blocking({
8894
let handle = handle.clone();
89-
move || Self::handle_serial_receive(rx_port, handle, on_line)
95+
move || Self::handle_serial_receive(rx_port, handle, tx_abort, on_line)
9096
});
91-
92-
// 主线程处理键盘输入
93-
let mut key_state = KeySequenceState::Normal;
94-
95-
while handle.is_running() {
96-
// 非阻塞读取键盘事件
97-
if event::poll(Duration::from_millis(10)).is_ok()
98-
&& let Ok(Event::Key(key)) = event::read()
99-
&& key.kind == KeyEventKind::Press
100-
{
101-
// 检测 Ctrl+A+x 退出序列
102-
match key_state {
103-
KeySequenceState::Normal => {
104-
if key.code == KeyCode::Char('a')
105-
&& key.modifiers.contains(KeyModifiers::CONTROL)
106-
{
107-
key_state = KeySequenceState::CtrlAPressed;
108-
} else {
109-
// 普通按键,发送到串口
110-
Self::send_key_to_serial(&tx_port, key)?;
111-
}
112-
}
113-
KeySequenceState::CtrlAPressed => {
114-
if key.code == KeyCode::Char('x') {
115-
// 用户请求退出
116-
eprintln!("\r\nExit by: Ctrl+A+x");
117-
handle.stop();
118-
break;
119-
} else {
120-
// 不是x键,发送上一个按键并重置状态
121-
if let KeyCode::Char('a') = key.code {
122-
// 如果还是 Ctrl+A,保持状态
123-
} else {
124-
// 发送 Ctrl+A 和当前按键
125-
Self::send_ctrl_a_to_serial(&tx_port)?;
126-
Self::send_key_to_serial(&tx_port, key)?;
127-
key_state = KeySequenceState::Normal;
128-
}
129-
}
130-
}
131-
}
132-
}
133-
}
134-
13597
// 等待接收线程结束
136-
let _ = rx_handle.join();
98+
let _ = rx_handle.await?;
99+
let _ = tx_handle.await;
137100
info!("Serial terminal exited");
138101
Ok(())
139102
}
140103

141104
fn handle_serial_receive<F>(
142105
rx_port: Arc<Mutex<Rx>>,
143106
handle: Arc<TermHandle>,
107+
tx_abort: AbortHandle,
144108
on_line: F,
145109
) -> io::Result<()>
146110
where
@@ -189,7 +153,7 @@ impl SerialTerm {
189153
}
190154
}
191155
}
192-
156+
tx_abort.abort();
193157
Ok(())
194158
}
195159

@@ -489,4 +453,65 @@ impl SerialTerm {
489453
tx_port.lock().unwrap().flush()?;
490454
Ok(())
491455
}
456+
457+
async fn tx_work_async(handle: Arc<TermHandle>, tx_port: Arc<Mutex<Tx>>) -> anyhow::Result<()> {
458+
// 使用 EventStream 异步处理键盘事件
459+
let mut reader = EventStream::new();
460+
let mut key_state = KeySequenceState::Normal;
461+
462+
while handle.is_running() {
463+
// 使用 EventStream::next() 异步等待事件,不会阻塞
464+
match reader.next().await {
465+
Some(Ok(Event::Key(key))) if key.kind == KeyEventKind::Press => {
466+
// 检测 Ctrl+A+x 退出序列
467+
match key_state {
468+
KeySequenceState::Normal => {
469+
if key.code == KeyCode::Char('a')
470+
&& key.modifiers.contains(KeyModifiers::CONTROL)
471+
{
472+
key_state = KeySequenceState::CtrlAPressed;
473+
} else {
474+
// 普通按键,发送到串口
475+
if let Err(e) = Self::send_key_to_serial(&tx_port, key) {
476+
eprintln!("\r\n发送按键失败: {}", e);
477+
}
478+
}
479+
}
480+
KeySequenceState::CtrlAPressed => {
481+
if key.code == KeyCode::Char('x') {
482+
// 用户请求退出
483+
eprintln!("\r\nExit by: Ctrl+A+x");
484+
handle.stop();
485+
break;
486+
} else {
487+
// 不是x键,发送上一个按键并重置状态
488+
if key.code != KeyCode::Char('a') {
489+
if let Err(e) = Self::send_ctrl_a_to_serial(&tx_port) {
490+
eprintln!("\r\n发送 Ctrl+A 失败: {}", e);
491+
}
492+
if let Err(e) = Self::send_key_to_serial(&tx_port, key) {
493+
eprintln!("\r\n发送按键失败: {}", e);
494+
}
495+
key_state = KeySequenceState::Normal;
496+
}
497+
}
498+
}
499+
}
500+
}
501+
Some(Err(e)) => {
502+
eprintln!("\r\n键盘事件错误: {}", e);
503+
break;
504+
}
505+
None => {
506+
// EventStream 结束
507+
break;
508+
}
509+
Some(Ok(_)) => {
510+
// 忽略非按键事件(鼠标、调整大小等)
511+
}
512+
}
513+
}
514+
515+
Ok(())
516+
}
492517
}

0 commit comments

Comments
 (0)