menu.4th revision 241363
1\ Copyright (c) 2003 Scott Long <scottl@freebsd.org>
2\ Copyright (c) 2003 Aleksander Fafula <alex@fafula.com>
3\ Copyright (c) 2006-2012 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 241363 2012-10-09 01:31:03Z 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/U | -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			s" menu_optionstext" getenv dup -1 <> if
492				type
493			else
494				drop ." Options:"
495			then
496		then
497
498		\ If this is the ACPI menu option, act accordingly.
499		dup menuacpi @ = if
500			acpimenuitem ( -- C-Addr/U | -1 )
501		else
502			loader_color? if
503				s" ansi_caption[x]"
504			else
505				s" menu_caption[x]"
506			then
507		then
508
509		( C-Addr/U | -1 )
510		dup -1 <> if
511			\ replace 'x' with current iteration
512			-rot 2dup 13 + c! rot
513        
514			\ test for environment variable
515			getenv dup -1 <> if
516				printmenuitem ( C-Addr/U -- N )
517        
518				s" menukeyN !" \ generate cmd to store result
519				-rot 2dup 7 + c! rot
520        
521				evaluate
522			else
523				drop
524			then
525		else
526			drop
527
528			s" menu_command[x]"
529			-rot 2dup 13 + c! rot ( replace 'x' )
530			unsetenv
531		then
532
533		1+ dup 56 > \ add 1 to iterator, continue if less than 57
534	until
535	drop \ iterator
536
537	\ Optionally add a reboot option to the menu
538	menurebootadded @ true <> if
539		s" menu_reboot" getenv -1 <> if
540			drop       \ no need for the value
541			s" Reboot" \ menu caption (required by printmenuitem)
542
543			printmenuitem
544			menureboot !
545		else
546			0 menureboot !
547		then
548	then
549;
550
551\ Takes a single integer on the stack and updates the timeout display. The
552\ integer must be between 0 and 9 (we will only update a single digit in the
553\ source message).
554\ 
555: menu-timeout-update ( N -- )
556
557	dup 9 > if ( N N 9 -- N )
558		drop ( N -- )
559		9 ( maximum: -- N )
560	then
561
562	dup 0 < if ( N N 0 -- N )
563		drop ( N -- )
564		0 ( minimum: -- N )
565	then
566
567	48 + ( convert single-digit numeral to ASCII: N 48 -- N )
568
569	s" Autoboot in N seconds. [Space] to pause" ( N -- N Addr C )
570
571	2 pick 48 - 0> if ( N Addr C N 48 -- N Addr C )
572
573		\ Modify 'N' (Addr+12) above to reflect time-left
574
575		-rot	( N Addr C -- C N Addr )
576		tuck	( C N Addr -- C Addr N Addr )
577		12 +	( C Addr N Addr -- C Addr N Addr2 )
578		c!	( C Addr N Addr2 -- C Addr )
579		swap	( C Addr -- Addr C )
580
581		menu_timeout_x @
582		menu_timeout_y @
583		at-xy ( position cursor: Addr C N N -- Addr C )
584
585		type ( print message: Addr C -- )
586
587	else ( N Addr C N -- N Addr C )
588
589		menu_timeout_x @
590		menu_timeout_y @
591		at-xy ( position cursor: N Addr C N N -- N Addr C )
592
593		spaces ( erase message: N Addr C -- N Addr )
594		2drop ( N Addr -- )
595
596	then
597
598	0 25 at-xy ( position cursor back at bottom-left )
599;
600
601\ This function blocks program flow (loops forever) until a key is pressed.
602\ The key that was pressed is added to the top of the stack in the form of its
603\ decimal ASCII representation. This function is called by the menu-display
604\ function. You need not call it directly.
605\ 
606: getkey ( -- ascii_keycode )
607
608	begin \ loop forever
609
610		menu_timeout_enabled @ 1 = if
611			( -- )
612			seconds ( get current time: -- N )
613			dup menu_time @ <> if ( has time elapsed?: N N N -- N )
614
615				\ At least 1 second has elapsed since last loop
616				\ so we will decrement our "timeout" (really a
617				\ counter, insuring that we do not proceed too
618				\ fast) and update our timeout display.
619
620				menu_time ! ( update time record: N -- )
621				menu_timeout @ ( "time" remaining: -- N )
622				dup 0> if ( greater than 0?: N N 0 -- N )
623					1- ( decrement counter: N -- N )
624					dup menu_timeout !
625						( re-assign: N N Addr -- N )
626				then
627				( -- N )
628
629				dup 0= swap 0< or if ( N <= 0?: N N -- )
630					\ halt the timer
631					0 menu_timeout ! ( 0 Addr -- )
632					0 menu_timeout_enabled ! ( 0 Addr -- )
633				then
634
635				\ update the timer display ( N -- )
636				menu_timeout @ menu-timeout-update
637
638				menu_timeout @ 0= if
639					\ We've reached the end of the timeout
640					\ (user did not cancel by pressing ANY
641					\ key)
642
643					s" menu_timeout_command" getenv dup
644					-1 = if
645						drop \ clean-up
646					else
647						evaluate
648					then
649				then
650
651			else ( -- N )
652				\ No [detectable] time has elapsed (in seconds)
653				drop ( N -- )
654			then
655			( -- )
656		then
657
658		key? if \ Was a key pressed? (see loader(8))
659
660			\ An actual key was pressed (if the timeout is running,
661			\ kill it regardless of which key was pressed)
662			menu_timeout @ 0<> if
663				0 menu_timeout !
664				0 menu_timeout_enabled !
665
666				\ clear screen of timeout message
667				0 menu-timeout-update
668			then
669
670			\ get the key that was pressed and exit (if we
671			\ get a non-zero ASCII code)
672			key dup 0<> if
673				exit
674			else
675				drop
676			then
677		then
678		50 ms \ sleep for 50 milliseconds (see loader(8))
679
680	again
681;
682
683: menu-erase ( -- ) \ Erases menu and resets positioning variable to positon 1.
684
685	\ Clear the screen area associated with the interactive menu
686	menuX @ menuY @
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 1+
689	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
690	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
691	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
692	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces
693	2drop
694
695	\ Reset the starting index and position for the menu
696	menu_start 1- menuidx !
697	0 menurow !
698;
699
700\ Erase and redraw the menu. Useful if you change a caption and want to
701\ update the menu to reflect the new value.
702\ 
703: menu-redraw ( -- )
704	menu-erase
705	menu-create
706;
707
708\ This function initializes the menu. Call this from your `loader.rc' file
709\ before calling any other menu-related functions.
710\ 
711: menu-init ( -- )
712	menu_start
713	1- menuidx !    \ Initialize the starting index for the menu
714	0 menurow !     \ Initialize the starting position for the menu
715	42 13 2 9 box   \ Draw frame (w,h,x,y)
716	0 25 at-xy      \ Move cursor to the bottom for output
717;
718
719\ Main function. Call this from your `loader.rc' file.
720\ 
721: menu-display ( -- )
722
723	0 menu_timeout_enabled ! \ start with automatic timeout disabled
724
725	\ check indication that automatic execution after delay is requested
726	s" menu_timeout_command" getenv -1 <> if ( Addr C -1 -- | Addr )
727		drop ( just testing existence right now: Addr -- )
728
729		\ initialize state variables
730		seconds menu_time ! ( store the time we started )
731		1 menu_timeout_enabled ! ( enable automatic timeout )
732
733		\ read custom time-duration (if set)
734		s" autoboot_delay" getenv dup -1 = if
735			drop \ no custom duration (remove dup'd bunk -1)
736			menu_timeout_default \ use default setting
737		else
738			2dup ?number 0= if ( if not a number )
739				\ disable timeout if "NO", else use default
740				s" NO" compare-insensitive 0= if
741					0 menu_timeout_enabled !
742					0 ( assigned to menu_timeout below )
743				else
744					menu_timeout_default
745				then
746			else
747				-rot 2drop
748
749				\ boot immediately if less than zero
750				dup 0< if
751					drop
752					menu-create
753					0 25 at-xy
754					0 boot
755				then
756			then
757		then
758		menu_timeout ! ( store value on stack from above )
759
760		menu_timeout_enabled @ 1 = if
761			\ read custom column position (if set)
762			s" loader_menu_timeout_x" getenv dup -1 = if
763				drop \ no custom column position
764				menu_timeout_default_x \ use default setting
765			else
766				\ make sure custom position is a number
767				?number 0= if
768					menu_timeout_default_x \ or use default
769				then
770			then
771			menu_timeout_x ! ( store value on stack from above )
772        
773			\ read custom row position (if set)
774			s" loader_menu_timeout_y" getenv dup -1 = if
775				drop \ no custom row position
776				menu_timeout_default_y \ use default setting
777			else
778				\ make sure custom position is a number
779				?number 0= if
780					menu_timeout_default_y \ or use default
781				then
782			then
783			menu_timeout_y ! ( store value on stack from above )
784		then
785	then
786
787	menu-create
788
789	begin \ Loop forever
790
791		0 25 at-xy \ Move cursor to the bottom for output
792		getkey     \ Block here, waiting for a key to be pressed
793
794		dup -1 = if
795			drop exit \ Caught abort (abnormal return)
796		then
797
798		\ Boot if the user pressed Enter/Ctrl-M (13) or
799		\ Ctrl-Enter/Ctrl-J (10)
800		dup over 13 = swap 10 = or if
801			drop ( no longer needed )
802			s" boot" evaluate
803			exit ( pedantic; never reached )
804		then
805
806		\ Evaluate the decimal ASCII value against known menu item
807		\ key associations and act accordingly
808
809		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
810		begin
811			s" menukeyN @"
812
813			\ replace 'N' with current iteration
814			-rot 2dup 7 + c! rot
815
816			evaluate rot tuck = if
817
818				\ Adjust for missing ACPI menuitem on non-i386
819				arch-i386? true <> menuacpi @ 0<> and if
820					menuacpi @ over 2dup < -rot = or
821					over 58 < and if
822					( key >= menuacpi && key < 58: N -- N )
823						1+
824					then
825				then
826
827				\ base env name for the value (x is a number)
828				s" menu_command[x]"
829
830				\ Copy ASCII number to string at offset 13
831				-rot 2dup 13 + c! rot
832
833				\ Test for the environment variable
834				getenv dup -1 <> if
835					\ Execute the stored procedure
836					evaluate
837
838					\ We expect there to be a non-zero
839					\  value left on the stack after
840					\ executing the stored procedure.
841					\ If so, continue to run, else exit.
842
843					0= if
844						drop \ key pressed
845						drop \ loop iterator
846						exit
847					else
848						swap \ need iterator on top
849					then
850				then
851
852				\ Re-adjust for missing ACPI menuitem
853				arch-i386? true <> menuacpi @ 0<> and if
854					swap
855					menuacpi @ 1+ over 2dup < -rot = or
856					over 59 < and if
857						1-
858					then
859					swap
860				then
861			else
862				swap \ need iterator on top
863			then
864
865			\ 
866			\ Check for menu keycode shortcut(s)
867			\ 
868			s" menu_keycode[x]"
869			-rot 2dup 13 + c! rot
870			getenv dup -1 = if
871				drop
872			else
873				?number 0<> if
874					rot tuck = if
875						swap
876						s" menu_command[x]"
877						-rot 2dup 13 + c! rot
878						getenv dup -1 <> if
879							evaluate
880							0= if
881								2drop
882								exit
883							then
884						else
885							drop
886						then
887					else
888						swap
889					then
890				then
891			then
892
893			1+ dup 56 > \ increment iterator
894			            \ continue if less than 57
895		until
896		drop \ loop iterator
897
898		menureboot @ = if 0 reboot then
899
900	again	\ Non-operational key was pressed; repeat
901;
902
903\ This function unsets all the possible environment variables associated with
904\ creating the interactive menu.
905\ 
906: menu-unset ( -- )
907
908	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
909	begin
910		\ Unset variables in-order of appearance in menu.4th(8)
911
912		s" menu_caption[x]"	\ basename for caption variable
913		-rot 2dup 13 + c! rot	\ replace 'x' with current iteration
914		unsetenv		\ not erroneous to unset unknown var
915
916		s" menu_command[x]"	\ command basename
917		-rot 2dup 13 + c! rot	\ replace 'x'
918		unsetenv
919
920		s" menu_keycode[x]"	\ keycode basename
921		-rot 2dup 13 + c! rot	\ replace 'x'
922		unsetenv
923
924		s" ansi_caption[x]"	\ ANSI caption basename
925		-rot 2dup 13 + c! rot	\ replace 'x'
926		unsetenv
927
928		s" toggled_text[x]"	\ toggle_menuitem caption basename
929		-rot 2dup 13 + c! rot	\ replace 'x'
930		unsetenv
931
932		s" toggled_ansi[x]"	\ toggle_menuitem ANSI caption basename
933		-rot 2dup 13 + c! rot	\ replace 'x'
934		unsetenv
935
936		s" menu_caption[x][y]"	\ cycle_menuitem caption
937		-rot 2dup 13 + c! rot	\ replace 'x'
938		49 -rot
939		begin
940			16 2over rot + c! \ replace 'y'
941			2dup unsetenv
942
943			rot 1+ dup 56 > 2swap rot
944		until
945		2drop drop
946
947		s" ansi_caption[x][y]"	\ cycle_menuitem ANSI caption
948		-rot 2dup 13 + c! rot	\ replace 'x'
949		49 -rot
950		begin
951			16 2over rot + c! \ replace 'y'
952			2dup unsetenv
953
954			rot 1+ dup 56 > 2swap rot
955		until
956		2drop drop
957
958		s" 0 menukeyN !"	\ basename for key association var
959		-rot 2dup 9 + c! rot	\ replace 'N' with current iteration
960		evaluate		\ assign zero (0) to key assoc. var
961
962		1+ dup 56 >	\ increment, continue if less than 57
963	until
964	drop \ iterator
965
966	\ unset the timeout command
967	s" menu_timeout_command" unsetenv
968
969	\ clear the "Reboot" menu option flag
970	s" menu_reboot" unsetenv
971	0 menureboot !
972
973	\ clear the ACPI menu option flag
974	s" menu_acpi" unsetenv
975	0 menuacpi !
976
977	\ clear the "Options" menu separator flag
978	s" menu_options" unsetenv
979	s" menu_optionstext" unsetenv
980	0 menuoptions !
981
982;
983
984\ This function both unsets menu variables and visually erases the menu area
985\ in-preparation for another menu.
986\ 
987: menu-clear ( -- )
988	menu-unset
989	menu-erase
990;
991
992\ Assign configuration values
993bullet menubllt !
99410 menuY !
9955 menuX !
996
997\ Initialize our boolean state variables
9980 toggle_state1 !
9990 toggle_state2 !
10000 toggle_state3 !
10010 toggle_state4 !
10020 toggle_state5 !
10030 toggle_state6 !
10040 toggle_state7 !
10050 toggle_state8 !
1006
1007\ Initialize our array state variables
10080 cycle_state1 !
10090 cycle_state2 !
10100 cycle_state3 !
10110 cycle_state4 !
10120 cycle_state5 !
10130 cycle_state6 !
10140 cycle_state7 !
10150 cycle_state8 !
1016
1017\ Initialize string containers
10180 init_text1 c!
10190 init_text2 c!
10200 init_text3 c!
10210 init_text4 c!
10220 init_text5 c!
10230 init_text6 c!
10240 init_text7 c!
10250 init_text8 c!
1026