xref: /freebsd-14.2/stand/lua/menu.lua (revision ab4a4d40)
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