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