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