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