From faf7f670e8f462159d09e6d4d5001b0fc62f9c0e Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 3 Dec 2025 07:41:35 -0600 Subject: [PATCH 01/26] fix(mypy): remove default args in pre-commit, i.e ignore-missing-imports --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 849122d..a4c1bb9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,4 +9,5 @@ repos: rev: v1.18.2 hooks: - id: mypy + args: [] files: "^(pywayland|example)\/.*" From 7d69180d0f72ae78bb0079952f91a59f9256742e Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Mon, 8 Dec 2025 08:51:13 -0600 Subject: [PATCH 02/26] fix(coveralls): bump version Since coveralls==4.0.2, there is no longer a restriction of versions of python 3.13+, so it is safe to use higher versions when resolving. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7fe02d0..3d321f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,8 @@ ci = ["setuptools>=77.0.0"] test = [ "pytest", "pytest-cov", - "coverage>=6.5.0", - "coveralls>=3.3.1", + "coverage>=7.15.0", + "coveralls>=4.1.0", ] docs = [ "sphinx", From 2deac73b9326bd5733205c6b02a41eacf2df86fd Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:31:41 -0600 Subject: [PATCH 03/26] feat(pre-commit): bump versions --- .pre-commit-config.yaml | 4 ++-- pywayland/_ffi/ffi.pyi | 10 +++++----- pywayland/_ffi/lib.pyi | 4 +--- pywayland/utils.py | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4c1bb9..ae1c4ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.4 + rev: v0.16.0 hooks: - id: ruff-check args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.18.2 + rev: v2.3.0 hooks: - id: mypy args: [] diff --git a/pywayland/_ffi/ffi.pyi b/pywayland/_ffi/ffi.pyi index 848976a..8771675 100644 --- a/pywayland/_ffi/ffi.pyi +++ b/pywayland/_ffi/ffi.pyi @@ -84,18 +84,18 @@ _CDataO = TypeVar("_CDataO", bound=CData) NULL: Any @overload -def new(cdecl: str) -> _CDataT: ... # type: ignore [type-var, misc] +def new(cdecl: str) -> _CDataT: ... # type: ignore [type-var] @overload -def new(cdecl: str, init: Any) -> _CDataT: ... # type: ignore [type-var, misc] +def new(cdecl: str, init: Any) -> _CDataT: ... # type: ignore [type-var] def gc( - cdata: _CDataT, destructor: None | Callable[[_CDataT], None], size: int = 0 + cdata: _CDataT, destructor: Callable[[_CDataT], None] | None, size: int = 0 ) -> _CDataT: ... def buffer(cdata: _CDataT, size: int = -1) -> bytearray: ... def string(cdata: CharCData) -> bytes: ... def release(cdata: _CDataT) -> None: ... def def_extern() -> Callable[[_F], _F]: ... -def new_handle(self: Any) -> _CDataT: ... # type: ignore [type-var, misc] +def new_handle(self: Any) -> _CDataT: ... # type: ignore [type-var] def from_handle(cdata: _CDataT) -> Any: ... -def cast(new_type: str, cdata: _CDataT) -> _CDataO: ... # type: ignore [type-var, misc] +def cast(new_type: str, cdata: _CDataT) -> _CDataO: ... # type: ignore [type-var] def addressof(cdata: _CDataT) -> _CDataT: ... def offsetof(cdecl: str, offset: Any) -> int: ... diff --git a/pywayland/_ffi/lib.pyi b/pywayland/_ffi/lib.pyi index 2a1b9bb..06c5537 100644 --- a/pywayland/_ffi/lib.pyi +++ b/pywayland/_ffi/lib.pyi @@ -1,5 +1,3 @@ -from typing import Any - from .ffi import ( CData, CharCData, @@ -110,7 +108,7 @@ def wl_proxy_marshal_array_constructor( opcode: int, args_ptr: WlArgumentCData, interface_ptr: WlInterfaceCData, -) -> Any: ... +) -> WlProxyCData: ... def wl_proxy_destroy(proxy: WlProxyCData) -> None: ... def wl_proxy_add_dispatcher( proxy: WlProxyCData, diff --git a/pywayland/utils.py b/pywayland/utils.py index d06af00..01ab10b 100644 --- a/pywayland/utils.py +++ b/pywayland/utils.py @@ -93,7 +93,7 @@ def close(self) -> None: def wl_container_of( ptr: ffi._CDataT, ctype: str, member: str, *, ffi: Any = ffi -) -> ffi._CDataO: # type: ignore [type-var, misc] +) -> ffi._CDataO: # type: ignore [type-var] """ #define wl_container_of(ptr, sample, member) \ (__typeof__(sample))((char *)(ptr) - \ From cf4cd68581a349403a2bcc7c25d523df16905544 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:33:05 -0600 Subject: [PATCH 04/26] fix(MANIFEST): clean *.txt --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index ad1f662..25019f0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include *.txt include *.yaml recursive-include pywayland *.pyi recursive-include test *.py From 1322a438287b1a8dd0af22b7641df4e4c6ded7a9 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:21:20 -0600 Subject: [PATCH 05/26] docs: reorganize install to prevent ImportError --- doc/install.rst | 52 +++++++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index d6be320..ee4c163 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -45,11 +45,8 @@ Any additional unfulfilled dependencies should be downloaded. Installing from Source ---------------------- -You can download and run PyWayland from source, which will not only give you -the latest improvements and fixes, but will let you build the protocol files -against a different version than is available through pip (the version of -Wayland the protocol is compiled against is listed on the top of the PyPI_ -page). +You can download and run PyWayland from source, which will let you build the +protocol files against a different version than is available through pip. Getting the Source ^^^^^^^^^^^^^^^^^^ @@ -70,8 +67,8 @@ non-PyPy installations. Note that PyPy platforms ship with cffi. .. _cffi: https://cffi.readthedocs.org/en/latest/ -Generating the Wayland Protocol -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Wayland Protocols +^^^^^^^^^^^^^^^^^ At this point, you have the base PyWayland module, which contains some core objects and objects specific to client and server implementations. The client @@ -79,46 +76,33 @@ and server exchange messages defined in the Wayland protocol, which is an XML file that ships with Wayland. The scanner parses this XML file and generates the relevant objects. -If the Wayland protocol file is in the default location -(``/usr/share/wayland/wayland.xml``) or can be found with ``pkg-config``, you -should be able to build the protocol files without any problems:: - - $ python -m pywayland.scanner - -This will output the protocol files to the directory ``./pywayland/protocol/``. -The input file and the output directory can be set from the command line -options, see ``python -m pywayland.scanner -h`` for more information. - Running PyWayland inplace ^^^^^^^^^^^^^^^^^^^^^^^^^ -Once the protocol files are created, you can generate the cffi module. Note: -this is only required if you want to run from the source in place. If the -libwayland header files are correctly installed, you will just need to run:: +If the libwayland header files are correctly installed and if the Wayland +protocol file is in the default location (``/usr/share/wayland/wayland.xml``) +or can be found with ``pkg-config``, you can build the cffi module and then +generate the protocol files without any problems:: $ python pywayland/ffi_build.py + $ python -m pywayland.scanner --with-protocols -At this point, you should be able to use the PyWayland library. You can check -that you have everything installed correctly by running the associated -test-suite (note that you will also need ``pytest`` to run the tests). Simply -run:: - - $ pytest +This will output the protocol files to the directory ``./pywayland/protocol/``. +The input file and the output directory can be set from the command line +options, see ``python -m pywayland.scanner -h`` for more information. -from the root directory. +You can check that you have everything installed correctly by running the +associated test-suite . Simply run ``pytest`` from the root directory. Installing PyWayland ^^^^^^^^^^^^^^^^^^^^ -The package can be installed from source using typical ``setup.py`` -mechanisms:: +The package can be installed from source using typical pip mechanisms:: - $ python setup.py install + $ pip install -e . -Additional arguments can be used to automatically generate the Wayland -protocols for the standard Wayland package (which will fail if it cannot run) -and the wayland-protocols package (which will be attempted by default, but will -not raise an error if it fails). +The Wayland protocols will need to be manually generated with the scanner, as +described above, or with the installed script ``pywayland-scanner``. If you have any problems or have any feedback, please report back to the `issue tracker`_, contribution is always welcome, see :ref:`contributing`. From 6280e04bf8e6cc0a4b091753291ac034be9f752d Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:24:05 -0600 Subject: [PATCH 06/26] fix: comply with PEP 621 metadata Dynamic generation was not working because of pyproject.toml configuration, and an ImportError when importing versions --- setup.py | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/setup.py b/setup.py index ba3ea48..d97f9ac 100644 --- a/setup.py +++ b/setup.py @@ -12,49 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import subprocess - from setuptools import setup -# For the purposes of uploading to PyPI, we'll get the version of Wayland here -with open("README.rst") as f: - rst_input = f.read().strip().split("\n") -try: - from pywayland import ( - __version__ as pywayland_version, - ) - from pywayland import ( - __wayland_version__ as wayland_version, - ) - - version_tag = f"v{pywayland_version}" -except Exception: - pass -else: - version = f"Built against Wayland {wayland_version}\n" - rst_input.insert(3, version) - - # replace all of the badges and links to point to the current version - rst_input = rst_input[:-9] - rst_input.extend( - [ - f".. |ci| image:: https://github.com/flacjacket/pywayland/actions/workflows/ci.yml/badge.svg?branch={version_tag}", - " :target: https://github.com/flacjacket/pywayland/actions", - " :alt: Build Status", - f".. |coveralls| image:: https://coveralls.io/repos/flacjacket/pywayland/badge.svg?branch={version_tag}", - f" :target: https://coveralls.io/github/flacjacket/pywayland?branch={version_tag}", - " :alt: Build Coverage", - f".. |docs| image:: https://readthedocs.org/projects/pywayland/badge/?version={version_tag}", - f" :target: https://pywayland.readthedocs.io/en/{version_tag}/", - " :alt: Documentation Status", - ] - ) - -long_description = "\n".join(rst_input) - -subprocess.run(["python", "-m", "pywayland.scanner", "--with-protocols"]) setup( - long_description=long_description, - long_description_content_type="text/x-rst", cffi_modules=["pywayland/ffi_build.py:ffi_builder"], ) From 5bfb4874756a35a6e03145234759c60ae20501d0 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:33:17 -0600 Subject: [PATCH 07/26] feat: simplify workflows Use pip install editable to skip rebuilding the cffi modules Use package manager meson for faster installs --- .github/workflows/ci.yml | 25 ++++++------------------- .github/workflows/release.yml | 19 +++++-------------- pyproject.toml | 3 +-- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c066aff..1983b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,11 +23,11 @@ jobs: echo "LD_LIBRARY_PATH=${HOME}/wayland/usr/lib" >> $GITHUB_ENV echo "LIBRARY_PATH=${HOME}/wayland/usr/lib" >> $GITHUB_ENV echo "PKG_CONFIG_PATH=${HOME}/wayland/usr/share/pkgconfig:${HOME}/wayland/usr/lib/pkgconfig" >> $GITHUB_ENV - - name: Install meson + - name: Install dependencies run: | sudo apt-get install -y --no-install-recommends \ + meson \ ninja-build - sudo pip install meson - name: Download and unpack Wayland source run: | wget --no-check-certificate $WAYLAND_URL @@ -41,15 +41,14 @@ jobs: working-directory: wayland-${{ matrix.wayland-version }} run: | meson setup build --prefix=/usr -Ddocumentation=false - ninja -C build DESTDIR=~/wayland ninja -C build install sudo ninja -C build install - name: Build wayland-protocols working-directory: wayland-protocols-${{ matrix.wayland-protocols-version }} run: | meson setup build --prefix=/usr - ninja -C build DESTDIR=~/wayland ninja -C build install + sudo ninja -C build install - name: Create artifact run: tar czf ~/wayland.tar.gz -C ${HOME}/wayland/ . - name: Upload built libraries @@ -91,12 +90,10 @@ jobs: echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV - name: Install Python dependencies run: | - pip install .[ci] - python pywayland/ffi_build.py + pip install -e .[test] python -m pywayland.scanner --with-protocols - name: Run unit tests run: | - pip install .[test] pytest --cov pywayland --cov-report term-missing - name: Push coverage run: coveralls --service=github @@ -139,12 +136,10 @@ jobs: echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV - name: Install Python dependencies run: | - pip install .[ci] - python pywayland/ffi_build.py + pip install -e .[docs] python -m pywayland.scanner --with-protocols - name: Build docs run: | - pip install .[docs] sphinx-build -W -b html doc doc/_build/html pre-commit-test: name: pre-commit test @@ -169,11 +164,6 @@ jobs: - name: Set environment variables run: | echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV - - name: Install Python dependencies - run: | - pip install .[ci] - python pywayland/ffi_build.py - python -m pywayland.scanner --with-protocols - name: Run pre-commit test uses: pre-commit/action@v3.0.1 packaging-test: @@ -201,13 +191,10 @@ jobs: echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV - name: Install Python dependencies run: | - pip install .[ci] - python pywayland/ffi_build.py + pip install -e .[packaging] python -m pywayland.scanner --with-protocols - name: Run packaging test run: | - pip install .[packaging] check-manifest - python setup.py check -m -s python -m build --sdist twine check dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 724f977..638c9ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ jobs: yum -y install \ libffi-devel \ libxml2-devel \ + meson \ ninja-build \ wget - name: Download and unpack Wayland source @@ -44,22 +45,18 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ env.python-version }} - - name: Install python dependencies - run: | - pip install meson ninja - name: Build Wayland working-directory: wayland-${{ env.wayland-version }} run: | meson setup build --prefix=/usr -Ddocumentation=false - ninja -C build DESTDIR=/wayland ninja -C build install ninja -C build install - name: Build Wayland protocols working-directory: wayland-protocols-${{ env.wayland-protocols-version }} run: | meson setup build --prefix=/usr - ninja -C build DESTDIR=/wayland ninja -C build install + ninja -C build install - name: Create artifact run: tar czf /wayland.tar.gz -C /wayland/ . - name: Upload built libraries @@ -93,9 +90,7 @@ jobs: PYTHON_VERSION: ${{ matrix.python-version }} - uses: actions/checkout@v4 - name: Install Python dependencies - run: | - python --version - python -m pip install auditwheel build + run: pip install auditwheel build - name: Build wheels run: | python -m build --wheel @@ -134,9 +129,7 @@ jobs: PP_VERSION: ${{ matrix.pypy-version }} - uses: actions/checkout@v4 - name: Install Python dependencies - run: | - python --version - python -m pip install auditwheel build + run: pip install auditwheel build - name: Build wheels run: | python -m build --wheel @@ -199,9 +192,7 @@ jobs: PYTHON_VERSION: ${{ env.python-version }} - uses: actions/checkout@v4 - name: Install Python dependencies - run: | - python --version - python -m pip install build + run: pip install build - name: Build source run: | python -m build --sdist diff --git a/pyproject.toml b/pyproject.toml index 3d321f3..db424ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,6 @@ source = "https://github.com/flacjacket/pywayland" issues = "https://github.com/flacjacket/pywayland/issues" [project.optional-dependencies] -ci = ["setuptools>=77.0.0"] test = [ "pytest", "pytest-cov", @@ -89,7 +88,7 @@ ignore = [ ".coveragerc", "doc/**", "example/**", - "pywayland/protocol/*.py", + "pywayland/protocol/**", ] [tool.mypy] From 2efd76ce310ea8fbb0dd3d8622875c9f1eb49fa9 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:28:27 -0600 Subject: [PATCH 08/26] feat: bump wayland and protocols versions --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/release.yml | 4 ++-- doc/protocol_build.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1983b5b..e17eef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,8 @@ jobs: strategy: fail-fast: false matrix: - wayland-version: ["1.24.0"] - wayland-protocols-version: ["1.45"] + wayland-version: ["1.26.0"] + wayland-protocols-version: ["1.49"] steps: - name: Setup environment run: | @@ -71,8 +71,8 @@ jobs: - "3.13" - "3.14" - "pypy-3.11" - wayland-version: ["1.24.0"] - wayland-protocols-version: ["1.45"] + wayland-version: ["1.26.0"] + wayland-protocols-version: ["1.49"] steps: - uses: actions/checkout@v4 - name: Download wayland libraries @@ -117,8 +117,8 @@ jobs: needs: build-wayland env: python-version: "3.14" - wayland-version: "1.24.0" - wayland-protocols-version: "1.45" + wayland-version: "1.26.0" + wayland-protocols-version: "1.49" steps: - uses: actions/checkout@v4 - name: Download wayland libraries @@ -147,8 +147,8 @@ jobs: needs: build-wayland env: python-version: "3.14" - wayland-version: "1.24.0" - wayland-protocols-version: "1.45" + wayland-version: "1.26.0" + wayland-protocols-version: "1.49" steps: - uses: actions/checkout@v4 - name: Download wayland libraries @@ -172,8 +172,8 @@ jobs: needs: build-wayland env: python-version: "3.14" - wayland-version: "1.24.0" - wayland-protocols-version: "1.45" + wayland-version: "1.26.0" + wayland-protocols-version: "1.49" steps: - uses: actions/checkout@v4 - name: Download wayland libraries diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 638c9ca..55e76ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,8 @@ jobs: container: quay.io/pypa/manylinux_2_34_x86_64 env: python-version: "3.14" - wayland-version: "1.24.0" - wayland-protocols-version: "1.45" + wayland-version: "1.26.0" + wayland-protocols-version: "1.49" steps: - name: Install dependencies run: | diff --git a/doc/protocol_build.py b/doc/protocol_build.py index 136bbce..9234a7c 100644 --- a/doc/protocol_build.py +++ b/doc/protocol_build.py @@ -3,8 +3,8 @@ import tarfile import urllib.request -wayland_version = "1.24.0" -protocols_version = "1.45" +wayland_version = "1.26.0" +protocols_version = "1.49" wayland_source = f"https://cgit.freedesktop.org/wayland/wayland/plain/protocol/wayland.xml?id={wayland_version}" protocols_source = f"https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/{protocols_version}/downloads/wayland-protocols-{protocols_version}.tar.xz" From 72fb94b05473b0913ca9c922ab132808d66b0743 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:48:54 -0600 Subject: [PATCH 09/26] feat: generate protocols on install Use custom build_backend to run scanner script Remove setup.py legacy Update related documentation --- .github/workflows/ci.yml | 3 --- MANIFEST.in | 1 + build_backend.py | 31 +++++++++++++++++++++++++++++++ doc/install.rst | 3 --- pyproject.toml | 3 ++- setup.py | 19 ------------------- 6 files changed, 34 insertions(+), 26 deletions(-) create mode 100644 build_backend.py delete mode 100644 setup.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e17eef3..c07f164 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,6 @@ jobs: - name: Install Python dependencies run: | pip install -e .[test] - python -m pywayland.scanner --with-protocols - name: Run unit tests run: | pytest --cov pywayland --cov-report term-missing @@ -137,7 +136,6 @@ jobs: - name: Install Python dependencies run: | pip install -e .[docs] - python -m pywayland.scanner --with-protocols - name: Build docs run: | sphinx-build -W -b html doc doc/_build/html @@ -192,7 +190,6 @@ jobs: - name: Install Python dependencies run: | pip install -e .[packaging] - python -m pywayland.scanner --with-protocols - name: Run packaging test run: | check-manifest diff --git a/MANIFEST.in b/MANIFEST.in index 25019f0..bed3559 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ +include *.py include *.yaml recursive-include pywayland *.pyi recursive-include test *.py diff --git a/build_backend.py b/build_backend.py new file mode 100644 index 0000000..ead7d86 --- /dev/null +++ b/build_backend.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import subprocess +import sys +from collections.abc import Callable +from typing import Any, TypeVar + +from setuptools import build_meta as _orig +from setuptools.build_meta import * # noqa: F403 +from setuptools.dist import Distribution + +R = TypeVar("R") + + +def _generate(fn: Callable[..., R]) -> Callable[..., R]: + def _wrapped(*args: Any, **kwargs: Any) -> R: + # Run the ffi_build.py script to generate the CFFI bindings + subprocess.run([sys.executable, "pywayland/ffi_build.py"], check=True) + # Run the scanner module to generate the protocol files + subprocess.run( + [sys.executable, "-m", "pywayland.scanner", "--with-protocols"], check=True + ) + Distribution.has_ext_modules = lambda self: True + return fn(*args, **kwargs) + + return _wrapped + + +build_wheel = _generate(_orig.build_wheel) +build_sdist = _generate(_orig.build_sdist) +build_editable = _generate(_orig.build_editable) diff --git a/doc/install.rst b/doc/install.rst index ee4c163..983195c 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -101,9 +101,6 @@ The package can be installed from source using typical pip mechanisms:: $ pip install -e . -The Wayland protocols will need to be manually generated with the scanner, as -described above, or with the installed script ``pywayland-scanner``. - If you have any problems or have any feedback, please report back to the `issue tracker`_, contribution is always welcome, see :ref:`contributing`. diff --git a/pyproject.toml b/pyproject.toml index db424ae..bdb0763 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,8 @@ requires = [ "wheel", "cffi>=1.12.0; platform_python_implementation != 'PyPy'", ] -build-backend = "setuptools.build_meta" +build-backend = "build_backend" +backend-path = ["."] [project] name = "pywayland" diff --git a/setup.py b/setup.py deleted file mode 100644 index d97f9ac..0000000 --- a/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2015 Sean Vig -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from setuptools import setup - -setup( - cffi_modules=["pywayland/ffi_build.py:ffi_builder"], -) From 52d8d17e506faa04fc1a82c95571696275e53993 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:03:09 -0600 Subject: [PATCH 10/26] fix: include cffi module in packaging --- build_backend.py | 10 ++++++++-- pyproject.toml | 2 +- pywayland/ffi_build.py | 10 +++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/build_backend.py b/build_backend.py index ead7d86..9cd7975 100644 --- a/build_backend.py +++ b/build_backend.py @@ -3,6 +3,7 @@ import subprocess import sys from collections.abc import Callable +from pathlib import Path from typing import Any, TypeVar from setuptools import build_meta as _orig @@ -14,11 +15,16 @@ def _generate(fn: Callable[..., R]) -> Callable[..., R]: def _wrapped(*args: Any, **kwargs: Any) -> R: + repo_root = Path(__file__).resolve().parent # Run the ffi_build.py script to generate the CFFI bindings - subprocess.run([sys.executable, "pywayland/ffi_build.py"], check=True) + subprocess.run( + [sys.executable, "pywayland/ffi_build.py"], check=True, cwd=repo_root + ) # Run the scanner module to generate the protocol files subprocess.run( - [sys.executable, "-m", "pywayland.scanner", "--with-protocols"], check=True + [sys.executable, "-m", "pywayland.scanner", "--with-protocols"], + check=True, + cwd=repo_root, ) Distribution.has_ext_modules = lambda self: True return fn(*args, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index bdb0763..940919c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ zip-safe = false include = ["pywayland*"] [tool.setuptools.package-data] -pywayland = ["py.typed"] +pywayland = ["py.typed", "_ffi*.so"] [tool.setuptools.dynamic] version = {attr = "pywayland.version.__version__"} diff --git a/pywayland/ffi_build.py b/pywayland/ffi_build.py index 6021e35..beae720 100644 --- a/pywayland/ffi_build.py +++ b/pywayland/ffi_build.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path + from cffi import FFI ############################################################################### @@ -448,5 +450,11 @@ ffi_builder.cdef(CDEF) +def ffi_compile(verbose: bool = False) -> None: + # Build into the project root so the generated module lands at + # pywayland/_ffi.* regardless of the current working directory. + ffi_builder.compile(tmpdir=Path(__file__).parent.parent.as_posix(), verbose=verbose) + + if __name__ == "__main__": - ffi_builder.compile() + ffi_compile() From d33213f4af9635d431e3c137f44bfffae83c2e4c Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:11:47 -0600 Subject: [PATCH 11/26] fix: sdist without *.so file --- .github/workflows/ci.yml | 4 ++++ README.rst | 6 +++++ build_backend.py | 47 ++++++++++++++++++++++++++++++---------- doc/install.rst | 6 ++++- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c07f164..02acd55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,10 @@ jobs: - name: Set environment variables run: | echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV + - name: Generate protocol files + working-directory: pywayland + run: | + python -m scanner --with-protocols - name: Run pre-commit test uses: pre-commit/action@v3.0.1 packaging-test: diff --git a/README.rst b/README.rst index e1f23ca..8c999d5 100644 --- a/README.rst +++ b/README.rst @@ -53,6 +53,12 @@ protocol files can be generated by the scanner module:: $ python -m pywayland.scanner +If ``pywayland._ffi`` is not built yet, run the scanner as a top-level module +from the ``./pywayland`` directory instead:: + + $ cd pywayland + $ python -m scanner + See the help for this module to use non-default locations for the input and output of the scanner. diff --git a/build_backend.py b/build_backend.py index 9cd7975..dc9cc2b 100644 --- a/build_backend.py +++ b/build_backend.py @@ -3,6 +3,7 @@ import subprocess import sys from collections.abc import Callable +from functools import wraps from pathlib import Path from typing import Any, TypeVar @@ -13,25 +14,47 @@ R = TypeVar("R") -def _generate(fn: Callable[..., R]) -> Callable[..., R]: - def _wrapped(*args: Any, **kwargs: Any) -> R: +def run_scanner(fn: Callable[..., R]) -> Callable[..., R]: + @wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> R: repo_root = Path(__file__).resolve().parent - # Run the ffi_build.py script to generate the CFFI bindings - subprocess.run( - [sys.executable, "pywayland/ffi_build.py"], check=True, cwd=repo_root - ) # Run the scanner module to generate the protocol files subprocess.run( - [sys.executable, "-m", "pywayland.scanner", "--with-protocols"], + [sys.executable, "-m", "scanner", "--with-protocols"], check=True, - cwd=repo_root, + cwd=repo_root / "pywayland", + ) + return fn(*args, **kwargs) + + return wrapper + + +def run_ffi_build(fn: Callable[..., R]) -> Callable[..., R]: + @wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> R: + repo_root = Path(__file__).resolve().parent + # Run the ffi_build.py script to generate the CFFI bindings + subprocess.run( + [sys.executable, "pywayland/ffi_build.py"], check=True, cwd=repo_root ) Distribution.has_ext_modules = lambda self: True return fn(*args, **kwargs) - return _wrapped + return wrapper + + +@run_scanner +@run_ffi_build +def build_wheel(*args: Any, **kwargs: Any) -> str: + return _orig.build_wheel(*args, **kwargs) + + +@run_scanner +@run_ffi_build +def build_editable(*args: Any, **kwargs: Any) -> str: + return _orig.build_editable(*args, **kwargs) -build_wheel = _generate(_orig.build_wheel) -build_sdist = _generate(_orig.build_sdist) -build_editable = _generate(_orig.build_editable) +@run_scanner +def build_sdist(*args: Any, **kwargs: Any) -> str: + return _orig.build_sdist(*args, **kwargs) diff --git a/doc/install.rst b/doc/install.rst index 983195c..5dec841 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -99,7 +99,11 @@ Installing PyWayland The package can be installed from source using typical pip mechanisms:: - $ pip install -e . + $ pip install . + +This uses the custom build backend, which runs protocol generation and cffi +binding generation automatically. Manual pre-compilation is not required for +regular pip builds. If you have any problems or have any feedback, please report back to the `issue tracker`_, contribution is always welcome, see :ref:`contributing`. From e48e2aba2982c4ee246121e1be4fb44eec46511a Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:01:46 -0600 Subject: [PATCH 12/26] feat(workflows): use ubuntu-26.04 image --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/release.yml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02acd55..532f11e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: jobs: build-wayland: name: Build Wayland libraries - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -59,7 +59,7 @@ jobs: if-no-files-found: error unit-test: name: Python ${{ matrix.python-version}} unit tests - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: build-wayland strategy: fail-fast: false @@ -103,7 +103,7 @@ jobs: coverage: name: Finalize Coverage needs: unit-test - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Coveralls Finished uses: coverallsapp/github-action@master @@ -112,7 +112,7 @@ jobs: parallel-finished: true docs-test: name: docs check - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: build-wayland env: python-version: "3.14" @@ -141,7 +141,7 @@ jobs: sphinx-build -W -b html doc doc/_build/html pre-commit-test: name: pre-commit test - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: build-wayland env: python-version: "3.14" @@ -170,7 +170,7 @@ jobs: uses: pre-commit/action@v3.0.1 packaging-test: name: packaging test - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: build-wayland env: python-version: "3.14" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55e76ec..e5ad826 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: jobs: build-wayland: name: Build Wayland libraries - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 container: quay.io/pypa/manylinux_2_34_x86_64 env: python-version: "3.14" @@ -67,7 +67,7 @@ jobs: if-no-files-found: error build-wheel-cpython: name: Build CPython wheels - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 container: quay.io/pypa/manylinux_2_34_x86_64 needs: build-wayland strategy: @@ -102,7 +102,7 @@ jobs: path: output_wheels/*.whl build-wheel-pypy: name: Build PyPy wheels - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 container: quay.io/pypa/manylinux_2_34_x86_64 needs: build-wayland strategy: @@ -141,7 +141,7 @@ jobs: path: output_wheels/*.whl test-wheel: name: Test wheels - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: [build-wheel-cpython, build-wheel-pypy] strategy: fail-fast: false @@ -171,7 +171,7 @@ jobs: print(pywayland.__wayland_version__) build-source: name: Build source package - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 container: quay.io/pypa/manylinux_2_34_x86_64 needs: build-wayland env: @@ -203,7 +203,7 @@ jobs: path: dist/*.tar.gz upload-wheel: name: Upload wheels - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: [test-wheel, build-source] steps: - name: Download wheels From cb7e62a7aac7e9e235faab2cfe33a7de9cac17d4 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:22:55 -0600 Subject: [PATCH 13/26] fix(ci): remove sheduler to prevent automatic disable --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 532f11e..2534ca4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,6 @@ on: pull_request: push: workflow_dispatch: - schedule: - # Run at 17:29 every Saturday - - cron: '29 17 * * 6' jobs: build-wayland: From 439a5a30261421a8ee903ce1c8293febdac77bd1 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:23:43 -0600 Subject: [PATCH 14/26] fix(workflows): use meson from pip --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2534ca4..1a2a72e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,8 +23,8 @@ jobs: - name: Install dependencies run: | sudo apt-get install -y --no-install-recommends \ - meson \ ninja-build + sudo pip install meson - name: Download and unpack Wayland source run: | wget --no-check-certificate $WAYLAND_URL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e5ad826..b168830 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,6 @@ jobs: yum -y install \ libffi-devel \ libxml2-devel \ - meson \ ninja-build \ wget - name: Download and unpack Wayland source @@ -45,6 +44,9 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ env.python-version }} + - name: Install meson + run: | + pip install meson - name: Build Wayland working-directory: wayland-${{ env.wayland-version }} run: | From 8670847f510ade6ca8a819ece567809037ebe72d Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:50:34 -0600 Subject: [PATCH 15/26] fix(packaging): install dependencies without package --- .github/workflows/ci.yml | 2 +- pyproject.toml | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a2a72e..f25fdbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,7 +190,7 @@ jobs: echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV - name: Install Python dependencies run: | - pip install -e .[packaging] + pip install build check-manifest twine - name: Run packaging test run: | check-manifest diff --git a/pyproject.toml b/pyproject.toml index 940919c..431587d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,11 +51,6 @@ docs = [ "sphinx", "sphinx_rtd_theme", ] -packaging = [ - "build", - "check-manifest", - "twine", -] [project.scripts] pywayland-scanner = "pywayland.scanner.__main__:main" From 7163de2a911ecd5c63b37fe0d4d69bc054e9a2b3 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:51:45 -0600 Subject: [PATCH 16/26] fix(unit-test): require setuptools in test deps group --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 431587d..6bbfa9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ test = [ "pytest-cov", "coverage>=7.15.0", "coveralls>=4.1.0", + "setuptools>=77.0.0", ] docs = [ "sphinx", From 8711e4830d3ba365e4e0eccbb73042b8c547167d Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 1 Aug 2026 08:21:17 -0600 Subject: [PATCH 17/26] fix(workflows): move to node 24 actions --- .github/workflows/ci.yml | 32 +++++++++++++++++--------------- .github/workflows/release.yml | 28 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f25fdbe..d491052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - name: Create artifact run: tar czf ~/wayland.tar.gz -C ${HOME}/wayland/ . - name: Upload built libraries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wayland-${{ matrix.wayland-version }}_${{ matrix.wayland-protocols-version }} path: ~/wayland.tar.gz @@ -71,15 +71,15 @@ jobs: wayland-version: ["1.26.0"] wayland-protocols-version: ["1.49"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland-${{ matrix.wayland-version }}_${{ matrix.wayland-protocols-version }} - name: Unpack wayland artifact run: sudo tar xf wayland.tar.gz -C / - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set environment variables @@ -103,7 +103,7 @@ jobs: runs-on: ubuntu-26.04 steps: - name: Coveralls Finished - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true @@ -116,15 +116,15 @@ jobs: wayland-version: "1.26.0" wayland-protocols-version: "1.49" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland-${{ env.wayland-version }}_${{ env.wayland-protocols-version }} - name: Unpack wayland artifact run: sudo tar xf wayland.tar.gz -C / - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ env.python-version }} - name: Set environment variables @@ -145,15 +145,15 @@ jobs: wayland-version: "1.26.0" wayland-protocols-version: "1.49" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland-${{ env.wayland-version }}_${{ env.wayland-protocols-version }} - name: Unpack wayland artifact run: sudo tar xf wayland.tar.gz -C / - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ env.python-version }} - name: Set environment variables @@ -164,7 +164,9 @@ jobs: run: | python -m scanner --with-protocols - name: Run pre-commit test - uses: pre-commit/action@v3.0.1 + run: | + pip install pre-commit + pre-commit run --show-diff-on-failure --color=always --all-files packaging-test: name: packaging test runs-on: ubuntu-26.04 @@ -174,15 +176,15 @@ jobs: wayland-version: "1.26.0" wayland-protocols-version: "1.49" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland-${{ env.wayland-version }}_${{ env.wayland-protocols-version }} - name: Unpack wayland artifact run: sudo tar xf wayland.tar.gz -C / - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ env.python-version }} - name: Set environment variables diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b168830..3b200d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: - name: Create artifact run: tar czf /wayland.tar.gz -C /wayland/ . - name: Upload built libraries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wayland path: /wayland.tar.gz @@ -78,7 +78,7 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland - name: Unpack wayland artifact @@ -90,7 +90,7 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ matrix.python-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install Python dependencies run: pip install auditwheel build - name: Build wheels @@ -98,7 +98,7 @@ jobs: python -m build --wheel auditwheel repair --plat manylinux_2_34_x86_64 -w output_wheels dist/pywayland-*.whl - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.python-version }} path: output_wheels/*.whl @@ -114,7 +114,7 @@ jobs: pypy-version: ["7.3"] steps: - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland - name: Unpack wayland artifact @@ -129,7 +129,7 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} PP_VERSION: ${{ matrix.pypy-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install Python dependencies run: pip install auditwheel build - name: Build wheels @@ -137,7 +137,7 @@ jobs: python -m build --wheel auditwheel repair --plat manylinux_2_34_x86_64 -w output_wheels dist/pywayland-*.whl - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wheels-pypy-${{ matrix.python-version }} path: output_wheels/*.whl @@ -157,11 +157,11 @@ jobs: - "pypy-3.11" steps: - name: Download wheels - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wheels-${{ matrix.python-version }} - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Install wheel @@ -180,7 +180,7 @@ jobs: python-version: "3.14" steps: - name: Download wayland libraries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: wayland - name: Unpack wayland artifact @@ -192,14 +192,14 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ env.python-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install Python dependencies run: pip install build - name: Build source run: | python -m build --sdist - name: Upload source - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: source path: dist/*.tar.gz @@ -209,13 +209,13 @@ jobs: needs: [test-wheel, build-source] steps: - name: Download wheels - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: pattern: wheels-* path: dist/ merge-multiple: true - name: Download source - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: source path: dist/ From b73851fca7b4fcc00f9943010c292f6bb99b3f4f Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:53:19 -0600 Subject: [PATCH 18/26] feat: rewrite stubs for improved readability --- pywayland/_ffi/ffi.pyi | 92 ++++---- pywayland/_ffi/lib.pyi | 334 ++++++++++++++------------- pywayland/client/display.py | 2 +- pywayland/client/eventqueue.py | 6 +- pywayland/dispatcher.py | 4 +- pywayland/ffi_build.py | 83 +++---- pywayland/protocol_core/globals.py | 6 +- pywayland/protocol_core/interface.py | 22 +- pywayland/protocol_core/message.py | 24 +- pywayland/protocol_core/proxy.py | 10 +- pywayland/protocol_core/resource.py | 9 +- pywayland/server/client.py | 8 +- pywayland/server/display.py | 6 +- pywayland/server/eventloop.py | 29 +-- pywayland/server/listener.py | 12 +- pywayland/utils.py | 2 +- 16 files changed, 317 insertions(+), 332 deletions(-) diff --git a/pywayland/_ffi/ffi.pyi b/pywayland/_ffi/ffi.pyi index 8771675..15b88ea 100644 --- a/pywayland/_ffi/ffi.pyi +++ b/pywayland/_ffi/ffi.pyi @@ -5,74 +5,88 @@ class CData: def __getitem__(self, idx: int) -> Self: ... def __setitem__(self, idx: int, elem: Self) -> None: ... -class DispatcherFuncT: ... -class ResourceDestroyFuncT: ... -class EventLoopFdFuncT: ... -class EventLoopSignalFuncT: ... -class EventLoopTimerFuncT: ... -class EventLoopIdleFuncT: ... -class GlobalBindFuncT: ... -class NotifyFuncT: ... - # built-in cdata types class CharCData(CData): ... - -# wayland cdata types -class WlArgumentCData(CData): +class DispatcherFunc: ... +class ResourceDestroyFunc: ... +class EventLoopFdFunc: ... +class EventLoopSignalFunc: ... +class EventLoopTimerFunc: ... +class EventLoopIdleFunc: ... +class GlobalBindFunc: ... +class NotifyFunc: ... + +class WlArgument(CData): i: int u: int f: int s: CharCData - o: WlObjectCData + o: WlObject n: int - a: WlArrayCData + a: WlArray h: int -class WlArrayCData(CData): +class WlArray(CData): size: int alloc: int data: CData -class WlClientCData(CData): ... -class WlDisplayCData(CData): ... -class WlEventLoopCData(CData): ... -class WlEventSourceCData(CData): ... -class WlGlobalCData(CData): ... +class WlClient(CData): + pass + +class WlDisplay(CData): + pass + +class WlEventLoop(CData): + pass + +class WlEventSource(CData): + pass -class WlInterfaceCData(CData): +class WlGlobal(CData): + pass + +class WlInterface(CData): name: CharCData version: int method_count: int - methods: WlMessageCData + methods: WlMessage event_count: int - events: WlMessageCData + events: WlMessage -class WlListCData(CData): +class WlList(CData): @property - def prev(self) -> WlListCData: ... + def prev(self) -> WlList: ... @property - def next(self) -> WlListCData: ... + def next(self) -> WlList: ... -class WlListenerCData(CData): - link: WlListCData - notify: NotifyFuncT +class WlListener(CData): + link: WlList + notify: NotifyFunc -class WlListenerContainerCData(CData): +class WlListenerContainer(CData): handle: CData - destroy_listener: WlListenerCData + destroy_listener: WlListener -class WlMessageCData(CData): +class WlMessage(CData): name: CharCData signature: CharCData - types: WlInterfaceCData + types: WlInterface + +class WlObject(CData): + pass + +class WlProxy(CData): + pass + +class WlQueue(CData): + pass -class WlObjectCData(CData): ... -class WlProxyCData(CData): ... -class WlQueueCData(CData): ... -class WlResourceCData(CData): ... +class WlResource(CData): + pass -class WlSignalCData(CData): - listener_list: WlListCData +class WlSignal(CData): + listener_list: WlList # special types _FuncType = Callable[..., Any] diff --git a/pywayland/_ffi/lib.pyi b/pywayland/_ffi/lib.pyi index 06c5537..f8e3404 100644 --- a/pywayland/_ffi/lib.pyi +++ b/pywayland/_ffi/lib.pyi @@ -1,201 +1,205 @@ from .ffi import ( CData, CharCData, - DispatcherFuncT, - EventLoopFdFuncT, - EventLoopIdleFuncT, - EventLoopSignalFuncT, - EventLoopTimerFuncT, - GlobalBindFuncT, - NotifyFuncT, - ResourceDestroyFuncT, - WlArgumentCData, - WlClientCData, - WlDisplayCData, - WlEventLoopCData, - WlEventSourceCData, - WlGlobalCData, - WlInterfaceCData, - WlListCData, - WlListenerCData, - WlProxyCData, - WlQueueCData, - WlResourceCData, - WlSignalCData, + DispatcherFunc, + EventLoopFdFunc, + EventLoopIdleFunc, + EventLoopSignalFunc, + EventLoopTimerFunc, + GlobalBindFunc, + NotifyFunc, + ResourceDestroyFunc, + WlArgument, + WlClient, + WlDisplay, + WlEventLoop, + WlEventSource, + WlGlobal, + WlInterface, + WlList, + WlListener, + WlProxy, + WlQueue, + WlResource, + WlSignal, ) +############################################################################### +# wayland-version.h +############################################################################### + WAYLAND_VERSION_MAJOR: int WAYLAND_VERSION_MINOR: int WAYLAND_VERSION_MICRO: int -dispatcher_func: DispatcherFuncT -resource_destroy_func: ResourceDestroyFuncT -event_loop_fd_func: EventLoopFdFuncT -event_loop_signal_func: EventLoopSignalFuncT -event_loop_timer_func: EventLoopTimerFuncT -event_loop_idle_func: EventLoopIdleFuncT -global_bind_func: GlobalBindFuncT -notify_func: NotifyFuncT - -# Display functionality -def wl_display_create() -> WlDisplayCData: ... -def wl_display_destroy(display: WlDisplayCData) -> None: ... -def wl_display_destroy_clients(display: WlDisplayCData) -> None: ... -def wl_display_flush_clients(display: WlDisplayCData) -> None: ... -def wl_display_get_event_loop(display: WlDisplayCData) -> WlEventLoopCData: ... -def wl_display_add_socket(display: WlDisplayCData, name: bytes) -> int: ... -def wl_display_add_socket_auto(display: WlDisplayCData) -> CharCData: ... -def wl_display_terminate(display: WlDisplayCData) -> None: ... -def wl_display_run(display: WlDisplayCData) -> None: ... -def wl_display_get_serial(display: WlDisplayCData) -> int: ... -def wl_display_next_serial(display: WlDisplayCData) -> int: ... -def wl_display_init_shm(display: WlDisplayCData) -> int: ... -def wl_display_add_shm_format(display: WlDisplayCData, format: int) -> int: ... -def wl_event_queue_destroy(queue: WlQueueCData) -> None: ... -def wl_display_connect(name: bytes | CharCData) -> WlDisplayCData: ... -def wl_display_connect_to_fd(fd: int) -> WlDisplayCData: ... -def wl_display_disconnect(display: WlDisplayCData) -> None: ... -def wl_display_get_fd(display: WlDisplayCData) -> int: ... -def wl_display_dispatch(display: WlDisplayCData) -> int: ... -def wl_display_dispatch_pending(display: WlDisplayCData) -> int: ... -def wl_display_dispatch_queue(display: WlDisplayCData, queue: WlQueueCData) -> int: ... -def wl_display_dispatch_queue_pending( - display: WlDisplayCData, queue: WlQueueCData -) -> int: ... -def wl_display_roundtrip(display: WlDisplayCData) -> int: ... -def wl_display_roundtrip_queue(display: WlDisplayCData, queue: WlQueueCData) -> int: ... -def wl_display_get_error(display: WlDisplayCData) -> int: ... -def wl_display_read_events(display: WlDisplayCData) -> int: ... -def wl_display_prepare_read(display: WlDisplayCData) -> int: ... -def wl_display_prepare_read_queue( - display: WlDisplayCData, queue: WlQueueCData -) -> int: ... -def wl_display_flush(display: WlDisplayCData) -> int: ... -def wl_display_create_queue(display: WlDisplayCData) -> WlQueueCData: ... +############################################################################### +# wayland-util.h +############################################################################### -# Fixed_t handling +# wl_fixed_t methods def wl_fixed_to_double(f: int) -> float: ... def wl_fixed_from_double(d: float) -> int: ... def wl_fixed_from_int(i: int) -> int: ... -# Client functionality -def wl_client_create(display: WlDisplayCData, fd: int) -> WlClientCData: ... -def wl_client_destroy(client: WlClientCData) -> None: ... -def wl_client_flush(client: WlClientCData) -> None: ... -def wl_client_add_destroy_listener( - client: WlClientCData, listener: WlListenerCData -) -> None: ... -def wl_client_get_object(client: WlClientCData, id: int) -> WlResourceCData: ... -def wl_client_get_credentials( - client: WlClientCData, pid: CData, uid: CData, gid: CData -) -> None: ... -def os_create_anonymous_file(size: int) -> int: ... +# wl_list methods +def wl_list_init(list: WlList) -> None: ... +def wl_list_insert(list: WlList, elm: WlList) -> None: ... +def wl_list_remove(list: WlList) -> None: ... + +############################################################################### +# wayland-client-core.h +############################################################################### -# List functionality -def wl_list_init(list: WlListCData) -> None: ... -def wl_list_insert(list: WlListCData, elm: WlListCData) -> None: ... -def wl_list_remove(list: WlListCData) -> None: ... -def wl_list_length(list: WlListCData) -> int: ... -def wl_list_empty(list: WlListCData) -> int: ... -def wl_list_insert_list(list: WlListCData, other: WlListCData) -> None: ... +# wl_event_queue methods +def wl_event_queue_destroy(queue: WlQueue) -> None: ... -# Proxy functionality +# wl_proxy methods def wl_proxy_marshal_array( - proxy: WlProxyCData, opcode: int, args_ptr: WlArgumentCData + proxy: WlProxy, opcode: int, args_ptr: WlArgument ) -> None: ... def wl_proxy_marshal_array_constructor( - proxy: WlProxyCData, - opcode: int, - args_ptr: WlArgumentCData, - interface_ptr: WlInterfaceCData, -) -> WlProxyCData: ... -def wl_proxy_destroy(proxy: WlProxyCData) -> None: ... + proxy: WlProxy, opcode: int, args_ptr: WlArgument, interface_ptr: WlInterface +) -> WlProxy: ... +def wl_proxy_destroy(proxy: WlProxy) -> None: ... def wl_proxy_add_dispatcher( - proxy: WlProxyCData, - dispatcher_func: DispatcherFuncT, - dispatcher_data: CData, - data: CData, + proxy: WlProxy, dispatcher_func: DispatcherFunc, dispatcher_data: CData, data: CData ) -> int: ... -# Resource functionality -def wl_resource_post_event_array( - resource: WlResourceCData, opcode: int, args: WlArgumentCData -) -> None: ... -def wl_resource_post_error( - resource: WlResourceCData, code: int, msg: bytes, *args: str -) -> None: ... -def wl_resource_create( - client: WlClientCData, interface: WlInterfaceCData, version: int, id: int -) -> WlResourceCData: ... -def wl_resource_set_dispatcher( - resource: WlResourceCData, - dispatcher_func: DispatcherFuncT, - implementation: CData, - data: CData, - resource_destroy_func: ResourceDestroyFuncT, -) -> None: ... -def wl_resource_destroy(resource: WlResourceCData) -> None: ... -def wl_resource_get_id(resource: WlResourceCData) -> int: ... -def wl_resource_get_user_data(resource: WlResourceCData) -> CData: ... -def wl_resource_get_version(resource: WlResourceCData) -> int: ... -def wl_resource_get_client(resource: WlResourceCData) -> WlClientCData: ... -def wl_resource_add_destroy_listener( - resource: WlResourceCData, listener: WlListenerCData -) -> None: ... -def wl_signal_init(ptr: WlSignalCData) -> None: ... -def wl_signal_add(ptr: WlSignalCData, listener: WlListenerCData) -> None: ... -def wl_signal_emit(ptr: WlSignalCData, data: CData) -> None: ... +# wl_display methods +def wl_display_connect(name: bytes | CharCData) -> WlDisplay: ... +def wl_display_connect_to_fd(fd: int) -> WlDisplay: ... +def wl_display_disconnect(display: WlDisplay) -> None: ... +def wl_display_get_fd(display: WlDisplay) -> int: ... +def wl_display_dispatch(display: WlDisplay) -> int: ... +def wl_display_dispatch_queue(display: WlDisplay, queue: WlQueue) -> int: ... +def wl_display_dispatch_queue_pending(display: WlDisplay, queue: WlQueue) -> int: ... +def wl_display_dispatch_pending(display: WlDisplay) -> int: ... +def wl_display_get_error(display: WlDisplay) -> int: ... +def wl_display_flush(display: WlDisplay) -> int: ... +def wl_display_roundtrip_queue(display: WlDisplay, queue: WlQueue) -> int: ... +def wl_display_roundtrip(display: WlDisplay) -> int: ... +def wl_display_create_queue(display: WlDisplay) -> WlQueue: ... +def wl_display_prepare_read_queue(display: WlDisplay, queue: WlQueue) -> int: ... +def wl_display_prepare_read(display: WlDisplay) -> int: ... +def wl_display_read_events(display: WlDisplay) -> int: ... -# Global functionality -def wl_global_create( - display: WlDisplayCData, - interface: WlInterfaceCData, - version: int, - data: CData, - global_bind_func: GlobalBindFuncT, -) -> WlGlobalCData: ... -def wl_global_destroy(data: WlGlobalCData) -> None: ... +############################################################################### +# wayland-server-core.h +############################################################################### -# Eventloop functionality -def wl_event_loop_add_destroy_listener( - ptr: WlEventLoopCData, listener: WlListenerCData -) -> None: ... -def wl_event_loop_create() -> WlEventLoopCData: ... -def wl_event_loop_destroy(event_loop: WlEventLoopCData) -> None: ... +# event kind enum +WL_EVENT_READABLE: int +WL_EVENT_WRITABLE: int +WL_EVENT_HANGUP: int +WL_EVENT_ERROR: int + +# wl_signal methods +def wl_signal_init(ptr: WlSignal) -> None: ... +def wl_signal_add(ptr: WlSignal, listener: WlListener) -> None: ... +def wl_signal_emit(ptr: WlSignal, data: CData) -> None: ... + +# wl_event_loop methods +def wl_event_loop_create() -> WlEventLoop: ... +def wl_event_loop_destroy(event_loop: WlEventLoop) -> None: ... def wl_event_loop_add_fd( - ptr: WlEventLoopCData, + ptr: WlEventLoop, fd: int, mask: int, - event_loop_fd_func: EventLoopFdFuncT, + event_loop_fd_func: EventLoopFdFunc, data: CData, -) -> WlEventSourceCData: ... -def wl_event_source_fd_update(ptr: WlEventSourceCData, mask: int) -> int: ... +) -> WlEventSource: ... def wl_event_loop_add_timer( - ptr: WlEventLoopCData, - event_loop_timer_func: EventLoopTimerFuncT, - data: CData, -) -> WlEventSourceCData: ... + ptr: WlEventLoop, event_loop_timer_func: EventLoopTimerFunc, data: CData +) -> WlEventSource: ... def wl_event_loop_add_signal( - ptr: WlEventLoopCData, + ptr: WlEventLoop, signal: int, - event_loop_signal_func: EventLoopSignalFuncT, + event_loop_signal_func: EventLoopSignalFunc, data: CData, -) -> WlEventSourceCData: ... -def wl_event_loop_dispatch(ptr: WlEventLoopCData, timeout: int) -> int: ... -def wl_event_loop_dispatch_idle(ptr: WlEventLoopCData) -> None: ... +) -> WlEventSource: ... +def wl_event_loop_dispatch(ptr: WlEventLoop, timeout: int) -> int: ... +def wl_event_loop_dispatch_idle(ptr: WlEventLoop) -> None: ... def wl_event_loop_add_idle( - ptr: WlEventLoopCData, - event_loop_idle_func: EventLoopIdleFuncT, + ptr: WlEventLoop, event_loop_idle_func: EventLoopIdleFunc, data: CData +) -> WlEventSource: ... +def wl_event_loop_add_destroy_listener( + ptr: WlEventLoop, listener: WlListener +) -> None: ... + +# wl_event_source methods +def wl_event_source_timer_update(ptr: WlEventSource, ms_delay: int) -> int: ... +def wl_event_source_remove(ptr: WlEventSource) -> int: ... +def wl_event_source_check(ptr: WlEventSource) -> None: ... + +# wl_display methods +def wl_display_create() -> WlDisplay: ... +def wl_display_destroy(display: WlDisplay) -> None: ... +def wl_display_get_event_loop(display: WlDisplay) -> WlEventLoop: ... +def wl_display_add_socket(display: WlDisplay, name: bytes | CharCData) -> int: ... +def wl_display_add_socket_auto(display: WlDisplay) -> CharCData: ... +def wl_display_terminate(display: WlDisplay) -> None: ... +def wl_display_run(display: WlDisplay) -> None: ... +def wl_display_flush_clients(display: WlDisplay) -> None: ... +def wl_display_destroy_clients(display: WlDisplay) -> None: ... +def wl_display_get_serial(display: WlDisplay) -> int: ... +def wl_display_next_serial(display: WlDisplay) -> int: ... +def wl_display_init_shm(display: WlDisplay) -> int: ... +def wl_display_add_shm_format(display: WlDisplay, format: int) -> CData: ... + +# wl_global methods +def wl_global_create( + display: WlDisplay, + interface: WlInterface, + version: int, data: CData, -) -> WlEventSourceCData: ... -def wl_event_loop_get_fd(ptr: WlEventLoopCData) -> int: ... + global_bind_func: GlobalBindFunc, +) -> WlGlobal: ... +def wl_global_destroy(data: WlGlobal) -> None: ... -# Eventsource functionality -def wl_event_source_timer_update(ptr: WlEventSourceCData, ms_delay: int) -> None: ... -def wl_event_source_remove(ptr: WlEventSourceCData) -> None: ... -def wl_event_source_check(ptr: WlEventSourceCData) -> None: ... +# wl_client methods +def wl_client_create(display: WlDisplay, fd: int) -> WlClient: ... +def wl_client_destroy(client: WlClient) -> None: ... +def wl_client_flush(client: WlClient) -> None: ... +def wl_client_get_credentials( + client: WlClient, pid: CData, uid: CData, gid: CData +) -> None: ... +def wl_client_add_destroy_listener(client: WlClient, listener: WlListener) -> None: ... +def wl_client_get_object(client: WlClient, id: int) -> WlResource: ... -WL_EVENT_READABLE: int -WL_EVENT_WRITABLE: int -WL_EVENT_HANGUP: int -WL_EVENT_ERROR: int +# wl_resource methods +def wl_resource_post_event_array( + resource: WlResource, opcode: int, args: WlArgument +) -> None: ... +def wl_resource_post_error( + resource: WlResource, code: int, msg: bytes | CharCData +) -> None: ... +def wl_resource_create( + client: WlClient, interface: WlInterface, version: int, id: int +) -> WlResource: ... +def wl_resource_set_dispatcher( + resource: WlResource, + dispatcher_func: DispatcherFunc, + implementation: CData, + data: CData, + resource_destroy_func: ResourceDestroyFunc, +) -> None: ... +def wl_resource_destroy(resource: WlResource) -> None: ... +def wl_resource_get_id(resource: WlResource) -> int: ... +def wl_resource_get_client(resource: WlResource) -> WlClient: ... +def wl_resource_get_user_data(resource: WlResource) -> CData: ... +def wl_resource_add_destroy_listener( + resource: WlResource, listener: WlListener +) -> None: ... + +# anonymous file methods +def os_create_anonymous_file(size: int) -> int: ... + +# cffi callback functions +dispatcher_func: DispatcherFunc +resource_destroy_func: ResourceDestroyFunc +event_loop_fd_func: EventLoopFdFunc +event_loop_signal_func: EventLoopSignalFunc +event_loop_timer_func: EventLoopTimerFunc +event_loop_idle_func: EventLoopIdleFunc +global_bind_func: GlobalBindFunc +notify_func: NotifyFunc diff --git a/pywayland/client/display.py b/pywayland/client/display.py index 4be6eda..4db1e48 100644 --- a/pywayland/client/display.py +++ b/pywayland/client/display.py @@ -95,7 +95,7 @@ def __init__(self, name_or_fd: int | str | None = None) -> None: self._children: WeakSet[EventQueue | Proxy[Any]] = WeakSet() self._name_or_fd = name_or_fd - self._ptr: ffi.WlDisplayCData | None = None # type: ignore [assignment] + self._ptr: ffi.WlDisplay | None = None # type: ignore [assignment] def __enter__(self) -> Display: """Connect to the display in a context manager""" diff --git a/pywayland/client/eventqueue.py b/pywayland/client/eventqueue.py index 739887d..283f5d2 100644 --- a/pywayland/client/eventqueue.py +++ b/pywayland/client/eventqueue.py @@ -23,10 +23,10 @@ if TYPE_CHECKING: from pywayland.client import Display -weakkeydict: WeakKeyDictionary[ffi.WlQueueCData | None, Display] = WeakKeyDictionary() +weakkeydict: WeakKeyDictionary[ffi.WlQueue | None, Display] = WeakKeyDictionary() -def _event_queue_destroy(display: Display, cdata: ffi.WlQueueCData) -> None: +def _event_queue_destroy(display: Display, cdata: ffi.WlQueue) -> None: # we should be careful that the display is still around if display._ptr is None: return @@ -55,7 +55,7 @@ def __init__(self, display: Display) -> None: # create a destructor, save data and display destructor = functools.partial(_event_queue_destroy, display) - self._ptr: ffi.WlQueueCData | None = ffi.gc(ptr, destructor) + self._ptr: ffi.WlQueue | None = ffi.gc(ptr, destructor) self._display: Display | None = display display._children.add(self) diff --git a/pywayland/dispatcher.py b/pywayland/dispatcher.py index 683607c..0f5d9dd 100644 --- a/pywayland/dispatcher.py +++ b/pywayland/dispatcher.py @@ -33,7 +33,7 @@ def dispatcher_func( target: ffi.CData, opcode: int, message: Message, - c_args: ffi.WlArgumentCData, + c_args: ffi.WlArgument, ) -> int: # `data` is the handle to proxy/resource python object # `target` is the wl_proxy/wl_resource for self, this should be the same as self._ptr @@ -66,7 +66,7 @@ def dispatcher_func( # void (*wl_resource_destroy_func_t)(struct wl_resource *resource) @ffi.def_extern() -def resource_destroy_func(res_ptr: ffi.WlResourceCData) -> None: +def resource_destroy_func(res_ptr: ffi.WlResource) -> None: # the user data to the resource is the handle to the resource resource_handle = lib.wl_resource_get_user_data(res_ptr) resource = ffi.from_handle(resource_handle) diff --git a/pywayland/ffi_build.py b/pywayland/ffi_build.py index beae720..e160015 100644 --- a/pywayland/ffi_build.py +++ b/pywayland/ffi_build.py @@ -19,6 +19,7 @@ ############################################################################### # wayland-version.h ############################################################################### + CDEF = """ #define WAYLAND_VERSION_MAJOR ... #define WAYLAND_VERSION_MINOR ... @@ -28,7 +29,8 @@ ############################################################################### # wayland-util.h ############################################################################### -# wl_fixed_t handling + +# wl_fixed_t methods CDEF += """ typedef int32_t wl_fixed_t; static inline double wl_fixed_to_double(wl_fixed_t f); @@ -36,13 +38,17 @@ static inline wl_fixed_t wl_fixed_from_int(int i); """ -# Event/request dispatching structs +# wl_message struct CDEF += """ struct wl_message { const char *name; const char *signature; const struct wl_interface **types; }; +""" + +# wl_interface struct +CDEF += """ struct wl_interface { const char *name; int version; @@ -51,6 +57,10 @@ int event_count; const struct wl_message *events; }; +""" + +# wl_argument union +CDEF += """ union wl_argument { int32_t i; /**< signed integer */ uint32_t u; /**< unsigned integer */ @@ -63,7 +73,7 @@ }; """ -# wl_array methods +# wl_array struct CDEF += """ struct wl_array { size_t size; @@ -82,12 +92,9 @@ void wl_list_init (struct wl_list *list); void wl_list_insert (struct wl_list *list, struct wl_list *elm); void wl_list_remove(struct wl_list *elm); -int wl_list_length (const struct wl_list *list); -int wl_list_empty(const struct wl_list *list); -void wl_list_insert_list (struct wl_list *list, struct wl_list *other); """ -# Dispatcher callback +# dispatcher function callback CDEF += """ typedef int (*wl_dispatcher_func_t)(const void *, void *, uint32_t, const struct wl_message *, @@ -95,8 +102,9 @@ """ ############################################################################### -# wayland-client.h +# wayland-client-core.h ############################################################################### + # wl_eventqueue methods CDEF += """ void wl_event_queue_destroy(struct wl_event_queue *queue); @@ -106,19 +114,14 @@ CDEF += """ void wl_proxy_marshal_array(struct wl_proxy *p, uint32_t opcode, union wl_argument *args); -struct wl_proxy *wl_proxy_create(struct wl_proxy *factory, - const struct wl_interface *interface); struct wl_proxy * wl_proxy_marshal_array_constructor(struct wl_proxy *proxy, uint32_t opcode, union wl_argument *args, const struct wl_interface *interface); - void wl_proxy_destroy(struct wl_proxy *proxy); int wl_proxy_add_dispatcher(struct wl_proxy *proxy, wl_dispatcher_func_t dispatcher_func, const void * dispatcher_data, void *data); -void wl_proxy_set_user_data(struct wl_proxy *proxy, void *user_data); -void *wl_proxy_get_user_data(struct wl_proxy *proxy); """ # wl_display methods @@ -128,27 +131,28 @@ void wl_display_disconnect(struct wl_display *display); int wl_display_get_fd(struct wl_display *display); int wl_display_dispatch(struct wl_display *display); -int wl_display_dispatch_pending(struct wl_display *display); int wl_display_dispatch_queue(struct wl_display *display, struct wl_event_queue *queue); int wl_display_dispatch_queue_pending(struct wl_display *display, struct wl_event_queue *queue); -int wl_display_roundtrip(struct wl_display *display); +int wl_display_dispatch_pending(struct wl_display *display); +int wl_display_get_error(struct wl_display *display); +int wl_display_flush(struct wl_display *display); int wl_display_roundtrip_queue(struct wl_display *display, struct wl_event_queue *queue); -int wl_display_get_error(struct wl_display *display); -int wl_display_read_events(struct wl_display *display); -int wl_display_prepare_read(struct wl_display *display); +int wl_display_roundtrip(struct wl_display *display); +struct wl_event_queue *wl_display_create_queue(struct wl_display *display); int wl_display_prepare_read_queue(struct wl_display *display, struct wl_event_queue *queue); -int wl_display_flush(struct wl_display *display); -struct wl_event_queue *wl_display_create_queue(struct wl_display *display); +int wl_display_prepare_read(struct wl_display *display); +int wl_display_read_events(struct wl_display *display); """ ############################################################################### -# wayland-server.h +# wayland-server-core.h ############################################################################### -# wl_eventloop enum + +# event kind enum CDEF += """ enum { WL_EVENT_READABLE = ..., @@ -167,6 +171,7 @@ }; """ +# wl_signal methods CDEF += """ struct wl_signal { struct wl_list listener_list; @@ -177,23 +182,19 @@ void wl_signal_emit(struct wl_signal *signal, void *data); """ -# wl_eventloop callbacks and methods +# wl_event_loop methods CDEF += """ typedef int (*wl_event_loop_fd_func_t)(int fd, uint32_t mask, void *data); typedef int (*wl_event_loop_timer_func_t)(void *data); typedef int (*wl_event_loop_signal_func_t)(int signal_number, void *data); typedef void (*wl_event_loop_idle_func_t)(void *data); -void wl_event_loop_add_destroy_listener(struct wl_event_loop *loop, - struct wl_listener * listener); - struct wl_event_loop *wl_event_loop_create(void); void wl_event_loop_destroy(struct wl_event_loop *loop); struct wl_event_source *wl_event_loop_add_fd(struct wl_event_loop *loop, int fd, uint32_t mask, wl_event_loop_fd_func_t func, void *data); -int wl_event_source_fd_update(struct wl_event_source *source, uint32_t mask); struct wl_event_source *wl_event_loop_add_timer(struct wl_event_loop *loop, wl_event_loop_timer_func_t func, void *data); @@ -202,13 +203,13 @@ int signal_number, wl_event_loop_signal_func_t func, void *data); - int wl_event_loop_dispatch(struct wl_event_loop *loop, int timeout); void wl_event_loop_dispatch_idle(struct wl_event_loop *loop); struct wl_event_source *wl_event_loop_add_idle(struct wl_event_loop *loop, wl_event_loop_idle_func_t func, void *data); -int wl_event_loop_get_fd(struct wl_event_loop *loop); +void wl_event_loop_add_destroy_listener(struct wl_event_loop *loop, + struct wl_listener * listener); """ # wl_event_source methods @@ -228,12 +229,10 @@ const char *wl_display_add_socket_auto(struct wl_display *display); void wl_display_terminate(struct wl_display *display); void wl_display_run(struct wl_display *display); - +void wl_display_flush_clients(struct wl_display *display); +void wl_display_destroy_clients(struct wl_display *display); uint32_t wl_display_get_serial(struct wl_display *display); uint32_t wl_display_next_serial(struct wl_display *display); -void wl_display_destroy_clients(struct wl_display *display); -void wl_display_flush_clients(struct wl_display *display); - int wl_display_init_shm(struct wl_display *display); uint32_t *wl_display_add_shm_format(struct wl_display *display, uint32_t format); """ @@ -251,20 +250,18 @@ # wl_client methods CDEF += """ +typedef int pid_t; +typedef unsigned int uid_t; +typedef unsigned int gid_t; struct wl_client; struct wl_client *wl_client_create(struct wl_display *display, int fd); void wl_client_destroy(struct wl_client *client); void wl_client_flush(struct wl_client *client); - -typedef int pid_t; -typedef unsigned int uid_t; -typedef unsigned int gid_t; void wl_client_get_credentials(struct wl_client *client, pid_t *pid, uid_t *uid, gid_t *gid); void wl_client_add_destroy_listener(struct wl_client *client, struct wl_listener *listener); - struct wl_resource * wl_client_get_object(struct wl_client *client, uint32_t id); """ @@ -275,10 +272,8 @@ void wl_resource_post_event_array(struct wl_resource *resource, uint32_t opcode, union wl_argument *args); - void wl_resource_post_error(struct wl_resource *resource, uint32_t code, const char *msg, ...); - struct wl_resource * wl_resource_create(struct wl_client *client, const struct wl_interface *interface, @@ -289,18 +284,14 @@ const void *implementation, void *data, wl_resource_destroy_func_t destroy); - void wl_resource_destroy(struct wl_resource *resource); uint32_t wl_resource_get_id(struct wl_resource *resource); +struct wl_client * +wl_resource_get_client(struct wl_resource *resource); void * wl_resource_get_user_data(struct wl_resource *resource); -int -wl_resource_get_version(struct wl_resource *resource); - -struct wl_client * wl_resource_get_client(struct wl_resource *resource); - void wl_resource_add_destroy_listener(struct wl_resource *resource, struct wl_listener * listener); diff --git a/pywayland/protocol_core/globals.py b/pywayland/protocol_core/globals.py index dccee9b..b80b921 100644 --- a/pywayland/protocol_core/globals.py +++ b/pywayland/protocol_core/globals.py @@ -29,13 +29,13 @@ else: T = TypeVar("T") -weakkeydict: WeakKeyDictionary[ffi.WlGlobalCData, ServerDisplay] = WeakKeyDictionary() +weakkeydict: WeakKeyDictionary[ffi.WlGlobal, ServerDisplay] = WeakKeyDictionary() # void (*wl_global_bind_func_t)(struct wl_client *client, void *data, uint32_t version, uint32_t id) @ffi.def_extern() def global_bind_func( - client_ptr: ffi.WlClientCData, data: ffi.CData, version: int, id: int + client_ptr: ffi.WlClient, data: ffi.CData, version: int, id: int ) -> None: # `data` is the handle to Global callback_info = ffi.from_handle(data) @@ -76,7 +76,7 @@ def __init__(self, display: ServerDisplay, version: int | None = None): if display._ptr is None or display._ptr == ffi.NULL: raise ValueError("Display has been destroyed or couldn't initialize") - self._ptr: ffi.WlGlobalCData | None + self._ptr: ffi.WlGlobal | None if version is None: version = self.interface.version diff --git a/pywayland/protocol_core/interface.py b/pywayland/protocol_core/interface.py index a16a1da..9b0dbd3 100644 --- a/pywayland/protocol_core/interface.py +++ b/pywayland/protocol_core/interface.py @@ -30,7 +30,7 @@ from .proxy import Proxy from .resource import Resource -weakkeydict: WeakKeyDictionary[ffi.WlInterfaceCData, tuple[object, ...]] = ( +weakkeydict: WeakKeyDictionary[ffi.WlInterface, tuple[object, ...]] = ( WeakKeyDictionary() ) @@ -46,7 +46,7 @@ def __init__(self, name: str, bases: tuple[Any], dct: dict[Any, Any]): self.requests: list[Message] = [] # Initialize the interface cdata - self._ptr: ffi.WlInterfaceCData = ffi.new("struct wl_interface *") + self._ptr: ffi.WlInterface = ffi.new("struct wl_interface *") class Interface(metaclass=InterfaceMeta): @@ -60,13 +60,13 @@ class Interface(metaclass=InterfaceMeta): :func:`Interface.request` decorators. """ - _ptr: ffi.WlInterfaceCData + _ptr: ffi.WlInterface name: str version: int proxy_class: type[Proxy[Any]] resource_class: type[Resource[Any]] global_class: type[Global[Any]] - registry: WeakValueDictionary[ffi.WlObjectCData | ffi.WlProxyCData, Proxy[Any]] + registry: WeakValueDictionary[ffi.WlObject | ffi.WlProxy, Proxy[Any]] @classmethod def event( @@ -123,18 +123,10 @@ def _gen_c(cls) -> None: cls._ptr.name = name cls._ptr.version = cls.version - keep_alive: list[ - tuple[ - ffi.CharCData, - ffi.CharCData, - ffi.WlInterfaceCData, - ] - ] = [] + keep_alive: list[tuple[ffi.CharCData, ffi.CharCData, ffi.WlInterface]] = [] # Determine the number of methods to assign and assign them cls._ptr.method_count = len(cls.requests) - methods_ptr: ffi.WlMessageCData = ffi.new( - "struct wl_message[]", len(cls.requests) - ) + methods_ptr: ffi.WlMessage = ffi.new("struct wl_message[]", len(cls.requests)) cls._ptr.methods = methods_ptr # Iterate over the methods @@ -142,7 +134,7 @@ def _gen_c(cls) -> None: keep_alive.append(message.build_message_struct(methods_ptr[i])) cls._ptr.event_count = len(cls.events) - events_ptr: ffi.WlMessageCData = ffi.new("struct wl_message[]", len(cls.events)) + events_ptr: ffi.WlMessage = ffi.new("struct wl_message[]", len(cls.events)) cls._ptr.events = events_ptr # Iterate over the methods for i, message in enumerate(cls.events): diff --git a/pywayland/protocol_core/message.py b/pywayland/protocol_core/message.py index 7271f52..c0fda4d 100644 --- a/pywayland/protocol_core/message.py +++ b/pywayland/protocol_core/message.py @@ -28,7 +28,7 @@ from pywayland.protocol_core import Proxy -weakkeydict: WeakKeyDictionary[ffi.WlArgumentCData, tuple[ffi.CData, ...]] = ( +weakkeydict: WeakKeyDictionary[ffi.WlArgument, tuple[ffi.CData, ...]] = ( WeakKeyDictionary() ) @@ -69,12 +69,8 @@ def _marshaled_arguments(self) -> Iterable[Argument]: yield arg def build_message_struct( - self, wl_message_struct: ffi.WlMessageCData - ) -> tuple[ - ffi.CharCData, - ffi.CharCData, - ffi.WlInterfaceCData, - ]: + self, wl_message_struct: ffi.WlMessage + ) -> tuple[ffi.CharCData, ffi.CharCData, ffi.WlInterface]: """Bulid the wl_message struct for this message :param wl_message_struct: @@ -91,7 +87,7 @@ def build_message_struct( wl_message_struct.name = name cdata_signature: ffi.CharCData = ffi.new("char[]", signature.encode()) wl_message_struct.signature = cdata_signature - types: ffi.WlInterfaceCData = ffi.new( + types: ffi.WlInterface = ffi.new( "struct wl_interface* []", len(list(self._marshaled_arguments)) ) wl_message_struct.types = types @@ -106,7 +102,7 @@ def build_message_struct( return name, cdata_signature, types def c_to_arguments( - self, args_ptr: ffi.WlArgumentCData + self, args_ptr: ffi.WlArgument ) -> list[int | float | str | bytearray | Proxy[Any] | ffi.CData | None]: """Create a list of arguments @@ -119,7 +115,7 @@ def c_to_arguments( :returns: list of args """ args: list[int | float | str | bytearray | Proxy[Any] | ffi.CData | None] = [] - proxy_ptr: ffi.WlProxyCData + proxy_ptr: ffi.WlProxy for i, argument in enumerate(self.arguments): arg_ptr = args_ptr[i] @@ -179,7 +175,7 @@ def c_to_arguments( return args - def arguments_to_c(self, *args: Any) -> ffi.WlArgumentCData: + def arguments_to_c(self, *args: Any) -> ffi.WlArgument: """Create an array of `wl_argument` C structs Generate the CFFI cdata array of `wl_argument` structs that correspond @@ -190,7 +186,7 @@ def arguments_to_c(self, *args: Any) -> ffi.WlArgumentCData: :returns: cdata `union wl_argument []` of args """ nargs = len(list(self._marshaled_arguments)) - args_ptr: ffi.WlArgumentCData = ffi.new("union wl_argument []", nargs) + args_ptr: ffi.WlArgument = ffi.new("union wl_argument []", nargs) arg_iter = iter(args) refs: list[ffi.CData] = [] @@ -226,7 +222,7 @@ def arguments_to_c(self, *args: Any) -> ffi.WlArgumentCData: refs.append(new_string) args_ptr[i].s = new_string elif argument.argument_type == ArgumentType.Object: - new_obj: ffi.WlObjectCData + new_obj: ffi.WlObject if arg is None: if not argument.nullable: raise Exception @@ -237,7 +233,7 @@ def arguments_to_c(self, *args: Any) -> ffi.WlArgumentCData: args_ptr[i].o = new_obj elif argument.argument_type == ArgumentType.Array: # TODO: this is a bit messy, we probably don't want to put everything in one buffer like this - new_array: ffi.WlArrayCData = ffi.new("struct wl_array *") + new_array: ffi.WlArray = ffi.new("struct wl_array *") new_data: ffi.CData = ffi.new("void []", len(arg)) new_array.alloc = new_array.size = len(arg) ffi.buffer(new_data)[:] = arg diff --git a/pywayland/protocol_core/proxy.py b/pywayland/protocol_core/proxy.py index e860cfe..b3a7923 100644 --- a/pywayland/protocol_core/proxy.py +++ b/pywayland/protocol_core/proxy.py @@ -42,7 +42,7 @@ class Proxy(Generic[T]): :func:`Proxy.add_listener`. :param ptr: The pointer to the Wayland proxy object - :type ptr: ffi.WlProxyCData or None + :type ptr: ffi.WlProxy or None :param display: The display associated with this proxy :type display: :class:`~pywayland.client.Display` or Self or None """ @@ -50,9 +50,9 @@ class Proxy(Generic[T]): interface: type[T] def __init__( - self, ptr: ffi.WlProxyCData | None, display: ClientDisplay | Self | None = None + self, ptr: ffi.WlProxy | None, display: ClientDisplay | Self | None = None ) -> None: - self._ptr: ffi.WlProxyCData | None + self._ptr: ffi.WlProxy | None self._display: ClientDisplay | Self | None self.user_data: Any = None self.dispatcher = Dispatcher(self.interface.events) @@ -117,7 +117,7 @@ def _marshal(self, opcode: int, *args: Any) -> None: args_ptr = self.interface.requests[opcode].arguments_to_c(*args) # Write the event into the connection queue - proxy: ffi.WlProxyCData = ffi.cast("struct wl_proxy *", self._ptr) + proxy: ffi.WlProxy = ffi.cast("struct wl_proxy *", self._ptr) lib.wl_proxy_marshal_array(proxy, opcode, args_ptr) def _marshal_constructor( @@ -129,7 +129,7 @@ def _marshal_constructor( args_ptr = self.interface.requests[opcode].arguments_to_c(*args) # Write the event into the connection queue and build a new proxy from the given args - proxy: ffi.WlProxyCData = ffi.cast("struct wl_proxy *", self._ptr) + proxy: ffi.WlProxy = ffi.cast("struct wl_proxy *", self._ptr) proxy_ptr = lib.wl_proxy_marshal_array_constructor( proxy, opcode, args_ptr, interface._ptr ) diff --git a/pywayland/protocol_core/resource.py b/pywayland/protocol_core/resource.py index b284120..58731e4 100644 --- a/pywayland/protocol_core/resource.py +++ b/pywayland/protocol_core/resource.py @@ -52,10 +52,7 @@ class Resource(Generic[T]): interface: type[T] def __init__( - self, - client: Client | ffi.WlClientCData, - version: int | None = None, - id: int = 0, + self, client: Client | ffi.WlClient, version: int | None = None, id: int = 0 ) -> None: if version is None: version = self.interface.version @@ -69,7 +66,7 @@ def __init__( client_ptr = client assert client_ptr is not None - self._ptr: ffi.WlResourceCData | None = lib.wl_resource_create( + self._ptr: ffi.WlResource | None = lib.wl_resource_create( client_ptr, self.interface._ptr, version, id ) self.id = lib.wl_resource_get_id(self._ptr) @@ -106,7 +103,7 @@ def _post_event(self, opcode: int, *args: Any) -> None: args_ptr = self.interface.events[opcode].arguments_to_c(*args) # Write the event array to this object - resource: ffi.WlResourceCData = ffi.cast("struct wl_resource *", self._ptr) + resource: ffi.WlResource = ffi.cast("struct wl_resource *", self._ptr) lib.wl_resource_post_event_array(resource, opcode, args_ptr) @ensure_valid diff --git a/pywayland/server/client.py b/pywayland/server/client.py index 8c7abc3..a09cc69 100644 --- a/pywayland/server/client.py +++ b/pywayland/server/client.py @@ -28,7 +28,7 @@ from .listener import Listener -def _client_destroy(display: Display, cdata: ffi.WlClientCData) -> None: +def _client_destroy(display: Display, cdata: ffi.WlClient) -> None: # do nothing if the display is already destroyed if display.destroyed: logging.error("Display destroyed before client") @@ -62,7 +62,7 @@ def __init__( self, display: Display | None = None, fd: int | None = None, - ptr: ffi.WlClientCData | None = None, + ptr: ffi.WlClient | None = None, ) -> None: if ptr is None: if display is None or display._ptr is None or fd is None: @@ -74,7 +74,7 @@ def __init__( ptr = lib.wl_client_create(display._ptr, fd) destructor = functools.partial(_client_destroy, display) - self._ptr: ffi.WlClientCData | None = ffi.gc(ptr, destructor) + self._ptr: ffi.WlClient | None = ffi.gc(ptr, destructor) else: self._ptr = ptr @@ -147,7 +147,7 @@ def get_object(self, object_id: int) -> Any: return ffi.from_handle(resource_handle) @classmethod - def from_resource(cls, resource: ffi.WlResourceCData) -> Client: + def from_resource(cls, resource: ffi.WlResource) -> Client: """Look up the corresponding wl_client for a wl_resource :param resource: The wl_resource diff --git a/pywayland/server/display.py b/pywayland/server/display.py index 221e5b7..d66ac5e 100644 --- a/pywayland/server/display.py +++ b/pywayland/server/display.py @@ -28,7 +28,7 @@ from pywayland.protocol.wayland import WlShm -def _full_display_gc(ptr: ffi.WlDisplayCData) -> None: +def _full_display_gc(ptr: ffi.WlDisplay) -> None: """Destroy the Display cdata pointer, but only after destroying the clients""" lib.wl_display_destroy_clients(ptr) lib.wl_display_destroy(ptr) @@ -37,13 +37,13 @@ def _full_display_gc(ptr: ffi.WlDisplayCData) -> None: class Display: """Create a Wayland Display object""" - def __init__(self, ptr: ffi.WlDisplayCData | None = None) -> None: + def __init__(self, ptr: ffi.WlDisplay | None = None) -> None: if ptr is None: ptr = lib.wl_display_create() if ptr == ffi.NULL: raise MemoryError("Unable to create wl_display object") - self._ptr: ffi.WlDisplayCData | None = ffi.gc(ptr, _full_display_gc) + self._ptr: ffi.WlDisplay | None = ffi.gc(ptr, _full_display_gc) def __enter__(self) -> Display: """Use the Display in a context manager, which automatically destroys the Display""" diff --git a/pywayland/server/eventloop.py b/pywayland/server/eventloop.py index acd8942..a63b1cb 100644 --- a/pywayland/server/eventloop.py +++ b/pywayland/server/eventloop.py @@ -81,9 +81,7 @@ class EventSource: :type cdata: `ffi cdata` """ - def __init__( - self, eventloop: EventLoop, cdata: ffi.WlEventSourceCData | None - ) -> None: + def __init__(self, eventloop: EventLoop, cdata: ffi.WlEventSource | None) -> None: self._eventloop = eventloop self._ptr = cdata @@ -129,7 +127,7 @@ class FdMask(enum.IntFlag): def __init__(self, display: Display | None = None) -> None: if display and display._ptr is not None: - self._ptr: ffi.WlEventLoopCData | None = lib.wl_display_get_event_loop( + self._ptr: ffi.WlEventLoop | None = lib.wl_display_get_event_loop( display._ptr ) else: @@ -138,7 +136,7 @@ def __init__(self, display: Display | None = None) -> None: self._ptr = ffi.gc(ptr, lib.wl_event_loop_destroy) self.event_sources: WeakSet[EventSource] = WeakSet() - self._callback_handles: list[ffi.WlEventSourceCData] = [] + self._callback_handles: list[ffi.WlEventSource] = [] def destroy(self) -> None: """Destroy the event loop""" @@ -183,7 +181,7 @@ def add_fd( """ assert self._ptr is not None callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSourceCData = ffi.new_handle(callback) + handle: ffi.WlEventSource = ffi.new_handle(callback) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_fd( @@ -196,10 +194,7 @@ def add_fd( @ensure_valid def add_signal( - self, - signal_number: int, - callback: CallbackInfo, - data: ffi.CData | None = None, + self, signal_number: int, callback: CallbackInfo, data: ffi.CData | None = None ) -> EventSource: """Add signal callback @@ -220,7 +215,7 @@ def add_signal( """ assert self._ptr is not None callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSourceCData = ffi.new_handle(callback) + handle: ffi.WlEventSource = ffi.new_handle(callback) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_signal( @@ -233,9 +228,7 @@ def add_signal( @ensure_valid def add_timer( - self, - callback: CallbackInfo, - data: ffi.CData | None = None, + self, callback: CallbackInfo, data: ffi.CData | None = None ) -> EventSource: """Add timer callback @@ -257,7 +250,7 @@ def add_timer( """ assert self._ptr is not None callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSourceCData = ffi.new_handle(callback) + handle: ffi.WlEventSource = ffi.new_handle(callback) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_timer( @@ -270,9 +263,7 @@ def add_timer( @ensure_valid def add_idle( - self, - callback: CallbackInfo, - data: ffi.CData | None = None, + self, callback: CallbackInfo, data: ffi.CData | None = None ) -> EventSource: """Add idle callback @@ -283,7 +274,7 @@ def add_idle( """ assert self._ptr is not None callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSourceCData = ffi.new_handle(callback) + handle: ffi.WlEventSource = ffi.new_handle(callback) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_idle( diff --git a/pywayland/server/listener.py b/pywayland/server/listener.py index c031391..d20c2ab 100644 --- a/pywayland/server/listener.py +++ b/pywayland/server/listener.py @@ -29,8 +29,8 @@ # void (*wl_notify_func_t)(struct wl_listener *listener, void *data); @ffi.def_extern() -def notify_func(listener_ptr: ffi.WlListenerCData, data: ffi.CData) -> None: - container: ffi.WlListenerContainerCData = wl_container_of( +def notify_func(listener_ptr: ffi.WlListener, data: ffi.CData) -> None: + container: ffi.WlListenerContainer = wl_container_of( listener_ptr, "struct wl_listener_container *", "destroy_listener" ) listener = ffi.from_handle(container.handle) @@ -63,13 +63,13 @@ class Listener: """ def __init__(self, function: Callable[..., Any]) -> None: - self._ptr: ffi.WlListenerCData | None + self._ptr: ffi.WlListener | None self._handle: ffi.CData = ffi.new_handle(self) # we need a way to get this Python object from the `struct # wl_listener*`, so we put the pointer in a container struct that # contains both the wl_listener and a pointer to our ffi handle - self.container: ffi.WlListenerContainerCData = ffi.new( + self.container: ffi.WlListenerContainer = ffi.new( "struct wl_listener_container *" ) self.container.handle = self._handle @@ -99,11 +99,11 @@ class Signal: def __init__( self, *, - ptr: ffi.WlSignalCData | None = None, + ptr: ffi.WlSignal | None = None, data_wrapper: Callable[..., Any] | None = None, ) -> None: if ptr is None: - self._ptr: ffi.WlSignalCData = ffi.new("struct wl_signal *") + self._ptr: ffi.WlSignal = ffi.new("struct wl_signal *") lib.wl_signal_init(self._ptr) else: self._ptr = ptr diff --git a/pywayland/utils.py b/pywayland/utils.py index 01ab10b..623e01a 100644 --- a/pywayland/utils.py +++ b/pywayland/utils.py @@ -114,7 +114,7 @@ def wl_container_of( def wl_list_for_each( - ctype: str, head: ffi.WlListCData, member: str, *, ffi: Any = ffi + ctype: str, head: ffi.WlList, member: str, *, ffi: Any = ffi ) -> Iterator[ffi.CData]: """ #define wl_list_for_each(pos, head, member) \ From b6f9812cb51ed0fab0f66080ad5638b247fa07b8 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:40:04 -0600 Subject: [PATCH 19/26] feat: check that wheel includes protocols --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b200d9..ad90340 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,6 +171,9 @@ jobs: run: | import pywayland print(pywayland.__wayland_version__) + + import pywayland.protocol.wayland + import pywayland.protocol.xdg_shell build-source: name: Build source package runs-on: ubuntu-26.04 From 0b84f71d98b9fca00e0c01a70deabe9110867353 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:41:57 -0600 Subject: [PATCH 20/26] fix(resource): id attribute overlap with protocols attr name --- pywayland/protocol_core/resource.py | 7 ++++++- test/test_resource.py | 11 ++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pywayland/protocol_core/resource.py b/pywayland/protocol_core/resource.py index 58731e4..7dc1e98 100644 --- a/pywayland/protocol_core/resource.py +++ b/pywayland/protocol_core/resource.py @@ -69,7 +69,6 @@ def __init__( self._ptr: ffi.WlResource | None = lib.wl_resource_create( client_ptr, self.interface._ptr, version, id ) - self.id = lib.wl_resource_get_id(self._ptr) self._handle: ffi.CData = ffi.new_handle(self) lib.wl_resource_set_dispatcher( @@ -86,6 +85,12 @@ def destroy(self) -> None: lib.wl_resource_destroy(self._ptr) self._ptr = None + @ensure_valid + def get_id(self) -> int: + """Get the id of the Resource""" + assert self._ptr is not None + return lib.wl_resource_get_id(self._ptr) + @ensure_valid def add_destroy_listener(self, listener: Listener) -> None: """Add a listener for the destroy signal diff --git a/test/test_resource.py b/test/test_resource.py index ec1f86c..ba85448 100644 --- a/test/test_resource.py +++ b/test/test_resource.py @@ -29,7 +29,7 @@ def test_create_resource(): assert res.version == 4 # Fetching the client object by id gives the resource back again - assert client.get_object(res.id) == res + assert client.get_object(res.get_id()) == res client.user_data = 0xBEE assert client.user_data == 0xBEE @@ -62,6 +62,7 @@ def test_destroy_resource(): # Create resource res = WlDisplay.resource_class(client, version=4) + res_id = res.get_id() # Attach a destructor and a destroy notification res.dispatcher.destructor = _destroy_callback res.add_destroy_listener(listener) @@ -71,7 +72,7 @@ def test_destroy_resource(): assert destroyed assert notified - assert client.get_object(res.id) is None + assert client.get_object(res_id) is None # Create resource res = WlDisplay.resource_class(client, version=2) @@ -96,11 +97,11 @@ def notest_create_resource_with_same_id(): # Create resource res = WlDisplay.resource_class(client, version=2) - assert client.get_object(res.id) == res + assert client.get_object(res.get_id()) == res # This should replace the old one - res2 = WlDisplay.resource_class(client, version=1, id=res.id) - assert client.get_object(res.id) == res2 + res2 = WlDisplay.resource_class(client, version=1, id=res.get_id()) + assert client.get_object(res.get_id()) == res2 res2.destroy() res.destroy() From 1b9a2db12373f80add0f2c316458186661b970f6 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:40:25 -0600 Subject: [PATCH 21/26] feat: annotate Callable types --- build_backend.py | 11 ++++++----- pywayland/protocol_core/interface.py | 15 +++++++++------ pywayland/protocol_core/message.py | 9 ++++++--- pywayland/server/listener.py | 9 ++++++--- pywayland/utils.py | 11 +++++++---- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/build_backend.py b/build_backend.py index dc9cc2b..5c4d9c9 100644 --- a/build_backend.py +++ b/build_backend.py @@ -5,18 +5,19 @@ from collections.abc import Callable from functools import wraps from pathlib import Path -from typing import Any, TypeVar +from typing import Any, ParamSpec, TypeVar from setuptools import build_meta as _orig from setuptools.build_meta import * # noqa: F403 from setuptools.dist import Distribution +P = ParamSpec("P") R = TypeVar("R") -def run_scanner(fn: Callable[..., R]) -> Callable[..., R]: +def run_scanner(fn: Callable[P, R]) -> Callable[P, R]: @wraps(fn) - def wrapper(*args: Any, **kwargs: Any) -> R: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: repo_root = Path(__file__).resolve().parent # Run the scanner module to generate the protocol files subprocess.run( @@ -29,9 +30,9 @@ def wrapper(*args: Any, **kwargs: Any) -> R: return wrapper -def run_ffi_build(fn: Callable[..., R]) -> Callable[..., R]: +def run_ffi_build(fn: Callable[P, R]) -> Callable[P, R]: @wraps(fn) - def wrapper(*args: Any, **kwargs: Any) -> R: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: repo_root = Path(__file__).resolve().parent # Run the ffi_build.py script to generate the CFFI bindings subprocess.run( diff --git a/pywayland/protocol_core/interface.py b/pywayland/protocol_core/interface.py index 9b0dbd3..a057f8b 100644 --- a/pywayland/protocol_core/interface.py +++ b/pywayland/protocol_core/interface.py @@ -23,13 +23,16 @@ if TYPE_CHECKING: from collections.abc import Callable - from typing import Any + from typing import Any, ParamSpec, TypeVar from .argument import Argument from .globals import Global from .proxy import Proxy from .resource import Resource + P = ParamSpec("P") + R = TypeVar("R") + weakkeydict: WeakKeyDictionary[ffi.WlInterface, tuple[object, ...]] = ( WeakKeyDictionary() ) @@ -66,12 +69,12 @@ class Interface(metaclass=InterfaceMeta): proxy_class: type[Proxy[Any]] resource_class: type[Resource[Any]] global_class: type[Global[Any]] - registry: WeakValueDictionary[ffi.WlObject | ffi.WlProxy, Proxy[Any]] + registry: WeakValueDictionary[ffi.WlProxy, Proxy[Any]] @classmethod def event( cls, *arguments: Argument, version: int | None = None - ) -> Callable[..., Any]: + ) -> Callable[[Callable[P, R]], Callable[P, R]]: """Decorator for interface events Adds the decorated method to the list of events of the interface @@ -83,7 +86,7 @@ def event( :type version: int or None """ - def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(func: Callable[P, R]) -> Callable[P, R]: cls.events.append(Message(func, arguments, version)) return func @@ -92,7 +95,7 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: @classmethod def request( cls, *arguments: Argument, version: int | None = None - ) -> Callable[..., Any]: + ) -> Callable[[Callable[P, R]], Callable[P, R]]: """Decorator for interface requests Adds the decorated method to the list of requests of the interface @@ -104,7 +107,7 @@ def request( :type version: int or None """ - def wrapper(func: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(func: Callable[P, R]) -> Callable[P, R]: cls.requests.append(Message(func, arguments, version)) return func diff --git a/pywayland/protocol_core/message.py b/pywayland/protocol_core/message.py index c0fda4d..e93d4e1 100644 --- a/pywayland/protocol_core/message.py +++ b/pywayland/protocol_core/message.py @@ -24,7 +24,10 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterable - from typing import Any + from typing import Any, ParamSpec, TypeVar + + P = ParamSpec("P") + R = TypeVar("R") from pywayland.protocol_core import Proxy @@ -41,7 +44,7 @@ class Message: the type of method, whether it is a server-side or client-side method. :param func: The function that is represented by the message - :type func: Callable[..., Any] + :type func: Callable :param arguments: The arguments of the message :type arguments: tuple of :class:`~pywayland.protocol_core.Argument` :param version: The version of the message, or None @@ -50,7 +53,7 @@ class Message: def __init__( self, - func: Callable[..., Any], + func: Callable[P, R], arguments: tuple[Argument, ...], version: int | None, ) -> None: diff --git a/pywayland/server/listener.py b/pywayland/server/listener.py index d20c2ab..73326d0 100644 --- a/pywayland/server/listener.py +++ b/pywayland/server/listener.py @@ -22,7 +22,10 @@ if TYPE_CHECKING: from collections.abc import Callable - from typing import Any + from typing import Any, ParamSpec, TypeVar + + P = ParamSpec("P") + R = TypeVar("R") logger = getLogger(__package__) @@ -62,7 +65,7 @@ class Listener: :type function: callable """ - def __init__(self, function: Callable[..., Any]) -> None: + def __init__(self, function: Callable[P, R]) -> None: self._ptr: ffi.WlListener | None self._handle: ffi.CData = ffi.new_handle(self) @@ -100,7 +103,7 @@ def __init__( self, *, ptr: ffi.WlSignal | None = None, - data_wrapper: Callable[..., Any] | None = None, + data_wrapper: Callable[P, R] | None = None, ) -> None: if ptr is None: self._ptr: ffi.WlSignal = ffi.new("struct wl_signal *") diff --git a/pywayland/utils.py b/pywayland/utils.py index 623e01a..a3bdc28 100644 --- a/pywayland/utils.py +++ b/pywayland/utils.py @@ -24,17 +24,20 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterator from types import TracebackType - from typing import Any + from typing import Any, ParamSpec, TypeVar + P = ParamSpec("P") + R = TypeVar("R") -def ensure_valid(func: Callable[..., Any]) -> Callable[..., Any]: + +def ensure_valid(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) - def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: + def wrapper(self: Any, *args: P.args, **kwargs: P.kwargs) -> R: if self._ptr is None: raise ValueError(f"{self.__class__.__name__} object has been destroyed") return func(self, *args, **kwargs) - return wrapper + return wrapper # type: ignore [return-value] class AnonymousFile: From 5a3e72a50ff4a681f7249e3ba1da29f51a531059 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:41:06 -0600 Subject: [PATCH 22/26] feat: improve eventloop func annotations --- pywayland/server/eventloop.py | 37 ++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/pywayland/server/eventloop.py b/pywayland/server/eventloop.py index a63b1cb..4730c77 100644 --- a/pywayland/server/eventloop.py +++ b/pywayland/server/eventloop.py @@ -23,8 +23,13 @@ from pywayland.utils import ensure_valid if TYPE_CHECKING: + from collections.abc import Callable + from typing import TypeVar + from pywayland.server import Display, Listener + T = TypeVar("T") + CallbackInfo = namedtuple("CallbackInfo", ["callback", "data"]) # TODO: add error handling to all callbacks @@ -149,11 +154,7 @@ def destroy(self) -> None: @ensure_valid def add_fd( - self, - fd: int, - callback: CallbackInfo, - mask: FdMask = FdMask.WL_EVENT_READABLE, - data: ffi.CData | None = None, + self, fd: int, callback: Callable[[int, int, T], int], mask: FdMask, data: T ) -> EventSource: """Add file descriptor callback @@ -180,8 +181,8 @@ def add_fd( :meth:`pywayland.server.eventloop.EventSource.check()` """ assert self._ptr is not None - callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSource = ffi.new_handle(callback) + callback_info = CallbackInfo(callback=callback, data=data) + handle: ffi.WlEventSource = ffi.new_handle(callback_info) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_fd( @@ -194,7 +195,7 @@ def add_fd( @ensure_valid def add_signal( - self, signal_number: int, callback: CallbackInfo, data: ffi.CData | None = None + self, signal_number: int, callback: Callable[[int, T], int], data: T ) -> EventSource: """Add signal callback @@ -214,8 +215,8 @@ def add_signal( :returns: :class:`EventSource` for specified callback """ assert self._ptr is not None - callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSource = ffi.new_handle(callback) + callback_info = CallbackInfo(callback=callback, data=data) + handle: ffi.WlEventSource = ffi.new_handle(callback_info) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_signal( @@ -227,9 +228,7 @@ def add_signal( return event_source @ensure_valid - def add_timer( - self, callback: CallbackInfo, data: ffi.CData | None = None - ) -> EventSource: + def add_timer(self, callback: Callable[[T], int], data: T) -> EventSource: """Add timer callback Triggers function call after a specified time. @@ -249,8 +248,8 @@ def add_timer( :meth:`pywayland.server.eventloop.EventSource.timer_update()` """ assert self._ptr is not None - callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSource = ffi.new_handle(callback) + callback_info = CallbackInfo(callback=callback, data=data) + handle: ffi.WlEventSource = ffi.new_handle(callback_info) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_timer( @@ -262,9 +261,7 @@ def add_timer( return event_source @ensure_valid - def add_idle( - self, callback: CallbackInfo, data: ffi.CData | None = None - ) -> EventSource: + def add_idle(self, callback: Callable[[T], None], data: T) -> EventSource: """Add idle callback :param callback: Callback function @@ -273,8 +270,8 @@ def add_idle( :returns: :class:`EventSource` for specified callback """ assert self._ptr is not None - callback = CallbackInfo(callback=callback, data=data) - handle: ffi.WlEventSource = ffi.new_handle(callback) + callback_info = CallbackInfo(callback=callback, data=data) + handle: ffi.WlEventSource = ffi.new_handle(callback_info) self._callback_handles.append(handle) event_source_cdata = lib.wl_event_loop_add_idle( From a959186197994e55bc4aab193e18ab087925bd34 Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:44:11 -0600 Subject: [PATCH 23/26] feat(scanner): annotate wl_proxy class as return type At runtime the value corresponds to the protocol proxy class and not to the Proxy wrapper in protocol_core --- example/simple_touch.py | 4 ++-- example/surface.py | 8 ++++---- pyproject.toml | 1 + pywayland/protocol_core/argument.py | 10 ---------- pywayland/protocol_core/proxy.py | 5 ++--- pywayland/scanner/method.py | 4 ++++ pywayland/scanner/request.py | 2 +- test/scanner_files/scanner_test_v1.py | 14 +++++++------- 8 files changed, 21 insertions(+), 27 deletions(-) diff --git a/example/simple_touch.py b/example/simple_touch.py index 0b461a3..10858bd 100644 --- a/example/simple_touch.py +++ b/example/simple_touch.py @@ -200,7 +200,7 @@ def touch_create(width: int, height: int) -> TouchDictT: touch["surface"] = touch["compositor"].create_surface() if "shell" in touch: - touch["shell_surface"] = touch["shell"].get_shell_surface(touch["surface"]) + touch["shell_surface"] = touch["shell"].get_shell_surface(touch["surface"]) # type: ignore [arg-type] if "shell_surface" in touch: print("shell") @@ -211,7 +211,7 @@ def touch_create(width: int, height: int) -> TouchDictT: touch["surface"].user_data = touch create_shm_buffer(touch, width, height) - touch["surface"].attach(touch["buffer"], 0, 0) + touch["surface"].attach(touch["buffer"], 0, 0) # type: ignore [arg-type] touch["surface"].damage(0, 0, width, height) touch["surface"].commit() diff --git a/example/surface.py b/example/surface.py index 8f2949a..c1028de 100644 --- a/example/surface.py +++ b/example/surface.py @@ -108,7 +108,7 @@ def registry_global_remover(registry: WlRegistryProxy, id_: int) -> None: print(f"got a registry losing event for {id}") -def create_buffer(window: Window) -> WlBufferProxy | None: +def create_buffer(window: Window) -> WlBufferProxy: assert window.shm is not None stride = WIDTH * 4 size = stride * HEIGHT @@ -128,7 +128,7 @@ def create_buffer(window: Window) -> WlBufferProxy | None: def create_window(window: Window) -> None: assert window.surface is not None window.buffer = create_buffer(window) - window.surface.attach(window.buffer, 0, 0) + window.surface.attach(window.buffer, 0, 0) # type: ignore [arg-type] window.surface.commit() @@ -200,7 +200,7 @@ def main() -> None: if window.wm_base: xdg_surface: XdgSurfaceProxy - xdg_surface = window.wm_base.get_xdg_surface(window.surface) + xdg_surface = window.wm_base.get_xdg_surface(window.surface) # type: ignore [arg-type] xdg_surface.dispatcher["configure"] = xdg_surface_configure_handler toplevel: XdgToplevelProxy = xdg_surface.get_toplevel() @@ -211,7 +211,7 @@ def main() -> None: elif window.shell: shell_surface: WlShellSurfaceProxy - shell_surface = window.shell.get_shell_surface(window.surface) + shell_surface = window.shell.get_shell_surface(window.surface) # type: ignore [arg-type] shell_surface.set_toplevel() shell_surface.dispatcher["ping"] = shell_surface_ping_handler diff --git a/pyproject.toml b/pyproject.toml index 6bbfa9e..010b660 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,7 @@ warn_unreachable = true [[tool.mypy.overrides]] module = "pywayland.protocol.*" disallow_any_generics = false +warn_return_any = false [[tool.mypy.overrides]] module = ["cffi"] diff --git a/pywayland/protocol_core/argument.py b/pywayland/protocol_core/argument.py index 1e9f06c..aa004bf 100644 --- a/pywayland/protocol_core/argument.py +++ b/pywayland/protocol_core/argument.py @@ -20,19 +20,9 @@ from pywayland.scanner.argument import ArgumentType if TYPE_CHECKING: - from typing import Any - from .interface import Interface -class classproperty: - def __init__(self, f: Any) -> None: - self.f = f - - def __get__(self, obj: Any, owner: Any) -> Any: - return self.f(owner) - - @dataclass(frozen=True) class Argument: argument_type: ArgumentType diff --git a/pywayland/protocol_core/proxy.py b/pywayland/protocol_core/proxy.py index b3a7923..1dca263 100644 --- a/pywayland/protocol_core/proxy.py +++ b/pywayland/protocol_core/proxy.py @@ -28,7 +28,6 @@ from .interface import Interface T = TypeVar("T", bound=Interface) - InterfaceT = TypeVar("InterfaceT", bound=Interface) else: T = TypeVar("T") @@ -121,8 +120,8 @@ def _marshal(self, opcode: int, *args: Any) -> None: lib.wl_proxy_marshal_array(proxy, opcode, args_ptr) def _marshal_constructor( - self, opcode: int, interface: type[InterfaceT], *args: Any - ) -> Proxy[InterfaceT]: + self, opcode: int, interface: type[Interface], *args: Any + ) -> Any: """Marshal the given arguments into the Wayland wire format for a constructor""" assert self._ptr is not None # Create wl_argument array diff --git a/pywayland/scanner/method.py b/pywayland/scanner/method.py index 5e4021d..04003d2 100644 --- a/pywayland/scanner/method.py +++ b/pywayland/scanner/method.py @@ -72,6 +72,10 @@ def imports( imports.append((import_path, import_class)) + # import the proxy class for the interface if the return type is a proxy + if self.return_type == f"{import_class}Proxy": + imports.append((import_path, f"{import_class}Proxy")) + return imports @property diff --git a/pywayland/scanner/request.py b/pywayland/scanner/request.py index 5a621b0..22849fc 100644 --- a/pywayland/scanner/request.py +++ b/pywayland/scanner/request.py @@ -148,7 +148,7 @@ def return_type(self) -> str: """The return type for the request.""" if self.new_id: if self.new_id.interface: - return f"Proxy[{self.new_id.interface_class}]" + return f"{self.new_id.interface_class}Proxy" else: return "Proxy[T]" return "None" diff --git a/test/scanner_files/scanner_test_v1.py b/test/scanner_files/scanner_test_v1.py index 914efde..7d79cd6 100644 --- a/test/scanner_files/scanner_test_v1.py +++ b/test/scanner_files/scanner_test_v1.py @@ -236,7 +236,7 @@ class WlCoreProxy(Proxy[WlCore]): Argument(ArgumentType.Uint), Argument(ArgumentType.Fixed), ) - def make_request(self, the_int: int, the_uint: int, the_fixed: float) -> Proxy[WlCore]: + def make_request(self, the_int: int, the_uint: int, the_fixed: float) -> WlCoreProxy: """A request The request asks the server for an event. @@ -263,7 +263,7 @@ def make_request(self, the_int: int, the_uint: int, the_fixed: float) -> Proxy[W Argument(ArgumentType.Fixed), Argument(ArgumentType.NewId, interface=WlCore), ) - def make_request2(self, the_int: int, the_uint: int, the_fixed: float) -> Proxy[WlCore]: + def make_request2(self, the_int: int, the_uint: int, the_fixed: float) -> WlCoreProxy: """A request The request asks the server for an event but move the args around. @@ -296,7 +296,7 @@ class WlDestructorProxy(Proxy[WlDestructor]): Argument(ArgumentType.Int), Argument(ArgumentType.Uint), ) - def create_interface(self, x: int, y: int, width: int, height: int, format: int) -> Proxy[WlDestructor]: + def create_interface(self, x: int, y: int, width: int, height: int, format: int) -> WlDestructorProxy: """Create another interface Create a :class:`WlDestructor` interface object @@ -345,7 +345,7 @@ class WlRequestsProxy(Proxy[WlRequests]): Argument(ArgumentType.Uint), Argument(ArgumentType.FileDescriptor), ) - def make_request(self, the_int: int, the_uint: int, the_fd: int) -> Proxy[WlCore]: + def make_request(self, the_int: int, the_uint: int, the_fd: int) -> WlCoreProxy: """A request The request asks the server for an event. @@ -377,7 +377,7 @@ def no_args(self) -> None: @WlRequests.request( Argument(ArgumentType.NewId, interface=WlCore), ) - def create_id(self) -> Proxy[WlCore]: + def create_id(self) -> WlCoreProxy: """Create an id With a description @@ -391,7 +391,7 @@ def create_id(self) -> Proxy[WlCore]: @WlRequests.request( Argument(ArgumentType.NewId, interface=WlCore), ) - def create_id2(self) -> Proxy[WlCore]: + def create_id2(self) -> WlCoreProxy: """Create an id without a description :returns: @@ -422,7 +422,7 @@ def allow_null(self, serial: int, mime_type: str | None) -> None: Argument(ArgumentType.NewId, interface=WlEvents), Argument(ArgumentType.Object, interface=WlCore, nullable=True), ) - def make_import(self, object: WlCore | None) -> Proxy[WlEvents]: + def make_import(self, object: WlCore | None) -> WlEventsProxy: """Request that causes an import A request method that causes an imoprt of other interfaces, both as a From ff68f4c5dc0c8bdafa06339bf604dffd96b5563a Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:01:57 -0600 Subject: [PATCH 24/26] feat(scanner): annotate wl_proxy class as argument type Request in protocol files require the proxy version of the interface at runtime so lets annotate that instead --- example/simple_touch.py | 4 ++-- example/surface.py | 9 ++++----- pywayland/scanner/method.py | 12 ++++++++---- pywayland/scanner/request.py | 6 ++++++ test/scanner_files/scanner_test_v1.py | 2 +- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/example/simple_touch.py b/example/simple_touch.py index 10858bd..0b461a3 100644 --- a/example/simple_touch.py +++ b/example/simple_touch.py @@ -200,7 +200,7 @@ def touch_create(width: int, height: int) -> TouchDictT: touch["surface"] = touch["compositor"].create_surface() if "shell" in touch: - touch["shell_surface"] = touch["shell"].get_shell_surface(touch["surface"]) # type: ignore [arg-type] + touch["shell_surface"] = touch["shell"].get_shell_surface(touch["surface"]) if "shell_surface" in touch: print("shell") @@ -211,7 +211,7 @@ def touch_create(width: int, height: int) -> TouchDictT: touch["surface"].user_data = touch create_shm_buffer(touch, width, height) - touch["surface"].attach(touch["buffer"], 0, 0) # type: ignore [arg-type] + touch["surface"].attach(touch["buffer"], 0, 0) touch["surface"].damage(0, 0, width, height) touch["surface"].commit() diff --git a/example/surface.py b/example/surface.py index c1028de..8899743 100644 --- a/example/surface.py +++ b/example/surface.py @@ -34,7 +34,6 @@ WlShellSurfaceProxy, WlShmPoolProxy, WlShmProxy, - WlShmResource, WlSurfaceProxy, ) from pywayland.protocol.xdg_shell import ( @@ -76,7 +75,7 @@ def shell_surface_ping_handler(shell_surface: WlShellSurfaceProxy, serial: int) print("pinged/ponged") -def shm_format_handler(shm: WlShmResource, format_: int) -> None: +def shm_format_handler(shm: WlShmProxy, format_: int) -> None: format_enum = WlShm.format(format_) print(f"Possible shmem format: {format_enum.name}") @@ -128,7 +127,7 @@ def create_buffer(window: Window) -> WlBufferProxy: def create_window(window: Window) -> None: assert window.surface is not None window.buffer = create_buffer(window) - window.surface.attach(window.buffer, 0, 0) # type: ignore [arg-type] + window.surface.attach(window.buffer, 0, 0) window.surface.commit() @@ -200,7 +199,7 @@ def main() -> None: if window.wm_base: xdg_surface: XdgSurfaceProxy - xdg_surface = window.wm_base.get_xdg_surface(window.surface) # type: ignore [arg-type] + xdg_surface = window.wm_base.get_xdg_surface(window.surface) xdg_surface.dispatcher["configure"] = xdg_surface_configure_handler toplevel: XdgToplevelProxy = xdg_surface.get_toplevel() @@ -211,7 +210,7 @@ def main() -> None: elif window.shell: shell_surface: WlShellSurfaceProxy - shell_surface = window.shell.get_shell_surface(window.surface) # type: ignore [arg-type] + shell_surface = window.shell.get_shell_surface(window.surface) shell_surface.set_toplevel() shell_surface.dispatcher["ping"] = shell_surface_ping_handler diff --git a/pywayland/scanner/method.py b/pywayland/scanner/method.py index 04003d2..8439e5d 100644 --- a/pywayland/scanner/method.py +++ b/pywayland/scanner/method.py @@ -18,7 +18,7 @@ from collections.abc import Iterable from dataclasses import dataclass -from .argument import Argument +from .argument import Argument, ArgumentType from .description import Description from .element import Element from .printer import Printer @@ -72,9 +72,13 @@ def imports( imports.append((import_path, import_class)) - # import the proxy class for the interface if the return type is a proxy - if self.return_type == f"{import_class}Proxy": - imports.append((import_path, f"{import_class}Proxy")) + if self.method_type == "request": + # import the proxy class if the argument is a proxy + if arg.type == ArgumentType.Object: + imports.append((import_path, f"{import_class}Proxy")) + # import the proxy class if the return type is a proxy + if self.return_type == f"{import_class}Proxy": + imports.append((import_path, f"{import_class}Proxy")) return imports diff --git a/pywayland/scanner/request.py b/pywayland/scanner/request.py index 22849fc..1111ef5 100644 --- a/pywayland/scanner/request.py +++ b/pywayland/scanner/request.py @@ -80,6 +80,12 @@ def method_args(self) -> Iterable[str]: # Need a string (interface name) and int (interface version) yield f"{NO_IFACE}: type[T]" yield f"{NO_IFACE_VERSION}: int" + elif arg.type == ArgumentType.Object and arg.interface: + # Method args for a known interface return the proxy class of the interface + signature = f"{arg.name}: {arg.interface_class}Proxy" + if arg.allow_null: + signature += " | None" + yield signature else: yield arg.signature diff --git a/test/scanner_files/scanner_test_v1.py b/test/scanner_files/scanner_test_v1.py index 7d79cd6..53017b0 100644 --- a/test/scanner_files/scanner_test_v1.py +++ b/test/scanner_files/scanner_test_v1.py @@ -422,7 +422,7 @@ def allow_null(self, serial: int, mime_type: str | None) -> None: Argument(ArgumentType.NewId, interface=WlEvents), Argument(ArgumentType.Object, interface=WlCore, nullable=True), ) - def make_import(self, object: WlCore | None) -> WlEventsProxy: + def make_import(self, object: WlCoreProxy | None) -> WlEventsProxy: """Request that causes an import A request method that causes an imoprt of other interfaces, both as a From 9389a8dffb42d1bddf3135f7ab9d43c4229f667c Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Sat, 8 Aug 2026 08:45:24 -0600 Subject: [PATCH 25/26] feat(scanner): wl_registry.bind type return as Any The bind() func returns the proxy of the param interface which cannot be known statically, so return Any and leave it to the user to annotate --- example/color_manager.py | 23 ++++++------- example/simple_touch.py | 47 ++++++++++++--------------- example/surface.py | 8 +++-- example/wlinfo.py | 13 +++++--- pywayland/scanner/argument.py | 4 +-- pywayland/scanner/event.py | 12 +------ pywayland/scanner/interface.py | 6 +--- pywayland/scanner/protocol.py | 13 +------- pywayland/scanner/request.py | 13 +++----- test/scanner_files/scanner_test_v1.py | 6 ++-- 10 files changed, 58 insertions(+), 87 deletions(-) diff --git a/example/color_manager.py b/example/color_manager.py index 1b11ce9..12a5407 100644 --- a/example/color_manager.py +++ b/example/color_manager.py @@ -5,6 +5,7 @@ from __future__ import annotations +import enum from typing import TYPE_CHECKING from pywayland.client import Display @@ -17,43 +18,43 @@ class App: def __init__(self) -> None: - self.color_manager = None - self.render_intents: list[int] = [] - self.features: list[int] = [] - self.transfer_functions: list[int] = [] - self.primaries: list[int] = [] + self.color_manager: WpColorManagerV1Proxy | None = None + self.render_intents: list[enum.IntEnum] = [] + self.features: list[enum.IntEnum] = [] + self.transfer_functions: list[enum.IntEnum] = [] + self.primaries: list[enum.IntEnum] = [] def color_manager_supported_intent_cb( color_manager: WpColorManagerV1Proxy, render_intent: int ) -> None: - app = color_manager.user_data + app: App = color_manager.user_data app.render_intents.append(WpColorManagerV1.render_intent(render_intent)) def color_manager_supported_feature_cb( color_manager: WpColorManagerV1Proxy, feature: int ) -> None: - app = color_manager.user_data + app: App = color_manager.user_data app.features.append(WpColorManagerV1.feature(feature)) def color_manager_supported_tf_named_cb( color_manager: WpColorManagerV1Proxy, transfer_function: int ) -> None: - app = color_manager.user_data + app: App = color_manager.user_data app.transfer_functions.append(WpColorManagerV1.transfer_function(transfer_function)) def color_manager_supported_primaries_named_cb( color_manager: WpColorManagerV1Proxy, primaries: int ) -> None: - app = color_manager.user_data + app: App = color_manager.user_data app.primaries.append(WpColorManagerV1.primaries(primaries)) def color_manager_done_cb(color_manager: WpColorManagerV1Proxy) -> None: - app = color_manager.user_data + app: App = color_manager.user_data print(f"Render intents ({len(app.render_intents)})") for render_intent in app.render_intents: @@ -75,7 +76,7 @@ def color_manager_done_cb(color_manager: WpColorManagerV1Proxy) -> None: def registry_global_cb( registry: WlRegistryProxy, name: int, interface: str, version: int ) -> None: - app = registry.user_data + app: App = registry.user_data if interface == "wp_color_manager_v1": app.color_manager = registry.bind(name, WpColorManagerV1, version) app.color_manager.dispatcher["supported_intent"] = ( diff --git a/example/simple_touch.py b/example/simple_touch.py index 0b461a3..204f83f 100644 --- a/example/simple_touch.py +++ b/example/simple_touch.py @@ -56,6 +56,11 @@ class TouchDictT(TypedDict, total=False): wl_touch: WlTouchProxy seat: WlSeatProxy + class SeatDictT(TypedDict, total=False): + touch: TouchDictT + wl_touch: WlTouchProxy | None + seat: WlSeatProxy + this_file = os.path.abspath(__file__) this_dir = os.path.split(this_file)[0] @@ -93,14 +98,10 @@ def handle_touch_down( x: float, y: float, ) -> int: - # touch = wl_touch.user_data - # touch_paint(touch, x, y, id) return 0 def handle_touch_up(wl_touch: WlTouch, serial: int, time: int, id: int) -> int: - # touch = wl_touch.user_data - # touch_paint(touch, x, y, id) return 0 @@ -108,23 +109,21 @@ def handle_touch_motion( wl_touch: WlTouch, time: int, id: int, x: float, y: float ) -> int: print(wl_touch, time, id, x, y) - # touch = wl_touch.user_data - # touch_paint(touch, x, y, id) return 0 def handle_seat_capabilities(wl_seat: WlSeatProxy, capabilities: int) -> int: print("capabilities") - seat = wl_seat.user_data + seat: SeatDictT = wl_seat.user_data touch = seat["touch"] if (capabilities & WlSeat.capability.touch.value) and seat["wl_touch"] is None: - seat = {} - seat["wl_touch"] = wl_seat.get_touch() - seat["wl_touch"].user_data = touch - seat["wl_touch"].dispatcher["up"] = handle_touch_up - seat["wl_touch"].dispatcher["down"] = handle_touch_down - seat["wl_touch"].dispatcher["motion"] = handle_touch_motion + wl_touch = wl_seat.get_touch() + wl_touch.user_data = touch + wl_touch.dispatcher["up"] = handle_touch_up + wl_touch.dispatcher["down"] = handle_touch_down + wl_touch.dispatcher["motion"] = handle_touch_motion + seat["wl_touch"] = wl_touch elif not (capabilities & WlSeat.capability.touch.value) and seat["wl_touch"]: seat["wl_touch"].destroy() seat["wl_touch"] = None @@ -133,7 +132,7 @@ def handle_seat_capabilities(wl_seat: WlSeatProxy, capabilities: int) -> int: def handle_shm_format(wl_shm: WlShmProxy, fmt: int) -> int: print("format") - touch = wl_shm.user_data + touch: TouchDictT = wl_shm.user_data if fmt == WlShm.format.argb8888.value: touch["has_argb"] = True @@ -153,27 +152,23 @@ def handle_registry_global( ) -> int: print("global", id_num, iface_name) - touch = wl_registry.user_data + touch: TouchDictT = wl_registry.user_data if iface_name == "wl_compositor": touch["compositor"] = wl_registry.bind(id_num, WlCompositor, version) elif iface_name == "wl_seat": - seat = {} + seat: SeatDictT = {} seat["touch"] = touch seat["wl_touch"] = None - - wl_seat = wl_registry.bind(id_num, WlSeat, version) - wl_seat.dispatcher["capabilities"] = handle_seat_capabilities - wl_seat.user_data = seat - seat["seat"] = wl_seat + seat["seat"] = wl_registry.bind(id_num, WlSeat, version) + seat["seat"].dispatcher["capabilities"] = handle_seat_capabilities + seat["seat"].user_data = seat elif iface_name == "wl_shell": touch["shell"] = wl_registry.bind(id_num, WlShell, version) elif iface_name == "wl_shm": touch["has_argb"] = False - - shm = wl_registry.bind(id_num, WlShm, version) - shm.user_data = touch - shm.dispatcher["format"] = handle_shm_format - touch["shm"] = shm + touch["shm"] = wl_registry.bind(id_num, WlShm, version) + touch["shm"].user_data = touch + touch["shm"].dispatcher["format"] = handle_shm_format return 1 diff --git a/example/surface.py b/example/surface.py index 8899743..bb2fffa 100644 --- a/example/surface.py +++ b/example/surface.py @@ -87,7 +87,7 @@ def wm_base_ping_handler(xdg_wm_base: XdgWmBaseProxy, serial: int) -> None: def registry_global_handler( registry: WlRegistryProxy, id_: int, interface: str, version: int ) -> None: - window = registry.user_data + window: Window = registry.user_data if interface == "wl_compositor": print("got compositor") window.compositor = registry.bind(id_, WlCompositor, version) @@ -99,12 +99,13 @@ def registry_global_handler( window.shm = registry.bind(id_, WlShm, version) window.shm.dispatcher["format"] = shm_format_handler elif interface == "xdg_wm_base": + print("got xdg_wm_base") window.wm_base = registry.bind(id_, XdgWmBase, version) window.wm_base.dispatcher["ping"] = wm_base_ping_handler def registry_global_remover(registry: WlRegistryProxy, id_: int) -> None: - print(f"got a registry losing event for {id}") + print(f"got a registry losing event for {id_}") def create_buffer(window: Window) -> WlBufferProxy: @@ -132,11 +133,12 @@ def create_window(window: Window) -> None: def redraw(callback: WlCallbackProxy, time: int, destroy_callback: bool = True) -> None: - window = callback.user_data + window: Window = callback.user_data if destroy_callback: callback._destroy() paint(window) + assert window.surface is not None window.surface.damage(0, 0, WIDTH, HEIGHT) callback = window.surface.frame() diff --git a/example/wlinfo.py b/example/wlinfo.py index 477d734..5f04ab6 100644 --- a/example/wlinfo.py +++ b/example/wlinfo.py @@ -23,7 +23,12 @@ if TYPE_CHECKING: from collections.abc import Iterator - from pywayland.protocol.wayland import WlRegistryProxy + from pywayland.protocol.wayland import ( + WlOutputProxy, + WlRegistryProxy, + WlSeatProxy, + WlShmProxy, + ) FieldList = list[tuple[str, int, int] | str] @@ -90,7 +95,7 @@ def append(s: str) -> None: return info.seat.append(f" {s}") _add_interface_info(info.seat, interface=interface, version=version, name=id_num) - seat = registry.bind(id_num, WlSeat, version) + seat: WlSeatProxy = registry.bind(id_num, WlSeat, version) seat.dispatcher["name"] = handle_name seat.dispatcher["capabilities"] = handle_capabilities @@ -146,7 +151,7 @@ def append(s: str) -> None: return info.output.append(f" {s}") _add_interface_info(info.output, interface=interface, version=version, name=id_num) - output = registry.bind(id_num, WlOutput, version) + output: WlOutputProxy = registry.bind(id_num, WlOutput, version) output.dispatcher["name"] = handle_name output.dispatcher["description"] = handle_description output.dispatcher["geometry"] = handle_geometry @@ -166,7 +171,7 @@ def append(s: str) -> None: _add_interface_info(info.shm, interface=interface, version=version, name=id_num) append("formats:") - shm = registry.bind(id_num, WlShm, version) + shm: WlShmProxy = registry.bind(id_num, WlShm, version) shm.dispatcher["format"] = handle_format diff --git a/pywayland/scanner/argument.py b/pywayland/scanner/argument.py index f255fb1..21de213 100644 --- a/pywayland/scanner/argument.py +++ b/pywayland/scanner/argument.py @@ -119,12 +119,12 @@ def _annotation(self) -> str: if self.interface: base_annotation = self.interface_class else: - base_annotation = "Any" + base_annotation = "object" elif self.type == ArgumentType.NewId: if self.interface: base_annotation = self.interface_class else: - base_annotation = "Any" + base_annotation = "type[Interface]" elif self.type == ArgumentType.Array: base_annotation = "list" elif self.type == ArgumentType.FileDescriptor: diff --git a/pywayland/scanner/event.py b/pywayland/scanner/event.py index c6d9d5c..ba4adca 100644 --- a/pywayland/scanner/event.py +++ b/pywayland/scanner/event.py @@ -18,7 +18,7 @@ from collections.abc import Iterator from dataclasses import dataclass -from .argument import Argument, ArgumentType +from .argument import Argument from .description import Description from .method import Method from .printer import Printer @@ -75,13 +75,3 @@ def output_body(self, printer: Printer, opcode: int) -> None: @property def return_type(self) -> str: return "None" - - @property - def needs_any(self) -> bool: - for arg in self.arg: - if ( - arg.type in (ArgumentType.Object, ArgumentType.NewId) - and not arg.interface - ): - return True - return False diff --git a/pywayland/scanner/interface.py b/pywayland/scanner/interface.py index 997a7ea..9cd74e0 100644 --- a/pywayland/scanner/interface.py +++ b/pywayland/scanner/interface.py @@ -75,13 +75,9 @@ def resource_class_name(self) -> str: def global_class_name(self) -> str: return f"{self.class_name}Global" - @property - def needs_t_type(self) -> bool: - return any(req.new_id and not req.new_id.interface for req in self.request) - @property def needs_any_type(self) -> bool: - return any(req.needs_any for req in self.request + self.event) + return any(req.return_type == "Any" for req in self.request) @property def needs_argument_type(self) -> bool: diff --git a/pywayland/scanner/protocol.py b/pywayland/scanner/protocol.py index 8772e67..f4226ff 100644 --- a/pywayland/scanner/protocol.py +++ b/pywayland/scanner/protocol.py @@ -93,16 +93,9 @@ def output(self, output_dir: str, all_imports: dict[str, str]) -> None: printer() printer("import enum") - typing_imports = [] if any(iface.needs_any_type for iface in self.interface): - typing_imports.extend(["Any"]) - - if any(iface.needs_t_type for iface in self.interface): - typing_imports.extend(["TypeVar"]) - - if typing_imports: printer() - printer(f"from typing import {', '.join(sorted(typing_imports))}") + printer("from typing import Any") pywayland_imports = ["Interface", "Global", "Proxy", "Resource"] if any(iface.needs_argument_type for iface in self.interface): @@ -121,10 +114,6 @@ def output(self, output_dir: str, all_imports: dict[str, str]) -> None: for module, import_ in sorted(interface_imports): printer(f"from {module} import {import_}") - if "TypeVar" in typing_imports: - printer() - printer('T = TypeVar("T", bound=Interface)') - with open(protocol_path, "wb") as f: printer.write(f) printer.clear() diff --git a/pywayland/scanner/request.py b/pywayland/scanner/request.py index 1111ef5..13f6536 100644 --- a/pywayland/scanner/request.py +++ b/pywayland/scanner/request.py @@ -78,7 +78,7 @@ def method_args(self) -> Iterable[str]: continue # A `new_id` with no interface, c.f. wl_registry_bind # Need a string (interface name) and int (interface version) - yield f"{NO_IFACE}: type[T]" + yield f"{NO_IFACE}: type[Interface]" yield f"{NO_IFACE_VERSION}: int" elif arg.type == ArgumentType.Object and arg.interface: # Method args for a known interface return the proxy class of the interface @@ -156,12 +156,7 @@ def return_type(self) -> str: if self.new_id.interface: return f"{self.new_id.interface_class}Proxy" else: - return "Proxy[T]" + # If interface is unknown, the return type is a proxy of any type, + # which is not a valid type hint. So we return "Any" instead. + return "Any" return "None" - - @property - def needs_any(self) -> bool: - for arg in self.arg: - if arg.type == ArgumentType.Object and not arg.interface: - return True - return False diff --git a/test/scanner_files/scanner_test_v1.py b/test/scanner_files/scanner_test_v1.py index 53017b0..6a14dbb 100644 --- a/test/scanner_files/scanner_test_v1.py +++ b/test/scanner_files/scanner_test_v1.py @@ -18,12 +18,10 @@ import enum -from typing import TypeVar +from typing import Any from pywayland.protocol_core import Argument, ArgumentType, Global, Interface, Proxy, Resource -T = TypeVar("T", bound=Interface) - class WlCore(Interface): """Interface object @@ -449,7 +447,7 @@ def versioned(self) -> None: Argument(ArgumentType.Uint), Argument(ArgumentType.NewId), ) - def new_id_no_interface(self, name: int, interface: type[T], version: int) -> Proxy[T]: + def new_id_no_interface(self, name: int, interface: type[Interface], version: int) -> Any: """Create a new id, but with no interface A method with an argument for a new_id, but with no corresponding From 23ff1016cd16b02214696a0d0aa794c0e600eb3c Mon Sep 17 00:00:00 2001 From: RFCreate <107062289+RFCreate@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:09:20 -0600 Subject: [PATCH 26/26] feat(stubs): type check ffi closer to typeshed --- pywayland/_ffi/ffi.pyi | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/pywayland/_ffi/ffi.pyi b/pywayland/_ffi/ffi.pyi index 15b88ea..c162977 100644 --- a/pywayland/_ffi/ffi.pyi +++ b/pywayland/_ffi/ffi.pyi @@ -1,5 +1,5 @@ from collections.abc import Callable -from typing import Any, Self, TypeVar, overload +from typing import Any, ParamSpec, Self, TypeVar, overload class CData: def __getitem__(self, idx: int) -> Self: ... @@ -7,6 +7,8 @@ class CData: # built-in cdata types class CharCData(CData): ... + +# extern functions class DispatcherFunc: ... class ResourceDestroyFunc: ... class EventLoopFdFunc: ... @@ -89,27 +91,27 @@ class WlSignal(CData): listener_list: WlList # special types -_FuncType = Callable[..., Any] -_F = TypeVar("_F", bound=_FuncType) +P = ParamSpec("P") +R = TypeVar("R") _CDataT = TypeVar("_CDataT", bound=CData) _CDataO = TypeVar("_CDataO", bound=CData) # Any type of CData NULL: Any +def new(cdecl: str, init: Any = None) -> _CDataT: ... # type: ignore [type-var] @overload -def new(cdecl: str) -> _CDataT: ... # type: ignore [type-var] -@overload -def new(cdecl: str, init: Any) -> _CDataT: ... # type: ignore [type-var] def gc( - cdata: _CDataT, destructor: Callable[[_CDataT], None] | None, size: int = 0 + cdata: _CDataT, destructor: Callable[[_CDataT], Any], size: int = 0 ) -> _CDataT: ... +@overload +def gc(cdata: _CDataT, destructor: None, size: int = 0) -> None: ... def buffer(cdata: _CDataT, size: int = -1) -> bytearray: ... -def string(cdata: CharCData) -> bytes: ... -def release(cdata: _CDataT) -> None: ... -def def_extern() -> Callable[[_F], _F]: ... -def new_handle(self: Any) -> _CDataT: ... # type: ignore [type-var] -def from_handle(cdata: _CDataT) -> Any: ... -def cast(new_type: str, cdata: _CDataT) -> _CDataO: ... # type: ignore [type-var] -def addressof(cdata: _CDataT) -> _CDataT: ... -def offsetof(cdecl: str, offset: Any) -> int: ... +def string(cdata: CharCData, maxlen: int = -1) -> bytes: ... +def release(x: _CDataT) -> None: ... +def def_extern() -> Callable[[Callable[P, R]], Callable[P, R]]: ... +def new_handle(x: Any) -> _CDataT: ... # type: ignore [type-var] +def from_handle(x: _CDataT) -> Any: ... +def cast(cdecl: str, source: _CDataT) -> _CDataO: ... # type: ignore [type-var] +def addressof(cdata: _CDataT, *fields_or_indexes: str | int) -> _CDataT: ... +def offsetof(cdecl: str, *fields_or_indexes: str | int) -> int: ...