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