> For the complete documentation index, see [llms.txt](https://bob-mcd-team.gitbook.io/uefi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bob-mcd-team.gitbook.io/uefi/uefi-development/boot-option/18.readkeystroke.md).

# 18. ReadKeyStroke 함수로 사용자 입력 처리

이번 장에서는 사용자의 입력을 처리할 수 있는 애플리케이션을 만든다.

사용자의 입력을 처리하기 위해서는 `ReadKeyStroke` 가 필요하다.

```
EFI_SIMPLE_TEXT_INPUT_PROTOCOL.ReadKeyStroke()

Summary:
Reads the next keystroke from the input device.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_INPUT_READ_KEY) (
 IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
 OUT EFI_INPUT_KEY *Key
 );

Parameters:
This 		A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL instance.
Key 		A pointer to a buffer that is filled in with the keystroke information
		for the key that was pressed.

Description:
The ReadKeyStroke() function reads the next keystroke from the input device.

Status Codes Returned:
EFI_SUCCESS 		The keystroke information was returned.
EFI_NOT_READY 		There was no keystroke data available.
EFI_DEVICE_ERROR 	The keystroke information was not returned due to hardware errors.
```

EFI\_INPUT\_KEY 함수는 아래와 같이 정의된다.

```
typedef struct {
 UINT16 ScanCode;
 CHAR16 UnicodeChar;
} EFI_INPUT_KEY;
```

간단한 애플리케이션을 만들어 본다. 아래 코드는 `ReadKeyStroke`를 사용해 사용자의 키 입력을 받는 간단한 프로그램이다. 애플리케이션은 무한 반복으로 사용자가 `k` 를 입력하면 `Correct!` 라는 문자열을 출력하고 종료되고 다른 키를 입력할 경우 `Wrong!` 을 출력하며 프로그램 종료는 `q` 를 입력하여 종료할 수 있다.

애플리케이션을 만드는 방법은 설명하지 않고 아래 코드에서는 `UefiMain`을 사용하기 때문에 `HelloWorld` 애플리케이션의 `*.inf` 파일을 수정하여 사용한다.

```c
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  UINTN Index;
  EFI_INPUT_KEY Key;

  Print(L"Try to guess the secret symbol!\n");
  Print(L"To quit press 'q'\n");

  while(TRUE) {
    gBS->WaitForEvent(1, &(gST->ConIn->WaitForKey), &Index);
    gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
    Print(L"ScanCode = %04x, UnicodeChar = %04x (%c)\n", Key.ScanCode, Key.UnicodeChar, Key.UnicodeChar);

    if (Key.UnicodeChar == 'k') {
      Print(L"Correct!\n");
      break;
    } else if (Key.UnicodeChar == 'q') {
      Print(L"Bye!\n");
      break;
    } else {
      Print(L"Wrong!\n");
    }
  }
  gST->ConIn->Reset(gST->ConIn, FALSE);
  return EFI_SUCCESS;
}
```

<figure><img src="/files/LCcugiLe9inhycuwEmTx" alt=""><figcaption><p>InteractiveApp 실행</p></figcaption></figure>

### QEMU에서 그래픽 옵션이 있는 경우와 없는 경우의 키 입력 차이

QEMU를 `-nographic` 옵션을 사용해 실행한다.

```
qemu-system-x86_64 \
  -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
  -drive format=raw,file=fat:rw:~/UEFI_disk \
  -nographic
```

`InteractiveApp.efi` 을 실행해서 `PageUp` 키를 입력하면 아래와 같이 출력이 나타난다.

```
ScanCode = 0017, UnicodeChar = 0000 ( )
Wrong!
ScanCode = 0000, UnicodeChar = 005B ([)
Wrong!
ScanCode = 0000, UnicodeChar = 0035 (5)
Wrong!
ScanCode = 0000, UnicodeChar = 007E (~)
Wrong!
```

`SimpleTextIn` 프로토콜의  UEFI 스펙과 EDKII 헤더에 따르면 `PageUp` 키는 이스케이프 스캔 코드로 시작하는 4개의 기호가 아닌 다른 스캔 코드로 시작되어야 한다.\
(<https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/SimpleTextIn.h>)

```
#define SCAN_PAGE_UP    0x0009
...
#define SCAN_ESC        0x0017
```

이 문제는 QEMU의 `-nographic` 옵션으로 인해 발생한다. QEMU를 사용하면 모든 특수 키를 변환하여 콘솔 터미널에서 발생하는 것처럼 시퀀스를 이스케이프한다.

정상적인 키 입력을 보기 위해서는 QEMU를 그래픽 모드로 실행한다.

```
qemu-system-x86_64 \
  -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
  -drive format=raw,file=fat:rw:~/UEFI_disk
```

시스템에서 기본 그래픽을 지원하지 않는 경우 VNC 옵션으로 QEMU를 실행할 수 있다.\
QEMU는 제공한 포트에 VNC 서버를 생성하며, VNC 클라이언트 앱을 통해 해당 포트에 연결할 수 있다.\
ex) VNC 뷰어(<https://www.realvnc.com/en/connect/download/viewer/>)

아래 명령은 `127.0.0.1:1` 주소로 VNC 서버를 생성한다.

```
qemu-system-x86_64 \
  -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
  -drive format=raw,file=fat:rw:~/UEFI_disk \
  -vnc :1
```

그래픽 모드로 실행된 QEMU에서 `Interactive.efi` 를 실행해서 `PageUp`키를 입력하면 정상적으로 입력받는 것을 볼 수 있다.

![](/files/O9RZGWfbOEpppVNIT2h9)
