]> code.delx.au - refind/blob - refind/screen.c
e1154fff43bdd680c3040e38ed77141d059ed004
[refind] / refind / screen.c
1 /*
2 * refind/screen.c
3 * Screen handling functions
4 *
5 * Copyright (c) 2006 Christoph Pfisterer
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * Neither the name of Christoph Pfisterer nor the names of the
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36
37 #include "global.h"
38 #include "screen.h"
39 #include "config.h"
40 #include "libegint.h"
41 #include "../include/refit_call_wrapper.h"
42
43 #include "../include/egemb_refind_banner.h"
44
45 // Console defines and variables
46
47 UINTN ConWidth;
48 UINTN ConHeight;
49 CHAR16 *BlankLine;
50
51 static VOID DrawScreenHeader(IN CHAR16 *Title);
52
53 // UGA defines and variables
54
55 UINTN UGAWidth;
56 UINTN UGAHeight;
57 BOOLEAN AllowGraphicsMode;
58
59 EG_PIXEL StdBackgroundPixel = { 0xbf, 0xbf, 0xbf, 0 };
60 EG_PIXEL MenuBackgroundPixel = { 0xbf, 0xbf, 0xbf, 0 };
61
62 static BOOLEAN GraphicsScreenDirty;
63
64 // general defines and variables
65
66 static BOOLEAN haveError = FALSE;
67
68 //
69 // Screen initialization and switching
70 //
71
72 VOID InitScreen(VOID)
73 {
74 UINTN i;
75
76 // initialize libeg
77 egInitScreen();
78
79 if (egHasGraphicsMode()) {
80 egGetScreenSize(&UGAWidth, &UGAHeight);
81 AllowGraphicsMode = TRUE;
82 } else {
83 AllowGraphicsMode = FALSE;
84 egSetGraphicsModeEnabled(FALSE); // just to be sure we are in text mode
85 }
86 GraphicsScreenDirty = TRUE;
87
88 // disable cursor
89 refit_call2_wrapper(ST->ConOut->EnableCursor, ST->ConOut, FALSE);
90
91 // get size of text console
92 if (refit_call4_wrapper(ST->ConOut->QueryMode, ST->ConOut, ST->ConOut->Mode->Mode, &ConWidth, &ConHeight) != EFI_SUCCESS) {
93 // use default values on error
94 ConWidth = 80;
95 ConHeight = 25;
96 }
97
98 // make a buffer for a whole text line
99 BlankLine = AllocatePool((ConWidth + 1) * sizeof(CHAR16));
100 for (i = 0; i < ConWidth; i++)
101 BlankLine[i] = ' ';
102 BlankLine[i] = 0;
103
104 // show the banner (even when in graphics mode)
105 DrawScreenHeader(L"Initializing...");
106 }
107
108 VOID SetupScreen(VOID)
109 {
110 if (GlobalConfig.TextOnly) {
111 // switch to text mode if requested
112 AllowGraphicsMode = FALSE;
113 SwitchToText(FALSE);
114
115 } else if (AllowGraphicsMode) {
116 // clear screen and show banner
117 // (now we know we'll stay in graphics mode)
118 if ((GlobalConfig.RequestedScreenWidth > 0) && (GlobalConfig.RequestedScreenHeight > 0) &&
119 egSetScreenSize(GlobalConfig.RequestedScreenWidth, GlobalConfig.RequestedScreenHeight)) {
120 UGAWidth = GlobalConfig.RequestedScreenWidth;
121 UGAHeight = GlobalConfig.RequestedScreenHeight;
122 } // if user requested a particular screen resolution
123 SwitchToGraphics();
124 BltClearScreen(TRUE);
125 }
126 }
127
128 VOID SwitchToText(IN BOOLEAN CursorEnabled)
129 {
130 egSetGraphicsModeEnabled(FALSE);
131 refit_call2_wrapper(ST->ConOut->EnableCursor, ST->ConOut, CursorEnabled);
132 }
133
134 VOID SwitchToGraphics(VOID)
135 {
136 if (AllowGraphicsMode && !egIsGraphicsModeEnabled()) {
137 egSetGraphicsModeEnabled(TRUE);
138 GraphicsScreenDirty = TRUE;
139 }
140 }
141
142 //
143 // Screen control for running tools
144 //
145
146 VOID BeginTextScreen(IN CHAR16 *Title)
147 {
148 DrawScreenHeader(Title);
149 SwitchToText(FALSE);
150
151 // reset error flag
152 haveError = FALSE;
153 }
154
155 VOID FinishTextScreen(IN BOOLEAN WaitAlways)
156 {
157 if (haveError || WaitAlways) {
158 SwitchToText(FALSE);
159 PauseForKey();
160 }
161
162 // reset error flag
163 haveError = FALSE;
164 }
165
166 VOID BeginExternalScreen(IN BOOLEAN UseGraphicsMode, IN CHAR16 *Title)
167 {
168 EG_PIXEL DarkBackgroundPixel = { 0x0, 0x0, 0x0, 0 };
169
170 if (!AllowGraphicsMode)
171 UseGraphicsMode = FALSE;
172
173 if (UseGraphicsMode) {
174 SwitchToGraphics();
175 BltClearScreen(FALSE);
176 } else {
177 egClearScreen(&DarkBackgroundPixel);
178 DrawScreenHeader(Title);
179 } // if/else
180
181 // show the header
182 // DrawScreenHeader(Title);
183
184 if (!UseGraphicsMode)
185 SwitchToText(TRUE);
186
187 // reset error flag
188 haveError = FALSE;
189 }
190
191 VOID FinishExternalScreen(VOID)
192 {
193 // make sure we clean up later
194 GraphicsScreenDirty = TRUE;
195
196 if (haveError) {
197 SwitchToText(FALSE);
198 PauseForKey();
199 }
200
201 // reset error flag
202 haveError = FALSE;
203 }
204
205 VOID TerminateScreen(VOID)
206 {
207 // clear text screen
208 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
209 refit_call1_wrapper(ST->ConOut->ClearScreen, ST->ConOut);
210
211 // enable cursor
212 refit_call2_wrapper(ST->ConOut->EnableCursor, ST->ConOut, TRUE);
213 }
214
215 static VOID DrawScreenHeader(IN CHAR16 *Title)
216 {
217 UINTN y;
218
219 // clear to black background
220 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
221 refit_call1_wrapper(ST->ConOut->ClearScreen, ST->ConOut);
222
223 // paint header background
224 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BANNER);
225 for (y = 0; y < 3; y++) {
226 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 0, y);
227 Print(BlankLine);
228 }
229
230 // print header text
231 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 3, 1);
232 Print(L"rEFInd - %s", Title);
233
234 // reposition cursor
235 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
236 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 0, 4);
237 }
238
239 //
240 // Keyboard input
241 //
242
243 static BOOLEAN ReadAllKeyStrokes(VOID)
244 {
245 BOOLEAN GotKeyStrokes;
246 EFI_STATUS Status;
247 EFI_INPUT_KEY key;
248
249 GotKeyStrokes = FALSE;
250 for (;;) {
251 Status = refit_call2_wrapper(ST->ConIn->ReadKeyStroke, ST->ConIn, &key);
252 if (Status == EFI_SUCCESS) {
253 GotKeyStrokes = TRUE;
254 continue;
255 }
256 break;
257 }
258 return GotKeyStrokes;
259 }
260
261 VOID PauseForKey(VOID)
262 {
263 UINTN index;
264
265 Print(L"\n* Hit any key to continue *");
266
267 if (ReadAllKeyStrokes()) { // remove buffered key strokes
268 refit_call1_wrapper(BS->Stall, 5000000); // 5 seconds delay
269 ReadAllKeyStrokes(); // empty the buffer again
270 }
271
272 refit_call3_wrapper(BS->WaitForEvent, 1, &ST->ConIn->WaitForKey, &index);
273 ReadAllKeyStrokes(); // empty the buffer to protect the menu
274
275 Print(L"\n");
276 }
277
278 #if REFIT_DEBUG > 0
279 VOID DebugPause(VOID)
280 {
281 // show console and wait for key
282 SwitchToText(FALSE);
283 PauseForKey();
284
285 // reset error flag
286 haveError = FALSE;
287 }
288 #endif
289
290 VOID EndlessIdleLoop(VOID)
291 {
292 UINTN index;
293
294 for (;;) {
295 ReadAllKeyStrokes();
296 refit_call3_wrapper(BS->WaitForEvent, 1, &ST->ConIn->WaitForKey, &index);
297 }
298 }
299
300 //
301 // Error handling
302 //
303
304 #ifdef __MAKEWITH_GNUEFI
305 BOOLEAN CheckFatalError(IN EFI_STATUS Status, IN CHAR16 *where)
306 {
307 CHAR16 ErrorName[64];
308
309 if (!EFI_ERROR(Status))
310 return FALSE;
311
312 StatusToString(ErrorName, Status);
313 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_ERROR);
314 Print(L"Fatal Error: %s %s\n", ErrorName, where);
315 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
316 haveError = TRUE;
317
318 //BS->Exit(ImageHandle, ExitStatus, ExitDataSize, ExitData);
319
320 return TRUE;
321 }
322
323 BOOLEAN CheckError(IN EFI_STATUS Status, IN CHAR16 *where)
324 {
325 CHAR16 ErrorName[64];
326
327 if (!EFI_ERROR(Status))
328 return FALSE;
329
330 StatusToString(ErrorName, Status);
331 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_ERROR);
332 Print(L"Error: %s %s\n", ErrorName, where);
333 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
334 haveError = TRUE;
335
336 return TRUE;
337 }
338 #else
339 BOOLEAN CheckFatalError(IN EFI_STATUS Status, IN CHAR16 *where)
340 {
341 // CHAR16 ErrorName[64];
342
343 if (!EFI_ERROR(Status))
344 return FALSE;
345
346 // StatusToString(ErrorName, Status);
347 gST->ConOut->SetAttribute (gST->ConOut, ATTR_ERROR);
348 Print(L"Fatal Error: %r %s\n", Status, where);
349 gST->ConOut->SetAttribute (gST->ConOut, ATTR_BASIC);
350 haveError = TRUE;
351
352 //gBS->Exit(ImageHandle, ExitStatus, ExitDataSize, ExitData);
353
354 return TRUE;
355 }
356
357 BOOLEAN CheckError(IN EFI_STATUS Status, IN CHAR16 *where)
358 {
359 // CHAR16 ErrorName[64];
360
361 if (!EFI_ERROR(Status))
362 return FALSE;
363
364 // StatusToString(ErrorName, Status);
365 gST->ConOut->SetAttribute (gST->ConOut, ATTR_ERROR);
366 Print(L"Error: %r %s\n", Status, where);
367 gST->ConOut->SetAttribute (gST->ConOut, ATTR_BASIC);
368 haveError = TRUE;
369
370 return TRUE;
371 }
372 #endif
373
374 //
375 // Graphics functions
376 //
377
378 VOID SwitchToGraphicsAndClear(VOID)
379 {
380 SwitchToGraphics();
381 if (GraphicsScreenDirty)
382 BltClearScreen(TRUE);
383 }
384
385 VOID BltClearScreen(IN BOOLEAN ShowBanner)
386 {
387 static EG_IMAGE *Banner = NULL;
388
389 if (ShowBanner && !(GlobalConfig.HideUIFlags & HIDEUI_FLAG_BANNER)) {
390 // load banner on first call
391 if (Banner == NULL) {
392 if (GlobalConfig.BannerFileName == NULL)
393 Banner = egPrepareEmbeddedImage(&egemb_refind_banner, FALSE);
394 else
395 Banner = egLoadImage(SelfDir, GlobalConfig.BannerFileName, FALSE);
396 if (Banner != NULL)
397 MenuBackgroundPixel = Banner->PixelData[0];
398 }
399
400 // clear and draw banner
401 egClearScreen(&MenuBackgroundPixel);
402 if (Banner != NULL)
403 BltImage(Banner, (UGAWidth - Banner->Width) >> 1,
404 ((UGAHeight - LAYOUT_TOTAL_HEIGHT) >> 1) + LAYOUT_BANNER_HEIGHT - Banner->Height);
405
406 } else {
407 // clear to standard background color
408 egClearScreen(&StdBackgroundPixel);
409 }
410
411 GraphicsScreenDirty = FALSE;
412 }
413
414 VOID BltImage(IN EG_IMAGE *Image, IN UINTN XPos, IN UINTN YPos)
415 {
416 egDrawImage(Image, XPos, YPos);
417 GraphicsScreenDirty = TRUE;
418 }
419
420 VOID BltImageAlpha(IN EG_IMAGE *Image, IN UINTN XPos, IN UINTN YPos, IN EG_PIXEL *BackgroundPixel)
421 {
422 EG_IMAGE *CompImage;
423
424 // compose on background
425 CompImage = egCreateFilledImage(Image->Width, Image->Height, FALSE, BackgroundPixel);
426 egComposeImage(CompImage, Image, 0, 0);
427
428 // blit to screen and clean up
429 egDrawImage(CompImage, XPos, YPos);
430 egFreeImage(CompImage);
431 GraphicsScreenDirty = TRUE;
432 }
433
434 // VOID BltImageComposite(IN EG_IMAGE *BaseImage, IN EG_IMAGE *TopImage, IN UINTN XPos, IN UINTN YPos)
435 // {
436 // UINTN TotalWidth, TotalHeight, CompWidth, CompHeight, OffsetX, OffsetY;
437 // EG_IMAGE *CompImage;
438 //
439 // // initialize buffer with base image
440 // CompImage = egCopyImage(BaseImage);
441 // TotalWidth = BaseImage->Width;
442 // TotalHeight = BaseImage->Height;
443 //
444 // // place the top image
445 // CompWidth = TopImage->Width;
446 // if (CompWidth > TotalWidth)
447 // CompWidth = TotalWidth;
448 // OffsetX = (TotalWidth - CompWidth) >> 1;
449 // CompHeight = TopImage->Height;
450 // if (CompHeight > TotalHeight)
451 // CompHeight = TotalHeight;
452 // OffsetY = (TotalHeight - CompHeight) >> 1;
453 // egComposeImage(CompImage, TopImage, OffsetX, OffsetY);
454 //
455 // // blit to screen and clean up
456 // egDrawImage(CompImage, XPos, YPos);
457 // egFreeImage(CompImage);
458 // GraphicsScreenDirty = TRUE;
459 // }
460
461 VOID BltImageCompositeBadge(IN EG_IMAGE *BaseImage, IN EG_IMAGE *TopImage, IN EG_IMAGE *BadgeImage, IN UINTN XPos, IN UINTN YPos)
462 {
463 UINTN TotalWidth = 0, TotalHeight = 0, CompWidth = 0, CompHeight = 0, OffsetX = 0, OffsetY = 0;
464 EG_IMAGE *CompImage = NULL;
465
466 // initialize buffer with base image
467 if (BaseImage != NULL) {
468 CompImage = egCopyImage(BaseImage);
469 TotalWidth = BaseImage->Width;
470 TotalHeight = BaseImage->Height;
471 }
472
473 // place the top image
474 if ((TopImage != NULL) && (CompImage != NULL)) {
475 CompWidth = TopImage->Width;
476 if (CompWidth > TotalWidth)
477 CompWidth = TotalWidth;
478 OffsetX = (TotalWidth - CompWidth) >> 1;
479 CompHeight = TopImage->Height;
480 if (CompHeight > TotalHeight)
481 CompHeight = TotalHeight;
482 OffsetY = (TotalHeight - CompHeight) >> 1;
483 egComposeImage(CompImage, TopImage, OffsetX, OffsetY);
484 }
485
486 // place the badge image
487 if (BadgeImage != NULL && CompImage != NULL && (BadgeImage->Width + 8) < CompWidth && (BadgeImage->Height + 8) < CompHeight) {
488 OffsetX += CompWidth - 8 - BadgeImage->Width;
489 OffsetY += CompHeight - 8 - BadgeImage->Height;
490 egComposeImage(CompImage, BadgeImage, OffsetX, OffsetY);
491 }
492
493 // blit to screen and clean up
494 egDrawImage(CompImage, XPos, YPos);
495 egFreeImage(CompImage);
496 GraphicsScreenDirty = TRUE;
497 }
498
499 // Line-editing functions borrowed from gummiboot (cursor_left(), cursor_right(), & line_edit())
500
501 static void cursor_left(UINTN *cursor, UINTN *first)
502 {
503 if ((*cursor) > 0)
504 (*cursor)--;
505 else if ((*first) > 0)
506 (*first)--;
507 }
508
509 static void cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len)
510 {
511 if ((*cursor)+2 < x_max)
512 (*cursor)++;
513 else if ((*first) + (*cursor) < len)
514 (*first)++;
515 }
516
517 BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) {
518 CHAR16 *line;
519 UINTN size;
520 UINTN len;
521 UINTN first;
522 CHAR16 *print;
523 UINTN cursor;
524 BOOLEAN exit;
525 BOOLEAN enter;
526
527 if (!line_in)
528 line_in = L"";
529 size = StrLen(line_in) + 1024;
530 line = AllocatePool(size * sizeof(CHAR16));
531 StrCpy(line, line_in);
532 len = StrLen(line);
533 print = AllocatePool(x_max * sizeof(CHAR16));
534
535 refit_call2_wrapper(ST->ConOut->EnableCursor, ST->ConOut, TRUE);
536
537 first = 0;
538 cursor = 0;
539 enter = FALSE;
540 exit = FALSE;
541 while (!exit) {
542 UINTN index;
543 EFI_STATUS err;
544 EFI_INPUT_KEY key;
545 UINTN i;
546
547 i = len - first;
548 if (i >= x_max-2)
549 i = x_max-2;
550 CopyMem(print, line + first, i * sizeof(CHAR16));
551 print[i++] = ' ';
552 print[i] = '\0';
553
554 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 0, y_pos);
555 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, print);
556 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, cursor, y_pos);
557
558 refit_call3_wrapper(BS->WaitForEvent, 1, &ST->ConIn->WaitForKey, &index);
559 err = refit_call2_wrapper(ST->ConIn->ReadKeyStroke, ST->ConIn, &key);
560 if (EFI_ERROR(err))
561 continue;
562
563 switch (key.ScanCode) {
564 case SCAN_ESC:
565 exit = TRUE;
566 break;
567 case SCAN_HOME:
568 cursor = 0;
569 first = 0;
570 continue;
571 case SCAN_END:
572 cursor = len;
573 if (cursor >= x_max) {
574 cursor = x_max-2;
575 first = len - (x_max-2);
576 }
577 continue;
578 case SCAN_UP:
579 while((first + cursor) && line[first + cursor] == ' ')
580 cursor_left(&cursor, &first);
581 while((first + cursor) && line[first + cursor] != ' ')
582 cursor_left(&cursor, &first);
583 while((first + cursor) && line[first + cursor] == ' ')
584 cursor_left(&cursor, &first);
585 if (first + cursor != len && first + cursor)
586 cursor_right(&cursor, &first, x_max, len);
587 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, cursor, y_pos);
588 continue;
589 case SCAN_DOWN:
590 while(line[first + cursor] && line[first + cursor] == ' ')
591 cursor_right(&cursor, &first, x_max, len);
592 while(line[first + cursor] && line[first + cursor] != ' ')
593 cursor_right(&cursor, &first, x_max, len);
594 while(line[first + cursor] && line[first + cursor] == ' ')
595 cursor_right(&cursor, &first, x_max, len);
596 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, cursor, y_pos);
597 continue;
598 case SCAN_RIGHT:
599 if (first + cursor == len)
600 continue;
601 cursor_right(&cursor, &first, x_max, len);
602 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, cursor, y_pos);
603 continue;
604 case SCAN_LEFT:
605 cursor_left(&cursor, &first);
606 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, cursor, y_pos);
607 continue;
608 case SCAN_DELETE:
609 if (len == 0)
610 continue;
611 if (first + cursor == len)
612 continue;
613 for (i = first + cursor; i < len; i++)
614 line[i] = line[i+1];
615 line[len-1] = ' ';
616 len--;
617 continue;
618 }
619
620 switch (key.UnicodeChar) {
621 case CHAR_LINEFEED:
622 case CHAR_CARRIAGE_RETURN:
623 if (StrCmp(line, line_in) != 0) {
624 *line_out = line;
625 line = NULL;
626 }
627 enter = TRUE;
628 exit = TRUE;
629 break;
630 case CHAR_BACKSPACE:
631 if (len == 0)
632 continue;
633 if (first == 0 && cursor == 0)
634 continue;
635 for (i = first + cursor-1; i < len; i++)
636 line[i] = line[i+1];
637 len--;
638 if (cursor > 0)
639 cursor--;
640 if (cursor > 0 || first == 0)
641 continue;
642 /* show full line if it fits */
643 if (len < x_max-2) {
644 cursor = first;
645 first = 0;
646 continue;
647 }
648 /* jump left to see what we delete */
649 if (first > 10) {
650 first -= 10;
651 cursor = 10;
652 } else {
653 cursor = first;
654 first = 0;
655 }
656 continue;
657 case '\t':
658 case ' ' ... '~':
659 case 0x80 ... 0xffff:
660 if (len+1 == size)
661 continue;
662 for (i = len; i > first + cursor; i--)
663 line[i] = line[i-1];
664 line[first + cursor] = key.UnicodeChar;
665 len++;
666 line[len] = '\0';
667 if (cursor+2 < x_max)
668 cursor++;
669 else if (first + cursor < len)
670 first++;
671 continue;
672 }
673 }
674
675 refit_call2_wrapper(ST->ConOut->EnableCursor, ST->ConOut, FALSE);
676 FreePool(print);
677 FreePool(line);
678 return enter;
679 } /* BOOLEAN line_edit() */