menu.4th revision 235560
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 <dteske@freebsd.org>
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 235560 2012-05-17 20:00:34Z dteske $
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" ( [22m )
135	then
136	menuidx @ .
137	loader_color? if
138		." [37m" ( [39m )
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				\ boot immediately if less than zero
746				dup 0< if
747					drop
748					menu-create
749					0 25 at-xy
750					0 boot
751				then
752			then
753		then
754		menu_timeout ! ( store value on stack from above )
755
756		menu_timeout_enabled @ 1 = if
757			\ read custom column position (if set)
758			s" loader_menu_timeout_x" getenv dup -1 = if
759				drop \ no custom column position
760				menu_timeout_default_x \ use default setting
761			else
762				\ make sure custom position is a number
763				?number 0= if
764					menu_timeout_default_x \ or use default
765				then
766			then
767			menu_timeout_x ! ( store value on stack from above )
768        
769			\ read custom row position (if set)
770			s" loader_menu_timeout_y" getenv dup -1 = if
771				drop \ no custom row position
772				menu_timeout_default_y \ use default setting
773			else
774				\ make sure custom position is a number
775				?number 0= if
776					menu_timeout_default_y \ or use default
777				then
778			then
779			menu_timeout_y ! ( store value on stack from above )
780		then
781	then
782
783	menu-create
784
785	begin \ Loop forever
786
787		0 25 at-xy \ Move cursor to the bottom for output
788		getkey     \ Block here, waiting for a key to be pressed
789
790		dup -1 = if
791			drop exit \ Caught abort (abnormal return)
792		then
793
794		\ Boot if the user pressed Enter/Ctrl-M (13) or
795		\ Ctrl-Enter/Ctrl-J (10)
796		dup over 13 = swap 10 = or if
797			drop ( no longer needed )
798			s" boot" evaluate
799			exit ( pedantic; never reached )
800		then
801
802		\ Evaluate the decimal ASCII value against known menu item
803		\ key associations and act accordingly
804
805		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
806		begin
807			s" menukeyN @"
808
809			\ replace 'N' with current iteration
810			-rot 2dup 7 + c! rot
811
812			evaluate rot tuck = if
813
814				\ Adjust for missing ACPI menuitem on non-i386
815				arch-i386? true <> menuacpi @ 0<> and if
816					menuacpi @ over 2dup < -rot = or
817					over 58 < and if
818					( key >= menuacpi && key < 58: N -- N )
819						1+
820					then
821				then
822
823				\ base env name for the value (x is a number)
824				s" menu_command[x]"
825
826				\ Copy ASCII number to string at offset 13
827				-rot 2dup 13 + c! rot
828
829				\ Test for the environment variable
830				getenv dup -1 <> if
831					\ Execute the stored procedure
832					evaluate
833
834					\ We expect there to be a non-zero
835					\  value left on the stack after
836					\ executing the stored procedure.
837					\ If so, continue to run, else exit.
838
839					0= if
840						drop \ key pressed
841						drop \ loop iterator
842						exit
843					else
844						swap \ need iterator on top
845					then
846				then
847
848				\ Re-adjust for missing ACPI menuitem
849				arch-i386? true <> menuacpi @ 0<> and if
850					swap
851					menuacpi @ 1+ over 2dup < -rot = or
852					over 59 < and if
853						1-
854					then
855					swap
856				then
857			else
858				swap \ need iterator on top
859			then
860
861			\ 
862			\ Check for menu keycode shortcut(s)
863			\ 
864			s" menu_keycode[x]"
865			-rot 2dup 13 + c! rot
866			getenv dup -1 = if
867				drop
868			else
869				?number 0<> if
870					rot tuck = if
871						swap
872						s" menu_command[x]"
873						-rot 2dup 13 + c! rot
874						getenv dup -1 <> if
875							evaluate
876							0= if
877								2drop
878								exit
879							then
880						else
881							drop
882						then
883					else
884						swap
885					then
886				then
887			then
888
889			1+ dup 56 > \ increment iterator
890			            \ continue if less than 57
891		until
892		drop \ loop iterator
893
894		menureboot @ = if 0 reboot then
895
896	again	\ Non-operational key was pressed; repeat
897;
898
899\ This function unsets all the possible environment variables associated with
900\ creating the interactive menu.
901\ 
902: menu-unset ( -- )
903
904	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
905	begin
906		\ Unset variables in-order of appearance in menu.4th(8)
907
908		s" menu_caption[x]"	\ basename for caption variable
909		-rot 2dup 13 + c! rot	\ replace 'x' with current iteration
910		unsetenv		\ not erroneous to unset unknown var
911
912		s" menu_command[x]"	\ command basename
913		-rot 2dup 13 + c! rot	\ replace 'x'
914		unsetenv
915
916		s" menu_keycode[x]"	\ keycode basename
917		-rot 2dup 13 + c! rot	\ replace 'x'
918		unsetenv
919
920		s" ansi_caption[x]"	\ ANSI caption basename
921		-rot 2dup 13 + c! rot	\ replace 'x'
922		unsetenv
923
924		s" toggled_text[x]"	\ toggle_menuitem caption basename
925		-rot 2dup 13 + c! rot	\ replace 'x'
926		unsetenv
927
928		s" toggled_ansi[x]"	\ toggle_menuitem ANSI caption basename
929		-rot 2dup 13 + c! rot	\ replace 'x'
930		unsetenv
931
932		s" menu_caption[x][y]"	\ cycle_menuitem caption
933		-rot 2dup 13 + c! rot	\ replace 'x'
934		49 -rot
935		begin
936			16 2over rot + c! \ replace 'y'
937			2dup unsetenv
938
939			rot 1+ dup 56 > 2swap rot
940		until
941		2drop drop
942
943		s" ansi_caption[x][y]"	\ cycle_menuitem ANSI caption
944		-rot 2dup 13 + c! rot	\ replace 'x'
945		49 -rot
946		begin
947			16 2over rot + c! \ replace 'y'
948			2dup unsetenv
949
950			rot 1+ dup 56 > 2swap rot
951		until
952		2drop drop
953
954		s" 0 menukeyN !"	\ basename for key association var
955		-rot 2dup 9 + c! rot	\ replace 'N' with current iteration
956		evaluate		\ assign zero (0) to key assoc. var
957
958		1+ dup 56 >	\ increment, continue if less than 57
959	until
960	drop \ iterator
961
962	\ unset the timeout command
963	s" menu_timeout_command" unsetenv
964
965	\ clear the "Reboot" menu option flag
966	s" menu_reboot" unsetenv
967	0 menureboot !
968
969	\ clear the ACPI menu option flag
970	s" menu_acpi" unsetenv
971	0 menuacpi !
972
973	\ clear the "Options" menu separator flag
974	s" menu_options" unsetenv
975	0 menuoptions !
976
977;
978
979\ This function both unsets menu variables and visually erases the menu area
980\ in-preparation for another menu.
981\ 
982: menu-clear ( -- )
983	menu-unset
984	menu-erase
985;
986
987\ Assign configuration values
988bullet menubllt !
98910 menuY !
9905 menuX !
991
992\ Initialize our boolean state variables
9930 toggle_state1 !
9940 toggle_state2 !
9950 toggle_state3 !
9960 toggle_state4 !
9970 toggle_state5 !
9980 toggle_state6 !
9990 toggle_state7 !
10000 toggle_state8 !
1001
1002\ Initialize our array state variables
10030 cycle_state1 !
10040 cycle_state2 !
10050 cycle_state3 !
10060 cycle_state4 !
10070 cycle_state5 !
10080 cycle_state6 !
10090 cycle_state7 !
10100 cycle_state8 !
1011
1012\ Initialize string containers
10130 init_text1 c!
10140 init_text2 c!
10150 init_text3 c!
10160 init_text4 c!
10170 init_text5 c!
10180 init_text6 c!
10190 init_text7 c!
10200 init_text8 c!
1021