diff --git a/.github/workflows/lint-and-build.yml b/.github/workflows/lint-and-build.yml index a9f76005..0215268d 100644 --- a/.github/workflows/lint-and-build.yml +++ b/.github/workflows/lint-and-build.yml @@ -81,6 +81,11 @@ jobs: matrix: os: [windows-latest, windows-11-arm, ubuntu-22.04, ubuntu-22.04-arm] python-version: ["3.14"] + wine-compat: [""] + include: + - os: windows-latest + python-version: "3.14" + wine-compat: "-WineCompat" steps: - uses: actions/checkout@v6 # https://github.com/astral-sh/uv/issues/12906#issuecomment-3587439179 @@ -99,9 +104,9 @@ jobs: with: python-version: ${{ !startsWith(matrix.os, 'ubuntu') && matrix.python-version || null }} # endregion - - run: scripts/install.ps1 + - run: scripts/install.ps1 ${{ matrix.wine-compat }} shell: pwsh - - run: scripts/build.ps1 + - run: "scripts/build.ps1 ${{ matrix.wine-compat }}" shell: pwsh - name: Add empty profile run: echo "" > dist/settings.toml @@ -118,7 +123,8 @@ jobs: with: name: > AutoSplit v${{ steps.artifact_vars.outputs.AUTOSPLIT_VERSION }} - for ${{ steps.artifact_vars.outputs.OS }} (Python ${{ matrix.python-version }}) + for ${{ steps.artifact_vars.outputs.OS }}${{ matrix.wine-compat }} + (Python ${{ matrix.python-version }}) path: | dist/AutoSplit* dist/settings.toml @@ -126,7 +132,9 @@ jobs: - name: Upload Build logs uses: actions/upload-artifact@v6 with: - name: Build logs for ${{ steps.artifact_vars.outputs.OS }} (Python ${{ matrix.python-version }}) + name: > + Build logs for ${{ steps.artifact_vars.outputs.OS }}${{ matrix.wine-compat }} + (Python ${{ matrix.python-version }}) path: | build/AutoSplit/*.toc build/AutoSplit/*.txt diff --git a/README.md b/README.md index 2da0dd2c..791186b1 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ To understand how to use AutoSplit and how it works in-depth, please read the [t ### Compatibility - Windows 10 and 11. +- Wine 10.1+ ([very limited](#wine-limitations)) + - Useful if you want to use Desktop version of LiveSplit on Linux - Linux (still in early development) - Should work on Ubuntu 22.04+ - Wayland is not currently supported @@ -77,6 +79,13 @@ See the [installation instructions](https://github.com/Toufool/LiveSplit.AutoSpl - The Perceptual Hash Comparison Method similarity may differ by 3.125% on ARM64. (this will be solved eventually, we have to use a fallback method for now) - Native ARM64 builds go completely untested. There may be unforseen issues. +### Wine Limitations + +- Only the BitBlt Capture method is supported. Wine [does not support `CreateDirect3D11DeviceFromDXGIDevice`](https://bugs.winehq.org/show_bug.cgi?id=52487) +- No Video Capture Device (ie Webcam, OBS Virtual Cam). Wine [does not support DirectShow Device Enumeration](https://gitlab.winehq.org/wine/wine/-/wikis/Hardware#restrictions:~:text=camera) +- Only applications running within the **same wineserver instance** can be recorded. Which means that if you want to record a Steam Game for example, you would need to run AutoSplit through Steam (add it as a non-Steam game) using the same Proton version as the target game. + - In practice, at the time of writing this, you can't even run AutoSplit through Proton yet, as even its Experimental version is still on Wine 10.0, and AutoSplit [requires Wine 10.1+](https://gitlab.winehq.org/wine/wine/-/releases/wine-10.1#:~:text=crealf) + ## Resources Still need help? diff --git a/scripts/build.ps1 b/scripts/build.ps1 index ea7ed9d5..89947f30 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -1,4 +1,5 @@ #! /usr/bin/pwsh +param([switch]$WineCompat) Push-Location "$PSScriptRoot/.." # Avoid issues with space in path @@ -18,8 +19,10 @@ Splash._check_tcl_tk_compatibility() '--optimize=2', # Remove asserts and docstrings for smaller build '--additional-hooks-dir=Pyinstaller/hooks', "--add-data=pyproject.toml$([System.IO.Path]::PathSeparator).", - '--upx-dir=scripts/.upx' '--icon=res/icon.ico') + if (-not $WineCompat) { + $arguments += '--upx-dir=scripts/.upx' + } if ($SupportsSplashScreen) { # https://github.com/pyinstaller/pyinstaller/issues/9022 $arguments += @('--splash=res/splash.png') diff --git a/scripts/install.ps1 b/scripts/install.ps1 index b0f486de..39daa6f9 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,4 +1,5 @@ #! /usr/bin/pwsh +param([switch]$WineCompat) # Validating user groups on Linux if ($IsLinux) { @@ -38,8 +39,14 @@ if ($IsLinux) { } } -# UPX is only used by PyInstaller on Windows -if ($IsWindows -and [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'X64') { +# UPX is only used by PyInstaller on Windows, +# Doesn't work on ARM64, +# and we avoid using it on the "wine-compatible build" +if (` + $IsWindows ` + -and [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'X64' ` + -and -not $WineCompat +) { $UPXVersion = '5.0.1' $UPXFolderName = "upx-$UPXVersion-win64" Write-Output "Installing $UPXFolderName" diff --git a/src/capture_method/WindowsGraphicsCaptureMethod.py b/src/capture_method/WindowsGraphicsCaptureMethod.py index d75f9135..7896b388 100644 --- a/src/capture_method/WindowsGraphicsCaptureMethod.py +++ b/src/capture_method/WindowsGraphicsCaptureMethod.py @@ -8,7 +8,9 @@ from typing import TYPE_CHECKING, cast, override import numpy as np +import win32api import win32gui +import winerror from cv2.typing import MatLike from winrt.windows.graphics import SizeInt32 from winrt.windows.graphics.capture import Direct3D11CaptureFramePool, GraphicsCaptureSession @@ -29,6 +31,22 @@ WGC_NO_BORDER_MIN_BUILD = 20348 +try: + # Wine hasn't implemented CreateDirect3D11DeviceFromDXGIDevice yet + # https://bugs.winehq.org/show_bug.cgi?id=52487 + # Keep this check for a while even after it's implemented + # TODO: Fix these pywin32 types in typeshed + IS_WGC_SUPPORTED = WINDOWS_BUILD_NUMBER >= WGC_MIN_BUILD and bool( + win32api.GetProcAddress( # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] + win32api.LoadLibrary("d3d11.dll"), # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] + "CreateDirect3D11DeviceFromDXGIDevice", # pyright: ignore[reportArgumentType] + ) + ) +except win32api.error as exception: + if exception.winerror != winerror.ERROR_PROC_NOT_FOUND: + raise + IS_WGC_SUPPORTED = False # pyright: ignore[reportConstantRedefinition] + async def convert_d3d_surface_to_software_bitmap(surface: IDirect3DSurface): return await SoftwareBitmap.create_copy_from_surface_async(surface) diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py index c7439f85..54e5405d 100644 --- a/src/capture_method/__init__.py +++ b/src/capture_method/__init__.py @@ -10,10 +10,10 @@ from capture_method.CaptureMethodBase import CaptureMethodBase from capture_method.VideoCaptureDeviceCaptureMethod import VideoCaptureDeviceCaptureMethod -from utils import WGC_MIN_BUILD, WINDOWS_BUILD_NUMBER, first, get_input_device_resolution +from utils import first, get_input_device_resolution if sys.platform == "win32": - from pygrabber.dshow_graph import FilterGraph + import winerror from capture_method.BitBltCaptureMethod import BitBltCaptureMethod from capture_method.DesktopDuplicationCaptureMethod import ( @@ -23,7 +23,10 @@ from capture_method.ForceFullContentRenderingCaptureMethod import ( ForceFullContentRenderingCaptureMethod, ) - from capture_method.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod + from capture_method.WindowsGraphicsCaptureMethod import ( + IS_WGC_SUPPORTED, + WindowsGraphicsCaptureMethod, + ) if sys.platform == "linux": from PIL import features @@ -120,7 +123,7 @@ def get(self, key: CaptureMethodEnum, default: object = None, /): CAPTURE_METHODS = CaptureMethodDict() if sys.platform == "win32": # Windows Graphics Capture requires a minimum Windows Build - if WINDOWS_BUILD_NUMBER >= WGC_MIN_BUILD: + if IS_WGC_SUPPORTED: CAPTURE_METHODS[CaptureMethodEnum.WINDOWS_GRAPHICS_CAPTURE] = WindowsGraphicsCaptureMethod CAPTURE_METHODS[CaptureMethodEnum.BITBLT] = BitBltCaptureMethod if IS_DESKTOP_DUPLICATION_SUPPORTED: @@ -161,6 +164,13 @@ class CameraInfo: def get_input_devices(): if sys.platform == "win32": + try: + from pygrabber.dshow_graph import FilterGraph # noqa: PLC0415 + except OSError as exception: + # wine can choke on D3D Device Enumeration if missing directshow + if exception.winerror != winerror.TYPE_E_CANTLOADLIBRARY: + raise + return list[str]() return FilterGraph().get_input_devices() cameras: list[str] = []