我正在创建一个交互式控制台应用程序,该应用程序应在 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' )
{
// 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.
}
}
Weather Vane
暗示的一个简单的解决方案,我已经使用了多年。运行此代码还可以让您发现并定义更多的 F 键——您知道,
F1
、
F2
,还有
arrow keys
,等等。注意:您希望将 256 添加到
_getch()
作为
GetKey()
中的扩展键。这会将扩展键值置于公共键的范围之外,例如
A thru Z
....注意
F10
本身只能是值
68
——并且that 与
SHIFT + '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;
}