diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c066aff..d491052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,19 +3,16 @@ on: pull_request: push: workflow_dispatch: - schedule: - # Run at 17:29 every Saturday - - cron: '29 17 * * 6' jobs: build-wayland: name: Build Wayland libraries - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 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: | @@ -23,7 +20,7 @@ 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 \ ninja-build @@ -41,26 +38,25 @@ 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 - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wayland-${{ matrix.wayland-version }}_${{ matrix.wayland-protocols-version }} path: ~/wayland.tar.gz 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 @@ -72,18 +68,18 @@ 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 + - 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 @@ -91,12 +87,9 @@ jobs: 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 + pip install -e .[test] - name: Run unit tests run: | - pip install .[test] pytest --cov pywayland --cov-report term-missing - name: Push coverage run: coveralls --service=github @@ -107,31 +100,31 @@ 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 + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} 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" - 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 + - 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 @@ -139,61 +132,59 @@ jobs: 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 + pip install -e .[docs] - name: Build docs run: | - pip install .[docs] 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" - 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 + - 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 run: | echo "XDG_RUNTIME_DIR=/tmp" >> $GITHUB_ENV - - name: Install Python dependencies + - name: Generate protocol files + working-directory: pywayland run: | - pip install .[ci] - python pywayland/ffi_build.py - python -m pywayland.scanner --with-protocols + 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-24.04 + runs-on: ubuntu-26.04 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 + - 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 @@ -201,13 +192,9 @@ jobs: 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 + pip install build check-manifest twine - 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..ad90340 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,12 +8,12 @@ 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" - 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: | @@ -44,33 +44,32 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ env.python-version }} - - name: Install python dependencies + - name: Install meson run: | - pip install meson ninja + pip install meson - 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 - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wayland path: /wayland.tar.gz 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: @@ -79,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 @@ -91,23 +90,21 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ matrix.python-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - 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 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 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: @@ -117,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 @@ -132,23 +129,21 @@ 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: | - python --version - python -m pip install auditwheel build + run: pip install auditwheel build - name: Build wheels run: | 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 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 @@ -162,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 @@ -176,16 +171,19 @@ 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-24.04 + runs-on: ubuntu-26.04 container: quay.io/pypa/manylinux_2_34_x86_64 needs: build-wayland env: 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 @@ -197,32 +195,30 @@ jobs: shell: bash env: PYTHON_VERSION: ${{ env.python-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install Python dependencies - run: | - python --version - python -m pip install build + 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 upload-wheel: name: Upload wheels - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 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/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 849122d..ae1c4ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,13 @@ 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: [] files: "^(pywayland|example)\/.*" diff --git a/MANIFEST.in b/MANIFEST.in index ad1f662..bed3559 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include *.txt +include *.py include *.yaml recursive-include pywayland *.pyi recursive-include test *.py 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 new file mode 100644 index 0000000..5c4d9c9 --- /dev/null +++ b/build_backend.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import subprocess +import sys +from collections.abc import Callable +from functools import wraps +from pathlib import Path +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[P, R]) -> Callable[P, R]: + @wraps(fn) + 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( + [sys.executable, "-m", "scanner", "--with-protocols"], + check=True, + cwd=repo_root / "pywayland", + ) + return fn(*args, **kwargs) + + return wrapper + + +def run_ffi_build(fn: Callable[P, R]) -> Callable[P, R]: + @wraps(fn) + 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( + [sys.executable, "pywayland/ffi_build.py"], check=True, cwd=repo_root + ) + Distribution.has_ext_modules = lambda self: True + return fn(*args, **kwargs) + + 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) + + +@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 d6be320..5dec841 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,34 @@ 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 . -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). +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`. 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" 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 8f2949a..bb2fffa 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}") @@ -88,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) @@ -100,15 +99,16 @@ 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 | None: +def create_buffer(window: Window) -> WlBufferProxy: assert window.shm is not None stride = WIDTH * 4 size = stride * HEIGHT @@ -133,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/pyproject.toml b/pyproject.toml index 7fe02d0..010b660 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" @@ -40,22 +41,17 @@ 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", - "coverage>=6.5.0", - "coveralls>=3.3.1", + "coverage>=7.15.0", + "coveralls>=4.1.0", + "setuptools>=77.0.0", ] docs = [ "sphinx", "sphinx_rtd_theme", ] -packaging = [ - "build", - "check-manifest", - "twine", -] [project.scripts] pywayland-scanner = "pywayland.scanner.__main__:main" @@ -67,7 +63,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__"} @@ -89,7 +85,7 @@ ignore = [ ".coveragerc", "doc/**", "example/**", - "pywayland/protocol/*.py", + "pywayland/protocol/**", ] [tool.mypy] @@ -114,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/_ffi/ffi.pyi b/pywayland/_ffi/ffi.pyi index 848976a..c162977 100644 --- a/pywayland/_ffi/ffi.pyi +++ b/pywayland/_ffi/ffi.pyi @@ -1,101 +1,117 @@ 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: ... 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): +# extern functions +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 WlGlobal(CData): + pass -class WlInterfaceCData(CData): +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 WlObjectCData(CData): ... -class WlProxyCData(CData): ... -class WlQueueCData(CData): ... -class WlResourceCData(CData): ... +class WlObject(CData): + pass -class WlSignalCData(CData): - listener_list: WlListCData +class WlProxy(CData): + pass + +class WlQueue(CData): + pass + +class WlResource(CData): + pass + +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, misc] -@overload -def new(cdecl: str, init: Any) -> _CDataT: ... # type: ignore [type-var, misc] def gc( - cdata: _CDataT, destructor: None | Callable[[_CDataT], 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, misc] -def from_handle(cdata: _CDataT) -> Any: ... -def cast(new_type: str, cdata: _CDataT) -> _CDataO: ... # type: ignore [type-var, misc] -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: ... diff --git a/pywayland/_ffi/lib.pyi b/pywayland/_ffi/lib.pyi index 2a1b9bb..f8e3404 100644 --- a/pywayland/_ffi/lib.pyi +++ b/pywayland/_ffi/lib.pyi @@ -1,203 +1,205 @@ -from typing import Any - 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: ... -# 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: ... +############################################################################### +# wayland-client-core.h +############################################################################### -# Proxy functionality +# wl_event_queue methods +def wl_event_queue_destroy(queue: WlQueue) -> None: ... + +# 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, -) -> Any: ... -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 6021e35..e160015 100644 --- a/pywayland/ffi_build.py +++ b/pywayland/ffi_build.py @@ -12,11 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path + from cffi import FFI ############################################################################### # wayland-version.h ############################################################################### + CDEF = """ #define WAYLAND_VERSION_MAJOR ... #define WAYLAND_VERSION_MINOR ... @@ -26,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); @@ -34,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; @@ -49,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 */ @@ -61,7 +73,7 @@ }; """ -# wl_array methods +# wl_array struct CDEF += """ struct wl_array { size_t size; @@ -80,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 *, @@ -93,8 +102,9 @@ """ ############################################################################### -# wayland-client.h +# wayland-client-core.h ############################################################################### + # wl_eventqueue methods CDEF += """ void wl_event_queue_destroy(struct wl_event_queue *queue); @@ -104,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 @@ -126,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 = ..., @@ -165,6 +171,7 @@ }; """ +# wl_signal methods CDEF += """ struct wl_signal { struct wl_list listener_list; @@ -175,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); @@ -200,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 @@ -226,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); """ @@ -249,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); """ @@ -273,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, @@ -287,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); @@ -448,5 +441,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() 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/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..a057f8b 100644 --- a/pywayland/protocol_core/interface.py +++ b/pywayland/protocol_core/interface.py @@ -23,14 +23,17 @@ 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 -weakkeydict: WeakKeyDictionary[ffi.WlInterfaceCData, tuple[object, ...]] = ( + P = ParamSpec("P") + R = TypeVar("R") + +weakkeydict: WeakKeyDictionary[ffi.WlInterface, tuple[object, ...]] = ( WeakKeyDictionary() ) @@ -46,7 +49,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,18 +63,18 @@ 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.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 @@ -123,18 +126,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 +137,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..e93d4e1 100644 --- a/pywayland/protocol_core/message.py +++ b/pywayland/protocol_core/message.py @@ -24,11 +24,14 @@ 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 -weakkeydict: WeakKeyDictionary[ffi.WlArgumentCData, tuple[ffi.CData, ...]] = ( +weakkeydict: WeakKeyDictionary[ffi.WlArgument, tuple[ffi.CData, ...]] = ( WeakKeyDictionary() ) @@ -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: @@ -69,12 +72,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 +90,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 +105,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 +118,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 +178,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 +189,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 +225,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 +236,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..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") @@ -42,7 +41,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 +49,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,19 +116,19 @@ 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( - 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 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..7dc1e98 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,10 +66,9 @@ 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) self._handle: ffi.CData = ffi.new_handle(self) lib.wl_resource_set_dispatcher( @@ -89,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 @@ -106,7 +108,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/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/method.py b/pywayland/scanner/method.py index 5e4021d..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,6 +72,14 @@ def imports( imports.append((import_path, import_class)) + 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 @property 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 5a621b0..13f6536 100644 --- a/pywayland/scanner/request.py +++ b/pywayland/scanner/request.py @@ -78,8 +78,14 @@ 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 + signature = f"{arg.name}: {arg.interface_class}Proxy" + if arg.allow_null: + signature += " | None" + yield signature else: yield arg.signature @@ -148,14 +154,9 @@ 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]" + # 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/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..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 @@ -81,9 +86,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 +132,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 +141,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""" @@ -151,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 @@ -182,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.WlEventSourceCData = 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( @@ -196,10 +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 @@ -219,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.WlEventSourceCData = 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( @@ -232,11 +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. @@ -256,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.WlEventSourceCData = 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( @@ -269,11 +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 @@ -282,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.WlEventSourceCData = 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( diff --git a/pywayland/server/listener.py b/pywayland/server/listener.py index c031391..73326d0 100644 --- a/pywayland/server/listener.py +++ b/pywayland/server/listener.py @@ -22,15 +22,18 @@ 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__) # 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) @@ -62,14 +65,14 @@ class Listener: :type function: callable """ - def __init__(self, function: Callable[..., Any]) -> None: - self._ptr: ffi.WlListenerCData | None + def __init__(self, function: Callable[P, R]) -> 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 +102,11 @@ class Signal: def __init__( self, *, - ptr: ffi.WlSignalCData | None = None, - data_wrapper: Callable[..., Any] | None = None, + ptr: ffi.WlSignal | None = None, + data_wrapper: Callable[P, R] | 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 d06af00..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: @@ -93,7 +96,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) - \ @@ -114,7 +117,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) \ diff --git a/setup.py b/setup.py deleted file mode 100644 index ba3ea48..0000000 --- a/setup.py +++ /dev/null @@ -1,60 +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. - -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"], -) diff --git a/test/scanner_files/scanner_test_v1.py b/test/scanner_files/scanner_test_v1.py index 914efde..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 @@ -236,7 +234,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 +261,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 +294,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 +343,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 +375,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 +389,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 +420,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: WlCoreProxy | None) -> WlEventsProxy: """Request that causes an import A request method that causes an imoprt of other interfaces, both as a @@ -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 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()