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