diff --git a/Application/ModernSetupApp/ModernSetupApp.c b/Application/ModernSetupApp/ModernSetupApp.c index 432a93e..3cd62d7 100644 --- a/Application/ModernSetupApp/ModernSetupApp.c +++ b/Application/ModernSetupApp/ModernSetupApp.c @@ -307,7 +307,7 @@ UefiMain ( } } else if (Page == PageDashboard) { if (ModernSetupGetDashboardQuickGrid (&Ui, mModernSetupPreferences.DashboardDensity, &DashboardGrid) && - ((DashboardSelection + DashboardGrid.CardsPerRow) < DASHBOARD_QUICK_CARD_COUNT)) + ((DashboardSelection + DashboardGrid.CardsPerRow) < ModernSetupDashboardVisibleQuickCardCount ())) { DashboardSelection += DashboardGrid.CardsPerRow; } @@ -339,7 +339,7 @@ UefiMain ( { DashboardSelection--; } else if (!DashboardGrid.Visible) { - DashboardSelection = (DashboardSelection == 0) ? (DASHBOARD_QUICK_CARD_COUNT - 1) : (DashboardSelection - 1); + DashboardSelection = (DashboardSelection == 0) ? (ModernSetupDashboardVisibleQuickCardCount () - 1) : (DashboardSelection - 1); } } else if (Focus == SetupFocusNav) { Page = (Page == 0) ? (PageMax - 1) : (Page - 1); @@ -359,11 +359,11 @@ UefiMain ( ModernSetupCancelPreferencePopup (); } else if (Page == PageDashboard) { if (ModernSetupGetDashboardQuickGrid (&Ui, mModernSetupPreferences.DashboardDensity, &DashboardGrid)) { - if ((((DashboardSelection % DashboardGrid.CardsPerRow) + 1) < DashboardGrid.CardsPerRow) && ((DashboardSelection + 1) < DASHBOARD_QUICK_CARD_COUNT)) { + if ((((DashboardSelection % DashboardGrid.CardsPerRow) + 1) < DashboardGrid.CardsPerRow) && ((DashboardSelection + 1) < ModernSetupDashboardVisibleQuickCardCount ())) { DashboardSelection++; } } else { - DashboardSelection = (DashboardSelection + 1) % DASHBOARD_QUICK_CARD_COUNT; + DashboardSelection = (DashboardSelection + 1) % ModernSetupDashboardVisibleQuickCardCount (); } StatusMessage[0] = L'\0'; } else { diff --git a/Application/ModernSetupApp/ModernSetupAppActions.c b/Application/ModernSetupApp/ModernSetupAppActions.c index c6b5853..b94ce04 100644 --- a/Application/ModernSetupApp/ModernSetupAppActions.c +++ b/Application/ModernSetupApp/ModernSetupAppActions.c @@ -34,6 +34,85 @@ STATIC CONST MODERN_SETUP_DASHBOARD_ROUTE mDashboardCategoryRoutes[DASHBOARD_QU { PageServerInventory, SetupFocusNav } }; +/** + Decide whether a standardized dashboard quick-card is applicable on the + current platform. See the contract in ModernSetupAppInternal.h. + + @param[in] CardIndex Catalog card index in [0, DASHBOARD_QUICK_CARD_COUNT). + + @retval TRUE The card should be shown on this platform. + @retval FALSE The card is hidden (or CardIndex is out of range). +**/ +BOOLEAN +ModernSetupDashboardQuickCardApplicable ( + IN UINTN CardIndex + ) +{ + MODERN_SETUP_PROVIDER_SNAPSHOT Providers; + + if (CardIndex >= DASHBOARD_QUICK_CARD_COUNT) { + return FALSE; + } + + // + // Every catalog card is universally applicable except the trailing + // server-inventory card, which is server-class content. + // + if (CardIndex != MODERN_SETUP_DASHBOARD_SERVER_CARD) { + return TRUE; + } + + ModernSetupGetCachedProviderSnapshot (&Providers); + + // + // Show the server-inventory card when the chassis reports a server form + // factor, or when a management provider (IPMI / Redfish / SMBIOS management + // interface) is live -- so a managed workstation still surfaces it while a + // plain client desktop or VM does not. + // + if (StrCmp (Providers.Platform.FormFactor, L"Server") == 0) { + return TRUE; + } + + if (!EFI_ERROR (Providers.ManagementStatus) && + (Providers.Management.IpmiProtocolPresent || + Providers.Management.RedfishDiscoverPresent || + Providers.Management.SmbiosManagementInterfacePresent)) + { + return TRUE; + } + + return FALSE; +} + +/** + Return the number of dashboard quick-cards visible on the current platform. + See the contract in ModernSetupAppInternal.h. + + @return Visible quick-card count in [1, DASHBOARD_QUICK_CARD_COUNT]. +**/ +UINTN +ModernSetupDashboardVisibleQuickCardCount ( + VOID + ) +{ + UINTN Index; + UINTN Count; + + Count = 0; + for (Index = 0; Index < DASHBOARD_QUICK_CARD_COUNT; Index++) { + if (ModernSetupDashboardQuickCardApplicable (Index)) { + Count++; + } + } + + // + // The Continue/Boot/Devices core cards are always applicable, so the count is + // never zero; defend against a future predicate change regardless. + // + return MAX (Count, (UINTN)1); +} + BOOLEAN mModernSetupLanguageDropdownOpen; UINTN mModernSetupLanguageDropdownSelection; BOOLEAN mModernSetupPreferencePopupOpen; @@ -88,12 +167,20 @@ ModernSetupGetDashboardQuickGrid ( UINTN CardAreaWidth; UINTN MaxRows; UINTN ValueMinHeight; + UINTN CardCount; BOOLEAN Compact; if ((Ui == NULL) || (Grid == NULL)) { return FALSE; } + // + // Lay the grid out for the cards actually visible on this platform (the + // server-inventory card may be hidden), so the columns/rows reflow instead of + // leaving a gap. See Docs/AppFeatureStandard.md. + // + CardCount = ModernSetupDashboardVisibleQuickCardCount (); + ZeroMem (Grid, sizeof (*Grid)); Compact = (BOOLEAN)(DashboardDensity == ModernUiDashboardDensityCompact); Content = ModernSetupContentRect (Ui); @@ -115,16 +202,16 @@ ModernSetupGetDashboardQuickGrid ( MaxRows = (Grid->Panel.Height > (Grid->CardTop + ValueMinHeight + DASHBOARD_QUICK_CARD_BOTTOM)) ? ((Grid->Panel.Height - Grid->CardTop - DASHBOARD_QUICK_CARD_BOTTOM + Grid->CardGap) / (ValueMinHeight + Grid->CardGap)) : 1; - MaxRows = MAX (1, MIN (DASHBOARD_QUICK_CARD_COUNT, MaxRows)); - Grid->CardsPerRow = (DASHBOARD_QUICK_CARD_COUNT + MaxRows - 1) / MaxRows; + MaxRows = MAX (1, MIN (CardCount, MaxRows)); + Grid->CardsPerRow = (CardCount + MaxRows - 1) / MaxRows; if ((Grid->Panel.Width >= 760) && (Grid->CardsPerRow < 3)) { Grid->CardsPerRow = 3; } else if ((Grid->Panel.Width >= 500) && (Grid->CardsPerRow < 2)) { Grid->CardsPerRow = 2; } - Grid->CardsPerRow = MIN (DASHBOARD_QUICK_CARD_COUNT, Grid->CardsPerRow); - Grid->Rows = (DASHBOARD_QUICK_CARD_COUNT + Grid->CardsPerRow - 1) / Grid->CardsPerRow; + Grid->CardsPerRow = MIN (CardCount, Grid->CardsPerRow); + Grid->Rows = (CardCount + Grid->CardsPerRow - 1) / Grid->CardsPerRow; Grid->CardWidth = (CardAreaWidth > (Grid->CardGap * (Grid->CardsPerRow - 1))) ? ((CardAreaWidth - (Grid->CardGap * (Grid->CardsPerRow - 1))) / Grid->CardsPerRow) : MAX (1, CardAreaWidth / Grid->CardsPerRow); @@ -250,6 +337,14 @@ ModernSetupGetDashboardCategoryRoute ( return FALSE; } + // + // A card hidden on this platform (e.g. server-inventory on a client) MUST NOT + // be Enter-activatable even if a stale selection index points at it. + // + if (!ModernSetupDashboardQuickCardApplicable (Selection)) { + return FALSE; + } + *Route = mDashboardCategoryRoutes[Selection]; return TRUE; } @@ -604,7 +699,7 @@ ModernSetupGetPageSelectableCount ( MODERN_SETUP_DASHBOARD_QUICK_GRID Grid; return ModernSetupGetDashboardQuickGrid (Ui, mModernSetupPreferences.DashboardDensity, &Grid) ? - DASHBOARD_QUICK_CARD_COUNT : 0; + ModernSetupDashboardVisibleQuickCardCount () : 0; } case PageBoot: { diff --git a/Application/ModernSetupApp/ModernSetupAppChrome.c b/Application/ModernSetupApp/ModernSetupAppChrome.c index f9b9575..b2629b1 100644 --- a/Application/ModernSetupApp/ModernSetupAppChrome.c +++ b/Application/ModernSetupApp/ModernSetupAppChrome.c @@ -12,6 +12,7 @@ STATIC CONST PAGE_DESCRIPTOR mPages[] = { { PageDashboard, ModernUiStringPageDashboard, ModernUiStringPageDashboardHint }, + { PageSystemInfo, ModernUiStringPageSystemInfo, ModernUiStringPageSystemInfoHint }, { PageBoot, ModernUiStringPageBoot, ModernUiStringPageBootHint }, { PageDevices, ModernUiStringPageDevices, ModernUiStringPageDevicesHint }, { PageSecurity, ModernUiStringPageSecurity, ModernUiStringPageSecurityHint }, @@ -27,6 +28,7 @@ STATIC CONST PAGE_DESCRIPTOR mPages[] = { STATIC CONST CHAR16 *mEnglishCompactTabLabels[] = { L"Main", + L"System", L"Boot", L"Devices", L"Security", @@ -42,6 +44,7 @@ STATIC CONST CHAR16 *mEnglishCompactTabLabels[] = { STATIC CONST CHAR16 *mChineseCompactTabLabels[] = { L"主页", + L"系统", L"启动", L"设备", L"安全", diff --git a/Application/ModernSetupApp/ModernSetupAppDashboard.c b/Application/ModernSetupApp/ModernSetupAppDashboard.c index 85db77b..1f36034 100644 --- a/Application/ModernSetupApp/ModernSetupAppDashboard.c +++ b/Application/ModernSetupApp/ModernSetupAppDashboard.c @@ -56,8 +56,10 @@ STATIC CONST MODERN_UI_STRING_ID mDashboardCardGroupLabel[DASHBOARD_QUICK_CARD_ @param[in] Label Label text. Must not be NULL. @param[in] Value Value text. Must not be NULL. **/ +#define DASHBOARD_INFO_ROW_STEP 32 + STATIC -VOID +UINTN DrawDashboardInfoRow ( IN MODERN_UI_RENDER_CONTEXT *Ui, IN CONST MODERN_UI_THEME *Theme, @@ -73,7 +75,18 @@ DrawDashboardInfoRow ( UINTN ValueX; if (Width < 32) { - return; + return Y; + } + + // + // Skip placeholder/empty values so a flowed card does not show "N/A" clutter + // or leave a gap. The caller passes the returned Y as the next row position, + // so skipped rows collapse. Callers that keep fixed Y can ignore the result. + // + if ((Value == NULL) || (Value[0] == CHAR_NULL) || + (StrCmp (Value, L"N/A") == 0) || (StrCmp (Value, L"Limited data") == 0)) + { + return Y; } Background = ModernUiBlendColor (Theme->Surface, Theme->BackgroundBlack, 30); @@ -81,6 +94,7 @@ DrawDashboardInfoRow ( ValueX = X + LabelWidth; ModernUiDrawTextFit (Ui, X, Y, LabelWidth - 8, Label, Theme->MutedText, Background); ModernUiDrawTextFit (Ui, ValueX, Y, (Width > LabelWidth) ? (Width - LabelWidth) : Width, Value, Theme->Text, Background); + return Y + DASHBOARD_INFO_ROW_STEP; } /** @@ -274,7 +288,7 @@ DashboardProviderHealthText ( case ModernSetupProviderHealthReady: return L"就绪"; case ModernSetupProviderHealthDegraded: - return L"Degraded"; + return L"退化"; case ModernSetupProviderHealthNotReady: default: return L"未就绪"; @@ -337,7 +351,7 @@ ModernSetupDrawDashboard ( CHAR16 Resolution[48]; CHAR16 BootCount[48]; CHAR16 DeviceCount[48]; - CHAR16 MemoryText[48]; + CHAR16 MemoryText[96]; CHAR16 SecurityText[48]; CHAR16 ArchitectureText[96]; CHAR16 ProviderCountText[48]; @@ -379,7 +393,11 @@ ModernSetupDrawDashboard ( ModernSetupGetCachedProviderSnapshot (&Providers); ModernSetupGetProviderHealthSummary (&Providers, &ProviderHealth); - UnicodeSPrint (MemoryText, sizeof (MemoryText), L"%lu MB", Providers.Platform.MemorySizeMb); + if (Providers.Platform.MemoryDetail[0] != L'\0') { + UnicodeSPrint (MemoryText, sizeof (MemoryText), L"%lu MB (%s)", Providers.Platform.MemorySizeMb, Providers.Platform.MemoryDetail); + } else { + UnicodeSPrint (MemoryText, sizeof (MemoryText), L"%lu MB", Providers.Platform.MemorySizeMb); + } UnicodeSPrint (ArchitectureText, sizeof (ArchitectureText), L"%s", Providers.Platform.Architecture); UnicodeSPrint ( SecurityText, @@ -390,7 +408,7 @@ ModernSetupDrawDashboard ( ); UnicodeSPrint (ProviderCountText, sizeof (ProviderCountText), Zh ? L"%u/%u 就绪" : L"%u/%u ready", ProviderHealth.ReadyProviders, ProviderHealth.TotalProviders); if (ProviderHealth.State == ModernSetupProviderHealthReady) { - UnicodeSPrint (ProviderIssueText, sizeof (ProviderIssueText), Zh ? L"OK" : L"All providers ready"); + UnicodeSPrint (ProviderIssueText, sizeof (ProviderIssueText), Zh ? L"已就绪" : L"All providers ready"); } else { UnicodeSPrint (ProviderIssueText, sizeof (ProviderIssueText), Zh ? L"%s 不可用" : L"%s unavailable", ProviderHealth.FirstIssueName); } @@ -490,14 +508,25 @@ ModernSetupDrawDashboard ( } DrawDashboardSection (Ui, Theme, SystemPanel, Zh ? L"系统状态" : L"System Information", TRUE); ModernUiDrawFocusFrame (Ui, SystemPanel, (BOOLEAN)(Focus == SetupFocusContent), Theme); - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 58, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringFirmwareVendor), Providers.Platform.FirmwareVendor); - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 90, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringFirmwareRevision), Providers.Platform.FirmwareRevision); - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 122, SystemPanel.Width - 44, Zh ? L"平台" : L"Platform", Providers.Platform.Platform); - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 154, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringFormFactor), Providers.Platform.FormFactor); - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 186, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringBootMode), Providers.Platform.BootMode); - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 218, SystemPanel.Width - 44, Zh ? L"内存" : L"Memory", MemoryText); - if (TopHeight >= 260) { - DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, SystemPanel.Y + 250, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringDisplay), Resolution); + { + // + // Flow the system-status rows: each row advances the running Y, and rows + // whose provider value is a placeholder ("N/A") collapse instead of leaving + // a labelled blank, so the card reads clean. + // + UINTN RowY; + + RowY = SystemPanel.Y + 58; + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringFirmwareVendor), Providers.Platform.FirmwareVendor); + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringFirmwareRevision), Providers.Platform.FirmwareRevision); + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, Zh ? L"平台" : L"Platform", Providers.Platform.Platform); + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, L"CPU", Providers.Platform.Processor); + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringFormFactor), Providers.Platform.FormFactor); + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringBootMode), Providers.Platform.BootMode); + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, Zh ? L"内存" : L"Memory", MemoryText); + if (TopHeight >= 260) { + RowY = DrawDashboardInfoRow (Ui, Theme, SystemPanel.X + 22, RowY, SystemPanel.Width - 44, ModernUiGetString (ModernUiStringDisplay), Resolution); + } } if (MonitorPanel.Width > 0) { @@ -519,7 +548,7 @@ ModernSetupDrawDashboard ( if (Grid.Visible) { DrawDashboardSection (Ui, Theme, QuickPanel, ModernUiGetString (ModernUiStringSetupCategories), FALSE); - for (CardIndex = 0; CardIndex < DASHBOARD_QUICK_CARD_COUNT; CardIndex++) { + for (CardIndex = 0; CardIndex < ModernSetupDashboardVisibleQuickCardCount (); CardIndex++) { CardX = QuickPanel.X + 20 + ((CardIndex % Grid.CardsPerRow) * (Grid.CardWidth + Grid.CardGap)); CardY = QuickPanel.Y + Grid.CardTop + ((CardIndex / Grid.CardsPerRow) * (Grid.CardHeight + Grid.CardGap)); QuickCard = (MODERN_UI_RECT){ CardX, CardY, Grid.CardWidth, Grid.CardHeight }; diff --git a/Application/ModernSetupApp/ModernSetupAppInternal.h b/Application/ModernSetupApp/ModernSetupAppInternal.h index 249ab48..6410c7a 100644 --- a/Application/ModernSetupApp/ModernSetupAppInternal.h +++ b/Application/ModernSetupApp/ModernSetupAppInternal.h @@ -51,6 +51,16 @@ #define MAX_DEVICE_ROWS 9 #define DASHBOARD_QUICK_CARD_COUNT 8 #define MODERN_SETUP_DASHBOARD_CONTINUE_CARD 0 +// +// The server-inventory card is the last entry in the standardized quick-card +// catalog (see Docs/AppFeatureStandard.md). It is the only class-scoped card: +// it is hidden on client/unknown platforms unless a management provider reports +// live data. Because it is the trailing entry, the visible card count simply +// shrinks from the tail and the visible index still equals the catalog index -- +// no mid-array remapping is needed. Any future class-scoped card MUST also be +// kept at the tail to preserve that invariant. +// +#define MODERN_SETUP_DASHBOARD_SERVER_CARD (DASHBOARD_QUICK_CARD_COUNT - 1) #define DASHBOARD_SECTION_TITLE_TOP 12 #define DASHBOARD_QUICK_CARD_TOP 64 #define DASHBOARD_QUICK_CARD_GAP 40 @@ -60,6 +70,7 @@ typedef enum { PageDashboard = 0, + PageSystemInfo, PageBoot, PageDevices, PageSecurity, @@ -353,6 +364,45 @@ ModernSetupDashboardSelectionRequestsContinue ( IN UINTN Selection ); +/** + Decide whether a standardized dashboard quick-card is applicable on the + current platform. + + This is the single, data-driven applicability predicate required by + Docs/AppFeatureStandard.md. All catalog cards are applicable except the + trailing server-inventory card (MODERN_SETUP_DASHBOARD_SERVER_CARD), which is + applicable only when the platform is server-class or a management provider + (IPMI / Redfish / SMBIOS management interface) reports live data. The cached + provider snapshot is consulted; no providers are re-probed here. + + @param[in] CardIndex Catalog card index in [0, DASHBOARD_QUICK_CARD_COUNT). + + @retval TRUE The card should be shown, navigable, and route-activatable. + @retval FALSE The card is hidden on this platform (CardIndex out of range + also returns FALSE). +**/ +BOOLEAN +ModernSetupDashboardQuickCardApplicable ( + IN UINTN CardIndex + ); + +/** + Return the number of dashboard quick-cards visible on the current platform. + + Counts the applicable catalog cards (see + ModernSetupDashboardQuickCardApplicable). Because the only class-scoped card + is the trailing entry, the result is a contiguous prefix length in + [1, DASHBOARD_QUICK_CARD_COUNT]: the visible card index equals the catalog + index, so grid layout, keyboard navigation, and route resolution can all use + this count directly without remapping. + + @return Visible quick-card count for the current platform snapshot. +**/ +UINTN +ModernSetupDashboardVisibleQuickCardCount ( + VOID + ); + EFI_STATUS ModernSetupGetCachedBootOptions ( OUT CONST MODERN_UI_BOOT_OPTION **Options, diff --git a/Application/ModernSetupApp/ModernSetupAppPages.c b/Application/ModernSetupApp/ModernSetupAppPages.c index f174ffa..1159fe3 100644 --- a/Application/ModernSetupApp/ModernSetupAppPages.c +++ b/Application/ModernSetupApp/ModernSetupAppPages.c @@ -1042,6 +1042,145 @@ DrawFirmware ( ); } +/** + Draw the System Information page: a read-only detail view of the real platform, + processor, memory, and firmware identity collected by the platform provider. + + All values come from the cached provider snapshot (no re-probe). The page parses + no IFR and writes nothing; it is a deeper read-only companion to the dashboard + System Information panel. + + @param[in] Ui Initialized render context. Must not be NULL. + @param[in] Theme Theme token table. Must not be NULL. + @param[in] Focus Current focus area. +**/ +STATIC +VOID +MODERN_SETUP_NOINLINE +DrawSystemInfo ( + IN MODERN_UI_RENDER_CONTEXT *Ui, + IN CONST MODERN_UI_THEME *Theme, + IN SETUP_FOCUS Focus + ) +{ + MODERN_SETUP_PROVIDER_SNAPSHOT Providers; + MODERN_UI_PLATFORM_SUMMARY *Summary; + CONST CHAR8 *Language; + BOOLEAN Zh; + CHAR16 MemoryText[96]; + CONST CHAR16 *Labels[13]; + CONST CHAR16 *Values[13]; + CONST CHAR16 *Groups[13]; + UINTN Count; + + ModernSetupGetCachedProviderSnapshot (&Providers); + Summary = &Providers.Platform; + Language = ModernUiGetLanguage (); + Zh = (BOOLEAN)((Language[0] == 'z') && (Language[1] == 'h')); + + if (Summary->MemoryDetail[0] != L'\0') { + UnicodeSPrint (MemoryText, sizeof (MemoryText), L"%lu MB (%s)", Summary->MemorySizeMb, Summary->MemoryDetail); + } else { + UnicodeSPrint (MemoryText, sizeof (MemoryText), L"%lu MB", Summary->MemorySizeMb); + } + + Count = 0; + + Groups[Count] = Zh ? L"系统" : L"System"; + Labels[Count] = Zh ? L"平台" : L"Platform"; + Values[Count] = Summary->Platform; + Count++; + if (Summary->Baseboard[0] != L'\0') { + // + // "Baseboard" stays English in the zh UI: the zh glyphs are outside the + // embedded subset (graceful-fallback policy). + // + Groups[Count] = NULL; + Labels[Count] = L"Baseboard"; + Values[Count] = Summary->Baseboard; + Count++; + } + + Groups[Count] = NULL; + Labels[Count] = L"CPU"; + Values[Count] = Summary->Processor; + Count++; + Groups[Count] = NULL; + Labels[Count] = Zh ? L"内存" : L"Memory"; + Values[Count] = MemoryText; + Count++; + Groups[Count] = NULL; + Labels[Count] = Zh ? L"Arch" : L"Architecture"; + Values[Count] = Summary->Architecture; + Count++; + Groups[Count] = NULL; + Labels[Count] = ModernUiGetString (ModernUiStringFormFactor); + Values[Count] = Summary->FormFactor; + Count++; + Groups[Count] = NULL; + Labels[Count] = ModernUiGetString (ModernUiStringBootMode); + Values[Count] = Summary->BootMode; + Count++; + + // + // Identity rows are appended only when SMBIOS actually reports them, so the + // page collapses cleanly on platforms with thin SMBIOS instead of stacking + // empty rows (same philosophy as the dashboard N/A reflow). + // + if (Summary->Serial[0] != L'\0') { + // + // "Serial number" stays English in the zh UI (glyphs outside the subset). + // + Groups[Count] = NULL; + Labels[Count] = L"Serial number"; + Values[Count] = Summary->Serial; + Count++; + } + + if (Summary->Uuid[0] != L'\0') { + Groups[Count] = NULL; + Labels[Count] = L"UUID"; + Values[Count] = Summary->Uuid; + Count++; + } + + Groups[Count] = ModernUiGetString (ModernUiStringGroupFirmware); + Labels[Count] = ModernUiGetString (ModernUiStringFirmwareVendor); + Values[Count] = Summary->FirmwareVendor; + Count++; + Groups[Count] = NULL; + Labels[Count] = ModernUiGetString (ModernUiStringFirmwareRevision); + Values[Count] = Summary->FirmwareRevision; + Count++; + if (Summary->BiosVersion[0] != L'\0') { + Groups[Count] = NULL; + Labels[Count] = Zh ? L"BIOS 版本" : L"BIOS version"; + Values[Count] = Summary->BiosVersion; + Count++; + } + + if (Summary->BiosDate[0] != L'\0') { + // + // "BIOS date" stays English in the zh UI (日/期 outside the subset). + // + Groups[Count] = NULL; + Labels[Count] = L"BIOS date"; + Values[Count] = Summary->BiosDate; + Count++; + } + + DrawProviderSummaryPage ( + Ui, + Theme, + Focus, + ModernUiGetString (ModernUiStringPageSystemInfo), + Labels, + Values, + Groups, + Count + ); +} + /** Draw the Diagnostics page with read-only bring-up and table state. @@ -1942,6 +2081,9 @@ ModernSetupDrawCurrentPage ( case PageDashboard: ModernSetupDrawDashboard (Ui, Theme, Focus, DashboardSelection); break; + case PageSystemInfo: + DrawSystemInfo (Ui, Theme, Focus); + break; case PageBoot: DrawBoot (Ui, Theme, Focus, BootSelection); break; diff --git a/Application/ModernSetupApp/ModernSetupAppProvider.c b/Application/ModernSetupApp/ModernSetupAppProvider.c index 6f43d74..bbd89a4 100644 --- a/Application/ModernSetupApp/ModernSetupAppProvider.c +++ b/Application/ModernSetupApp/ModernSetupAppProvider.c @@ -65,6 +65,7 @@ InitializeProviderSnapshotDefaults ( SetUnknownText (Snapshot->Platform.FirmwareRevision, ARRAY_SIZE (Snapshot->Platform.FirmwareRevision)); SetUnknownText (Snapshot->Platform.Architecture, ARRAY_SIZE (Snapshot->Platform.Architecture)); SetUnknownText (Snapshot->Platform.Platform, ARRAY_SIZE (Snapshot->Platform.Platform)); + SetUnknownText (Snapshot->Platform.Processor, ARRAY_SIZE (Snapshot->Platform.Processor)); SetUnknownText (Snapshot->Platform.FormFactor, ARRAY_SIZE (Snapshot->Platform.FormFactor)); SetUnknownText (Snapshot->Platform.BootMode, ARRAY_SIZE (Snapshot->Platform.BootMode)); SetUnknownText (Snapshot->Firmware.Vendor, ARRAY_SIZE (Snapshot->Firmware.Vendor)); diff --git a/Assets/Screenshots/modern-aarch64-dashboard.png b/Assets/Screenshots/modern-aarch64-dashboard.png new file mode 100644 index 0000000..0af2db1 Binary files /dev/null and b/Assets/Screenshots/modern-aarch64-dashboard.png differ diff --git a/Assets/Screenshots/modern-loongarch-dashboard.png b/Assets/Screenshots/modern-loongarch-dashboard.png index 4d79bde..5c00028 100644 Binary files a/Assets/Screenshots/modern-loongarch-dashboard.png and b/Assets/Screenshots/modern-loongarch-dashboard.png differ diff --git a/Assets/Screenshots/modern-ovmf-x64-dashboard-graphite-gold.png b/Assets/Screenshots/modern-ovmf-x64-dashboard-graphite-gold.png index 7cde150..753aef2 100644 Binary files a/Assets/Screenshots/modern-ovmf-x64-dashboard-graphite-gold.png and b/Assets/Screenshots/modern-ovmf-x64-dashboard-graphite-gold.png differ diff --git a/Assets/Screenshots/modern-ovmf-x64-systeminfo.png b/Assets/Screenshots/modern-ovmf-x64-systeminfo.png new file mode 100644 index 0000000..fd1c044 Binary files /dev/null and b/Assets/Screenshots/modern-ovmf-x64-systeminfo.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7196d39..066ec11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,113 @@ this file as both a release log and a lightweight development progress record. ## Unreleased +### Changed + +- Refreshed the GitHub showcase screenshots (`Assets/Screenshots/`) to the current + app: the OVMF X64 dashboard hero and the LoongArch dashboard are re-captured + (now showing the System tab, real SMBIOS platform/CPU/memory data, and the + platform-adaptive quick cards), and new captures are added for the OVMF X64 + System Information page (`modern-ovmf-x64-systeminfo.png`) and the ArmVirt + AArch64 dashboard (`modern-aarch64-dashboard.png`). README gallery updated to + match. RISC-V capture is pending (its edk2 DEBUG firmware asserts before GOP). + +### Added + +- The System Information page now shows the deeper SMBIOS identity that does not + fit the dashboard: **Baseboard** (" ", Type 2), + **Serial number** and **UUID** (Type 1, with the all-zero/all-FF "not + present/not settable" UUID sentinels suppressed), and the BIOS-vendor-owned + **BIOS version / BIOS date** strings (Type 0) alongside the numeric firmware + revision. Each row is appended only when SMBIOS actually reports a usable value + (placeholder strings filtered), so the page collapses cleanly on thin-SMBIOS + platforms. `MODERN_UI_PLATFORM_SUMMARY` gains appended (additive) `Serial`, + `Uuid`, `Baseboard`, `BiosVersion`, and `BiosDate` fields. +- New **System Information** page (`PageSystemInfo`), a dedicated read-only detail + view reachable as the second navigation tab (after the dashboard). It shows the + real platform identity (SMBIOS Type 1), CPU (Type 4), memory type/speed + (Type 17), architecture, form factor, boot mode, and firmware vendor/revision in + grouped rows -- a deeper companion to the dashboard System Information panel. The + page parses no IFR and writes nothing; all values come from the cached provider + snapshot. Adds the `ModernUiStringPageSystemInfo`/`...Hint` strings (EN + zh: + "系统规格") and a `SETUP_PAGE` entry; the nav rail, tab labels, and the normative + page set in `Docs/AppFeatureStandard.md` are updated together. +- The dashboard Memory row now appends real module detail read from SMBIOS + Type 17 (Memory Device): " MB (-, DIMMs)" -- e.g. + "8192 MB (DDR4-3200, 2 DIMMs)". The type prefix (DDR3/DDR4/DDR5/LPDDR4/... via a + MemoryType map) and speed (configured clock preferred over rated) are each + omitted when not reported, and the row falls back to the bare total size when + Type 17 is absent. `MODERN_UI_PLATFORM_SUMMARY` gains an appended (additive) + `MemoryDetail` field; the total size still comes from the UEFI memory map. +- The dashboard System Information panel now shows a real **CPU** row read from + SMBIOS Type 4 (Processor Information): " (C/T)" + -- e.g. "QEMU Virtual CPU version 2.5+ (4C/8T)" -- honoring the SMBIOS 0xFF + CoreCount2/ThreadCount2 escape for high core counts and filtering placeholder + version strings. `MODERN_UI_PLATFORM_SUMMARY` gains an appended (additive, + end-of-struct) `Processor` field per `Docs/API_COMPATIBILITY.md`; it degrades to + the localized Unknown text when SMBIOS Type 4 is absent. +- The dashboard "Platform" value now shows the real system identity read from + SMBIOS Type 1 (System Information) -- " ", e.g. + "QEMU Standard PC (Q35 + ICH9, 2009)" -- instead of the hardcoded generic + "UEFI platform" string. Well-known meaningless OEM placeholder strings + ("To Be Filled By O.E.M.", "Not Specified", "System Product Name", etc.) are + filtered out, and the generic label remains as the fallback when SMBIOS Type 1 + is absent or reports no usable identity. `ModernUiPlatformDataLib` gains a small + SMBIOS string-set reader for this; it stays read-only and cross-architecture. + +### Fixed + +- The modern in-setup display engine no longer blanks the screen when the + graphics renderer is unavailable. Previously, when GOP was absent the + text-print path (`PrintInternal`) emitted neither GOP graphics nor console + text, so a form rendered as a blank screen (the modern engine had replaced the + native text DisplayEngine). It now falls back to plain text-console output + (`OutputString`, padded to the field width) so the form stays readable. This is + the graceful-degradation path required for GOP-absent and degenerate-mode + robustness (LVGL productization Gate 4). + +### Changed + +- The modern renderer now refuses GOP modes below a usable minimum + (`MODERN_UI_MIN_RENDER_WIDTH` x `MODERN_UI_MIN_RENDER_HEIGHT` = 640x480) in both + the GOP and LVGL `ModernUiRendererInit`. Below that, init returns + `EFI_NOT_FOUND` so the in-setup display engine degrades to text rendering and + the front-page app exits to the native shell, instead of painting broken + chrome. Normal targets (>= 800x600) are unaffected. +- The LVGL renderer's GOP mode-change re-init path is corrected: the LVGL display + resolution is now updated (`lv_display_set_resolution`) to match the + reallocated canvas, and the canvas object is created once and rebound rather + than re-created each time (which orphaned the previous canvas and its freed + buffer). Fixes the first post-mode-change frame and a per-mode-change object + leak. + +- The front-page dashboard quick-category cards are now **platform-class + adaptive** per the new normative App feature standard + (`Docs/AppFeatureStandard.md`). The quick-card *catalog* is unchanged (8 + entries: Continue, Boot, Devices, Provider status, Firmware, Power, + Performance, Server inventory), but the trailing **Server inventory** card is + now hidden on client/unknown platforms unless the chassis reports a server + form factor or a management provider (IPMI / Redfish / SMBIOS management + interface) is live. On a client desktop or a VM the grid reflows to seven + cards; on a managed/server platform all eight show. Card hiding is driven by a + single applicability predicate (`ModernSetupDashboardQuickCardApplicable`); the + grid layout, keyboard navigation, and route resolution all bound on the + resulting visible count, and a hidden card is neither focusable nor + Enter-activatable. Smoke now asserts the catalog stays fixed while requiring + the data-driven visible-count helpers, instead of pinning a fixed visible + count. + +- The firmware revision shown on the front-page dashboard System Information card + and the Firmware/Platform provider summaries is now humanized. `gST->FirmwareRevision` + (conventionally `(major << 16) | minor`) renders as `. (0x........)` + -- e.g. `1.00 (0x00010000)` -- instead of a bare `0x00010000`, so the value is + readable at a glance while the exact encoded hex is still available. Applies to + both `ModernUiPlatformDataLib` and `ModernUiFirmwareDataLib`. +- Front-page dashboard provider-health text no longer mixes languages in the + Simplified Chinese UI. The "Degraded" state now reads `退化` (was the English + "Degraded" leaking into the otherwise-Chinese `就绪 / 未就绪` set), and the + all-providers-ready hint reads `已就绪` (was a terse `OK`). Both use glyphs + already present in the embedded Noto Sans CJK SC subset (no font change). + ### Added - The in-setup DisplayEngine chrome is now localized. The header product/mode diff --git a/Docs/AppFeatureStandard.md b/Docs/AppFeatureStandard.md new file mode 100644 index 0000000..b693f65 --- /dev/null +++ b/Docs/AppFeatureStandard.md @@ -0,0 +1,198 @@ + + +# ModernSetup App Feature Standard + +Language: English | [简体中文](AppFeatureStandard.zh-CN.md) + +This document is the **normative** specification for what the ModernSetup +standard front-page App exposes: its page set, its dashboard structure, the +quick-access category cards, and how those adapt per platform class. Where the +[IBV and Platform Setup Survey](IbvAndPlatformSetupSurvey.md) and the +[Productization Feature Matrix](ProductizationFeatureMatrix.md) are *reference* +material (what the broader firmware ecosystem does), this document is +*prescriptive* (what the App MUST/SHOULD do to conform). The reorg of +`Application/ModernSetupApp/` and the `Tests/Smoke/smoke_validate.py` guards +both track this standard. + +Key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** are +used in the RFC 2119 sense. + +## 1. Scope and the ownership boundary + +The App is a read-only first screen plus a set of safe entry points. It is **not** +a second setup-policy engine. + +- The App **MUST NOT** parse IFR/VFR, implement `ConfigAccess`, mutate HII + forms, or write varstores. Every real configuration action either launches a + boot option (through `UefiBootManagerLib`) or enters native edk2 FormBrowser + via `EFI_FORM_BROWSER2_PROTOCOL.SendForm()`. +- The App **MUST** present platform-specific policy (CPU frequency/voltage, + memory timing/profile/RAS, chipset/SoC straps, fan curves, PCIe resource + policy, BMC networking, key/TPM management) as a *summary* and/or a native + *entry point* only. These remain native-owned. +- Information the App shows **MUST** come from the read-only `ModernUi*DataLib` + providers (see the Provider Roadmap in the Feature Matrix), never from + hard-coded board assumptions. + +This boundary is identical to the one enforced by smoke; this document does not +relax it. + +## 2. Platform classes + +The App standardizes around five platform classes. The class is derived from the +SMBIOS form factor and management-capability providers at runtime; it is a +*presentation* hint only and never gates a security or policy decision. + +| Class | Code intent | Typical examples | +| --- | --- | --- | +| `Client-Desktop` | Desktop / workstation / AIO / NUC / mini PC. | OVMF X64 desktop, ARM/LoongArch desktop. | +| `Client-Mobile` | Laptop / 2-in-1 / tablet with a battery. | Notebook-class products. | +| `Server` | Rack/blade/server boards with management. | x86/Arm/LoongArch servers, RISC-V server prototypes. | +| `Embedded` | Industrial / appliance boards. | ARM/RISC-V/LoongArch boards. | +| `Unknown` | Form factor not reported (common in VMs). | QEMU/OVMF without SMBIOS chassis data. | + +`Unknown` **MUST** behave as the most inclusive superset that is still safe to +show — i.e. it follows the `Client-Desktop` card set plus any card whose +provider reports live data. The App never hides a card *because* the class is +unknown; it only hides a card that is both class-inapplicable **and** backed by +an unavailable provider. + +## 3. Canonical page set + +The App's category pages are exactly the `SETUP_PAGE` enum and **MUST** stay in +this order. Adding a page is an additive API change (append before `PageMax`). + +| Page | Purpose | App shows | Native owner | +| --- | --- | --- | --- | +| `PageDashboard` | First-glance platform state. | System Information + Platform Health panels, quick-category grid. | — | +| `PageSystemInfo` | Read-only system specification detail. | Real platform identity (SMBIOS Type 1), CPU (Type 4), memory type/speed (Type 17), architecture, form factor, boot mode, firmware vendor/revision. A deeper companion to the dashboard System Information panel. | — (SMBIOS/UEFI read-only). | +| `PageBoot` | Boot inventory + launch. | `Boot####` active/hidden/category/path; Enter launches the selected entry. | Boot Maintenance HII. | +| `PageDevices` | Device / HII entry inventory. | HII formsets, device-path rows, Driver Health. | Each driver formset. | +| `PageSecurity` | Security posture. | Secure Boot, Setup Mode, PK/KEK/db/dbx, TPM/TCG/TCM presence. | SecurityPkg / platform HII. | +| `PageFirmware` | Firmware lifecycle. | Capsule support, humanized firmware revision, recovery/update entry. | Capsule/update HII or app. | +| `PageDiagnostics` | Bring-up / service visibility. | ACPI/SMBIOS presence, memory-map/handle/table counts, provider health. | Platform diagnostics HII. | +| `PageManagement` | Server / remote management. | BMC/IPMI/Redfish presence, host interface. | BMC/Redfish HII. | +| `PagePower` | Power / thermal visibility. | ACPI state, chassis thermal state, power-supply presence, demo health trend. | Platform power/thermal HII. | +| `PagePerformance` | CPU/memory + tuning entry. | Processor/memory inventory, CPU I/O protocol, virtualization/RAS entry hints. | Platform tuning/RAS/PCIe HII. | +| `PageServerInventory` | Server asset/management rollup + PCIe policy hints. | Management + PCIe capability summary; ReBAR/4G/SR-IOV entry hints. | Platform management/PCIe HII. | +| `PagePreferences` | App-local UX preferences. | Theme, density, language, OEM watermark toggle. | — (App-owned, no platform state). | +| `PageExit` | Session / shell control. | Continue, reset, native UiApp fallback, language. | Native FormBrowser save/discard. | + +PCIe policy is surfaced through `PageServerInventory` / `PagePerformance` entry +hints; it does **not** get its own top-level page in the App, and the App +**MUST NOT** expose writable PCIe controls. + +## 4. Dashboard structure + +The dashboard **MUST** be three zones, top to bottom: + +1. **System Information panel** — read-only identity/inventory: firmware vendor, + humanized firmware revision (`major.minor (0xhex)`), platform, form factor, + boot mode, memory, display mode. Rows that resolve to `N/A`/`Unknown`/`Limited + data` **SHOULD** be collapsed (the row flows up) rather than shown as a dead + placeholder. +2. **Platform Health panel** — architecture, provider health summary, coverage, + first issue. Present when horizontal space allows; otherwise the System panel + spans full width. +3. **Quick-category grid** — the standardized navigation cards in §5. + +Status that already appears in panels 1–2 **MUST NOT** be the *sole* purpose of a +quick card: every quick card is a navigation entry first (Enter routes to its +page) and a one-line status second. + +## 5. Standardized quick-category cards + +The quick grid is an ordered catalog. Each card is a navigation entry: `Title` is +its category, `Value`/`Detail` are a one-line live status, and Enter routes to the +mapped page/focus. The canonical catalog: + +| # | Card | Routes to | Group | One-line status | +| --- | --- | --- | --- | --- | +| 0 | Continue boot | `PageExit` / content | Exit | "Same as native Continue". | +| 1 | Boot options | `PageBoot` / content | Boot & Devices | Boot entry count + mode/secure hint. | +| 2 | Devices | `PageDevices` / content | Boot & Devices | HII handle / table count. | +| 3 | Provider status | `PageDiagnostics` / nav | Platform Health | Provider health + coverage. | +| 4 | Firmware | `PageFirmware` / nav | Platform Health | Vendor + humanized revision; capsule presence. | +| 5 | Power / Thermal | `PagePower` / nav | Power & Performance | Chassis thermal / sensor or ACPI+SMBIOS presence. | +| 6 | Performance | `PagePerformance` / nav | Power & Performance | CPU/Memory/PCIe readiness. | +| 7 | Server inventory | `PageServerInventory` / nav | Management | Management presence + PCIe root count. | + +### 5.1 Per-platform-class applicability + +Each card is `Always` (shown on every class), or class-scoped. A class-scoped +card is shown when its class matches **or** its backing provider reports live +data; otherwise it is hidden and the grid reflows. + +| Card | Client-Desktop | Client-Mobile | Server | Embedded | Driver | +| --- | --- | --- | --- | --- | --- | +| Continue boot | Always | Always | Always | Always | — | +| Boot options | Always | Always | Always | Always | `ModernUiBootDataLib` | +| Devices | Always | Always | Always | Always | `ModernUiDeviceDataLib` | +| Provider status | Always | Always | Always | Always | diagnostics rollup | +| Firmware | Always | Always | Always | Always | `ModernUiFirmwareDataLib` | +| Power / Thermal | Always | Always (battery emphasis) | Always | Show if provider live | `ModernUiPowerDataLib` | +| Performance | Always | Always | Always | Show if provider live | `ModernUiPerformanceDataLib` | +| **Server inventory** | **Hidden** | **Hidden** | **Always** | Show if mgmt/PCIe live | `ModernUiManagementDataLib` / `ModernUiPcieDataLib` | + +The only card that is hard class-scoped today is **Server inventory**: it is +server-class content (BMC/IPMI/Redfish + PCIe root policy) and **MUST** be hidden +on `Client-Desktop`/`Client-Mobile` **unless** a management or PCIe provider +reports live data (so a managed workstation or a desktop with discoverable PCIe +policy still surfaces it). On `Unknown`, it follows the live-provider rule. + +Future class-scoped additions (e.g. a Battery card for `Client-Mobile`, a +Recovery card for `Embedded`) **SHOULD** extend this table rather than branch ad +hoc in drawing code. + +### 5.2 Known gaps (non-blocking, tracked here) + +- **Security has no quick card.** Security posture is reachable through the nav + rail (`PageSecurity`) and summarized in the dashboard, but a first-class + Security quick card is a recommended future addition (it is a P0 surface in the + survey). Adding it is an additive change to this catalog. +- **Battery / Recovery cards** are not yet implemented for `Client-Mobile` / + `Embedded`; the applicability table reserves their slots. + +## 6. Conformance and enforcement + +- The visible quick-card count is **variable** by platform class. Smoke + **MUST** assert the *catalog* count (the array length) and the + *route-table* length agree, and that every catalog card maps to a valid + `SETUP_PAGE`. Smoke **MUST NOT** assert a fixed *visible* count, because that + is now class-dependent. +- Card hiding **MUST** be data/class driven (a single applicability predicate), + not a per-card `if` scattered through `ModernSetupDrawDashboard`. +- Every card route **MUST** resolve to a real page; a hidden card **MUST NOT** + be focusable or Enter-activatable (keyboard navigation skips hidden cards). +- Localized card text **MUST** use only glyphs present in the embedded + Noto Sans CJK SC subset (`Library/ModernUiRendererLib/ModernUiGlyphs.c`); when + a Simplified-Chinese term is not covered, the English term is the + graceful fallback (per the CJK strategy in + [LvglProductizationPlan.md](LvglProductizationPlan.md)). + +## 7. XArch (per-architecture) notes + +The card *catalog* and *applicability* are architecture-neutral — the same App +build runs on X64, AARCH64, LOONGARCH64, and RISCV64. Architecture only affects +which providers report live data: + +- `Server inventory` typically shows on x86/Arm servers; it is provider-gated on + LoongArch/RISC-V server prototypes and hidden on all client/VM targets unless a + provider is live. +- `Power / Thermal` and `Performance` degrade to presence/`N/A` on targets whose + ACPI/SMBIOS/inventory providers are thin (common on RISC-V/LoongArch VMs). +- No card is gated on a hard-coded `ARCH` value; gating is by provider liveness + and platform class only. + +## 8. Change control + +Changes to the canonical page set (§3) or the card catalog (§5) are user-visible +and **MUST** be recorded in `CHANGELOG.md` and reflected in both this standard +and the smoke guards in the same PR. The Chinese mirror +([AppFeatureStandard.zh-CN.md](AppFeatureStandard.zh-CN.md)) **MUST** be updated +alongside the English source. diff --git a/Docs/AppFeatureStandard.zh-CN.md b/Docs/AppFeatureStandard.zh-CN.md new file mode 100644 index 0000000..27b6cdc --- /dev/null +++ b/Docs/AppFeatureStandard.zh-CN.md @@ -0,0 +1,171 @@ + + +# ModernSetup App 功能规范 + +语言:[English](AppFeatureStandard.md) | 简体中文 + +本文档是 ModernSetup 标准首页 App 的**规范性**定义:它规定 App 暴露哪些页面、 +仪表盘结构、快捷分类卡片,以及这些内容如何按平台类别自适应。 +[IBV 与平台 Setup 调研](IbvAndPlatformSetupSurvey.zh-CN.md) 和 +[产品化功能矩阵](ProductizationFeatureMatrix.zh-CN.md) 是*参考*资料(更广的固件 +生态在做什么),而本文档是*强制性*的(App 必须/应当怎么做才算合规)。 +`Application/ModernSetupApp/` 的重排和 `Tests/Smoke/smoke_validate.py` 的守卫 +都以本规范为准。 + +关键词 **必须**(MUST)、**禁止**(MUST NOT)、**应当**(SHOULD)、**不应**(SHOULD NOT)、 +**可以**(MAY) 按 RFC 2119 语义使用。 + +## 1. 范围与所有权边界 + +App 是一个只读首页加一组安全入口,它**不是**第二套 Setup 策略引擎。 + +- App **禁止**解析 IFR/VFR、实现 `ConfigAccess`、改写 HII 表单或写 varstore。 + 任何真实配置动作要么通过 `UefiBootManagerLib` 启动一个 boot 项,要么通过 + `EFI_FORM_BROWSER2_PROTOCOL.SendForm()` 进入原生 edk2 FormBrowser。 +- App **必须**把平台专属策略(CPU 频率/电压、内存时序/profile/RAS、芯片组/SoC + strap、风扇曲线、PCIe 资源策略、BMC 网络、密钥/TPM 管理)仅作为*摘要*和/或 + 原生*入口*呈现,这些仍归原生所有。 +- App 显示的信息**必须**来自只读的 `ModernUi*DataLib` provider(见功能矩阵的 + Provider Roadmap),绝不依赖写死的板级假设。 + +此边界与 smoke 强制的边界一致,本文档不放宽它。 + +## 2. 平台类别 + +App 围绕五个平台类别做标准化。类别在运行时由 SMBIOS form factor 与管理能力 +provider 推导,仅作*呈现*提示,绝不用于决定安全或策略。 + +| 类别 | 代码意图 | 典型示例 | +| --- | --- | --- | +| `Client-Desktop` | 台式/工作站/AIO/NUC/迷你机。 | OVMF X64 台式、ARM/LoongArch 台式。 | +| `Client-Mobile` | 笔记本/二合一/带电池平板。 | 笔记本类产品。 | +| `Server` | 带管理的机架/刀片/服务器主板。 | x86/Arm/LoongArch 服务器、RISC-V 服务器原型。 | +| `Embedded` | 工业/专用设备主板。 | ARM/RISC-V/LoongArch 板卡。 | +| `Unknown` | 未上报 form factor(虚拟机常见)。 | 无 SMBIOS chassis 数据的 QEMU/OVMF。 | + +`Unknown` **必须**表现为仍然安全可显示的最宽容超集 —— 即遵循 `Client-Desktop` +卡片集,外加任何其 provider 上报了实时数据的卡片。App 绝不*因为*类别未知而隐藏 +卡片;它只隐藏那些既不适用于当前类别**又**没有可用 provider 的卡片。 + +## 3. 规范页面集 + +App 的分类页面就是 `SETUP_PAGE` 枚举,且**必须**保持此顺序。新增页面属于附加式 +API 变更(追加在 `PageMax` 之前)。 + +| 页面 | 用途 | App 显示 | 原生所有者 | +| --- | --- | --- | --- | +| `PageDashboard` | 一眼概览平台状态。 | 系统信息 + 平台健康面板、快捷分类网格。 | — | +| `PageSystemInfo` | 只读系统规格详情。 | 真实平台身份(SMBIOS Type 1)、CPU(Type 4)、内存类型/速度(Type 17)、架构、外形、启动模式、固件厂商/版本。是仪表盘系统信息面板的深入伴随页。 | —(SMBIOS/UEFI 只读)。 | +| `PageBoot` | 启动清单 + 启动。 | `Boot####` 激活/隐藏/类别/路径;回车启动所选项。 | Boot Maintenance HII。 | +| `PageDevices` | 设备/HII 入口清单。 | HII formset、设备路径行、Driver Health。 | 各驱动 formset。 | +| `PageSecurity` | 安全态势。 | Secure Boot、Setup Mode、PK/KEK/db/dbx、TPM/TCG/TCM 存在性。 | SecurityPkg/平台 HII。 | +| `PageFirmware` | 固件生命周期。 | Capsule 支持、人性化固件版本、恢复/更新入口。 | Capsule/更新 HII 或 app。 | +| `PageDiagnostics` | 引导/服务可见性。 | ACPI/SMBIOS 存在性、内存映射/句柄/表计数、provider 健康。 | 平台诊断 HII。 | +| `PageManagement` | 服务器/远程管理。 | BMC/IPMI/Redfish 存在性、host interface。 | BMC/Redfish HII。 | +| `PagePower` | 电源/散热可见性。 | ACPI 状态、机箱热状态、电源记录存在性、演示健康趋势。 | 平台电源/散热 HII。 | +| `PagePerformance` | CPU/内存 + 调优入口。 | 处理器/内存清单、CPU I/O 协议、虚拟化/RAS 入口提示。 | 平台调优/RAS/PCIe HII。 | +| `PageServerInventory` | 服务器资产/管理汇总 + PCIe 策略提示。 | 管理 + PCIe 能力摘要;ReBAR/4G/SR-IOV 入口提示。 | 平台管理/PCIe HII。 | +| `PagePreferences` | App 本地 UX 偏好。 | 主题、密度、语言、OEM 水印开关。 | —(App 自有,无平台状态)。 | +| `PageExit` | 会话/shell 控制。 | 继续、重置、原生 UiApp 回退、语言。 | 原生 FormBrowser 保存/放弃。 | + +PCIe 策略通过 `PageServerInventory`/`PagePerformance` 的入口提示呈现;它在 App +里**不**单独成为顶级页面,且 App **禁止**暴露可写的 PCIe 控制。 + +## 4. 仪表盘结构 + +仪表盘**必须**自上而下分三个区: + +1. **系统信息面板** —— 只读身份/清单:固件厂商、人性化固件版本 + (`主.次 (0x十六进制)`)、平台、form factor、boot mode、内存、显示模式。 + 解析为 `N/A`/`Unknown`/`Limited data` 的行**应当**折叠(行上浮),而不是 + 显示成一条死占位。 +2. **平台健康面板** —— 架构、provider 健康摘要、覆盖度、首个问题。横向空间足够 + 时显示;否则系统面板占满整宽。 +3. **快捷分类网格** —— §5 的标准化导航卡片。 + +已经出现在面板 1–2 里的状态**禁止**成为某个快捷卡片的*唯一*目的:每个快捷卡片 +首先是导航入口(回车跳到对应页面),其次才是一行状态。 + +## 5. 标准化快捷分类卡片 + +快捷网格是一个有序目录。每个卡片是导航入口:`Title` 是分类,`Value`/`Detail` +是一行实时状态,回车路由到映射的页面/焦点。规范目录: + +| # | 卡片 | 路由到 | 分组 | 一行状态 | +| --- | --- | --- | --- | --- | +| 0 | 继续启动 | `PageExit` / content | Exit | “等同原生 Continue”。 | +| 1 | 启动选项 | `PageBoot` / content | Boot & Devices | 启动项数 + 模式/安全提示。 | +| 2 | 设备 | `PageDevices` / content | Boot & Devices | HII 句柄/表计数。 | +| 3 | Provider 状态 | `PageDiagnostics` / nav | Platform Health | provider 健康 + 覆盖度。 | +| 4 | 固件 | `PageFirmware` / nav | Platform Health | 厂商 + 人性化版本;capsule 存在性。 | +| 5 | 电源/散热 | `PagePower` / nav | Power & Performance | 机箱热/传感器或 ACPI+SMBIOS 存在性。 | +| 6 | 性能 | `PagePerformance` / nav | Power & Performance | CPU/内存/PCIe 就绪度。 | +| 7 | 服务器清单 | `PageServerInventory` / nav | Management | 管理存在性 + PCIe root 数。 | + +### 5.1 按平台类别的适用性 + +每个卡片要么 `Always`(所有类别都显示),要么按类别限定。被类别限定的卡片,当其 +类别匹配**或**其后端 provider 上报实时数据时显示;否则隐藏,网格回流。 + +| 卡片 | Client-Desktop | Client-Mobile | Server | Embedded | Driver | +| --- | --- | --- | --- | --- | --- | +| 继续启动 | Always | Always | Always | Always | — | +| 启动选项 | Always | Always | Always | Always | `ModernUiBootDataLib` | +| 设备 | Always | Always | Always | Always | `ModernUiDeviceDataLib` | +| Provider 状态 | Always | Always | Always | Always | 诊断汇总 | +| 固件 | Always | Always | Always | Always | `ModernUiFirmwareDataLib` | +| 电源/散热 | Always | Always(强调电池) | Always | provider 有数据则显示 | `ModernUiPowerDataLib` | +| 性能 | Always | Always | Always | provider 有数据则显示 | `ModernUiPerformanceDataLib` | +| **服务器清单** | **隐藏** | **隐藏** | **Always** | 管理/PCIe 有数据则显示 | `ModernUiManagementDataLib` / `ModernUiPcieDataLib` | + +当前唯一硬性按类别限定的卡片是**服务器清单**:它是服务器类内容(BMC/IPMI/Redfish ++ PCIe root 策略),在 `Client-Desktop`/`Client-Mobile` 上**必须**隐藏,**除非** +有管理或 PCIe provider 上报实时数据(这样带管理的工作站、或能发现 PCIe 策略的 +台式机仍会显示它)。在 `Unknown` 上遵循 live-provider 规则。 + +未来按类别新增(如 `Client-Mobile` 的电池卡、`Embedded` 的恢复卡)**应当**扩展 +本表,而不是在绘制代码里临时分支。 + +### 5.2 已知缺口(非阻塞,在此追踪) + +- **Security 没有快捷卡片。** 安全态势可经导航栏(`PageSecurity`)到达,并在仪表 + 盘里有摘要,但一个一等的 Security 快捷卡片是推荐的未来新增(在调研里它是 P0 + 面)。新增它属于对本目录的附加式变更。 +- **电池/恢复卡片**尚未为 `Client-Mobile`/`Embedded` 实现;适用性表为它们预留了 + 位置。 + +## 6. 合规与强制 + +- 可见快捷卡片数按平台类别**可变**。smoke **必须**断言*目录*数(数组长度)与 + *路由表*长度一致、且每个目录卡片都映射到合法的 `SETUP_PAGE`。smoke **禁止** + 断言固定的*可见*数量,因为它现在依类别而定。 +- 卡片隐藏**必须**由数据/类别驱动(单一适用性谓词),而不是散落在 + `ModernSetupDrawDashboard` 里的逐卡 `if`。 +- 每个卡片路由**必须**解析到真实页面;隐藏的卡片**禁止**可聚焦或可回车激活 + (键盘导航跳过隐藏卡片)。 +- 本地化卡片文本**必须**只用内嵌 Noto Sans CJK SC 子集 + (`Library/ModernUiRendererLib/ModernUiGlyphs.c`)里有的字形;当某简体中文词 + 未被覆盖,英文词作为优雅回退(见 + [LvglProductizationPlan.md](LvglProductizationPlan.md) 的 CJK 策略)。 + +## 7. XArch(按架构)说明 + +卡片*目录*与*适用性*是架构中立的 —— 同一 App 构建运行于 X64、AARCH64、 +LOONGARCH64、RISCV64。架构只影响哪些 provider 上报实时数据: + +- `服务器清单`通常在 x86/Arm 服务器显示;在 LoongArch/RISC-V 服务器原型上由 + provider 决定,在所有客户端/VM 目标上除非有 provider 实时数据否则隐藏。 +- `电源/散热`与`性能`在 ACPI/SMBIOS/清单 provider 较薄的目标上退化为存在性/`N/A` + (RISC-V/LoongArch VM 常见)。 +- 没有卡片以写死的 `ARCH` 值门控;门控仅依 provider 实时性与平台类别。 + +## 8. 变更控制 + +对规范页面集(§3)或卡片目录(§5)的变更是用户可见的,**必须**记入 +`CHANGELOG.md`,并在同一 PR 内同时反映到本规范与 smoke 守卫。中文镜像 +(本文件)**必须**随英文源同步更新。 diff --git a/Docs/LvglProductizationPlan.md b/Docs/LvglProductizationPlan.md index 65e1f9f..7ac4646 100644 --- a/Docs/LvglProductizationPlan.md +++ b/Docs/LvglProductizationPlan.md @@ -57,22 +57,62 @@ for all targets, without yet forcing it as the default. **Acceptance:** CI builds `MODERN_SETUP_DISPLAY_ENGINE=lvgl` for all four targets; smoke passes with the LVGL libs in a supported (non-default) overlay. -## Gate 2 — CJK / i18n coverage (the first true product gate) +## Gate 2 — CJK / i18n coverage **Current:** demand-driven Noto Sans CJK SC subset (182 glyphs, **18×18 A8**, OFL) generated by `Scripts/generate-font-glyphs.py` from the package's own strings + selected demo `.uni`; anything outside falls back to the firmware HII font. Single glyph size. +### Key reframing: we only own the glyphs we control + +There are **two** languages on screen, and they are not the same problem: + +1. **Our shell UI** (chrome / front-page app) — the strings are ours, so the + demand-driven subset covers them 100%. Always renders correctly in zh-Hans. +2. **HII form content** (driver/platform setting names and values) — owned by + the driver. FormBrowser already falls back to English when a driver ships no + zh translation, which is the overwhelmingly common case. **No Chinese to + draw → no missing-glyph problem, no font needed.** + +So the only case that ever needs more than the demand-driven subset is a driver +that *itself* ships Chinese HII strings using characters outside our subset. +That is a narrow need. Rather than pay ~1.2 MB to cover it everywhere, the +product policy is: + +> **Force zh only on strings we control (subset covers them fully); let HII +> content follow its own available language (zh where the driver provides it, +> English otherwise); and any glyph that still can't be drawn degrades to +> readable text — never a tofu box / blank.** + +This makes the heavy standard subset a **narrow-need optional**, not a default. +Per-language fallback (English when no zh) is clean and free; per-glyph +"swap this one char to English" is intentionally **not** attempted (a string has +one language version). + +### Tiers + +| Tier | Covers | Memory | Default | +| --- | --- | --- | --- | +| **1. Demand-driven subset** | our shell UI strings | ~tens of KB (current ~59 KB) | **Always on** | +| **Graceful fallback** | anything unrenderable → readable English / last-resort placeholder, never tofu | 0 | **Always on** | +| **2. GB2312 L1 (optional)** | arbitrary common Simplified Chinese (~3,755) for products with Chinese-localized third-party driver forms | ~1.2 MB (18×18 A8 ≈ 324 B/glyph) | **Off** (PCD opt-in) | +| **3. Full CJK** | ~21,000 — not embedded | ~6.8 MB | Not pursued | + +### Steps + | ✓ | Step | Effort | Note | | --- | --- | --- | --- | -| [ ] | **Decide the coverage tier** | S (decision) | Options + memory (18×18 A8 ≈ 324 B/glyph): **(a)** demand-driven per-platform scan — lightest (~tens of KB), needs build-time string knowledge, misses late third-party driver strings; **(b)** standard **GB2312 L1** (~3,755) ≈ **1.2 MB** — predictable arbitrary coverage; **(c)** runtime font from an FFS/font HII package — no embed cost, platform supplies it. Recommendation: **tiered** — keep (a) for our own UI + ship an optional **(b)** behind a PCD (`PcdModernSetupCjkSubset = none\|gb2312l1`) for platforms that need it; HII fallback last. | -| [ ] | **Size matching** | M | The embedded glyph is composited at its native 18 px; the Latin run uses Montserrat 14–24. Decide: generate the CJK subset at the UI's actual sizes (14/16/18/20 → ~4× memory) **or** standardize in-setup body text to one size and verify CJK/Latin baseline alignment. Verify mixed-run vertical alignment in `ModernUiDrawText`. | -| [ ] | zh HII form validation | M | Render a Chinese HII form (zh DriverSample / a platform zh formset) and confirm no missing glyphs at the chosen tier; confirm fallback quality when a glyph is absent (no tofu boxes — define a `?`/placeholder policy). | -| [ ] | Build integration | S | Keep the generated table committed (no build-time Python dep); document regeneration; OFL attribution already in `THIRD_PARTY_NOTICES.md`. | +| [ ] | **Graceful fallback policy** | S–M | Guarantee no tofu/blank: a code point absent from the subset, the HII font, and any enabled tier renders as a readable placeholder (or the run is shown in its English source where available). This is the default-product CJK story; it removes the need for tier 2 in most builds. | +| [ ] | **Optional GB2312 L1 subset** | M | Generate a standard ~3,755-char subset behind a PCD (`PcdModernSetupCjkSubset = none\|gb2312l1`, default `none`) for the narrow set of products that must render Chinese third-party HII forms. | +| [ ] | **Size matching** | M | The embedded glyph composites at 18 px; the Latin run uses Montserrat 14–24. Generate the subset at the UI's actual sizes **or** standardize in-setup body text to one size; verify CJK/Latin baseline alignment in `ModernUiDrawText`. | +| [ ] | zh validation | M | Confirm our shell UI renders fully zh; confirm a driver-only-English form shows English cleanly; confirm a zh form with an out-of-subset glyph degrades to readable text, not tofu. | +| [ ] | Build integration | S | Keep generated tables committed (no build-time Python dep); OFL attribution already in `THIRD_PARTY_NOTICES.md`. | -**Acceptance:** the target coverage tier renders a Chinese platform form with no -missing glyphs, size-consistent with Latin, within the platform memory budget. +**Acceptance:** with only tier 1 + graceful fallback (the default), the shell UI +is fully zh, English-only driver forms read cleanly, and **no screen ever shows a +tofu box / blank glyph**. The optional GB2312 tier additionally renders +arbitrary common-Chinese HII forms within the platform memory budget. ## Gate 3 — Memory & performance budget @@ -95,17 +135,18 @@ failures or leaks under sustained navigation. ## Gate 4 — Resolution & robustness **Current:** canvas sizes to the active GOP mode and re-inits on change; guards -skip draws when a region is too small. No mode-matrix validation, no defined -GOP-absent path. +skip draws when a region is too small. Graceful degradation (GOP-absent + +degenerate-mode) and mode-change re-init are now defined and fixed; mode-matrix +visual validation is the remaining open item. | ✓ | Step | Effort | Note | | --- | --- | --- | --- | -| [ ] | Resolution matrix | M | Validate 1024×768, 1280×800, 1920×1080, and a small mode (e.g. 800×600). Confirm chrome/rows/right-rail/popups/watermark scale and the size guards behave. | -| [ ] | Re-init correctness on mode change | S | Verify the `mCanvasW != Context->Width` re-init path frees+reallocates cleanly and the first post-change frame is correct. | -| [ ] | GOP-absent / degenerate fallback | M | Define behavior when GOP is unavailable or the mode is below the minimum usable size: the engine should degrade gracefully (skip modern draw, let native text render) rather than blank the screen. | +| [ ] | Resolution matrix | M | Validate 1024×768, 1280×800, 1920×1080, and a small mode (e.g. 800×600). Confirm chrome/rows/right-rail/popups/watermark scale and the size guards behave. Needs a per-resolution OVMF build (resolution PCDs) + capture loop. | +| [x] | Re-init correctness on mode change | S | LVGL re-init now syncs `lv_display_set_resolution` to the reallocated canvas and creates the canvas object once (rebinding its buffer) instead of re-creating it each change (which orphaned the prior canvas + its freed buffer). Commit `fix(displayengine): degrade gracefully on absent/degenerate GOP mode`. | +| [x] | GOP-absent / degenerate fallback | M | Both `ModernUiRendererInit` backends refuse modes below `MODERN_UI_MIN_RENDER_WIDTH`×`_HEIGHT` (640×480); the in-setup display engine's `PrintInternal` now falls back to text-console `OutputString` (padded) when the renderer is unavailable instead of emitting nothing (blank screen), and the front-page app exits to the native shell. | -**Acceptance:** correct rendering across the matrix; graceful degradation with no -GOP or an unusably small mode. +**Acceptance:** correct rendering across the matrix (open); graceful degradation +with no GOP or an unusably small mode (done). ## Gate 5 — Interaction completeness & polish @@ -148,16 +189,28 @@ GOP or an unusably small mode. default build would require `External/lvgl`, so `Scripts/bootstrap-edk2.sh` must init that submodule; (c) the hardware gate (Gate 6) still stands. So the default flip is sequenced *after* all-target lvgl support + CI. -2. **CJK coverage → tiered.** Demand-driven Noto subset for the package's own UI - (done) + an optional **GB2312 L1** subset behind a PCD for platforms needing - arbitrary coverage + HII fallback last. +2. **CJK coverage → tiered, with graceful English fallback as the default.** We + only own the glyphs we control: the demand-driven subset (done) covers our + shell UI fully, while HII form content follows its own available language + (English when a driver ships no zh — the common case). The default product + needs no more than tier 1 + a guarantee that any unrenderable glyph degrades + to readable text, never a tofu box. The standard **GB2312 L1** subset is a + PCD-gated **narrow-need optional** (default off) only for products that must + render Chinese-localized third-party HII forms. See Gate 2. 3. **Per-platform DXEFV budget** — open; measure RELEASE baselines (Gate 3) then decide whether the ~4 MB BS-pool canvas is acceptable on the tightest target. 4. **Hardware availability** — open; which real boards exist per arch (Gate 6). ## Progress log -- 2026-06-08: Gate 1 started — smoke now CI-gates the lvgl overlay on ovmf-x64 + - loongarch (the targets that accept `lvgl` today). Remaining Gate 1: add lvgl - support to armvirt + riscvvirt, de-spike framing, then flip the default with - the bootstrap submodule init. +- 2026-06-08: Gate 1 started — smoke CI-gates the lvgl overlay on ovmf-x64 + + loongarch. +- 2026-06-08: Gate 1 — armvirt + riscvvirt build scripts gained lvgl mode; smoke + now exercises lvgl overlay generation on all four targets. +- 2026-06-08: Gate 1 — de-spiked the LVGL renderer INF framing; `bootstrap-edk2.sh` + now initializes `External/lvgl`; the default `MODERN_SETUP_DISPLAY_ENGINE` is + now `lvgl` on the build-verified targets (ovmf-x64, loongarch), with + armvirt/riscvvirt left on `modern` until their lvgl cross-compile is verified. + Bare `Scripts/build-ovmf-x64.sh` builds lvgl by default (DXEFV ~43%). Gate 1 + effectively complete except the armvirt/riscv default flip (gated on + cross-compile) and any package rename out of "spike". diff --git a/Docs/README.md b/Docs/README.md index ac7e89c..c2a145d 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -17,6 +17,7 @@ Simplified Chinese counterparts for the core project docs. | Topic | English | 简体中文 | | --- | --- | --- | | XArch architecture model | [XArch.md](XArch.md) | [XArch.zh-CN.md](XArch.zh-CN.md) | +| App feature standard (normative) | [AppFeatureStandard.md](AppFeatureStandard.md) | [AppFeatureStandard.zh-CN.md](AppFeatureStandard.zh-CN.md) | | Productization feature matrix | [ProductizationFeatureMatrix.md](ProductizationFeatureMatrix.md) | [ProductizationFeatureMatrix.zh-CN.md](ProductizationFeatureMatrix.zh-CN.md) | | Productization validation matrix | [ProductizationValidationMatrix.md](ProductizationValidationMatrix.md) | [ProductizationValidationMatrix.zh-CN.md](ProductizationValidationMatrix.zh-CN.md) | | Module boundaries | [MODULE_BOUNDARIES.md](MODULE_BOUNDARIES.md) | [MODULE_BOUNDARIES.zh-CN.md](MODULE_BOUNDARIES.zh-CN.md) | diff --git a/Include/ModernUi/ModernUiPlatformData.h b/Include/ModernUi/ModernUiPlatformData.h index dcaa83c..79b367b 100644 --- a/Include/ModernUi/ModernUiPlatformData.h +++ b/Include/ModernUi/ModernUiPlatformData.h @@ -23,6 +23,31 @@ typedef struct { CHAR16 FormFactor[MODERN_UI_PLATFORM_TEXT_MAX]; CHAR16 BootMode[MODERN_UI_PLATFORM_TEXT_MAX]; UINT64 MemorySizeMb; + // + // Appended (additive): processor identity from SMBIOS Type 4, e.g. + // "QEMU Virtual CPU version 2.5+ (4C/8T)". Empty/Unknown when Type 4 is absent. + // + CHAR16 Processor[MODERN_UI_PLATFORM_TEXT_MAX]; + // + // Appended (additive): memory type/speed/DIMM-count detail from SMBIOS Type 17, + // e.g. "DDR4-3200, 2 DIMMs". Empty when Type 17 is absent; the total size is in + // MemorySizeMb above. + // + CHAR16 MemoryDetail[MODERN_UI_PLATFORM_TEXT_MAX]; + // + // Appended (additive): deeper system-detail identity for the System + // Information page. Each is empty when its SMBIOS source is absent. + // Serial - system serial number (SMBIOS Type 1). + // Uuid - system UUID, canonical string form (SMBIOS Type 1). + // Baseboard - " " (SMBIOS Type 2). + // BiosVersion - firmware version string (SMBIOS Type 0). + // BiosDate - firmware release date (SMBIOS Type 0). + // + CHAR16 Serial[MODERN_UI_PLATFORM_TEXT_MAX]; + CHAR16 Uuid[MODERN_UI_PLATFORM_TEXT_MAX]; + CHAR16 Baseboard[MODERN_UI_PLATFORM_TEXT_MAX]; + CHAR16 BiosVersion[MODERN_UI_PLATFORM_TEXT_MAX]; + CHAR16 BiosDate[MODERN_UI_PLATFORM_TEXT_MAX]; } MODERN_UI_PLATFORM_SUMMARY; /** diff --git a/Include/ModernUi/ModernUiRenderer.h b/Include/ModernUi/ModernUiRenderer.h index 4e278ad..0c18c20 100644 --- a/Include/ModernUi/ModernUiRenderer.h +++ b/Include/ModernUi/ModernUiRenderer.h @@ -17,6 +17,17 @@ #include +// +// Minimum GOP mode the modern renderer treats as usable. The modern chrome +// (header, tab rail, right help rail, popups) cannot lay out below roughly VGA; +// when the active mode is smaller (or GOP is absent), ModernUiRendererInit +// fails so callers degrade gracefully -- the in-setup display engine falls back +// to plain text-console output and the front-page app exits to the native shell +// -- instead of painting broken or blank chrome. +// +#define MODERN_UI_MIN_RENDER_WIDTH 640 +#define MODERN_UI_MIN_RENDER_HEIGHT 480 + typedef struct { UINTN X; UINTN Y; diff --git a/Include/ModernUi/ModernUiString.h b/Include/ModernUi/ModernUiString.h index 81a3721..333e67b 100644 --- a/Include/ModernUi/ModernUiString.h +++ b/Include/ModernUi/ModernUiString.h @@ -143,6 +143,8 @@ typedef enum { ModernUiStringPreferenceDefaults, ModernUiStringPreferenceSavedFormat, ModernUiStringPreferenceDefaultsLoaded, + ModernUiStringPageSystemInfo, + ModernUiStringPageSystemInfoHint, ModernUiStringMax } MODERN_UI_STRING_ID; diff --git a/Library/ModernUiCustomizedDisplayLib/CustomizedDisplayLibInternal.c b/Library/ModernUiCustomizedDisplayLib/CustomizedDisplayLibInternal.c index 5a1c320..926cd27 100644 --- a/Library/ModernUiCustomizedDisplayLib/CustomizedDisplayLibInternal.c +++ b/Library/ModernUiCustomizedDisplayLib/CustomizedDisplayLibInternal.c @@ -559,13 +559,21 @@ ModernDisplayDrawFormTitleContext ( TitleY = (Layout->ContentTopRow * CellHeight) + MIN (6, MAX (2, CellHeight / 5)); TitleWidth = (TitleRightColumn - TitleLeftColumn) * CellWidth; + // + // Render the form title as a prominent section header: brighter than plain + // muted body text (kept as a blend toward MutedText so it still reads as a + // heading, not a value), so each form is clearly anchored by its title. + // Presentation only. (An accent underline below the title is intentionally + // not drawn here: it sits in the content band the native FormBrowser repaints + // after the chrome, which would wipe it.) + // ModernUiDrawTextFit ( &mModernRenderContext, TitleX, TitleY, TitleWidth, PrintableTitle, - Theme->MutedText, + ModernUiBlendColor (Theme->Text, Theme->MutedText, 22), Theme->BackgroundBlack ); } @@ -2402,6 +2410,7 @@ PrintInternal ( UINTN TextMaxWidth; UINTN TextInset; UINTN MeasuredWidth; + UINTN Emitted; CHAR16 *Printable; EFI_GRAPHICS_OUTPUT_BLT_PIXEL Foreground; EFI_GRAPHICS_OUTPUT_BLT_PIXEL Background; @@ -2521,6 +2530,21 @@ PrintInternal ( mModernCursorColumn = DrawColumn + TotalCount; mModernCursorRow = DrawRow; + } else { + // + // Renderer unavailable (GOP absent, or a mode below the usable minimum): + // fall back to plain text-console output so the form stays readable instead + // of blanking. SetCursorPosition/SetAttribute above already positioned and + // themed the cell; pad to the caller's field width so a previously longer + // string at this position is overwritten, matching the native text grid. + // + Out->OutputString (Out, Buffer); + for (Emitted = TotalCount; Emitted < Width; Emitted++) { + Out->OutputString (Out, L" "); + } + + mModernCursorColumn = ((Column == (UINTN)-1) ? mModernCursorColumn : Column) + MAX (TotalCount, Width); + mModernCursorRow = (Row == (UINTN)-1) ? mModernCursorRow : Row; } FreePool (Buffer); diff --git a/Library/ModernUiFirmwareDataLib/ModernUiFirmwareDataLib.c b/Library/ModernUiFirmwareDataLib/ModernUiFirmwareDataLib.c index 4d012d9..929174f 100644 --- a/Library/ModernUiFirmwareDataLib/ModernUiFirmwareDataLib.c +++ b/Library/ModernUiFirmwareDataLib/ModernUiFirmwareDataLib.c @@ -95,7 +95,19 @@ ModernUiFirmwareDataGetSummary ( L"%s", (gST->FirmwareVendor == NULL) ? L"Unknown" : gST->FirmwareVendor ); - UnicodeSPrint (Summary->Revision, sizeof (Summary->Revision), L"0x%08x", gST->FirmwareRevision); + // + // gST->FirmwareRevision is conventionally encoded as (major << 16) | minor. + // Surface the human-readable major.minor form while keeping the raw hex so a + // firmware engineer can still read the exact encoded value. + // + UnicodeSPrint ( + Summary->Revision, + sizeof (Summary->Revision), + L"%u.%02u (0x%08x)", + (UINT32)(gST->FirmwareRevision >> 16), + (UINT32)(gST->FirmwareRevision & 0xFFFF), + gST->FirmwareRevision + ); Summary->CapsuleRuntimeServices = (BOOLEAN)((gRT->UpdateCapsule != NULL) && (gRT->QueryCapsuleCapabilities != NULL)); Summary->CapsuleArchProtocol = IsProtocolPresent (&gEfiCapsuleArchProtocolGuid); diff --git a/Library/ModernUiLvglRendererLib/ModernUiRendererLib.c b/Library/ModernUiLvglRendererLib/ModernUiRendererLib.c index b72320c..9b135ca 100644 --- a/Library/ModernUiLvglRendererLib/ModernUiRendererLib.c +++ b/Library/ModernUiLvglRendererLib/ModernUiRendererLib.c @@ -213,6 +213,23 @@ ModernUiRendererInit ( Context->Width = Context->Gop->Mode->Info->HorizontalResolution; Context->Height = Context->Gop->Mode->Info->VerticalResolution; + // + // Refuse a mode too small for the modern chrome so callers fall back to plain + // text rendering instead of painting broken layout. See MODERN_UI_MIN_RENDER_*. + // + if ((Context->Width < MODERN_UI_MIN_RENDER_WIDTH) || (Context->Height < MODERN_UI_MIN_RENDER_HEIGHT)) { + DEBUG (( + DEBUG_WARN, + "%a: GOP mode %ux%u below modern minimum %ux%u; falling back\n", + __func__, + Context->Width, + Context->Height, + MODERN_UI_MIN_RENDER_WIDTH, + MODERN_UI_MIN_RENDER_HEIGHT + )); + return EFI_NOT_FOUND; + } + Status = gBS->LocateProtocol ( &gEfiHiiFontProtocolGuid, NULL, @@ -275,6 +292,13 @@ ModernUiRendererInit ( } lv_display_set_flush_cb (mDisplay, LvglBridgeFlush); + } else { + // + // Mode change: keep the LVGL display resolution in sync with the newly + // sized canvas so the screen object and flush geometry match the active + // GOP mode on the first post-change frame. + // + lv_display_set_resolution (mDisplay, (int32_t)Context->Width, (int32_t)Context->Height); } lv_display_set_buffers ( @@ -285,13 +309,20 @@ ModernUiRendererInit ( LV_DISPLAY_RENDER_MODE_PARTIAL ); - mCanvas = lv_canvas_create (lv_display_get_screen_active (mDisplay)); + // + // Create the canvas object once and rebind its buffer on every (re)build. + // Re-creating it each time would orphan the previous canvas (and leave it + // pointing at the buffer just freed above) on a mode change. + // if (mCanvas == NULL) { - FreePool (mCanvasBuf); - FreePool (mDispBuf); - mCanvasBuf = NULL; - mDispBuf = NULL; - return EFI_OUT_OF_RESOURCES; + mCanvas = lv_canvas_create (lv_display_get_screen_active (mDisplay)); + if (mCanvas == NULL) { + FreePool (mCanvasBuf); + FreePool (mDispBuf); + mCanvasBuf = NULL; + mDispBuf = NULL; + return EFI_OUT_OF_RESOURCES; + } } lv_canvas_set_buffer ( diff --git a/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf b/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf index db68bd1..93ac2a5 100644 --- a/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf +++ b/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf @@ -1,14 +1,16 @@ ## @file -# LVGL-backed renderer library for ModernSetupPkg (experimental/lvgl-spike). +# LVGL-backed renderer library for ModernSetupPkg. # # Drop-in replacement for Library/ModernUiRendererLib: same ModernUiRendererLib # class and ModernUiRenderer.h API, but every primitive is composited by LVGL's # software draw pipeline (lv_draw_rect / lv_draw_label) into a shadow canvas and -# BLT'd to GOP. CJK runs fall back to the firmware HII font, which LVGL's bundled -# Latin fonts do not cover. +# BLT'd to GOP. CJK runs use the embedded Noto Sans CJK SC subset and fall back +# to the firmware HII font, which LVGL's bundled Latin fonts do not cover. # -# experimental/lvgl-spike only; selected by MODERN_SETUP_DISPLAY_ENGINE=lvgl. -# Never wired into a default firmware overlay. +# Selected by MODERN_SETUP_DISPLAY_ENGINE=lvgl. Supported and CI-gated (smoke +# exercises the lvgl overlay on all four XArch targets); it is the same +# ModernDisplayEngineDxe interaction backend as the GOP renderer, with this +# library swapped in. See Docs/LvglProductizationPlan.md for the path to default. # # Copyright (c) 2026, MarsDoge. All rights reserved.
# Author: MarsDoge (Dongyan Qian) diff --git a/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.c b/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.c index 13a436c..a4dc60a 100644 --- a/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.c +++ b/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -164,6 +165,582 @@ GetFormFactorName ( } } +/** + Return a pointer to the Nth (1-based) string in an SMBIOS record's string set. + + SMBIOS strings follow the formatted area (Record->Length bytes from the record + start) as a sequence of NUL-terminated ASCII strings ending in a double NUL. + String number 0 means "no string". + + @param[in] Record SMBIOS record header. May be NULL. + @param[in] StringNumber 1-based SMBIOS string reference; 0 means none. + + @retval NULL Record is NULL, StringNumber is 0, or the string is absent. + @retval others Pointer to the requested NUL-terminated ASCII string (read-only + into the live SMBIOS record; do not free). +**/ +STATIC +CHAR8 * +GetSmbiosString ( + IN EFI_SMBIOS_TABLE_HEADER *Record, + IN UINT8 StringNumber + ) +{ + CHAR8 *String; + UINT8 Index; + + if ((Record == NULL) || (StringNumber == 0)) { + return NULL; + } + + String = (CHAR8 *)Record + Record->Length; + for (Index = 1; Index < StringNumber; Index++) { + if (*String == 0) { + // + // The string set ended before reaching StringNumber. + // + return NULL; + } + + while (*String != 0) { + String++; + } + + String++; + } + + return (*String != 0) ? String : NULL; +} + +/** + Return TRUE when an SMBIOS identity string carries no meaningful value. + + Many boards ship well-known placeholder strings (e.g. "To Be Filled By O.E.M.") + that read worse than the generic fallback. Treat those, and empty strings, as + absent so the caller can fall back cleanly. + + @param[in] String ASCII string to test. May be NULL. + + @retval TRUE String is NULL, empty, or a known placeholder. + @retval FALSE String carries a usable value. +**/ +STATIC +BOOLEAN +IsSmbiosPlaceholder ( + IN CHAR8 *String + ) +{ + STATIC CONST CHAR8 *Placeholders[] = { + "To Be Filled By O.E.M.", + "Not Specified", + "Not Applicable", + "System manufacturer", + "System Product Name", + "Default string", + "Default String", + "None", + "OEM", + "O.E.M." + }; + UINTN Index; + + if ((String == NULL) || (String[0] == '\0')) { + return TRUE; + } + + for (Index = 0; Index < ARRAY_SIZE (Placeholders); Index++) { + if (AsciiStrCmp (String, Placeholders[Index]) == 0) { + return TRUE; + } + } + + return FALSE; +} + +/** + Read the platform/product identity from SMBIOS Type 1 when available. + + Composes "Manufacturer ProductName" (or whichever single field is present) so + the dashboard shows the real system name instead of a generic placeholder. The + buffer is left empty when SMBIOS Type 1 is absent or reports no identity + strings, so the caller can apply its own fallback. + + @param[out] Buffer Destination buffer. Must not be NULL. + @param[in] Count Number of CHAR16 entries in Buffer. +**/ +STATIC +VOID +GetSmbiosSystemName ( + OUT CHAR16 *Buffer, + IN UINTN Count + ) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + EFI_SMBIOS_HANDLE Handle; + EFI_SMBIOS_TABLE_HEADER *Record; + EFI_SMBIOS_TYPE Type; + SMBIOS_TABLE_TYPE1 *Type1; + CHAR8 *Manufacturer; + CHAR8 *ProductName; + + if ((Buffer == NULL) || (Count == 0)) { + return; + } + + Buffer[0] = L'\0'; + Smbios = NULL; + Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios); + if (EFI_ERROR (Status) || (Smbios == NULL)) { + return; + } + + Handle = SMBIOS_HANDLE_PI_RESERVED; + Type = SMBIOS_TYPE_SYSTEM_INFORMATION; + Record = NULL; + Status = Smbios->GetNext (Smbios, &Handle, &Type, &Record, NULL); + if (EFI_ERROR (Status) || (Record == NULL)) { + return; + } + + Type1 = (SMBIOS_TABLE_TYPE1 *)Record; + Manufacturer = GetSmbiosString (Record, Type1->Manufacturer); + ProductName = GetSmbiosString (Record, Type1->ProductName); + if (IsSmbiosPlaceholder (Manufacturer)) { + Manufacturer = NULL; + } + + if (IsSmbiosPlaceholder (ProductName)) { + ProductName = NULL; + } + + // + // SMBIOS strings are ASCII; widen with %a. Prefer "Manufacturer ProductName" + // when both are present, otherwise whichever single field is reported. + // + if ((Manufacturer != NULL) && (ProductName != NULL)) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a %a", Manufacturer, ProductName); + } else if (ProductName != NULL) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a", ProductName); + } else if (Manufacturer != NULL) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a", Manufacturer); + } +} + +/** + Read the processor identity from SMBIOS Type 4 when available. + + Composes " (C/T)" -- e.g. + "Intel(R) Xeon(R) ... (8C/16T)" -- so the dashboard shows a real CPU instead of + a placeholder. Honors the SMBIOS 0xFF "see CoreCount2/ThreadCount2" escape for + high core counts. Leaves the buffer empty when Type 4 is absent or reports + neither a usable version string nor a core count, so the caller can fall back. + + @param[out] Buffer Destination buffer. Must not be NULL. + @param[in] Count Number of CHAR16 entries in Buffer. +**/ +STATIC +VOID +GetSmbiosProcessor ( + OUT CHAR16 *Buffer, + IN UINTN Count + ) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + EFI_SMBIOS_HANDLE Handle; + EFI_SMBIOS_TABLE_HEADER *Record; + EFI_SMBIOS_TYPE Type; + SMBIOS_TABLE_TYPE4 *Type4; + CHAR8 *Version; + UINT32 Cores; + UINT32 Threads; + + if ((Buffer == NULL) || (Count == 0)) { + return; + } + + Buffer[0] = L'\0'; + Smbios = NULL; + Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios); + if (EFI_ERROR (Status) || (Smbios == NULL)) { + return; + } + + Handle = SMBIOS_HANDLE_PI_RESERVED; + Type = SMBIOS_TYPE_PROCESSOR_INFORMATION; + Record = NULL; + Status = Smbios->GetNext (Smbios, &Handle, &Type, &Record, NULL); + if (EFI_ERROR (Status) || (Record == NULL)) { + return; + } + + Type4 = (SMBIOS_TABLE_TYPE4 *)Record; + Version = GetSmbiosString (Record, Type4->ProcessorVersion); + if (IsSmbiosPlaceholder (Version)) { + Version = NULL; + } + + // + // CoreCount/ThreadCount are UINT8; 0xFF means the real value is in the UINT16 + // CoreCount2/ThreadCount2 fields (present when the record is long enough). + // + Cores = Type4->CoreCount; + Threads = Type4->ThreadCount; + if ((Cores == 0xFF) && (Record->Length >= OFFSET_OF (SMBIOS_TABLE_TYPE4, CoreCount2) + sizeof (Type4->CoreCount2))) { + Cores = Type4->CoreCount2; + } + + if ((Threads == 0xFF) && (Record->Length >= OFFSET_OF (SMBIOS_TABLE_TYPE4, ThreadCount2) + sizeof (Type4->ThreadCount2))) { + Threads = Type4->ThreadCount2; + } + + if ((Version != NULL) && (Cores > 0)) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a (%uC/%uT)", Version, Cores, Threads); + } else if (Version != NULL) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a", Version); + } else if (Cores > 0) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%u cores / %u threads", Cores, Threads); + } +} + +/** + Read the system serial number and UUID from SMBIOS Type 1 when available. + + The serial is filtered through the placeholder list; the UUID is rendered in + canonical GUID text form and suppressed when SMBIOS reports the all-zero + ("not present") or all-FF ("not settable") sentinel values. Either buffer is + left empty when its field is absent, so the caller can hide the row. + + @param[out] Serial Destination for the serial number. Must not be NULL. + @param[in] SerialCount Number of CHAR16 entries in Serial. + @param[out] Uuid Destination for the UUID text. Must not be NULL. + @param[in] UuidCount Number of CHAR16 entries in Uuid. +**/ +STATIC +VOID +GetSmbiosSystemDetail ( + OUT CHAR16 *Serial, + IN UINTN SerialCount, + OUT CHAR16 *Uuid, + IN UINTN UuidCount + ) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + EFI_SMBIOS_HANDLE Handle; + EFI_SMBIOS_TABLE_HEADER *Record; + EFI_SMBIOS_TYPE Type; + SMBIOS_TABLE_TYPE1 *Type1; + CHAR8 *SerialString; + GUID UuidValue; + CONST UINT8 *UuidBytes; + BOOLEAN AllZero; + BOOLEAN AllOnes; + UINTN Index; + + if ((Serial == NULL) || (SerialCount == 0) || (Uuid == NULL) || (UuidCount == 0)) { + return; + } + + Serial[0] = L'\0'; + Uuid[0] = L'\0'; + Smbios = NULL; + Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios); + if (EFI_ERROR (Status) || (Smbios == NULL)) { + return; + } + + Handle = SMBIOS_HANDLE_PI_RESERVED; + Type = SMBIOS_TYPE_SYSTEM_INFORMATION; + Record = NULL; + Status = Smbios->GetNext (Smbios, &Handle, &Type, &Record, NULL); + if (EFI_ERROR (Status) || (Record == NULL)) { + return; + } + + Type1 = (SMBIOS_TABLE_TYPE1 *)Record; + SerialString = GetSmbiosString (Record, Type1->SerialNumber); + if (!IsSmbiosPlaceholder (SerialString)) { + UnicodeSPrint (Serial, SerialCount * sizeof (CHAR16), L"%a", SerialString); + } + + // + // SMBIOS records are byte-packed, so Type1->Uuid can be unaligned. Copy it to + // an aligned local before any structured (%g) access -- a multi-byte read of + // the unaligned GUID faults on strict-alignment targets (e.g. AArch64). + // SMBIOS defines all-zero as "UUID not present" and all-FF as "present but + // not settable"; neither is a usable identity. The EFI_GUID field layout + // already matches the SMBIOS 2.6+ byte order, so %g prints canonically. + // + CopyMem (&UuidValue, &Type1->Uuid, sizeof (UuidValue)); + UuidBytes = (CONST UINT8 *)&UuidValue; + AllZero = TRUE; + AllOnes = TRUE; + for (Index = 0; Index < sizeof (UuidValue); Index++) { + if (UuidBytes[Index] != 0x00) { + AllZero = FALSE; + } + + if (UuidBytes[Index] != 0xFF) { + AllOnes = FALSE; + } + } + + if (!AllZero && !AllOnes) { + UnicodeSPrint (Uuid, UuidCount * sizeof (CHAR16), L"%g", &UuidValue); + } +} + +/** + Read the baseboard identity from SMBIOS Type 2 when available. + + Composes " " with placeholder filtering, mirroring the + Type 1 system-name composition. Leaves the buffer empty when Type 2 is absent + or reports no usable strings. + + @param[out] Buffer Destination buffer. Must not be NULL. + @param[in] Count Number of CHAR16 entries in Buffer. +**/ +STATIC +VOID +GetSmbiosBaseboard ( + OUT CHAR16 *Buffer, + IN UINTN Count + ) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + EFI_SMBIOS_HANDLE Handle; + EFI_SMBIOS_TABLE_HEADER *Record; + EFI_SMBIOS_TYPE Type; + SMBIOS_TABLE_TYPE2 *Type2; + CHAR8 *Manufacturer; + CHAR8 *Product; + + if ((Buffer == NULL) || (Count == 0)) { + return; + } + + Buffer[0] = L'\0'; + Smbios = NULL; + Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios); + if (EFI_ERROR (Status) || (Smbios == NULL)) { + return; + } + + Handle = SMBIOS_HANDLE_PI_RESERVED; + Type = SMBIOS_TYPE_BASEBOARD_INFORMATION; + Record = NULL; + Status = Smbios->GetNext (Smbios, &Handle, &Type, &Record, NULL); + if (EFI_ERROR (Status) || (Record == NULL)) { + return; + } + + Type2 = (SMBIOS_TABLE_TYPE2 *)Record; + Manufacturer = GetSmbiosString (Record, Type2->Manufacturer); + Product = GetSmbiosString (Record, Type2->ProductName); + if (IsSmbiosPlaceholder (Manufacturer)) { + Manufacturer = NULL; + } + + if (IsSmbiosPlaceholder (Product)) { + Product = NULL; + } + + if ((Manufacturer != NULL) && (Product != NULL)) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a %a", Manufacturer, Product); + } else if (Product != NULL) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a", Product); + } else if (Manufacturer != NULL) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%a", Manufacturer); + } +} + +/** + Read the firmware version string and release date from SMBIOS Type 0 when + available. + + These are the BIOS-vendor-owned strings (e.g. "edk2-stable202505" and + "05/30/2025") and complement the numeric gST->FirmwareRevision. Each buffer is + left empty when its field is absent or a placeholder, so the caller can hide + the row. + + @param[out] Version Destination for the version string. Must not be NULL. + @param[in] VersionCount Number of CHAR16 entries in Version. + @param[out] Date Destination for the release date. Must not be NULL. + @param[in] DateCount Number of CHAR16 entries in Date. +**/ +STATIC +VOID +GetSmbiosBiosInfo ( + OUT CHAR16 *Version, + IN UINTN VersionCount, + OUT CHAR16 *Date, + IN UINTN DateCount + ) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + EFI_SMBIOS_HANDLE Handle; + EFI_SMBIOS_TABLE_HEADER *Record; + EFI_SMBIOS_TYPE Type; + SMBIOS_TABLE_TYPE0 *Type0; + CHAR8 *VersionString; + CHAR8 *DateString; + + if ((Version == NULL) || (VersionCount == 0) || (Date == NULL) || (DateCount == 0)) { + return; + } + + Version[0] = L'\0'; + Date[0] = L'\0'; + Smbios = NULL; + Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios); + if (EFI_ERROR (Status) || (Smbios == NULL)) { + return; + } + + Handle = SMBIOS_HANDLE_PI_RESERVED; + Type = SMBIOS_TYPE_BIOS_INFORMATION; + Record = NULL; + Status = Smbios->GetNext (Smbios, &Handle, &Type, &Record, NULL); + if (EFI_ERROR (Status) || (Record == NULL)) { + return; + } + + Type0 = (SMBIOS_TABLE_TYPE0 *)Record; + VersionString = GetSmbiosString (Record, Type0->BiosVersion); + DateString = GetSmbiosString (Record, Type0->BiosReleaseDate); + if (!IsSmbiosPlaceholder (VersionString)) { + UnicodeSPrint (Version, VersionCount * sizeof (CHAR16), L"%a", VersionString); + } + + if (!IsSmbiosPlaceholder (DateString)) { + UnicodeSPrint (Date, DateCount * sizeof (CHAR16), L"%a", DateString); + } +} + +/** + Map an SMBIOS Type 17 MemoryType enumeration to a short display string. + + @param[in] MemoryType SMBIOS MEMORY_DEVICE_TYPE value. + + @retval NULL The type is unknown/unmapped (caller omits the type prefix). + @retval others Static short label, e.g. L"DDR4". +**/ +STATIC +CONST CHAR16 * +MemoryTypeToString ( + IN UINT8 MemoryType + ) +{ + switch (MemoryType) { + case MemoryTypeSdram: + return L"SDRAM"; + case MemoryTypeDdr: + return L"DDR"; + case MemoryTypeDdr2: + case MemoryTypeDdr2FbDimm: + return L"DDR2"; + case MemoryTypeDdr3: + return L"DDR3"; + case MemoryTypeDdr4: + return L"DDR4"; + case MemoryTypeDdr5: + return L"DDR5"; + case MemoryTypeLpddr: + return L"LPDDR"; + case MemoryTypeLpddr2: + return L"LPDDR2"; + case MemoryTypeLpddr3: + return L"LPDDR3"; + case MemoryTypeLpddr4: + return L"LPDDR4"; + case MemoryTypeLpddr5: + return L"LPDDR5"; + default: + return NULL; + } +} + +/** + Read the memory type/speed/DIMM-count detail from SMBIOS Type 17 when available. + + Walks every Type 17 (Memory Device) record, counts the populated modules, and + takes the type and speed from the first populated module to compose a detail + string such as "DDR4-3200, 2 DIMMs". The configured clock speed is preferred + over the rated speed; the type prefix and speed are each omitted when not + reported. Leaves the buffer empty when SMBIOS Type 17 is absent or no module is + populated, so the caller can show the bare total size. + + @param[out] Buffer Destination buffer. Must not be NULL. + @param[in] Count Number of CHAR16 entries in Buffer. +**/ +STATIC +VOID +GetSmbiosMemoryDetail ( + OUT CHAR16 *Buffer, + IN UINTN Count + ) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + EFI_SMBIOS_HANDLE Handle; + EFI_SMBIOS_TABLE_HEADER *Record; + EFI_SMBIOS_TYPE Type; + SMBIOS_TABLE_TYPE17 *Type17; + CONST CHAR16 *TypeString; + UINTN DimmCount; + UINT16 Speed; + CONST CHAR16 *Unit; + + if ((Buffer == NULL) || (Count == 0)) { + return; + } + + Buffer[0] = L'\0'; + Smbios = NULL; + Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios); + if (EFI_ERROR (Status) || (Smbios == NULL)) { + return; + } + + TypeString = NULL; + DimmCount = 0; + Speed = 0; + Handle = SMBIOS_HANDLE_PI_RESERVED; + Type = SMBIOS_TYPE_MEMORY_DEVICE; + while (!EFI_ERROR (Smbios->GetNext (Smbios, &Handle, &Type, &Record, NULL)) && (Record != NULL)) { + Type17 = (SMBIOS_TABLE_TYPE17 *)Record; + // + // Size 0 = slot empty; 0xFFFF = unknown. Count only populated modules. + // + if ((Type17->Size == 0) || (Type17->Size == 0xFFFF)) { + continue; + } + + DimmCount++; + if (TypeString == NULL) { + TypeString = MemoryTypeToString (Type17->MemoryType); + Speed = (Type17->ConfiguredMemoryClockSpeed != 0) ? Type17->ConfiguredMemoryClockSpeed : Type17->Speed; + } + } + + if (DimmCount == 0) { + return; + } + + Unit = (DimmCount == 1) ? L"DIMM" : L"DIMMs"; + if ((TypeString != NULL) && (Speed > 0)) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%s-%u, %u %s", TypeString, Speed, DimmCount, Unit); + } else if (TypeString != NULL) { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%s, %u %s", TypeString, DimmCount, Unit); + } else { + UnicodeSPrint (Buffer, Count * sizeof (CHAR16), L"%u %s", DimmCount, Unit); + } +} + /** Read the platform form factor from SMBIOS Type 3 when available. @@ -252,9 +829,44 @@ ModernUiPlatformDataGetSummary ( L"%s", (gST->FirmwareVendor == NULL) ? L"Unknown" : gST->FirmwareVendor ); - UnicodeSPrint (Summary->FirmwareRevision, sizeof (Summary->FirmwareRevision), L"0x%08x", gST->FirmwareRevision); + // + // gST->FirmwareRevision is conventionally encoded as (major << 16) | minor. + // Surface the human-readable major.minor form while keeping the raw hex so a + // firmware engineer can still read the exact encoded value. + // + UnicodeSPrint ( + Summary->FirmwareRevision, + sizeof (Summary->FirmwareRevision), + L"%u.%02u (0x%08x)", + (UINT32)(gST->FirmwareRevision >> 16), + (UINT32)(gST->FirmwareRevision & 0xFFFF), + gST->FirmwareRevision + ); UnicodeSPrint (Summary->Architecture, sizeof (Summary->Architecture), L"%s", GetArchitectureName ()); - UnicodeSPrint (Summary->Platform, sizeof (Summary->Platform), L"UEFI platform"); + // + // Prefer the real SMBIOS Type 1 system identity; fall back to the generic + // label only when SMBIOS reports no product/manufacturer strings. + // + GetSmbiosSystemName (Summary->Platform, ARRAY_SIZE (Summary->Platform)); + if (Summary->Platform[0] == L'\0') { + UnicodeSPrint (Summary->Platform, sizeof (Summary->Platform), L"UEFI platform"); + } + + GetSmbiosProcessor (Summary->Processor, ARRAY_SIZE (Summary->Processor)); + GetSmbiosMemoryDetail (Summary->MemoryDetail, ARRAY_SIZE (Summary->MemoryDetail)); + GetSmbiosSystemDetail ( + Summary->Serial, + ARRAY_SIZE (Summary->Serial), + Summary->Uuid, + ARRAY_SIZE (Summary->Uuid) + ); + GetSmbiosBaseboard (Summary->Baseboard, ARRAY_SIZE (Summary->Baseboard)); + GetSmbiosBiosInfo ( + Summary->BiosVersion, + ARRAY_SIZE (Summary->BiosVersion), + Summary->BiosDate, + ARRAY_SIZE (Summary->BiosDate) + ); GetSmbiosFormFactor (Summary->FormFactor, ARRAY_SIZE (Summary->FormFactor)); GetBootModeName (Summary->BootMode, ARRAY_SIZE (Summary->BootMode)); diff --git a/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.inf b/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.inf index 7fa2c9c..7cc4974 100644 --- a/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.inf +++ b/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.inf @@ -24,6 +24,7 @@ ModernSetupPkg/ModernSetupPkg.dec [LibraryClasses] + BaseLib BaseMemoryLib MemoryAllocationLib PrintLib diff --git a/Library/ModernUiRendererLib/ModernUiRendererLib.c b/Library/ModernUiRendererLib/ModernUiRendererLib.c index 6fbce28..0fa171a 100644 --- a/Library/ModernUiRendererLib/ModernUiRendererLib.c +++ b/Library/ModernUiRendererLib/ModernUiRendererLib.c @@ -73,6 +73,23 @@ ModernUiRendererInit ( Context->Width = Context->Gop->Mode->Info->HorizontalResolution; Context->Height = Context->Gop->Mode->Info->VerticalResolution; + // + // Refuse a mode too small for the modern chrome so callers fall back to plain + // text rendering instead of painting broken layout. See MODERN_UI_MIN_RENDER_*. + // + if ((Context->Width < MODERN_UI_MIN_RENDER_WIDTH) || (Context->Height < MODERN_UI_MIN_RENDER_HEIGHT)) { + DEBUG (( + DEBUG_WARN, + "%a: GOP mode %ux%u below modern minimum %ux%u; falling back\n", + __func__, + Context->Width, + Context->Height, + MODERN_UI_MIN_RENDER_WIDTH, + MODERN_UI_MIN_RENDER_HEIGHT + )); + return EFI_NOT_FOUND; + } + Status = gBS->LocateProtocol ( &gEfiHiiFontProtocolGuid, NULL, diff --git a/Library/ModernUiStringLib/ModernUiStringLib.c b/Library/ModernUiStringLib/ModernUiStringLib.c index 2ea863b..d0701c7 100644 --- a/Library/ModernUiStringLib/ModernUiStringLib.c +++ b/Library/ModernUiStringLib/ModernUiStringLib.c @@ -151,7 +151,9 @@ STATIC CONST CHAR16 *mEnglishStrings[ModernUiStringMax] = { L"Save preferences", L"Load defaults", L"Preferences saved: %r", - L"Preference defaults loaded; save to persist." + L"Preference defaults loaded; save to persist.", + L"System Information", + L"Platform, firmware and memory specifications" }; STATIC CONST CHAR16 *mSimplifiedChineseStrings[ModernUiStringMax] = { @@ -283,7 +285,9 @@ STATIC CONST CHAR16 *mSimplifiedChineseStrings[ModernUiStringMax] = { L"保存偏好", L"载入默认值", L"偏好保存结果:%r", - L"已载入默认偏好;保存后持久化。" + L"已载入默认偏好;保存后持久化。", + L"系统规格", + L"平台、固件与内存规格" }; /** diff --git a/README.md b/README.md index 2d097ee..9e391f1 100644 --- a/README.md +++ b/README.md @@ -576,21 +576,21 @@ Screenshots for GitHub presentation belong under `Assets/Screenshots/`. Keep captures focused on ModernSetup itself, not vendor firmware screens or copied assets. -Current OVMF X64 capture: +Current OVMF X64 captures: ![ModernSetup OVMF X64 dashboard](Assets/Screenshots/modern-ovmf-x64-dashboard-graphite-gold.png) +![ModernSetup OVMF X64 System Information page](Assets/Screenshots/modern-ovmf-x64-systeminfo.png) + Current LoongArch captures: ![ModernSetup LoongArch dashboard](Assets/Screenshots/modern-loongarch-dashboard.png) ![ModernDisplayEngine LoongArch device page](Assets/Screenshots/modern-loongarch-displayengine-device.png) -Current ArmVirt `ModernSetupApp` captures: - -![ModernSetupApp v0.4 dashboard](Assets/Screenshots/setup-v0.4-dashboard.png) +Current ArmVirt AArch64 `ModernSetupApp` captures: -![ModernSetupApp dashboard](Assets/Screenshots/modern-app-dashboard.png) +![ModernSetup ArmVirt AArch64 dashboard](Assets/Screenshots/modern-aarch64-dashboard.png) ![ModernSetupApp English exit page](Assets/Screenshots/modern-app-en-exit.png) diff --git a/Scripts/bootstrap-edk2.sh b/Scripts/bootstrap-edk2.sh index be41c16..cd784a2 100755 --- a/Scripts/bootstrap-edk2.sh +++ b/Scripts/bootstrap-edk2.sh @@ -24,6 +24,15 @@ fi echo "Updating edk2 submodule: External/edk2" git submodule update --init -- External/edk2 +# External/lvgl is the upstream LVGL submodule consumed by the LVGL display +# engine (the default backend on the targets that build it, e.g. ovmf-x64 and +# loongarch). Initialize it when the .gitmodules entry is present so a default +# MODERN_SETUP_DISPLAY_ENGINE=lvgl build works from a fresh checkout. +if git config -f .gitmodules --get submodule.External/lvgl.url >/dev/null 2>&1; then + echo "Updating LVGL submodule: External/lvgl" + git submodule update --init -- External/lvgl +fi + # Initialize edk2 first-level dependencies only. A full recursive update pulls # optional nested OpenSSL test submodules, which are not required for the # ModernSetupPkg BaseTools/app/OVMF baseline and can make validation slow, diff --git a/Scripts/build-loongarchvirt.sh b/Scripts/build-loongarchvirt.sh index 1e9ce38..319f9f1 100755 --- a/Scripts/build-loongarchvirt.sh +++ b/Scripts/build-loongarchvirt.sh @@ -15,7 +15,7 @@ TARGET="${TARGET:-DEBUG}" JOBS="${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 4)}" MODERN_SETUP_DEMO_DRIVER_SAMPLE="${MODERN_SETUP_DEMO_DRIVER_SAMPLE:-1}" MODERN_SETUP_THEME="${MODERN_SETUP_THEME:-graphite-gold}" -MODERN_SETUP_DISPLAY_ENGINE="${MODERN_SETUP_DISPLAY_ENGINE:-modern}" +MODERN_SETUP_DISPLAY_ENGINE="${MODERN_SETUP_DISPLAY_ENGINE:-lvgl}" MODERN_SETUP_INCLUDE_APP="${MODERN_SETUP_INCLUDE_APP:-0}" MODERN_SETUP_REPLACE_UIAPP="${MODERN_SETUP_REPLACE_UIAPP:-0}" GCC_LOONGARCH64_PREFIX="${GCC_LOONGARCH64_PREFIX:-}" diff --git a/Scripts/build-ovmf-x64.sh b/Scripts/build-ovmf-x64.sh index c51fcd2..9d4d2e6 100755 --- a/Scripts/build-ovmf-x64.sh +++ b/Scripts/build-ovmf-x64.sh @@ -15,7 +15,7 @@ TARGET="${TARGET:-DEBUG}" TOOL_CHAIN_TAG="${TOOL_CHAIN_TAG:-GCC}" JOBS="${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)}" MODERN_SETUP_THEME="${MODERN_SETUP_THEME:-graphite-gold}" -MODERN_SETUP_DISPLAY_ENGINE="${MODERN_SETUP_DISPLAY_ENGINE:-modern}" +MODERN_SETUP_DISPLAY_ENGINE="${MODERN_SETUP_DISPLAY_ENGINE:-lvgl}" MODERN_SETUP_REPLACE_UIAPP="${MODERN_SETUP_REPLACE_UIAPP:-0}" # Opt-in control-rich VFR test driver (checkbox/numeric/date/time/string/ # password/ordered-list), reachable via Device Manager. Used to exercise the diff --git a/Tests/Smoke/smoke_validate.py b/Tests/Smoke/smoke_validate.py index 577f696..283a3c2 100755 --- a/Tests/Smoke/smoke_validate.py +++ b/Tests/Smoke/smoke_validate.py @@ -90,6 +90,7 @@ MODERN_SETUP_APP_INF = MODERN_SETUP_APP_DIR / "ModernSetupApp.inf" APP_NOINLINE_DRAW_HELPERS = ( "DrawProviderSummaryPage", + "DrawSystemInfo", "DrawBoot", "DrawHiiReadOnlyPreview", "DrawDevices", @@ -1276,6 +1277,26 @@ def check_modern_setup_app_module_boundaries(root: Path) -> list[str]: raise SmokeFailure("Dashboard quick-card expansion must expose native Continue plus setup category cards") if "DASHBOARD_QUICK_CARD_COUNT" not in dashboard_body: raise SmokeFailure("ModernSetupAppDashboard.c must layout cards from DASHBOARD_QUICK_CARD_COUNT") + # + # Per Docs/AppFeatureStandard.md the quick-card *catalog* is fixed (asserted + # above) but the *visible* count is platform-class variable, gated by a + # single data-driven applicability predicate. Lock that in: the predicate and + # the visible-count helper must exist, and the drawing + navigation paths must + # bound on the visible count rather than on the fixed catalog constant. + # + for helper in ("ModernSetupDashboardQuickCardApplicable", "ModernSetupDashboardVisibleQuickCardCount"): + if helper not in actions_body: + raise SmokeFailure(f"ModernSetupAppActions.c must define the platform-class card helper: {helper}") + if "ModernSetupDashboardVisibleQuickCardCount" not in dashboard_body: + raise SmokeFailure( + "ModernSetupAppDashboard.c must draw quick cards from the platform-visible count " + "(ModernSetupDashboardVisibleQuickCardCount), not a fixed catalog count" + ) + if "ModernSetupDashboardVisibleQuickCardCount" not in app_body: + raise SmokeFailure( + "ModernSetupApp.c dashboard navigation must bound on the platform-visible card count " + "(ModernSetupDashboardVisibleQuickCardCount)" + ) if "DASHBOARD_QUICK_VALUE_MIN_HEIGHT" not in dashboard_body: raise SmokeFailure("ModernSetupAppDashboard.c must keep Dashboard card values visible in compact layouts") layout_defines = {