C/C++ 以及 Rust 中的 getch 实现

getch 是一个在 C 语言编程中常用的函数,用于从键盘读取一个字符,但不回显到屏幕上。

在 Windows 环境下,getch 实现通常包含在 <conio.h> 头文件中。需要注意的是,getch 这个符号并非标准,标准的符号是 _getch,虽然 getch 一般会被指向 _getch,但你应当使用 _getch 而非 getch

在 Unix/Linux 环境下,没有系统提供的 getch 实现,我们可以通过以下方法实现:

#include <termio.h>

int getch(void) {
    struct termios tm, tm_old;
    int fd = 0, ch;

    if (tcgetattr(fd, &tm) < 0) { // 保存现在的终端设置
        return -1;
    }

    tm_old = tm;
    cfmakeraw(&tm); // 更改终端为原始模式,该模式下所有的输入数据以字节处理
    if (tcsetattr(fd, TCSANOW, &tm) < 0) { // 设置上更改之后的设置
        return -1;
    }

    ch = getchar();
    if (tcsetattr(fd, TCSANOW, &tm_old) < 0) { // 更改设置为最初的样子
        return -1;
    }

    return ch;
}

其中 struct termiostcgetattrtcsetattrcfmakeraw 以及 getchar 的定义为:

typedef unsigned char	cc_t;
typedef unsigned int	speed_t;
typedef unsigned int	tcflag_t;
#define NCCS 32
struct termios{
    tcflag_t c_iflag;		/* input mode flags */
    tcflag_t c_oflag;		/* output mode flags */
    tcflag_t c_cflag;		/* control mode flags */
    tcflag_t c_lflag;		/* local mode flags */
    cc_t c_line;			/* line discipline */
    cc_t c_cc[NCCS];		/* control characters */
    speed_t c_ispeed;		/* input speed */
    speed_t c_ospeed;		/* output speed */
};

int tcgetattr(int __fd, struct termios *__termios_p);
void cfmakeraw(struct termios *__termios_p);
int tcsetattr(int __fd, int __optional_actions, const struct termios *__termios_p);
int getchar(void);

据此,我们可以通过 Rust 的 FFI 为 rust 实现一个 getch

#[cfg(target_os = "windows")]
mod conio {
    use std::os::raw::c_int;

    extern "C" {
        pub fn _getch() -> c_int;
    }
}

#[cfg(target_os = "linux")]
#[allow(non_camel_case_types)]
mod conio {
    use std::os::raw::c_int;
    type tcflag_t = ::std::os::raw::c_uint;
    type speed_t = ::std::os::raw::c_uint;
    type cc_t = ::std::os::raw::c_uchar;

    const NCCS: usize = 32;
    const TCSANOW: i32 = 0;

    #[repr(C)]
    #[derive(Default, Copy, Clone)]
    struct termios {
        c_iflag: tcflag_t,
        c_oflag: tcflag_t,
        c_cflag: tcflag_t,
        c_lflag: tcflag_t,
        c_line: cc_t,
        c_cc: [cc_t; NCCS],
        c_ispeed: speed_t,
        c_ospeed: speed_t,
    }

    extern "C" {
        fn tcgetattr(__fd: c_int, __termios_p: *mut termios) -> c_int;
        fn tcsetattr(__fd: c_int, __optional_actions: c_int, __termios_p: *const termios) -> c_int;
        fn cfmakeraw(__termios_p: *mut termios);
        fn getchar() -> c_int;
    }

    #[allow(unused_mut, unused_assignments)]
    pub fn _getch() -> c_int {
        unsafe {
            let mut tm: termios = Default::default();
            let mut tm_old: termios = Default::default();
            let fd = 0;
            let mut ch: c_int;
            if tcgetattr(fd, &mut tm) < 0 {
                return -1;
            }

            tm_old = tm;
            cfmakeraw(&mut tm);
            if tcsetattr(fd, TCSANOW, &mut tm) < 0 {
                return -1;
            }

            ch = getchar();
            if tcsetattr(fd, TCSANOW, &mut tm_old) < 0 {
                return -1;
            }

            ch
        }
    }
}

#[allow(unused_unsafe)]
fn getch() -> char {
    unsafe { conio::_getch() as u8 as char }
}
rust·c++·c
43 views
Comments
登录后评论
Sign In