如何仅用Rust标准库检测键盘按键以控制无人机?
能否仅用Rust标准库实现实时按键检测?
仅依靠纯Rust标准库(仅std crate)无法实现实时单按键捕获——因为标准库没有提供修改终端/控制台输入模式的API:默认情况下,stdin是行缓冲模式,必须等用户按下回车才会将输入传递给程序,没法实时获取单个WASD按键的按下事件。
但如果允许编写平台相关的unsafe代码(直接调用系统API,不依赖外部crate),可以绕过这个限制实现需求,以下是分平台的实现思路和代码示例:
Unix系统(Linux/macOS)
需要调用系统的tcgetattr/tcsetattr接口修改终端属性,将终端从规范模式切换为原始模式,从而实时读取单个按键:
use std::io::{self, Read}; use std::os::unix::io::AsRawFd; use std::ptr; // 自动恢复终端原始属性的守卫结构体 struct TerminalGuard { orig_termios: libc::termios, } impl Drop for TerminalGuard { fn drop(&mut self) { let fd = io::stdin().as_raw_fd(); unsafe { libc::tcsetattr(fd, libc::TCSANOW, &self.orig_termios); } } } fn set_raw_mode() -> io::Result<TerminalGuard> { let fd = io::stdin().as_raw_fd(); let mut orig_termios = libc::termios::default(); unsafe { if libc::tcgetattr(fd, &mut orig_termios) != 0 { return Err(io::Error::last_os_error()); } let mut raw_mode = orig_termios; // 关闭行缓冲、回显、信号触发 raw_mode.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG); raw_mode.c_cc[libc::VMIN] = 1; // 至少读取1个字节 raw_mode.c_cc[libc::VTIME] = 0; // 无超时 if libc::tcsetattr(fd, libc::TCSANOW, &raw_mode) != 0 { return Err(io::Error::last_os_error()); } } Ok(TerminalGuard { orig_termios }) } fn main() -> io::Result<()> { let _guard = set_raw_mode()?; println!("按WASD控制无人机,按Q退出"); let mut buf = [0; 1]; loop { io::stdin().read_exact(&mut buf)?; match buf[0] { b'w' | b'W' => { println!("发送前进指令"); // 在这里插入你的UDP发送逻辑 } b'a' | b'A' => { println!("发送左移指令"); // 插入UDP发送逻辑 } b's' | b'S' => { println!("发送后退指令"); // 插入UDP发送逻辑 } b'd' | b'D' => { println!("发送右移指令"); // 插入UDP发送逻辑 } b'q' | b'Q' => { println!("退出程序"); break; } _ => {} } } Ok(()) }
Windows系统
调用Windows API的GetConsoleMode/SetConsoleMode修改控制台输入模式:
use std::io::{self, Read}; use std::ptr; type HANDLE = *mut u8; type DWORD = u32; const STD_INPUT_HANDLE: DWORD = 0xfffffff5; const ENABLE_ECHO_INPUT: DWORD = 0x0004; const ENABLE_LINE_INPUT: DWORD = 0x0002; const ENABLE_PROCESSED_INPUT: DWORD = 0x0001; #[link(name = "kernel32")] extern "system" { fn GetStdHandle(nStdHandle: DWORD) -> HANDLE; fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: *mut DWORD) -> i32; fn SetConsoleMode(hConsoleHandle: HANDLE, dwMode: DWORD) -> i32; } // 自动恢复控制台原始模式的守卫结构体 struct ConsoleGuard { orig_mode: DWORD, } impl Drop for ConsoleGuard { fn drop(&mut self) { let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; unsafe { SetConsoleMode(handle, self.orig_mode); } } } fn set_raw_mode() -> io::Result<ConsoleGuard> { let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; let mut orig_mode = 0; unsafe { if GetConsoleMode(handle, &mut orig_mode) == 0 { return Err(io::Error::last_os_error()); } let raw_mode = orig_mode & !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); if SetConsoleMode(handle, raw_mode) == 0 { return Err(io::Error::last_os_error()); } } Ok(ConsoleGuard { orig_mode }) } fn main() -> io::Result<()> { let _guard = set_raw_mode()?; println!("按WASD控制无人机,按Q退出"); let mut buf = [0; 1]; loop { io::stdin().read_exact(&mut buf)?; match buf[0] { b'w' | b'W' => { println!("发送前进指令"); // 在这里插入你的UDP发送逻辑 } b'a' | b'A' => { println!("发送左移指令"); // 插入UDP发送逻辑 } b's' | b'S' => { println!("发送后退指令"); // 插入UDP发送逻辑 } b'd' | b'D' => { println!("发送右移指令"); // 插入UDP发送逻辑 } b'q' | b'Q' => { println!("退出程序"); break; } _ => {} } } Ok(()) }
补充说明
- 上述代码均未依赖外部crate,但属于平台相关的unsafe实现,需要针对不同系统编写不同代码。
- 若追求跨平台、无unsafe的简洁实现,推荐使用
termion或crossterm这类成熟的终端控制crate,它们已经封装了所有平台的底层逻辑,不需要自己处理系统API调用。
内容的提问来源于stack exchange,提问作者Blanks
相关产品推荐
相关产品推荐

