diff --git a/.flake8 b/.flake8
index 65910611..2715e740 100644
--- a/.flake8
+++ b/.flake8
@@ -12,7 +12,7 @@ per-file-ignores=
__init__.pyi:Q000
; PyQt methods
ignore-names=closeEvent,paintEvent,keyPressEvent,mousePressEvent,mouseMoveEvent,mouseReleaseEvent
-; McCabe max-complexity is also taken care of by Pylint and doesn't fail teh build there
+; McCabe max-complexity is also taken care of by Pylint and doesn't fail the build there
; So this is the hard limit
max-complexity=32
inline-quotes="
diff --git a/README.md b/README.md
index 2e43e50d..4c9e24c4 100644
--- a/README.md
+++ b/README.md
@@ -116,7 +116,6 @@ This program can be used to automatically start, split, and reset your preferred
- {d} dummy split image. When matched, it moves to the next image without hitting your split hotkey.
- {b} split when similarity goes below the threshold rather than above. When a split image filename has this flag, the split image similarity will go above the threshold, do nothing, and then split the next time the similarity goes below the threshold.
- {p} pause flag. When a split image filename has this flag, it will hit your pause hotkey rather than your split hokey.
- - A pause flag and a dummy flag `{pd}` cannot be used together
- Filename examples:
- `001_SplitName_(0.9)_[10].png` is a split image with a threshold of 0.9 and a pause time of 10 seconds.
- `002_SplitName_(0.9)_[10]_{d}.png` is the second split image with a threshold of 0.9, pause time of 10, and is a dummy split.
@@ -156,9 +155,9 @@ Given these splits: 1 dummy, 2 normal, 3 dummy, 4 dummy, 5 normal, 6 normal.
In this situation you would have only 3 splits in LiveSplit/wsplit (even though there are 6 split images, only 3 are "real" splits). This basically results in 3 groups of splits: 1st split is images 1 and 2. 2nd split is images 3, 4 and 5. 3rd split is image 6.
- If you are in the 1st or 2nd image and press the skip key, it will end up on the 3rd image
-- If you are in the 3rd, 4th or 5th image and press the undo key, it will end up on the 1st image
+- If you are in the 3rd, 4th or 5th image and press the undo key, it will end up on the 2nd image
- If you are in the 3rd, 4th or 5th image and press the skip key, it will end up on the 6th image
-- If you are in the 6th image and press the undo key, it will end up on the 3rd image
+- If you are in the 6th image and press the undo key, it will end up on the 5th image
### Loop Split Images
diff --git a/pyproject.toml b/pyproject.toml
index 8001ad5f..3f71a85f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,6 +15,11 @@ aggressive = 3
[tool.pyright]
pythonPlatform = "Windows"
typeCheckingMode = "strict"
+# Extra strict
+reportPropertyTypeMismatch=true
+reportUninitializedInstanceVariable=true
+reportCallInDefaultInitializer=true
+reportImplicitStringConcatenation=true
ignore = [
# Auto generated
"src/gen/",
diff --git a/res/design.ui b/res/design.ui
index 80334881..ea0037ef 100644
--- a/res/design.ui
+++ b/res/design.ui
@@ -1,7 +1,7 @@
- main_window
-
+ MainWindow
+
0
@@ -37,7 +37,7 @@
AutoSplit
-
+
:/resources/icon.ico:/resources/icon.ico
@@ -93,6 +93,9 @@
+
+ false
+
650
@@ -108,7 +111,10 @@
Reset
-
+
+
+ false
+
650
@@ -124,7 +130,10 @@
Undo
-
+
+
+ false
+
712
@@ -196,6 +205,9 @@
+
+ Qt::AlignCenter
+
@@ -212,6 +224,9 @@
+
+ Qt::AlignCenter
+
@@ -265,7 +280,7 @@
- 9999
+
@@ -332,7 +347,7 @@
- Image Filename
+ -
Qt::AlignCenter
@@ -507,7 +522,7 @@
- Window Name
+ -
Qt::AlignCenter
@@ -516,8 +531,8 @@
- 450
- 309
+ 451
+ 313
67
20
@@ -796,8 +811,8 @@
- 450
- 345
+ 449
+ 344
98
16
@@ -809,8 +824,8 @@
- 551
- 345
+ 550
+ 344
98
16
@@ -822,17 +837,20 @@
- 519
- 309
+ 520
+ 313
131
20
- x/x
+ N/A
+
+ false
+
449
@@ -852,6 +870,9 @@
+
+ false
+
744
@@ -874,8 +895,8 @@
select_region_button
start_auto_splitter_button
reset_button
- undo_button
- skip_button
+ undo_split_button
+ skip_split_button
check_fps_button
fps_label
current_image_label
@@ -1027,7 +1048,7 @@
height_spinbox
-
+
diff --git a/res/settings.ui b/res/settings.ui
index 22aa4a35..ee4dfd38 100644
--- a/res/settings.ui
+++ b/res/settings.ui
@@ -1,13 +1,13 @@
- dialog_settings
-
+ DialogSettings
+
0
0
289
- 570
+ 540
@@ -19,13 +19,13 @@
289
- 570
+ 540
289
- 570
+ 540
@@ -40,19 +40,6 @@
false
-
-
-
- 127
- 538
- 156
- 24
-
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Save
-
-
@@ -93,7 +80,7 @@
false
-
+
138
@@ -102,23 +89,17 @@
22
-
-
-
-
- 0
+
+ ArrowCursor
- 30.000000000000000
+ 20
- 5000.000000000000000
-
-
- 1.000000000000000
+ 240
- 60.000000000000000
+ 60
@@ -244,7 +225,7 @@
Default Pause Time (sec):
-
+
167
@@ -288,7 +269,7 @@
Default Similarity Threshold:
-
+
167
@@ -332,7 +313,7 @@
false
-
+
6
@@ -351,10 +332,10 @@
teset
- <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings</p></body></html>
+ <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/Toufool/Auto-Split#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings.</p></body></html>
-
+
6
@@ -373,7 +354,7 @@
Default Delay Time (ms):
-
+
167
@@ -382,26 +363,14 @@
22
+
+ ArrowCursor
+
After an image is matched, this is the amount of time in millseconds that will be delayed before splitting.
-
-
-
-
- 0
-
-
- 0.000000000000000
-
- 36000000.000000000000000
-
-
- 1.000000000000000
-
-
- 0.000000000000000
+ 999999999
@@ -430,7 +399,7 @@
180
- 133
+ 130
81
21
@@ -443,10 +412,13 @@
+
+ true
+
76
- 28
+ 30
94
20
@@ -455,7 +427,7 @@
- false
+ true
@@ -481,7 +453,7 @@
6
- 31
+ 32
71
16
@@ -494,7 +466,7 @@
76
- 53
+ 55
94
20
@@ -505,9 +477,6 @@
-
- true
-
true
@@ -532,7 +501,7 @@
6
- 55
+ 57
41
16
@@ -545,7 +514,7 @@
180
- 53
+ 55
81
21
@@ -590,7 +559,7 @@
76
- 133
+ 130
94
20
@@ -622,7 +591,7 @@
180
- 106
+ 105
81
21
@@ -638,7 +607,7 @@
6
- 108
+ 107
61
16
@@ -651,7 +620,7 @@
76
- 106
+ 105
94
20
@@ -672,46 +641,13 @@
skip_split_input
pause_input
default_comparison_method
- default_similarity_threshold_double_spinbox
- default_pause_time_double_spinbox
+ default_similarity_threshold_spinbox
+ default_pause_time_spinbox
loop_splits_checkbox
fps_limit_spinbox
live_capture_region_checkbox
force_print_window_checkbox
-
-
- save_cancel_dialog_button_box
- accepted()
- dialog_settings
- accept()
-
-
- 194
- 541
-
-
- 140
- 282
-
-
-
-
- save_cancel_dialog_button_box
- rejected()
- dialog_settings
- reject()
-
-
- 194
- 541
-
-
- 140
- 282
-
-
-
-
+
diff --git a/scripts/compile_resources.bat b/scripts/compile_resources.bat
index 3c1398ff..d5ef6cf0 100644
--- a/scripts/compile_resources.bat
+++ b/scripts/compile_resources.bat
@@ -2,5 +2,6 @@ cd "%~dp0.."
md .\src\gen
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-rcc ".\res\resources.qrc" -o ".\src\gen\resources_rc.py"
diff --git a/scripts/designer.bat b/scripts/designer.bat
index 3b7af65e..d3b61064 100644
--- a/scripts/designer.bat
+++ b/scripts/designer.bat
@@ -11,4 +11,9 @@ IF NOT DEFINED PYTHONPATH (
SET PYTHONPATH=!pythonFiles[0]!
)
-START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe" "%~d0%~p0..\res\design.ui" "%~d0%~p0..\res\about.ui" "%~d0%~p0..\res\update_checker.ui"
+START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe"^
+ "%~d0%~p0..\res\design.ui"^
+ "%~d0%~p0..\res\about.ui"^
+ "%~d0%~p0..\res\settings.ui"^
+ "%~d0%~p0..\res\update_checker.ui"
+
diff --git a/src/AutoControlledWorker.py b/src/AutoControlledWorker.py
index 0c252d44..36b231d9 100644
--- a/src/AutoControlledWorker.py
+++ b/src/AutoControlledWorker.py
@@ -20,6 +20,8 @@ def run(self):
except RuntimeError:
self.autosplit.show_error_signal.emit(error_messages.stdin_lost)
break
+ except EOFError:
+ continue
# TODO: "AutoSplit Integration" needs to call this and wait instead of outright killing the app.
# For now this can only used in a Development environment
if line == "kill":
diff --git a/src/AutoSplit.py b/src/AutoSplit.py
index 97031748..91d8b88f 100644
--- a/src/AutoSplit.py
+++ b/src/AutoSplit.py
@@ -28,18 +28,18 @@
import error_messages
import settings_file as settings
from AutoControlledWorker import AutoControlledWorker
-from capture_windows import capture_region, Rect, set_ui_image
-from gen import about, design, update_checker
-from hotkeys import send_command, after_setting_hotkey, set_split_hotkey, set_reset_hotkey, set_skip_split_hotkey, \
- set_undo_split_hotkey, set_pause_hotkey
-from menu_bar import open_about, VERSION, view_help, check_for_updates, open_update_checker
+from capture_windows import capture_region, set_ui_image
+from gen import about, design, settings as settings_ui, update_checker
+from hotkeys import send_command, after_setting_hotkey
+from menu_bar import get_default_settings_from_ui, open_about, VERSION, open_settings, view_help, check_for_updates, \
+ open_update_checker
from screen_region import select_region, select_window, align_region, validate_before_parsing
from settings_file import FROZEN
from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG, parse_and_validate_images
-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"
-START_IMAGE_TEXT = "Start Image"
+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")
START_AUTO_SPLITTER_TEXT = "Start Auto Splitter"
CHECK_FPS_ITERATIONS = 10
@@ -47,14 +47,14 @@
os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
-def make_excepthook(main_window: AutoSplit):
+def make_excepthook(autosplit: 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.show_error_signal.emit(lambda: error_messages.exception_traceback(
+ autosplit.show_error_signal.emit(lambda: error_messages.exception_traceback(
"AutoSplit encountered an unhandled exception and will try to recover, "
- f"however, there is no guarantee everything will work properly. {CREATE_NEW_ISSUE_MESSAGE}",
+ + f"however, there is no guarantee it will keep working properly. {CREATE_NEW_ISSUE_MESSAGE}",
exception))
return excepthook
@@ -82,9 +82,10 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow):
timer_start_image = QtCore.QTimer()
# Widgets
- AboutWidget: about.Ui_AboutAutoSplitWidget
- UpdateCheckerWidget: update_checker.Ui_UpdateChecker
- CheckForUpdatesThread: QtCore.QThread
+ AboutWidget: Optional[about.Ui_AboutAutoSplitWidget] = None
+ UpdateCheckerWidget: Optional[update_checker.Ui_UpdateChecker] = None
+ CheckForUpdatesThread: Optional[QtCore.QThread] = None
+ SettingsWidget: Optional[settings_ui.Ui_DialogSettings] = None
# hotkeys need to be initialized to be passed as thread arguments in hotkeys.py
# and for type safety in both hotkeys.py and settings_file.py
@@ -95,13 +96,10 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow):
pause_hotkey: Optional[Callable[[], None]] = None
# Initialize a few attributes
- split_image_directory = ""
hwnd = 0
"""Window Handle used for Capture Region"""
- window_text = ""
- selection = Rect()
last_saved_settings: list[Union[str, float, int, bool]] = []
- live_image_function_on_open = True
+ similarity = 0.0
split_image_number = 0
split_images_and_loop_number: list[tuple[AutoSplitImage, int]] = []
split_groups: list[list[int]] = []
@@ -130,34 +128,32 @@ def __init__(self, parent: Optional[QWidget] = None):
# Setup global error handling
self.show_error_signal.connect(lambda errorMessageBox: errorMessageBox())
- # Whithin LiveSplit excepthook needs to use main_window's signals to show errors
+ # Whithin LiveSplit excepthook needs to use MainWindow's signals to show errors
sys.excepthook = make_excepthook(self)
self.setupUi(self)
+ # Get default values defined in SettingsDialog
+ self.settings_dict = get_default_settings_from_ui(self)
settings.load_check_for_updates_on_open(self)
- # close all processes when closing window
self.action_view_help.triggered.connect(view_help)
self.action_about.triggered.connect(lambda: open_about(self))
self.action_check_for_updates.triggered.connect(lambda: check_for_updates(self))
- self.action_save_settings.triggered.connect(lambda: settings.save_settings(self))
- self.action_save_settings_as.triggered.connect(lambda: settings.save_settings_as(self))
- self.action_load_settings.triggered.connect(lambda: settings.load_settings(self))
+ self.action_settings.triggered.connect(lambda: open_settings(self))
+ self.action_save_profile.triggered.connect(lambda: settings.save_settings(self))
+ self.action_save_profile_as.triggered.connect(lambda: settings.save_settings_as(self))
+ self.action_load_profile.triggered.connect(lambda: settings.load_settings(self))
+
+ if self.SettingsWidget:
+ self.SettingsWidget.split_input.setEnabled(False)
+ self.SettingsWidget.reset_input.setEnabled(False)
+ self.SettingsWidget.skip_split_input.setEnabled(False)
+ self.SettingsWidget.undo_split_input.setEnabled(False)
+ self.SettingsWidget.pause_input.setEnabled(False)
if self.is_auto_controlled:
- self.set_split_hotkey_button.setEnabled(False)
- self.set_reset_hotkey_button.setEnabled(False)
- self.set_skip_split_hotkey_button.setEnabled(False)
- self.set_undo_split_hotkey_button.setEnabled(False)
- self.set_pause_hotkey_button.setEnabled(False)
self.start_auto_splitter_button.setEnabled(False)
- self.split_input.setEnabled(False)
- self.reset_input.setEnabled(False)
- self.skip_split_input.setEnabled(False)
- self.undo_split_input.setEnabled(False)
- self.pause_input.setEnabled(False)
- self.timer_global_hotkeys_label.setText("Hotkeys Inactive - Use LiveSplit Hotkeys")
# Send version and process ID to stdout
print(f"{VERSION}\n{os.getpid()}", flush=True)
@@ -183,14 +179,9 @@ def __init__(self, parent: Optional[QWidget] = None):
self.undo_split_button.clicked.connect(self.__undo_split)
self.next_image_button.clicked.connect(lambda: self.__skip_split(True))
self.previous_image_button.clicked.connect(lambda: self.__undo_split(True))
- self.set_split_hotkey_button.clicked.connect(lambda: set_split_hotkey(self))
- self.set_reset_hotkey_button.clicked.connect(lambda: set_reset_hotkey(self))
- self.set_skip_split_hotkey_button.clicked.connect(lambda: set_skip_split_hotkey(self))
- self.set_undo_split_hotkey_button.clicked.connect(lambda: set_undo_split_hotkey(self))
- self.set_pause_hotkey_button.clicked.connect(lambda: set_pause_hotkey(self))
self.align_region_button.clicked.connect(lambda: align_region(self))
self.select_window_button.clicked.connect(lambda: select_window(self))
- self.start_image_reload_button.clicked.connect(lambda: self.load_start_image(True, True))
+ self.reload_start_image_button.clicked.connect(lambda: self.load_start_image(True, True))
self.action_check_for_updates_on_open.changed.connect(lambda: settings.set_check_for_updates_on_open(
self,
self.action_check_for_updates_on_open.isChecked())
@@ -213,7 +204,7 @@ def __init__(self, parent: Optional[QWidget] = None):
self.pause_signal.connect(self.pause)
# live image checkbox
- self.live_image_checkbox.clicked.connect(self.check_live_image)
+ self.timer_live_image.start(int(1000 / 60))
self.timer_live_image.timeout.connect(self.__live_image_function)
# Automatic timer start
@@ -236,49 +227,37 @@ def __browse(self):
new_split_image_directory = QFileDialog.getExistingDirectory(
self,
"Select Split Image Directory",
- os.path.join(self.split_image_directory or settings.auto_split_directory, ".."))
+ os.path.join(self.settings_dict["split_image_directory"] or settings.auto_split_directory, ".."))
# If the user doesn't select a folder, it defaults to "".
if new_split_image_directory:
# set the split image folder line to the directory text
- self.split_image_directory = new_split_image_directory
+ self.settings_dict["split_image_directory"] = new_split_image_directory
self.split_image_folder_input.setText(f"{new_split_image_directory}/")
self.load_start_image()
- def check_live_image(self):
- if self.live_image_checkbox.isChecked():
- self.timer_live_image.start(int(1000 / 60))
- else:
- self.timer_live_image.stop()
- self.__live_image_function()
-
def __live_image_function(self):
- try:
- self.capture_region_window_label.setText(self.window_text)
- if not self.window_text:
- self.timer_live_image.stop()
- self.live_image.clear()
- if self.live_image_function_on_open:
- self.live_image_function_on_open = False
- return
- # Set live image in UI
- if self.hwnd:
- capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked())
- set_ui_image(self.live_image, capture, False)
-
- except AttributeError:
- pass
+ self.capture_region_window_label.setText(self.settings_dict["captured_window_title"])
+ if not (self.settings_dict["live_capture_region"] and self.settings_dict["captured_window_title"]):
+ self.live_image.clear()
+ return
+ # Set live image in UI
+ if self.hwnd:
+ capture = capture_region(self.hwnd,
+ self.settings_dict["capture_region"],
+ self.settings_dict["force_print_window"])
+ set_ui_image(self.live_image, capture, False)
def load_start_image(self, started_by_button: bool = False, wait_for_delay: bool = True):
self.timer_start_image.stop()
- self.current_split_image_file_label.setText(" ")
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: not found")
+ self.current_image_file_label.setText("-")
+ self.start_image_status_value_label.setText("not found")
QApplication.processEvents()
if not self.is_auto_controlled \
- and (not self.split_input.text()
- or not self.reset_input.text()
- or not self.pause_input.text()):
+ and (not self.settings_dict["split_hotkey"]
+ or not self.settings_dict["reset_hotkey"]
+ or not self.settings_dict["pause_hotkey"]):
error_messages.load_start_image()
return
@@ -295,17 +274,17 @@ def load_start_image(self, started_by_button: bool = False, wait_for_delay: bool
start_pause_time = self.start_image.get_pause_time(self)
if not wait_for_delay and start_pause_time > 0:
self.check_start_image_timestamp = time() + start_pause_time
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: paused")
- self.highest_similarity_label.setText(" ")
- self.current_similarity_threshold_number_label.setText(" ")
+ self.start_image_status_value_label.setText("paused")
+ self.table_current_image_highest_label.setText("-")
+ self.table_current_image_threshold_label.setText("-")
else:
self.check_start_image_timestamp = 0.0
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: ready")
+ self.start_image_status_value_label.setText("ready")
self.__update_split_image(self.start_image)
self.highest_similarity = 0.0
self.start_image_split_below_threshold = False
- self.timer_start_image.start(int(1000 / self.fps_limit_spinbox.value()))
+ self.timer_start_image.start(int(1000 / self.settings_dict["fps_limit"]))
QApplication.processEvents()
@@ -313,35 +292,31 @@ def __start_image_function(self):
if self.start_image is None \
or not self.start_image \
or time() < self.check_start_image_timestamp \
- or (not self.split_input.text() and not self.is_auto_controlled):
- pause_time_left = f"{self.check_start_image_timestamp - time():.1f}"
+ or (not self.settings_dict["split_hotkey"] and not self.is_auto_controlled):
+ pause_time_left = self.check_start_image_timestamp - time()
self.current_split_image.setText(
- f"None\n (Paused before loading {START_IMAGE_TEXT}).\n {pause_time_left} sec remaining")
+ f"None\n (Paused before loading Start Image).\n {seconds_remaining_text(pause_time_left)}")
return
if self.check_start_image_timestamp > 0:
self.check_start_image_timestamp = 0.0
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: ready")
+ self.start_image_status_value_label.setText("ready")
self.__update_split_image(self.start_image)
capture = self.__get_capture_for_comparison()
start_image_threshold = self.start_image.get_similarity_threshold(self)
start_image_similarity = self.start_image.compare_with_capture(self, capture)
- self.current_similarity_threshold_number_label.setText(f"{start_image_threshold:.2f}")
+ self.table_current_image_threshold_label.setText(f"{start_image_threshold:.2f}")
# Show live similarity if the checkbox is checked
- self.live_similarity_label.setText(str(start_image_similarity)[:4]
- if self.show_live_similarity_checkbox.isChecked()
- else " ")
+ self.table_current_image_live_label.setText(str(start_image_similarity)[:4])
# If the similarity becomes higher than highest similarity, set it as such.
if start_image_similarity > self.highest_similarity:
self.highest_similarity = start_image_similarity
# Show live highest similarity if the checkbox is checked
- self.highest_similarity_label.setText(str(self.highest_similarity)[:4]
- if self.show_highest_similarity_checkbox.isChecked()
- else " ")
+ self.table_current_image_highest_label.setText(str(self.highest_similarity)[:4])
# If the {b} flag is set, let similarity go above threshold first, then split on similarity below threshold
# Otherwise just split when similarity goes above threshold
@@ -361,46 +336,34 @@ def __start_image_function(self):
# delay start image if needed
if self.start_image.delay > 0:
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: delaying start...")
+ self.start_image_status_value_label.setText("delaying start...")
delay_start_time = time()
start_delay = self.start_image.delay / 1000
while time() - delay_start_time < start_delay:
- delay_time_left = round(start_delay - (time() - delay_start_time), 1)
+ delay_time_left = start_delay - (time() - delay_start_time)
self.current_split_image.setText(
- f"Delayed Before Starting:\n {delay_time_left} sec remaining")
+ f"Delayed Before Starting:\n {seconds_remaining_text(delay_time_left)}")
# Email sent to pyqt@riverbankcomputing.com
QtTest.QTest.qWait(1) # type: ignore
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: started")
+ self.start_image_status_value_label.setText("started")
send_command(self, "start")
# Email sent to pyqt@riverbankcomputing.com
- QtTest.QTest.qWait(int(1 / self.fps_limit_spinbox.value())) # type: ignore
+ QtTest.QTest.qWait(int(1 / self.settings_dict["fps_limit"])) # type: ignore
self.start_auto_splitter()
# update x, y, width, height when spinbox values are changed
def __update_x(self):
- try:
- self.selection.left = self.x_spinbox.value()
- self.selection.right = self.selection.left + self.width_spinbox.value()
- self.check_live_image()
- except AttributeError:
- pass
+ self.settings_dict["capture_region"].x = self.x_spinbox.value()
def __update_y(self):
- try:
- self.selection.top = self.y_spinbox.value()
- self.selection.bottom = self.selection.top + self.height_spinbox.value()
- self.check_live_image()
- except AttributeError:
- pass
+ self.settings_dict["capture_region"].y = self.y_spinbox.value()
def __update_width(self):
- self.selection.right = self.selection.left + self.width_spinbox.value()
- self.check_live_image()
+ self.settings_dict["capture_region"].width = self.width_spinbox.value()
def __update_height(self):
- self.selection.bottom = self.selection.top + self.height_spinbox.value()
- self.check_live_image()
+ self.settings_dict["capture_region"].height = self.height_spinbox.value()
def __take_screenshot(self):
if not validate_before_parsing(self, check_empty_directory=False):
@@ -411,13 +374,17 @@ def __take_screenshot(self):
# which is a problem, but I doubt anyone will get to 1000 split images...
screenshot_index = 1
while True:
- screenshot_path = os.path.join(self.split_image_directory, f"{screenshot_index:03}_SplitImage.png")
+ screenshot_path = os.path.join(
+ self.settings_dict["split_image_directory"],
+ f"{screenshot_index:03}_SplitImage.png")
if not os.path.exists(screenshot_path):
break
screenshot_index += 1
# Grab screenshot of capture region
- capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked())
+ capture = capture_region(self.hwnd,
+ self.settings_dict["capture_region"],
+ self.settings_dict["force_print_window"])
if capture is None:
error_messages.region()
return
@@ -427,7 +394,7 @@ def __take_screenshot(self):
os.startfile(screenshot_path)
def __check_fps(self):
- self.fps_value_label.setText(" ")
+ self.fps_value_label.clear()
if not (validate_before_parsing(self) and parse_and_validate_images(self)):
return
@@ -444,9 +411,7 @@ def __check_fps(self):
while count < CHECK_FPS_ITERATIONS:
capture = self.__get_capture_for_comparison()
_ = image.compare_with_capture(self, capture)
- set_ui_image(self.current_split_image, image.bytes, True)
count += 1
- self.current_split_image.clear()
# calculate FPS
t1 = time()
@@ -470,9 +435,9 @@ def __undo_split(self, navigate_image_only: bool = False):
return
if not navigate_image_only:
- for i, group in enumerate(self.split_groups):
+ for i, group in enumerate(self.split_groups,):
if i > 0 and self.split_image_number in group:
- self.split_image_number = self.split_groups[i - 1][0]
+ self.split_image_number = self.split_groups[i - 1][-1]
break
else:
self.split_image_number -= 1
@@ -521,15 +486,15 @@ def start_auto_splitter(self):
or (not self.start_auto_splitter_button.isEnabled() and not self.is_auto_controlled):
return
- start_label: str = self.start_image_label.text()
+ start_label: str = self.start_image_status_value_label.text()
if start_label.endswith("ready") or start_label.endswith("paused"):
- self.start_image_label.setText(f"{START_IMAGE_TEXT}: not ready")
+ self.start_image_status_value_label.setText("not ready")
self.start_auto_splitter_signal.emit()
def __check_for_reset(self):
if self.start_auto_splitter_button.text() == START_AUTO_SPLITTER_TEXT:
- if self.auto_start_on_reset_checkbox.isChecked():
+ if self.settings_dict["loop_splits"]:
self.start_auto_splitter_signal.emit()
else:
self.gui_changes_on_reset()
@@ -537,7 +502,7 @@ def __check_for_reset(self):
return False
def __auto_splitter(self):
- if not self.split_input.text() and not self.is_auto_controlled:
+ if not self.settings_dict["split_hotkey"] and not self.is_auto_controlled:
self.gui_changes_on_reset()
error_messages.split_hotkey()
return
@@ -606,20 +571,14 @@ def __auto_splitter(self):
self.similarity = self.split_image.compare_with_capture(self, capture)
# show live similarity if the checkbox is checked
- self.live_similarity_label.setText(
- str(self.similarity)[:4]
- if self.show_live_similarity_checkbox.isChecked()
- else " ")
+ self.table_current_image_live_label.setText(str(self.similarity)[:4])
# if the similarity becomes higher than highest similarity, set it as such.
if self.similarity > self.highest_similarity:
self.highest_similarity = self.similarity
# show live highest similarity if the checkbox is checked
- self.highest_similarity_label.setText(
- str(self.highest_similarity)[:4]
- if self.show_highest_similarity_checkbox.isChecked()
- else " ")
+ self.table_current_image_highest_label.setText(str(self.highest_similarity)[:4])
# If its the last split image and last loop number, disable the next image button
# If its the first split image, disable the undo split and previous image buttons
@@ -645,7 +604,7 @@ def __auto_splitter(self):
break
# limit the number of time the comparison runs to reduce cpu usage
- frame_interval: float = 1 / self.fps_limit_spinbox.value()
+ frame_interval: float = 1 / self.settings_dict["fps_limit"]
# Email sent to pyqt@riverbankcomputing.com
QtTest.QTest.qWait(int(frame_interval - (time() - start) % frame_interval)) # type: ignore
QApplication.processEvents()
@@ -663,13 +622,13 @@ def __auto_splitter(self):
self.waiting_for_split_delay = True
self.undo_split_button.setEnabled(False)
self.skip_split_button.setEnabled(False)
- self.current_split_image_file_label.setText(" ")
+ self.current_image_file_label.clear()
# check for reset while delayed and display a counter of the remaining split delay time
delay_start_time = time()
while time() - delay_start_time < split_delay:
- delay_time_left = round(split_delay - (time() - delay_start_time), 1)
- self.current_split_image.setText(f"Delayed Split: {delay_time_left} sec remaining")
+ delay_time_left = split_delay - (time() - delay_start_time)
+ self.current_split_image.setText(f"Delayed Split: {seconds_remaining_text(delay_time_left)}")
if self.__check_for_reset():
return
@@ -687,7 +646,7 @@ def __auto_splitter(self):
# if loop check box is checked and its the last split, go to first split.
# else go to the next split image.
- if self.loop_checkbox.isChecked() and self.split_image_number == number_of_split_images - 1:
+ if self.settings_dict["loop_splits"] and self.split_image_number == number_of_split_images - 1:
self.split_image_number = 0
else:
self.split_image_number += 1
@@ -716,8 +675,8 @@ def __auto_splitter(self):
if pause_time > 0:
pause_start_time = time()
while time() - pause_start_time < pause_time:
- pause_time_left = round(pause_time - (time() - pause_start_time), 1)
- self.current_split_image.setText(f"None (Paused). {pause_time_left} sec remaining")
+ pause_time_left = pause_time - (time() - pause_start_time)
+ self.current_split_image.setText(f"None (Paused). {seconds_remaining_text(pause_time_left)}")
if self.__check_for_reset():
return
@@ -742,46 +701,49 @@ def gui_changes_on_start(self):
self.timer_start_image.stop()
self.start_auto_splitter_button.setText("Running...")
self.browse_button.setEnabled(False)
- self.start_image_reload_button.setEnabled(False)
+ self.reload_start_image_button.setEnabled(False)
self.previous_image_button.setEnabled(True)
self.next_image_button.setEnabled(True)
+ if self.SettingsWidget:
+ self.SettingsWidget.set_split_hotkey_button.setEnabled(False)
+ self.SettingsWidget.set_reset_hotkey_button.setEnabled(False)
+ self.SettingsWidget.set_skip_split_hotkey_button.setEnabled(False)
+ self.SettingsWidget.set_undo_split_hotkey_button.setEnabled(False)
+ self.SettingsWidget.set_pause_hotkey_button.setEnabled(False)
+
if not self.is_auto_controlled:
self.start_auto_splitter_button.setEnabled(False)
self.reset_button.setEnabled(True)
self.undo_split_button.setEnabled(True)
self.skip_split_button.setEnabled(True)
- self.set_split_hotkey_button.setEnabled(False)
- self.set_reset_hotkey_button.setEnabled(False)
- self.set_skip_split_hotkey_button.setEnabled(False)
- self.set_undo_split_hotkey_button.setEnabled(False)
- self.set_pause_hotkey_button.setEnabled(False)
QApplication.processEvents()
def gui_changes_on_reset(self):
self.start_auto_splitter_button.setText(START_AUTO_SPLITTER_TEXT)
- self.image_loop_label.setText("Image Loop: -")
- self.current_split_image.setText(" ")
- self.current_split_image_file_label.setText(" ")
- self.live_similarity_label.setText(" ")
- self.highest_similarity_label.setText(" ")
- self.current_similarity_threshold_number_label.setText(" ")
+ self.image_loop_value_label.setText("N/A")
+ self.current_split_image.clear()
+ self.current_image_file_label.clear()
+ self.table_current_image_live_label.setText("-")
+ self.table_current_image_highest_label.setText("-")
+ self.table_current_image_threshold_label.setText("-")
self.browse_button.setEnabled(True)
- self.start_image_reload_button.setEnabled(True)
+ self.reload_start_image_button.setEnabled(True)
self.previous_image_button.setEnabled(False)
self.next_image_button.setEnabled(False)
+ if self.SettingsWidget:
+ self.SettingsWidget.set_split_hotkey_button.setEnabled(True)
+ self.SettingsWidget.set_reset_hotkey_button.setEnabled(True)
+ self.SettingsWidget.set_skip_split_hotkey_button.setEnabled(True)
+ self.SettingsWidget.set_undo_split_hotkey_button.setEnabled(True)
+ self.SettingsWidget.set_pause_hotkey_button.setEnabled(True)
if not self.is_auto_controlled:
self.start_auto_splitter_button.setEnabled(True)
self.reset_button.setEnabled(False)
self.undo_split_button.setEnabled(False)
self.skip_split_button.setEnabled(False)
- self.set_split_hotkey_button.setEnabled(True)
- self.set_reset_hotkey_button.setEnabled(True)
- self.set_skip_split_hotkey_button.setEnabled(True)
- self.set_undo_split_hotkey_button.setEnabled(True)
- self.set_pause_hotkey_button.setEnabled(True)
QApplication.processEvents()
self.load_start_image(False, False)
@@ -790,18 +752,22 @@ def __get_capture_for_comparison(self):
"""
Grab capture region and resize for comparison
"""
- capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked())
+ capture = capture_region(self.hwnd,
+ self.settings_dict["capture_region"],
+ self.settings_dict["force_print_window"])
# This most likely means we lost capture (ie the captured window was closed, crashed, etc.)
if capture is None:
# Try to recover by using the window name
self.live_image.setText("Trying to recover window...")
# https://github.com/kaluluosi/pywin32-stubs/issues/7
- hwnd = win32gui.FindWindow(None, self.window_text) # type: ignore
+ hwnd = win32gui.FindWindow(None, self.settings_dict["captured_window_title"]) # type: ignore
# Don't fallback to desktop
if hwnd:
self.hwnd = hwnd
- capture = capture_region(self.hwnd, self.selection, self.force_print_window_checkbox.isChecked())
+ capture = capture_region(self.hwnd,
+ self.settings_dict["capture_region"],
+ self.settings_dict["force_print_window"])
return None if capture is None else cv2.resize(capture, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST)
def __reset_if_should(self, capture: Optional[cv2.ndarray]):
@@ -833,15 +799,15 @@ def __update_split_image(self, specific_image: Optional[AutoSplitImage] = None):
if self.split_image.bytes is not None:
set_ui_image(self.current_split_image, self.split_image.bytes, True)
- self.current_split_image_file_label.setText(self.split_image.filename)
- self.current_similarity_threshold_number_label.setText(f"{self.split_image.get_similarity_threshold(self):.2f}")
+ self.current_image_file_label.setText(self.split_image.filename)
+ self.table_current_image_threshold_label.setText(f"{self.split_image.get_similarity_threshold(self):.2f}")
# Set Image Loop #
if specific_image and specific_image.image_type == ImageType.START:
- self.image_loop_label.setText("Image Loop: N/A")
+ self.image_loop_value_label.setText("N/A")
else:
loop_tuple = self.split_images_and_loop_number[self.split_image_number]
- self.image_loop_label.setText(f"Image Loop: {loop_tuple[1]}/{loop_tuple[0].loops}")
+ self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}")
self.highest_similarity = 0.0
# need to set split below threshold to false each time an image updates.
@@ -894,6 +860,10 @@ def exit_program():
exit_program()
+def seconds_remaining_text(seconds: float):
+ return f"{seconds:.1f} second{'' if 0 < seconds <= 1 else 's'} remaining"
+
+
def main():
# Call to QApplication outside the try-except so we can show error messages
app = QApplication(sys.argv)
diff --git a/src/AutoSplitImage.py b/src/AutoSplitImage.py
index 126f2abe..9eeaf8a9 100644
--- a/src/AutoSplitImage.py
+++ b/src/AutoSplitImage.py
@@ -46,7 +46,7 @@ def get_pause_time(self, default: Union[AutoSplit, float]):
"""
default_value: float = default \
if isinstance(default, float) \
- else default.pause_spinbox.value()
+ else default.settings_dict["default_pause_time"]
return default_value if self.__pause_time is None else self.__pause_time
def get_similarity_threshold(self, default: Union[AutoSplit, float]):
@@ -55,7 +55,7 @@ def get_similarity_threshold(self, default: Union[AutoSplit, float]):
"""
default_value: float = default \
if isinstance(default, float) \
- else default.similarity_threshold_spinbox.value()
+ else default.settings_dict["default_similarity_threshold"]
return default_value if self.__similarity_threshold is None else self.__similarity_threshold
def __init__(self, path: str):
@@ -109,7 +109,7 @@ def compare_with_capture(
"""
comparison_method: int = comparison \
if isinstance(comparison, int) \
- else comparison.comparison_method_combobox.currentIndex()
+ else comparison.settings_dict["default_comparison_method"]
if self.bytes is None or capture is None:
return 0.0
diff --git a/src/capture_windows.py b/src/capture_windows.py
index c991cc38..c6401efa 100644
--- a/src/capture_windows.py
+++ b/src/capture_windows.py
@@ -20,17 +20,15 @@
@dataclass
-class Rect(ctypes.wintypes.RECT):
- """
- Overrides `ctypes.wintypes.RECT` to replace c_long with int for math operators
- """
- left: int = -1 # type: ignore
- top: int = -1 # type: ignore
- right: int = -1 # type: ignore
- bottom: int = -1 # type: ignore
+class Region():
+ def __init__(self, x: int, y: int, width: int, height: int):
+ self.x = x
+ self.y = y
+ self.width = width
+ self.height = height
-def capture_region(hwnd: int, selection: Rect, print_window: bool):
+def capture_region(hwnd: int, selection: Region, print_window: bool):
"""
Captures an image of the region for a window matching the given
parameters of the bounding box
@@ -40,8 +38,6 @@ def capture_region(hwnd: int, selection: Rect, print_window: bool):
@return: The image of the region in the window in BGRA format
"""
- width: int = selection.right - selection.left
- height: int = selection.bottom - selection.top
# If the window closes while it's being manipulated, it could cause a crash
try:
window_dc: int = win32gui.GetWindowDC(hwnd)
@@ -54,16 +50,17 @@ def capture_region(hwnd: int, selection: Rect, print_window: bool):
compatible_dc = cast(PyCDC, dc_object.CreateCompatibleDC())
bitmap: PyCBitmap = win32ui.CreateBitmap()
- bitmap.CreateCompatibleBitmap(dc_object, width, height)
+ bitmap.CreateCompatibleBitmap(dc_object, selection.width, selection.height)
compatible_dc.SelectObject(bitmap)
- compatible_dc.BitBlt((0, 0), (width, height), dc_object, (selection.left, selection.top), win32con.SRCCOPY)
+ compatible_dc.BitBlt((0, 0), (selection.width, selection.height), dc_object,
+ (selection.x, selection.y), win32con.SRCCOPY)
# https://github.com/kaluluosi/pywin32-stubs/issues/5
# pylint: disable=no-member
except (win32ui.error, pywintypes.error): # type: ignore
return None
image = np.frombuffer(cast(bytes, bitmap.GetBitmapBits(True)), dtype="uint8")
- image.shape = (height, width, 4)
+ image.shape = (selection.height, selection.width, 4)
try:
dc_object.DeleteDC()
diff --git a/src/error_messages.py b/src/error_messages.py
index 5d8bd382..cb437f36 100644
--- a/src/error_messages.py
+++ b/src/error_messages.py
@@ -31,12 +31,12 @@ def split_image_directory_empty():
def image_type(image: str):
set_text_message(f'"{image}" is not a valid image file, does not exist, '
- "or the full image file path contains a special character.")
+ + "or the full image file path contains a special character.")
def region():
set_text_message("No region is selected or the Capture Region window is not open. "
- "Select a region or load settings while the Capture Region window is open.")
+ + "Select a region or load settings while the Capture Region window is open.")
def split_hotkey():
@@ -45,7 +45,7 @@ def split_hotkey():
def pause_hotkey():
set_text_message("Your split image folder contains an image filename with a pause flag {p}, "
- "but no pause hotkey is set.")
+ + "but no pause hotkey is set.")
def align_region_image_type():
@@ -82,7 +82,7 @@ def no_settings_file_on_open():
def too_many_settings_files_on_open():
set_text_message("Too many settings files found. "
- "Only one can be loaded on open if placed in the same folder as AutoSplit.exe")
+ + "Only one can be loaded on open if placed in the same folder as AutoSplit.exe")
def check_for_updates():
@@ -91,7 +91,7 @@ def check_for_updates():
def load_start_image():
set_text_message("Start Image found, but cannot be loaded unless Start, Reset, and Pause hotkeys are set. "
- "Please set these hotkeys, and then click the Reload Start Image button.")
+ + "Please set these hotkeys, and then click the Reload Start Image button.")
def stdin_lost():
diff --git a/src/hotkeys.py b/src/hotkeys.py
index 80701127..9fcdf23c 100644
--- a/src/hotkeys.py
+++ b/src/hotkeys.py
@@ -1,14 +1,15 @@
from __future__ import annotations
from typing import Literal, Optional, TYPE_CHECKING, Union
from collections.abc import Callable
+
if TYPE_CHECKING:
from AutoSplit import AutoSplit
import threading
-from keyboard._keyboard_event import KeyboardEvent, KEY_DOWN
+
import keyboard # https://github.com/boppreh/keyboard/issues/505
import pyautogui # https://github.com/asweigart/pyautogui/issues/645
-# While not usually recommended, we don'thread manipulate the mouse, and we don'thread want the extra delay
+# While not usually recommended, we don't manipulate the mouse, and we don't want the extra delay
pyautogui.FAILSAFE = False
SET_HOTKEY_TEXT = "Set Hotkey"
@@ -18,27 +19,29 @@
# do all of these after you click "Set Hotkey" but before you type the hotkey.
def before_setting_hotkey(autosplit: AutoSplit):
autosplit.start_auto_splitter_button.setEnabled(False)
- autosplit.set_split_hotkey_button.setEnabled(False)
- autosplit.set_reset_hotkey_button.setEnabled(False)
- autosplit.set_skip_split_hotkey_button.setEnabled(False)
- autosplit.set_undo_split_hotkey_button.setEnabled(False)
- autosplit.set_pause_hotkey_button.setEnabled(False)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_split_hotkey_button.setEnabled(False)
+ autosplit.SettingsWidget.set_reset_hotkey_button.setEnabled(False)
+ autosplit.SettingsWidget.set_skip_split_hotkey_button.setEnabled(False)
+ autosplit.SettingsWidget.set_undo_split_hotkey_button.setEnabled(False)
+ autosplit.SettingsWidget.set_pause_hotkey_button.setEnabled(False)
# do all of these things after you set a hotkey. a signal connects to this because
# changing GUI stuff in the hotkey thread was causing problems
def after_setting_hotkey(autosplit: AutoSplit):
- autosplit.set_split_hotkey_button.setText(SET_HOTKEY_TEXT)
- autosplit.set_reset_hotkey_button.setText(SET_HOTKEY_TEXT)
- autosplit.set_skip_split_hotkey_button.setText(SET_HOTKEY_TEXT)
- autosplit.set_undo_split_hotkey_button.setText(SET_HOTKEY_TEXT)
- autosplit.set_pause_hotkey_button.setText(SET_HOTKEY_TEXT)
autosplit.start_auto_splitter_button.setEnabled(True)
- autosplit.set_split_hotkey_button.setEnabled(True)
- autosplit.set_reset_hotkey_button.setEnabled(True)
- autosplit.set_skip_split_hotkey_button.setEnabled(True)
- autosplit.set_undo_split_hotkey_button.setEnabled(True)
- autosplit.set_pause_hotkey_button.setEnabled(True)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_split_hotkey_button.setText(SET_HOTKEY_TEXT)
+ autosplit.SettingsWidget.set_reset_hotkey_button.setText(SET_HOTKEY_TEXT)
+ autosplit.SettingsWidget.set_skip_split_hotkey_button.setText(SET_HOTKEY_TEXT)
+ autosplit.SettingsWidget.set_undo_split_hotkey_button.setText(SET_HOTKEY_TEXT)
+ autosplit.SettingsWidget.set_pause_hotkey_button.setText(SET_HOTKEY_TEXT)
+ autosplit.SettingsWidget.set_split_hotkey_button.setEnabled(True)
+ autosplit.SettingsWidget.set_reset_hotkey_button.setEnabled(True)
+ autosplit.SettingsWidget.set_skip_split_hotkey_button.setEnabled(True)
+ autosplit.SettingsWidget.set_undo_split_hotkey_button.setEnabled(True)
+ autosplit.SettingsWidget.set_pause_hotkey_button.setEnabled(True)
def is_digit(key: Optional[str]):
@@ -57,15 +60,15 @@ def send_command(autosplit: AutoSplit, command: Commands):
if autosplit.is_auto_controlled:
print(command, flush=True)
elif command in {"split", "start"}:
- _send_hotkey(autosplit.split_input.text())
+ _send_hotkey(autosplit.settings_dict["split_hotkey"])
elif command == "pause":
- _send_hotkey(autosplit.pause_input.text())
+ _send_hotkey(autosplit.settings_dict["pause_hotkey"])
elif command == "reset":
- _send_hotkey(autosplit.reset_input.text())
+ _send_hotkey(autosplit.settings_dict["reset_hotkey"])
elif command == "skip":
- _send_hotkey(autosplit.skip_split_input.text())
+ _send_hotkey(autosplit.settings_dict["skip_split_hotkey"])
elif command == "undo":
- _send_hotkey(autosplit.undo_split_input.text())
+ _send_hotkey(autosplit.settings_dict["undo_split_hotkey"])
else:
raise KeyError(f"'{command}' is not a valid LiveSplit.AutoSplitIntegration command")
@@ -97,11 +100,11 @@ def _send_hotkey(key_or_scan_code: Union[int, str]):
pyautogui.hotkey(key_or_scan_code.replace(" ", ""))
-def __validate_keypad(expected_key: str, keyboard_event: KeyboardEvent) -> bool:
+def __validate_keypad(expected_key: str, keyboard_event: keyboard.KeyboardEvent) -> bool:
# Prevent "(keypad)delete", "(keypad)./decimal" and "del" from triggering each other
# as well as "." and "(keypad)./decimal"
if keyboard_event.scan_code in {83, 52}:
- # TODO: "del" won'thread work with "(keypad)delete" if localized in non-english (ie: "suppr" in french)
+ # TODO: "del" won't work with "(keypad)delete" if localized in non-english (ie: "suppr" in french)
return expected_key == keyboard_event.name
# Prevent "action keys" from triggering "keypad keys"
if keyboard_event.name and is_digit(keyboard_event.name[-1]):
@@ -122,34 +125,35 @@ def __validate_keypad(expected_key: str, keyboard_event: KeyboardEvent) -> bool:
# We're doing the check here instead of saving the key code because it'll
# cause issues with save files and the non-keypad shared keys are localized
-# while the keypad ones aren'thread.
+# while the keypad ones aren't.
-# Since we reuse the key string we set to send to LiveSplit, we can'thread use fake names like "num home".
+# Since we reuse the key string we set to send to LiveSplit, we can't use fake names like "num home".
# We're also trying to achieve the same hotkey behaviour as LiveSplit has.
-def _hotkey_action(keyboard_event: KeyboardEvent, key_name: str, action: Callable[[], None]):
- if keyboard_event.event_type == KEY_DOWN and __validate_keypad(key_name, keyboard_event):
+def _hotkey_action(keyboard_event: keyboard.KeyboardEvent, key_name: str, action: Callable[[], None]):
+ if keyboard_event.event_type == keyboard.KEY_DOWN and __validate_keypad(key_name, keyboard_event):
action()
-def __get_key_name(keyboard_event: KeyboardEvent):
+def __get_key_name(keyboard_event: keyboard.KeyboardEvent):
return f"num {keyboard_event.name}" \
if keyboard_event.is_keypad and is_digit(keyboard_event.name) \
else str(keyboard_event.name)
def __is_key_already_set(autosplit: AutoSplit, key_name: str):
- return key_name in (autosplit.split_input.text(),
- autosplit.reset_input.text(),
- autosplit.skip_split_input.text(),
- autosplit.undo_split_input.text(),
- autosplit.pause_input.text())
+ return key_name in (autosplit.settings_dict["split_hotkey"],
+ autosplit.settings_dict["reset_hotkey"],
+ autosplit.settings_dict["skip_split_hotkey"],
+ autosplit.settings_dict["undo_split_hotkey"],
+ autosplit.settings_dict["pause_hotkey"])
# --------------------HOTKEYS--------------------------
# TODO: Refactor to de-duplicate all this code, including settings_file.py
# Going to comment on one func, and others will be similar.
def set_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""):
- autosplit.set_split_hotkey_button.setText(PRESS_A_KEY_TEXT)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_split_hotkey_button.setText(PRESS_A_KEY_TEXT)
# disable some buttons
before_setting_hotkey(autosplit)
@@ -188,7 +192,9 @@ def callback():
autosplit.split_hotkey = keyboard.hook_key(
key_name,
lambda error: _hotkey_action(error, key_name, autosplit.start_auto_splitter))
- autosplit.split_input.setText(key_name)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.split_input.setText(key_name)
+ autosplit.settings_dict["split_hotkey"] = key_name
autosplit.after_setting_hotkey_signal.emit()
# try to remove the previously set hotkey if there is one.
@@ -198,7 +204,8 @@ def callback():
def set_reset_hotkey(autosplit: AutoSplit, preselected_key: str = ""):
- autosplit.set_reset_hotkey_button.setText(PRESS_A_KEY_TEXT)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_reset_hotkey_button.setText(PRESS_A_KEY_TEXT)
before_setting_hotkey(autosplit)
def callback():
@@ -215,7 +222,9 @@ def callback():
autosplit.reset_hotkey = keyboard.hook_key(
key_name,
lambda error: _hotkey_action(error, key_name, autosplit.reset_signal.emit))
- autosplit.reset_input.setText(key_name)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.reset_input.setText(key_name)
+ autosplit.settings_dict["reset_hotkey"] = key_name
autosplit.after_setting_hotkey_signal.emit()
_unhook(autosplit.reset_hotkey)
@@ -224,7 +233,8 @@ def callback():
def set_skip_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""):
- autosplit.set_skip_split_hotkey_button.setText(PRESS_A_KEY_TEXT)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_skip_split_hotkey_button.setText(PRESS_A_KEY_TEXT)
before_setting_hotkey(autosplit)
def callback():
@@ -241,7 +251,9 @@ def callback():
autosplit.skip_split_hotkey = keyboard.hook_key(
key_name,
lambda error: _hotkey_action(error, key_name, autosplit.skip_split_signal.emit))
- autosplit.skip_split_input.setText(key_name)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.skip_split_input.setText(key_name)
+ autosplit.settings_dict["skip_split_hotkey"] = key_name
autosplit.after_setting_hotkey_signal.emit()
_unhook(autosplit.skip_split_hotkey)
@@ -250,7 +262,8 @@ def callback():
def set_undo_split_hotkey(autosplit: AutoSplit, preselected_key: str = ""):
- autosplit.set_undo_split_hotkey_button.setText(PRESS_A_KEY_TEXT)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_undo_split_hotkey_button.setText(PRESS_A_KEY_TEXT)
before_setting_hotkey(autosplit)
def callback():
@@ -267,7 +280,9 @@ def callback():
autosplit.undo_split_hotkey = keyboard.hook_key(
key_name,
lambda error: _hotkey_action(error, key_name, autosplit.undo_split_signal.emit))
- autosplit.undo_split_input.setText(key_name)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.undo_split_input.setText(key_name)
+ autosplit.settings_dict["undo_split_hotkey"] = key_name
autosplit.after_setting_hotkey_signal.emit()
_unhook(autosplit.undo_split_hotkey)
@@ -276,7 +291,8 @@ def callback():
def set_pause_hotkey(autosplit: AutoSplit, preselected_key: str = ""):
- autosplit.set_pause_hotkey_button.setText(PRESS_A_KEY_TEXT)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.set_pause_hotkey_button.setText(PRESS_A_KEY_TEXT)
before_setting_hotkey(autosplit)
def callback():
@@ -293,7 +309,9 @@ def callback():
autosplit.pause_hotkey = keyboard.hook_key(
key_name,
lambda error: _hotkey_action(error, key_name, autosplit.pause_signal.emit))
- autosplit.pause_input.setText(key_name)
+ if autosplit.SettingsWidget:
+ autosplit.SettingsWidget.pause_input.setText(key_name)
+ autosplit.settings_dict["pause_hotkey"] = key_name
autosplit.after_setting_hotkey_signal.emit()
_unhook(autosplit.pause_hotkey)
diff --git a/src/menu_bar.py b/src/menu_bar.py
index 66cb9c85..d90c1e8c 100644
--- a/src/menu_bar.py
+++ b/src/menu_bar.py
@@ -1,5 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
+
if TYPE_CHECKING:
from AutoSplit import AutoSplit
@@ -14,7 +15,9 @@
import error_messages
import settings_file as settings
-from gen import about, design, resources_rc, update_checker # noqa: F401
+from capture_windows import Region
+from gen import about, design, resources_rc, settings as settings_ui, update_checker # noqa: F401
+from hotkeys import set_split_hotkey, set_reset_hotkey, set_skip_split_hotkey, set_undo_split_hotkey, set_pause_hotkey
# AutoSplit Version number
VERSION = "1.6.1"
@@ -82,7 +85,7 @@ def __init__(self, autosplit: AutoSplit, check_on_open: bool):
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]
+ latest_version = str(response.json()["name"]).split("v")[1]
self.autosplit.update_checker_widget_signal.emit(latest_version, self.check_on_open)
except (RequestException, KeyError, JSONDecodeError):
if not self.check_on_open:
@@ -92,3 +95,107 @@ def run(self):
def check_for_updates(autosplit: AutoSplit, check_on_open: bool = False):
autosplit.CheckForUpdatesThread = __CheckForUpdatesThread(autosplit, check_on_open)
autosplit.CheckForUpdatesThread.start()
+
+
+class __SettingsWidget(QtWidgets.QDialog, settings_ui.Ui_DialogSettings):
+ def __init__(self, autosplit: AutoSplit):
+ super().__init__()
+ self.setupUi(self)
+ self.autosplit = autosplit
+
+ def set_value(key: str, value: Any):
+ autosplit.settings_dict[key] = value
+
+# region Set initial values
+ # Hotkeys
+ self.split_input.setText(autosplit.settings_dict["split_hotkey"])
+ self.reset_input.setText(autosplit.settings_dict["reset_hotkey"])
+ self.undo_split_input.setText(autosplit.settings_dict["undo_split_hotkey"])
+ self.skip_split_input.setText(autosplit.settings_dict["skip_split_hotkey"])
+ self.pause_input.setText(autosplit.settings_dict["pause_hotkey"])
+
+ # Capture Settings
+ self.fps_limit_spinbox.setValue(autosplit.settings_dict["fps_limit"])
+ self.live_capture_region_checkbox.setChecked(autosplit.settings_dict["live_capture_region"])
+ self.force_print_window_checkbox.setChecked(autosplit.settings_dict["force_print_window"])
+
+ # Image Settings
+ self.default_comparison_method.setCurrentIndex(autosplit.settings_dict["default_comparison_method"])
+ self.default_similarity_threshold_spinbox.setValue(autosplit.settings_dict["default_similarity_threshold"])
+ self.default_delay_time_spinbox.setValue(autosplit.settings_dict["default_delay_time"])
+ self.default_pause_time_spinbox.setValue(autosplit.settings_dict["default_pause_time"])
+ self.loop_splits_checkbox.setChecked(autosplit.settings_dict["loop_splits"])
+# endregion
+# region Binding
+ # Hotkeys
+ self.set_split_hotkey_button.clicked.connect(lambda: set_split_hotkey(self.autosplit))
+ self.set_reset_hotkey_button.clicked.connect(lambda: set_reset_hotkey(self.autosplit))
+ self.set_skip_split_hotkey_button.clicked.connect(lambda: set_skip_split_hotkey(self.autosplit))
+ self.set_undo_split_hotkey_button.clicked.connect(lambda: set_undo_split_hotkey(self.autosplit))
+ self.set_pause_hotkey_button.clicked.connect(lambda: set_pause_hotkey(self.autosplit))
+
+ # Capture Settings
+ self.fps_limit_spinbox.valueChanged.connect(lambda: set_value(
+ "fps_limit",
+ self.fps_limit_spinbox.value()))
+ self.live_capture_region_checkbox.stateChanged.connect(lambda: set_value(
+ "live_capture_region",
+ self.live_capture_region_checkbox.isChecked()))
+ self.force_print_window_checkbox.stateChanged.connect(lambda: set_value(
+ "force_print_window",
+ self.force_print_window_checkbox.isChecked()))
+
+ # Image Settings
+ self.default_comparison_method.currentIndexChanged.connect(lambda: set_value(
+ "default_comparison_method",
+ self.default_comparison_method.currentIndex()))
+ self.default_similarity_threshold_spinbox.valueChanged.connect(lambda: set_value(
+ "default_similarity_threshold",
+ self.default_similarity_threshold_spinbox.value()))
+ self.default_delay_time_spinbox.valueChanged.connect(lambda: set_value(
+ "default_delay_time",
+ self.default_delay_time_spinbox.value()))
+ self.default_pause_time_spinbox.valueChanged.connect(lambda: set_value(
+ "default_pause_time",
+ self.default_pause_time_spinbox.value()))
+ self.loop_splits_checkbox.stateChanged.connect(lambda: set_value(
+ "loop_splits",
+ self.loop_splits_checkbox.isChecked()))
+# endregion
+
+ self.show()
+
+
+def open_settings(autosplit: AutoSplit):
+ autosplit.SettingsWidget = __SettingsWidget(autosplit)
+
+
+def get_default_settings_from_ui(autosplit: AutoSplit):
+ temp_dialog = QtWidgets.QDialog()
+ default_settings_dialog = settings_ui.Ui_DialogSettings()
+ default_settings_dialog.setupUi(temp_dialog)
+ default_settings: settings.SettingsDict = {
+ "split_hotkey": default_settings_dialog.split_input.text(),
+ "reset_hotkey": default_settings_dialog.reset_input.text(),
+ "undo_split_hotkey": default_settings_dialog.undo_split_input.text(),
+ "skip_split_hotkey": default_settings_dialog.skip_split_input.text(),
+ "pause_hotkey": default_settings_dialog.pause_input.text(),
+ "fps_limit": default_settings_dialog.fps_limit_spinbox.value(),
+ "live_capture_region": default_settings_dialog.live_capture_region_checkbox.isChecked(),
+ "force_print_window": default_settings_dialog.force_print_window_checkbox.isChecked(),
+ "default_comparison_method": default_settings_dialog.default_comparison_method.currentIndex(),
+ "default_similarity_threshold": default_settings_dialog.default_similarity_threshold_spinbox.value(),
+ "default_delay_time": default_settings_dialog.default_delay_time_spinbox.value(),
+ "default_pause_time": default_settings_dialog.default_pause_time_spinbox.value(),
+ "loop_splits": default_settings_dialog.loop_splits_checkbox.isChecked(),
+
+ "split_image_directory": autosplit.split_image_folder_input.text(),
+ "captured_window_title": "",
+ "capture_region": Region(
+ autosplit.x_spinbox.value(),
+ autosplit.y_spinbox.value(),
+ autosplit.width_spinbox.value(),
+ autosplit.height_spinbox.value())
+ }
+ del temp_dialog
+ return default_settings
diff --git a/src/screen_region.py b/src/screen_region.py
index 03bbf85e..2342dd8d 100644
--- a/src/screen_region.py
+++ b/src/screen_region.py
@@ -45,7 +45,7 @@ def select_region(autosplit: AutoSplit):
error_messages.region()
return
autosplit.hwnd = hwnd
- autosplit.window_text = window_text
+ autosplit.settings_dict["captured_window_title"] = window_text
offset_x, offset_y, *_ = win32gui.GetWindowRect(autosplit.hwnd)
__set_region_values(autosplit,
@@ -76,7 +76,7 @@ def select_window(autosplit: AutoSplit):
error_messages.region()
return
autosplit.hwnd = hwnd
- autosplit.window_text = window_text
+ autosplit.settings_dict["captured_window_title"] = window_text
# Getting window bounds
# On Windows there is a shadow around the windows that we need to account for
@@ -134,8 +134,8 @@ def align_region(autosplit: AutoSplit):
# subregion being searched for to align the image.
capture = capture_windows.capture_region(
autosplit.hwnd,
- autosplit.selection,
- autosplit.force_print_window_checkbox.isChecked())
+ autosplit.settings_dict["capture_region"],
+ autosplit.settings_dict["force_print_window"])
if capture is None:
error_messages.region()
@@ -151,25 +151,23 @@ def align_region(autosplit: AutoSplit):
# The new region can be defined by using the min_loc point and the best_height and best_width of the template.
__set_region_values(autosplit,
- left=autosplit.selection.left + best_loc[0],
- top=autosplit.selection.top + best_loc[1],
+ left=autosplit.settings_dict["capture_region"].x + best_loc[0],
+ top=autosplit.settings_dict["capture_region"].y + best_loc[1],
width=best_width,
height=best_height)
def __set_region_values(autosplit: AutoSplit, left: int, top: int, width: int, height: int):
- autosplit.selection.left = left
- autosplit.selection.top = top
- autosplit.selection.right = left + width
- autosplit.selection.bottom = top + height
+ autosplit.settings_dict["capture_region"].x = left
+ autosplit.settings_dict["capture_region"].y = top
+ autosplit.settings_dict["capture_region"].width = width
+ autosplit.settings_dict["capture_region"].height = height
autosplit.x_spinbox.setValue(left)
autosplit.y_spinbox.setValue(top)
autosplit.width_spinbox.setValue(width)
autosplit.height_spinbox.setValue(height)
- autosplit.check_live_image()
-
def __test_alignment(capture: cv2.ndarray, template: cv2.ndarray):
# Obtain the best matching point for the template within the
@@ -213,11 +211,11 @@ def __test_alignment(capture: cv2.ndarray, template: cv2.ndarray):
def validate_before_parsing(autosplit: AutoSplit, show_error: bool = True, check_empty_directory: bool = True):
error = None
- if not autosplit.split_image_directory:
+ if not autosplit.settings_dict["split_image_directory"]:
error = error_messages.split_image_directory
- elif not os.path.isdir(autosplit.split_image_directory):
+ elif not os.path.isdir(autosplit.settings_dict["split_image_directory"]):
error = error_messages.split_image_directory_not_found
- elif check_empty_directory and not os.listdir(autosplit.split_image_directory):
+ elif check_empty_directory and not os.listdir(autosplit.settings_dict["split_image_directory"]):
error = error_messages.split_image_directory_empty
elif autosplit.hwnd <= 0 or not win32gui.GetWindowText(autosplit.hwnd):
error = error_messages.region
diff --git a/src/settings_file.py b/src/settings_file.py
index c0315c23..cc6fec44 100644
--- a/src/settings_file.py
+++ b/src/settings_file.py
@@ -1,5 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, TypedDict
+
if TYPE_CHECKING:
from AutoSplit import AutoSplit
@@ -12,6 +13,7 @@
from PyQt6 import QtCore, QtWidgets
import error_messages
+from capture_windows import Region
from gen import design
from hotkeys import set_pause_hotkey, set_reset_hotkey, set_skip_split_hotkey, set_split_hotkey, set_undo_split_hotkey
@@ -21,6 +23,26 @@
auto_split_directory = os.path.dirname(sys.executable if FROZEN else os.path.abspath(__file__))
+class SettingsDict(TypedDict):
+ split_hotkey: str
+ reset_hotkey: str
+ undo_split_hotkey: str
+ skip_split_hotkey: str
+ pause_hotkey: str
+ fps_limit: int
+ live_capture_region: bool
+ force_print_window: bool
+ default_comparison_method: int
+ default_similarity_threshold: float
+ default_delay_time: int
+ default_pause_time: float
+ loop_splits: bool
+
+ split_image_directory: str
+ captured_window_title: str
+ capture_region: Region
+
+
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module: str, name: str):
@@ -29,27 +51,27 @@ def find_class(self, module: str, name: str):
def get_save_settings_values(autosplit: AutoSplit):
return [
- autosplit.split_image_directory,
- autosplit.similarity_threshold_spinbox.value(),
- autosplit.comparison_method_combobox.currentIndex(),
- autosplit.pause_spinbox.value(),
- int(autosplit.fps_limit_spinbox.value()),
- autosplit.split_input.text(),
- autosplit.reset_input.text(),
- autosplit.skip_split_input.text(),
- autosplit.undo_split_input.text(),
- autosplit.pause_input.text(),
- autosplit.x_spinbox.value(),
- autosplit.y_spinbox.value(),
- autosplit.width_spinbox.value(),
- autosplit.height_spinbox.value(),
- autosplit.window_text,
+ autosplit.settings_dict["split_image_directory"],
+ autosplit.settings_dict["default_similarity_threshold"],
+ autosplit.settings_dict["default_comparison_method"],
+ autosplit.settings_dict["default_pause_time"],
+ autosplit.settings_dict["fps_limit"],
+ autosplit.settings_dict["split_hotkey"],
+ autosplit.settings_dict["reset_hotkey"],
+ autosplit.settings_dict["skip_split_hotkey"],
+ autosplit.settings_dict["undo_split_hotkey"],
+ autosplit.settings_dict["pause_hotkey"],
+ autosplit.settings_dict["capture_region"].x,
+ autosplit.settings_dict["capture_region"].y,
+ autosplit.settings_dict["capture_region"].width,
+ autosplit.settings_dict["capture_region"].height,
+ autosplit.settings_dict["captured_window_title"],
0,
0,
1,
- int(autosplit.loop_checkbox.isChecked()),
- int(autosplit.auto_start_on_reset_checkbox.isChecked()),
- autosplit.force_print_window_checkbox.isChecked()]
+ autosplit.settings_dict["loop_splits"],
+ 0,
+ autosplit.settings_dict["force_print_window"]]
def have_settings_changed(autosplit: AutoSplit):
@@ -123,12 +145,12 @@ def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str
autosplit.show_error_signal.emit(error_messages.invalid_settings)
return False
- autosplit.split_image_directory = settings[0]
+ autosplit.settings_dict["split_image_directory"] = settings[0]
autosplit.split_image_folder_input.setText(settings[0])
- autosplit.similarity_threshold_spinbox.setValue(settings[1])
- autosplit.comparison_method_combobox.setCurrentIndex(settings[2])
- autosplit.pause_spinbox.setValue(settings[3])
- autosplit.fps_limit_spinbox.setValue(settings[4])
+ autosplit.settings_dict["default_similarity_threshold"] = settings[1]
+ autosplit.settings_dict["default_comparison_method"] = settings[2]
+ autosplit.settings_dict["default_pause_time"] = settings[3]
+ autosplit.settings_dict["fps_limit"] = settings[4]
keyboard.unhook_all()
if not autosplit.is_auto_controlled:
set_split_hotkey(autosplit, settings[5])
@@ -140,20 +162,19 @@ def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str
autosplit.y_spinbox.setValue(settings[11])
autosplit.width_spinbox.setValue(settings[12])
autosplit.height_spinbox.setValue(settings[13])
- autosplit.window_text = settings[14]
- autosplit.loop_checkbox.setChecked(bool(settings[18]))
- autosplit.auto_start_on_reset_checkbox.setChecked(bool(settings[19]))
- autosplit.force_print_window_checkbox.setChecked(settings[20])
+ autosplit.settings_dict["captured_window_title"] = settings[14]
+ autosplit.settings_dict["loop_splits"] = settings[18]
+ autosplit.settings_dict["force_print_window"] = settings[20]
- if autosplit.window_text:
+ if autosplit.settings_dict["captured_window_title"]:
# https://github.com/kaluluosi/pywin32-stubs/issues/7
- hwnd = win32gui.FindWindow(None, autosplit.window_text) # type: ignore
+ hwnd = win32gui.FindWindow(None, autosplit.settings_dict["captured_window_title"]) # type: ignore
if hwnd:
autosplit.hwnd = hwnd
else:
autosplit.live_image.setText("Reload settings after opening"
- f'\n"{autosplit.window_text}"'
- "\nto automatically load Live Capture")
+ + f'\n"{autosplit.settings_dict["captured_window_title"]}"'
+ + "\nto automatically load Capture Region")
return True
@@ -170,7 +191,6 @@ def load_settings(
return
autosplit.last_successfully_loaded_settings_file_path = load_settings_file_path
- autosplit.check_live_image()
autosplit.load_start_image()
diff --git a/src/split_parser.py b/src/split_parser.py
index 27862ed9..6e4a584f 100644
--- a/src/split_parser.py
+++ b/src/split_parser.py
@@ -153,9 +153,9 @@ def __pop_image_type(split_image: list[AutoSplitImage], image_type: ImageType):
def parse_and_validate_images(autosplit: AutoSplit):
# Get split images
all_images = [
- AutoSplitImage(os.path.join(autosplit.split_image_directory, image_name))
+ AutoSplitImage(os.path.join(autosplit.settings_dict["split_image_directory"], image_name))
for image_name
- in os.listdir(autosplit.split_image_directory)]
+ in os.listdir(autosplit.settings_dict["split_image_directory"])]
# Find non-split images and then remove them from the list
autosplit.start_image = __pop_image_type(all_images, ImageType.START)
@@ -171,7 +171,7 @@ def parse_and_validate_images(autosplit: AutoSplit):
return False
# error out if there is a {p} flag but no pause hotkey set and is not auto controlled.
- if (not autosplit.pause_input.text()
+ if (not autosplit.settings_dict["pause_hotkey"]
and image.check_flag(PAUSE_FLAG)
and not autosplit.is_auto_controlled):
autosplit.gui_changes_on_reset()
@@ -181,7 +181,7 @@ def parse_and_validate_images(autosplit: AutoSplit):
# Check that there's only one reset image
if image.image_type == ImageType.RESET:
# If there is no reset hotkey set but a reset image is present, and is not auto controlled, throw an error.
- if not autosplit.reset_input.text() and not autosplit.is_auto_controlled:
+ if not autosplit.settings_dict["reset_hotkey"] and not autosplit.is_auto_controlled:
autosplit.gui_changes_on_reset()
error_messages.reset_hotkey()
return False
diff --git a/typings/keyboard/__init__.pyi b/typings/keyboard/__init__.pyi
index 18f68af6..86bd06ec 100644
--- a/typings/keyboard/__init__.pyi
+++ b/typings/keyboard/__init__.pyi
@@ -3,6 +3,7 @@ This type stub file was generated by pyright.
"""
from __future__ import print_function as _print_function
import typing
+import collections.abc
import re as _re
import itertools as _itertools
@@ -13,6 +14,7 @@ from threading import Lock as _Lock, Thread as _Thread
from ._keyboard_event import KEY_DOWN, KEY_UP, KeyboardEvent
from ._generic import GenericListener as _GenericListener
from ._canonical_names import all_modifiers, normalize_name, sided_modifiers
+__all__ = ["all_modifiers", "normalize_name", "sided_modifiers", "KEY_DOWN", "KEY_UP", "KeyboardEvent"]
try:
# Python2
@@ -103,12 +105,12 @@ key events. In this case `keyboard` will be unable to report events.
- This program makes no attempt to hide itself, so don't use it for keyloggers or online gaming bots. Be responsible.
"""
-Callback = typing.Callable[[KeyboardEvent], None]
+Callback = collections.abc.Callable[[KeyboardEvent], None]
version: str
-_is_str = typing.Callable[[typing.Any], bool]
-_is_number = typing.Callable[[typing.Any], bool]
-_is_list: typing.Callable[[typing.Any], bool]
+_is_str = collections.abc.Callable[[typing.Any], bool]
+_is_number = collections.abc.Callable[[typing.Any], bool]
+_is_list: collections.abc.Callable[[typing.Any], bool]
class _State:
@@ -120,10 +122,6 @@ class _Event(_UninterruptibleEvent):
...
-if _platform.system() == 'Windows':
- ...
-else:
- ...
_modifier_scan_codes: set
@@ -197,14 +195,16 @@ class _KeyboardListener(_GenericListener):
_listener: _KeyboardListener
-def key_to_scan_codes(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], error_if_missing: bool = ...) -> typing.List[int]:
+def key_to_scan_codes(key: typing.Union[int, str, typing.List[typing.Union[int, str]]],
+ error_if_missing: bool = ...) -> typing.List[int]:
"""
Returns a list of scan codes associated with this key (name or scan code).
"""
...
-def parse_hotkey(hotkey) -> tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...]]] | tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...], ...]] | tuple[Unknown, ...]:
+def parse_hotkey(hotkey) -> tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...]]
+ ] | tuple[tuple[tuple[Unknown] | Unknown | tuple[()] | tuple[Unknown, ...], ...]] | tuple[Unknown, ...]:
"""
Parses a user-provided hotkey into nested tuples representing the
parsed structure, with the bottom values being lists of scan codes.
@@ -274,10 +274,10 @@ def call_later(fn, args=..., delay=...) -> None:
...
-_hooks: dict[typing.Callable, Unknown]
+_hooks: dict[collections.abc.Callable, Unknown]
-def hook(callback: Callback, suppress=..., on_remove=...) -> typing.Callable[[], None]:
+def hook(callback: Callback, suppress=..., on_remove=...) -> collections.abc.Callable[[], None]:
"""
Installs a global listener on all available keyboards, invoking `callback`
each time a key is pressed or released.
@@ -296,21 +296,22 @@ def hook(callback: Callback, suppress=..., on_remove=...) -> typing.Callable[[],
...
-def on_press(callback: Callback, suppress=...) -> typing.Callable[[], None]:
+def on_press(callback: Callback, suppress=...) -> collections.abc.Callable[[], None]:
"""
Invokes `callback` for every KEY_DOWN event. For details see `hook`.
"""
...
-def on_release(callback: Callback, suppress=...) -> typing.Callable[[], None]:
+def on_release(callback: Callback, suppress=...) -> collections.abc.Callable[[], None]:
"""
Invokes `callback` for every KEY_UP event. For details see `hook`.
"""
...
-def hook_key(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], callback: Callback, suppress: bool = ...) -> typing.Callable[[], None]:
+def hook_key(key: typing.Union[int, str, typing.List[typing.Union[int, str]]],
+ callback: Callback, suppress: bool = ...) -> collections.abc.Callable[[], None]:
"""
Hooks key up and key down events for a single key. Returns the event handler
created. To remove a hooked key use `unhook_key(key)` or
@@ -322,21 +323,21 @@ def hook_key(key: typing.Union[int, str, typing.List[typing.Union[int, str]]], c
...
-def on_press_key(key, callback: Callback, suppress=...) -> typing.Callable[[], None]:
+def on_press_key(key, callback: Callback, suppress=...) -> collections.abc.Callable[[], None]:
"""
Invokes `callback` for KEY_DOWN event related to the given key. For details see `hook`.
"""
...
-def on_release_key(key, callback: Callback, suppress=...) -> typing.Callable[[], None]:
+def on_release_key(key, callback: Callback, suppress=...) -> collections.abc.Callable[[], None]:
"""
Invokes `callback` for KEY_UP event related to the given key. For details see `hook`.
"""
...
-def unhook(remove: typing.Callable[[], None]) -> None:
+def unhook(remove: collections.abc.Callable[[], None]) -> None:
"""
Removes a previously added hook, either by callback or by the return value
of `hook`.
@@ -355,7 +356,7 @@ def unhook_all() -> None:
...
-def block_key(key) -> typing.Callable[[], None]:
+def block_key(key) -> collections.abc.Callable[[], None]:
"""
Suppresses all key events of the given key, regardless of modifiers.
"""
@@ -365,7 +366,7 @@ def block_key(key) -> typing.Callable[[], None]:
unblock_key = unhook_key
-def remap_key(src, dst) -> typing.Callable[[], None]:
+def remap_key(src, dst) -> collections.abc.Callable[[], None]:
"""
Whenever the key `src` is pressed or released, regardless of modifiers,
press or release the hotkey `dst` instead.
@@ -388,7 +389,8 @@ def parse_hotkey_combinations(hotkey) -> tuple[tuple[tuple[Unknown, ...], ...],
_hotkeys: dict
-def add_hotkey(hotkey, callback: Callback, args=..., suppress=..., timeout=..., trigger_on_release=...) -> typing.Callable[[], None]:
+def add_hotkey(hotkey, callback: collections.abc.Callable, args=..., suppress=..., timeout=...,
+ trigger_on_release=...) -> collections.abc.Callable[[], None]:
"""
Invokes a callback every time a hotkey is pressed. The hotkey must
be in the format `ctrl+shift+a, s`. This would trigger when the user holds
@@ -453,7 +455,7 @@ def unhook_all_hotkeys() -> None:
unregister_all_hotkeys = remove_all_hotkeys = clear_all_hotkeys = unhook_all_hotkeys
-def remap_hotkey(src, dst, suppress=..., trigger_on_release=...) -> typing.Callable[[], None]:
+def remap_hotkey(src, dst, suppress=..., trigger_on_release=...) -> collections.abc.Callable[[], None]:
"""
Whenever the hotkey `src` is pressed, suppress it and send
`dst` instead.
@@ -589,10 +591,11 @@ def get_typed_strings(events, allow_backspace=...):
...
-_recording: typing.Optional[tuple[Unknown | _queue.Queue[Unknown], typing.Callable[[], None]]]
+_recording: typing.Optional[tuple[Unknown | _queue.Queue[Unknown], collections.abc.Callable[[], None]]]
-def start_recording(recorded_events_queue=...) -> tuple[Unknown | _queue.Queue[Unknown], typing.Callable[[], None]]:
+def start_recording(recorded_events_queue=...) -> tuple[Unknown
+ | _queue.Queue[Unknown], collections.abc.Callable[[], None]]:
"""
Starts recording all keyboard events into a global variable, or the given
queue if any. Returns the queue of events and the hooked function.
@@ -639,7 +642,13 @@ replay = play
_word_listeners: dict
-def add_word_listener(word, callback: Callback, triggers=..., match_suffix=..., timeout=...) -> typing.Callable[[], None]:
+def add_word_listener(
+ word,
+ callback: Callback,
+ triggers=...,
+ match_suffix=...,
+ timeout=...) -> collections.abc.Callable[[],
+ None]:
"""
Invokes a callback every time a sequence of characters is typed (e.g. 'pet')
and followed by a trigger key (e.g. space). Modifiers (e.g. alt, ctrl,
@@ -676,7 +685,8 @@ def remove_word_listener(word_or_handler) -> None:
...
-def add_abbreviation(source_text, replacement_text, match_suffix=..., timeout=...) -> typing.Callable[[], None]:
+def add_abbreviation(source_text, replacement_text, match_suffix=...,
+ timeout=...) -> collections.abc.Callable[[], None]:
"""
Registers a hotkey that replaces one typed text with another. For example
diff --git a/typings/keyboard/_canonical_names.pyi b/typings/keyboard/_canonical_names.pyi
new file mode 100644
index 00000000..94cb7a64
--- /dev/null
+++ b/typings/keyboard/_canonical_names.pyi
@@ -0,0 +1,11 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+canonical_names = ...
+sided_modifiers = ...
+all_modifiers = ...
+
+
+def normalize_name(name: str) -> str:
+ ...