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

Windows控制台键盘输入处理:platform_console_read_key实现问询

跨平台交互式控制台输入的Windows平台实现

需求背景

我正在开发一款可同时运行在Linux和Windows系统上的交互式控制台应用程序,核心需求如下:

  • 从stdin获取键盘输入,用户仅能输入特定ASCII字符且有字符数量限制
  • 按下键提交输入供程序内部状态评估
  • 按下键直接触发退出程序,无需提交输入
    目前该功能仅在Linux上可用,需要补充Windows平台的实现。

现有实现架构

输入提示功能通过两个函数分层实现:

  1. 通用处理函数prompt_user_input:负责输入逻辑的通用处理(输入长度限制、有效字符过滤、回显等)
  2. 平台层函数platform_console_read_key:负责与系统底层交互读取按键,目前仅完成Linux版本,Windows版本缺失

platform_console_read_key核心逻辑

  • 立即从stdin读取最多6字节到缓冲区(用于解析非ASCII键的转义码)
  • 若首字节不是转义序列标识,直接返回该ASCII字符
  • 若首字节是转义序列标识:
    • 剩余缓冲区为空时,返回独立键的键码
    • 剩余缓冲区有内容时,解析转义序列为扩展键码(#define定义的大于255的整数)
    • 无法识别的转义序列返回特殊值0
  • 严重I/O错误返回特殊值KEY_COUNT

prompt_user_input核心逻辑

  • 接收参数指定允许的最大输入字符数
  • 循环调用platform_console_read_key读取按键
  • 读取到或键时跳出循环
  • 其他按键通过filter_user_input判断有效性:
    • 有效且未达输入上限时,添加到输入缓冲区并回显
    • 无效或达上限时跳过

问题

如何为Windows平台实现platform_console_read_key函数?具体包括:

  • 如何在Windows终端立即读取6字节键盘输入?
  • 如何确定输入字节的格式,将Windows键码映射为与Linux版本一致的返回值?

Linux平台参考代码

bool
prompt_user_input
(   const u8 char_count
)
{
    printf ( "Type your response: " );

    // Clear any previous user input.
    memset ( ( *state ).in , 0 , sizeof ( ( *state ).in ) );

    KEY key;
    u8 indx = 0;
    for (;;)
    {
        key = platform_console_read_key ();
        
        if ( key == KEY_COUNT )
        {
            // ERROR
            return false;
        }
        
        // Submit response? Y/N
        if ( key == KEY_ENTER )
        {
            return true;
        }
        
        // Quit signal? Y/N
        if ( key == KEY_ESCAPE )
        {
            //...
            return true;
        }

        // Handle backspace.
        if ( key == KEY_BACKSPACE )
        {
            if ( !indx )
            {
                continue;
            }
            indx -= 1;
            ( *state ).in[ indx ] = 0;
        }

        // Response too long? Y/N
        else if ( indx >= char_count )
        {
            indx = char_count;
            continue;
        }

        // Invalid keycode? Y/N
        else if ( !filter_user_input ( key ) )
        {
            continue;
        }

        // Write to input buffer.
        else
        {
            ( *state ).in[ indx ] = key;
            indx += 1;
        }

        // Render the character.
        printf ( "%s"
               , ( key == KEY_BACKSPACE ) ? "\b \b"
                                          : ( char[] ){ key , 0 }
               );
    }
}

KEY
platform_console_read_key
( void )
{
    KEY key = KEY_COUNT;

    // Configure terminal for non-canonical input.
    struct termios tty;
    struct termios tty_;
    tcgetattr ( STDIN_FILENO , &tty );
    tty_ = tty;
    tty_.c_lflag &= ~( ICANON | ECHO );
    tcsetattr ( STDIN_FILENO , TCSANOW , &tty_ );
    fflush ( stdout ); // In case echo functionality desired.
    
    // Read the key from the input stream.
    char in[ 6 ]; // Reserve up to six bytes to handle special keys.
    memset ( in , 0 , sizeof ( in ) );
    i32 result = read ( STDIN_FILENO , in , sizeof ( in ) );
    
    // I/O error.
    if ( result < 0 )
    {
        key = KEY_COUNT;
        goto platform_console_read_key_end;
    }

    // End of transmission (I/O error).
    if (   in[ 0 ] == 4
        || in[ 1 ] == 4
        || in[ 2 ] == 4
        || in[ 3 ] == 4
        || in[ 4 ] == 4
        || in[ 5 ] == 4
        )
    {
        key = KEY_COUNT;
        goto platform_console_read_key_end;
    }

    // ANSI escape sequence.
    if ( *in == '\033' )
    {
        // Standalone keycode.
        if ( !in[ 1 ] )
        {
            key = KEY_ESCAPE;
            goto platform_console_read_key_end;
        }

        // Composite keycode.
        else
        {
            if ( in[ 1 ] == '[' )
            {
                // ...
            }
            else
            {
                key = 0;
            }
            goto platform_console_read_key_end;
        }
    }

    // Standalone ASCII character.
    else
    {
        // Backspace key is mapped to ASCII 'delete' (for some reason).
        key = ( *in == KEY_DELETE ) ? KEY_BACKSPACE : *in;
        goto platform_console_read_key_end;
    }

    // Reset terminal to canonical input mode.
    platform_console_read_key_end:
        tcsetattr ( STDIN_FILENO , TCSANOW , &tty );
        fflush ( stdout ); // In case echo functionality desired.
        return key;
}

解决方案:Windows平台platform_console_read_key实现

使用<conio.h>头文件的_getch()函数实现无缓冲按键读取,以下是最小实现(保证与Linux版本行为一致):

KEY
platform_console_read_key
( void )
{
    i32 getch;

    getch = _getch ();

    // Error.
    if ( getch < 0 )
    {
        return KEY_COUNT;
    }

    // Standalone ASCII keycode.
    if ( getch != 0 && getch != 224 && getch < 0x100 )
    {
        return newline ( getch ) ? KEY_ENTER
                                 : ( getch == '\033' ) ? KEY_ESCAPE
                                                       : getch
                                                       ;
    }

    // Extended keycode.
    getch = _getch ();
    switch ( getch )
    {
        default: return 0;  // Unknown keycode.
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 18:25:05