From 9c7b2ae430bca18b1fc1a7329c4b0497f4da4b1d Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 9 Dec 2023 05:09:04 -0500 Subject: [PATCH] All scripts and WSL GUI work --- .vscode/settings.json | 4 +- README.md | 5 ++- docs/CONTRIBUTING.md | 2 +- scripts/install.ps1 | 38 ++++++++++--------- scripts/linux_build_and_install_python.bash | 28 ++++++++++++++ scripts/requirements.txt | 3 +- .../Screenshot using QT attempt.py | 12 ++---- src/capture_method/ScrotCaptureMethod.py | 6 +-- .../VideoCaptureDeviceCaptureMethod.py | 3 +- src/capture_method/XDisplayCaptureMethod.py | 6 +-- src/capture_method/__init__.py | 6 +-- src/error_messages.py | 2 +- src/region_selection.py | 12 ++++-- src/utils.py | 2 + 14 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 scripts/linux_build_and_install_python.bash diff --git a/.vscode/settings.json b/.vscode/settings.json index 5adca23d..815e9679 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,9 +21,9 @@ "editor.tabSize": 2, "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": true, + "source.fixAll": "explicit", // Let dedicated linter (Ruff) organize imports - "source.organizeImports": false, + "source.organizeImports": "never" }, "emeraldwalk.runonsave": { "commands": [ diff --git a/README.md b/README.md index f651eb03..695f63cd 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ This program can be used to automatically start, split, and reset your preferred - Windows 10 and 11. - Linux (Only tested on Ubuntu 22.04) - - Wayland is not supported + - Wayland is not currently supported + - WSL2/WSLg requires an additional Desktop Environment, external X11 server, and/or systemd - Python 3.10+ (Not required for normal use. Refer to the [build instructions](/docs/build%20instructions.md) if you'd like run the application directly in Python). ## OPTIONS @@ -286,6 +287,8 @@ Not a developer? You can still help through the following methods: - Sharing AutoSplit with other speedrunners - Upvoting the following upstream issues in libraries and tools we use: - + - + - - - - diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 41f66bbe..d6066670 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,4 +1,4 @@ - + # Contributing guidelines diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 762c2c1d..7735670d 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -34,13 +34,8 @@ $dev = If ($Env:GITHUB_JOB -eq 'Build') { '' } Else { '-dev' } If ($IsLinux) { If (-not $Env:GITHUB_JOB -or $Env:GITHUB_JOB -eq 'Build') { sudo apt-get update - # python3-tk for splash screen, npm for pyright - sudo apt-get install -y python3-pip python3-tk npm - # Helps ensure build machine has the required PySide6 libraries for all target machines. - # Not everything here is required, but using the documentation from - # https://wiki.qt.io/Building_Qt_5_from_Git#Libxcb - # TODO: Test if still necessary with PySide6 - sudo apt-get install -y '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev + # python3-tk for splash screen, npm for pyright, the rest for PySide6 + sudo apt-get install -y python3-pip python3-tk npm libegl1 libxkbcommon } } # Ensures installation tools are up to date. This also aliases pip to pip3 on MacOS. @@ -61,21 +56,28 @@ pip install PyAutoGUI ImageHash scipy --no-deps --upgrade $libPath = &"$python" -c 'import pyautogui as _; print(_.__path__[0])' (Get-Content "$libPath/_pyautogui_win.py").replace('ctypes.windll.user32.SetProcessDPIAware()', 'pass') | Set-Content "$libPath/_pyautogui_win.py" -$libPath = &"$python" -c 'import pymonctl as _; print(_.__path__[0])' -(Get-Content "$libPath/_pymonctl_win.py").replace('ctypes.windll.shcore.SetProcessDpiAwareness(2)', 'pass') | - Set-Content "$libPath/_pymonctl_win.py" -$libPath = &"$python" -c 'import pywinbox as _; print(_.__path__[0])' -(Get-Content "$libPath/_pywinbox_win.py").replace('ctypes.windll.shcore.SetProcessDpiAwareness(2)', 'pass') | - Set-Content "$libPath/_pywinbox_win.py" +If ($IsWindows) { + $libPath = &"$python" -c 'import pymonctl as _; print(_.__path__[0])' + (Get-Content "$libPath/_pymonctl_win.py").replace('ctypes.windll.shcore.SetProcessDpiAwareness(2)', 'pass') | + Set-Content "$libPath/_pymonctl_win.py" + $libPath = &"$python" -c 'import pywinbox as _; print(_.__path__[0])' + (Get-Content "$libPath/_pywinbox_win.py").replace('ctypes.windll.shcore.SetProcessDpiAwareness(2)', 'pass') | + Set-Content "$libPath/_pywinbox_win.py" + pip uninstall pyscreeze +} +# Because Ubuntu 22.04 is forced to use an older version of PySide6, we do a dirty typing patch +# https://bugreports.qt.io/browse/QTBUG-114635 +If ($IsLinux) { + $libPath = &"$python" -c 'import PySide6 as _; print(_.__path__[0])' + (Get-Content "$libPath/QtWidgets.pyi").replace('-> Tuple:', '-> Tuple[str, ...]:') | + Set-Content "$libPath/QtWidgets.pyi" +} # Uninstall optional dependencies if PyAutoGUI was installed outside this script -# pyscreeze -> pyscreenshot -> mss deps call SetProcessDpiAwareness +# pyscreeze -> pyscreenshot -> mss deps call SetProcessDpiAwareness, used to be installed # pygetwindow, pymsgbox, pytweening, MouseInfo are picked up by PySide6 # (also --exclude from build script, but more consistent with unfrozen run) pip uninstall pyscreenshot mss pygetwindow pymsgbox pytweening MouseInfo -y -If (-not $IsLinux) { - pip uninstall pyscreeze -} - +If ($IsWindows) { pip uninstall pyscreeze } # Don't compile resources on the Build CI job as it'll do so in build script If ($dev) { diff --git a/scripts/linux_build_and_install_python.bash b/scripts/linux_build_and_install_python.bash new file mode 100644 index 00000000..20c82e75 --- /dev/null +++ b/scripts/linux_build_and_install_python.bash @@ -0,0 +1,28 @@ +cd .. + +# Update package lists +sudo apt update + +# Install dependent libraries: +sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev tk-dev + +# Download Python binary package: +wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz + +# Unzip the package: +tar -xzf Python-3.10.13.tgz + +# Execute configure script +cd Python-3.10.13 +./configure --enable-optimizations --enable-shared + +# Build Python 3.10 +make -j 2 + +# Install Python 3.10 +sudo make install + +# Verify the installation +python3.10 -V + +echo "If Python version did not print, you may need to stop active processes" diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 33ad5096..8871a35d 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -14,7 +14,8 @@ psutil>=5.9.6 # Python 3.12 fixes PyAutoGUI PyWinCtl>=0.0.42 # py.typed # When needed, dev builds can be found at https://download.qt.io/snapshots/ci/pyside/dev?C=M;O=D -PySide6-Essentials>=6.6.0 # Python 3.12 support +PySide6-Essentials>=6.6.0 ; sys_platform == 'win32' # Python 3.12 support +PySide6-Essentials<6.5.1 ; sys_platform == 'linux' # Wayland issue on Ubuntu 22.04 https://bugreports.qt.io/browse/QTBUG-114635 requests>=2.28.2 # charset_normalizer 3.x update toml typing-extensions>=4.4.0 # @override decorator support diff --git a/src/capture_method/Screenshot using QT attempt.py b/src/capture_method/Screenshot using QT attempt.py index b87b8caf..7846abd1 100644 --- a/src/capture_method/Screenshot using QT attempt.py +++ b/src/capture_method/Screenshot using QT attempt.py @@ -3,25 +3,21 @@ if sys.platform != "linux": raise OSError() -from typing import TYPE_CHECKING, cast +from typing import cast -import cv2 import numpy as np from cv2.typing import MatLike from PySide6.QtCore import QBuffer, QIODeviceBase from PySide6.QtGui import QGuiApplication -from capture_method.CaptureMethodBase import CaptureMethodBase +from capture_method.CaptureMethodBase import ThreadedLoopCaptureMethod from typing_extensions import override -if TYPE_CHECKING: - from AutoSplit import AutoSplit - -class ScrotCaptureMethod(CaptureMethodBase): +class QtCaptureMethod(ThreadedLoopCaptureMethod): _render_full_content = False @override - def get_frame(self): + def _read_action(self): buffer = QBuffer() buffer.open(QIODeviceBase.OpenModeFlag.ReadWrite) winid = self._autosplit_ref.winId() diff --git a/src/capture_method/ScrotCaptureMethod.py b/src/capture_method/ScrotCaptureMethod.py index 349562ea..21c54489 100644 --- a/src/capture_method/ScrotCaptureMethod.py +++ b/src/capture_method/ScrotCaptureMethod.py @@ -40,11 +40,7 @@ def _read_action(self): selection["height"], ), ) - return np.array(image) - - @override - def get_frame(self): - image = super().get_frame() + image = np.array(image) if not is_valid_image(image): return None return cv2.cvtColor(image, cv2.COLOR_RGB2BGRA) diff --git a/src/capture_method/VideoCaptureDeviceCaptureMethod.py b/src/capture_method/VideoCaptureDeviceCaptureMethod.py index d838a128..4cce4924 100644 --- a/src/capture_method/VideoCaptureDeviceCaptureMethod.py +++ b/src/capture_method/VideoCaptureDeviceCaptureMethod.py @@ -43,11 +43,13 @@ class VideoCaptureDeviceCaptureMethod(ThreadedLoopCaptureMethod): capture_device: cv2.VideoCapture def __init__(self, autosplit: "AutoSplit"): + super().__init__(autosplit) self.capture_device = cv2.VideoCapture(autosplit.settings_dict["capture_device_id"]) self.capture_device.setExceptionMode(True) # The video capture device isn't accessible, don't bother with it. if not self.capture_device.isOpened(): + self.close() return # Ensure we're using the right camera size. And not OpenCV's default 640x480 @@ -62,7 +64,6 @@ def __init__(self, autosplit: "AutoSplit"): except cv2.error: # Some cameras don't allow changing the resolution pass - super().__init__(autosplit) @override def close(self): diff --git a/src/capture_method/XDisplayCaptureMethod.py b/src/capture_method/XDisplayCaptureMethod.py index b3209460..7a9aa121 100644 --- a/src/capture_method/XDisplayCaptureMethod.py +++ b/src/capture_method/XDisplayCaptureMethod.py @@ -43,11 +43,7 @@ def _read_action(self): ), xdisplay=self._xdisplay, ) - return np.array(image) - - @override - def get_frame(self): - image = super().get_frame() + image = np.array(image) if not is_valid_image(image): return None return cv2.cvtColor(image, cv2.COLOR_RGB2BGRA) diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py index 46648da2..65e6d832 100644 --- a/src/capture_method/__init__.py +++ b/src/capture_method/__init__.py @@ -24,7 +24,7 @@ if sys.platform == "linux": import pyscreeze - from PIL import features + from PIL import UnidentifiedImageError, features from capture_method.ScrotCaptureMethod import ScrotCaptureMethod from capture_method.XDisplayCaptureMethod import XDisplayCaptureMethod @@ -152,7 +152,7 @@ def get(self, key: CaptureMethodEnum, __default: object = None): CAPTURE_METHODS[CaptureMethodEnum.XDISPLAY] = XDisplayCaptureMethod try: pyscreeze.screenshot() - except NotImplementedError: + except UnidentifiedImageError: pass else: # TODO: Investigate solution for Slow Scrot: @@ -202,7 +202,7 @@ def get_input_devices(): return cameras -def get_input_device_resolution(index: int): +def get_input_device_resolution(index: int) -> tuple[int, int] | None: if sys.platform != "win32": return (0, 0) filter_graph = FilterGraph() diff --git a/src/error_messages.py b/src/error_messages.py index 9d5df4fe..6cf64805 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -168,7 +168,7 @@ def linux_groups(): def linux_uinput(): set_text_message( "Failed to create a device file using `uinput` module. " - + "This can happen when runnign Linux under WSL. " + + "This can happen when running Linux under WSL. " + "Keyboard events have been disabled.", ) diff --git a/src/region_selection.py b/src/region_selection.py index b5c9dcb9..da6880f0 100644 --- a/src/region_selection.py +++ b/src/region_selection.py @@ -8,7 +8,6 @@ from cv2.typing import MatLike from PySide6 import QtCore, QtGui, QtWidgets from PySide6.QtTest import QTest -from pywinctl import getTopWindowAt from typing_extensions import override import error_messages @@ -34,6 +33,11 @@ if sys.platform == "linux": from Xlib.display import Display + # This variable may be missing in desktopless environment. x11 | wayland + os.environ.setdefault("XDG_SESSION_TYPE", "x11") + +# Must come after the linux XDG_SESSION_TYPE environment variable is set +from pywinctl import getTopWindowAt if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -113,7 +117,7 @@ def select_region(autosplit: "AutoSplit"): if not window: error_messages.region() return - hwnd = window.getHandle().id if sys.platform == "linux" else window.getHandle() + hwnd = window.getHandle() window_text = window.title if not is_valid_hwnd(hwnd) or not window_text: error_messages.region() @@ -162,7 +166,7 @@ def select_window(autosplit: "AutoSplit"): if not window: error_messages.region() return - hwnd = window.getHandle().id if sys.platform == "linux" else window.getHandle() + hwnd = window.getHandle() window_text = window.title if not is_valid_hwnd(hwnd) or not window_text: error_messages.region() @@ -179,7 +183,7 @@ def select_window(autosplit: "AutoSplit"): border_width = ceil((window_width - client_width) / 2) titlebar_with_border_height = window_height - client_height - border_width else: - data = window.getHandle().get_geometry()._data # noqa: SLF001 + data = window._xWin.get_geometry()._data # pyright:ignore[reportPrivateUsage] # noqa: SLF001 client_height = data["height"] client_width = data["width"] border_width = data["border_width"] diff --git a/src/utils.py b/src/utils.py index fae6c11d..ea7425e9 100644 --- a/src/utils.py +++ b/src/utils.py @@ -90,6 +90,8 @@ def first(iterable: Iterable[T]) -> T: def try_delete_dc(dc: "PyCDC"): + if sys.platform != "win32": + raise OSError try: dc.DeleteDC() except win32ui.error: