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