diff --git a/.github/workflows/lint-and-build.yml b/.github/workflows/lint-and-build.yml index 5f166411..7f5cccd3 100644 --- a/.github/workflows/lint-and-build.yml +++ b/.github/workflows/lint-and-build.yml @@ -80,7 +80,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 + # python-xlib: https://github.com/python-xlib/python-xlib/pull/298 + UV_NO_BINARY_PACKAGE: keyboard PyAutoGUI beslogic-ruff-config python-xlib strategy: fail-fast: false # Pyright is version and platform sensible diff --git a/README.md b/README.md index 36bff735..7ee7eea2 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ Not a developer? You can still help through the following methods: - Sharing AutoSplit with other speedrunners - Starring the repository Example - Upvoting 👍 the following upstream issues in libraries and tools we use: + - + - - - - diff --git a/docs/tutorial.md b/docs/tutorial.md index db71feb3..6da52037 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -75,9 +75,9 @@ ##### Linux -- **X11 XCB** (slower, requires XCB)\ - Uses the XCB library to take screenshots of the X11 server. -- **Scrot** (fast, may leave files in `/tmp`)\ +- **X11** (fastest)\ + Request screenshots directly from the X11 server. +- **Scrot** (slow, may leave files in `/tmp`)\ Uses Scrot (SCReenshOT) to take screenshots.\ Leaves behind a screenshot file in `/tmp` if interrupted. diff --git a/pyproject.toml b/pyproject.toml index 753ca720..3e6cc86d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,6 @@ dependencies = [ "winrt-Windows.Graphics.Imaging >=3.2.1; sys_platform == 'win32'", # # Linux-only dependencies - "pillow >=13.0.0; sys_platform == 'linux'", "python-xlib >=0.33; sys_platform == 'linux'", ] [dependency-groups] @@ -66,7 +65,6 @@ dev = [ "types-pywin32 >=306.0.0.20240130; sys_platform == 'win32'", # # OS-specific, but installable on other OSes for convenient typing references - "pillow >=13.0.0", "pygrabber", "python-xlib", "typed-D3DShot[numpy]", @@ -98,21 +96,25 @@ override-dependencies = [ # https://github.com/opencv/opencv-python/issues/1201 "numpy >=2.5.2", ] +# python-xlib's setup.py imports pkg_resources, removed in setuptools 81 +# https://github.com/python-xlib/python-xlib/issues/286 +build-constraint-dependencies = ["setuptools <81"] exclude-newer = "1 week" [tool.uv.exclude-newer-package] typed-D3DShot = false # I own it -pillow = "6 day" ### # Development channels ### [tool.uv.sources] beslogic-ruff-config = { git = "https://github.com/Beslogic/Beslogic-Ruff-Config", rev = "39c27278054fc428b54205581a0e23caca7ad77a" } +# Quadratic receive buffer fix for large GetImage replies (X11 capture) +# https://github.com/python-xlib/python-xlib/pull/298 +python-xlib = { git = "https://github.com/Avasam/python-xlib", branch = "fix-quadratic-recv" } # pywin32 = { git = "https://github.com/mhammond/pywin32.git", marker = "python_version >= '3.15'" } # pyinstaller = { git = "https://github.com/pyinstaller/pyinstaller.git", 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.index]] exclude-newer = false # Anaconda index doesn't have upload dates diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 7b53e9a4..56d73cff 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -118,7 +118,7 @@ try { # Icon as PNG (freedesktop doesn't support .ico), converted from res/icon.ico. # Not splash.png, which uses hard transparency for the Tcl/Tk splash. New-Item -ItemType Directory -Path build/AppDir/usr/share/icons/hicolor/256x256/apps -Force | Out-Null - uv run --active python -c "from PIL import Image; Image.open('res/icon.ico').save('build/AppDir/AutoSplit.png')" + uv run --active python -c "import sys; from PySide6.QtGui import QImage; sys.exit(not QImage('res/icon.ico').save('build/AppDir/AutoSplit.png'))" # Top-level -> .DirIcon (file thumbnail); hicolor copy -> desktop integration (menu/taskbar). Copy-Item build/AppDir/AutoSplit.png build/AppDir/usr/share/icons/hicolor/256x256/apps/AutoSplit.png $date = Get-Date -Format 'yyyy-MM-dd' diff --git a/src/capture_method/ScrotCaptureMethod.py b/src/capture_method/ScrotCaptureMethod.py index 19420196..8088060d 100644 --- a/src/capture_method/ScrotCaptureMethod.py +++ b/src/capture_method/ScrotCaptureMethod.py @@ -43,7 +43,7 @@ def _scrot_screenshot(x: int, y: int, width: int, height: int): class ScrotCaptureMethod(CaptureMethodBase): name = "Scrot" - short_description = "fast, may leave files in `/tmp`" + short_description = "slow, may leave files in `/tmp`" description = """ Uses Scrot (SCReenshOT) to take screenshots. Leaves behind a screenshot file in `/tmp` if interrupted.""" diff --git a/src/capture_method/X11CaptureMethod.py b/src/capture_method/X11CaptureMethod.py new file mode 100644 index 00000000..d9e0cf18 --- /dev/null +++ b/src/capture_method/X11CaptureMethod.py @@ -0,0 +1,83 @@ +import sys + +if sys.platform != "linux": + raise OSError + +from typing import TYPE_CHECKING, override + +import cv2 +import numpy as np +from pywinctl import getWindowsWithTitle +from Xlib import X +from Xlib.display import Display +from Xlib.error import BadMatch, BadWindow + +from capture_method.CaptureMethodBase import CaptureMethodBase +from utils import ALPHA_CHANNEL_COUNT, is_valid_image + +if TYPE_CHECKING: + from Xlib.protocol.request import GetImage + + from AutoSplit import AutoSplit + +ALL_PLANES = 0xFFFFFFFF + + +def _image_reply_to_bgr(reply: GetImage, width: int, height: int): + image = np.frombuffer(reply.data, dtype=np.uint8) + # X pads 24-bit depth to 32 bits per pixel (BGRX). The 4th byte is padding, not alpha. + # Other depths (16, 30) use different pixel layouts we don't decode, so return None. + if image.size != width * height * ALPHA_CHANNEL_COUNT: + return None + image = image.reshape((height, width, ALPHA_CHANNEL_COUNT)) + if not is_valid_image(image): + return None + return cv2.cvtColor(image, cv2.COLOR_BGRA2BGR) + + +class X11CaptureMethod(CaptureMethodBase): + name = "X11" + short_description = "fastest" + description = "\nRequests screenshots directly from the X11 server." + + def __init__(self, autosplit: AutoSplit): + super().__init__(autosplit) + self._display = Display() + + @override + def close(self): + self._display.close() + + @override + def get_frame(self): + if not self.check_selected_region_exists(): + return None + + root = self._display.screen().root + try: + window_coords = root.translate_coords(self._autosplit_ref.hwnd, 0, 0)._data # noqa: SLF001 + except BadWindow: + return None + + selection = self._autosplit_ref.settings_dict["capture_region"] + try: + reply = root.get_image( + selection["x"] + window_coords["x"], + selection["y"] + window_coords["y"], + selection["width"], + selection["height"], + X.ZPixmap, + ALL_PLANES, + ) + except BadMatch: + # Region is (partially) outside the screen + return None + return _image_reply_to_bgr(reply, selection["width"], selection["height"]) + + @override + def recover_window(self, captured_window_title: str): + windows = getWindowsWithTitle(captured_window_title) + if len(windows) == 0: + return False + self._autosplit_ref.hwnd = windows[0].getHandle() + return self.check_selected_region_exists() diff --git a/src/capture_method/XcbCaptureMethod.py b/src/capture_method/XcbCaptureMethod.py deleted file mode 100644 index 0cf3cf65..00000000 --- a/src/capture_method/XcbCaptureMethod.py +++ /dev/null @@ -1,80 +0,0 @@ -import sys - -if sys.platform != "linux": - raise OSError - -from typing import TYPE_CHECKING, override - -import cv2 -import numpy as np -from PIL import ImageGrab -from pywinctl import getWindowsWithTitle -from Xlib.display import Display -from Xlib.error import BadWindow - -from capture_method.CaptureMethodBase import CaptureMethodBase -from utils import is_valid_image - -if TYPE_CHECKING: - from AutoSplit import AutoSplit - - -class XcbCaptureMethod(CaptureMethodBase): - name = "X11 XCB" - short_description = "slower, requires XCB" - description = "\nUses the XCB library to take screenshots of the X11 server." - - _xdisplay: str | None = "" # ":0" - - def __init__(self, autosplit: AutoSplit): - super().__init__(autosplit) - self._display = Display() - - @override - def close(self): - self._display.close() - - @override - def get_frame(self): - if not self.check_selected_region_exists(): - return None - - root = self._display.screen().root - try: - window_coords = root.translate_coords(self._autosplit_ref.hwnd, 0, 0)._data # noqa: SLF001 - except BadWindow: - return None - # image = window.get_image( - # selection["x"], - # selection["y"], - # selection["width"], - # selection["height"], - # 1, - # 0, - # ) - - selection = self._autosplit_ref.settings_dict["capture_region"] - x = selection["x"] + window_coords["x"] - y = selection["y"] + window_coords["y"] - image = ImageGrab.grab( - ( - x, - y, - x + selection["width"], - y + selection["height"], - ), - # Passing a non-None xdisplay also prevents pillow falling back to a screenshot tool - xdisplay=self._xdisplay, - ) - image = np.array(image) - if not is_valid_image(image): - return None - return cv2.cvtColor(image, cv2.COLOR_RGB2BGR) - - @override - def recover_window(self, captured_window_title: str): - windows = getWindowsWithTitle(captured_window_title) - if len(windows) == 0: - return False - self._autosplit_ref.hwnd = windows[0].getHandle() - return self.check_selected_region_exists() diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py index 9bc4b2a0..7c9cf4fa 100644 --- a/src/capture_method/__init__.py +++ b/src/capture_method/__init__.py @@ -32,10 +32,8 @@ if sys.platform == "linux": import fcntl - from PIL import features - from capture_method.ScrotCaptureMethod import IS_SCROT_SUPPORTED, ScrotCaptureMethod - from capture_method.XcbCaptureMethod import XcbCaptureMethod + from capture_method.X11CaptureMethod import X11CaptureMethod from vidioc_querycap import V4L2_CAP_VIDEO_CAPTURE, VIDIOC_QUERYCAP, v4l2_capability @@ -82,7 +80,7 @@ def _generate_next_value_( PRINTWINDOW_RENDERFULLCONTENT = auto() DESKTOP_DUPLICATION = auto() SCROT = auto() - XCB = auto() + X11 = auto() VIDEO_CAPTURE_DEVICE = auto() @@ -138,8 +136,7 @@ def get(self, key: CaptureMethodEnum, default: object = None, /): ForceFullContentRenderingCaptureMethod ) elif sys.platform == "linux": - if features.check_feature(feature="xcb"): - CAPTURE_METHODS[CaptureMethodEnum.XCB] = XcbCaptureMethod + CAPTURE_METHODS[CaptureMethodEnum.X11] = X11CaptureMethod if IS_SCROT_SUPPORTED: CAPTURE_METHODS[CaptureMethodEnum.SCROT] = ScrotCaptureMethod CAPTURE_METHODS[CaptureMethodEnum.VIDEO_CAPTURE_DEVICE] = VideoCaptureDeviceCaptureMethod diff --git a/tests/test_import_all_modules.py b/tests/test_import_all_modules.py index b69c7c4b..59b5f2d6 100644 --- a/tests/test_import_all_modules.py +++ b/tests/test_import_all_modules.py @@ -30,7 +30,7 @@ "linux": { "capture_method.Screenshot using QT attempt", "capture_method.ScrotCaptureMethod", - "capture_method.XcbCaptureMethod", + "capture_method.X11CaptureMethod", }, } EXPECTED_OS_ERRORS = frozenset( diff --git a/uv.lock b/uv.lock index 46dc5def..4f57511f 100644 --- a/uv.lock +++ b/uv.lock @@ -19,11 +19,11 @@ exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for exclude-newer-span = "P1W" [options.exclude-newer-package] -pillow = { timestamp = "0001-01-01T00:00:00Z", span = "P6D" } typed-d3dshot = false [manifest] overrides = [{ name = "numpy", specifier = ">=2.5.2" }] +build-constraints = [{ name = "setuptools", specifier = "<81" }] [[manifest.dependency-metadata]] name = "pyautogui" @@ -88,7 +88,6 @@ dependencies = [ { name = "opencv-contrib-python-headless", version = "4.11.0.86", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'ARM64' and sys_platform == 'win32'" }, { name = "opencv-contrib-python-headless", version = "5.0.0.93", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'ARM64' or sys_platform != 'win32'" }, { name = "packaging" }, - { name = "pillow", marker = "sys_platform != 'win32'" }, { name = "pyautogui" }, { name = "pygrabber", marker = "sys_platform == 'win32'" }, { name = "pyinstaller" }, @@ -113,7 +112,6 @@ dev = [ { name = "beslogic-ruff-config" }, { name = "dprint-py" }, { name = "mypy" }, - { name = "pillow" }, { name = "pygrabber" }, { name = "pyright", extra = ["nodejs"] }, { name = "python-xlib" }, @@ -139,12 +137,11 @@ requires-dist = [ { name = "opencv-contrib-python-headless", marker = "platform_machine != 'ARM64' or sys_platform != 'win32'", specifier = ">=5.0" }, { name = "opencv-contrib-python-headless", marker = "platform_machine == 'ARM64' and sys_platform == 'win32'", specifier = "==4.11.*" }, { name = "packaging", specifier = ">=20.0" }, - { name = "pillow", marker = "sys_platform == 'linux'", specifier = ">=13.0.0" }, { name = "pyautogui", specifier = ">=0.9.52" }, { name = "pygrabber", marker = "sys_platform == 'win32'", specifier = ">=0.2" }, { name = "pyinstaller", specifier = ">=6.21.0" }, { name = "pyside6-essentials", specifier = ">=6.9.0" }, - { name = "python-xlib", marker = "sys_platform == 'linux'", specifier = ">=0.33" }, + { name = "python-xlib", marker = "sys_platform == 'linux'", git = "https://github.com/Avasam/python-xlib?branch=fix-quadratic-recv" }, { name = "pywin32", marker = "sys_platform == 'win32'", specifier = ">=312" }, { name = "pywinctl", specifier = ">=0.0.42" }, { name = "tomli-w", specifier = ">=1.1.0" }, @@ -164,10 +161,9 @@ dev = [ { name = "beslogic-ruff-config", git = "https://github.com/Beslogic/Beslogic-Ruff-Config?rev=39c27278054fc428b54205581a0e23caca7ad77a" }, { name = "dprint-py", specifier = ">=0.50.0.0" }, { name = "mypy", specifier = ">=2.2" }, - { name = "pillow", specifier = ">=13.0.0" }, { name = "pygrabber" }, { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.400" }, - { name = "python-xlib" }, + { name = "python-xlib", git = "https://github.com/Avasam/python-xlib?branch=fix-quadratic-recv" }, { name = "ruff" }, { name = "typed-d3dshot", extras = ["numpy"] }, { name = "types-keyboard" }, @@ -498,48 +494,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, ] -[[package]] -name = "pillow" -version = "13.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/a4/9cc30690a394a8305e8be4c9036aa16d0dfddadd09079860b582f562496d/pillow-13.0.0.tar.gz", hash = "sha256:cdcd1ba258178f7356d74170c9a81ba4eab6a5d55748bc402a5a2a6fe6be3510", size = 47058631, upload-time = "2026-10-16T22:18:09.718Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/45/fbad9c7f5e2f1745c373a434d46072bc84eddfefaa28b852425ac957fda6/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:44629d4498c9cb6b2051806155f22141df0d8a929c56134b166655232dfb2f8c", size = 4190721, upload-time = "2026-10-16T22:16:27.524Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/2074902a259a90692c5dbf132885d21671644cbbd3f690047516243a55db/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bf97ba1b5542050e9b0c74e632323b6d8136cc99d64e193abebee22fe6013c76", size = 4290236, upload-time = "2026-10-16T22:16:29.562Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a5/3d1bb93b8ea38d15d21356c1b8e9f50971ac592aaa248f7fb71739094c0e/pillow-13.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:aa694c49787040943570d61c9b8fc01271bd2a2cfdd55f0d896f8aecdce1a981", size = 3723093, upload-time = "2026-10-16T22:16:31.249Z" }, - { url = "https://files.pythonhosted.org/packages/83/16/b603274617b3e6728da3a0133d4fba1ea8d7fbe0c4ea5fef9cbffa398b8b/pillow-13.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5933fcea5a5ba64c686f55c886a115cbfd92f2900460784a1e79909fdbafd7b6", size = 6516058, upload-time = "2026-10-16T22:16:37.228Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f2/1371707ce438ba9a43f9d24089828494ec6f9396e28cd7903aa704dd6306/pillow-13.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e91c2b1cbeae768c98752e67e717e283d68dae45717b41f2d91eaee648e37b7d", size = 7142574, upload-time = "2026-10-16T22:16:39.172Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a9/62cf68587f910bc87c5951bcb6e9430c7643a65076adf5f18b4bf350e2bd/pillow-13.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f6299e494e46253d40c34653cac369412f44ca53b5f85d848d3f95b1ed0546c", size = 6614295, upload-time = "2026-10-16T22:16:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c9/9e9b48178649730299be0bafbf5f810a5b6977538f5a92d47e4ba5f5d608/pillow-13.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f9bd7d260d986f3553518c5d723544346fa48031f8aef3265d786aeb59845bc0", size = 7224841, upload-time = "2026-10-16T22:16:43.118Z" }, - { url = "https://files.pythonhosted.org/packages/8f/48/c44e91384b1b6c072a714a63d00608acc16a6a968735e478e4be4384e5ac/pillow-13.0.0-cp314-cp314-win32.whl", hash = "sha256:2e31c4e68cffd7b1f116127626813b82fcba19edf6b940a7b0c266d6484d0055", size = 6508694, upload-time = "2026-10-16T22:16:45.883Z" }, - { url = "https://files.pythonhosted.org/packages/50/e2/7ad17747517e4c813dca78257d6bb593466f8231baf1384bc98d92745edb/pillow-13.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:f7cbb7a64e7ec664775195e4f81923bc8a93d9451b963f844e59118f63147a7a", size = 7281830, upload-time = "2026-10-16T22:16:47.725Z" }, - { url = "https://files.pythonhosted.org/packages/41/9f/e5b51f2ce8ef4563215334035403ce61a244f93a2e65c24a940131618f87/pillow-13.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6aaa7871aebca081eb5c45f83e845bbd9712ae99fa9286dbfdcee835e63f4b4", size = 2695468, upload-time = "2026-10-16T22:16:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/c9/52/43ded0ac08e77e39d17bd6d894162572201b5e8fdc4a022b14c55a1960a4/pillow-13.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ca8f69b7c77fa96dad99f8638c1fc4f1c0bed4acaa1baf7a3c31bdb80a0ab1", size = 6607008, upload-time = "2026-10-16T22:16:55.525Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2e/4a57ed216d5bf877387cba4d44c02aa87abe51ad2e06404aa6b597c4da39/pillow-13.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bce6a29c3898191f4c9998e4d4e8f69790caeabde46e54da4bc235ce5bde683", size = 7224281, upload-time = "2026-10-16T22:16:57.738Z" }, - { url = "https://files.pythonhosted.org/packages/5c/3d/2929b248e135752d63aaeabf53f6de797e905b7b6263f9c1e2adade20aba/pillow-13.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:816752a5ef76a80484c8c441c85593b7ea9304f6231fc5776ad1fbff7e773058", size = 6695600, upload-time = "2026-10-16T22:16:59.842Z" }, - { url = "https://files.pythonhosted.org/packages/bc/33/fb0da992cf084ed1ee6ee05e96b6191e35c351eb707ca2df874feb24ce47/pillow-13.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5bdb38ba220b61b4ec31dd1046dea6b016d3c6850a23f43ed8b58332b2fc668e", size = 7300589, upload-time = "2026-10-16T22:17:01.686Z" }, - { url = "https://files.pythonhosted.org/packages/bb/af/c30dd9ef6a3dce84ecb1b2393cac629047a32e66279056c6cb46c3ffd65c/pillow-13.0.0-cp314-cp314t-win32.whl", hash = "sha256:06903aac074d00c988e77a1fc435286dc498506d4ca8d6f69df9babddf4ecc15", size = 6517204, upload-time = "2026-10-16T22:17:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/85/d5/ef3a8ae7609c8ab26570abf18194882534b480d245a9148b895a2262c004/pillow-13.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9a635068d9793c7f0d929f39f18b58f1bd86c236e8e7b64167b86af9232ebca0", size = 7286695, upload-time = "2026-10-16T22:17:05.543Z" }, - { url = "https://files.pythonhosted.org/packages/84/3c/0861db0c69a03689ebe3e7599d35668a414deb5aaafd8255215e4e62e3d3/pillow-13.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0d1a9cf7bc6917538b20f8d06b9ab2147e542f68849e96fc58eb66bdf6acc692", size = 2698098, upload-time = "2026-10-16T22:17:07.674Z" }, - { url = "https://files.pythonhosted.org/packages/6e/eb/e86a41962a3a31ad495a3d6a912d9002b01bba4a6fa71c28703abdca7f69/pillow-13.0.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7d5e097eabd5d4915c27ac9f6e9325bb1e01a7c94d7348b5c4451d3fc30e8521", size = 4191213, upload-time = "2026-10-16T22:17:09.449Z" }, - { url = "https://files.pythonhosted.org/packages/dd/04/7e7445e706afec84ef06dc25b2c77f9cecff242c23730c62928ad1089d4a/pillow-13.0.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:51ebb529fc5e8abdd55302f6c3f8943037348ec2384329a3e55677f69ae2fdfc", size = 4290650, upload-time = "2026-10-16T22:17:11.196Z" }, - { url = "https://files.pythonhosted.org/packages/46/85/d534089068ac40c0f61f650389a296c812eab745a8a733f81feec39ae955/pillow-13.0.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:c87a04e7f152ee4db5dbe15f0bfa3fe628a1aeedbe66b81c43b87f52c813f9ac", size = 3723078, upload-time = "2026-10-16T22:17:12.772Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d0/c0ff25bb0427ec70f32aac995453321fc3c4494a46c7292ff02d9d260b04/pillow-13.0.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f8db54177035a2b5f717f94475b74907e12a96dc74f6d487f3966b0a4694071", size = 6527349, upload-time = "2026-10-16T22:17:18.534Z" }, - { url = "https://files.pythonhosted.org/packages/85/11/e46fa0bcc0fc117f1eacc206b3d51f04d583fab328b4b740672d0438d83f/pillow-13.0.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f36aa77f74b14a2103622b196402c71330e4e7cf4122ee45c854e9a01e0eecf", size = 7152873, upload-time = "2026-10-16T22:17:20.415Z" }, - { url = "https://files.pythonhosted.org/packages/7c/53/2bdeca705f3a58679fa9201233193c86a30238340ca29b3a152c3fcc719b/pillow-13.0.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:149a1c323ba366f90b48e6f1a6db6582eb50512df374588de56fc909abc0f0f3", size = 6626871, upload-time = "2026-10-16T22:17:22.361Z" }, - { url = "https://files.pythonhosted.org/packages/14/de/3829030f209722ade3753bb456b805a0e7f885cf885b483407365c82e9ca/pillow-13.0.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e892354a424b1e82a78ba16785822bdd40de06f19446a3d4a28047df1a4f8932", size = 7236439, upload-time = "2026-10-16T22:17:24.352Z" }, - { url = "https://files.pythonhosted.org/packages/44/b1/67fc37388382bc036a74f04da5bb6fcbf90ec3ac1f45893e06176c373fd3/pillow-13.0.0-cp315-cp315-win32.whl", hash = "sha256:8e459b613cf31f8980f584387b4fe47bf08e076e288d61aa8eba1f7fd4ddbb81", size = 6508688, upload-time = "2026-10-16T22:17:26.314Z" }, - { url = "https://files.pythonhosted.org/packages/44/05/fdb6a79aee10861165526b41b7b440e36ee1ff58613b2fbefe3acbf06d4f/pillow-13.0.0-cp315-cp315-win_amd64.whl", hash = "sha256:d4737f50d23ddd11d6685faacbb9252887dde0bc7997127757247983d74399d6", size = 7281107, upload-time = "2026-10-16T22:17:28.198Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9c/6c6f7dc2a566467baf121394e83de25ac287cffe34cc030726beb4be0224/pillow-13.0.0-cp315-cp315-win_arm64.whl", hash = "sha256:2310e2adfc13c6d457d7d0f2ffdd5c56a74a317650cf765887df8d515a647ac0", size = 2695513, upload-time = "2026-10-16T22:17:30.013Z" }, - { url = "https://files.pythonhosted.org/packages/b3/64/0861ea4e03c50b14af9ed9dd8b9f388ab42f445c623a9fcbab19aef7aa55/pillow-13.0.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20822140c9a2ba9c5e42df342b0a892ab2e102fc0e3db967a133bb9ce3f898bb", size = 6612582, upload-time = "2026-10-16T22:17:35.415Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/cd1e9b3f60a9f3d5ebe46da36955c26004092a501f7f2c42f9fcc26611bc/pillow-13.0.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffea6b2986a797c84b6d1d27d4f43e0b83c73fc16281a930891cd237cf0d08fe", size = 7230176, upload-time = "2026-10-16T22:17:37.362Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ef/ffb0a2fc85690714fb7ac662956949645d57f15bf4426f520da361e7b6e3/pillow-13.0.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:05cd2cd39055d0b9650060e4513ef4a9a0aea870083950bd046a0d6490f93a22", size = 6703640, upload-time = "2026-10-16T22:17:39.313Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c2/6402ee3ccb3363aeed8fdb3f9fe56703ac5377a539f0b81b53999d7e121f/pillow-13.0.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c6fd031333dedcad7ce3e03d244093f43504f209e86925ebdb2b868fa4037630", size = 7309231, upload-time = "2026-10-16T22:17:41.021Z" }, - { url = "https://files.pythonhosted.org/packages/fc/96/d6b82e8b8c06ad143b66b12e0b31051e4bf489b4fabca1f390a14fe1ebde/pillow-13.0.0-cp315-cp315t-win32.whl", hash = "sha256:c50c80e3591e5cf9a8dfa6007d4f10732f02e32263850999286e56ccd650cfb2", size = 6517200, upload-time = "2026-10-16T22:17:42.886Z" }, - { url = "https://files.pythonhosted.org/packages/60/d4/0062e6005e9b270b165015aafd343364ac378ec5a685431ea42baad33357/pillow-13.0.0-cp315-cp315t-win_amd64.whl", hash = "sha256:adb3c2124c68e480f4632d06cb7caa3b48a51678475f483b4edb1c6dee29ea43", size = 7287488, upload-time = "2026-10-16T22:17:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/e4/81/b8eb349746be7bce47dd95337c6634e84910af91b59aaa9159c034c614fa/pillow-13.0.0-cp315-cp315t-win_arm64.whl", hash = "sha256:4a25756244bdcce3b50580baadf320fa48297f0eeb8e114ba64139a40d4d0fc8", size = 2698073, upload-time = "2026-10-16T22:17:47.556Z" }, -] - [[package]] name = "pyautogui" version = "0.9.54" @@ -646,14 +600,10 @@ wheels = [ [[package]] name = "python-xlib" version = "0.33" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://github.com/Avasam/python-xlib?branch=fix-quadratic-recv#b1be4367f960ae06d7cb2fb24c58e716ba44877f" } dependencies = [ { 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 = [ - { url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" }, -] [[package]] name = "pywin32"