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