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 core.isFramebufferConsole() and 312 loader.term_putimage ~= nil and 313 branddef.image ~= nil then 314 if loader.term_putimage(branddef.image, 1, 1, 0, 7, 0) 315 then 316 return true 317 end 318 end 319 draw(x, y, graphic) 320end 321 322local function drawlogo() 323 local x = tonumber(loader.getenv("loader_logo_x")) or 324 logo_position.x 325 local y = tonumber(loader.getenv("loader_logo_y")) or 326 logo_position.y 327 328 local logo = loader.getenv("loader_logo") 329 local colored = color.isEnabled() 330 331 local logodef = getLogodef(logo) 332 333 if logodef == nil or logodef.graphic == nil or 334 (not colored and logodef.requires_color) then 335 -- Choose a sensible default 336 if colored then 337 logodef = getLogodef(drawer.default_color_logodef) 338 else 339 logodef = getLogodef(drawer.default_bw_logodef) 340 end 341 342 -- Something has gone terribly wrong. 343 if logodef == nil then 344 logodef = getLogodef(drawer.default_fallback_logodef) 345 end 346 end 347 348 if logodef ~= nil and logodef.graphic == none then 349 shift = logodef.shift 350 else 351 shift = default_shift 352 end 353 354 x = x + shift.x 355 y = y + shift.y 356 357 if logodef ~= nil and logodef.shift ~= nil then 358 x = x + logodef.shift.x 359 y = y + logodef.shift.y 360 end 361 362 if core.isFramebufferConsole() and 363 loader.term_putimage ~= nil and 364 logodef.image ~= nil then 365 local y1 = 15 366 367 if logodef.image_rl ~= nil then 368 y1 = logodef.image_rl 369 end 370 if loader.term_putimage(logodef.image, x, y, 0, y + y1, 0) 371 then 372 return true 373 end 374 end 375 draw(x, y, logodef.graphic) 376end 377 378local function drawitem(func) 379 local console = loader.getenv("console") 380 local c 381 382 for c in string.gmatch(console, "%w+") do 383 loader.setenv("console", c) 384 func() 385 end 386 loader.setenv("console", console) 387end 388 389fbsd_brand = { 390" ______ ____ _____ _____ ", 391" | ____| | _ \\ / ____| __ \\ ", 392" | |___ _ __ ___ ___ | |_) | (___ | | | |", 393" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 394" | | | | | __/ __/| |_) |____) | |__| |", 395" | | | | | | || | | |", 396" |_| |_| \\___|\\___||____/|_____/|_____/ " 397} 398none = {""} 399 400menu_name_handlers = { 401 -- Menu name handlers should take the menu being drawn and entry being 402 -- drawn as parameters, and return the name of the item. 403 -- This is designed so that everything, including menu separators, may 404 -- have their names derived differently. The default action for entry 405 -- types not specified here is to use entry.name directly. 406 [core.MENU_SEPARATOR] = function(_, entry) 407 if entry.name ~= nil then 408 if type(entry.name) == "function" then 409 return entry.name() 410 end 411 return entry.name 412 end 413 return "" 414 end, 415 [core.MENU_CAROUSEL_ENTRY] = function(_, entry) 416 local carid = entry.carousel_id 417 local caridx = config.getCarouselIndex(carid) 418 local choices = entry.items 419 if type(choices) == "function" then 420 choices = choices() 421 end 422 if #choices < caridx then 423 caridx = 1 424 end 425 return entry.name(caridx, choices[caridx], choices) 426 end, 427} 428 429branddefs = { 430 -- Indexed by valid values for loader_brand in loader.conf(5). Valid 431 -- keys are: graphic (table depicting graphic) 432 ["fbsd"] = { 433 graphic = fbsd_brand, 434 image = "/boot/images/freebsd-brand-rev.png", 435 }, 436 ["none"] = { 437 graphic = none, 438 }, 439} 440 441logodefs = { 442 -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys 443 -- are: requires_color (boolean), graphic (table depicting graphic), and 444 -- shift (table containing x and y). 445 ["tribute"] = { 446 graphic = fbsd_brand, 447 }, 448 ["tributebw"] = { 449 graphic = fbsd_brand, 450 }, 451 ["none"] = { 452 graphic = none, 453 shift = {x = 17, y = 0}, 454 }, 455} 456 457brand_position = {x = 2, y = 1} 458logo_position = {x = 46, y = 4} 459menu_position = {x = 5, y = 10} 460frame_size = {w = 42, h = 13} 461default_shift = {x = 0, y = 0} 462shift = default_shift 463 464-- Module exports 465drawer.default_brand = 'fbsd' 466drawer.default_color_logodef = 'orb' 467drawer.default_bw_logodef = 'orbbw' 468-- For when things go terribly wrong; this def should be present here in the 469-- drawer module in case it's a filesystem issue. 470drawer.default_fallback_logodef = 'none' 471 472-- These should go away after FreeBSD 13; only available for backwards 473-- compatibility with old logo- files. 474function drawer.addBrand(name, def) 475 branddefs[name] = def 476end 477 478function drawer.addLogo(name, def) 479 logodefs[name] = def 480end 481 482drawer.frame_styles = { 483 -- Indexed by valid values for loader_menu_frame in loader.conf(5). 484 -- All of the keys appearing below must be set for any menu frame style 485 -- added to drawer.frame_styles. 486 ["ascii"] = { 487 horizontal = "-", 488 vertical = "|", 489 top_left = "+", 490 bottom_left = "+", 491 top_right = "+", 492 bottom_right = "+", 493 }, 494 ["single"] = { 495 horizontal = "\xE2\x94\x80", 496 vertical = "\xE2\x94\x82", 497 top_left = "\xE2\x94\x8C", 498 bottom_left = "\xE2\x94\x94", 499 top_right = "\xE2\x94\x90", 500 bottom_right = "\xE2\x94\x98", 501 }, 502 ["double"] = { 503 horizontal = "\xE2\x95\x90", 504 vertical = "\xE2\x95\x91", 505 top_left = "\xE2\x95\x94", 506 bottom_left = "\xE2\x95\x9A", 507 top_right = "\xE2\x95\x97", 508 bottom_right = "\xE2\x95\x9D", 509 }, 510} 511 512function drawer.drawscreen(menudef) 513 -- drawlogo() must go first. 514 -- it determines the positions of other elements 515 drawitem(drawlogo) 516 drawitem(drawbrand) 517 drawitem(drawbox) 518 return drawmenu(menudef) 519end 520 521return drawer 522