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