diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..f74d7190 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main, master, develop, dev, 2.0.0] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop, dev, 2.0.0 ] + schedule: + - cron: '26 13 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/build-and-lint.yml b/.github/workflows/lint-and-build.yml similarity index 96% rename from .github/workflows/build-and-lint.yml rename to .github/workflows/lint-and-build.yml index ae10b564..775b7925 100644 --- a/.github/workflows/build-and-lint.yml +++ b/.github/workflows/lint-and-build.yml @@ -1,17 +1,25 @@ # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions -name: Build and lint +name: Lint and build on: push: branches: - main - master - 2.0.0 + paths: + - '**.py' + - '**.pyi' + - '**.ui' pull_request: branches: - main - master - dev - dev* + paths: + - '**.py' + - '**.pyi' + - '**.ui' jobs: Pyright: runs-on: windows-latest @@ -54,7 +62,7 @@ jobs: - run: scripts/compile_resources.bat - name: Analysing the code with ${{ job.name }} run: pylint --reports=y --output-format=colorized $(git ls-files '**/*.py*') - Bandit: + Flake8: runs-on: windows-latest strategy: matrix: @@ -72,8 +80,8 @@ jobs: pip install -r "scripts/requirements.txt" - run: scripts/compile_resources.bat - name: Analysing the code with ${{ job.name }} - run: bandit -n 1 --severity-level medium --recursive src - Flake8: + run: flake8 + Bandit: runs-on: windows-latest strategy: matrix: @@ -91,7 +99,7 @@ jobs: pip install -r "scripts/requirements.txt" - run: scripts/compile_resources.bat - name: Analysing the code with ${{ job.name }} - run: flake8 + run: bandit -n 1 --severity-level medium --recursive src Build: runs-on: windows-latest strategy: diff --git a/.markdownlint.json b/.markdownlint.json index efb4ef2f..9ecf9f17 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,5 +1,6 @@ { "default": true, - "MD025": false, - "MD013": false + "MD001": false, + "MD013": false, + "MD025": false } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d4464eda..49ec5de6 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,4 @@ +// Keep in alphabetical order { "recommendations": [ "bungcip.better-toml", @@ -5,11 +6,22 @@ "eamodio.gitlens", "ms-python.python", "ms-python.vscode-pylance", + "pkief.material-icon-theme", "shardulm94.trailing-spaces", "sonarsource.sonarlint-vscode" ], "unwantedRecommendations": [ + // VSCode has implemented an optimized version + "coenraads.bracket-pair-colorizer", + "coenraads.bracket-pair-colorizer-2", + // Lots of conflicts + "esbenp.prettier-vscode", + // Replaced by ESLint + "eg2.tslint", + "ms-vscode.vscode-typescript-tslint-plugin", + // Obsoleted by Pylance "ms-pyright.pyright", - "esbenp.prettier-vscode" + // The ESLint plugin is sufficient in JS-only projects + "sonarsource.sonarlint-vscode", ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 5336311a..6252fc0e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,36 +10,11 @@ }, "editor.detectIndentation": false, "editor.tabSize": 2, - "[python]": { - "editor.tabSize": 4, - "editor.rulers": [ - 72, // PEP8-17 docstrings - // 79, // PEP8-17 default max - // 88, // Black default - 99, // PEP8-17 acceptable max - 120, // Our hard rule - ] - }, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": true, "source.fixAll.markdownlint": true, }, - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.linting.pylintCategorySeverity.convention": "Warning", - "python.linting.pylintCategorySeverity.refactor": "Warning", - "python.linting.flake8Enabled": true, - "python.linting.flake8CategorySeverity.E": "Warning", - // PyRight obsoletes mypy - "python.linting.mypyEnabled": false, - // Is already wrapped by Flake8, prospector and pylama - "python.linting.pycodestyleEnabled": false, - // Just another wrapper, use Flake8 OR this - "python.linting.prospectorEnabled": false, - // Just another wrapper, use Flake8 OR this - "python.linting.pylamaEnabled": false, - "python.linting.banditEnabled": true, "files.insertFinalNewline": true, "trailing-spaces.includeEmptyLines": true, "trailing-spaces.trimOnSave": true, @@ -67,4 +42,41 @@ "**/*.code-search": true, "typings": true, }, + "[python]": { + "editor.tabSize": 4, + "editor.rulers": [ + 72, // PEP8-17 docstrings + // 79, // PEP8-17 default max + // 88, // Black default + 99, // PEP8-17 acceptable max + 120, // Our hard rule + ] + }, + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintCategorySeverity.convention": "Warning", + "python.linting.pylintCategorySeverity.refactor": "Warning", + "python.linting.flake8Enabled": true, + "python.linting.flake8CategorySeverity.E": "Warning", + // PyRight obsoletes mypy + "python.linting.mypyEnabled": false, + // Is already wrapped by Flake8, prospector and pylama + "python.linting.pycodestyleEnabled": false, + // Just another wrapper, use Flake8 OR this + "python.linting.prospectorEnabled": false, + // Just another wrapper, use Flake8 OR this + "python.linting.pylamaEnabled": false, + "python.linting.banditEnabled": true, + // Copy those over to your user settings + "sonarlint.rules": { + "python:S1192": { + "level": "off" + }, + "python:S3776": { + "level": "off" + }, + "python:S107": { + "level": "off" + }, + }, } diff --git a/PyInstaller/hooks/hook-requests.py b/PyInstaller/hooks/hook-requests.py new file mode 100644 index 00000000..13de4b6b --- /dev/null +++ b/PyInstaller/hooks/hook-requests.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files + +# Get the cacert.pem +datas = collect_data_files("certifi") diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..ee4fd8d7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | :-------: | +| main | :white_check_mark: | +| dev* | :white_check_mark: | +| everything else | WIP branches, security support may be lacking | + +## Reporting a Vulnerability + +This is a small project, maintained by a few volunteers of the community. Just open up an issue, be clear, explain why it's a vulnerability as well as your recommendations to fix it. Please provide sources if possible. diff --git a/res/update_checker.ui b/res/update_checker.ui index 12ffade1..a2f767ae 100644 --- a/res/update_checker.ui +++ b/res/update_checker.ui @@ -49,9 +49,9 @@ - 17 + 20 10 - 281 + 218 16 @@ -62,13 +62,13 @@ - + There is an update available for AutoSplit. - 17 + 20 30 91 16 @@ -81,7 +81,7 @@ - 17 + 20 50 81 16 @@ -94,14 +94,14 @@ - 17 - 76 - 241 + 20 + 80 + 119 16 - + Open download page? @@ -117,7 +117,7 @@ Qt::NoFocus - + Open @@ -130,7 +130,7 @@ - + Later @@ -162,9 +162,9 @@ - 17 - 100 - 141 + 20 + 102 + 131 20 diff --git a/scripts/build.bat b/scripts/build.bat index f8a5f6b8..bfc6d7ee 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -1,2 +1,2 @@ CALL "%~p0compile_resources.bat" -pyinstaller -w -F --icon=res\icon.ico "%~p0..\src\AutoSplit.py" +pyinstaller --windowed --onefile --additional-hooks-dir=Pyinstaller\hooks --icon=res\icon.ico "%~p0..\src\AutoSplit.py" diff --git a/scripts/lint.ps1 b/scripts/lint.ps1 index 97a72d41..6f66ff32 100644 --- a/scripts/lint.ps1 +++ b/scripts/lint.ps1 @@ -4,8 +4,8 @@ pyright echo "`nRunning Pylint..." pylint --score=n --output-format=colorized $(git ls-files '**/*.py*') -echo "`nRunning Bandit..." -bandit -f custom --silent --recursive src - echo "`nRunning Flake8..." flake8 + +echo "`nRunning Bandit..." +bandit -f custom --silent --recursive src diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 30cf1dab..4a438a17 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -10,24 +10,25 @@ # Creating AutoSplit.exe with PyInstaller: .\scripts\build.bat # # Dependencies: +ImageHash +keyboard numpy>=1.22.0rc1 opencv-python<=4.5.3.56 # https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/110 +packaging +Pillow PyQt6 PyQt6-tools PySide6 -Pillow -ImageHash -pywin32 -keyboard -packaging pyautogui +pywin32 requests # Linting and Types -pywin32-stubs -types-requests +bandit flake8 +flake8-quotes pylint -bandit +pywin32-stubs +types-requests # # Comment this out if you don't want to build AutoSplit.exe: PyInstaller diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 40e81aea..465c9682 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -19,6 +19,7 @@ from copy import copy from time import time +import certifi import cv2 import numpy as np from PyQt6 import QtCore, QtGui, QtTest @@ -31,14 +32,14 @@ import split_parser from AutoControlledWorker import AutoControlledWorker from capture_windows import capture_region, Rect -from gen import design +from compare import checkIfImageHasTransparency, compareImage +from gen import about, design, update_checker from hotkeys import send_command, afterSettingHotkey, setSplitHotkey, setResetHotkey, setSkipSplitHotkey, \ setUndoSplitHotkey, setPauseHotkey -from menu_bar import AboutWidget, VERSION, UpdateCheckerWidget, about, viewHelp, checkForUpdates +from menu_bar import open_about, VERSION, viewHelp, checkForUpdates, open_update_checker from screen_region import selectRegion, selectWindow, alignRegion, validateBeforeComparison +from settings_file import FROZEN from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG -from compare import checkIfImageHasTransparency, compareImage - # Resize to these width and height so that FPS performance increases COMPARISON_RESIZE_WIDTH = 320 @@ -47,9 +48,23 @@ DISPLAY_RESIZE_WIDTH = 240 DISPLAY_RESIZE_HEIGHT = 180 DISPLAY_RESIZE = (DISPLAY_RESIZE_WIDTH, DISPLAY_RESIZE_HEIGHT) -CREATE_NEW_ISSUE_MESSAGE = \ - "Please create a New Issue at " -"github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below" +CREATE_NEW_ISSUE_MESSAGE = "Please create a New Issue at " \ + "github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below" + +# Needed when compiled, along with the custom hook-requests PyInstaller hook +os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() + + +def make_excepthook(main_window: AutoSplit): + def excepthook(exception_type: type[BaseException], exception: BaseException, _traceback: Optional[TracebackType]): + # Catch Keyboard Interrupts for a clean close + if exception_type is KeyboardInterrupt or isinstance(exception, KeyboardInterrupt): + sys.exit(0) + main_window.showErrorSignal.emit(lambda: error_messages.exceptionTraceback( + "AutoSplit encountered an unhandled exception and will try to recover, " + f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", + exception)) + return excepthook class AutoSplit(QMainWindow, design.Ui_MainWindow): @@ -67,6 +82,7 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): undoSplitSignal = QtCore.pyqtSignal() pauseSignal = QtCore.pyqtSignal() afterSettingHotkeySignal = QtCore.pyqtSignal() + updateCheckerWidgetSignal = QtCore.pyqtSignal(str, bool) # Use this signal when trying to show an error from outside the main thread showErrorSignal = QtCore.pyqtSignal(FunctionType) @@ -75,8 +91,9 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): timerStartImage = QtCore.QTimer() # Windows - aboutWidget: AboutWidget - updateCheckerWidget: UpdateCheckerWidget + AboutWidget: about.Ui_aboutAutoSplitWidget + UpdateCheckerWidget: update_checker.Ui_UpdateChecker + CheckForUpdatesThread: QtCore.QThread # Settings split_image_directory = "" @@ -149,13 +166,19 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) + + # Setup global error handling + self.showErrorSignal.connect(lambda errorMessageBox: errorMessageBox()) + # Whithin LiveSplit excepthook needs to use main_window's signals to show errors + sys.excepthook = make_excepthook(self) + self.setupUi(self) settings.loadPyQtSettings(self) # close all processes when closing window self.actionView_Help.triggered.connect(viewHelp) - self.actionAbout.triggered.connect(lambda: about(self)) + self.actionAbout.triggered.connect(lambda: open_about(self)) self.actionCheck_for_Updates.triggered.connect(lambda: checkForUpdates(self)) self.actionSave_Settings.triggered.connect(lambda: settings.saveSettings(self)) self.actionSave_Settings_As.triggered.connect(lambda: settings.saveSettingsAs(self)) @@ -210,6 +233,10 @@ def __init__(self, parent: Optional[QWidget] = None): self.alignregionButton.clicked.connect(lambda: alignRegion(self)) self.selectwindowButton.clicked.connect(lambda: selectWindow(self)) self.startImageReloadButton.clicked.connect(lambda: self.loadStartImage(True, True)) + self.actionCheck_for_Updates_on_Open.changed.connect(lambda: settings.set_check_for_updates_on_open( + self, + self.actionCheck_for_Updates_on_Open.isChecked()) + ) # update x, y, width, and height when changing the value of these spinbox's are changed self.xSpinBox.valueChanged.connect(self.updateX) @@ -221,10 +248,11 @@ def __init__(self, parent: Optional[QWidget] = None): self.updateCurrentSplitImage.connect(self.updateSplitImageGUI) self.afterSettingHotkeySignal.connect(lambda: afterSettingHotkey(self)) self.startAutoSplitterSignal.connect(self.autoSplitter) + self.updateCheckerWidgetSignal.connect(lambda latest_version, check_on_open: + open_update_checker(self, latest_version, check_on_open)) 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) @@ -240,6 +268,12 @@ def __init__(self, parent: Optional[QWidget] = None): if not self.is_auto_controlled: settings.loadSettings(self, load_settings_on_open=True) + self.show() + + # Needs to be after Ui_MainWindow.show() to be shown overtop + if self.actionCheck_for_Updates_on_Open.isChecked(): + checkForUpdates(self, check_on_open=True) + # FUNCTIONS def getGlobalSettingsValues(self): @@ -1104,12 +1138,10 @@ def updateSplitImage(self, custom_image_file: str = "", from_start_image: bool = self.similarity = 0 self.highest_similarity = 0.001 - # exit safely when closing the window - def closeEvent(self, a0: Optional[QtGui.QCloseEvent] = None): - # save global setting values here - self.setting_check_for_updates_on_open.setValue("check_for_updates_on_open", - self.actionCheck_for_Updates_on_Open.isChecked()) + """ + Exit safely when closing the window + """ def exitProgram(): if a0 is not None: @@ -1154,28 +1186,26 @@ def exitProgram(): def main(): + # Call to QApplication outside the try-except so we can show error messages app = QApplication(sys.argv) 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) + AutoSplit() - # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C) - timer = QtCore.QTimer() - timer.timeout.connect(lambda: None) - timer.start(500) + if not FROZEN: + # 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: # pylint: disable=broad-except + except Exception as exception: # pylint: disable=broad-except # We really want to catch everything here + message = f"AutoSplit encountered an unrecoverable exception and will now close. {CREATE_NEW_ISSUE_MESSAGE}" # Print error to console if not running in executable - if getattr(sys, "frozen", False): - error_messages.exceptionTraceback( - f"AutoSplit encountered an unrecoverable exception and will now close. {CREATE_NEW_ISSUE_MESSAGE}", - exception) + if FROZEN: + error_messages.exceptionTraceback(message, exception) else: + print(message) traceback.print_exception(type(exception), exception, exception.__traceback__) sys.exit(1) @@ -1185,16 +1215,5 @@ def main(): sys.exit(exit_code) -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, " - f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}", - exception) - - if __name__ == "__main__": - sys.excepthook = excepthook main() diff --git a/src/gen/design.pyi b/src/gen/design.pyi index 6d951853..a7a38ee1 100644 --- a/src/gen/design.pyi +++ b/src/gen/design.pyi @@ -1,6 +1,9 @@ +from PyQt6.QtGui import QAction from PyQt6.QtWidgets import QMainWindow class Ui_MainWindow(): + actionCheck_for_Updates_on_Open: QAction + def setupUi(self, MainWindow: QMainWindow) -> None: ... diff --git a/src/menu_bar.py b/src/menu_bar.py index cf67f09d..15e6e82d 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -4,18 +4,24 @@ from AutoSplit import AutoSplit import os + import requests -from PyQt6 import QtWidgets +from simplejson.errors import JSONDecodeError from packaging import version -from gen import about as about_, resources_rc, update_checker # noqa: F401 +from PyQt6 import QtWidgets +from PyQt6.QtCore import QThread +from requests.exceptions import RequestException + import error_messages +import settings_file as settings +from gen import about, design, resources_rc, update_checker # noqa: F401 # AutoSplit Version number VERSION = "1.6.1" # About Window -class AboutWidget(QtWidgets.QWidget, about_.Ui_aboutAutoSplitWidget): +class __AboutWidget(QtWidgets.QWidget, about.Ui_aboutAutoSplitWidget): def __init__(self): super().__init__() self.setupUi(self) @@ -25,57 +31,64 @@ def __init__(self): self.show() -class UpdateCheckerWidget(QtWidgets.QWidget, update_checker.Ui_UpdateChecker): - def __init__(self, latest_version: str, autosplit: AutoSplit, check_for_updates_on_open: bool = False): +def open_about(autosplit: AutoSplit): + autosplit.AboutWidget = __AboutWidget() + + +class __UpdateCheckerWidget(QtWidgets.QWidget, update_checker.Ui_UpdateChecker): + def __init__(self, latest_version: str, design_window: design.Ui_MainWindow, check_on_open: bool = False): super().__init__() self.setupUi(self) self.labelCurrentVersionNumber.setText(VERSION) self.labelLatestVersionNumber.setText(latest_version) self.pushButtonLeft.clicked.connect(self.openUpdate) - self.pushButtonRight.clicked.connect(self.closeWindow) - self.autosplit = autosplit + self.checkBoxDoNotAskMeAgain.stateChanged.connect(self.doNotAskMeAgainStateChanged) + self.design_window = design_window if version.parse(latest_version) > version.parse(VERSION): - self.labelUpdateStatus.setText("There is an update available for AutoSplit.") - self.labelGoToDownload.setText("Open download page?") - self.pushButtonLeft.setVisible(True) - self.pushButtonLeft.setText("Open") - self.pushButtonRight.setText("Later") - if not check_for_updates_on_open: - self.checkBoxDoNotAskMeAgain.setVisible(False) + self.checkBoxDoNotAskMeAgain.setVisible(check_on_open) self.show() - elif not check_for_updates_on_open: + elif not check_on_open: self.labelUpdateStatus.setText("You are on the latest AutoSplit version.") + self.labelGoToDownload.setVisible(False) self.pushButtonLeft.setVisible(False) self.pushButtonRight.setText("OK") self.checkBoxDoNotAskMeAgain.setVisible(False) self.show() def openUpdate(self): - if self.checkBoxDoNotAskMeAgain.isChecked(): - self.autosplit.actionCheck_for_Updates_on_Open.setChecked(False) os.system('start "" https://github.com/Toufool/Auto-Split/releases/latest') self.close() - def closeWindow(self): - if self.checkBoxDoNotAskMeAgain.isChecked(): - self.autosplit.actionCheck_for_Updates_on_Open.setChecked(False) - self.close() + def doNotAskMeAgainStateChanged(self): + settings.set_check_for_updates_on_open( + self.design_window, + self.checkBoxDoNotAskMeAgain.isChecked()) + + +def open_update_checker(autosplit: AutoSplit, latest_version: str, check_on_open: bool): + autosplit.UpdateCheckerWidget = __UpdateCheckerWidget(latest_version, autosplit, check_on_open) def viewHelp(): os.system('start "" https://github.com/Toufool/Auto-Split#tutorial') -def about(autosplit: AutoSplit): - autosplit.aboutWidget = AboutWidget() +class __CheckForUpdatesThread(QThread): + def __init__(self, autosplit: AutoSplit, check_on_open: bool): + super().__init__() + self.autosplit = autosplit + self.check_on_open = check_on_open + + def run(self): + try: + response = requests.get("https://api.github.com/repos/Toufool/Auto-Split/releases/latest") + latest_version = response.json()["name"].split("v")[1] + self.autosplit.updateCheckerWidgetSignal.emit(latest_version, self.check_on_open) + except (RequestException, KeyError, JSONDecodeError): + if not self.check_on_open: + self.autosplit.showErrorSignal.emit(error_messages.checkForUpdatesError) -def checkForUpdates(autosplit: AutoSplit, check_for_updates_on_open: bool = False): - try: - response = requests.get("https://api.github.com/repos/Toufool/Auto-Split/releases/latest") - latest_version = response.json()["name"].split("v")[1] - except requests.exceptions.RequestException: - if not check_for_updates_on_open: - error_messages.checkForUpdatesError() - else: - autosplit.updateCheckerWidget = UpdateCheckerWidget(latest_version, autosplit, check_for_updates_on_open) +def checkForUpdates(autosplit: AutoSplit, check_on_open: bool = False): + autosplit.CheckForUpdatesThread = __CheckForUpdatesThread(autosplit, check_on_open) + autosplit.CheckForUpdatesThread.start() diff --git a/src/settings_file.py b/src/settings_file.py index 55636563..9a30cb37 100644 --- a/src/settings_file.py +++ b/src/settings_file.py @@ -8,20 +8,24 @@ import pickle import keyboard # https://github.com/boppreh/keyboard/issues/505 from win32 import win32gui -from PyQt6 import QtWidgets +from PyQt6 import QtCore, QtWidgets import error_messages +from gen import design # TODO with settings refactoring from hotkeys import _hotkey_action # type: ignore +# Keyword "frozen" is for setting basedir while in onefile mode in pyinstaller +FROZEN = hasattr(sys, "frozen") + # Get the directory of either AutoSplit.exe or AutoSplit.py -auto_split_directory = os.path.dirname(sys.executable if getattr(sys, "frozen", False) else os.path.abspath(__file__)) +auto_split_directory = os.path.dirname(sys.executable if FROZEN else os.path.abspath(__file__)) class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module: str, name: str): - raise pickle.UnpicklingError("'%s.%s' is forbidden" % (module, name)) + raise pickle.UnpicklingError(f"'{module}.{name}' is forbidden") def loadPyQtSettings(autosplit: AutoSplit): @@ -319,3 +323,26 @@ def loadSettings(autosplit: AutoSplit, load_settings_on_open: bool = False, load autosplit.last_successfully_loaded_settings_file_path = autosplit.load_settings_file_path autosplit.checkLiveImage() autosplit.loadStartImage() + + +def load_check_for_updates_on_open(designWindow: design.Ui_MainWindow): + """ + Retrieve the "Check For Updates On Open" QSettings and set the checkbox state + These are only global settings values. They are not *pkl settings values. + """ + + value = QtCore \ + .QSettings("AutoSplit", "Check For Updates On Open") \ + .value("check_for_updates_on_open", True, type=bool) + designWindow.actionCheck_for_Updates_on_Open.setChecked(value) + + +def set_check_for_updates_on_open(designWindow: design.Ui_MainWindow, value: bool): + """ + Sets the "Check For Updates On Open" QSettings value and the checkbox state + """ + + designWindow.actionCheck_for_Updates_on_Open.setChecked(value) + QtCore \ + .QSettings("AutoSplit", "Check For Updates On Open") \ + .setValue("check_for_updates_on_open", value) diff --git a/typings/simplejson/errors.pyi b/typings/simplejson/errors.pyi new file mode 100644 index 00000000..d98b7495 --- /dev/null +++ b/typings/simplejson/errors.pyi @@ -0,0 +1,34 @@ +""" +This type stub file was generated by pyright. +""" + +"""Error classes used by simplejson +""" +__all__ = ['JSONDecodeError'] +def linecol(doc, pos): # -> tuple[Unknown, Unknown]: + ... + +def errmsg(msg, doc, pos, end=...): # -> str: + ... + +class JSONDecodeError(ValueError): + """Subclass of ValueError with the following additional properties: + + msg: The unformatted error message + doc: The JSON document being parsed + pos: The start index of doc where parsing failed + end: The end index of doc where parsing failed (may be None) + lineno: The line corresponding to pos + colno: The column corresponding to pos + endlineno: The line corresponding to end (may be None) + endcolno: The column corresponding to end (may be None) + + """ + def __init__(self, msg, doc, pos, end=...) -> None: + ... + + def __reduce__(self): # -> tuple[Type[Self@JSONDecodeError], tuple[Unknown, Unknown, Unknown, Unknown]]: + ... + + +