Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/stand/lua/drawer.lua
34680 views
1
--
2
-- SPDX-License-Identifier: BSD-2-Clause
3
--
4
-- Copyright (c) 2015 Pedro Souza <[email protected]>
5
-- Copyright (c) 2018 Kyle Evans <[email protected]>
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
30
local color = require("color")
31
local config = require("config")
32
local core = require("core")
33
local screen = require("screen")
34
35
local drawer = {}
36
37
local fbsd_brand
38
local none
39
40
local menu_name_handlers
41
local branddefs
42
local logodefs
43
local brand_position
44
local logo_position
45
local menu_position
46
local frame_size
47
local default_shift
48
local shift
49
50
-- Make this code compatible with older loader binaries. We moved the term_*
51
-- functions from loader to the gfx. if we're running on an older loader that
52
-- has these functions, create aliases for them in gfx. The loader binary might
53
-- be so old as to not have them, but in that case, we want to copy the nil
54
-- values. The new loader will provide loader.* versions of all the gfx.*
55
-- functions for backwards compatibility, so we only define the functions we use
56
-- here.
57
if gfx == nil then
58
gfx = {}
59
gfx.term_drawrect = loader.term_drawrect
60
gfx.term_putimage = loader.term_putimage
61
end
62
63
local function menuEntryName(drawing_menu, entry)
64
local name_handler = menu_name_handlers[entry.entry_type]
65
66
if name_handler ~= nil then
67
return name_handler(drawing_menu, entry)
68
end
69
if type(entry.name) == "function" then
70
return entry.name()
71
end
72
return entry.name
73
end
74
75
local function processFile(gfxname)
76
if gfxname == nil then
77
return false, "Missing filename"
78
end
79
80
local ret = try_include('gfx-' .. gfxname)
81
if ret == nil then
82
return false, "Failed to include gfx-" .. gfxname
83
end
84
85
-- Legacy format
86
if type(ret) ~= "table" then
87
return true
88
end
89
90
for gfxtype, def in pairs(ret) do
91
if gfxtype == "brand" then
92
drawer.addBrand(gfxname, def)
93
elseif gfxtype == "logo" then
94
drawer.addLogo(gfxname, def)
95
else
96
return false, "Unknown graphics type '" .. gfxtype ..
97
"'"
98
end
99
end
100
101
return true
102
end
103
104
-- Backwards compatibility shims for previous FreeBSD versions, please document
105
-- new additions
106
local function adapt_fb_shim(def)
107
-- In FreeBSD 14.x+, we have improved framebuffer support in the loader
108
-- and some graphics may have images that we can actually draw on the
109
-- screen. Those graphics may come with shifts that are distinct from
110
-- the ASCII version, so we move both ascii and image versions into
111
-- their own tables.
112
if not def.ascii then
113
def.ascii = {
114
image = def.graphic,
115
requires_color = def.requires_color,
116
shift = def.shift,
117
}
118
end
119
if def.image then
120
assert(not def.fb,
121
"Unrecognized graphic definition format")
122
123
-- Legacy images may have adapted a shift from the ASCII
124
-- version, or perhaps we just didn't care enough to adjust it.
125
-- Steal the shift.
126
def.fb = {
127
image = def.image,
128
width = def.image_rl,
129
shift = def.shift,
130
}
131
end
132
133
def.adapted = true
134
return def
135
end
136
137
local function getBranddef(brand)
138
if brand == nil then
139
return nil
140
end
141
-- Look it up
142
local branddef = branddefs[brand]
143
144
-- Try to pull it in
145
if branddef == nil then
146
local res, err = processFile(brand)
147
if not res then
148
-- This fallback should go away after FreeBSD 13.
149
try_include('brand-' .. brand)
150
-- If the fallback also failed, print whatever error
151
-- we encountered in the original processing.
152
if branddefs[brand] == nil then
153
print(err)
154
return nil
155
end
156
end
157
158
branddef = branddefs[brand]
159
elseif not branddef.adapted then
160
adapt_fb_shim(branddef)
161
end
162
163
return branddef
164
end
165
166
local function getLogodef(logo)
167
if logo == nil then
168
return nil
169
end
170
-- Look it up
171
local logodef = logodefs[logo]
172
173
-- Try to pull it in
174
if logodef == nil then
175
local res, err = processFile(logo)
176
if not res then
177
-- This fallback should go away after FreeBSD 13.
178
try_include('logo-' .. logo)
179
-- If the fallback also failed, print whatever error
180
-- we encountered in the original processing.
181
if logodefs[logo] == nil then
182
print(err)
183
return nil
184
end
185
end
186
187
logodef = logodefs[logo]
188
elseif not logodef.adapted then
189
adapt_fb_shim(logodef)
190
end
191
192
return logodef
193
end
194
195
local function draw(x, y, logo)
196
for i = 1, #logo do
197
screen.setcursor(x, y + i - 1)
198
printc(logo[i])
199
end
200
end
201
202
local function drawmenu(menudef)
203
local x = menu_position.x
204
local y = menu_position.y
205
206
if string.lower(loader.getenv("loader_menu") or "") == "none" then
207
return
208
end
209
210
x = x + shift.x
211
y = y + shift.y
212
213
-- print the menu and build the alias table
214
local alias_table = {}
215
local entry_num = 0
216
local menu_entries = menudef.entries
217
local effective_line_num = 0
218
if type(menu_entries) == "function" then
219
menu_entries = menu_entries()
220
end
221
for _, e in ipairs(menu_entries) do
222
-- Allow menu items to be conditionally visible by specifying
223
-- a visible function.
224
if e.visible ~= nil and not e.visible() then
225
goto continue
226
end
227
effective_line_num = effective_line_num + 1
228
if e.entry_type ~= core.MENU_SEPARATOR then
229
entry_num = entry_num + 1
230
screen.setcursor(x, y + effective_line_num)
231
232
printc(entry_num .. ". " .. menuEntryName(menudef, e))
233
234
-- fill the alias table
235
alias_table[tostring(entry_num)] = e
236
if e.alias ~= nil then
237
for _, a in ipairs(e.alias) do
238
alias_table[a] = e
239
end
240
end
241
else
242
screen.setcursor(x, y + effective_line_num)
243
printc(menuEntryName(menudef, e))
244
end
245
::continue::
246
end
247
return alias_table
248
end
249
250
local function defaultframe()
251
if core.isSerialConsole() then
252
return "ascii"
253
end
254
return "double"
255
end
256
257
local function gfxenabled()
258
return (loader.getenv("loader_gfx") or "yes"):lower() ~= "no"
259
end
260
local function gfxcapable()
261
return core.isFramebufferConsole() and gfx.term_putimage
262
end
263
264
local function drawframe()
265
local x = menu_position.x - 3
266
local y = menu_position.y - 1
267
local w = frame_size.w
268
local h = frame_size.h
269
270
local framestyle = loader.getenv("loader_menu_frame") or defaultframe()
271
local framespec = drawer.frame_styles[framestyle]
272
-- If we don't have a framespec for the current frame style, just don't
273
-- draw a box.
274
if framespec == nil then
275
return false
276
end
277
278
local hl = framespec.horizontal
279
local vl = framespec.vertical
280
281
local tl = framespec.top_left
282
local bl = framespec.bottom_left
283
local tr = framespec.top_right
284
local br = framespec.bottom_right
285
286
x = x + shift.x
287
y = y + shift.y
288
289
if gfxenabled() and gfxcapable() then
290
gfx.term_drawrect(x, y, x + w, y + h)
291
return true
292
end
293
294
screen.setcursor(x, y); printc(tl)
295
screen.setcursor(x, y + h); printc(bl)
296
screen.setcursor(x + w, y); printc(tr)
297
screen.setcursor(x + w, y + h); printc(br)
298
299
screen.setcursor(x + 1, y)
300
for _ = 1, w - 1 do
301
printc(hl)
302
end
303
304
screen.setcursor(x + 1, y + h)
305
for _ = 1, w - 1 do
306
printc(hl)
307
end
308
309
for i = 1, h - 1 do
310
screen.setcursor(x, y + i)
311
printc(vl)
312
screen.setcursor(x + w, y + i)
313
printc(vl)
314
end
315
return true
316
end
317
318
local function drawbox()
319
local x = menu_position.x - 3
320
local y = menu_position.y - 1
321
local w = frame_size.w
322
local menu_header = loader.getenv("loader_menu_title") or
323
"Welcome to FreeBSD"
324
local menu_header_align = loader.getenv("loader_menu_title_align")
325
local menu_header_x
326
327
if string.lower(loader.getenv("loader_menu") or "") == "none" then
328
return
329
end
330
331
x = x + shift.x
332
y = y + shift.y
333
334
if drawframe(x, y, w) == false then
335
return
336
end
337
338
if menu_header_align ~= nil then
339
menu_header_align = menu_header_align:lower()
340
if menu_header_align == "left" then
341
-- Just inside the left border on top
342
menu_header_x = x + 1
343
elseif menu_header_align == "right" then
344
-- Just inside the right border on top
345
menu_header_x = x + w - #menu_header
346
end
347
end
348
if menu_header_x == nil then
349
menu_header_x = x + (w // 2) - (#menu_header // 2)
350
end
351
screen.setcursor(menu_header_x - 1, y)
352
if menu_header ~= "" then
353
printc(" " .. menu_header .. " ")
354
end
355
356
end
357
358
local function drawbrand()
359
local x = tonumber(loader.getenv("loader_brand_x")) or
360
brand_position.x
361
local y = tonumber(loader.getenv("loader_brand_y")) or
362
brand_position.y
363
364
local branddef = getBranddef(loader.getenv("loader_brand"))
365
366
if branddef == nil then
367
branddef = getBranddef(drawer.default_brand)
368
end
369
370
local graphic = branddef.ascii.image
371
372
x = x + shift.x
373
y = y + shift.y
374
375
local gfx_requested = branddef.fb and gfxenabled()
376
if gfx_requested and gfxcapable() then
377
if branddef.fb.shift then
378
x = x + (branddef.fb.shift.x or 0)
379
y = y + (branddef.fb.shift.y or 0)
380
end
381
if gfx.term_putimage(branddef.fb.image, x, y, 0, 7, 0) then
382
return true
383
end
384
elseif branddef.ascii.shift then
385
x = x + (branddef.ascii.shift.x or 0)
386
y = y + (branddef.ascii.shift.y or 0)
387
end
388
draw(x, y, graphic)
389
end
390
391
local function drawlogo()
392
local x = tonumber(loader.getenv("loader_logo_x")) or
393
logo_position.x
394
local y = tonumber(loader.getenv("loader_logo_y")) or
395
logo_position.y
396
397
local logo = loader.getenv("loader_logo")
398
local colored = color.isEnabled()
399
400
local logodef = getLogodef(logo)
401
402
if logodef == nil or logodef.ascii == nil or
403
(not colored and logodef.ascii.requires_color) then
404
-- Choose a sensible default
405
if colored then
406
logodef = getLogodef(drawer.default_color_logodef)
407
else
408
logodef = getLogodef(drawer.default_bw_logodef)
409
end
410
411
-- Something has gone terribly wrong.
412
if logodef == nil then
413
logodef = getLogodef(drawer.default_fallback_logodef)
414
end
415
end
416
417
-- This is a special little hack for the "none" logo to re-align the
418
-- menu and the brand to avoid having a lot of extraneous whitespace on
419
-- the right side.
420
if logodef and logodef.ascii.image == none then
421
shift = logodef.shift
422
else
423
shift = default_shift
424
end
425
426
x = x + shift.x
427
y = y + shift.y
428
429
local gfx_requested = logodef.fb and gfxenabled()
430
if gfx_requested and gfxcapable() then
431
local y1 = logodef.fb.width or 15
432
433
if logodef.fb.shift then
434
x = x + (logodef.fb.shift.x or 0)
435
y = y + (logodef.fb.shift.y or 0)
436
end
437
if gfx.term_putimage(logodef.fb.image, x, y, 0, y + y1, 0) then
438
return true
439
end
440
elseif logodef.ascii.shift then
441
x = x + (logodef.ascii.shift.x or 0)
442
y = y + (logodef.ascii.shift.y or 0)
443
end
444
445
draw(x, y, logodef.ascii.image)
446
end
447
448
local function drawitem(func)
449
local console = loader.getenv("console")
450
451
for c in string.gmatch(console, "%w+") do
452
loader.setenv("console", c)
453
func()
454
end
455
loader.setenv("console", console)
456
end
457
458
fbsd_brand = {
459
" ______ ____ _____ _____ ",
460
" | ____| | _ \\ / ____| __ \\ ",
461
" | |___ _ __ ___ ___ | |_) | (___ | | | |",
462
" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |",
463
" | | | | | __/ __/| |_) |____) | |__| |",
464
" | | | | | | || | | |",
465
" |_| |_| \\___|\\___||____/|_____/|_____/ "
466
}
467
none = {""}
468
469
menu_name_handlers = {
470
-- Menu name handlers should take the menu being drawn and entry being
471
-- drawn as parameters, and return the name of the item.
472
-- This is designed so that everything, including menu separators, may
473
-- have their names derived differently. The default action for entry
474
-- types not specified here is to use entry.name directly.
475
[core.MENU_SEPARATOR] = function(_, entry)
476
if entry.name ~= nil then
477
if type(entry.name) == "function" then
478
return entry.name()
479
end
480
return entry.name
481
end
482
return ""
483
end,
484
[core.MENU_CAROUSEL_ENTRY] = function(_, entry)
485
local carid = entry.carousel_id
486
local caridx = config.getCarouselIndex(carid)
487
local choices = entry.items
488
if type(choices) == "function" then
489
choices = choices()
490
end
491
if #choices < caridx then
492
caridx = 1
493
end
494
return entry.name(caridx, choices[caridx], choices)
495
end,
496
}
497
498
branddefs = {
499
-- Indexed by valid values for loader_brand in loader.conf(5). Valid
500
-- keys are: graphic (table depicting graphic)
501
["fbsd"] = {
502
ascii = {
503
image = fbsd_brand,
504
},
505
fb = {
506
image = "/boot/images/freebsd-brand-rev.png",
507
},
508
},
509
["none"] = {
510
ascii = { image = none },
511
},
512
}
513
514
logodefs = {
515
-- Indexed by valid values for loader_logo in loader.conf(5). Valid keys
516
-- are: requires_color (boolean), graphic (table depicting graphic), and
517
-- shift (table containing x and y).
518
["tribute"] = {
519
ascii = {
520
image = fbsd_brand,
521
},
522
},
523
["tributebw"] = {
524
ascii = {
525
image = fbsd_brand,
526
},
527
},
528
["none"] = {
529
ascii = {
530
image = none,
531
},
532
shift = {x = 17, y = 0},
533
},
534
}
535
536
brand_position = {x = 2, y = 1}
537
logo_position = {x = 40, y = 10}
538
menu_position = {x = 5, y = 10}
539
frame_size = {w = 39, h = 14}
540
default_shift = {x = 0, y = 0}
541
shift = default_shift
542
543
-- Module exports
544
drawer.default_brand = 'fbsd'
545
drawer.default_color_logodef = 'orb'
546
drawer.default_bw_logodef = 'orbbw'
547
-- For when things go terribly wrong; this def should be present here in the
548
-- drawer module in case it's a filesystem issue.
549
drawer.default_fallback_logodef = 'none'
550
551
function drawer.addBrand(name, def)
552
branddefs[name] = adapt_fb_shim(def)
553
end
554
555
function drawer.addLogo(name, def)
556
logodefs[name] = adapt_fb_shim(def)
557
end
558
559
drawer.frame_styles = {
560
-- Indexed by valid values for loader_menu_frame in loader.conf(5).
561
-- All of the keys appearing below must be set for any menu frame style
562
-- added to drawer.frame_styles.
563
["ascii"] = {
564
horizontal = "-",
565
vertical = "|",
566
top_left = "+",
567
bottom_left = "+",
568
top_right = "+",
569
bottom_right = "+",
570
},
571
}
572
573
if core.hasUnicode() then
574
-- unicode based framing characters
575
drawer.frame_styles["single"] = {
576
horizontal = "\xE2\x94\x80",
577
vertical = "\xE2\x94\x82",
578
top_left = "\xE2\x94\x8C",
579
bottom_left = "\xE2\x94\x94",
580
top_right = "\xE2\x94\x90",
581
bottom_right = "\xE2\x94\x98",
582
}
583
drawer.frame_styles["double"] = {
584
horizontal = "\xE2\x95\x90",
585
vertical = "\xE2\x95\x91",
586
top_left = "\xE2\x95\x94",
587
bottom_left = "\xE2\x95\x9A",
588
top_right = "\xE2\x95\x97",
589
bottom_right = "\xE2\x95\x9D",
590
}
591
else
592
-- non-unicode cons25-style framing characters
593
drawer.frame_styles["single"] = {
594
horizontal = "\xC4",
595
vertical = "\xB3",
596
top_left = "\xDA",
597
bottom_left = "\xC0",
598
top_right = "\xBF",
599
bottom_right = "\xD9",
600
}
601
drawer.frame_styles["double"] = {
602
horizontal = "\xCD",
603
vertical = "\xBA",
604
top_left = "\xC9",
605
bottom_left = "\xC8",
606
top_right = "\xBB",
607
bottom_right = "\xBC",
608
}
609
end
610
611
function drawer.drawscreen(menudef)
612
-- drawlogo() must go first.
613
-- it determines the positions of other elements
614
drawitem(drawlogo)
615
drawitem(drawbrand)
616
drawitem(drawbox)
617
return drawmenu(menudef)
618
end
619
620
return drawer
621
622