diff --git a/src/AutoSplit.py b/src/AutoSplit.py
index c39b101e..0550da6c 100644
--- a/src/AutoSplit.py
+++ b/src/AutoSplit.py
@@ -1,6 +1,8 @@
#!/usr/bin/python3.9
# -*- coding: utf-8 -*-
-from typing import Callable, List, Optional
+import traceback
+from types import FunctionType, TracebackType
+from typing import Callable, List, Optional, Type
from copy import copy
from PyQt6 import QtCore, QtGui, QtTest, QtWidgets
@@ -51,6 +53,8 @@ class AutoSplit(QtWidgets.QMainWindow, design.Ui_MainWindow):
undoSplitSignal = QtCore.pyqtSignal()
pauseSignal = QtCore.pyqtSignal()
afterSettingHotkeySignal = QtCore.pyqtSignal()
+ # Use this signal when trying to show an error from outside the main thread
+ showErrorSignal = QtCore.pyqtSignal(FunctionType)
def __init__(self, parent=None):
super(AutoSplit, self).__init__(parent)
@@ -104,7 +108,7 @@ def run(self):
try:
line = input()
except RuntimeError:
- # stdin not supported or lost, stop looking for inputs
+ self.autosplit.showErrorSignal.emit(error_messages.stdinLostError)
break
# TODO: "AutoSplit Integration" needs to call this and wait instead of outright killing the app.
# TODO: See if we can also get LiveSplit to wait on Exit in "AutoSplit Integration"
@@ -169,6 +173,7 @@ def run(self):
self.resetSignal.connect(self.reset)
self.skipSplitSignal.connect(self.skipSplit)
self.undoSplitSignal.connect(self.undoSplit)
+ self.showErrorSignal.connect(lambda errorMessageBox: errorMessageBox())
# live image checkbox
self.liveimageCheckBox.clicked.connect(self.checkLiveImage)
@@ -1193,22 +1198,50 @@ def exit():
def main():
app = QtWidgets.QApplication(sys.argv)
- app.setWindowIcon(QtGui.QIcon(':/resources/icon.ico'))
-
- main_window = AutoSplit()
- main_window.show()
- if main_window.actionCheck_for_Updates_on_Open.isChecked():
- checkForUpdates(main_window, check_for_updates_on_open=True)
+ try:
+ app.setWindowIcon(QtGui.QIcon(':/resources/icon.ico'))
+ main_window = AutoSplit()
+ main_window.show()
+ # Needs to be after main_window.show() to be shown over
+ if main_window.actionCheck_for_Updates_on_Open.isChecked():
+ checkForUpdates(main_window, check_for_updates_on_open=True)
+
+ # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C)
+ timer = QtCore.QTimer()
+ timer.timeout.connect(lambda: None)
+ timer.start(500)
+
+ exit_code = app.exec()
+ except Exception as exception:
+ # Print error to console if not running in executable
+ if getattr(sys, 'frozen', False):
+ error_messages.exceptionTraceback(
+ "AutoSplit encountered an unrecoverable exception and will now close itself.
"
+ "Please copy the following message over at
"
+ "github.com/Toufool/Auto-Split/issues",
+ exception)
+ else:
+ traceback.print_exception(type(exception), exception, exception.__traceback__)
+ sys.exit(1)
- # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C)
- timer = QtCore.QTimer()
- timer.timeout.connect(lambda: None)
- timer.start(500)
# Catch Keyboard Interrupts for a clean close
- signal.signal(signal.SIGINT, lambda _, __: sys.exit(app))
+ signal.signal(signal.SIGINT, lambda code, _: sys.exit(code))
+
+ sys.exit(exit_code)
- sys.exit(app.exec())
+
+def excepthook(exceptionType: Type[BaseException], exception: BaseException, traceback: Optional[TracebackType]):
+ # Catch Keyboard Interrupts for a clean close
+ if exceptionType is KeyboardInterrupt:
+ sys.exit(0)
+ error_messages.exceptionTraceback(
+ "AutoSplit encountered an unhandled exception and will try to recover, "
+ "however, things may not work quite right.
"
+ "Please copy the following message over at
"
+ "github.com/Toufool/Auto-Split/issues",
+ exception)
if __name__ == '__main__':
+ sys.excepthook = excepthook
main()
diff --git a/src/error_messages.py b/src/error_messages.py
index 1a0e40f1..925d6341 100644
--- a/src/error_messages.py
+++ b/src/error_messages.py
@@ -1,12 +1,20 @@
# Error messages
-from PyQt6 import QtWidgets
+import traceback
+from PyQt6 import QtCore, QtWidgets
-def setTextMessage(message: str):
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle('Error')
- msgBox.setText(message)
- msgBox.exec()
+def setTextMessage(message: str, details: str = ''):
+ messageBox = QtWidgets.QMessageBox()
+ messageBox.setWindowTitle('Error')
+ messageBox.setTextFormat(QtCore.Qt.TextFormat.RichText)
+ messageBox.setText(message)
+ if details:
+ messageBox.setDetailedText(details)
+ for button in messageBox.buttons():
+ if messageBox.buttonRole(button) == QtWidgets.QMessageBox.ButtonRole.ActionRole:
+ button.click()
+ break
+ messageBox.exec()
def splitImageDirectoryError():
@@ -76,5 +84,16 @@ def noSettingsFileOnOpenError():
def tooManySettingsFilesOnOpenError():
setTextMessage("Too many settings files found. Only one can be loaded on open if placed in the same folder as AutoSplit.exe")
+
def checkForUpdatesError():
setTextMessage("An error occurred while attempting to check for updates. Please check your connection.")
+
+
+def stdinLostError():
+ setTextMessage("stdin not supported or lost, external control like LiveSplit integration will not work.")
+
+
+def exceptionTraceback(message: str, exception: Exception):
+ setTextMessage(
+ message,
+ "\n".join(traceback.format_exception(None, exception, exception.__traceback__)))
diff --git a/src/settings_file.py b/src/settings_file.py
index 26f15b2d..052bed5c 100644
--- a/src/settings_file.py
+++ b/src/settings_file.py
@@ -157,10 +157,11 @@ def saveSettingsAs(self: AutoSplit):
def loadSettings(self: AutoSplit, load_settings_on_open: bool = False, load_settings_from_livesplit: bool = False):
if load_settings_on_open:
- settings_files = []
- for file in os.listdir(auto_split_directory):
- if file.endswith(".pkl"):
- settings_files.append(file)
+ settings_files = [
+ file for file
+ in os.listdir(auto_split_directory)
+ if file.endswith(".pkl")
+ ]
# find all .pkls in AutoSplit folder, error if there is none or more than 1
if len(settings_files) < 1:
@@ -190,8 +191,7 @@ def loadSettings(self: AutoSplit, load_settings_on_open: bool = False, load_sett
settings: List[Union[str, int]] = pickle.load(f)
settings_count = len(settings)
if settings_count < 18:
- if not load_settings_from_livesplit:
- error_messages.oldVersionSettingsFileError()
+ self.showErrorSignal.emit(error_messages.oldVersionSettingsFileError)
return
# v1.3-1.4 settings. Add default pause_key and auto_start_on_reset_setting
if settings_count == 18:
@@ -199,8 +199,7 @@ def loadSettings(self: AutoSplit, load_settings_on_open: bool = False, load_sett
settings.insert(20, 0)
# v1.5 settings
elif settings_count != 20:
- if not load_settings_from_livesplit:
- error_messages.invalidSettingsError()
+ self.showErrorSignal.emit(error_messages.invalidSettingsError)
return
self.last_loaded_settings = [
self.split_image_directory,
@@ -224,11 +223,7 @@ def loadSettings(self: AutoSplit, load_settings_on_open: bool = False, load_sett
self.loop_setting,
self.auto_start_on_reset_setting] = settings
except (FileNotFoundError, MemoryError, pickle.UnpicklingError):
- # HACK / Workaround: Executing the error QMessageBox from the auto-controlled Worker Thread makes it hangs.
- # I don't like this solution as we should probably ensure the Worker works nicely with PyQt instead,
- # but in the mean time, this will do.
- if not load_settings_from_livesplit:
- error_messages.invalidSettingsError()
+ self.showErrorSignal.emit(error_messages.invalidSettingsError)
return
self.splitimagefolderLineEdit.setText(self.split_image_directory)