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