xref: /reactos/boot/freeldr/freeldr/ui/tui.c (revision 798ea907)
1 /*
2  *  FreeLoader
3  *  Copyright (C) 1998-2003  Brian Palmer  <brianp@sginet.com>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include <freeldr.h>
21 
22 typedef struct _SMALL_RECT
23 {
24     SHORT Left;
25     SHORT Top;
26     SHORT Right;
27     SHORT Bottom;
28 } SMALL_RECT, *PSMALL_RECT;
29 
30 PVOID TextVideoBuffer = NULL;
31 
32 /* GENERIC TUI UTILS *********************************************************/
33 
34 /*
35  * TuiPrintf()
36  * Prints formatted text to the screen.
37  */
38 INT
TuiPrintf(_In_ PCSTR Format,...)39 TuiPrintf(
40     _In_ PCSTR Format, ...)
41 {
42     INT i;
43     INT Length;
44     va_list ap;
45     CHAR Buffer[512];
46 
47     va_start(ap, Format);
48     Length = _vsnprintf(Buffer, sizeof(Buffer), Format, ap);
49     va_end(ap);
50 
51     if (Length == -1)
52         Length = (INT)sizeof(Buffer);
53 
54     for (i = 0; i < Length; i++)
55     {
56         MachConsPutChar(Buffer[i]);
57     }
58 
59     return Length;
60 }
61 
62 VOID
TuiTruncateStringEllipsis(_Inout_z_ PSTR StringText,_In_ ULONG MaxChars)63 TuiTruncateStringEllipsis(
64     _Inout_z_ PSTR StringText,
65     _In_ ULONG MaxChars)
66 {
67     /* If it's too large, just add some ellipsis past the maximum */
68     if (strlen(StringText) > MaxChars)
69         strcpy(&StringText[MaxChars - 3], "...");
70 }
71 
72 /*
73  * DrawText()
74  * Displays a string on a single screen line.
75  * This function assumes coordinates are zero-based.
76  */
77 VOID
TuiDrawText(_In_ ULONG X,_In_ ULONG Y,_In_ PCSTR Text,_In_ UCHAR Attr)78 TuiDrawText(
79     _In_ ULONG X,
80     _In_ ULONG Y,
81     _In_ PCSTR Text,
82     _In_ UCHAR Attr)
83 {
84     TuiDrawText2(X, Y, 0 /*(ULONG)strlen(Text)*/, Text, Attr);
85 }
86 
87 /*
88  * DrawText2()
89  * Displays a string on a single screen line.
90  * This function assumes coordinates are zero-based.
91  * MaxNumChars is the maximum number of characters to display.
92  * If MaxNumChars == 0, then display the whole string.
93  */
94 VOID
TuiDrawText2(_In_ ULONG X,_In_ ULONG Y,_In_opt_ ULONG MaxNumChars,_In_reads_or_z_ (MaxNumChars)PCSTR Text,_In_ UCHAR Attr)95 TuiDrawText2(
96     _In_ ULONG X,
97     _In_ ULONG Y,
98     _In_opt_ ULONG MaxNumChars,
99     _In_reads_or_z_(MaxNumChars) PCSTR Text,
100     _In_ UCHAR Attr)
101 {
102     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
103     ULONG i, j;
104 
105     /* Don't display anything if we are out of the screen */
106     if ((X >= UiScreenWidth) || (Y >= UiScreenHeight))
107         return;
108 
109     /* Draw the text, not exceeding the width */
110     for (i = X, j = 0; Text[j] && i < UiScreenWidth && (MaxNumChars > 0 ? j < MaxNumChars : TRUE); i++, j++)
111     {
112         ScreenMemory[((Y*2)*UiScreenWidth)+(i*2)]   = (UCHAR)Text[j];
113         ScreenMemory[((Y*2)*UiScreenWidth)+(i*2)+1] = Attr;
114     }
115 }
116 
117 VOID
TuiDrawCenteredText(_In_ ULONG Left,_In_ ULONG Top,_In_ ULONG Right,_In_ ULONG Bottom,_In_ PCSTR TextString,_In_ UCHAR Attr)118 TuiDrawCenteredText(
119     _In_ ULONG Left,
120     _In_ ULONG Top,
121     _In_ ULONG Right,
122     _In_ ULONG Bottom,
123     _In_ PCSTR TextString,
124     _In_ UCHAR Attr)
125 {
126     SIZE_T TextLength;
127     SIZE_T Index, LastIndex;
128     ULONG  LineBreakCount;
129     ULONG  BoxWidth, BoxHeight;
130     ULONG  RealLeft, RealTop;
131     ULONG  X, Y;
132     CHAR   Temp[2];
133 
134     /* Query text length */
135     TextLength = strlen(TextString);
136 
137     /* Count the new lines and the box width */
138     LineBreakCount = 0;
139     BoxWidth = 0;
140     LastIndex = 0;
141     for (Index = 0; Index < TextLength; Index++)
142     {
143         /* Scan for new lines */
144         if (TextString[Index] == '\n')
145         {
146             /* Remember the new line */
147             LastIndex = Index;
148             LineBreakCount++;
149         }
150         else
151         {
152             /* Check for new larger box width */
153             if ((Index - LastIndex) > BoxWidth)
154             {
155                 /* Update it */
156                 BoxWidth = (ULONG)(Index - LastIndex);
157             }
158         }
159     }
160 
161     /* Base the box height on the number of lines */
162     BoxHeight = LineBreakCount + 1;
163 
164     /*
165      * Create the centered coordinates.
166      * Here, the Left/Top/Right/Bottom rectangle is a hint, around
167      * which we center the "real" text rectangle RealLeft/RealTop.
168      */
169     RealLeft = (Left + Right - BoxWidth + 1) / 2;
170     RealTop  = (Top + Bottom - BoxHeight + 1) / 2;
171 
172     /* Now go for a second scan */
173     LastIndex = 0;
174     for (Index = 0; Index < TextLength; Index++)
175     {
176         /* Look for new lines again */
177         if (TextString[Index] == '\n')
178         {
179             /* Update where the text should start */
180             RealTop++;
181             LastIndex = 0;
182         }
183         else
184         {
185             /* We've got a line of text to print, do it */
186             X = (ULONG)(RealLeft + LastIndex);
187             Y = RealTop;
188             LastIndex++;
189             Temp[0] = TextString[Index];
190             Temp[1] = 0;
191             TuiDrawText(X, Y, Temp, Attr);
192         }
193     }
194 }
195 
196 /* FULL TUI THEME ************************************************************/
197 
198 #define TAG_TUI_SCREENBUFFER 'SiuT'
199 #define TAG_TUI_PALETTE      'PiuT'
200 
201 extern UCHAR MachDefaultTextColor;
202 
TuiInitialize(VOID)203 BOOLEAN TuiInitialize(VOID)
204 {
205     MachVideoHideShowTextCursor(FALSE);
206     MachVideoSetTextCursorPosition(0, 0);
207     MachVideoClearScreen(ATTR(COLOR_GRAY, COLOR_BLACK));
208 
209     TextVideoBuffer = VideoAllocateOffScreenBuffer();
210     if (TextVideoBuffer == NULL)
211     {
212         return FALSE;
213     }
214 
215     /* Load default settings with "Full" TUI Theme */
216 
217     UiStatusBarFgColor    = COLOR_BLACK;
218     UiStatusBarBgColor    = COLOR_CYAN;
219     UiBackdropFgColor     = COLOR_WHITE;
220     UiBackdropBgColor     = COLOR_BLUE;
221     UiBackdropFillStyle   = MEDIUM_FILL;
222     UiTitleBoxFgColor     = COLOR_WHITE;
223     UiTitleBoxBgColor     = COLOR_RED;
224     UiMessageBoxFgColor   = COLOR_WHITE;
225     UiMessageBoxBgColor   = COLOR_BLUE;
226     UiMenuFgColor         = COLOR_WHITE;
227     UiMenuBgColor         = COLOR_BLUE;
228     UiTextColor           = COLOR_YELLOW;
229     UiSelectedTextColor   = COLOR_BLACK;
230     UiSelectedTextBgColor = COLOR_GRAY;
231     UiEditBoxTextColor    = COLOR_WHITE;
232     UiEditBoxBgColor      = COLOR_BLACK;
233 
234     UiShowTime          = TRUE;
235     UiMenuBox           = TRUE;
236     UiCenterMenu        = TRUE;
237     UiUseSpecialEffects = FALSE;
238 
239     // TODO: Have a boolean to show/hide title box?
240     RtlStringCbCopyA(UiTitleBoxTitleText, sizeof(UiTitleBoxTitleText),
241                      "Boot Menu");
242 
243     RtlStringCbCopyA(UiTimeText, sizeof(UiTimeText),
244                      "[Time Remaining: %d]");
245 
246     return TRUE;
247 }
248 
TuiUnInitialize(VOID)249 VOID TuiUnInitialize(VOID)
250 {
251     /* Do nothing if already uninitialized */
252     if (!TextVideoBuffer)
253         return;
254 
255     if (UiUseSpecialEffects)
256     {
257         TuiFadeOut();
258     }
259     else
260     {
261         MachVideoSetDisplayMode(NULL, FALSE);
262     }
263 
264     VideoFreeOffScreenBuffer();
265     TextVideoBuffer = NULL;
266 
267     MachVideoClearScreen(ATTR(COLOR_GRAY, COLOR_BLACK));
268     MachVideoSetTextCursorPosition(0, 0);
269     MachVideoHideShowTextCursor(TRUE);
270 }
271 
TuiDrawBackdrop(VOID)272 VOID TuiDrawBackdrop(VOID)
273 {
274     /* Fill in the background (excluding title box & status bar) */
275     TuiFillArea(0,
276                 TUI_TITLE_BOX_CHAR_HEIGHT,
277                 UiScreenWidth - 1,
278                 UiScreenHeight - 2,
279                 UiBackdropFillStyle,
280                 ATTR(UiBackdropFgColor, UiBackdropBgColor));
281 
282     /* Draw the title box */
283     TuiDrawBox(0,
284                0,
285                UiScreenWidth - 1,
286                TUI_TITLE_BOX_CHAR_HEIGHT - 1,
287                D_VERT,
288                D_HORZ,
289                TRUE,
290                FALSE,
291                ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
292 
293     /* Draw version text */
294     TuiDrawText(2,
295                 1,
296                 VERSION,
297                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
298 
299     /* Draw copyright */
300     TuiDrawText(2,
301                 2,
302                 BY_AUTHOR,
303                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
304     TuiDrawText(2,
305                 3,
306                 AUTHOR_EMAIL,
307                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
308 
309     /* Draw help text */
310     TuiDrawText(UiScreenWidth - 16, 3,
311                 /*"F1 for Help"*/ "F8 for Options",
312                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
313 
314     /* Draw title text */
315     TuiDrawText((UiScreenWidth - (ULONG)strlen(UiTitleBoxTitleText)) / 2,
316                 2,
317                 UiTitleBoxTitleText,
318                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
319 
320     /* Update the date & time */
321     TuiUpdateDateTime();
322     VideoCopyOffScreenBufferToVRAM();
323 }
324 
325 /*
326  * FillArea()
327  * This function assumes coordinates are zero-based
328  */
TuiFillArea(ULONG Left,ULONG Top,ULONG Right,ULONG Bottom,CHAR FillChar,UCHAR Attr)329 VOID TuiFillArea(ULONG Left, ULONG Top, ULONG Right, ULONG Bottom, CHAR FillChar, UCHAR Attr /* Color Attributes */)
330 {
331     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
332     ULONG  i, j;
333 
334     /* Clip the area to the screen */
335     if ((Left >= UiScreenWidth) || (Top >= UiScreenHeight))
336     {
337         return;
338     }
339     if (Right >= UiScreenWidth)
340         Right = UiScreenWidth - 1;
341     if (Bottom >= UiScreenHeight)
342         Bottom = UiScreenHeight - 1;
343 
344     /* Loop through each line and column and fill it in */
345     for (i = Top; i <= Bottom; ++i)
346     {
347         for (j = Left; j <= Right; ++j)
348         {
349             ScreenMemory[((i*2)*UiScreenWidth)+(j*2)] = (UCHAR)FillChar;
350             ScreenMemory[((i*2)*UiScreenWidth)+(j*2)+1] = Attr;
351         }
352     }
353 }
354 
355 /*
356  * DrawShadow()
357  * This function assumes coordinates are zero-based
358  */
TuiDrawShadow(ULONG Left,ULONG Top,ULONG Right,ULONG Bottom)359 VOID TuiDrawShadow(ULONG Left, ULONG Top, ULONG Right, ULONG Bottom)
360 {
361     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
362     ULONG  i;
363     BOOLEAN RightShadow = (Right < (UiScreenWidth - 1));
364     BOOLEAN DoubleRightShadow = ((Right + 1) < (UiScreenWidth - 1));
365     BOOLEAN BottomShadow = (Bottom < (UiScreenHeight - 1));
366     BOOLEAN DoubleWidth = (UiScreenHeight < 34);
367 
368     /* Cap the right and bottom borders */
369     Right = min(Right, UiScreenWidth - 1);
370     Bottom = min(Bottom, UiScreenHeight - 1);
371 
372     /* Shade the bottom of the area */
373     if (BottomShadow)
374     {
375         i = Left + (DoubleWidth ? 2 : 1);
376         for (; i <= Right; ++i)
377         {
378             ScreenMemory[(((Bottom+1)*2)*UiScreenWidth)+(i*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
379         }
380     }
381 
382     /* Shade the right of the area */
383     if (RightShadow)
384     {
385         for (i = Top + 1; i <= Bottom; ++i)
386         {
387             ScreenMemory[((i*2)*UiScreenWidth)+((Right+1)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
388         }
389     }
390     if (DoubleWidth && DoubleRightShadow)
391     {
392         for (i = Top + 1; i <= Bottom; ++i)
393         {
394             ScreenMemory[((i*2)*UiScreenWidth)+((Right+2)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
395         }
396     }
397 
398     /* Shade the bottom right corner */
399     if (RightShadow && BottomShadow)
400     {
401         ScreenMemory[(((Bottom+1)*2)*UiScreenWidth)+((Right+1)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
402     }
403     if (DoubleWidth && DoubleRightShadow && BottomShadow)
404     {
405         ScreenMemory[(((Bottom+1)*2)*UiScreenWidth)+((Right+2)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
406     }
407 }
408 
409 /*
410  * DrawBox()
411  * This function assumes coordinates are zero-based
412  */
413 VOID
TuiDrawBoxTopLine(_In_ ULONG Left,_In_ ULONG Top,_In_ ULONG Right,_In_ UCHAR VertStyle,_In_ UCHAR HorzStyle,_In_ UCHAR Attr)414 TuiDrawBoxTopLine(
415     _In_ ULONG Left,
416     _In_ ULONG Top,
417     _In_ ULONG Right,
418     _In_ UCHAR VertStyle,
419     _In_ UCHAR HorzStyle,
420     _In_ UCHAR Attr)
421 {
422     UCHAR ULCorner, URCorner;
423 
424     /* Calculate the corner values */
425     if (HorzStyle == HORZ)
426     {
427         if (VertStyle == VERT)
428         {
429             ULCorner = UL;
430             URCorner = UR;
431         }
432         else // VertStyle == D_VERT
433         {
434             ULCorner = VD_UL;
435             URCorner = VD_UR;
436         }
437     }
438     else // HorzStyle == D_HORZ
439     {
440         if (VertStyle == VERT)
441         {
442             ULCorner = HD_UL;
443             URCorner = HD_UR;
444         }
445         else // VertStyle == D_VERT
446         {
447             ULCorner = D_UL;
448             URCorner = D_UR;
449         }
450     }
451 
452     TuiFillArea(Left, Top, Left, Top, ULCorner, Attr);
453     TuiFillArea(Left+1, Top, Right-1, Top, HorzStyle, Attr);
454     TuiFillArea(Right, Top, Right, Top, URCorner, Attr);
455 }
456 
457 VOID
TuiDrawBoxBottomLine(_In_ ULONG Left,_In_ ULONG Bottom,_In_ ULONG Right,_In_ UCHAR VertStyle,_In_ UCHAR HorzStyle,_In_ UCHAR Attr)458 TuiDrawBoxBottomLine(
459     _In_ ULONG Left,
460     _In_ ULONG Bottom,
461     _In_ ULONG Right,
462     _In_ UCHAR VertStyle,
463     _In_ UCHAR HorzStyle,
464     _In_ UCHAR Attr)
465 {
466     UCHAR LLCorner, LRCorner;
467 
468     /* Calculate the corner values */
469     if (HorzStyle == HORZ)
470     {
471         if (VertStyle == VERT)
472         {
473             LLCorner = LL;
474             LRCorner = LR;
475         }
476         else // VertStyle == D_VERT
477         {
478             LLCorner = VD_LL;
479             LRCorner = VD_LR;
480         }
481     }
482     else // HorzStyle == D_HORZ
483     {
484         if (VertStyle == VERT)
485         {
486             LLCorner = HD_LL;
487             LRCorner = HD_LR;
488         }
489         else // VertStyle == D_VERT
490         {
491             LLCorner = D_LL;
492             LRCorner = D_LR;
493         }
494     }
495 
496     TuiFillArea(Left, Bottom, Left, Bottom, LLCorner, Attr);
497     TuiFillArea(Left+1, Bottom, Right-1, Bottom, HorzStyle, Attr);
498     TuiFillArea(Right, Bottom, Right, Bottom, LRCorner, Attr);
499 }
500 
501 VOID
TuiDrawBox(_In_ ULONG Left,_In_ ULONG Top,_In_ ULONG Right,_In_ ULONG Bottom,_In_ UCHAR VertStyle,_In_ UCHAR HorzStyle,_In_ BOOLEAN Fill,_In_ BOOLEAN Shadow,_In_ UCHAR Attr)502 TuiDrawBox(
503     _In_ ULONG Left,
504     _In_ ULONG Top,
505     _In_ ULONG Right,
506     _In_ ULONG Bottom,
507     _In_ UCHAR VertStyle,
508     _In_ UCHAR HorzStyle,
509     _In_ BOOLEAN Fill,
510     _In_ BOOLEAN Shadow,
511     _In_ UCHAR Attr)
512 {
513     /* Fill in the box background */
514     if (Fill)
515         TuiFillArea(Left, Top, Right, Bottom, ' ', Attr);
516 
517     /* Fill in the top horizontal line */
518     TuiDrawBoxTopLine(Left, Top, Right, VertStyle, HorzStyle, Attr);
519 
520     /* Fill in the vertical left and right lines */
521     TuiFillArea(Left, Top+1, Left, Bottom-1, VertStyle, Attr);
522     TuiFillArea(Right, Top+1, Right, Bottom-1, VertStyle, Attr);
523 
524     /* Fill in the bottom horizontal line */
525     TuiDrawBoxBottomLine(Left, Bottom, Right, VertStyle, HorzStyle, Attr);
526 
527     /* Draw the shadow */
528     if (Shadow)
529         TuiDrawShadow(Left, Top, Right, Bottom);
530 }
531 
TuiDrawStatusText(PCSTR StatusText)532 VOID TuiDrawStatusText(PCSTR StatusText)
533 {
534     SIZE_T    i;
535 
536     TuiDrawText(0, UiScreenHeight-1, " ", ATTR(UiStatusBarFgColor, UiStatusBarBgColor));
537     TuiDrawText(1, UiScreenHeight-1, StatusText, ATTR(UiStatusBarFgColor, UiStatusBarBgColor));
538 
539     for (i=strlen(StatusText)+1; i<UiScreenWidth; i++)
540     {
541         TuiDrawText((ULONG)i, UiScreenHeight-1, " ", ATTR(UiStatusBarFgColor, UiStatusBarBgColor));
542     }
543 
544     VideoCopyOffScreenBufferToVRAM();
545 }
546 
TuiUpdateDateTime(VOID)547 VOID TuiUpdateDateTime(VOID)
548 {
549     TIMEINFO* TimeInfo;
550     PCSTR   DayPostfix;
551     BOOLEAN PMHour = FALSE;
552     CHAR Buffer[40];
553 
554     /* Don't draw the time if this has been disabled */
555     if (!UiShowTime) return;
556 
557     TimeInfo = ArcGetTime();
558     if (TimeInfo->Year < 1 || 9999 < TimeInfo->Year ||
559         TimeInfo->Month < 1 || 12 < TimeInfo->Month ||
560         TimeInfo->Day < 1 || 31 < TimeInfo->Day ||
561         23 < TimeInfo->Hour ||
562         59 < TimeInfo->Minute ||
563         59 < TimeInfo->Second)
564     {
565         /* This happens on QEmu sometimes. We just skip updating. */
566         return;
567     }
568 
569     /* Get the day postfix */
570     if (1 == TimeInfo->Day || 21 == TimeInfo->Day || 31 == TimeInfo->Day)
571         DayPostfix = "st";
572     else if (2 == TimeInfo->Day || 22 == TimeInfo->Day)
573         DayPostfix = "nd";
574     else if (3 == TimeInfo->Day || 23 == TimeInfo->Day)
575         DayPostfix = "rd";
576     else
577         DayPostfix = "th";
578 
579     /* Build the date string in format: "MMMM dx yyyy" */
580     RtlStringCbPrintfA(Buffer, sizeof(Buffer),
581                        "%s %d%s %d",
582                        UiMonthNames[TimeInfo->Month - 1],
583                        TimeInfo->Day,
584                        DayPostfix,
585                        TimeInfo->Year);
586 
587     /* Draw the date */
588     TuiDrawText(UiScreenWidth - (ULONG)strlen(Buffer) - 2, 1,
589                 Buffer, ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
590 
591     /* Get the hour and change from 24-hour mode to 12-hour */
592     if (TimeInfo->Hour > 12)
593     {
594         TimeInfo->Hour -= 12;
595         PMHour = TRUE;
596     }
597     if (TimeInfo->Hour == 0)
598     {
599         TimeInfo->Hour = 12;
600     }
601 
602     /* Build the time string in format: "h:mm:ss tt" */
603     RtlStringCbPrintfA(Buffer, sizeof(Buffer),
604                        "  %d:%02d:%02d %s",
605                        TimeInfo->Hour,
606                        TimeInfo->Minute,
607                        TimeInfo->Second,
608                        PMHour ? "PM" : "AM");
609 
610     /* Draw the time */
611     TuiDrawText(UiScreenWidth - (ULONG)strlen(Buffer) - 2, 2,
612                 Buffer, ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
613 }
614 
615 _Ret_maybenull_
__drv_allocatesMem(Mem)616 __drv_allocatesMem(Mem)
617 PUCHAR
618 TuiSaveScreen(VOID)
619 {
620     PUCHAR Buffer;
621     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
622     ULONG i;
623 
624     /* Allocate the buffer */
625     Buffer = FrLdrTempAlloc(UiScreenWidth * UiScreenHeight * 2,
626                             TAG_TUI_SCREENBUFFER);
627     if (!Buffer)
628         return NULL;
629 
630     /* Loop through each cell and copy it */
631     for (i=0; i < (UiScreenWidth * UiScreenHeight * 2); i++)
632     {
633         Buffer[i] = ScreenMemory[i];
634     }
635 
636     return Buffer;
637 }
638 
639 VOID
TuiRestoreScreen(_In_opt_ __drv_freesMem (Mem)PUCHAR Buffer)640 TuiRestoreScreen(
641     _In_opt_ __drv_freesMem(Mem) PUCHAR Buffer)
642 {
643     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
644     ULONG i;
645 
646     if (!Buffer)
647         return;
648 
649     /* Loop through each cell and copy it */
650     for (i=0; i < (UiScreenWidth * UiScreenHeight * 2); i++)
651     {
652         ScreenMemory[i] = Buffer[i];
653     }
654 
655     /* Free the buffer */
656     FrLdrTempFree(Buffer, TAG_TUI_SCREENBUFFER);
657 
658     VideoCopyOffScreenBufferToVRAM();
659 }
660 
661 static VOID
TuiDrawMsgBoxCommon(_In_ PCSTR MessageText,_Out_ PSMALL_RECT MsgBoxRect)662 TuiDrawMsgBoxCommon(
663     _In_ PCSTR MessageText,
664     _Out_ PSMALL_RECT MsgBoxRect)
665 {
666     INT width = 8;
667     INT height = 1;
668     INT curline = 0;
669     INT k;
670     size_t i, j;
671     INT x1, x2, y1, y2;
672     CHAR temp[260];
673 
674     /* Find the height */
675     for (i = 0; i < strlen(MessageText); i++)
676     {
677         if (MessageText[i] == '\n')
678             height++;
679     }
680 
681     /* Find the width */
682     for (i = j = k = 0; i < height; i++)
683     {
684         while ((MessageText[j] != '\n') && (MessageText[j] != ANSI_NULL))
685         {
686             j++;
687             k++;
688         }
689 
690         if (k > width)
691             width = k;
692 
693         k = 0;
694         j++;
695     }
696 
697     /* Account for the message box margins & bottom button/edit box */
698     width  += 4; // Border & space on left and right.
699     height += 5; // Border on top and bottom, plus 3 lines for button/edit box.
700 
701     /* Calculate the centered box area, also ensuring that the top-left
702      * corner is always visible if the borders are partly off-screen */
703     x1 = (UiScreenWidth - min(width, UiScreenWidth)) / 2;
704     if (UiCenterMenu && (height <= UiScreenHeight - TUI_TITLE_BOX_CHAR_HEIGHT - 1))
705     {
706         /* Exclude the header and the status bar */
707         // y1 = (UiScreenHeight - TUI_TITLE_BOX_CHAR_HEIGHT - 1 - height) / 2
708         //      + TUI_TITLE_BOX_CHAR_HEIGHT;
709         y1 = (UiScreenHeight + TUI_TITLE_BOX_CHAR_HEIGHT - 1 - height) / 2;
710     }
711     else
712     {
713         y1 = (UiScreenHeight - min(height, UiScreenHeight)) / 2;
714     }
715     x2 = x1 + width - 1;
716     y2 = y1 + height - 1;
717 
718     MsgBoxRect->Left = x1; MsgBoxRect->Right  = x2;
719     MsgBoxRect->Top  = y1; MsgBoxRect->Bottom = y2;
720 
721 
722     /* Draw the box */
723     TuiDrawBox(x1, y1, x2, y2, D_VERT, D_HORZ, TRUE, TRUE,
724                ATTR(UiMessageBoxFgColor, UiMessageBoxBgColor));
725 
726     /* Draw the text */
727     for (i = j = 0; i < strlen(MessageText) + 1; i++)
728     {
729         if ((MessageText[i] == '\n') || (MessageText[i] == ANSI_NULL))
730         {
731             temp[j] = 0;
732             j = 0;
733             UiDrawText(x1 + 2, y1 + 1 + curline, temp,
734                        ATTR(UiMessageBoxFgColor, UiMessageBoxBgColor));
735             curline++;
736         }
737         else
738         {
739             temp[j++] = MessageText[i];
740         }
741     }
742 }
743 
744 VOID
TuiMessageBox(_In_ PCSTR MessageText)745 TuiMessageBox(
746     _In_ PCSTR MessageText)
747 {
748     PVOID ScreenBuffer;
749 
750     /* Save the screen contents */
751     ScreenBuffer = TuiSaveScreen();
752 
753     /* Display the message box */
754     TuiMessageBoxCritical(MessageText);
755 
756     /* Restore the screen contents */
757     TuiRestoreScreen(ScreenBuffer);
758 }
759 
760 VOID
TuiMessageBoxCritical(_In_ PCSTR MessageText)761 TuiMessageBoxCritical(
762     _In_ PCSTR MessageText)
763 {
764     SMALL_RECT BoxRect;
765     CHAR key;
766 
767     /* Draw the common parts of the message box */
768     TuiDrawMsgBoxCommon(MessageText, &BoxRect);
769 
770     /* Draw centered OK button */
771     UiDrawText((BoxRect.Left + BoxRect.Right) / 2 - 3,
772                BoxRect.Bottom - 2,
773                "   OK   ",
774                ATTR(COLOR_BLACK, COLOR_GRAY));
775 
776     /* Draw status text */
777     UiDrawStatusText("Press ENTER to continue");
778 
779     VideoCopyOffScreenBufferToVRAM();
780 
781     for (;;)
782     {
783         if (MachConsKbHit())
784         {
785             key = MachConsGetCh();
786             if (key == KEY_EXTENDED)
787                 key = MachConsGetCh();
788 
789             if ((key == KEY_ENTER) || (key == KEY_SPACE) || (key == KEY_ESC))
790                 break;
791         }
792 
793         TuiUpdateDateTime();
794 
795         VideoCopyOffScreenBufferToVRAM();
796 
797         MachHwIdle();
798     }
799 }
800 
801 static VOID
TuiSetProgressBarText(_In_ PCSTR ProgressText)802 TuiSetProgressBarText(
803     _In_ PCSTR ProgressText)
804 {
805     ULONG ProgressBarWidth;
806     CHAR ProgressString[256];
807 
808     /* Make sure the progress bar is enabled */
809     ASSERT(UiProgressBar.Show);
810 
811     /* Calculate the width of the bar proper */
812     ProgressBarWidth = UiProgressBar.Right - UiProgressBar.Left + 1;
813 
814     /* First make sure the progress bar text fits */
815     RtlStringCbCopyA(ProgressString, sizeof(ProgressString), ProgressText);
816     TuiTruncateStringEllipsis(ProgressString, ProgressBarWidth);
817 
818     /* Clear the text area */
819     TuiFillArea(UiProgressBar.Left, UiProgressBar.Top,
820                 UiProgressBar.Right, UiProgressBar.Bottom - 1,
821                 ' ', ATTR(UiTextColor, UiMenuBgColor));
822 
823     /* Draw the "Loading..." text */
824     TuiDrawCenteredText(UiProgressBar.Left, UiProgressBar.Top,
825                         UiProgressBar.Right, UiProgressBar.Bottom - 1,
826                         ProgressString, ATTR(UiTextColor, UiMenuBgColor));
827 }
828 
829 static VOID
TuiTickProgressBar(_In_ ULONG SubPercentTimes100)830 TuiTickProgressBar(
831     _In_ ULONG SubPercentTimes100)
832 {
833     ULONG ProgressBarWidth;
834     ULONG FillCount;
835 
836     /* Make sure the progress bar is enabled */
837     ASSERT(UiProgressBar.Show);
838 
839     ASSERT(SubPercentTimes100 <= (100 * 100));
840 
841     /* Calculate the width of the bar proper */
842     ProgressBarWidth = UiProgressBar.Right - UiProgressBar.Left + 1;
843 
844     /* Compute fill count */
845     // FillCount = (ProgressBarWidth * Position) / Range;
846     FillCount = ProgressBarWidth * SubPercentTimes100 / (100 * 100);
847 
848     /* Fill the progress bar */
849     /* Draw the percent complete -- Use the fill character */
850     if (FillCount > 0)
851     {
852         TuiFillArea(UiProgressBar.Left, UiProgressBar.Bottom,
853                     UiProgressBar.Left + FillCount - 1, UiProgressBar.Bottom,
854                     '\xDB', ATTR(UiTextColor, UiMenuBgColor));
855     }
856     /* Fill the remaining with shadow blanks */
857     TuiFillArea(UiProgressBar.Left + FillCount, UiProgressBar.Bottom,
858                 UiProgressBar.Right, UiProgressBar.Bottom,
859                 '\xB2', ATTR(UiTextColor, UiMenuBgColor));
860 
861     TuiUpdateDateTime();
862     VideoCopyOffScreenBufferToVRAM();
863 }
864 
865 static VOID
866 TuiDrawProgressBar(
867     _In_ ULONG Left,
868     _In_ ULONG Top,
869     _In_ ULONG Right,
870     _In_ ULONG Bottom,
871     _In_ PCSTR ProgressText);
872 
873 static VOID
TuiDrawProgressBarCenter(_In_ PCSTR ProgressText)874 TuiDrawProgressBarCenter(
875     _In_ PCSTR ProgressText)
876 {
877     ULONG Left, Top, Right, Bottom, Width, Height;
878 
879     /* Build the coordinates and sizes */
880     Height = 2;
881     Width  = 50; // Allow for 50 "bars"
882     Left = (UiScreenWidth - Width) / 2;
883     Top  = (UiScreenHeight - Height + 4) / 2;
884     Right  = Left + Width - 1;
885     Bottom = Top + Height - 1;
886 
887     /* Inflate to include the box margins */
888     Left -= 2;
889     Right += 2;
890     Top -= 1;
891     Bottom += 1;
892 
893     /* Draw the progress bar */
894     TuiDrawProgressBar(Left, Top, Right, Bottom, ProgressText);
895 }
896 
897 static VOID
TuiDrawProgressBar(_In_ ULONG Left,_In_ ULONG Top,_In_ ULONG Right,_In_ ULONG Bottom,_In_ PCSTR ProgressText)898 TuiDrawProgressBar(
899     _In_ ULONG Left,
900     _In_ ULONG Top,
901     _In_ ULONG Right,
902     _In_ ULONG Bottom,
903     _In_ PCSTR ProgressText)
904 {
905     /* Draw the box */
906     TuiDrawBox(Left, Top, Right, Bottom,
907                VERT, HORZ, TRUE, TRUE,
908                ATTR(UiMenuFgColor, UiMenuBgColor));
909 
910     /* Exclude the box margins */
911     Left += 2;
912     Right -= 2;
913     Top += 1;
914     Bottom -= 1;
915 
916     UiInitProgressBar(Left, Top, Right, Bottom, ProgressText);
917 }
918 
TuiTextToColor(PCSTR ColorText)919 UCHAR TuiTextToColor(PCSTR ColorText)
920 {
921     static const struct
922     {
923         PCSTR ColorName;
924         UCHAR ColorValue;
925     } Colors[] =
926     {
927         {"Black"  , COLOR_BLACK  },
928         {"Blue"   , COLOR_BLUE   },
929         {"Green"  , COLOR_GREEN  },
930         {"Cyan"   , COLOR_CYAN   },
931         {"Red"    , COLOR_RED    },
932         {"Magenta", COLOR_MAGENTA},
933         {"Brown"  , COLOR_BROWN  },
934         {"Gray"   , COLOR_GRAY   },
935         {"DarkGray"    , COLOR_DARKGRAY    },
936         {"LightBlue"   , COLOR_LIGHTBLUE   },
937         {"LightGreen"  , COLOR_LIGHTGREEN  },
938         {"LightCyan"   , COLOR_LIGHTCYAN   },
939         {"LightRed"    , COLOR_LIGHTRED    },
940         {"LightMagenta", COLOR_LIGHTMAGENTA},
941         {"Yellow"      , COLOR_YELLOW      },
942         {"White"       , COLOR_WHITE       },
943     };
944     ULONG i;
945 
946     if (_stricmp(ColorText, "Default") == 0)
947         return MachDefaultTextColor;
948 
949     for (i = 0; i < RTL_NUMBER_OF(Colors); ++i)
950     {
951         if (_stricmp(ColorText, Colors[i].ColorName) == 0)
952             return Colors[i].ColorValue;
953     }
954 
955     return COLOR_BLACK;
956 }
957 
TuiTextToFillStyle(PCSTR FillStyleText)958 UCHAR TuiTextToFillStyle(PCSTR FillStyleText)
959 {
960     static const struct
961     {
962         PCSTR FillStyleName;
963         UCHAR FillStyleValue;
964     } FillStyles[] =
965     {
966         {"None"  , ' '},
967         {"Light" , LIGHT_FILL },
968         {"Medium", MEDIUM_FILL},
969         {"Dark"  , DARK_FILL  },
970     };
971     ULONG i;
972 
973     for (i = 0; i < RTL_NUMBER_OF(FillStyles); ++i)
974     {
975         if (_stricmp(FillStyleText, FillStyles[i].FillStyleName) == 0)
976             return FillStyles[i].FillStyleValue;
977     }
978 
979     return LIGHT_FILL;
980 }
981 
TuiFadeInBackdrop(VOID)982 VOID TuiFadeInBackdrop(VOID)
983 {
984     PPALETTE_ENTRY TuiFadePalette = NULL;
985 
986     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed())
987     {
988         TuiFadePalette = (PPALETTE_ENTRY)FrLdrTempAlloc(sizeof(PALETTE_ENTRY) * 64,
989                                                         TAG_TUI_PALETTE);
990 
991         if (TuiFadePalette != NULL)
992         {
993             VideoSavePaletteState(TuiFadePalette, 64);
994             VideoSetAllColorsToBlack(64);
995         }
996     }
997 
998     // Draw the backdrop and title box
999     TuiDrawBackdrop();
1000 
1001     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed() && TuiFadePalette != NULL)
1002     {
1003         VideoFadeIn(TuiFadePalette, 64);
1004         FrLdrTempFree(TuiFadePalette, TAG_TUI_PALETTE);
1005     }
1006 }
1007 
TuiFadeOut(VOID)1008 VOID TuiFadeOut(VOID)
1009 {
1010     PPALETTE_ENTRY TuiFadePalette = NULL;
1011 
1012     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed())
1013     {
1014         TuiFadePalette = (PPALETTE_ENTRY)FrLdrTempAlloc(sizeof(PALETTE_ENTRY) * 64,
1015                                                         TAG_TUI_PALETTE);
1016 
1017         if (TuiFadePalette != NULL)
1018         {
1019             VideoSavePaletteState(TuiFadePalette, 64);
1020         }
1021     }
1022 
1023     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed() && TuiFadePalette != NULL)
1024     {
1025         VideoFadeOut(64);
1026     }
1027 
1028     MachVideoSetDisplayMode(NULL, FALSE);
1029 
1030     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed() && TuiFadePalette != NULL)
1031     {
1032         VideoRestorePaletteState(TuiFadePalette, 64);
1033         FrLdrTempFree(TuiFadePalette, TAG_TUI_PALETTE);
1034     }
1035 
1036 }
1037 
TuiEditBox(PCSTR MessageText,PCHAR EditTextBuffer,ULONG Length)1038 BOOLEAN TuiEditBox(PCSTR MessageText, PCHAR EditTextBuffer, ULONG Length)
1039 {
1040     CHAR    key;
1041     BOOLEAN Extended;
1042     INT     EditBoxLine;
1043     ULONG   EditBoxStartX, EditBoxEndX;
1044     INT     EditBoxCursorX;
1045     ULONG   EditBoxTextLength, EditBoxTextPosition;
1046     INT     EditBoxTextDisplayIndex;
1047     BOOLEAN ReturnCode;
1048     SMALL_RECT BoxRect;
1049     PVOID ScreenBuffer;
1050 
1051     /* Save the screen contents */
1052     ScreenBuffer = TuiSaveScreen();
1053 
1054     /* Draw the common parts of the message box */
1055     TuiDrawMsgBoxCommon(MessageText, &BoxRect);
1056 
1057     EditBoxTextLength = (ULONG)strlen(EditTextBuffer);
1058     EditBoxTextLength = min(EditBoxTextLength, Length - 1);
1059     EditBoxTextPosition = 0;
1060     EditBoxLine = BoxRect.Bottom - 2;
1061     EditBoxStartX = BoxRect.Left + 3;
1062     EditBoxEndX = BoxRect.Right - 3;
1063 
1064     // Draw the edit box background and the text
1065     UiFillArea(EditBoxStartX, EditBoxLine, EditBoxEndX, EditBoxLine, ' ', ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1066     UiDrawText2(EditBoxStartX, EditBoxLine, EditBoxEndX - EditBoxStartX + 1, EditTextBuffer, ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1067 
1068     // Show the cursor
1069     EditBoxCursorX = EditBoxStartX;
1070     MachVideoSetTextCursorPosition(EditBoxCursorX, EditBoxLine);
1071     MachVideoHideShowTextCursor(TRUE);
1072 
1073     // Draw status text
1074     UiDrawStatusText("Press ENTER to continue, or ESC to cancel");
1075 
1076     VideoCopyOffScreenBufferToVRAM();
1077 
1078     //
1079     // Enter the text. Please keep in mind that the default input mode
1080     // of the edit boxes is in insertion mode, that is, you can insert
1081     // text without erasing the existing one.
1082     //
1083     for (;;)
1084     {
1085         if (MachConsKbHit())
1086         {
1087             Extended = FALSE;
1088             key = MachConsGetCh();
1089             if (key == KEY_EXTENDED)
1090             {
1091                 Extended = TRUE;
1092                 key = MachConsGetCh();
1093             }
1094 
1095             if (key == KEY_ENTER)
1096             {
1097                 ReturnCode = TRUE;
1098                 break;
1099             }
1100             else if (key == KEY_ESC)
1101             {
1102                 ReturnCode = FALSE;
1103                 break;
1104             }
1105             else if (key == KEY_BACKSPACE) // Remove a character
1106             {
1107                 if ( (EditBoxTextLength > 0) && (EditBoxTextPosition > 0) &&
1108                      (EditBoxTextPosition <= EditBoxTextLength) )
1109                 {
1110                     EditBoxTextPosition--;
1111                     memmove(EditTextBuffer + EditBoxTextPosition,
1112                             EditTextBuffer + EditBoxTextPosition + 1,
1113                             EditBoxTextLength - EditBoxTextPosition);
1114                     EditBoxTextLength--;
1115                     EditTextBuffer[EditBoxTextLength] = 0;
1116                 }
1117                 else
1118                 {
1119                     MachBeep();
1120                 }
1121             }
1122             else if (Extended && key == KEY_DELETE) // Remove a character
1123             {
1124                 if ( (EditBoxTextLength > 0) &&
1125                      (EditBoxTextPosition < EditBoxTextLength) )
1126                 {
1127                     memmove(EditTextBuffer + EditBoxTextPosition,
1128                             EditTextBuffer + EditBoxTextPosition + 1,
1129                             EditBoxTextLength - EditBoxTextPosition);
1130                     EditBoxTextLength--;
1131                     EditTextBuffer[EditBoxTextLength] = 0;
1132                 }
1133                 else
1134                 {
1135                     MachBeep();
1136                 }
1137             }
1138             else if (Extended && key == KEY_HOME) // Go to the start of the buffer
1139             {
1140                 EditBoxTextPosition = 0;
1141             }
1142             else if (Extended && key == KEY_END) // Go to the end of the buffer
1143             {
1144                 EditBoxTextPosition = EditBoxTextLength;
1145             }
1146             else if (Extended && key == KEY_RIGHT) // Go right
1147             {
1148                 if (EditBoxTextPosition < EditBoxTextLength)
1149                     EditBoxTextPosition++;
1150                 else
1151                     MachBeep();
1152             }
1153             else if (Extended && key == KEY_LEFT) // Go left
1154             {
1155                 if (EditBoxTextPosition > 0)
1156                     EditBoxTextPosition--;
1157                 else
1158                     MachBeep();
1159             }
1160             else if (!Extended) // Add this key to the buffer
1161             {
1162                 if ( (EditBoxTextLength   < Length - 1) &&
1163                      (EditBoxTextPosition < Length - 1) )
1164                 {
1165                     memmove(EditTextBuffer + EditBoxTextPosition + 1,
1166                             EditTextBuffer + EditBoxTextPosition,
1167                             EditBoxTextLength - EditBoxTextPosition);
1168                     EditTextBuffer[EditBoxTextPosition] = key;
1169                     EditBoxTextPosition++;
1170                     EditBoxTextLength++;
1171                     EditTextBuffer[EditBoxTextLength] = 0;
1172                 }
1173                 else
1174                 {
1175                     MachBeep();
1176                 }
1177             }
1178             else
1179             {
1180                 MachBeep();
1181             }
1182         }
1183 
1184         // Draw the edit box background
1185         UiFillArea(EditBoxStartX, EditBoxLine, EditBoxEndX, EditBoxLine, ' ', ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1186 
1187         // Fill the text in
1188         if (EditBoxTextPosition > (EditBoxEndX - EditBoxStartX))
1189         {
1190             EditBoxTextDisplayIndex = EditBoxTextPosition - (EditBoxEndX - EditBoxStartX);
1191             EditBoxCursorX = EditBoxEndX;
1192         }
1193         else
1194         {
1195             EditBoxTextDisplayIndex = 0;
1196             EditBoxCursorX = EditBoxStartX + EditBoxTextPosition;
1197         }
1198         UiDrawText2(EditBoxStartX, EditBoxLine, EditBoxEndX - EditBoxStartX + 1, &EditTextBuffer[EditBoxTextDisplayIndex], ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1199 
1200         // Move the cursor
1201         MachVideoSetTextCursorPosition(EditBoxCursorX, EditBoxLine);
1202 
1203         TuiUpdateDateTime();
1204 
1205         VideoCopyOffScreenBufferToVRAM();
1206 
1207         MachHwIdle();
1208     }
1209 
1210     // Hide the cursor again
1211     MachVideoHideShowTextCursor(FALSE);
1212 
1213     /* Restore the screen contents */
1214     TuiRestoreScreen(ScreenBuffer);
1215 
1216     return ReturnCode;
1217 }
1218 
1219 const UIVTBL TuiVtbl =
1220 {
1221     TuiInitialize,
1222     TuiUnInitialize,
1223     TuiDrawBackdrop,
1224     TuiFillArea,
1225     TuiDrawShadow,
1226     TuiDrawBox,
1227     TuiDrawText,
1228     TuiDrawText2,
1229     TuiDrawCenteredText,
1230     TuiDrawStatusText,
1231     TuiUpdateDateTime,
1232     TuiMessageBox,
1233     TuiMessageBoxCritical,
1234     TuiDrawProgressBarCenter,
1235     TuiDrawProgressBar,
1236     TuiSetProgressBarText,
1237     TuiTickProgressBar,
1238     TuiEditBox,
1239     TuiTextToColor,
1240     TuiTextToFillStyle,
1241     TuiFadeInBackdrop,
1242     TuiFadeOut,
1243     TuiDisplayMenu,
1244     TuiDrawMenu,
1245 };
1246