xref: /freebsd-12.1/stand/lua/menu.lua (revision 16759360)
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) and (m ~= menu.welcome) then
270			break
271		elseif (key == core.KEY_ENTER) then
272			core.boot();
273			-- Should not return
274		end
275
276		key = string.char(key)
277		-- check to see if key is an alias
278		local sel_entry = nil;
279		for k, v in pairs(alias_table) do
280			if (key == k) then
281				sel_entry = v;
282			end
283		end
284
285		-- if we have an alias do the assigned action:
286		if(sel_entry ~= nil) then
287			if (sel_entry.entry_type == core.MENU_ENTRY) then
288				-- run function
289				sel_entry.func();
290			elseif (sel_entry.entry_type == core.MENU_CAROUSEL_ENTRY) then
291				-- carousel (rotating) functionality
292				local carid = sel_entry.carousel_id;
293				local caridx = menu.getCarouselIndex(carid);
294				local choices = sel_entry.items();
295
296				caridx = (caridx % #choices) + 1;
297				menu.setCarouselIndex(carid, caridx);
298				sel_entry.func(choices[caridx]);
299			elseif (sel_entry.entry_type == core.MENU_SUBMENU) then
300				-- recurse
301				cont = menu.run(sel_entry.submenu());
302			elseif (sel_entry.entry_type == core.MENU_RETURN) then
303				-- break recurse
304				cont = false;
305			end
306			-- if we got an alias key the screen is out of date:
307			screen.clear();
308			screen.defcursor();
309			alias_table = drawer.drawscreen(m);
310		end
311	end
312
313	if (m == menu.welcome) then
314		screen.defcursor();
315		print("Exiting menu!");
316		return false;
317	end
318
319	return true;
320end
321
322function menu.skip()
323	if core.bootserial() then
324		return true;
325	end
326	local c = string.lower(loader.getenv("console") or "");
327	if (c:match("^efi[ ;]") or c:match("[ ;]efi[ ;]")) ~= nil then
328		return true;
329	end
330
331	c = string.lower(loader.getenv("beastie_disable") or "");
332	print("beastie_disable", c);
333	return c == "yes";
334end
335
336function menu.autoboot()
337	if menu.already_autoboot == true then
338		return;
339	end
340	menu.already_autoboot = true;
341
342	local ab = loader.getenv("autoboot_delay");
343	if ab == "NO" or ab == "no" then
344		core.boot();
345	end
346	ab = tonumber(ab) or 10;
347
348	local x = loader.getenv("loader_menu_timeout_x") or 5;
349	local y = loader.getenv("loader_menu_timeout_y") or 22;
350
351	local endtime = loader.time() + ab;
352	local time;
353
354	repeat
355		time = endtime - loader.time();
356		screen.setcursor(x, y);
357		print("Autoboot in "..time.." seconds, hit [Enter] to boot"
358			      .." or any other key to stop     ");
359		screen.defcursor();
360		if io.ischar() then
361			local ch = io.getchar();
362			if ch == core.KEY_ENTER then
363				break;
364			else
365				-- prevent autoboot when escaping to interpreter
366				loader.setenv("autoboot_delay", "NO");
367				-- erase autoboot msg
368				screen.setcursor(0, y);
369				print("                                        "
370					      .."                                        ");
371				screen.defcursor();
372				return;
373			end
374		end
375
376		loader.delay(50000);
377	until time <= 0
378	core.boot();
379
380end
381
382function OnOff(str, b)
383	if (b) then
384		return str .. color.escapef(color.GREEN).."On"..color.escapef(color.WHITE);
385	else
386		return str .. color.escapef(color.RED).."off"..color.escapef(color.WHITE);
387	end
388end
389
390return menu
391