Files
OpCore-Simplify/Scripts/pages/compatibility_page.py
Hoang Hong Quan 0e608a56ce Add GUI Support for OpCore Simplify (#512)
* Refactor OpCore-Simplify to GUI version

* New ConfigEditor

* Add requirement checks and installation in launchers

* Add GitHub Actions workflow to generate manifest.json

* Set compression level for asset

* Skip .git and __pycache__ folders

* Refactor update process to include integrity checker

* Add SMBIOS model selection

* Update README.md

* Update to main branch
2025-12-30 14:19:47 +07:00

557 lines
22 KiB
Python

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
from qfluentwidgets import SubtitleLabel, BodyLabel, ScrollArea, FluentIcon, GroupHeaderCardWidget, CardWidget, StrongBodyLabel
from Scripts.styles import COLORS, SPACING
from Scripts import ui_utils
from Scripts.datasets import os_data, pci_data
class CompatibilityStatusBanner:
def __init__(self, parent=None, ui_utils_instance=None, layout=None):
self.parent = parent
self.ui_utils = ui_utils_instance if ui_utils_instance else ui_utils.UIUtils()
self.layout = layout
self.card = None
self.body_label = None
self.note_label = None
def _create_card(self, card_type, icon, title, message, note=""):
body_text = message
if note:
body_text += "<br><br><i style=\"color: {}; font-size: 12px;\">{}</i>".format(COLORS["text_secondary"], note)
if self.card:
if self.layout:
self.layout.removeWidget(self.card)
self.card.setParent(None)
self.card.deleteLater()
self.card = self.ui_utils.custom_card(
card_type=card_type,
icon=icon,
title=title,
body=body_text,
parent=self.parent
)
self.card.setVisible(True)
if self.layout:
self.layout.insertWidget(2, self.card)
return self.card
def show_error(self, title, message, note=""):
self._create_card("error", FluentIcon.CLOSE, title, message, note)
def show_success(self, title, message, note=""):
self._create_card("success", FluentIcon.ACCEPT, title, message, note)
def setVisible(self, visible):
if self.card:
self.card.setVisible(visible)
class CompatibilityPage(ScrollArea):
def __init__(self, parent, ui_utils_instance=None):
super().__init__(parent)
self.setObjectName("compatibilityPage")
self.controller = parent
self.scrollWidget = QWidget()
self.expandLayout = QVBoxLayout(self.scrollWidget)
self.ui_utils = ui_utils_instance if ui_utils_instance else ui_utils.UIUtils()
self.contentWidget = None
self.contentLayout = None
self.native_support_label = None
self.ocl_support_label = None
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setWidget(self.scrollWidget)
self.setWidgetResizable(True)
self.enableTransparentBackground()
self._init_ui()
def _init_ui(self):
self.expandLayout.setContentsMargins(SPACING["xxlarge"], SPACING["xlarge"], SPACING["xxlarge"], SPACING["xlarge"])
self.expandLayout.setSpacing(SPACING["large"])
self.expandLayout.addWidget(self.ui_utils.create_step_indicator(2))
header_container = QWidget()
header_layout = QHBoxLayout(header_container)
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.setSpacing(SPACING["large"])
title_block = QWidget()
title_layout = QVBoxLayout(title_block)
title_layout.setContentsMargins(0, 0, 0, 0)
title_layout.setSpacing(SPACING["tiny"])
title_label = SubtitleLabel("Hardware Compatibility")
title_layout.addWidget(title_label)
subtitle_label = BodyLabel("Review hardware compatibility with macOS")
subtitle_label.setStyleSheet("color: {};".format(COLORS["text_secondary"]))
title_layout.addWidget(subtitle_label)
header_layout.addWidget(title_block, 1)
self.expandLayout.addWidget(header_container)
self.status_banner = CompatibilityStatusBanner(self.scrollWidget, self.ui_utils, self.expandLayout)
self.expandLayout.addSpacing(SPACING["large"])
self.contentWidget = QWidget()
self.contentLayout = QVBoxLayout(self.contentWidget)
self.contentLayout.setContentsMargins(0, 0, 0, 0)
self.contentLayout.setSpacing(SPACING["large"])
self.expandLayout.addWidget(self.contentWidget)
self.placeholder_label = BodyLabel("Load a hardware report to see compatibility information")
self.placeholder_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.placeholder_label.setStyleSheet("color: #605E5C; padding: 40px;")
self.placeholder_label.setWordWrap(True)
self.contentLayout.addWidget(self.placeholder_label)
self.contentLayout.addStretch()
def update_status_banner(self):
if not self.controller.hardware_state.hardware_report:
self.status_banner.setVisible(False)
return
if self.controller.hardware_state.compatibility_error:
self._show_error_banner()
return
self._show_support_banner()
def _show_error_banner(self):
codes = self.controller.hardware_state.compatibility_error
if isinstance(codes, str):
codes = [codes]
code_map = {
"ERROR_MISSING_SSE4": (
"Missing required SSE4.x instruction set.",
"Your CPU is not supported by macOS versions newer than Sierra (10.12)."
),
"ERROR_NO_COMPATIBLE_GPU": (
"You cannot install macOS without a supported GPU.",
"Please do NOT spam my inbox or issue tracker about this issue anymore!"
),
"ERROR_INTEL_VMD": (
"Intel VMD controllers are not supported in macOS.",
"Please disable Intel VMD in the BIOS settings and try again with new hardware report."
),
"ERROR_NO_COMPATIBLE_STORAGE": (
"No compatible storage controller for macOS was found!",
"Consider purchasing a compatible SSD NVMe for your system."
)
}
title = "Hardware Compatibility Issue"
messages = []
notes = []
for code in codes:
msg, note = code_map.get(code, (code, ""))
messages.append(msg)
if note:
notes.append(note)
self.status_banner.show_error(
title,
"\n".join(messages),
"\n".join(notes)
)
def _show_support_banner(self):
if self.controller.macos_state.native_version:
min_ver_name = os_data.get_macos_name_by_darwin(self.controller.macos_state.native_version[0])
max_ver_name = os_data.get_macos_name_by_darwin(self.controller.macos_state.native_version[-1])
native_range = min_ver_name if min_ver_name == max_ver_name else "{} to {}".format(min_ver_name, max_ver_name)
message = "Native macOS support: {}".format(native_range)
if self.controller.macos_state.ocl_patched_version:
oclp_max_name = os_data.get_macos_name_by_darwin(self.controller.macos_state.ocl_patched_version[0])
oclp_min_name = os_data.get_macos_name_by_darwin(self.controller.macos_state.ocl_patched_version[-1])
oclp_range = oclp_min_name if oclp_min_name == oclp_max_name else "{} to {}".format(oclp_min_name, oclp_max_name)
message += "\nOpenCore Legacy Patcher extended support: {}".format(oclp_range)
self.status_banner.show_success("Hardware is Compatible", message)
else:
self.status_banner.show_error(
"Incompatible Hardware",
"No supported macOS version found for this hardware configuration."
)
def format_compatibility(self, compat_tuple):
if not compat_tuple or compat_tuple == (None, None):
return "Unsupported", "#D13438"
max_ver, min_ver = compat_tuple
if max_ver and min_ver:
max_name = os_data.get_macos_name_by_darwin(max_ver)
min_name = os_data.get_macos_name_by_darwin(min_ver)
if max_name == min_name:
return "Up to {}".format(max_name), "#0078D4"
else:
return "{} to {}".format(min_name, max_name), "#107C10"
return "Unknown", "#605E5C"
def update_display(self):
if not self.contentLayout:
return
while self.contentLayout.count() > 0:
item = self.contentLayout.takeAt(0)
widget = item.widget()
if widget:
widget.deleteLater()
if not self.controller.hardware_state.hardware_report:
self._show_placeholder()
return
report = self.controller.hardware_state.hardware_report
cards_added = 0
cards_added += self._add_cpu_card(report)
cards_added += self._add_gpu_card(report)
cards_added += self._add_sound_card(report)
cards_added += self._add_network_card(report)
cards_added += self._add_storage_card(report)
cards_added += self._add_bluetooth_card(report)
cards_added += self._add_biometric_card(report)
cards_added += self._add_sd_card(report)
if cards_added == 0:
self._show_no_data_label()
self.contentLayout.addStretch()
self.update_status_banner()
self.scrollWidget.updateGeometry()
self.scrollWidget.update()
self.update()
def _show_placeholder(self):
self.placeholder_label = BodyLabel("Load hardware report to see compatibility information")
self.placeholder_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.placeholder_label.setStyleSheet("color: #605E5C; padding: 40px;")
self.placeholder_label.setWordWrap(True)
self.contentLayout.addWidget(self.placeholder_label)
self.contentLayout.addStretch()
def _show_no_data_label(self):
no_data_card = self.ui_utils.custom_card(
card_type="error",
icon=FluentIcon.CLOSE,
title="No compatible hardware information found in the report.",
body="Please ensure the hardware report contains valid device data.",
parent=self.scrollWidget
)
self.contentLayout.addWidget(no_data_card)
def _add_compatibility_group(self, card, title, compat):
compat_text, compat_color = self.format_compatibility(compat)
self.ui_utils.add_group_with_indent(
card,
self.ui_utils.get_compatibility_icon(compat),
title,
compat_text,
self.ui_utils.create_info_widget("", compat_color),
indent_level=1
)
def _add_cpu_card(self, report):
if "CPU" not in report: return 0
cpu_info = report["CPU"]
if not isinstance(cpu_info, dict): return 0
cpu_card = GroupHeaderCardWidget(self.scrollWidget)
cpu_card.setTitle("CPU")
name = cpu_info.get("Processor Name", "Unknown")
self.ui_utils.add_group_with_indent(
cpu_card,
self.ui_utils.colored_icon(FluentIcon.TAG, COLORS["primary"]),
"Processor",
name,
indent_level=0
)
self._add_compatibility_group(cpu_card, "macOS Compatibility", cpu_info.get("Compatibility", (None, None)))
details = []
if cpu_info.get("Codename"):
details.append("Codename: {}".format(cpu_info.get("Codename")))
if cpu_info.get("Core Count"):
details.append("Cores: {}".format(cpu_info.get("Core Count")))
if details:
self.ui_utils.add_group_with_indent(
cpu_card,
self.ui_utils.colored_icon(FluentIcon.INFO, COLORS["info"]),
"Details",
"".join(details),
indent_level=1
)
self.contentLayout.addWidget(cpu_card)
return 1
def _add_gpu_card(self, report):
if "GPU" not in report or not report["GPU"]: return 0
gpu_card = GroupHeaderCardWidget(self.scrollWidget)
gpu_card.setTitle("Graphics")
for idx, (gpu_name, gpu_info) in enumerate(report["GPU"].items()):
device_type = gpu_info.get("Device Type", "Unknown")
self.ui_utils.add_group_with_indent(
gpu_card,
self.ui_utils.colored_icon(FluentIcon.PHOTO, COLORS["primary"]),
gpu_name,
"Type: {}".format(device_type),
indent_level=0
)
self._add_compatibility_group(gpu_card, "macOS Compatibility", gpu_info.get("Compatibility", (None, None)))
if "OCLP Compatibility" in gpu_info:
oclp_compat = gpu_info.get("OCLP Compatibility")
oclp_text, oclp_color = self.format_compatibility(oclp_compat)
self.ui_utils.add_group_with_indent(
gpu_card,
self.ui_utils.colored_icon(FluentIcon.IOT, COLORS["primary"]),
"OCLP Compatibility",
oclp_text,
self.ui_utils.create_info_widget("Extended support with OpenCore Legacy Patcher", COLORS["text_secondary"]),
indent_level=1
)
if "Monitor" in report:
self._add_monitor_info(gpu_card, gpu_name, gpu_info, report["Monitor"])
self.contentLayout.addWidget(gpu_card)
return 1
def _add_monitor_info(self, gpu_card, gpu_name, gpu_info, monitors):
connected_monitors = []
for monitor_name, monitor_info in monitors.items():
if monitor_info.get("Connected GPU") == gpu_name:
connector = monitor_info.get("Connector Type", "Unknown")
monitor_str = "{} ({})".format(monitor_name, connector)
manufacturer = gpu_info.get("Manufacturer", "")
raw_device_id = gpu_info.get("Device ID", "")
device_id = raw_device_id[5:] if len(raw_device_id) > 5 else raw_device_id
if "Intel" in manufacturer and device_id.startswith(("01", "04", "0A", "0C", "0D")):
if connector == "VGA":
monitor_str += " (Unsupported)"
connected_monitors.append(monitor_str)
if connected_monitors:
self.ui_utils.add_group_with_indent(
gpu_card,
self.ui_utils.colored_icon(FluentIcon.VIEW, COLORS["info"]),
"Connected Displays",
", ".join(connected_monitors),
indent_level=1
)
def _add_sound_card(self, report):
if "Sound" not in report or not report["Sound"]: return 0
sound_card = GroupHeaderCardWidget(self.scrollWidget)
sound_card.setTitle("Audio")
for audio_device, audio_props in report["Sound"].items():
self.ui_utils.add_group_with_indent(
sound_card,
self.ui_utils.colored_icon(FluentIcon.MUSIC, COLORS["primary"]),
audio_device,
"",
indent_level=0
)
self._add_compatibility_group(sound_card, "macOS Compatibility", audio_props.get("Compatibility", (None, None)))
endpoints = audio_props.get("Audio Endpoints", [])
if endpoints:
self.ui_utils.add_group_with_indent(
sound_card,
self.ui_utils.colored_icon(FluentIcon.HEADPHONE, COLORS["info"]),
"Audio Endpoints",
", ".join(endpoints),
indent_level=1
)
self.contentLayout.addWidget(sound_card)
return 1
def _add_network_card(self, report):
if "Network" not in report or not report["Network"]: return 0
network_card = GroupHeaderCardWidget(self.scrollWidget)
network_card.setTitle("Network")
for device_name, device_props in report["Network"].items():
self.ui_utils.add_group_with_indent(
network_card,
self.ui_utils.colored_icon(FluentIcon.WIFI, COLORS["primary"]),
device_name,
"",
indent_level=0
)
self._add_compatibility_group(network_card, "macOS Compatibility", device_props.get("Compatibility", (None, None)))
if "OCLP Compatibility" in device_props:
oclp_compat = device_props.get("OCLP Compatibility")
oclp_text, oclp_color = self.format_compatibility(oclp_compat)
self.ui_utils.add_group_with_indent(
network_card,
self.ui_utils.colored_icon(FluentIcon.IOT, COLORS["primary"]),
"OCLP Compatibility",
oclp_text,
self.ui_utils.create_info_widget("Extended support with OpenCore Legacy Patcher", COLORS["text_secondary"]),
indent_level=1
)
self._add_continuity_info(network_card, device_props)
self.contentLayout.addWidget(network_card)
return 1
def _add_continuity_info(self, network_card, device_props):
device_id = device_props.get("Device ID", "")
if not device_id: return
continuity_info = ""
continuity_color = COLORS["text_secondary"]
if device_id in pci_data.BroadcomWiFiIDs:
continuity_info = "Full support (AirDrop, Handoff, Universal Clipboard, Instant Hotspot, etc.)"
continuity_color = COLORS["success"]
elif device_id in pci_data.IntelWiFiIDs:
continuity_info = "Partial (Handoff and Universal Clipboard with AirportItlwm) - AirDrop, Universal Clipboard, Instant Hotspot,... not available"
continuity_color = COLORS["warning"]
elif device_id in pci_data.AtherosWiFiIDs:
continuity_info = "Limited support (No Continuity features available). Atheros cards are not recommended for macOS."
continuity_color = COLORS["error"]
if continuity_info:
self.ui_utils.add_group_with_indent(
network_card,
self.ui_utils.colored_icon(FluentIcon.SYNC, continuity_color),
"Continuity Features",
continuity_info,
self.ui_utils.create_info_widget("", continuity_color),
indent_level=1
)
def _add_storage_card(self, report):
if "Storage Controllers" not in report or not report["Storage Controllers"]: return 0
storage_card = GroupHeaderCardWidget(self.scrollWidget)
storage_card.setTitle("Storage")
for controller_name, controller_props in report["Storage Controllers"].items():
self.ui_utils.add_group_with_indent(
storage_card,
self.ui_utils.colored_icon(FluentIcon.FOLDER, COLORS["primary"]),
controller_name,
"",
indent_level=0
)
self._add_compatibility_group(storage_card, "macOS Compatibility", controller_props.get("Compatibility", (None, None)))
disk_drives = controller_props.get("Disk Drives", [])
if disk_drives:
self.ui_utils.add_group_with_indent(
storage_card,
self.ui_utils.colored_icon(FluentIcon.FOLDER, COLORS["info"]),
"Disk Drives",
", ".join(disk_drives),
indent_level=1
)
self.contentLayout.addWidget(storage_card)
return 1
def _add_bluetooth_card(self, report):
if "Bluetooth" not in report or not report["Bluetooth"]: return 0
bluetooth_card = GroupHeaderCardWidget(self.scrollWidget)
bluetooth_card.setTitle("Bluetooth")
for bluetooth_name, bluetooth_props in report["Bluetooth"].items():
self.ui_utils.add_group_with_indent(
bluetooth_card,
self.ui_utils.colored_icon(FluentIcon.BLUETOOTH, COLORS["primary"]),
bluetooth_name,
"",
indent_level=0
)
self._add_compatibility_group(bluetooth_card, "macOS Compatibility", bluetooth_props.get("Compatibility", (None, None)))
self.contentLayout.addWidget(bluetooth_card)
return 1
def _add_biometric_card(self, report):
if "Biometric" not in report or not report["Biometric"]: return 0
bio_card = GroupHeaderCardWidget(self.scrollWidget)
bio_card.setTitle("Biometric")
self.ui_utils.add_group_with_indent(
bio_card,
self.ui_utils.colored_icon(FluentIcon.CLOSE, COLORS["warning"]),
"Hardware Limitation",
"Biometric authentication in macOS requires Apple T2 Chip, which is not available for Hackintosh systems.",
self.ui_utils.create_info_widget("", COLORS["warning"]),
indent_level=0
)
for bio_device, bio_props in report["Biometric"].items():
self.ui_utils.add_group_with_indent(
bio_card,
self.ui_utils.colored_icon(FluentIcon.FINGERPRINT, COLORS["error"]),
bio_device,
"Unsupported",
indent_level=0
)
self.contentLayout.addWidget(bio_card)
return 1
def _add_sd_card(self, report):
if "SD Controller" not in report or not report["SD Controller"]: return 0
sd_card = GroupHeaderCardWidget(self.scrollWidget)
sd_card.setTitle("SD Controller")
for controller_name, controller_props in report["SD Controller"].items():
self.ui_utils.add_group_with_indent(
sd_card,
self.ui_utils.colored_icon(FluentIcon.SAVE, COLORS["primary"]),
controller_name,
"",
indent_level=0
)
self._add_compatibility_group(sd_card, "macOS Compatibility", controller_props.get("Compatibility", (None, None)))
self.contentLayout.addWidget(sd_card)
return 1
def refresh(self):
self.update_display()