我正在创建一个应该在Linux和Windows操作系统上运行的交互式控制台应用程序.在该程序中,有要求从stdin
进行键盘输入的提示.在每次提示时,用户只能输入特定的ASCII字符以及特定数量的字符.如果按下<Escape>
或<Enter>
键,提示也有特殊的行为;<Enter>
提交提示,由内部程序状态判断,<Escape>
触发命令,退出程序而不需要提交提示.目前,这些提示只在Linux上有效,因为我不知道如何在Windows上实现它们.
输入提示符目前通过两个函数实现:通用处理程序函数prompt_user_input
和平台层函数platform_console_read_key
,后者目前只有一个工作的Linux实现,而不是Windows.
platform_console_read_key
是这样工作的:
- 立即从
stdin
中读取最多6个字节到缓冲区中(多个字节允许解析非ASCII键的ANSI转义码). - 如果第一个字节读取is not转义序列指示符,则第一个字节作为常规ASCII字符返回.
- 如果第一字节is是转义序列指示符,则解析缓冲器的字节2-6.
- 如果缓冲区的其余部分为空,则该字符是独立的,因此执行步骤2.
- 如果缓冲区的后半部分有什么东西,那么它实际上是一个转义序列.try 将其解析并作为"扩展密钥代码"返回(对于整数>;255,这只是一个
#define
). - 如果缓冲区的后半部分有内容,并且它是函数未处理的转义代码,则返回特定值
0
.
- 如果存在任何类型的严重I/O错误,则返回特定值
KEY_COUNT
.
我这样读字符的原因是因为我在prompt_user_input
中分别处理platform_console_read_key
返回的每个键代码,以确定程序应该如何进行.prompt_user_input
是这样的:
- 接受一个参数,该参数是可以输入的最大字符数.
- 在无限循环中通过
platform_console_read_key
读取密钥. - 如果最后一个关键字与
<Enter>
或<Escape>
匹配,则退出循环. - 否则,使用
filter_user_input
谓词查看它是否是有效的键.- 如果它有效,则将其添加到输入缓冲区,只要写入缓冲区的字符数不超过函数参数允许的字符数.还可以将其打印到屏幕上(即" echo "功能).
- 如果达到最大写入数,或者密钥无效,则继续(即转到步骤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.
}
}