1-- 2-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org> 3-- All rights reserved. 4-- 5-- Redistribution and use in source and binary forms, with or without 6-- modification, are permitted provided that the following conditions 7-- are met: 8-- 1. Redistributions of source code must retain the above copyright 9-- notice, this list of conditions and the following disclaimer. 10-- 2. Redistributions in binary form must reproduce the above copyright 11-- notice, this list of conditions and the following disclaimer in the 12-- documentation and/or other materials provided with the distribution. 13-- 14-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24-- SUCH DAMAGE. 25-- 26-- $FreeBSD$ 27-- 28 29 30local menu = {}; 31 32local core = require("core"); 33local color = require("color"); 34local config = require("config"); 35local screen = require("screen"); 36local drawer = require("drawer"); 37 38local OnOff; 39local skip; 40local run; 41local autoboot; 42local carousel_choices = {}; 43 44--loader menu tree: 45--rooted at menu.welcome 46--submenu declarations: 47local boot_options; 48local welcome; 49 50menu.boot_options = { 51 -- return to welcome menu 52 { 53 entry_type = core.MENU_RETURN, 54 name = function() 55 return "Back to main menu"..color.highlight(" [Backspace]"); 56 end 57 }, 58 59 -- load defaults 60 { 61 entry_type = core.MENU_ENTRY, 62 name = function() 63 return "Load System "..color.highlight("D").."efaults"; 64 end, 65 func = function() 66 core.setDefaults(); 67 end, 68 alias = {"d", "D"} 69 }, 70 71 { 72 entry_type = core.MENU_SEPARATOR, 73 name = function() 74 return ""; 75 end 76 }, 77 78 { 79 entry_type = core.MENU_SEPARATOR, 80 name = function() 81 return "Boot Options:"; 82 end 83 }, 84 85 -- acpi 86 { 87 entry_type = core.MENU_ENTRY, 88 name = function() 89 return OnOff(color.highlight("A").."CPI :", core.acpi); 90 end, 91 func = function() 92 core.setACPI(); 93 end, 94 alias = {"a", "A"} 95 }, 96 -- safe mode 97 { 98 entry_type = core.MENU_ENTRY, 99 name = function() 100 return OnOff("Safe "..color.highlight("M").."ode :", core.sm); 101 end, 102 func = function() 103 core.setSafeMode(); 104 end, 105 alias = {"m", "M"} 106 }, 107 -- single user 108 { 109 entry_type = core.MENU_ENTRY, 110 name = function() 111 return OnOff(color.highlight("S").."ingle user:", core.su); 112 end, 113 func = function() 114 core.setSingleUser(); 115 end, 116 alias = {"s", "S"} 117 }, 118 -- verbose boot 119 { 120 entry_type = core.MENU_ENTRY, 121 name = function() 122 return OnOff(color.highlight("V").."erbose :", core.verbose); 123 end, 124 func = function() 125 core.setVerbose(); 126 end, 127 alias = {"v", "V"} 128 }, 129}; 130 131menu.welcome = { 132 -- boot multi user 133 { 134 entry_type = core.MENU_ENTRY, 135 name = function() 136 return color.highlight("B").."oot Multi user "..color.highlight("[Enter]"); 137 end, 138 func = function() 139 core.setSingleUser(false); 140 core.boot(); 141 end, 142 alias = {"b", "B"} 143 }, 144 145 -- boot single user 146 { 147 entry_type = core.MENU_ENTRY, 148 name = function() 149 return "Boot "..color.highlight("S").."ingle user"; 150 end, 151 func = function() 152 core.setSingleUser(true); 153 core.boot(); 154 end, 155 alias = {"s", "S"} 156 }, 157 158 -- escape to interpreter 159 { 160 entry_type = core.MENU_RETURN, 161 name = function() 162 return color.highlight("Esc").."ape to loader prompt"; 163 end, 164 func = function() 165 loader.setenv("autoboot_delay", "NO"); 166 end, 167 alias = {core.KEYSTR_ESCAPE} 168 }, 169 170 -- reboot 171 { 172 entry_type = core.MENU_ENTRY, 173 name = function() 174 return color.highlight("R").."eboot"; 175 end, 176 func = function() 177 loader.perform("reboot"); 178 end, 179 alias = {"r", "R"} 180 }, 181 182 183 { 184 entry_type = core.MENU_SEPARATOR, 185 name = function() 186 return ""; 187 end 188 }, 189 190 { 191 entry_type = core.MENU_SEPARATOR, 192 name = function() 193 return "Options:"; 194 end 195 }, 196 197 -- kernel options 198 { 199 entry_type = core.MENU_CAROUSEL_ENTRY, 200 carousel_id = "kernel", 201 items = core.kernelList, 202 name = function(idx, choice, all_choices) 203 if (#all_choices == 0) then 204 return "Kernel: "; 205 end 206 207 local is_default = (idx == 1); 208 local kernel_name = ""; 209 local name_color; 210 if (is_default) then 211 name_color = color.escapef(color.GREEN); 212 kernel_name = "default/"; 213 else 214 name_color = color.escapef(color.BLUE); 215 end 216 kernel_name = kernel_name .. name_color .. choice .. 217 color.default(); 218 return color.highlight("K").."ernel: " .. kernel_name .. 219 " (" .. idx .. 220 " of " .. #all_choices .. ")"; 221 end, 222 func = function(idx, choice, all_choices) 223 if (#all_choices > 1) then 224 config.reload(choice); 225 end 226 end, 227 alias = {"k", "K"} 228 }, 229 230 -- boot options 231 { 232 entry_type = core.MENU_SUBMENU, 233 name = function() 234 return "Boot "..color.highlight("O").."ptions"; 235 end, 236 submenu = function() 237 return menu.boot_options; 238 end, 239 alias = {"o", "O"} 240 } 241 242}; 243 244-- The first item in every carousel is always the default item. 245function menu.getCarouselIndex(id) 246 local val = carousel_choices[id]; 247 if (val == nil) then 248 return 1; 249 end 250 return val; 251end 252 253function menu.setCarouselIndex(id, idx) 254 carousel_choices[id] = idx; 255end 256 257function menu.run(m) 258 259 if (menu.skip()) then 260 core.autoboot(); 261 return false; 262 end 263 264 if (m == nil) then 265 m = menu.welcome; 266 end 267 268 -- redraw screen 269 screen.clear(); 270 screen.defcursor(); 271 local alias_table = drawer.drawscreen(m); 272 273 menu.autoboot(); 274 275 cont = true; 276 while (cont) do 277 local key = io.getchar(); 278 279 -- Special key behaviors 280 if ((key == core.KEY_BACKSPACE) or (key == core.KEY_DELETE)) and 281 (m ~= menu.welcome) then 282 break 283 elseif (key == core.KEY_ENTER) then 284 core.boot(); 285 -- Should not return 286 end 287 288 key = string.char(key) 289 -- check to see if key is an alias 290 local sel_entry = nil; 291 for k, v in pairs(alias_table) do 292 if (key == k) then 293 sel_entry = v; 294 end 295 end 296 297 -- if we have an alias do the assigned action: 298 if (sel_entry ~= nil) then 299 if (sel_entry.entry_type == core.MENU_ENTRY) then 300 -- run function 301 sel_entry.func(); 302 elseif (sel_entry.entry_type == core.MENU_CAROUSEL_ENTRY) then 303 -- carousel (rotating) functionality 304 local carid = sel_entry.carousel_id; 305 local caridx = menu.getCarouselIndex(carid); 306 local choices = sel_entry.items(); 307 308 if (#choices > 0) then 309 caridx = (caridx % #choices) + 1; 310 menu.setCarouselIndex(carid, caridx); 311 sel_entry.func(caridx, choices[caridx], 312 choices); 313 end 314 elseif (sel_entry.entry_type == core.MENU_SUBMENU) then 315 -- recurse 316 cont = menu.run(sel_entry.submenu()); 317 elseif (sel_entry.entry_type == core.MENU_RETURN) then 318 -- allow entry to have a function/side effect 319 if (sel_entry.func ~= nil) then 320 sel_entry.func(); 321 end 322 -- break recurse 323 cont = false; 324 end 325 -- if we got an alias key the screen is out of date: 326 screen.clear(); 327 screen.defcursor(); 328 alias_table = drawer.drawscreen(m); 329 end 330 end 331 332 if (m == menu.welcome) then 333 screen.defcursor(); 334 print("Exiting menu!"); 335 return false; 336 end 337 338 return true; 339end 340 341function menu.skip() 342 if (core.bootserial() )then 343 return true; 344 end 345 local c = string.lower(loader.getenv("console") or ""); 346 if ((c:match("^efi[ ;]") or c:match("[ ;]efi[ ;]")) ~= nil) then 347 return true; 348 end 349 350 c = string.lower(loader.getenv("beastie_disable") or ""); 351 print("beastie_disable", c); 352 return c == "yes"; 353end 354 355function menu.autoboot() 356 if (menu.already_autoboot == true) then 357 return; 358 end 359 menu.already_autoboot = true; 360 361 local ab = loader.getenv("autoboot_delay"); 362 if (ab ~= nil) and (ab:lower() == "no") then 363 return; 364 elseif (tonumber(ab) == -1) then 365 core.boot(); 366 end 367 ab = tonumber(ab) or 10; 368 369 local x = loader.getenv("loader_menu_timeout_x") or 5; 370 local y = loader.getenv("loader_menu_timeout_y") or 22; 371 372 local endtime = loader.time() + ab; 373 local time; 374 375 repeat 376 time = endtime - loader.time(); 377 screen.setcursor(x, y); 378 print("Autoboot in "..time.." seconds, hit [Enter] to boot" 379 .." or any other key to stop "); 380 screen.defcursor(); 381 if (io.ischar()) then 382 local ch = io.getchar(); 383 if (ch == core.KEY_ENTER) then 384 break; 385 else 386 -- erase autoboot msg 387 screen.setcursor(0, y); 388 print(" " 389 .." "); 390 screen.defcursor(); 391 return; 392 end 393 end 394 395 loader.delay(50000); 396 until time <= 0 397 core.boot(); 398 399end 400 401function OnOff(str, b) 402 if (b) then 403 return str .. color.escapef(color.GREEN).."On"..color.escapef(color.WHITE); 404 else 405 return str .. color.escapef(color.RED).."off"..color.escapef(color.WHITE); 406 end 407end 408 409return menu; 410