From 4f1cb8abccb5266c47cdd21ef485c2f1550db630 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 8 May 2023 15:16:10 -0600 Subject: [PATCH] GUI: Add installer flashing --- ...ler.py => gui_macos_installer_download.py} | 25 +- resources/wx_gui/gui_macos_installer_flash.py | 507 ++++++++++++++++++ resources/wx_gui/gui_main_menu.py | 4 +- 3 files changed, 531 insertions(+), 5 deletions(-) rename resources/wx_gui/{gui_macos_installer.py => gui_macos_installer_download.py} (93%) create mode 100644 resources/wx_gui/gui_macos_installer_flash.py diff --git a/resources/wx_gui/gui_macos_installer.py b/resources/wx_gui/gui_macos_installer_download.py similarity index 93% rename from resources/wx_gui/gui_macos_installer.py rename to resources/wx_gui/gui_macos_installer_download.py index 2fc1211c2..fe3012ad7 100644 --- a/resources/wx_gui/gui_macos_installer.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -4,7 +4,7 @@ import threading from pathlib import Path -from resources.wx_gui import gui_main_menu, gui_support, gui_download +from resources.wx_gui import gui_main_menu, gui_support, gui_download, gui_macos_installer_flash from resources import ( constants, macos_installer_handler, @@ -18,9 +18,9 @@ class macOSInstallerFrame(wx.Frame): """ Create a frame for downloading and creating macOS installers Uses a Modal Dialog for smoother transition from other frames + Note: Flashing installers is passed to gui_macos_installer_flash.py """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): - # super(macOSInstallerFrame, self).__init__(parent, title=title, size=(350, 200)) self.constants: constants.Constants = global_constants self.title: str = title @@ -131,6 +131,11 @@ class macOSInstallerFrame(wx.Frame): installer_button.Center(wx.HORIZONTAL) spacer += 25 + # Since installers are sorted by version, set the latest installer as the default button + # Note that on full display, the last installer is generally a beta + if show_full is False and app == list(installers.keys())[-1]: + installer_button.SetDefault() + # Show all available installers show_all_button = wx.Button(dialog, label="Show all available installers" if show_full is False else "Show only latest installers", pos=(-1, installer_button.GetPosition()[1] + installer_button.GetSize()[1]), size=(180, 30)) show_all_button.Bind(wx.EVT_BUTTON, lambda event: self._display_available_installers(event, not show_full)) @@ -276,6 +281,7 @@ class macOSInstallerFrame(wx.Frame): self.on_existing() + def on_download(self, event: wx.Event) -> None: self.frame_modal.Close() self.parent.Hide() @@ -283,7 +289,20 @@ class macOSInstallerFrame(wx.Frame): self.parent.Close() def on_existing(self, event: wx.Event = None) -> None: - pass + frames = [self, self.frame_modal, self.parent] + for frame in frames: + if frame: + frame.Close() + gui_macos_installer_flash.macOSInstallerFlashFrame( + None, + title=self.title, + global_constants=self.constants, + **({"screen_location": self.GetScreenPosition()} if self else {}) + ) + for frame in frames: + if frame: + frame.Destroy() + def on_return(self, event: wx.Event) -> None: self.frame_modal.Close() diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py new file mode 100644 index 000000000..516f1aff0 --- /dev/null +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -0,0 +1,507 @@ +import wx +import logging +import threading +import time +import subprocess +import plistlib +import tempfile + +from pathlib import Path + +from resources.wx_gui import gui_main_menu, gui_build +from resources import ( + constants, + macos_installer_handler, + utilities, + network_handler, + kdk_handler, +) +from data import os_data + + +class macOSInstallerFlashFrame(wx.Frame): + + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + super(macOSInstallerFlashFrame, self).__init__(parent, title=title, size=(350, 200)) + + self.constants: constants.Constants = global_constants + self.title: str = title + + self.available_installers_local: dict = {} + self.available_disks: dict = {} + self.prepare_result: bool = False + + self.frame_modal: wx.Dialog = None + + self._generate_elements() + + self.SetPosition(screen_location) if screen_location else self.Centre() + self.Show() + + self._populate_installers() + + + def _generate_elements(self) -> None: + """ + Fetches local macOS Installers for users to select from + """ + + # Title: Fetching local macOS Installers + title_label = wx.StaticText(self, label="Fetching local macOS Installers", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Center(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, 30), size=(200, 30)) + progress_bar.Center(wx.HORIZONTAL) + progress_bar.Pulse() + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + + + def _populate_installers(self) -> None: + # Grab installer catalog + def fetch_installers(): + self.available_installers_local = macos_installer_handler.LocalInstallerCatalog().available_apps + + thread = threading.Thread(target=fetch_installers) + thread.start() + + while thread.is_alive(): + wx.Yield() + + frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) + frame_modal.Center(wx.HORIZONTAL) + + # Title: Select macOS Installer + title_label = wx.StaticText(frame_modal, label="Select local macOS Installer", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Center(wx.HORIZONTAL) + + # List of installers + if self.available_installers_local: + logging.info("Installer(s) found:") + spacer = 10 + for app in self.available_installers_local: + logging.info(f"- {self.available_installers_local[app]['Short Name']}: {self.available_installers_local[app]['Version']} ({self.available_installers_local[app]['Build']})") + installer_button = wx.Button(frame_modal, label=f"{self.available_installers_local[app]['Short Name']}: {self.available_installers_local[app]['Version']} ({self.available_installers_local[app]['Build']})", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + spacer), size=(300, 30)) + installer_button.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.on_select(self.available_installers_local[temp])) + installer_button.Center(wx.HORIZONTAL) + spacer += 25 + else: + installer_button = wx.StaticText(frame_modal, label="No installers found in '/Applications'", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + installer_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + installer_button.Center(wx.HORIZONTAL) + + # Button: Return to Main Menu + cancel_button = wx.Button(frame_modal, label="Return to Main Menu", pos=(-1, installer_button.GetPosition()[1] + installer_button.GetSize()[1]), size=(150, 30)) + cancel_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + cancel_button.Center(wx.HORIZONTAL) + + # Set size of frame + frame_modal.SetSize((-1, cancel_button.GetPosition()[1] + cancel_button.GetSize()[1] + 40)) + + frame_modal.ShowWindowModal() + self.frame_modal = frame_modal + + + def on_select(self, installer: dict) -> None: + self.frame_modal.Destroy() + + for child in self.GetChildren(): + child.Destroy() + + # Fetching information on local disks + title_label = wx.StaticText(self, label="Fetching information on local disks", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Center(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, 30), size=(200, 30)) + progress_bar.Center(wx.HORIZONTAL) + progress_bar.Pulse() + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + + # Fetch local disks + def fetch_disks(): + self.available_disks = macos_installer_handler.InstallerCreation().list_disk_to_format() + + thread = threading.Thread(target=fetch_disks) + thread.start() + + while thread.is_alive(): + wx.Yield() + + self.frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) + + # Title: Select local disk + title_label = wx.StaticText(self.frame_modal, label="Select local disk", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Center(wx.HORIZONTAL) + + # Label: Selected USB will be erased, please backup any data + warning_label = wx.StaticText(self.frame_modal, label="Selected USB will be erased, please backup any data", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + warning_label.Center(wx.HORIZONTAL) + + # List of disks + if self.available_disks: + spacer = 5 + for disk in self.available_disks: + logging.info(f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}") + disk_button = wx.Button(self.frame_modal, label=f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + spacer), size=(300, 30)) + disk_button.Bind(wx.EVT_BUTTON, lambda event, temp=disk: self.on_select_disk(self.available_disks[temp], installer)) + disk_button.Center(wx.HORIZONTAL) + else: + disk_button = wx.StaticText(self.frame_modal, label="No disks found", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5)) + disk_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + disk_button.Center(wx.HORIZONTAL) + + # Button: Return to Main Menu + cancel_button = wx.Button(self.frame_modal, label="Return to Main Menu", pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1]), size=(150, 30)) + cancel_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + cancel_button.Center(wx.HORIZONTAL) + + # Set size of frame + self.frame_modal.SetSize((-1, cancel_button.GetPosition()[1] + cancel_button.GetSize()[1] + 40)) + self.frame_modal.ShowWindowModal() + + + def on_select_disk(self, disk: dict, installer: dict) -> None: + answer = wx.MessageBox(f"Are you sure you want to erase '{disk['name']}'?", "Confirmation", wx.YES_NO | wx.ICON_QUESTION) + if answer != wx.YES: + return + + self.frame_modal.Destroy() + + for child in self.GetChildren(): + child.Destroy() + + self.SetSize((450, -1)) + + # Title: Creating Installer: {installer_name} + title_label = wx.StaticText(self, label=f"Creating Installer: {installer['Short Name']}", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Center(wx.HORIZONTAL) + + # Label: Creating macOS installers can take 30min+ on slower USB drives. + warning_label = wx.StaticText(self, label="Creating macOS installers can take 30min+ on slower USB drives.", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + warning_label.Center(wx.HORIZONTAL) + + # Label: We will notify you when the installer is ready. + warning_label = wx.StaticText(self, label="We will notify you when the installer is ready.", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5)) + warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + warning_label.Center(wx.HORIZONTAL) + + # Label: Bytes Written: 0 MB + bytes_written_label = wx.StaticText(self, label="Bytes Written: 0000.0 MB", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5)) + bytes_written_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + bytes_written_label.Center(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, bytes_written_label.GetPosition()[1] + bytes_written_label.GetSize()[1] + 5), size=(300, 30)) + progress_bar.Center(wx.HORIZONTAL) + progress_bar.Pulse() + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + self.Show() + + # Prepare resources + if self._prepare_resources(installer['Path'], disk['identifier']) is False: + wx.MessageBox("Failed to prepare resources, cannot continue.", "Error", wx.OK | wx.ICON_ERROR) + self.on_return_to_main_menu() + return + + # Base Size + estimated_size = 16000 + # AutoPkg (700MB~) + estimated_size += 700 if self.constants.detected_os >= os_data.os_data.big_sur else 0 + # KDK (700MB~, and overhead for copying to installer) + estimated_size += 700 * 2 if self.constants.detected_os >= os_data.os_data.ventura else 0 + + progress_bar.SetRange(estimated_size) + + root_disk = disk['identifier'][5:] + initial_bytes_written = float(utilities.monitor_disk_output(root_disk)) + self.result = False + def flash(): + self.result = self._flash_installer(root_disk) + + thread = threading.Thread(target=flash) + thread.start() + + # Wait for installer to be created + while thread.is_alive(): + total_bytes_written = float(utilities.monitor_disk_output(root_disk)) + bytes_written = total_bytes_written - initial_bytes_written + bytes_written_label.SetLabel(f"Bytes Written: {bytes_written:.2f} MB") + progress_bar.SetValue(int(bytes_written)) + wx.Yield() + + if self.result is False: + return + + # Next verify the installer + progress_bar.Pulse() + bytes_written_label.SetLabel("Validating Installer Integrity...") + error_message = self._validate_installer_pkg(disk['identifier']) + + if error_message != "": + progress_bar.SetValue(0) + wx.MessageBox(f"Failed to validate installer, cannot continue.\n This can generally happen due to a faulty USB drive, as flashing is an intensive process that can trigger hardware faults not normally seen. \n\n{error_message}", "Corrupted Installer!", wx.OK | wx.ICON_ERROR) + self.on_return_to_main_menu() + return + + progress_bar.SetValue(estimated_size) + + # Notify user + answer = wx.MessageBox("Installer created successfully, would you like to continue and Install OpenCore to this disk?", "Successfully created the macOS installer!", wx.YES_NO | wx.ICON_QUESTION) + if answer != wx.YES: + self.on_return_to_main_menu() + return + + # Install OpenCore + self.Hide() + gui_build.BuildFrame( + parent=None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + self.Destroy() + + + def _prepare_resources(self, installer_path: str, disk: str) -> None: + + def prepare_script(self, installer_path: str, disk: str, constants: constants.Constants): + self.prepare_result = macos_installer_handler.InstallerCreation().generate_installer_creation_script(constants.payload_path, installer_path, disk) + + thread = threading.Thread(target=prepare_script, args=(self, installer_path, disk, self.constants)) + thread.start() + + while thread.is_alive(): + wx.Yield() + + return self.prepare_result + + + def _flash_installer(self, disk) -> bool: + utilities.disable_sleep_while_running() + logging.info("- Creating macOS installer") + + thread = threading.Thread(target=self._auto_package_handler) + thread.start() + + # print contents of installer.sh + with open(self.constants.installer_sh_path, "r") as f: + logging.info(f"- installer.sh contents:\n{f.read()}") + + args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path] + result = subprocess.run(args, capture_output=True, text=True) + output = result.stdout + error = result.stderr if result.stderr else "" + + if "Install media now available at" not in output: + logging.info("- Failed to create macOS installer") + popup = wx.MessageDialog(self, f"Failed to create macOS installer\n\nOutput: {output}\n\nError: {error}", "Error", wx.OK | wx.ICON_ERROR) + popup.ShowModal() + self.on_return_to_main_menu() + return False + + logging.info("- Successfully created macOS installer") + while thread.is_alive(): + # wait for download_thread to finish + # though highly unlikely this thread is still alive (flashing an Installer will take a while) + time.sleep(0.1) + logging.info("- Installing Root Patcher to drive") + self._install_installer_pkg(disk) + + utilities.enable_sleep_after_running() + return True + + + def _auto_package_handler(self): + """ + Function's main goal is to grab the correct AutoPkg-Assets.pkg and unzip it + Note the following: + - When running a release build, pull from Github's release page with the same versioning + - When running from source/unable to find on Github, use the nightly.link variant + - If nightly also fails, fall back to the manually uploaded variant + """ + link = self.constants.installer_pkg_url + if network_handler.NetworkUtilities(link).validate_link() is False: + logging.info("- Stock Install.pkg is missing on Github, falling back to Nightly") + link = self.constants.installer_pkg_url_nightly + + if link.endswith(".zip"): + path = self.constants.installer_pkg_zip_path + else: + path = self.constants.installer_pkg_path + + autopkg_download = network_handler.DownloadObject(link, path) + autopkg_download.download(spawn_thread=False) + + if autopkg_download.download_complete is False: + logging.warning("- Failed to download Install.pkg") + logging.warning(autopkg_download.error_msg) + return + + # Download thread will re-enable Idle Sleep after downloading + utilities.disable_sleep_while_running() + if not str(path).endswith(".zip"): + return + if Path(self.constants.installer_pkg_path).exists(): + subprocess.run(["rm", self.constants.installer_pkg_path]) + subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.installer_pkg_zip_path, self.constants.payload_path]) + + + def _install_installer_pkg(self, disk): + disk = disk + "s2" # ESP sits at 1, and we know macOS will have created the main partition at 2 + + if not Path(self.constants.installer_pkg_path).exists(): + return + + path = utilities.grab_mount_point_from_disk(disk) + if not Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists(): + return + + os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb")) + kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"]) + if int(kernel_version) < os_data.os_data.big_sur: + logging.info("- Installer unsupported, requires Big Sur or newer") + return + + subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) + subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"]) + + self._kdk_chainload(os_version["ProductBuildVersion"], os_version["ProductVersion"], Path(path + "/Library/Packages/")) + + + def _kdk_chainload(self, build: str, version: str, download_dir: str): + """ + Download the correct KDK to be chainloaded in the macOS installer + + Parameters + build (str): The build number of the macOS installer (e.g. 20A5343j) + version (str): The version of the macOS installer (e.g. 11.0.1) + """ + + kdk_dmg_path = Path(download_dir) / "KDK.dmg" + kdk_pkg_path = Path(download_dir) / "KDK.pkg" + + if kdk_dmg_path.exists(): + kdk_dmg_path.unlink() + if kdk_pkg_path.exists(): + kdk_pkg_path.unlink() + + logging.info("- Initiating KDK download") + logging.info(f" - Build: {build}") + logging.info(f" - Version: {version}") + logging.info(f" - Working Directory: {download_dir}") + + kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, build, version, ignore_installed=True) + if kdk_obj.success is False: + logging.info("- Failed to retrieve KDK") + logging.info(kdk_obj.error_msg) + return + + kdk_download_obj = kdk_obj.retrieve_download(override_path=kdk_dmg_path) + if kdk_download_obj is None: + logging.info("- Failed to retrieve KDK") + logging.info(kdk_obj.error_msg) + + # Check remaining disk space before downloading + space = utilities.get_free_space(download_dir) + if space < (kdk_obj.kdk_url_expected_size * 2): + logging.info("- Not enough disk space to download and install KDK") + logging.info(f"- Attempting to download locally first") + if space < kdk_obj.kdk_url_expected_size: + logging.info("- Not enough disk space to install KDK, skipping") + return + # Ideally we'd download the KDK onto the disk to display progress in the UI + # However we'll just download to our temp directory and move it to the target disk + kdk_dmg_path = self.constants.kdk_download_path + + kdk_download_obj.download(spawn_thread=False) + if kdk_download_obj.download_complete is False: + logging.info("- Failed to download KDK") + logging.info(kdk_download_obj.error_msg) + return + + if not kdk_dmg_path.exists(): + logging.info(f"- KDK missing: {kdk_dmg_path}") + return + + # Now that we have a KDK, extract it to get the pkg + with tempfile.TemporaryDirectory() as mount_point: + logging.info("- Mounting KDK") + result = subprocess.run(["hdiutil", "attach", kdk_dmg_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to mount KDK") + logging.info(result.stdout.decode("utf-8")) + return + + logging.info("- Copying KDK") + subprocess.run(["cp", "-r", f"{mount_point}/KernelDebugKit.pkg", kdk_pkg_path]) + + logging.info("- Unmounting KDK") + result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to unmount KDK") + logging.info(result.stdout.decode("utf-8")) + return + + logging.info("- Removing KDK Disk Image") + kdk_dmg_path.unlink() + + def _validate_installer_pkg(self, disk: str) -> bool: + verification_success = False + error_message = "" + def integrity_check(): + nonlocal error_message + path = utilities.grab_mount_point_from_disk(disk + "s2") + dmg_path = path + f"/{path.split('/')[2]}.app/Contents/SharedSupport/SharedSupport.dmg" + if not Path(dmg_path).exists(): + logging.error(f"Failed to find {dmg_path}") + error_message = f"Failed to find {dmg_path}" + return error_message + result = subprocess.run(["hdiutil", "verify", dmg_path],stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + if result.stdout: + logging.error(result.stdout.decode("utf-8")) + error_message = "STDOUT: " + result.stdout.decode("utf-8") + if result.stderr: + logging.error(result.stderr.decode("utf-8")) + error_message += "\n\nSTDERR: " + result.stderr.decode("utf-8") + + + thread = threading.Thread(target=integrity_check) + thread.start() + while thread.is_alive(): + wx.Yield() + + if verification_success: + return error_message + + logging.error(error_message) + return error_message + + + def on_return_to_main_menu(self, event: wx.Event = None): + if self.frame_modal: + self.frame_modal.Hide() + main_menu_frame = gui_main_menu.MainMenu( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + main_menu_frame.Show() + if self.frame_modal: + self.frame_modal.Destroy() + self.Destroy() \ No newline at end of file diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 3542f9d8e..8ea17889f 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -1,10 +1,10 @@ import wx from resources.wx_gui import ( gui_build, + gui_macos_installer_download, gui_sys_patch, gui_support, gui_help, - gui_macos_installer, ) from resources import constants @@ -94,7 +94,7 @@ class MainMenu(wx.Frame): def on_create_macos_installer(self, event: wx.Event = None): - gui_macos_installer.macOSInstallerFrame( + gui_macos_installer_download.macOSInstallerFrame( parent=self, title=self.title, global_constants=self.constants,