/** * 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; UCHAR DialogColor; UCHAR TextColor; UINT_PTR ResX; UINT_PTR ResY; UINT_PTR PosX; UINT_PTR PosY; UINT_PTR Width; UINT_PTR Height; } XTBL_DIALOG_HANDLE, *PXTBL_DIALOG_HANDLE; /** * Determines dialog box size based on enabled components and message length. * * @param Handle * Supplies a pointer to the dialog box handle. * * @param Message * Supplies a pointer to the message string put on the dialog box. * * @return This routine does not return any value. * * @since XT 1.0 */ XTCDECL VOID BlpDetermineDialogBoxSize(IN OUT 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 */ BlQueryConsoleMode(&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; } /** * Draws dialog box with caption and message. * * @param Handle * Supplies a pointer to the dialog box handle. * * @param Caption * Specifies a caption string put on the dialog box. * * @param Message * Specifies a message string put on the dialog box. * * @return This routine does not return any value. * * @since XT 1.0 */ XTCDECL VOID BlpDrawDialogBox(IN OUT 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; /* Set dialog colors */ if(Handle->Attributes & TUI_DIALOG_ERROR_BOX) { /* Error dialog with red background and brown button */ Handle->DialogColor = EFI_TEXT_BGCOLOR_RED; Handle->TextColor = EFI_TEXT_FGCOLOR_WHITE; } else { /* Generic dialog with blue background and cyan button */ Handle->DialogColor = EFI_TEXT_BGCOLOR_BLUE; Handle->TextColor = EFI_TEXT_FGCOLOR_WHITE; } /* Get caption length */ CaptionLength = RtlWideStringLength(Caption, 0); /* Set dialog box colors */ BlSetConsoleAttributes(Handle->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++; } } /** * Draws an active or inactive button in the dialog box, depending on the attributes. * * @param Handle * Supplies a pointer to the dialog box handle. * * @return This routine does not return any value. * * @since XT 1.0 */ 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 */ BlDisableConsoleCursor(); BlSetConsoleAttributes(ButtonColor | TextColor); BlSetCursorPosition(Handle->ResX / 2 - 4, Handle->PosY + Handle->Height - 2); BlConsolePrint(L"[ OK ]"); } /** * Draws an active or inactive input field in the dialog box, depending on the attributes. * * @param Handle * Supplies a pointer to the dialog box handle. * * @param InputFieldText * Supplies a pointer to the wide char string that will be displayed in the input field. * * @return This routine does not return any value. * * @since XT 1.0 */ 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 */ BlDisableConsoleCursor(); 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 */ BlEnableConsoleCursor(); } } /** * Draws a progress bar component in the dialog box. * * @param Handle * Supplies a pointer to the dialog box handle. * * @param Percentage * Specifies the percentage progress of the progress bar. * * @return This routine does not return any value. * * @since XT 1.0 */ 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 */ BlDisableConsoleCursor(); BlConsoleWrite(ProgressBar); } /** * Displays a red error dialog box with the specified caption and message. * * @param Caption * Supplies a caption string put on the dialog box. * * @param Message * Supplies a message string put on the dialog box. * * @return This routine does not return any value. * * @since XT 1.0 */ 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 */ BlDisableConsoleCursor(); 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 */ BlWaitForEfiEvent(1, &EfiSystemTable->ConIn->WaitForKey, &Index); BlReadKeyStroke(&Key); BlResetConsoleInputBuffer(); } /* Clear screen to remove dialog box */ BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); BlClearConsoleScreen(); } 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 */ BlDisableConsoleCursor(); 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 */ BlWaitForEfiEvent(1, &EfiSystemTable->ConIn->WaitForKey, &Index); BlReadKeyStroke(&Key); BlResetConsoleInputBuffer(); } /* Clear screen to remove dialog box */ BlSetConsoleAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY); BlClearConsoleScreen(); } 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 */ BlDisableConsoleCursor(); 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 */ BlWaitForEfiEvent(1, &EfiSystemTable->ConIn->WaitForKey, &Index); BlReadKeyStroke(&Key); BlResetConsoleInputBuffer(); /* 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); BlClearConsoleScreen(); } 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 */ BlDisableConsoleCursor(); 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); BlSetConsoleAttributes(Handle->DialogColor | Handle->TextColor); 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); }