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 drawbox() 148 local x = menu_position.x - 3 149 local y = menu_position.y - 1 150 local w = frame_size.w 151 local h = frame_size.h 152 153 local framestyle = loader.getenv("loader_menu_frame") or "double" 154 local framespec = drawer.frame_styles[framestyle] 155 -- If we don't have a framespec for the current frame style, just don't 156 -- draw a box. 157 if framespec == nil then 158 return 159 end 160 161 local hl = framespec.horizontal 162 local vl = framespec.vertical 163 164 local tl = framespec.top_left 165 local bl = framespec.bottom_left 166 local tr = framespec.top_right 167 local br = framespec.bottom_right 168 169 x = x + shift.x 170 y = y + shift.y 171 172 screen.setcursor(x, y); printc(tl) 173 screen.setcursor(x, y + h); printc(bl) 174 screen.setcursor(x + w, y); printc(tr) 175 screen.setcursor(x + w, y + h); printc(br) 176 177 screen.setcursor(x + 1, y) 178 for _ = 1, w - 1 do 179 printc(hl) 180 end 181 182 screen.setcursor(x + 1, y + h) 183 for _ = 1, w - 1 do 184 printc(hl) 185 end 186 187 for i = 1, h - 1 do 188 screen.setcursor(x, y + i) 189 printc(vl) 190 screen.setcursor(x + w, y + i) 191 printc(vl) 192 end 193 194 local menu_header = loader.getenv("loader_menu_title") or 195 "Welcome to FreeBSD" 196 local menu_header_align = loader.getenv("loader_menu_title_align") 197 local menu_header_x 198 199 if menu_header_align ~= nil then 200 menu_header_align = menu_header_align:lower() 201 if menu_header_align == "left" then 202 -- Just inside the left border on top 203 menu_header_x = x + 1 204 elseif menu_header_align == "right" then 205 -- Just inside the right border on top 206 menu_header_x = x + w - #menu_header 207 end 208 end 209 if menu_header_x == nil then 210 menu_header_x = x + (w / 2) - (#menu_header / 2) 211 end 212 screen.setcursor(menu_header_x, y) 213 printc(menu_header) 214end 215 216local function drawbrand() 217 local x = tonumber(loader.getenv("loader_brand_x")) or 218 brand_position.x 219 local y = tonumber(loader.getenv("loader_brand_y")) or 220 brand_position.y 221 222 local branddef = getBranddef(loader.getenv("loader_brand")) 223 224 if branddef == nil then 225 branddef = getBranddef(drawer.default_brand) 226 end 227 228 local graphic = branddef.graphic 229 230 x = x + shift.x 231 y = y + shift.y 232 draw(x, y, graphic) 233end 234 235local function drawlogo() 236 local x = tonumber(loader.getenv("loader_logo_x")) or 237 logo_position.x 238 local y = tonumber(loader.getenv("loader_logo_y")) or 239 logo_position.y 240 241 local logo = loader.getenv("loader_logo") 242 local colored = color.isEnabled() 243 244 local logodef = getLogodef(logo) 245 246 if logodef == nil or logodef.graphic == nil or 247 (not colored and logodef.requires_color) then 248 -- Choose a sensible default 249 if colored then 250 logodef = getLogodef(drawer.default_color_logodef) 251 else 252 logodef = getLogodef(drawer.default_bw_logodef) 253 end 254 end 255 256 if logodef ~= nil and logodef.graphic == none then 257 shift = logodef.shift 258 else 259 shift = default_shift 260 end 261 262 x = x + shift.x 263 y = y + shift.y 264 265 if logodef ~= nil and logodef.shift ~= nil then 266 x = x + logodef.shift.x 267 y = y + logodef.shift.y 268 end 269 270 draw(x, y, logodef.graphic) 271end 272 273fbsd_brand = { 274" ______ ____ _____ _____ ", 275" | ____| | _ \\ / ____| __ \\ ", 276" | |___ _ __ ___ ___ | |_) | (___ | | | |", 277" | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 278" | | | | | __/ __/| |_) |____) | |__| |", 279" | | | | | | || | | |", 280" |_| |_| \\___|\\___||____/|_____/|_____/ " 281} 282none = {""} 283 284menu_name_handlers = { 285 -- Menu name handlers should take the menu being drawn and entry being 286 -- drawn as parameters, and return the name of the item. 287 -- This is designed so that everything, including menu separators, may 288 -- have their names derived differently. The default action for entry 289 -- types not specified here is to use entry.name directly. 290 [core.MENU_SEPARATOR] = function(_, entry) 291 if entry.name ~= nil then 292 if type(entry.name) == "function" then 293 return entry.name() 294 end 295 return entry.name 296 end 297 return "" 298 end, 299 [core.MENU_CAROUSEL_ENTRY] = function(_, entry) 300 local carid = entry.carousel_id 301 local caridx = config.getCarouselIndex(carid) 302 local choices = entry.items 303 if type(choices) == "function" then 304 choices = choices() 305 end 306 if #choices < caridx then 307 caridx = 1 308 end 309 return entry.name(caridx, choices[caridx], choices) 310 end, 311} 312 313branddefs = { 314 -- Indexed by valid values for loader_brand in loader.conf(5). Valid 315 -- keys are: graphic (table depicting graphic) 316 ["fbsd"] = { 317 graphic = fbsd_brand, 318 }, 319 ["none"] = { 320 graphic = none, 321 }, 322} 323 324logodefs = { 325 -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys 326 -- are: requires_color (boolean), graphic (table depicting graphic), and 327 -- shift (table containing x and y). 328 ["tribute"] = { 329 graphic = fbsd_brand, 330 }, 331 ["tributebw"] = { 332 graphic = fbsd_brand, 333 }, 334 ["none"] = { 335 graphic = none, 336 shift = {x = 17, y = 0}, 337 }, 338} 339 340brand_position = {x = 2, y = 1} 341logo_position = {x = 46, y = 4} 342menu_position = {x = 5, y = 10} 343frame_size = {w = 42, h = 13} 344default_shift = {x = 0, y = 0} 345shift = default_shift 346 347-- Module exports 348drawer.default_brand = 'fbsd' 349drawer.default_color_logodef = 'orb' 350drawer.default_bw_logodef = 'orbbw' 351 352function drawer.addBrand(name, def) 353 branddefs[name] = def 354end 355 356function drawer.addLogo(name, def) 357 logodefs[name] = def 358end 359 360drawer.frame_styles = { 361 -- Indexed by valid values for loader_menu_frame in loader.conf(5). 362 -- All of the keys appearing below must be set for any menu frame style 363 -- added to drawer.frame_styles. 364 ["ascii"] = { 365 horizontal = "-", 366 vertical = "|", 367 top_left = "+", 368 bottom_left = "+", 369 top_right = "+", 370 bottom_right = "+", 371 }, 372 ["single"] = { 373 horizontal = "\xC4", 374 vertical = "\xB3", 375 top_left = "\xDA", 376 bottom_left = "\xC0", 377 top_right = "\xBF", 378 bottom_right = "\xD9", 379 }, 380 ["double"] = { 381 horizontal = "\xCD", 382 vertical = "\xBA", 383 top_left = "\xC9", 384 bottom_left = "\xC8", 385 top_right = "\xBB", 386 bottom_right = "\xBC", 387 }, 388} 389 390function drawer.drawscreen(menudef) 391 -- drawlogo() must go first. 392 -- it determines the positions of other elements 393 drawlogo() 394 drawbrand() 395 drawbox() 396 return drawmenu(menudef) 397end 398 399return drawer 400