xref: /freebsd/stand/lua/menu.lua (revision d7584aa09fcf8d96544e5bfb3ea78f50d7720c0c)
1088b4f5fSWarner Losh--
272e39d71SKyle Evans-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
372e39d71SKyle Evans--
4088b4f5fSWarner Losh-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5e12ff891SKyle Evans-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
6088b4f5fSWarner Losh-- All rights reserved.
7088b4f5fSWarner Losh--
8088b4f5fSWarner Losh-- Redistribution and use in source and binary forms, with or without
9088b4f5fSWarner Losh-- modification, are permitted provided that the following conditions
10088b4f5fSWarner Losh-- are met:
11088b4f5fSWarner Losh-- 1. Redistributions of source code must retain the above copyright
12088b4f5fSWarner Losh--    notice, this list of conditions and the following disclaimer.
13088b4f5fSWarner Losh-- 2. Redistributions in binary form must reproduce the above copyright
14088b4f5fSWarner Losh--    notice, this list of conditions and the following disclaimer in the
15088b4f5fSWarner Losh--    documentation and/or other materials provided with the distribution.
16088b4f5fSWarner Losh--
17088b4f5fSWarner Losh-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18088b4f5fSWarner Losh-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19088b4f5fSWarner Losh-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20088b4f5fSWarner Losh-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21088b4f5fSWarner Losh-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22088b4f5fSWarner Losh-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23088b4f5fSWarner Losh-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24088b4f5fSWarner Losh-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25088b4f5fSWarner Losh-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26088b4f5fSWarner Losh-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27088b4f5fSWarner Losh-- SUCH DAMAGE.
28088b4f5fSWarner Losh--
29088b4f5fSWarner Losh-- $FreeBSD$
30088b4f5fSWarner Losh--
31088b4f5fSWarner Losh
3276c75816SKyle Evanslocal cli = require("cli")
33aedd6be5SKyle Evanslocal core = require("core")
34aedd6be5SKyle Evanslocal color = require("color")
35aedd6be5SKyle Evanslocal config = require("config")
36aedd6be5SKyle Evanslocal screen = require("screen")
37aedd6be5SKyle Evanslocal drawer = require("drawer")
38088b4f5fSWarner Losh
39aedd6be5SKyle Evanslocal menu = {}
40c8518398SKyle Evans
41beafe961SKyle Evanslocal drawn_menu
4243f7d9d1SKyle Evanslocal return_menu_entry = {
4343f7d9d1SKyle Evans	entry_type = core.MENU_RETURN,
4443f7d9d1SKyle Evans	name = "Back to main menu" .. color.highlight(" [Backspace]"),
4543f7d9d1SKyle Evans}
46ca16d83fSKyle Evans
4704af4229SKyle Evanslocal function OnOff(str, value)
4804af4229SKyle Evans	if value then
498ce1744fSKyle Evans		return str .. color.escapefg(color.GREEN) .. "On" ..
506dd078dfSToomas Soome		    color.resetfg()
51e15abd1fSKyle Evans	else
528ce1744fSKyle Evans		return str .. color.escapefg(color.RED) .. "off" ..
536dd078dfSToomas Soome		    color.resetfg()
54e15abd1fSKyle Evans	end
55e15abd1fSKyle Evansend
56e15abd1fSKyle Evans
579ed6f9efSKyle Evanslocal function bootenvSet(env)
587efc058fSKyle Evans	loader.setenv("vfs.root.mountfrom", env)
597efc058fSKyle Evans	loader.setenv("currdev", env .. ":")
607efc058fSKyle Evans	config.reload()
61*d7584aa0SCyrus Rahman	if loader.getenv("kernelname") ~= nil then
62*d7584aa0SCyrus Rahman		loader.perform("unload")
63*d7584aa0SCyrus Rahman	end
647efc058fSKyle Evansend
657efc058fSKyle Evans
669b17aa27SJessica Clarkelocal function multiUserPrompt()
679b17aa27SJessica Clarke	return loader.getenv("loader_menu_multi_user_prompt") or "Multi user"
689b17aa27SJessica Clarkeend
699b17aa27SJessica Clarke
70b5746545SKyle Evans-- Module exports
718d415029SKyle Evansmenu.handlers = {
728d415029SKyle Evans	-- Menu handlers take the current menu and selected entry as parameters,
738d415029SKyle Evans	-- and should return a boolean indicating whether execution should
748d415029SKyle Evans	-- continue or not. The return value may be omitted if this entry should
758d415029SKyle Evans	-- have no bearing on whether we continue or not, indicating that we
768d415029SKyle Evans	-- should just continue after execution.
77e2df27e3SKyle Evans	[core.MENU_ENTRY] = function(_, entry)
788d415029SKyle Evans		-- run function
79aedd6be5SKyle Evans		entry.func()
808d415029SKyle Evans	end,
81e2df27e3SKyle Evans	[core.MENU_CAROUSEL_ENTRY] = function(_, entry)
828d415029SKyle Evans		-- carousel (rotating) functionality
83aedd6be5SKyle Evans		local carid = entry.carousel_id
84aedd6be5SKyle Evans		local caridx = config.getCarouselIndex(carid)
854f437f9eSKyle Evans		local choices = entry.items
864f437f9eSKyle Evans		if type(choices) == "function" then
874f437f9eSKyle Evans			choices = choices()
884f437f9eSKyle Evans		end
899f71d421SKyle Evans		if #choices > 0 then
90aedd6be5SKyle Evans			caridx = (caridx % #choices) + 1
91aedd6be5SKyle Evans			config.setCarouselIndex(carid, caridx)
92aedd6be5SKyle Evans			entry.func(caridx, choices[caridx], choices)
938d415029SKyle Evans		end
948d415029SKyle Evans	end,
95e2df27e3SKyle Evans	[core.MENU_SUBMENU] = function(_, entry)
96da9ab827SKyle Evans		menu.process(entry.submenu)
978d415029SKyle Evans	end,
98e2df27e3SKyle Evans	[core.MENU_RETURN] = function(_, entry)
998d415029SKyle Evans		-- allow entry to have a function/side effect
1009f71d421SKyle Evans		if entry.func ~= nil then
101aedd6be5SKyle Evans			entry.func()
1028d415029SKyle Evans		end
103aedd6be5SKyle Evans		return false
1048d415029SKyle Evans	end,
105aedd6be5SKyle Evans}
106280e990bSKyle Evans-- loader menu tree is rooted at menu.welcome
107088b4f5fSWarner Losh
1087efc058fSKyle Evansmenu.boot_environments = {
1097efc058fSKyle Evans	entries = {
1107efc058fSKyle Evans		-- return to welcome menu
11143f7d9d1SKyle Evans		return_menu_entry,
1127efc058fSKyle Evans		{
1137efc058fSKyle Evans			entry_type = core.MENU_CAROUSEL_ENTRY,
1147efc058fSKyle Evans			carousel_id = "be_active",
1157efc058fSKyle Evans			items = core.bootenvList,
1167efc058fSKyle Evans			name = function(idx, choice, all_choices)
1177efc058fSKyle Evans				if #all_choices == 0 then
1187efc058fSKyle Evans					return "Active: "
1197efc058fSKyle Evans				end
1207efc058fSKyle Evans
1217efc058fSKyle Evans				local is_default = (idx == 1)
1227efc058fSKyle Evans				local bootenv_name = ""
1237efc058fSKyle Evans				local name_color
1247efc058fSKyle Evans				if is_default then
1258ce1744fSKyle Evans					name_color = color.escapefg(color.GREEN)
1267efc058fSKyle Evans				else
1278ce1744fSKyle Evans					name_color = color.escapefg(color.BLUE)
1287efc058fSKyle Evans				end
1297efc058fSKyle Evans				bootenv_name = bootenv_name .. name_color ..
1308ce1744fSKyle Evans				    choice .. color.resetfg()
1317efc058fSKyle Evans				return color.highlight("A").."ctive: " ..
1327efc058fSKyle Evans				    bootenv_name .. " (" .. idx .. " of " ..
1337efc058fSKyle Evans				    #all_choices .. ")"
1347efc058fSKyle Evans			end,
135e2df27e3SKyle Evans			func = function(_, choice, _)
1367efc058fSKyle Evans				bootenvSet(choice)
1377efc058fSKyle Evans			end,
1387efc058fSKyle Evans			alias = {"a", "A"},
1397efc058fSKyle Evans		},
1407efc058fSKyle Evans		{
1417efc058fSKyle Evans			entry_type = core.MENU_ENTRY,
142277f38abSMariusz Zaborski			visible = function()
143277f38abSMariusz Zaborski				return core.isRewinded() == false
144277f38abSMariusz Zaborski			end,
1457efc058fSKyle Evans			name = function()
1467efc058fSKyle Evans				return color.highlight("b") .. "ootfs: " ..
1477efc058fSKyle Evans				    core.bootenvDefault()
1487efc058fSKyle Evans			end,
1497efc058fSKyle Evans			func = function()
1507efc058fSKyle Evans				-- Reset active boot environment to the default
1517efc058fSKyle Evans				config.setCarouselIndex("be_active", 1)
1527efc058fSKyle Evans				bootenvSet(core.bootenvDefault())
1537efc058fSKyle Evans			end,
1547efc058fSKyle Evans			alias = {"b", "B"},
1557efc058fSKyle Evans		},
1567efc058fSKyle Evans	},
1577efc058fSKyle Evans}
1587efc058fSKyle Evans
159088b4f5fSWarner Loshmenu.boot_options = {
160d8757746SKyle Evans	entries = {
161088b4f5fSWarner Losh		-- return to welcome menu
16243f7d9d1SKyle Evans		return_menu_entry,
163088b4f5fSWarner Losh		-- load defaults
164088b4f5fSWarner Losh		{
165a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
166a51f9f0cSKyle Evans			name = "Load System " .. color.highlight("D") ..
167a51f9f0cSKyle Evans			    "efaults",
168a51f9f0cSKyle Evans			func = core.setDefaults,
1693cd5547bSKyle Evans			alias = {"d", "D"},
170088b4f5fSWarner Losh		},
171088b4f5fSWarner Losh		{
172a7cf0562SKyle Evans			entry_type = core.MENU_SEPARATOR,
173088b4f5fSWarner Losh		},
174088b4f5fSWarner Losh		{
175a7cf0562SKyle Evans			entry_type = core.MENU_SEPARATOR,
176a51f9f0cSKyle Evans			name = "Boot Options:",
177088b4f5fSWarner Losh		},
178088b4f5fSWarner Losh		-- acpi
179088b4f5fSWarner Losh		{
180a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
181c1ab36f5SKyle Evans			visible = core.isSystem386,
182088b4f5fSWarner Losh			name = function()
183fd2b19b3SKyle Evans				return OnOff(color.highlight("A") ..
184aedd6be5SKyle Evans				    "CPI       :", core.acpi)
185088b4f5fSWarner Losh			end,
186a51f9f0cSKyle Evans			func = core.setACPI,
1873cd5547bSKyle Evans			alias = {"a", "A"},
188088b4f5fSWarner Losh		},
189088b4f5fSWarner Losh		-- safe mode
190088b4f5fSWarner Losh		{
191a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
192088b4f5fSWarner Losh			name = function()
19357099121SKyle Evans				return OnOff("Safe " .. color.highlight("M") ..
194aedd6be5SKyle Evans				    "ode  :", core.sm)
195088b4f5fSWarner Losh			end,
196a51f9f0cSKyle Evans			func = core.setSafeMode,
1973cd5547bSKyle Evans			alias = {"m", "M"},
198088b4f5fSWarner Losh		},
199088b4f5fSWarner Losh		-- single user
200088b4f5fSWarner Losh		{
201a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
202088b4f5fSWarner Losh			name = function()
203fd2b19b3SKyle Evans				return OnOff(color.highlight("S") ..
204aedd6be5SKyle Evans				    "ingle user:", core.su)
205088b4f5fSWarner Losh			end,
206a51f9f0cSKyle Evans			func = core.setSingleUser,
2073cd5547bSKyle Evans			alias = {"s", "S"},
208088b4f5fSWarner Losh		},
209088b4f5fSWarner Losh		-- verbose boot
210088b4f5fSWarner Losh		{
211a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
212088b4f5fSWarner Losh			name = function()
213fd2b19b3SKyle Evans				return OnOff(color.highlight("V") ..
214aedd6be5SKyle Evans				    "erbose    :", core.verbose)
215088b4f5fSWarner Losh			end,
216a51f9f0cSKyle Evans			func = core.setVerbose,
2173cd5547bSKyle Evans			alias = {"v", "V"},
218088b4f5fSWarner Losh		},
219d8757746SKyle Evans	},
220aedd6be5SKyle Evans}
221088b4f5fSWarner Losh
222088b4f5fSWarner Loshmenu.welcome = {
223303253e5SKyle Evans	entries = function()
224aedd6be5SKyle Evans		local menu_entries = menu.welcome.all_entries
225d2187b39SRyan Moeller		local multi_user = menu_entries.multi_user
226d2187b39SRyan Moeller		local single_user = menu_entries.single_user
227d2187b39SRyan Moeller		local boot_entry_1, boot_entry_2
2289f71d421SKyle Evans		if core.isSingleUserBoot() then
229d2187b39SRyan Moeller			-- Swap the first two menu items on single user boot.
230d2187b39SRyan Moeller			-- We'll cache the alternate entries for performance.
231d2187b39SRyan Moeller			local alts = menu_entries.alts
232d2187b39SRyan Moeller			if alts == nil then
233d2187b39SRyan Moeller				single_user = core.deepCopyTable(single_user)
234d2187b39SRyan Moeller				multi_user = core.deepCopyTable(multi_user)
235d2187b39SRyan Moeller				single_user.name = single_user.alternate_name
236d2187b39SRyan Moeller				multi_user.name = multi_user.alternate_name
237d2187b39SRyan Moeller				menu_entries.alts = {
238d2187b39SRyan Moeller					single_user = single_user,
239d2187b39SRyan Moeller					multi_user = multi_user,
240d2187b39SRyan Moeller				}
241d2187b39SRyan Moeller			else
242d2187b39SRyan Moeller				single_user = alts.single_user
243d2187b39SRyan Moeller				multi_user = alts.multi_user
2449a0904b0SKyle Evans			end
245d2187b39SRyan Moeller			boot_entry_1, boot_entry_2 = single_user, multi_user
246d2187b39SRyan Moeller		else
247d2187b39SRyan Moeller			boot_entry_1, boot_entry_2 = multi_user, single_user
248303253e5SKyle Evans		end
249d2187b39SRyan Moeller		return {
250d2187b39SRyan Moeller			boot_entry_1,
251d2187b39SRyan Moeller			boot_entry_2,
252d2187b39SRyan Moeller			menu_entries.prompt,
253d2187b39SRyan Moeller			menu_entries.reboot,
2548f3b3610SWarner Losh			menu_entries.console,
255d2187b39SRyan Moeller			{
256d2187b39SRyan Moeller				entry_type = core.MENU_SEPARATOR,
257d2187b39SRyan Moeller			},
258d2187b39SRyan Moeller			{
259d2187b39SRyan Moeller				entry_type = core.MENU_SEPARATOR,
260d2187b39SRyan Moeller				name = "Options:",
261d2187b39SRyan Moeller			},
262d2187b39SRyan Moeller			menu_entries.kernel_options,
263d2187b39SRyan Moeller			menu_entries.boot_options,
264277f38abSMariusz Zaborski			menu_entries.zpool_checkpoints,
265d2187b39SRyan Moeller			menu_entries.boot_envs,
266d2187b39SRyan Moeller			menu_entries.chainload,
267e7ccd5b4SWarner Losh			menu_entries.vendor,
268d2187b39SRyan Moeller		}
269303253e5SKyle Evans	end,
270303253e5SKyle Evans	all_entries = {
271d2187b39SRyan Moeller		multi_user = {
272a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
2739b17aa27SJessica Clarke			name = function()
2749b17aa27SJessica Clarke				return color.highlight("B") .. "oot " ..
2759b17aa27SJessica Clarke				    multiUserPrompt() .. " " ..
2769b17aa27SJessica Clarke				    color.highlight("[Enter]")
2779b17aa27SJessica Clarke			end,
2785c1b5165SKyle Evans			-- Not a standard menu entry function!
2799b17aa27SJessica Clarke			alternate_name = function()
2809b17aa27SJessica Clarke				return color.highlight("B") .. "oot " ..
2819b17aa27SJessica Clarke				    multiUserPrompt()
2829b17aa27SJessica Clarke			end,
283088b4f5fSWarner Losh			func = function()
284aedd6be5SKyle Evans				core.setSingleUser(false)
285aedd6be5SKyle Evans				core.boot()
286088b4f5fSWarner Losh			end,
2873cd5547bSKyle Evans			alias = {"b", "B"},
288088b4f5fSWarner Losh		},
289d2187b39SRyan Moeller		single_user = {
290a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
291a51f9f0cSKyle Evans			name = "Boot " .. color.highlight("S") .. "ingle user",
2925c1b5165SKyle Evans			-- Not a standard menu entry function!
293a51f9f0cSKyle Evans			alternate_name = "Boot " .. color.highlight("S") ..
294a51f9f0cSKyle Evans			    "ingle user " .. color.highlight("[Enter]"),
295088b4f5fSWarner Losh			func = function()
296aedd6be5SKyle Evans				core.setSingleUser(true)
297aedd6be5SKyle Evans				core.boot()
298088b4f5fSWarner Losh			end,
2993cd5547bSKyle Evans			alias = {"s", "S"},
300088b4f5fSWarner Losh		},
3018f3b3610SWarner Losh		console = {
3028f3b3610SWarner Losh			entry_type = core.MENU_ENTRY,
3038f3b3610SWarner Losh			name = function()
3048f3b3610SWarner Losh				return color.highlight("C") .. "ons: " .. core.getConsoleName()
3058f3b3610SWarner Losh			end,
3068f3b3610SWarner Losh			func = function()
3078f3b3610SWarner Losh				core.nextConsoleChoice()
3088f3b3610SWarner Losh			end,
3098f3b3610SWarner Losh			alias = {"c", "C"},
3108f3b3610SWarner Losh		},
311d2187b39SRyan Moeller		prompt = {
312a7cf0562SKyle Evans			entry_type = core.MENU_RETURN,
313a51f9f0cSKyle Evans			name = color.highlight("Esc") .. "ape to loader prompt",
314ef625845SKyle Evans			func = function()
315aedd6be5SKyle Evans				loader.setenv("autoboot_delay", "NO")
316ef625845SKyle Evans			end,
3173cd5547bSKyle Evans			alias = {core.KEYSTR_ESCAPE},
318088b4f5fSWarner Losh		},
319d2187b39SRyan Moeller		reboot = {
320a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
321a51f9f0cSKyle Evans			name = color.highlight("R") .. "eboot",
322088b4f5fSWarner Losh			func = function()
323aedd6be5SKyle Evans				loader.perform("reboot")
324088b4f5fSWarner Losh			end,
3253cd5547bSKyle Evans			alias = {"r", "R"},
326088b4f5fSWarner Losh		},
327d2187b39SRyan Moeller		kernel_options = {
328a7cf0562SKyle Evans			entry_type = core.MENU_CAROUSEL_ENTRY,
329ada26c4aSKyle Evans			carousel_id = "kernel",
330ada26c4aSKyle Evans			items = core.kernelList,
331ada26c4aSKyle Evans			name = function(idx, choice, all_choices)
3329f71d421SKyle Evans				if #all_choices == 0 then
333aedd6be5SKyle Evans					return "Kernel: "
334088b4f5fSWarner Losh				end
335b1b1f2b8SKyle Evans
336aedd6be5SKyle Evans				local is_default = (idx == 1)
337aedd6be5SKyle Evans				local kernel_name = ""
338aedd6be5SKyle Evans				local name_color
3399f71d421SKyle Evans				if is_default then
3408ce1744fSKyle Evans					name_color = color.escapefg(color.GREEN)
341aedd6be5SKyle Evans					kernel_name = "default/"
342bcf48a15SKyle Evans				else
3438ce1744fSKyle Evans					name_color = color.escapefg(color.BLUE)
344b1b1f2b8SKyle Evans				end
345fd2b19b3SKyle Evans				kernel_name = kernel_name .. name_color ..
3468ce1744fSKyle Evans				    choice .. color.resetfg()
347fd2b19b3SKyle Evans				return color.highlight("K") .. "ernel: " ..
348fd2b19b3SKyle Evans				    kernel_name .. " (" .. idx .. " of " ..
349aedd6be5SKyle Evans				    #all_choices .. ")"
350088b4f5fSWarner Losh			end,
351e2df27e3SKyle Evans			func = function(_, choice, _)
352e414851fSKyle Evans				if loader.getenv("kernelname") ~= nil then
353e414851fSKyle Evans					loader.perform("unload")
354e414851fSKyle Evans				end
355322a2dddSKyle Evans				config.selectKernel(choice)
356088b4f5fSWarner Losh			end,
3573cd5547bSKyle Evans			alias = {"k", "K"},
358088b4f5fSWarner Losh		},
359d2187b39SRyan Moeller		boot_options = {
360a7cf0562SKyle Evans			entry_type = core.MENU_SUBMENU,
361a51f9f0cSKyle Evans			name = "Boot " .. color.highlight("O") .. "ptions",
3629a28f948SKyle Evans			submenu = menu.boot_options,
3633cd5547bSKyle Evans			alias = {"o", "O"},
364d8757746SKyle Evans		},
365277f38abSMariusz Zaborski		zpool_checkpoints = {
366277f38abSMariusz Zaborski			entry_type = core.MENU_ENTRY,
367277f38abSMariusz Zaborski			name = function()
36894510c29SKyle Evans				local rewind = "No"
369277f38abSMariusz Zaborski				if core.isRewinded() then
370277f38abSMariusz Zaborski					rewind = "Yes"
371277f38abSMariusz Zaborski				end
372277f38abSMariusz Zaborski				return "Rewind ZFS " .. color.highlight("C") ..
373277f38abSMariusz Zaborski					"heckpoint: " .. rewind
374277f38abSMariusz Zaborski			end,
375277f38abSMariusz Zaborski			func = function()
376277f38abSMariusz Zaborski				core.changeRewindCheckpoint()
377277f38abSMariusz Zaborski				if core.isRewinded() then
378277f38abSMariusz Zaborski					bootenvSet(
379277f38abSMariusz Zaborski					    core.bootenvDefaultRewinded())
380277f38abSMariusz Zaborski				else
381277f38abSMariusz Zaborski					bootenvSet(core.bootenvDefault())
382277f38abSMariusz Zaborski				end
383277f38abSMariusz Zaborski				config.setCarouselIndex("be_active", 1)
384277f38abSMariusz Zaborski			end,
385277f38abSMariusz Zaborski			visible = function()
386277f38abSMariusz Zaborski				return core.isZFSBoot() and
387277f38abSMariusz Zaborski				    core.isCheckpointed()
388277f38abSMariusz Zaborski			end,
389277f38abSMariusz Zaborski			alias = {"c", "C"},
390277f38abSMariusz Zaborski		},
391d2187b39SRyan Moeller		boot_envs = {
3927efc058fSKyle Evans			entry_type = core.MENU_SUBMENU,
3937efc058fSKyle Evans			visible = function()
3947efc058fSKyle Evans				return core.isZFSBoot() and
3957efc058fSKyle Evans				    #core.bootenvList() > 1
3967efc058fSKyle Evans			end,
3977efc058fSKyle Evans			name = "Boot " .. color.highlight("E") .. "nvironments",
3987efc058fSKyle Evans			submenu = menu.boot_environments,
3997efc058fSKyle Evans			alias = {"e", "E"},
4007efc058fSKyle Evans		},
401d2187b39SRyan Moeller		chainload = {
402bdf12807SKyle Evans			entry_type = core.MENU_ENTRY,
403bdf12807SKyle Evans			name = function()
404bdf12807SKyle Evans				return 'Chain' .. color.highlight("L") ..
405bdf12807SKyle Evans				    "oad " .. loader.getenv('chain_disk')
406bdf12807SKyle Evans			end,
407bdf12807SKyle Evans			func = function()
408bdf12807SKyle Evans				loader.perform("chain " ..
409bdf12807SKyle Evans				    loader.getenv('chain_disk'))
410bdf12807SKyle Evans			end,
411bdf12807SKyle Evans			visible = function()
412bdf12807SKyle Evans				return loader.getenv('chain_disk') ~= nil
413bdf12807SKyle Evans			end,
414bdf12807SKyle Evans			alias = {"l", "L"},
415bdf12807SKyle Evans		},
416e7ccd5b4SWarner Losh		vendor = {
417e7ccd5b4SWarner Losh			entry_type = core.MENU_ENTRY,
418556e66b7SWarner Losh			visible = function()
419556e66b7SWarner Losh				return false
420556e66b7SWarner Losh			end
421e7ccd5b4SWarner Losh		},
422d8757746SKyle Evans	},
423aedd6be5SKyle Evans}
424088b4f5fSWarner Losh
42520a81676SKyle Evansmenu.default = menu.welcome
42628384160SKyle Evans-- current_alias_table will be used to keep our alias table consistent across
42728384160SKyle Evans-- screen redraws, instead of relying on whatever triggered the redraw to update
42828384160SKyle Evans-- the local alias_table in menu.process.
42928384160SKyle Evansmenu.current_alias_table = {}
43020a81676SKyle Evans
4312a11b810SKyle Evansfunction menu.draw(menudef)
432beafe961SKyle Evans	-- Clear the screen, reset the cursor, then draw
433aedd6be5SKyle Evans	screen.clear()
4342a11b810SKyle Evans	menu.current_alias_table = drawer.drawscreen(menudef)
4352a11b810SKyle Evans	drawn_menu = menudef
436decacd91SKyle Evans	screen.defcursor()
43728384160SKyle Evansend
43828384160SKyle Evans
439ca16d83fSKyle Evans-- 'keypress' allows the caller to indicate that a key has been pressed that we
440ca16d83fSKyle Evans-- should process as our initial input.
4412a11b810SKyle Evansfunction menu.process(menudef, keypress)
4422a11b810SKyle Evans	assert(menudef ~= nil)
44328384160SKyle Evans
4442a11b810SKyle Evans	if drawn_menu ~= menudef then
4452a11b810SKyle Evans		menu.draw(menudef)
4467dcffa90SKyle Evans	end
447ca16d83fSKyle Evans
448da9ab827SKyle Evans	while true do
449ca16d83fSKyle Evans		local key = keypress or io.getchar()
450ca16d83fSKyle Evans		keypress = nil
451088b4f5fSWarner Losh
452b458bf0dSKyle Evans		-- Special key behaviors
4539f71d421SKyle Evans		if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
4542a11b810SKyle Evans		    menudef ~= menu.default then
455aedd6be5SKyle Evans			break
4569f71d421SKyle Evans		elseif key == core.KEY_ENTER then
457aedd6be5SKyle Evans			core.boot()
458041929aaSKyle Evans			-- Should not return.  If it does, escape menu handling
459041929aaSKyle Evans			-- and drop to loader prompt.
460041929aaSKyle Evans			return false
461abc4f7e7SKyle Evans		end
462abc4f7e7SKyle Evans
463abc4f7e7SKyle Evans		key = string.char(key)
464088b4f5fSWarner Losh		-- check to see if key is an alias
465aedd6be5SKyle Evans		local sel_entry = nil
46628384160SKyle Evans		for k, v in pairs(menu.current_alias_table) do
4679f71d421SKyle Evans			if key == k then
468aedd6be5SKyle Evans				sel_entry = v
46928384160SKyle Evans				break
470088b4f5fSWarner Losh			end
471088b4f5fSWarner Losh		end
472088b4f5fSWarner Losh
473088b4f5fSWarner Losh		-- if we have an alias do the assigned action:
4749f71d421SKyle Evans		if sel_entry ~= nil then
475aedd6be5SKyle Evans			local handler = menu.handlers[sel_entry.entry_type]
4762a11b810SKyle Evans			assert(handler ~= nil)
477da9ab827SKyle Evans			-- The handler's return value indicates if we
478da9ab827SKyle Evans			-- need to exit this menu.  An omitted or true
479da9ab827SKyle Evans			-- return value means to continue.
4802a11b810SKyle Evans			if handler(menudef, sel_entry) == false then
481da9ab827SKyle Evans				return
482aefcaa7eSKyle Evans			end
48328384160SKyle Evans			-- If we got an alias key the screen is out of date...
48428384160SKyle Evans			-- redraw it.
4852a11b810SKyle Evans			menu.draw(menudef)
486088b4f5fSWarner Losh		end
487088b4f5fSWarner Losh	end
488088b4f5fSWarner Loshend
489088b4f5fSWarner Losh
490da9ab827SKyle Evansfunction menu.run()
491041929aaSKyle Evans	local autoboot_key
492c84dbc53SKyle Evans	local delay = loader.getenv("autoboot_delay")
493c84dbc53SKyle Evans
494c84dbc53SKyle Evans	if delay ~= nil and delay:lower() == "no" then
495c84dbc53SKyle Evans		delay = nil
496c84dbc53SKyle Evans	else
497c84dbc53SKyle Evans		delay = tonumber(delay) or 10
498c84dbc53SKyle Evans	end
499c84dbc53SKyle Evans
500c84dbc53SKyle Evans	if delay == -1 then
501c84dbc53SKyle Evans		core.boot()
502c84dbc53SKyle Evans		return
503c84dbc53SKyle Evans	end
504c84dbc53SKyle Evans
505beafe961SKyle Evans	menu.draw(menu.default)
506c84dbc53SKyle Evans
507041929aaSKyle Evans	if delay ~= nil then
508041929aaSKyle Evans		autoboot_key = menu.autoboot(delay)
509041929aaSKyle Evans
510041929aaSKyle Evans		-- autoboot_key should return the key pressed.  It will only
511041929aaSKyle Evans		-- return nil if we hit the timeout and executed the timeout
512041929aaSKyle Evans		-- command.  Bail out.
513041929aaSKyle Evans		if autoboot_key == nil then
514041929aaSKyle Evans			return
515041929aaSKyle Evans		end
516041929aaSKyle Evans	end
517ca16d83fSKyle Evans
518ca16d83fSKyle Evans	menu.process(menu.default, autoboot_key)
519beafe961SKyle Evans	drawn_menu = nil
520da9ab827SKyle Evans
521da9ab827SKyle Evans	screen.defcursor()
522da9ab827SKyle Evans	print("Exiting menu!")
523088b4f5fSWarner Loshend
524088b4f5fSWarner Losh
525c84dbc53SKyle Evansfunction menu.autoboot(delay)
5261495c98fSKyle Evans	local x = loader.getenv("loader_menu_timeout_x") or 4
5271495c98fSKyle Evans	local y = loader.getenv("loader_menu_timeout_y") or 23
528c84dbc53SKyle Evans	local endtime = loader.time() + delay
529aedd6be5SKyle Evans	local time
5303244729fSKyle Evans	local last
531088b4f5fSWarner Losh	repeat
532aedd6be5SKyle Evans		time = endtime - loader.time()
5333244729fSKyle Evans		if last == nil or last ~= time then
5343244729fSKyle Evans			last = time
535aedd6be5SKyle Evans			screen.setcursor(x, y)
53657099121SKyle Evans			print("Autoboot in " .. time ..
537ed107537SKyle Evans			    " seconds. [Space] to pause ")
538aedd6be5SKyle Evans			screen.defcursor()
5393244729fSKyle Evans		end
5409f71d421SKyle Evans		if io.ischar() then
541aedd6be5SKyle Evans			local ch = io.getchar()
5429f71d421SKyle Evans			if ch == core.KEY_ENTER then
543aedd6be5SKyle Evans				break
544088b4f5fSWarner Losh			else
545088b4f5fSWarner Losh				-- erase autoboot msg
546aedd6be5SKyle Evans				screen.setcursor(0, y)
54766964bbcSKyle Evans				print(string.rep(" ", 80))
548aedd6be5SKyle Evans				screen.defcursor()
54912b95c84SKyle Evans				return ch
550088b4f5fSWarner Losh			end
551088b4f5fSWarner Losh		end
552088b4f5fSWarner Losh
553aedd6be5SKyle Evans		loader.delay(50000)
554aedd6be5SKyle Evans	until time <= 0
555088b4f5fSWarner Losh
556a76f8a5bSKyle Evans	local cmd = loader.getenv("menu_timeout_command") or "boot"
557e9c3ceb1SKyle Evans	cli_execute_unparsed(cmd)
558041929aaSKyle Evans	return nil
559088b4f5fSWarner Loshend
560088b4f5fSWarner Losh
56176c75816SKyle Evans-- CLI commands
5628f7f3d08SKyle Evansfunction cli.menu()
56376c75816SKyle Evans	menu.run()
56476c75816SKyle Evansend
56576c75816SKyle Evans
566aedd6be5SKyle Evansreturn menu
567