Earlier we've used DEBUG
macros to investigate when EFI_HII_CONFIG_ACCESS_PROTOCOL.CallBack()
function is called. It is a little cumbersome, since we had to build our driver in debug mode and constantly monitor OVMF log for messages. So let's learn how to create popup windows in edk2.
UEFI specification defines the following protocol for that:
EFI_HII_POPUP_PROTOCOL
Summary:
This protocol provides services to display a popup window.
The protocol is typically produced by the forms browser and consumed by a driver’s callback handler.
GUID
#define EFI_HII_POPUP_PROTOCOL_GUID { 0x4311edc0, 0x6054, 0x46d4, { 0x9e, 0x40, 0x89, 0x3e, 0xa9, 0x52, 0xfc, 0xcc } }
Protocol Interface Structure:
typedef struct {
UINT64 Revision;
EFI_HII_CREATE_POPUP CreatePopup;
} EFI_HII_POPUP_PROTOCOL;
Parameters:
Revision Protocol revision
CreatePopup Displays a popup window
The Revision
is just equal to 1 for now, the function that we need is CreatePopup()
:
EFI_HII_POPUP_PROTOCOL.CreatePopup()
Summary:
Displays a popup window.
Prototype:
typedef
EFI_STATUS
(EFIAPI * EFI_HII_CREATE_POPUP) (
IN EFI_HII_POPUP_PROTOCOL *This,
IN EFI_HII_POPUP_STYLE PopupStyle,
IN EFI_HII_POPUP_TYPE PopupType,
EFI_HII_HANDLE HiiHandle
IN EFI_STRING_ID Message,
OUT EFI_HII_POPUP_SELECTION *UserSelection OPTIONAL,
);
Parameters:
This A pointer to the EFI_HII_POPUP_PROTOCOL instance.
PopupStyle Popup style to use
PopupType Type of the popup to display
HiiHandle HII handle of the string pack containing Message
Message A message to display in the popup box.
UserSelection User selection
Description:
The CreatePopup() function displays a modal message box that contains string specified by Message. Explicit line break characters can be used to specify a multi-line message. A popup window may contain user selectable options. The option selected by a user is returned via an optional UserSelection parameter.
In the nutshell this function creates a popup looking like this:
+-------------------------------------------+
| ERROR/WARNING/INFO |
|-------------------------------------------|
| popup messages |
| |
| user selectable options |
+-------------------------------------------+
There are 3 possible popup window styles (PopupStyle
argument) - Info, Warning, Error:
typedef enum {
EfiHiiPopupStyleInfo,
EfiHiiPopupStyleWarning,
EfiHiiPopupStyleError
} EFI_HII_POPUP_STYLE;
And 4 possible popup window types (PopupType
argument) - Ok, Ok-Cancel, Yes-No, Yes-No-Cancel:
typedef enum {
EfiHiiPopupTypeOk,
EfiHiiPopupTypeOkCancel,
EfiHiiPopupTypeYesNo,
EfiHiiPopupTypeYesNoCancel
} EFI_HII_POPUP_TYPE;
Based on the option that user has selected, the return parameter UserSelection
would have one of these values:
typedef enum {
EfiHiiPopupSelectionOk,
EfiHiiPopupSelectionCancel,
EfiHiiPopupSelectionYes,
EfiHiiPopupSelectionNo
} EFI_HII_POPUP_SELECTION;
If you want to look at the function implementation check out https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/DisplayEngineDxe/Popup.c.
Now let's transform our driver code to output popup instead of the DEBUG
macro. We'll abstract all the functionality in the HIIPopupCallbackInfo
function:
STATIC
EFI_STATUS
EFIAPI
Callback (
IIN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN EFI_BROWSER_ACTION Action,
IN EFI_QUESTION_ID QuestionId,
IN UINT8 Type,
IN OUT EFI_IFR_TYPE_VALUE *Value,
OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
)
{
//DEBUG ((EFI_D_INFO, "Callback: Action=%s, QuestionId=0x%04x, Type=%s, Value=", ActionToStr(Action), QuestionId, TypeToStr(Type)));
//DebugCallbackValue(Type, Value);
HIIPopupCallbackInfo(Action, QuestionId, Type, Value);
return EFI_UNSUPPORTED;
}
The trick with a separate DEBUG
statement (hidden inside the DebugCallbackValue
) for the Value
printing will not work here, since we can pass exactly 1 string to the EFI_HII_POPUP_PROTOCOL.CreatePopup()
. So let's write CallbackValueToStr
that utilizes UnicodeSPrint
function to return a string. Let's return it in an argument, so the caller would be responsible for the memory allocation/deallocation:
VOID CallbackValueToStr(UINT8 Type, EFI_IFR_TYPE_VALUE *Value, EFI_STRING* ValueStr, UINTN ValueStrSize)
{
switch (Type) {
case EFI_IFR_TYPE_NUM_SIZE_8:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%d", Value->u8);
break;
case EFI_IFR_TYPE_NUM_SIZE_16:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%d", Value->u16);
break;
case EFI_IFR_TYPE_NUM_SIZE_32:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%d", Value->u32);
break;
case EFI_IFR_TYPE_NUM_SIZE_64:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%ld", Value->u64);
break;
case EFI_IFR_TYPE_BOOLEAN:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%d", Value->b);
break;
case EFI_IFR_TYPE_TIME:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%02d:%02d:%02d", Value->time.Hour, Value->time.Minute, Value->time.Second);
break;
case EFI_IFR_TYPE_DATE:
UnicodeSPrint(*ValueStr, ValueStrSize, L"%04d/%02d/%02d", Value->date.Year, Value->date.Month, Value->date.Day);
break;
case EFI_IFR_TYPE_STRING:
if (Value->string)
UnicodeSPrint(*ValueStr, ValueStrSize, L"%s", HiiGetString(mHiiHandle, Value->string, "en-US"));
else
UnicodeSPrint(*ValueStr, ValueStrSize, L"NO STRING!");
break;
default:
UnicodeSPrint(*ValueStr, ValueStrSize, L"Unknown");
break;
}
}
Now to the HIIPopupCallbackInfo
. First we need to locate the EFI_HII_POPUP_PROTOCOL
protocol:
EFI_STATUS Status;
EFI_HII_POPUP_PROTOCOL* HiiPopup;
Status = gBS->LocateProtocol(&gEfiHiiPopupProtocolGuid,
NULL,
(VOID **)&HiiPopup);
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_ERROR, "Error! Can't find EFI_HII_POPUP_PROTOCOL\n"));
return;
}
Don't forget to add the necessary include statement:
#include <Protocol/HiiPopup.h>
And add gEfiHiiPopupProtocolGuid
to the driver INF file:
[Protocols]
...
gEfiHiiPopupProtocolGuid
Now we need to create a string that would be printed in the popup message. Since the function accepts the argument in a form of EFI_STRING_ID
, we need to add a string to our Strings.uni
file:
#string POPUP_MESSAGE #language en-US "Popup message"
Let's fill it dynamically with the HiiSetString
:
UINTN Size = 300;
EFI_STRING ValueStr = AllocateZeroPool(Size);
CallbackValueToStr(Type, Value, &ValueStr, Size);
EFI_STRING PopupStr = AllocateZeroPool(Size);
UnicodeSPrint(PopupStr, Size, L"Callback:\nAction=%s\nQuestionId=0x%04x\nType=%s\nValue=%s", ActionToStr(Action), QuestionId, TypeToStr(Type), ValueStr);
FreePool(ValueStr);
HiiSetString(mHiiHandle, STRING_TOKEN(POPUP_MESSAGE), PopupStr, NULL);
<...>
FreePool(PopupStr);
All the arguments are in place, so we can call the CreatePopup
function:
EFI_HII_POPUP_SELECTION UserSelection;
Status = HiiPopup->CreatePopup(HiiPopup,
EfiHiiPopupStyleInfo,
EfiHiiPopupTypeOkCancel,
mHiiHandle,
STRING_TOKEN(POPUP_MESSAGE),
&UserSelection
);
Here is a complete code for the HIIPopupCallbackInfo
:
VOID HIIPopupCallbackInfo(EFI_BROWSER_ACTION Action, EFI_QUESTION_ID QuestionId, UINT8 Type, EFI_IFR_TYPE_VALUE* Value)
{
EFI_STATUS Status;
EFI_HII_POPUP_PROTOCOL* HiiPopup;
Status = gBS->LocateProtocol(&gEfiHiiPopupProtocolGuid,
NULL,
(VOID **)&HiiPopup);
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_ERROR, "Error! Can't find EFI_HII_POPUP_PROTOCOL\n"));
return;
}
UINTN Size = 300;
EFI_STRING ValueStr = AllocateZeroPool(Size);
CallbackValueToStr(Type, Value, &ValueStr, Size);
EFI_STRING PopupStr = AllocateZeroPool(Size);
UnicodeSPrint(PopupStr, Size, L"Callback:\nAction=%s\nQuestionId=0x%04x\nType=%s\nValue=%s", ActionToStr(Action), QuestionId, TypeToStr(Type), ValueStr);
FreePool(ValueStr);
HiiSetString(mHiiHandle, STRING_TOKEN(POPUP_OK_MESSAGE), PopupStr, NULL);
EFI_HII_POPUP_SELECTION UserSelection;
Status = HiiPopup->CreatePopup(HiiPopup,
EfiHiiPopupStyleInfo,
EfiHiiPopupTypeOk,
mHiiHandle,
STRING_TOKEN(POPUP_OK_MESSAGE),
&UserSelection
);
FreePool(PopupStr);
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_ERROR, "Error! Can't create popup, %r\n", Status));
return;
}
}
Here we've used EfiHiiPopupStyleInfo
/EfiHiiPopupTypeOk
arguments to style our popup.
As you remember the first callback call that is executed in our case was:
Callback: Action=EFI_BROWSER_ACTION_FORM_OPEN, QuestionId=0x5555, Type=EFI_IFR_TYPE_BOOLEAN, Value=0
Here is how it looks now in a form of a popup message. As you can see it is printed by the browser before even drawing our form:
There is not much different in style between the EfiHiiPopupStyleInfo
/EfiHiiPopupStyleWarning
/EfiHiiPopupStyleError
. This just changes the popup header.
For example here is the same popup with a style EfiHiiPopupStyleWarning
:
And with the style EfiHiiPopupStyleError
:
Changing the popup type between the EfiHiiPopupTypeOk
/EfiHiiPopupTypeOkCancel
/EfiHiiPopupTypeYesNo
/EfiHiiPopupTypeYesNoCancel
modifies the user selection options in the bottom of the window. For example here is how EfiHiiPopupTypeOkCancel
look like:
I hope you can imagine how EfiHiiPopupTypeYesNo
/EfiHiiPopupTypeYesNoCancel
look like. In case you need to check what the option user pressed, you just check the return argument EFI_HII_POPUP_SELECTION UserSelection
.
Before the UEFI specification has introduced the EFI_HII_POPUP_PROTOCOL.CreatePopup()
function, edk2 had its own out-of-spec function for popup creation defined in the UefiLib
- CreatePopUp
:
/**
Draws a dialog box to the console output device specified by
ConOut defined in the EFI_SYSTEM_TABLE and waits for a keystroke
from the console input device specified by ConIn defined in the
EFI_SYSTEM_TABLE.
If there are no strings in the variable argument list, then ASSERT().
If all the strings in the variable argument list are empty, then ASSERT().
@param[in] Attribute Specifies the foreground and background color of the popup.
@param[out] Key A pointer to the EFI_KEY value of the key that was
pressed. This is an optional parameter that may be NULL.
If it is NULL then no wait for a keypress will be performed.
@param[in] ... The variable argument list that contains pointers to Null-
terminated Unicode strings to display in the dialog box.
The variable argument list is terminated by a NULL.
**/
VOID
EFIAPI
CreatePopUp (
IN UINTN Attribute,
OUT EFI_INPUT_KEY *Key OPTIONAL,
...
)
If you are interested in the implementation you can find it here.
So as you can see the function prototype is different. Because of that this function can be usefull in different scenarious. Since the EFI_HII_POPUP_PROTOCOL.CreatePopup()
function is closely interlinked with the HII infrastructure, you need to have EFI_STRING_ID
from some EFI_HII_HANDLE
to call it. It gives you a possibility for multilanguage strings, but makes it hard to use in simple console UEFI_APPLICATION
s.
On the other case this CreatePopUp
function gives you different control for the popup window. There no user selector options, but you can monitor the exact key that user presses and have more control on the color style of the window.
For the color style check out the defines form the https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/SimpleTextOut.h. There several options for the color:
#define EFI_BLACK 0x00
#define EFI_BLUE 0x01
#define EFI_GREEN 0x02
#define EFI_CYAN 0x03
#define EFI_RED 0x04
#define EFI_MAGENTA 0x05
#define EFI_BROWN 0x06
#define EFI_LIGHTGRAY 0x07
#define EFI_BRIGHT 0x08
#define EFI_DARKGRAY 0x08
#define EFI_LIGHTBLUE 0x09
#define EFI_LIGHTGREEN 0x0A
#define EFI_LIGHTCYAN 0x0B
#define EFI_LIGHTRED 0x0C
#define EFI_LIGHTMAGENTA 0x0D
#define EFI_YELLOW 0x0E
#define EFI_WHITE 0x0F
You can create different foreground/backgroud color combinations with the:
#define EFI_TEXT_ATTR(Foreground, Background) ((Foreground) | ((Background) << 4))
If you don't want to use the EFI_TEXT_ATTR
macro, for some reason there are also separate defines for the background color:
#define EFI_BACKGROUND_BLACK 0x00
#define EFI_BACKGROUND_BLUE 0x10
#define EFI_BACKGROUND_GREEN 0x20
#define EFI_BACKGROUND_CYAN 0x30
#define EFI_BACKGROUND_RED 0x40
#define EFI_BACKGROUND_MAGENTA 0x50
#define EFI_BACKGROUND_BROWN 0x60
#define EFI_BACKGROUND_LIGHTGRAY 0x70
So you can use (EFI_WHITE | EFI_BACKGROUND_BLACK)
or EFI_TEXT_ATTR(EFI_WHITE, EFI_BLACK)
and get the same result. EFI_TEXT_ATTR
macro is not widely used in the edk2 code, I guess that can be connected to the fact that is not easy to remember where is foreground, and where is backgroud in the text like EFI_TEXT_ATTR(EFI_WHITE, EFI_BLACK)
.
Anyway, let's try to use CreatePopUp
function. Couple of notices:
- The function returns not when user presses ENTER, but when he presses any key. So if you want to react only to the ENTER key, you need to check returned value of the
EFI_INPUT_KEY
argument similar to this:
EFI_INPUT_KEY Key;
...
do {
CreatePopUp(..., &Key, ...);
} while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
- The function doesn't display correctly multiline strings with
\n
symbol inside. If you want a multiline popup message, you need to pass each string one by one ending withNULL
.
With that in mind let's write a function PopupCallbackInfo
that uses CreatePopUp
call:
VOID PopupCallbackInfo(EFI_BROWSER_ACTION Action, EFI_QUESTION_ID QuestionId, UINT8 Type, EFI_IFR_TYPE_VALUE* Value)
{
EFI_INPUT_KEY Key;
UINTN Size = 100;
EFI_STRING ActionStr = AllocateZeroPool(Size);
EFI_STRING QuestionIdStr = AllocateZeroPool(Size);
EFI_STRING TypeStr = AllocateZeroPool(Size);
EFI_STRING ValueStr = AllocateZeroPool(Size);
EFI_STRING ValStr = AllocateZeroPool(Size);
CallbackValueToStr(Type, Value, &ValStr, Size);
UnicodeSPrint(ActionStr, Size, L"Action=%s", ActionToStr(Action));
UnicodeSPrint(QuestionIdStr, Size, L"QuestionId=0x%04x", QuestionId);
UnicodeSPrint(TypeStr, Size, L"Type=%s", TypeToStr(Type));
UnicodeSPrint(ValueStr, Size, L"Value=%s", ValStr);
do {
CreatePopUp(EFI_TEXT_ATTR(EFI_LIGHTGRAY, EFI_BLUE), &Key, L"Callback:", ActionStr, QuestionIdStr, TypeStr, ValueStr, NULL);
} while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
FreePool(ActionStr);
FreePool(QuestionIdStr);
FreePool(TypeStr);
FreePool(ValueStr);
FreePool(ValStr);
}
This is how a popup would look like in this case:
You can compare the style of this popup with the window from the EFI_HII_POPUP_PROTOCOL.CreatePopup()
.
With the CreatePopUp
you can get wild with color. Here is window styled with the EFI_TEXT_ATTR(EFI_RED, EFI_BLACK)
If you really want to, you can use popups in console applications. Just be aware, that you need to clear the console yourself after each call, because on the other case the popup will be left on the screen even when you've exited the popup window.
Here is an example when application has issued two popup messages. The first one was created with the EFI_HII_POPUP_PROTOCOL.CreatePopup()
, and the second one with the CreatePopUp
call. As you can see they've overlayered each other, and the screen wasn't cleared even after the application has finished its execution.
To clear the screen in such cases you can use:
gST->ConOut->ClearScreen(gST->ConOut);