Files
OpenCore-Legacy-Patcher/resources/wx_gui/gui_support.py
2023-05-24 12:24:09 -06:00

303 lines
9.7 KiB
Python

import wx
import os
import sys
import time
import logging
import threading
import subprocess
import applescript
from pathlib import Path
from resources.wx_gui import gui_about
from resources import constants
from data import model_array, os_data, smbios_data
class AutoUpdateStages:
INACTIVE = 0
CHECKING = 1
BUILDING = 2
INSTALLING = 3
ROOT_PATCHING = 4
FINISHED = 5
class GenerateMenubar:
def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None:
self.frame: wx.Frame = frame
self.constants: constants.Constants = global_constants
def generate(self) -> wx.MenuBar:
menubar = wx.MenuBar()
fileMenu = wx.Menu()
aboutItem = fileMenu.Append(wx.ID_ABOUT, "&About OpenCore Legacy Patcher")
fileMenu.AppendSeparator()
relaunchItem = fileMenu.Append(wx.ID_ANY, "&Relaunch as Root")
menubar.Append(fileMenu, "&File")
self.frame.SetMenuBar(menubar)
self.frame.Bind(wx.EVT_MENU, lambda event: gui_about.AboutFrame(self.constants), aboutItem)
self.frame.Bind(wx.EVT_MENU, lambda event: RelaunchApplicationAsRoot(self.frame, self.constants).relaunch(None), relaunchItem)
if os.geteuid() == 0:
relaunchItem.Enable(False)
class GaugePulseCallback:
"""
Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+
Dirty hack, however better to display some form of animation than none at all
"""
def __init__(self, global_constants: constants.Constants, gauge: wx.Gauge) -> None:
self.gauge: wx.Gauge = gauge
self.pulse_thread: threading.Thread = None
self.pulse_thread_active: bool = False
self.gauge_value: int = 0
self.pulse_forward: bool = True
self.max_value: int = gauge.GetRange()
self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal()
def start_pulse(self) -> None:
if self.non_metal_alternative is False:
self.gauge.Pulse()
return
self.pulse_thread_active = True
self.pulse_thread = threading.Thread(target=self._pulse)
self.pulse_thread.start()
def stop_pulse(self) -> None:
if self.non_metal_alternative is False:
return
self.pulse_thread_active = False
self.pulse_thread.join()
def _pulse(self) -> None:
while self.pulse_thread_active:
if self.gauge_value == 0:
self.pulse_forward = True
elif self.gauge_value == self.max_value:
self.pulse_forward = False
if self.pulse_forward:
self.gauge_value += 1
else:
self.gauge_value -= 1
wx.CallAfter(self.gauge.SetValue, self.gauge_value)
time.sleep(0.005)
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
"""
if self.constants.custom_model:
return True
if self.constants.host_is_hackintosh is True:
return False
if self.constants.allow_oc_everywhere is True:
return True
if self.constants.computer.real_model in model_array.SupportedSMBIOS:
return True
return False
def host_is_non_metal(self, general_check: bool = False):
"""
Check if host is non-metal
Primarily for wx.Gauge().Pulse() workaround (where animation doesn't work on Monterey+)
"""
if self.constants.detected_os < os_data.os_data.monterey and general_check is False:
return False
if self.constants.detected_os < os_data.os_data.big_sur and general_check is True:
return False
if not Path("/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLightOld.dylib").exists():
# SkyLight stubs are only used on non-Metal
return False
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
"""
model = self.constants.custom_model if self.constants.custom_model else self.constants.computer.real_model
if model in smbios_data.smbios_dictionary:
if smbios_data.smbios_dictionary[model]["CPU Generation"] >= gen:
return True
return False
class PayloadMount:
def __init__(self, global_constants: constants.Constants, frame: wx.Frame) -> None:
self.constants: constants.Constants = global_constants
self.frame: wx.Frame = frame
def is_unpack_finished(self):
if self.constants.unpack_thread.is_alive():
return False
if Path(self.constants.payload_kexts_path).exists():
return True
# Raise error to end program
popup = wx.MessageDialog(
self.frame,
f"During unpacking of our internal files, we seemed to have encountered an error.\n\nIf you keep seeing this error, please try rebooting and redownloading the application.",
"Internal Error occurred!",
style=wx.OK | wx.ICON_EXCLAMATION
)
popup.ShowModal()
self.frame.Freeze()
sys.exit(1)
class ThreadHandler(logging.Handler):
"""
Reroutes logging output to a wx.TextCtrl using UI callbacks
"""
def __init__(self, text_box: wx.TextCtrl):
logging.Handler.__init__(self)
self.text_box = text_box
def emit(self, record: logging.LogRecord):
wx.CallAfter(self.text_box.AppendText, self.format(record) + '\n')
class RestartHost:
"""
Restarts the host machine
"""
def __init__(self, frame: wx.Frame) -> None:
self.frame: wx.Frame = frame
def restart(self, event: wx.Event = None, message: str = ""):
self.popup = wx.MessageDialog(
self.frame,
message,
"Reboot to apply?",
wx.YES_NO | wx.YES_DEFAULT | wx.ICON_INFORMATION
)
self.popup.SetYesNoLabels("Reboot", "Ignore")
answer = self.popup.ShowModal()
if answer == wx.ID_YES:
# Reboots with Count Down prompt (user can still dismiss if needed)
self.frame.Hide()
wx.Yield()
try:
applescript.AppleScript('tell app "loginwindow" to «event aevtrrst»').run()
except applescript.ScriptError as e:
logging.error(f"Error while trying to reboot: {e}")
sys.exit(0)
class RelaunchApplicationAsRoot:
"""
Relaunches the application as root
"""
def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None:
self.constants = global_constants
self.frame: wx.Frame = frame
def relaunch(self, event: wx.Event):
self.dialog = wx.MessageDialog(
self.frame,
"OpenCore Legacy Patcher needs to relaunch as admin to continue. You will be prompted to enter your password.",
"Relaunch as root?",
wx.YES_NO | wx.ICON_QUESTION
)
# Show Dialog Box
if self.dialog.ShowModal() != wx.ID_YES:
logging.info("User cancelled relaunch")
return
timer: int = 5
program_arguments: str = ""
if event:
if event.GetEventObject() != wx.Menu:
try:
if event.GetEventObject().GetLabel() in ["Start Root Patching", "Reinstall Root Patches"]:
program_arguments = " --gui_patch"
elif event.GetEventObject().GetLabel() == "Revert Root Patches":
program_arguments = " --gui_unpatch"
except TypeError:
pass
if self.constants.launcher_script is None:
program_arguments = f"'{self.constants.launcher_binary}'{program_arguments}"
else:
program_arguments = f"{self.constants.launcher_binary} {self.constants.launcher_script}{program_arguments}"
# Relaunch as root
args = [
"osascript",
"-e",
f'''do shell script "{program_arguments}"'''
' with prompt "OpenCore Legacy Patcher needs administrator privileges to relaunch as admin."'
" with administrator privileges"
" without altering line endings",
]
self.frame.DestroyChildren()
self.frame.SetSize(300, 300)
self.frame.Centre()
# Header
header = wx.StaticText(self.frame, label="Relaunching as root", pos=(-1, 5))
header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
header.Centre(wx.HORIZONTAL)
# Add count down label
countdown_label = wx.StaticText(self.frame, label=f"Closing old process in {timer} seconds", pos=(0, header.GetPosition().y + header.GetSize().height + 3))
countdown_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
countdown_label.Centre(wx.HORIZONTAL)
# Set size of frame
self.frame.SetSize((-1, countdown_label.GetPosition().y + countdown_label.GetSize().height + 40))
wx.Yield()
logging.info(f"Relaunching as root with command: {program_arguments}")
subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
wx.Yield()
countdown_label.SetLabel(f"Closing old process in {timer} seconds")
time.sleep(1)
timer -= 1
if timer == 0:
break
sys.exit(0)