diff --git a/xtldr2/textui.c b/xtldr2/textui.c new file mode 100644 index 0000000..4aa61b6 --- /dev/null +++ b/xtldr2/textui.c @@ -0,0 +1,704 @@ +/** + * PROJECT: ExectOS + * COPYRIGHT: See COPYING.md in the top level directory + * FILE: xtldr/textui.c + * DESCRIPTION: Text console User Interface (TUI) support for XT Boot Loader + * DEVELOPERS: Rafal Kupiec + */ + +#include + + +/* TUI dialog box attributes */ +#define TUI_DIALOG_GENERIC_BOX 1 +#define TUI_DIALOG_ERROR_BOX 2 +#define TUI_DIALOG_ACTIVE_BUTTON 4 +#define TUI_DIALOG_INACTIVE_BUTTON 8 +#define TUI_DIALOG_ACTIVE_INPUT_FIELD 16 +#define TUI_DIALOG_INACTIVE_INPUT_FIELD 32 +#define TUI_DIALOG_PROGRESS_BAR 64 + +#define TUI_MAX_DIALOG_WIDTH 100 + +typedef struct _XTBL_DIALOG_HANDLE +{ + UCHAR Attributes; + UINT_PTR ResX; + UINT_PTR ResY; + UINT_PTR PosX; + UINT_PTR PosY; + UINT_PTR Width; + UINT_PTR Height; +} XTBL_DIALOG_HANDLE, *PXTBL_DIALOG_HANDLE; + +XTCDECL +VOID +BlpDetermineDialogBoxSize(IN PXTBL_DIALOG_HANDLE Handle, + IN PWCHAR Message) +{ + UINT_PTR Width, Height, LineLength; + SIZE_T Index, MessageLength; + UCHAR Attributes; + ULONG Mask; + + /* Set minimum dialog window size */ + Height = 4; + Width = 36; + + /* Zero line length */ + LineLength = 0; + + /* Adjust window height according to enabled components */ + Mask = 1; + Attributes = Handle->Attributes; + while(Mask) + { + /* Check enabled components that affect dialog window size */ + switch(Attributes & Mask) + { + case TUI_DIALOG_ACTIVE_BUTTON: + case TUI_DIALOG_INACTIVE_BUTTON: + Height += 1; + break; + case TUI_DIALOG_ACTIVE_INPUT_FIELD: + case TUI_DIALOG_INACTIVE_INPUT_FIELD: + case TUI_DIALOG_PROGRESS_BAR: + Height += 2; + break; + } + + /* Update component attributes mask */ + Attributes &= ~Mask; + Mask <<= 1; + } + + /* Check if input field is active */ + if(Handle->Attributes & (TUI_DIALOG_ACTIVE_INPUT_FIELD | TUI_DIALOG_INACTIVE_INPUT_FIELD)) + { + /* Set maximum dialog window width to fit input field */ + Width = TUI_MAX_DIALOG_WIDTH; + } + + /* Get message length and count dialog window dimensions */ + MessageLength = RtlWideStringLength(Message, 0); + for(Index = 0; Index < MessageLength; Index++) + { + /* Check if this is multiline message */ + if(Message[Index] == L'\n' || Index == MessageLength - 1) + { + /* Check if this line exceeds current dialog window width */ + if(LineLength > Width) + { + /* Update dialog window width */ + Width = LineLength; + } + /* Increase dialog window height to fit next line */ + Height++; + LineLength = 0; + } + else + { + /* Increase dialog window width to fit next character */ + LineLength++; + } + } + + /* Add more space to dialog window to fit side borders */ + Width += 4; + + /* Get console resolution */ + BlConsoleQueryMode(&Handle->ResX, &Handle->ResY); + + /* Make sure dialog window fits in the buffer */ + if(Width > TUI_MAX_DIALOG_WIDTH) + { + /* Set maximum dialog window width */ + Width = TUI_MAX_DIALOG_WIDTH; + } + + /* Make sure dialog window fits on the screen (X axis) and it is not too small for input field */ + if(Width > (Handle->ResX - 2)) + { + /* Set maximum dialog window width */ + Width = Handle->ResX - 2; + } + + /* Make sure dialog window fits on the screen (Y axis)*/ + if(Height > (Handle->ResY - 2)) + { + /* Set maximum dialog window height */ + Height = Handle->ResY - 2; + } + + /* Set dialog window final dimensions */ + Handle->PosX = (Handle->ResX - Width) / 2; + Handle->PosY = (Handle->ResY - Height) / 2; + Handle->Width = Width; + Handle->Height = Height; +} + +XTCDECL +VOID +BlpDrawDialogBox(IN PXTBL_DIALOG_HANDLE Handle, + IN PWCHAR Caption, + IN PWCHAR Message) +{ + WCHAR BoxLine[TUI_MAX_DIALOG_WIDTH]; + PWCHAR MsgLine, LastMsgLine; + UINT_PTR Line, PosX, PosY; + SIZE_T CaptionLength; + UCHAR DialogColor; + + /* Set dialog colors */ + if(Handle->Attributes & TUI_DIALOG_ERROR_BOX) + { + /* Error dialog with red background and brown button */ + DialogColor = EFI_TEXT_BGCOLOR_RED; + } + else + { + /* Generic dialog with blue background and cyan button */ + DialogColor = EFI_TEXT_BGCOLOR_BLUE; + } + + /* Get caption length */ + CaptionLength = RtlWideStringLength(Caption, 0); + + /* Set dialog box colors */ + BlSetConsoleAttributes(DialogColor | 0x0F); + + /* Iterate through dialog box lines */ + for(PosY = Handle->PosY; PosY < Handle->PosY + Handle->Height; PosY++) + { + /* Set cursor position in the appropriate place */ + BlSetCursorPosition(Handle->PosX, PosY); + + /* Draw dialog box */ + if(PosY == Handle->PosY) + { + /* Draw top line of the dialog box, starting from the left corner */ + BoxLine[0] = EFI_TEXT_BOX_DOWN_RIGHT; + BoxLine[1] = EFI_TEXT_BOX_VERTICAL_LEFT; + + /* Fill caption area with spaces */ + for(PosX = 2; PosX < CaptionLength + 4; PosX++) + { + BoxLine[PosX] = ' '; + } + + /* End caption area with vertical line */ + BoxLine[CaptionLength + 4] = EFI_TEXT_BOX_VERTICAL_RIGHT; + + /* Draw bottom line */ + for(PosX = CaptionLength + 5; PosX < Handle->Width - 1; PosX++) + { + BoxLine[PosX] = EFI_TEXT_BOX_HORIZONTAL; + } + + /* End with top right corner */ + BoxLine[Handle->Width - 1] = EFI_TEXT_BOX_DOWN_LEFT; + } + else if(PosY == Handle->PosY + Handle->Height - 1) + { + /* Draw bottom line of the dialog box, starting from the left corner */ + BoxLine[0] = EFI_TEXT_BOX_UP_LEFT; + + /* Fill bottom with horizontal line */ + for(PosX = 1; PosX < Handle->Width - 1; PosX++) + { + BoxLine[PosX] = EFI_TEXT_BOX_HORIZONTAL; + } + + /* End with bottom right corner */ + BoxLine[Handle->Width - 1] = EFI_TEXT_BOX_UP_RIGHT; + } + else + { + /* Draw the middle of the dialog box */ + BoxLine[0] = EFI_TEXT_BOX_VERTICAL; + + /* Fill dialog box with spaces */ + for(PosX = 1; PosX < Handle->Width - 1; PosX++) + { + BoxLine[PosX] = ' '; + } + + /* End with vertical line */ + BoxLine[Handle->Width - 1] = EFI_TEXT_BOX_VERTICAL; + } + + /* Add null terminator to the end of the line */ + BoxLine[Handle->Width] = 0; + + /* Write the line to the console */ + BlConsoleWrite(BoxLine); + } + + /* Write dialog box caption */ + BlSetCursorPosition(Handle->PosX + 3, Handle->PosY); + BlConsolePrint(L"%S", Caption); + + /* Tokenize dialog box message */ + MsgLine = RtlTokenizeWideString(Message, L"\n", &LastMsgLine); + + /* Iterate through message lines */ + Line = 0; + while(MsgLine) + { + /* Write line in the dialog box */ + BlSetCursorPosition(Handle->PosX + 2, Handle->PosY + 2 + Line); + BlConsolePrint(L"%S", MsgLine); + + /* Get next line */ + MsgLine = RtlTokenizeWideString(NULL, L"\n", &LastMsgLine); + Line++; + } +} + +XTCDECL +VOID +BlpDrawDialogButton(IN PXTBL_DIALOG_HANDLE Handle) +{ + ULONG ButtonColor, TextColor; + + /* Set dialog button colors */ + if(Handle->Attributes & TUI_DIALOG_ACTIVE_BUTTON) + { + /* This is an active button */ + if(Handle->Attributes & TUI_DIALOG_ERROR_BOX) + { + /* This is an error dialog box */ + ButtonColor = EFI_TEXT_BGCOLOR_BROWN; + TextColor = EFI_TEXT_FGCOLOR_WHITE; + } + else + { + /* This is a generic dialog box */ + ButtonColor = EFI_TEXT_BGCOLOR_CYAN; + TextColor = EFI_TEXT_FGCOLOR_WHITE; + } + } + else + { + /* This is an inactive button */ + ButtonColor = EFI_TEXT_BGCOLOR_LIGHTGRAY; + TextColor = EFI_TEXT_FGCOLOR_BLACK; + } + + /* Disable cursor and draw dialog button */ + BlConsoleDisableCursor(); + BlSetConsoleAttributes(ButtonColor | TextColor); + BlSetCursorPosition(Handle->ResX / 2 - 4, Handle->PosY + Handle->Height - 2); + BlConsolePrint(L"[ OK ]"); +} + +XTCDECL +VOID +BlpDrawDialogInputField(IN PXTBL_DIALOG_HANDLE Handle, + IN PWCHAR InputFieldText) +{ + WCHAR InputField[TUI_MAX_DIALOG_WIDTH]; + ULONG InputColor, TextColor; + UINT_PTR Index, Position; + + /* Set dialog button colors */ + if(Handle->Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + /* This is an active input field */ + if(Handle->Attributes & TUI_DIALOG_ERROR_BOX) + { + /* This is an error dialog box */ + InputColor = EFI_TEXT_BGCOLOR_BROWN; + TextColor = EFI_TEXT_FGCOLOR_WHITE; + } + else + { + /* This is a generic dialog box */ + InputColor = EFI_TEXT_BGCOLOR_CYAN; + TextColor = EFI_TEXT_FGCOLOR_WHITE; + } + } + else + { + /* This is an inactive input field */ + InputColor = EFI_TEXT_BGCOLOR_LIGHTGRAY; + TextColor = EFI_TEXT_FGCOLOR_BLACK; + } + + /* Set progress bar color and position */ + BlSetConsoleAttributes(InputColor | TextColor); + Position = (Handle->Attributes & (TUI_DIALOG_ACTIVE_BUTTON | TUI_DIALOG_INACTIVE_BUTTON)) ? 4 : 3; + BlSetCursorPosition(Handle->PosX + 4, Handle->PosY + Handle->Height - Position); + + /* Draw input field */ + for(Index = 0; Index < Handle->Width - 8; Index++) + { + /* Fill input field with spaces */ + InputField[Index] = L' '; + } + + /* Disable cursor and write input field to console */ + BlConsoleDisableCursor(); + BlConsoleWrite(InputField); + + /* Write input field text */ + BlSetCursorPosition(Handle->PosX + 4, Handle->PosY + Handle->Height - Position); + BlConsoleWrite(InputFieldText); + + /* Check if this is an active input field */ + if(Handle->Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + /* Enable cursor for active input field */ + BlConsoleEnableCursor(); + } +} + +XTCDECL +VOID +BlpDrawDialogProgressBar(IN PXTBL_DIALOG_HANDLE Handle, + IN UCHAR Percentage) +{ + UINT_PTR Index, ProgressLength, ProgressBarLength; + WCHAR ProgressBar[TUI_MAX_DIALOG_WIDTH]; + UINT_PTR Position; + + /* Determine progress bar length and calculate progress */ + ProgressBarLength = Handle->Width - 8; + ProgressLength = (ProgressBarLength * Percentage) / 100; + + /* Set progress bar color and position */ + BlSetConsoleAttributes(EFI_TEXT_FGCOLOR_YELLOW); + Position = (Handle->Attributes & (TUI_DIALOG_ACTIVE_BUTTON | TUI_DIALOG_INACTIVE_BUTTON)) ? 4 : 3; + BlSetCursorPosition(Handle->PosX + 4, Handle->PosY + Handle->Height - Position); + + /* Draw progress bar */ + for(Index = 0; Index < ProgressBarLength; Index++) + { + /* Fill progress bar */ + if(Index < ProgressLength) + { + /* Fill with full block */ + ProgressBar[Index] = EFI_TEXT_BOX_FULL_BLOCK; + } + else + { + /* Fill with light block */ + ProgressBar[Index] = EFI_TEXT_BOX_LIGHT_BLOCK; + } + } + + /* Terminate progress bar string */ + ProgressBar[Index] = 0; + + /* Disable cursor and write progress bar to console */ + BlConsoleDisableCursor(); + BlConsoleWrite(ProgressBar); +} + + + + + +XTCDECL +VOID +BlDisplayErrorDialog(IN PWCHAR Caption, + IN PWCHAR Message) +{ + XTBL_DIALOG_HANDLE Handle; + EFI_INPUT_KEY Key; + UINT_PTR Index; + + /* Set dialog window attributes */ + Handle.Attributes = TUI_DIALOG_ERROR_BOX | TUI_DIALOG_ACTIVE_BUTTON; + + /* Determine dialog window size and position */ + BlpDetermineDialogBoxSize(&Handle, Message); + + /* Disable cursor and draw dialog box */ + BlConsoleDisableCursor(); + BlpDrawDialogBox(&Handle, Caption, Message); + + /* Draw active button */ + BlpDrawDialogButton(&Handle); + + /* Initialize key stroke */ + Key.ScanCode = 0; + Key.UnicodeChar = 0; + + /* Wait until ENTER or ESC key is pressed */ + while(Key.ScanCode != 0x17 && Key.UnicodeChar != 0x0D) + { + /* Wait for key press and read key stroke */ + EfiSystemTable->BootServices->WaitForEvent(1, &EfiSystemTable->ConIn->WaitForKey, &Index); + EfiSystemTable->ConIn->ReadKeyStroke(EfiSystemTable->ConIn, &Key); + EfiSystemTable->ConIn->Reset(EfiSystemTable->ConIn, FALSE); + } + + /* Clear screen to remove dialog box */ + BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); + BlConsoleClearScreen(); +} + +XTCDECL +VOID +BlDisplayInfoDialog(IN PWCHAR Caption, + IN PWCHAR Message) +{ + XTBL_DIALOG_HANDLE Handle; + EFI_INPUT_KEY Key; + UINT_PTR Index; + + /* Set dialog window attributes */ + Handle.Attributes = TUI_DIALOG_GENERIC_BOX | TUI_DIALOG_ACTIVE_BUTTON; + + /* Determine dialog window size and position */ + BlpDetermineDialogBoxSize(&Handle, Message); + + /* Disable cursor and draw dialog box */ + BlConsoleDisableCursor(); + BlpDrawDialogBox(&Handle, Caption, Message); + + /* Draw active button */ + BlpDrawDialogButton(&Handle); + + /* Initialize key stroke */ + Key.ScanCode = 0; + Key.UnicodeChar = 0; + + /* Wait until ENTER or ESC key is pressed */ + while(Key.ScanCode != 0x17 && Key.UnicodeChar != 0x0D) + { + /* Wait for key press and read key stroke */ + EfiSystemTable->BootServices->WaitForEvent(1, &EfiSystemTable->ConIn->WaitForKey, &Index); + EfiSystemTable->ConIn->ReadKeyStroke(EfiSystemTable->ConIn, &Key); + EfiSystemTable->ConIn->Reset(EfiSystemTable->ConIn, FALSE); + } + + /* Clear screen to remove dialog box */ + BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); + BlConsoleClearScreen(); +} + +XTCDECL +VOID +BlDisplayInputDialog(IN PWCHAR Caption, + IN PWCHAR Message, + IN PWCHAR InputFieldBuffer) +{ + XTBL_DIALOG_HANDLE Handle; + SIZE_T InputFieldLength, TextCursorPosition, TextIndex, TextPosition; + EFI_INPUT_KEY Key; + UINT_PTR Index; + + /* Set dialog window attributes */ + Handle.Attributes = TUI_DIALOG_GENERIC_BOX | TUI_DIALOG_ACTIVE_INPUT_FIELD | TUI_DIALOG_INACTIVE_BUTTON; + + /* Determine dialog window size and position */ + BlpDetermineDialogBoxSize(&Handle, Message); + + /* Disable cursor and draw dialog box */ + BlConsoleDisableCursor(); + BlpDrawDialogBox(&Handle, Caption, Message); + + /* Draw inactive button */ + BlpDrawDialogButton(&Handle); + + /* Draw active input field */ + BlpDrawDialogInputField(&Handle, InputFieldBuffer); + + /* Initialize key stroke */ + Key.ScanCode = 0; + Key.UnicodeChar = 0; + + /* Determine input field length */ + InputFieldLength = RtlWideStringLength(InputFieldBuffer, 0); + if(InputFieldLength > Handle.Width - 8) + { + InputFieldLength = Handle.Width - 8; + } + + /* Start at first character */ + TextPosition = 0; + BlSetCursorPosition(Handle.PosX + 4 + TextPosition, Handle.PosY + Handle.Height - 4); + + /* Wait until ENTER or ESC key is pressed */ + while(TRUE) + { + /* Wait for key press and read key stroke */ + EfiSystemTable->BootServices->WaitForEvent(1, &EfiSystemTable->ConIn->WaitForKey, &Index); + EfiSystemTable->ConIn->ReadKeyStroke(EfiSystemTable->ConIn, &Key); + EfiSystemTable->ConIn->Reset(EfiSystemTable->ConIn, FALSE); + + /* Check key press scan code */ + if(Key.ScanCode == 0x17) + { + /* ESC key pressed, return */ + break; + } + else if(Key.UnicodeChar == 0x09) + { + /* TAB key pressed, toggle input field and button */ + Handle.Attributes ^= (TUI_DIALOG_ACTIVE_INPUT_FIELD | TUI_DIALOG_INACTIVE_INPUT_FIELD); + Handle.Attributes ^= (TUI_DIALOG_ACTIVE_BUTTON | TUI_DIALOG_INACTIVE_BUTTON); + } + else if(Key.ScanCode == 0x03) + { + /* RIGHT key pressed, move cursor forward */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD && TextPosition < InputFieldLength) + { + TextPosition++; + } + } + else if(Key.ScanCode == 0x04) + { + /* LEFT key pressed, move cursor back */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD && TextPosition > 0) + { + TextPosition--; + } + } + else if(Key.ScanCode == 0x05) + { + /* HOME key pressed, move cursor to the beginning */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + TextPosition = 0; + } + } + else if(Key.ScanCode == 0x06) + { + /* END key pressed, move cursor to the end */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + TextPosition = InputFieldLength; + } + } + else if(Key.ScanCode == 0x08) + { + /* DELETE key pressed, delete character */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + if(InputFieldLength > 0 && TextPosition < InputFieldLength) + { + RtlMoveMemory(InputFieldBuffer + TextPosition, InputFieldBuffer + TextPosition + 1, InputFieldLength - TextPosition); + InputFieldLength--; + InputFieldBuffer[InputFieldLength] = 0; + } + } + } + else if(Key.UnicodeChar == 0x08) + { + /* BACKSPACE key pressed, delete character */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + if(InputFieldLength > 0 && TextPosition > 0 && TextPosition <= InputFieldLength) + { + TextPosition--; + RtlMoveMemory(InputFieldBuffer + TextPosition, InputFieldBuffer + TextPosition + 1, InputFieldLength - TextPosition); + InputFieldLength--; + InputFieldBuffer[InputFieldLength] = 0; + } + } + } + else if(Key.UnicodeChar == 0x0D) + { + /* ENTER key pressed */ + } + else + { + /* Other key pressed, add character to the buffer */ + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD && Key.UnicodeChar != 0) + { + if(InputFieldLength < Handle.Width - 8 - 1 && TextPosition < Handle.Width - 8 - 1) + { + RtlMoveMemory(InputFieldBuffer + TextPosition + 1, InputFieldBuffer + TextPosition, InputFieldLength - TextPosition); + InputFieldBuffer[TextPosition] = Key.UnicodeChar; + TextPosition++; + InputFieldLength++; + InputFieldBuffer[InputFieldLength] = 0; + } + } + } + + if(TextPosition > (Handle.Width - 8)) + { + TextIndex = TextPosition - (Handle.Width - 8); + TextCursorPosition = Handle.Width - 8; + } + else + { + TextIndex = 0; + TextCursorPosition = TextPosition; + } + + /* Redraw input field and button */ + BlpDrawDialogButton(&Handle); + BlpDrawDialogInputField(&Handle, &InputFieldBuffer[TextIndex]); + + if(Handle.Attributes & TUI_DIALOG_ACTIVE_INPUT_FIELD) + { + BlSetCursorPosition(Handle.PosX + 4 + TextCursorPosition, Handle.PosY + Handle.Height - 4); + } + } + + /* Clear screen to remove dialog box */ + BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); + BlConsoleClearScreen(); +} + +XTCDECL +XTBL_DIALOG_HANDLE +BlDisplayProgressDialog(IN PWCHAR Caption, + IN PWCHAR Message, + IN UCHAR Percentage) +{ + XTBL_DIALOG_HANDLE Handle; + + /* Set dialog window attributes */ + Handle.Attributes = TUI_DIALOG_GENERIC_BOX | TUI_DIALOG_PROGRESS_BAR; + + /* Determine dialog window size and position */ + BlpDetermineDialogBoxSize(&Handle, Message); + + /* Disable cursor and draw dialog box */ + BlConsoleDisableCursor(); + BlpDrawDialogBox(&Handle, Caption, Message); + + /* Draw active button */ + BlpDrawDialogProgressBar(&Handle, Percentage); + + /* Return dialog handle */ + return Handle; +} + +// TODO: Common routine for printing text on dialog window +XTCDECL +VOID +BlUpdateProgressBar(IN PXTBL_DIALOG_HANDLE Handle, + IN PWCHAR Message, + IN UCHAR Percentage) +{ + SIZE_T Index, Length; + + /* Check if message needs an update */ + if(Message != NULL) + { + /* Determine message length */ + Length = RtlWideStringLength(Message, 0); + + /* Update message in the dialog box */ + BlSetCursorPosition(Handle->PosX + 2, Handle->PosY + 2); + BlConsolePrint(L"%S", Message); + + if(Length < Handle->Width - 4) + { + for(Index = Length; Index < Handle->Width - 4; Index++) + { + BlConsolePrint(L" "); + } + } + } + + /* Update progress bar */ + BlpDrawDialogProgressBar(Handle, Percentage); +}