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 getBranddef(brand) 65 if brand == nil then 66 return nil 67 end 68 -- Look it up 69 local branddef = branddefs[brand] 70 71 -- Try to pull it in 72 if branddef == nil then 73 try_include('brand-' .. brand) 74 branddef = branddefs[brand] 75 end 76 77 return branddef 78end 79 80local function getLogodef(logo) 81 if logo == nil then 82 return nil 83 end 84 -- Look it up 85 local logodef = logodefs[logo] 86 87 -- Try to pull it in 88 if logodef == nil then 89 try_include('logo-' .. logo) 90 logodef = logodefs[logo] 91 end 92 93 return logodef 94end 95 96local function draw(x, y, logo) 97 for i = 1, #logo do 98 screen.setcursor(x, y + i - 1) 99 printc(logo[i]) 100 end 101end 102 103local function drawmenu(menudef) 104 local x = menu_position.x 105 local y = menu_position.y 106 107 x = x + shift.x 108 y = y + shift.y 109 110 -- print the menu and build the alias table 111 local alias_table = {} 112 local entry_num = 0 113 local menu_entries = menudef.entries 114 local effective_line_num = 0 115 if type(menu_entries) == "function" then 116 menu_entries = menu_entries() 117 end 118 for _, e in ipairs(menu_entries) do 119 -- Allow menu items to be conditionally visible by specifying 120 -- a visible function. 121 if e.visible ~= nil and not e.visible() then 122 goto continue 123 end 124 effective_line_num = effective_line_num + 1 125 if e.entry_type ~= core.MENU_SEPARATOR then 126 entry_num = entry_num + 1 127 screen.setcursor(x, y + effective_line_num) 128 129 printc(entry_num .. ". " .. menuEntryName(menudef, e)) 130 131 -- fill the alias table 132 alias_table[tostring(entry_num)] = e 133 if e.alias ~= nil then 134 for _, a in ipairs(e.alias) do 135 alias_table[a] = e 136 end 137 end 138 else 139 screen.setcursor(x, y + effective_line_num) 140 printc(menuEntryName(menudef, e)) 141 end 142 ::continue:: 143 end 144 return alias_table 145end 146 147local function defaultframe() 148 if core.isSerialConsole() then 149 return "ascii" 150 end 151 return "double" 152end 153 154local function drawbox() 155 local x = menu_position.x - 3 156 local y = menu_position.y - 1 157 local w = frame_size.w 158 local h = frame_size.h 159 160 local framestyle = loader.getenv("loader_menu_frame") or defaultframe() 161 local framespec = drawer.frame_styles[framestyle] 162 -- If we don't have a framespec for the current frame style, just don't 163 -- draw a box. 164 if framespec == nil then 165 return 166 end 167 168 local hl = framespec.horizontal 169 local vl = framespec.vertical 170 171 local tl = framespec.top_left 172 local bl = framespec.bottom_left 173 local tr = framespec.top_right 174 local br = framespec.bottom_right 175 176 x = x + shift.x 177 y = y + shift.y 178 179 screen.setcursor(x, y); printc(tl) 180 screen.setcursor(x, y + h); printc(bl) 181 screen.setcursor(x + w, y); printc(tr) 182 screen.setcursor(x + w, y + h); printc(br) 183 184 screen.setcursor(x + 1, y) 185 for _ = 1, w - 1 do 186 printc(hl) 187 end 188 189 screen.setcursor(x + 1, y + h) 190 for _ = 1, w - 1 do 191 printc(hl) 192 end 193 194 for i = 1, h - 1 do 195 screen.setcursor(x, y + i) 196 printc(vl) 197 screen.setcursor(x + w, y + i) 198 printc(vl) 199 end 200 201 local menu_header = loader.getenv("loader_menu_title") or 202 "Welcome to FreeBSD" 203 local menu_header_align = loader.getenv("loader_menu_title_align") 204 local menu_header_x 205 206 if menu_header_align ~= nil then 207 menu_header_align = menu_header_align:lower() 208 if menu_header_align == "left" then 209 -- Just inside the left border on top 210 menu_header_x = x + 1 211 elseif menu_header_align == "right" then 212 -- Just inside the right border on top 213 menu_header_x = x + w - #menu_header 214 end 215 end 216 if menu_header_x == nil then 217 menu_header_x = x + (w / 2) - (#menu_header / 2) 218 end 219 screen.setcursor(menu_header_x, y) 220 printc(menu_header) 221end 222 223local function drawbrand() 224 local x = tonumber(loader.getenv("loader_brand_x")) or 225 brand_position.x 226 local y = tonumber(loader.getenv("loader_brand_y")) or 227 brand_position.y 228 229 local branddef = getBranddef(loader.getenv("loader_brand")) 230 231 if branddef == nil then 232 branddef = getBranddef(drawer.default_brand) 233 end 234 235 local graphic = branddef.graphic 236 237 x = x + shift.x 238 y = y + shift.y 239 draw(x, y, graphic) 240end 241 242local function drawlogo() 243 local x = tonumber(loader.getenv("loader_logo_x")) or 244 logo_position.x 245 local y = tonumber(loader.getenv("loader_logo_y")) or 246 logo_position.y 247 248 local logo = loader.getenv("loader_logo") 249 local colored = color.isEnabled() 250 251 local logodef = getLogodef(logo) 252 253 if logodef == nil or logodef.graphic == nil or 254 (not colored and logodef.requires_color) then 255 -- Choose a sensible default 256 if colored then 257 logodef = getLogodef(drawer.default_color_logodef) 258 else 259 logodef = getLogodef(drawer.default_bw_logodef) 260 end 261 262 -- Something has gone terribly wrong. 263 if logodef == nil then 264 logodef = getLogodef(drawer.default_fallback_logodef) 265 end 266 end 267 268 if logodef ~= nil and logodef.graphic == none then 269 shift = logodef.shift 270 else 271 shift = default_shift 272 end 273 274 x = x + shift.x 275 y = y + shift.y 276 277 if logodef ~= nil and logodef.shift ~= nil then 278 x = x + logodef.shift.x 279 y = y + logodef.shift.y 280 end 281 282 draw(x, y, logodef.graphic) 283end 284 285fbsd_brand = { 286" ______ ____ _____ _____ ", 287" | ____| | _ \\ / ____| __ \\ ", 288" | |___ _ __ ___ ___ | |_) | (___ | | | |", 289" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 290" | | | | | __/ __/| |_) |____) | |__| |", 291" | | | | | | || | | |", 292" |_| |_| \\___|\\___||____/|_____/|_____/ " 293} 294none = {""} 295 296menu_name_handlers = { 297 -- Menu name handlers should take the menu being drawn and entry being 298 -- drawn as parameters, and return the name of the item. 299 -- This is designed so that everything, including menu separators, may 300 -- have their names derived differently. The default action for entry 301 -- types not specified here is to use entry.name directly. 302 [core.MENU_SEPARATOR] = function(_, entry) 303 if entry.name ~= nil then 304 if type(entry.name) == "function" then 305 return entry.name() 306 end 307 return entry.name 308 end 309 return "" 310 end, 311 [core.MENU_CAROUSEL_ENTRY] = function(_, entry) 312 local carid = entry.carousel_id 313 local caridx = config.getCarouselIndex(carid) 314 local choices = entry.items 315 if type(choices) == "function" then 316 choices = choices() 317 end 318 if #choices < caridx then 319 caridx = 1 320 end 321 return entry.name(caridx, choices[caridx], choices) 322 end, 323} 324 325branddefs = { 326 -- Indexed by valid values for loader_brand in loader.conf(5). Valid 327 -- keys are: graphic (table depicting graphic) 328 ["fbsd"] = { 329 graphic = fbsd_brand, 330 }, 331 ["none"] = { 332 graphic = none, 333 }, 334} 335 336logodefs = { 337 -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys 338 -- are: requires_color (boolean), graphic (table depicting graphic), and 339 -- shift (table containing x and y). 340 ["tribute"] = { 341 graphic = fbsd_brand, 342 }, 343 ["tributebw"] = { 344 graphic = fbsd_brand, 345 }, 346 ["none"] = { 347 graphic = none, 348 shift = {x = 17, y = 0}, 349 }, 350} 351 352brand_position = {x = 2, y = 1} 353logo_position = {x = 46, y = 4} 354menu_position = {x = 5, y = 10} 355frame_size = {w = 42, h = 13} 356default_shift = {x = 0, y = 0} 357shift = default_shift 358 359-- Module exports 360drawer.default_brand = 'fbsd' 361drawer.default_color_logodef = 'orb' 362drawer.default_bw_logodef = 'orbbw' 363-- For when things go terribly wrong; this def should be present here in the 364-- drawer module in case it's a filesystem issue. 365drawer.default_fallback_logodef = 'none' 366 367function drawer.addBrand(name, def) 368 branddefs[name] = def 369end 370 371function drawer.addLogo(name, def) 372 logodefs[name] = def 373end 374 375drawer.frame_styles = { 376 -- Indexed by valid values for loader_menu_frame in loader.conf(5). 377 -- All of the keys appearing below must be set for any menu frame style 378 -- added to drawer.frame_styles. 379 ["ascii"] = { 380 horizontal = "-", 381 vertical = "|", 382 top_left = "+", 383 bottom_left = "+", 384 top_right = "+", 385 bottom_right = "+", 386 }, 387 ["single"] = { 388 horizontal = "\xE2\x94\x80", 389 vertical = "\xE2\x94\x82", 390 top_left = "\xE2\x94\x8C", 391 bottom_left = "\xE2\x94\x94", 392 top_right = "\xE2\x94\x90", 393 bottom_right = "\xE2\x94\x98", 394 }, 395 ["double"] = { 396 horizontal = "\xE2\x95\x90", 397 vertical = "\xE2\x95\x91", 398 top_left = "\xE2\x95\x94", 399 bottom_left = "\xE2\x95\x9A", 400 top_right = "\xE2\x95\x97", 401 bottom_right = "\xE2\x95\x9D", 402 }, 403} 404 405function drawer.drawscreen(menudef) 406 -- drawlogo() must go first. 407 -- it determines the positions of other elements 408 drawlogo() 409 drawbrand() 410 drawbox() 411 return drawmenu(menudef) 412end 413 414return drawer 415