1-- 2-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org> 3-- Copyright (C) 2018 Kyle Evans <kevans@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-- 29 30 31local core = require("core"); 32local color = require("color"); 33local config = require("config"); 34local screen = require("screen"); 35local drawer = require("drawer"); 36 37local menu = {}; 38 39local skip; 40local run; 41local autoboot; 42local carousel_choices = {}; 43 44local OnOff = function(str, b) 45 if (b) then 46 return str .. color.escapef(color.GREEN) .. "On" .. 47 color.escapef(color.WHITE); 48 else 49 return str .. color.escapef(color.RED) .. "off" .. 50 color.escapef(color.WHITE); 51 end 52end 53 54 55menu.handlers = { 56 -- Menu handlers take the current menu and selected entry as parameters, 57 -- and should return a boolean indicating whether execution should 58 -- continue or not. The return value may be omitted if this entry should 59 -- have no bearing on whether we continue or not, indicating that we 60 -- should just continue after execution. 61 [core.MENU_ENTRY] = function(current_menu, entry) 62 -- run function 63 entry.func(); 64 end, 65 [core.MENU_CAROUSEL_ENTRY] = function(current_menu, entry) 66 -- carousel (rotating) functionality 67 local carid = entry.carousel_id; 68 local caridx = menu.getCarouselIndex(carid); 69 local choices = entry.items(); 70 71 if (#choices > 0) then 72 caridx = (caridx % #choices) + 1; 73 menu.setCarouselIndex(carid, caridx); 74 entry.func(caridx, choices[caridx], choices); 75 end 76 end, 77 [core.MENU_SUBMENU] = function(current_menu, entry) 78 -- recurse 79 return menu.run(entry.submenu()); 80 end, 81 [core.MENU_RETURN] = function(current_menu, entry) 82 -- allow entry to have a function/side effect 83 if (entry.func ~= nil) then 84 entry.func(); 85 end 86 return false; 87 end, 88}; 89-- loader menu tree is rooted at menu.welcome 90 91menu.boot_options = { 92 entries = { 93 -- return to welcome menu 94 { 95 entry_type = core.MENU_RETURN, 96 name = function() 97 return "Back to main menu" .. 98 color.highlight(" [Backspace]"); 99 end 100 }, 101 102 -- load defaults 103 { 104 entry_type = core.MENU_ENTRY, 105 name = function() 106 return "Load System " .. color.highlight("D") .. 107 "efaults"; 108 end, 109 func = function() 110 core.setDefaults(); 111 end, 112 alias = {"d", "D"} 113 }, 114 115 { 116 entry_type = core.MENU_SEPARATOR, 117 name = function() 118 return ""; 119 end 120 }, 121 122 { 123 entry_type = core.MENU_SEPARATOR, 124 name = function() 125 return "Boot Options:"; 126 end 127 }, 128 129 -- acpi 130 { 131 entry_type = core.MENU_ENTRY, 132 name = function() 133 return OnOff(color.highlight("A") .. 134 "CPI :", core.acpi); 135 end, 136 func = function() 137 core.setACPI(); 138 end, 139 alias = {"a", "A"} 140 }, 141 -- safe mode 142 { 143 entry_type = core.MENU_ENTRY, 144 name = function() 145 return OnOff("Safe " .. color.highlight("M") .. 146 "ode :", core.sm); 147 end, 148 func = function() 149 core.setSafeMode(); 150 end, 151 alias = {"m", "M"} 152 }, 153 -- single user 154 { 155 entry_type = core.MENU_ENTRY, 156 name = function() 157 return OnOff(color.highlight("S") .. 158 "ingle user:", core.su); 159 end, 160 func = function() 161 core.setSingleUser(); 162 end, 163 alias = {"s", "S"} 164 }, 165 -- verbose boot 166 { 167 entry_type = core.MENU_ENTRY, 168 name = function() 169 return OnOff(color.highlight("V") .. 170 "erbose :", core.verbose); 171 end, 172 func = function() 173 core.setVerbose(); 174 end, 175 alias = {"v", "V"} 176 }, 177 }, 178}; 179 180menu.welcome = { 181 entries = function() 182 local menu_entries = menu.welcome.all_entries; 183 -- Swap the first two menu items on single user boot 184 if (core.isSingleUserBoot()) then 185 -- We'll cache the swapped menu, for performance 186 if (menu.welcome.swapped_menu ~= nil) then 187 return menu.welcome.swapped_menu; 188 end 189 -- Shallow copy the table 190 menu_entries = core.shallowCopyTable(menu_entries); 191 192 -- Swap the first two menu entries 193 menu_entries[1], menu_entries[2] = 194 menu_entries[2], menu_entries[1]; 195 196 -- Then set their names to their alternate names 197 menu_entries[1].name, menu_entries[2].name = 198 menu_entries[1].alternate_name, 199 menu_entries[2].alternate_name; 200 menu.welcome.swapped_menu = menu_entries; 201 end 202 return menu_entries; 203 end, 204 all_entries = { 205 -- boot multi user 206 { 207 entry_type = core.MENU_ENTRY, 208 name = function() 209 return color.highlight("B") .. 210 "oot Multi user " .. 211 color.highlight("[Enter]"); 212 end, 213 -- Not a standard menu entry function! 214 alternate_name = function() 215 return color.highlight("B") .. 216 "oot Multi user"; 217 end, 218 func = function() 219 core.setSingleUser(false); 220 core.boot(); 221 end, 222 alias = {"b", "B"} 223 }, 224 225 -- boot single user 226 { 227 entry_type = core.MENU_ENTRY, 228 name = function() 229 return "Boot " .. color.highlight("S") .. 230 "ingle user"; 231 end, 232 -- Not a standard menu entry function! 233 alternate_name = function() 234 return "Boot " .. color.highlight("S") .. 235 "ingle user " .. color.highlight("[Enter]"); 236 end, 237 func = function() 238 core.setSingleUser(true); 239 core.boot(); 240 end, 241 alias = {"s", "S"} 242 }, 243 244 -- escape to interpreter 245 { 246 entry_type = core.MENU_RETURN, 247 name = function() 248 return color.highlight("Esc") .. 249 "ape to loader prompt"; 250 end, 251 func = function() 252 loader.setenv("autoboot_delay", "NO"); 253 end, 254 alias = {core.KEYSTR_ESCAPE} 255 }, 256 257 -- reboot 258 { 259 entry_type = core.MENU_ENTRY, 260 name = function() 261 return color.highlight("R") .. "eboot"; 262 end, 263 func = function() 264 loader.perform("reboot"); 265 end, 266 alias = {"r", "R"} 267 }, 268 269 270 { 271 entry_type = core.MENU_SEPARATOR, 272 name = function() 273 return ""; 274 end 275 }, 276 277 { 278 entry_type = core.MENU_SEPARATOR, 279 name = function() 280 return "Options:"; 281 end 282 }, 283 284 -- kernel options 285 { 286 entry_type = core.MENU_CAROUSEL_ENTRY, 287 carousel_id = "kernel", 288 items = core.kernelList, 289 name = function(idx, choice, all_choices) 290 if (#all_choices == 0) then 291 return "Kernel: "; 292 end 293 294 local is_default = (idx == 1); 295 local kernel_name = ""; 296 local name_color; 297 if (is_default) then 298 name_color = color.escapef(color.GREEN); 299 kernel_name = "default/"; 300 else 301 name_color = color.escapef(color.BLUE); 302 end 303 kernel_name = kernel_name .. name_color .. 304 choice .. color.default(); 305 return color.highlight("K") .. "ernel: " .. 306 kernel_name .. " (" .. idx .. " of " .. 307 #all_choices .. ")"; 308 end, 309 func = function(idx, choice, all_choices) 310 config.selectkernel(choice); 311 end, 312 alias = {"k", "K"} 313 }, 314 315 -- boot options 316 { 317 entry_type = core.MENU_SUBMENU, 318 name = function() 319 return "Boot " .. color.highlight("O") .. 320 "ptions"; 321 end, 322 submenu = function() 323 return menu.boot_options; 324 end, 325 alias = {"o", "O"} 326 }, 327 }, 328}; 329 330-- The first item in every carousel is always the default item. 331function menu.getCarouselIndex(id) 332 local val = carousel_choices[id]; 333 if (val == nil) then 334 return 1; 335 end 336 return val; 337end 338 339function menu.setCarouselIndex(id, idx) 340 carousel_choices[id] = idx; 341end 342 343function menu.run(m) 344 345 if (menu.skip()) then 346 core.autoboot(); 347 return false; 348 end 349 350 if (m == nil) then 351 m = menu.welcome; 352 end 353 354 -- redraw screen 355 screen.clear(); 356 screen.defcursor(); 357 local alias_table = drawer.drawscreen(m); 358 359 menu.autoboot(); 360 361 cont = true; 362 while (cont) do 363 local key = io.getchar(); 364 365 -- Special key behaviors 366 if ((key == core.KEY_BACKSPACE) or (key == core.KEY_DELETE)) and 367 (m ~= menu.welcome) then 368 break; 369 elseif (key == core.KEY_ENTER) then 370 core.boot(); 371 -- Should not return 372 end 373 374 key = string.char(key) 375 -- check to see if key is an alias 376 local sel_entry = nil; 377 for k, v in pairs(alias_table) do 378 if (key == k) then 379 sel_entry = v; 380 end 381 end 382 383 -- if we have an alias do the assigned action: 384 if (sel_entry ~= nil) then 385 -- Get menu handler 386 local handler = menu.handlers[sel_entry.entry_type]; 387 if (handler ~= nil) then 388 -- The handler's return value indicates whether 389 -- we need to exit this menu. An omitted return 390 -- value means "continue" by default. 391 cont = handler(m, sel_entry); 392 if (cont == nil) then 393 cont = true; 394 end 395 end 396 -- if we got an alias key the screen is out of date: 397 screen.clear(); 398 screen.defcursor(); 399 alias_table = drawer.drawscreen(m); 400 end 401 end 402 403 if (m == menu.welcome) then 404 screen.defcursor(); 405 print("Exiting menu!"); 406 config.loadelf(); 407 return false; 408 end 409 410 return true; 411end 412 413function menu.skip() 414 if (core.isSerialBoot()) then 415 return true; 416 end 417 local c = string.lower(loader.getenv("console") or ""); 418 if ((c:match("^efi[ ;]") or c:match("[ ;]efi[ ;]")) ~= nil) then 419 return true; 420 end 421 422 c = string.lower(loader.getenv("beastie_disable") or ""); 423 print("beastie_disable", c); 424 return c == "yes"; 425end 426 427function menu.autoboot() 428 if (menu.already_autoboot == true) then 429 return; 430 end 431 menu.already_autoboot = true; 432 433 local ab = loader.getenv("autoboot_delay"); 434 if (ab ~= nil) and (ab:lower() == "no") then 435 return; 436 elseif (tonumber(ab) == -1) then 437 core.boot(); 438 end 439 ab = tonumber(ab) or 10; 440 441 local x = loader.getenv("loader_menu_timeout_x") or 5; 442 local y = loader.getenv("loader_menu_timeout_y") or 22; 443 444 local endtime = loader.time() + ab; 445 local time; 446 447 repeat 448 time = endtime - loader.time(); 449 screen.setcursor(x, y); 450 print("Autoboot in " .. time .. 451 " seconds, hit [Enter] to boot" .. 452 " or any other key to stop "); 453 screen.defcursor(); 454 if (io.ischar()) then 455 local ch = io.getchar(); 456 if (ch == core.KEY_ENTER) then 457 break; 458 else 459 -- erase autoboot msg 460 screen.setcursor(0, y); 461 print(" " 462 .. " "); 463 screen.defcursor(); 464 return; 465 end 466 end 467 468 loader.delay(50000); 469 until time <= 0; 470 core.boot(); 471 472end 473 474return menu; 475