]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/lua/drawer.lua
lualoader: improve the design of the brand-/logo- mechanism
[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 drawbox()
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
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         screen.setcursor(x, y); printc(tl)
231         screen.setcursor(x, y + h); printc(bl)
232         screen.setcursor(x + w, y); printc(tr)
233         screen.setcursor(x + w, y + h); printc(br)
234
235         screen.setcursor(x + 1, y)
236         for _ = 1, w - 1 do
237                 printc(hl)
238         end
239
240         screen.setcursor(x + 1, y + h)
241         for _ = 1, w - 1 do
242                 printc(hl)
243         end
244
245         for i = 1, h - 1 do
246                 screen.setcursor(x, y + i)
247                 printc(vl)
248                 screen.setcursor(x + w, y + i)
249                 printc(vl)
250         end
251
252         local menu_header = loader.getenv("loader_menu_title") or
253             "Welcome to FreeBSD"
254         local menu_header_align = loader.getenv("loader_menu_title_align")
255         local menu_header_x
256
257         if menu_header_align ~= nil then
258                 menu_header_align = menu_header_align:lower()
259                 if menu_header_align == "left" then
260                         -- Just inside the left border on top
261                         menu_header_x = x + 1
262                 elseif menu_header_align == "right" then
263                         -- Just inside the right border on top
264                         menu_header_x = x + w - #menu_header
265                 end
266         end
267         if menu_header_x == nil then
268                 menu_header_x = x + (w / 2) - (#menu_header / 2)
269         end
270         screen.setcursor(menu_header_x, y)
271         printc(menu_header)
272 end
273
274 local function drawbrand()
275         local x = tonumber(loader.getenv("loader_brand_x")) or
276             brand_position.x
277         local y = tonumber(loader.getenv("loader_brand_y")) or
278             brand_position.y
279
280         local branddef = getBranddef(loader.getenv("loader_brand"))
281
282         if branddef == nil then
283                 branddef = getBranddef(drawer.default_brand)
284         end
285
286         local graphic = branddef.graphic
287
288         x = x + shift.x
289         y = y + shift.y
290         draw(x, y, graphic)
291 end
292
293 local function drawlogo()
294         local x = tonumber(loader.getenv("loader_logo_x")) or
295             logo_position.x
296         local y = tonumber(loader.getenv("loader_logo_y")) or
297             logo_position.y
298
299         local logo = loader.getenv("loader_logo")
300         local colored = color.isEnabled()
301
302         local logodef = getLogodef(logo)
303
304         if logodef == nil or logodef.graphic == nil or
305             (not colored and logodef.requires_color) then
306                 -- Choose a sensible default
307                 if colored then
308                         logodef = getLogodef(drawer.default_color_logodef)
309                 else
310                         logodef = getLogodef(drawer.default_bw_logodef)
311                 end
312
313                 -- Something has gone terribly wrong.
314                 if logodef == nil then
315                         logodef = getLogodef(drawer.default_fallback_logodef)
316                 end
317         end
318
319         if logodef ~= nil and logodef.graphic == none then
320                 shift = logodef.shift
321         else
322                 shift = default_shift
323         end
324
325         x = x + shift.x
326         y = y + shift.y
327
328         if logodef ~= nil and logodef.shift ~= nil then
329                 x = x + logodef.shift.x
330                 y = y + logodef.shift.y
331         end
332
333         draw(x, y, logodef.graphic)
334 end
335
336 fbsd_brand = {
337 "  ______               ____   _____ _____  ",
338 " |  ____|             |  _ \\ / ____|  __ \\ ",
339 " | |___ _ __ ___  ___ | |_) | (___ | |  | |",
340 " |  ___| '__/ _ \\/ _ \\|  _ < \\___ \\| |  | |",
341 " | |   | | |  __/  __/| |_) |____) | |__| |",
342 " | |   | | |    |    ||     |      |      |",
343 " |_|   |_|  \\___|\\___||____/|_____/|_____/ "
344 }
345 none = {""}
346
347 menu_name_handlers = {
348         -- Menu name handlers should take the menu being drawn and entry being
349         -- drawn as parameters, and return the name of the item.
350         -- This is designed so that everything, including menu separators, may
351         -- have their names derived differently. The default action for entry
352         -- types not specified here is to use entry.name directly.
353         [core.MENU_SEPARATOR] = function(_, entry)
354                 if entry.name ~= nil then
355                         if type(entry.name) == "function" then
356                                 return entry.name()
357                         end
358                         return entry.name
359                 end
360                 return ""
361         end,
362         [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
363                 local carid = entry.carousel_id
364                 local caridx = config.getCarouselIndex(carid)
365                 local choices = entry.items
366                 if type(choices) == "function" then
367                         choices = choices()
368                 end
369                 if #choices < caridx then
370                         caridx = 1
371                 end
372                 return entry.name(caridx, choices[caridx], choices)
373         end,
374 }
375
376 branddefs = {
377         -- Indexed by valid values for loader_brand in loader.conf(5). Valid
378         -- keys are: graphic (table depicting graphic)
379         ["fbsd"] = {
380                 graphic = fbsd_brand,
381         },
382         ["none"] = {
383                 graphic = none,
384         },
385 }
386
387 logodefs = {
388         -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys
389         -- are: requires_color (boolean), graphic (table depicting graphic), and
390         -- shift (table containing x and y).
391         ["tribute"] = {
392                 graphic = fbsd_brand,
393         },
394         ["tributebw"] = {
395                 graphic = fbsd_brand,
396         },
397         ["none"] = {
398                 graphic = none,
399                 shift = {x = 17, y = 0},
400         },
401 }
402
403 brand_position = {x = 2, y = 1}
404 logo_position = {x = 46, y = 4}
405 menu_position = {x = 5, y = 10}
406 frame_size = {w = 42, h = 13}
407 default_shift = {x = 0, y = 0}
408 shift = default_shift
409
410 -- Module exports
411 drawer.default_brand = 'fbsd'
412 drawer.default_color_logodef = 'orb'
413 drawer.default_bw_logodef = 'orbbw'
414 -- For when things go terribly wrong; this def should be present here in the
415 -- drawer module in case it's a filesystem issue.
416 drawer.default_fallback_logodef = 'none'
417
418 -- These should go away after FreeBSD 13; only available for backwards
419 -- compatibility with old logo- files.
420 function drawer.addBrand(name, def)
421         branddefs[name] = def
422 end
423
424 function drawer.addLogo(name, def)
425         logodefs[name] = def
426 end
427
428 drawer.frame_styles = {
429         -- Indexed by valid values for loader_menu_frame in loader.conf(5).
430         -- All of the keys appearing below must be set for any menu frame style
431         -- added to drawer.frame_styles.
432         ["ascii"] = {
433                 horizontal      = "-",
434                 vertical        = "|",
435                 top_left        = "+",
436                 bottom_left     = "+",
437                 top_right       = "+",
438                 bottom_right    = "+",
439         },
440         ["single"] = {
441                 horizontal      = "\xE2\x94\x80",
442                 vertical        = "\xE2\x94\x82",
443                 top_left        = "\xE2\x94\x8C",
444                 bottom_left     = "\xE2\x94\x94",
445                 top_right       = "\xE2\x94\x90",
446                 bottom_right    = "\xE2\x94\x98",
447         },
448         ["double"] = {
449                 horizontal      = "\xE2\x95\x90",
450                 vertical        = "\xE2\x95\x91",
451                 top_left        = "\xE2\x95\x94",
452                 bottom_left     = "\xE2\x95\x9A",
453                 top_right       = "\xE2\x95\x97",
454                 bottom_right    = "\xE2\x95\x9D",
455         },
456 }
457
458 function drawer.drawscreen(menudef)
459         -- drawlogo() must go first.
460         -- it determines the positions of other elements
461         drawlogo()
462         drawbrand()
463         drawbox()
464         return drawmenu(menudef)
465 end
466
467 return drawer