menu.4th revision 222417
1\ Copyright (c) 2003 Scott Long <scottl@freebsd.org>
2\ Copyright (c) 2003 Aleksander Fafula <alex@fafula.com>
3\ Copyright (c) 2006-2011 Devin Teske <devinteske@hotmail.com>
4\ All rights reserved.
5\ 
6\ Redistribution and use in source and binary forms, with or without
7\ modification, are permitted provided that the following conditions
8\ are met:
9\ 1. Redistributions of source code must retain the above copyright
10\    notice, this list of conditions and the following disclaimer.
11\ 2. Redistributions in binary form must reproduce the above copyright
12\    notice, this list of conditions and the following disclaimer in the
13\    documentation and/or other materials provided with the distribution.
14\ 
15\ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16\ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17\ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18\ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19\ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20\ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21\ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22\ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23\ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24\ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25\ SUCH DAMAGE.
26\ 
27\ $FreeBSD: head/sys/boot/forth/menu.4th 222417 2011-05-28 08:50:38Z julian $
28
29marker task-menu.4th
30
31\ Frame drawing
32include /boot/frames.4th
33
34f_double        \ Set frames to double (see frames.4th). Replace with
35                \ f_single if you want single frames.
3646 constant dot \ ASCII definition of a period (in decimal)
37
38 4 constant menu_timeout_default_x \ default column position of timeout
3923 constant menu_timeout_default_y \ default row position of timeout msg
4010 constant menu_timeout_default   \ default timeout (in seconds)
41
42\ Customize the following values with care
43
44  1 constant menu_start \ Numerical prefix of first menu item
45dot constant bullet     \ Menu bullet (appears after numerical prefix)
46  5 constant menu_x     \ Row position of the menu (from the top)
47 10 constant menu_y     \ Column position of the menu (from left side)
48
49\ Menu Appearance
50variable menuidx   \ Menu item stack for number prefixes
51variable menurow   \ Menu item stack for positioning
52variable menubllt  \ Menu item bullet
53
54\ Menu Positioning
55variable menuX     \ Menu X offset (columns)
56variable menuY     \ Menu Y offset (rows)
57
58\ Menu-item key association/detection
59variable menukey1
60variable menukey2
61variable menukey3
62variable menukey4
63variable menukey5
64variable menukey6
65variable menukey7
66variable menukey8
67variable menureboot
68variable menurebootadded
69variable menuacpi
70variable menuoptions
71
72\ Menu timer [count-down] variables
73variable menu_timeout_enabled \ timeout state (internal use only)
74variable menu_time            \ variable for tracking the passage of time
75variable menu_timeout         \ determined configurable delay duration
76variable menu_timeout_x       \ column position of timeout message
77variable menu_timeout_y       \ row position of timeout message
78
79\ Boolean option status variables
80variable toggle_state1
81variable toggle_state2
82variable toggle_state3
83variable toggle_state4
84variable toggle_state5
85variable toggle_state6
86variable toggle_state7
87variable toggle_state8
88
89\ Array option status variables
90variable cycle_state1
91variable cycle_state2
92variable cycle_state3
93variable cycle_state4
94variable cycle_state5
95variable cycle_state6
96variable cycle_state7
97variable cycle_state8
98
99\ Containers for storing the initial caption text
100create init_text1 255 allot
101create init_text2 255 allot
102create init_text3 255 allot
103create init_text4 255 allot
104create init_text5 255 allot
105create init_text6 255 allot
106create init_text7 255 allot
107create init_text8 255 allot
108
109: arch-i386? ( -- BOOL ) \ Returns TRUE (-1) on i386, FALSE (0) otherwise.
110	s" arch-i386" environment? dup if
111		drop
112	then
113;
114
115\ This function prints a menu item at menuX (row) and menuY (column), returns
116\ the incremental decimal ASCII value associated with the menu item, and
117\ increments the cursor position to the next row for the creation of the next
118\ menu item. This function is called by the menu-create function. You need not
119\ call it directly.
120\ 
121: printmenuitem ( menu_item_str -- ascii_keycode )
122
123	menurow dup @ 1+ swap ! ( increment menurow )
124	menuidx dup @ 1+ swap ! ( increment menuidx )
125
126	\ Calculate the menuitem row position
127	menurow @ menuY @ +
128
129	\ Position the cursor at the menuitem position
130	dup menuX @ swap at-xy
131
132	\ Print the value of menuidx
133	loader_color? if
134		." [1m"
135	then
136	menuidx @ .
137	loader_color? if
138		." [37m"
139	then
140
141	\ Move the cursor forward 1 column
142	dup menuX @ 1+ swap at-xy
143
144	menubllt @ emit	\ Print the menu bullet using the emit function
145
146	\ Move the cursor to the 3rd column from the current position
147	\ to allow for a space between the numerical prefix and the
148	\ text caption
149	menuX @ 3 + swap at-xy
150
151	\ Print the menu caption (we expect a string to be on the stack
152	\ prior to invoking this function)
153	type
154
155	\ Here we will add the ASCII decimal of the numerical prefix
156	\ to the stack (decimal ASCII for `1' is 49) as a "return value"
157	menuidx @ 48 +
158;
159
160: toggle_menuitem ( N -- N ) \ toggles caption text and internal menuitem state
161
162	\ ASCII numeral equal to user-selected menu item must be on the stack.
163	\ We do not modify the stack, so the ASCII numeral is left on top.
164
165	s" init_textN"          \ base name of buffer
166	-rot 2dup 9 + c! rot    \ replace 'N' with ASCII num
167
168	evaluate c@ 0= if
169		\ NOTE: no need to check toggle_stateN since the first time we
170		\ are called, we will populate init_textN. Further, we don't
171		\ need to test whether menu_caption[x] (ansi_caption[x] when
172		\ loader_color=1) is available since we would not have been
173		\ called if the caption was NULL.
174
175		\ base name of environment variable
176		loader_color? if
177			s" ansi_caption[x]"
178		else
179			s" menu_caption[x]"
180		then	
181		-rot 2dup 13 + c! rot    \ replace 'x' with ASCII numeral
182
183		getenv dup -1 <> if
184
185			s" init_textN"          \ base name of buffer
186			4 pick                  \ copy ASCII num to top
187			rot tuck 9 + c! swap    \ replace 'N' with ASCII num
188			evaluate
189
190			\ now we have the buffer c-addr on top
191			\ ( followed by c-addr/u of current caption )
192
193			\ Copy the current caption into our buffer
194			2dup c! -rot \ store strlen at first byte
195			begin
196				rot 1+    \ bring alt addr to top and increment
197				-rot -rot \ bring buffer addr to top
198				2dup c@ swap c! \ copy current character
199				1+     \ increment buffer addr
200				rot 1- \ bring buffer len to top and decrement
201				dup 0= \ exit loop if buffer len is zero
202			until
203			2drop \ buffer len/addr
204			drop  \ alt addr
205
206		else
207			drop
208		then
209	then
210
211	\ Now we are certain to have init_textN populated with the initial
212	\ value of menu_caption[x] (ansi_caption[x] with loader_color enabled).
213	\ We can now use init_textN as the untoggled caption and
214	\ toggled_text[x] (toggled_ansi[x] with loader_color enabled) as the
215	\ toggled caption and store the appropriate value into menu_caption[x]
216	\ (again, ansi_caption[x] with loader_color enabled). Last, we'll
217	\ negate the toggled state so that we reverse the flow on subsequent
218	\ calls.
219
220	s" toggle_stateN @"      \ base name of toggle state var
221	-rot 2dup 12 + c! rot    \ replace 'N' with ASCII numeral
222
223	evaluate 0= if
224		\ state is OFF, toggle to ON
225
226		\ base name of toggled text var
227		loader_color? if
228			s" toggled_ansi[x]"
229		else
230			s" toggled_text[x]"
231		then
232		-rot 2dup 13 + c! rot    \ replace 'x' with ASCII num
233
234		getenv dup -1 <> if
235			\ Assign toggled text to menu caption
236
237			\ base name of caption var
238			loader_color? if
239				s" ansi_caption[x]"
240			else
241				s" menu_caption[x]"
242			then
243			4 pick                   \ copy ASCII num to top
244			rot tuck 13 + c! swap    \ replace 'x' with ASCII num
245
246			setenv \ set new caption
247		else
248			\ No toggled text, keep the same caption
249
250			drop
251		then
252
253		true \ new value of toggle state var (to be stored later)
254	else
255		\ state is ON, toggle to OFF
256
257		s" init_textN"           \ base name of initial text buffer
258		-rot 2dup 9 + c! rot     \ replace 'N' with ASCII numeral
259		evaluate                 \ convert string to c-addr
260		count                    \ convert c-addr to c-addr/u
261
262		\ base name of caption var
263		loader_color? if
264			s" ansi_caption[x]"
265		else
266			s" menu_caption[x]"
267		then
268		4 pick                   \ copy ASCII num to top
269		rot tuck 13 + c! swap    \ replace 'x' with ASCII numeral
270
271		setenv    \ set new caption
272		false     \ new value of toggle state var (to be stored below)
273	then
274
275	\ now we'll store the new toggle state (on top of stack)
276	s" toggle_stateN"        \ base name of toggle state var
277	3 pick                   \ copy ASCII numeral to top
278	rot tuck 12 + c! swap    \ replace 'N' with ASCII numeral
279	evaluate                 \ convert string to addr
280	!                        \ store new value
281;
282
283: cycle_menuitem ( N -- N ) \ cycles through array of choices for a menuitem
284
285	\ ASCII numeral equal to user-selected menu item must be on the stack.
286	\ We do not modify the stack, so the ASCII numeral is left on top.
287
288	s" cycle_stateN"         \ base name of array state var
289	-rot 2dup 11 + c! rot    \ replace 'N' with ASCII numeral
290
291	evaluate    \ we now have a pointer to the proper variable
292	dup @       \ resolve the pointer (but leave it on the stack)
293	1+          \ increment the value
294
295	\ Before assigning the (incremented) value back to the pointer,
296	\ let's test for the existence of this particular array element.
297	\ If the element exists, we'll store index value and move on.
298	\ Otherwise, we'll loop around to zero and store that.
299
300	dup 48 + \ duplicate Array index and convert to ASCII numeral
301
302	\ base name of array caption text
303	loader_color? if
304		s" ansi_caption[x][y]"          
305	else
306		s" menu_caption[x][y]"          
307	then
308	-rot tuck 16 + c! swap          \ replace 'y' with Array index
309	4 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
310
311	\ Now test for the existence of our incremented array index in the
312	\ form of $menu_caption[x][y] ($ansi_caption[x][y] with loader_color
313	\ enabled) as set in loader.rc(5), et. al.
314
315	getenv dup -1 = if
316		\ No caption set for this array index. Loop back to zero.
317
318		drop    ( getenv cruft )
319		drop    ( incremented array index )
320		0       ( new array index that will be stored later )
321
322		\ base name of caption var
323		loader_color? if
324			s" ansi_caption[x][0]"
325		else
326			s" menu_caption[x][0]"
327		then
328		4 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
329
330		getenv dup -1 = if
331			\ This is highly unlikely to occur, but to make
332			\ sure that things move along smoothly, allocate
333			\ a temporary NULL string
334
335			s" "
336		then
337	then
338
339	\ At this point, we should have the following on the stack (in order,
340	\ from bottom to top):
341	\ 
342	\    N      - Ascii numeral representing the menu choice (inherited)
343	\    Addr   - address of our internal cycle_stateN variable
344	\    N      - zero-based number we intend to store to the above
345	\    C-Addr - string value we intend to store to menu_caption[x]
346	\             (or ansi_caption[x] with loader_color enabled)
347	\ 
348	\ Let's perform what we need to with the above.
349
350	\ base name of menuitem caption var
351	loader_color? if
352		s" ansi_caption[x]"
353	else
354		s" menu_caption[x]"
355	then
356	6 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
357	setenv                          \ set the new caption
358
359	swap ! \ update array state variable
360;
361
362: acpipresent? ( -- flag ) \ Returns TRUE if ACPI is present, FALSE otherwise
363	s" hint.acpi.0.rsdp" getenv
364	dup -1 = if
365		drop false exit
366	then
367	2drop
368	true
369;
370
371: acpienabled? ( -- flag ) \ Returns TRUE if ACPI is enabled, FALSE otherwise
372	s" hint.acpi.0.disabled" getenv
373	dup -1 <> if
374		s" 0" compare 0<> if
375			false exit
376		then
377	else
378		drop
379	then
380	true
381;
382
383\ This function prints the appropriate menuitem basename to the stack if an
384\ ACPI option is to be presented to the user, otherwise returns -1. Used
385\ internally by menu-create, you need not (nor should you) call this directly.
386\ 
387: acpimenuitem ( -- C-Addr | -1 )
388
389	arch-i386? if
390		acpipresent? if
391			acpienabled? if
392				loader_color? if
393					s" toggled_ansi[x]"
394				else
395					s" toggled_text[x]"
396				then
397			else
398				loader_color? if
399					s" ansi_caption[x]"
400				else
401					s" menu_caption[x]"
402				then
403			then
404		else
405			menuidx dup @ 1+ swap ! ( increment menuidx )
406			-1
407		then
408	else
409		-1
410	then
411;
412
413\ This function creates the list of menu items. This function is called by the
414\ menu-display function. You need not be call it directly.
415\ 
416: menu-create ( -- )
417
418	\ Print the frame caption at (x,y)
419	s" loader_menu_title" getenv dup -1 = if
420		drop s" Welcome to FreeBSD"
421	then
422	24 over 2 / - 9 at-xy type 
423
424	\ Print our menu options with respective key/variable associations.
425	\ `printmenuitem' ends by adding the decimal ASCII value for the
426	\ numerical prefix to the stack. We store the value left on the stack
427	\ to the key binding variable for later testing against a character
428	\ captured by the `getkey' function.
429
430	\ Note that any menu item beyond 9 will have a numerical prefix on the
431	\ screen consisting of the first digit (ie. 1 for the tenth menu item)
432	\ and the key required to activate that menu item will be the decimal
433	\ ASCII of 48 plus the menu item (ie. 58 for the tenth item, aka. `:')
434	\ which is misleading and not desirable.
435	\ 
436	\ Thus, we do not allow more than 8 configurable items on the menu
437	\ (with "Reboot" as the optional ninth and highest numbered item).
438
439	\ 
440	\ Initialize the ACPI option status.
441	\ 
442	0 menuacpi !
443	s" menu_acpi" getenv -1 <> if
444		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
445			menuacpi !
446			arch-i386? if acpipresent? if
447				\ 
448				\ Set menu toggle state to active state
449				\ (required by generic toggle_menuitem)
450				\ 
451				menuacpi @
452				s" acpienabled? toggle_stateN !"
453				-rot tuck 25 + c! swap
454				evaluate
455			then then
456		else
457			drop
458		then
459	then
460
461	\ 
462	\ Initialize the menu_options visual separator.
463	\ 
464	0 menuoptions !
465	s" menu_options" getenv -1 <> if
466		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
467			menuoptions !
468		else
469			drop
470		then
471	then
472
473	\ Initialize "Reboot" menu state variable (prevents double-entry)
474	false menurebootadded !
475
476	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
477	begin
478		\ If the "Options:" separator, print it.
479		dup menuoptions @ = if
480			\ Optionally add a reboot option to the menu
481			s" menu_reboot" getenv -1 <> if
482				drop
483				s" Reboot" printmenuitem menureboot !
484				true menurebootadded !
485			then
486
487			menuX @
488			menurow @ 2 + menurow !
489			menurow @ menuY @ +
490			at-xy
491			." Options:"
492		then
493
494		\ If this is the ACPI menu option, act accordingly.
495		dup menuacpi @ = if
496			acpimenuitem ( -- C-Addr | -1 )
497		else
498			loader_color? if
499				s" ansi_caption[x]"
500			else
501				s" menu_caption[x]"
502			then
503		then
504
505		( C-Addr | -1 )
506		dup -1 <> if
507			\ replace 'x' with current iteration
508			-rot 2dup 13 + c! rot
509        
510			\ test for environment variable
511			getenv dup -1 <> if
512				printmenuitem ( C-Addr -- N )
513        
514				s" menukeyN !" \ generate cmd to store result
515				-rot 2dup 7 + c! rot
516        
517				evaluate
518			else
519				drop
520			then
521		else
522			drop
523
524			s" menu_command[x]"
525			-rot 2dup 13 + c! rot ( replace 'x' )
526			unsetenv
527		then
528
529		1+ dup 56 > \ add 1 to iterator, continue if less than 57
530	until
531	drop \ iterator
532
533	\ Optionally add a reboot option to the menu
534	menurebootadded @ true <> if
535		s" menu_reboot" getenv -1 <> if
536			drop       \ no need for the value
537			s" Reboot" \ menu caption (required by printmenuitem)
538
539			printmenuitem
540			menureboot !
541		else
542			0 menureboot !
543		then
544	then
545;
546
547\ Takes a single integer on the stack and updates the timeout display. The
548\ integer must be between 0 and 9 (we will only update a single digit in the
549\ source message).
550\ 
551: menu-timeout-update ( N -- )
552
553	dup 9 > if ( N N 9 -- N )
554		drop ( N -- )
555		9 ( maximum: -- N )
556	then
557
558	dup 0 < if ( N N 0 -- N )
559		drop ( N -- )
560		0 ( minimum: -- N )
561	then
562
563	48 + ( convert single-digit numeral to ASCII: N 48 -- N )
564
565	s" Autoboot in N seconds. [Space] to pause" ( N -- N Addr C )
566
567	2 pick 48 - 0> if ( N Addr C N 48 -- N Addr C )
568
569		\ Modify 'N' (Addr+12) above to reflect time-left
570
571		-rot	( N Addr C -- C N Addr )
572		tuck	( C N Addr -- C Addr N Addr )
573		12 +	( C Addr N Addr -- C Addr N Addr2 )
574		c!	( C Addr N Addr2 -- C Addr )
575		swap	( C Addr -- Addr C )
576
577		menu_timeout_x @
578		menu_timeout_y @
579		at-xy ( position cursor: Addr C N N -- Addr C )
580
581		type ( print message: Addr C -- )
582
583	else ( N Addr C N -- N Addr C )
584
585		menu_timeout_x @
586		menu_timeout_y @
587		at-xy ( position cursor: N Addr C N N -- N Addr C )
588
589		spaces ( erase message: N Addr C -- N Addr )
590		2drop ( N Addr -- )
591
592	then
593
594	0 25 at-xy ( position cursor back at bottom-left )
595;
596
597\ This function blocks program flow (loops forever) until a key is pressed.
598\ The key that was pressed is added to the top of the stack in the form of its
599\ decimal ASCII representation. This function is called by the menu-display
600\ function. You need not call it directly.
601\ 
602: getkey ( -- ascii_keycode )
603
604	begin \ loop forever
605
606		menu_timeout_enabled @ 1 = if
607			( -- )
608			seconds ( get current time: -- N )
609			dup menu_time @ <> if ( has time elapsed?: N N N -- N )
610
611				\ At least 1 second has elapsed since last loop
612				\ so we will decrement our "timeout" (really a
613				\ counter, insuring that we do not proceed too
614				\ fast) and update our timeout display.
615
616				menu_time ! ( update time record: N -- )
617				menu_timeout @ ( "time" remaining: -- N )
618				dup 0> if ( greater than 0?: N N 0 -- N )
619					1- ( decrement counter: N -- N )
620					dup menu_timeout !
621						( re-assign: N N Addr -- N )
622				then
623				( -- N )
624
625				dup 0= swap 0< or if ( N <= 0?: N N -- )
626					\ halt the timer
627					0 menu_timeout ! ( 0 Addr -- )
628					0 menu_timeout_enabled ! ( 0 Addr -- )
629				then
630
631				\ update the timer display ( N -- )
632				menu_timeout @ menu-timeout-update
633
634				menu_timeout @ 0= if
635					\ We've reached the end of the timeout
636					\ (user did not cancel by pressing ANY
637					\ key)
638
639					s" menu_timeout_command" getenv dup
640					-1 = if
641						drop \ clean-up
642					else
643						evaluate
644					then
645				then
646
647			else ( -- N )
648				\ No [detectable] time has elapsed (in seconds)
649				drop ( N -- )
650			then
651			( -- )
652		then
653
654		key? if \ Was a key pressed? (see loader(8))
655
656			\ An actual key was pressed (if the timeout is running,
657			\ kill it regardless of which key was pressed)
658			menu_timeout @ 0<> if
659				0 menu_timeout !
660				0 menu_timeout_enabled !
661
662				\ clear screen of timeout message
663				0 menu-timeout-update
664			then
665
666			\ get the key that was pressed and exit (if we
667			\ get a non-zero ASCII code)
668			key dup 0<> if
669				exit
670			else
671				drop
672			then
673		then
674		50 ms \ sleep for 50 milliseconds (see loader(8))
675
676	again
677;
678
679: menu-erase ( -- ) \ Erases menu and resets positioning variable to positon 1.
680
681	\ Clear the screen area associated with the interactive menu
682	menuX @ menuY @
683	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
684	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
685	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
686	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
687	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
688	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces
689	2drop
690
691	\ Reset the starting index and position for the menu
692	menu_start 1- menuidx !
693	0 menurow !
694;
695
696\ Erase and redraw the menu. Useful if you change a caption and want to
697\ update the menu to reflect the new value.
698\ 
699: menu-redraw ( -- )
700	menu-erase
701	menu-create
702;
703
704\ This function initializes the menu. Call this from your `loader.rc' file
705\ before calling any other menu-related functions.
706\ 
707: menu-init ( -- )
708	menu_start
709	1- menuidx !    \ Initialize the starting index for the menu
710	0 menurow !     \ Initialize the starting position for the menu
711	42 13 2 9 box   \ Draw frame (w,h,x,y)
712	0 25 at-xy      \ Move cursor to the bottom for output
713;
714
715\ Main function. Call this from your `loader.rc' file.
716\ 
717: menu-display ( -- )
718
719	0 menu_timeout_enabled ! \ start with automatic timeout disabled
720
721	\ check indication that automatic execution after delay is requested
722	s" menu_timeout_command" getenv -1 <> if ( Addr C -1 -- | Addr )
723		drop ( just testing existence right now: Addr -- )
724
725		\ initialize state variables
726		seconds menu_time ! ( store the time we started )
727		1 menu_timeout_enabled ! ( enable automatic timeout )
728
729		\ read custom time-duration (if set)
730		s" autoboot_delay" getenv dup -1 = if
731			drop \ no custom duration (remove dup'd bunk -1)
732			menu_timeout_default \ use default setting
733		else
734			2dup ?number 0= if ( if not a number )
735				\ disable timeout if "NO", else use default
736				s" NO" compare-insensitive 0= if
737					0 menu_timeout_enabled !
738					0 ( assigned to menu_timeout below )
739				else
740					menu_timeout_default
741				then
742			else
743				-rot 2drop
744
745				\ disable timeout if less than zero
746				dup 0< if
747					drop
748					0 menu_timeout_enabled !
749					0 ( assigned to menu_timeout below )
750				then
751			then
752		then
753		menu_timeout ! ( store value on stack from above )
754
755		menu_timeout_enabled @ 1 = if
756			\ read custom column position (if set)
757			s" loader_menu_timeout_x" getenv dup -1 = if
758				drop \ no custom column position
759				menu_timeout_default_x \ use default setting
760			else
761				\ make sure custom position is a number
762				?number 0= if
763					menu_timeout_default_x \ or use default
764				then
765			then
766			menu_timeout_x ! ( store value on stack from above )
767        
768			\ read custom row position (if set)
769			s" loader_menu_timeout_y" getenv dup -1 = if
770				drop \ no custom row position
771				menu_timeout_default_y \ use default setting
772			else
773				\ make sure custom position is a number
774				?number 0= if
775					menu_timeout_default_y \ or use default
776				then
777			then
778			menu_timeout_y ! ( store value on stack from above )
779		then
780	then
781
782	menu-create
783
784	begin \ Loop forever
785
786		0 25 at-xy \ Move cursor to the bottom for output
787		getkey     \ Block here, waiting for a key to be pressed
788
789		dup -1 = if
790			drop exit \ Caught abort (abnormal return)
791		then
792
793		\ Boot if the user pressed Enter/Ctrl-M (13) or
794		\ Ctrl-Enter/Ctrl-J (10)
795		dup over 13 = swap 10 = or if
796			drop ( no longer needed )
797			s" boot" evaluate
798			exit ( pedantic; never reached )
799		then
800
801		\ Evaluate the decimal ASCII value against known menu item
802		\ key associations and act accordingly
803
804		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
805		begin
806			s" menukeyN @"
807
808			\ replace 'N' with current iteration
809			-rot 2dup 7 + c! rot
810
811			evaluate rot tuck = if
812
813				\ Adjust for missing ACPI menuitem on non-i386
814				arch-i386? true <> menuacpi @ 0<> and if
815					menuacpi @ over 2dup < -rot = or
816					over 58 < and if
817					( key >= menuacpi && key < 58: N -- N )
818						1+
819					then
820				then
821
822				\ base env name for the value (x is a number)
823				s" menu_command[x]"
824
825				\ Copy ASCII number to string at offset 13
826				-rot 2dup 13 + c! rot
827
828				\ Test for the environment variable
829				getenv dup -1 <> if
830					\ Execute the stored procedure
831					evaluate
832
833					\ We expect there to be a non-zero
834					\  value left on the stack after
835					\ executing the stored procedure.
836					\ If so, continue to run, else exit.
837
838					0= if
839						drop \ key pressed
840						drop \ loop iterator
841						exit
842					else
843						swap \ need iterator on top
844					then
845				then
846
847				\ Re-adjust for missing ACPI menuitem
848				arch-i386? true <> menuacpi @ 0<> and if
849					swap
850					menuacpi @ 1+ over 2dup < -rot = or
851					over 59 < and if
852						1-
853					then
854					swap
855				then
856			else
857				swap \ need iterator on top
858			then
859
860			\ 
861			\ Check for menu keycode shortcut(s)
862			\ 
863			s" menu_keycode[x]"
864			-rot 2dup 13 + c! rot
865			getenv dup -1 = if
866				drop
867			else
868				?number 0<> if
869					rot tuck = if
870						swap
871						s" menu_command[x]"
872						-rot 2dup 13 + c! rot
873						getenv dup -1 <> if
874							evaluate
875							0= if
876								2drop
877								exit
878							then
879						else
880							drop
881						then
882					else
883						swap
884					then
885				then
886			then
887
888			1+ dup 56 > \ increment iterator
889			            \ continue if less than 57
890		until
891		drop \ loop iterator
892
893		menureboot @ = if 0 reboot then
894
895	again	\ Non-operational key was pressed; repeat
896;
897
898\ This function unsets all the possible environment variables associated with
899\ creating the interactive menu. Call this when you want to clear the menu
900\ area in preparation for another menu.
901\ 
902: menu-clear ( -- )
903
904	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
905	begin
906		\ basename for caption variable
907		loader_color? if
908			s" ansi_caption[x]"
909		else
910			s" menu_caption[x]"
911		then
912		-rot 2dup 13 + c! rot	\ replace 'x' with current iteration
913		unsetenv		\ not erroneous to unset unknown var
914
915		s" 0 menukeyN !"	\ basename for key association var
916		-rot 2dup 9 + c! rot	\ replace 'N' with current iteration
917		evaluate		\ assign zero (0) to key assoc. var
918
919		1+ dup 56 >	\ increment, continue if less than 57
920	until
921	drop \ iterator
922
923	\ clear the "Reboot" menu option flag
924	s" menu_reboot" unsetenv
925	0 menureboot !
926
927	\ clear the ACPI menu option flag
928	s" menu_acpi" unsetenv
929	0 menuacpi !
930
931	\ clear the "Options" menu separator flag
932	s" menu_options" unsetenv
933	0 menuoptions !
934
935	menu-erase
936;
937
938\ Assign configuration values
939bullet menubllt !
94010 menuY !
9415 menuX !
942
943\ Initialize our boolean state variables
9440 toggle_state1 !
9450 toggle_state2 !
9460 toggle_state3 !
9470 toggle_state4 !
9480 toggle_state5 !
9490 toggle_state6 !
9500 toggle_state7 !
9510 toggle_state8 !
952
953\ Initialize our array state variables
9540 cycle_state1 !
9550 cycle_state2 !
9560 cycle_state3 !
9570 cycle_state4 !
9580 cycle_state5 !
9590 cycle_state6 !
9600 cycle_state7 !
9610 cycle_state8 !
962
963\ Initialize string containers
9640 init_text1 c!
9650 init_text2 c!
9660 init_text3 c!
9670 init_text4 c!
9680 init_text5 c!
9690 init_text6 c!
9700 init_text7 c!
9710 init_text8 c!
972