menu.4th revision 241523
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 241523 2012-10-14 06:52:49Z 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\ Menu initialization status variables
80variable init_state1
81variable init_state2
82variable init_state3
83variable init_state4
84variable init_state5
85variable init_state6
86variable init_state7
87variable init_state8
88
89\ Boolean option status variables
90variable toggle_state1
91variable toggle_state2
92variable toggle_state3
93variable toggle_state4
94variable toggle_state5
95variable toggle_state6
96variable toggle_state7
97variable toggle_state8
98
99\ Array option status variables
100variable cycle_state1
101variable cycle_state2
102variable cycle_state3
103variable cycle_state4
104variable cycle_state5
105variable cycle_state6
106variable cycle_state7
107variable cycle_state8
108
109\ Containers for storing the initial caption text
110create init_text1 255 allot
111create init_text2 255 allot
112create init_text3 255 allot
113create init_text4 255 allot
114create init_text5 255 allot
115create init_text6 255 allot
116create init_text7 255 allot
117create init_text8 255 allot
118
119: arch-i386? ( -- BOOL ) \ Returns TRUE (-1) on i386, FALSE (0) otherwise.
120	s" arch-i386" environment? dup if
121		drop
122	then
123;
124
125\ This function prints a menu item at menuX (row) and menuY (column), returns
126\ the incremental decimal ASCII value associated with the menu item, and
127\ increments the cursor position to the next row for the creation of the next
128\ menu item. This function is called by the menu-create function. You need not
129\ call it directly.
130\ 
131: printmenuitem ( menu_item_str -- ascii_keycode )
132
133	menurow dup @ 1+ swap ! ( increment menurow )
134	menuidx dup @ 1+ swap ! ( increment menuidx )
135
136	\ Calculate the menuitem row position
137	menurow @ menuY @ +
138
139	\ Position the cursor at the menuitem position
140	dup menuX @ swap at-xy
141
142	\ Print the value of menuidx
143	loader_color? if
144		." [1m" ( [22m )
145	then
146	menuidx @ .
147	loader_color? if
148		." [37m" ( [39m )
149	then
150
151	\ Move the cursor forward 1 column
152	dup menuX @ 1+ swap at-xy
153
154	menubllt @ emit	\ Print the menu bullet using the emit function
155
156	\ Move the cursor to the 3rd column from the current position
157	\ to allow for a space between the numerical prefix and the
158	\ text caption
159	menuX @ 3 + swap at-xy
160
161	\ Print the menu caption (we expect a string to be on the stack
162	\ prior to invoking this function)
163	type
164
165	\ Here we will add the ASCII decimal of the numerical prefix
166	\ to the stack (decimal ASCII for `1' is 49) as a "return value"
167	menuidx @ 48 +
168;
169
170: toggle_menuitem ( N -- N ) \ toggles caption text and internal menuitem state
171
172	\ ASCII numeral equal to user-selected menu item must be on the stack.
173	\ We do not modify the stack, so the ASCII numeral is left on top.
174
175	s" init_textN"          \ base name of buffer
176	-rot 2dup 9 + c! rot    \ replace 'N' with ASCII num
177
178	evaluate c@ 0= if
179		\ NOTE: no need to check toggle_stateN since the first time we
180		\ are called, we will populate init_textN. Further, we don't
181		\ need to test whether menu_caption[x] (ansi_caption[x] when
182		\ loader_color=1) is available since we would not have been
183		\ called if the caption was NULL.
184
185		\ base name of environment variable
186		loader_color? if
187			s" ansi_caption[x]"
188		else
189			s" menu_caption[x]"
190		then	
191		-rot 2dup 13 + c! rot    \ replace 'x' with ASCII numeral
192
193		getenv dup -1 <> if
194
195			s" init_textN"          \ base name of buffer
196			4 pick                  \ copy ASCII num to top
197			rot tuck 9 + c! swap    \ replace 'N' with ASCII num
198			evaluate
199
200			\ now we have the buffer c-addr on top
201			\ ( followed by c-addr/u of current caption )
202
203			\ Copy the current caption into our buffer
204			2dup c! -rot \ store strlen at first byte
205			begin
206				rot 1+    \ bring alt addr to top and increment
207				-rot -rot \ bring buffer addr to top
208				2dup c@ swap c! \ copy current character
209				1+     \ increment buffer addr
210				rot 1- \ bring buffer len to top and decrement
211				dup 0= \ exit loop if buffer len is zero
212			until
213			2drop \ buffer len/addr
214			drop  \ alt addr
215
216		else
217			drop
218		then
219	then
220
221	\ Now we are certain to have init_textN populated with the initial
222	\ value of menu_caption[x] (ansi_caption[x] with loader_color enabled).
223	\ We can now use init_textN as the untoggled caption and
224	\ toggled_text[x] (toggled_ansi[x] with loader_color enabled) as the
225	\ toggled caption and store the appropriate value into menu_caption[x]
226	\ (again, ansi_caption[x] with loader_color enabled). Last, we'll
227	\ negate the toggled state so that we reverse the flow on subsequent
228	\ calls.
229
230	s" toggle_stateN @"      \ base name of toggle state var
231	-rot 2dup 12 + c! rot    \ replace 'N' with ASCII numeral
232
233	evaluate 0= if
234		\ state is OFF, toggle to ON
235
236		\ base name of toggled text var
237		loader_color? if
238			s" toggled_ansi[x]"
239		else
240			s" toggled_text[x]"
241		then
242		-rot 2dup 13 + c! rot    \ replace 'x' with ASCII num
243
244		getenv dup -1 <> if
245			\ Assign toggled text to menu caption
246
247			\ base name of caption var
248			loader_color? if
249				s" ansi_caption[x]"
250			else
251				s" menu_caption[x]"
252			then
253			4 pick                   \ copy ASCII num to top
254			rot tuck 13 + c! swap    \ replace 'x' with ASCII num
255
256			setenv \ set new caption
257		else
258			\ No toggled text, keep the same caption
259
260			drop
261		then
262
263		true \ new value of toggle state var (to be stored later)
264	else
265		\ state is ON, toggle to OFF
266
267		s" init_textN"           \ base name of initial text buffer
268		-rot 2dup 9 + c! rot     \ replace 'N' with ASCII numeral
269		evaluate                 \ convert string to c-addr
270		count                    \ convert c-addr to c-addr/u
271
272		\ base name of caption var
273		loader_color? if
274			s" ansi_caption[x]"
275		else
276			s" menu_caption[x]"
277		then
278		4 pick                   \ copy ASCII num to top
279		rot tuck 13 + c! swap    \ replace 'x' with ASCII numeral
280
281		setenv    \ set new caption
282		false     \ new value of toggle state var (to be stored below)
283	then
284
285	\ now we'll store the new toggle state (on top of stack)
286	s" toggle_stateN"        \ base name of toggle state var
287	3 pick                   \ copy ASCII numeral to top
288	rot tuck 12 + c! swap    \ replace 'N' with ASCII numeral
289	evaluate                 \ convert string to addr
290	!                        \ store new value
291;
292
293: cycle_menuitem ( N -- N ) \ cycles through array of choices for a menuitem
294
295	\ ASCII numeral equal to user-selected menu item must be on the stack.
296	\ We do not modify the stack, so the ASCII numeral is left on top.
297
298	s" cycle_stateN"         \ base name of array state var
299	-rot 2dup 11 + c! rot    \ replace 'N' with ASCII numeral
300
301	evaluate    \ we now have a pointer to the proper variable
302	dup @       \ resolve the pointer (but leave it on the stack)
303	1+          \ increment the value
304
305	\ Before assigning the (incremented) value back to the pointer,
306	\ let's test for the existence of this particular array element.
307	\ If the element exists, we'll store index value and move on.
308	\ Otherwise, we'll loop around to zero and store that.
309
310	dup 48 + \ duplicate Array index and convert to ASCII numeral
311
312	\ base name of array caption text
313	loader_color? if
314		s" ansi_caption[x][y]"          
315	else
316		s" menu_caption[x][y]"          
317	then
318	-rot tuck 16 + c! swap          \ replace 'y' with Array index
319	4 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
320
321	\ Now test for the existence of our incremented array index in the
322	\ form of $menu_caption[x][y] ($ansi_caption[x][y] with loader_color
323	\ enabled) as set in loader.rc(5), et. al.
324
325	getenv dup -1 = if
326		\ No caption set for this array index. Loop back to zero.
327
328		drop    ( getenv cruft )
329		drop    ( incremented array index )
330		0       ( new array index that will be stored later )
331
332		\ base name of caption var
333		loader_color? if
334			s" ansi_caption[x][0]"
335		else
336			s" menu_caption[x][0]"
337		then
338		4 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
339
340		getenv dup -1 = if
341			\ This is highly unlikely to occur, but to make
342			\ sure that things move along smoothly, allocate
343			\ a temporary NULL string
344
345			s" "
346		then
347	then
348
349	\ At this point, we should have the following on the stack (in order,
350	\ from bottom to top):
351	\ 
352	\    N      - Ascii numeral representing the menu choice (inherited)
353	\    Addr   - address of our internal cycle_stateN variable
354	\    N      - zero-based number we intend to store to the above
355	\    C-Addr - string value we intend to store to menu_caption[x]
356	\             (or ansi_caption[x] with loader_color enabled)
357	\ 
358	\ Let's perform what we need to with the above.
359
360	\ base name of menuitem caption var
361	loader_color? if
362		s" ansi_caption[x]"
363	else
364		s" menu_caption[x]"
365	then
366	6 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
367	setenv                          \ set the new caption
368
369	swap ! \ update array state variable
370;
371
372: acpipresent? ( -- flag ) \ Returns TRUE if ACPI is present, FALSE otherwise
373	s" hint.acpi.0.rsdp" getenv
374	dup -1 = if
375		drop false exit
376	then
377	2drop
378	true
379;
380
381: acpienabled? ( -- flag ) \ Returns TRUE if ACPI is enabled, FALSE otherwise
382	s" hint.acpi.0.disabled" getenv
383	dup -1 <> if
384		s" 0" compare 0<> if
385			false exit
386		then
387	else
388		drop
389	then
390	true
391;
392
393\ This function prints the appropriate menuitem basename to the stack if an
394\ ACPI option is to be presented to the user, otherwise returns -1. Used
395\ internally by menu-create, you need not (nor should you) call this directly.
396\ 
397: acpimenuitem ( -- C-Addr/U | -1 )
398
399	arch-i386? if
400		acpipresent? if
401			acpienabled? if
402				loader_color? if
403					s" toggled_ansi[x]"
404				else
405					s" toggled_text[x]"
406				then
407			else
408				loader_color? if
409					s" ansi_caption[x]"
410				else
411					s" menu_caption[x]"
412				then
413			then
414		else
415			menuidx dup @ 1+ swap ! ( increment menuidx )
416			-1
417		then
418	else
419		-1
420	then
421;
422
423\ This function creates the list of menu items. This function is called by the
424\ menu-display function. You need not be call it directly.
425\ 
426: menu-create ( -- )
427
428	\ Print the frame caption at (x,y)
429	s" loader_menu_title" getenv dup -1 = if
430		drop s" Welcome to FreeBSD"
431	then
432	24 over 2 / - 9 at-xy type 
433
434	\ If $menu_init is set, evaluate it (allowing for whole menus to be
435	\ constructed dynamically -- as this function could conceivably set
436	\ the remaining environment variables to construct the menu entirely).
437	\ 
438	s" menu_init" getenv dup -1 <> if
439		evaluate
440	else
441		drop
442	then
443
444	\ Print our menu options with respective key/variable associations.
445	\ `printmenuitem' ends by adding the decimal ASCII value for the
446	\ numerical prefix to the stack. We store the value left on the stack
447	\ to the key binding variable for later testing against a character
448	\ captured by the `getkey' function.
449
450	\ Note that any menu item beyond 9 will have a numerical prefix on the
451	\ screen consisting of the first digit (ie. 1 for the tenth menu item)
452	\ and the key required to activate that menu item will be the decimal
453	\ ASCII of 48 plus the menu item (ie. 58 for the tenth item, aka. `:')
454	\ which is misleading and not desirable.
455	\ 
456	\ Thus, we do not allow more than 8 configurable items on the menu
457	\ (with "Reboot" as the optional ninth and highest numbered item).
458
459	\ 
460	\ Initialize the ACPI option status.
461	\ 
462	0 menuacpi !
463	s" menu_acpi" getenv -1 <> if
464		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
465			menuacpi !
466			arch-i386? if acpipresent? if
467				\ 
468				\ Set menu toggle state to active state
469				\ (required by generic toggle_menuitem)
470				\ 
471				menuacpi @
472				s" acpienabled? toggle_stateN !"
473				-rot tuck 25 + c! swap
474				evaluate
475			then then
476		else
477			drop
478		then
479	then
480
481	\ 
482	\ Initialize the menu_options visual separator.
483	\ 
484	0 menuoptions !
485	s" menu_options" getenv -1 <> if
486		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
487			menuoptions !
488		else
489			drop
490		then
491	then
492
493	\ Initialize "Reboot" menu state variable (prevents double-entry)
494	false menurebootadded !
495
496	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
497	begin
498		\ If the "Options:" separator, print it.
499		dup menuoptions @ = if
500			\ Optionally add a reboot option to the menu
501			s" menu_reboot" getenv -1 <> if
502				drop
503				s" Reboot" printmenuitem menureboot !
504				true menurebootadded !
505			then
506
507			menuX @
508			menurow @ 2 + menurow !
509			menurow @ menuY @ +
510			at-xy
511			s" menu_optionstext" getenv dup -1 <> if
512				type
513			else
514				drop ." Options:"
515			then
516		then
517
518		\ If this is the ACPI menu option, act accordingly.
519		dup menuacpi @ = if
520			acpimenuitem ( -- C-Addr/U | -1 )
521		else
522			\ make sure we have not already initialized this item
523			s" init_stateN"
524			-rot 2dup 10 + c! rot \ repace 'N'
525			evaluate dup @ 0= if
526				1 swap !
527
528				\ If this menuitem has an initializer, run it
529				s" menu_init[x]"
530				-rot 2dup 10 + c! rot \ replace 'x'
531				getenv dup -1 <> if
532					evaluate
533				else
534					drop
535				then
536			else
537				drop
538			then
539
540			loader_color? if
541				s" ansi_caption[x]"
542			else
543				s" menu_caption[x]"
544			then
545		then
546
547		( C-Addr/U | -1 )
548		dup -1 <> if
549			\ replace 'x' with current iteration
550			-rot 2dup 13 + c! rot
551        
552			\ test for environment variable
553			getenv dup -1 <> if
554				printmenuitem ( C-Addr/U -- N )
555        
556				s" menukeyN !" \ generate cmd to store result
557				-rot 2dup 7 + c! rot
558        
559				evaluate
560			else
561				drop
562			then
563		else
564			drop
565
566			s" menu_command[x]"
567			-rot 2dup 13 + c! rot ( replace 'x' )
568			unsetenv
569		then
570
571		1+ dup 56 > \ add 1 to iterator, continue if less than 57
572	until
573	drop \ iterator
574
575	\ Optionally add a reboot option to the menu
576	menurebootadded @ true <> if
577		s" menu_reboot" getenv -1 <> if
578			drop       \ no need for the value
579			s" Reboot" \ menu caption (required by printmenuitem)
580
581			printmenuitem
582			menureboot !
583		else
584			0 menureboot !
585		then
586	then
587;
588
589\ Takes a single integer on the stack and updates the timeout display. The
590\ integer must be between 0 and 9 (we will only update a single digit in the
591\ source message).
592\ 
593: menu-timeout-update ( N -- )
594
595	dup 9 > if ( N N 9 -- N )
596		drop ( N -- )
597		9 ( maximum: -- N )
598	then
599
600	dup 0 < if ( N N 0 -- N )
601		drop ( N -- )
602		0 ( minimum: -- N )
603	then
604
605	48 + ( convert single-digit numeral to ASCII: N 48 -- N )
606
607	s" Autoboot in N seconds. [Space] to pause" ( N -- N Addr C )
608
609	2 pick 48 - 0> if ( N Addr C N 48 -- N Addr C )
610
611		\ Modify 'N' (Addr+12) above to reflect time-left
612
613		-rot	( N Addr C -- C N Addr )
614		tuck	( C N Addr -- C Addr N Addr )
615		12 +	( C Addr N Addr -- C Addr N Addr2 )
616		c!	( C Addr N Addr2 -- C Addr )
617		swap	( C Addr -- Addr C )
618
619		menu_timeout_x @
620		menu_timeout_y @
621		at-xy ( position cursor: Addr C N N -- Addr C )
622
623		type ( print message: Addr C -- )
624
625	else ( N Addr C N -- N Addr C )
626
627		menu_timeout_x @
628		menu_timeout_y @
629		at-xy ( position cursor: N Addr C N N -- N Addr C )
630
631		spaces ( erase message: N Addr C -- N Addr )
632		2drop ( N Addr -- )
633
634	then
635
636	0 25 at-xy ( position cursor back at bottom-left )
637;
638
639\ This function blocks program flow (loops forever) until a key is pressed.
640\ The key that was pressed is added to the top of the stack in the form of its
641\ decimal ASCII representation. This function is called by the menu-display
642\ function. You need not call it directly.
643\ 
644: getkey ( -- ascii_keycode )
645
646	begin \ loop forever
647
648		menu_timeout_enabled @ 1 = if
649			( -- )
650			seconds ( get current time: -- N )
651			dup menu_time @ <> if ( has time elapsed?: N N N -- N )
652
653				\ At least 1 second has elapsed since last loop
654				\ so we will decrement our "timeout" (really a
655				\ counter, insuring that we do not proceed too
656				\ fast) and update our timeout display.
657
658				menu_time ! ( update time record: N -- )
659				menu_timeout @ ( "time" remaining: -- N )
660				dup 0> if ( greater than 0?: N N 0 -- N )
661					1- ( decrement counter: N -- N )
662					dup menu_timeout !
663						( re-assign: N N Addr -- N )
664				then
665				( -- N )
666
667				dup 0= swap 0< or if ( N <= 0?: N N -- )
668					\ halt the timer
669					0 menu_timeout ! ( 0 Addr -- )
670					0 menu_timeout_enabled ! ( 0 Addr -- )
671				then
672
673				\ update the timer display ( N -- )
674				menu_timeout @ menu-timeout-update
675
676				menu_timeout @ 0= if
677					\ We've reached the end of the timeout
678					\ (user did not cancel by pressing ANY
679					\ key)
680
681					s" menu_timeout_command" getenv dup
682					-1 = if
683						drop \ clean-up
684					else
685						evaluate
686					then
687				then
688
689			else ( -- N )
690				\ No [detectable] time has elapsed (in seconds)
691				drop ( N -- )
692			then
693			( -- )
694		then
695
696		key? if \ Was a key pressed? (see loader(8))
697
698			\ An actual key was pressed (if the timeout is running,
699			\ kill it regardless of which key was pressed)
700			menu_timeout @ 0<> if
701				0 menu_timeout !
702				0 menu_timeout_enabled !
703
704				\ clear screen of timeout message
705				0 menu-timeout-update
706			then
707
708			\ get the key that was pressed and exit (if we
709			\ get a non-zero ASCII code)
710			key dup 0<> if
711				exit
712			else
713				drop
714			then
715		then
716		50 ms \ sleep for 50 milliseconds (see loader(8))
717
718	again
719;
720
721: menu-erase ( -- ) \ Erases menu and resets positioning variable to positon 1.
722
723	\ Clear the screen area associated with the interactive menu
724	menuX @ menuY @
725	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
726	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
727	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
728	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
729	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
730	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces
731	2drop
732
733	\ Reset the starting index and position for the menu
734	menu_start 1- menuidx !
735	0 menurow !
736;
737
738\ Erase and redraw the menu. Useful if you change a caption and want to
739\ update the menu to reflect the new value.
740\ 
741: menu-redraw ( -- )
742	menu-erase
743	menu-create
744;
745
746\ This function initializes the menu. Call this from your `loader.rc' file
747\ before calling any other menu-related functions.
748\ 
749: menu-init ( -- )
750	menu_start
751	1- menuidx !    \ Initialize the starting index for the menu
752	0 menurow !     \ Initialize the starting position for the menu
753	42 13 2 9 box   \ Draw frame (w,h,x,y)
754	0 25 at-xy      \ Move cursor to the bottom for output
755;
756
757\ Main function. Call this from your `loader.rc' file.
758\ 
759: menu-display ( -- )
760
761	0 menu_timeout_enabled ! \ start with automatic timeout disabled
762
763	\ check indication that automatic execution after delay is requested
764	s" menu_timeout_command" getenv -1 <> if ( Addr C -1 -- | Addr )
765		drop ( just testing existence right now: Addr -- )
766
767		\ initialize state variables
768		seconds menu_time ! ( store the time we started )
769		1 menu_timeout_enabled ! ( enable automatic timeout )
770
771		\ read custom time-duration (if set)
772		s" autoboot_delay" getenv dup -1 = if
773			drop \ no custom duration (remove dup'd bunk -1)
774			menu_timeout_default \ use default setting
775		else
776			2dup ?number 0= if ( if not a number )
777				\ disable timeout if "NO", else use default
778				s" NO" compare-insensitive 0= if
779					0 menu_timeout_enabled !
780					0 ( assigned to menu_timeout below )
781				else
782					menu_timeout_default
783				then
784			else
785				-rot 2drop
786
787				\ boot immediately if less than zero
788				dup 0< if
789					drop
790					menu-create
791					0 25 at-xy
792					0 boot
793				then
794			then
795		then
796		menu_timeout ! ( store value on stack from above )
797
798		menu_timeout_enabled @ 1 = if
799			\ read custom column position (if set)
800			s" loader_menu_timeout_x" getenv dup -1 = if
801				drop \ no custom column position
802				menu_timeout_default_x \ use default setting
803			else
804				\ make sure custom position is a number
805				?number 0= if
806					menu_timeout_default_x \ or use default
807				then
808			then
809			menu_timeout_x ! ( store value on stack from above )
810        
811			\ read custom row position (if set)
812			s" loader_menu_timeout_y" getenv dup -1 = if
813				drop \ no custom row position
814				menu_timeout_default_y \ use default setting
815			else
816				\ make sure custom position is a number
817				?number 0= if
818					menu_timeout_default_y \ or use default
819				then
820			then
821			menu_timeout_y ! ( store value on stack from above )
822		then
823	then
824
825	menu-create
826
827	begin \ Loop forever
828
829		0 25 at-xy \ Move cursor to the bottom for output
830		getkey     \ Block here, waiting for a key to be pressed
831
832		dup -1 = if
833			drop exit \ Caught abort (abnormal return)
834		then
835
836		\ Boot if the user pressed Enter/Ctrl-M (13) or
837		\ Ctrl-Enter/Ctrl-J (10)
838		dup over 13 = swap 10 = or if
839			drop ( no longer needed )
840			s" boot" evaluate
841			exit ( pedantic; never reached )
842		then
843
844		\ Evaluate the decimal ASCII value against known menu item
845		\ key associations and act accordingly
846
847		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
848		begin
849			s" menukeyN @"
850
851			\ replace 'N' with current iteration
852			-rot 2dup 7 + c! rot
853
854			evaluate rot tuck = if
855
856				\ Adjust for missing ACPI menuitem on non-i386
857				arch-i386? true <> menuacpi @ 0<> and if
858					menuacpi @ over 2dup < -rot = or
859					over 58 < and if
860					( key >= menuacpi && key < 58: N -- N )
861						1+
862					then
863				then
864
865				\ base env name for the value (x is a number)
866				s" menu_command[x]"
867
868				\ Copy ASCII number to string at offset 13
869				-rot 2dup 13 + c! rot
870
871				\ Test for the environment variable
872				getenv dup -1 <> if
873					\ Execute the stored procedure
874					evaluate
875
876					\ We expect there to be a non-zero
877					\  value left on the stack after
878					\ executing the stored procedure.
879					\ If so, continue to run, else exit.
880
881					0= if
882						drop \ key pressed
883						drop \ loop iterator
884						exit
885					else
886						swap \ need iterator on top
887					then
888				then
889
890				\ Re-adjust for missing ACPI menuitem
891				arch-i386? true <> menuacpi @ 0<> and if
892					swap
893					menuacpi @ 1+ over 2dup < -rot = or
894					over 59 < and if
895						1-
896					then
897					swap
898				then
899			else
900				swap \ need iterator on top
901			then
902
903			\ 
904			\ Check for menu keycode shortcut(s)
905			\ 
906			s" menu_keycode[x]"
907			-rot 2dup 13 + c! rot
908			getenv dup -1 = if
909				drop
910			else
911				?number 0<> if
912					rot tuck = if
913						swap
914						s" menu_command[x]"
915						-rot 2dup 13 + c! rot
916						getenv dup -1 <> if
917							evaluate
918							0= if
919								2drop
920								exit
921							then
922						else
923							drop
924						then
925					else
926						swap
927					then
928				then
929			then
930
931			1+ dup 56 > \ increment iterator
932			            \ continue if less than 57
933		until
934		drop \ loop iterator
935
936		menureboot @ = if 0 reboot then
937
938	again	\ Non-operational key was pressed; repeat
939;
940
941\ This function unsets all the possible environment variables associated with
942\ creating the interactive menu.
943\ 
944: menu-unset ( -- )
945
946	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
947	begin
948		\ Unset variables in-order of appearance in menu.4th(8)
949
950		s" menu_caption[x]"	\ basename for caption variable
951		-rot 2dup 13 + c! rot	\ replace 'x' with current iteration
952		unsetenv		\ not erroneous to unset unknown var
953
954		s" menu_command[x]"	\ command basename
955		-rot 2dup 13 + c! rot	\ replace 'x'
956		unsetenv
957
958		s" menu_init[x]"	\ initializer basename
959		-rot 2dup 10 + c! rot	\ replace 'x'
960		unsetenv
961
962		s" menu_keycode[x]"	\ keycode basename
963		-rot 2dup 13 + c! rot	\ replace 'x'
964		unsetenv
965
966		s" ansi_caption[x]"	\ ANSI caption basename
967		-rot 2dup 13 + c! rot	\ replace 'x'
968		unsetenv
969
970		s" toggled_text[x]"	\ toggle_menuitem caption basename
971		-rot 2dup 13 + c! rot	\ replace 'x'
972		unsetenv
973
974		s" toggled_ansi[x]"	\ toggle_menuitem ANSI caption basename
975		-rot 2dup 13 + c! rot	\ replace 'x'
976		unsetenv
977
978		s" menu_caption[x][y]"	\ cycle_menuitem caption
979		-rot 2dup 13 + c! rot	\ replace 'x'
980		48 -rot
981		begin
982			16 2over rot + c! \ replace 'y'
983			2dup unsetenv
984
985			rot 1+ dup 57 > 2swap rot
986		until
987		2drop drop
988
989		s" ansi_caption[x][y]"	\ cycle_menuitem ANSI caption
990		-rot 2dup 13 + c! rot	\ replace 'x'
991		48 -rot
992		begin
993			16 2over rot + c! \ replace 'y'
994			2dup unsetenv
995
996			rot 1+ dup 57 > 2swap rot
997		until
998		2drop drop
999
1000		s" 0 menukeyN !"	\ basename for key association var
1001		-rot 2dup 9 + c! rot	\ replace 'N' with current iteration
1002		evaluate		\ assign zero (0) to key assoc. var
1003
1004		s" 0 init_stateN !"	\ used by menu-create
1005		-rot 2dup 12 + c! rot	\ replace 'N'
1006		evaluate
1007
1008		1+ dup 56 >	\ increment, continue if less than 57
1009	until
1010	drop \ iterator
1011
1012	\ unset the timeout command
1013	s" menu_timeout_command" unsetenv
1014
1015	\ clear the "Reboot" menu option flag
1016	s" menu_reboot" unsetenv
1017	0 menureboot !
1018
1019	\ clear the ACPI menu option flag
1020	s" menu_acpi" unsetenv
1021	0 menuacpi !
1022
1023	\ clear the "Options" menu separator flag
1024	s" menu_options" unsetenv
1025	s" menu_optionstext" unsetenv
1026	0 menuoptions !
1027
1028	\ clear the menu initializer
1029	s" menu_init" unsetenv
1030;
1031
1032\ This function both unsets menu variables and visually erases the menu area
1033\ in-preparation for another menu.
1034\ 
1035: menu-clear ( -- )
1036	menu-unset
1037	menu-erase
1038;
1039
1040\ Assign configuration values
1041bullet menubllt !
104210 menuY !
10435 menuX !
1044
1045\ Initialize our menu initialization state variables
10460 init_state1 !
10470 init_state2 !
10480 init_state3 !
10490 init_state4 !
10500 init_state5 !
10510 init_state6 !
10520 init_state7 !
10530 init_state8 !
1054
1055\ Initialize our boolean state variables
10560 toggle_state1 !
10570 toggle_state2 !
10580 toggle_state3 !
10590 toggle_state4 !
10600 toggle_state5 !
10610 toggle_state6 !
10620 toggle_state7 !
10630 toggle_state8 !
1064
1065\ Initialize our array state variables
10660 cycle_state1 !
10670 cycle_state2 !
10680 cycle_state3 !
10690 cycle_state4 !
10700 cycle_state5 !
10710 cycle_state6 !
10720 cycle_state7 !
10730 cycle_state8 !
1074
1075\ Initialize string containers
10760 init_text1 c!
10770 init_text2 c!
10780 init_text3 c!
10790 init_text4 c!
10800 init_text5 c!
10810 init_text6 c!
10820 init_text7 c!
10830 init_text8 c!
1084