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