aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_18/README.md
blob: cb566dcf317f2b6e3def938ceb0f182675ce36cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

Let's create an app that can actually handle input from user.

To do this we will need `ReadKeyStroke` function:
```
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.
```

The `EFI_INPUT_KEY` function is defined like this:
```
typedef struct {
 UINT16 ScanCode;
 CHAR16 UnicodeChar;
} EFI_INPUT_KEY;
```


Create `InteractiveApp` with the following code:
```
#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;
}
```

The code is pretty simple, so I hope no detailed explanation is needed.
App asks for the secret symbol and loops until user presses the correct one ('k'), or presses 'q'.

```
FS0:\> InteractiveApp.efi
Try to guess the secret symbol!
To quit press 'q'
ScanCode = 0000, UnicodeChar = 0066 (f)
Wrong!
ScanCode = 0000, UnicodeChar = 0074 (t)
Wrong!
ScanCode = 0000, UnicodeChar = 0073 (s)
Wrong!
ScanCode = 0000, UnicodeChar = 0069 (i)
Wrong!
ScanCode = 0000, UnicodeChar = 0061 (a)
Wrong!
ScanCode = 0000, UnicodeChar = 006E (n)
Wrong!
ScanCode = 0000, UnicodeChar = 006B (k)
Correct!
FS0:\>
```

# Differences in keystroke handling with and without `-nographic` option in QEMU

In our lessons we launch QEMU with a `-nographic` option:
```
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
```

With our app now you can observe how special keys are handled in this mode. For example this is the output from the "PageUp" key:
```
ScanCode = 0017, UnicodeChar = 0000 ( )
Wrong!
ScanCode = 0000, UnicodeChar = 005B ([)
Wrong!
ScanCode = 0000, UnicodeChar = 0035 (5)
Wrong!
ScanCode = 0000, UnicodeChar = 007E (~)
Wrong!
```
But according to the UEFI spec and edk2 header for the `SimpleTextIn` protocol (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/SimpleTextIn.h) "PageUp" key should be displayed as a diferrent scan code, not as 4 symbols starting with the escape scan code:
```
#define SCAN_PAGE_UP    0x0009
...
#define SCAN_ESC        0x0017
```

The problem is in the `-nographic` QEMU option. With it QEMU transforms all special keys to escape sequences like it happens in any console terminal.

If you want to see 'native' behaviour you should run QEMU in graphic mode:
```
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
```
If your system doesn't support native graphics, you can run QEMU with a VNC option. QEMU will create a VNC server on the port that you've provided, and you can connect to that port via VNC client app (like VNC viewer for example https://www.realvnc.com/en/connect/download/viewer/).

This command would produce VNC server on a `127.0.0.1:1` address:
```
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
```

If you connect to QEMU launched with graphics you can observe that the "PageUp" press produces expected code:

![PageUp](PageUp.png?raw=true "PageUp scan code on QEMU with graphics")