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