我正在创建一个应该在Linux和Windows操作系统上运行的交互式控制台应用程序.在该程序中,有要求从stdin进行键盘输入的提示.在每次提示时,用户只能输入特定的ASCII字符以及特定数量的字符.如果按下<Escape><Enter>键,提示也有特殊的行为;<Enter>提交提示,由内部程序状态判断,<Escape>触发命令,退出程序而不需要提交提示.目前,这些提示只在Linux上有效,因为我不知道如何在Windows上实现它们.

输入提示符目前通过两个函数实现:通用处理程序函数prompt_user_input和平台层函数platform_console_read_key,后者目前只有一个工作的Linux实现,而不是Windows.

platform_console_read_key是这样工作的:

  1. 立即从stdin中读取最多6个字节到缓冲区中(多个字节允许解析非ASCII键的ANSI转义码).
  2. 如果第一个字节读取is not转义序列指示符,则第一个字节作为常规ASCII字符返回.
  3. 如果第一字节is是转义序列指示符,则解析缓冲器的字节2-6.
    1. 如果缓冲区的其余部分为空,则该字符是独立的,因此执行步骤2.
    2. 如果缓冲区的后半部分有什么东西,那么它实际上是一个转义序列.try 将其解析并作为"扩展密钥代码"返回(对于整数&gt;255,这只是一个#define).
    3. 如果缓冲区的后半部分有内容,并且它是函数未处理的转义代码,则返回特定值0.
  4. 如果存在任何类型的严重I/O错误,则返回特定值KEY_COUNT.

我这样读字符的原因是因为我在prompt_user_input中分别处理platform_console_read_key返回的每个键代码,以确定程序应该如何进行.prompt_user_input是这样的:

  1. 接受一个参数,该参数是可以输入的最大字符数.
  2. 在无限循环中通过platform_console_read_key读取密钥.
  3. 如果最后一个关键字与<Enter><Escape>匹配,则退出循环.
  4. 否则,使用filter_user_input谓词查看它是否是有效的键.
    1. 如果它有效,则将其添加到输入缓冲区,只要写入缓冲区的字符数不超过函数参数允许的字符数.还可以将其打印到屏幕上(即" echo "功能).
    2. 如果达到最大写入数,或者密钥无效,则继续(即转到步骤2).

我的问题是,我将如何为Windows平台层实现platform_console_read_key.如何在Windows终端上立即读取6个字节的键盘输入?另外,我如何确定这6个字节将如何格式化,以便将Windows键代码映射到函数的返回值,从而使函数的行为与Linux版本的行为匹配?

下面是正在运行的Linux代码,其中平台层包括<termios.h><unistd.h>.为了清晰和易于测试,我省略了一些不相关的代码,并添加了大约printf条语句.还请注意,在prompt_user_input中有对全局( *state ).in的引用,这是输入缓冲区;假设它对于所提供的char_count总是足够大的.

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;
}

EDIT

解决了,多亏了公认的答案.下面是platform_read_console_key的最小Windows实现,它使prompt_user_input的行为保持不变.它使用<conio.h>报头.

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.
    }
}

推荐答案

这是用户Weather Vane暗示的一个简单的解决方案,我已经使用了很多年.运行这段代码还允许您发现并定义更多的F键--您知道的,F1F2arrow keys等等.

注意:您希望将GetKey()中的扩展键添加为256到_getch(). 这会将扩展的键值放置在公共键的范围之外,如A thru Z..注意,F10本身只能是值68,而thatSHIFT + 'D'的键值匹配. 不好,看到了吗?

此代码也适用于CTRL个以上的组合键.也适用于限制范围内的ALT个键组合.ALT键组合变得有点麻烦,因为例如,ALT+TAB key在MS Windows中的应用程序之间切换.因此,当你的应用程序开始与操作系统竞争击键操作时,行为可能会变得不确定.而且--根据经验--您可以添加CTRL行代码来补救这些问题,但名义上只会提高性能和范围.

代码在Visual Studio中编译,并在发布到SO之前在Win10上进行测试.

#include <stdio.h>
#include <conio.h>

#define KEY_ESCAPE  27
#define KEY_BACKSPACE 8  
#define KEY_ENTER 13
#define KEY_F10  324


/*---------------------------------------------

    GetKey()

    Thanks John Wagner
*---------------------------------------------*/
int GetKey(void) 
{
    int c = _getch();
    if(c ==0 || c == 224)
        c = 256 + _getch(); /* If extended key (like F10), add 256. */
    return c;
}

int main () 
{
    int key;

    printf("Hit a key -- ESC key quits\n");

    do{
        key = GetKey();

        switch(key)
        {
            case KEY_ENTER:
                printf("ENTER key detected.\n");
            break;

            case KEY_BACKSPACE:
                printf("BACKSPACE key detected.\n");
            break;

            case KEY_F10:
                printf("MENU(F10) key detected.\n");
            break;

            case 'y':
            case 'Y':
                printf("User says yes!\n");
            break;

            case 'n':
            case 'N':
                printf("User says No!\n");
            break;

            default:
                printf("Key value: %d\n", key);
            break;
        }

    }while (key != KEY_ESCAPE);

    return 0;
}

C++相关问答推荐

在32位处理器上优化53—32位模计算>

在函数中使用复合文字来初始化C语言中的变量

C:fopen是如何实现二进制模式和文本模式的?

变量>;-1如何在C中准确求值?

将 struct 变量赋给自身(通过指针取消引用)是否定义了行为?

我在这里正确地解释了C操作顺序吗?

在C语言中,是否可以使枚举数向后计数?

加密解密工作正常,但返回错误0x80090005

限制不同类型的限定符

使用%f格式说明符打印整数值

在吉陀罗中,_2_1_和CONCAT11是什么意思?

当我将偏移量更改为任何非零值时,C中的mmap共享内存出现无效参数错误

在Ubuntu上使用库部署C程序的最佳实践

如何解释数组中的*(ptr)和*(ptr+2)?

在我的函数中实现va_arg的问题

与指针的原始C数组或C++向量<;向量<;双>>;

WSASocket在哪里定义?

macos/arm64 上地址空间不使用第一位吗?

在 C 语言中,为什么 10/3 应该给出 3.333 却给出 3.000? (保存 10 和 3 的变量被声明为double)

C代码加密文件无法使用openssl解密