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 drawbox() 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 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 screen.setcursor(x, y); printc(tl) 231 screen.setcursor(x, y + h); printc(bl) 232 screen.setcursor(x + w, y); printc(tr) 233 screen.setcursor(x + w, y + h); printc(br) 234 235 screen.setcursor(x + 1, y) 236 for _ = 1, w - 1 do 237 printc(hl) 238 end 239 240 screen.setcursor(x + 1, y + h) 241 for _ = 1, w - 1 do 242 printc(hl) 243 end 244 245 for i = 1, h - 1 do 246 screen.setcursor(x, y + i) 247 printc(vl) 248 screen.setcursor(x + w, y + i) 249 printc(vl) 250 end 251 252 local menu_header = loader.getenv("loader_menu_title") or 253 "Welcome to FreeBSD" 254 local menu_header_align = loader.getenv("loader_menu_title_align") 255 local menu_header_x 256 257 if menu_header_align ~= nil then 258 menu_header_align = menu_header_align:lower() 259 if menu_header_align == "left" then 260 -- Just inside the left border on top 261 menu_header_x = x + 1 262 elseif menu_header_align == "right" then 263 -- Just inside the right border on top 264 menu_header_x = x + w - #menu_header 265 end 266 end 267 if menu_header_x == nil then 268 menu_header_x = x + (w / 2) - (#menu_header / 2) 269 end 270 screen.setcursor(menu_header_x, y) 271 printc(menu_header) 272end 273 274local function drawbrand() 275 local x = tonumber(loader.getenv("loader_brand_x")) or 276 brand_position.x 277 local y = tonumber(loader.getenv("loader_brand_y")) or 278 brand_position.y 279 280 local branddef = getBranddef(loader.getenv("loader_brand")) 281 282 if branddef == nil then 283 branddef = getBranddef(drawer.default_brand) 284 end 285 286 local graphic = branddef.graphic 287 288 x = x + shift.x 289 y = y + shift.y 290 draw(x, y, graphic) 291end 292 293local function drawlogo() 294 local x = tonumber(loader.getenv("loader_logo_x")) or 295 logo_position.x 296 local y = tonumber(loader.getenv("loader_logo_y")) or 297 logo_position.y 298 299 local logo = loader.getenv("loader_logo") 300 local colored = color.isEnabled() 301 302 local logodef = getLogodef(logo) 303 304 if logodef == nil or logodef.graphic == nil or 305 (not colored and logodef.requires_color) then 306 -- Choose a sensible default 307 if colored then 308 logodef = getLogodef(drawer.default_color_logodef) 309 else 310 logodef = getLogodef(drawer.default_bw_logodef) 311 end 312 313 -- Something has gone terribly wrong. 314 if logodef == nil then 315 logodef = getLogodef(drawer.default_fallback_logodef) 316 end 317 end 318 319 if logodef ~= nil and logodef.graphic == none then 320 shift = logodef.shift 321 else 322 shift = default_shift 323 end 324 325 x = x + shift.x 326 y = y + shift.y 327 328 if logodef ~= nil and logodef.shift ~= nil then 329 x = x + logodef.shift.x 330 y = y + logodef.shift.y 331 end 332 333 draw(x, y, logodef.graphic) 334end 335 336fbsd_brand = { 337" ______ ____ _____ _____ ", 338" | ____| | _ \\ / ____| __ \\ ", 339" | |___ _ __ ___ ___ | |_) | (___ | | | |", 340" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 341" | | | | | __/ __/| |_) |____) | |__| |", 342" | | | | | | || | | |", 343" |_| |_| \\___|\\___||____/|_____/|_____/ " 344} 345none = {""} 346 347menu_name_handlers = { 348 -- Menu name handlers should take the menu being drawn and entry being 349 -- drawn as parameters, and return the name of the item. 350 -- This is designed so that everything, including menu separators, may 351 -- have their names derived differently. The default action for entry 352 -- types not specified here is to use entry.name directly. 353 [core.MENU_SEPARATOR] = function(_, entry) 354 if entry.name ~= nil then 355 if type(entry.name) == "function" then 356 return entry.name() 357 end 358 return entry.name 359 end 360 return "" 361 end, 362 [core.MENU_CAROUSEL_ENTRY] = function(_, entry) 363 local carid = entry.carousel_id 364 local caridx = config.getCarouselIndex(carid) 365 local choices = entry.items 366 if type(choices) == "function" then 367 choices = choices() 368 end 369 if #choices < caridx then 370 caridx = 1 371 end 372 return entry.name(caridx, choices[caridx], choices) 373 end, 374} 375 376branddefs = { 377 -- Indexed by valid values for loader_brand in loader.conf(5). Valid 378 -- keys are: graphic (table depicting graphic) 379 ["fbsd"] = { 380 graphic = fbsd_brand, 381 }, 382 ["none"] = { 383 graphic = none, 384 }, 385} 386 387logodefs = { 388 -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys 389 -- are: requires_color (boolean), graphic (table depicting graphic), and 390 -- shift (table containing x and y). 391 ["tribute"] = { 392 graphic = fbsd_brand, 393 }, 394 ["tributebw"] = { 395 graphic = fbsd_brand, 396 }, 397 ["none"] = { 398 graphic = none, 399 shift = {x = 17, y = 0}, 400 }, 401} 402 403brand_position = {x = 2, y = 1} 404logo_position = {x = 46, y = 4} 405menu_position = {x = 5, y = 10} 406frame_size = {w = 42, h = 13} 407default_shift = {x = 0, y = 0} 408shift = default_shift 409 410-- Module exports 411drawer.default_brand = 'fbsd' 412drawer.default_color_logodef = 'orb' 413drawer.default_bw_logodef = 'orbbw' 414-- For when things go terribly wrong; this def should be present here in the 415-- drawer module in case it's a filesystem issue. 416drawer.default_fallback_logodef = 'none' 417 418-- These should go away after FreeBSD 13; only available for backwards 419-- compatibility with old logo- files. 420function drawer.addBrand(name, def) 421 branddefs[name] = def 422end 423 424function drawer.addLogo(name, def) 425 logodefs[name] = def 426end 427 428drawer.frame_styles = { 429 -- Indexed by valid values for loader_menu_frame in loader.conf(5). 430 -- All of the keys appearing below must be set for any menu frame style 431 -- added to drawer.frame_styles. 432 ["ascii"] = { 433 horizontal = "-", 434 vertical = "|", 435 top_left = "+", 436 bottom_left = "+", 437 top_right = "+", 438 bottom_right = "+", 439 }, 440 ["single"] = { 441 horizontal = "\xE2\x94\x80", 442 vertical = "\xE2\x94\x82", 443 top_left = "\xE2\x94\x8C", 444 bottom_left = "\xE2\x94\x94", 445 top_right = "\xE2\x94\x90", 446 bottom_right = "\xE2\x94\x98", 447 }, 448 ["double"] = { 449 horizontal = "\xE2\x95\x90", 450 vertical = "\xE2\x95\x91", 451 top_left = "\xE2\x95\x94", 452 bottom_left = "\xE2\x95\x9A", 453 top_right = "\xE2\x95\x97", 454 bottom_right = "\xE2\x95\x9D", 455 }, 456} 457 458function drawer.drawscreen(menudef) 459 -- drawlogo() must go first. 460 -- it determines the positions of other elements 461 drawlogo() 462 drawbrand() 463 drawbox() 464 return drawmenu(menudef) 465end 466 467return drawer 468