Chameleon

Chameleon Svn Source Tree

Root/branches/Chimera/i386/boot2/options.c

1/*
2 * Copyright (c) 1999-2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Portions Copyright (c) 1999-2004 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 2.0 (the "License"). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
20 * under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25#include "boot.h"
26#include "bootstruct.h"
27#include "fdisk.h"
28#include "ramdisk.h"
29#include "gui.h"
30#include "term.h"
31#include "embedded.h"
32#include "pci.h"
33#include "modules.h"
34
35#if DEBUG
36#define DBG(x...)printf(x)
37#else
38#define DBG(x...)msglog(x)
39#endif
40
41bool showBootBanner = true; //Azi:showinfo
42static bool shouldboot = false;
43
44extern int multiboot_timeout;
45extern int multiboot_timeout_set;
46
47extern BVRef bvChain;
48//extern intmenucount;
49
50extern intgDeviceCount;
51
52intselectIndex = 0;
53MenuItem * menuItems = NULL;
54
55enum {
56 kMenuTopRow = 5,
57 kMenuMaxItems = 10,
58 kScreenLastRow = 24
59};
60
61//==========================================================================
62
63typedef struct {
64 int x;
65 int y;
66 int type;
67} CursorState;
68
69static void changeCursor( int col, int row, int type, CursorState * cs )
70{
71 if (cs) getCursorPositionAndType( &cs->x, &cs->y, &cs->type );
72 setCursorType( type );
73 setCursorPosition( col, row, 0 );
74}
75
76static void moveCursor( int col, int row )
77{
78 setCursorPosition( col, row, 0 );
79}
80
81static void restoreCursor( const CursorState * cs )
82{
83 setCursorPosition( cs->x, cs->y, 0 );
84 setCursorType( cs->type );
85}
86
87//==========================================================================
88
89/* Flush keyboard buffer; returns TRUE if any of the flushed
90 * characters was F8.
91 */
92
93static bool flushKeyboardBuffer(void)
94{
95 bool status = false;
96
97 while ( readKeyboardStatus() ) {
98 if (bgetc() == 0x4200) status = true;
99 }
100 return status;
101}
102
103//==========================================================================
104
105static int countdown( const char * msg, int row, int timeout )
106{
107 unsigned long time;
108 int ch = 0;
109 int col = strlen(msg) + 1;
110
111 flushKeyboardBuffer();
112
113if( bootArgs->Video.v_display == VGA_TEXT_MODE )
114{
115moveCursor( 0, row );
116printf(msg);
117
118} else {
119
120position_t p = pos( gui.screen.width / 2 + 1 , ( gui.devicelist.pos.y + 3 ) + ( ( gui.devicelist.height - gui.devicelist.iconspacing ) / 2 ) );
121
122char dummy[80];
123getBootVolumeDescription( gBootVolume, dummy, sizeof(dummy) - 1, true );
124drawDeviceIcon( gBootVolume, gui.screen.pixmap, p, true );
125drawStrCenteredAt( (char *) msg, &font_small, gui.screen.pixmap, gui.countdown.pos );
126
127// make this screen the new background
128memcpy( gui.backbuffer->pixels, gui.screen.pixmap->pixels, gui.backbuffer->width * gui.backbuffer->height * 4 );
129
130}
131
132int multi_buff = 18 * (timeout);
133 int multi = ++multi_buff;
134
135 int lasttime=0;
136
137 for ( time = time18(), timeout++; timeout > 0; )
138 {
139if( time18() > lasttime)
140{
141multi--;
142lasttime=time18();
143}
144
145 if ( (ch = readKeyboardStatus()) )
146 break;
147
148 // Count can be interrupted by holding down shift,
149 // control or alt key
150 if ( ( readKeyboardShiftFlags() & 0x0F ) != 0 )
151{
152 ch = 1;
153 break;
154 }
155
156 if ( time18() >= time )
157 {
158 time += 18;
159 timeout--;
160
161if( bootArgs->Video.v_display == VGA_TEXT_MODE )
162{
163moveCursor( col, row );
164printf("(%d) ", timeout);
165}
166 }
167
168if( bootArgs->Video.v_display != VGA_TEXT_MODE )
169{
170drawProgressBar( gui.screen.pixmap, 100, gui.progressbar.pos , ( multi * 100 / multi_buff ) );
171gui.redraw = true;
172updateVRAM();
173}
174
175 }
176
177 flushKeyboardBuffer();
178
179 return ch;
180}
181
182//==========================================================================
183
184char gBootArgs[BOOT_STRING_LEN];
185static char * gBootArgsPtr = gBootArgs;
186static char * gBootArgsEnd = gBootArgs + BOOT_STRING_LEN - 1;
187static char booterCommand[BOOT_STRING_LEN];
188static char booterParam[BOOT_STRING_LEN];
189
190static void clearBootArgs(void)
191{
192gBootArgsPtr = gBootArgs;
193memset(gBootArgs, '\0', BOOT_STRING_LEN);
194
195if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
196clearGraphicBootPrompt();
197}
198execute_hook("ClearArgs", NULL, NULL, NULL, NULL);
199}
200
201void addBootArg(const char * argStr)
202{
203if ( (gBootArgsPtr + strlen(argStr) + 1) < gBootArgsEnd)
204{
205if(gBootArgsPtr != gBootArgs) *gBootArgsPtr++ = ' ';
206strcat(gBootArgs, argStr);
207gBootArgsPtr += strlen(argStr);
208}
209}
210
211//==========================================================================
212
213static void showBootPrompt(int row, bool visible)
214{
215extern char bootPrompt[];
216extern char bootRescanPrompt[];
217
218if( bootArgs->Video.v_display == VGA_TEXT_MODE ) {
219changeCursor( 0, row, kCursorTypeUnderline, 0 );
220clearScreenRows( row, kScreenLastRow );
221}
222
223clearBootArgs();
224
225if (visible) {
226if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
227if (gEnableCDROMRescan) {
228printf( bootRescanPrompt );
229} else {
230printf( bootPrompt );
231printf( gBootArgs );
232}
233}
234} else {
235if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
236clearGraphicBootPrompt();
237} else {
238printf("Press Enter to start up the foreign OS. ");
239}
240}
241}
242
243//==========================================================================
244
245static void updateBootArgs( int key )
246{
247 key = ASCII_KEY(key);
248
249 switch ( key )
250 {
251 case KEY_BKSP:
252 if ( gBootArgsPtr > gBootArgs )
253 {
254 *--gBootArgsPtr = '\0';
255
256 int x, y, t;
257 getCursorPositionAndType( &x, &y, &t );
258 if ( x == 0 && y )
259 {
260 x = 80; y--;
261 }
262 if (x) {
263x--;
264}
265
266if( bootArgs->Video.v_display == VGA_TEXT_MODE )
267{
268setCursorPosition( x, y, 0 );
269putca(' ', 0x07, 1);
270}
271 else
272 {
273 updateGraphicBootPrompt();
274 }
275 }
276break;
277
278 default:
279 if ( key >= ' ' && gBootArgsPtr < gBootArgsEnd)
280 {
281 *gBootArgsPtr++ = key;
282
283 if( bootArgs->Video.v_display != VGA_TEXT_MODE ) updateGraphicBootPrompt();
284 else if ( key >= ' ' && key < 0x7f) putchar(key);
285}
286
287break;
288 }
289}
290
291//==========================================================================
292
293static const MenuItem * gMenuItems = NULL;
294
295static int gMenuItemCount;
296static int gMenuRow;
297static int gMenuHeight;
298static int gMenuTop;
299static int gMenuBottom;
300static int gMenuSelection;
301
302static int gMenuStart;
303static int gMenuEnd;
304
305static void printMenuItem( const MenuItem * item, int highlight )
306{
307 printf(" ");
308
309 if ( highlight )
310 putca(' ', 0x70, strlen(item->name) + 4);
311 else
312 putca(' ', 0x07, 40);
313
314 printf(" %40s\n", item->name);
315}
316
317//==========================================================================
318
319static void showMenu( const MenuItem * items, int count,
320 int selection, int row, int height )
321{
322 int i;
323 CursorState cursorState;
324
325 if ( items == NULL || count == 0 )
326return;
327
328 // head and tail points to the start and the end of the list.
329 // top and bottom points to the first and last visible items
330 // in the menu window.
331
332 gMenuItems= items;
333 gMenuRow= row;
334 gMenuHeight= height;
335 gMenuItemCount= count;
336 gMenuTop= 0;
337 gMenuBottom= MIN( count, height ) - 1;
338 gMenuSelection= selection;
339
340 gMenuStart= 0;
341 gMenuEnd = MIN( count, gui.maxdevices ) - 1;
342
343// If the selected item is not visible, shift the list down.
344
345 if ( gMenuSelection > gMenuBottom )
346 {
347 gMenuTop += ( gMenuSelection - gMenuBottom );
348 gMenuBottom = gMenuSelection;
349 }
350
351if ( gMenuSelection > gMenuEnd )
352 {
353gMenuStart += ( gMenuSelection - gMenuEnd );
354 gMenuEnd = gMenuSelection;
355 }
356
357// Draw the visible items.
358
359if( bootArgs->Video.v_display != VGA_TEXT_MODE )
360
361drawDeviceList(gMenuStart, gMenuEnd, gMenuSelection);
362
363else {
364
365changeCursor( 0, row, kCursorTypeHidden, &cursorState );
366
367for ( i = gMenuTop; i <= gMenuBottom; i++ )
368{
369printMenuItem( &items[i], (i == gMenuSelection) );
370}
371
372restoreCursor( &cursorState );
373 }
374}
375
376//==========================================================================
377
378static int updateMenu( int key, void ** paramPtr )
379{
380 int moved = 0;
381
382 union {
383 struct {
384 unsigned int
385 selectionUp : 1,
386 selectionDown : 1,
387 scrollUp : 1,
388 scrollDown : 1;
389 } f;
390 unsigned int w;
391 } draw = {{0}};
392
393 if ( gMenuItems == NULL )
394return 0;
395
396if( bootArgs->Video.v_display != VGA_TEXT_MODE )
397{
398int res;
399
400// set navigation keys for horizontal layout as defaults
401int previous= 0x4B00;// left arrow
402int subsequent= 0x4D00;// right arrow
403int menu= 0x5000;// down arrow
404
405if ( gui.layout == VerticalLayout )
406{
407// set navigation keys for vertical layout
408previous= 0x4800;// up arrow
409subsequent= 0x5000;// down arrow
410menu= 0x4B00;// right arrow
411}
412
413if ( key == previous )
414{
415if ( gMenuSelection > gMenuTop )
416draw.f.selectionUp = 1;
417else if ( gMenuTop > 0 )
418draw.f.scrollDown = 1;
419
420}
421
422else if ( key == subsequent )
423{
424if ( gMenuSelection != gMenuBottom)
425draw.f.selectionDown = 1;
426else if ( gMenuBottom < ( gMenuItemCount - 1 ) )
427draw.f.scrollUp = 1;
428}
429
430else if ( key == menu )
431{
432if ( gui.menu.draw )
433updateInfoMenu(key);
434else
435drawInfoMenu();
436}
437
438else if ( gui.menu.draw )
439{
440res = updateInfoMenu(key);
441
442if ( res == CLOSE_INFO_MENU )
443gui.menu.draw = false;
444else
445{
446shouldboot = ( res != DO_NOT_BOOT );
447
448if ( shouldboot )
449gui.menu.draw = false;
450
451switch (res)
452{
453case BOOT_NORMAL:
454gVerboseMode = false;
455gBootMode = kBootModeNormal;
456break;
457
458case BOOT_VERBOSE:
459gVerboseMode = true;
460gBootMode = kBootModeNormal;
461addBootArg(kVerboseModeFlag);
462break;
463
464case BOOT_IGNORECACHE:
465gVerboseMode = false;
466gBootMode = kBootModeNormal;
467addBootArg(kIgnoreCachesFlag);
468break;
469
470case BOOT_SINGLEUSER:
471gVerboseMode = true;
472gBootMode = kBootModeNormal;
473addBootArg(kSingleUserModeFlag);
474break;
475}
476
477}
478
479}
480
481} else {
482switch ( key )
483{
484 case 0x4800: // Up Arrow
485if ( gMenuSelection != gMenuTop )
486draw.f.selectionUp = 1;
487else if ( gMenuTop > 0 )
488draw.f.scrollDown = 1;
489break;
490
491case 0x5000: // Down Arrow
492if ( gMenuSelection != gMenuBottom )
493draw.f.selectionDown = 1;
494else if ( gMenuBottom < (gMenuItemCount - 1) )
495draw.f.scrollUp = 1;
496break;
497}
498}
499
500 if ( draw.w )
501 {
502 if ( draw.f.scrollUp )
503 {
504 scollPage(0, gMenuRow, 40, gMenuRow + gMenuHeight - 1, 0x07, 1, 1);
505 gMenuTop++; gMenuBottom++;
506gMenuStart++; gMenuEnd++;
507 draw.f.selectionDown = 1;
508 }
509
510 if ( draw.f.scrollDown )
511 {
512 scollPage(0, gMenuRow, 40, gMenuRow + gMenuHeight - 1, 0x07, 1, -1);
513 gMenuTop--; gMenuBottom--;
514 gMenuStart--; gMenuEnd--;
515 draw.f.selectionUp = 1;
516 }
517
518 if ( draw.f.selectionUp || draw.f.selectionDown )
519 {
520
521CursorState cursorState;
522
523// Set cursor at current position, and clear inverse video.
524
525if( bootArgs->Video.v_display == VGA_TEXT_MODE )
526{
527changeCursor( 0, gMenuRow + gMenuSelection - gMenuTop, kCursorTypeHidden, &cursorState );
528printMenuItem( &gMenuItems[gMenuSelection], 0 );
529}
530
531if ( draw.f.selectionUp )
532{
533gMenuSelection--;
534if(( gMenuSelection - gMenuStart) == -1 )
535{
536gMenuStart--;
537gMenuEnd--;
538}
539
540} else {
541gMenuSelection++;
542if(( gMenuSelection - ( gui.maxdevices - 1) - gMenuStart) > 0 )
543{
544gMenuStart++;
545gMenuEnd++;
546}
547 }
548
549if( bootArgs->Video.v_display == VGA_TEXT_MODE )
550 {
551moveCursor( 0, gMenuRow + gMenuSelection - gMenuTop );
552printMenuItem( &gMenuItems[gMenuSelection], 1 );
553restoreCursor( &cursorState );
554
555 } else
556
557drawDeviceList (gMenuStart, gMenuEnd, gMenuSelection);
558
559}
560
561 *paramPtr = gMenuItems[gMenuSelection].param;
562 moved = 1;
563 }
564
565return moved;
566}
567
568//==========================================================================
569
570static void skipblanks( const char ** cpp )
571{
572 while ( **(cpp) == ' ' || **(cpp) == '\t' ) ++(*cpp);
573}
574
575//==========================================================================
576
577static const char * extractKernelName( char ** cpp )
578{
579 char * kn = *cpp;
580 char * cp = *cpp;
581 char c;
582
583 // Convert char to lower case.
584
585 c = *cp | 0x20;
586
587 // Must start with a letter or a '/'.
588
589 if ( (c < 'a' || c > 'z') && ( c != '/' ) )
590 return 0;
591
592 // Keep consuming characters until we hit a separator.
593
594 while ( *cp && (*cp != '=') && (*cp != ' ') && (*cp != '\t') )
595 cp++;
596
597 // Only SPACE or TAB separator is accepted.
598 // Reject everything else.
599
600 if (*cp == '=')
601 return 0;
602
603 // Overwrite the separator, and move the pointer past
604 // the kernel name.
605
606 if (*cp != '\0') *cp++ = '\0';
607 *cpp = cp;
608
609 return kn;
610}
611
612//==========================================================================
613
614static void
615printMemoryInfo(void)
616{
617 int line;
618 int i;
619 MemoryRange *mp = bootInfo->memoryMap;
620
621 // Activate and clear page 1
622 setActiveDisplayPage(1);
623 clearScreenRows(0, 24);
624 setCursorPosition( 0, 0, 1 );
625
626 printf("BIOS reported memory ranges:\n");
627 line = 1;
628 for (i=0; i<bootInfo->memoryMapCount; i++) {
629 printf("Base 0x%08x%08x, ",
630 (unsigned long)(mp->base >> 32),
631 (unsigned long)(mp->base));
632 printf("length 0x%08x%08x, type %d\n",
633 (unsigned long)(mp->length >> 32),
634 (unsigned long)(mp->length),
635 mp->type);
636 if (line++ > 20) {
637 pause();
638 line = 0;
639 }
640 mp++;
641 }
642 if (line > 0) {
643 pause();
644 }
645
646 setActiveDisplayPage(0);
647}
648
649char *getMemoryInfoString()
650{
651int i, bufflen;
652MemoryRange *mp = bootInfo->memoryMap;
653char *buff = malloc(sizeof(char)*1024);
654if(!buff) {
655return 0;
656}
657
658static const char info[] = "BIOS reported memory ranges:\n";
659bufflen = sprintf(buff, "%s", info);
660
661for (i = 0;
662(i < bootInfo->memoryMapCount) && (bufflen < 1024); /* prevent buffer overflow */
663i++) {
664bufflen += snprintf(buff+bufflen, 1024-bufflen, "Base 0x%08x%08x, ",
665 (unsigned long)(mp->base >> 32),
666 (unsigned long)(mp->base));
667bufflen += snprintf(buff+bufflen, 1024-bufflen, "length 0x%08x%08x, type %d\n",
668 (unsigned long)(mp->length >> 32),
669 (unsigned long)(mp->length),
670 mp->type);
671mp++;
672}
673return buff;
674}
675
676//==========================================================================
677
678void lspci(void)
679{
680if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
681setActiveDisplayPage(1);
682clearScreenRows(0, 24);
683setCursorPosition(0, 0, 1);
684}
685
686dump_pci_dt(root_pci_dev->children);
687
688pause();
689
690if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
691setActiveDisplayPage(0);
692}
693}
694
695//==========================================================================
696
697int getBootOptions(bool firstRun)
698{
699int i;
700int key;
701int nextRow;
702int timeout;
703int bvCount;
704BVRef bvr;
705BVRef menuBVR;
706bool showPrompt, newShowPrompt, isCDROM;
707
708// Initialize default menu selection entry.
709gBootVolume = menuBVR = selectBootVolume(bvChain);
710
711if (biosDevIsCDROM(gBIOSDev)) {
712isCDROM = true;
713} else {
714isCDROM = false;
715}
716
717// ensure we're in graphics mode if gui is setup
718if (firstRun && gui.initialised && bootArgs->Video.v_display == VGA_TEXT_MODE)
719{
720setVideoMode(GRAPHICS_MODE, 0);
721}
722
723// Clear command line boot arguments
724clearBootArgs();
725
726// Allow user to override default timeout.
727if (multiboot_timeout_set) {
728timeout = multiboot_timeout;
729} else if (!getIntForKey(kTimeoutKey, &timeout, &bootInfo->chameleonConfig)) {
730/* If there is no timeout key in the file use the default timeout
731 which is different for CDs vs. hard disks. However, if not booting
732 a CD and no config file could be loaded set the timeout
733 to zero which causes the menu to display immediately.
734 This way, if no partitions can be found, that is the disk is unpartitioned
735 or simply cannot be read) then an empty menu is displayed.
736 If some partitions are found, for example a Windows partition, then
737 these will be displayed in the menu as foreign partitions.
738 */
739if (isCDROM) {
740timeout = kCDBootTimeout;
741} else {
742timeout = sysConfigValid ? kBootTimeout : 0;
743}
744}
745
746if (timeout < 0) {
747gBootMode |= kBootModeQuiet;
748}
749
750// If the user is holding down a modifier key, enter safe mode.
751if ((readKeyboardShiftFlags() & 0x0F) != 0) {
752gBootMode |= kBootModeSafe;
753}
754
755// Checking user pressed keys
756bool f8press = false, spress = false, vpress = false;
757while (readKeyboardStatus()) {
758key = bgetc ();
759if (key == 0x4200) f8press = true;
760if ((key & 0xff) == 's' || (key & 0xff) == 'S') spress = true;
761if ((key & 0xff) == 'v' || (key & 0xff) == 'V') vpress = true;
762}
763// If user typed F8, abort quiet mode, and display the menu.
764if (f8press) {
765gBootMode &= ~kBootModeQuiet;
766timeout = 0;
767}
768// If user typed 'v' or 'V', boot in verbose mode.
769if ((gBootMode & kBootModeQuiet) && firstRun && vpress) {
770addBootArg(kVerboseModeFlag);
771}
772// If user typed 's' or 'S', boot in single user mode.
773if ((gBootMode & kBootModeQuiet) && firstRun && spress) {
774addBootArg(kSingleUserModeFlag);
775}
776
777if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
778setCursorPosition(0, 0, 0);
779clearScreenRows(0, kScreenLastRow);
780if (!(gBootMode & kBootModeQuiet)) {
781// Display banner and show hardware info.
782printf(bootBanner, (bootInfo->convmem + bootInfo->extmem) / 1024);
783printf(getVBEInfoString());
784}
785changeCursor(0, kMenuTopRow, kCursorTypeUnderline, 0);
786verbose("Scanning device %x...", gBIOSDev);
787}
788
789// When booting from CD, default to hard drive boot when possible.
790if (isCDROM && firstRun) {
791const char *val;
792char *prompt = NULL;
793char *name = NULL;
794int cnt;
795int optionKey;
796
797if (getValueForKey(kCDROMPromptKey, &val, &cnt, &bootInfo->chameleonConfig)) {
798prompt = malloc(cnt + 1);
799strncat(prompt, val, cnt);
800} else {
801name = malloc(80);
802getBootVolumeDescription(gBootVolume, name, 79, false);
803prompt = malloc(256);
804sprintf(prompt, "Press any key to start up from %s, or press F8 to enter startup options.", name);
805free(name);
806}
807
808if (getIntForKey( kCDROMOptionKey, &optionKey, &bootInfo->chameleonConfig )) {
809// The key specified is a special key.
810} else {
811// Default to F8.
812optionKey = 0x4200;
813}
814
815// If the timeout is zero then it must have been set above due to the
816// early catch of F8 which means the user wants to set boot options
817// which we ought to interpret as meaning he wants to boot the CD.
818if (timeout != 0) {
819key = countdown(prompt, kMenuTopRow, timeout);
820} else {
821key = optionKey;
822}
823
824if (prompt != NULL) {
825free(prompt);
826}
827
828clearScreenRows( kMenuTopRow, kMenuTopRow + 2 );
829
830// Hit the option key ?
831if (key == optionKey) {
832gBootMode &= ~kBootModeQuiet;
833timeout = 0;
834} else {
835key = key & 0xFF;
836
837// Try booting hard disk if user pressed 'h'
838if (biosDevIsCDROM(gBIOSDev) && key == 'h') {
839BVRef bvr;
840
841// Look at partitions hosting OS X other than the CD-ROM
842for (bvr = bvChain; bvr; bvr=bvr->next) {
843if ((bvr->flags & kBVFlagSystemVolume) && bvr->biosdev != gBIOSDev) {
844gBootVolume = bvr;
845}
846}
847}
848goto done;
849}
850}
851
852if (gBootMode & kBootModeQuiet) {
853// No input allowed from user.
854goto done;
855}
856
857if (firstRun && timeout > 0 && countdown("Press any key to enter startup options.", kMenuTopRow, timeout) == 0) {
858// If the user is holding down a modifier key,
859// enter safe mode.
860if ((readKeyboardShiftFlags() & 0x0F) != 0) {
861gBootMode |= kBootModeSafe;
862}
863goto done;
864}
865
866if (gDeviceCount > 0) {
867// Allocate memory for an array of menu items.
868menuItems = malloc(sizeof(MenuItem) * gDeviceCount);
869if (menuItems == NULL) {
870goto done;
871}
872
873// Associate a menu item for each BVRef.
874for (bvr=bvChain, i=gDeviceCount-1, selectIndex=-1; bvr; bvr=bvr->next) {
875if (bvr->visible) {
876getBootVolumeDescription(bvr, menuItems[i].name, sizeof(menuItems[i].name) - 1, true);
877menuItems[i].param = (void *) bvr;
878if (bvr == menuBVR) {
879selectIndex = i;
880}
881i--;
882}
883}
884// Jief : In case the default partition (returned by selectBootVolume) is not in the menu
885if ( selectIndex == -1 )
886{
887selectIndex = 0;
888
889// gDeviceCount is actually > 0, so menuItems[selectIndex] exists
890menuBVR = (BVRef)(menuItems[selectIndex].param);
891// what happen is bvChain is empty ?
892}
893}
894
895if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
896// redraw the background buffer
897gui.logo.draw = true;
898drawBackground();
899gui.devicelist.draw = true;
900gui.redraw = true;
901if (!(gBootMode & kBootModeQuiet)) {
902
903// Check if "Boot Banner"=N switch is present in config file.
904getBoolForKey(kBootBannerKey, &showBootBanner, &bootInfo->chameleonConfig);
905if (showBootBanner) {
906// Display banner and show hardware info.
907gprintf(&gui.screen, bootBanner + 1, (bootInfo->convmem + bootInfo->extmem) / 1024);
908}
909
910// redraw background
911memcpy(gui.backbuffer->pixels, gui.screen.pixmap->pixels, gui.backbuffer->width * gui.backbuffer->height * 4);
912}
913} else {
914// Clear screen and hide the blinking cursor.
915clearScreenRows(kMenuTopRow, kMenuTopRow + 2);
916changeCursor(0, kMenuTopRow, kCursorTypeHidden, 0);
917}
918
919nextRow = kMenuTopRow;
920showPrompt = true;
921
922if (gDeviceCount) {
923if( bootArgs->Video.v_display == VGA_TEXT_MODE ) {
924printf("Use \30\31 keys to select the startup volume.");
925}
926showMenu( menuItems, gDeviceCount, selectIndex, kMenuTopRow + 2, kMenuMaxItems );
927nextRow += MIN( gDeviceCount, kMenuMaxItems ) + 3;
928}
929
930// Show the boot prompt.
931showPrompt = (gDeviceCount == 0) || (menuBVR->flags & kBVFlagNativeBoot);
932showBootPrompt( nextRow, showPrompt );
933
934do {
935if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
936// redraw background
937memcpy( gui.backbuffer->pixels, gui.screen.pixmap->pixels, gui.backbuffer->width * gui.backbuffer->height * 4 );
938// reset cursor co-ords
939gui.debug.cursor = pos( gui.screen.width - 160 , 10 );
940}
941key = getchar();
942updateMenu( key, (void **) &menuBVR );
943newShowPrompt = (gDeviceCount == 0) || (menuBVR->flags & kBVFlagNativeBoot);
944
945if (newShowPrompt != showPrompt) {
946showPrompt = newShowPrompt;
947showBootPrompt( nextRow, showPrompt );
948}
949
950if (showPrompt) {
951updateBootArgs(key);
952}
953
954switch (key) {
955case KEY_ENTER:
956if (gui.menu.draw) {
957key=0;
958break;
959}
960if (*gBootArgs == '?') {
961char * argPtr = gBootArgs;
962
963// Skip the leading "?" character.
964argPtr++;
965getNextArg(&argPtr, booterCommand);
966getNextArg(&argPtr, booterParam);
967
968/*
969* TODO: this needs to be refactored.
970*/
971if (strcmp( booterCommand, "video" ) == 0) {
972if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
973showInfoBox(getVBEInfoString(), getVBEModeInfoString());
974} else {
975printVBEModeInfo();
976}
977} else if ( strcmp( booterCommand, "memory" ) == 0) {
978if (bootArgs->Video.v_display != VGA_TEXT_MODE ) {
979showInfoBox("Memory Map", getMemoryInfoString());
980} else {
981printMemoryInfo();
982}
983} else if (strcmp(booterCommand, "lspci") == 0) {
984lspci();
985} else if (strcmp(booterCommand, "more") == 0) {
986showTextFile(booterParam);
987} else if (strcmp(booterCommand, "rd") == 0) {
988processRAMDiskCommand(&argPtr, booterParam);
989} else if (strcmp(booterCommand, "norescan") == 0) {
990if (gEnableCDROMRescan) {
991gEnableCDROMRescan = false;
992break;
993}
994} else {
995showHelp();
996}
997key = 0;
998showBootPrompt(nextRow, showPrompt);
999break;
1000}
1001gBootVolume = menuBVR;
1002setRootVolume(menuBVR);
1003gBIOSDev = menuBVR->biosdev;
1004break;
1005
1006case KEY_ESC:
1007clearBootArgs();
1008break;
1009
1010case KEY_F5:
1011// New behavior:
1012// Clear gBootVolume to restart the loop
1013// if the user enabled rescanning the optical drive.
1014// Otherwise boot the default boot volume.
1015if (gEnableCDROMRescan) {
1016gBootVolume = NULL;
1017clearBootArgs();
1018}
1019break;
1020
1021case KEY_F10:
1022gScanSingleDrive = false;
1023scanDisks(gBIOSDev, &bvCount);
1024gBootVolume = NULL;
1025clearBootArgs();
1026break;
1027
1028case KEY_TAB:
1029// New behavior:
1030// Switch between text & graphic interfaces
1031// Only Permitted if started in graphics interface
1032if (useGUI) {
1033if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
1034setVideoMode(VGA_TEXT_MODE, 0);
1035
1036setCursorPosition(0, 0, 0);
1037clearScreenRows(0, kScreenLastRow);
1038
1039// Display banner and show hardware info.
1040printf(bootBanner, (bootInfo->convmem + bootInfo->extmem) / 1024);
1041printf(getVBEInfoString());
1042
1043clearScreenRows(kMenuTopRow, kMenuTopRow + 2);
1044changeCursor(0, kMenuTopRow, kCursorTypeHidden, 0);
1045
1046nextRow = kMenuTopRow;
1047showPrompt = true;
1048
1049if (gDeviceCount) {
1050printf("Use \30\31 keys to select the startup volume.");
1051showMenu(menuItems, gDeviceCount, selectIndex, kMenuTopRow + 2, kMenuMaxItems);
1052nextRow += MIN(gDeviceCount, kMenuMaxItems) + 3;
1053}
1054
1055showPrompt = (gDeviceCount == 0) || (menuBVR->flags & kBVFlagNativeBoot);
1056showBootPrompt(nextRow, showPrompt);
1057//changeCursor( 0, kMenuTopRow, kCursorTypeUnderline, 0 );
1058} else {
1059gui.redraw = true;
1060setVideoMode(GRAPHICS_MODE, 0);
1061updateVRAM();
1062 updateGraphicBootPrompt();
1063}
1064}
1065key = 0;
1066break;
1067
1068default:
1069key = 0;
1070break;
1071}
1072} while (0 == key);
1073
1074done:
1075if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
1076clearScreenRows(kMenuTopRow, kScreenLastRow);
1077changeCursor(0, kMenuTopRow, kCursorTypeUnderline, 0);
1078}
1079shouldboot = false;
1080gui.menu.draw = false;
1081if (menuItems) {
1082free(menuItems);
1083menuItems = NULL;
1084}
1085// MacMan commented out the next line to prevent writing the command line boot options to nvram
1086//execute_hook("BootOptions", gBootArgs, gBootArgsPtr, NULL, NULL);
1087return 0;
1088}
1089
1090//==========================================================================
1091
1092char gBootUUIDString[32+4+1] = ""; // UUID of the boot volume e.g. 5EB1869F-C4FA-3502-BDEB-3B8ED5D87292
1093extern unsigned char chainbootdev;
1094extern unsigned char chainbootflag;
1095
1096bool copyArgument(const char *argName, const char *val, int cnt, char **argP, int *cntRemainingP)
1097{
1098 int argLen = argName ? strlen(argName) : 0;
1099 int len = argLen + cnt + 1; // +1 to account for space
1100
1101if (argName)
1102 {
1103len++; // +1 to account for '='
1104 }
1105
1106 if (len > *cntRemainingP) {
1107 error("Warning: boot arguments too long, truncating\n");
1108 return false;
1109 }
1110
1111 if (argName) {
1112 strncpy( *argP, argName, argLen );
1113 *argP += argLen;
1114 *argP[0] = '=';
1115 (*argP)++;
1116 }
1117
1118 strncpy( *argP, val, cnt );
1119 *argP += cnt;
1120 *argP[0] = ' ';
1121 (*argP)++;
1122
1123 *cntRemainingP -= len;
1124 return true;
1125}
1126
1127//
1128// Returns TRUE if an argument was copied, FALSE otherwise
1129bool
1130processBootArgument(
1131 const char *argName, // The argument to search for
1132 const char *userString, // Typed-in boot arguments
1133 const char *kernelFlags, // Kernel flags from config table
1134 const char *configTable,
1135 char **argP, // Output value
1136 int *cntRemainingP, // Output count
1137 char *foundVal, // found value
1138 int foundValSize // max found value size
1139 ) {
1140const char *val;
1141int cnt;
1142bool found = false;
1143
1144if (getValueForBootKey(userString, argName, &val, &cnt)) {
1145// Don't copy; these values will be copied at the end of argument processing.
1146found = true;
1147} else if (getValueForBootKey(kernelFlags, argName, &val, &cnt)) {
1148// Don't copy; these values will be copied at the end of argument processing.
1149found = true;
1150} else if (getValueForKey(argName, &val, &cnt, &bootInfo->chameleonConfig)) {
1151copyArgument(argName, val, cnt, argP, cntRemainingP);
1152found = true;
1153}
1154if (found && foundVal) {
1155strlcpy(foundVal, val, foundValSize);
1156}
1157return found;
1158}
1159
1160// Maximum config table value size
1161#define VALUE_SIZE 2048
1162
1163int
1164processBootOptions()
1165{
1166const char *cp = gBootArgs;
1167const char *val = 0;
1168const char *kernel;
1169int cnt;
1170int userCnt;
1171int cntRemaining;
1172char *argP;
1173char *configKernelFlags;
1174char *valueBuffer;
1175
1176valueBuffer = malloc(VALUE_SIZE);
1177
1178skipblanks( &cp );
1179
1180// Update the unit and partition number.
1181
1182if ( gBootVolume ) {
1183if (!( gBootVolume->flags & kBVFlagNativeBoot )) {
1184readBootSector( gBootVolume->biosdev, gBootVolume->part_boff, (void *) 0x7c00 );
1185//
1186// Setup edx, and signal intention to chain load the
1187// foreign booter.
1188//
1189
1190chainbootdev = gBootVolume->biosdev;
1191chainbootflag = 1;
1192
1193return 1;
1194}
1195
1196setRootVolume(gBootVolume);
1197
1198}
1199// If no boot volume fail immediately because we're just going to fail
1200// trying to load the config file anyway.
1201else {
1202return -1;
1203}
1204
1205 // Find out which version mac os we're booting.
1206 strlcpy(gMacOSVersion, gBootVolume->OSVersion, sizeof(gMacOSVersion));
1207// printf("OS Version Booted %s\n", gBootVolume->OSVersion);
1208
1209// Load config table specified by the user, or use the default.
1210
1211if (!getValueForBootKey(cp, "config", &val, &cnt)) {
1212val = 0;
1213cnt = 0;
1214}
1215
1216// Load com.apple.Boot.plist from the selected volume
1217// and use its contents to override default bootConfig.
1218
1219loadSystemConfig(&bootInfo->bootConfig);
1220loadChameleonConfig(&bootInfo->chameleonConfig, NULL);
1221
1222// Use the kernel name specified by the user, or fetch the name
1223// in the config table, or use the default if not specified.
1224// Specifying a kernel name on the command line, or specifying
1225// a non-default kernel name in the config file counts as
1226// overriding the kernel, which causes the kernelcache not
1227// to be used.
1228
1229gOverrideKernel = false;
1230if (( kernel = extractKernelName((char **)&cp) ))
1231 {
1232strlcpy( bootInfo->bootFile, kernel, sizeof(bootInfo->bootFile) );
1233} else {
1234if ( getValueForKey( kKernelNameKey, &val, &cnt, &bootInfo->bootConfig ) )
1235 {
1236strlcpy( bootInfo->bootFile, val, cnt+1 );
1237} else {
1238 if ((checkOSVersion("10.10"))) {
1239 strlcpy( bootInfo->bootFile, kDefaultKernelYosemite, sizeof(bootInfo->bootFile) );
1240 } else {
1241 strlcpy( bootInfo->bootFile, kDefaultKernel, sizeof(bootInfo->bootFile) );
1242 }
1243}
1244}
1245if ((strcmp( bootInfo->bootFile, kDefaultKernel ) != 0) && (strcmp( bootInfo->bootFile, kDefaultKernelYosemite ) != 0))
1246 {
1247gOverrideKernel = true;
1248}
1249
1250cntRemaining = BOOT_STRING_LEN - 2; // save 1 for NULL, 1 for space
1251argP = bootArgs->CommandLine;
1252
1253// Get config kernel flags, if not ignored.
1254if (getValueForBootKey(cp, kIgnoreBootFileFlag, &val, &cnt) ||
1255 !getValueForKey( kKernelFlagsKey, &val, &cnt, &bootInfo->bootConfig ))
1256 {
1257val = "";
1258cnt = 0;
1259}
1260configKernelFlags = malloc(cnt + 1);
1261strlcpy(configKernelFlags, val, cnt + 1);
1262
1263// boot-uuid can be set either on the command-line or in the config file
1264if (!processBootArgument(kBootUUIDKey, cp, configKernelFlags, bootInfo->config,
1265 &argP, &cntRemaining, gBootUUIDString, sizeof(gBootUUIDString)))
1266 {
1267//
1268// Try an alternate method for getting the root UUID on boot helper partitions.
1269//
1270if (gBootVolume->flags & kBVFlagBooter)
1271 {
1272// Load the configuration store in the boot helper partition
1273if (loadHelperConfig(&bootInfo->helperConfig) == 0)
1274 {
1275val = getStringForKey(kHelperRootUUIDKey, &bootInfo->helperConfig);
1276if (val != NULL)
1277 {
1278strlcpy(gBootUUIDString, val, sizeof(gBootUUIDString));
1279}
1280}
1281}
1282/*
1283// Try to get the volume uuid string
1284if (!strlen(gBootUUIDString) && gBootVolume->fs_getuuid)
1285 {
1286gBootVolume->fs_getuuid(gBootVolume, gBootUUIDString);
1287}
1288*/
1289// If we have the volume uuid add it to the commandline arguments
1290if (strlen(gBootUUIDString))
1291 {
1292copyArgument(kBootUUIDKey, gBootUUIDString, strlen(gBootUUIDString), &argP, &cntRemaining);
1293}
1294 // Try to get the volume uuid string
1295 if (!strlen(gBootUUIDString) && gBootVolume->fs_getuuid)
1296 {
1297 gBootVolume->fs_getuuid(gBootVolume, gBootUUIDString);
1298 DBG("boot-uuid: %s\n", gBootUUIDString);
1299 }
1300}
1301
1302if (!processBootArgument(kRootDeviceKey, cp, configKernelFlags, bootInfo->config,
1303 &argP, &cntRemaining, gRootDevice, ROOT_DEVICE_SIZE))
1304 {
1305cnt = 0;
1306if ( getValueForKey( kBootDeviceKey, &val, &cnt, &bootInfo->chameleonConfig))
1307 {
1308valueBuffer[0] = '*';
1309cnt++;
1310strlcpy(valueBuffer + 1, val, cnt);
1311val = valueBuffer;
1312} else { /*
1313if (strlen(gBootUUIDString))
1314 {
1315val = "*uuid";
1316cnt = 5;
1317} else { */
1318// Don't set "rd=.." if there is no boot device key
1319// and no UUID.
1320val = "";
1321cnt = 0;
1322/*} */
1323}
1324
1325if (cnt > 0)
1326 {
1327copyArgument( kRootDeviceKey, val, cnt, &argP, &cntRemaining);
1328}
1329strlcpy( gRootDevice, val, (cnt + 1));
1330}
1331
1332/*
1333 * Removed. We don't need this anymore.
1334 *
1335if (!processBootArgument(kPlatformKey, cp, configKernelFlags, bootInfo->config,
1336 &argP, &cntRemaining, gPlatformName, sizeof(gCacheNameAdler)))
1337 {
1338getPlatformName(gPlatformName);
1339copyArgument(kPlatformKey, gPlatformName, strlen(gPlatformName), &argP, &cntRemaining);
1340}
1341*/
1342
1343if (!getValueForBootKey(cp, kSafeModeFlag, &val, &cnt) &&
1344 !getValueForBootKey(configKernelFlags, kSafeModeFlag, &val, &cnt))
1345 {
1346if (gBootMode & kBootModeSafe)
1347 {
1348copyArgument(0, kSafeModeFlag, strlen(kSafeModeFlag), &argP, &cntRemaining);
1349}
1350}
1351
1352// Store the merged kernel flags and boot args.
1353
1354cnt = strlen(configKernelFlags);
1355if (cnt)
1356 {
1357if (cnt > cntRemaining)
1358 {
1359error("Warning: boot arguments too long, truncating\n");
1360cnt = cntRemaining;
1361}
1362strncpy(argP, configKernelFlags, cnt);
1363argP[cnt++] = ' ';
1364cntRemaining -= cnt;
1365}
1366userCnt = strlen(cp);
1367if (userCnt > cntRemaining)
1368 {
1369error("Warning: boot arguments too long, truncating\n");
1370userCnt = cntRemaining;
1371}
1372strncpy(&argP[cnt], cp, userCnt);
1373argP[cnt+userCnt] = '\0';
1374
1375if(!shouldboot)
1376 {
1377gVerboseMode = getValueForKey( kVerboseModeFlag, &val, &cnt, &bootInfo->chameleonConfig ) ||
1378getValueForKey( kSingleUserModeFlag, &val, &cnt, &bootInfo->chameleonConfig );
1379
1380gBootMode = ( getValueForKey( kSafeModeFlag, &val, &cnt, &bootInfo->chameleonConfig ) ) ?
1381kBootModeSafe : kBootModeNormal;
1382
1383if ( getValueForKey( kIgnoreCachesFlag, &val, &cnt, &bootInfo->chameleonConfig ) )
1384 {
1385gBootMode = kBootModeSafe;
1386}
1387}
1388
1389if ( getValueForKey( kMKextCacheKey, &val, &cnt, &bootInfo->bootConfig ) )
1390 {
1391strlcpy(gMKextName, val, cnt + 1);
1392} else {
1393gMKextName[0]=0;
1394}
1395
1396free(configKernelFlags);
1397free(valueBuffer);
1398
1399return 0;
1400}
1401
1402
1403//==========================================================================
1404// Load the help file and display the file contents on the screen.
1405
1406void showTextBuffer(char *buf_orig, int size)
1407{
1408char*bp;
1409char* buf;
1410intline;
1411intline_offset;
1412intc;
1413
1414if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
1415showInfoBox( "Press q to continue, space for next page.\n",buf_orig );
1416return;
1417}
1418
1419// Create a copy so that we don't mangle the original
1420buf = malloc(size + 1);
1421memcpy(buf, buf_orig, size);
1422
1423
1424 bp = buf;
1425 while (size-- > 0) {
1426if (*bp == '\n') {
1427*bp = '\0';
1428}
1429bp++;
1430 }
1431 *bp = '\1';
1432 line_offset = 0;
1433
1434 setActiveDisplayPage(1);
1435
1436 while (1) {
1437clearScreenRows(0, 24);
1438setCursorPosition(0, 0, 1);
1439bp = buf;
1440for (line = 0; *bp != '\1' && line < line_offset; line++) {
1441while (*bp != '\0') {
1442bp++;
1443}
1444bp++;
1445}
1446for (line = 0; *bp != '\1' && line < 23; line++) {
1447setCursorPosition(0, line, 1);
1448printf("%s\n", bp);
1449while (*bp != '\0') {
1450bp++;
1451}
1452bp++;
1453}
1454
1455setCursorPosition(0, 23, 1);
1456if (*bp == '\1') {
1457printf("[Type %sq or space to quit viewer]", (line_offset > 0) ? "p for previous page, " : "");
1458} else {
1459printf("[Type %s%sq to quit viewer]", (line_offset > 0) ? "p for previous page, " : "", (*bp != '\1') ? "space for next page, " : "");
1460}
1461
1462c = getchar();
1463if (c == 'q' || c == 'Q') {
1464break;
1465}
1466if ((c == 'p' || c == 'P') && line_offset > 0) {
1467line_offset -= 23;
1468}
1469if (c == ' ') {
1470if (*bp == '\1') {
1471break;
1472} else {
1473line_offset += 23;
1474}
1475}
1476 }
1477 setActiveDisplayPage(0);
1478}
1479
1480void showHelp(void)
1481{
1482if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
1483showInfoBox("Help. Press q to quit.\n", (char *)BootHelp_txt);
1484} else {
1485showTextBuffer((char *)BootHelp_txt, BootHelp_txt_len);
1486}
1487}
1488
1489void showTextFile(const char * filename)
1490{
1491#define MAX_TEXT_FILE_SIZE 65536
1492char*buf;
1493intfd;
1494intsize;
1495
1496if ((fd = open_bvdev("bt(0,0)", filename, 0)) < 0) {
1497printf("\nFile not found: %s\n", filename);
1498sleep(2);
1499return;
1500}
1501
1502 size = file_size(fd);
1503 if (size > MAX_TEXT_FILE_SIZE) {
1504size = MAX_TEXT_FILE_SIZE;
1505}
1506 buf = malloc(size);
1507 read(fd, buf, size);
1508 close(fd);
1509showTextBuffer(buf, size);
1510free(buf);
1511}
1512
1513// This is a very simplistic prompting scheme that just grabs two hex characters
1514// Eventually we need to do something more user-friendly like display a menu
1515// based off of the Multiboot device list
1516
1517int selectAlternateBootDevice(int bootdevice)
1518{
1519int key;
1520int newbootdevice;
1521int digitsI = 0;
1522char *end;
1523char digits[3] = {0,0,0};
1524
1525// We've already printed the current boot device so user knows what it is
1526printf("Typical boot devices are 80 (First HD), 81 (Second HD)\n");
1527printf("Enter two-digit hexadecimal boot device [%02x]: ", bootdevice);
1528do {
1529key = getchar();
1530switch (ASCII_KEY(key)) {
1531case KEY_BKSP:
1532if (digitsI > 0) {
1533int x, y, t;
1534getCursorPositionAndType(&x, &y, &t);
1535// Assume x is not 0;
1536x--;
1537setCursorPosition(x,y,0); // back up one char
1538// Overwrite with space without moving cursor position
1539putca(' ', 0x07, 1);
1540digitsI--;
1541} else {
1542// TODO: Beep or something
1543}
1544break;
1545
1546case KEY_ENTER:
1547digits[digitsI] = '\0';
1548newbootdevice = strtol(digits, &end, 16);
1549if (end == digits && *end == '\0') {
1550// User entered empty string
1551printf("\nUsing default boot device %x\n", bootdevice);
1552key = 0;
1553} else if(end != digits && *end == '\0') {
1554bootdevice = newbootdevice;
1555printf("\n");
1556key = 0; // We gots da boot device
1557} else {
1558printf("\nCouldn't parse. try again: ");
1559digitsI = 0;
1560}
1561break;
1562
1563default:
1564if (isxdigit(ASCII_KEY(key)) && digitsI < 2) {
1565putchar(ASCII_KEY(key));
1566digits[digitsI++] = ASCII_KEY(key);
1567} else {
1568// TODO: Beep or something
1569}
1570break;
1571};
1572} while (key != 0);
1573
1574return bootdevice;
1575}
1576
1577bool promptForRescanOption(void)
1578{
1579printf("\nWould you like to enable media rescan option?\nPress ENTER to enable or any key to skip.\n");
1580if (getchar() == KEY_ENTER) {
1581return true;
1582} else {
1583return false;
1584}
1585}
1586

Archive Download this file

Revision: 2403