From a80fffa8e97f5c9afa396d932611e95196cac3a9 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 16 Jun 2026 22:03:23 -0400 Subject: [PATCH 01/10] Portrait mode initial implementation, still needs some tweaks --- .github/workflows/lint-and-build.yml | 2 + res/design.ui | 2012 +++++++++++++++----------- src/AutoSplit.py | 86 +- 3 files changed, 1215 insertions(+), 885 deletions(-) diff --git a/.github/workflows/lint-and-build.yml b/.github/workflows/lint-and-build.yml index 7c90f8a7..1d1c5ddc 100644 --- a/.github/workflows/lint-and-build.yml +++ b/.github/workflows/lint-and-build.yml @@ -34,6 +34,8 @@ env: APPIMAGE_EXTRACT_AND_RUN: true # Avoid needing libfuse2 concurrency: + # Cancel previous runs for the same PR + # Don't cancel successive pushes to main group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/res/design.ui b/res/design.ui index 0ddab8d1..a25c9069 100644 --- a/res/design.ui +++ b/res/design.ui @@ -7,22 +7,10 @@ 0 0 - 786 - 426 + 772 + 504 - - - 786 - 426 - - - - - 786 - 426 - - 9 @@ -36,875 +24,1135 @@ :/resources/icon.ico:/resources/icon.ico - - - - 11 - 145 - 49 - 20 - - - - X - - - Qt::AlignmentFlag::AlignCenter - - - - - - 10 - 67 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Select Region - - - - - - 657 - 369 - 121 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Start Auto Splitter - - - - - false - - - - 657 - 339 - 121 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Reset - - - - - false - - - - 657 - 310 - 59 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Undo - - - - - false - - - - 719 - 310 - 59 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Skip - - - - - - 10 - 270 - 53 - 24 - - - - Qt::FocusPolicy::NoFocus - - - calculate the average max FPS of the set capture region - - - Max FPS - - - - - - 92 - 272 - 21 - 20 - - - - FPS - - - - - - 127 - 67 - 320 - 240 - - - - QFrame::Shape::Box - - - QFrame::Shadow::Plain - - - 1 - - - 0 - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 456 - 67 - 320 - 240 - - - - QFrame::Shape::Box - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 456 - 31 - 318 - 20 - - - - Current Image - - - Qt::AlignmentFlag::AlignCenter - - - - - - 11 - 190 - 49 - 20 - - - - Width - - - Qt::AlignmentFlag::AlignCenter - - - - - - 66 - 190 - 49 - 20 - - - - Height - - - Qt::AlignmentFlag::AlignCenter - - - - - - 65 - 272 - 26 - 20 - - - - - - - - - - 11 - 210 - 51 - 24 - - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 1 - - - 10000 - - - 640 - - - - - - 66 - 210 - 51 - 24 - - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 1 - - - 10000 - - - 480 - - - - - - 127 - 31 - 318 - 20 - - - - Capture Region - - - Qt::AlignmentFlag::AlignCenter - - - - - - 484 - 49 - 264 - 20 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 10 - 240 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Take Screenshot - - - - - - 11 - 165 - 51 - 24 - - - - QAbstractSpinBox::ButtonSymbols::UpDownArrows - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 0 - - - 999999999 - - - 1 - - - 0 - - - - - - 66 - 165 - 51 - 24 - - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 0 - - - 999999999 - - - 0 - - - - - - 66 - 145 - 49 - 20 - - - - Y - - - Qt::AlignmentFlag::AlignCenter - - - - - - 10 - 119 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Align the capture region using a reference image - - - Align Region - - - - - - 10 - 93 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Select Window - - - - - - 696 - 5 - 81 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Browse... - - - - - - 10 - 9 - 111 - 16 - - - - Split Image Folder: - - - - - - 127 - 6 - 561 - 22 - - - - true - - - - - - 127 - 49 - 318 - 20 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 458 - 313 - 81 - 20 - - - - Image Loop: - - - - - - 127 - 312 - 321 - 84 - - - - Similarity Viewer - - - Qt::AlignmentFlag::AlignCenter - - - - - 133 - 22 - 57 - 16 - - - - Live - - - Qt::AlignmentFlag::AlignCenter - - - - - - 197 - 22 - 57 - 16 - - - - Highest - - - Qt::AlignmentFlag::AlignCenter - - - - - - 261 - 22 - 56 - 16 - - - - Threshold - - - Qt::AlignmentFlag::AlignCenter - - - - - - 0 - 39 - 322 - 3 - - - - Qt::Orientation::Horizontal - - - - - - 10 - 42 - 111 - 16 - - - - Current Image - - - - - - 10 - 64 - 111 - 16 - - - - Reset Image - - - - - - 0 - 61 - 322 - 3 - - - - Qt::Orientation::Horizontal - - - - - - 129 - 20 - 3 - 62 - - - - Qt::Orientation::Vertical - - - - - - 193 - 20 - 3 - 95 - - - - Qt::Orientation::Vertical - - - - - - 257 - 20 - 3 - 95 - - - - Qt::Orientation::Vertical - - - - - - 133 - 43 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 197 - 43 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 260 - 43 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 133 - 64 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 197 - 64 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 260 - 64 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - - 457 - 369 - 131 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Reload Start Image - - - - - - 458 - 344 - 121 - 20 - - - - Start Image Status: - - - - - - 577 - 344 - 81 - 20 - - - - status - - - - - - 537 - 313 - 121 - 20 - - - - N/A - - - - - false - - - - 455 - 49 - 27 - 18 - - - - Qt::FocusPolicy::NoFocus - - - Previous image - - - < - - - - - false - - - - 750 - 49 - 27 - 18 - - - - Qt::FocusPolicy::NoFocus - - - Next image - - - > - - + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 4 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + 111 + 0 + + + + + 111 + 16777215 + + + + Split Image Folder: + + + + + + + true + + + + + + + Qt::FocusPolicy::NoFocus + + + Browse... + + + + + + + + + + QSplitter::handle { background: transparent; } + + + Qt::Orientation::Horizontal + + + + + 112 + 16777215 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 0 + 41 + + + + + + + + Qt::FocusPolicy::NoFocus + + + Select Region + + + + + + + Qt::FocusPolicy::NoFocus + + + Select Window + + + + + + + Qt::FocusPolicy::NoFocus + + + Align the capture region using a reference image + + + Align Region + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Width + + + Qt::AlignmentFlag::AlignCenter + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 1 + + + 9999 + + + 480 + + + + + + + QAbstractSpinBox::ButtonSymbols::UpDownArrows + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 9999 + + + 0 + + + + + + + Height + + + Qt::AlignmentFlag::AlignCenter + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 9999 + + + 0 + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 1 + + + 9999 + + + 640 + + + + + + + X + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Y + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + + Qt::FocusPolicy::NoFocus + + + Take Screenshot + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 54 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + calculate the average max FPS of the set capture region + + + Max FPS + + + + + + + + 0 + 0 + + + + + + + + + + + FPS + + + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + 322 + 16777215 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + Capture Region + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 20 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 320 + 240 + + + + + 320 + 240 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + 1 + + + 0 + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 0 + + + + + 0 + 84 + + + + + 16777215 + 84 + + + + Similarity Viewer + + + Qt::AlignmentFlag::AlignCenter + + + + + 129 + 22 + 56 + 16 + + + + Live + + + Qt::AlignmentFlag::AlignCenter + + + + + + 194 + 22 + 56 + 16 + + + + Highest + + + Qt::AlignmentFlag::AlignCenter + + + + + + 258 + 22 + 56 + 16 + + + + Threshold + + + Qt::AlignmentFlag::AlignCenter + + + + + + 1 + 39 + 318 + 3 + + + + Qt::Orientation::Horizontal + + + + + + 10 + 42 + 111 + 16 + + + + Current Image + + + + + + 10 + 64 + 111 + 16 + + + + Reset Image + + + + + + 1 + 61 + 318 + 3 + + + + Qt::Orientation::Horizontal + + + + + + 124 + 20 + 3 + 64 + + + + Qt::Orientation::Vertical + + + + + + 189 + 20 + 3 + 64 + + + + Qt::Orientation::Vertical + + + + + + 254 + 20 + 3 + 64 + + + + Qt::Orientation::Vertical + + + + + + 129 + 43 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 194 + 43 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 258 + 43 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 129 + 64 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 194 + 64 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 258 + 64 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + 322 + 16777215 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + Current Image + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 4 + + + 1 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 0 + 0 + + + + + 32 + 20 + + + + Qt::FocusPolicy::NoFocus + + + Previous image + + + < + + + + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + + false + + + + 0 + 0 + + + + + 32 + 20 + + + + Qt::FocusPolicy::NoFocus + + + Next image + + + > + + + + + + + + + + + 320 + 240 + + + + + 320 + 240 + + + + QFrame::Shape::Box + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Image Loop: + + + + + + + N/A + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Start Image Status: + + + + + + + status + + + + + + + + + + Qt::FocusPolicy::NoFocus + + + Reload Start Image + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 4 + + + + + false + + + + 56 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + Undo + + + + + + + false + + + + 56 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + Skip + + + + + + + + + + false + + + Qt::FocusPolicy::NoFocus + + + Reset + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + Start Auto Splitter + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + + + 0 0 - 786 + 772 22 + + + File + + + + + + + + + + View + + + Help @@ -917,17 +1165,8 @@ - - - File - - - - - - - + @@ -1028,6 +1267,11 @@ QAction::MenuRole::AboutQtRole + + + Toggle Layout to Portrait + + split_image_folder_input diff --git a/src/AutoSplit.py b/src/AutoSplit.py index fd4917a5..6ba77310 100755 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -37,7 +37,14 @@ def do_nothing(*_): ... import cv2 from PySide6 import QtCore, QtGui from PySide6.QtTest import QTest -from PySide6.QtWidgets import QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox +from PySide6.QtWidgets import ( + QApplication, + QBoxLayout, + QFileDialog, + QLabel, + QMainWindow, + QMessageBox, +) import error_messages import user_profile @@ -92,6 +99,8 @@ def do_nothing(*_): ... CHECK_FPS_ITERATIONS = 10 +_SETTINGS = QtCore.QSettings("AutoSplit", "AutoSplit") + class AutoSplit(QMainWindow, design.Ui_MainWindow): # Parse command line args @@ -223,6 +232,16 @@ def _show_error_signal_slot(error_message_box: Callable[..., object]): self.action_check_for_updates_on_open.isChecked(), ), ) + self.action_toggle_layout.triggered.connect(self.toggle_layout) + + # left panel stays at sizeHint; center and right expand + self.main_splitter.setStretchFactor(0, 0) + self.main_splitter.setStretchFactor(1, 1) + self.main_splitter.setStretchFactor(2, 1) + self._layout_is_portrait = False + self._sync_toggle_layout_text() + if int(_SETTINGS.value("layout_is_portrait", 0)): + self.toggle_layout() # update x, y, width, and height when changing the value of these spinbox's are changed self.x_spinbox.valueChanged.connect(self.__update_x) @@ -982,6 +1001,71 @@ def __update_split_image(self, specific_image: AutoSplitImage | None = None): loop_tuple = self.split_images_and_loop_number[self.split_image_number] self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}") + def toggle_layout(self) -> None: + if not self._layout_is_portrait: + # Landscape → portrait + right_layout = self.right_panel.layout() + right_layout.insertWidget(0, self.capture_region_label) + right_layout.insertWidget(1, self.capture_region_window_label) + right_layout.insertWidget(2, self.live_image) + right_layout.insertWidget(3, self.similarity_viewer_groupbox) + self.center_panel.setVisible(False) + self.left_panel.layout().addWidget(self.split_controls_panel) + self._layout_is_portrait = True + self._apply_split_controls_layout() + else: + # Portrait → landscape + center_layout = self.center_panel.layout() + center_layout.insertWidget(0, self.capture_region_label) + center_layout.insertWidget(1, self.capture_region_window_label) + center_layout.insertWidget(2, self.live_image) + center_layout.insertWidget(3, self.similarity_viewer_groupbox) + self.center_panel.setVisible(True) + self.right_panel.layout().addWidget(self.split_controls_panel) + self._layout_is_portrait = False + self._apply_split_controls_layout() + _SETTINGS.setValue("layout_is_portrait", int(self._layout_is_portrait)) + self._sync_toggle_layout_text() + + def _apply_split_controls_layout(self) -> None: + """Two columns in landscape; single stacked column in portrait (narrow panel).""" + self.start_image_status_layout.setDirection( + QBoxLayout.Direction.TopToBottom + if self._layout_is_portrait + else QBoxLayout.Direction.LeftToRight + ) + layout = self.split_controls_layout + layout.removeItem(self.split_controls_spacer) + widgets = ( + self.image_loop_row, + self.start_image_status_row, + self.reload_start_image_button, + self.action_row, + self.reset_button, + self.start_auto_splitter_button, + ) + for widget in widgets: + layout.removeWidget(widget) + if self._layout_is_portrait: + for row, widget in enumerate(widgets): + layout.addWidget(widget, row, 0) + layout.addItem(self.split_controls_spacer, len(widgets), 0, 1, 1) + else: + layout.addWidget(self.image_loop_row, 0, 0) + layout.addWidget(self.start_image_status_row, 1, 0) + layout.addWidget(self.reload_start_image_button, 2, 0) + layout.addWidget(self.action_row, 0, 1) + layout.addWidget(self.reset_button, 1, 1) + layout.addWidget(self.start_auto_splitter_button, 2, 1) + layout.addItem(self.split_controls_spacer, 3, 0, 1, 2) + + def _sync_toggle_layout_text(self) -> None: + self.action_toggle_layout.setText( + "Toggle Layout to Landscape" + if self._layout_is_portrait + else "Toggle Layout to Portrait" + ) + @override def closeEvent(self, event: QtGui.QCloseEvent | None = None): """Exit safely when closing the window.""" From 4546a5d043be1431cf6d7a556e88fb1549266001 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 28 Jun 2026 22:13:26 +0000 Subject: [PATCH 02/10] [autofix.ci] apply automated fixes --- res/AutoSplit.metainfo.xml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/res/AutoSplit.metainfo.xml b/res/AutoSplit.metainfo.xml index f64cab31..757b2e84 100644 --- a/res/AutoSplit.metainfo.xml +++ b/res/AutoSplit.metainfo.xml @@ -1,16 +1,21 @@ - + io.github.Toufool.AutoSplit CC-BY-4.0 AutoSplit Image comparison based auto splitter for speedrunning -

This program can be used to automatically start, split, and reset +

This program can be used to automatically start, split, and reset your preferred speedrun timer by comparing images to a capture region.

-

This allows you to focus more on your speedrun and less on managing your timer. +

This allows you to focus more on your speedrun and less on managing your timer. It also improves the accuracy of your splits.

-

It can be used in tandem with any speedrun timer that accepts hotkeys +

It can be used in tandem with any speedrun timer that accepts hotkeys (LiveSplit, WSplit, etc.), and can be integrated with LiveSplit.

@@ -54,7 +59,8 @@ AutoSplit.desktop + https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html#tag-release-url + --> https://github.com/Toufool/AutoSplit/releases @@ -77,11 +83,13 @@ + source="https://raw.githubusercontent.com/Toufool/AutoSplit/refs/heads/main/docs/example.gif" + /> + source="https://raw.githubusercontent.com/Toufool/AutoSplit/refs/heads/main/docs/mask_example_image.gif" + /> From cc84aed4f3841d5a9fb9350233a155b8652baf0b Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 28 Jun 2026 18:28:00 -0400 Subject: [PATCH 03/10] Make dprint point from venv in autofix test --- .github/workflows/autofix.yml | 5 +- pyproject.toml | 6 +- res/AutoSplit.metainfo.xml | 18 +++-- uv.lock | 138 +++++++++++++++++----------------- 4 files changed, 88 insertions(+), 79 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 0c74bdd9..08c2426a 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -17,13 +17,14 @@ jobs: - uses: astral-sh/setup-uv@v7 with: activate-environment: true - - run: uv sync --locked --only-group=ruff + - run: uv sync --locked --only-group=formatters + - run: ruff check --fix - run: ruff format # Format even if the the previous step failed if: ${{ !cancelled() }} - - run: npx dprint fmt + - run: dprint fmt # Format even if the the previous step failed if: ${{ !cancelled() }} diff --git a/pyproject.toml b/pyproject.toml index f4429fd1..13b4185a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,17 +41,17 @@ dependencies = [ "python-xlib >=0.33; sys_platform == 'linux'", ] [dependency-groups] -ruff = [ +formatters = [ "beslogic-ruff-config", + "dprint-py >=0.50.0.0", "ruff", # Version should come from beslogic-ruff-config and the lock file ] dev = [ # # Linters & Formatters - "dprint-py >=0.50.0.0", "mypy >=2.1", # TODO: Bump when 3.15 wheels "pyright[nodejs] >=1.1.400", # reportPrivateImportUsage behaviour change - { include-group = "ruff" }, + { include-group = "formatters" }, # # Types "types-PyAutoGUI", diff --git a/res/AutoSplit.metainfo.xml b/res/AutoSplit.metainfo.xml index f64cab31..968943e9 100644 --- a/res/AutoSplit.metainfo.xml +++ b/res/AutoSplit.metainfo.xml @@ -6,12 +6,18 @@ AutoSplit Image comparison based auto splitter for speedrunning -

This program can be used to automatically start, split, and reset - your preferred speedrun timer by comparing images to a capture region.

-

This allows you to focus more on your speedrun and less on managing your timer. - It also improves the accuracy of your splits.

-

It can be used in tandem with any speedrun timer that accepts hotkeys - (LiveSplit, WSplit, etc.), and can be integrated with LiveSplit.

+

+ This program can be used to automatically start, split, and reset + your preferred speedrun timer by comparing images to a capture region. +

+

+ This allows you to focus more on your speedrun and less on managing your timer. + It also improves the accuracy of your splits. +

+

+ It can be used in tandem with any speedrun timer that accepts hotkeys + (LiveSplit, WSplit, etc.), and can be integrated with LiveSplit. +

Utility diff --git a/uv.lock b/uv.lock index 5d375b8e..29f06ef0 100644 --- a/uv.lock +++ b/uv.lock @@ -81,20 +81,20 @@ name = "autosplit" version = "2.3.4" source = { virtual = "." } dependencies = [ - { name = "keyboard", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "levenshtein", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "opencv-contrib-python-headless", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "packaging", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pillow", marker = "sys_platform == 'linux'" }, - { name = "pyautogui", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "keyboard" }, + { name = "levenshtein" }, + { name = "numpy" }, + { name = "opencv-contrib-python-headless" }, + { name = "packaging" }, + { name = "pillow", marker = "sys_platform != 'win32'" }, + { name = "pyautogui" }, { name = "pygrabber", marker = "sys_platform == 'win32'" }, - { name = "pyinstaller", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pyside6-essentials", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, + { name = "pyinstaller" }, + { name = "pyside6-essentials" }, + { name = "python-xlib", marker = "sys_platform != 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "pywinctl", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "tomli-w", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pywinctl" }, + { name = "tomli-w" }, { name = "typed-d3dshot", extra = ["numpy"], marker = "sys_platform == 'win32'" }, { name = "winrt-windows-foundation", marker = "sys_platform == 'win32'" }, { name = "winrt-windows-graphics", marker = "sys_platform == 'win32'" }, @@ -108,20 +108,21 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "beslogic-ruff-config", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "dprint-py", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "mypy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pyright", extra = ["nodejs"], marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "ruff", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "types-keyboard", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "types-pyautogui", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "types-pyinstaller", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "types-python-xlib", marker = "sys_platform == 'linux'" }, + { name = "beslogic-ruff-config" }, + { name = "dprint-py" }, + { name = "mypy" }, + { name = "pyright", extra = ["nodejs"] }, + { name = "ruff" }, + { name = "types-keyboard" }, + { name = "types-pyautogui" }, + { name = "types-pyinstaller" }, + { name = "types-python-xlib", marker = "sys_platform != 'win32'" }, { name = "types-pywin32", marker = "sys_platform == 'win32'" }, ] -ruff = [ - { name = "beslogic-ruff-config", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "ruff", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +formatters = [ + { name = "beslogic-ruff-config" }, + { name = "dprint-py" }, + { name = "ruff" }, ] [package.metadata] @@ -164,8 +165,9 @@ dev = [ { name = "types-python-xlib", marker = "sys_platform == 'linux'" }, { name = "types-pywin32", marker = "sys_platform == 'win32'", specifier = ">=306.0.0.20240130" }, ] -ruff = [ +formatters = [ { name = "beslogic-ruff-config", git = "https://github.com/Beslogic/Beslogic-Ruff-Config?rev=312cfab8a1e2653639a2ef665e99eac6c7412ba7" }, + { name = "dprint-py", specifier = ">=0.50.0.0" }, { name = "ruff" }, ] @@ -174,7 +176,7 @@ name = "beslogic-ruff-config" version = "1.1.0" source = { git = "https://github.com/Beslogic/Beslogic-Ruff-Config?rev=312cfab8a1e2653639a2ef665e99eac6c7412ba7#312cfab8a1e2653639a2ef665e99eac6c7412ba7" } dependencies = [ - { name = "ruff", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "ruff" }, ] [[package]] @@ -201,8 +203,8 @@ name = "ewmhlib" version = "0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux'" }, + { name = "python-xlib" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657, upload-time = "2024-04-17T08:15:56.338Z" }, @@ -218,7 +220,7 @@ name = "levenshtein" version = "0.27.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "rapidfuzz", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "rapidfuzz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/56/dcf68853b062e3b94bdc3d011cc4198779abc5b9dc134146a062920ce2e2/levenshtein-0.27.3.tar.gz", hash = "sha256:1ac326b2c84215795163d8a5af471188918b8797b4953ec87aaba22c9c1f9fc0", size = 393269, upload-time = "2025-11-01T12:14:31.04Z" } wheels = [ @@ -277,11 +279,11 @@ name = "mypy" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ast-serialize", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "librt", marker = "(platform_python_implementation != 'PyPy' and sys_platform == 'linux') or (platform_python_implementation != 'PyPy' and sys_platform == 'win32')" }, - { name = "mypy-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pathspec", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } wheels = [ @@ -357,7 +359,7 @@ name = "opencv-contrib-python-headless" version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/cc/295e9a4e783ca71ba1b8fbd34e51bc603eba4611afcfc7de1b09b2d6ed8d/opencv-contrib-python-headless-4.11.0.86.tar.gz", hash = "sha256:839319098a73264c580c97cb1ca835f7fce3d30e4fa9fa6d4d0618fff551be0b", size = 150579288, upload-time = "2025-01-16T13:54:11.763Z" } wheels = [ @@ -428,8 +430,8 @@ name = "pygrabber" version = "0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "comtypes", marker = "sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'win32'" }, + { name = "comtypes" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/24/e7e5efa1d915853f3f34cedf751cbee9d88bee47a41122d495ee95452dad/pygrabber-0.2.tar.gz", hash = "sha256:e584b119d4c9b9a8f339eb34d1fe7fdd16214a2bf5a1876171145caebdb9e413", size = 18487, upload-time = "2023-10-20T07:17:14.97Z" } wheels = [ @@ -441,12 +443,12 @@ name = "pyinstaller" version = "6.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "altgraph", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "packaging", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "altgraph" }, + { name = "packaging" }, { name = "pefile", marker = "sys_platform == 'win32'" }, - { name = "pyinstaller-hooks-contrib", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, - { name = "setuptools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/01/80/9e0dad9c69a7cfd4b5aaede8c6225d762bab7247a2a6b7651e1995522001/pyinstaller-6.17.0.tar.gz", hash = "sha256:be372bd911392b88277e510940ac32a5c2a6ce4b8d00a311c78fa443f4f27313", size = 4014147, upload-time = "2025-11-24T19:43:32.109Z" } wheels = [ @@ -467,8 +469,8 @@ name = "pyinstaller-hooks-contrib" version = "2025.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "setuptools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "packaging" }, + { name = "setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/4f/e33132acdb8f732978e577b8a0130a412cbfe7a3414605e3fd380a975522/pyinstaller_hooks_contrib-2025.10.tar.gz", hash = "sha256:a1a737e5c0dccf1cf6f19a25e2efd109b9fec9ddd625f97f553dac16ee884881", size = 168155, upload-time = "2025-11-22T09:34:36.138Z" } wheels = [ @@ -480,10 +482,10 @@ name = "pymonctl" version = "0.92" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, + { name = "ewmhlib", marker = "sys_platform != 'win32'" }, + { name = "python-xlib", marker = "sys_platform != 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/2d/13/076a20da28b82be281f7e43e16d9da0f545090f5d14b2125699232b9feba/PyMonCtl-0.92-py3-none-any.whl", hash = "sha256:2495d8dab78f9a7dbce37e74543e60b8bd404a35c3108935697dda7768611b5a", size = 45945, upload-time = "2024-04-22T10:07:09.566Z" }, @@ -494,8 +496,8 @@ name = "pyright" version = "1.1.410" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nodeenv", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nodeenv" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" } wheels = [ @@ -504,7 +506,7 @@ wheels = [ [package.optional-dependencies] nodejs = [ - { name = "nodejs-wheel-binaries", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nodejs-wheel-binaries" }, ] [[package]] @@ -512,7 +514,7 @@ name = "pyside6-essentials" version = "6.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "shiboken6", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "shiboken6" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/59/6a/ea0db68d40a1c487fd255634896f4e37b6560e3ef1f57ca5139bf6509b1f/PySide6_Essentials-6.9.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e5da48883f006c6206ef85874db74ddebcdf69b0281bd4f1642b1c5ac1d54aea", size = 96416183, upload-time = "2025-06-03T13:12:48.945Z" }, @@ -526,7 +528,7 @@ name = "python-xlib" version = "0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six", marker = "sys_platform == 'linux'" }, + { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } wheels = [ @@ -560,10 +562,10 @@ name = "pywinbox" version = "0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, + { name = "ewmhlib", marker = "sys_platform != 'win32'" }, + { name = "python-xlib", marker = "sys_platform != 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/b1/37/d59397221e15d2a7f38afaa4e8e6b8c244d818044f7daa0bdc5988df0a69/PyWinBox-0.7-py3-none-any.whl", hash = "sha256:8b2506a8dd7afa0a910b368762adfac885274132ef9151b0c81b0d2c6ffd6f83", size = 12274, upload-time = "2024-04-17T10:10:31.899Z" }, @@ -574,12 +576,12 @@ name = "pywinctl" version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pymonctl", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, + { name = "ewmhlib", marker = "sys_platform != 'win32'" }, + { name = "pymonctl" }, + { name = "python-xlib", marker = "sys_platform != 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "pywinbox", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pywinbox" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/be/33/8e4f632210b28fc9e998a9ab990e7ed97ecd2800cc50038e3800e1d85dbe/PyWinCtl-0.4.1-py3-none-any.whl", hash = "sha256:4d875e22969e1c6239d8c73156193630aaab876366167b8d97716f956384b089", size = 63158, upload-time = "2024-09-23T08:33:39.881Z" }, @@ -677,7 +679,7 @@ name = "typed-d3dshot" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "comtypes", marker = "sys_platform == 'win32'" }, + { name = "comtypes" }, ] sdist = { url = "https://files.pythonhosted.org/packages/47/e8/c1d050bd5d71ba7eab613677bfafe7e43b50dcf10a7698e331231049e5f4/typed_d3dshot-1.1.1.tar.gz", hash = "sha256:c1f3acc984ad1eeb6b3199153ffe0d585f57007195a03d1b9222e9324d2fcfc5", size = 25013, upload-time = "2026-06-13T05:25:05.558Z" } wheels = [ @@ -686,7 +688,7 @@ wheels = [ [package.optional-dependencies] numpy = [ - { name = "numpy", marker = "sys_platform == 'win32'" }, + { name = "numpy" }, ] [[package]] @@ -748,7 +750,7 @@ name = "winrt-runtime" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "sys_platform == 'win32'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/16/dd/acdd527c1d890c8f852cc2af644aa6c160974e66631289420aa871b05e65/winrt_runtime-3.2.1.tar.gz", hash = "sha256:c8dca19e12b234ae6c3dadf1a4d0761b51e708457492c13beb666556958801ea", size = 21721, upload-time = "2025-06-06T14:40:27.593Z" } wheels = [ @@ -762,7 +764,7 @@ name = "winrt-windows-foundation" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "winrt-runtime", marker = "sys_platform == 'win32'" }, + { name = "winrt-runtime" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/55/098ce7ea0679efcc1298b269c48768f010b6c68f90c588f654ec874c8a74/winrt_windows_foundation-3.2.1.tar.gz", hash = "sha256:ad2f1fcaa6c34672df45527d7c533731fdf65b67c4638c2b4aca949f6eec0656", size = 30485, upload-time = "2025-06-06T14:41:53.344Z" } wheels = [ @@ -776,7 +778,7 @@ name = "winrt-windows-graphics" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "winrt-runtime", marker = "sys_platform == 'win32'" }, + { name = "winrt-runtime" }, ] sdist = { url = "https://files.pythonhosted.org/packages/30/e5/781e29e2c459280d600009c1a1f11f03e83896a515977ccf7ddc92f8b501/winrt_windows_graphics-3.2.1.tar.gz", hash = "sha256:7f40ce7770ebb45319f9bb3d9104e58b1f20cdf3c33d6d0540e59a2ae752b674", size = 7776, upload-time = "2025-06-06T14:42:05.859Z" } wheels = [ @@ -790,7 +792,7 @@ name = "winrt-windows-graphics-capture" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "winrt-runtime", marker = "sys_platform == 'win32'" }, + { name = "winrt-runtime" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4f/32/751884ce5a857f363a6e854d56efd6d707e331585440d2db95ff88743f58/winrt_windows_graphics_capture-3.2.1.tar.gz", hash = "sha256:c4c3a4bc2c87062e4a09a42b7f6f1411f201ca04142954b1223dc46c72240efd", size = 10410, upload-time = "2025-06-06T14:42:06.623Z" } wheels = [ @@ -815,7 +817,7 @@ name = "winrt-windows-graphics-directx" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "winrt-runtime", marker = "sys_platform == 'win32'" }, + { name = "winrt-runtime" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/86/2f94a297db367b78c747b88da61551e59b1e62c553882018883110ec7bee/winrt_windows_graphics_directx-3.2.1.tar.gz", hash = "sha256:3e7e875055644151eedbc556806d90e8c53ef6375a6c98669a9ddacbe0953586", size = 4503, upload-time = "2025-06-06T14:42:07.786Z" } wheels = [ @@ -829,7 +831,7 @@ name = "winrt-windows-graphics-directx-direct3d11" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "winrt-runtime", marker = "sys_platform == 'win32'" }, + { name = "winrt-runtime" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2d/d3/23933ea9f39952e4ae679eaa9eef34dec05dbdd35e2eb192dce5fc4fff8f/winrt_windows_graphics_directx_direct3d11-3.2.1.tar.gz", hash = "sha256:157618f9d5071ff422098472fa96524fe9385a39776ff4483d5a66a510a8114c", size = 8656, upload-time = "2025-06-06T14:42:08.388Z" } wheels = [ @@ -854,7 +856,7 @@ name = "winrt-windows-graphics-imaging" version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "winrt-runtime", marker = "sys_platform == 'win32'" }, + { name = "winrt-runtime" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3c/90/14655f93d5cc4224ae6c44866c36928926b54c8fe7ae4a465b10332bd935/winrt_windows_graphics_imaging-3.2.1.tar.gz", hash = "sha256:1e0bdb08625b0ce144a2e76dd5ed86605906e41196df92d660c8f87a993a1513", size = 26786, upload-time = "2025-06-06T14:42:11.95Z" } wheels = [ From e4c27283a40f5964cd21c615ef7b951a53fc9acd Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 28 Jun 2026 19:05:52 -0400 Subject: [PATCH 04/10] type-checking fixes and some unrelated improvements --- src/AutoSplit.py | 29 +++++++++++++++-------------- src/menu_bar.py | 18 ++++++++++++------ src/user_profile.py | 35 +---------------------------------- src/utils.py | 3 +++ 4 files changed, 31 insertions(+), 54 deletions(-) diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 4699c825..d69bb0d4 100755 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -32,7 +32,7 @@ def do_nothing(*_): ... from copy import deepcopy from time import time from types import FunctionType -from typing import TYPE_CHECKING, NoReturn, override +from typing import TYPE_CHECKING, NoReturn, cast, override import cv2 from PySide6 import QtCore, QtGui @@ -44,6 +44,7 @@ def do_nothing(*_): ... QLabel, QMainWindow, QMessageBox, + QVBoxLayout, ) import error_messages @@ -85,6 +86,7 @@ def do_nothing(*_): ... FROZEN, ONE_SECOND, RUNNING_WAYLAND, + SETTINGS, auto_split_directory, decimal, flatten, @@ -99,8 +101,6 @@ def do_nothing(*_): ... CHECK_FPS_ITERATIONS = 10 -_SETTINGS = QtCore.QSettings("AutoSplit", "AutoSplit") - class AutoSplit(QMainWindow, design.Ui_MainWindow): # Parse command line args @@ -181,7 +181,9 @@ def _show_error_signal_slot(error_message_box: Callable[..., object]): # Get default values defined in SettingsDialog self.settings_dict = get_default_settings_from_ui(self) - user_profile.load_check_for_updates_on_open(self) + self.action_check_for_updates_on_open.setChecked( + cast("bool", SETTINGS.value("check_for_updates_on_open", True, type=bool)) + ) if self.is_auto_controlled: self.start_auto_splitter_button.setEnabled(False) @@ -227,10 +229,9 @@ def _show_error_signal_slot(error_message_box: Callable[..., object]): lambda: self.__reload_start_image(started_by_button=True) ) self.action_check_for_updates_on_open.changed.connect( - lambda: user_profile.set_check_for_updates_on_open( - self, - self.action_check_for_updates_on_open.isChecked(), - ), + lambda: SETTINGS.setValue( + "check_for_updates_on_open", self.action_check_for_updates_on_open.isChecked() + ) ) self.action_toggle_layout.triggered.connect(self.toggle_layout) @@ -240,7 +241,7 @@ def _show_error_signal_slot(error_message_box: Callable[..., object]): self.main_splitter.setStretchFactor(2, 1) self._layout_is_portrait = False self._sync_toggle_layout_text() - if int(_SETTINGS.value("layout_is_portrait", 0)): + if SETTINGS.value("layout_is_portrait", False, type=bool): self.toggle_layout() # update x, y, width, and height when changing the value of these spinbox's are changed @@ -1008,27 +1009,27 @@ def __update_split_image(self, specific_image: AutoSplitImage | None = None): def toggle_layout(self) -> None: if not self._layout_is_portrait: # Landscape → portrait - right_layout = self.right_panel.layout() + right_layout = cast("QVBoxLayout", self.right_panel.layout()) right_layout.insertWidget(0, self.capture_region_label) right_layout.insertWidget(1, self.capture_region_window_label) right_layout.insertWidget(2, self.live_image) right_layout.insertWidget(3, self.similarity_viewer_groupbox) self.center_panel.setVisible(False) - self.left_panel.layout().addWidget(self.split_controls_panel) + cast("QVBoxLayout", self.left_panel.layout()).addWidget(self.split_controls_panel) self._layout_is_portrait = True self._apply_split_controls_layout() else: # Portrait → landscape - center_layout = self.center_panel.layout() + center_layout = cast("QVBoxLayout", self.center_panel.layout()) center_layout.insertWidget(0, self.capture_region_label) center_layout.insertWidget(1, self.capture_region_window_label) center_layout.insertWidget(2, self.live_image) center_layout.insertWidget(3, self.similarity_viewer_groupbox) self.center_panel.setVisible(True) - self.right_panel.layout().addWidget(self.split_controls_panel) + cast("QVBoxLayout", self.right_panel.layout()).addWidget(self.split_controls_panel) self._layout_is_portrait = False self._apply_split_controls_layout() - _SETTINGS.setValue("layout_is_portrait", int(self._layout_is_portrait)) + SETTINGS.setValue("layout_is_portrait", self._layout_is_portrait) self._sync_toggle_layout_text() def _apply_split_controls_layout(self) -> None: diff --git a/src/menu_bar.py b/src/menu_bar.py index a8f99084..b2fe8390 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -15,7 +15,6 @@ from PySide6.QtWidgets import QFileDialog import error_messages -import user_profile from capture_method import ( CAPTURE_METHODS, CameraInfo, @@ -25,9 +24,17 @@ ) from gen import about, design, settings as settings_ui, update_checker from hotkeys import HOTKEYS, HOTKEYS_WHEN_AUTOCONTROLLED, CommandStr, set_hotkey -from utils import AUTOSPLIT_VERSION, GITHUB_REPOSITORY, ONE_SECOND, decimal, fire_and_forget +from utils import ( + AUTOSPLIT_VERSION, + GITHUB_REPOSITORY, + ONE_SECOND, + SETTINGS, + decimal, + fire_and_forget, +) if TYPE_CHECKING: + import user_profile from AutoSplit import AutoSplit HALF_BRIGHTNESS = 128 @@ -98,10 +105,9 @@ def open_update(self): self.close() def do_not_ask_me_again_state_changed(self): - user_profile.set_check_for_updates_on_open( - self.design_window, - not self.do_not_ask_again_checkbox.isChecked(), - ) + value = not self.do_not_ask_again_checkbox.isChecked() + self.design_window.action_check_for_updates_on_open.setChecked(value) + SETTINGS.setValue("check_for_updates_on_open", value) def open_update_checker(autosplit: AutoSplit, latest_version: str, *, check_on_open: bool): diff --git a/src/user_profile.py b/src/user_profile.py index 6aa93cd2..461bc023 100644 --- a/src/user_profile.py +++ b/src/user_profile.py @@ -7,7 +7,7 @@ from warnings import deprecated import tomli_w -from PySide6 import QtCore, QtWidgets +from PySide6 import QtWidgets import error_messages from capture_method import CAPTURE_METHODS, CaptureMethodEnum, Region, change_capture_method @@ -17,7 +17,6 @@ if TYPE_CHECKING: from AutoSplit import AutoSplit - from gen import design class UserProfileDict(TypedDict): @@ -225,35 +224,3 @@ def load_settings_on_open(autosplit: AutoSplit): return load_settings(autosplit, os.path.join(auto_split_directory, settings_files[0])) - - -def load_check_for_updates_on_open(autosplit: AutoSplit): - """ - Retrieve the "Check For Updates On Open" QSettings and set the checkbox state - These are only global settings values. They are not *toml settings values. - """ - # Type not inferred by PySide6: https://bugreports.qt.io/browse/PYSIDE-2542 - value = cast( - "bool", - QtCore.QSettings( - "AutoSplit", - "Check For Updates On Open", - ).value( - "check_for_updates_on_open", - True, - type=bool, - ), - ) - autosplit.action_check_for_updates_on_open.setChecked(value) - - -def set_check_for_updates_on_open(design_window: design.Ui_MainWindow, value: bool): # noqa: FBT001 - """Sets the "Check For Updates On Open" QSettings value and the checkbox state.""" - design_window.action_check_for_updates_on_open.setChecked(value) - QtCore.QSettings( - "AutoSplit", - "Check For Updates On Open", - ).setValue( - "check_for_updates_on_open", - value, - ) diff --git a/src/utils.py b/src/utils.py index 097a7215..ac3573d3 100644 --- a/src/utils.py +++ b/src/utils.py @@ -17,6 +17,7 @@ import cv2 import numpy as np +from PySide6 import QtCore from gen.build_vars import AUTOSPLIT_BUILD_NUMBER, AUTOSPLIT_GITHUB_REPOSITORY @@ -84,6 +85,8 @@ def find_tesseract_path(): BGRA_CHANNEL_COUNT = 4 """How many channels in a BGRA image""" +SETTINGS = QtCore.QSettings("AutoSplit", "AutoSplit") + class ImageShape(IntEnum): Y = 0 From 2fc00981325364f1fb45d2b8dddcd04252c31704 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 28 Jun 2026 19:10:46 -0400 Subject: [PATCH 05/10] More code simplification --- src/AutoSplit.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/AutoSplit.py b/src/AutoSplit.py index d69bb0d4..81377716 100755 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -240,7 +240,6 @@ def _show_error_signal_slot(error_message_box: Callable[..., object]): self.main_splitter.setStretchFactor(1, 1) self.main_splitter.setStretchFactor(2, 1) self._layout_is_portrait = False - self._sync_toggle_layout_text() if SETTINGS.value("layout_is_portrait", False, type=bool): self.toggle_layout() @@ -1007,30 +1006,30 @@ def __update_split_image(self, specific_image: AutoSplitImage | None = None): self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}") def toggle_layout(self) -> None: - if not self._layout_is_portrait: - # Landscape → portrait + self._layout_is_portrait = not self._layout_is_portrait + + if self._layout_is_portrait: # Landscape --> portrait right_layout = cast("QVBoxLayout", self.right_panel.layout()) right_layout.insertWidget(0, self.capture_region_label) right_layout.insertWidget(1, self.capture_region_window_label) right_layout.insertWidget(2, self.live_image) right_layout.insertWidget(3, self.similarity_viewer_groupbox) - self.center_panel.setVisible(False) cast("QVBoxLayout", self.left_panel.layout()).addWidget(self.split_controls_panel) - self._layout_is_portrait = True - self._apply_split_controls_layout() - else: - # Portrait → landscape + else: # Portrait --> landscape center_layout = cast("QVBoxLayout", self.center_panel.layout()) center_layout.insertWidget(0, self.capture_region_label) center_layout.insertWidget(1, self.capture_region_window_label) center_layout.insertWidget(2, self.live_image) center_layout.insertWidget(3, self.similarity_viewer_groupbox) - self.center_panel.setVisible(True) cast("QVBoxLayout", self.right_panel.layout()).addWidget(self.split_controls_panel) - self._layout_is_portrait = False - self._apply_split_controls_layout() + self.center_panel.setVisible(not self._layout_is_portrait) + self._apply_split_controls_layout() + self.action_toggle_layout.setText( + "Toggle Layout to Landscape" + if self._layout_is_portrait + else "Toggle Layout to Portrait" + ) SETTINGS.setValue("layout_is_portrait", self._layout_is_portrait) - self._sync_toggle_layout_text() def _apply_split_controls_layout(self) -> None: """Two columns in landscape; single stacked column in portrait (narrow panel).""" @@ -1064,13 +1063,6 @@ def _apply_split_controls_layout(self) -> None: layout.addWidget(self.start_auto_splitter_button, 2, 1) layout.addItem(self.split_controls_spacer, 3, 0, 1, 2) - def _sync_toggle_layout_text(self) -> None: - self.action_toggle_layout.setText( - "Toggle Layout to Landscape" - if self._layout_is_portrait - else "Toggle Layout to Portrait" - ) - @override def closeEvent(self, event: QtGui.QCloseEvent | None = None): """Exit safely when closing the window.""" From 2b4e7dfb9529db7a2de0ad3d70e0d5150ea5978b Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 28 Jun 2026 22:23:26 -0400 Subject: [PATCH 06/10] Fully working, with size lock --- res/design.ui | 14 +++++++------- src/AutoSplit.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/res/design.ui b/res/design.ui index a25c9069..d920f810 100644 --- a/res/design.ui +++ b/res/design.ui @@ -8,7 +8,7 @@ 0 0 772 - 504 + 432 @@ -917,9 +917,6 @@ - - 2 - 2 @@ -932,6 +929,9 @@ 2 + + 2 + @@ -1021,6 +1021,9 @@ + + 4 + 0 @@ -1033,9 +1036,6 @@ 0 - - 4 - diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 81377716..572b28ac 100755 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -274,6 +274,8 @@ def _update_checker_widget_signal_slot(latest_version: str, check_on_open: bool) self.timer_start_image.timeout.connect(self.__compare_capture_for_auto_start) self.show() + # Lock the window to a fixed, content-fitting size (non-resizable). + QtCore.QTimer.singleShot(0, self._shrink_window_to_fit) # https://pyinstaller.org/en/stable/advanced-topics.html#module-pyi_splash # doc implies calling `pyi_splash.is_alive()` should return false should be enough, but in @@ -1030,6 +1032,17 @@ def toggle_layout(self) -> None: else "Toggle Layout to Portrait" ) SETTINGS.setValue("layout_is_portrait", self._layout_is_portrait) + # The window keeps its old geometry after re-layout, leaving blank space. + # Shrink it back to fit; deferred because the panel/splitter size hints + # only settle over the next couple of layout passes. + QtCore.QTimer.singleShot(0, self._shrink_window_to_fit) + + def _shrink_window_to_fit(self) -> None: + # setFixedSize both fits the window to its content and removes the + # user resize handles. Two passes: the panel/splitter size hints only + # settle over consecutive layout passes. + self.setFixedSize(self.sizeHint()) + QtCore.QTimer.singleShot(0, lambda: self.setFixedSize(self.sizeHint())) def _apply_split_controls_layout(self) -> None: """Two columns in landscape; single stacked column in portrait (narrow panel).""" From 9c4356f13e6dce6034332e028c98e51bf22594cb Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 2 Jul 2026 23:28:28 -0400 Subject: [PATCH 07/10] Discard changes to res/AutoSplit.metainfo.xml --- res/AutoSplit.metainfo.xml | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/res/AutoSplit.metainfo.xml b/res/AutoSplit.metainfo.xml index dbc0f233..f64cab31 100644 --- a/res/AutoSplit.metainfo.xml +++ b/res/AutoSplit.metainfo.xml @@ -6,18 +6,12 @@ AutoSplit Image comparison based auto splitter for speedrunning -

- This program can be used to automatically start, split, and reset - your preferred speedrun timer by comparing images to a capture region. -

-

- This allows you to focus more on your speedrun and less on managing your timer. - It also improves the accuracy of your splits. -

-

- It can be used in tandem with any speedrun timer that accepts hotkeys - (LiveSplit, WSplit, etc.), and can be integrated with LiveSplit. -

+

This program can be used to automatically start, split, and reset + your preferred speedrun timer by comparing images to a capture region.

+

This allows you to focus more on your speedrun and less on managing your timer. + It also improves the accuracy of your splits.

+

It can be used in tandem with any speedrun timer that accepts hotkeys + (LiveSplit, WSplit, etc.), and can be integrated with LiveSplit.

Utility @@ -59,7 +53,8 @@ https://github.com/Toufool/AutoSplit/blob/main/docs/CONTRIBUTING.md AutoSplit.desktop - + https://github.com/Toufool/AutoSplit/releases @@ -82,13 +77,11 @@ + source="https://raw.githubusercontent.com/Toufool/AutoSplit/refs/heads/main/docs/example.gif" /> + source="https://raw.githubusercontent.com/Toufool/AutoSplit/refs/heads/main/docs/mask_example_image.gif" /> From e3ffa36ea9f4b85366560b4d70e8aed048179b04 Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 2 Jul 2026 23:29:31 -0400 Subject: [PATCH 08/10] Fix log window toggle breaking height --- res/design.ui | 12 ++++++++++++ src/AutoSplit.py | 18 ++++-------------- src/menu_bar.py | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/res/design.ui b/res/design.ui index d0d46c0d..8da89c2a 100644 --- a/res/design.ui +++ b/res/design.ui @@ -1244,6 +1244,18 @@ ClickableLabel:hover { background-color: palette(midlight); }
+ + + 0 + 148 + + + + + 16777215 + 148 + + monospace diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 6f8f6c07..0ef53066 100755 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -323,20 +323,8 @@ def _update_checker_widget_signal_slot(latest_version: str, check_on_open: bool) _last_log_footer_entry: log_capture.LogLine | None = None """Last (timestamp, text, is_stderr) shown in the footer, kept so it can be re-rendered.""" - _collapsed_height = 0 - """ - Window height with the log panel collapsed; captured from the .ui to fix the height per state. - """ - - _log_panel_height = 0 - """Window growth when the panel opens; derived from the .ui (expanded - collapsed height).""" - def _setup_log_footer(self): """Wire up the clickable log footer and the expandable log history panel.""" - # Both come from the .ui: collapsed = minimumSize height, panel = designed (expanded) - # geometry height minus that. Captured before setFixedHeight() overwrites minimumHeight(). - self._collapsed_height = self.minimumHeight() - self._log_panel_height = self.height() - self._collapsed_height # The status bar doesn't add() its .ui child, and stretch isn't expressible there. self.status_bar.addWidget(self.log_footer_label, 1) self.status_bar.setContentsMargins(0, 0, 0, 0) # Let the label's padding define the insets. @@ -368,8 +356,10 @@ def _setup_log_footer(self): def _set_log_panel_visible(self, show: bool): # noqa: FBT001 # boolean value setter, not an arbitrary flag self.log_dock.setVisible(show) - # Fix the height per state so it can't be dragged to over-expand or hide content. - self.setFixedHeight(self._collapsed_height + (self._log_panel_height if show else 0)) + # The dock lives outside the central widget (a bottom dock area), so showing it grows the + # window downward without shifting content. Refit to the new content in whatever orientation + # the window currently has, sharing the layout-toggle's sizing path (fixed, non-resizable). + self._shrink_window_to_fit() self._refresh_log_footer() # Flip the chevron to match the new state. def _toggle_log_panel(self): diff --git a/src/menu_bar.py b/src/menu_bar.py index ed4b5dae..052aba6f 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -15,6 +15,7 @@ from PySide6.QtWidgets import QFileDialog import error_messages +import user_profile from capture_method import ( CAPTURE_METHODS, CameraInfo, @@ -27,7 +28,6 @@ from utils import AUTOSPLIT_VERSION, GITHUB_REPOSITORY, ONE_SECOND, decimal, fire_and_forget if TYPE_CHECKING: - import user_profile from AutoSplit import AutoSplit HALF_BRIGHTNESS = 128 From 0b89460edd0cf0b0583fa480d0e8c41043571533 Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 2 Jul 2026 23:54:04 -0400 Subject: [PATCH 09/10] Fix window width issue --- res/design.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/design.ui b/res/design.ui index 8da89c2a..f77b6ec7 100644 --- a/res/design.ui +++ b/res/design.ui @@ -1201,7 +1201,7 @@ ClickableLabel:hover { background-color: palette(midlight); } - + 1 0 From ae6a576aa53f81da028cf60aa1ef0c46c5d6b1e3 Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 2 Jul 2026 23:57:34 -0400 Subject: [PATCH 10/10] Discard changes to uv.lock --- uv.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/uv.lock b/uv.lock index 9ff10b3e..a1c9bc4b 100644 --- a/uv.lock +++ b/uv.lock @@ -470,7 +470,6 @@ version = "2026.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, - { name = "setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/5b/c9fe0db5e83ee1c39b2258fa21d23b15e1a60786b6c5990ee5074ead8bb6/pyinstaller_hooks_contrib-2026.6.tar.gz", hash = "sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725", size = 173354, upload-time = "2026-06-08T22:37:16.152Z" } wheels = [