1-- 2-- SPDX-License-Identifier: BSD-2-Clause 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 30local color = require("color") 31local config = require("config") 32local core = require("core") 33local screen = require("screen") 34 35local drawer = {} 36 37local fbsd_brand 38local none 39 40local menu_name_handlers 41local branddefs 42local logodefs 43local brand_position 44local logo_position 45local menu_position 46local frame_size 47local default_shift 48local 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. 57if gfx == nil then 58 gfx = {} 59 gfx.term_drawrect = loader.term_drawrect 60 gfx.term_putimage = loader.term_putimage 61end 62 63local 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 73end 74 75local 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 102end 103 104local function getBranddef(brand) 105 if brand == nil then 106 return nil 107 end 108 -- Look it up 109 local branddef = branddefs[brand] 110 111 -- Try to pull it in 112 if branddef == nil then 113 local res, err = processFile(brand) 114 if not res then 115 -- This fallback should go away after FreeBSD 13. 116 try_include('brand-' .. brand) 117 -- If the fallback also failed, print whatever error 118 -- we encountered in the original processing. 119 if branddefs[brand] == nil then 120 print(err) 121 return nil 122 end 123 end 124 125 branddef = branddefs[brand] 126 end 127 128 return branddef 129end 130 131local function getLogodef(logo) 132 if logo == nil then 133 return nil 134 end 135 -- Look it up 136 local logodef = logodefs[logo] 137 138 -- Try to pull it in 139 if logodef == nil then 140 local res, err = processFile(logo) 141 if not res then 142 -- This fallback should go away after FreeBSD 13. 143 try_include('logo-' .. logo) 144 -- If the fallback also failed, print whatever error 145 -- we encountered in the original processing. 146 if logodefs[logo] == nil then 147 print(err) 148 return nil 149 end 150 end 151 152 logodef = logodefs[logo] 153 end 154 155 return logodef 156end 157 158local function draw(x, y, logo) 159 for i = 1, #logo do 160 screen.setcursor(x, y + i - 1) 161 printc(logo[i]) 162 end 163end 164 165local function drawmenu(menudef) 166 local x = menu_position.x 167 local y = menu_position.y 168 169 x = x + shift.x 170 y = y + shift.y 171 172 -- print the menu and build the alias table 173 local alias_table = {} 174 local entry_num = 0 175 local menu_entries = menudef.entries 176 local effective_line_num = 0 177 if type(menu_entries) == "function" then 178 menu_entries = menu_entries() 179 end 180 for _, e in ipairs(menu_entries) do 181 -- Allow menu items to be conditionally visible by specifying 182 -- a visible function. 183 if e.visible ~= nil and not e.visible() then 184 goto continue 185 end 186 effective_line_num = effective_line_num + 1 187 if e.entry_type ~= core.MENU_SEPARATOR then 188 entry_num = entry_num + 1 189 screen.setcursor(x, y + effective_line_num) 190 191 printc(entry_num .. ". " .. menuEntryName(menudef, e)) 192 193 -- fill the alias table 194 alias_table[tostring(entry_num)] = e 195 if e.alias ~= nil then 196 for _, a in ipairs(e.alias) do 197 alias_table[a] = e 198 end 199 end 200 else 201 screen.setcursor(x, y + effective_line_num) 202 printc(menuEntryName(menudef, e)) 203 end 204 ::continue:: 205 end 206 return alias_table 207end 208 209local function defaultframe() 210 if core.isSerialConsole() then 211 return "ascii" 212 end 213 return "double" 214end 215 216local function drawframe() 217 local x = menu_position.x - 3 218 local y = menu_position.y - 1 219 local w = frame_size.w 220 local h = frame_size.h 221 222 local framestyle = loader.getenv("loader_menu_frame") or defaultframe() 223 local framespec = drawer.frame_styles[framestyle] 224 -- If we don't have a framespec for the current frame style, just don't 225 -- draw a box. 226 if framespec == nil then 227 return false 228 end 229 230 local hl = framespec.horizontal 231 local vl = framespec.vertical 232 233 local tl = framespec.top_left 234 local bl = framespec.bottom_left 235 local tr = framespec.top_right 236 local br = framespec.bottom_right 237 238 x = x + shift.x 239 y = y + shift.y 240 241 if core.isFramebufferConsole() and gfx.term_drawrect ~= nil then 242 gfx.term_drawrect(x, y, x + w, y + h) 243 return true 244 end 245 246 screen.setcursor(x, y); printc(tl) 247 screen.setcursor(x, y + h); printc(bl) 248 screen.setcursor(x + w, y); printc(tr) 249 screen.setcursor(x + w, y + h); printc(br) 250 251 screen.setcursor(x + 1, y) 252 for _ = 1, w - 1 do 253 printc(hl) 254 end 255 256 screen.setcursor(x + 1, y + h) 257 for _ = 1, w - 1 do 258 printc(hl) 259 end 260 261 for i = 1, h - 1 do 262 screen.setcursor(x, y + i) 263 printc(vl) 264 screen.setcursor(x + w, y + i) 265 printc(vl) 266 end 267 return true 268end 269 270local function drawbox() 271 local x = menu_position.x - 3 272 local y = menu_position.y - 1 273 local w = frame_size.w 274 local menu_header = loader.getenv("loader_menu_title") or 275 "Welcome to FreeBSD" 276 local menu_header_align = loader.getenv("loader_menu_title_align") 277 local menu_header_x 278 279 x = x + shift.x 280 y = y + shift.y 281 282 if drawframe(x, y, w) == false then 283 return 284 end 285 286 if menu_header_align ~= nil then 287 menu_header_align = menu_header_align:lower() 288 if menu_header_align == "left" then 289 -- Just inside the left border on top 290 menu_header_x = x + 1 291 elseif menu_header_align == "right" then 292 -- Just inside the right border on top 293 menu_header_x = x + w - #menu_header 294 end 295 end 296 if menu_header_x == nil then 297 menu_header_x = x + (w // 2) - (#menu_header // 2) 298 end 299 screen.setcursor(menu_header_x - 1, y) 300 if menu_header ~= "" then 301 printc(" " .. menu_header .. " ") 302 end 303 304end 305 306local function drawbrand() 307 local x = tonumber(loader.getenv("loader_brand_x")) or 308 brand_position.x 309 local y = tonumber(loader.getenv("loader_brand_y")) or 310 brand_position.y 311 312 local branddef = getBranddef(loader.getenv("loader_brand")) 313 314 if branddef == nil then 315 branddef = getBranddef(drawer.default_brand) 316 end 317 318 local graphic = branddef.graphic 319 320 x = x + shift.x 321 y = y + shift.y 322 if branddef.shift ~= nil then 323 x = x + branddef.shift.x 324 y = y + branddef.shift.y 325 end 326 327 if core.isFramebufferConsole() and 328 gfx.term_putimage ~= nil and 329 branddef.image ~= nil then 330 if gfx.term_putimage(branddef.image, 1, 1, 0, 7, 0) 331 then 332 return true 333 end 334 end 335 draw(x, y, graphic) 336end 337 338local function drawlogo() 339 local x = tonumber(loader.getenv("loader_logo_x")) or 340 logo_position.x 341 local y = tonumber(loader.getenv("loader_logo_y")) or 342 logo_position.y 343 344 local logo = loader.getenv("loader_logo") 345 local colored = color.isEnabled() 346 347 local logodef = getLogodef(logo) 348 349 if logodef == nil or logodef.graphic == nil or 350 (not colored and logodef.requires_color) then 351 -- Choose a sensible default 352 if colored then 353 logodef = getLogodef(drawer.default_color_logodef) 354 else 355 logodef = getLogodef(drawer.default_bw_logodef) 356 end 357 358 -- Something has gone terribly wrong. 359 if logodef == nil then 360 logodef = getLogodef(drawer.default_fallback_logodef) 361 end 362 end 363 364 if logodef ~= nil and logodef.graphic == none then 365 shift = logodef.shift 366 else 367 shift = default_shift 368 end 369 370 x = x + shift.x 371 y = y + shift.y 372 373 if logodef ~= nil and logodef.shift ~= nil then 374 x = x + logodef.shift.x 375 y = y + logodef.shift.y 376 end 377 378 if core.isFramebufferConsole() and 379 gfx.term_putimage ~= nil and 380 logodef.image ~= nil then 381 local y1 = 15 382 383 if logodef.image_rl ~= nil then 384 y1 = logodef.image_rl 385 end 386 if gfx.term_putimage(logodef.image, x, y, 0, y + y1, 0) 387 then 388 return true 389 end 390 end 391 draw(x, y, logodef.graphic) 392end 393 394local function drawitem(func) 395 local console = loader.getenv("console") 396 397 for c in string.gmatch(console, "%w+") do 398 loader.setenv("console", c) 399 func() 400 end 401 loader.setenv("console", console) 402end 403 404fbsd_brand = { 405" ______ ____ _____ _____ ", 406" | ____| | _ \\ / ____| __ \\ ", 407" | |___ _ __ ___ ___ | |_) | (___ | | | |", 408" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 409" | | | | | __/ __/| |_) |____) | |__| |", 410" | | | | | | || | | |", 411" |_| |_| \\___|\\___||____/|_____/|_____/ " 412} 413none = {""} 414 415menu_name_handlers = { 416 -- Menu name handlers should take the menu being drawn and entry being 417 -- drawn as parameters, and return the name of the item. 418 -- This is designed so that everything, including menu separators, may 419 -- have their names derived differently. The default action for entry 420 -- types not specified here is to use entry.name directly. 421 [core.MENU_SEPARATOR] = function(_, entry) 422 if entry.name ~= nil then 423 if type(entry.name) == "function" then 424 return entry.name() 425 end 426 return entry.name 427 end 428 return "" 429 end, 430 [core.MENU_CAROUSEL_ENTRY] = function(_, entry) 431 local carid = entry.carousel_id 432 local caridx = config.getCarouselIndex(carid) 433 local choices = entry.items 434 if type(choices) == "function" then 435 choices = choices() 436 end 437 if #choices < caridx then 438 caridx = 1 439 end 440 return entry.name(caridx, choices[caridx], choices) 441 end, 442} 443 444branddefs = { 445 -- Indexed by valid values for loader_brand in loader.conf(5). Valid 446 -- keys are: graphic (table depicting graphic) 447 ["fbsd"] = { 448 graphic = fbsd_brand, 449 image = "/boot/images/freebsd-brand-rev.png", 450 }, 451 ["none"] = { 452 graphic = none, 453 }, 454} 455 456logodefs = { 457 -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys 458 -- are: requires_color (boolean), graphic (table depicting graphic), and 459 -- shift (table containing x and y). 460 ["tribute"] = { 461 graphic = fbsd_brand, 462 }, 463 ["tributebw"] = { 464 graphic = fbsd_brand, 465 }, 466 ["none"] = { 467 graphic = none, 468 shift = {x = 17, y = 0}, 469 }, 470} 471 472brand_position = {x = 2, y = 1} 473logo_position = {x = 46, y = 4} 474menu_position = {x = 5, y = 10} 475frame_size = {w = 42, h = 14} 476default_shift = {x = 0, y = 0} 477shift = default_shift 478 479-- Module exports 480drawer.default_brand = 'fbsd' 481drawer.default_color_logodef = 'orb' 482drawer.default_bw_logodef = 'orbbw' 483-- For when things go terribly wrong; this def should be present here in the 484-- drawer module in case it's a filesystem issue. 485drawer.default_fallback_logodef = 'none' 486 487-- These should go away after FreeBSD 13; only available for backwards 488-- compatibility with old logo- files. 489function drawer.addBrand(name, def) 490 branddefs[name] = def 491end 492 493function drawer.addLogo(name, def) 494 logodefs[name] = def 495end 496 497drawer.frame_styles = { 498 -- Indexed by valid values for loader_menu_frame in loader.conf(5). 499 -- All of the keys appearing below must be set for any menu frame style 500 -- added to drawer.frame_styles. 501 ["ascii"] = { 502 horizontal = "-", 503 vertical = "|", 504 top_left = "+", 505 bottom_left = "+", 506 top_right = "+", 507 bottom_right = "+", 508 }, 509} 510 511if core.hasUnicode() then 512 -- unicode based framing characters 513 drawer.frame_styles["single"] = { 514 horizontal = "\xE2\x94\x80", 515 vertical = "\xE2\x94\x82", 516 top_left = "\xE2\x94\x8C", 517 bottom_left = "\xE2\x94\x94", 518 top_right = "\xE2\x94\x90", 519 bottom_right = "\xE2\x94\x98", 520 } 521 drawer.frame_styles["double"] = { 522 horizontal = "\xE2\x95\x90", 523 vertical = "\xE2\x95\x91", 524 top_left = "\xE2\x95\x94", 525 bottom_left = "\xE2\x95\x9A", 526 top_right = "\xE2\x95\x97", 527 bottom_right = "\xE2\x95\x9D", 528 } 529else 530 -- non-unicode cons25-style framing characters 531 drawer.frame_styles["single"] = { 532 horizontal = "\xC4", 533 vertical = "\xB3", 534 top_left = "\xDA", 535 bottom_left = "\xC0", 536 top_right = "\xBF", 537 bottom_right = "\xD9", 538 } 539 drawer.frame_styles["double"] = { 540 horizontal = "\xCD", 541 vertical = "\xBA", 542 top_left = "\xC9", 543 bottom_left = "\xC8", 544 top_right = "\xBB", 545 bottom_right = "\xBC", 546 } 547end 548 549function drawer.drawscreen(menudef) 550 -- drawlogo() must go first. 551 -- it determines the positions of other elements 552 drawitem(drawlogo) 553 drawitem(drawbrand) 554 drawitem(drawbox) 555 return drawmenu(menudef) 556end 557 558return drawer 559