You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅用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(())
}

补充说明

  1. 上述代码均未依赖外部crate,但属于平台相关的unsafe实现,需要针对不同系统编写不同代码。
  2. 若追求跨平台、无unsafe的简洁实现,推荐使用termion或crossterm这类成熟的终端控制crate,它们已经封装了所有平台的底层逻辑,不需要自己处理系统API调用。

内容的提问来源于stack exchange,提问作者Blanks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 21:00:52