Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
aee8134
Linux free-threaded build attempt
Avasam Jun 13, 2026
bd1e22a
Bump comment
Avasam Jun 13, 2026
46fde2f
Move CMAKE_ARGS to pyproject.tom
Avasam Jun 13, 2026
08ffb4d
Update cmake_args
Avasam Jun 13, 2026
223dbaf
Add more wheels
Avasam Jun 13, 2026
8578e44
update lock
Avasam Jun 13, 2026
f4def2b
try setup msvc for windows
Avasam Jun 14, 2026
75ec65b
regular opencv windows
Avasam Jun 14, 2026
a27c895
fix ambiguous url
Avasam Jun 14, 2026
462456e
opencv-contrib-python-headless ==4.12.0.88
Avasam Jun 14, 2026
dec914c
numpy shape vs reshape
Avasam Jun 14, 2026
f21be6c
forgot lock file
Avasam Jun 14, 2026
c8674eb
FIx some types
Avasam Jun 14, 2026
5f1b0fa
add back vars to ps1 script
Avasam Jun 14, 2026
54b07de
remove extra wheels
Avasam Jun 14, 2026
eda1d0c
Bump minimum Ubuntu to 24.04+ for ARM64
Avasam Jun 14, 2026
12a7fe5
upgrade pyside6-essentials
Avasam Jun 14, 2026
870d27e
missing spaces
Avasam Jun 14, 2026
2e479fb
Try 4.12.0.88
Avasam Jun 14, 2026
b93279a
oops
Avasam Jun 14, 2026
c521f50
typos
Avasam Jun 14, 2026
b8f5360
Merge branch 'main' into freethreading-builds-python-3.14
Avasam Jun 15, 2026
60b9d7a
Merge branch 'main' into freethreading-builds-python-3.14
Avasam Jun 15, 2026
94bf807
include windows free-threading builds
Avasam Jun 17, 2026
37a96f7
Fully disable UPX + strip
Avasam Jun 17, 2026
d6d3394
oops
Avasam Jun 17, 2026
e170824
Merge branch 'main' into freethreading-builds-python-3.14
Avasam Jun 21, 2026
a5e2b53
allow build for windows
Avasam Jun 21, 2026
98157c5
Merge branch 'freethreading-builds-python-3.14' of https://github.com…
Avasam Jun 21, 2026
007bf97
lock update
Avasam Jun 21, 2026
1d17c5a
Fix UV_NO_BUILD
Avasam Jun 21, 2026
cfb9c42
24.04
Avasam Jun 21, 2026
9576c8a
Reduce changes
Avasam Jun 22, 2026
3d81bc7
Fix UV_NO_BUILD
Avasam Jun 22, 2026
bbf58b5
more hacks and workarounds all around
Avasam Jun 23, 2026
6f0a884
pyside6 free-threading builds
Avasam Jun 23, 2026
ac50666
Merge branch 'main' of https://github.com/Toufool/AutoSplit into free…
Avasam Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/lint-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ jobs:
# Per-package no-binary overrides no-build, allowing
# known pure-Python source-only dependencies to still build.
# The Build job intentionally builds some binary packages from source.
UV_NO_BINARY_PACKAGE: keyboard PyAutoGUI beslogic-ruff-config
UV_NO_BINARY_PACKAGE: keyboard PyAutoGUI beslogic-ruff-config pywin32
# pywin32 doesn't have free-threading wheels, so must install from source since no "nogil" markers
strategy:
fail-fast: false
# Pyright is version and platform sensible
Expand Down Expand Up @@ -92,12 +93,17 @@ jobs:
# Only the Python version we plan on shipping matters.
matrix:
os: [windows-latest, windows-11-arm, ubuntu-24.04, ubuntu-24.04-arm]
python-version: ["3.14"]
# Note: For free-threading, only take the latest, which has more threading issues solved
python-version: ["3.14", "3.14t"]
wine-compat: [""]
include:
- os: windows-latest
python-version: "3.14"
wine-compat: "-WineCompat"

- os: windows-latest
python-version: "3.14t"
wine-compat: "-WineCompat"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -121,6 +127,13 @@ jobs:
&& format('{0}-{1}', matrix.python-version, endsWith(matrix.os, 'arm') && 'aarch64' || 'x86_64'))
|| null }}
# endregion
# MinGW (cc) fails SIZEOF_PY_INTPTR_T detection on ARM64; force MSVC for numpy sdist
# TODO: Remove this action once we use free-threading wheels
- name: Set up MSVC ARM64 environment
if: ${{ matrix.os == 'windows-11-arm' && endsWith(matrix.python-version, 't') }}
uses: ilammy/msvc-dev-cmd@v1
with:
arch: arm64
- run: scripts/install.ps1 ${{ matrix.wine-compat }}
- run: "scripts/build.ps1 ${{ matrix.wine-compat }}"
- name: Run test suite
Expand Down
33 changes: 30 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[project]
name = "AutoSplit"
version = "2.3.4" # (remember to bump in uv.lock too)
requires-python = ">=3.14"
requires-python = ">=3.14,<3.15"
dependencies = [
"Levenshtein >=0.27.3", # free-threaded # TODO: Bump when 3.15 wheels
"PyAutoGUI >=0.9.52",
"PyWinCtl >=0.0.42", # py.typed
"keyboard @ git+https://github.com/boppreh/keyboard.git", # Fix install on macos and linux-ci https://github.com/boppreh/keyboard/pull/568
"numpy >=2.3.2", # Python 3.14 support # TODO: Bump when 3.15 wheels
"opencv-contrib-python-headless >=4.13", # glibc 2.28 wheels (for aarch) # TODO: Bump when 3.15 wheels # TODO: Bump to 5.0 after a week for restored sdist
"opencv-contrib-python-headless >=5.0", # Restored sdist
"packaging >=20.0", # py.typed
"tomli-w >=1.1.0", # Typing fixes

Expand Down Expand Up @@ -80,22 +80,49 @@ dependency-metadata = [
{ name = "PyAutoGUI", requires-dist = [] },
{ name = "types-PyAutoGUI", requires-dist = [] },
]
override-dependencies = [
# opencv-contrib-python-headless==4.12.0.88 requires numpy>=2,<2.3.0 but that's a bad upper bound
"numpy>=2.3.2",
]
find-links = ["./scripts"] # Discover local wheels

exclude-newer = "1 week"
[tool.uv.extra-build-dependencies]
# opencv-python's build-system.requires only pins numpy up to python_version=='3.13',
# so building its sdist on 3.14(t) gets no numpy in the isolated build env
# (ModuleNotFoundError: No module named 'numpy'). Inject it back here.
opencv-contrib-python-headless = ["numpy>=2.3.2"]
opencv-python = ["numpy>=2.3.2"]
[tool.uv.exclude-newer-package]
typed-D3DShot = false # I own it
pyside6-essentials = false
shiboken6 = false
opencv-contrib-python-headless = "12 hours"

###
# Development channels
###
[tool.uv.sources]
beslogic-ruff-config = { git = "https://github.com/Beslogic/Beslogic-Ruff-Config", rev = "312cfab8a1e2653639a2ef665e99eac6c7412ba7" }
# pywin32 = { git = "https://github.com/mhammond/pywin32.git", marker = "python_version >= '3.15'" }
pywin32 = { git = "https://github.com/mhammond/pywin32.git" } # Lack of free-threading markers force to always install from source
# pyinstaller = { url = "https://github.com/pyinstaller/pyinstaller/archive/develop.zip", marker = "python_version >= '3.15'" }
# numpy = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }
# pillow = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }

[tool.uv.extra-build-variables]
# https://github.com/opencv/opencv-python#source-distributions
#
# BUILD_opencv_dnn/ENABLE_NEON
# Allows building OpenCV on Windows ARM64 when only sdist is available
# https://github.com/opencv/opencv-python/issues/1092#issuecomment-2862538656
#
# ENABLE_CONTRIB/ENABLE_HEADLESS:
# Match flavors to opencv-contrib-python-headless when building from git source repo
#
# PYTHON3_LIMITED_API: Enable free-threaded builds
opencv-contrib-python-headless = { CMAKE_ARGS = "-DBUILD_opencv_dnn=OFF -DENABLE_NEON=OFF -DENABLE_CONTRIB=1 -DENABLE_HEADLESS=1 -DPYTHON3_LIMITED_API=OFF" }
opencv-python = { CMAKE_ARGS = "-DBUILD_opencv_dnn=OFF -DENABLE_NEON=OFF -DENABLE_CONTRIB=1 -DENABLE_HEADLESS=1 -DPYTHON3_LIMITED_API=OFF" }

[[tool.uv.index]]
exclude-newer = false # Anaconda index doesn't have upload dates
# https://anaconda.org/scientific-python-nightly-wheels/
Expand Down
Binary file not shown.
4 changes: 2 additions & 2 deletions scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ try {

# CI not allowed to skip splash screen, it MUST build (will fail when calling PyInstaller)
$SupportsSplashScreen = $Env:GITHUB_JOB -or [System.Convert]::ToBoolean(
$(uv run --active scripts/check_splash_support.py))
$(uv run --active --no-sync scripts/check_splash_support.py))

$arguments = @(
'src/AutoSplit.py',
Expand Down Expand Up @@ -49,7 +49,7 @@ try {
}

Write-Output "pyinstaller $($arguments -join ' ')"
& uv run --active pyinstaller @arguments
& uv run --active --no-sync pyinstaller @arguments

if ($IsLinux) {
# Hoist the onedir output so files sit directly in the AppDir root.
Expand Down
130 changes: 130 additions & 0 deletions scripts/build_pyside.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#! /usr/bin/pwsh

# Builds free-threaded (cp314t) PySide6-Essentials + shiboken6 wheels from source
# and drops them into scripts/ for `uv pip install` (via find-links) to pick up.
#
# Why this exists:
# - There are no official free-threading (cp314t) PySide6 wheels yet.
# - The PyPI wheels are built against the limited API (abi3), which is
# incompatible with the free-threaded ABI and segfaults on import / in
# pyside6-uic. Building with --limited-api=no produces proper
# *.cpython-314t-*.so modules that load under a free-threaded interpreter.
#
# This is intentionally NOT part of install.ps1: the build takes ~20min and
# only needs to be re-run when the (system) Qt version changes. The resulting
# wheels are committed to scripts/.
#
# Linux x86_64 only. Run from anywhere; paths are resolved against scripts/.

param(
# PySide6 version to build. Must match the system Qt version (PySide X.Y.Z
# links against Qt X.Y.Z). Defaults to whatever qtpaths reports.
[string]$Version,
# Qt modules to build. AutoSplit only uses Core, Gui, Widgets and Test.
# Order matters: a module must come after the modules it depends on
# (QtTest includes QtWidgets' generated header).
[string]$ModuleSubset = 'Core,Gui,Widgets,Test'
)

$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true

if (-not $IsLinux) {
throw 'build_pyside.ps1 only supports Linux. Other platforms use the PyPI wheels.'
}

# --- Discover Qt -------------------------------------------------------------
$qtpaths = (Get-Command qtpaths6 -ErrorAction SilentlyContinue)?.Source
if (-not $qtpaths) { $qtpaths = '/usr/lib/qt6/bin/qtpaths6' }
if (-not (Test-Path $qtpaths)) { throw "qtpaths6 not found (looked for $qtpaths). Install qt6-base-dev-tools." }
if (-not $Version) { $Version = (& $qtpaths --qt-version).Trim() }
Write-Output "Building PySide6-Essentials $Version against Qt at $qtpaths"

# --- Discover LLVM/Clang (shiboken's binding generator needs libclang) -------
# Pick the highest installed /usr/lib/llvm-N. setup_clang() reads LLVM_INSTALL_DIR
# and find_package(Clang) needs ClangConfig.cmake (shipped by clang-N).
$llvmDir = Get-ChildItem /usr/lib -Directory -Filter 'llvm-*' -ErrorAction SilentlyContinue |
Sort-Object { [int]($_.Name -replace 'llvm-', '') } -Descending |
Select-Object -First 1 -ExpandProperty FullName
if (-not $llvmDir) { throw 'No /usr/lib/llvm-N found. Install llvm-<N>-dev and clang-<N>.' }
$llvmVersion = ($llvmDir -replace '.*llvm-', '')
Write-Output "Using LLVM/Clang $llvmVersion at $llvmDir"

# --- System build dependencies ----------------------------------------------
if ((Get-Command apt-get, dpkg-query -ErrorAction SilentlyContinue).Count -eq 2) {
$packages = @(
'cmake',
'qt6-base-dev', # Qt CMake config + headers
'qt6-base-private-dev', # private headers shiboken needs
'libclang-dev', # clang-c C API headers
"llvm-$llvmVersion-dev", # LLVMConfig.cmake
"clang-$llvmVersion" # ClangConfig.cmake
)
$missing = $packages.Where({
@(dpkg-query -W -f='${db:Status-Status}\n' $_ 2>$null) -notcontains 'installed'
})
if ($missing) {
sudo apt-get update
sudo apt-get install -y $missing
}
}

# --- Work in a throwaway directory ------------------------------------------
$workDir = Join-Path ([System.IO.Path]::GetTempPath()) "pyside-build-$Version"
if (Test-Path $workDir) { Remove-Item $workDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $workDir | Out-Null
Push-Location $workDir
try {
# Isolated build venv on the free-threaded interpreter. pyside-setup pins
# setuptools/wheel; newer setuptools vendors a wheel that drops get_abi_tag,
# which silently breaks pyside's bdist_wheel override (custom options like
# --module-subset stop being recognized).
uv venv --python 3.14t buildenv
$py = Join-Path $workDir 'buildenv/bin/python'
uv pip install --python buildenv `
'setuptools==78.1.0' 'wheel==0.43.0' 'packaging==24.2' 'build==1.2.2.post1' `
distro patchelf 'mypy>=1.15.0'

# pyside-setup source at the matching tag
git clone --depth 1 --branch "v$Version" https://code.qt.io/pyside/pyside-setup pyside-setup
Push-Location pyside-setup
try {
$Env:LLVM_INSTALL_DIR = $llvmDir
$Env:CLANG_INSTALL_DIR = $llvmDir
# --limited-api=no is the crux: free-threading is incompatible with abi3.
& $py setup.py bdist_wheel `
--qtpaths=$qtpaths `
--module-subset=$ModuleSubset `
--limited-api=no `
--parallel=$([Environment]::ProcessorCount)
if ($LASTEXITCODE -ne 0) { throw 'PySide6 build failed' }
}
finally { Pop-Location }

$dist = Join-Path $workDir 'pyside-setup/dist'

# setup.py emits a monolithic "PySide6" wheel. With only qtbase modules that
# is exactly the content of PySide6-Essentials, so rename it (Name in METADATA
# + dist-info dir + filename) to slot into the existing find-links install.
$psideWheel = Get-ChildItem $dist -Filter 'PySide6-*.whl' | Select-Object -First 1
& $py -m wheel unpack $psideWheel.FullName -d $workDir/unpack
$unpacked = Get-ChildItem "$workDir/unpack" -Directory | Select-Object -First 1
$di = Get-ChildItem $unpacked.FullName -Directory -Filter 'PySide6-*.dist-info' | Select-Object -First 1
(Get-Content "$($di.FullName)/METADATA") `
-replace '^Name: PySide6\s*$', 'Name: PySide6-Essentials' |
Set-Content "$($di.FullName)/METADATA"
Rename-Item $di.FullName ($di.Name -replace '^PySide6-', 'PySide6_Essentials-')
& $py -m wheel pack $unpacked.FullName -d $dist

# --- Publish to scripts/ ---------------------------------------------------
# Remove any previously built free-threaded wheels, then copy the new ones.
Get-ChildItem $PSScriptRoot -Filter 'PySide6_Essentials-*-cp314*.whl' | Remove-Item -Force
Get-ChildItem $PSScriptRoot -Filter 'pyside6_essentials-*-cp314*.whl' | Remove-Item -Force
Get-ChildItem $PSScriptRoot -Filter 'shiboken6-*-cp314*.whl' | Remove-Item -Force
Copy-Item (Join-Path $dist 'PySide6_Essentials-*-cp314*.whl') $PSScriptRoot
Copy-Item (Join-Path $dist 'shiboken6-*-cp314*.whl') $PSScriptRoot

Write-Output 'Built and copied to scripts/:'
Get-ChildItem $PSScriptRoot -Filter '*-cp314*.whl' | ForEach-Object { Write-Output " $($_.Name)" }
}
finally { Pop-Location }
10 changes: 5 additions & 5 deletions scripts/compile_resources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ Set-Location "$PSScriptRoot/.."

New-Item ./src/gen -ItemType directory -Force | Out-Null
New-Item ./src/gen/__init__.py -ItemType File -Force | Out-Null
uv run --active pyside6-uic --from-imports './res/about.ui' -o './src/gen/about.py'
uv run --active pyside6-uic --from-imports './res/design.ui' -o './src/gen/design.py'
uv run --active pyside6-uic --from-imports './res/settings.ui' -o './src/gen/settings.py'
uv run --active pyside6-uic --from-imports './res/update_checker.ui' -o './src/gen/update_checker.py'
uv run --active pyside6-rcc './res/resources.qrc' -o './src/gen/resources_rc.py'
uv run --active --no-sync pyside6-uic --from-imports './res/about.ui' -o './src/gen/about.py'
uv run --active --no-sync pyside6-uic --from-imports './res/design.ui' -o './src/gen/design.py'
uv run --active --no-sync pyside6-uic --from-imports './res/settings.ui' -o './src/gen/settings.py'
uv run --active --no-sync pyside6-uic --from-imports './res/update_checker.ui' -o './src/gen/update_checker.py'
uv run --active --no-sync pyside6-rcc './res/resources.qrc' -o './src/gen/resources_rc.py'
$files = Get-ChildItem ./src/gen/ *.py
foreach ($file in $files) {
(Get-Content $file.PSPath) |
Expand Down
2 changes: 1 addition & 1 deletion scripts/designer.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#! /usr/bin/pwsh

uv run --active pyside6-designer res/*.ui
uv run --active --no-sync pyside6-designer res/*.ui
17 changes: 16 additions & 1 deletion scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ if ($IsLinux) {
$packages = @(
# For running tests headless on CI
($Env:GITHUB_JOB ? 'xvfb' : $null),
# scikit-build (opencv) defaults to Ninja generator
'ninja-build'
# Required by pymonctl at import
'x11-xserver-utils',
# For splash screen
Expand Down Expand Up @@ -104,16 +106,29 @@ if (`
}

# https://github.com/opencv/opencv-python#source-distributions
#
# Allows building OpenCV on Windows ARM64 when only sdist is available
# https://github.com/opencv/opencv-python/issues/1092#issuecomment-2862538656
$Env:CMAKE_ARGS = '-DBUILD_opencv_dnn=OFF -DENABLE_NEON=OFF'
# Match flavors to opencv-contrib-python-headless when building from git source repo
$Env:CMAKE_ARGS += ' -DENABLE_CONTRIB=1 -DENABLE_HEADLESS=1'
# Enable free-threaded builds which don't support the limited API
$Env:CMAKE_ARGS += ' -DPYTHON3_LIMITED_API=OFF'

$prod = if ($Env:GITHUB_JOB -eq 'Build') { '--no-dev' } else { }
$lock = if ($Env:GITHUB_JOB) { '--locked' } else { }
# Verbose to see sdist progression
$uvSyncArgs = @('sync', '--active') + $prod + $lock # + '--verbose'
# Exclude PySide6: uv sync/lock can't distinguish GIL vs free-threading ABI.
# uv pip install does wheel-tag selection at runtime — picks local cp314t wheel
# for free-threading Python, falls back to PyPI abi3 for GIL Python / Windows.
$uvSyncArgs = @('sync', '--active', '--no-install-package', 'pyside6-essentials', '--no-install-package', 'shiboken6') + $prod + $lock # + '--verbose'
Write-Output "Installing Python dependencies with: uv $uvSyncArgs"
uv @uvSyncArgs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

Write-Output 'Installing PySide6 with ABI-aware wheel selection'
uv pip install pyside6-essentials shiboken6
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Don't compile resources on the Build CI job as it'll do so in build script
if (-not $prod) {
Expand Down
10 changes: 5 additions & 5 deletions scripts/lint.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ Set-Location "$PSScriptRoot/.."
$exitCodes = 0

Write-Host "`nRunning dprint fmt ..."
uv run --active dprint fmt
uv run --active --no-sync dprint fmt

Write-Host "`nRunning Ruff check ..."
uv run --active ruff check --fix
uv run --active --no-sync ruff check --fix
$exitCodes += $LastExitCode
# Ruff already prints success/failure

Write-Host "`nRunning Ruff format ..."
uv run --active ruff format
uv run --active --no-sync ruff format

# Skip pyright-python's update check, avoiding the npm query (faster, offline-safe) and its warning.
$Env:PYRIGHT_PYTHON_IGNORE_WARNINGS = $true
$pyrightVersion = $(uv run --active pyright --version).replace('pyright ', '')
$pyrightVersion = $(uv run --active --no-sync pyright --version).replace('pyright ', '')
Write-Host "`nRunning Pyright $pyrightVersion ..."
uv run --active pyright src/
uv run --active --no-sync pyright src/
$exitCodes += $LastExitCode
if ($LastExitCode -gt 0) {
Write-Host "`Pyright failed ($LastExitCode)" -ForegroundColor Red
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion scripts/start.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

param ([string]$p1)
& "$PSScriptRoot/compile_resources.ps1"
uv run --active "$PSScriptRoot/../src/AutoSplit.py" $p1
uv run --active --no-sync "$PSScriptRoot/../src/AutoSplit.py" $p1
3 changes: 2 additions & 1 deletion src/AutoControlledThread.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override

from PySide6 import QtCore

Expand All @@ -16,6 +16,7 @@ def __init__(self, autosplit: AutoSplit):
self._autosplit_ref = autosplit
super().__init__()

@override
@QtCore.Slot()
def run(self):
while True:
Expand Down
2 changes: 1 addition & 1 deletion src/capture_method/ScrotCaptureMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ def _scrot_screenshot(x: int, y: int, width: int, height: int):
"-z",
screenshot_file,
))
return imread(screenshot_file, cv2.IMREAD_COLOR_RGB)
except subprocess.CalledProcessError:
# This can happen when trying to capture a region OOB
# scrot is rude and prints directly to TTY, no stderr :/
return None
return imread(screenshot_file, cv2.IMREAD_COLOR_RGB)


class ScrotCaptureMethod(CaptureMethodBase):
Expand Down
Loading
Loading