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 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 visible = core.isSystem386, 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 330function menu.run(m) 331 332 if menu.skip() then 333 core.autoboot() 334 return false 335 end 336 337 if m == nil then 338 m = menu.welcome 339 end 340 341 -- redraw screen 342 screen.clear() 343 screen.defcursor() 344 local alias_table = drawer.drawscreen(m) 345 346 menu.autoboot() 347 348 cont = true 349 while cont do 350 local key = io.getchar() 351 352 -- Special key behaviors 353 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and 354 m ~= menu.welcome then 355 break 356 elseif key == core.KEY_ENTER then 357 core.boot() 358 -- Should not return 359 end 360 361 key = string.char(key) 362 -- check to see if key is an alias 363 local sel_entry = nil 364 for k, v in pairs(alias_table) do 365 if key == k then 366 sel_entry = v 367 end 368 end 369 370 -- if we have an alias do the assigned action: 371 if sel_entry ~= nil then 372 -- Get menu handler 373 local handler = menu.handlers[sel_entry.entry_type] 374 if handler ~= nil then 375 -- The handler's return value indicates whether 376 -- we need to exit this menu. An omitted return 377 -- value means "continue" by default. 378 cont = handler(m, sel_entry) 379 if cont == nil then 380 cont = true 381 end 382 end 383 -- if we got an alias key the screen is out of date: 384 screen.clear() 385 screen.defcursor() 386 alias_table = drawer.drawscreen(m) 387 end 388 end 389 390 if m == menu.welcome then 391 screen.defcursor() 392 print("Exiting menu!") 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[ ;]") ~= nil 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