我正在创建一个交互式命令行应用程序,它应该在 Linux 和 Windows 操作系统上运行。在程序中,有提示要求从
stdin
进行键盘输入。在每次提示时,用户只能输入特定的 ASCII 字符以及特定数量的字符。如果按下 <Escape>
或 <Enter>
键,提示也会有特殊行为; <Enter>
提交要由内部程序状态评估的提示,<Escape>
触发退出程序的命令,而不需要提交提示。目前,这些提示仅适用于 Linux,因为我不知道如何在 Windows 上实现它们。
输入提示当前通过两个函数实现:通用处理函数
prompt_user_input
和平台层函数 platform_console_read_key
,目前只有 Linux 实现可用,Windows 没有。
platform_console_read_key
的工作原理如下:
stdin
读取最多六个字节到缓冲区中(多个字节允许解析非 ASCII 键的 ANSI 转义码)。#define
)。0
。KEY_COUNT
。我之所以这样读取字符,是因为我在
platform_console_read_key
中分别处理了prompt_user_input
返回的每个关键代码,以确定程序应该如何进行。 prompt_user_input
的工作原理如下:
platform_console_read_key
无限循环读取密钥。<Enter>
或 <Escape>
匹配,则从循环中中断。filter_user_input
谓词查看它是否是有效键。
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.
platform_memory_clear ( in , 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' || *in == '~' )
{
// Standalone keycode.
if ( !in[ 1 ] )
{
switch ( *in )
{
case '\033': key = KEY_ESCAPE ;break;
case '~' : key = '~' ;break;
default : key = 0 ;break;
}
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;
}
注意:您希望在 GetKey() 中为扩展键添加 256 到 getch()。这将扩展键值置于 A 到 Z 等常用键的范围之外......注意 F10 本身只能是值 68 ——并且
that 与 SHIFT + 'D' 的键值匹配。不好,看到了吗?
#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)
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;
}