]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/lua/drawer.lua
byacc: Update to 20230201.
[FreeBSD/FreeBSD.git] / stand / lua / drawer.lua
1 --
2 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 --
4 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5 -- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
6 -- All rights reserved.
7 --
8 -- Redistribution and use in source and binary forms, with or without
9 -- modification, are permitted provided that the following conditions
10 -- are met:
11 -- 1. Redistributions of source code must retain the above copyright
12 --    notice, this list of conditions and the following disclaimer.
13 -- 2. Redistributions in binary form must reproduce the above copyright
14 --    notice, this list of conditions and the following disclaimer in the
15 --    documentation and/or other materials provided with the distribution.
16 --
17 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 -- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 -- SUCH DAMAGE.
28 --
29 -- $FreeBSD$
30 --
31
32 local color = require("color")
33 local config = require("config")
34 local core = require("core")
35 local screen = require("screen")
36
37 local drawer = {}
38
39 local fbsd_brand
40 local none
41
42 local menu_name_handlers
43 local branddefs
44 local logodefs
45 local brand_position
46 local logo_position
47 local menu_position
48 local frame_size
49 local default_shift
50 local shift
51
52 local function menuEntryName(drawing_menu, entry)
53         local name_handler = menu_name_handlers[entry.entry_type]
54
55         if name_handler ~= nil then
56                 return name_handler(drawing_menu, entry)
57         end
58         if type(entry.name) == "function" then
59                 return entry.name()
60         end
61         return entry.name
62 end
63
64 local function processFile(gfxname)
65         if gfxname == nil then
66                 return false, "Missing filename"
67         end
68
69         local ret = try_include('gfx-' .. gfxname)
70         if ret == nil then
71                 return false, "Failed to include gfx-" .. gfxname
72         end
73
74         -- Legacy format
75         if type(ret) ~= "table" then
76                 return true
77         end
78
79         for gfxtype, def in pairs(ret) do
80                 if gfxtype == "brand" then
81                         drawer.addBrand(gfxname, def)
82                 elseif gfxtype == "logo" then
83                         drawer.addLogo(gfxname, def)
84                 else
85                         return false, "Unknown graphics type '" .. gfxtype ..
86                             "'"
87                 end
88         end
89
90         return true
91 end
92
93 local function getBranddef(brand)
94         if brand == nil then
95                 return nil
96         end
97         -- Look it up
98         local branddef = branddefs[brand]
99
100         -- Try to pull it in
101         if branddef == nil then
102                 local res, err = processFile(brand)
103                 if not res then
104                         -- This fallback should go away after FreeBSD 13.
105                         try_include('brand-' .. brand)
106                         -- If the fallback also failed, print whatever error
107                         -- we encountered in the original processing.
108                         if branddefs[brand] == nil then
109                                 print(err)
110                                 return nil
111                         end
112                 end
113
114                 branddef = branddefs[brand]
115         end
116
117         return branddef
118 end
119
120 local function getLogodef(logo)
121         if logo == nil then
122                 return nil
123         end
124         -- Look it up
125         local logodef = logodefs[logo]
126
127         -- Try to pull it in
128         if logodef == nil then
129                 local res, err = processFile(logo)
130                 if not res then
131                         -- This fallback should go away after FreeBSD 13.
132                         try_include('logo-' .. logo)
133                         -- If the fallback also failed, print whatever error
134                         -- we encountered in the original processing.
135                         if logodefs[logo] == nil then
136                                 print(err)
137                                 return nil
138                         end
139                 end
140
141                 logodef = logodefs[logo]
142         end
143
144         return logodef
145 end
146
147 local function draw(x, y, logo)
148         for i = 1, #logo do
149                 screen.setcursor(x, y + i - 1)
150                 printc(logo[i])
151         end
152 end
153
154 local function drawmenu(menudef)
155         local x = menu_position.x
156         local y = menu_position.y
157
158         x = x + shift.x
159         y = y + shift.y
160
161         -- print the menu and build the alias table
162         local alias_table = {}
163         local entry_num = 0
164         local menu_entries = menudef.entries
165         local effective_line_num = 0
166         if type(menu_entries) == "function" then
167                 menu_entries = menu_entries()
168         end
169         for _, e in ipairs(menu_entries) do
170                 -- Allow menu items to be conditionally visible by specifying
171                 -- a visible function.
172                 if e.visible ~= nil and not e.visible() then
173                         goto continue
174                 end
175                 effective_line_num = effective_line_num + 1
176                 if e.entry_type ~= core.MENU_SEPARATOR then
177                         entry_num = entry_num + 1
178                         screen.setcursor(x, y + effective_line_num)
179
180                         printc(entry_num .. ". " .. menuEntryName(menudef, e))
181
182                         -- fill the alias table
183                         alias_table[tostring(entry_num)] = e
184                         if e.alias ~= nil then
185                                 for _, a in ipairs(e.alias) do
186                                         alias_table[a] = e
187                                 end
188                         end
189                 else
190                         screen.setcursor(x, y + effective_line_num)
191                         printc(menuEntryName(menudef, e))
192                 end
193                 ::continue::
194         end
195         return alias_table
196 end
197
198 local function defaultframe()
199         if core.isSerialConsole() then
200                 return "ascii"
201         end
202         return "double"
203 end
204
205 local function drawframe()
206         local x = menu_position.x - 3
207         local y = menu_position.y - 1
208         local w = frame_size.w
209         local h = frame_size.h
210
211         local framestyle = loader.getenv("loader_menu_frame") or defaultframe()
212         local framespec = drawer.frame_styles[framestyle]
213         -- If we don't have a framespec for the current frame style, just don't
214         -- draw a box.
215         if framespec == nil then
216                 return false
217         end
218
219         local hl = framespec.horizontal
220         local vl = framespec.vertical
221
222         local tl = framespec.top_left
223         local bl = framespec.bottom_left
224         local tr = framespec.top_right
225         local br = framespec.bottom_right
226
227         x = x + shift.x
228         y = y + shift.y
229
230         if core.isFramebufferConsole() and loader.term_drawrect ~= nil then
231                 loader.term_drawrect(x, y, x + w, y + h)
232                 return true
233         end
234
235         screen.setcursor(x, y); printc(tl)
236         screen.setcursor(x, y + h); printc(bl)
237         screen.setcursor(x + w, y); printc(tr)
238         screen.setcursor(x + w, y + h); printc(br)
239
240         screen.setcursor(x + 1, y)
241         for _ = 1, w - 1 do
242                 printc(hl)
243         end
244
245         screen.setcursor(x + 1, y + h)
246         for _ = 1, w - 1 do
247                 printc(hl)
248         end
249
250         for i = 1, h - 1 do
251                 screen.setcursor(x, y + i)
252                 printc(vl)
253                 screen.setcursor(x + w, y + i)
254                 printc(vl)
255         end
256         return true
257 end
258
259 local function drawbox()
260         local x = menu_position.x - 3
261         local y = menu_position.y - 1
262         local w = frame_size.w
263         local menu_header = loader.getenv("loader_menu_title") or
264             "Welcome to FreeBSD"
265         local menu_header_align = loader.getenv("loader_menu_title_align")
266         local menu_header_x
267
268         x = x + shift.x
269         y = y + shift.y
270
271         if drawframe(x, y, w) == false then
272                 return
273         end
274
275         if menu_header_align ~= nil then
276                 menu_header_align = menu_header_align:lower()
277                 if menu_header_align == "left" then
278                         -- Just inside the left border on top
279                         menu_header_x = x + 1
280                 elseif menu_header_align == "right" then
281                         -- Just inside the right border on top
282                         menu_header_x = x + w - #menu_header
283                 end
284         end
285         if menu_header_x == nil then
286                 menu_header_x = x + (w // 2) - (#menu_header // 2)
287         end
288         screen.setcursor(menu_header_x - 1, y)
289         if menu_header ~= "" then
290                 printc(" " .. menu_header .. " ")
291         end
292
293 end
294
295 local function drawbrand()
296         local x = tonumber(loader.getenv("loader_brand_x")) or
297             brand_position.x
298         local y = tonumber(loader.getenv("loader_brand_y")) or
299             brand_position.y
300
301         local branddef = getBranddef(loader.getenv("loader_brand"))
302
303         if branddef == nil then
304                 branddef = getBranddef(drawer.default_brand)
305         end
306
307         local graphic = branddef.graphic
308
309         x = x + shift.x
310         y = y + shift.y
311         if branddef.shift ~= nil then
312                 x = x + branddef.shift.x
313                 y = y + branddef.shift.y
314         end
315
316         if core.isFramebufferConsole() and
317             loader.term_putimage ~= nil and
318             branddef.image ~= nil then
319                 if loader.term_putimage(branddef.image, 1, 1, 0, 7, 0)
320                 then
321                         return true
322                 end
323         end
324         draw(x, y, graphic)
325 end
326
327 local function drawlogo()
328         local x = tonumber(loader.getenv("loader_logo_x")) or
329             logo_position.x
330         local y = tonumber(loader.getenv("loader_logo_y")) or
331             logo_position.y
332
333         local logo = loader.getenv("loader_logo")
334         local colored = color.isEnabled()
335
336         local logodef = getLogodef(logo)
337
338         if logodef == nil or logodef.graphic == nil or
339             (not colored and logodef.requires_color) then
340                 -- Choose a sensible default
341                 if colored then
342                         logodef = getLogodef(drawer.default_color_logodef)
343                 else
344                         logodef = getLogodef(drawer.default_bw_logodef)
345                 end
346
347                 -- Something has gone terribly wrong.
348                 if logodef == nil then
349                         logodef = getLogodef(drawer.default_fallback_logodef)
350                 end
351         end
352
353         if logodef ~= nil and logodef.graphic == none then
354                 shift = logodef.shift
355         else
356                 shift = default_shift
357         end
358
359         x = x + shift.x
360         y = y + shift.y
361
362         if logodef ~= nil and logodef.shift ~= nil then
363                 x = x + logodef.shift.x
364                 y = y + logodef.shift.y
365         end
366
367         if core.isFramebufferConsole() and
368             loader.term_putimage ~= nil and
369             logodef.image ~= nil then
370                 local y1 = 15
371
372                 if logodef.image_rl ~= nil then
373                         y1 = logodef.image_rl
374                 end
375                 if loader.term_putimage(logodef.image, x, y, 0, y + y1, 0)
376                 then
377                         return true
378                 end
379         end
380         draw(x, y, logodef.graphic)
381 end
382
383 local function drawitem(func)
384         local console = loader.getenv("console")
385
386         for c in string.gmatch(console, "%w+") do
387                 loader.setenv("console", c)
388                 func()
389         end
390         loader.setenv("console", console)
391 end
392
393 fbsd_brand = {
394 "  ______               ____   _____ _____  ",
395 " |  ____|             |  _ \\ / ____|  __ \\ ",
396 " | |___ _ __ ___  ___ | |_) | (___ | |  | |",
397 " |  ___| '__/ _ \\/ _ \\|  _ < \\___ \\| |  | |",
398 " | |   | | |  __/  __/| |_) |____) | |__| |",
399 " | |   | | |    |    ||     |      |      |",
400 " |_|   |_|  \\___|\\___||____/|_____/|_____/ "
401 }
402 none = {""}
403
404 menu_name_handlers = {
405         -- Menu name handlers should take the menu being drawn and entry being
406         -- drawn as parameters, and return the name of the item.
407         -- This is designed so that everything, including menu separators, may
408         -- have their names derived differently. The default action for entry
409         -- types not specified here is to use entry.name directly.
410         [core.MENU_SEPARATOR] = function(_, entry)
411                 if entry.name ~= nil then
412                         if type(entry.name) == "function" then
413                                 return entry.name()
414                         end
415                         return entry.name
416                 end
417                 return ""
418         end,
419         [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
420                 local carid = entry.carousel_id
421                 local caridx = config.getCarouselIndex(carid)
422                 local choices = entry.items
423                 if type(choices) == "function" then
424                         choices = choices()
425                 end
426                 if #choices < caridx then
427                         caridx = 1
428                 end
429                 return entry.name(caridx, choices[caridx], choices)
430         end,
431 }
432
433 branddefs = {
434         -- Indexed by valid values for loader_brand in loader.conf(5). Valid
435         -- keys are: graphic (table depicting graphic)
436         ["fbsd"] = {
437                 graphic = fbsd_brand,
438                 image = "/boot/images/freebsd-brand-rev.png",
439         },
440         ["none"] = {
441                 graphic = none,
442         },
443 }
444
445 logodefs = {
446         -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys
447         -- are: requires_color (boolean), graphic (table depicting graphic), and
448         -- shift (table containing x and y).
449         ["tribute"] = {
450                 graphic = fbsd_brand,
451         },
452         ["tributebw"] = {
453                 graphic = fbsd_brand,
454         },
455         ["none"] = {
456                 graphic = none,
457                 shift = {x = 17, y = 0},
458         },
459 }
460
461 brand_position = {x = 2, y = 1}
462 logo_position = {x = 46, y = 4}
463 menu_position = {x = 5, y = 10}
464 frame_size = {w = 42, h = 13}
465 default_shift = {x = 0, y = 0}
466 shift = default_shift
467
468 -- Module exports
469 drawer.default_brand = 'fbsd'
470 drawer.default_color_logodef = 'orb'
471 drawer.default_bw_logodef = 'orbbw'
472 -- For when things go terribly wrong; this def should be present here in the
473 -- drawer module in case it's a filesystem issue.
474 drawer.default_fallback_logodef = 'none'
475
476 -- These should go away after FreeBSD 13; only available for backwards
477 -- compatibility with old logo- files.
478 function drawer.addBrand(name, def)
479         branddefs[name] = def
480 end
481
482 function drawer.addLogo(name, def)
483         logodefs[name] = def
484 end
485
486 drawer.frame_styles = {
487         -- Indexed by valid values for loader_menu_frame in loader.conf(5).
488         -- All of the keys appearing below must be set for any menu frame style
489         -- added to drawer.frame_styles.
490         ["ascii"] = {
491                 horizontal      = "-",
492                 vertical        = "|",
493                 top_left        = "+",
494                 bottom_left     = "+",
495                 top_right       = "+",
496                 bottom_right    = "+",
497         },
498         ["single"] = {
499                 horizontal      = "\xE2\x94\x80",
500                 vertical        = "\xE2\x94\x82",
501                 top_left        = "\xE2\x94\x8C",
502                 bottom_left     = "\xE2\x94\x94",
503                 top_right       = "\xE2\x94\x90",
504                 bottom_right    = "\xE2\x94\x98",
505         },
506         ["double"] = {
507                 horizontal      = "\xE2\x95\x90",
508                 vertical        = "\xE2\x95\x91",
509                 top_left        = "\xE2\x95\x94",
510                 bottom_left     = "\xE2\x95\x9A",
511                 top_right       = "\xE2\x95\x97",
512                 bottom_right    = "\xE2\x95\x9D",
513         },
514 }
515
516 function drawer.drawscreen(menudef)
517         -- drawlogo() must go first.
518         -- it determines the positions of other elements
519         drawitem(drawlogo)
520         drawitem(drawbrand)
521         drawitem(drawbox)
522         return drawmenu(menudef)
523 end
524
525 return drawer