664 lines
19 KiB
C

/** @file
This file is part of OpenCanopy, OpenCore GUI.
Copyright (c) 2018-2019, Download-Fritz. All rights reserved.<BR>
SPDX-License-Identifier: BSD-3-Clause
**/
#include <Uefi.h>
#include <IndustryStandard/AppleIcon.h>
#include <Protocol/OcInterface.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OcBootManagementLib.h>
#include <Library/OcStorageLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Library/BaseLib.h>
#include <Guid/AppleVariable.h>
#include <Protocol/UserInterfaceTheme.h>
#include "OpenCanopy.h"
#include "BmfLib.h"
#include "GuiApp.h"
GLOBAL_REMOVE_IF_UNREFERENCED BOOT_PICKER_GUI_CONTEXT mGuiContext;
//
// FIXME: Should not be global here.
//
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL mHighlightPixel = { 0xAF, 0xAF, 0xAF, 0x32 };
STATIC
CONST CHAR8 *
mLabelNames[LABEL_NUM_TOTAL] = {
[LABEL_GENERIC_HDD] = "EFIBoot",
[LABEL_APPLE] = "Apple",
[LABEL_APPLE_RECOVERY] = "AppleRecv",
[LABEL_APPLE_TIME_MACHINE] = "AppleTM",
[LABEL_WINDOWS] = "Windows",
[LABEL_OTHER] = "Other",
[LABEL_TOOL] = "Tool",
[LABEL_RESET_NVRAM] = "ResetNVRAM",
[LABEL_SHELL] = "Shell",
[LABEL_SIP_IS_ENABLED] = "SIPEnabled",
[LABEL_SIP_IS_DISABLED] = "SIPDisabled"
};
STATIC
CONST CHAR8 *
mIconNames[ICON_NUM_TOTAL] = {
[ICON_CURSOR] = "Cursor",
[ICON_SELECTED] = "Selected",
[ICON_SELECTOR] = "Selector",
[ICON_SET_DEFAULT] = "SetDefault",
[ICON_LEFT] = "Left",
[ICON_RIGHT] = "Right",
[ICON_SHUT_DOWN] = "ShutDown",
[ICON_RESTART] = "Restart",
[ICON_BUTTON_FOCUS] = "BtnFocus",
[ICON_PASSWORD] = "Password",
[ICON_DOT] = "Dot",
[ICON_ENTER] = "Enter",
[ICON_LOCK] = "Lock",
[ICON_GENERIC_HDD] = "HardDrive",
[ICON_APPLE] = "Apple",
[ICON_APPLE_RECOVERY] = "AppleRecv",
[ICON_APPLE_TIME_MACHINE] = "AppleTM",
[ICON_WINDOWS] = "Windows",
[ICON_OTHER] = "Other",
[ICON_TOOL] = "Tool",
[ICON_RESET_NVRAM] = "ResetNVRAM",
[ICON_SHELL] = "Shell"
};
STATIC
VOID
InternalSafeFreePool (
IN CONST VOID *Memory
)
{
if (Memory != NULL) {
FreePool ((VOID *)Memory);
}
}
STATIC
VOID
InternalContextDestruct (
IN OUT BOOT_PICKER_GUI_CONTEXT *Context
)
{
UINT32 Index;
UINT32 Index2;
for (Index = 0; Index < ICON_NUM_TOTAL; ++Index) {
for (Index2 = 0; Index2 < ICON_TYPE_COUNT; ++Index2) {
InternalSafeFreePool (Context->Icons[Index][Index2].Buffer);
}
}
for (Index = 0; Index < LABEL_NUM_TOTAL; ++Index) {
InternalSafeFreePool (Context->Labels[Index].Buffer);
}
InternalSafeFreePool (Context->Background.Buffer);
InternalSafeFreePool (Context->FontContext.FontImage.Buffer);
/*
InternalSafeFreePool (Context->Poof[0].Buffer);
InternalSafeFreePool (Context->Poof[1].Buffer);
InternalSafeFreePool (Context->Poof[2].Buffer);
InternalSafeFreePool (Context->Poof[3].Buffer);
InternalSafeFreePool (Context->Poof[4].Buffer);
*/
}
STATIC
EFI_STATUS
LoadImageFileFromStorage (
OUT GUI_IMAGE *Images,
IN OC_STORAGE_CONTEXT *Storage,
IN CONST CHAR8 *ImageFilePath,
IN UINT8 Scale,
IN UINT32 MatchWidth,
IN UINT32 MatchHeight,
IN BOOLEAN Icon,
IN CONST CHAR8 *Prefix,
IN BOOLEAN AllowLessSize
)
{
EFI_STATUS Status;
CHAR16 Path[OC_STORAGE_SAFE_PATH_MAX];
UINT8 *FileData;
UINT32 FileSize;
UINT32 ImageCount;
UINT32 Index;
ASSERT (ImageFilePath != NULL);
ASSERT (Scale == 1 || Scale == 2);
ImageCount = Icon ? ICON_TYPE_COUNT : 1; ///< Icons can be external.
for (Index = 0; Index < ImageCount; ++Index) {
Status = OcUnicodeSafeSPrint (
Path,
sizeof (Path),
OPEN_CORE_IMAGE_PATH L"%a\\%a%a.icns",
Prefix,
Index > 0 ? "Ext" : "",
ImageFilePath
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "OCUI: Cannot fit %a\n", ImageFilePath));
return EFI_OUT_OF_RESOURCES;
}
UnicodeUefiSlashes (Path);
Status = EFI_NOT_FOUND;
if (OcStorageExistsFileUnicode (Storage, Path)) {
FileData = OcStorageReadFileUnicode (Storage, Path, &FileSize);
if ((FileData != NULL) && (FileSize > 0)) {
Status = GuiIcnsToImageIcon (
&Images[Index],
FileData,
FileSize,
Scale,
MatchWidth,
MatchHeight,
AllowLessSize
);
}
if (FileData != NULL) {
FreePool (FileData);
}
}
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_INFO,
"OCUI: Failed to load image (%u/%u) %s prefix:%a icon:%d - %r\n",
Index+1,
ImageCount,
Path,
Prefix,
Icon,
Status
));
if (Index == ICON_TYPE_BASE) {
STATIC_ASSERT (
ICON_TYPE_BASE == 0,
"Memory may be leaked due to previously loaded images."
);
return EFI_NOT_FOUND;
}
Images[Index].Width = 0;
Images[Index].Height = 0;
Images[Index].Buffer = NULL;
}
}
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
LoadLabelFileFromStorageForScale (
IN OC_STORAGE_CONTEXT *Storage,
IN CONST CHAR8 *LabelFilePath,
IN UINT8 Scale,
OUT VOID **FileData,
OUT UINT32 *FileSize
)
{
EFI_STATUS Status;
CHAR16 Path[OC_STORAGE_SAFE_PATH_MAX];
ASSERT (Scale == 1 || Scale == 2);
Status = OcUnicodeSafeSPrint (
Path,
sizeof (Path),
OPEN_CORE_LABEL_PATH L"%a.%a",
LabelFilePath,
Scale == 2 ? "l2x" : "lbl"
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "OCUI: Cannot fit %a\n", LabelFilePath));
return EFI_OUT_OF_RESOURCES;
}
*FileData = OcStorageReadFileUnicode (Storage, Path, FileSize);
if (*FileData == NULL) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to load %s\n", Path));
return EFI_NOT_FOUND;
}
if (*FileSize == 0) {
FreePool (*FileData);
DEBUG ((DEBUG_WARN, "OCUI: Empty %s\n", Path));
return EFI_NOT_FOUND;
}
return EFI_SUCCESS;
}
EFI_STATUS
LoadLabelFromStorage (
IN OC_STORAGE_CONTEXT *Storage,
IN CONST CHAR8 *ImageFilePath,
IN UINT8 Scale,
IN BOOLEAN Inverted,
OUT GUI_IMAGE *Image
)
{
VOID *ImageData;
UINT32 ImageSize;
EFI_STATUS Status;
ASSERT (Scale == 1 || Scale == 2);
Status = LoadLabelFileFromStorageForScale (Storage, ImageFilePath, Scale, &ImageData, &ImageSize);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GuiLabelToImage (Image, ImageData, ImageSize, Scale, Inverted);
FreePool (ImageData);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to decode label %a - %r\n", ImageFilePath, Status));
}
return Status;
}
EFI_STATUS
InternalGetFlavourIcon (
IN BOOT_PICKER_GUI_CONTEXT *GuiContext,
IN OC_STORAGE_CONTEXT *Storage,
IN CHAR8 *FlavourName,
IN UINTN FlavourNameLen,
IN UINT32 IconTypeIndex,
IN BOOLEAN UseFlavourIcon,
OUT GUI_IMAGE *EntryIcon,
OUT BOOLEAN *CustomIcon
)
{
EFI_STATUS Status;
CHAR16 Path[OC_STORAGE_SAFE_PATH_MAX];
CHAR8 ImageName[OC_MAX_CONTENT_FLAVOUR_SIZE];
UINT8 *FileData;
UINT32 FileSize;
UINTN Index;
ASSERT (EntryIcon != NULL);
ASSERT (CustomIcon != NULL);
if ((FlavourNameLen == 0) ||
(OcAsciiStrniCmp (FlavourName, OC_FLAVOUR_AUTO, FlavourNameLen) == 0)
)
{
return EFI_UNSUPPORTED;
}
//
// Look in preloaded icons
//
for (Index = ICON_NUM_SYS; Index < ICON_NUM_TOTAL; ++Index) {
if (OcAsciiStrniCmp (FlavourName, mIconNames[Index], FlavourNameLen) == 0) {
if (GuiContext->Icons[Index][IconTypeIndex].Buffer != NULL) {
CopyMem (EntryIcon, &GuiContext->Icons[Index][IconTypeIndex], sizeof (*EntryIcon));
*CustomIcon = FALSE;
return EFI_SUCCESS;
}
break;
}
}
//
// Look for custom icon
//
if (!UseFlavourIcon) {
return EFI_NOT_FOUND;
}
AsciiStrnCpyS (ImageName, OC_MAX_CONTENT_FLAVOUR_SIZE, FlavourName, FlavourNameLen);
Status = OcUnicodeSafeSPrint (
Path,
sizeof (Path),
OPEN_CORE_IMAGE_PATH L"%a\\%a%a.icns",
GuiContext->Prefix,
IconTypeIndex > 0 ? "Ext" : "",
ImageName
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "OCUI: Cannot fit %a\n", ImageName));
return Status;
}
UnicodeUefiSlashes (Path);
DEBUG ((DEBUG_INFO, "OCUI: Trying flavour icon %s\n", Path));
Status = EFI_NOT_FOUND;
if (OcStorageExistsFileUnicode (Storage, Path)) {
FileData = OcStorageReadFileUnicode (Storage, Path, &FileSize);
if ((FileData != NULL) && (FileSize > 0)) {
Status = GuiIcnsToImageIcon (
EntryIcon,
FileData,
FileSize,
GuiContext->Scale,
BOOT_ENTRY_ICON_DIMENSION,
BOOT_ENTRY_ICON_DIMENSION,
FALSE
);
}
if (FileData != NULL) {
FreePool (FileData);
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "OCUI: Invalid icon file\n"));
}
}
if (!EFI_ERROR (Status)) {
ASSERT (EntryIcon->Buffer != NULL);
*CustomIcon = TRUE;
return EFI_SUCCESS;
}
return EFI_NOT_FOUND;
}
EFI_STATUS
InternalContextConstruct (
OUT BOOT_PICKER_GUI_CONTEXT *Context,
IN OC_STORAGE_CONTEXT *Storage,
IN OC_PICKER_CONTEXT *Picker
)
{
EFI_STATUS Status;
EFI_USER_INTERFACE_THEME_PROTOCOL *UiTheme;
VOID *FontImage;
VOID *FontData;
UINT32 FontImageSize;
UINT32 FontDataSize;
UINTN UiScaleSize;
UINT32 Index;
UINT32 ImageWidth;
UINT32 ImageHeight;
BOOLEAN Result;
BOOLEAN AllowLessSize;
ASSERT (Context != NULL);
Context->Scale = 1;
UiScaleSize = sizeof (Context->Scale);
Status = gRT->GetVariable (
APPLE_UI_SCALE_VARIABLE_NAME,
&gAppleVendorVariableGuid,
NULL,
&UiScaleSize,
(VOID *)&Context->Scale
);
if (EFI_ERROR (Status) || (Context->Scale != 2)) {
Context->Scale = 1;
}
Status = gBS->LocateProtocol (
&gEfiUserInterfaceThemeProtocolGuid,
NULL,
(VOID **)&UiTheme
);
if (!EFI_ERROR (Status)) {
Status = UiTheme->GetBackgroundColor (&Context->BackgroundColor.Raw);
}
if (EFI_ERROR (Status)) {
Context->BackgroundColor.Raw = APPLE_COLOR_SYRAH_BLACK;
}
if (AsciiStrCmp (Picker->PickerVariant, "Auto") == 0) {
if (Context->BackgroundColor.Raw == APPLE_COLOR_LIGHT_GRAY) {
Context->Prefix = "Acidanthera\\Chardonnay";
} else {
Context->Prefix = "Acidanthera\\GoldenGate";
}
} else if (AsciiStrCmp (Picker->PickerVariant, "Default") == 0) {
Context->Prefix = "Acidanthera\\GoldenGate";
} else {
Context->Prefix = Picker->PickerVariant;
}
LoadImageFileFromStorage (
&Context->Background,
Storage,
"Background",
Context->Scale,
0,
0,
FALSE,
Context->Prefix,
FALSE
);
if (Context->BackgroundColor.Raw == APPLE_COLOR_SYRAH_BLACK) {
Context->LightBackground = FALSE;
} else if (Context->BackgroundColor.Raw == APPLE_COLOR_LIGHT_GRAY) {
Context->LightBackground = TRUE;
} else {
//
// FIXME: Support proper W3C formula.
//
Context->LightBackground = (Context->BackgroundColor.Pixel.Red * 299U
+ Context->BackgroundColor.Pixel.Green * 587U
+ Context->BackgroundColor.Pixel.Blue * 114U) >= 186000;
}
//
// Set background colour with full opacity.
//
Context->BackgroundColor.Pixel.Reserved = 0xFF;
for (Index = 0; Index < ICON_NUM_TOTAL; ++Index) {
AllowLessSize = FALSE;
if (Index == ICON_CURSOR) {
ImageWidth = MAX_CURSOR_DIMENSION;
ImageHeight = MAX_CURSOR_DIMENSION;
AllowLessSize = TRUE;
} else if (Index == ICON_SELECTED) {
ImageWidth = BOOT_SELECTOR_BACKGROUND_DIMENSION;
ImageHeight = BOOT_SELECTOR_BACKGROUND_DIMENSION;
} else if ((Index == ICON_SELECTOR) || (Index == ICON_SET_DEFAULT)) {
ImageWidth = BOOT_SELECTOR_BUTTON_WIDTH;
ImageHeight = BOOT_SELECTOR_BUTTON_HEIGHT;
AllowLessSize = TRUE;
} else if ((Index == ICON_LEFT) || (Index == ICON_RIGHT)) {
ImageWidth = BOOT_SCROLL_BUTTON_DIMENSION;
ImageHeight = BOOT_SCROLL_BUTTON_DIMENSION;
} else if ((Index == ICON_SHUT_DOWN) || (Index == ICON_RESTART)) {
ImageWidth = BOOT_ACTION_BUTTON_DIMENSION;
ImageHeight = BOOT_ACTION_BUTTON_DIMENSION;
AllowLessSize = TRUE;
} else if (Index == ICON_BUTTON_FOCUS) {
ImageWidth = BOOT_ACTION_BUTTON_FOCUS_DIMENSION;
ImageHeight = BOOT_ACTION_BUTTON_FOCUS_DIMENSION;
AllowLessSize = TRUE;
} else if (Index == ICON_PASSWORD) {
ImageWidth = PASSWORD_BOX_WIDTH;
ImageHeight = PASSWORD_BOX_HEIGHT;
AllowLessSize = TRUE;
} else if (Index == ICON_LOCK) {
ImageWidth = PASSWORD_LOCK_DIMENSION;
ImageHeight = PASSWORD_LOCK_DIMENSION;
AllowLessSize = TRUE;
} else if (Index == ICON_ENTER) {
ImageWidth = PASSWORD_ENTER_WIDTH;
ImageHeight = PASSWORD_ENTER_HEIGHT;
AllowLessSize = TRUE;
} else if (Index == ICON_DOT) {
ImageWidth = PASSWORD_DOT_DIMENSION;
ImageHeight = PASSWORD_DOT_DIMENSION;
AllowLessSize = TRUE;
} else {
ImageWidth = BOOT_ENTRY_ICON_DIMENSION;
ImageHeight = BOOT_ENTRY_ICON_DIMENSION;
}
Status = LoadImageFileFromStorage (
Context->Icons[Index],
Storage,
mIconNames[Index],
Context->Scale,
ImageWidth,
ImageHeight,
Index >= ICON_NUM_SYS,
Context->Prefix,
AllowLessSize
);
if (!EFI_ERROR (Status)) {
if ((Index == ICON_SELECTOR) || (Index == ICON_SET_DEFAULT) || (Index == ICON_LEFT) || (Index == ICON_RIGHT) || (Index == ICON_SHUT_DOWN) || (Index == ICON_RESTART) || (Index == ICON_ENTER)) {
Status = GuiCreateHighlightedImage (
&Context->Icons[Index][ICON_TYPE_HELD],
&Context->Icons[Index][ICON_TYPE_BASE],
&mHighlightPixel
);
if (Index == ICON_SET_DEFAULT) {
if (Context->Icons[Index]->Width != Context->Icons[ICON_SELECTOR]->Width) {
Status = EFI_UNSUPPORTED;
DEBUG ((
DEBUG_WARN,
"OCUI: %a width %upx != %a width %upx\n",
mIconNames[Index],
Context->Icons[Index]->Width,
mIconNames[ICON_SELECTOR],
Context->Icons[ICON_SELECTOR]->Width
));
STATIC_ASSERT (
ICON_SELECTOR < ICON_SET_DEFAULT,
"Selector must be loaded before SetDefault."
);
}
}
} else if ( (Index == ICON_GENERIC_HDD)
&& (Context->Icons[Index][ICON_TYPE_EXTERNAL].Buffer == NULL))
{
//
// For generic disk icon being able to distinguish internal and external
// disk icons is a security requirement. These icons are used whenever
// 'typed' external icons are not available.
//
Status = EFI_NOT_FOUND;
DEBUG ((DEBUG_WARN, "OCUI: Missing external disk icon\n"));
STATIC_ASSERT (
ICON_GENERIC_HDD < ICON_NUM_MANDATORY,
"The base icon should must be cleaned up explicitly."
);
} else if (Index == ICON_CURSOR) {
if ( (Context->Icons[ICON_CURSOR][ICON_TYPE_BASE].Width < MIN_CURSOR_DIMENSION * Context->Scale)
|| (Context->Icons[ICON_CURSOR][ICON_TYPE_BASE].Height < MIN_CURSOR_DIMENSION * Context->Scale))
{
DEBUG ((
DEBUG_INFO,
"OCUI: Expected at least %dx%d for cursor, actual %dx%d\n",
MIN_CURSOR_DIMENSION * Context->Scale,
MIN_CURSOR_DIMENSION * Context->Scale,
Context->Icons[ICON_CURSOR][ICON_TYPE_BASE].Width,
Context->Icons[ICON_CURSOR][ICON_TYPE_BASE].Height
));
Status = EFI_UNSUPPORTED;
}
}
} else {
ZeroMem (&Context->Icons[Index], sizeof (Context->Icons[Index]));
}
if (EFI_ERROR (Status) && (Index < ICON_NUM_MANDATORY)) {
DEBUG ((DEBUG_WARN, "OCUI: Failed to load images for %a\n", Context->Prefix));
InternalContextDestruct (Context);
return EFI_UNSUPPORTED;
}
}
for (Index = 0; Index < LABEL_NUM_TOTAL; ++Index) {
Status = LoadLabelFromStorage (
Storage,
mLabelNames[Index],
Context->Scale,
Context->LightBackground,
&Context->Labels[Index]
);
if (EFI_ERROR (Status)) {
Context->Labels[Index].Buffer = NULL;
DEBUG ((DEBUG_WARN, "OCUI: Failed to load images\n"));
InternalContextDestruct (Context);
return EFI_UNSUPPORTED;
}
}
if (Context->Scale == 2) {
FontImage = OcStorageReadFileUnicode (Storage, OPEN_CORE_FONT_PATH L"Font_2x.png", &FontImageSize);
FontData = OcStorageReadFileUnicode (Storage, OPEN_CORE_FONT_PATH L"Font_2x.bin", &FontDataSize);
} else {
FontImage = OcStorageReadFileUnicode (Storage, OPEN_CORE_FONT_PATH L"Font_1x.png", &FontImageSize);
FontData = OcStorageReadFileUnicode (Storage, OPEN_CORE_FONT_PATH L"Font_1x.bin", &FontDataSize);
}
if ((FontImage != NULL) && (FontData != NULL)) {
Result = GuiFontConstruct (
&Context->FontContext,
FontImage,
FontImageSize,
FontData,
FontDataSize,
Context->Scale
);
if (Context->FontContext.BmfContext.Height != (BOOT_ENTRY_LABEL_HEIGHT - BOOT_ENTRY_LABEL_TEXT_OFFSET) * Context->Scale) {
DEBUG ((
DEBUG_WARN,
"OCUI: Font has height %d instead of %d\n",
Context->FontContext.BmfContext.Height,
(BOOT_ENTRY_LABEL_HEIGHT - BOOT_ENTRY_LABEL_TEXT_OFFSET) * Context->Scale
));
Result = FALSE;
}
} else {
Result = FALSE;
}
if (!Result) {
DEBUG ((DEBUG_WARN, "OCUI: Font init failed\n"));
InternalContextDestruct (Context);
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
CONST GUI_IMAGE *
InternalGetCursorImage (
IN BOOT_PICKER_GUI_CONTEXT *Context
)
{
ASSERT (Context != NULL);
//
// ATTENTION: All images must have the same dimensions.
//
return &Context->Icons[ICON_CURSOR][ICON_TYPE_BASE];
}