1088b4f5fSWarner Losh-- 2088b4f5fSWarner Losh-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org> 3df74a61fSKyle Evans-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org> 4088b4f5fSWarner Losh-- All rights reserved. 5088b4f5fSWarner Losh-- 6088b4f5fSWarner Losh-- Redistribution and use in source and binary forms, with or without 7088b4f5fSWarner Losh-- modification, are permitted provided that the following conditions 8088b4f5fSWarner Losh-- are met: 9088b4f5fSWarner Losh-- 1. Redistributions of source code must retain the above copyright 10088b4f5fSWarner Losh-- notice, this list of conditions and the following disclaimer. 11088b4f5fSWarner Losh-- 2. Redistributions in binary form must reproduce the above copyright 12088b4f5fSWarner Losh-- notice, this list of conditions and the following disclaimer in the 13088b4f5fSWarner Losh-- documentation and/or other materials provided with the distribution. 14088b4f5fSWarner Losh-- 15088b4f5fSWarner Losh-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16088b4f5fSWarner Losh-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17088b4f5fSWarner Losh-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18088b4f5fSWarner Losh-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19088b4f5fSWarner Losh-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20088b4f5fSWarner Losh-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21088b4f5fSWarner Losh-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22088b4f5fSWarner Losh-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23088b4f5fSWarner Losh-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24088b4f5fSWarner Losh-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25088b4f5fSWarner Losh-- SUCH DAMAGE. 26088b4f5fSWarner Losh-- 27088b4f5fSWarner Losh-- $FreeBSD$ 28088b4f5fSWarner Losh-- 29088b4f5fSWarner Losh 30088b4f5fSWarner Loshlocal color = require("color"); 31a7cf0562SKyle Evanslocal core = require("core"); 32088b4f5fSWarner Loshlocal screen = require("screen"); 33088b4f5fSWarner Losh 34c8518398SKyle Evanslocal drawer = {}; 35c8518398SKyle Evans 3602122e53SKyle Evanslocal fbsd_logo; 3702122e53SKyle Evanslocal beastie_color; 3802122e53SKyle Evanslocal beastie; 3902122e53SKyle Evanslocal fbsd_logo_v; 4002122e53SKyle Evanslocal orb; 4102122e53SKyle Evanslocal none; 42bb26c57dSKyle Evanslocal none_shifted = false; 4302122e53SKyle Evans 44*e15abd1fSKyle Evanslocal menu_entry_name = function(drawing_menu, entry) 45*e15abd1fSKyle Evans local name_handler = drawer.menu_name_handlers[entry.entry_type]; 46*e15abd1fSKyle Evans 47*e15abd1fSKyle Evans if (name_handler ~= nil) then 48*e15abd1fSKyle Evans return name_handler(drawing_menu, entry); 49*e15abd1fSKyle Evans end 50*e15abd1fSKyle Evans return entry.name(); 51*e15abd1fSKyle Evansend 52*e15abd1fSKyle Evans 53*e15abd1fSKyle Evanslocal shift_brand_text = function(shift) 54*e15abd1fSKyle Evans drawer.brand_position.x = drawer.brand_position.x + shift.x; 55*e15abd1fSKyle Evans drawer.brand_position.y = drawer.brand_position.y + shift.y; 56*e15abd1fSKyle Evans drawer.menu_position.x = drawer.menu_position.x + shift.x; 57*e15abd1fSKyle Evans drawer.menu_position.y = drawer.menu_position.y + shift.y; 58*e15abd1fSKyle Evans drawer.box_pos_dim.x = drawer.box_pos_dim.x + shift.x; 59*e15abd1fSKyle Evans drawer.box_pos_dim.y = drawer.box_pos_dim.y + shift.y; 60*e15abd1fSKyle Evansend 61*e15abd1fSKyle Evans 62da56fe38SKyle Evansdrawer.menu_name_handlers = { 63da56fe38SKyle Evans -- Menu name handlers should take the menu being drawn and entry being 64da56fe38SKyle Evans -- drawn as parameters, and return the name of the item. 65da56fe38SKyle Evans -- This is designed so that everything, including menu separators, may 66da56fe38SKyle Evans -- have their names derived differently. The default action for entry 67da56fe38SKyle Evans -- types not specified here is to call and use entry.name(). 68da56fe38SKyle Evans [core.MENU_CAROUSEL_ENTRY] = function(drawing_menu, entry) 69da56fe38SKyle Evans local carid = entry.carousel_id; 70da56fe38SKyle Evans local caridx = menu.getCarouselIndex(carid); 71da56fe38SKyle Evans local choices = entry.items(); 72da56fe38SKyle Evans 73da56fe38SKyle Evans if (#choices < caridx) then 74da56fe38SKyle Evans caridx = 1; 75da56fe38SKyle Evans end 76da56fe38SKyle Evans return entry.name(caridx, choices[caridx], choices); 77da56fe38SKyle Evans end, 78da56fe38SKyle Evans}; 79da56fe38SKyle Evans 80088b4f5fSWarner Loshdrawer.brand_position = {x = 2, y = 1}; 8102122e53SKyle Evansdrawer.logo_position = {x = 46, y = 1}; 8202122e53SKyle Evansdrawer.menu_position = {x = 6, y = 11}; 8302122e53SKyle Evansdrawer.box_pos_dim = {x = 3, y = 10, w = 41, h = 11}; 8402122e53SKyle Evans 8502122e53SKyle Evansfbsd_logo = { 86088b4f5fSWarner Losh " ______ ____ _____ _____ ", 87088b4f5fSWarner Losh " | ____| | _ \\ / ____| __ \\ ", 88088b4f5fSWarner Losh " | |___ _ __ ___ ___ | |_) | (___ | | | |", 89088b4f5fSWarner Losh " | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", 90088b4f5fSWarner Losh " | | | | | __/ __/| |_) |____) | |__| |", 91088b4f5fSWarner Losh " | | | | | | || | | |", 92088b4f5fSWarner Losh " |_| |_| \\___|\\___||____/|_____/|_____/ " 93088b4f5fSWarner Losh}; 94088b4f5fSWarner Losh 9502122e53SKyle Evansbeastie_color = { 96088b4f5fSWarner Losh " \027[31m, ,", 97088b4f5fSWarner Losh " /( )`", 98088b4f5fSWarner Losh " \\ \\___ / |", 99088b4f5fSWarner Losh " /- \027[37m_\027[31m `-/ '", 100088b4f5fSWarner Losh " (\027[37m/\\/ \\\027[31m \\ /\\", 101088b4f5fSWarner Losh " \027[37m/ / |\027[31m ` \\", 102088b4f5fSWarner Losh " \027[34mO O \027[37m) \027[31m/ |", 103088b4f5fSWarner Losh " \027[37m`-^--'\027[31m`< '", 104088b4f5fSWarner Losh " (_.) _ ) /", 105088b4f5fSWarner Losh " `.___/` /", 106088b4f5fSWarner Losh " `-----' /", 107088b4f5fSWarner Losh " \027[33m<----.\027[31m __ / __ \\", 108088b4f5fSWarner Losh " \027[33m<----|====\027[31mO)))\027[33m==\027[31m) \\) /\027[33m====|", 109088b4f5fSWarner Losh " \027[33m<----'\027[31m `--' `.__,' \\", 110088b4f5fSWarner Losh " | |", 111088b4f5fSWarner Losh " \\ / /\\", 112088b4f5fSWarner Losh " \027[36m______\027[31m( (_ / \\______/", 113088b4f5fSWarner Losh " \027[36m,' ,-----' |", 114088b4f5fSWarner Losh " `--{__________)\027[37m" 115088b4f5fSWarner Losh}; 116088b4f5fSWarner Losh 11702122e53SKyle Evansbeastie = { 118088b4f5fSWarner Losh " , ,", 119088b4f5fSWarner Losh " /( )`", 120088b4f5fSWarner Losh " \\ \\___ / |", 121088b4f5fSWarner Losh " /- _ `-/ '", 122088b4f5fSWarner Losh " (/\\/ \\ \\ /\\", 123088b4f5fSWarner Losh " / / | ` \\", 124088b4f5fSWarner Losh " O O ) / |", 125088b4f5fSWarner Losh " `-^--'`< '", 126088b4f5fSWarner Losh " (_.) _ ) /", 127088b4f5fSWarner Losh " `.___/` /", 128088b4f5fSWarner Losh " `-----' /", 129088b4f5fSWarner Losh " <----. __ / __ \\", 130088b4f5fSWarner Losh " <----|====O)))==) \\) /====|", 131088b4f5fSWarner Losh " <----' `--' `.__,' \\", 132088b4f5fSWarner Losh " | |", 133088b4f5fSWarner Losh " \\ / /\\", 134088b4f5fSWarner Losh " ______( (_ / \\______/", 135088b4f5fSWarner Losh " ,' ,-----' |", 136088b4f5fSWarner Losh " `--{__________)" 137088b4f5fSWarner Losh}; 138088b4f5fSWarner Losh 13902122e53SKyle Evansfbsd_logo_v = { 140088b4f5fSWarner Losh " ______", 141088b4f5fSWarner Losh " | ____| __ ___ ___ ", 142088b4f5fSWarner Losh " | |__ | '__/ _ \\/ _ \\", 143088b4f5fSWarner Losh " | __|| | | __/ __/", 144088b4f5fSWarner Losh " | | | | | | |", 145088b4f5fSWarner Losh " |_| |_| \\___|\\___|", 146088b4f5fSWarner Losh " ____ _____ _____", 147088b4f5fSWarner Losh " | _ \\ / ____| __ \\", 148088b4f5fSWarner Losh " | |_) | (___ | | | |", 149088b4f5fSWarner Losh " | _ < \\___ \\| | | |", 150088b4f5fSWarner Losh " | |_) |____) | |__| |", 151088b4f5fSWarner Losh " | | | |", 152088b4f5fSWarner Losh " |____/|_____/|_____/" 153088b4f5fSWarner Losh}; 154088b4f5fSWarner Losh 15502122e53SKyle Evansorb_color = { 156088b4f5fSWarner Losh " \027[31m``` \027[31;1m`\027[31m", 157088b4f5fSWarner Losh " s` `.....---...\027[31;1m....--.``` -/\027[31m", 158088b4f5fSWarner Losh " +o .--` \027[31;1m/y:` +.\027[31m", 159088b4f5fSWarner Losh " yo`:. \027[31;1m:o `+-\027[31m", 160088b4f5fSWarner Losh " y/ \027[31;1m-/` -o/\027[31m", 161088b4f5fSWarner Losh " .- \027[31;1m::/sy+:.\027[31m", 162088b4f5fSWarner Losh " / \027[31;1m`-- /\027[31m", 163088b4f5fSWarner Losh " `: \027[31;1m:`\027[31m", 164088b4f5fSWarner Losh " `: \027[31;1m:`\027[31m", 165088b4f5fSWarner Losh " / \027[31;1m/\027[31m", 166088b4f5fSWarner Losh " .- \027[31;1m-.\027[31m", 167088b4f5fSWarner Losh " -- \027[31;1m-.\027[31m", 168088b4f5fSWarner Losh " `:` \027[31;1m`:`", 169088b4f5fSWarner Losh " \027[31;1m.-- `--.", 170088b4f5fSWarner Losh " .---.....----.\027[37m" 171088b4f5fSWarner Losh}; 172088b4f5fSWarner Losh 17302122e53SKyle Evansorb = { 174088b4f5fSWarner Losh " ``` `", 175088b4f5fSWarner Losh " s` `.....---.......--.``` -/", 176088b4f5fSWarner Losh " +o .--` /y:` +.", 177088b4f5fSWarner Losh " yo`:. :o `+-", 178088b4f5fSWarner Losh " y/ -/` -o/", 179088b4f5fSWarner Losh " .- ::/sy+:.", 180088b4f5fSWarner Losh " / `-- /", 181088b4f5fSWarner Losh " `: :`", 182088b4f5fSWarner Losh " `: :`", 183088b4f5fSWarner Losh " / /", 184088b4f5fSWarner Losh " .- -.", 185088b4f5fSWarner Losh " -- -.", 186088b4f5fSWarner Losh " `:` `:`", 187088b4f5fSWarner Losh " .-- `--.", 188088b4f5fSWarner Losh " .---.....----." 189088b4f5fSWarner Losh}; 190088b4f5fSWarner Losh 19102122e53SKyle Evansnone = {""}; 192088b4f5fSWarner Losh 19329aa5794SKyle Evansdrawer.branddefs = { 194699578a6SKyle Evans -- Indexed by valid values for loader_brand in loader.conf(5). Valid 195699578a6SKyle Evans -- keys are: graphic (table depicting graphic) 19629aa5794SKyle Evans ["fbsd"] = { 19729aa5794SKyle Evans graphic = fbsd_logo, 19829aa5794SKyle Evans }, 19929aa5794SKyle Evans ["none"] = { 20029aa5794SKyle Evans graphic = none, 20129aa5794SKyle Evans }, 20229aa5794SKyle Evans}; 20329aa5794SKyle Evans 204bb26c57dSKyle Evansdrawer.logodefs = { 205bb26c57dSKyle Evans -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys 206752b2d40SKyle Evans -- are: requires_color (boolean), graphic (table depicting graphic), and 207bb26c57dSKyle Evans -- shift (table containing x and y). 208bb26c57dSKyle Evans ["beastie"] = { 209bb26c57dSKyle Evans requires_color = true, 210752b2d40SKyle Evans graphic = beastie_color, 211bb26c57dSKyle Evans }, 212bb26c57dSKyle Evans ["beastiebw"] = { 213752b2d40SKyle Evans graphic = beastie, 214bb26c57dSKyle Evans }, 215bb26c57dSKyle Evans ["fbsdbw"] = { 216752b2d40SKyle Evans graphic = fbsd_logo_v, 217bb26c57dSKyle Evans shift = {x = 5, y = 4}, 218bb26c57dSKyle Evans }, 219bb26c57dSKyle Evans ["orb"] = { 220bb26c57dSKyle Evans requires_color = true, 221752b2d40SKyle Evans graphic = orb_color, 222bb26c57dSKyle Evans shift = {x = 2, y = 4}, 223bb26c57dSKyle Evans }, 224bb26c57dSKyle Evans ["orbbw"] = { 225752b2d40SKyle Evans graphic = orb, 226bb26c57dSKyle Evans shift = {x = 2, y = 4}, 227bb26c57dSKyle Evans }, 228bb26c57dSKyle Evans ["tribute"] = { 229752b2d40SKyle Evans graphic = fbsd_logo, 230bb26c57dSKyle Evans }, 231bb26c57dSKyle Evans ["tributebw"] = { 232752b2d40SKyle Evans graphic = fbsd_logo, 233bb26c57dSKyle Evans }, 234bb26c57dSKyle Evans ["none"] = { 235752b2d40SKyle Evans graphic = none, 236bb26c57dSKyle Evans shift = {x = 17, y = 0}, 237bb26c57dSKyle Evans }, 238bb26c57dSKyle Evans}; 239bb26c57dSKyle Evans 240088b4f5fSWarner Loshfunction drawer.drawscreen(menu_opts) 241088b4f5fSWarner Losh -- drawlogo() must go first. 242088b4f5fSWarner Losh -- it determines the positions of other elements 243088b4f5fSWarner Losh drawer.drawlogo(); 244088b4f5fSWarner Losh drawer.drawbrand(); 245088b4f5fSWarner Losh drawer.drawbox(); 246088b4f5fSWarner Losh return drawer.drawmenu(menu_opts); 247088b4f5fSWarner Loshend 248088b4f5fSWarner Losh 249088b4f5fSWarner Loshfunction drawer.drawmenu(m) 250088b4f5fSWarner Losh x = drawer.menu_position.x; 251088b4f5fSWarner Losh y = drawer.menu_position.y; 252088b4f5fSWarner Losh 253088b4f5fSWarner Losh -- print the menu and build the alias table 254088b4f5fSWarner Losh local alias_table = {}; 255088b4f5fSWarner Losh local entry_num = 0; 256d8757746SKyle Evans local menu_entries = m.entries; 2572e716cecSKyle Evans if (type(menu_entries) == "function") then 2582e716cecSKyle Evans menu_entries = menu_entries(); 2592e716cecSKyle Evans end 260d8757746SKyle Evans for line_num, e in ipairs(menu_entries) do 2614a4fb4f8SKyle Evans -- Allow menu items to be conditionally visible by specifying 2624a4fb4f8SKyle Evans -- a visible function. 2634a4fb4f8SKyle Evans if (e.visible ~= nil) and (not e.visible()) then 264ddb76e07SKyle Evans goto continue; 2654a4fb4f8SKyle Evans end 266a7cf0562SKyle Evans if (e.entry_type ~= core.MENU_SEPARATOR) then 267088b4f5fSWarner Losh entry_num = entry_num + 1; 268088b4f5fSWarner Losh screen.setcursor(x, y + line_num); 269ada26c4aSKyle Evans 2702413c411SKyle Evans print(entry_num .. ". " .. menu_entry_name(m, e)); 271088b4f5fSWarner Losh 272088b4f5fSWarner Losh -- fill the alias table 273088b4f5fSWarner Losh alias_table[tostring(entry_num)] = e; 274196ba166SKyle Evans if (e.alias ~= nil) then 275088b4f5fSWarner Losh for n, a in ipairs(e.alias) do 276088b4f5fSWarner Losh alias_table[a] = e; 277088b4f5fSWarner Losh end 278196ba166SKyle Evans end 279088b4f5fSWarner Losh else 280088b4f5fSWarner Losh screen.setcursor(x, y + line_num); 2812413c411SKyle Evans print(menu_entry_name(m, e)); 282088b4f5fSWarner Losh end 2834a4fb4f8SKyle Evans ::continue:: 284088b4f5fSWarner Losh end 285088b4f5fSWarner Losh return alias_table; 286088b4f5fSWarner Loshend 287088b4f5fSWarner Losh 288088b4f5fSWarner Losh 289088b4f5fSWarner Loshfunction drawer.drawbox() 290088b4f5fSWarner Losh x = drawer.box_pos_dim.x; 291088b4f5fSWarner Losh y = drawer.box_pos_dim.y; 292088b4f5fSWarner Losh w = drawer.box_pos_dim.w; 293088b4f5fSWarner Losh h = drawer.box_pos_dim.h; 294088b4f5fSWarner Losh 295088b4f5fSWarner Losh local hl = string.char(0xCD); 296088b4f5fSWarner Losh local vl = string.char(0xBA); 297088b4f5fSWarner Losh 298088b4f5fSWarner Losh local tl = string.char(0xC9); 299088b4f5fSWarner Losh local bl = string.char(0xC8); 300088b4f5fSWarner Losh local tr = string.char(0xBB); 301088b4f5fSWarner Losh local br = string.char(0xBC); 302088b4f5fSWarner Losh 303088b4f5fSWarner Losh screen.setcursor(x, y); print(tl); 304088b4f5fSWarner Losh screen.setcursor(x, y+h); print(bl); 305088b4f5fSWarner Losh screen.setcursor(x+w, y); print(tr); 306088b4f5fSWarner Losh screen.setcursor(x+w, y+h); print(br); 307088b4f5fSWarner Losh 308088b4f5fSWarner Losh for i = 1, w-1 do 309088b4f5fSWarner Losh screen.setcursor(x+i, y); 310088b4f5fSWarner Losh print(hl); 311088b4f5fSWarner Losh screen.setcursor(x+i, y+h); 312088b4f5fSWarner Losh print(hl); 313088b4f5fSWarner Losh end 314088b4f5fSWarner Losh 315088b4f5fSWarner Losh for i = 1, h-1 do 316088b4f5fSWarner Losh screen.setcursor(x, y+i); 317088b4f5fSWarner Losh print(vl); 318088b4f5fSWarner Losh screen.setcursor(x+w, y+i); 319088b4f5fSWarner Losh print(vl); 320088b4f5fSWarner Losh end 321088b4f5fSWarner Losh 322088b4f5fSWarner Losh screen.setcursor(x+(w/2)-9, y); 323088b4f5fSWarner Losh print("Welcome to FreeBSD"); 324088b4f5fSWarner Loshend 325088b4f5fSWarner Losh 326088b4f5fSWarner Loshfunction drawer.draw(x, y, logo) 327088b4f5fSWarner Losh for i = 1, #logo do 328088b4f5fSWarner Losh screen.setcursor(x, y + i); 329088b4f5fSWarner Losh print(logo[i]); 330088b4f5fSWarner Losh end 331088b4f5fSWarner Loshend 332088b4f5fSWarner Losh 333088b4f5fSWarner Loshfunction drawer.drawbrand() 33424a1bd54SKyle Evans local x = tonumber(loader.getenv("loader_brand_x")) or 33524a1bd54SKyle Evans drawer.brand_position.x; 33624a1bd54SKyle Evans local y = tonumber(loader.getenv("loader_brand_y")) or 33724a1bd54SKyle Evans drawer.brand_position.y; 338088b4f5fSWarner Losh 33929aa5794SKyle Evans local graphic = drawer.branddefs[loader.getenv("loader_brand")]; 34029aa5794SKyle Evans if (graphic == nil) then 34129aa5794SKyle Evans graphic = fbsd_logo; 34229aa5794SKyle Evans end 34329aa5794SKyle Evans drawer.draw(x, y, graphic); 344088b4f5fSWarner Loshend 345088b4f5fSWarner Losh 346088b4f5fSWarner Loshfunction drawer.drawlogo() 34724a1bd54SKyle Evans local x = tonumber(loader.getenv("loader_logo_x")) or 34824a1bd54SKyle Evans drawer.logo_position.x; 34924a1bd54SKyle Evans local y = tonumber(loader.getenv("loader_logo_y")) or 35024a1bd54SKyle Evans drawer.logo_position.y; 351088b4f5fSWarner Losh 352088b4f5fSWarner Losh local logo = loader.getenv("loader_logo"); 353088b4f5fSWarner Losh local colored = color.isEnabled(); 354088b4f5fSWarner Losh 355bb26c57dSKyle Evans -- Lookup 356bb26c57dSKyle Evans local logodef = drawer.logodefs[logo]; 357bb26c57dSKyle Evans 358752b2d40SKyle Evans if (logodef ~= nil) and (logodef.graphic == none) then 359088b4f5fSWarner Losh -- centre brand and text if no logo 360bb26c57dSKyle Evans if (not none_shifted) then 361bb26c57dSKyle Evans shift_brand_text(logodef.shift); 362bb26c57dSKyle Evans none_shifted = true; 363088b4f5fSWarner Losh end 364752b2d40SKyle Evans elseif (logodef == nil) or (logodef.graphic == nil) or 365bb26c57dSKyle Evans ((not colored) and logodef.requires_color) then 366bb26c57dSKyle Evans -- Choose a sensible default 36724a1bd54SKyle Evans if (colored) then 368bb26c57dSKyle Evans logodef = drawer.logodefs["orb"]; 369088b4f5fSWarner Losh else 370bb26c57dSKyle Evans logodef = drawer.logodefs["orbbw"]; 371088b4f5fSWarner Losh end 372088b4f5fSWarner Losh end 373bb26c57dSKyle Evans if (logodef.shift ~= nil) then 374bb26c57dSKyle Evans x = x + logodef.shift.x; 375bb26c57dSKyle Evans y = y + logodef.shift.y; 376bb26c57dSKyle Evans end 377752b2d40SKyle Evans drawer.draw(x, y, logodef.graphic); 378088b4f5fSWarner Loshend 379088b4f5fSWarner Losh 38024a1bd54SKyle Evansreturn drawer; 381