1-- 2-- Copyright (c) 2015 Pedro Souza <[email protected]> 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 alias = {core.KEYSTR_ESCAPE} 165 }, 166 167 -- reboot 168 { 169 entry_type = core.MENU_ENTRY, 170 name = function() 171 return color.highlight("R").."eboot"; 172 end, 173 func = function() 174 loader.perform("reboot"); 175 end, 176 alias = {"r", "R"} 177 }, 178 179 180 { 181 entry_type = core.MENU_SEPARATOR, 182 name = function() 183 return ""; 184 end 185 }, 186 187 { 188 entry_type = core.MENU_SEPARATOR, 189 name = function() 190 return "Options:"; 191 end 192 }, 193 194 -- kernel options 195 { 196 entry_type = core.MENU_CAROUSEL_ENTRY, 197 carousel_id = "kernel", 198 items = core.kernelList, 199 name = function(idx, choice, all_choices) 200 if #all_choices == 0 then 201 return "Kernel: "; 202 end 203 204 local kernel_name = color.escapef(color.GREEN) .. 205 choice .. color.default(); 206 if (idx == 1) then 207 kernel_name = "default/" .. kernel_name; 208 end 209 return color.highlight("K").."ernel: " .. kernel_name .. 210 " (" .. idx .. 211 " of " .. #all_choices .. ")"; 212 end, 213 func = function(choice) 214 config.reload(choice); 215 end, 216 alias = {"k", "K"} 217 }, 218 219 -- boot options 220 { 221 entry_type = core.MENU_SUBMENU, 222 name = function() 223 return "Boot "..color.highlight("O").."ptions"; 224 end, 225 submenu = function() 226 return menu.boot_options; 227 end, 228 alias = {"o", "O"} 229 } 230 231}; 232 233-- The first item in every carousel is always the default item. 234function menu.getCarouselIndex(id) 235 local val = carousel_choices[id]; 236 if (val == nil) then 237 return 1; 238 end 239 return val; 240end 241 242function menu.setCarouselIndex(id, idx) 243 carousel_choices[id] = idx; 244end 245 246function menu.run(m) 247 248 if (menu.skip()) then 249 core.autoboot(); 250 return false; 251 end 252 253 if (m == nil) then 254 m = menu.welcome; 255 end 256 257 -- redraw screen 258 screen.clear(); 259 screen.defcursor(); 260 local alias_table = drawer.drawscreen(m); 261 262-- menu.autoboot(); 263 264 cont = true; 265 while cont do 266 local key = io.getchar(); 267 268 -- Special key behaviors 269 if ((key == core.KEY_BACKSPACE) or (key == core.KEY_DELETE)) and 270 (m ~= menu.welcome) then 271 break 272 elseif (key == core.KEY_ENTER) then 273 core.boot(); 274 -- Should not return 275 end 276 277 key = string.char(key) 278 -- check to see if key is an alias 279 local sel_entry = nil; 280 for k, v in pairs(alias_table) do 281 if (key == k) then 282 sel_entry = v; 283 end 284 end 285 286 -- if we have an alias do the assigned action: 287 if(sel_entry ~= nil) then 288 if (sel_entry.entry_type == core.MENU_ENTRY) then 289 -- run function 290 sel_entry.func(); 291 elseif (sel_entry.entry_type == core.MENU_CAROUSEL_ENTRY) then 292 -- carousel (rotating) functionality 293 local carid = sel_entry.carousel_id; 294 local caridx = menu.getCarouselIndex(carid); 295 local choices = sel_entry.items(); 296 297 caridx = (caridx % #choices) + 1; 298 menu.setCarouselIndex(carid, caridx); 299 sel_entry.func(choices[caridx]); 300 elseif (sel_entry.entry_type == core.MENU_SUBMENU) then 301 -- recurse 302 cont = menu.run(sel_entry.submenu()); 303 elseif (sel_entry.entry_type == core.MENU_RETURN) then 304 -- break recurse 305 cont = false; 306 end 307 -- if we got an alias key the screen is out of date: 308 screen.clear(); 309 screen.defcursor(); 310 alias_table = drawer.drawscreen(m); 311 end 312 end 313 314 if (m == menu.welcome) then 315 screen.defcursor(); 316 print("Exiting menu!"); 317 return false; 318 end 319 320 return true; 321end 322 323function menu.skip() 324 if core.bootserial() then 325 return true; 326 end 327 local c = string.lower(loader.getenv("console") or ""); 328 if (c:match("^efi[ ;]") or c:match("[ ;]efi[ ;]")) ~= nil then 329 return true; 330 end 331 332 c = string.lower(loader.getenv("beastie_disable") or ""); 333 print("beastie_disable", c); 334 return c == "yes"; 335end 336 337function menu.autoboot() 338 if menu.already_autoboot == true then 339 return; 340 end 341 menu.already_autoboot = true; 342 343 local ab = loader.getenv("autoboot_delay"); 344 if ab == "NO" or ab == "no" then 345 core.boot(); 346 end 347 ab = tonumber(ab) or 10; 348 349 local x = loader.getenv("loader_menu_timeout_x") or 5; 350 local y = loader.getenv("loader_menu_timeout_y") or 22; 351 352 local endtime = loader.time() + ab; 353 local time; 354 355 repeat 356 time = endtime - loader.time(); 357 screen.setcursor(x, y); 358 print("Autoboot in "..time.." seconds, hit [Enter] to boot" 359 .." or any other key to stop "); 360 screen.defcursor(); 361 if io.ischar() then 362 local ch = io.getchar(); 363 if ch == core.KEY_ENTER then 364 break; 365 else 366 -- prevent autoboot when escaping to interpreter 367 loader.setenv("autoboot_delay", "NO"); 368 -- erase autoboot msg 369 screen.setcursor(0, y); 370 print(" " 371 .." "); 372 screen.defcursor(); 373 return; 374 end 375 end 376 377 loader.delay(50000); 378 until time <= 0 379 core.boot(); 380 381end 382 383function OnOff(str, b) 384 if (b) then 385 return str .. color.escapef(color.GREEN).."On"..color.escapef(color.WHITE); 386 else 387 return str .. color.escapef(color.RED).."off"..color.escapef(color.WHITE); 388 end 389end 390 391return menu 392