From c6643125e109bbe92d7db1c7fe9ccb6d780c89f0 Mon Sep 17 00:00:00 2001 From: Aiken Harris Date: Wed, 27 Aug 2025 19:15:38 +0200 Subject: [PATCH] Implement boot entry editor --- xtldr/config.c | 229 +++++++++++++++++++- xtldr/globals.c | 8 + xtldr/includes/globals.h | 3 + xtldr/includes/xtldr.h | 33 +++ xtldr/textui.c | 456 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 696 insertions(+), 33 deletions(-) diff --git a/xtldr/config.c b/xtldr/config.c index 68d7e61..60c4f66 100644 --- a/xtldr/config.c +++ b/xtldr/config.c @@ -4,11 +4,84 @@ * FILE: xtldr/config.c * DESCRIPTION: XT Boot Loader Configuration * DEVELOPERS: Rafal Kupiec + * Aiken Harris */ #include +/** + * @brief Retrieves the value of a specific OS boot option from a list. + * + * @param Options + * A pointer to the head of a list of XTBL_CONFIG_ENTRY structures. + * + * @param OptionName + * A pointer to wide string that contains the name of the boot option to retrieve. + * + * @param OptionValue + * A pointer to a variable that receives a pointer to the retrieved boot option's value. + * + * @return This routine returns a status code. + * + * @since XT 1.0 + */ +XTCDECL +EFI_STATUS +BlGetBootOptionValue(IN PLIST_ENTRY Options, + IN CONST PWCHAR OptionName, + OUT PWCHAR *OptionValue) +{ + PXTBL_CONFIG_ENTRY ConfigEntry; + PLIST_ENTRY ConfigList; + ULONG KeyLength, ValueLength; + EFI_STATUS Status; + + /* Assume the option will not be found */ + *OptionValue = NULL; + + /* Get the length of the option name we are looking for */ + KeyLength = RtlWideStringLength(OptionName, 0); + + /* Start iterating from the first entry in the options list */ + ConfigList = Options->Flink; + while(ConfigList != Options) + { + /* Get the container record for the current config entry */ + ConfigEntry = CONTAIN_RECORD(ConfigList, XTBL_CONFIG_ENTRY, Flink); + + /* Compare the current entry's name with the requested option name */ + if(RtlCompareWideStringInsensitive(ConfigEntry->Name, OptionName, KeyLength) == 0) + { + /* Found the option, now prepare to copy its value */ + ValueLength = RtlWideStringLength(ConfigEntry->Value, 0); + + /* Allocate memory for the output value string */ + Status = BlAllocateMemoryPool((ValueLength + 1) * sizeof(WCHAR), (PVOID *)OptionValue); + if(Status != STATUS_EFI_SUCCESS) + { + /* Memory allocation failure, print debug message and return status code */ + BlDebugPrint(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status); + *OptionValue = NULL; + return Status; + } + + /* Copy the value and NULL-terminate the new string */ + RtlCopyMemory(*OptionValue, ConfigEntry->Value, ValueLength * sizeof(WCHAR)); + (*OptionValue)[ValueLength] = L'\0'; + + /* Successfully retrieved the option value, return success */ + return STATUS_EFI_SUCCESS; + } + + /* Move to the next entry in the list */ + ConfigList = ConfigList->Flink; + } + + /* Option not found */ + return STATUS_EFI_NOT_FOUND; +} + /** * Returns a boolean value of the specified configuration key. * @@ -101,6 +174,156 @@ BlGetConfigValue(IN CONST PWCHAR ConfigName) return NULL; } +/** + * Retrieves the list of user-editable boot options. + * + * @param OptionsArray + * A pointer to a variable that will receive the pointer to the array of editable option names. + * + * @param OptionsCount + * A pointer to a variable that will be updated with the number of elements in the OptionsArray. + * + * @return This routine does not return any value. + * + * @since XT 1.0 + */ +XTCDECL +VOID +BlGetEditableOptions(OUT CONST PWCHAR **OptionsArray, + OUT PSIZE_T OptionsCount) +{ + ULONG Count = 0; + + /* Return a pointer to the global array of editable options */ + *OptionsArray = BlpEditableConfigOptions; + + /* Calculate the number of elements in the array */ + while(BlpEditableConfigOptions[Count]) + { + Count++; + } + + /* Return the number of elements */ + *OptionsCount = Count; +} + +/** + * Sets the value of a specific OS boot option in a list, or adds it if it doesn't exist. + * + * @param Options + * A pointer to the head of a list of XTBL_CONFIG_ENTRY structures. + * + * @param OptionName + * A pointer to a wide string that contains the name of the boot option to set. + * + * @param OptionValue + * A pointer to a wide string that contains the new value for the boot option. + * + * @return This routine returns a status code. + * + * @since XT 1.0 + */ +XTCDECL +EFI_STATUS +BlSetBootOptionValue(IN PLIST_ENTRY Options, + IN CONST PWCHAR OptionName, + IN CONST PWCHAR OptionValue) +{ + PXTBL_CONFIG_ENTRY ConfigEntry; + PLIST_ENTRY ConfigList; + ULONG Length; + EFI_STATUS Status; + + /* Get the length of the option name we are looking for */ + Length = RtlWideStringLength(OptionName, 0); + + /* Start iterating from the first entry in the options list */ + ConfigList = Options->Flink; + while(ConfigList != Options) + { + /* Get the container record for the current config entry */ + ConfigEntry = CONTAIN_RECORD(ConfigList, XTBL_CONFIG_ENTRY, Flink); + + /* Compare the current entry's name with the requested option name */ + if(RtlCompareWideStringInsensitive(ConfigEntry->Name, OptionName, Length) == 0) + { + /* Found the option, get its length */ + Length = RtlWideStringLength(OptionValue, 0); + + /* Reallocate memory for the new value */ + Status = BlFreeMemoryPool(ConfigEntry->Value); + if(Status != STATUS_EFI_SUCCESS) + { + /* Failed to free memory, return status code */ + return Status; + } + + /* Allocate new memory for the updated value */ + Status = BlAllocateMemoryPool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Value); + if(Status != STATUS_EFI_SUCCESS) + { + /* Memory allocation failure, print debug message and return status code */ + BlDebugPrint(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status); + return Status; + } + + /* Copy the value and NULL-terminate the new string */ + RtlCopyMemory(ConfigEntry->Value, OptionValue, Length * sizeof(WCHAR)); + ConfigEntry->Value[Length] = L'\0'; + return STATUS_EFI_SUCCESS; + } + + /* Move to the next entry in the list */ + ConfigList = ConfigList->Flink; + } + + /* Option not found, allocate memory for the new one */ + Status = BlAllocateMemoryPool(sizeof(XTBL_CONFIG_ENTRY), (PVOID *)&ConfigEntry); + if(Status != STATUS_EFI_SUCCESS) + { + /* Memory allocation failure, print debug message and return status code */ + BlDebugPrint(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status); + return Status; + } + + /* Allocate memory for the option name */ + Length = RtlWideStringLength(OptionName, 0); + Status = BlAllocateMemoryPool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Name); + if(Status != STATUS_EFI_SUCCESS) + { + /* Memory allocation failure, print debug message and return status code */ + BlDebugPrint(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status); + BlFreeMemoryPool(ConfigEntry); + return Status; + } + + /* Copy the option name and NULL-terminate the new string */ + RtlCopyMemory(ConfigEntry->Name, OptionName, Length * sizeof(WCHAR)); + ConfigEntry->Name[Length] = L'\0'; + + /* Allocate memory for the option value */ + Length = RtlWideStringLength(OptionValue, 0); + Status = BlAllocateMemoryPool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Value); + if(Status != STATUS_EFI_SUCCESS) + { + /* Memory allocation failure, print debug message and return status code */ + BlDebugPrint(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status); + BlFreeMemoryPool(ConfigEntry->Name); + BlFreeMemoryPool(ConfigEntry); + return Status; + } + + /* Copy the value and NULL-terminate the new string */ + RtlCopyMemory(ConfigEntry->Value, OptionValue, Length * sizeof(WCHAR)); + ConfigEntry->Value[Length] = L'\0'; + + /* Insert the new config entry at the end of the options list */ + RtlInsertTailList(Options, &ConfigEntry->Flink); + + /* Return success */ + return STATUS_EFI_SUCCESS; +} + /** * Updates existing configuration value. * @@ -110,7 +333,7 @@ BlGetConfigValue(IN CONST PWCHAR ConfigName) * @param ConfigValue * Specifies the new configuration value. * - * @return This routine returns status code. + * @return This routine returns a status code. * * @since XT 1.0 */ @@ -174,7 +397,7 @@ BlSetConfigValue(IN CONST PWCHAR ConfigName, /** * Loads and parses XTLDR configuration file. * - * @return This routine returns status code. + * @return This routine returns a status code. * * @since XT 1.0 */ @@ -568,7 +791,7 @@ BlpParseConfigFile(IN CONST PCHAR RawConfig, * @param ConfigData * Provides a buffer to store the data read from the configuration file. * - * @return This routine returns status code. + * @return This routine returns a status code. * * @since XT 1.0 */ diff --git a/xtldr/globals.c b/xtldr/globals.c index 7c8db0e..d88b201 100644 --- a/xtldr/globals.c +++ b/xtldr/globals.c @@ -21,6 +21,13 @@ LIST_ENTRY BlpConfig; /* XT Boot Loader loaded configuration */ LIST_ENTRY BlpConfigSections; +/* List of user-editable boot options */ +PWCHAR BlpEditableConfigOptions[] = { + L"BootModules", L"SystemType", L"SystemPath", + L"KernelFile", L"InitrdFile", L"HalFile", + L"Parameters", NULL +}; + /* XT Boot Loader protocol */ XTBL_LOADER_PROTOCOL BlpLdrProtocol; @@ -41,3 +48,4 @@ EFI_HANDLE EfiImageHandle; /* EFI System Table */ PEFI_SYSTEM_TABLE EfiSystemTable; + diff --git a/xtldr/includes/globals.h b/xtldr/includes/globals.h index f86dd83..5798f47 100644 --- a/xtldr/includes/globals.h +++ b/xtldr/includes/globals.h @@ -24,6 +24,9 @@ EXTERN LIST_ENTRY BlpConfig; /* XT Boot Loader loaded configuration */ EXTERN LIST_ENTRY BlpConfigSections; +/* List of user-editable boot options */ +EXTERN PWCHAR BlpEditableConfigOptions[]; + /* XT Boot Loader protocol */ EXTERN XTBL_LOADER_PROTOCOL BlpLdrProtocol; diff --git a/xtldr/includes/xtldr.h b/xtldr/includes/xtldr.h index b008f89..25bad02 100644 --- a/xtldr/includes/xtldr.h +++ b/xtldr/includes/xtldr.h @@ -71,6 +71,10 @@ XTCDECL VOID BlDisplayBootMenu(); +XTCDECL +VOID +BlDisplayEditMenu(IN PXTBL_BOOTMENU_ITEM MenuEntry); + XTCDECL VOID BlDisplayErrorDialog(IN PWCHAR Caption, @@ -134,6 +138,12 @@ BOOLEAN BlGetBooleanParameter(IN CONST PWCHAR Parameters, IN CONST PWCHAR Needle); +XTCDECL +EFI_STATUS +BlGetBootOptionValue(IN PLIST_ENTRY Options, + IN CONST PWCHAR OptionName, + OUT PWCHAR *OptionValue); + XTCDECL BOOLEAN BlGetConfigBooleanValue(IN CONST PWCHAR ConfigName); @@ -147,6 +157,11 @@ EFI_STATUS BlGetConfigurationTable(IN PEFI_GUID TableGuid, OUT PVOID *Table); +XTCDECL +VOID +BlGetEditableOptions(OUT CONST PWCHAR **OptionsArray, + OUT PSIZE_T OptionsCount); + XTCDECL EFI_STATUS BlGetEfiPath(IN PWCHAR SystemPath, @@ -332,6 +347,12 @@ XTCDECL VOID BlResetConsoleInputBuffer(); +XTCDECL +EFI_STATUS +BlSetBootOptionValue(IN PLIST_ENTRY Options, + IN CONST PWCHAR OptionName, + IN CONST PWCHAR OptionValue); + XTCDECL EFI_STATUS BlSetConfigValue(IN CONST PWCHAR ConfigName, @@ -456,6 +477,18 @@ VOID BlpDrawDialogProgressBar(IN PXTBL_DIALOG_HANDLE Handle, IN UCHAR Percentage); +XTCDECL +VOID +BlpDrawEditMenu(OUT PXTBL_DIALOG_HANDLE Handle); + +XTCDECL +EFI_STATUS +BlpDrawEditMenuEntry(IN PXTBL_DIALOG_HANDLE Handle, + IN PWCHAR OptionName, + IN PWCHAR OptionValue, + IN UINT Position, + IN BOOLEAN Highlighted); + XTCDECL PEFI_DEVICE_PATH_PROTOCOL BlpDuplicateDevicePath(IN PEFI_DEVICE_PATH_PROTOCOL DevicePath); diff --git a/xtldr/textui.c b/xtldr/textui.c index f69dbb7..ff7c523 100644 --- a/xtldr/textui.c +++ b/xtldr/textui.c @@ -107,6 +107,8 @@ BlDisplayBootMenu() BlpDrawBootMenuEntry(&Handle, MenuEntries[TopVisibleEntry + Index].EntryName, Index, (TopVisibleEntry + Index) == HighligtedEntryId); } + + /* Clear redraw entries flag */ RedrawEntries = FALSE; } } @@ -198,6 +200,7 @@ BlDisplayBootMenu() if(HighligtedEntryId > 0) { /* Highlight previous entry */ + OldHighligtedEntryId = HighligtedEntryId; HighligtedEntryId--; /* Check if we need to scroll the view */ @@ -214,9 +217,6 @@ BlDisplayBootMenu() OldHighligtedEntryId - TopVisibleEntry, FALSE); BlpDrawBootMenuEntry(&Handle, MenuEntries[HighligtedEntryId].EntryName, HighligtedEntryId - TopVisibleEntry, TRUE); - - /* Update old highlighted entry */ - OldHighligtedEntryId = HighligtedEntryId; } } else if(Key.ScanCode == 0x02) @@ -225,6 +225,7 @@ BlDisplayBootMenu() if(HighligtedEntryId < NumberOfEntries - 1) { /* Highlight next entry */ + OldHighligtedEntryId = HighligtedEntryId; HighligtedEntryId++; /* Check if we need to scroll the view */ @@ -241,9 +242,6 @@ BlDisplayBootMenu() OldHighligtedEntryId - TopVisibleEntry, FALSE); BlpDrawBootMenuEntry(&Handle, MenuEntries[HighligtedEntryId].EntryName, HighligtedEntryId - TopVisibleEntry, TRUE); - - /* Update old highlighted entry */ - OldHighligtedEntryId = HighligtedEntryId; } } else if(Key.ScanCode == 0x09) @@ -326,7 +324,7 @@ BlDisplayBootMenu() else if(Key.UnicodeChar == 0x65) { /* 'e' key pressed, edit the highlighted entry */ - BlDisplayErrorDialog(L"XTLDR", L"Editing boot menu entries is not implemented yet!"); + BlDisplayEditMenu(&MenuEntries[HighligtedEntryId]); RedrawBootMenu = TRUE; /* Break from boot menu event loop to redraw whole boot menu */ @@ -379,6 +377,232 @@ BlDisplayBootMenu() } } +/** + * Displays a simple TUI-based edit menu. + * + * @return This routine does not return any value. + * + * @since XT 1.0 + */ +XTCDECL +VOID +BlDisplayEditMenu(IN PXTBL_BOOTMENU_ITEM MenuEntry) +{ + ULONG HighligtedOptionId, Index, NumberOfOptions, OldHighligtedOptionId, TopVisibleEntry, VisibleEntries; + XTBL_DIALOG_HANDLE Handle; + BOOLEAN RedrawEditMenu, RedrawEntries; + EFI_INPUT_KEY Key; + UINT_PTR EventIndex; + PWCHAR NewValue, OptionName, OriginalValue, Value, ValueToEdit; + CONST PWCHAR *EditableOptions; + + /* Draw edit menu */ + BlpDrawEditMenu(&Handle); + + /* Get the list of user editable options */ + BlGetEditableOptions(&EditableOptions, &NumberOfOptions); + + /* Calculate how many entries can be visible in the menu box */ + VisibleEntries = Handle.Height - 2; + + /* Assume the first option is highlighted by default */ + HighligtedOptionId = 0; + OldHighligtedOptionId = 0; + TopVisibleEntry = 0; + + /* Set redraw flags to not redraw the menu itself, but fill it with entries */ + RedrawEditMenu = FALSE; + RedrawEntries = TRUE; + + /* Infinite edit menu loop */ + while(TRUE) + { + /* Redraw edit menu frame if requested */ + if(RedrawEditMenu) + { + BlpDrawEditMenu(&Handle); + RedrawEditMenu = FALSE; + RedrawEntries = TRUE; + } + + /* Sanity check to ensure we do not display more entries than possible */ + if(VisibleEntries > NumberOfOptions) + { + VisibleEntries = NumberOfOptions; + } + + /* Check if we need to redraw boot menu entries */ + if(RedrawEntries) + { + /* Iterate through all menu entries */ + for(Index = 0; Index < VisibleEntries; Index++) + { + /* Draw menu entry */ + BlGetBootOptionValue(MenuEntry->Options, EditableOptions[TopVisibleEntry + Index], &Value); + BlpDrawEditMenuEntry(&Handle, EditableOptions[TopVisibleEntry + Index], Value, Index, + (TopVisibleEntry + Index) == HighligtedOptionId); + + /* Free allocated value string if needed */ + if(Value != NULL) + { + BlFreeMemoryPool(Value); + } + } + + /* Clear redraw entries flag */ + RedrawEntries = FALSE; + } + + /* Wait for EFI event and read key stroke */ + BlWaitForEfiEvent(1, &EfiSystemTable->ConIn->WaitForKey, &EventIndex); + BlReadKeyStroke(&Key); + + /* Check key press scan code */ + if(Key.UnicodeChar == 0x0D) + { + /* ENTER key pressed, edit the highlighted option */ + OptionName = EditableOptions[HighligtedOptionId]; + BlGetBootOptionValue(MenuEntry->Options, OptionName, &OriginalValue); + + /* If the original value is NULL, use an empty string for editing */ + if(OriginalValue == NULL) + { + ValueToEdit = L""; + } + else + { + ValueToEdit = OriginalValue; + } + + /* Display input dialog to edit the option value */ + NewValue = ValueToEdit; + BlDisplayInputDialog(OptionName, L"Enter new value:", &NewValue); + + /* Check if the value was changed */ + if(NewValue != ValueToEdit) + { + /* Update the boot option with the new value and free the old value */ + BlSetBootOptionValue(MenuEntry->Options, OptionName, NewValue); + BlFreeMemoryPool(NewValue); + } + + /* Free the original value if it was allocated */ + if(OriginalValue != NULL) + { + BlFreeMemoryPool(OriginalValue); + } + + /* Mark the edit menu for redraw */ + RedrawEditMenu = TRUE; + } + else if(Key.ScanCode == 0x01) + { + /* UpArrow key pressed, go to previous entry if possible */ + if(HighligtedOptionId > 0) + { + /* Highlight previous entry */ + OldHighligtedOptionId = HighligtedOptionId; + HighligtedOptionId--; + + /* Check if we need to scroll the view */ + if(HighligtedOptionId < TopVisibleEntry) + { + /* Scroll the view */ + TopVisibleEntry = HighligtedOptionId; + RedrawEntries = TRUE; + continue; + } + + /* Redraw old highlighted entry */ + BlGetBootOptionValue(MenuEntry->Options, EditableOptions[OldHighligtedOptionId], &Value); + BlpDrawEditMenuEntry(&Handle, EditableOptions[OldHighligtedOptionId], Value, OldHighligtedOptionId - TopVisibleEntry, FALSE); + + /* Free allocated value string if needed */ + if(Value != NULL) + { + BlFreeMemoryPool(Value); + } + + /* Redraw new highlighted entry */ + BlGetBootOptionValue(MenuEntry->Options, EditableOptions[HighligtedOptionId], &Value); + BlpDrawEditMenuEntry(&Handle, EditableOptions[HighligtedOptionId], Value, HighligtedOptionId - TopVisibleEntry, TRUE); + + /* Free allocated value string if needed */ + if(Value != NULL) + { + BlFreeMemoryPool(Value); + } + } + } + else if(Key.ScanCode == 0x02) + { + /* DownArrow key pressed, go to next entry if possible */ + if(HighligtedOptionId < NumberOfOptions - 1) + { + /* Highlight next entry */ + OldHighligtedOptionId = HighligtedOptionId; + HighligtedOptionId++; + + /* Check if we need to scroll the view */ + if(HighligtedOptionId >= TopVisibleEntry + VisibleEntries) + { + /* Scroll the view */ + TopVisibleEntry = HighligtedOptionId - VisibleEntries + 1; + RedrawEntries = TRUE; + continue; + } + + /* Redraw old highlighted entry */ + BlGetBootOptionValue(MenuEntry->Options, EditableOptions[OldHighligtedOptionId], &Value); + BlpDrawEditMenuEntry(&Handle, EditableOptions[OldHighligtedOptionId], Value, OldHighligtedOptionId - TopVisibleEntry, FALSE); + + /* Free allocated value string if needed */ + if(Value != NULL) + { + BlFreeMemoryPool(Value); + } + + /* Redraw new highlighted entry */ + BlGetBootOptionValue(MenuEntry->Options, EditableOptions[HighligtedOptionId], &Value); + BlpDrawEditMenuEntry(&Handle, EditableOptions[HighligtedOptionId], Value, HighligtedOptionId - TopVisibleEntry, TRUE); + + /* Free allocated value string if needed */ + if(Value != NULL) + { + BlFreeMemoryPool(Value); + } + } + } + else if(Key.ScanCode == 0x09) + { + /* PageUp key pressed, go to top entry */ + if(HighligtedOptionId != 0) + { + /* Highlight first entry */ + HighligtedOptionId = 0; + TopVisibleEntry = 0; + RedrawEntries = TRUE; + } + } + else if(Key.ScanCode == 0x0A) + { + /* PageDown key pressed, go to bottom entry */ + if(HighligtedOptionId != NumberOfOptions - 1) + { + /* Highlight last entry */ + HighligtedOptionId = NumberOfOptions - 1; + TopVisibleEntry = (NumberOfOptions > VisibleEntries) ? (NumberOfOptions - VisibleEntries) : 0; + RedrawEntries = TRUE; + } + } + else if(Key.ScanCode == 0x17) + { + /* ESC key pressed, exit edit menu */ + break; + } + } +} + /** * Displays a red error dialog box with the specified caption and message. * @@ -505,12 +729,11 @@ XTCDECL VOID BlDisplayInputDialog(IN PWCHAR Caption, IN PWCHAR Message, - IN PWCHAR *InputFieldText) + IN OUT PWCHAR *InputFieldText) { SIZE_T InputFieldLength, TextCursorPosition, TextIndex, TextPosition; XTBL_DIALOG_HANDLE Handle; PWCHAR InputFieldBuffer; - SIZE_T BufferLength; EFI_INPUT_KEY Key; EFI_STATUS Status; UINT_PTR Index; @@ -535,9 +758,11 @@ BlDisplayInputDialog(IN PWCHAR Caption, Key.ScanCode = 0; Key.UnicodeChar = 0; - /* Get initial input text length and allocate a buffer */ - BufferLength = RtlWideStringLength(*InputFieldText, 0); - Status = BlAllocateMemoryPool(BufferLength * sizeof(WCHAR), (PVOID *)&InputFieldBuffer); + /* Determine input field length */ + InputFieldLength = RtlWideStringLength(*InputFieldText, 0); + + /* Allocate a buffer for storing the input field text */ + Status = BlAllocateMemoryPool(2048 * sizeof(WCHAR), (PVOID *)&InputFieldBuffer); if(Status != STATUS_EFI_SUCCESS) { /* Memory allocation failure, print error message and return */ @@ -547,15 +772,8 @@ BlDisplayInputDialog(IN PWCHAR Caption, } /* Copy input text into edit buffer */ - RtlCopyMemory(InputFieldBuffer, *InputFieldText, BufferLength * sizeof(WCHAR)); - InputFieldBuffer[BufferLength] = L'\0'; - - /* Determine input field length */ - InputFieldLength = BufferLength; - if(InputFieldLength > Handle.Width - 8) - { - InputFieldLength = Handle.Width - 8; - } + RtlCopyMemory(InputFieldBuffer, *InputFieldText, InputFieldLength * sizeof(WCHAR)); + InputFieldBuffer[InputFieldLength] = L'\0'; /* Start at first character */ TextPosition = 0; @@ -618,12 +836,16 @@ BlDisplayInputDialog(IN PWCHAR Caption, /* DELETE key pressed, delete character */ if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT) { + /* Check if buffer is not empty */ if(InputFieldLength > 0 && TextPosition < InputFieldLength) { + /* Delete character */ RtlMoveMemory(InputFieldBuffer + TextPosition, InputFieldBuffer + TextPosition + 1, (InputFieldLength - TextPosition) * sizeof(WCHAR)); + + /* Decrement length and null terminate string */ InputFieldLength--; - InputFieldBuffer[InputFieldLength] = 0; + InputFieldBuffer[InputFieldLength] = L'\0'; } } } @@ -632,13 +854,17 @@ BlDisplayInputDialog(IN PWCHAR Caption, /* BACKSPACE key pressed, delete character */ if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT) { + /* Check if buffer is not empty */ if(InputFieldLength > 0 && TextPosition > 0 && TextPosition <= InputFieldLength) { - TextPosition--; + /* Delete character */ RtlMoveMemory(InputFieldBuffer + TextPosition, InputFieldBuffer + TextPosition + 1, (InputFieldLength - TextPosition) * sizeof(WCHAR)); + + /* Decrement length, position and null terminate string */ + TextPosition--; InputFieldLength--; - InputFieldBuffer[InputFieldLength] = 0; + InputFieldBuffer[InputFieldLength] = L'\0'; } } } @@ -653,15 +879,23 @@ BlDisplayInputDialog(IN PWCHAR Caption, /* Other key pressed, add character to the buffer */ if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT && Key.UnicodeChar != 0) { - RtlMoveMemory(InputFieldBuffer + TextPosition + 1, InputFieldBuffer + TextPosition, - (InputFieldLength - TextPosition) * sizeof(WCHAR)); - InputFieldBuffer[TextPosition] = Key.UnicodeChar; - TextPosition++; - InputFieldLength++; - InputFieldBuffer[InputFieldLength] = 0; + /* Check if buffer is full */ + if(InputFieldLength < 2047) + { + /* Insert character at current position */ + RtlMoveMemory(InputFieldBuffer + TextPosition + 1, InputFieldBuffer + TextPosition, + (InputFieldLength - TextPosition) * sizeof(WCHAR)); + InputFieldBuffer[TextPosition] = Key.UnicodeChar; + + /* Increment length, position and null terminate string */ + TextPosition++; + InputFieldLength++; + InputFieldBuffer[InputFieldLength] = L'\0'; + } } } + /* Calculate text index and cursor position */ if(TextPosition > (Handle.Width - 9)) { TextIndex = TextPosition - (Handle.Width - 9); @@ -677,6 +911,7 @@ BlDisplayInputDialog(IN PWCHAR Caption, BlpDrawDialogButton(&Handle); BlpDrawDialogInputField(&Handle, &InputFieldBuffer[TextIndex]); + /* Set cursor position if input field is active */ if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT) { BlSetCursorPosition(Handle.PosX + 4 + TextCursorPosition, Handle.PosY + Handle.Height - 4); @@ -1395,3 +1630,164 @@ BlpDrawDialogProgressBar(IN PXTBL_DIALOG_HANDLE Handle, BlDisableConsoleCursor(); BlConsoleWrite(ProgressBar); } + +/** + * Draws a text-based boot edition menu. + * + * @param Handle + * Supplies a pointer to the edition menu handle. + * + * @return This function does not return any value. + * + * @since XT 1.0 + */ +XTCDECL +VOID +BlpDrawEditMenu(OUT PXTBL_DIALOG_HANDLE Handle) +{ + /* Query console screen resolution */ + BlQueryConsoleMode(&Handle->ResX, &Handle->ResY); + + /* Set boot menu parameters */ + Handle->Attributes = 0; + Handle->DialogColor = EFI_TEXT_BGCOLOR_BLACK; + Handle->TextColor = EFI_TEXT_FGCOLOR_LIGHTGRAY; + Handle->PosX = 3; + Handle->PosY = 3; + Handle->Width = Handle->ResX - 6; + Handle->Height = Handle->ResY - 10; + + /* Clear screen and disable cursor */ + BlSetConsoleAttributes(Handle->DialogColor | Handle->TextColor); + BlClearConsoleScreen(); + BlDisableConsoleCursor(); + + /* Check if debugging enabled */ + if(DEBUG) + { + /* Print debug version of XTLDR banner */ + BlSetCursorPosition((Handle->ResX - 44) / 2, 1); + BlConsolePrint(L"XTLDR Boot Loader v%d.%d (%s-%s)\n", + XTLDR_VERSION_MAJOR, XTLDR_VERSION_MINOR, XTOS_VERSION_DATE, XTOS_VERSION_HASH); + } + else + { + /* Print standard XTLDR banner */ + BlSetCursorPosition((Handle->ResX - 22) / 2, 1); + BlConsolePrint(L"XTLDR Boot Loader v%d.%d\n", XTLDR_VERSION_MAJOR, XTLDR_VERSION_MINOR); + } + + /* Draw empty dialog box for boot menu */ + BlpDrawDialogBox(Handle, L"Edit Options", NULL); + + /* Print help message below the edit menu */ + BlSetCursorPosition(0, Handle->PosY + Handle->Height); + BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); + BlConsolePrint(L" Use cursors to change the selection. Press ENTER key to edit the chosen\n" + L" option or ESC to return to the main boot menu."); +} + +/** + * Draws edit menu entry at the specified position. + * + * @param Handle + * Supplies a pointer to the boot menu handle. + * + * @param OptionName + * Supplies a pointer to the buffer containing a part of the menu entry name (an option name). + * + * @param OptionValue + * Supplies a pointer to the buffer containing a part of the menu entry name (an option value). + * + * @param Position + * Specifies entry position on the list in the boot menu. + * + * @param Highlighted + * Specifies whether this entry should be highlighted or not. + * + * @return This routine does not return any value. + * + * @since XT 1.0 + */ +XTCDECL +EFI_STATUS +BlpDrawEditMenuEntry(IN PXTBL_DIALOG_HANDLE Handle, + IN PWCHAR OptionName, + IN PWCHAR OptionValue, + IN UINT Position, + IN BOOLEAN Highlighted) +{ + BOOLEAN Allocation; + PWCHAR DisplayValue, ShortValue; + UINT Index; + ULONG OptionNameLength, OptionValueLength, OptionWidth; + EFI_STATUS Status; + + /* Assume no allocation was made */ + Allocation = FALSE; + + /* Set display value depending on input */ + DisplayValue = (OptionValue != NULL) ? OptionValue : L""; + + /* Determine lengths */ + OptionNameLength = RtlWideStringLength(OptionName, 0); + OptionValueLength = RtlWideStringLength(DisplayValue, 0); + OptionWidth = Handle->Width - 4 - (OptionNameLength + 2); + + /* Check if value needs to be truncated */ + if(OptionValueLength > OptionWidth) + { + /* Allocate buffer for new, shortened value */ + Status = BlAllocateMemoryPool((OptionWidth + 1) * sizeof(WCHAR), (PVOID *)&ShortValue); + if(Status != STATUS_EFI_SUCCESS) + { + /* Memory allocation failure, print debug message and return */ + BlDebugPrint(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status); + return Status; + } + + /* Copy a desired value length into the allocated buffer and append "..." */ + RtlCopyMemory(ShortValue, DisplayValue, (OptionWidth - 3) * sizeof(WCHAR)); + RtlCopyMemory(ShortValue + OptionWidth - 3, L"...", 3 * sizeof(WCHAR)); + ShortValue[OptionWidth] = L'\0'; + + /* Mark that allocation was made and set new display value */ + Allocation = TRUE; + DisplayValue = ShortValue; + } + + /* Move cursor to the right position */ + BlSetCursorPosition(5, 4 + Position); + + /* Check whether this entry should be highlighted */ + if(Highlighted) + { + /* Highlight this entry */ + BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_LIGHTGRAY | EFI_TEXT_FGCOLOR_BLACK); + } + else + { + /* Set default colors */ + BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); + } + + /* Clear menu entry */ + for(Index = 0; Index < Handle->Width - 4; Index++) + { + BlConsolePrint(L" "); + } + + /* Print menu entry */ + BlSetCursorPosition(5, 4 + Position); + BlConsolePrint(L"%S: %S", OptionName, DisplayValue); + + /* Check if allocation was made */ + if(Allocation) + { + /* Free allocated memory */ + BlFreeMemoryPool(DisplayValue); + } + + /* Return success */ + return STATUS_EFI_SUCCESS; +}