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 32local color = require("color") 33local config = require("config") 34local core = require("core") 35local screen = require("screen") 36 37local drawer = {} 38 39local fbsd_brand 40local none 41 42local menu_name_handlers 43local branddefs 44local logodefs 45local brand_position 46local logo_position 47local menu_position 48local frame_size 49local default_shift 50local shift 51 52local 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 62end 63 64local 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 91end 92 93local 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 118end 119 120local 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 145end 146 147local function draw(x, y, logo) 148 for i = 1, #logo do 149 screen.setcursor(x, y + i - 1) 150 printc(logo[i]) 151 end 152end 153 154local 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 196end 197 198local function defaultframe() 199 if core.isSerialConsole() then 200 return "ascii" 201 end 202 return "double" 203end 204 205local 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 257end 258 259local 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 293end 294 295local 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) 325end 326 327local 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) 381end 382 383local 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) 391end 392 393fbsd_brand = { 394" ______ ____ _____ _____ ", 395" | ____| | _ \\ / ____| __ \\ ", 396" | |___ _ __ ___ ___ | |_) | (___ | | | |", 397" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 398" | | | | | __/ __/| |_) |____) | |__| |", 399" | | | | | | || | | |", 400" |_| |_| \\___|\\___||____/|_____/|_____/ " 401} 402none = {""} 403 404menu_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 433branddefs = { 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 445logodefs = { 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 461brand_position = {x = 2, y = 1} 462logo_position = {x = 46, y = 4} 463menu_position = {x = 5, y = 10} 464frame_size = {w = 42, h = 13} 465default_shift = {x = 0, y = 0} 466shift = default_shift 467 468-- Module exports 469drawer.default_brand = 'fbsd' 470drawer.default_color_logodef = 'orb' 471drawer.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. 474drawer.default_fallback_logodef = 'none' 475 476-- These should go away after FreeBSD 13; only available for backwards 477-- compatibility with old logo- files. 478function drawer.addBrand(name, def) 479 branddefs[name] = def 480end 481 482function drawer.addLogo(name, def) 483 logodefs[name] = def 484end 485 486drawer.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 516function 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) 523end 524 525return drawer 526