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$
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 5 constant menu_default_x         \ default column position of timeout
3910 constant menu_default_y         \ default row position of timeout msg
40 4 constant menu_timeout_default_x \ default column position of timeout
4123 constant menu_timeout_default_y \ default row position of timeout msg
4210 constant menu_timeout_default   \ default timeout (in seconds)
43
44\ Customize the following values with care
45
46  1 constant menu_start \ Numerical prefix of first menu item
47dot constant bullet     \ Menu bullet (appears after numerical prefix)
48  5 constant menu_x     \ Row position of the menu (from the top)
49 10 constant menu_y     \ Column position of the menu (from left side)
50
51\ Menu Appearance
52variable menuidx   \ Menu item stack for number prefixes
53variable menurow   \ Menu item stack for positioning
54variable menubllt  \ Menu item bullet
55
56\ Menu Positioning
57variable menuX     \ Menu X offset (columns)
58variable menuY     \ Menu Y offset (rows)
59
60\ Menu-item key association/detection
61variable menukey1
62variable menukey2
63variable menukey3
64variable menukey4
65variable menukey5
66variable menukey6
67variable menukey7
68variable menukey8
69variable menureboot
70variable menurebootadded
71variable menuacpi
72variable menuoptions
73variable menukernel
74
75\ Parsing of kernels into menu-items
76variable kernidx
77variable kernlen
78variable kernmenuidx
79
80\ Menu timer [count-down] variables
81variable menu_timeout_enabled \ timeout state (internal use only)
82variable menu_time            \ variable for tracking the passage of time
83variable menu_timeout         \ determined configurable delay duration
84variable menu_timeout_x       \ column position of timeout message
85variable menu_timeout_y       \ row position of timeout message
86
87\ Menu initialization status variables
88variable init_state1
89variable init_state2
90variable init_state3
91variable init_state4
92variable init_state5
93variable init_state6
94variable init_state7
95variable init_state8
96
97\ Boolean option status variables
98variable toggle_state1
99variable toggle_state2
100variable toggle_state3
101variable toggle_state4
102variable toggle_state5
103variable toggle_state6
104variable toggle_state7
105variable toggle_state8
106
107\ Array option status variables
108variable cycle_state1
109variable cycle_state2
110variable cycle_state3
111variable cycle_state4
112variable cycle_state5
113variable cycle_state6
114variable cycle_state7
115variable cycle_state8
116
117\ Containers for storing the initial caption text
118create init_text1 64 allot
119create init_text2 64 allot
120create init_text3 64 allot
121create init_text4 64 allot
122create init_text5 64 allot
123create init_text6 64 allot
124create init_text7 64 allot
125create init_text8 64 allot
126
127\ Containers for parsing kernels into menu-items
128create kerncapbuf 64 allot
129create kerndefault 64 allot
130create kernelsbuf 256 allot
131
132: +c! ( N C-ADDR/U K -- C-ADDR/U )
133	3 pick 3 pick	( n c-addr/u k -- n c-addr/u k n c-addr )
134	rot + c!	( n c-addr/u k n c-addr -- n c-addr/u )
135	rot drop	( n c-addr/u -- c-addr/u )
136;
137
138: delim? ( C -- BOOL )
139	dup  32 =		( c -- c bool )		\ [sp] space
140	over  9 = or		( c bool -- c bool )	\ [ht] horizontal tab
141	over 10 = or		( c bool -- c bool )	\ [nl] newline
142	over 13 = or		( c bool -- c bool )	\ [cr] carriage return
143	over [char] , =	or	( c bool -- c bool )	\ comma
144	swap drop		( c bool -- bool )	\ return boolean
145;
146
147: menukeyN      ( N -- ADDR )   s" menukeyN"       7 +c! evaluate ;
148: init_stateN   ( N -- ADDR )   s" init_stateN"   10 +c! evaluate ;
149: toggle_stateN ( N -- ADDR )   s" toggle_stateN" 12 +c! evaluate ;
150: cycle_stateN  ( N -- ADDR )   s" cycle_stateN"  11 +c! evaluate ;
151: init_textN    ( N -- C-ADDR ) s" init_textN"     9 +c! evaluate ;
152
153: kernel[x]          ( N -- C-ADDR/U )   s" kernel[x]"           7 +c! ;
154: menu_init[x]       ( N -- C-ADDR/U )   s" menu_init[x]"       10 +c! ;
155: menu_command[x]    ( N -- C-ADDR/U )   s" menu_command[x]"    13 +c! ;
156: menu_caption[x]    ( N -- C-ADDR/U )   s" menu_caption[x]"    13 +c! ;
157: ansi_caption[x]    ( N -- C-ADDR/U )   s" ansi_caption[x]"    13 +c! ;
158: menu_keycode[x]    ( N -- C-ADDR/U )   s" menu_keycode[x]"    13 +c! ;
159: toggled_text[x]    ( N -- C-ADDR/U )   s" toggled_text[x]"    13 +c! ;
160: toggled_ansi[x]    ( N -- C-ADDR/U )   s" toggled_ansi[x]"    13 +c! ;
161: menu_caption[x][y] ( N M -- C-ADDR/U ) s" menu_caption[x][y]" 16 +c! 13 +c! ;
162: ansi_caption[x][y] ( N M -- C-ADDR/U ) s" ansi_caption[x][y]" 16 +c! 13 +c! ;
163
164: arch-i386? ( -- BOOL ) \ Returns TRUE (-1) on i386, FALSE (0) otherwise.
165	s" arch-i386" environment? dup if
166		drop
167	then
168;
169
170\ This function prints a menu item at menuX (row) and menuY (column), returns
171\ the incremental decimal ASCII value associated with the menu item, and
172\ increments the cursor position to the next row for the creation of the next
173\ menu item. This function is called by the menu-create function. You need not
174\ call it directly.
175\ 
176: printmenuitem ( menu_item_str -- ascii_keycode )
177
178	menurow dup @ 1+ swap ! ( increment menurow )
179	menuidx dup @ 1+ swap ! ( increment menuidx )
180
181	\ Calculate the menuitem row position
182	menurow @ menuY @ +
183
184	\ Position the cursor at the menuitem position
185	dup menuX @ swap at-xy
186
187	\ Print the value of menuidx
188	loader_color? if
189		." [1m" ( [22m )
190	then
191	menuidx @ .
192	loader_color? if
193		." [37m" ( [39m )
194	then
195
196	\ Move the cursor forward 1 column
197	dup menuX @ 1+ swap at-xy
198
199	menubllt @ emit	\ Print the menu bullet using the emit function
200
201	\ Move the cursor to the 3rd column from the current position
202	\ to allow for a space between the numerical prefix and the
203	\ text caption
204	menuX @ 3 + swap at-xy
205
206	\ Print the menu caption (we expect a string to be on the stack
207	\ prior to invoking this function)
208	type
209
210	\ Here we will add the ASCII decimal of the numerical prefix
211	\ to the stack (decimal ASCII for `1' is 49) as a "return value"
212	menuidx @ 48 +
213;
214
215: toggle_menuitem ( N -- N ) \ toggles caption text and internal menuitem state
216
217	\ ASCII numeral equal to user-selected menu item must be on the stack.
218	\ We do not modify the stack, so the ASCII numeral is left on top.
219
220	dup init_textN c@ 0= if
221		\ NOTE: no need to check toggle_stateN since the first time we
222		\ are called, we will populate init_textN. Further, we don't
223		\ need to test whether menu_caption[x] (ansi_caption[x] when
224		\ loader_color?=1) is available since we would not have been
225		\ called if the caption was NULL.
226
227		\ base name of environment variable
228		dup ( n -- n n ) \ key pressed
229		loader_color? if
230			ansi_caption[x]
231		else
232			menu_caption[x]
233		then	
234		getenv dup -1 <> if
235
236			2 pick ( n c-addr/u -- n c-addr/u n )
237			init_textN ( n c-addr/u n -- n c-addr/u c-addr )
238
239			\ now we have the buffer c-addr on top
240			\ ( followed by c-addr/u of current caption )
241
242			\ Copy the current caption into our buffer
243			2dup c! -rot \ store strlen at first byte
244			begin
245				rot 1+    \ bring alt addr to top and increment
246				-rot -rot \ bring buffer addr to top
247				2dup c@ swap c! \ copy current character
248				1+     \ increment buffer addr
249				rot 1- \ bring buffer len to top and decrement
250				dup 0= \ exit loop if buffer len is zero
251			until
252			2drop \ buffer len/addr
253			drop  \ alt addr
254
255		else
256			drop
257		then
258	then
259
260	\ Now we are certain to have init_textN populated with the initial
261	\ value of menu_caption[x] (ansi_caption[x] with loader_color enabled).
262	\ We can now use init_textN as the untoggled caption and
263	\ toggled_text[x] (toggled_ansi[x] with loader_color enabled) as the
264	\ toggled caption and store the appropriate value into menu_caption[x]
265	\ (again, ansi_caption[x] with loader_color enabled). Last, we'll
266	\ negate the toggled state so that we reverse the flow on subsequent
267	\ calls.
268
269	dup toggle_stateN @ 0= if
270		\ state is OFF, toggle to ON
271
272		dup ( n -- n n ) \ key pressed
273		loader_color? if
274			toggled_ansi[x]
275		else
276			toggled_text[x]
277		then
278		getenv dup -1 <> if
279			\ Assign toggled text to menu caption
280			2 pick ( n c-addr/u -- n c-addr/u n ) \ key pressed
281			loader_color? if
282				ansi_caption[x]
283			else
284				menu_caption[x]
285			then
286			setenv
287		else
288			\ No toggled text, keep the same caption
289			drop ( n -1 -- n ) \ getenv cruft
290		then
291
292		true \ new value of toggle state var (to be stored later)
293	else
294		\ state is ON, toggle to OFF
295
296		dup init_textN count ( n -- n c-addr/u )
297
298		\ Assign init_textN text to menu caption
299		2 pick ( n c-addr/u -- n c-addr/u n ) \ key pressed
300		loader_color? if
301			ansi_caption[x]
302		else
303			menu_caption[x]
304		then
305		setenv
306
307		false \ new value of toggle state var (to be stored below)
308	then
309
310	\ now we'll store the new toggle state (on top of stack)
311	over toggle_stateN !
312;
313
314: cycle_menuitem ( N -- N ) \ cycles through array of choices for a menuitem
315
316	\ ASCII numeral equal to user-selected menu item must be on the stack.
317	\ We do not modify the stack, so the ASCII numeral is left on top.
318
319	dup cycle_stateN dup @ 1+ \ get value and increment
320
321	\ Before assigning the (incremented) value back to the pointer,
322	\ let's test for the existence of this particular array element.
323	\ If the element exists, we'll store index value and move on.
324	\ Otherwise, we'll loop around to zero and store that.
325
326	dup 48 + ( n addr k -- n addr k k' )
327	         \ duplicate array index and convert to ASCII numeral
328
329	3 pick swap ( n addr k k' -- n addr k n k' ) \ (n,k') as (x,y)
330	loader_color? if
331		ansi_caption[x][y]
332	else
333		menu_caption[x][y]
334	then
335	( n addr k n k' -- n addr k c-addr/u )
336
337	\ Now test for the existence of our incremented array index in the
338	\ form of $menu_caption[x][y] ($ansi_caption[x][y] with loader_color
339	\ enabled) as set in loader.rc(5), et. al.
340
341	getenv dup -1 = if
342		\ No caption set for this array index. Loop back to zero.
343
344		drop ( n addr k -1 -- n addr k ) \ getenv cruft
345		drop 0 ( n addr k -- n addr 0 )  \ new value to store later
346
347		2 pick [char] 0 ( n addr 0 -- n addr 0 n 48 ) \ (n,48) as (x,y)
348		loader_color? if
349			ansi_caption[x][y]
350		else
351			menu_caption[x][y]
352		then
353		( n addr 0 n 48 -- n addr 0 c-addr/u )
354		getenv dup -1 = if
355			\ Highly unlikely to occur, but to ensure things move
356			\ along smoothly, allocate a temporary NULL string
357			drop ( cruft ) s" "
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					s" toggled_ansi[x]"
416				else
417					s" toggled_text[x]"
418				then
419			else
420				loader_color? if
421					s" ansi_caption[x]"
422				else
423					s" 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 parses $kernels into variables that are used by the menu to
436\ display wich kernel to boot when the [overloaded] `boot' word is interpreted.
437\ Used internally by menu-create, you need not (nor should you) call this
438\ directly.
439\ 
440: parse-kernels ( N -- ) \ kernidx
441	kernidx ! ( n -- )	\ store provided `x' value
442	[char] 0 kernmenuidx !	\ initialize `y' value for menu_caption[x][y]
443
444	\ Attempt to get a list of kernels, fall back to sensible default
445	s" kernels" getenv dup -1 = if
446		drop ( cruft )
447		s" kernel kernel.old"
448	then ( -- c-addr/u )
449
450	\ Check to see if the user has altered $kernel by comparing it against
451	\ $kernel[N] where N is kernel_state (the actively displayed kernel).
452	s" kernel_state" evaluate @ 48 + s" kernel[N]" 7 +c! getenv
453	dup -1 <> if
454		s" kernel" getenv dup -1 = if
455			drop ( cruft ) s" "
456		then
457		2swap 2over compare 0= if
458			2drop FALSE ( skip below conditional )
459		else \ User has changed $kernel
460			TRUE ( slurp in new value )
461		then
462	else \ We haven't yet parsed $kernels into $kernel[N]
463		drop ( getenv cruft )
464		s" kernel" getenv dup -1 = if
465			drop ( cruft ) s" "
466		then
467		TRUE ( slurp in initial value )
468	then ( c-addr/u -- c-addr/u c-addr/u,-1 | 0 )
469	if \ slurp new value into kerndefault
470		kerndefault 1+ 0 2swap strcat swap 1- c!
471	then
472
473	\ Clear out existing parsed-kernels
474	kernidx @ [char] 0
475	begin
476		dup kernel[x] unsetenv
477		2dup menu_caption[x][y] unsetenv
478		2dup ansi_caption[x][y] unsetenv
479		1+ dup [char] 8 >
480	until
481	2drop
482
483	\ Step through the string until we find the end
484	begin
485		0 kernlen ! \ initialize length of value
486
487		\ Skip leading whitespace and/or comma delimiters
488		begin
489			dup 0<> if
490				over c@ delim? ( c-addr/u -- c-addr/u bool )
491			else
492				false ( c-addr/u -- c-addr/u bool )
493			then
494		while
495			1- swap 1+ swap ( c-addr/u -- c-addr'/u' )
496		repeat
497		( c-addr/u -- c-addr'/u' )
498
499		dup 0= if \ end of string while eating whitespace
500			2drop ( c-addr/u -- )
501			kernmenuidx @ [char] 0 <> if \ found at least one
502				exit \ all done
503			then
504
505			\ No entries in $kernels; use $kernel instead
506			s" kernel" getenv dup -1 = if
507				drop ( cruft ) s" "
508			then ( -- c-addr/u )
509			dup kernlen ! \ store entire value length as kernlen
510		else
511			\ We're still within $kernels parsing toward the end;
512			\ find delimiter/end to determine kernlen
513			2dup ( c-addr/u -- c-addr/u c-addr/u )
514			begin dup 0<> while
515				over c@ delim? if
516					drop 0 ( break ) \ found delimiter
517				else
518					kernlen @ 1+ kernlen ! \ incrememnt
519					1- swap 1+ swap \ c-addr++ u--
520				then
521			repeat
522			2drop ( c-addr/u c-addr'/u' -- c-addr/u )
523
524			\ If this is the first entry, compare it to $kernel
525			\ If different, then insert $kernel beforehand
526			kernmenuidx @ [char] 0 = if
527				over kernlen @ kerndefault count compare if
528					kernelsbuf 0 kerndefault count strcat
529					s" ," strcat 2swap strcat
530					kerndefault count swap drop kernlen !
531				then
532			then
533		then
534		( c-addr/u -- c-addr'/u' )
535
536		\ At this point, we should have something on the stack to store
537		\ as the next kernel menu option; start assembling variables
538
539		over kernlen @ ( c-addr/u -- c-addr/u c-addr/u2 )
540
541		\ Assign first to kernel[x]
542		2dup kernmenuidx @ kernel[x] setenv
543
544		\ Assign second to menu_caption[x][y]
545		kerncapbuf 0 s" [K]ernel: " strcat
546		2over strcat
547		kernidx @ kernmenuidx @ menu_caption[x][y]
548		setenv
549
550		\ Assign third to ansi_caption[x][y]
551		kerncapbuf 0 s" [1mK[37mernel: " strcat
552		kernmenuidx @ [char] 0 = if
553			s" default/[32m"
554		else
555			s" [34;1m"
556		then strcat
557		2over strcat
558		s" [37m" strcat
559		kernidx @ kernmenuidx @ ansi_caption[x][y]
560		setenv
561
562		2drop ( c-addr/u c-addr/u2 -- c-addr/u )
563
564		kernmenuidx @ 1+ dup kernmenuidx ! [char] 8 > if
565			2drop ( c-addr/u -- ) exit
566		then
567
568		kernlen @ - swap kernlen @ + swap ( c-addr/u -- c-addr'/u' )
569	again
570;
571
572\ This function goes through the kernels that were discovered by the
573\ parse-kernels function [above], adding " (# of #)" text to the end of each
574\ caption.
575\ 
576: tag-kernels ( -- )
577	kernidx @ ( -- x ) dup 0= if exit then
578	[char] 0 s"  (Y of Z)" ( x -- x y c-addr/u )
579	kernmenuidx @ -rot 7 +c! \ Replace 'Z' with number of kernels parsed
580	begin
581		2 pick 1+ -rot 2 +c! \ Replace 'Y' with current ASCII num
582
583		2over menu_caption[x][y] getenv dup -1 <> if
584			2dup + 1- c@ [char] ) = if
585				2drop \ Already tagged
586			else
587				kerncapbuf 0 2swap strcat
588				2over strcat
589				5 pick 5 pick menu_caption[x][y] setenv
590			then
591		else
592			drop ( getenv cruft )
593		then
594
595		2over ansi_caption[x][y] getenv dup -1 <> if
596			2dup + 1- c@ [char] ) = if
597				2drop \ Already tagged
598			else
599				kerncapbuf 0 2swap strcat
600				2over strcat
601				5 pick 5 pick ansi_caption[x][y] setenv
602			then
603		else
604			drop ( getenv cruft )
605		then
606
607		rot 1+ dup [char] 8 > if
608			-rot 2drop TRUE ( break )
609		else
610			-rot FALSE
611		then
612	until
613	2drop ( x y -- )
614;
615
616\ This function creates the list of menu items. This function is called by the
617\ menu-display function. You need not be call it directly.
618\ 
619: menu-create ( -- )
620
621	\ Print the frame caption at (x,y)
622	s" loader_menu_title" getenv dup -1 = if
623		drop s" Welcome to FreeBSD"
624	then
625	TRUE ( use default alignment )
626	s" loader_menu_title_align" getenv dup -1 <> if
627		2dup s" left" compare-insensitive 0= if ( 1 )
628			2drop ( c-addr/u ) drop ( bool )
629			menuX @ menuY @ 1-
630			FALSE ( don't use default alignment )
631		else ( 1 ) 2dup s" right" compare-insensitive 0= if ( 2 )
632			2drop ( c-addr/u ) drop ( bool )
633			menuX @ 42 + 4 - over - menuY @ 1-
634			FALSE ( don't use default alignment )
635		else ( 2 ) 2drop ( c-addr/u ) then ( 1 ) then
636	else
637		drop ( getenv cruft )
638	then
639	if ( use default center alignement? )
640		menuX @ 19 + over 2 / - menuY @ 1-
641	then
642	at-xy type 
643
644	\ If $menu_init is set, evaluate it (allowing for whole menus to be
645	\ constructed dynamically -- as this function could conceivably set
646	\ the remaining environment variables to construct the menu entirely).
647	\ 
648	s" menu_init" getenv dup -1 <> if
649		evaluate
650	else
651		drop
652	then
653
654	\ Print our menu options with respective key/variable associations.
655	\ `printmenuitem' ends by adding the decimal ASCII value for the
656	\ numerical prefix to the stack. We store the value left on the stack
657	\ to the key binding variable for later testing against a character
658	\ captured by the `getkey' function.
659
660	\ Note that any menu item beyond 9 will have a numerical prefix on the
661	\ screen consisting of the first digit (ie. 1 for the tenth menu item)
662	\ and the key required to activate that menu item will be the decimal
663	\ ASCII of 48 plus the menu item (ie. 58 for the tenth item, aka. `:')
664	\ which is misleading and not desirable.
665	\ 
666	\ Thus, we do not allow more than 8 configurable items on the menu
667	\ (with "Reboot" as the optional ninth and highest numbered item).
668
669	\ 
670	\ Initialize the ACPI option status.
671	\ 
672	0 menuacpi !
673	s" menu_acpi" getenv -1 <> if
674		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
675			menuacpi !
676			arch-i386? if acpipresent? if
677				\ 
678				\ Set menu toggle state to active state
679				\ (required by generic toggle_menuitem)
680				\ 
681				acpienabled? menuacpi @ toggle_stateN !
682			then then
683		else
684			drop
685		then
686	then
687
688	\ 
689	\ Initialize kernel captions after parsing $kernels
690	\ 
691	0 menukernel !
692	s" menu_kernel" getenv -1 <> if
693		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
694			dup menukernel !
695			dup parse-kernels tag-kernels
696
697			\ Get the current cycle state (entry to use)
698			s" kernel_state" evaluate @ 48 + ( n -- n y )
699
700			\ If state is invalid, reset
701			dup kernmenuidx @ 1- > if
702				drop [char] 0 ( n y -- n 48 )
703				0 s" kernel_state" evaluate !
704				over s" init_kernel" evaluate drop
705			then
706
707			\ Set the current non-ANSI caption
708			2dup swap dup ( n y -- n y y n n )
709			s" set menu_caption[x]=$menu_caption[x][y]"
710			17 +c! 34 +c! 37 +c! evaluate
711			( n y y n n c-addr/u -- n y  )
712
713			\ Set the current ANSI caption
714			2dup swap dup ( n y -- n y y n n )
715			s" set ansi_caption[x]=$ansi_caption[x][y]"
716			17 +c! 34 +c! 37 +c! evaluate
717			( n y y n n c-addr/u -- n y )
718
719			\ Initialize cycle state from stored value
720			48 - ( n y -- n k )
721			s" init_cyclestate" evaluate ( n k -- n )
722
723			\ Set $kernel to $kernel[y]
724			s" activate_kernel" evaluate ( n -- n )
725		then
726		drop
727	then
728
729	\ 
730	\ Initialize the menu_options visual separator.
731	\ 
732	0 menuoptions !
733	s" menu_options" getenv -1 <> if
734		c@ dup 48 > over 57 < and if ( '1' <= c1 <= '8' )
735			menuoptions !
736		else
737			drop
738		then
739	then
740
741	\ Initialize "Reboot" menu state variable (prevents double-entry)
742	false menurebootadded !
743
744	menu_start
745	1- menuidx !    \ Initialize the starting index for the menu
746	0 menurow !     \ Initialize the starting position for the menu
747
748	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
749	begin
750		\ If the "Options:" separator, print it.
751		dup menuoptions @ = if
752			\ Optionally add a reboot option to the menu
753			s" menu_reboot" getenv -1 <> if
754				drop
755				s" Reboot" printmenuitem menureboot !
756				true menurebootadded !
757			then
758
759			menuX @
760			menurow @ 2 + menurow !
761			menurow @ menuY @ +
762			at-xy
763			s" menu_optionstext" getenv dup -1 <> if
764				type
765			else
766				drop ." Options:"
767			then
768		then
769
770		\ If this is the ACPI menu option, act accordingly.
771		dup menuacpi @ = if
772			dup acpimenuitem ( n -- n n c-addr/u | n n -1 )
773			dup -1 <> if
774				13 +c! ( n n c-addr/u -- n c-addr/u )
775				       \ replace 'x' with n
776			else
777				swap drop ( n n -1 -- n -1 )
778				over menu_command[x] unsetenv
779			then
780		else
781			\ make sure we have not already initialized this item
782			dup init_stateN dup @ 0= if
783				1 swap !
784
785				\ If this menuitem has an initializer, run it
786				dup menu_init[x]
787				getenv dup -1 <> if
788					evaluate
789				else
790					drop
791				then
792			else
793				drop
794			then
795
796			dup
797			loader_color? if
798				ansi_caption[x]
799			else
800				menu_caption[x]
801			then
802		then
803
804		dup -1 <> if
805			\ test for environment variable
806			getenv dup -1 <> if
807				printmenuitem ( c-addr/u -- n )
808				dup menukeyN !
809			else
810				drop
811			then
812		else
813			drop
814		then
815
816		1+ dup 56 > \ add 1 to iterator, continue if less than 57
817	until
818	drop \ iterator
819
820	\ Optionally add a reboot option to the menu
821	menurebootadded @ true <> if
822		s" menu_reboot" getenv -1 <> if
823			drop       \ no need for the value
824			s" Reboot" \ menu caption (required by printmenuitem)
825
826			printmenuitem
827			menureboot !
828		else
829			0 menureboot !
830		then
831	then
832;
833
834\ Takes a single integer on the stack and updates the timeout display. The
835\ integer must be between 0 and 9 (we will only update a single digit in the
836\ source message).
837\ 
838: menu-timeout-update ( N -- )
839
840	\ Enforce minimum/maximum
841	dup 9 > if drop 9 then
842	dup 0 < if drop 0 then
843
844	s" Autoboot in N seconds. [Space] to pause" ( n -- n c-addr/u )
845
846	2 pick 0> if
847		rot 48 + -rot ( n c-addr/u -- n' c-addr/u ) \ convert to ASCII
848		12 +c!        ( n' c-addr/u -- c-addr/u )   \ replace 'N' above
849
850		menu_timeout_x @ menu_timeout_y @ at-xy \ position cursor
851		type ( c-addr/u -- ) \ print message
852	else
853		menu_timeout_x @ menu_timeout_y @ at-xy \ position cursor
854		spaces ( n c-addr/u -- n c-addr ) \ erase message
855		2drop ( n c-addr -- )
856	then
857
858	0 25 at-xy ( position cursor back at bottom-left )
859;
860
861\ This function blocks program flow (loops forever) until a key is pressed.
862\ The key that was pressed is added to the top of the stack in the form of its
863\ decimal ASCII representation. This function is called by the menu-display
864\ function. You need not call it directly.
865\ 
866: getkey ( -- ascii_keycode )
867
868	begin \ loop forever
869
870		menu_timeout_enabled @ 1 = if
871			( -- )
872			seconds ( get current time: -- N )
873			dup menu_time @ <> if ( has time elapsed?: N N N -- N )
874
875				\ At least 1 second has elapsed since last loop
876				\ so we will decrement our "timeout" (really a
877				\ counter, insuring that we do not proceed too
878				\ fast) and update our timeout display.
879
880				menu_time ! ( update time record: N -- )
881				menu_timeout @ ( "time" remaining: -- N )
882				dup 0> if ( greater than 0?: N N 0 -- N )
883					1- ( decrement counter: N -- N )
884					dup menu_timeout !
885						( re-assign: N N Addr -- N )
886				then
887				( -- N )
888
889				dup 0= swap 0< or if ( N <= 0?: N N -- )
890					\ halt the timer
891					0 menu_timeout ! ( 0 Addr -- )
892					0 menu_timeout_enabled ! ( 0 Addr -- )
893				then
894
895				\ update the timer display ( N -- )
896				menu_timeout @ menu-timeout-update
897
898				menu_timeout @ 0= if
899					\ We've reached the end of the timeout
900					\ (user did not cancel by pressing ANY
901					\ key)
902
903					s" menu_timeout_command"  getenv dup
904					-1 = if
905						drop \ clean-up
906					else
907						evaluate
908					then
909				then
910
911			else ( -- N )
912				\ No [detectable] time has elapsed (in seconds)
913				drop ( N -- )
914			then
915			( -- )
916		then
917
918		key? if \ Was a key pressed? (see loader(8))
919
920			\ An actual key was pressed (if the timeout is running,
921			\ kill it regardless of which key was pressed)
922			menu_timeout @ 0<> if
923				0 menu_timeout !
924				0 menu_timeout_enabled !
925
926				\ clear screen of timeout message
927				0 menu-timeout-update
928			then
929
930			\ get the key that was pressed and exit (if we
931			\ get a non-zero ASCII code)
932			key dup 0<> if
933				exit
934			else
935				drop
936			then
937		then
938		50 ms \ sleep for 50 milliseconds (see loader(8))
939
940	again
941;
942
943: menu-erase ( -- ) \ Erases menu and resets positioning variable to positon 1.
944
945	\ Clear the screen area associated with the interactive menu
946	menuX @ menuY @
947	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
948	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
949	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
950	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
951	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces 1+
952	2dup at-xy 38 spaces 1+		2dup at-xy 38 spaces
953	2drop
954
955	\ Reset the starting index and position for the menu
956	menu_start 1- menuidx !
957	0 menurow !
958;
959
960\ Erase and redraw the menu. Useful if you change a caption and want to
961\ update the menu to reflect the new value.
962\ 
963: menu-redraw ( -- )
964	menu-erase
965	menu-create
966;
967
968\ This function initializes the menu. Call this from your `loader.rc' file
969\ before calling any other menu-related functions.
970\ 
971: menu-init ( -- )
972	menu_start
973	1- menuidx !    \ Initialize the starting index for the menu
974	0 menurow !     \ Initialize the starting position for the menu
975
976	\ Assign configuration values
977	s" loader_menu_y" getenv dup -1 = if
978		drop \ no custom row position
979		menu_default_y
980	else
981		\ make sure custom position is a number
982		?number 0= if
983			menu_default_y \ or use default
984		then
985	then
986	menuY !
987	s" loader_menu_x" getenv dup -1 = if
988		drop \ no custom column position
989		menu_default_x
990	else
991		\ make sure custom position is a number
992		?number 0= if
993			menu_default_x \ or use default
994		then
995	then
996	menuX !
997
998	\ Interpret a custom frame type for the menu
999	TRUE ( draw a box? default yes, but might be altered below )
1000	s" loader_menu_frame" getenv dup -1 = if ( 1 )
1001		drop \ no custom frame type
1002	else ( 1 )  2dup s" single" compare-insensitive 0= if ( 2 )
1003		f_single ( see frames.4th )
1004	else ( 2 )  2dup s" double" compare-insensitive 0= if ( 3 )
1005		f_double ( see frames.4th )
1006	else ( 3 ) s" none" compare-insensitive 0= if ( 4 )
1007		drop FALSE \ don't draw a box
1008	( 4 ) then ( 3 ) then ( 2 )  then ( 1 ) then
1009	if
1010		42 13 menuX @ 3 - menuY @ 1- box \ Draw frame (w,h,x,y)
1011	then
1012
1013	0 25 at-xy \ Move cursor to the bottom for output
1014;
1015
1016\ Main function. Call this from your `loader.rc' file.
1017\ 
1018: menu-display ( -- )
1019
1020	0 menu_timeout_enabled ! \ start with automatic timeout disabled
1021
1022	\ check indication that automatic execution after delay is requested
1023	s" menu_timeout_command" getenv -1 <> if ( Addr C -1 -- | Addr )
1024		drop ( just testing existence right now: Addr -- )
1025
1026		\ initialize state variables
1027		seconds menu_time ! ( store the time we started )
1028		1 menu_timeout_enabled ! ( enable automatic timeout )
1029
1030		\ read custom time-duration (if set)
1031		s" autoboot_delay" getenv dup -1 = if
1032			drop \ no custom duration (remove dup'd bunk -1)
1033			menu_timeout_default \ use default setting
1034		else
1035			2dup ?number 0= if ( if not a number )
1036				\ disable timeout if "NO", else use default
1037				s" NO" compare-insensitive 0= if
1038					0 menu_timeout_enabled !
1039					0 ( assigned to menu_timeout below )
1040				else
1041					menu_timeout_default
1042				then
1043			else
1044				-rot 2drop
1045
1046				\ boot immediately if less than zero
1047				dup 0< if
1048					drop
1049					menu-create
1050					0 25 at-xy
1051					0 boot
1052				then
1053			then
1054		then
1055		menu_timeout ! ( store value on stack from above )
1056
1057		menu_timeout_enabled @ 1 = if
1058			\ read custom column position (if set)
1059			s" loader_menu_timeout_x" getenv dup -1 = if
1060				drop \ no custom column position
1061				menu_timeout_default_x \ use default setting
1062			else
1063				\ make sure custom position is a number
1064				?number 0= if
1065					menu_timeout_default_x \ or use default
1066				then
1067			then
1068			menu_timeout_x ! ( store value on stack from above )
1069        
1070			\ read custom row position (if set)
1071			s" loader_menu_timeout_y" getenv dup -1 = if
1072				drop \ no custom row position
1073				menu_timeout_default_y \ use default setting
1074			else
1075				\ make sure custom position is a number
1076				?number 0= if
1077					menu_timeout_default_y \ or use default
1078				then
1079			then
1080			menu_timeout_y ! ( store value on stack from above )
1081		then
1082	then
1083
1084	menu-create
1085
1086	begin \ Loop forever
1087
1088		0 25 at-xy \ Move cursor to the bottom for output
1089		getkey     \ Block here, waiting for a key to be pressed
1090
1091		dup -1 = if
1092			drop exit \ Caught abort (abnormal return)
1093		then
1094
1095		\ Boot if the user pressed Enter/Ctrl-M (13) or
1096		\ Ctrl-Enter/Ctrl-J (10)
1097		dup over 13 = swap 10 = or if
1098			drop ( no longer needed )
1099			s" boot" evaluate
1100			exit ( pedantic; never reached )
1101		then
1102
1103		dup menureboot @ = if 0 reboot then
1104
1105		\ Evaluate the decimal ASCII value against known menu item
1106		\ key associations and act accordingly
1107
1108		49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
1109		begin
1110			dup menukeyN @
1111			rot tuck = if
1112
1113				\ Adjust for missing ACPI menuitem on non-i386
1114				arch-i386? true <> menuacpi @ 0<> and if
1115					menuacpi @ over 2dup < -rot = or
1116					over 58 < and if
1117					( key >= menuacpi && key < 58: N -- N )
1118						1+
1119					then
1120				then
1121
1122				\ Test for the environment variable
1123				dup menu_command[x]
1124				getenv dup -1 <> if
1125					\ Execute the stored procedure
1126					evaluate
1127
1128					\ We expect there to be a non-zero
1129					\  value left on the stack after
1130					\ executing the stored procedure.
1131					\ If so, continue to run, else exit.
1132
1133					0= if
1134						drop \ key pressed
1135						drop \ loop iterator
1136						exit
1137					else
1138						swap \ need iterator on top
1139					then
1140				then
1141
1142				\ Re-adjust for missing ACPI menuitem
1143				arch-i386? true <> menuacpi @ 0<> and if
1144					swap
1145					menuacpi @ 1+ over 2dup < -rot = or
1146					over 59 < and if
1147						1-
1148					then
1149					swap
1150				then
1151			else
1152				swap \ need iterator on top
1153			then
1154
1155			\ 
1156			\ Check for menu keycode shortcut(s)
1157			\ 
1158			dup menu_keycode[x]
1159			getenv dup -1 = if
1160				drop
1161			else
1162				?number 0<> if
1163					rot tuck = if
1164						swap
1165						dup menu_command[x]
1166						getenv dup -1 <> if
1167							evaluate
1168							0= if
1169								2drop
1170								exit
1171							then
1172						else
1173							drop
1174						then
1175					else
1176						swap
1177					then
1178				then
1179			then
1180
1181			1+ dup 56 > \ increment iterator
1182			            \ continue if less than 57
1183		until
1184		drop \ loop iterator
1185		drop \ key pressed
1186
1187	again	\ Non-operational key was pressed; repeat
1188;
1189
1190\ This function unsets all the possible environment variables associated with
1191\ creating the interactive menu.
1192\ 
1193: menu-unset ( -- )
1194
1195	49 \ Iterator start (loop range 49 to 56; ASCII '1' to '8')
1196	begin
1197		dup menu_init[x]    unsetenv	\ menu initializer
1198		dup menu_command[x] unsetenv	\ menu command
1199		dup menu_caption[x] unsetenv	\ menu caption
1200		dup ansi_caption[x] unsetenv	\ ANSI caption
1201		dup menu_keycode[x] unsetenv	\ menu keycode
1202		dup toggled_text[x] unsetenv	\ toggle_menuitem caption
1203		dup toggled_ansi[x] unsetenv	\ toggle_menuitem ANSI caption
1204
1205		48 \ Iterator start (inner range 48 to 57; ASCII '0' to '9')
1206		begin
1207			\ cycle_menuitem caption and ANSI caption
1208			2dup menu_caption[x][y] unsetenv
1209			2dup ansi_caption[x][y] unsetenv
1210			1+ dup 57 >
1211		until
1212		drop \ inner iterator
1213
1214		0 over menukeyN      !	\ used by menu-create, menu-display
1215		0 over init_stateN   !	\ used by menu-create
1216		0 over toggle_stateN !	\ used by toggle_menuitem
1217		0 over init_textN   c!	\ used by toggle_menuitem
1218		0 over cycle_stateN  !	\ used by cycle_menuitem
1219
1220		1+ dup 56 >	\ increment, continue if less than 57
1221	until
1222	drop \ iterator
1223
1224	s" menu_timeout_command" unsetenv	\ menu timeout command
1225	s" menu_reboot"          unsetenv	\ Reboot menu option flag
1226	s" menu_acpi"            unsetenv	\ ACPI menu option flag
1227	s" menu_kernel"          unsetenv	\ Kernel menu option flag
1228	s" menu_options"         unsetenv	\ Options separator flag
1229	s" menu_optionstext"     unsetenv	\ separator display text
1230	s" menu_init"            unsetenv	\ menu initializer
1231
1232	0 menureboot !
1233	0 menuacpi !
1234	0 menuoptions !
1235;
1236
1237\ This function both unsets menu variables and visually erases the menu area
1238\ in-preparation for another menu.
1239\ 
1240: menu-clear ( -- )
1241	menu-unset
1242	menu-erase
1243;
1244
1245bullet menubllt !
1246
1247\ Initialize our menu initialization state variables
12480 init_state1 !
12490 init_state2 !
12500 init_state3 !
12510 init_state4 !
12520 init_state5 !
12530 init_state6 !
12540 init_state7 !
12550 init_state8 !
1256
1257\ Initialize our boolean state variables
12580 toggle_state1 !
12590 toggle_state2 !
12600 toggle_state3 !
12610 toggle_state4 !
12620 toggle_state5 !
12630 toggle_state6 !
12640 toggle_state7 !
12650 toggle_state8 !
1266
1267\ Initialize our array state variables
12680 cycle_state1 !
12690 cycle_state2 !
12700 cycle_state3 !
12710 cycle_state4 !
12720 cycle_state5 !
12730 cycle_state6 !
12740 cycle_state7 !
12750 cycle_state8 !
1276
1277\ Initialize string containers
12780 init_text1 c!
12790 init_text2 c!
12800 init_text3 c!
12810 init_text4 c!
12820 init_text5 c!
12830 init_text6 c!
12840 init_text7 c!
12850 init_text8 c!
1286