C I/O:在Windows控制台上处理键盘输入

guicsvcw  于 9个月前  发布在  Windows
关注(0)|答案(1)|浏览(87)

我正在创建一个交互式控制台应用程序,应该在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转义码)。
1.如果读取的第一个字节 * 不是 * 转义序列指示符,则第一个字节将作为常规ASCII字符返回。
1.如果第一个字节 * 是 * 转义序列指示符,则解析缓冲区的字节2-6。
1.如果缓冲区的其余部分为空,则字符是独立的,执行步骤2。
1.如果缓冲区的后面部分有东西,那么它实际上是一个转义序列。尝试解析并将其作为“扩展键代码”(对于> 255的整数,它只是一个#define)返回。
1.如果在缓冲区的后面部分有一些东西,并且它是函数没有处理的转义码,则返回特殊值0
1.如果有任何类型的严重I/O错误,则返回特殊值KEY_COUNT
我这样读取字符的原因是因为我在prompt_user_input中分别处理platform_console_read_key返回的每个键代码,以确定程序应该如何进行。prompt_user_input的工作方式如下:
1.取一个参数,它是可以输入的最大字符数。
1.通过platform_console_read_key无限循环读取密钥。
1.如果last key匹配<Enter><Escape>,则中断循环。
1.否则,使用filter_user_input predicate 查看它是否是有效键。
1.如果它是有效的,将它添加到输入缓冲区,只要写入缓冲区的字符数不超过函数参数所允许的数量。同时将它打印到屏幕上(即“echo”功能)。
1.如果已达到最大写入次数,或者密钥无效,则继续(即,后藤转到步骤2)。
我的问题是如何为Windows平台层实现platform_console_read_key。我如何在Windows终端立即读取6个字节的键盘输入?另外,我如何确定这6个字节将如何格式化,以便我可以将Windows键代码Map到函数的返回值,从而使函数的行为与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;
}

字符串

已编辑

解决了,感谢接受的答案。这里是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.
    }
}

m528fe3b

m528fe3b1#

下面是一个简单的解决方案,用户Weather Vane暗示,我已经用了很多年了。运行这段代码还可以让你发现并定义更多的F键--你知道,F1F2,还有arrow keys,等等。
注意事项:你想在GetKey()中的扩展键_getch()上加上256。这将扩展键值放在了像A thru Z...这样的公共键的范围之外。注意F10本身只会是值68--并且 thatSHIFT + 'D'的键值匹配。不好,明白吗?
此代码也适用于CTRL+键组合。也适用于ALT键组合在限制范围内。ALT键组合变得有点毛茸茸的,因为ALT+TAB key,例如,在MS Windows中的应用程序之间切换。因此,当您的应用程序开始与操作系统竞争操作时,而且--根据经验--您可以添加100行代码来补救这些事情,但只能名义上提高性能和范围。
代码在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;
}

字符串

相关问题