Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/lint-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
branches:
- main
- master
- dev*
paths:
- "**.py"
- "**.ui"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ reportUnusedCallResult = "none"
reportMissingTypeStubs = "warning"
# False positives with TYPE_CHECKING
reportImportCycles = "information"
# False positives with PyQt .connect
# False positives with PySide .connect
reportFunctionMemberAccess = "none"
# Extra runtime safety
reportUnnecessaryComparison = "warning"
Expand Down
19 changes: 16 additions & 3 deletions res/design.ui
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
</property>
<property name="windowIcon">
<iconset resource="resources.qrc">
<normaloff>:/resources/icon.ico</normaloff>
:/resources/icon.ico
</iconset>
<normaloff>:/resources/icon.ico</normaloff>:/resources/icon.ico</iconset>
</property>
<widget class="QWidget" name="central_widget">
<widget class="QLabel" name="x_label">
Expand Down Expand Up @@ -974,6 +972,9 @@
<property name="shortcut">
<string>F1</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
<action name="action_about">
<property name="text">
Expand All @@ -990,6 +991,9 @@
<property name="shortcut">
<string>Ctrl+S</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
<action name="action_load_profile">
<property name="text">
Expand All @@ -998,6 +1002,9 @@
<property name="shortcut">
<string>Ctrl+O</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
<action name="action_save_profile_as">
<property name="text">
Expand All @@ -1006,6 +1013,9 @@
<property name="shortcut">
<string>Ctrl+Shift+S</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
<action name="action_check_for_updates">
<property name="text">
Expand All @@ -1019,6 +1029,9 @@
<property name="shortcut">
<string>Ctrl+,</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
<property name="menuRole">
<enum>QAction::PreferencesRole</enum>
</property>
Expand Down
14 changes: 10 additions & 4 deletions scripts/compile_resources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ $originalDirectory = $pwd
Set-Location "$PSScriptRoot/.."

New-Item -Force -ItemType directory ./src/gen | Out-Null
pyuic6 './res/about.ui' -o './src/gen/about.py'
pyuic6 './res/design.ui' -o './src/gen/design.py'
pyuic6 './res/settings.ui' -o './src/gen/settings.py'
pyuic6 './res/update_checker.ui' -o './src/gen/update_checker.py'
pyside6-uic './res/about.ui' -o './src/gen/about.py'
pyside6-uic './res/design.ui' -o './src/gen/design.py'
pyside6-uic './res/settings.ui' -o './src/gen/settings.py'
pyside6-uic './res/update_checker.ui' -o './src/gen/update_checker.py'
pyside6-rcc './res/resources.qrc' -o './src/gen/resources_rc.py'
$files = Get-ChildItem ./src/gen/ *.py
foreach ($file in $files) {
(Get-Content $file.PSPath) |
ForEach-Object { $_ -replace 'import resources_rc', 'from . import resources_rc' } |
Set-Content $file.PSPath
}
Write-Host 'Generated code from .ui files'

$build_vars_path = "$PSScriptRoot/../src/gen/build_vars.py"
Expand Down
4 changes: 2 additions & 2 deletions scripts/requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ add-trailing-comma>=2.3.0 # Added support for with statement
autopep8>=2.0.0 # New checks
ruff>=0.0.262 # Avoid N802 violations for @override + Avoid adding required imports to stub files
#
# Run `./scripts/designer.ps1` to quickly open the bundled PyQt Designer.
# Run `./scripts/designer.ps1` to quickly open the bundled Qt Designer.
# Can also be downloaded externally as a non-python package
qt6-applications
# qt6-applications
# Types
types-d3dshot
types-keyboard
Expand Down
9 changes: 4 additions & 5 deletions scripts/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
# Usage: ./scripts/install.ps1
#
# If you're having issues with the libraries, you might want to first run:
# pip uninstall -y -r ./scripts/requirements-dev.txt
# pip uninstall -y -r ./scripts/requirements-dev.txt PySide6 shiboken6
#
# Creating an AutoSplit executable with PyInstaller: ./scripts/build.ps1
#
# PySide6 dev wheels: https://download.qt.io/snapshots/ci/pyside/dev/?C=M;O=D
#
# Dependencies:
certifi
ImageHash>=4.3.1 # Contains type information + setup as package not module
Expand All @@ -19,17 +21,14 @@ packaging
Pillow>=9.2 # gnome-screeshot checks
psutil
PyAutoGUI
# 6.4.1 fixes the rare Illegal Operation issue from RTADan, but the dev wheels are currently broken :/
# 2.0.0-beta.2 contains the fixes if anyone needs it.
PyQt6>=6.4.0 # Python 3.11 support
PySide6-Essentials>=6.5 # fixes https://bugreports.qt.io/browse/PYSIDE-2189 and https://bugreports.qt.io/browse/PYSIDE-1603
requests<=2.28.1 # 2.28.2 has issues with PyInstaller https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/534
toml
typing-extensions>=4.4.0 # @override decorator support
#
# Build and compile resources
pyinstaller>=5.5 # Python 3.11 support
pyinstaller-hooks-contrib>=2022.9 # opencv-python 4.6 support. Changes for pywintypes and comtypes
PySide6-Essentials>=6.4.0.1 # Python 3.11 support
#
# https://peps.python.org/pep-0508/#environment-markers
#
Expand Down
5 changes: 3 additions & 2 deletions src/AutoControlledWorker.py → src/AutoControlledThread.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING

from PyQt6 import QtCore
from PySide6 import QtCore

import error_messages
import user_profile
Expand All @@ -11,11 +11,12 @@
from AutoSplit import AutoSplit


class AutoControlledWorker(QtCore.QObject):
class AutoControlledThread(QtCore.QThread):
def __init__(self, autosplit: AutoSplit):
self.autosplit = autosplit
super().__init__()

@QtCore.Slot()
def run(self):
while True:
try:
Expand Down
90 changes: 41 additions & 49 deletions src/AutoSplit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@
import os
import signal
import sys
from collections.abc import Callable
from time import time
from types import FunctionType
from typing import NoReturn
from typing import NoReturn, Optional

import certifi
import cv2
from AutoControlledThread import AutoControlledThread
from psutil import process_iter
from PyQt6 import QtCore, QtGui
from PyQt6.QtTest import QTest
from PyQt6.QtWidgets import QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox, QWidget
from PySide6 import QtCore, QtGui
from PySide6.QtTest import QTest
from PySide6.QtWidgets import QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox
from typing_extensions import override

import error_messages
import user_profile
from AutoControlledWorker import AutoControlledWorker
from AutoSplitImage import START_KEYWORD, AutoSplitImage, ImageType
from capture_method import CaptureMethodBase, CaptureMethodEnum
from gen import about, design, settings, update_checker
Expand Down Expand Up @@ -49,28 +50,31 @@
)

CHECK_FPS_ITERATIONS = 10
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 2

# Needed when compiled, along with the custom hook-requests PyInstaller hook
os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
myappid = f"Toufool.AutoSplit.v{AUTOSPLIT_VERSION}"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
# qt.qpa.window: SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed: COM error 0x5: Access is denied. # noqa: E501 # pylint: disable=line-too-long
# ctypes.windll.user32.SetProcessDpiAwarenessContext(2)


class AutoSplit(QMainWindow, design.Ui_MainWindow):
# Parse command line args
is_auto_controlled = "--auto-controlled" in sys.argv

# Signals
start_auto_splitter_signal = QtCore.pyqtSignal()
reset_signal = QtCore.pyqtSignal()
skip_split_signal = QtCore.pyqtSignal()
undo_split_signal = QtCore.pyqtSignal()
pause_signal = QtCore.pyqtSignal()
after_setting_hotkey_signal = QtCore.pyqtSignal()
update_checker_widget_signal = QtCore.pyqtSignal(str, bool)
load_start_image_signal = QtCore.pyqtSignal([], [bool], [bool, bool])
start_auto_splitter_signal = QtCore.Signal()
reset_signal = QtCore.Signal()
skip_split_signal = QtCore.Signal()
undo_split_signal = QtCore.Signal()
pause_signal = QtCore.Signal()
after_setting_hotkey_signal = QtCore.Signal()
update_checker_widget_signal = QtCore.Signal(str, bool)
load_start_image_signal = QtCore.Signal(type[Optional[bool]], type[Optional[bool]])
# Use this signal when trying to show an error from outside the main thread
show_error_signal = QtCore.pyqtSignal(FunctionType)
show_error_signal = QtCore.Signal(FunctionType)

# Timers
timer_live_image = QtCore.QTimer()
Expand Down Expand Up @@ -110,13 +114,15 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow):
reset_image: AutoSplitImage | None = None
split_images: list[AutoSplitImage] = []
split_image: AutoSplitImage | None = None
update_auto_control: QtCore.QThread | None = None
update_auto_control: AutoControlledThread | None = None

def __init__(self, parent: QWidget | None = None): # noqa: PLR0915
super().__init__(parent)
def __init__(self): # noqa: PLR0915
super().__init__()

# Setup global error handling
self.show_error_signal.connect(lambda error_message_box: error_message_box())
def _show_error_signal_slot(error_message_box: Callable[..., object]):
return error_message_box()
self.show_error_signal.connect(_show_error_signal_slot)
sys.excepthook = error_messages.make_excepthook(self)

self.setupUi(self)
Expand Down Expand Up @@ -146,10 +152,7 @@ def __init__(self, parent: QWidget | None = None): # noqa: PLR0915
print(f"{AUTOSPLIT_VERSION}\n{os.getpid()}", flush=True)

# Use and Start the thread that checks for updates from LiveSplit
self.update_auto_control = QtCore.QThread()
worker = AutoControlledWorker(self)
worker.moveToThread(self.update_auto_control)
self.update_auto_control.started.connect(worker.run)
self.update_auto_control = AutoControlledThread(self)
self.update_auto_control.start()

# split image folder line edit text
Expand All @@ -162,23 +165,10 @@ def __init__(self, parent: QWidget | None = None): # noqa: PLR0915
self.action_about_qt_for_python.triggered.connect(about_qt_for_python)
self.action_check_for_updates.triggered.connect(lambda: check_for_updates(self))
self.action_settings.triggered.connect(lambda: open_settings(self))
# PyQt6 typing is wrong
self.action_save_profile.triggered.connect(
lambda: user_profile.save_settings(self), # pyright: ignore[reportGeneralTypeIssues]
)
self.action_save_profile_as.triggered.connect(
lambda: user_profile.save_settings_as(self), # pyright: ignore[reportGeneralTypeIssues]
)
self.action_save_profile.triggered.connect(lambda: user_profile.save_settings(self))
self.action_save_profile_as.triggered.connect(lambda: user_profile.save_settings_as(self))
self.action_load_profile.triggered.connect(lambda: user_profile.load_settings(self))

# Shortcut context can't be set through the designer because of a bug in pyuic6 that generates invalid code
# Email sent to pyqt@riverbankcomputing.com
self.action_view_help.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
self.action_settings.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
self.action_save_profile.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
self.action_save_profile_as.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
self.action_load_profile.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)

# Connecting button clicks to functions
self.browse_button.clicked.connect(self.__browse)
self.select_region_button.clicked.connect(lambda: select_region(self))
Expand Down Expand Up @@ -206,12 +196,12 @@ def __init__(self, parent: QWidget | None = None): # noqa: PLR0915
# connect signals to functions
self.after_setting_hotkey_signal.connect(lambda: after_setting_hotkey(self))
self.start_auto_splitter_signal.connect(self.__auto_splitter)
self.update_checker_widget_signal.connect(
lambda latest_version, check_on_open: open_update_checker(self, latest_version, check_on_open),
)

def _update_checker_widget_signal_slot(latest_version: str, check_on_open: bool):
return open_update_checker(self, latest_version, check_on_open)
self.update_checker_widget_signal.connect(_update_checker_widget_signal_slot)

self.load_start_image_signal.connect(self.__load_start_image)
self.load_start_image_signal[bool].connect(self.__load_start_image)
self.load_start_image_signal[bool, bool].connect(self.__load_start_image)
self.reset_signal.connect(self.reset)
self.skip_split_signal.connect(self.skip_split)
self.undo_split_signal.connect(self.undo_split)
Expand Down Expand Up @@ -699,6 +689,8 @@ def __similarity_threshold_loop(self, number_of_split_images: int, dummy_splits_

QTest.qWait(wait_delta_ms)

return False

def __pause_loop(self, stop_time: float, message: str):
"""
Wait for a certain time and show the timer to the user.
Expand Down Expand Up @@ -785,7 +777,7 @@ def gui_changes_on_reset(self, safe_to_reload_start_image: bool = False):

QApplication.processEvents()
if safe_to_reload_start_image:
self.load_start_image_signal[bool, bool].emit(False, False)
self.load_start_image_signal.emit(False, False)

def __get_capture_for_comparison(self):
"""Grab capture region and resize for comparison."""
Expand Down Expand Up @@ -867,15 +859,15 @@ def __update_split_image(self, specific_image: AutoSplitImage | None = None):
self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}")

@override
def closeEvent(self, a0: QtGui.QCloseEvent | None = None):
def closeEvent(self, event: QtGui.QCloseEvent | None = None):
"""Exit safely when closing the window."""

def exit_program() -> NoReturn:
if self.update_auto_control:
self.update_auto_control.terminate()
self.capture_method.close(self)
if a0 is not None:
a0.accept()
if event is not None:
event.accept()
if self.is_auto_controlled:
# stop main thread (which is probably blocked reading input) via an interrupt signal
os.kill(os.getpid(), signal.SIGINT)
Expand All @@ -884,7 +876,7 @@ def exit_program() -> NoReturn:
# Simulates LiveSplit quitting without asking. See "TODO" at update_auto_control Worker
# This also more gracefully exits LiveSplit
# Users can still manually save their settings
if a0 is None:
if event is None:
exit_program()

if user_profile.have_settings_changed(self):
Expand All @@ -905,11 +897,11 @@ def exit_program() -> NoReturn:
if user_profile.save_settings(self):
exit_program()
else:
a0.ignore()
event.ignore()
if warning is QMessageBox.StandardButton.No:
exit_program()
if warning is QMessageBox.StandardButton.Cancel:
a0.ignore()
event.ignore()
else:
exit_program()

Expand Down
4 changes: 2 additions & 2 deletions src/error_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from types import TracebackType
from typing import TYPE_CHECKING, NoReturn

from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets

from utils import FROZEN, GITHUB_REPOSITORY

Expand Down Expand Up @@ -177,7 +177,7 @@ def excepthook(exception_type: type[BaseException], exception: BaseException, _t
# HACK: Can happen when starting the region selector while capturing with WindowsGraphicsCapture
if (
exception_type is SystemError
and str(exception) == "<class 'PyQt6.QtGui.QPaintEvent'> returned a result with an error set"
and str(exception) == "<class 'PySide6.QtGui.QPaintEvent'> returned a result with an error set"
):
return
# Whithin LiveSplit excepthook needs to use MainWindow's signals to show errors
Expand Down
2 changes: 1 addition & 1 deletion src/hotkeys.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import keyboard
import pyautogui
from PyQt6 import QtWidgets
from PySide6 import QtWidgets

import error_messages
from utils import fire_and_forget, is_digit
Expand Down
Loading