From d8a79cf67ea1d99178075b73615138f85804b3ae Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 24 May 2023 12:24:09 -0600 Subject: [PATCH 01/28] Reformat logging system --- Build-Binary.command | 84 ++++++------- resources/arguments.py | 9 +- resources/build/support.py | 8 +- resources/global_settings.py | 10 +- resources/install.py | 30 ++--- resources/kdk_handler.py | 116 +++++++++--------- resources/logging_handler.py | 74 ++++++++--- resources/macos_installer_handler.py | 6 +- resources/main.py | 7 +- resources/network_handler.py | 34 ++--- resources/reroute_payloads.py | 14 +-- resources/sys_patch/sys_patch_detect.py | 2 +- resources/sys_patch/sys_patch_generate.py | 6 +- resources/sys_patch/sys_patch_helpers.py | 22 ++-- resources/updates.py | 2 +- resources/utilities.py | 6 +- resources/validation.py | 8 +- resources/wx_gui/gui_about.py | 2 + resources/wx_gui/gui_build.py | 3 +- resources/wx_gui/gui_download.py | 3 + resources/wx_gui/gui_entry.py | 4 +- resources/wx_gui/gui_help.py | 3 +- resources/wx_gui/gui_install_oc.py | 9 +- .../wx_gui/gui_macos_installer_download.py | 2 +- resources/wx_gui/gui_macos_installer_flash.py | 51 ++++---- resources/wx_gui/gui_main_menu.py | 5 +- resources/wx_gui/gui_settings.py | 4 +- resources/wx_gui/gui_support.py | 2 +- resources/wx_gui/gui_sys_patch.py | 17 ++- resources/wx_gui/gui_update.py | 8 ++ 30 files changed, 311 insertions(+), 240 deletions(-) diff --git a/Build-Binary.command b/Build-Binary.command index 9251b27d2..e1c886f6b 100755 --- a/Build-Binary.command +++ b/Build-Binary.command @@ -30,7 +30,7 @@ class CreateBinary: def __init__(self): start = time.time() - print("- Starting build script") + print("Starting build script") self.args = self._parse_arguments() @@ -39,7 +39,7 @@ class CreateBinary: self._preflight_processes() self._build_binary() self._postflight_processes() - print(f"- Build script completed in {str(round(time.time() - start, 2))} seconds") + print(f"Build script completed in {str(round(time.time() - start, 2))} seconds") def _set_cwd(self): @@ -48,7 +48,7 @@ class CreateBinary: """ os.chdir(Path(__file__).resolve().parent) - print(f"- Current Working Directory: \n\t{os.getcwd()}") + print(f"Current Working Directory: \n\t{os.getcwd()}") def _parse_arguments(self): @@ -88,7 +88,7 @@ class CreateBinary: pyinstaller_path = f"{python_bin_dir}pyinstaller" if not Path(pyinstaller_path).exists(): - print(f" - pyinstaller not found:\n\t{pyinstaller_path}") + print(f"- pyinstaller not found:\n\t{pyinstaller_path}") raise Exception("pyinstaller not found") self.pyinstaller_path = pyinstaller_path @@ -99,7 +99,7 @@ class CreateBinary: Start preflight processes """ - print("- Starting preflight processes") + print("Starting preflight processes") self._setup_pathing() self._delete_extra_binaries() self._download_resources() @@ -111,7 +111,7 @@ class CreateBinary: Start postflight processes """ - print("- Starting postflight processes") + print("Starting postflight processes") self._patch_load_command() self._add_commit_data() self._post_flight_cleanup() @@ -124,19 +124,19 @@ class CreateBinary: """ if Path(f"./dist/OpenCore-Patcher.app").exists(): - print("- Found OpenCore-Patcher.app, removing...") + print("Found OpenCore-Patcher.app, removing...") rm_output = subprocess.run( ["rm", "-rf", "./dist/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if rm_output.returncode != 0: - print("- Remove failed") + print("Remove failed") print(rm_output.stderr.decode('utf-8')) raise Exception("Remove failed") self._embed_key() - print("- Building GUI binary...") + print("Building GUI binary...") build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"] build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -144,12 +144,12 @@ class CreateBinary: self._strip_key() if build_result.returncode != 0: - print("- Build failed") + print("Build failed") print(build_result.stderr.decode('utf-8')) raise Exception("Build failed") # Next embed support icns into ./Resources - print("- Embedding icns...") + print("Embedding icns...") for file in Path("payloads/Icon/AppIcons").glob("*.icns"): subprocess.run( ["cp", str(file), "./dist/OpenCore-Patcher.app/Contents/Resources/"], @@ -165,15 +165,15 @@ class CreateBinary: """ if not self.args.key: - print("- No developer key provided, skipping...") + print("No developer key provided, skipping...") return if not self.args.site: - print("- No site provided, skipping...") + print("No site provided, skipping...") return - print("- Embedding developer key...") + print("Embedding developer key...") if not Path("./resources/analytics_handler.py").exists(): - print("- analytics_handler.py not found") + print("analytics_handler.py not found") return lines = [] @@ -196,15 +196,15 @@ class CreateBinary: """ if not self.args.key: - print("- No developer key provided, skipping...") + print("No developer key provided, skipping...") return if not self.args.site: - print("- No site provided, skipping...") + print("No site provided, skipping...") return - print("- Stripping developer key...") + print("Stripping developer key...") if not Path("./resources/analytics_handler.py").exists(): - print("- analytics_handler.py not found") + print("analytics_handler.py not found") return lines = [] @@ -247,17 +247,17 @@ class CreateBinary: ] - print("- Deleting extra binaries...") + print("Deleting extra binaries...") for file in Path("payloads").glob(pattern="*"): if file.is_dir(): if file.name in whitelist_folders: continue - print(f" - Deleting {file.name}") + print(f"- Deleting {file.name}") subprocess.run(["rm", "-rf", file]) else: if file.name in whitelist_files: continue - print(f" - Deleting {file.name}") + print(f"- Deleting {file.name}") subprocess.run(["rm", "-f", file]) @@ -271,23 +271,23 @@ class CreateBinary: "Universal-Binaries.dmg" ] - print("- Downloading required resources...") + print("Downloading required resources...") for resource in required_resources: if Path(f"./{resource}").exists(): if self.args.reset_binaries: - print(f" - Removing old {resource}") + print(f"- Removing old {resource}") rm_output = subprocess.run( ["rm", "-rf", f"./{resource}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if rm_output.returncode != 0: - print("- Remove failed") + print("Remove failed") print(rm_output.stderr.decode('utf-8')) raise Exception("Remove failed") else: - print(f" - {resource} already exists, skipping download") + print(f"- {resource} already exists, skipping download") continue - print(f" - Downloading {resource}...") + print(f"- Downloading {resource}...") download_result = subprocess.run( [ @@ -298,11 +298,11 @@ class CreateBinary: ) if download_result.returncode != 0: - print(" - Download failed") + print("- Download failed") print(download_result.stderr.decode('utf-8')) raise Exception("Download failed") if not Path(f"./{resource}").exists(): - print(f" - {resource} not found") + print(f"- {resource} not found") raise Exception(f"{resource} not found") @@ -315,20 +315,20 @@ class CreateBinary: if Path("./payloads.dmg").exists(): if not self.args.reset_binaries: - print(" - payloads.dmg already exists, skipping creation") + print("- payloads.dmg already exists, skipping creation") return - print(" - Removing old payloads.dmg") + print("- Removing old payloads.dmg") rm_output = subprocess.run( ["rm", "-rf", "./payloads.dmg"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if rm_output.returncode != 0: - print("- Remove failed") + print("Remove failed") print(rm_output.stderr.decode('utf-8')) raise Exception("Remove failed") - print(" - Generating DMG...") + print("- Generating DMG...") dmg_output = subprocess.run([ 'hdiutil', 'create', './payloads.dmg', '-megabytes', '32000', # Overlays can only be as large as the disk image allows @@ -339,11 +339,11 @@ class CreateBinary: '-passphrase', 'password', '-encryption' ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if dmg_output.returncode != 0: - print(" - DMG generation failed") + print("- DMG generation failed") print(dmg_output.stderr.decode('utf-8')) raise Exception("DMG generation failed") - print(" - DMG generation complete") + print("- DMG generation complete") def _add_commit_data(self): @@ -352,7 +352,7 @@ class CreateBinary: """ if not self.args.branch and not self.args.commit and not self.args.commit_date: - print(" - No commit data provided, adding source info") + print("- No commit data provided, adding source info") branch = "Built from source" commit_url = "" commit_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) @@ -360,7 +360,7 @@ class CreateBinary: branch = self.args.branch commit_url = self.args.commit commit_date = self.args.commit_date - print(" - Adding commit data to Info.plist") + print("- Adding commit data to Info.plist") plist_path = Path("./dist/OpenCore-Patcher.app/Contents/Info.plist") plist = plistlib.load(Path(plist_path).open("rb")) plist["Github"] = { @@ -389,7 +389,7 @@ class CreateBinary: """ - print(" - Patching LC_VERSION_MIN_MACOSX") + print("- Patching LC_VERSION_MIN_MACOSX") path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher' find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D) replace = b'\x00\x0A\x0A\x00' # 10.10 (0xA0A) @@ -406,13 +406,13 @@ class CreateBinary: """ path = "./dist/OpenCore-Patcher" - print(f" - Removing {path}") + print(f"- Removing {path}") rm_output = subprocess.run( ["rm", "-rf", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if rm_output.returncode != 0: - print(f" - Remove failed: {path}") + print(f"- Remove failed: {path}") print(rm_output.stderr.decode('utf-8')) raise Exception(f"Remove failed: {path}") @@ -422,13 +422,13 @@ class CreateBinary: Validate generated binary """ - print(" - Validating binary") + print("- Validating binary") validate_output = subprocess.run( ["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if validate_output.returncode != 0: - print(" - Validation failed") + print("- Validation failed") print(validate_output.stderr.decode('utf-8')) raise Exception("Validation failed") diff --git a/resources/arguments.py b/resources/arguments.py index c9568283f..d96672564 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -59,9 +59,9 @@ class arguments: Start root volume patching """ - logging.info("- Set System Volume patching") + logging.info("Set System Volume patching") if "Library/InstallerSandboxes/" in str(self.constants.payload_path): - logging.info("- Running from Installer Sandbox") + logging.info("- Running from Installer Sandbox, blocking OS updaters") thread = threading.Thread(target=sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch) thread.start() while thread.is_alive(): @@ -75,7 +75,7 @@ class arguments: """ Start root volume unpatching """ - logging.info("- Set System Volume unpatching") + logging.info("Set System Volume unpatching") sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_unpatch() @@ -84,7 +84,7 @@ class arguments: Start root volume auto patching """ - logging.info("- Set Auto patching") + logging.info("Set Auto patching") sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch() @@ -92,6 +92,7 @@ class arguments: """ Start config building process """ + logging.info("Set OpenCore Build") if self.args.model: if self.args.model: diff --git a/resources/build/support.py b/resources/build/support.py index a79b14140..5f0bcf43c 100644 --- a/resources/build/support.py +++ b/resources/build/support.py @@ -136,7 +136,7 @@ class BuildSupport: for acpi in config_plist["ACPI"]["Add"]: if not Path(self.constants.opencore_release_folder / Path("EFI/OC/ACPI") / Path(acpi["Path"])).exists(): - logging.info(f" - Missing ACPI Table: {acpi['Path']}") + logging.info(f"- Missing ACPI Table: {acpi['Path']}") raise Exception(f"Missing ACPI Table: {acpi['Path']}") for kext in config_plist["Kernel"]["Add"]: @@ -155,19 +155,19 @@ class BuildSupport: for tool in config_plist["Misc"]["Tools"]: if not Path(self.constants.opencore_release_folder / Path("EFI/OC/Tools") / Path(tool["Path"])).exists(): - logging.info(f" - Missing tool: {tool['Path']}") + logging.info(f"- Missing tool: {tool['Path']}") raise Exception(f"Missing tool: {tool['Path']}") for driver in config_plist["UEFI"]["Drivers"]: if not Path(self.constants.opencore_release_folder / Path("EFI/OC/Drivers") / Path(driver["Path"])).exists(): - logging.info(f" - Missing driver: {driver['Path']}") + logging.info(f"- Missing driver: {driver['Path']}") raise Exception(f"Missing driver: {driver['Path']}") # Validating local files # Report if they have no associated config.plist entry (i.e. they're not being used) for tool_files in Path(self.constants.opencore_release_folder / Path("EFI/OC/Tools")).glob("*"): if tool_files.name not in [x["Path"] for x in config_plist["Misc"]["Tools"]]: - logging.info(f" - Missing tool from config: {tool_files.name}") + logging.info(f"- Missing tool from config: {tool_files.name}") raise Exception(f"Missing tool from config: {tool_files.name}") for driver_file in Path(self.constants.opencore_release_folder / Path("EFI/OC/Drivers")).glob("*"): diff --git a/resources/global_settings.py b/resources/global_settings.py index de8b3db80..0cb4645a1 100644 --- a/resources/global_settings.py +++ b/resources/global_settings.py @@ -48,7 +48,7 @@ class GlobalEnviromentSettings: try: plistlib.dump(plist, Path(self.global_settings_plist).open("wb")) except PermissionError: - logging.info("- Failed to write to global settings file") + logging.info("Failed to write to global settings file") def _generate_settings_file(self) -> None: @@ -57,7 +57,7 @@ class GlobalEnviromentSettings: try: plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb")) except PermissionError: - logging.info("- Permission error: Unable to write to global settings file") + logging.info("Permission error: Unable to write to global settings file") def _convert_defaults_to_global_settings(self) -> None: @@ -76,14 +76,14 @@ class GlobalEnviromentSettings: try: plistlib.dump(global_settings_plist, Path(self.global_settings_plist).open("wb")) except PermissionError: - logging.info("- Permission error: Unable to write to global settings file") + logging.info("Permission error: Unable to write to global settings file") return # delete defaults plist try: Path(defaults_path).unlink() except PermissionError: - logging.info("- Permission error: Unable to delete defaults plist") + logging.info("Permission error: Unable to delete defaults plist") def _fix_file_permission(self) -> None: @@ -100,6 +100,6 @@ class GlobalEnviromentSettings: # Set file permission to allow any user to write to log file result = subprocess.run(["chmod", "777", self.global_settings_plist], capture_output=True) if result.returncode != 0: - logging.warning("- Failed to fix settings file permissions:") + logging.warning("Failed to fix settings file permissions:") if result.stderr: logging.warning(result.stderr.decode("utf-8")) \ No newline at end of file diff --git a/resources/install.py b/resources/install.py index 1ff497de5..6fe7e11ef 100644 --- a/resources/install.py +++ b/resources/install.py @@ -88,13 +88,13 @@ class tui_disk_installation: def install_opencore(self, full_disk_identifier: str): # TODO: Apple Script fails in Yosemite(?) and older - logging.info(f"- Mounting partition: {full_disk_identifier}") + logging.info(f"Mounting partition: {full_disk_identifier}") if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status: try: applescript.AppleScript(f'''do shell script "diskutil mount {full_disk_identifier}" with prompt "OpenCore Legacy Patcher needs administrator privileges to mount this volume." with administrator privileges without altering line endings''').run() except applescript.ScriptError as e: if "User canceled" in str(e): - logging.info("- Mount cancelled by user") + logging.info("Mount cancelled by user") return logging.info(f"An error occurred: {e}") if utilities.check_boot_mode() == "safe_boot": @@ -104,7 +104,7 @@ class tui_disk_installation: else: result = subprocess.run(f"diskutil mount {full_disk_identifier}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode != 0: - logging.info("- Mount failed") + logging.info("Mount failed") logging.info(result.stderr.decode()) return @@ -124,18 +124,18 @@ class tui_disk_installation: return False if (mount_path / Path("EFI/OC")).exists(): - logging.info("- Removing preexisting EFI/OC folder") + logging.info("Removing preexisting EFI/OC folder") subprocess.run(["rm", "-rf", mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if (mount_path / Path("System")).exists(): - logging.info("- Removing preexisting System folder") + logging.info("Removing preexisting System folder") subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if (mount_path / Path("boot.efi")).exists(): - logging.info("- Removing preexisting boot.efi") + logging.info("Removing preexisting boot.efi") subprocess.run(["rm", mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - logging.info("- Copying OpenCore onto EFI partition") + logging.info("Copying OpenCore onto EFI partition") subprocess.run(["mkdir", "-p", mount_path / Path("EFI")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("System"), mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -144,7 +144,7 @@ class tui_disk_installation: subprocess.run(["cp", self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if self.constants.boot_efi is True: - logging.info("- Converting Bootstrap to BOOTx64.efi") + logging.info("Converting Bootstrap to BOOTx64.efi") if (mount_path / Path("EFI/BOOT")).exists(): subprocess.run(["rm", "-rf", mount_path / Path("EFI/BOOT")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) Path(mount_path / Path("EFI/BOOT")).mkdir() @@ -152,23 +152,23 @@ class tui_disk_installation: subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if self._determine_sd_card(sd_type) is True: - logging.info("- Adding SD Card icon") + logging.info("Adding SD Card icon") subprocess.run(["cp", self.constants.icon_path_sd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif ssd_type is True: - logging.info("- Adding SSD icon") + logging.info("Adding SSD icon") subprocess.run(["cp", self.constants.icon_path_ssd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif disk_type == "USB": - logging.info("- Adding External USB Drive icon") + logging.info("Adding External USB Drive icon") subprocess.run(["cp", self.constants.icon_path_external, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) else: - logging.info("- Adding Internal Drive icon") + logging.info("Adding Internal Drive icon") subprocess.run(["cp", self.constants.icon_path_internal, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - logging.info("- Cleaning install location") + logging.info("Cleaning install location") if not self.constants.recovery_status: - logging.info("- Unmounting EFI partition") + logging.info("Unmounting EFI partition") subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() - logging.info("- OpenCore transfer complete") + logging.info("OpenCore transfer complete") return True \ No newline at end of file diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index be91bb8ca..0c59bf13d 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -94,7 +94,7 @@ class KernelDebugKitObject: global KDK_ASSET_LIST - logging.info("- Pulling KDK list from KdkSupportPkg API") + logging.info("Pulling KDK list from KdkSupportPkg API") if KDK_ASSET_LIST: return KDK_ASSET_LIST @@ -107,11 +107,11 @@ class KernelDebugKitObject: timeout=5 ) except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError): - logging.info("- Could not contact KDK API") + logging.info("Could not contact KDK API") return None if results.status_code != 200: - logging.info("- Could not fetch KDK list") + logging.info("Could not fetch KDK list") return None KDK_ASSET_LIST = sorted(results.json(), key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reverse=True) @@ -138,12 +138,12 @@ class KernelDebugKitObject: if os_data.os_conversion.os_to_kernel(str(parsed_version.major)) < os_data.os_data.ventura: self.error_msg = "KDKs are not required for macOS Monterey or older" - logging.warning(f"- {self.error_msg}") + logging.warning(f"{self.error_msg}") return self.kdk_installed_path = self._local_kdk_installed() if self.kdk_installed_path: - logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") + logging.info(f"KDK already installed ({Path(self.kdk_installed_path).name}), skipping") self.kdk_already_installed = True self.success = True return @@ -151,29 +151,29 @@ class KernelDebugKitObject: remote_kdk_version = self._get_remote_kdks() if remote_kdk_version is None: - logging.warning("- Failed to fetch KDK list, falling back to local KDK matching") + logging.warning("Failed to fetch KDK list, falling back to local KDK matching") # First check if a KDK matching the current macOS version is installed # ex. 13.0.1 vs 13.0 loose_version = f"{parsed_version.major}.{parsed_version.minor}" - logging.info(f"- Checking for KDKs loosely matching {loose_version}") + logging.info(f"Checking for KDKs loosely matching {loose_version}") self.kdk_installed_path = self._local_kdk_installed(match=loose_version, check_version=True) if self.kdk_installed_path: - logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") + logging.info(f"Found matching KDK: {Path(self.kdk_installed_path).name}") self.kdk_already_installed = True self.success = True return older_version = f"{parsed_version.major}.{parsed_version.minor - 1 if parsed_version.minor > 0 else 0}" - logging.info(f"- Checking for KDKs matching {older_version}") + logging.info(f"Checking for KDKs matching {older_version}") self.kdk_installed_path = self._local_kdk_installed(match=older_version, check_version=True) if self.kdk_installed_path: - logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") + logging.info(f"Found matching KDK: {Path(self.kdk_installed_path).name}") self.kdk_already_installed = True self.success = True return - logging.warning(f"- Couldn't find KDK matching {host_version} or {older_version}, please install one manually") + logging.warning(f"Couldn't find KDK matching {host_version} or {older_version}, please install one manually") self.error_msg = f"Could not contact KdkSupportPkg API, and no KDK matching {host_version} ({host_build}) or {older_version} was installed.\nPlease ensure you have a network connection or manually install a KDK." @@ -211,32 +211,32 @@ class KernelDebugKitObject: if self.kdk_url == "": if self.kdk_closest_match_url == "": - logging.warning(f"- No KDKs found for {host_build} ({host_version})") + logging.warning(f"No KDKs found for {host_build} ({host_version})") self.error_msg = f"No KDKs found for {host_build} ({host_version})" return - logging.info(f"- No direct match found for {host_build}, falling back to closest match") - logging.info(f"- Closest Match: {self.kdk_closest_match_url_build} ({self.kdk_closest_match_url_version})") + logging.info(f"No direct match found for {host_build}, falling back to closest match") + logging.info(f"Closest Match: {self.kdk_closest_match_url_build} ({self.kdk_closest_match_url_version})") self.kdk_url = self.kdk_closest_match_url self.kdk_url_build = self.kdk_closest_match_url_build self.kdk_url_version = self.kdk_closest_match_url_version self.kdk_url_expected_size = self.kdk_closest_match_url_expected_size else: - logging.info(f"- Direct match found for {host_build} ({host_version})") + logging.info(f"Direct match found for {host_build} ({host_version})") # Check if this KDK is already installed self.kdk_installed_path = self._local_kdk_installed(match=self.kdk_url_build) if self.kdk_installed_path: - logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") + logging.info(f"KDK already installed ({Path(self.kdk_installed_path).name}), skipping") self.kdk_already_installed = True self.success = True return - logging.info("- Following KDK is recommended:") - logging.info(f"- KDK Build: {self.kdk_url_build}") - logging.info(f"- KDK Version: {self.kdk_url_version}") - logging.info(f"- KDK URL: {self.kdk_url}") + logging.info("Following KDK is recommended:") + logging.info(f"- KDK Build: {self.kdk_url_build}") + logging.info(f"- KDK Version: {self.kdk_url_version}") + logging.info(f"- KDK URL: {self.kdk_url}") self.success = True @@ -256,7 +256,7 @@ class KernelDebugKitObject: self.error_msg = "" if self.kdk_already_installed: - logging.info("- No download required, KDK already installed") + logging.info("No download required, KDK already installed") self.success = True return None @@ -265,7 +265,7 @@ class KernelDebugKitObject: logging.error(self.error_msg) return None - logging.info(f"- Returning DownloadObject for KDK: {Path(self.kdk_url).name}") + logging.info(f"Returning DownloadObject for KDK: {Path(self.kdk_url).name}") self.success = True kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path) @@ -294,7 +294,7 @@ class KernelDebugKitObject: plist_path.touch() plistlib.dump(kdk_dict, plist_path.open("wb"), sort_keys=False) except Exception as e: - logging.error(f"- Failed to generate KDK Info.plist: {e}") + logging.error(f"Failed to generate KDK Info.plist: {e}") def _local_kdk_valid(self, kdk_path: Path) -> bool: @@ -314,14 +314,14 @@ class KernelDebugKitObject: """ if not Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").exists(): - logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist") + logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist") self._remove_kdk(kdk_path) return False # Get build from KDK kdk_plist_data = plistlib.load(Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").open("rb")) if "ProductBuildVersion" not in kdk_plist_data: - logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion") + logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion") self._remove_kdk(kdk_path) return False @@ -331,7 +331,7 @@ class KernelDebugKitObject: result = subprocess.run(["pkgutil", "--files", f"com.apple.pkg.KDK.{kdk_build}"], capture_output=True) if result.returncode != 0: # If pkg receipt is missing, we'll fallback to legacy validation - logging.info(f"- pkg receipt missing for {kdk_path.name}, falling back to legacy validation") + logging.info(f"pkg receipt missing for {kdk_path.name}, falling back to legacy validation") return self._local_kdk_valid_legacy(kdk_path) # Go through each line of the pkg receipt and ensure it exists @@ -339,7 +339,7 @@ class KernelDebugKitObject: if not line.startswith("System/Library/Extensions"): continue if not Path(f"{kdk_path}/{line}").exists(): - logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}") + logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}") self._remove_kdk(kdk_path) return False @@ -368,7 +368,7 @@ class KernelDebugKitObject: for kext in KEXT_CATALOG: if not Path(f"{kdk_path}/System/Library/Extensions/{kext}").exists(): - logging.info(f"- Corrupted KDK found, removing due to missing: {kdk_path}/System/Library/Extensions/{kext}") + logging.info(f"Corrupted KDK found, removing due to missing: {kdk_path}/System/Library/Extensions/{kext}") self._remove_kdk(kdk_path) return False @@ -427,15 +427,15 @@ class KernelDebugKitObject: if not kdk_pkg.name.endswith(f"{match}.pkg"): continue - logging.info(f"- Found KDK backup: {kdk_pkg.name}") + logging.info(f"Found KDK backup: {kdk_pkg.name}") if self.passive is False: - logging.info("- Attempting KDK restoration") + logging.info("Attempting KDK restoration") if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg): - logging.info("- Successfully restored KDK") + logging.info("Successfully restored KDK") return self._local_kdk_installed(match=match, check_version=check_version) else: # When in passive mode, we're just checking if a KDK could be restored - logging.info("- KDK restoration skipped, running in passive mode") + logging.info("KDK restoration skipped, running in passive mode") return kdk_pkg return None @@ -453,22 +453,22 @@ class KernelDebugKitObject: return if os.getuid() != 0: - logging.warning("- Cannot remove KDK, not running as root") + logging.warning("Cannot remove KDK, not running as root") return if not Path(kdk_path).exists(): - logging.warning(f"- KDK does not exist: {kdk_path}") + logging.warning(f"KDK does not exist: {kdk_path}") return rm_args = ["rm", "-rf" if Path(kdk_path).is_dir() else "-f", kdk_path] result = utilities.elevated(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: - logging.warning(f"- Failed to remove KDK: {kdk_path}") - logging.warning(f"- {result.stdout.decode('utf-8')}") + logging.warning(f"Failed to remove KDK: {kdk_path}") + logging.warning(f"{result.stdout.decode('utf-8')}") return - logging.info(f"- Successfully removed KDK: {kdk_path}") + logging.info(f"Successfully removed KDK: {kdk_path}") def _remove_unused_kdks(self, exclude_builds: list = None) -> None: @@ -495,7 +495,7 @@ class KernelDebugKitObject: if not Path(KDK_INSTALL_PATH).exists(): return - logging.info("- Cleaning unused KDKs") + logging.info("Cleaning unused KDKs") for kdk_folder in Path(KDK_INSTALL_PATH).iterdir(): if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"): should_remove = True @@ -532,17 +532,17 @@ class KernelDebugKitObject: # TODO: should we use the checksum from the API? result = subprocess.run(["hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode != 0: - logging.info("- Error: Kernel Debug Kit checksum verification failed!") - logging.info(f"- Output: {result.stderr.decode('utf-8')}") + logging.info("Error: Kernel Debug Kit checksum verification failed!") + logging.info(f"Output: {result.stderr.decode('utf-8')}") msg = "Kernel Debug Kit checksum verification failed, please try again.\n\nIf this continues to fail, ensure you're downloading on a stable network connection (ie. Ethernet)" - logging.info(f"- {msg}") + logging.info(f"{msg}") self.error_msg = msg return False self._remove_unused_kdks() self.success = True - logging.info("- Kernel Debug Kit checksum verified") + logging.info("Kernel Debug Kit checksum verified") return True @@ -568,17 +568,17 @@ class KernelDebugKitUtilities: """ if os.getuid() != 0: - logging.warning("- Cannot install KDK, not running as root") + logging.warning("Cannot install KDK, not running as root") return False - logging.info(f"- Installing KDK package: {kdk_path.name}") - logging.info(f" - This may take a while...") + logging.info(f"Installing KDK package: {kdk_path.name}") + logging.info(f"- This may take a while...") # TODO: Check whether enough disk space is available result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: - logging.info("- Failed to install KDK:") + logging.info("Failed to install KDK:") logging.info(result.stdout.decode('utf-8')) if result.stderr: logging.info(result.stderr.decode('utf-8')) @@ -599,21 +599,21 @@ class KernelDebugKitUtilities: """ if os.getuid() != 0: - logging.warning("- Cannot install KDK, not running as root") + logging.warning("Cannot install KDK, not running as root") return False - logging.info(f"- Extracting downloaded KDK disk image") + logging.info(f"Extracting downloaded KDK disk image") with tempfile.TemporaryDirectory() as mount_point: result = subprocess.run(["hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: - logging.info("- Failed to mount KDK:") + logging.info("Failed to mount KDK:") logging.info(result.stdout.decode('utf-8')) return False kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg") if not kdk_pkg_path.exists(): - logging.warning("- Failed to find KDK package in DMG, likely corrupted!!!") + logging.warning("Failed to find KDK package in DMG, likely corrupted!!!") self._unmount_disk_image(mount_point) return False @@ -624,7 +624,7 @@ class KernelDebugKitUtilities: self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}")) self._unmount_disk_image(mount_point) - logging.info("- Successfully installed KDK") + logging.info("Successfully installed KDK") return True def _unmount_disk_image(self, mount_point) -> None: @@ -647,31 +647,31 @@ class KernelDebugKitUtilities: """ if not kdk_path.exists(): - logging.warning("- KDK does not exist, cannot create backup") + logging.warning("KDK does not exist, cannot create backup") return if not kdk_info_plist.exists(): - logging.warning("- KDK Info.plist does not exist, cannot create backup") + logging.warning("KDK Info.plist does not exist, cannot create backup") return kdk_info_dict = plistlib.load(kdk_info_plist.open("rb")) if 'version' not in kdk_info_dict or 'build' not in kdk_info_dict: - logging.warning("- Malformed KDK Info.plist provided, cannot create backup") + logging.warning("Malformed KDK Info.plist provided, cannot create backup") return if os.getuid() != 0: - logging.warning("- Cannot create KDK backup, not running as root") + logging.warning("Cannot create KDK backup, not running as root") return kdk_dst_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg" kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}") - logging.info(f"- Creating backup: {kdk_dst_name}") + logging.info(f"Creating backup: {kdk_dst_name}") if kdk_dst_path.exists(): - logging.info("- Backup already exists, skipping") + logging.info("Backup already exists, skipping") return result = utilities.elevated(["cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: - logging.info("- Failed to create KDK backup:") + logging.info("Failed to create KDK backup:") logging.info(result.stdout.decode('utf-8')) \ No newline at end of file diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 88cdc2022..6590509b4 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -1,5 +1,7 @@ +import wx import os import sys +import time import logging import threading import traceback @@ -32,22 +34,24 @@ class InitializeLoggingSupport: """ def __init__(self, global_constants: constants.Constants) -> None: - self.log_filename: str = "OpenCore-Patcher.log" - self.log_filepath: Path = None - self.constants: constants.Constants = global_constants + self.log_filename: str = f"OpenCore-Patcher-{self.constants.patcher_version}.log" + self.log_filepath: Path = None + self.original_excepthook: sys = sys.excepthook self.original_thread_excepthook: threading = threading.excepthook - self.max_file_size: int = 1024 * 1024 * 10 # 10 MB - self.file_size_redline: int = 1024 * 1024 * 9 # 9 MB, when to start cleaning log file + self.max_file_size: int = 1024 * 1024 # 1 MB + self.file_size_redline: int = 1024 * 1024 - 1024 * 100 # 900 KB, when to start cleaning log file self._initialize_logging_path() self._clean_log_file() self._attempt_initialize_logging_configuration() + self._start_logging() self._implement_custom_traceback_handler() self._fix_file_permission() + self._clean_prior_version_logs() def _initialize_logging_path(self) -> None: @@ -61,9 +65,6 @@ class InitializeLoggingSupport: # Likely in an installer environment, store in /Users/Shared self.log_filepath = Path("/Users/Shared") / self.log_filename - print("- Initializing logging framework...") - print(f" - Log file: {self.log_filepath}") - def _clean_log_file(self) -> None: """ @@ -87,7 +88,33 @@ class InitializeLoggingSupport: # Rename current log file to backup log file self.log_filepath.rename(backup_log_filepath) except Exception as e: - print(f"- Failed to clean log file: {e}") + print(f"Failed to clean log file: {e}") + + + def _clean_prior_version_logs(self) -> None: + """ + Clean logs from old Patcher versions + + If file is more than a week old, assume it's unused and delete it + """ + + time_threshold = time.time() - 60 * 60 * 24 * 7 + + for file in self.log_filepath.parent.glob("OpenCore-Patcher*"): + if not file.is_file(): + continue + + if not file.name.endswith(".log"): + continue + + if file.name == self.log_filename: + continue + + if file.stat().st_mtime < time_threshold: + try: + file.unlink() + except Exception as e: + print(f"Failed to clean prior version log file: {e}") def _fix_file_permission(self) -> None: @@ -103,9 +130,13 @@ class InitializeLoggingSupport: result = subprocess.run(["chmod", "777", self.log_filepath], capture_output=True) if result.returncode != 0: - print(f"- Failed to fix log file permissions") + logging.error(f"Failed to fix log file permissions") + if result.stdout: + logging.error("STDOUT:") + logging.error(result.stdout.decode("utf-8")) if result.stderr: - print(result.stderr.decode("utf-8")) + logging.error("STDERR:") + logging.error(result.stderr.decode("utf-8")) def _initialize_logging_configuration(self, log_to_file: bool = True) -> None: @@ -122,7 +153,7 @@ class InitializeLoggingSupport: logging.basicConfig( level=logging.NOTSET, - format="%(asctime)s - %(filename)s (%(lineno)d): %(message)s", + format="[%(asctime)s] [%(filename)-32s] [%(lineno)-4d]: %(message)s", handlers=[ logging.StreamHandler(stream = sys.stdout), logging.FileHandler(self.log_filepath) if log_to_file is True else logging.NullHandler() @@ -143,11 +174,26 @@ class InitializeLoggingSupport: try: self._initialize_logging_configuration() except Exception as e: - print(f"- Failed to initialize logging framework: {e}") - print("- Retrying without logging to file...") + print(f"Failed to initialize logging framework: {e}") + print("Retrying without logging to file...") self._initialize_logging_configuration(log_to_file=False) + def _start_logging(self): + """ + Start logging, used as easily identifiable start point in logs + """ + + str_msg = f"# OpenCore Legacy Patcher ({self.constants.patcher_version}) #" + str_len = len(str_msg) + + logging.info('#' * str_len) + logging.info(str_msg) + logging.info('#' * str_len) + + logging.info(f"Log file set to: {self.log_filepath}") + + def _implement_custom_traceback_handler(self) -> None: """ Reroute traceback to logging module diff --git a/resources/macos_installer_handler.py b/resources/macos_installer_handler.py index 23b70f622..a7e08d975 100644 --- a/resources/macos_installer_handler.py +++ b/resources/macos_installer_handler.py @@ -53,7 +53,7 @@ class InstallerCreation(): bool: True if successful, False otherwise """ - logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time") + logging.info("Extracting macOS installer from InstallAssistant.pkg\n This may take some time") try: applescript.AppleScript( f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' @@ -62,11 +62,11 @@ class InstallerCreation(): " without altering line endings", ).run() except Exception as e: - logging.info("- Failed to install InstallAssistant") + logging.info("Failed to install InstallAssistant") logging.info(f" Error Code: {e}") return False - logging.info("- InstallAssistant installed") + logging.info("InstallAssistant installed") return True diff --git a/resources/main.py b/resources/main.py index f231dac1f..7b3f8d450 100644 --- a/resources/main.py +++ b/resources/main.py @@ -32,8 +32,6 @@ class OpenCoreLegacyPatcher: logging_handler.InitializeLoggingSupport(self.constants) - logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...") - self._generate_base_data() if utilities.check_cli_args() is None: @@ -98,18 +96,17 @@ class OpenCoreLegacyPatcher: if utilities.check_cli_args() is None: self.constants.cli_mode = False - logging.info(f"- No arguments present, loading {'GUI' if self.constants.wxpython_variant is True else 'TUI'} mode") return - logging.info("- Detected arguments, switching to CLI mode") + logging.info("Detected arguments, switching to CLI mode") self.constants.gui_mode = True # Assumes no user interaction is required ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch", "--update_installed"] if not any(x in sys.argv for x in ignore_args): self.constants.current_path = Path.cwd() if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): - logging.info("- Rerouting payloads location") self.constants.payload_path = sys._MEIPASS / Path("payloads") + logging.info("Rerouting payloads location") ignore_args = ignore_args.pop(0) if not any(x in sys.argv for x in ignore_args): diff --git a/resources/network_handler.py b/resources/network_handler.py index 5efd0be14..3a8387c37 100644 --- a/resources/network_handler.py +++ b/resources/network_handler.py @@ -204,10 +204,10 @@ class DownloadObject: """ self.status = DownloadStatus.DOWNLOADING - logging.info(f"- Starting download: {self.filename}") + logging.info(f"Starting download: {self.filename}") if spawn_thread: if self.active_thread: - logging.error("- Download already in progress") + logging.error("Download already in progress") return self.should_checksum = verify_checksum self.active_thread = threading.Thread(target=self._download, args=(display_progress,)) @@ -267,8 +267,8 @@ class DownloadObject: else: raise Exception("Content-Length missing from headers") except Exception as e: - logging.error(f"- Error determining file size {self.url}: {str(e)}") - logging.error("- Assuming file size is 0") + logging.error(f"Error determining file size {self.url}: {str(e)}") + logging.error("Assuming file size is 0") self.total_file_size = 0.0 @@ -295,17 +295,17 @@ class DownloadObject: try: if Path(path).exists(): - logging.info(f"- Deleting existing file: {path}") + logging.info(f"Deleting existing file: {path}") Path(path).unlink() return True if not Path(path).parent.exists(): - logging.info(f"- Creating directory: {Path(path).parent}") + logging.info(f"Creating directory: {Path(path).parent}") Path(path).parent.mkdir(parents=True, exist_ok=True) available_space = utilities.get_free_space(Path(path).parent) if self.total_file_size > available_space: - msg = f"- Not enough free space to download {self.filename}, need {utilities.human_fmt(self.total_file_size)}, have {utilities.human_fmt(available_space)}" + msg = f"Not enough free space to download {self.filename}, need {utilities.human_fmt(self.total_file_size)}, have {utilities.human_fmt(available_space)}" logging.error(msg) raise Exception(msg) @@ -313,7 +313,7 @@ class DownloadObject: self.error = True self.error_msg = str(e) self.status = DownloadStatus.ERROR - logging.error(f"- Error preparing working directory {path}: {self.error_msg}") + logging.error(f"Error preparing working directory {path}: {self.error_msg}") return False return True @@ -353,21 +353,21 @@ class DownloadObject: if display_progress and i % 100: # Don't use logging here, as we'll be spamming the log file if self.total_file_size == 0.0: - print(f"- Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}") + print(f"Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}") else: - print(f"- Downloaded {self.get_percent():.2f}% of {self.filename} ({utilities.human_fmt(self.get_speed())}/s) ({self.get_time_remaining():.2f} seconds remaining)") + print(f"Downloaded {self.get_percent():.2f}% of {self.filename} ({utilities.human_fmt(self.get_speed())}/s) ({self.get_time_remaining():.2f} seconds remaining)") self.download_complete = True - logging.info(f"- Download complete: {self.filename}") - logging.info("- Stats:") - logging.info(f"- Downloaded size: {utilities.human_fmt(self.downloaded_file_size)}") - logging.info(f"- Time elapsed: {(time.time() - self.start_time):.2f} seconds") - logging.info(f"- Speed: {utilities.human_fmt(self.downloaded_file_size / (time.time() - self.start_time))}/s") - logging.info(f"- Location: {self.filepath}") + logging.info(f"Download complete: {self.filename}") + logging.info("Stats:") + logging.info(f"- Downloaded size: {utilities.human_fmt(self.downloaded_file_size)}") + logging.info(f"- Time elapsed: {(time.time() - self.start_time):.2f} seconds") + logging.info(f"- Speed: {utilities.human_fmt(self.downloaded_file_size / (time.time() - self.start_time))}/s") + logging.info(f"- Location: {self.filepath}") except Exception as e: self.error = True self.error_msg = str(e) self.status = DownloadStatus.ERROR - logging.error(f"- Error downloading {self.url}: {self.error_msg}") + logging.error(f"Error downloading {self.url}: {self.error_msg}") self.status = DownloadStatus.COMPLETE utilities.enable_sleep_after_running() diff --git a/resources/reroute_payloads.py b/resources/reroute_payloads.py index 760e359fb..2272b5fd7 100644 --- a/resources/reroute_payloads.py +++ b/resources/reroute_payloads.py @@ -28,10 +28,10 @@ class RoutePayloadDiskImage: """ if self.constants.wxpython_variant is True and not self.constants.launcher_script: - logging.info("- Running in Binary GUI mode, switching to tmp directory") + logging.info("Running in Binary GUI mode, switching to tmp directory") self.temp_dir = tempfile.TemporaryDirectory() - logging.info(f"- New payloads location: {self.temp_dir.name}") - logging.info("- Creating payloads directory") + logging.info(f"New payloads location: {self.temp_dir.name}") + logging.info("Creating payloads directory") Path(self.temp_dir.name / Path("payloads")).mkdir(parents=True, exist_ok=True) self._unmount_active_dmgs(unmount_all_active=False) output = subprocess.run( @@ -45,12 +45,12 @@ class RoutePayloadDiskImage: stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) if output.returncode == 0: - logging.info("- Mounted payloads.dmg") + logging.info("Mounted payloads.dmg") self.constants.current_path = Path(self.temp_dir.name) self.constants.payload_path = Path(self.temp_dir.name) / Path("payloads") atexit.register(self._unmount_active_dmgs, unmount_all_active=False) else: - logging.info("- Failed to mount payloads.dmg") + logging.info("Failed to mount payloads.dmg") logging.info(f"Output: {output.stdout.decode()}") logging.info(f"Return Code: {output.returncode}") @@ -78,13 +78,13 @@ class RoutePayloadDiskImage: # Check that only our personal payloads.dmg is unmounted if "shadow-path" in image: if self.temp_dir.name in image["shadow-path"]: - logging.info(f"- Unmounting personal {variant}") + logging.info(f"Unmounting personal {variant}") subprocess.run( ["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) else: - logging.info(f"- Unmounting {variant} at: {image['system-entities'][0]['dev-entry']}") + logging.info(f"Unmounting {variant} at: {image['system-entities'][0]['dev-entry']}") subprocess.run( ["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index 9a2752efe..2766c0cd5 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -92,7 +92,7 @@ class DetectRootPatch: non_metal_os = os_data.os_data.catalina for i, gpu in enumerate(gpus): if gpu.class_code and gpu.class_code != 0xFFFFFFFF: - logging.info(f"- Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}") + logging.info(f"Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}") if gpu.arch in [device_probe.NVIDIA.Archs.Tesla] and self.constants.force_nv_web is False: if self.constants.detected_os > non_metal_os: self.nvidia_tesla = True diff --git a/resources/sys_patch/sys_patch_generate.py b/resources/sys_patch/sys_patch_generate.py index 4443e45fa..ebb0f5c89 100644 --- a/resources/sys_patch/sys_patch_generate.py +++ b/resources/sys_patch/sys_patch_generate.py @@ -40,7 +40,7 @@ class GenerateRootPatchSets: utilities.cls() - logging.info("- The following patches will be applied:") + logging.info("The following patches will be applied:") if self.hardware_details["Graphics: Intel Ironlake"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) @@ -184,8 +184,8 @@ class GenerateRootPatchSets: del(required_patches[patch_name]) else: if required_patches[patch_name]["Display Name"]: - logging.info(f" - {required_patches[patch_name]['Display Name']}") + logging.info(f"- {required_patches[patch_name]['Display Name']}") else: - logging.info(" - No patch sets found for booted model") + logging.info("- No patch sets found for booted model") return required_patches \ No newline at end of file diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index ec8504b6c..816dfb855 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -42,10 +42,10 @@ class SysPatchHelpers: if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock: return - logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") + logging.info(f"Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus) - logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}") + logging.info(f"Replacing {board_to_patch} with {self.constants.computer.reported_board_id}") board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex()) reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex()) @@ -54,12 +54,12 @@ class SysPatchHelpers: # Pad the reported Board ID with zeros to match the length of the board to patch reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex)) elif len(board_to_patch_hex) < len(reported_board_hex): - logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}") + logging.info(f"Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}") raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!") path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB" if not Path(path).exists(): - logging.info(f"- Error: Could not find {path}") + logging.info(f"Error: Could not find {path}") raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") with open(path, 'rb') as f: @@ -128,7 +128,7 @@ class SysPatchHelpers: if self.constants.detected_os < os_data.os_data.ventura: return - logging.info("- Disabling WindowServer Caching") + logging.info("Disabling WindowServer Caching") # Invoke via 'bash -c' to resolve pathing utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) # Disable writing to WindowServer folder @@ -150,12 +150,12 @@ class SysPatchHelpers: if self.constants.detected_os < os_data.os_data.ventura: return - logging.info("- Parsing Notification Centre Widgets") + logging.info("Parsing Notification Centre Widgets") file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist" file_path = Path(file_path).expanduser() if not file_path.exists(): - logging.info(" - Defaults file not found, skipping") + logging.info("- Defaults file not found, skipping") return did_find = False @@ -178,7 +178,7 @@ class SysPatchHelpers: continue if not b'com.apple.news' in sub_data[sub_entry][2]: continue - logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") + logging.info(f"- Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") data["widgets"]["instances"].remove(widget) did_find = True @@ -210,10 +210,10 @@ class SysPatchHelpers: if self.constants.detected_os < os_data.os_data.big_sur: return - logging.info("- Installing Kernel Collection syncing utility") + logging.info("Installing Kernel Collection syncing utility") result = utilities.elevated([self.constants.rsrrepair_userspace_path, "--install"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: - logging.info(f" - Failed to install RSRRepair: {result.stdout.decode()}") + logging.info(f"- Failed to install RSRRepair: {result.stdout.decode()}") def patch_gpu_compiler_libraries(self, mount_point: Union[str, Path]): @@ -262,7 +262,7 @@ class SysPatchHelpers: if not file.name.startswith("31001."): continue - logging.info(f"- Merging GPUCompiler.framework libraries to match binary") + logging.info(f"Merging GPUCompiler.framework libraries to match binary") src_dir = f"{LIBRARY_DIR}/{file.name}" if not Path(f"{DEST_DIR}/lib").exists(): diff --git a/resources/updates.py b/resources/updates.py index 850a1b0e6..8b0df26c8 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -116,7 +116,7 @@ class CheckBinaryUpdates: return None for asset in data_set["assets"]: - logging.info(f"- Found asset: {asset['name']}") + logging.info(f"Found asset: {asset['name']}") if self._determine_remote_type(asset["name"]) == self._determine_local_build_type(): available_binaries.update({ asset['name']: { diff --git a/resources/utilities.py b/resources/utilities.py index 63d3fad97..3165aab92 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -158,7 +158,7 @@ sleep_process = None def disable_sleep_while_running(): global sleep_process - logging.info("- Disabling Idle Sleep") + logging.info("Disabling Idle Sleep") if sleep_process is None: # If sleep_process is active, we'll just keep it running sleep_process = subprocess.Popen(["caffeinate", "-d", "-i", "-s"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -168,7 +168,7 @@ def disable_sleep_while_running(): def enable_sleep_after_running(): global sleep_process if sleep_process: - logging.info("- Re-enabling Idle Sleep") + logging.info("Re-enabling Idle Sleep") sleep_process.kill() sleep_process = None @@ -516,7 +516,7 @@ def block_os_updaters(): for bad_process in bad_processes: if bad_process in current_process: if pid != "": - logging.info(f"- Killing Process: {pid} - {current_process.split('/')[-1]}") + logging.info(f"Killing Process: {pid} - {current_process.split('/')[-1]}") subprocess.run(["kill", "-9", pid]) break diff --git a/resources/validation.py b/resources/validation.py index b0b8fee7c..4d9ff59de 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -120,7 +120,7 @@ class PatcherValidation: logging.info(f"File not found: {source_file}") raise Exception(f"Failed to find {source_file}") - logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}") + logging.info(f"Validating against Darwin {major_kernel}.{minor_kernel}") if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None): raise Exception("Failed to generate patchset plist") @@ -153,13 +153,13 @@ class PatcherValidation: ) if output.returncode != 0: - logging.info("- Failed to mount Universal-Binaries.dmg") + logging.info("Failed to mount Universal-Binaries.dmg") logging.info(f"Output: {output.stdout.decode()}") logging.info(f"Return Code: {output.returncode}") raise Exception("Failed to mount Universal-Binaries.dmg") - logging.info("- Mounted Universal-Binaries.dmg") + logging.info("Mounted Universal-Binaries.dmg") for supported_os in [os_data.os_data.big_sur, os_data.os_data.monterey, os_data.os_data.ventura]: @@ -179,7 +179,7 @@ class PatcherValidation: ) if output.returncode != 0: - logging.info("- Failed to unmount Universal-Binaries.dmg") + logging.info("Failed to unmount Universal-Binaries.dmg") logging.info(f"Output: {output.stdout.decode()}") logging.info(f"Return Code: {output.returncode}") diff --git a/resources/wx_gui/gui_about.py b/resources/wx_gui/gui_about.py index f73cd29cf..1bb18be05 100644 --- a/resources/wx_gui/gui_about.py +++ b/resources/wx_gui/gui_about.py @@ -2,6 +2,7 @@ import wx import wx.adv +import logging from resources import constants @@ -12,6 +13,7 @@ class AboutFrame(wx.Frame): if wx.FindWindowByName("About"): return + logging.info("Generating About frame") super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) self.constants: constants.Constants = global_constants self.Centre() diff --git a/resources/wx_gui/gui_build.py b/resources/wx_gui/gui_build.py index a9a6487b0..5dafddf26 100644 --- a/resources/wx_gui/gui_build.py +++ b/resources/wx_gui/gui_build.py @@ -19,6 +19,7 @@ class BuildFrame(wx.Frame): Uses a Modal Dialog for smoother transition from other frames """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None: + logging.info("Initializing Build Frame") super(BuildFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, global_constants).generate() @@ -121,7 +122,7 @@ class BuildFrame(wx.Frame): try: build.BuildOpenCore(self.constants.custom_model or self.constants.computer.real_model, self.constants) except: - logging.error("- An internal error occurred while building:\n") + logging.error("An internal error occurred while building:\n") logging.error(traceback.format_exc()) logger.removeHandler(logger.handlers[2]) diff --git a/resources/wx_gui/gui_download.py b/resources/wx_gui/gui_download.py index 1f15cecec..a926f8570 100644 --- a/resources/wx_gui/gui_download.py +++ b/resources/wx_gui/gui_download.py @@ -1,5 +1,6 @@ # Generate UI for downloading files import wx +import logging from resources import ( constants, @@ -13,6 +14,7 @@ class DownloadFrame(wx.Frame): Update provided frame with download stats """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, download_obj: network_handler.DownloadObject, item_name: str) -> None: + logging.info("Initializing Download Frame") self.constants: constants.Constants = global_constants self.title: str = title self.parent: wx.Frame = parent @@ -91,6 +93,7 @@ class DownloadFrame(wx.Frame): Terminate download """ if wx.MessageBox("Are you sure you want to cancel the download?", "Cancel Download", wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT) == wx.YES: + logging.info("User cancelled download") self.user_cancelled = True self.download_obj.stop() diff --git a/resources/wx_gui/gui_entry.py b/resources/wx_gui/gui_entry.py index 49a92605e..cc8a167e9 100644 --- a/resources/wx_gui/gui_entry.py +++ b/resources/wx_gui/gui_entry.py @@ -52,7 +52,7 @@ class EntryPoint: entry = gui_sys_patch.SysPatchFrame patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() - logging.info(f"- Loading wxPython GUI: {entry.__name__}") + logging.info(f"Entry point set: {entry.__name__}") self.frame: wx.Frame = entry( None, title=f"{self.constants.patcher_name} ({self.constants.patcher_version})", @@ -79,7 +79,7 @@ class EntryPoint: if not self.frame: return - logging.info("- Cleaning up wxPython GUI") + logging.info("Cleaning up wxPython GUI") self.frame.SetTransparent(0) wx.Yield() diff --git a/resources/wx_gui/gui_help.py b/resources/wx_gui/gui_help.py index b5c880ed0..4f0871a7f 100644 --- a/resources/wx_gui/gui_help.py +++ b/resources/wx_gui/gui_help.py @@ -1,5 +1,6 @@ # Generate UI for help menu import wx +import logging import webbrowser from resources import constants @@ -10,7 +11,7 @@ class HelpFrame(wx.Frame): Append to main menu through a modal dialog """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None: - + logging.info("Initializing Help Frame") self.dialog = wx.Dialog(parent, title=title, size=(300, 200)) self.constants: constants.Constants = global_constants diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py index b6371eefd..f4ddd9e55 100644 --- a/resources/wx_gui/gui_install_oc.py +++ b/resources/wx_gui/gui_install_oc.py @@ -13,6 +13,7 @@ class InstallOCFrame(wx.Frame): Create a frame for installing OpenCore to disk """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + logging.info("Initializing Install OpenCore Frame") super(InstallOCFrame, self).__init__(parent, title=title, size=(300, 120), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, global_constants).generate() @@ -124,13 +125,14 @@ class InstallOCFrame(wx.Frame): # disk6s1 -> disk6 disk_root = self.constants.booted_oc_disk.strip("disk") disk_root = "disk" + disk_root.split("s")[0] - logging.info(f"- Checking if booted disk is present: {disk_root}") + logging.info(f"Checking if booted disk is present: {disk_root}") # Add buttons for each disk items = len(self.available_disks) longest_label = max((len(self.available_disks[disk]['disk']) + len(self.available_disks[disk]['name']) + len(str(self.available_disks[disk]['size']))) for disk in self.available_disks) longest_label = longest_label * 9 spacer = 0 + logging.info("Available disks:") for disk in self.available_disks: # Create a button for each disk logging.info(f"- {self.available_disks[disk]['disk']} - {self.available_disks[disk]['name']} - {self.available_disks[disk]['size']}") @@ -195,6 +197,7 @@ class InstallOCFrame(wx.Frame): items = len(partitions) longest_label = max((len(partitions[partition]['partition']) + len(partitions[partition]['name']) + len(str(partitions[partition]['size']))) for partition in partitions) longest_label = longest_label * 10 + logging.info(f"Available partitions for {disk}:") for partition in partitions: logging.info(f"- {partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}") disk_button = wx.Button(dialog, label=f"{partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}", size=(longest_label,30), pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 5)) @@ -306,14 +309,14 @@ class InstallOCFrame(wx.Frame): """ Install OpenCore to disk """ - logging.info(f"- Installing OpenCore to {partition}") + logging.info(f"Installing OpenCore to {partition}") logger = logging.getLogger() logger.addHandler(gui_support.ThreadHandler(self.text_box)) try: self.result = install.tui_disk_installation(self.constants).install_opencore(partition) except: - logging.error("- An internal error occurred while installing:\n") + logging.error("An internal error occurred while installing:\n") logging.error(traceback.format_exc()) logger.removeHandler(logger.handlers[2]) diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py index 1dc261176..c80b4949b 100644 --- a/resources/wx_gui/gui_macos_installer_download.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -28,7 +28,7 @@ class macOSInstallerDownloadFrame(wx.Frame): 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): - + logging.info("Initializing macOS Installer Download Frame") self.constants: constants.Constants = global_constants self.title: str = title self.parent: wx.Frame = parent diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index adfce3df3..4295a88bf 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -22,6 +22,7 @@ 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): + logging.info("Initializing macOS Installer Flash Frame") super(macOSInstallerFlashFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, global_constants).generate() @@ -354,14 +355,14 @@ class macOSInstallerFlashFrame(wx.Frame): def _flash_installer(self, disk) -> bool: utilities.disable_sleep_while_running() - logging.info("- Creating macOS installer") + 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()}") + 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) @@ -369,17 +370,17 @@ class macOSInstallerFlashFrame(wx.Frame): error = result.stderr if result.stderr else "" if "Install media now available at" not in output: - logging.info("- Failed to create macOS installer") + 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() return False - logging.info("- Successfully created macOS installer") + 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") + logging.info("Installing Root Patcher to drive") self._install_installer_pkg(disk) utilities.enable_sleep_after_running() @@ -396,7 +397,7 @@ class macOSInstallerFlashFrame(wx.Frame): """ 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") + logging.info("Stock Install.pkg is missing on Github, falling back to Nightly") link = self.constants.installer_pkg_url_nightly if link.endswith(".zip"): @@ -408,7 +409,7 @@ class macOSInstallerFlashFrame(wx.Frame): autopkg_download.download(spawn_thread=False) if autopkg_download.download_complete is False: - logging.warning("- Failed to download Install.pkg") + logging.warning("Failed to download Install.pkg") logging.warning(autopkg_download.error_msg) return @@ -434,7 +435,7 @@ class macOSInstallerFlashFrame(wx.Frame): 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") + logging.info("Installer unsupported, requires Big Sur or newer") return subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) @@ -460,29 +461,29 @@ class macOSInstallerFlashFrame(wx.Frame): 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}") + 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("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("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") + 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") + 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 @@ -490,34 +491,34 @@ class macOSInstallerFlashFrame(wx.Frame): kdk_download_obj.download(spawn_thread=False) if kdk_download_obj.download_complete is False: - logging.info("- Failed to download KDK") + 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}") + 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") + 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("Failed to mount KDK") logging.info(result.stdout.decode("utf-8")) return - logging.info("- Copying KDK") + logging.info("Copying KDK") subprocess.run(["cp", "-r", f"{mount_point}/KernelDebugKit.pkg", kdk_pkg_path]) - logging.info("- Unmounting KDK") + 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("Failed to unmount KDK") logging.info(result.stdout.decode("utf-8")) return - logging.info("- Removing KDK Disk Image") + logging.info("Removing KDK Disk Image") kdk_dmg_path.unlink() def _validate_installer_pkg(self, disk: str) -> bool: diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 5bd63500d..05d4dc8c1 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -24,6 +24,7 @@ from data import os_data class MainFrame(wx.Frame): def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + logging.info("Initializing Main Menu Frame") super(MainFrame, self).__init__(parent, title=title, size=(600, 400), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, global_constants).generate() @@ -213,11 +214,11 @@ class MainFrame(wx.Frame): pop_up.ShowModal() if pop_up.GetReturnCode() != wx.ID_YES: - print("- Skipping OpenCore and root volume patch update...") + print("Skipping OpenCore and root volume patch update...") return - print("- Updating OpenCore and root volume patches...") + print("Updating OpenCore and root volume patches...") self.constants.update_stage = gui_support.AutoUpdateStages.CHECKING self.Hide() pos = self.GetPosition() diff --git a/resources/wx_gui/gui_settings.py b/resources/wx_gui/gui_settings.py index 97fd75167..ee1c47275 100644 --- a/resources/wx_gui/gui_settings.py +++ b/resources/wx_gui/gui_settings.py @@ -32,6 +32,7 @@ class SettingsFrame(wx.Frame): Modal-based Settings Frame """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + logging.info("Initializing Settings Frame") self.constants: constants.Constants = global_constants self.title: str = title self.parent: wx.Frame = parent @@ -1257,11 +1258,12 @@ Hardware Information: def on_export_constants(self, event: wx.Event) -> None: # Throw pop up to get save location - with wx.FileDialog(self.parent, "Save Constants File", wildcard="JSON files (*.txt)|*.txt", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog: + with wx.FileDialog(self.parent, "Save Constants File", wildcard="JSON files (*.txt)|*.txt", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, defaultFile=f"constants-{self.constants.patcher_version}.txt") as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return # Save the current contents in the file pathname = fileDialog.GetPath() + logging.info(f"Saving constants to {pathname}") with open(pathname, 'w') as file: file.write(pprint.pformat(vars(self.constants), indent=4)) \ No newline at end of file diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index c97deb84a..504c889a8 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -289,7 +289,7 @@ class RelaunchApplicationAsRoot: wx.Yield() - logging.info(f"- Relaunching as root with command: {program_arguments}") + logging.info(f"Relaunching as root with command: {program_arguments}") subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: diff --git a/resources/wx_gui/gui_sys_patch.py b/resources/wx_gui/gui_sys_patch.py index 69aab2d6f..5e4304920 100644 --- a/resources/wx_gui/gui_sys_patch.py +++ b/resources/wx_gui/gui_sys_patch.py @@ -33,6 +33,7 @@ class SysPatchFrame(wx.Frame): Uses a Modal Dialog for smoother transition from other frames """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None, patches: dict = {}): + logging.info("Initializing Root Patching Frame") self.frame = parent self.initiated_with_parent = False if not self.frame and patches == {}: @@ -167,7 +168,7 @@ class SysPatchFrame(wx.Frame): can_unpatch: bool = patches["Validation: Unpatching Possible"] if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): - logging.info("- No applicable patches available") + logging.info("No applicable patches available") patches = [] # Check if OCLP has already applied the same patches @@ -198,10 +199,11 @@ class SysPatchFrame(wx.Frame): anchor.Centre(wx.HORIZONTAL) anchor.Hide() + logging.info("Available patches:") for patch in patches: if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): i = i + 20 - logging.info(f"- Adding patch: {patch} - {patches[patch]}") + logging.info(f"- {patch}") patch_label = wx.StaticText(frame, label=f"- {patch}", pos=(anchor.GetPosition()[0], available_label.GetPosition()[1] + i)) patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) @@ -346,9 +348,10 @@ class SysPatchFrame(wx.Frame): # Labels i = 0 + logging.info("Available patches:") for patch in patches: if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): - logging.info(f"- Adding patch: {patch} - {patches[patch]}") + logging.info(f"- {patch}") patch_label = wx.StaticText(dialog, label=f"- {patch}", pos=(anchor.GetPosition()[0], label.GetPosition()[1] + 20 + i)) patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) i = i + 20 @@ -422,7 +425,7 @@ class SysPatchFrame(wx.Frame): try: sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_patch() except: - logging.error("- An internal error occurred while running the Root Patcher:\n") + logging.error("An internal error occurred while running the Root Patcher:\n") logging.error(traceback.format_exc()) logger.removeHandler(logger.handlers[2]) @@ -454,7 +457,7 @@ class SysPatchFrame(wx.Frame): try: sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_unpatch() except: - logging.error("- An internal error occurred while running the Root Patcher:\n") + logging.error("An internal error occurred while running the Root Patcher:\n") logging.error(traceback.format_exc()) logger.removeHandler(logger.handlers[2]) @@ -525,6 +528,8 @@ class SysPatchFrame(wx.Frame): Thus we'll need to see if the exact same OCLP build was used already """ + logging.info("Checking if new patches are needed") + if self.constants.commit_info[0] in ["Running from source", "Built from source"]: return True @@ -557,5 +562,5 @@ class SysPatchFrame(wx.Frame): logging.info(f"- Patch {patch} not installed") return True - logging.info("- No new patches detected for system") + logging.info("No new patches detected for system") return False \ No newline at end of file diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py index 23746ffce..b3e862ef0 100644 --- a/resources/wx_gui/gui_update.py +++ b/resources/wx_gui/gui_update.py @@ -22,6 +22,7 @@ class UpdateFrame(wx.Frame): Create a frame for updating the patcher """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: wx.Point, url: str = "", version_label: str = "") -> None: + logging.info("Initializing Update Frame") if parent: self.parent: wx.Frame = parent @@ -55,6 +56,9 @@ class UpdateFrame(wx.Frame): self.version_label = version_label self.url = url + logging.info(f"Update URL: {url}") + logging.info(f"Update Version: {version_label}") + self.frame: wx.Frame = wx.Frame( parent=parent if parent else self, title=self.title, @@ -180,6 +184,7 @@ class UpdateFrame(wx.Frame): ["ditto", "-xk", str(self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip"), str(self.constants.payload_path)], capture_output=True ) if result.returncode != 0: + logging.error(f"Failed to extract update. Error: {result.stderr.decode('utf-8')}") wx.CallAfter(self.progress_bar_animation.stop_pulse) wx.CallAfter(self.progress_bar.SetValue, 0) wx.CallAfter(wx.MessageBox, f"Failed to extract update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR) @@ -190,6 +195,7 @@ class UpdateFrame(wx.Frame): break if i == 1: + logging.error("Failed to extract update. Error: Update file does not exist") wx.CallAfter(self.progress_bar_animation.stop_pulse) wx.CallAfter(self.progress_bar.SetValue, 0) wx.CallAfter(wx.MessageBox, "Failed to extract update. Error: Update file does not exist", "Critical Error!", wx.OK | wx.ICON_ERROR) @@ -251,8 +257,10 @@ EOF wx.CallAfter(self.progress_bar_animation.stop_pulse) wx.CallAfter(self.progress_bar.SetValue, 0) if "User cancelled" in result.stderr.decode("utf-8"): + logging.info("User cancelled update") wx.CallAfter(wx.MessageBox, "User cancelled update", "Update Cancelled", wx.OK | wx.ICON_INFORMATION) else: + logging.critical(f"Failed to install update. Error: {result.stderr.decode('utf-8')}") wx.CallAfter(wx.MessageBox, f"Failed to install update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR) wx.CallAfter(sys.exit, 1) From 4f0b6057863a267e901c46a1a0cbd665d43cf117 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 25 May 2023 11:25:13 -0600 Subject: [PATCH 02/28] logging_handler.py: Reveal log on crash --- CHANGELOG.md | 10 +++++++++ resources/arguments.py | 2 +- resources/logging_handler.py | 3 +++ resources/macos_installer_handler.py | 3 +-- .../wx_gui/gui_macos_installer_download.py | 17 +++++++++++--- resources/wx_gui/gui_macos_installer_flash.py | 22 ++++++++++++++----- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b009160da..ef13677db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ ## 0.6.7 - Backend changes: - Call `setpgrp()` to prevent app from being killed if parent process is killed (ie. LaunchAgents) + - Rework logging handler: + - Implement formatted logging + - Allowing easier debugging + - Implement per-version file logging + - ex. OpenCore-Patcher-0.6.7.log + - Remove old logs after 1 week of inactivity + - Reduce log rollover to 1MB + - Reveal log file in Finder on main thread crash + - Resolve SharedSupport.dmg pathing error during macOS Installer Verification + - Applicable to systems with 2 (or more) USB Installers with the same name plugged in ## 0.6.6 - Implement option to disable ColorSync downgrade on HD 3000 Macs diff --git a/resources/arguments.py b/resources/arguments.py index d96672564..62d6eb3d7 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -50,7 +50,7 @@ class arguments: """ Enter validation mode """ - + logging.info("Set Validation Mode") validation.PatcherValidation(self.constants) diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 6590509b4..d8136725d 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -216,6 +216,9 @@ class InitializeLoggingSupport: applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"OK"}} default button "OK" with icon caution giving up after 30').run() + # Open log location + subprocess.run(["open", "--reveal", self.log_filepath]) + def custom_thread_excepthook(args) -> None: """ diff --git a/resources/macos_installer_handler.py b/resources/macos_installer_handler.py index a7e08d975..04a8f6bd2 100644 --- a/resources/macos_installer_handler.py +++ b/resources/macos_installer_handler.py @@ -53,7 +53,7 @@ class InstallerCreation(): bool: True if successful, False otherwise """ - logging.info("Extracting macOS installer from InstallAssistant.pkg\n This may take some time") + logging.info("Extracting macOS installer from InstallAssistant.pkg") try: applescript.AppleScript( f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' @@ -201,7 +201,6 @@ fi if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]): continue - logging.info(f"disk {disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})") list_disks.update({ disk: { "identifier": all_disks[disk]["identifier"], diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py index c80b4949b..adc569164 100644 --- a/resources/wx_gui/gui_macos_installer_download.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -36,6 +36,8 @@ class macOSInstallerDownloadFrame(wx.Frame): self.available_installers = None self.available_installers_latest = None + self.catalog_seed: macos_installer_handler.SeedType = macos_installer_handler.SeedType.DeveloperSeed + self.frame_modal = wx.Dialog(parent, title=title, size=(330, 200)) self._generate_elements(self.frame_modal) @@ -102,7 +104,8 @@ class macOSInstallerDownloadFrame(wx.Frame): # Grab installer catalog def _fetch_installers(): - remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=macos_installer_handler.SeedType.DeveloperSeed) + logging.info(f"Fetching installer catalog: {macos_installer_handler.SeedType(self.catalog_seed).name}") + remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=self.catalog_seed) self.available_installers = remote_obj.available_apps self.available_installers_latest = remote_obj.available_apps_latest @@ -138,8 +141,9 @@ class macOSInstallerDownloadFrame(wx.Frame): installers = self.available_installers_latest if show_full is False else self.available_installers if installers: spacer = 0 + logging.info(f"Available installers on SUCatalog ({'All entries' if show_full else 'Latest only'}):") for app in installers: - logging.info(f"macOS {installers[app]['Version']} ({installers[app]['Build']}):\n - Size: {utilities.human_fmt(installers[app]['Size'])}\n - Source: {installers[app]['Source']}\n - Variant: {installers[app]['Variant']}\n - Link: {installers[app]['Link']}\n") + logging.info(f"- macOS {installers[app]['Version']} ({installers[app]['Build']}):\n - Size: {utilities.human_fmt(installers[app]['Size'])}\n - Source: {installers[app]['Source']}\n - Variant: {installers[app]['Variant']}\n - Link: {installers[app]['Link']}\n") extra = " Beta" if installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"] else "" installer_button = wx.Button(dialog, label=f"macOS {installers[app]['Version']}{extra} ({installers[app]['Build']} - {utilities.human_fmt(installers[app]['Size'])})", pos=(-1, subtitle_label.GetPosition()[1] + subtitle_label.GetSize()[1] + 5 + spacer), size=(270, 30)) @@ -172,6 +176,7 @@ class macOSInstallerDownloadFrame(wx.Frame): """ Download macOS installer """ + logging.info(f"Selected macOS {app['Version']} ({app['Build']})") # Notify user whether their model is compatible with the selected installer problems = [] @@ -185,6 +190,7 @@ class macOSInstallerDownloadFrame(wx.Frame): problems.append("Lack of internal Keyboard/Mouse in macOS installer.") if problems: + logging.warning(f"Potential issues with {model} and {app['Version']} ({app['Build']}): {problems}") problems = "\n".join(problems) dlg = wx.MessageDialog(self.frame_modal, f"Your model ({model}) may not be fully supported by this installer. You may encounter the following issues:\n\n{problems}\n\nFor more information, see associated page. Otherwise, we recommend using macOS Monterey", "Potential Issues", wx.YES_NO | wx.CANCEL | wx.ICON_WARNING) dlg.SetYesNoCancelLabels("View Github Issue", "Download Anyways", "Cancel") @@ -198,6 +204,7 @@ class macOSInstallerDownloadFrame(wx.Frame): host_space = utilities.get_free_space() needed_space = app['Size'] * 2 if host_space < needed_space: + logging.error(f"Insufficient space to download and extract: {utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required") dlg = wx.MessageDialog(self.frame_modal, f"You do not have enough free space to download and extract this installer. Please free up some space and try again\n\n{utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required", "Insufficient Space", wx.OK | wx.ICON_WARNING) dlg.ShowModal() return @@ -249,6 +256,7 @@ class macOSInstallerDownloadFrame(wx.Frame): chunklist_stream = network_handler.NetworkUtilities().get(chunklist_link).content if chunklist_stream: + logging.info("Validating macOS installer") utilities.disable_sleep_while_running() chunk_obj = integrity_verification.ChunklistVerification(self.constants.payload_path / Path("InstallAssistant.pkg"), chunklist_stream) if chunk_obj.chunks: @@ -265,10 +273,13 @@ class macOSInstallerDownloadFrame(wx.Frame): wx.App.Get().Yield() if chunk_obj.status == integrity_verification.ChunklistStatus.FAILURE: - wx.MessageBox("Chunklist validation failed.\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", "Corrupted Installer!", wx.OK | wx.ICON_ERROR) + logging.error(f"Chunklist validation failed: Hash mismatch on {chunk_obj.current_chunk}") + wx.MessageBox(f"Chunklist validation failed: Hash mismatch on {chunk_obj.current_chunk}\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", "Corrupted Installer!", wx.OK | wx.ICON_ERROR) self.on_return_to_main_menu() return + logging.info("macOS installer validated") + # Extract installer title_label.SetLabel("Extracting macOS Installer") title_label.Centre(wx.HORIZONTAL) diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index 4295a88bf..fef0c066e 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -132,6 +132,7 @@ class macOSInstallerFlashFrame(wx.Frame): def on_select(self, installer: dict) -> None: + logging.info(f"Selected installer: {installer['Short Name']} ({installer['Version']} ({installer['Build']}))") self.frame_modal.Destroy() for child in self.GetChildren(): @@ -186,8 +187,9 @@ class macOSInstallerFlashFrame(wx.Frame): if self.available_disks: spacer = 5 entries = len(self.available_disks) + logging.info("Available disks:") for disk in self.available_disks: - logging.info(f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}") + 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.Centre(wx.HORIZONTAL) @@ -222,6 +224,8 @@ class macOSInstallerFlashFrame(wx.Frame): if answer != wx.YES: return + logging.info(f"Selected disk: {disk['name']}") + self.frame_modal.Destroy() for child in self.GetChildren(): @@ -262,6 +266,7 @@ class macOSInstallerFlashFrame(wx.Frame): # Prepare resources if self._prepare_resources(installer['Path'], disk['identifier']) is False: + logging.error("Failed to prepare resources, cannot continue.") wx.MessageBox("Failed to prepare resources, cannot continue.", "Error", wx.OK | wx.ICON_ERROR) self.on_return_to_main_menu() return @@ -281,6 +286,7 @@ class macOSInstallerFlashFrame(wx.Frame): initial_bytes_written = float(utilities.monitor_disk_output(root_disk)) self.result = False def _flash(): + logging.info(f"Flashing {installer['Path']} to {root_disk}") self.result = self._flash_installer(root_disk) thread = threading.Thread(target=_flash) @@ -298,6 +304,7 @@ class macOSInstallerFlashFrame(wx.Frame): wx.Yield() if self.result is False: + logging.error("Failed to flash installer, cannot continue.") self.on_return_to_main_menu() return @@ -522,12 +529,15 @@ class macOSInstallerFlashFrame(wx.Frame): kdk_dmg_path.unlink() def _validate_installer_pkg(self, disk: str) -> bool: - verification_success = False + logging.info("Validating installer pkg") 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" + for folder in Path(utilities.grab_mount_point_from_disk(disk + "s2")).glob("*.app"): + if folder.is_dir(): + dmg_path = folder / "Contents" / "SharedSupport" / "SharedSupport.dmg" + break + if not Path(dmg_path).exists(): logging.error(f"Failed to find {dmg_path}") error_message = f"Failed to find {dmg_path}" @@ -547,10 +557,10 @@ class macOSInstallerFlashFrame(wx.Frame): while thread.is_alive(): wx.Yield() - if verification_success: + if error_message == "": + logging.info("Installer pkg validated") return error_message - logging.error(error_message) return error_message From 4a7b6437a02389e76de3627d61191682389c6613 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 11:50:52 -0600 Subject: [PATCH 03/28] logging_handler.py: Switch to per-launch log file --- CHANGELOG.md | 7 +-- resources/logging_handler.py | 109 +++++++++++++++++------------------ 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef13677db..391735af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,9 @@ - Rework logging handler: - Implement formatted logging - Allowing easier debugging - - Implement per-version file logging - - ex. OpenCore-Patcher-0.6.7.log - - Remove old logs after 1 week of inactivity - - Reduce log rollover to 1MB + - Implement per-version, per-run file logging + - ex. OpenCore-Patcher (0.6.7) (2021-12-31-12-34-56).log + - Keep only 10 latest log files - Reveal log file in Finder on main thread crash - Resolve SharedSupport.dmg pathing error during macOS Installer Verification - Applicable to systems with 2 (or more) USB Installers with the same name plugged in diff --git a/resources/logging_handler.py b/resources/logging_handler.py index d8136725d..4e734b097 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -1,4 +1,3 @@ -import wx import os import sys import time @@ -36,7 +35,7 @@ class InitializeLoggingSupport: def __init__(self, global_constants: constants.Constants) -> None: self.constants: constants.Constants = global_constants - self.log_filename: str = f"OpenCore-Patcher-{self.constants.patcher_version}.log" + self.log_filename: str = f"OpenCore-Patcher_{self.constants.patcher_version}_{time.strftime('%Y-%m-%d_%H-%M-%S')}.log" self.log_filepath: Path = None self.original_excepthook: sys = sys.excepthook @@ -46,7 +45,6 @@ class InitializeLoggingSupport: self.file_size_redline: int = 1024 * 1024 - 1024 * 100 # 900 KB, when to start cleaning log file self._initialize_logging_path() - self._clean_log_file() self._attempt_initialize_logging_configuration() self._start_logging() self._implement_custom_traceback_handler() @@ -59,62 +57,57 @@ class InitializeLoggingSupport: Initialize logging framework storage path """ - self.log_filepath = Path(f"~/Library/Logs/{self.log_filename}").expanduser() + base_path = Path("~/Library/Logs").expanduser() + if not base_path.exists(): + # Likely in an installer environment, store in /Users/Shared + base_path = Path("/Users/Shared") + else: + # create Dortania folder if it doesn't exist + base_path = base_path / "Dortania" + if not base_path.exists(): + try: + base_path.mkdir() + except Exception as e: + logging.error(f"Failed to create Dortania folder: {e}") + base_path = Path("/Users/Shared") - if not self.log_filepath.parent.exists(): - # Likely in an installer environment, store in /Users/Shared - self.log_filepath = Path("/Users/Shared") / self.log_filename - - - def _clean_log_file(self) -> None: - """ - Determine if log file should be cleaned - - We check if we're near the max file size, and if so, we clean the log file - """ - - if not self.log_filepath.exists(): - return - - if self.log_filepath.stat().st_size < self.file_size_redline: - return - - # Check if backup log file exists - backup_log_filepath = self.log_filepath.with_suffix(".old.log") - try: - if backup_log_filepath.exists(): - backup_log_filepath.unlink() - - # Rename current log file to backup log file - self.log_filepath.rename(backup_log_filepath) - except Exception as e: - print(f"Failed to clean log file: {e}") + self.log_filepath = Path(f"{base_path}/{self.log_filename}").expanduser() def _clean_prior_version_logs(self) -> None: """ Clean logs from old Patcher versions - If file is more than a week old, assume it's unused and delete it + Keep 10 latest logs """ - time_threshold = time.time() - 60 * 60 * 24 * 7 + paths = [ + self.log_filepath.parent, # ~/Library/Logs/Dortania + self.log_filepath.parent.parent, # ~/Library/Logs (old location) + ] - for file in self.log_filepath.parent.glob("OpenCore-Patcher*"): - if not file.is_file(): - continue + logs = [] - if not file.name.endswith(".log"): - continue + for path in paths: + for file in path.glob("OpenCore-Patcher*"): + if not file.is_file(): + continue - if file.name == self.log_filename: - continue + if not file.name.endswith(".log"): + continue - if file.stat().st_mtime < time_threshold: - try: - file.unlink() - except Exception as e: - print(f"Failed to clean prior version log file: {e}") + if file.name == self.log_filename: + continue + + logs.append(file) + + logs.sort(key=lambda x: x.stat().st_mtime, reverse=True) + + for log in logs[9:]: + try: + log.unlink() + except Exception as e: + logging.error(f"Failed to delete log file: {e}") def _fix_file_permission(self) -> None: @@ -128,15 +121,21 @@ class InitializeLoggingSupport: if os.geteuid() != 0: return - result = subprocess.run(["chmod", "777", self.log_filepath], capture_output=True) - if result.returncode != 0: - logging.error(f"Failed to fix log file permissions") - if result.stdout: - logging.error("STDOUT:") - logging.error(result.stdout.decode("utf-8")) - if result.stderr: - logging.error("STDERR:") - logging.error(result.stderr.decode("utf-8")) + paths = [ + self.log_filepath, # ~/Library/Logs/Dortania/OpenCore-Patcher_{version}_{date}.log + self.log_filepath.parent, # ~/Library/Logs/Dortania + ] + + for path in paths: + result = subprocess.run(["chmod", "777", path], capture_output=True) + if result.returncode != 0: + logging.error(f"Failed to fix log file permissions") + if result.stdout: + logging.error("STDOUT:") + logging.error(result.stdout.decode("utf-8")) + if result.stderr: + logging.error("STDERR:") + logging.error(result.stderr.decode("utf-8")) def _initialize_logging_configuration(self, log_to_file: bool = True) -> None: From 12a2d5ade1028802b95177f234e8f031ad88d044 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 12:05:22 -0600 Subject: [PATCH 04/28] =?UTF-8?q?logging=5Fhandler.py:=20Don=E2=80=99t=20d?= =?UTF-8?q?isplay=20user=E2=80=99s=20username?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/logging_handler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 4e734b097..32ab74a04 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -190,7 +190,13 @@ class InitializeLoggingSupport: logging.info(str_msg) logging.info('#' * str_len) - logging.info(f"Log file set to: {self.log_filepath}") + logging.info("Log file set:") + # Display relative path to avoid disclosing user's username + try: + path = self.log_filepath.relative_to(Path.home()) + logging.info(f"~/{path}") + except ValueError: + logging.info(self.log_filepath) def _implement_custom_traceback_handler(self) -> None: From 79f6754c4f35898252ab8f9ca06a689bff7573d1 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 12:08:42 -0600 Subject: [PATCH 05/28] Sync changelog --- CHANGELOG.md | 1 + resources/utilities.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ed979a7..b3adef5b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - ex. OpenCore-Patcher (0.6.7) (2021-12-31-12-34-56).log - Keep only 10 latest log files - Reveal log file in Finder on main thread crash + - Avoid writing username to log file - Resolve SharedSupport.dmg pathing error during macOS Installer Verification - Applicable to systems with 2 (or more) USB Installers with the same name plugged in - Resolve payloads path being mis-routed during CLI calls diff --git a/resources/utilities.py b/resources/utilities.py index 3165aab92..e7f476c70 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -394,9 +394,6 @@ def get_firmware_vendor(*, decode: bool = False): value = value.strip("\0") return value -def dump_constants(constants): - with open(os.path.join(os.path.expanduser('~'), 'Desktop', 'internal_data.txt'), 'w') as f: - f.write(str(vars(constants))) def find_apfs_physical_volume(device): # ex: disk3s1s1 From bad9dbaecc35a15fd6447242994532fbd07f9cce Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 12:44:10 -0600 Subject: [PATCH 06/28] Add exception handler test --- resources/wx_gui/gui_settings.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_settings.py b/resources/wx_gui/gui_settings.py index ee1c47275..21dd8a6e1 100644 --- a/resources/wx_gui/gui_settings.py +++ b/resources/wx_gui/gui_settings.py @@ -824,6 +824,12 @@ class SettingsFrame(wx.Frame): "Check CHANGELOG before blindly updating.", ], }, + "Trigger Exception": { + "type": "button", + "function": self.on_test_exception, + "description": [ + ], + }, "wrap_around 1": { "type": "wrap_around", }, @@ -1266,4 +1272,8 @@ Hardware Information: pathname = fileDialog.GetPath() logging.info(f"Saving constants to {pathname}") with open(pathname, 'w') as file: - file.write(pprint.pformat(vars(self.constants), indent=4)) \ No newline at end of file + file.write(pprint.pformat(vars(self.constants), indent=4)) + + + def on_test_exception(self, event: wx.Event) -> None: + raise Exception("Test Exception") \ No newline at end of file From d725798c606c4c9c53bc2c68951c575befa1df0f Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 13:24:41 -0600 Subject: [PATCH 07/28] GUI SysPatch: Seperate Display and Patch frames --- resources/wx_gui/gui_entry.py | 11 +- resources/wx_gui/gui_install_oc.py | 4 +- resources/wx_gui/gui_main_menu.py | 5 +- resources/wx_gui/gui_sys_patch_display.py | 302 ++++++++++++++++++ ...ui_sys_patch.py => gui_sys_patch_start.py} | 227 ++----------- 5 files changed, 331 insertions(+), 218 deletions(-) create mode 100644 resources/wx_gui/gui_sys_patch_display.py rename resources/wx_gui/{gui_sys_patch.py => gui_sys_patch_start.py} (55%) diff --git a/resources/wx_gui/gui_entry.py b/resources/wx_gui/gui_entry.py index cc8a167e9..c2bcda0ff 100644 --- a/resources/wx_gui/gui_entry.py +++ b/resources/wx_gui/gui_entry.py @@ -9,8 +9,7 @@ from resources.wx_gui import ( gui_main_menu, gui_build, gui_install_oc, - gui_sys_patch, - gui_support, + gui_sys_patch_start, gui_update, ) from resources.sys_patch import sys_patch_detect @@ -23,7 +22,7 @@ class SupportedEntryPoints: MAIN_MENU = gui_main_menu.MainFrame BUILD_OC = gui_build.BuildFrame INSTALL_OC = gui_install_oc.InstallOCFrame - SYS_PATCH = gui_sys_patch.SysPatchFrame + SYS_PATCH = gui_sys_patch_start.SysPatchStartFrame UPDATE_APP = gui_update.UpdateFrame @@ -49,7 +48,7 @@ class EntryPoint: self._generate_base_data() if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv: - entry = gui_sys_patch.SysPatchFrame + entry = gui_sys_patch_start.SysPatchStartFrame patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() logging.info(f"Entry point set: {entry.__name__}") @@ -64,9 +63,9 @@ class EntryPoint: atexit.register(self.OnCloseFrame) if "--gui_patch" in sys.argv: - self.frame.start_root_patching(patches) + self.frame.start_root_patching() elif "--gui_unpatch" in sys.argv: - self.frame.revert_root_patching(patches) + self.frame.revert_root_patching() self.app.MainLoop() diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py index 3a0451e5c..70c2cbcb0 100644 --- a/resources/wx_gui/gui_install_oc.py +++ b/resources/wx_gui/gui_install_oc.py @@ -3,7 +3,7 @@ import threading import logging import traceback -from resources.wx_gui import gui_main_menu, gui_support, gui_sys_patch +from resources.wx_gui import gui_main_menu, gui_support, gui_sys_patch_display from resources import constants, install from data import os_data @@ -284,7 +284,7 @@ class InstallOCFrame(wx.Frame): popup_message.ShowModal() if popup_message.GetReturnCode() == wx.ID_YES: self.Hide() - gui_sys_patch.SysPatchFrame( + gui_sys_patch_display.SysPatchDisplayFrame( parent=None, title=self.title, global_constants=self.constants, diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 05d4dc8c1..8a574abd8 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -8,10 +8,11 @@ import webbrowser from resources.wx_gui import ( gui_build, gui_macos_installer_download, - gui_sys_patch, gui_support, gui_help, gui_settings, + gui_sys_patch_start, + gui_sys_patch_display, gui_update, ) from resources import ( @@ -278,7 +279,7 @@ class MainFrame(wx.Frame): def on_post_install_root_patch(self, event: wx.Event = None): - gui_sys_patch.SysPatchFrame( + gui_sys_patch_display.SysPatchDisplayFrame( parent=self, title=self.title, global_constants=self.constants, diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py new file mode 100644 index 000000000..0d305dc5d --- /dev/null +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -0,0 +1,302 @@ + +import wx +import os +import logging +import plistlib + +from pathlib import Path + +from resources import ( + constants, +) +from resources.sys_patch import ( + sys_patch_detect +) +from resources.wx_gui import ( + gui_main_menu, + gui_support, + gui_sys_patch_start, +) + + +class SysPatchDisplayFrame(wx.Frame): + """ + Create a modal frame for displaying root patches + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + logging.info("Initializing Root Patch Display Frame") + + if parent: + self.frame = parent + else: + self.frame = wx.Frame.__init__(self, parent, title=title, size=(360, 200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) + self.title = title + self.constants: constants.Constants = global_constants + self.frame_modal: wx.Dialog = None + self.return_button: wx.Button = None + self.available_patches: bool = False + + self.frame_modal = wx.Dialog(self.frame, title=title, size=(360, 200)) + + self._generate_elements_display_patches(self.frame_modal) + self.frame_modal.ShowWindowModal() + + if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: + if self.available_patches is False: + gui_support.RestartHost(self.frame).restart(message="No root patch updates needed!\n\nWould you like to reboot to apply the new OpenCore build?") + + + def _generate_elements_display_patches(self, frame: wx.Frame = None) -> None: + """ + Generate UI elements for root patching frame + + Format: + - Title label: Post-Install Menu + - Label: Available patches: + - Labels: {patch name} + - Button: Start Root Patching + - Button: Revert Root Patches + - Button: Return to Main Menu + """ + frame = self if not frame else frame + + title_label = wx.StaticText(frame, label="Post-Install Menu", pos=(-1, 10)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Label: Available patches: + available_label = wx.StaticText(frame, label="Available patches for your system:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10)) + available_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + available_label.Centre(wx.HORIZONTAL) + + # Labels: {patch name} + patches: dict = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() + can_unpatch: bool = patches["Validation: Unpatching Possible"] + + if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): + logging.info("No applicable patches available") + patches = [] + + # Check if OCLP has already applied the same patches + no_new_patches = not self._check_if_new_patches_needed(patches) if patches else False + + if not patches: + # Prompt user with no patches found + patch_label = wx.StaticText(frame, label="No patches required", pos=(-1, available_label.GetPosition()[1] + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + else: + # Add Label for each patch + i = 0 + if no_new_patches is True: + patch_label = wx.StaticText(frame, label="All applicable patches already installed", pos=(-1, available_label.GetPosition()[1] + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + i = i + 20 + else: + longest_patch = "" + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + if len(patch) > len(longest_patch): + longest_patch = patch + anchor = wx.StaticText(frame, label=longest_patch, pos=(-1, available_label.GetPosition()[1] + 20)) + anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + anchor.Centre(wx.HORIZONTAL) + anchor.Hide() + + logging.info("Available patches:") + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + i = i + 20 + logging.info(f"- {patch}") + patch_label = wx.StaticText(frame, label=f"- {patch}", pos=(anchor.GetPosition()[0], available_label.GetPosition()[1] + i)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + if i == 20: + patch_label.SetLabel(patch_label.GetLabel().replace("-", "")) + patch_label.Centre(wx.HORIZONTAL) + + if patches["Validation: Patching Possible"] is False: + # Cannot patch due to the following reasons: + patch_label = wx.StaticText(frame, label="Cannot patch due to the following reasons:", pos=(-1, patch_label.GetPosition()[1] + 25)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + longest_patch = "" + for patch in patches: + if not patch.startswith("Validation"): + continue + if patches[patch] is False: + continue + if patch == "Validation: Unpatching Possible": + continue + + if len(patch) > len(longest_patch): + longest_patch = patch + anchor = wx.StaticText(frame, label=longest_patch.split('Validation: ')[1], pos=(-1, patch_label.GetPosition()[1] + 20)) + anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + anchor.Centre(wx.HORIZONTAL) + anchor.Hide() + + i = 0 + for patch in patches: + if not patch.startswith("Validation"): + continue + if patches[patch] is False: + continue + if patch == "Validation: Unpatching Possible": + continue + + patch_label = wx.StaticText(frame, label=f"- {patch.split('Validation: ')[1]}", pos=(anchor.GetPosition()[0], anchor.GetPosition()[1] + i)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + i = i + 20 + + if i == 20: + patch_label.SetLabel(patch_label.GetLabel().replace("-", "")) + patch_label.Centre(wx.HORIZONTAL) + + else: + if self.constants.computer.oclp_sys_version and self.constants.computer.oclp_sys_date: + date = self.constants.computer.oclp_sys_date.split(" @") + date = date[0] if len(date) == 2 else "" + + patch_text = f"{self.constants.computer.oclp_sys_version}, {date}" + + patch_label = wx.StaticText(frame, label="Root Volume last patched:", pos=(-1, patch_label.GetPosition().y + 25)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + patch_label = wx.StaticText(frame, label=patch_text, pos=(available_label.GetPosition().x - 10, patch_label.GetPosition().y + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + + # Button: Start Root Patching + start_button = wx.Button(frame, label="Start Root Patching", pos=(10, patch_label.GetPosition().y + 25), size=(170, 30)) + start_button.Bind(wx.EVT_BUTTON, lambda event: self.on_start_root_patching(patches)) + start_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + start_button.Centre(wx.HORIZONTAL) + + # Button: Revert Root Patches + revert_button = wx.Button(frame, label="Revert Root Patches", pos=(10, start_button.GetPosition().y + start_button.GetSize().height - 5), size=(170, 30)) + revert_button.Bind(wx.EVT_BUTTON, lambda event: self.on_revert_root_patching(patches)) + revert_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + revert_button.Centre(wx.HORIZONTAL) + + # Button: Return to Main Menu + return_button = wx.Button(frame, label="Return to Main Menu", pos=(10, revert_button.GetPosition().y + revert_button.GetSize().height), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return_dismiss) + return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + return_button.Centre(wx.HORIZONTAL) + self.return_button = return_button + + # Disable buttons if unsupported + if not patches: + start_button.Disable() + else: + self.available_patches = True + if patches["Validation: Patching Possible"] is False: + start_button.Disable() + elif no_new_patches is False: + start_button.SetDefault() + else: + self.available_patches = False + if can_unpatch is False: + revert_button.Disable() + + # Relaunch as root if not root + if os.geteuid() != 0: + start_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) + revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) + + # Set frame size + frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 35)) + + + def on_start_root_patching(self, patches: dict): + frame = gui_sys_patch_start.SysPatchStartFrame( + parent=None, + title=self.title, + global_constants=self.constants, + patches=patches, + ) + frame.start_root_patching() + self.on_return_dismiss() + + + def on_revert_root_patching(self, patches: dict): + frame = gui_sys_patch_start.SysPatchStartFrame( + parent=None, + title=self.title, + global_constants=self.constants, + patches=patches, + ) + frame.revert_root_patching() + self.on_return_dismiss() + + + def on_return_to_main_menu(self, event: wx.Event = None): + # Get frame from event + frame_modal: wx.Dialog = event.GetEventObject().GetParent() + frame: wx.Frame = frame_modal.Parent + frame_modal.Hide() + frame.Hide() + + main_menu_frame = gui_main_menu.MainFrame( + None, + title=self.title, + global_constants=self.constants, + ) + main_menu_frame.Show() + frame.Destroy() + + + def on_return_dismiss(self, event: wx.Event = None): + self.frame_modal.Hide() + self.frame_modal.Destroy() + + + def _check_if_new_patches_needed(self, patches: dict) -> bool: + """ + Checks if any new patches are needed for the user to install + Newer users will assume the root patch menu will present missing patches. + Thus we'll need to see if the exact same OCLP build was used already + """ + + logging.info("Checking if new patches are needed") + + if self.constants.commit_info[0] in ["Running from source", "Built from source"]: + return True + + if self.constants.computer.oclp_sys_url != self.constants.commit_info[2]: + # If commits are different, assume patches are as well + return True + + oclp_plist = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist" + if not Path(oclp_plist).exists(): + # If it doesn't exist, no patches were ever installed + # ie. all patches applicable + return True + + oclp_plist_data = plistlib.load(open(oclp_plist, "rb")) + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + # Patches should share the same name as the plist key + # See sys_patch_dict.py for more info + patch_installed = False + for key in oclp_plist_data: + if isinstance(oclp_plist_data[key], (bool, int)): + continue + if "Display Name" not in oclp_plist_data[key]: + continue + if oclp_plist_data[key]["Display Name"] == patch: + patch_installed = True + break + + if patch_installed is False: + logging.info(f"- Patch {patch} not installed") + return True + + logging.info("No new patches detected for system") + return False \ No newline at end of file diff --git a/resources/wx_gui/gui_sys_patch.py b/resources/wx_gui/gui_sys_patch_start.py similarity index 55% rename from resources/wx_gui/gui_sys_patch.py rename to resources/wx_gui/gui_sys_patch_start.py index 5e4304920..565d798a5 100644 --- a/resources/wx_gui/gui_sys_patch.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -1,6 +1,5 @@ import wx -import os import sys import time import logging @@ -27,39 +26,27 @@ from resources.wx_gui import ( from data import os_data -class SysPatchFrame(wx.Frame): +class SysPatchStartFrame(wx.Frame): """ Create a frame for root patching Uses a Modal Dialog for smoother transition from other frames """ def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None, patches: dict = {}): logging.info("Initializing Root Patching Frame") - self.frame = parent - self.initiated_with_parent = False - if not self.frame and patches == {}: - super(SysPatchFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) - self.frame = self - self.frame.Centre() - else: - self.initiated_with_parent = True self.title = title self.constants: constants.Constants = global_constants self.frame_modal: wx.Dialog = None self.return_button: wx.Button = None self.available_patches: bool = False + self.patches: dict = patches - self.frame_modal = wx.Dialog(self.frame, title=title, size=(360, 200)) + super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + gui_support.GenerateMenubar(self, self.constants).generate() + self.Centre() - if patches: - return - - self._generate_elements_display_patches(self.frame_modal, patches) - self.frame_modal.ShowWindowModal() - - if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: - if self.available_patches is False: - gui_support.RestartHost(self.frame).restart(message="No root patch updates needed!\n\nWould you like to reboot to apply the new OpenCore build?") + if self.patches == {}: + self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() def _kdk_download(self, frame: wx.Frame = None) -> bool: @@ -137,178 +124,13 @@ class SysPatchFrame(wx.Frame): progress_bar.SetValue(100) logging.info("KDK download complete") + + for child in frame.GetChildren(): + child.Destroy() + return True - def _generate_elements_display_patches(self, frame: wx.Frame = None, patches: dict = {}) -> None: - """ - Generate UI elements for root patching frame - - Format: - - Title label: Post-Install Menu - - Label: Available patches: - - Labels: {patch name} - - Button: Start Root Patching - - Button: Revert Root Patches - - Button: Return to Main Menu - """ - frame = self if not frame else frame - - title_label = wx.StaticText(frame, label="Post-Install Menu", pos=(-1, 10)) - title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - title_label.Centre(wx.HORIZONTAL) - - # Label: Available patches: - available_label = wx.StaticText(frame, label="Available patches for your system:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10)) - available_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - available_label.Centre(wx.HORIZONTAL) - - # Labels: {patch name} - patches: dict = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() if not patches else patches - can_unpatch: bool = patches["Validation: Unpatching Possible"] - - if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): - logging.info("No applicable patches available") - patches = [] - - # Check if OCLP has already applied the same patches - no_new_patches = not self._check_if_new_patches_needed(patches) if patches else False - - if not patches: - # Prompt user with no patches found - patch_label = wx.StaticText(frame, label="No patches required", pos=(-1, available_label.GetPosition()[1] + 20)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - patch_label.Centre(wx.HORIZONTAL) - - else: - # Add Label for each patch - i = 0 - if no_new_patches is True: - patch_label = wx.StaticText(frame, label="All applicable patches already installed", pos=(-1, available_label.GetPosition()[1] + 20)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - patch_label.Centre(wx.HORIZONTAL) - i = i + 20 - else: - longest_patch = "" - for patch in patches: - if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): - if len(patch) > len(longest_patch): - longest_patch = patch - anchor = wx.StaticText(frame, label=longest_patch, pos=(-1, available_label.GetPosition()[1] + 20)) - anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - anchor.Centre(wx.HORIZONTAL) - anchor.Hide() - - logging.info("Available patches:") - for patch in patches: - if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): - i = i + 20 - logging.info(f"- {patch}") - patch_label = wx.StaticText(frame, label=f"- {patch}", pos=(anchor.GetPosition()[0], available_label.GetPosition()[1] + i)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - - if i == 20: - patch_label.SetLabel(patch_label.GetLabel().replace("-", "")) - patch_label.Centre(wx.HORIZONTAL) - - if patches["Validation: Patching Possible"] is False: - # Cannot patch due to the following reasons: - patch_label = wx.StaticText(frame, label="Cannot patch due to the following reasons:", pos=(-1, patch_label.GetPosition()[1] + 25)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - patch_label.Centre(wx.HORIZONTAL) - - longest_patch = "" - for patch in patches: - if not patch.startswith("Validation"): - continue - if patches[patch] is False: - continue - if patch == "Validation: Unpatching Possible": - continue - - if len(patch) > len(longest_patch): - longest_patch = patch - anchor = wx.StaticText(frame, label=longest_patch.split('Validation: ')[1], pos=(-1, patch_label.GetPosition()[1] + 20)) - anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - anchor.Centre(wx.HORIZONTAL) - anchor.Hide() - - i = 0 - for patch in patches: - if not patch.startswith("Validation"): - continue - if patches[patch] is False: - continue - if patch == "Validation: Unpatching Possible": - continue - - patch_label = wx.StaticText(frame, label=f"- {patch.split('Validation: ')[1]}", pos=(anchor.GetPosition()[0], anchor.GetPosition()[1] + i)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - i = i + 20 - - if i == 20: - patch_label.SetLabel(patch_label.GetLabel().replace("-", "")) - patch_label.Centre(wx.HORIZONTAL) - - else: - if self.constants.computer.oclp_sys_version and self.constants.computer.oclp_sys_date: - date = self.constants.computer.oclp_sys_date.split(" @") - date = date[0] if len(date) == 2 else "" - - patch_text = f"{self.constants.computer.oclp_sys_version}, {date}" - - patch_label = wx.StaticText(frame, label="Root Volume last patched:", pos=(-1, patch_label.GetPosition().y + 25)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - patch_label.Centre(wx.HORIZONTAL) - - patch_label = wx.StaticText(frame, label=patch_text, pos=(available_label.GetPosition().x - 10, patch_label.GetPosition().y + 20)) - patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - patch_label.Centre(wx.HORIZONTAL) - - - # Button: Start Root Patching - start_button = wx.Button(frame, label="Start Root Patching", pos=(10, patch_label.GetPosition().y + 25), size=(170, 30)) - start_button.Bind(wx.EVT_BUTTON, lambda event: self.start_root_patching(patches)) - start_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - start_button.Centre(wx.HORIZONTAL) - - # Button: Revert Root Patches - revert_button = wx.Button(frame, label="Revert Root Patches", pos=(10, start_button.GetPosition().y + start_button.GetSize().height - 5), size=(170, 30)) - revert_button.Bind(wx.EVT_BUTTON, lambda event: self.revert_root_patching(patches)) - revert_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - revert_button.Centre(wx.HORIZONTAL) - - # Button: Return to Main Menu - return_button = wx.Button(frame, label="Return to Main Menu", pos=(10, revert_button.GetPosition().y + revert_button.GetSize().height), size=(150, 30)) - return_button.Bind(wx.EVT_BUTTON, self.on_return_dismiss if self.initiated_with_parent else self.on_return_to_main_menu) - return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - return_button.Centre(wx.HORIZONTAL) - self.return_button = return_button - - # Disable buttons if unsupported - if not patches: - start_button.Disable() - else: - self.available_patches = True - if patches["Validation: Patching Possible"] is False: - start_button.Disable() - elif no_new_patches is False: - start_button.SetDefault() - else: - self.available_patches = False - if can_unpatch is False: - revert_button.Disable() - - # Relaunch as root if not root - uid = os.geteuid() - if uid != 0: - start_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) - revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) - - # Set frame size - frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 35)) - - def _generate_modal(self, patches: dict = {}, variant: str = "Root Patching"): """ Create UI for root patching/unpatching @@ -389,27 +211,21 @@ class SysPatchFrame(wx.Frame): dialog.ShowWindowModal() - def start_root_patching(self, patches: dict): - self.frame.Close() if self.frame else None - super(SysPatchFrame, self).__init__(None, title=self.title, size=(350, 260), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) - gui_support.GenerateMenubar(self, self.constants).generate() - self.Centre() - self.return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) if self.return_button else None - + def start_root_patching(self): logging.info("Starting root patching") while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False: wx.Yield() - if patches["Settings: Kernel Debug Kit missing"] is True: + if self.patches["Settings: Kernel Debug Kit missing"] is True: if self._kdk_download(self) is False: self.on_return_to_main_menu() return - self._generate_modal(patches, "Root Patching") + self._generate_modal(self.patches, "Root Patching") self.return_button.Disable() - thread = threading.Thread(target=self._start_root_patching, args=(patches,)) + thread = threading.Thread(target=self._start_root_patching, args=(self.patches,)) thread.start() while thread.is_alive(): @@ -430,18 +246,13 @@ class SysPatchFrame(wx.Frame): logger.removeHandler(logger.handlers[2]) - def revert_root_patching(self, patches: dict): - self.frame.Close() if self.frame else None - super(SysPatchFrame, self).__init__(None, title=self.title, size=(350, 260), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) - gui_support.GenerateMenubar(self, self.constants).generate() - self.Centre() - self.return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) if self.return_button else None - + def revert_root_patching(self): logging.info("Reverting root patches") - self._generate_modal(patches, "Revert Root Patches") + + self._generate_modal(self.patches, "Revert Root Patches") self.return_button.Disable() - thread = threading.Thread(target=self._revert_root_patching, args=(patches,)) + thread = threading.Thread(target=self._revert_root_patching, args=(self.patches,)) thread.start() while thread.is_alive(): From d8904bb3c0b0c3f4140ad141981052c16b76ba98 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 13:36:08 -0600 Subject: [PATCH 08/28] GUI: Add handling for missing parent --- resources/wx_gui/gui_sys_patch_display.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py index 0d305dc5d..c301eb7bf 100644 --- a/resources/wx_gui/gui_sys_patch_display.py +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -29,12 +29,16 @@ class SysPatchDisplayFrame(wx.Frame): if parent: self.frame = parent else: - self.frame = wx.Frame.__init__(self, parent, title=title, size=(360, 200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) + super().__init__(parent, title=title, size=(360, 200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) + self.frame = self + self.frame.Centre() + self.title = title self.constants: constants.Constants = global_constants self.frame_modal: wx.Dialog = None self.return_button: wx.Button = None self.available_patches: bool = False + self.init_with_parent = True if parent else False self.frame_modal = wx.Dialog(self.frame, title=title, size=(360, 200)) @@ -186,7 +190,7 @@ class SysPatchDisplayFrame(wx.Frame): # Button: Return to Main Menu return_button = wx.Button(frame, label="Return to Main Menu", pos=(10, revert_button.GetPosition().y + revert_button.GetSize().height), size=(150, 30)) - return_button.Bind(wx.EVT_BUTTON, self.on_return_dismiss) + return_button.Bind(wx.EVT_BUTTON, self.on_return_dismiss if self.init_with_parent else self.on_return_to_main_menu) return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) return_button.Centre(wx.HORIZONTAL) self.return_button = return_button From 01d666e2c3a720cf7e76e90c01bf02249e03c00d Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 13:49:15 -0600 Subject: [PATCH 09/28] GUI: Add handling for lack of parent --- resources/wx_gui/gui_sys_patch_display.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py index c301eb7bf..e4afd0d92 100644 --- a/resources/wx_gui/gui_sys_patch_display.py +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -226,7 +226,7 @@ class SysPatchDisplayFrame(wx.Frame): patches=patches, ) frame.start_root_patching() - self.on_return_dismiss() + self.on_return_dismiss() if self.init_with_parent else self.on_return_to_main_menu() def on_revert_root_patching(self, patches: dict): @@ -237,7 +237,7 @@ class SysPatchDisplayFrame(wx.Frame): patches=patches, ) frame.revert_root_patching() - self.on_return_dismiss() + self.on_return_dismiss() if self.init_with_parent else self.on_return_to_main_menu() def on_return_to_main_menu(self, event: wx.Event = None): From ea3ff5d3b65a5c8aab321ba0176448dfa3bd9399 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 20:01:12 -0600 Subject: [PATCH 10/28] GUI: Add progress bar for patch fetching --- resources/wx_gui/gui_sys_patch_display.py | 44 ++++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py index e4afd0d92..d909477fd 100644 --- a/resources/wx_gui/gui_sys_patch_display.py +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -1,8 +1,10 @@ import wx import os +import time import logging import plistlib +import threading from pathlib import Path @@ -43,7 +45,6 @@ class SysPatchDisplayFrame(wx.Frame): self.frame_modal = wx.Dialog(self.frame, title=title, size=(360, 200)) self._generate_elements_display_patches(self.frame_modal) - self.frame_modal.ShowWindowModal() if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: if self.available_patches is False: @@ -68,13 +69,45 @@ class SysPatchDisplayFrame(wx.Frame): title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) title_label.Centre(wx.HORIZONTAL) - # Label: Available patches: - available_label = wx.StaticText(frame, label="Available patches for your system:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10)) + # Label: Fetching patches... + available_label = wx.StaticText(frame, label="Fetching patches for host", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10)) available_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) available_label.Centre(wx.HORIZONTAL) + # Progress bar + progress_bar = wx.Gauge(frame, range=100, pos=(-1, available_label.GetPosition()[1] + available_label.GetSize()[1] + 10), size=(250, 20)) + progress_bar.Centre(wx.HORIZONTAL) + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + # Set window height + frame.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + # Labels: {patch name} - patches: dict = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() + patches: dict = {} + def _fetch_patches(self) -> None: + nonlocal patches + patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() + time.sleep(5) + + thread = threading.Thread(target=_fetch_patches, args=(self,)) + thread.start() + + frame.ShowWindowModal() + + while thread.is_alive(): + wx.Yield() + + + frame.Close() + + progress_bar.Hide() + progress_bar_animation.stop_pulse() + + available_label.SetLabel("Available patches for your system:") + available_label.Centre(wx.HORIZONTAL) + + can_unpatch: bool = patches["Validation: Unpatching Possible"] if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): @@ -215,7 +248,8 @@ class SysPatchDisplayFrame(wx.Frame): revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) # Set frame size - frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 35)) + frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 15)) + frame.ShowWindowModal() def on_start_root_patching(self, patches: dict): From 48cf833cb8d07ab38c0518b756c3187f26e55c68 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 27 May 2023 20:03:02 -0600 Subject: [PATCH 11/28] GUI: Strip sleep test --- resources/wx_gui/gui_sys_patch_display.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py index d909477fd..6129a11dd 100644 --- a/resources/wx_gui/gui_sys_patch_display.py +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -1,7 +1,6 @@ import wx import os -import time import logging import plistlib import threading @@ -88,7 +87,6 @@ class SysPatchDisplayFrame(wx.Frame): def _fetch_patches(self) -> None: nonlocal patches patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() - time.sleep(5) thread = threading.Thread(target=_fetch_patches, args=(self,)) thread.start() From abea113272985dce7f1989205813365368a1f0ce Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sun, 28 May 2023 09:15:33 -0600 Subject: [PATCH 12/28] GUI: Move Centre() to after init --- resources/wx_gui/gui_sys_patch_start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index 565d798a5..f92c87845 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -43,7 +43,6 @@ class SysPatchStartFrame(wx.Frame): super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - self.Centre() if self.patches == {}: self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() @@ -209,6 +208,7 @@ class SysPatchStartFrame(wx.Frame): dialog.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 33)) self.frame_modal = dialog dialog.ShowWindowModal() + self.Centre() def start_root_patching(self): From 597a4a6e146a3a3fb000137958c224d9cc0364d0 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sun, 28 May 2023 09:52:45 -0600 Subject: [PATCH 13/28] GUI: Log window sizing --- resources/wx_gui/gui_sys_patch_start.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index f92c87845..ffef152a9 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -44,6 +44,22 @@ class SysPatchStartFrame(wx.Frame): super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() + + logging.info("Display Properties before CentreOnScreen() call:") + logging.info(f" Screen Size: {wx.DisplaySize()}") + logging.info(f" Screen Position: {wx.Display().GetGeometry().GetPosition()}") + logging.info(f" Frame Size: {self.GetSize()}") + logging.info(f" Frame Position: {self.GetPosition()}") + + self.CentreOnScreen() + + logging.info("Display Properties after CentreOnScreen() call:") + logging.info(f" Screen Size: {wx.DisplaySize()}") + logging.info(f" Screen Position: {wx.Display().GetGeometry().GetPosition()}") + logging.info(f" Frame Size: {self.GetSize()}") + logging.info(f" Frame Position: {self.GetPosition()}") + + if self.patches == {}: self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() @@ -208,7 +224,6 @@ class SysPatchStartFrame(wx.Frame): dialog.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 33)) self.frame_modal = dialog dialog.ShowWindowModal() - self.Centre() def start_root_patching(self): From 80e836e3bb1b8c96654587e80ad3ef50e0b04ecf Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sun, 28 May 2023 10:57:05 -0600 Subject: [PATCH 14/28] gui_support.py: Implement manual window centreing --- CHANGELOG.md | 1 + resources/wx_gui/gui_about.py | 4 +- resources/wx_gui/gui_build.py | 2 +- resources/wx_gui/gui_install_oc.py | 2 +- .../wx_gui/gui_macos_installer_download.py | 2 +- resources/wx_gui/gui_macos_installer_flash.py | 3 +- resources/wx_gui/gui_main_menu.py | 2 +- resources/wx_gui/gui_support.py | 50 ++++++++++++++++++- resources/wx_gui/gui_sys_patch_display.py | 2 +- resources/wx_gui/gui_sys_patch_start.py | 17 +------ resources/wx_gui/gui_update.py | 6 +-- 11 files changed, 63 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3adef5b9..bbca9e594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Resolve SharedSupport.dmg pathing error during macOS Installer Verification - Applicable to systems with 2 (or more) USB Installers with the same name plugged in - Resolve payloads path being mis-routed during CLI calls + - Add UI when fetching root patches for host ## 0.6.6 - Implement option to disable ColorSync downgrade on HD 3000 Macs diff --git a/resources/wx_gui/gui_about.py b/resources/wx_gui/gui_about.py index 1bb18be05..611675de6 100644 --- a/resources/wx_gui/gui_about.py +++ b/resources/wx_gui/gui_about.py @@ -4,6 +4,8 @@ import wx import wx.adv import logging +from resources.wx_gui import gui_support + from resources import constants @@ -16,7 +18,7 @@ class AboutFrame(wx.Frame): logging.info("Generating About frame") super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) self.constants: constants.Constants = global_constants - self.Centre() + gui_support.Centre(self, self.constants) self.hyperlink_colour = (25, 179, 231) self._generate_elements(self) diff --git a/resources/wx_gui/gui_build.py b/resources/wx_gui/gui_build.py index 5dafddf26..14041299b 100644 --- a/resources/wx_gui/gui_build.py +++ b/resources/wx_gui/gui_build.py @@ -38,7 +38,7 @@ class BuildFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.BUILDING - self.Centre() + gui_support.Centre(self, self.constants) self.frame_modal.ShowWindowModal() self._invoke_build() diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py index 70c2cbcb0..ae14bfd52 100644 --- a/resources/wx_gui/gui_install_oc.py +++ b/resources/wx_gui/gui_install_oc.py @@ -33,7 +33,7 @@ class InstallOCFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.INSTALLING - self.Centre() + gui_support.Centre(self, self.constants) self.Show() self._display_disks() diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py index adc569164..e92370fdc 100644 --- a/resources/wx_gui/gui_macos_installer_download.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -84,7 +84,7 @@ class macOSInstallerDownloadFrame(wx.Frame): """ super(macOSInstallerDownloadFrame, self).__init__(None, title=self.title, size=(300, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - self.Centre() + gui_support.Centre(self, self.constants) # Title: Pulling installer catalog title_label = wx.StaticText(self, label="Pulling installer catalog", pos=(-1,5)) diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index fef0c066e..4fb518eb3 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -39,7 +39,7 @@ class macOSInstallerFlashFrame(wx.Frame): self._generate_elements() - self.Centre() + gui_support.Centre(self, self.constants) self.Show() self._populate_installers() @@ -79,7 +79,6 @@ class macOSInstallerFlashFrame(wx.Frame): wx.Yield() frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) - frame_modal.Centre(wx.HORIZONTAL) # Title: Select macOS Installer title_label = wx.StaticText(frame_modal, label="Select local macOS Installer", pos=(-1,5)) diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 8a574abd8..1c8de792e 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -39,7 +39,7 @@ class MainFrame(wx.Frame): self._generate_elements() - self.Centre() + gui_support.Centre(self, self.constants) self.Show() self._preflight_checks() diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index 504c889a8..689f6f142 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -50,7 +50,7 @@ class GenerateMenubar: class GaugePulseCallback: """ - Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+ + Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+ with non-Metal GPUs Dirty hack, however better to display some form of animation than none at all """ @@ -101,6 +101,54 @@ class GaugePulseCallback: time.sleep(0.005) +class Centre: + """ + Alternative to wx.Frame.Centre() for macOS Ventura+ on non-Metal GPUs + ---------- + As reported by socamx#3874, all of their non-Metal Mac minis would incorrectly centre + at the top of the screen, rather than the screen centre. Half of the window frame would + be off-screen, making it rather difficult to grab frame handle and move the window. + + This bug is only triggered when the application is launched via AppleScript, + ie. Relaunch as root for root patching. + + As calculating screen centre is trivial, this is safe for all non-Metal Macs. + + Test unit specs: + - Macmini4,1 (2010, Nvidia Tesla) + - Asus VP228HE 21.5" Display (1920x1080) + """ + + def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: + self.frame: wx.Frame = frame + self.constants: constants.Constants = global_constants + + self._centre() + + + def _centre(self) -> None: + """ + Calculate centre position of screen and set window position + """ + if self.constants.detected_os < os_data.os_data.ventura: + self.frame.Centre() + return + + if CheckProperties(self.constants).host_is_non_metal() is False: + self.frame.Centre() + return + + # Get screen resolution + screen_resolution = wx.DisplaySize() + window_size = self.frame.GetSize() + + # Calculate window position + x_pos = int((screen_resolution[0] - window_size[0]) / 2) + y_pos = int((screen_resolution[1] - window_size[1]) / 2) + + self.frame.SetPosition((x_pos, y_pos)) + + class CheckProperties: def __init__(self, global_constants: constants.Constants) -> None: diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py index 6129a11dd..04d155b27 100644 --- a/resources/wx_gui/gui_sys_patch_display.py +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -32,7 +32,7 @@ class SysPatchDisplayFrame(wx.Frame): else: super().__init__(parent, title=title, size=(360, 200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) self.frame = self - self.frame.Centre() + gui_support.Centre(self.frame, self.constants) self.title = title self.constants: constants.Constants = global_constants diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index ffef152a9..153fa460a 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -43,22 +43,7 @@ class SysPatchStartFrame(wx.Frame): super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - - - logging.info("Display Properties before CentreOnScreen() call:") - logging.info(f" Screen Size: {wx.DisplaySize()}") - logging.info(f" Screen Position: {wx.Display().GetGeometry().GetPosition()}") - logging.info(f" Frame Size: {self.GetSize()}") - logging.info(f" Frame Position: {self.GetPosition()}") - - self.CentreOnScreen() - - logging.info("Display Properties after CentreOnScreen() call:") - logging.info(f" Screen Size: {wx.DisplaySize()}") - logging.info(f" Screen Position: {wx.Display().GetGeometry().GetPosition()}") - logging.info(f" Frame Size: {self.GetSize()}") - logging.info(f" Frame Position: {self.GetPosition()}") - + gui_support.Centre(self, self.constants) if self.patches == {}: self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py index b3e862ef0..d9b685686 100644 --- a/resources/wx_gui/gui_update.py +++ b/resources/wx_gui/gui_update.py @@ -38,10 +38,10 @@ class UpdateFrame(wx.Frame): self.application_path = self.constants.payload_path / "OpenCore-Patcher.app" self.screen_location: wx.Point = screen_location if parent: - self.parent.Centre() + gui_support.Centre(self.parent, self.constants) self.screen_location = parent.GetScreenPosition() else: - self.Centre() + gui_support.Centre(self, self.constants) self.screen_location = self.GetScreenPosition() @@ -82,7 +82,7 @@ class UpdateFrame(wx.Frame): self.progress_bar = progress_bar self.progress_bar_animation = progress_bar_animation - self.frame.Centre() + gui_support.Centre(self.frame, self.constants) self.frame.Show() wx.Yield() From bfcabb3a4a9a690b48b9c0619900809d5a143417 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sun, 28 May 2023 13:13:49 -0600 Subject: [PATCH 15/28] logging_handler.py: Add support for sending crash reports --- resources/analytics_handler.py | 54 +++++++++++++++++++++++++++------- resources/logging_handler.py | 25 ++++++++++++---- resources/main.py | 2 +- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/resources/analytics_handler.py b/resources/analytics_handler.py index 64e276966..d72a16f4b 100644 --- a/resources/analytics_handler.py +++ b/resources/analytics_handler.py @@ -9,8 +9,9 @@ from resources import network_handler, constants, global_settings DATE_FORMAT: str = "%Y-%m-%d %H-%M-%S" ANALYTICS_SERVER: str = "" SITE_KEY: str = "" +CRASH_URL: str = ANALYTICS_SERVER + "/crash" -VALID_ENTRIES: dict = { +VALID_ANALYTICS_ENTRIES: dict = { 'KEY': str, # Prevent abuse (embedded at compile time) 'UNIQUE_IDENTITY': str, # Host's UUID as SHA1 hash 'APPLICATION_NAME': str, # ex. OpenCore Legacy Patcher @@ -23,17 +24,56 @@ VALID_ENTRIES: dict = { 'TIMESTAMP': datetime.datetime, # ex. 2021-09-01-12-00-00 } +VALID_CRASH_ENTRIES: dict = { + 'KEY': str, # Prevent abuse (embedded at compile time) + 'APPLICATION_VERSION': str, # ex. 0.2.0 + 'OS_VERSION': str, # ex. 10.15.7 + 'MODEL': str, # ex. MacBookPro11,5 + 'TIMESTAMP': datetime.datetime, # ex. 2021-09-01-12-00-00 + 'CRASH_LOG': str, # ex. "This is a crash log" +} + class Analytics: def __init__(self, global_constants: constants.Constants) -> None: self.constants: constants.Constants = global_constants + self.unique_identity = str(self.constants.computer.uuid_sha1) + self.application = str("OpenCore Legacy Patcher") + self.version = str(self.constants.patcher_version) + self.os = str(self.constants.detected_os_version) + self.model = str(self.constants.computer.real_model) + self.date = str(datetime.datetime.now().strftime(DATE_FORMAT)) + + def send_analytics(self) -> None: if global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") is True: return self._generate_base_data() - self._post_data() + self._post_analytics_data() + + + def send_crash_report(self, log_file: Path) -> None: + if ANALYTICS_SERVER == "": + return + if SITE_KEY == "": + return + if global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") is True: + return + if not log_file.exists(): + return + + crash_data= { + "KEY": SITE_KEY, + "APPLICATION_VERSION": self.version, + "OS_VERSION": self.os, + "MODEL": self.model, + "TIMESTAMP": self.date, + "CRASH_LOG": log_file.read_text() + } + + network_handler.NetworkUtilities().post(CRASH_URL, json = crash_data) def _get_country(self) -> str: @@ -54,12 +94,6 @@ class Analytics: def _generate_base_data(self) -> None: - - self.unique_identity = str(self.constants.computer.uuid_sha1) - self.application = str("OpenCore Legacy Patcher") - self.version = str(self.constants.patcher_version) - self.os = str( self.constants.detected_os_version) - self.model = str(self.constants.computer.real_model) self.gpus = [] self.firmware = str(self.constants.computer.firmware_vendor) @@ -78,14 +112,14 @@ class Analytics: 'GPUS': self.gpus, 'FIRMWARE': self.firmware, 'LOCATION': self.location, - 'TIMESTAMP': str(datetime.datetime.now().strftime(DATE_FORMAT)), + 'TIMESTAMP': self.date, } # convert to JSON: self.data = json.dumps(self.data) - def _post_data(self) -> None: + def _post_analytics_data(self) -> None: # Post data to analytics server if ANALYTICS_SERVER == "": return diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 32ab74a04..e9dc3a6a0 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -9,7 +9,7 @@ import applescript from pathlib import Path -from resources import constants +from resources import constants, analytics_handler class InitializeLoggingSupport: @@ -210,6 +210,12 @@ class InitializeLoggingSupport: """ logging.error("Uncaught exception in main thread", exc_info=(type, value, tb)) + logging.info("System Information:") + logging.info(f" Host Model Identifier: {self.constants.computer.real_model}") + logging.info(f" macOS Version: {self.constants.detected_os_version}") + logging.info(f" Patcher Version: {self.constants.patcher_version}") + logging.info(f" Arguments passed to Patcher: {sys.argv}") + if self.constants.cli_mode is True: return @@ -217,12 +223,21 @@ class InitializeLoggingSupport: error_msg += f"{type.__name__}: {value}" if tb: error_msg += f"\n\n{traceback.extract_tb(tb)[-1]}" - error_msg += "\n\nPlease report this error on our Discord server." + error_msg += "\n\nSend crash report to Dortania?" - applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"OK"}} default button "OK" with icon caution giving up after 30').run() + # applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"OK"}} default button "OK" with icon caution giving up after 30').run() - # Open log location - subprocess.run(["open", "--reveal", self.log_filepath]) + # Ask user if they want to send crash report + try: + result = applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"Yes", "No"}} default button "Yes" with icon caution giving up after 30').run() + except Exception as e: + logging.error(f"Failed to display crash report dialog: {e}") + return + + if result[applescript.AEType(b'bhit')] != "Yes": + return + + threading.Thread(target=analytics_handler.Analytics(self.constants).send_crash_report, args=(self.log_filepath,)).start() def custom_thread_excepthook(args) -> None: diff --git a/resources/main.py b/resources/main.py index e6fb5d9f6..e0448c12e 100644 --- a/resources/main.py +++ b/resources/main.py @@ -92,7 +92,7 @@ class OpenCoreLegacyPatcher: # Generate defaults defaults.GenerateDefaults(self.computer.real_model, True, self.constants) - threading.Thread(target=analytics_handler.Analytics, args=(self.constants,)).start() + threading.Thread(target=analytics_handler.Analytics(self.constants).send_analytics).start() if utilities.check_cli_args() is None: self.constants.cli_mode = False From a6879ede75a24de26d52e1ce6cc38f8013e08d92 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sun, 28 May 2023 14:37:34 -0600 Subject: [PATCH 16/28] logging_handler.py: Add debug info for crashes --- resources/logging_handler.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/resources/logging_handler.py b/resources/logging_handler.py index e9dc3a6a0..3dda566a2 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -1,6 +1,7 @@ import os import sys import time +import pprint import logging import threading import traceback @@ -209,12 +210,7 @@ class InitializeLoggingSupport: Reroute traceback in main thread to logging module """ logging.error("Uncaught exception in main thread", exc_info=(type, value, tb)) - - logging.info("System Information:") - logging.info(f" Host Model Identifier: {self.constants.computer.real_model}") - logging.info(f" macOS Version: {self.constants.detected_os_version}") - logging.info(f" Patcher Version: {self.constants.patcher_version}") - logging.info(f" Arguments passed to Patcher: {sys.argv}") + self._display_debug_properties() if self.constants.cli_mode is True: return @@ -257,3 +253,19 @@ class InitializeLoggingSupport: sys.excepthook = self.original_excepthook threading.excepthook = self.original_thread_excepthook + + + def _display_debug_properties(self) -> None: + """ + Display debug properties, primarily after main thread crash + """ + logging.info("Debug Properties:") + logging.info(f" Effective User ID: {os.geteuid()}") + logging.info(f" Effective Group ID: {os.getegid()}") + logging.info(f" Real User ID: {os.getuid()}") + logging.info(f" Real Group ID: {os.getgid()}") + logging.info(" Arguments passed to Patcher:") + for arg in sys.argv: + logging.info(f" {arg}") + + logging.info(f"Host Properties:\n{pprint.pformat(self.constants.computer.__dict__, indent=4)}") From 984ba65d413d741502dda4118614fb966d0c5e8f Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 09:12:04 -0600 Subject: [PATCH 17/28] GUI: Add delay handler --- resources/wx_gui/gui_entry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_entry.py b/resources/wx_gui/gui_entry.py index c2bcda0ff..306de034d 100644 --- a/resources/wx_gui/gui_entry.py +++ b/resources/wx_gui/gui_entry.py @@ -1,10 +1,11 @@ # Entry point for the wxPython GUI import wx import sys +import time import atexit import logging -from resources import constants +from resources import constants, global_settings from resources.wx_gui import ( gui_main_menu, gui_build, @@ -37,6 +38,10 @@ class EntryPoint: def _generate_base_data(self) -> None: + delay = global_settings.GlobalEnviromentSettings().read_property("APP_DELAY_TIME") + if not isinstance(delay, int or float): + delay = 1 + time.sleep(delay) self.app = wx.App() self.app.SetAppName(self.constants.patcher_name) From 0388eaa6f2a4594602a22b9bba192819436f90a6 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 09:28:25 -0600 Subject: [PATCH 18/28] GUI: Strip Centre() work-around --- resources/wx_gui/gui_about.py | 2 +- resources/wx_gui/gui_build.py | 2 +- resources/wx_gui/gui_install_oc.py | 2 +- .../wx_gui/gui_macos_installer_download.py | 2 +- resources/wx_gui/gui_macos_installer_flash.py | 2 +- resources/wx_gui/gui_main_menu.py | 2 +- resources/wx_gui/gui_support.py | 48 ------------------- resources/wx_gui/gui_sys_patch_display.py | 2 +- resources/wx_gui/gui_sys_patch_start.py | 2 +- resources/wx_gui/gui_update.py | 6 +-- 10 files changed, 11 insertions(+), 59 deletions(-) diff --git a/resources/wx_gui/gui_about.py b/resources/wx_gui/gui_about.py index 611675de6..73b0c84a1 100644 --- a/resources/wx_gui/gui_about.py +++ b/resources/wx_gui/gui_about.py @@ -18,7 +18,7 @@ class AboutFrame(wx.Frame): logging.info("Generating About frame") super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) self.constants: constants.Constants = global_constants - gui_support.Centre(self, self.constants) + self.Centre() self.hyperlink_colour = (25, 179, 231) self._generate_elements(self) diff --git a/resources/wx_gui/gui_build.py b/resources/wx_gui/gui_build.py index 14041299b..5dafddf26 100644 --- a/resources/wx_gui/gui_build.py +++ b/resources/wx_gui/gui_build.py @@ -38,7 +38,7 @@ class BuildFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.BUILDING - gui_support.Centre(self, self.constants) + self.Centre() self.frame_modal.ShowWindowModal() self._invoke_build() diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py index ae14bfd52..70c2cbcb0 100644 --- a/resources/wx_gui/gui_install_oc.py +++ b/resources/wx_gui/gui_install_oc.py @@ -33,7 +33,7 @@ class InstallOCFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.INSTALLING - gui_support.Centre(self, self.constants) + self.Centre() self.Show() self._display_disks() diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py index e92370fdc..adc569164 100644 --- a/resources/wx_gui/gui_macos_installer_download.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -84,7 +84,7 @@ class macOSInstallerDownloadFrame(wx.Frame): """ super(macOSInstallerDownloadFrame, self).__init__(None, title=self.title, size=(300, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - gui_support.Centre(self, self.constants) + self.Centre() # Title: Pulling installer catalog title_label = wx.StaticText(self, label="Pulling installer catalog", pos=(-1,5)) diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index 4fb518eb3..f1d5a5896 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -39,7 +39,7 @@ class macOSInstallerFlashFrame(wx.Frame): self._generate_elements() - gui_support.Centre(self, self.constants) + self.Centre() self.Show() self._populate_installers() diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 1c8de792e..8a574abd8 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -39,7 +39,7 @@ class MainFrame(wx.Frame): self._generate_elements() - gui_support.Centre(self, self.constants) + self.Centre() self.Show() self._preflight_checks() diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index 689f6f142..a8a88961b 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -101,54 +101,6 @@ class GaugePulseCallback: time.sleep(0.005) -class Centre: - """ - Alternative to wx.Frame.Centre() for macOS Ventura+ on non-Metal GPUs - ---------- - As reported by socamx#3874, all of their non-Metal Mac minis would incorrectly centre - at the top of the screen, rather than the screen centre. Half of the window frame would - be off-screen, making it rather difficult to grab frame handle and move the window. - - This bug is only triggered when the application is launched via AppleScript, - ie. Relaunch as root for root patching. - - As calculating screen centre is trivial, this is safe for all non-Metal Macs. - - Test unit specs: - - Macmini4,1 (2010, Nvidia Tesla) - - Asus VP228HE 21.5" Display (1920x1080) - """ - - def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: - self.frame: wx.Frame = frame - self.constants: constants.Constants = global_constants - - self._centre() - - - def _centre(self) -> None: - """ - Calculate centre position of screen and set window position - """ - if self.constants.detected_os < os_data.os_data.ventura: - self.frame.Centre() - return - - if CheckProperties(self.constants).host_is_non_metal() is False: - self.frame.Centre() - return - - # Get screen resolution - screen_resolution = wx.DisplaySize() - window_size = self.frame.GetSize() - - # Calculate window position - x_pos = int((screen_resolution[0] - window_size[0]) / 2) - y_pos = int((screen_resolution[1] - window_size[1]) / 2) - - self.frame.SetPosition((x_pos, y_pos)) - - class CheckProperties: def __init__(self, global_constants: constants.Constants) -> None: diff --git a/resources/wx_gui/gui_sys_patch_display.py b/resources/wx_gui/gui_sys_patch_display.py index 04d155b27..6129a11dd 100644 --- a/resources/wx_gui/gui_sys_patch_display.py +++ b/resources/wx_gui/gui_sys_patch_display.py @@ -32,7 +32,7 @@ class SysPatchDisplayFrame(wx.Frame): else: super().__init__(parent, title=title, size=(360, 200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) self.frame = self - gui_support.Centre(self.frame, self.constants) + self.frame.Centre() self.title = title self.constants: constants.Constants = global_constants diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index 153fa460a..565d798a5 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -43,7 +43,7 @@ class SysPatchStartFrame(wx.Frame): super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - gui_support.Centre(self, self.constants) + self.Centre() if self.patches == {}: self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py index d9b685686..b3e862ef0 100644 --- a/resources/wx_gui/gui_update.py +++ b/resources/wx_gui/gui_update.py @@ -38,10 +38,10 @@ class UpdateFrame(wx.Frame): self.application_path = self.constants.payload_path / "OpenCore-Patcher.app" self.screen_location: wx.Point = screen_location if parent: - gui_support.Centre(self.parent, self.constants) + self.parent.Centre() self.screen_location = parent.GetScreenPosition() else: - gui_support.Centre(self, self.constants) + self.Centre() self.screen_location = self.GetScreenPosition() @@ -82,7 +82,7 @@ class UpdateFrame(wx.Frame): self.progress_bar = progress_bar self.progress_bar_animation = progress_bar_animation - gui_support.Centre(self.frame, self.constants) + self.frame.Centre() self.frame.Show() wx.Yield() From e133beee3636f98d408ed35c9e3267d8179db01d Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 09:54:49 -0600 Subject: [PATCH 19/28] gui_sys_patch_start.py: Exit on KDK failure Nothing to recover from, best to close the app --- resources/wx_gui/gui_support.py | 1 + resources/wx_gui/gui_sys_patch_start.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index a8a88961b..343042c3e 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -106,6 +106,7 @@ class CheckProperties: def __init__(self, global_constants: constants.Constants) -> None: self.constants: constants.Constants = global_constants + def host_can_build(self): """ Check if host supports building OpenCore configs diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index 565d798a5..de8616324 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -104,6 +104,7 @@ class SysPatchStartFrame(wx.Frame): if kdk_download_obj.download_complete is False: return False + logging.info("KDK download complete, validating with hdiutil") header.SetLabel(f"Validating KDK: {self.kdk_obj.kdk_url_build}") header.Centre(wx.HORIZONTAL) @@ -219,8 +220,7 @@ class SysPatchStartFrame(wx.Frame): if self.patches["Settings: Kernel Debug Kit missing"] is True: if self._kdk_download(self) is False: - self.on_return_to_main_menu() - return + sys.exit(1) self._generate_modal(self.patches, "Root Patching") self.return_button.Disable() From cee8d8e76816b7089b1a29a2c542bd4ff15e8546 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 09:55:11 -0600 Subject: [PATCH 20/28] GUI: Fix delay logic --- resources/wx_gui/gui_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_entry.py b/resources/wx_gui/gui_entry.py index 306de034d..21ac2015d 100644 --- a/resources/wx_gui/gui_entry.py +++ b/resources/wx_gui/gui_entry.py @@ -39,7 +39,7 @@ class EntryPoint: def _generate_base_data(self) -> None: delay = global_settings.GlobalEnviromentSettings().read_property("APP_DELAY_TIME") - if not isinstance(delay, int or float): + if not (isinstance(delay, int) | isinstance(delay, float)): delay = 1 time.sleep(delay) self.app = wx.App() From 47a1c6a2e5dced473549c3e1d7b96eee4355af39 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 09:57:03 -0600 Subject: [PATCH 21/28] logging_handler.py: Add additional logging info --- resources/logging_handler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 3dda566a2..8a503350c 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -259,6 +259,10 @@ class InitializeLoggingSupport: """ Display debug properties, primarily after main thread crash """ + logging.info("Host Properties:") + logging.info(f" XNU Version: {self.constants.detected_os}.{self.constants.detected_os_minor}") + logging.info(f" XNU Build: {self.constants.detected_os_build}") + logging.info(f" macOS Version: {self.constants.detected_os_version}") logging.info("Debug Properties:") logging.info(f" Effective User ID: {os.geteuid()}") logging.info(f" Effective Group ID: {os.getegid()}") From 87234740bdad3c4b450762c60a80f315d8e5313d Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 10:02:54 -0600 Subject: [PATCH 22/28] =?UTF-8?q?logging=5Fhandler.py:=20Don=E2=80=99t=20a?= =?UTF-8?q?uto=20dismiss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/logging_handler.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 8a503350c..1f2a931f1 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -221,11 +221,9 @@ class InitializeLoggingSupport: error_msg += f"\n\n{traceback.extract_tb(tb)[-1]}" error_msg += "\n\nSend crash report to Dortania?" - # applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"OK"}} default button "OK" with icon caution giving up after 30').run() - # Ask user if they want to send crash report try: - result = applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"Yes", "No"}} default button "Yes" with icon caution giving up after 30').run() + result = applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"Yes", "No"}} default button "Yes" with icon caution').run() except Exception as e: logging.error(f"Failed to display crash report dialog: {e}") return From c62fefb575df69efd5ecd1b77e8dba49e1a362ff Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 10:10:15 -0600 Subject: [PATCH 23/28] GUI: Add error handling to int conversion --- resources/wx_gui/gui_macos_installer_flash.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index f1d5a5896..288052083 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -299,7 +299,11 @@ class macOSInstallerFlashFrame(wx.Frame): total_bytes_written = initial_bytes_written bytes_written = total_bytes_written - initial_bytes_written wx.CallAfter(bytes_written_label.SetLabel, f"Bytes Written: {bytes_written:.2f} MB") - wx.CallAfter(progress_bar.SetValue, int(bytes_written)) + try: + bytes_written = int(bytes_written) + except: + bytes_written = 0 + wx.CallAfter(progress_bar.SetValue, bytes_written) wx.Yield() if self.result is False: From d0eac893fe8929340f46f896247354bdef2c66ba Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 10:11:22 -0600 Subject: [PATCH 24/28] GUI: Move delay to sys_patch_start.py --- resources/wx_gui/gui_entry.py | 7 +------ resources/wx_gui/gui_sys_patch_start.py | 5 +++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/wx_gui/gui_entry.py b/resources/wx_gui/gui_entry.py index 21ac2015d..c2bcda0ff 100644 --- a/resources/wx_gui/gui_entry.py +++ b/resources/wx_gui/gui_entry.py @@ -1,11 +1,10 @@ # Entry point for the wxPython GUI import wx import sys -import time import atexit import logging -from resources import constants, global_settings +from resources import constants from resources.wx_gui import ( gui_main_menu, gui_build, @@ -38,10 +37,6 @@ class EntryPoint: def _generate_base_data(self) -> None: - delay = global_settings.GlobalEnviromentSettings().read_property("APP_DELAY_TIME") - if not (isinstance(delay, int) | isinstance(delay, float)): - delay = 1 - time.sleep(delay) self.app = wx.App() self.app.SetAppName(self.constants.patcher_name) diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index de8616324..b9e40da29 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -13,6 +13,7 @@ from pathlib import Path from resources import ( constants, kdk_handler, + global_settings, ) from resources.sys_patch import ( sys_patch, @@ -43,6 +44,10 @@ class SysPatchStartFrame(wx.Frame): super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() + delay = global_settings.GlobalEnviromentSettings().read_property("APP_DELAY_TIME") + if not (isinstance(delay, int) | isinstance(delay, float)): + delay = 1 + time.sleep(delay) self.Centre() if self.patches == {}: From 2435ea747c25574244ed000bab6c82c38a4f7cb4 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 11:14:37 -0600 Subject: [PATCH 25/28] Test custom centreing again --- resources/wx_gui/gui_about.py | 2 +- resources/wx_gui/gui_build.py | 2 +- resources/wx_gui/gui_install_oc.py | 2 +- .../wx_gui/gui_macos_installer_download.py | 2 +- resources/wx_gui/gui_macos_installer_flash.py | 2 +- resources/wx_gui/gui_main_menu.py | 3 +- resources/wx_gui/gui_support.py | 47 +++++++++++++++++++ resources/wx_gui/gui_sys_patch_start.py | 2 +- resources/wx_gui/gui_update.py | 2 +- 9 files changed, 56 insertions(+), 8 deletions(-) diff --git a/resources/wx_gui/gui_about.py b/resources/wx_gui/gui_about.py index 73b0c84a1..611675de6 100644 --- a/resources/wx_gui/gui_about.py +++ b/resources/wx_gui/gui_about.py @@ -18,7 +18,7 @@ class AboutFrame(wx.Frame): logging.info("Generating About frame") super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) self.constants: constants.Constants = global_constants - self.Centre() + gui_support.Centre(self, self.constants) self.hyperlink_colour = (25, 179, 231) self._generate_elements(self) diff --git a/resources/wx_gui/gui_build.py b/resources/wx_gui/gui_build.py index 5dafddf26..14041299b 100644 --- a/resources/wx_gui/gui_build.py +++ b/resources/wx_gui/gui_build.py @@ -38,7 +38,7 @@ class BuildFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.BUILDING - self.Centre() + gui_support.Centre(self, self.constants) self.frame_modal.ShowWindowModal() self._invoke_build() diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py index 70c2cbcb0..ae14bfd52 100644 --- a/resources/wx_gui/gui_install_oc.py +++ b/resources/wx_gui/gui_install_oc.py @@ -33,7 +33,7 @@ class InstallOCFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.INSTALLING - self.Centre() + gui_support.Centre(self, self.constants) self.Show() self._display_disks() diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py index adc569164..e92370fdc 100644 --- a/resources/wx_gui/gui_macos_installer_download.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -84,7 +84,7 @@ class macOSInstallerDownloadFrame(wx.Frame): """ super(macOSInstallerDownloadFrame, self).__init__(None, title=self.title, size=(300, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - self.Centre() + gui_support.Centre(self, self.constants) # Title: Pulling installer catalog title_label = wx.StaticText(self, label="Pulling installer catalog", pos=(-1,5)) diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index 288052083..45c75cc26 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -39,7 +39,7 @@ class macOSInstallerFlashFrame(wx.Frame): self._generate_elements() - self.Centre() + gui_support.Centre(self, self.constants) self.Show() self._populate_installers() diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 8a574abd8..073f80ed1 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -39,9 +39,10 @@ class MainFrame(wx.Frame): self._generate_elements() - self.Centre() + gui_support.Centre(self, self.constants) self.Show() + self._preflight_checks() diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index 343042c3e..d0c425950 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -23,6 +23,53 @@ class AutoUpdateStages: FINISHED = 5 +class Centre: + """ + Alternative to wx.Frame.Centre() for macOS Ventura+ on non-Metal GPUs + ---------- + As reported by socamx#3874, all of their non-Metal Mac minis would incorrectly centre + at the top of the screen, rather than the screen centre. Half of the window frame would + be off-screen, making it rather difficult to grab frame handle and move the window. + This bug is only triggered when the application is launched via AppleScript, + ie. Relaunch as root for root patching. + As calculating screen centre is trivial, this is safe for all non-Metal Macs. + Test unit specs: + - Macmini4,1 (2010, Nvidia Tesla) + - Asus VP228HE 21.5" Display (1920x1080) + """ + + def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: + self.frame: wx.Frame = frame + self.constants: constants.Constants = global_constants + + self._centre() + + + def _centre(self) -> None: + """ + Calculate centre position of screen and set window position + """ + if self.constants.detected_os < os_data.os_data.ventura: + self.frame.Centre() + return + + if CheckProperties(self.constants).host_is_non_metal() is False: + self.frame.Centre() + return + + # Get screen resolution + screen_resolution = wx.DisplaySize() + logging.info(f"Screen resolution: {screen_resolution}") + window_size = self.frame.GetSize() + logging.info(f"Window size: {window_size}") + + # Calculate window position + x_pos = int((screen_resolution[0] - window_size[0]) / 2) + y_pos = int((screen_resolution[1] - window_size[1]) / 2) + + self.frame.SetPosition((x_pos, y_pos)) + + class GenerateMenubar: def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index b9e40da29..9c10c7823 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -48,7 +48,7 @@ class SysPatchStartFrame(wx.Frame): if not (isinstance(delay, int) | isinstance(delay, float)): delay = 1 time.sleep(delay) - self.Centre() + gui_support.Centre(self, self.constants) if self.patches == {}: self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py index b3e862ef0..c463c74ae 100644 --- a/resources/wx_gui/gui_update.py +++ b/resources/wx_gui/gui_update.py @@ -41,7 +41,7 @@ class UpdateFrame(wx.Frame): self.parent.Centre() self.screen_location = parent.GetScreenPosition() else: - self.Centre() + gui_support.Centre(self, self.constants) self.screen_location = self.GetScreenPosition() From 6ab297423e3562bb29a38201eabf34cd557d3a60 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 12:46:28 -0600 Subject: [PATCH 26/28] Revert to old Centre logic --- resources/wx_gui/gui_about.py | 2 +- resources/wx_gui/gui_build.py | 2 +- resources/wx_gui/gui_install_oc.py | 2 +- .../wx_gui/gui_macos_installer_download.py | 2 +- resources/wx_gui/gui_macos_installer_flash.py | 2 +- resources/wx_gui/gui_main_menu.py | 2 +- resources/wx_gui/gui_support.py | 47 ------------------- resources/wx_gui/gui_sys_patch_start.py | 6 +-- resources/wx_gui/gui_update.py | 2 +- 9 files changed, 8 insertions(+), 59 deletions(-) diff --git a/resources/wx_gui/gui_about.py b/resources/wx_gui/gui_about.py index 611675de6..73b0c84a1 100644 --- a/resources/wx_gui/gui_about.py +++ b/resources/wx_gui/gui_about.py @@ -18,7 +18,7 @@ class AboutFrame(wx.Frame): logging.info("Generating About frame") super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) self.constants: constants.Constants = global_constants - gui_support.Centre(self, self.constants) + self.Centre() self.hyperlink_colour = (25, 179, 231) self._generate_elements(self) diff --git a/resources/wx_gui/gui_build.py b/resources/wx_gui/gui_build.py index 14041299b..5dafddf26 100644 --- a/resources/wx_gui/gui_build.py +++ b/resources/wx_gui/gui_build.py @@ -38,7 +38,7 @@ class BuildFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.BUILDING - gui_support.Centre(self, self.constants) + self.Centre() self.frame_modal.ShowWindowModal() self._invoke_build() diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py index ae14bfd52..70c2cbcb0 100644 --- a/resources/wx_gui/gui_install_oc.py +++ b/resources/wx_gui/gui_install_oc.py @@ -33,7 +33,7 @@ class InstallOCFrame(wx.Frame): if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: self.constants.update_stage = gui_support.AutoUpdateStages.INSTALLING - gui_support.Centre(self, self.constants) + self.Centre() self.Show() self._display_disks() diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py index e92370fdc..adc569164 100644 --- a/resources/wx_gui/gui_macos_installer_download.py +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -84,7 +84,7 @@ class macOSInstallerDownloadFrame(wx.Frame): """ super(macOSInstallerDownloadFrame, self).__init__(None, title=self.title, size=(300, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - gui_support.Centre(self, self.constants) + self.Centre() # Title: Pulling installer catalog title_label = wx.StaticText(self, label="Pulling installer catalog", pos=(-1,5)) diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py index 45c75cc26..288052083 100644 --- a/resources/wx_gui/gui_macos_installer_flash.py +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -39,7 +39,7 @@ class macOSInstallerFlashFrame(wx.Frame): self._generate_elements() - gui_support.Centre(self, self.constants) + self.Centre() self.Show() self._populate_installers() diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 073f80ed1..91b5502d7 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -39,7 +39,7 @@ class MainFrame(wx.Frame): self._generate_elements() - gui_support.Centre(self, self.constants) + self.Centre() self.Show() diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index d0c425950..343042c3e 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -23,53 +23,6 @@ class AutoUpdateStages: FINISHED = 5 -class Centre: - """ - Alternative to wx.Frame.Centre() for macOS Ventura+ on non-Metal GPUs - ---------- - As reported by socamx#3874, all of their non-Metal Mac minis would incorrectly centre - at the top of the screen, rather than the screen centre. Half of the window frame would - be off-screen, making it rather difficult to grab frame handle and move the window. - This bug is only triggered when the application is launched via AppleScript, - ie. Relaunch as root for root patching. - As calculating screen centre is trivial, this is safe for all non-Metal Macs. - Test unit specs: - - Macmini4,1 (2010, Nvidia Tesla) - - Asus VP228HE 21.5" Display (1920x1080) - """ - - def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: - self.frame: wx.Frame = frame - self.constants: constants.Constants = global_constants - - self._centre() - - - def _centre(self) -> None: - """ - Calculate centre position of screen and set window position - """ - if self.constants.detected_os < os_data.os_data.ventura: - self.frame.Centre() - return - - if CheckProperties(self.constants).host_is_non_metal() is False: - self.frame.Centre() - return - - # Get screen resolution - screen_resolution = wx.DisplaySize() - logging.info(f"Screen resolution: {screen_resolution}") - window_size = self.frame.GetSize() - logging.info(f"Window size: {window_size}") - - # Calculate window position - x_pos = int((screen_resolution[0] - window_size[0]) / 2) - y_pos = int((screen_resolution[1] - window_size[1]) / 2) - - self.frame.SetPosition((x_pos, y_pos)) - - class GenerateMenubar: def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: diff --git a/resources/wx_gui/gui_sys_patch_start.py b/resources/wx_gui/gui_sys_patch_start.py index 9c10c7823..6fe6483dc 100644 --- a/resources/wx_gui/gui_sys_patch_start.py +++ b/resources/wx_gui/gui_sys_patch_start.py @@ -44,11 +44,7 @@ class SysPatchStartFrame(wx.Frame): super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) gui_support.GenerateMenubar(self, self.constants).generate() - delay = global_settings.GlobalEnviromentSettings().read_property("APP_DELAY_TIME") - if not (isinstance(delay, int) | isinstance(delay, float)): - delay = 1 - time.sleep(delay) - gui_support.Centre(self, self.constants) + self.Centre() if self.patches == {}: self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py index c463c74ae..b3e862ef0 100644 --- a/resources/wx_gui/gui_update.py +++ b/resources/wx_gui/gui_update.py @@ -41,7 +41,7 @@ class UpdateFrame(wx.Frame): self.parent.Centre() self.screen_location = parent.GetScreenPosition() else: - gui_support.Centre(self, self.constants) + self.Centre() self.screen_location = self.GetScreenPosition() From 26fdb2ca2cf2f7c3804136237d5e962a775d1f00 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 29 May 2023 14:46:53 -0600 Subject: [PATCH 27/28] GUI: Force Pulse() usage on all --- resources/wx_gui/gui_support.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index 343042c3e..08c84166e 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -65,7 +65,8 @@ class GaugePulseCallback: self.max_value: int = gauge.GetRange() - self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal() + # self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal() + self.non_metal_alternative: bool = False def start_pulse(self) -> None: From 739c488698b18096b2d0a57d5ab5a6f5240fc846 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Tue, 30 May 2023 12:22:06 -0600 Subject: [PATCH 28/28] GUI: Disable Pulse() work-around on PSP 1.1.2 and newer --- CHANGELOG.md | 2 ++ resources/wx_gui/gui_support.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7733f83d8..3f3762285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ - Applicable to systems with 2 (or more) USB Installers with the same name plugged in - Resolve payloads path being mis-routed during CLI calls - Add UI when fetching root patches for host + - Remove progress bar work-around for non-Metal in Monterey and later + - Requires host to have been patched with PatcherSupportPkg 1.1.2 or newer - Increment Binaries: - PatcherSupportPkg 1.1.2 - release diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py index 08c84166e..c3a54a2fb 100644 --- a/resources/wx_gui/gui_support.py +++ b/resources/wx_gui/gui_support.py @@ -3,10 +3,13 @@ import os import sys import time import logging +import plistlib import threading import subprocess import applescript +import packaging.version + from pathlib import Path from resources.wx_gui import gui_about @@ -52,6 +55,8 @@ class GaugePulseCallback: """ Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+ with non-Metal GPUs Dirty hack, however better to display some form of animation than none at all + + Note: This work-around is no longer needed on hosts using PatcherSupportPkg 1.1.2 or newer """ def __init__(self, global_constants: constants.Constants, gauge: wx.Gauge) -> None: @@ -65,8 +70,10 @@ class GaugePulseCallback: self.max_value: int = gauge.GetRange() - # self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal() - self.non_metal_alternative: bool = False + self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal() + if self.non_metal_alternative is True: + if CheckProperties(global_constants).host_psp_version() >= packaging.version.Version("1.1.2"): + self.non_metal_alternative = False def start_pulse(self) -> None: @@ -140,6 +147,7 @@ class CheckProperties: return True + def host_has_cpu_gen(self, gen: int) -> bool: """ Check if host has a CPU generation equal to or greater than the specified generation @@ -151,6 +159,24 @@ class CheckProperties: return False + def host_psp_version(self) -> packaging.version.Version: + """ + Grab PatcherSupportPkg version from OpenCore-Legacy-Patcher.plist + """ + oclp_plist_path = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist" + if not Path(oclp_plist_path).exists(): + return packaging.version.Version("0.0.0") + + oclp_plist = plistlib.load(open(oclp_plist_path, "rb")) + if "PatcherSupportPkg" not in oclp_plist: + return packaging.version.Version("0.0.0") + + if oclp_plist["PatcherSupportPkg"].startswith("v"): + oclp_plist["PatcherSupportPkg"] = oclp_plist["PatcherSupportPkg"][1:] + + return packaging.version.parse(oclp_plist["PatcherSupportPkg"]) + + class PayloadMount: def __init__(self, global_constants: constants.Constants, frame: wx.Frame) -> None: