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