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 += "

{}".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()