]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/lua/menu.lua
lualoader: Directly reference submenu definition with submenu key
[FreeBSD/FreeBSD.git] / stand / lua / menu.lua
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
31 local core = require("core")
32 local color = require("color")
33 local config = require("config")
34 local screen = require("screen")
35 local drawer = require("drawer")
36
37 local menu = {}
38
39 local skip
40 local run
41 local autoboot
42
43 local 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
51 end
52
53 -- Module exports
54 menu.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
90 menu.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                 },
117
118                 {
119                         entry_type = core.MENU_SEPARATOR,
120                         name = function()
121                                 return "Boot Options:"
122                         end
123                 },
124
125                 -- acpi
126                 {
127                         entry_type = core.MENU_ENTRY,
128                         visible = core.isSystem386,
129                         name = function()
130                                 return OnOff(color.highlight("A") ..
131                                     "CPI       :", core.acpi)
132                         end,
133                         func = function()
134                                 core.setACPI()
135                         end,
136                         alias = {"a", "A"}
137                 },
138                 -- safe mode
139                 {
140                         entry_type = core.MENU_ENTRY,
141                         name = function()
142                                 return OnOff("Safe " .. color.highlight("M") ..
143                                     "ode  :", core.sm)
144                         end,
145                         func = function()
146                                 core.setSafeMode()
147                         end,
148                         alias = {"m", "M"}
149                 },
150                 -- single user
151                 {
152                         entry_type = core.MENU_ENTRY,
153                         name = function()
154                                 return OnOff(color.highlight("S") ..
155                                     "ingle user:", core.su)
156                         end,
157                         func = function()
158                                 core.setSingleUser()
159                         end,
160                         alias = {"s", "S"}
161                 },
162                 -- verbose boot
163                 {
164                         entry_type = core.MENU_ENTRY,
165                         name = function()
166                                 return OnOff(color.highlight("V") ..
167                                     "erbose    :", core.verbose)
168                         end,
169                         func = function()
170                                 core.setVerbose()
171                         end,
172                         alias = {"v", "V"}
173                 },
174         },
175 }
176
177 menu.welcome = {
178         entries = function()
179                 local menu_entries = menu.welcome.all_entries
180                 -- Swap the first two menu items on single user boot
181                 if core.isSingleUserBoot() then
182                         -- We'll cache the swapped menu, for performance
183                         if menu.welcome.swapped_menu ~= nil then
184                                 return menu.welcome.swapped_menu
185                         end
186                         -- Shallow copy the table
187                         menu_entries = core.shallowCopyTable(menu_entries)
188
189                         -- Swap the first two menu entries
190                         menu_entries[1], menu_entries[2] =
191                             menu_entries[2], menu_entries[1]
192
193                         -- Then set their names to their alternate names
194                         menu_entries[1].name, menu_entries[2].name =
195                             menu_entries[1].alternate_name,
196                             menu_entries[2].alternate_name
197                         menu.welcome.swapped_menu = menu_entries
198                 end
199                 return menu_entries
200         end,
201         all_entries = {
202                 -- boot multi user
203                 {
204                         entry_type = core.MENU_ENTRY,
205                         name = function()
206                                 return color.highlight("B") ..
207                                     "oot Multi user " ..
208                                     color.highlight("[Enter]")
209                         end,
210                         -- Not a standard menu entry function!
211                         alternate_name = function()
212                                 return color.highlight("B") ..
213                                     "oot Multi user"
214                         end,
215                         func = function()
216                                 core.setSingleUser(false)
217                                 core.boot()
218                         end,
219                         alias = {"b", "B"}
220                 },
221
222                 -- boot single user
223                 {
224                         entry_type = core.MENU_ENTRY,
225                         name = function()
226                                 return "Boot " .. color.highlight("S") ..
227                                     "ingle user"
228                         end,
229                         -- Not a standard menu entry function!
230                         alternate_name = function()
231                                 return "Boot " .. color.highlight("S") ..
232                                     "ingle user " .. color.highlight("[Enter]")
233                         end,
234                         func = function()
235                                 core.setSingleUser(true)
236                                 core.boot()
237                         end,
238                         alias = {"s", "S"}
239                 },
240
241                 -- escape to interpreter
242                 {
243                         entry_type = core.MENU_RETURN,
244                         name = function()
245                                 return color.highlight("Esc") ..
246                                     "ape to loader prompt"
247                         end,
248                         func = function()
249                                 loader.setenv("autoboot_delay", "NO")
250                         end,
251                         alias = {core.KEYSTR_ESCAPE}
252                 },
253
254                 -- reboot
255                 {
256                         entry_type = core.MENU_ENTRY,
257                         name = function()
258                                 return color.highlight("R") .. "eboot"
259                         end,
260                         func = function()
261                                 loader.perform("reboot")
262                         end,
263                         alias = {"r", "R"}
264                 },
265
266
267                 {
268                         entry_type = core.MENU_SEPARATOR,
269                 },
270
271                 {
272                         entry_type = core.MENU_SEPARATOR,
273                         name = function()
274                                 return "Options:"
275                         end
276                 },
277
278                 -- kernel options
279                 {
280                         entry_type = core.MENU_CAROUSEL_ENTRY,
281                         carousel_id = "kernel",
282                         items = core.kernelList,
283                         name = function(idx, choice, all_choices)
284                                 if #all_choices == 0 then
285                                         return "Kernel: "
286                                 end
287
288                                 local is_default = (idx == 1)
289                                 local kernel_name = ""
290                                 local name_color
291                                 if is_default then
292                                         name_color = color.escapef(color.GREEN)
293                                         kernel_name = "default/"
294                                 else
295                                         name_color = color.escapef(color.BLUE)
296                                 end
297                                 kernel_name = kernel_name .. name_color ..
298                                     choice .. color.default()
299                                 return color.highlight("K") .. "ernel: " ..
300                                     kernel_name .. " (" .. idx .. " of " ..
301                                     #all_choices .. ")"
302                         end,
303                         func = function(idx, choice, all_choices)
304                                 config.selectkernel(choice)
305                         end,
306                         alias = {"k", "K"}
307                 },
308
309                 -- boot options
310                 {
311                         entry_type = core.MENU_SUBMENU,
312                         name = function()
313                                 return "Boot " .. color.highlight("O") ..
314                                     "ptions"
315                         end,
316                         submenu = menu.boot_options,
317                         alias = {"o", "O"}
318                 },
319         },
320 }
321
322 menu.default = menu.welcome
323
324 function menu.run(m)
325
326         if menu.skip() then
327                 core.autoboot()
328                 return false
329         end
330
331         if m == nil then
332                 m = menu.default
333         end
334
335         -- redraw screen
336         screen.clear()
337         screen.defcursor()
338         local alias_table = drawer.drawscreen(m)
339
340         menu.autoboot()
341
342         cont = true
343         while cont do
344                 local key = io.getchar()
345
346                 -- Special key behaviors
347                 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
348                     m ~= menu.default then
349                         break
350                 elseif key == core.KEY_ENTER then
351                         core.boot()
352                         -- Should not return
353                 end
354
355                 key = string.char(key)
356                 -- check to see if key is an alias
357                 local sel_entry = nil
358                 for k, v in pairs(alias_table) do
359                         if key == k then
360                                 sel_entry = v
361                         end
362                 end
363
364                 -- if we have an alias do the assigned action:
365                 if sel_entry ~= nil then
366                         -- Get menu handler
367                         local handler = menu.handlers[sel_entry.entry_type]
368                         if handler ~= nil then
369                                 -- The handler's return value indicates whether
370                                 -- we need to exit this menu. An omitted return
371                                 -- value means "continue" by default.
372                                 cont = handler(m, sel_entry)
373                                 if cont == nil then
374                                         cont = true
375                                 end
376                         end
377                         -- if we got an alias key the screen is out of date:
378                         screen.clear()
379                         screen.defcursor()
380                         alias_table = drawer.drawscreen(m)
381                 end
382         end
383
384         if m == menu.default then
385                 screen.defcursor()
386                 print("Exiting menu!")
387                 return false
388         end
389
390         return true
391 end
392
393 function menu.skip()
394         if core.isSerialBoot() then
395                 return true
396         end
397         local c = string.lower(loader.getenv("console") or "")
398         if c:match("^efi[ ;]") ~= nil or c:match("[ ;]efi[ ;]") ~= nil then
399                 return true
400         end
401
402         c = string.lower(loader.getenv("beastie_disable") or "")
403         print("beastie_disable", c)
404         return c == "yes"
405 end
406
407 function menu.autoboot()
408         if menu.already_autoboot then
409                 return
410         end
411         menu.already_autoboot = true
412
413         local ab = loader.getenv("autoboot_delay")
414         if ab ~= nil and ab:lower() == "no" then
415                 return
416         elseif tonumber(ab) == -1 then
417                 core.boot()
418         end
419         ab = tonumber(ab) or 10
420
421         local x = loader.getenv("loader_menu_timeout_x") or 5
422         local y = loader.getenv("loader_menu_timeout_y") or 22
423
424         local endtime = loader.time() + ab
425         local time
426
427         repeat
428                 time = endtime - loader.time()
429                 screen.setcursor(x, y)
430                 print("Autoboot in " .. time ..
431                     " seconds, hit [Enter] to boot" ..
432                     " or any other key to stop     ")
433                 screen.defcursor()
434                 if io.ischar() then
435                         local ch = io.getchar()
436                         if ch == core.KEY_ENTER then
437                                 break
438                         else
439                                 -- erase autoboot msg
440                                 screen.setcursor(0, y)
441                                 print("                                        "
442                                     .. "                                        ")
443                                 screen.defcursor()
444                                 return
445                         end
446                 end
447
448                 loader.delay(50000)
449         until time <= 0
450         core.boot()
451
452 end
453
454 return menu