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