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