menu.4th revision 242923
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 242923 2012-11-12 18:38:54Z 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			drop ( getenv cruft )
346			s" "
347		then
348	then
349
350	\ At this point, we should have the following on the stack (in order,
351	\ from bottom to top):
352	\ 
353	\    N      - Ascii numeral representing the menu choice (inherited)
354	\    Addr   - address of our internal cycle_stateN variable
355	\    N      - zero-based number we intend to store to the above
356	\    C-Addr - string value we intend to store to menu_caption[x]
357	\             (or ansi_caption[x] with loader_color enabled)
358	\ 
359	\ Let's perform what we need to with the above.
360
361	\ base name of menuitem caption var
362	loader_color? if
363		s" ansi_caption[x]"
364	else
365		s" menu_caption[x]"
366	then
367	6 pick rot tuck 13 + c! swap    \ replace 'x' with menu choice
368	setenv                          \ set the new caption
369
370	swap ! \ update array state variable
371;
372
373: acpipresent? ( -- flag ) \ Returns TRUE if ACPI is present, FALSE otherwise
374	s" hint.acpi.0.rsdp" getenv
375	dup -1 = if
376		drop false exit
377	then
378	2drop
379	true
380;
381
382: acpienabled? ( -- flag ) \ Returns TRUE if ACPI is enabled, FALSE otherwise
383	s" hint.acpi.0.disabled" getenv
384	dup -1 <> if
385		s" 0" compare 0<> if
386			false exit
387		then
388	else
389		drop
390	then
391	true
392;
393
394\ This function prints the appropriate menuitem basename to the stack if an
395\ ACPI option is to be presented to the user, otherwise returns -1. Used
396\ internally by menu-create, you need not (nor should you) call this directly.
397\ 
398: acpimenuitem ( -- C-Addr/U | -1 )
399
400	arch-i386? if
401		acpipresent? if
402			acpienabled? if
403				loader_color? if
404					s" toggled_ansi[x]"
405				else
406					s" toggled_text[x]"
407				then
408			else
409				loader_color? if
410					s" ansi_caption[x]"
411				else
412					s" menu_caption[x]"
413				then
414			then
415		else
416			menuidx dup @ 1+ swap ! ( increment menuidx )
417			-1
418		then
419	else
420		-1
421	then
422;
423
424\ This function creates the list of menu items. This function is called by the
425\ menu-display function. You need not be call it directly.
426\ 
427: menu-create ( -- )
428
429	\ Print the frame caption at (x,y)
430	s" loader_menu_title" getenv dup -1 = if
431		drop s" Welcome to FreeBSD"
432	then
433	24 over 2 / - 9 at-xy type 
434
435	\ If $menu_init is set, evaluate it (allowing for whole menus to be
436	\ constructed dynamically -- as this function could conceivably set
437	\ the remaining environment variables to construct the menu entirely).
438	\ 
439	s" menu_init" getenv dup -1 <> if
440		evaluate
441	else
442		drop
443	then
444
445	\ Print our menu options with respective key/variable associations.
446	\ `printmenuitem' ends by adding the decimal ASCII value for the
447	\ numerical prefix to the stack. We store the value left on the stack
448	\ to the key binding variable for later testing against a character
449	\ captured by the `getkey' function.
450
451	\ Note that any menu item beyond 9 will have a numerical prefix on the
452	\ screen consisting of the first digit (ie. 1 for the tenth menu item)
453	\ and the key required to activate that menu item will be the decimal
454	\ ASCII of 48 plus the menu item (ie. 58 for the tenth item, aka. `:')
455	\ which is misleading and not desirable.
456	\ 
457	\ Thus, we do not allow more than 8 configurable items on the menu
458	\ (with "Reboot" as the optional ninth and highest numbered item).
459
460	\ 
461	\ Initialize the ACPI option status.
462	\ 
463	0 menuacpi !
464	s" menu_acpi" getenv -1 <> if
465		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
466			menuacpi !
467			arch-i386? if acpipresent? if
468				\ 
469				\ Set menu toggle state to active state
470				\ (required by generic toggle_menuitem)
471				\ 
472				menuacpi @
473				s" acpienabled? toggle_stateN !"
474				-rot tuck 25 + c! swap
475				evaluate
476			then then
477		else
478			drop
479		then
480	then
481
482	\ 
483	\ Initialize the menu_options visual separator.
484	\ 
485	0 menuoptions !
486	s" menu_options" getenv -1 <> if
487		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
488			menuoptions !
489		else
490			drop
491		then
492	then
493
494	\ Initialize "Reboot" menu state variable (prevents double-entry)
495	false menurebootadded !
496
497	menu_start
498	1- menuidx !    \ Initialize the starting index for the menu
499	0 menurow !     \ Initialize the starting position for the menu
500
501	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
502	begin
503		\ If the "Options:" separator, print it.
504		dup menuoptions @ = if
505			\ Optionally add a reboot option to the menu
506			s" menu_reboot" getenv -1 <> if
507				drop
508				s" Reboot" printmenuitem menureboot !
509				true menurebootadded !
510			then
511
512			menuX @
513			menurow @ 2 + menurow !
514			menurow @ menuY @ +
515			at-xy
516			s" menu_optionstext" getenv dup -1 <> if
517				type
518			else
519				drop ." Options:"
520			then
521		then
522
523		\ If this is the ACPI menu option, act accordingly.
524		dup menuacpi @ = if
525			acpimenuitem ( -- C-Addr/U | -1 )
526		else
527			\ make sure we have not already initialized this item
528			s" init_stateN"
529			-rot 2dup 10 + c! rot \ repace 'N'
530			evaluate dup @ 0= if
531				1 swap !
532
533				\ If this menuitem has an initializer, run it
534				s" menu_init[x]"
535				-rot 2dup 10 + c! rot \ replace 'x'
536				getenv dup -1 <> if
537					evaluate
538				else
539					drop
540				then
541			else
542				drop
543			then
544
545			loader_color? if
546				s" ansi_caption[x]"
547			else
548				s" menu_caption[x]"
549			then
550		then
551
552		( C-Addr/U | -1 )
553		dup -1 <> if
554			\ replace 'x' with current iteration
555			-rot 2dup 13 + c! rot
556        
557			\ test for environment variable
558			getenv dup -1 <> if
559				printmenuitem ( C-Addr/U -- N )
560        
561				s" menukeyN !" \ generate cmd to store result
562				-rot 2dup 7 + c! rot
563        
564				evaluate
565			else
566				drop
567			then
568		else
569			drop
570
571			s" menu_command[x]"
572			-rot 2dup 13 + c! rot ( replace 'x' )
573			unsetenv
574		then
575
576		1+ dup 56 > \ add 1 to iterator, continue if less than 57
577	until
578	drop \ iterator
579
580	\ Optionally add a reboot option to the menu
581	menurebootadded @ true <> if
582		s" menu_reboot" getenv -1 <> if
583			drop       \ no need for the value
584			s" Reboot" \ menu caption (required by printmenuitem)
585
586			printmenuitem
587			menureboot !
588		else
589			0 menureboot !
590		then
591	then
592;
593
594\ Takes a single integer on the stack and updates the timeout display. The
595\ integer must be between 0 and 9 (we will only update a single digit in the
596\ source message).
597\ 
598: menu-timeout-update ( N -- )
599
600	dup 9 > if ( N N 9 -- N )
601		drop ( N -- )
602		9 ( maximum: -- N )
603	then
604
605	dup 0 < if ( N N 0 -- N )
606		drop ( N -- )
607		0 ( minimum: -- N )
608	then
609
610	48 + ( convert single-digit numeral to ASCII: N 48 -- N )
611
612	s" Autoboot in N seconds. [Space] to pause" ( N -- N Addr C )
613
614	2 pick 48 - 0> if ( N Addr C N 48 -- N Addr C )
615
616		\ Modify 'N' (Addr+12) above to reflect time-left
617
618		-rot	( N Addr C -- C N Addr )
619		tuck	( C N Addr -- C Addr N Addr )
620		12 +	( C Addr N Addr -- C Addr N Addr2 )
621		c!	( C Addr N Addr2 -- C Addr )
622		swap	( C Addr -- Addr C )
623
624		menu_timeout_x @
625		menu_timeout_y @
626		at-xy ( position cursor: Addr C N N -- Addr C )
627
628		type ( print message: Addr C -- )
629
630	else ( N Addr C N -- N Addr C )
631
632		menu_timeout_x @
633		menu_timeout_y @
634		at-xy ( position cursor: N Addr C N N -- N Addr C )
635
636		spaces ( erase message: N Addr C -- N Addr )
637		2drop ( N Addr -- )
638
639	then
640
641	0 25 at-xy ( position cursor back at bottom-left )
642;
643
644\ This function blocks program flow (loops forever) until a key is pressed.
645\ The key that was pressed is added to the top of the stack in the form of its
646\ decimal ASCII representation. This function is called by the menu-display
647\ function. You need not call it directly.
648\ 
649: getkey ( -- ascii_keycode )
650
651	begin \ loop forever
652
653		menu_timeout_enabled @ 1 = if
654			( -- )
655			seconds ( get current time: -- N )
656			dup menu_time @ <> if ( has time elapsed?: N N N -- N )
657
658				\ At least 1 second has elapsed since last loop
659				\ so we will decrement our "timeout" (really a
660				\ counter, insuring that we do not proceed too
661				\ fast) and update our timeout display.
662
663				menu_time ! ( update time record: N -- )
664				menu_timeout @ ( "time" remaining: -- N )
665				dup 0> if ( greater than 0?: N N 0 -- N )
666					1- ( decrement counter: N -- N )
667					dup menu_timeout !
668						( re-assign: N N Addr -- N )
669				then
670				( -- N )
671
672				dup 0= swap 0< or if ( N <= 0?: N N -- )
673					\ halt the timer
674					0 menu_timeout ! ( 0 Addr -- )
675					0 menu_timeout_enabled ! ( 0 Addr -- )
676				then
677
678				\ update the timer display ( N -- )
679				menu_timeout @ menu-timeout-update
680
681				menu_timeout @ 0= if
682					\ We've reached the end of the timeout
683					\ (user did not cancel by pressing ANY
684					\ key)
685
686					s" menu_timeout_command" getenv dup
687					-1 = if
688						drop \ clean-up
689					else
690						evaluate
691					then
692				then
693
694			else ( -- N )
695				\ No [detectable] time has elapsed (in seconds)
696				drop ( N -- )
697			then
698			( -- )
699		then
700
701		key? if \ Was a key pressed? (see loader(8))
702
703			\ An actual key was pressed (if the timeout is running,
704			\ kill it regardless of which key was pressed)
705			menu_timeout @ 0<> if
706				0 menu_timeout !
707				0 menu_timeout_enabled !
708
709				\ clear screen of timeout message
710				0 menu-timeout-update
711			then
712
713			\ get the key that was pressed and exit (if we
714			\ get a non-zero ASCII code)
715			key dup 0<> if
716				exit
717			else
718				drop
719			then
720		then
721		50 ms \ sleep for 50 milliseconds (see loader(8))
722
723	again
724;
725
726: menu-erase ( -- ) \ Erases menu and resets positioning variable to positon 1.
727
728	\ Clear the screen area associated with the interactive menu
729	menuX @ menuY @
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 1+
735	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces
736	2drop
737
738	\ Reset the starting index and position for the menu
739	menu_start 1- menuidx !
740	0 menurow !
741;
742
743\ Erase and redraw the menu. Useful if you change a caption and want to
744\ update the menu to reflect the new value.
745\ 
746: menu-redraw ( -- )
747	menu-erase
748	menu-create
749;
750
751\ This function initializes the menu. Call this from your `loader.rc' file
752\ before calling any other menu-related functions.
753\ 
754: menu-init ( -- )
755	menu_start
756	1- menuidx !    \ Initialize the starting index for the menu
757	0 menurow !     \ Initialize the starting position for the menu
758	42 13 2 9 box   \ Draw frame (w,h,x,y)
759	0 25 at-xy      \ Move cursor to the bottom for output
760;
761
762\ Main function. Call this from your `loader.rc' file.
763\ 
764: menu-display ( -- )
765
766	0 menu_timeout_enabled ! \ start with automatic timeout disabled
767
768	\ check indication that automatic execution after delay is requested
769	s" menu_timeout_command" getenv -1 <> if ( Addr C -1 -- | Addr )
770		drop ( just testing existence right now: Addr -- )
771
772		\ initialize state variables
773		seconds menu_time ! ( store the time we started )
774		1 menu_timeout_enabled ! ( enable automatic timeout )
775
776		\ read custom time-duration (if set)
777		s" autoboot_delay" getenv dup -1 = if
778			drop \ no custom duration (remove dup'd bunk -1)
779			menu_timeout_default \ use default setting
780		else
781			2dup ?number 0= if ( if not a number )
782				\ disable timeout if "NO", else use default
783				s" NO" compare-insensitive 0= if
784					0 menu_timeout_enabled !
785					0 ( assigned to menu_timeout below )
786				else
787					menu_timeout_default
788				then
789			else
790				-rot 2drop
791
792				\ boot immediately if less than zero
793				dup 0< if
794					drop
795					menu-create
796					0 25 at-xy
797					0 boot
798				then
799			then
800		then
801		menu_timeout ! ( store value on stack from above )
802
803		menu_timeout_enabled @ 1 = if
804			\ read custom column position (if set)
805			s" loader_menu_timeout_x" getenv dup -1 = if
806				drop \ no custom column position
807				menu_timeout_default_x \ use default setting
808			else
809				\ make sure custom position is a number
810				?number 0= if
811					menu_timeout_default_x \ or use default
812				then
813			then
814			menu_timeout_x ! ( store value on stack from above )
815        
816			\ read custom row position (if set)
817			s" loader_menu_timeout_y" getenv dup -1 = if
818				drop \ no custom row position
819				menu_timeout_default_y \ use default setting
820			else
821				\ make sure custom position is a number
822				?number 0= if
823					menu_timeout_default_y \ or use default
824				then
825			then
826			menu_timeout_y ! ( store value on stack from above )
827		then
828	then
829
830	menu-create
831
832	begin \ Loop forever
833
834		0 25 at-xy \ Move cursor to the bottom for output
835		getkey     \ Block here, waiting for a key to be pressed
836
837		dup -1 = if
838			drop exit \ Caught abort (abnormal return)
839		then
840
841		\ Boot if the user pressed Enter/Ctrl-M (13) or
842		\ Ctrl-Enter/Ctrl-J (10)
843		dup over 13 = swap 10 = or if
844			drop ( no longer needed )
845			s" boot" evaluate
846			exit ( pedantic; never reached )
847		then
848
849		dup menureboot @ = if 0 reboot then
850
851		\ Evaluate the decimal ASCII value against known menu item
852		\ key associations and act accordingly
853
854		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
855		begin
856			s" menukeyN @"
857
858			\ replace 'N' with current iteration
859			-rot 2dup 7 + c! rot
860
861			evaluate rot tuck = if
862
863				\ Adjust for missing ACPI menuitem on non-i386
864				arch-i386? true <> menuacpi @ 0<> and if
865					menuacpi @ over 2dup < -rot = or
866					over 58 < and if
867					( key >= menuacpi && key < 58: N -- N )
868						1+
869					then
870				then
871
872				\ base env name for the value (x is a number)
873				s" menu_command[x]"
874
875				\ Copy ASCII number to string at offset 13
876				-rot 2dup 13 + c! rot
877
878				\ Test for the environment variable
879				getenv dup -1 <> if
880					\ Execute the stored procedure
881					evaluate
882
883					\ We expect there to be a non-zero
884					\  value left on the stack after
885					\ executing the stored procedure.
886					\ If so, continue to run, else exit.
887
888					0= if
889						drop \ key pressed
890						drop \ loop iterator
891						exit
892					else
893						swap \ need iterator on top
894					then
895				then
896
897				\ Re-adjust for missing ACPI menuitem
898				arch-i386? true <> menuacpi @ 0<> and if
899					swap
900					menuacpi @ 1+ over 2dup < -rot = or
901					over 59 < and if
902						1-
903					then
904					swap
905				then
906			else
907				swap \ need iterator on top
908			then
909
910			\ 
911			\ Check for menu keycode shortcut(s)
912			\ 
913			s" menu_keycode[x]"
914			-rot 2dup 13 + c! rot
915			getenv dup -1 = if
916				drop
917			else
918				?number 0<> if
919					rot tuck = if
920						swap
921						s" menu_command[x]"
922						-rot 2dup 13 + c! rot
923						getenv dup -1 <> if
924							evaluate
925							0= if
926								2drop
927								exit
928							then
929						else
930							drop
931						then
932					else
933						swap
934					then
935				then
936			then
937
938			1+ dup 56 > \ increment iterator
939			            \ continue if less than 57
940		until
941		drop \ loop iterator
942		drop \ key pressed
943
944	again	\ Non-operational key was pressed; repeat
945;
946
947\ This function unsets all the possible environment variables associated with
948\ creating the interactive menu.
949\ 
950: menu-unset ( -- )
951
952	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
953	begin
954		\ Unset variables in-order of appearance in menu.4th(8)
955
956		s" menu_caption[x]"	\ basename for caption variable
957		-rot 2dup 13 + c! rot	\ replace 'x' with current iteration
958		unsetenv		\ not erroneous to unset unknown var
959
960		s" menu_command[x]"	\ command basename
961		-rot 2dup 13 + c! rot	\ replace 'x'
962		unsetenv
963
964		s" menu_init[x]"	\ initializer basename
965		-rot 2dup 10 + c! rot	\ replace 'x'
966		unsetenv
967
968		s" menu_keycode[x]"	\ keycode basename
969		-rot 2dup 13 + c! rot	\ replace 'x'
970		unsetenv
971
972		s" ansi_caption[x]"	\ ANSI caption basename
973		-rot 2dup 13 + c! rot	\ replace 'x'
974		unsetenv
975
976		s" toggled_text[x]"	\ toggle_menuitem caption basename
977		-rot 2dup 13 + c! rot	\ replace 'x'
978		unsetenv
979
980		s" toggled_ansi[x]"	\ toggle_menuitem ANSI caption basename
981		-rot 2dup 13 + c! rot	\ replace 'x'
982		unsetenv
983
984		s" menu_caption[x][y]"	\ cycle_menuitem caption
985		-rot 2dup 13 + c! rot	\ replace 'x'
986		48 -rot
987		begin
988			16 2over rot + c! \ replace 'y'
989			2dup unsetenv
990
991			rot 1+ dup 57 > 2swap rot
992		until
993		2drop drop
994
995		s" ansi_caption[x][y]"	\ cycle_menuitem ANSI caption
996		-rot 2dup 13 + c! rot	\ replace 'x'
997		48 -rot
998		begin
999			16 2over rot + c! \ replace 'y'
1000			2dup unsetenv
1001
1002			rot 1+ dup 57 > 2swap rot
1003		until
1004		2drop drop
1005
1006		s" 0 menukeyN !"	\ basename for key association var
1007		-rot 2dup 9 + c! rot	\ replace 'N' with current iteration
1008		evaluate		\ assign zero (0) to key assoc. var
1009
1010		s" 0 init_stateN !"	\ used by menu-create
1011		-rot 2dup 12 + c! rot	\ replace 'N'
1012		evaluate
1013
1014		s" 0 toggle_stateN !"	\ used by toggle_menuitem
1015		-rot 2dup 14 + c! rot	\ replace 'N'
1016		evaluate
1017
1018		s" 0 cycle_stateN !"	\ used by cycle_menuitem
1019		-rot 2dup 13 + c! rot	\ replace 'N'
1020		evaluate
1021
1022		s" 0 init_textN c!"	\ used by toggle_menuitem
1023		-rot 2dup 11 + c! rot	\ replace 'N'
1024		evaluate
1025
1026		1+ dup 56 >	\ increment, continue if less than 57
1027	until
1028	drop \ iterator
1029
1030	\ unset the timeout command
1031	s" menu_timeout_command" unsetenv
1032
1033	\ clear the "Reboot" menu option flag
1034	s" menu_reboot" unsetenv
1035	0 menureboot !
1036
1037	\ clear the ACPI menu option flag
1038	s" menu_acpi" unsetenv
1039	0 menuacpi !
1040
1041	\ clear the "Options" menu separator flag
1042	s" menu_options" unsetenv
1043	s" menu_optionstext" unsetenv
1044	0 menuoptions !
1045
1046	\ clear the menu initializer
1047	s" menu_init" unsetenv
1048;
1049
1050\ This function both unsets menu variables and visually erases the menu area
1051\ in-preparation for another menu.
1052\ 
1053: menu-clear ( -- )
1054	menu-unset
1055	menu-erase
1056;
1057
1058\ Assign configuration values
1059bullet menubllt !
106010 menuY !
10615 menuX !
1062
1063\ Initialize our menu initialization state variables
10640 init_state1 !
10650 init_state2 !
10660 init_state3 !
10670 init_state4 !
10680 init_state5 !
10690 init_state6 !
10700 init_state7 !
10710 init_state8 !
1072
1073\ Initialize our boolean state variables
10740 toggle_state1 !
10750 toggle_state2 !
10760 toggle_state3 !
10770 toggle_state4 !
10780 toggle_state5 !
10790 toggle_state6 !
10800 toggle_state7 !
10810 toggle_state8 !
1082
1083\ Initialize our array state variables
10840 cycle_state1 !
10850 cycle_state2 !
10860 cycle_state3 !
10870 cycle_state4 !
10880 cycle_state5 !
10890 cycle_state6 !
10900 cycle_state7 !
10910 cycle_state8 !
1092
1093\ Initialize string containers
10940 init_text1 c!
10950 init_text2 c!
10960 init_text3 c!
10970 init_text4 c!
10980 init_text5 c!
10990 init_text6 c!
11000 init_text7 c!
11010 init_text8 c!
1102