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