././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1677078815.4854946 libpulse-0.2/.coveragerc0000644000000000000000000000013614375430437012253 0ustar00[run] omit = */tests/* tools/* [report] exclude_lines = raise NotImplementedError ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716476301.9144926 libpulse-0.2/.gitignore0000644000000000000000000000012114623654616012117 0ustar00/dist/ /docs/build/ /.coverage .emacs.desktop* *.pyc libpulse.cpp libpulse.cache ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716641548.6308718 libpulse-0.2/.gitlab-ci.yml0000644000000000000000000000341214624357415012567 0ustar00# Built from template located at: # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml variables: # Change pip's cache directory to be inside the project directory. PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" default: cache: paths: - .cache/pip .pulseaudio: &pulseaudio - apt-get update -yq && apt-get install -yq pulseaudio - pulseaudio --verbose --daemonize=yes .pipewire: &pipewire - apt-get update -yq && apt-get install -yq pulseaudio-utils xvfb pipewire wireplumber pipewire-pulse - | export 'XDG_RUNTIME_DIR=/tmp' export 'DISPLAY=:0.0' Xvfb -screen $DISPLAY 1920x1080x24 & pipewire & pipewire-pulse & wireplumber & .unittest-scripts: &unittest-scripts - python -m unittest --verbose --failfast .coverage-scripts: &coverage-scripts - PIP_BREAK_SYSTEM_PACKAGES=1 python -m pip install coverage - python -m coverage run -m unittest - python -m coverage report --show-missing pipwire-tests: image: python:3.11 stage: test script: - *pipewire - sleep 1 - pactl info - *unittest-scripts py38: image: python:3.8 stage: test script: - *pulseaudio - *unittest-scripts py39: image: python:3.9 stage: test script: - *pulseaudio - *unittest-scripts py310: image: python:3.10 stage: test script: - *pulseaudio - *unittest-scripts py311: image: python:3.11 stage: test script: - *pulseaudio - *unittest-scripts py312: image: python:3.12 stage: test script: - *pulseaudio - *unittest-scripts - *coverage-scripts coverage: '/TOTAL.* (100\%|\d?\d\%)$/' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716640796.5455117 libpulse-0.2/.gitlab/issue_templates/Default.md0000644000000000000000000000073614624356035016572 0ustar00#### Bug report. ### Your environment. - Pulseaudio version: - Pipewire version: ### Steps to reproduce. ### Relevant logs or configuration. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1672656718.6084452 libpulse-0.2/.readthedocs.yaml0000644000000000000000000000056414354533517013366 0ustar00# .readthedocs.yaml # Read the Docs configuration file. # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details. version: 2 build: os: ubuntu-22.04 tools: python: "3.10" sphinx: configuration: docs/source/conf.py # Declare the Python requirements required to build your docs. python: install: - requirements: docs/requirements.txt ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1646844371.9250107 libpulse-0.2/LICENSE0000644000000000000000000000207114212154724011127 0ustar00The MIT License (MIT) Copyright (c) 2022 Xavier de Gaye Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717933276.452922 libpulse-0.2/README.rst0000644000000000000000000000327214631312334011613 0ustar00Asyncio interface to the Pulseaudio and Pipewire pulse library. `libpulse`_ is a Python project based on `asyncio`_, that uses `ctypes`_ to interface with the ``pulse`` library of the PulseAudio and PipeWire sound servers. The interface is meant to be complete. That is, all the constants, structures, plain functions and async functions are made available by importing the libpulse module of the libpulse package. Calling an async function is simple: .. code-block:: python import asyncio from libpulse.libpulse import LibPulse async def main(): async with LibPulse('my libpulse') as lib_pulse: server_info = await lib_pulse.pa_context_get_server_info() print(server_info) asyncio.run(main()) Another example processing sink-input events: .. code-block:: python import asyncio from libpulse.libpulse import LibPulse, PA_SUBSCRIPTION_MASK_SINK_INPUT async def main(): async with LibPulse('my libpulse') as lib_pulse: await lib_pulse.pa_context_subscribe( PA_SUBSCRIPTION_MASK_SINK_INPUT) iterator = lib_pulse.get_events() async for event in iterator: some_function_to_process_the_event(event) asyncio.run(main()) See the libpulse `documentation`_. Requirements ============ Python version 3.8 or more recent. Installation ============ Install ``libpulse`` with pip:: $ python -m pip install libpulse .. _libpulse: https://gitlab.com/xdegaye/libpulse .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _ctypes: https://docs.python.org/3/library/ctypes.html .. _documentation: https://libpulse.readthedocs.io/en/stable/ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1681392950.730833 libpulse-0.2/docs/Makefile0000644000000000000000000000117614416002467012521 0ustar00# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1672657372.279913 libpulse-0.2/docs/requirements.txt0000644000000000000000000000004614354534734014350 0ustar00sphinx >= 5.3 sphinx_rtd_theme >= 1.1 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1718355901.009433 libpulse-0.2/docs/source/_static/coverage.svg0000644000000000000000000000166114633003675016325 0ustar00 coverage coverage 90.00% 90.00% ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1716641754.305358 libpulse-0.2/docs/source/conf.py0000644000000000000000000000250014624357732013661 0ustar00# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information import os import sys sys.path.insert(0, os.path.abspath('../..')) from libpulse import __version__ project = 'libpulse' copyright = '2024, Xavier de Gaye' author = 'Xavier de Gaye' # The short X.Y version version = __version__ # The full version, including alpha/beta/rc tags release = __version__ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [] templates_path = ['_templates'] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717859591.004651 libpulse-0.2/docs/source/development.rst0000644000000000000000000001065314631072407015435 0ustar00.. _Development: Development =========== Design ------ .. _Callbacks: Callbacks """"""""" All the asyncio coroutine methods of LibPulse ultimately call either ``_pa_get()`` or ``_pa_context_get_list()``. Both coroutines follow the same schema: - define an embedded function as the callback - create the ctypes function pointer for this callback - create a future - call the ``pulse`` async function and wait on the future that will be set by the callback when done. - return the result *So what happens when two asyncio tasks running concurrently are both waiting on their own future for the completion of the same callback ?* The answer is that there is no concurrency issue: ``ctypes`` creates each ctypes function pointer for the callback by calling `PyCFuncPtr_new()`_ which in turn calls `_ctypes_alloc_callback()`_. This last function uses ``libffi`` to allocate a closure (quoting the ``libffi`` documentation [#]_: ``closures work by assembling a tiny function at runtime``). So each one of the two callbacks is allocated its own closure and gets a different function pointer. Requirements ------------ Development: * GNU ``gcc`` and `pyclibrary`_ are used to parse the libpulse headers and create the ``pulse_types``, ``pulse_enums``, ``pulse_structs`` and ``pulse_functions`` modules of the libpulse package. To re-create those modules using the current libpulse headers run [#]_:: $ python -m tools.libpulse_parser libpulse * `coverage`_ is used to get the test suite coverage. * `flit`_ is used to publish libpulse to PyPi and may be used to install libpulse locally. At the root of the libpulse git repository, use the following command to install libpulse locally:: $ flit install --symlink [--python path/to/python] This symlinks libpulse into site-packages rather than copying it, so that you can test changes. Documentation: * `Sphinx`_ [#]_. * `Read the Docs theme`_. Documentation ------------- To build locally the documentation follow these steps: - Fetch the GitLab test coverage badge:: $ curl -o docs/source/_static/coverage.svg "https://gitlab.com/xdegaye/libpulse/badges/master/coverage.svg?min_medium=85&min_acceptable=90&min_good=90" - Build the html documentation and the man pages:: $ make -C docs clean html Updating the development version -------------------------------- In order to update the version at the `latest documentation`_ during development, after a change in the functionality or in the features, run the following commands:: $ python -m tools.set_devpt_version_name $ make -C docs clean html $ git commit -m "Update version" $ git push Releasing --------- * Run the test suite from the root of the project [#]_:: $ python -m unittest --verbose --catch --failfast * Get the test suite coverage:: $ coverage run -m unittest $ coverage report -m * Update ``__version__`` in libpulse/__init__.py. * Update docs/source/history.rst if needed. * Build locally the documentation, see the previous section. * Commit the changes:: $ git commit -m 'Version 0.n' $ git push * Tag the release and push:: $ git tag -a v0.n -m 'Version 0.n' $ git push --tags * Publish the new version to PyPi:: $ flit publish .. _PyCFuncPtr_new(): https://github.com/python/cpython/blob/38a25e9560cf0ff0b80d9e90bce793ff24c6e027/Modules/_ctypes/_ctypes.c#L3826 .. _`_ctypes_alloc_callback()`: https://github.com/python/cpython/blob/38a25e9560cf0ff0b80d9e90bce793ff24c6e027/Modules/_ctypes/callbacks.c#L348 .. _Read the Docs theme: https://docs.readthedocs.io/en/stable/faq.html#i-want-to-use-the-read-the-docs-theme-locally .. _Sphinx: https://www.sphinx-doc.org/ .. _`coverage`: https://pypi.org/project/coverage/ .. _flit: https://pypi.org/project/flit/ .. _unittest command line options: https://docs.python.org/3/library/unittest.html#command-line-options .. _latest documentation: https://libpulse.readthedocs.io/en/latest/ .. _pyclibrary: https://pypi.org/project/pyclibrary/ .. rubric:: Footnotes .. [#] The ``libffi`` documentation is included in the ``libffi`` package as a texinfo document to be browsed by the ``ìnfo`` utility or by ``emacs``. .. [#] The shell commands in this section are all run from the root of the repository. .. [#] Required versions at ``docs/requirements.txt``. .. [#] See `unittest command line options`_. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1718263772.6817298 libpulse-0.2/docs/source/history.rst0000644000000000000000000000105114632517735014615 0ustar00Release history =============== Version 0.2 - Add the ``pa_context_subscribe.py`` example. - Add the ``pa_context_load_module.py`` example. - Unreference stream pointer upon exit in ``pa_stream_new.py`` example. - Raise ``LibPulseArgumentError`` when args do not match the signature. - Document callback concurrent access. - ``stream_success_methods`` require a ``pa_stream *`` ctypes pointer as first argument. Version 0.1 - Publish the project on PyPi. - Raise an exception upon instantiation of more than one LibPulse instance. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1717081054.8670363 libpulse-0.2/docs/source/index.rst0000644000000000000000000000054414626111737014224 0ustar00.. libpulse documentation master file. libpulse |version| ================== .. image:: _static/coverage.svg :alt: libpulse test coverage .. include:: ../../README.rst .. toctree:: :hidden: :maxdepth: 2 :caption: Table of Contents ReadMe interface development history Repository ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1718290939.19093 libpulse-0.2/docs/source/interface.rst0000644000000000000000000002354714632604773015071 0ustar00Interface ========= Overview -------- The ``libpulse_ctypes`` module uses the ``pulse_types``, ``pulse_enums``, ``pulse_structs`` and ``pulse_functions`` modules of the libpulse package to build: - The libpulse ctypes foreign functions corresponding to the ``pulse`` functions. - The subclasses of the ctypes Structure corresponding to the ``pulse`` structures. - The constants corresponding to the enums of the ``pulse`` library. These four modules are generated from the headers of the ``pulse`` library and may be re-generated using ``gcc`` and ``pyclibrary`` as explained in the :ref:`Development` section, although this is not necessary. The ABI of the ``pulse`` library is pretty much stable and using recent versions of Pulseaudio and Pipewire generates exactly the same modules. The following sections describe the ``libpulse`` module of the libpulse package that provides the whole ctypes interface to the library. Variables --------- The ``pulse`` enums constants are defined as variables in the ``libpulse`` module namespace. The ``PA_INVALID_INDEX`` variable is also defined there. ``CTX_STATES`` Dictionary mapping the values of the ``pa_context_state`` enums with their string representation. For example CTX_STATES[0] is ``'PA_CONTEXT_UNCONNECTED'``. ``ERROR_CODES`` Dictionary mapping the values of the ``pa_error_code`` enums with their string representation. For example ERROR_CODES[0] is ``'PA_OK'``. ``struct_ctypes`` Dictionary of all the ``pulse`` structures defined as subclasses of the ctypes Structure. Functions --------- The ``pulse`` functions that are not async functions [#]_ have their corresponding ctypes foreign functions defined in the ``libpulse`` module namespace. They may be called directly once the LibPulse class has been instantiated. Async functions are implemented as methods of the LibPulse instance. They are asyncio coroutines, see below. Structures ---------- PulseStructure class """""""""""""""""""" A PulseStructure is instantiated with: - A ctypes pointer that is dereferenced using its ``contents`` attibute. - The subclass of ctypes Structure that corresponds to the type of this pointer which is found in the ``struct_ctypes`` dict. A PulseStructure instance includes its nested structures and the structures that are referenced by a member of the structure that is a pointer to another structure (recursively). The attributes of the PulseStructure instance are the names of the members of the ``pulse`` structure. Using structures """""""""""""""" non async functions `examples/pa_stream_new.py`_ shows how to create instances of two structures and pass their pointers to ``pa_stream_new()`` using ``struct_ctypes``. The example shows also how to build a PulseStructure from a pointer returned by ``pa_stream_get_sample_spec()``. async functions When a callback sets a pointer to a ``pulse`` structure as one of its arguments, the memory referenced by this pointer is very short-lived. A PulseStructure is then instantiated to make a deep copy of the structure. The PulseStructure instance is returned by the asyncio coroutine that handles this callback. See below how to call a ``pulse`` async function. PropList class """""""""""""" When the member of a ``pulse`` structure is a pointer to a ``proplist``, the corresponding PulseStructure attribute is set to an instance of PropList class. The PropList class is a subclass of ``dict`` and the elements of the proplist can be accessed as the elements of a dictionary. PulseEvent class ---------------- An instance of PulseEvent is returned by the async iterator returned by the get_events() method of a LibPulse instance. See below :ref:`pa_context_subscribe()`. Its attributes are:: facility: str - name of the facility, for example 'sink'. index: int - index of the facility. type: str - type of event, 'new', 'change' or 'remove'. LibPulse class -------------- The LibPulse class is an asyncio context manager. To instantiate the LibPulse instance run:: async with LibPulse('some name') as lib_pulse: statements using the 'lib_pulse' LibPulse instance ... A LibPulse instance manages the connection to the ``pulse`` library. There is only one instance of this class per asyncio event loop, and therefore only one instance per thread. Attributes """""""""" ``c_context`` Required by non async functions prefixed with ``pa_context_`` as their first argument. Note that this first argument is excluded from the LibPulse async methods, see below. ``loop`` The asyncio loop. ``state`` The ``pulse`` context state. A tuple whose first element is one of the constants of the ``pa_context_state`` enum as a string, and the second and last one is one of the constants of the ``pa_error_code`` enum as a string. Methods """"""" The ``pulse`` async functions [1]_ are implemented as LibPulse methods that are asyncio coroutines except for five :ref:`Not implemented` methods. See `examples/pa_context_load_module.py`_. These methods are sorted in four lists according to their signature and the signature of their callbacks. These lists are the LibPulse class attributes: - context_methods - context_success_methods - context_list_methods - stream_success_methods Methods arguments """"""""""""""""" The type of the first argument of the ``pulse`` async functions whose name starts with ``pa_context`` is ``pa_context *``. This argument is **omitted** upon invocation of the corresponding LibPulse method (the Libpulse instance already knows it as one of its attributes named ``c_context``). The type of the penultimate argument of the ``pulse`` async functions is the type of the callback. This argument is **omitted** upon invocation of the corresponding LibPulse method as the Libpulse instance already knows this type from the signature of the function in the ``pulse_functions`` module and the callback is implemented as an embedded function in the method definition. The type of the last argument of the ``pulse`` async functions is ``void *`` and the argument is meant to be used to match the callback invocation with the ``pulse`` function that triggered it when the implementation is done in C language. This last argument is not needed and **omitted** upon invocation of the corresponding LibPulse method (the callback is implemented as an embedded function in the method definition, more details at :ref:`Callbacks`). For example pa_context_get_server_info() is invoked as: .. code-block:: python server_info = await lib_pulse.pa_context_get_server_info() Methods return value """""""""""""""""""" The ``context_methods`` return an empty list if the callback has no other argument than ``pa_context *c`` and ``void *userdata``, they return a list if the callback has set more than one of its arguments, otherwise they return the unique argument set by the callback. The ``context_success_methods`` and ``stream_success_methods`` return an ``int``, either PA_OPERATION_DONE or PA_OPERATION_CANCELLED. PA_OPERATION_CANCELLED occurs as a result of the context getting disconnected while the operation is pending. The ``context_list_methods`` return a list after the ``pulse`` library has invoked repeatedly the callback. The callback is invoked only once for methods whose name ends with ``by_name`` or ``by_index`` and the result returned by those coroutines in that case is this single element instead of the list. .. _pa_context_subscribe(): pa_context_subscribe() """""""""""""""""""""" ``pa_context_subscribe()`` is one of the LibPulse async method. This method may be invoked at any time to change the subscription masks currently set, even from within the ``async for`` loop that iterates over the reception of libpulse events. After this method has been invoked for the first time, call the ``get_events()`` method to get an async iterator that returns the successive libpulse events. For example: .. code-block:: python # Start the iteration on sink-input events. await lib_pulse.pa_context_subscribe(PA_SUBSCRIPTION_MASK_SINK_INPUT) iterator = lib_pulse.get_events() async for event in iterator: await handle_the_event(event) ``event`` is an instance of PulseEvent. See also `examples/pa_context_subscribe.py`_. .. _Not implemented: Not implemented """"""""""""""" The following ``pulse`` async functions are not implemented as a method of a LibPulse instance: pa_signal_new() and pa_signal_set_destroy(): Signals are handled by asyncio and the hook signal support built into pulse abstract main loop is not needed. In the following functions the callback has to be handled by the libpulse module user: - pa_context_rttime_new() - pa_stream_write() - pa_stream_write_ext_free() An example on how to implement those coroutines can be found in the LibPulse class implementation of context state monitoring: - ``__init__()`` sets the function pointer (and keeps a refence to it to prevent Python garbage collection) to a LibPulse staticmethod named ``context_state_callback()`` that will be called as the ``pulse`` callback. The staticmethod gets the LibPulse instance through a call to the get_instance() method. - Upon entering the LibPulse context manager, the ``_pa_context_connect()`` method sets this fonction pointer as the callback in the call to ``pa_context_set_state_callback()``. .. _examples/pa_stream_new.py: https://gitlab.com/xdegaye/libpulse/-/blob/master/examples/pa_stream_new.py?ref_type=heads#L1 .. _examples/pa_context_load_module.py: https://gitlab.com/xdegaye/libpulse/-/blob/master/examples/pa_context_load_module.py?ref_type=heads#L1 .. _examples/pa_context_subscribe.py: https://gitlab.com/xdegaye/libpulse/-/blob/master/examples/pa_context_subscribe.py?ref_type=heads#L1 .. rubric:: Footnotes .. [#] ``pulse`` async functions are those functions that have a callback as one of their arguments and that do not set the callback. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1718265550.993823 libpulse-0.2/examples/pa_context_load_module.py0000644000000000000000000000576614632523317017044 0ustar00"""Example using LibPulse async methods. The 'LoadModule' async context manager is used to ensure that the module is unloaded before exiting. A null-sink is created by loading the 'module-null-sink' module and different async methods are used to get the 'pa_sink_info' structure of the newly created null-sink. Note the restrictions in the naming of the null-sink and of its description (see the comments). """ import sys import asyncio from libpulse.libpulse import (LibPulse, PA_INVALID_INDEX, pa_context_load_module, pa_context_unload_module, pa_context_get_sink_info_by_name, pa_context_get_sink_info_by_index, pa_context_get_sink_info_list, ) # NOTE: Space characters are NOT ALLOWED in the sink name. SINK_NAME = 'my-null-sink' # NOTE: Space characters in the value of a property MUST be escaped with a # backslash. MODULE_ARG = (f'sink_name="{SINK_NAME}" ' r'sink_properties=device.description="my\ description"') class LoadModule: def __init__(self, lib_pulse, name, argument): self.lib_pulse = lib_pulse self.name = name self.argument = argument self.index = PA_INVALID_INDEX async def __aenter__(self): self.index = await self.lib_pulse.pa_context_load_module( self.name, self.argument) if self.index == PA_INVALID_INDEX: print(f'Error: cannot load module {self.name}', file=sys.stderr) sys.exit(1) return self async def __aexit__(self, exc_type, exc_value, traceback): if self.index != PA_INVALID_INDEX: await self.lib_pulse.pa_context_unload_module(self.index) async def main(): async with LibPulse('my libpulse') as lib_pulse: # Create a null sink. async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG): # Get the pa_sink_info structure by name. sink_info = (await lib_pulse.pa_context_get_sink_info_by_name(SINK_NAME)) # Get the pa_sink_info structure by index. index = sink_info.index print(f"sink '{sink_info.name}' at index {index}") sink_info = (await lib_pulse.pa_context_get_sink_info_by_index(index)) # 'proplist' is a dict. description = sink_info.proplist['device.description'] print(f"device.description: '{description}'") # Get the pa_sink_info structure as element of the list. sink_infos = await lib_pulse.pa_context_get_sink_info_list() for sink_info in sink_infos: if sink_info.index == index: print('sink_info:\n', sink_info) break else: assert False, 'Cannot find our null sink in the list !' if __name__ == '__main__': asyncio.run(main()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1718291224.8351173 libpulse-0.2/examples/pa_context_subscribe.py0000644000000000000000000000445614632605431016532 0ustar00"""Example processing pulse events. An asyncio task is started that processes all pulse events and put them into an asyncio queue. The main() function loads a null-sink module and processes the events received from the queue until it receives the event signaling the creation of the module. """ import asyncio from libpulse.libpulse import (LibPulse, PA_SUBSCRIPTION_MASK_ALL, pa_context_subscribe, PulseEvent, ) from pa_context_load_module import LoadModule, MODULE_ARG async def get_events(lib_pulse, evt_queue, evt_ready): try: await lib_pulse.pa_context_subscribe(PA_SUBSCRIPTION_MASK_ALL) iterator = lib_pulse.get_events() # Signal main() that we are ready and processing events. evt_ready.set_result(True) async for event in iterator: await evt_queue.put(event) # Upon receiving CancelledError, the iterator raises StopAsyncIteration # to end the iteration. print('get_events(): asyncio task has been cancelled by main().') except Exception as e: await evt_queue.put(e) async def main(): evt_queue = asyncio.Queue() async with LibPulse('my libpulse') as lib_pulse: evt_ready = lib_pulse.loop.create_future() evt_task = asyncio.create_task(get_events(lib_pulse, evt_queue, evt_ready)) # Wait for the task to be ready. await evt_ready # Load the 'module-null-sink' module and process all pulse events # until we receive the event signaling the creation of this module. async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG) as loaded_module: while True: event = await evt_queue.get() if isinstance(event, Exception): raise event assert isinstance(event, PulseEvent) print('event:', event.facility, event.type, event.index) if (event.facility == 'module' and event.type == 'new' and event.index == loaded_module.index): evt_task.cancel() print('Got the event triggered by loading the module.') break asyncio.run(main()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1718086413.4566782 libpulse-0.2/examples/pa_stream_new.py0000644000000000000000000000477414631765415015164 0ustar00"""Example using ctypes pulse structures. 1) Two structures are built from scratch using their ctypes types. 2) pa_stream_new() is called using pointers to these structures and returns an opaque pointer. 3) pa_stream_get_sample_spec() returns a ctypes pointer that is used to build a PulseStructure instance. The type of a PulseStructure instance is a mapping type and printing its content shows that it matches the content of the pa_sample_spec structure used to create the stream. Note: ----- pa_stream_get_sample_spec() is a plain function (not a coroutine method of the LibPulse instance) and the PulseStructure instantiation must be done manually. This is not needed for the methods of the LibPulse instance whose async functions return a structure or a list of structures. """ import sys import asyncio import ctypes as ct from libpulse.libpulse import (LibPulse, PulseStructure, struct_ctypes, pa_stream_new, pa_stream_unref, pa_stream_get_sample_spec, ) async def main(): async with LibPulse('my libpulse') as lib_pulse: # Build the pa_sample_spec structure. c_sample_spec = struct_ctypes['pa_sample_spec'](3, 44100, 2) # Build the pa_channel_map structure. channel_labels = [0] * 32 channel_labels[0] = 1 channel_labels[1] = 2 C_MAP = ct.c_int * 32 c_map = C_MAP(*channel_labels) c_channel_map = struct_ctypes['pa_channel_map'](2, c_map) # Create the stream. c_pa_stream = pa_stream_new(lib_pulse.c_context, b'some name', ct.byref(c_sample_spec), ct.byref(c_channel_map)) # From the ctypes documentation: "NULL pointers have a False # boolean value". if not bool(c_pa_stream): print('Error: cannot create a new stream', file=sys.stderr) sys.exit(1) try: # Get the pa_sample_spec structure as a PulseStructure instance. c_sample_spec = pa_stream_get_sample_spec(c_pa_stream) sample_spec = PulseStructure(c_sample_spec.contents, struct_ctypes['pa_sample_spec']) # Print the attributes of sample_spec. # This will print: # {'format': 3, 'rate': 44100, 'channels': 2} print(sample_spec.__dict__) finally: pa_stream_unref(c_pa_stream) asyncio.run(main()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1718355824.7361233 libpulse-0.2/libpulse/__init__.py0000644000000000000000000000050414633003561014050 0ustar00"""Asyncio interface to the Pulseaudio and Pipewire pulse library.""" import sys __version__ = 'v0.2' MIN_PYTHON_VERSION = (3, 8) _version = sys.version_info[:2] if _version < MIN_PYTHON_VERSION: print(f'error: the python version must be at least' f' {MIN_PYTHON_VERSION}', file=sys.stderr) sys.exit(1) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1718266917.8170524 libpulse-0.2/libpulse/libpulse.py0000644000000000000000000007037414632526046014153 0ustar00"""ctypes interface to the pulse library based on asyncio.""" import sys import asyncio import logging import re import pprint import ctypes as ct from functools import partialmethod from .libpulse_ctypes import PulseCTypes, PA_INVALID_INDEX from .mainloop import MainLoop, pulse_ctypes, callback_func_ptr from .pulse_functions import pulse_functions from .pulse_enums import pulse_enums struct_ctypes = pulse_ctypes.struct_ctypes logger = logging.getLogger('libpuls') def _add_pulse_to_namespace(): def add_obj(name, obj): assert getattr(module, name, None) is None, f'{name} is duplicated' setattr(module, name, obj) # Add the pulse constants and functions to the module namespace. module = sys.modules[__name__] for name in pulse_functions['signatures']: func = pulse_ctypes.get_prototype(name) add_obj(name, func) for enum, constants in pulse_enums.items(): for name, value in constants.items(): add_obj(name, value) _add_pulse_to_namespace() del _add_pulse_to_namespace # Map values to their name. ERROR_CODES = dict((eval(var), var) for var in globals() if var.startswith('PA_ERR_') or var == 'PA_OK') CTX_STATES = dict((eval(state), state) for state in ('PA_CONTEXT_UNCONNECTED', 'PA_CONTEXT_CONNECTING', 'PA_CONTEXT_AUTHORIZING', 'PA_CONTEXT_SETTING_NAME', 'PA_CONTEXT_READY', 'PA_CONTEXT_FAILED', 'PA_CONTEXT_TERMINATED')) def event_codes_to_names(): def build_events_dict(mask): for fac in globals(): if fac.startswith(prefix): val = eval(fac) if (val & mask) and val != mask: yield val, fac[prefix_len:].lower() prefix = 'PA_SUBSCRIPTION_EVENT_' prefix_len = len(prefix) facilities = {0 : 'sink'} facilities.update(build_events_dict(PA_SUBSCRIPTION_EVENT_FACILITY_MASK)) event_types = {0: 'new'} event_types.update(build_events_dict(PA_SUBSCRIPTION_EVENT_TYPE_MASK)) return facilities, event_types # Dictionaries mapping libpulse events values to their names. EVENT_FACILITIES, EVENT_TYPES = event_codes_to_names() def run_in_task(coro): """Decorator to wrap a coroutine in a task of AsyncioTasks instance.""" async def wrapper(*args, **kwargs): lib_pulse = LibPulse.get_instance() if lib_pulse is None: raise LibPulseClosedError try: return await lib_pulse.libpulse_tasks.create_task( coro(*args, **kwargs)) except asyncio.CancelledError: logger.error(f'{coro.__qualname__}() has been cancelled') raise return wrapper class LibPulseError(Exception): pass class LibPulseClosedError(LibPulseError): pass class LibPulseStateError(LibPulseError): pass class LibPulseOperationError(LibPulseError): pass class LibPulseClosedIteratorError(LibPulseError): pass class LibPulseInstanceExistsError(LibPulseError): pass class LibPulseArgumentError(LibPulseError): pass class EventIterator: """Pulse events asynchronous iterator.""" QUEUE_CLOSED = object() def __init__(self): self.event_queue = asyncio.Queue() self.closed = False # Public methods. def close(self): self.closed = True # Private methods. def abort(self): while True: try: self.event_queue.get_nowait() except asyncio.QueueEmpty: break self.put_nowait(self.QUEUE_CLOSED) def put_nowait(self, obj): if not self.closed: self.event_queue.put_nowait(obj) def __aiter__(self): return self async def __anext__(self): if self.closed: logger.info('Events Asynchronous Iterator is closed') raise StopAsyncIteration try: event = await self.event_queue.get() except asyncio.CancelledError: self.close() raise StopAsyncIteration if event is not self.QUEUE_CLOSED: return event self.close() raise LibPulseClosedIteratorError('Got QUEUE_CLOSED') class AsyncioTasks: def __init__(self): self._tasks = set() def create_task(self, coro): task = asyncio.create_task(coro) self._tasks.add(task) task.add_done_callback(lambda t: self._tasks.remove(t)) return task def __iter__(self): for t in self._tasks: yield t class PulseEvent: """A libpulse event. Use the event_facilities() and event_types() static methods to get all the values currently defined by the libpulse library for 'facility' and 'type'. They correspond to some of the variables defined in the pulse_enums module under the pa_subscription_event_type Enum. attributes: facility: str - name of the facility, for example 'sink'. index: int - index of the facility. type: str - type of event, normaly 'new', 'change' or 'remove'. """ def __init__(self, event_type, index): fac = event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK assert fac in EVENT_FACILITIES self.facility = EVENT_FACILITIES[fac] type = event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK assert type in EVENT_TYPES self.type = EVENT_TYPES[type] self.index = index @staticmethod def event_facilities(): return list(EVENT_FACILITIES.values()) @staticmethod def event_types(): return list(EVENT_TYPES.values()) class PropList(dict): """Dictionary of the elements of a proplist whose value is a string.""" def __init__(self, c_pa_proplist): super().__init__() null_ptr = ct.POINTER(ct.c_void_p)() null_ptr_ptr = ct.pointer(null_ptr) while True: key = pa_proplist_iterate(c_pa_proplist, null_ptr_ptr) if isinstance(key, bytes): val = pa_proplist_gets(c_pa_proplist, key) if bool(val): self[key.decode()] = val.decode() elif not bool(key): break class PulseStructure: """The representation of a ctypes Structure. When returned by a callback as a pointer to a structure, one must make a deep copy of the elements of the structure as they are only temporarily available. """ ignored_pointer_names = set() array_sizes = { 'pa_card_port_info': 'n_ports', 'pa_source_port_info': 'n_ports', 'pa_sink_port_info': 'n_ports', 'pa_card_profile_info': 'n_profiles', 'pa_card_profile_info2': 'n_profiles', 'pa_format_info': 'n_formats', } def __init__(self, c_struct, c_structure_type): for name, c_type in c_structure_type._fields_: fq_name = f'{c_structure_type.__name__}.{name}: {c_type.__name__}' if fq_name in self.ignored_pointer_names: continue try: c_struct_val = getattr(c_struct, name) except AttributeError: assert False, (f'{fq_name} not found while instantiating' f' a PulseStructure') if c_type in PulseCTypes.numeric_types.values(): setattr(self, name, c_struct_val) # A NULL pointer. elif not bool(c_struct_val): setattr(self, name, None) elif c_type is ct.c_char_p: setattr(self, name, c_struct_val.decode()) # A proplist pointer. elif (isinstance(c_struct_val, ct._Pointer) and c_struct_val._type_.__name__ == 'pa_proplist'): setattr(self, name, PropList(c_struct_val)) # An array. elif isinstance(c_struct_val, ct.Array): # All libpulse arrays have a numeric type. setattr(self, name, c_struct_val[:]) # An array of pointers. elif (isinstance(c_struct_val, ct._Pointer) and isinstance(c_struct_val.contents, ct._Pointer)): ptr = c_struct_val.contents ctype = ptr._type_ if ctype.__name__ in struct_ctypes: val = [] size_attr = self.array_sizes[ctype.__name__] array_size = getattr(self, size_attr) i = 0 while True: if not bool(ptr[i]): break if i == array_size: break val.append(PulseStructure(ptr[i], ctype)) i += 1 setattr(self, name, val) else: self.ignore_member(fq_name) # A pointer. elif isinstance(c_struct_val, ct._Pointer): if c_struct_val._type_.__name__ in struct_ctypes: setattr(self, name, PulseStructure(c_struct_val.contents, c_struct_val._type_)) else: self.ignore_member(fq_name) # A structure. else: if c_type.__name__ in struct_ctypes: setattr(self, name, PulseStructure(c_struct_val, c_type)) else: self.ignore_member(fq_name) def ignore_member(self, name): self.ignored_pointer_names.add(name) logger.debug(f"Ignoring '{name}' structure member") def __repr__(self): return pprint.pformat(self.__dict__, sort_dicts=False) class LibPulse: """Interface to libpulse library as an asynchronous context manager.""" ASYNCIO_LOOPS = dict() # {asyncio loop: LibPulse instance} # Function signature: (pa_operation *, # [pa_context *, [args...], cb_t, void *]) # Callback signature: (void, [pa_context *, [objs...], void *]) context_methods = ( 'pa_context_drain', 'pa_context_get_server_info', 'pa_context_load_module', 'pa_context_play_sample_with_proplist', 'pa_context_send_message_to_object', 'pa_context_stat', ) # Function signature: (pa_operation *, # [pa_context *, [args...], cb_t, void *]) # Callback signature: (void, [pa_context *, int, void *]) context_success_methods = ( 'pa_context_exit_daemon', 'pa_context_kill_client', 'pa_context_kill_sink_input', 'pa_context_kill_source_output', 'pa_context_move_sink_input_by_index', 'pa_context_move_sink_input_by_name', 'pa_context_move_source_output_by_index', 'pa_context_move_source_output_by_name', 'pa_context_play_sample', 'pa_context_proplist_remove', 'pa_context_proplist_update', 'pa_context_remove_sample', 'pa_context_set_card_profile_by_index', 'pa_context_set_card_profile_by_name', 'pa_context_set_default_sink', 'pa_context_set_default_source', 'pa_context_set_name', 'pa_context_set_port_latency_offset', 'pa_context_set_sink_input_mute', 'pa_context_set_sink_input_volume', 'pa_context_set_sink_mute_by_index', 'pa_context_set_sink_mute_by_name', 'pa_context_set_sink_port_by_index', 'pa_context_set_sink_port_by_name', 'pa_context_set_sink_volume_by_index', 'pa_context_set_sink_volume_by_name', 'pa_context_set_source_mute_by_index', 'pa_context_set_source_mute_by_name', 'pa_context_set_source_output_mute', 'pa_context_set_source_output_volume', 'pa_context_set_source_port_by_index', 'pa_context_set_source_port_by_name', 'pa_context_set_source_volume_by_index', 'pa_context_set_source_volume_by_name', 'pa_context_subscribe', 'pa_context_suspend_sink_by_index', 'pa_context_suspend_sink_by_name', 'pa_context_suspend_source_by_index', 'pa_context_suspend_source_by_name', 'pa_context_unload_module', ) # Function signature: (pa_operation *, [pa_context *, cb_t, void *]) # Callback signature: (void, [pa_context *, struct *, int, void *]) context_list_methods = ( 'pa_context_get_card_info_by_index', 'pa_context_get_card_info_by_name', 'pa_context_get_card_info_list', 'pa_context_get_client_info', 'pa_context_get_client_info_list', 'pa_context_get_module_info', 'pa_context_get_module_info_list', 'pa_context_get_sample_info_by_index', 'pa_context_get_sample_info_by_name', 'pa_context_get_sample_info_list', 'pa_context_get_sink_info_by_index', 'pa_context_get_sink_info_by_name', 'pa_context_get_sink_info_list', 'pa_context_get_sink_input_info', 'pa_context_get_sink_input_info_list', 'pa_context_get_source_info_by_index', 'pa_context_get_source_info_by_name', 'pa_context_get_source_info_list', 'pa_context_get_source_output_info', 'pa_context_get_source_output_info_list', ) # Function signature: (pa_operation *, # [pa_stream *, [args...], cb_t, void *]) # Callback signature: (void, [pa_stream *, int, void *]) stream_success_methods = ( 'pa_stream_cork', 'pa_stream_drain', 'pa_stream_flush', 'pa_stream_prebuf', 'pa_stream_proplist_remove', 'pa_stream_proplist_update', 'pa_stream_set_buffer_attr', 'pa_stream_set_name', 'pa_stream_trigger', 'pa_stream_update_sample_rate', 'pa_stream_update_timing_info', ) def __init__(self, name): """'name' is the name of the application.""" assert isinstance(name, str) self.loop = asyncio.get_running_loop() if self.loop in self.ASYNCIO_LOOPS: raise LibPulseInstanceExistsError self.c_context = pa_context_new(MainLoop.C_MAINLOOP_API, name.encode()) # From the ctypes documentation: "NULL pointers have a False # boolean value". if not bool(self.c_context): raise RuntimeError('Cannot get context from libpulse library') self.closed = False self.state = ('PA_CONTEXT_UNCONNECTED', 'PA_OK') self.main_task = asyncio.current_task(self.loop) self.libpulse_tasks = AsyncioTasks() self.state_notification = self.loop.create_future() self.event_iterator = None self.ASYNCIO_LOOPS[self.loop] = self LibPulse.add_async_methods() # Keep a reference to prevent garbage collection. self.c_context_state_callback = callback_func_ptr( 'pa_context_notify_cb_t', LibPulse.context_state_callback) self.c_context_subscribe_callback = callback_func_ptr( 'pa_context_subscribe_cb_t', LibPulse.context_subscribe_callback) # Initialisation. @staticmethod def add_async_methods(): # Register the partial methods. method_types = { 'context_methods': LibPulse._pa_context_get, 'context_success_methods': LibPulse._pa_context_op_success, 'context_list_methods': LibPulse._pa_context_get_list, 'stream_success_methods': LibPulse._pa_stream_op_success, } this_module = sys.modules[__name__] for method_type, libpulse_method in method_types.items(): func_names = getattr(LibPulse, method_type) for func_name in func_names: setattr(LibPulse, func_name, partialmethod(libpulse_method, func_name)) if hasattr(this_module, func_name): delattr(this_module, func_name) @staticmethod def get_instance(): loop = asyncio.get_running_loop() try: return LibPulse.ASYNCIO_LOOPS[loop] except KeyError: return None @staticmethod def context_state_callback(c_context, c_userdata): """Call back that monitors the connection state.""" lib_pulse = LibPulse.get_instance() if lib_pulse is None: return st = pa_context_get_state(c_context) st = CTX_STATES[st] if st in ('PA_CONTEXT_READY', 'PA_CONTEXT_FAILED', 'PA_CONTEXT_TERMINATED'): error = pa_context_errno(c_context) error = ERROR_CODES[error] state = (st, error) logger.info(f'LibPulse connection: {state}') state_notification = lib_pulse.state_notification if not state_notification.done(): state_notification.set_result(state) elif not lib_pulse.closed and st != 'PA_CONTEXT_READY': # A task is used here instead of calling directly abort() so # that pa_context_connect() has the time to handle a # previous PA_CONTEXT_READY state. asyncio.create_task(lib_pulse.abort(state)) else: logger.debug(f'LibPulse connection: {st}') @run_in_task async def _pa_context_connect(self): """Connect the context to the default server.""" pa_context_set_state_callback(self.c_context, self.c_context_state_callback, None) rc = pa_context_connect(self.c_context, None, PA_CONTEXT_NOAUTOSPAWN, None) logger.debug(f'pa_context_connect return code: {rc}') await self.state_notification self.state = self.state_notification.result() if self.state[0] != 'PA_CONTEXT_READY': raise LibPulseStateError(self.state) @staticmethod def context_subscribe_callback(c_context, event_type, index, c_userdata): """Call back to handle pulseaudio events.""" lib_pulse = LibPulse.get_instance() if lib_pulse is None: return if lib_pulse.event_iterator is not None: lib_pulse.event_iterator.put_nowait(PulseEvent(event_type, index)) # Libpulse async methods workers. @staticmethod def get_callback_data(func_name): # Get name and signature of the callback argument of 'func_name'. func_sig = pulse_functions['signatures'][func_name] args = func_sig[1] for arg in args: if arg in pulse_functions['callbacks']: callback_name = arg callback_sig = pulse_functions['callbacks'][arg] assert len(args) >= 3 and arg == args[-2] return callback_name, callback_sig def call_ctypes_func(self, func_name, operation_type, cb_func_ptr, *func_args): # Call the 'func_name' ctypes function. args = [] for arg in func_args: arg = arg.encode() if isinstance(arg, str) else arg args.append(arg) func_proto = pulse_ctypes.get_prototype(func_name) try: c_operation = func_proto(operation_type, *args, cb_func_ptr, None) except ct.ArgumentError as e: first_arg = ('c_context' if operation_type == self.c_context else 'pa_stream') raise LibPulseArgumentError( f"\nException reported by ctypes:\n" f" {e!r}" f"\nFunction arguments:\n" f" {func_name}{(first_arg, *args, cb_func_ptr, None)}\n" ) return c_operation @staticmethod async def handle_operation(c_operation, future, errmsg): # From the ctypes documentation: "NULL pointers have a False # boolean value". if not bool(c_operation): future.cancel() raise LibPulseOperationError(errmsg) try: await future except asyncio.CancelledError: pa_operation_cancel(c_operation) raise finally: pa_operation_unref(c_operation) async def _pa_get(self, func_name, operation_type, *func_args): """Call an asynchronous pulse function that does not return a list. 'func_args' is the sequence of the arguments of the function preceding the callback in the function signature. The last argument (i.e. 'userdata') is set to None by call_ctypes_func(). """ def callback_func(c_operation_type, *c_results): results = [] for arg, c_result in zip(callback_sig[1][1:-1], c_results[:-1]): arg_list = arg.split() if arg_list[-1] == '*': assert arg_list[0] in struct_ctypes struct_name = arg_list[0] if not bool(c_result): results.append(None) else: results.append(PulseStructure(c_result.contents, struct_ctypes[struct_name])) else: results.append(c_result) if not notification.done(): notification.set_result(results) callback_data = self.get_callback_data(func_name) assert callback_data, f'{func_name} signature without a callback' callback_name, callback_sig = callback_data notification = self.loop.create_future() errmsg = f'Error at {func_name}()' # Await on the future. cb_func_ptr = callback_func_ptr(callback_name, callback_func) c_operation = self.call_ctypes_func(func_name, operation_type, cb_func_ptr, *func_args) await LibPulse.handle_operation(c_operation, notification, errmsg) results = notification.result() for result in results: if result is None: raise LibPulseOperationError(errmsg) if len(results) == 1: return results[0] return results @run_in_task async def _pa_context_get_list(self, func_name, *func_args): """Call an asynchronous pulse function that returns a list. 'func_args' is the sequence of the arguments of the function preceding the callback in the function signature. The last argument (i.e. 'userdata') is set to None by call_ctypes_func(). """ def info_callback(c_context, c_info, eol, c_userdata): # From the ctypes documentation: "NULL pointers have a False # boolean value". if not bool(c_info): if not notification.done(): notification.set_result(eol) else: arg = callback_sig[1][1] arg_list = arg.split() assert arg_list[-1] == '*' assert arg_list[0] in struct_ctypes struct_name = arg_list[0] infos.append(PulseStructure(c_info.contents, struct_ctypes[struct_name])) callback_data = self.get_callback_data(func_name) assert callback_data, f'{func_name} signature without a callback' callback_name, callback_sig = callback_data infos = [] notification = self.loop.create_future() errmsg = f'Error at {func_name}()' # Await on the future. cb_func_ptr = callback_func_ptr(callback_name, info_callback) c_operation = self.call_ctypes_func(func_name, self.c_context, cb_func_ptr, *func_args) await LibPulse.handle_operation(c_operation, notification, errmsg) eol = notification.result() if eol < 0: raise LibPulseOperationError(errmsg) if func_name.endswith(('_by_name', '_by_index')): assert len(infos) == 1 return infos[0] return infos @run_in_task async def _pa_context_get(self, func_name, *func_args): return await self._pa_get(func_name, self.c_context, *func_args) @run_in_task async def _pa_context_op_success(self, func_name, *func_args): success = await self._pa_get(func_name, self.c_context, *func_args) if success == PA_OPERATION_CANCELLED: logger.debug(f'Got PA_OPERATION_CANCELLED for {func_name}') return success @run_in_task async def _pa_stream_op_success(self, func_name, stream, *func_args): success = await self._pa_get(func_name, stream, *func_args) if success == PA_OPERATION_CANCELLED: logger.debug(f'Got PA_OPERATION_CANCELLED for {func_name}') return success # Context manager. async def abort(self, state): # Cancelling the main task does close the LibPulse context manager. logger.error(f'The LibPulse instance has been aborted: {state}') self.main_task.cancel() def close(self): if self.closed: return self.closed = True try: for task in self.libpulse_tasks: task.cancel() if self.event_iterator is not None: self.event_iterator.abort() pa_context_set_state_callback(self.c_context, None, None) pa_context_set_subscribe_callback(self.c_context, None, None) if self.state[0] == 'PA_CONTEXT_READY': pa_context_disconnect(self.c_context) logger.info('Disconnected from libpulse context') finally: pa_context_unref(self.c_context) for loop, lib_pulse in list(self.ASYNCIO_LOOPS.items()): if lib_pulse is self: del self.ASYNCIO_LOOPS[loop] break else: logger.error('Cannot remove LibPulse instance upon closing') MainLoop.close() logger.debug('LibPulse instance closed') async def __aenter__(self): try: # Set up the two callbacks that live until this instance is # closed. self.main_task = asyncio.current_task(self.loop) await self._pa_context_connect() pa_context_set_subscribe_callback(self.c_context, self.c_context_subscribe_callback, None) return self except asyncio.CancelledError: self.close() if self.state[0] != 'PA_CONTEXT_READY': raise LibPulseStateError(self.state) except Exception: self.close() raise async def __aexit__(self, exc_type, exc_value, traceback): self.close() if exc_type is asyncio.CancelledError: if self.state[0] != 'PA_CONTEXT_READY': raise LibPulseStateError(self.state) # Public methods. def get_events(self): """Return an Asynchronous Iterator of libpulse events. The iterator is used to run an async for loop over the PulseEvent instances. The async for loop can be terminated by invoking the close() method of the iterator from within the loop or from another task. """ if self.closed: raise LibPulseOperationError('The LibPulse instance is closed') if self.event_iterator is not None and not self.event_iterator.closed: raise LibPulseError('Not allowed: the current Asynchronous' ' Iterator must be closed first') self.event_iterator = EventIterator() return self.event_iterator async def log_server_info(self): if self.state[0] != 'PA_CONTEXT_READY': raise LibPulseStateError(self.state) server_info = await self.pa_context_get_server_info() server_name = server_info.server_name if re.match(r'.*\d+\.\d', server_name): # Pipewire includes the server version in the server name. logger.info(f'Server: {server_name}') else: logger.info(f'Server: {server_name} {server_info.server_version}') version = pa_context_get_protocol_version(self.c_context) server_ver = pa_context_get_server_protocol_version(self.c_context) logger.debug(f'libpulse library/server versions: ' f'{version}/{server_ver}') # 'server' is the name of the socket libpulse is connected to. server = pa_context_get_server(self.c_context) logger.debug(f'{server_name} connected to {server.decode()}') ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1716476301.917826 libpulse-0.2/libpulse/libpulse_ctypes.py0000644000000000000000000002400614623654616015536 0ustar00"""All the ctypes of the pulse library.""" import sys import sysconfig import pprint import functools import ctypes as ct from ctypes.util import find_library from .pulse_types import pulse_types from .pulse_enums import pulse_enums from .pulse_structs import pulse_structs from .pulse_functions import pulse_functions # /usr/include/pulse/def.h: # #define PA_INVALID_INDEX ((uint32_t) -1) PA_INVALID_INDEX = ct.c_uint32(-1).value class PulseCTypesError(Exception): pass class PulseCTypesLibError(PulseCTypesError): pass class PulseCTypesNameError(PulseCTypesError): pass class PulseCTypesSignatureError(PulseCTypesError): pass class PulseCTypesCallbackError(PulseCTypesError): pass def _time_t(): """Return a ctypes type for time_t.""" # The size of 'time_t' depends on the platform. # 'SIZEOF_TIME_T' is computed by configure when building Python. sizeof_time_t = sysconfig.get_config_var('SIZEOF_TIME_T') types = (ct.c_longlong, ct.c_long, ct.c_int) sizes = [ct.sizeof(t) for t in types] assert sizeof_time_t in sizes, 'Cannot find a ctypes match for time_t.' return types[sizes.index(sizeof_time_t)] class timeval(ct.Structure): _fields_ = [ ('tv_sec', _time_t()), ('tv_usec', ct.c_long), ] class PulseCTypes: numeric_types = { 'int': ct.c_int, 'int64_t': ct.c_int64, 'unsigned': ct.c_uint, 'unsigned int': ct.c_uint, 'unsigned long':ct.c_ulong, 'uint8_t': ct.c_uint8, 'uint32_t': ct.c_uint32, 'uint64_t': ct.c_uint64, 'size_t': ct.c_size_t, 'float': ct.c_float, } standard_ctypes = { 'void': None, 'char *': ct.c_char_p, 'void *': ct.c_void_p, } standard_ctypes.update(numeric_types) def __init__(self): self.known_ctypes = {} self.struct_ctypes = {} self.cb_types_params = None path = find_library('pulse') if path is None: raise PulseCTypesLibError('Cannot find the pulse library') self.clib = ct.CDLL(path) self.update_known_ctypes() self.update_struct_ctypes() def update_known_ctypes(self): for item in pulse_types: types = pulse_types[item].split() length = len(types) if length == 1: self.known_ctypes[item] = self.get_ctype(types[0]) continue elif length == 2: if types[0] == 'enum': self.known_ctypes[item] = ct.c_int continue elif types[0] == 'struct' and item == types[1]: continue raise PulseCTypesError(f'Unknown type: {item}: {types}') def get_member_ctype(self, types): # ctype_struct_class() helper. if types[0] in pulse_structs or types[0] == 'timeval': # Recursive call. struct_nested = self.ctype_struct_class(types[0]) if types[-1] == '*': ctype = ct.POINTER(struct_nested) else: ctype = struct_nested else: ctype = self.get_ctype(' '.join(types)) return ctype def ctype_struct_class(self, struct_name): """Build a ctypes Structure class.""" if struct_name in self.struct_ctypes: return self.struct_ctypes[struct_name] _fields_ = [] for member in pulse_structs[struct_name]: member_name = member[0] member_type = member[1] types = member_type.split() if types[0] == 'struct': types = types[1:] # An array of ctypes. if len(types) == 3 and types[1] == '*' and types[2] != '*': ctype = self.get_member_ctype(types[:1]) try: _fields_.append((member_name, (ctype * int(types[2])))) except ValueError: assert False, f'{struct_name}.{member_name}' # A pointer to a pointer. elif ''.join(types).endswith('**'): ctype = self.get_member_ctype(types[:1]) _fields_.append((member_name, ct.POINTER(ct.POINTER(ctype)))) else: ctype = self.get_member_ctype(types) _fields_.append((member_name, ctype)) # Create the Structure subclass. struct_class = type(struct_name, (ct.Structure, ), {'_fields_': tuple(_fields_)}) if len(struct_class._fields_) != 0: self.struct_ctypes[struct_name] = struct_class self.struct_ctypes[struct_name + ' *'] = ct.POINTER(struct_class) return struct_class def update_struct_ctypes(self): self.struct_ctypes['timeval'] = timeval self.struct_ctypes['timeval *'] = ct.POINTER(timeval) for struct_name in pulse_structs: self.ctype_struct_class(struct_name) def get_ctype(self, type_name): if type_name.startswith('struct '): type_name = type_name[7:] if type_name in self.standard_ctypes: return self.standard_ctypes[type_name] elif type_name in self.known_ctypes: return self.known_ctypes[type_name] elif type_name in self.struct_ctypes: return self.struct_ctypes[type_name] elif type_name.endswith('*'): return ct.c_void_p else: raise PulseCTypesError(f'Cannot convert to ctypes: {type_name}') @functools.lru_cache def get_callback(self, callback_name): try: val = pulse_functions['callbacks'][callback_name] except KeyError: raise PulseCTypesCallbackError( f"'{callback_name}' not a known callback") types = [] restype = self.get_ctype(val[0]) # The return type. types.append(restype) for arg in val[1]: # The args types. try: argtype = self.get_ctype(arg) except PulseCTypesError: # Not a known data type. So it must be a function pointer # to a callback. Call get_callback() recursively. assert arg in pulse_functions['callbacks'], ( f'{callback_name}: {val} - Error: {arg}') argtype = self.get_callback(arg) types.append(argtype) if self.cb_types_params is not None: self.cb_types_params[callback_name] = types return ct.CFUNCTYPE(*types) @functools.lru_cache def get_prototype(self, func_name): """Set the restype and argtypes of a 'clib' function name.""" # Ctypes does not allow None as a NULL callback function pointer. # Overriding _CFuncPtr.from_param() allows it. This is a hack as # _CFuncPtr is private. # See https://ctypes-users.narkive.com/wmJNDPu2/optional-callbacks- # passing-null-for-function-pointers. def from_param(cls, obj): if obj is None: return None # Return a NULL pointer. return ct._CFuncPtr.from_param(obj) try: func = getattr(self.clib, func_name) except AttributeError: raise PulseCTypesNameError( f"'{func_name}' is not a function of the pulse library") try: val = pulse_functions['signatures'][func_name] except KeyError: raise PulseCTypesSignatureError( f"'{func_name}' not a known signature") func.restype = self.get_ctype(val[0]) # The return type. argtypes = [] for arg in val[1]: # The args types. if arg == 'void': break # A function signature nested in this signature. if isinstance(arg, tuple): types = [] restype = self.get_ctype(arg[0]) # The return type. types.append(restype) for argument in arg[1]: # The args types. ctype = self.get_ctype(argument) types.append(ctype) argtype = ct.CFUNCTYPE(*types) argtype.from_param = classmethod(from_param) argtypes.append(argtype) continue try: argtype = self.get_ctype(arg) except PulseCTypesError: # Not a known data type. So it must be a function pointer to a # callback. argtype = self.get_callback(arg) argtype.from_param = classmethod(from_param) argtypes.append(argtype) func.argtypes = argtypes return func def python_object(ctypes_object, cls=None): obj = ct.cast(ctypes_object, ct.POINTER(ct.py_object)).contents.value if cls is not None: assert type(obj) is cls return obj def print_types(sections): types = PulseCTypes() for section in sections: if section == 'types': pprint.pprint(types.known_ctypes) elif section == 'structs': # Structures excluding pointer types. pprint.pprint(dict((t.__name__, t._fields_) for t in types.struct_ctypes.values() if hasattr(t, '_fields_'))) elif section == 'callbacks': types.cb_types_params = {} for callback_name in pulse_functions['callbacks']: types.get_callback(callback_name) pprint.pprint(types.cb_types_params) elif section == 'prototypes': for func_name in pulse_functions['signatures']: func = types.get_prototype(func_name) pprint.pprint(f'{func.__name__}: ' f'({func.restype}, {func.argtypes})') print('get_callback: ', types.get_callback.cache_info()) else: print(f"Error: '{section}' is not a valid section name") def main(): print_types(sys.argv[1:]) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1716476301.917826 libpulse-0.2/libpulse/mainloop.py0000644000000000000000000002310214623654616014142 0ustar00"""An implementation of the libpulse Main Loop based on asyncio.""" import sys import asyncio import logging import time import gc import ctypes as ct from .libpulse_ctypes import PulseCTypes, python_object, PulseCTypesLibError from .pulse_enums import pulse_enums logger = logging.getLogger('libpuls') try: pulse_ctypes = PulseCTypes() except PulseCTypesLibError as e: sys.exit(f'{e!r}') pa_io_event_flags = pulse_enums['pa_io_event_flags'] def callback_func_ptr(name, python_function): callback = pulse_ctypes.get_callback(name) return callback(python_function) def build_mainloop_api(): """Build an instance of the libpulse Main Loop API.""" api = {'io_new': IoEvent.io_new, 'io_enable': IoEvent.io_enable, 'io_free': IoEvent.io_free, 'io_set_destroy': PulseEvent.set_destroy, 'time_new': TimeEvent.time_new, 'time_restart': TimeEvent.time_restart, 'time_free': TimeEvent.time_free, 'time_set_destroy': PulseEvent.set_destroy, 'defer_new': DeferEvent.defer_new, 'defer_enable': DeferEvent.defer_enable, 'defer_free': DeferEvent.defer_free, 'defer_set_destroy': PulseEvent.set_destroy, 'quit': quit } class Mainloop_api(ct.Structure): _fields_ = [('userdata', ct.c_void_p)] for name in api: _fields_.append((name, pulse_ctypes.get_callback(name))) # Instantiate Mainloop_api. args = [callback_func_ptr(name, api[name]) for name in api] return Mainloop_api(None, *args) # Main Loop API functions. # There is only one asyncio loop and therefore only one MainLoop instance per # thread. The MainLoop instance referenced by any callback of the API is # obtained by calling MainLoop.get_instance(). class PulseEvent: DEBUG = False HASHES = [] def __init__(self, c_callback, c_userdata): self.mainloop = MainLoop.get_instance() self.c_callback = c_callback self.c_userdata = c_userdata self.c_destroy_cb = None self.c_self = ct.cast(ct.pointer(ct.py_object(self)), ct.c_void_p) PulseEvent.HASHES.append(hash(self)) self.debug(f'__init__ {self.__class__.__name__}') def debug(self, msg): if PulseEvent.DEBUG: index = PulseEvent.HASHES.index(hash(self)) + 1 logger.debug(f'{index}: {msg}') def destroy(self): self.debug(f'destroy-0 {self.__class__.__name__}') if self.c_destroy_cb: self.debug(f'destroy-1 {self.__class__.__name__}') self.c_destroy_cb(self.mainloop.C_MAINLOOP_API, self.c_self, self.c_userdata) def __del__(self): try: index = PulseEvent.HASHES.index(hash(self)) except ValueError: return PulseEvent.HASHES.pop(index) @staticmethod def set_destroy(c_event, c_callback): event = python_object(c_event) event.debug(f'set_destroy {event.__class__.__name__}') event.c_destroy_cb = c_callback @classmethod def cleanup(cls, mainloop): for event in cls.EVENTS: event.debug(f'cleanup {cls.__name__}') if event.mainloop is mainloop: event.destroy() class IoEvent(PulseEvent): EVENTS = set() def __init__(self, fd, c_callback, c_userdata): super().__init__(c_callback, c_userdata) self.fd = fd self.flags = pa_io_event_flags['PA_IO_EVENT_NULL'] def read_callback(self): self.debug('read_callback IoEvent') self.c_callback(self.mainloop.C_MAINLOOP_API, self.c_self, self.fd, pa_io_event_flags['PA_IO_EVENT_INPUT'], self.c_userdata) def write_callback(self): self.debug('write_callback IoEvent') self.c_callback(self.mainloop.C_MAINLOOP_API, self.c_self, self.fd, pa_io_event_flags['PA_IO_EVENT_OUTPUT'], self.c_userdata) def enable(self, flags): self.debug(f'enable IoEvent: {flags}') aio_loop = self.mainloop.aio_loop pa_io_event_input = pa_io_event_flags['PA_IO_EVENT_INPUT'] pa_io_event_output = pa_io_event_flags['PA_IO_EVENT_OUTPUT'] if flags & pa_io_event_input and not (self.flags & pa_io_event_input): aio_loop.add_reader(self.fd, self.read_callback) if not (flags & pa_io_event_input) and self.flags & pa_io_event_input: aio_loop.remove_reader(self.fd) if (flags & pa_io_event_output and not (self.flags & pa_io_event_output)): aio_loop.add_writer(self.fd, self.write_callback) if (not (flags & pa_io_event_output) and self.flags & pa_io_event_output): aio_loop.remove_writer(self.fd) self.flags = flags @staticmethod def io_new(c_mainloop_api, fd, flags, c_callback, c_userdata): event = IoEvent(fd, c_callback, c_userdata) event.enable(flags) IoEvent.EVENTS.add(event) return event.c_self.value @staticmethod def io_enable(c_io_event, flags): event = python_object(c_io_event, cls=IoEvent) event.debug(f'io_enable {flags}') event.enable(flags) @staticmethod def io_free(c_io_event): event = python_object(c_io_event, cls=IoEvent) event.debug('io_free') event.enable(pa_io_event_flags['PA_IO_EVENT_NULL']) IoEvent.EVENTS.remove(event) class TimeEvent(PulseEvent): EVENTS = set() def __init__(self, c_callback, c_userdata): super().__init__(c_callback, c_userdata) self.timer_handle = None def restart(self, c_time): if self.timer_handle is not None: self.debug('restart TimeEvent - cancel') self.timer_handle.cancel() self.timer_handle = None if c_time is not None: timeval = c_time.contents delay = timeval.tv_sec + timeval.tv_usec / 10**6 - time.time() self.debug(f'restart TimeEvent - delay: {delay}') self.timer_handle = self.mainloop.aio_loop.call_later( delay, self.c_callback, self.mainloop.C_MAINLOOP_API, self.c_self, c_time, self.c_userdata) @staticmethod def time_new(c_mainloop_api, c_time, c_callback, c_userdata): event = TimeEvent(c_callback, c_userdata) event.restart(c_time) TimeEvent.EVENTS.add(event) return event.c_self.value @staticmethod def time_restart(c_time_event, c_time): event = python_object(c_time_event, cls=TimeEvent) event.debug('time_restart') event.restart(c_time) @staticmethod def time_free(c_time_event): event = python_object(c_time_event, cls=TimeEvent) event.debug('time_free') event.restart(None) TimeEvent.EVENTS.remove(event) class DeferEvent(PulseEvent): EVENTS = set() def __init__(self, c_callback, c_userdata): super().__init__(c_callback, c_userdata) self.handle = None def enable(self, enable): self.debug(f'enable DeferEvent: {enable}') if self.handle is None and enable: self.handle = self.mainloop.aio_loop.call_soon(self.callback) elif self.handle is not None and not enable: self.handle.cancel() self.handle = None self.enabled = True if enable else False def callback(self): self.debug('callback DeferEvent') self.handle = None self.c_callback(self.mainloop.C_MAINLOOP_API, self.c_self, self.c_userdata) if self.enabled: self.handle = self.mainloop.aio_loop.call_soon(self.callback) @staticmethod def defer_new(c_mainloop_api, c_callback, c_userdata): event = DeferEvent(c_callback, c_userdata) event.enable(True) DeferEvent.EVENTS.add(event) return event.c_self.value @staticmethod def defer_enable(c_defer_event, enable): event = python_object(c_defer_event, cls=DeferEvent) event.debug(f'defer_enable {enable}') event.enable(enable) @staticmethod def defer_free(c_defer_event): event = python_object(c_defer_event, cls=DeferEvent) event.debug('defer_free') event.enable(False) DeferEvent.EVENTS.remove(event) def quit(c_mainloop_api, retval): logger.debug(f'quit() of the mainloop API called with retval={retval}') # Keep a reference to the Python Main loop API so that it is not garbage # collected. _mainloop_api = build_mainloop_api() class MainLoop: """An implementation of the libpulse Main Loop based on asyncio.""" ASYNCIO_LOOPS = dict() # {asyncio loop: MainLoop instance} C_MAINLOOP_API = ct.cast(ct.pointer(_mainloop_api), ct.c_void_p) def __init__(self, aio_loop): assert aio_loop not in MainLoop.ASYNCIO_LOOPS, ( 'There is already a MainLoop instance on this asyncio loop') self.aio_loop = aio_loop MainLoop.ASYNCIO_LOOPS[aio_loop] = self @staticmethod def get_instance(): aio_loop = asyncio.get_running_loop() mloop = MainLoop.ASYNCIO_LOOPS.get(aio_loop) return mloop if mloop is not None else MainLoop(aio_loop) @staticmethod def close(): mloop = MainLoop.get_instance() for cls in (IoEvent, TimeEvent, DeferEvent): cls.cleanup(mloop) gc.collect() for aio_loop, loop in list(MainLoop.ASYNCIO_LOOPS.items()): if loop is mloop: del MainLoop.ASYNCIO_LOOPS[aio_loop] break else: assert False, 'Cannot remove MainLoop instance upon closing' logger.info('LibPulse main loop closed') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716643049.5916257 libpulse-0.2/libpulse/pulse_enums.py0000644000000000000000000003356014624362352014666 0ustar00"""File pulse_enums. This file has been generated by libpulse_parser.py - DO NOT MODIFY. """ pulse_enums = \ {'pa_autoload_type': {'PA_AUTOLOAD_SINK': 0, 'PA_AUTOLOAD_SOURCE': 1}, 'pa_channel_map_def': {'PA_CHANNEL_MAP_AIFF': 0, 'PA_CHANNEL_MAP_ALSA': 1, 'PA_CHANNEL_MAP_AUX': 2, 'PA_CHANNEL_MAP_DEFAULT': 0, 'PA_CHANNEL_MAP_DEF_MAX': 5, 'PA_CHANNEL_MAP_OSS': 4, 'PA_CHANNEL_MAP_WAVEEX': 3}, 'pa_channel_position': {'PA_CHANNEL_POSITION_AUX0': 12, 'PA_CHANNEL_POSITION_AUX1': 13, 'PA_CHANNEL_POSITION_AUX10': 22, 'PA_CHANNEL_POSITION_AUX11': 23, 'PA_CHANNEL_POSITION_AUX12': 24, 'PA_CHANNEL_POSITION_AUX13': 25, 'PA_CHANNEL_POSITION_AUX14': 26, 'PA_CHANNEL_POSITION_AUX15': 27, 'PA_CHANNEL_POSITION_AUX16': 28, 'PA_CHANNEL_POSITION_AUX17': 29, 'PA_CHANNEL_POSITION_AUX18': 30, 'PA_CHANNEL_POSITION_AUX19': 31, 'PA_CHANNEL_POSITION_AUX2': 14, 'PA_CHANNEL_POSITION_AUX20': 32, 'PA_CHANNEL_POSITION_AUX21': 33, 'PA_CHANNEL_POSITION_AUX22': 34, 'PA_CHANNEL_POSITION_AUX23': 35, 'PA_CHANNEL_POSITION_AUX24': 36, 'PA_CHANNEL_POSITION_AUX25': 37, 'PA_CHANNEL_POSITION_AUX26': 38, 'PA_CHANNEL_POSITION_AUX27': 39, 'PA_CHANNEL_POSITION_AUX28': 40, 'PA_CHANNEL_POSITION_AUX29': 41, 'PA_CHANNEL_POSITION_AUX3': 15, 'PA_CHANNEL_POSITION_AUX30': 42, 'PA_CHANNEL_POSITION_AUX31': 43, 'PA_CHANNEL_POSITION_AUX4': 16, 'PA_CHANNEL_POSITION_AUX5': 17, 'PA_CHANNEL_POSITION_AUX6': 18, 'PA_CHANNEL_POSITION_AUX7': 19, 'PA_CHANNEL_POSITION_AUX8': 20, 'PA_CHANNEL_POSITION_AUX9': 21, 'PA_CHANNEL_POSITION_CENTER': 3, 'PA_CHANNEL_POSITION_FRONT_CENTER': 3, 'PA_CHANNEL_POSITION_FRONT_LEFT': 1, 'PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER': 8, 'PA_CHANNEL_POSITION_FRONT_RIGHT': 2, 'PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER': 9, 'PA_CHANNEL_POSITION_INVALID': -1, 'PA_CHANNEL_POSITION_LEFT': 1, 'PA_CHANNEL_POSITION_LFE': 7, 'PA_CHANNEL_POSITION_MAX': 51, 'PA_CHANNEL_POSITION_MONO': 0, 'PA_CHANNEL_POSITION_REAR_CENTER': 4, 'PA_CHANNEL_POSITION_REAR_LEFT': 5, 'PA_CHANNEL_POSITION_REAR_RIGHT': 6, 'PA_CHANNEL_POSITION_RIGHT': 2, 'PA_CHANNEL_POSITION_SIDE_LEFT': 10, 'PA_CHANNEL_POSITION_SIDE_RIGHT': 11, 'PA_CHANNEL_POSITION_SUBWOOFER': 7, 'PA_CHANNEL_POSITION_TOP_CENTER': 44, 'PA_CHANNEL_POSITION_TOP_FRONT_CENTER': 47, 'PA_CHANNEL_POSITION_TOP_FRONT_LEFT': 45, 'PA_CHANNEL_POSITION_TOP_FRONT_RIGHT': 46, 'PA_CHANNEL_POSITION_TOP_REAR_CENTER': 50, 'PA_CHANNEL_POSITION_TOP_REAR_LEFT': 48, 'PA_CHANNEL_POSITION_TOP_REAR_RIGHT': 49}, 'pa_context_flags': {'PA_CONTEXT_NOAUTOSPAWN': 1, 'PA_CONTEXT_NOFAIL': 2, 'PA_CONTEXT_NOFLAGS': 0}, 'pa_context_state': {'PA_CONTEXT_AUTHORIZING': 2, 'PA_CONTEXT_CONNECTING': 1, 'PA_CONTEXT_FAILED': 5, 'PA_CONTEXT_READY': 4, 'PA_CONTEXT_SETTING_NAME': 3, 'PA_CONTEXT_TERMINATED': 6, 'PA_CONTEXT_UNCONNECTED': 0}, 'pa_device_port_type': {'PA_DEVICE_PORT_TYPE_ANALOG': 22, 'PA_DEVICE_PORT_TYPE_AUX': 1, 'PA_DEVICE_PORT_TYPE_BLUETOOTH': 15, 'PA_DEVICE_PORT_TYPE_CAR': 18, 'PA_DEVICE_PORT_TYPE_EARPIECE': 8, 'PA_DEVICE_PORT_TYPE_HANDSET': 7, 'PA_DEVICE_PORT_TYPE_HANDSFREE': 17, 'PA_DEVICE_PORT_TYPE_HDMI': 10, 'PA_DEVICE_PORT_TYPE_HEADPHONES': 3, 'PA_DEVICE_PORT_TYPE_HEADSET': 6, 'PA_DEVICE_PORT_TYPE_HIFI': 19, 'PA_DEVICE_PORT_TYPE_LINE': 4, 'PA_DEVICE_PORT_TYPE_MIC': 5, 'PA_DEVICE_PORT_TYPE_NETWORK': 21, 'PA_DEVICE_PORT_TYPE_PHONE': 20, 'PA_DEVICE_PORT_TYPE_PORTABLE': 16, 'PA_DEVICE_PORT_TYPE_RADIO': 12, 'PA_DEVICE_PORT_TYPE_SPDIF': 9, 'PA_DEVICE_PORT_TYPE_SPEAKER': 2, 'PA_DEVICE_PORT_TYPE_TV': 11, 'PA_DEVICE_PORT_TYPE_UNKNOWN': 0, 'PA_DEVICE_PORT_TYPE_USB': 14, 'PA_DEVICE_PORT_TYPE_VIDEO': 13}, 'pa_device_type': {'PA_DEVICE_TYPE_SINK': 0, 'PA_DEVICE_TYPE_SOURCE': 1}, 'pa_direction': {'PA_DIRECTION_INPUT': 2, 'PA_DIRECTION_OUTPUT': 1}, 'pa_encoding': {'PA_ENCODING_AC3_IEC61937': 2, 'PA_ENCODING_ANY': 0, 'PA_ENCODING_DTSHD_IEC61937': 8, 'PA_ENCODING_DTS_IEC61937': 5, 'PA_ENCODING_EAC3_IEC61937': 3, 'PA_ENCODING_INVALID': -1, 'PA_ENCODING_MAX': 9, 'PA_ENCODING_MPEG2_AAC_IEC61937': 6, 'PA_ENCODING_MPEG_IEC61937': 4, 'PA_ENCODING_PCM': 1, 'PA_ENCODING_TRUEHD_IEC61937': 7}, 'pa_error_code': {'PA_ERR_ACCESS': 1, 'PA_ERR_AUTHKEY': 9, 'PA_ERR_BADSTATE': 15, 'PA_ERR_BUSY': 26, 'PA_ERR_COMMAND': 2, 'PA_ERR_CONNECTIONREFUSED': 6, 'PA_ERR_CONNECTIONTERMINATED': 11, 'PA_ERR_EXIST': 4, 'PA_ERR_FORKED': 24, 'PA_ERR_INTERNAL': 10, 'PA_ERR_INVALID': 3, 'PA_ERR_INVALIDSERVER': 13, 'PA_ERR_IO': 25, 'PA_ERR_KILLED': 12, 'PA_ERR_MAX': 27, 'PA_ERR_MODINITFAILED': 14, 'PA_ERR_NODATA': 16, 'PA_ERR_NOENTITY': 5, 'PA_ERR_NOEXTENSION': 21, 'PA_ERR_NOTIMPLEMENTED': 23, 'PA_ERR_NOTSUPPORTED': 19, 'PA_ERR_OBSOLETE': 22, 'PA_ERR_PROTOCOL': 7, 'PA_ERR_TIMEOUT': 8, 'PA_ERR_TOOLARGE': 18, 'PA_ERR_UNKNOWN': 20, 'PA_ERR_VERSION': 17, 'PA_OK': 0}, 'pa_io_event_flags': {'PA_IO_EVENT_ERROR': 8, 'PA_IO_EVENT_HANGUP': 4, 'PA_IO_EVENT_INPUT': 1, 'PA_IO_EVENT_NULL': 0, 'PA_IO_EVENT_OUTPUT': 2}, 'pa_operation_state': {'PA_OPERATION_CANCELLED': 2, 'PA_OPERATION_DONE': 1, 'PA_OPERATION_RUNNING': 0}, 'pa_port_available': {'PA_PORT_AVAILABLE_NO': 1, 'PA_PORT_AVAILABLE_UNKNOWN': 0, 'PA_PORT_AVAILABLE_YES': 2}, 'pa_prop_type_t': {'PA_PROP_TYPE_INT': 0, 'PA_PROP_TYPE_INT_ARRAY': 2, 'PA_PROP_TYPE_INT_RANGE': 1, 'PA_PROP_TYPE_INVALID': -1, 'PA_PROP_TYPE_STRING': 3, 'PA_PROP_TYPE_STRING_ARRAY': 4}, 'pa_sample_format': {'PA_SAMPLE_ALAW': 1, 'PA_SAMPLE_FLOAT32BE': 6, 'PA_SAMPLE_FLOAT32LE': 5, 'PA_SAMPLE_INVALID': -1, 'PA_SAMPLE_MAX': 13, 'PA_SAMPLE_S16BE': 4, 'PA_SAMPLE_S16LE': 3, 'PA_SAMPLE_S24BE': 10, 'PA_SAMPLE_S24LE': 9, 'PA_SAMPLE_S24_32BE': 12, 'PA_SAMPLE_S24_32LE': 11, 'PA_SAMPLE_S32BE': 8, 'PA_SAMPLE_S32LE': 7, 'PA_SAMPLE_U8': 0, 'PA_SAMPLE_ULAW': 2}, 'pa_seek_mode': {'PA_SEEK_ABSOLUTE': 1, 'PA_SEEK_RELATIVE': 0, 'PA_SEEK_RELATIVE_END': 3, 'PA_SEEK_RELATIVE_ON_READ': 2}, 'pa_sink_flags': {'PA_SINK_DECIBEL_VOLUME': 32, 'PA_SINK_DYNAMIC_LATENCY': 128, 'PA_SINK_FLAT_VOLUME': 64, 'PA_SINK_HARDWARE': 4, 'PA_SINK_HW_MUTE_CTRL': 16, 'PA_SINK_HW_VOLUME_CTRL': 1, 'PA_SINK_LATENCY': 2, 'PA_SINK_NETWORK': 8, 'PA_SINK_NOFLAGS': 0, 'PA_SINK_SET_FORMATS': 256}, 'pa_sink_state': {'PA_SINK_IDLE': 1, 'PA_SINK_INIT': -2, 'PA_SINK_INVALID_STATE': -1, 'PA_SINK_RUNNING': 0, 'PA_SINK_SUSPENDED': 2, 'PA_SINK_UNLINKED': -3}, 'pa_source_flags': {'PA_SOURCE_DECIBEL_VOLUME': 32, 'PA_SOURCE_DYNAMIC_LATENCY': 64, 'PA_SOURCE_FLAT_VOLUME': 128, 'PA_SOURCE_HARDWARE': 4, 'PA_SOURCE_HW_MUTE_CTRL': 16, 'PA_SOURCE_HW_VOLUME_CTRL': 1, 'PA_SOURCE_LATENCY': 2, 'PA_SOURCE_NETWORK': 8, 'PA_SOURCE_NOFLAGS': 0}, 'pa_source_state': {'PA_SOURCE_IDLE': 1, 'PA_SOURCE_INIT': -2, 'PA_SOURCE_INVALID_STATE': -1, 'PA_SOURCE_RUNNING': 0, 'PA_SOURCE_SUSPENDED': 2, 'PA_SOURCE_UNLINKED': -3}, 'pa_stream_direction': {'PA_STREAM_NODIRECTION': 0, 'PA_STREAM_PLAYBACK': 1, 'PA_STREAM_RECORD': 2, 'PA_STREAM_UPLOAD': 3}, 'pa_stream_flags': {'PA_STREAM_ADJUST_LATENCY': 8192, 'PA_STREAM_AUTO_TIMING_UPDATE': 8, 'PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND': 32768, 'PA_STREAM_DONT_MOVE': 512, 'PA_STREAM_EARLY_REQUESTS': 16384, 'PA_STREAM_FAIL_ON_SUSPEND': 131072, 'PA_STREAM_FIX_CHANNELS': 256, 'PA_STREAM_FIX_FORMAT': 64, 'PA_STREAM_FIX_RATE': 128, 'PA_STREAM_INTERPOLATE_TIMING': 2, 'PA_STREAM_NOFLAGS': 0, 'PA_STREAM_NOT_MONOTONIC': 4, 'PA_STREAM_NO_REMAP_CHANNELS': 16, 'PA_STREAM_NO_REMIX_CHANNELS': 32, 'PA_STREAM_PASSTHROUGH': 524288, 'PA_STREAM_PEAK_DETECT': 2048, 'PA_STREAM_RELATIVE_VOLUME': 262144, 'PA_STREAM_START_CORKED': 1, 'PA_STREAM_START_MUTED': 4096, 'PA_STREAM_START_UNMUTED': 65536, 'PA_STREAM_VARIABLE_RATE': 1024}, 'pa_stream_state': {'PA_STREAM_CREATING': 1, 'PA_STREAM_FAILED': 3, 'PA_STREAM_READY': 2, 'PA_STREAM_TERMINATED': 4, 'PA_STREAM_UNCONNECTED': 0}, 'pa_subscription_event_type': {'PA_SUBSCRIPTION_EVENT_AUTOLOAD': 8, 'PA_SUBSCRIPTION_EVENT_CARD': 9, 'PA_SUBSCRIPTION_EVENT_CHANGE': 16, 'PA_SUBSCRIPTION_EVENT_CLIENT': 5, 'PA_SUBSCRIPTION_EVENT_FACILITY_MASK': 15, 'PA_SUBSCRIPTION_EVENT_MODULE': 4, 'PA_SUBSCRIPTION_EVENT_NEW': 0, 'PA_SUBSCRIPTION_EVENT_REMOVE': 32, 'PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE': 6, 'PA_SUBSCRIPTION_EVENT_SERVER': 7, 'PA_SUBSCRIPTION_EVENT_SINK': 0, 'PA_SUBSCRIPTION_EVENT_SINK_INPUT': 2, 'PA_SUBSCRIPTION_EVENT_SOURCE': 1, 'PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT': 3, 'PA_SUBSCRIPTION_EVENT_TYPE_MASK': 48}, 'pa_subscription_mask': {'PA_SUBSCRIPTION_MASK_ALL': 767, 'PA_SUBSCRIPTION_MASK_AUTOLOAD': 256, 'PA_SUBSCRIPTION_MASK_CARD': 512, 'PA_SUBSCRIPTION_MASK_CLIENT': 32, 'PA_SUBSCRIPTION_MASK_MODULE': 16, 'PA_SUBSCRIPTION_MASK_NULL': 0, 'PA_SUBSCRIPTION_MASK_SAMPLE_CACHE': 64, 'PA_SUBSCRIPTION_MASK_SERVER': 128, 'PA_SUBSCRIPTION_MASK_SINK': 1, 'PA_SUBSCRIPTION_MASK_SINK_INPUT': 4, 'PA_SUBSCRIPTION_MASK_SOURCE': 2, 'PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT': 8}, 'pa_update_mode': {'PA_UPDATE_MERGE': 1, 'PA_UPDATE_REPLACE': 2, 'PA_UPDATE_SET': 0}} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716643049.6016257 libpulse-0.2/libpulse/pulse_functions.py0000644000000000000000000021120614624362352015542 0ustar00"""File pulse_functions. This file has been generated by libpulse_parser.py - DO NOT MODIFY. """ pulse_functions = \ {'callbacks': {'defer_enable': ('void', ['pa_defer_event *', 'int']), 'defer_free': ('void', ['pa_defer_event *']), 'defer_new': ('pa_defer_event *', ['pa_mainloop_api *', 'pa_defer_event_cb_t', 'void *']), 'defer_set_destroy': ('void', ['pa_defer_event *', 'pa_defer_event_destroy_cb_t']), 'io_enable': ('void', ['pa_io_event *', 'pa_io_event_flags_t']), 'io_free': ('void', ['pa_io_event *']), 'io_new': ('pa_io_event *', ['pa_mainloop_api *', 'int', 'pa_io_event_flags_t', 'pa_io_event_cb_t', 'void *']), 'io_set_destroy': ('void', ['pa_io_event *', 'pa_io_event_destroy_cb_t']), 'pa_autoload_info_cb_t': ('void', ['pa_context *', 'pa_autoload_info *', 'int', 'void *']), 'pa_card_info_cb_t': ('void', ['pa_context *', 'pa_card_info *', 'int', 'void *']), 'pa_client_info_cb_t': ('void', ['pa_context *', 'pa_client_info *', 'int', 'void *']), 'pa_context_event_cb_t': ('void', ['pa_context *', 'char *', 'pa_proplist *', 'void *']), 'pa_context_index_cb_t': ('void', ['pa_context *', 'uint32_t', 'void *']), 'pa_context_notify_cb_t': ('void', ['pa_context *', 'void *']), 'pa_context_play_sample_cb_t': ('void', ['pa_context *', 'uint32_t', 'void *']), 'pa_context_string_cb_t': ('void', ['pa_context *', 'int', 'char *', 'void *']), 'pa_context_subscribe_cb_t': ('void', ['pa_context *', 'pa_subscription_event_type_t', 'uint32_t', 'void *']), 'pa_context_success_cb_t': ('void', ['pa_context *', 'int', 'void *']), 'pa_defer_event_cb_t': ('void', ['pa_mainloop_api *', 'pa_defer_event *', 'void *']), 'pa_defer_event_destroy_cb_t': ('void', ['pa_mainloop_api *', 'pa_defer_event *', 'void *']), 'pa_free_cb_t': ('void', ['void *']), 'pa_io_event_cb_t': ('void', ['pa_mainloop_api *', 'pa_io_event *', 'int', 'pa_io_event_flags_t', 'void *']), 'pa_io_event_destroy_cb_t': ('void', ['pa_mainloop_api *', 'pa_io_event *', 'void *']), 'pa_module_info_cb_t': ('void', ['pa_context *', 'pa_module_info *', 'int', 'void *']), 'pa_operation_notify_cb_t': ('void', ['pa_operation *', 'void *']), 'pa_poll_func': ('int', ['struct pollfd *', 'unsigned long', 'int', 'void *']), 'pa_sample_info_cb_t': ('void', ['pa_context *', 'pa_sample_info *', 'int', 'void *']), 'pa_server_info_cb_t': ('void', ['pa_context *', 'pa_server_info *', 'void *']), 'pa_signal_cb_t': ('void', ['pa_mainloop_api *', 'pa_signal_event *', 'int', 'void *']), 'pa_signal_destroy_cb_t': ('void', ['pa_mainloop_api *', 'pa_signal_event *', 'void *']), 'pa_sink_info_cb_t': ('void', ['pa_context *', 'pa_sink_info *', 'int', 'void *']), 'pa_sink_input_info_cb_t': ('void', ['pa_context *', 'pa_sink_input_info *', 'int', 'void *']), 'pa_source_info_cb_t': ('void', ['pa_context *', 'pa_source_info *', 'int', 'void *']), 'pa_source_output_info_cb_t': ('void', ['pa_context *', 'pa_source_output_info *', 'int', 'void *']), 'pa_stat_info_cb_t': ('void', ['pa_context *', 'pa_stat_info *', 'void *']), 'pa_stream_event_cb_t': ('void', ['pa_stream *', 'char *', 'pa_proplist *', 'void *']), 'pa_stream_notify_cb_t': ('void', ['pa_stream *', 'void *']), 'pa_stream_request_cb_t': ('void', ['pa_stream *', 'size_t', 'void *']), 'pa_stream_success_cb_t': ('void', ['pa_stream *', 'int', 'void *']), 'pa_time_event_cb_t': ('void', ['pa_mainloop_api *', 'pa_time_event *', 'struct timeval *', 'void *']), 'pa_time_event_destroy_cb_t': ('void', ['pa_mainloop_api *', 'pa_time_event *', 'void *']), 'quit': ('void', ['pa_mainloop_api *', 'int']), 'time_free': ('void', ['pa_time_event *']), 'time_new': ('pa_time_event *', ['pa_mainloop_api *', 'struct timeval *', 'pa_time_event_cb_t', 'void *']), 'time_restart': ('void', ['pa_time_event *', 'struct timeval *']), 'time_set_destroy': ('void', ['pa_time_event *', 'pa_time_event_destroy_cb_t'])}, 'signatures': {'pa_ascii_filter': ('char *', ['char *']), 'pa_bytes_snprint': ('char *', ['char *', 'size_t', 'unsigned']), 'pa_channel_map_init': ('pa_channel_map *', ['pa_channel_map *']), 'pa_channel_map_init_auto': ('pa_channel_map *', ['pa_channel_map *', 'unsigned', 'pa_channel_map_def_t']), 'pa_channel_map_init_extend': ('pa_channel_map *', ['pa_channel_map *', 'unsigned', 'pa_channel_map_def_t']), 'pa_channel_map_init_mono': ('pa_channel_map *', ['pa_channel_map *']), 'pa_channel_map_init_stereo': ('pa_channel_map *', ['pa_channel_map *']), 'pa_channel_map_parse': ('pa_channel_map *', ['pa_channel_map *', 'char *']), 'pa_channel_map_snprint': ('char *', ['char *', 'size_t', 'pa_channel_map *']), 'pa_channel_position_to_pretty_string': ('char *', ['pa_channel_position_t']), 'pa_context_connect': ('int', ['pa_context *', 'char *', 'pa_context_flags_t', 'pa_spawn_api *']), 'pa_context_disconnect': ('void', ['pa_context *']), 'pa_context_drain': ('pa_operation *', ['pa_context *', 'pa_context_notify_cb_t', 'void *']), 'pa_context_errno': ('int', ['pa_context *']), 'pa_context_exit_daemon': ('pa_operation *', ['pa_context *', 'pa_context_success_cb_t', 'void *']), 'pa_context_get_card_info_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_card_info_cb_t', 'void *']), 'pa_context_get_card_info_by_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_card_info_cb_t', 'void *']), 'pa_context_get_card_info_list': ('pa_operation *', ['pa_context *', 'pa_card_info_cb_t', 'void *']), 'pa_context_get_client_info': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_client_info_cb_t', 'void *']), 'pa_context_get_client_info_list': ('pa_operation *', ['pa_context *', 'pa_client_info_cb_t', 'void *']), 'pa_context_get_index': ('uint32_t', ['pa_context *']), 'pa_context_get_module_info': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_module_info_cb_t', 'void *']), 'pa_context_get_module_info_list': ('pa_operation *', ['pa_context *', 'pa_module_info_cb_t', 'void *']), 'pa_context_get_protocol_version': ('uint32_t', ['pa_context *']), 'pa_context_get_sample_info_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_sample_info_cb_t', 'void *']), 'pa_context_get_sample_info_by_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_sample_info_cb_t', 'void *']), 'pa_context_get_sample_info_list': ('pa_operation *', ['pa_context *', 'pa_sample_info_cb_t', 'void *']), 'pa_context_get_server': ('char *', ['pa_context *']), 'pa_context_get_server_info': ('pa_operation *', ['pa_context *', 'pa_server_info_cb_t', 'void *']), 'pa_context_get_server_protocol_version': ('uint32_t', ['pa_context *']), 'pa_context_get_sink_info_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_sink_info_cb_t', 'void *']), 'pa_context_get_sink_info_by_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_sink_info_cb_t', 'void *']), 'pa_context_get_sink_info_list': ('pa_operation *', ['pa_context *', 'pa_sink_info_cb_t', 'void *']), 'pa_context_get_sink_input_info': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_sink_input_info_cb_t', 'void *']), 'pa_context_get_sink_input_info_list': ('pa_operation *', ['pa_context *', 'pa_sink_input_info_cb_t', 'void *']), 'pa_context_get_source_info_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_source_info_cb_t', 'void *']), 'pa_context_get_source_info_by_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_source_info_cb_t', 'void *']), 'pa_context_get_source_info_list': ('pa_operation *', ['pa_context *', 'pa_source_info_cb_t', 'void *']), 'pa_context_get_source_output_info': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_source_output_info_cb_t', 'void *']), 'pa_context_get_source_output_info_list': ('pa_operation *', ['pa_context *', 'pa_source_output_info_cb_t', 'void *']), 'pa_context_get_state': ('pa_context_state_t', ['pa_context *']), 'pa_context_get_tile_size': ('size_t', ['pa_context *', 'pa_sample_spec *']), 'pa_context_is_local': ('int', ['pa_context *']), 'pa_context_is_pending': ('int', ['pa_context *']), 'pa_context_kill_client': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_kill_sink_input': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_kill_source_output': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_load_cookie_from_file': ('int', ['pa_context *', 'char *']), 'pa_context_load_module': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'pa_context_index_cb_t', 'void *']), 'pa_context_move_sink_input_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'uint32_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_move_sink_input_by_name': ('pa_operation *', ['pa_context *', 'uint32_t', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_move_source_output_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'uint32_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_move_source_output_by_name': ('pa_operation *', ['pa_context *', 'uint32_t', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_new': ('pa_context *', ['pa_mainloop_api *', 'char *']), 'pa_context_new_with_proplist': ('pa_context *', ['pa_mainloop_api *', 'char *', 'pa_proplist *']), 'pa_context_play_sample': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'pa_volume_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_play_sample_with_proplist': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'pa_volume_t', 'pa_proplist *', 'pa_context_play_sample_cb_t', 'void *']), 'pa_context_proplist_remove': ('pa_operation *', ['pa_context *', 'char * *', 'pa_context_success_cb_t', 'void *']), 'pa_context_proplist_update': ('pa_operation *', ['pa_context *', 'pa_update_mode_t', 'pa_proplist *', 'pa_context_success_cb_t', 'void *']), 'pa_context_ref': ('pa_context *', ['pa_context *']), 'pa_context_remove_sample': ('pa_operation *', ['pa_context *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_rttime_new': ('pa_time_event *', ['pa_context *', 'pa_usec_t', 'pa_time_event_cb_t', 'void *']), 'pa_context_rttime_restart': ('void', ['pa_context *', 'pa_time_event *', 'pa_usec_t']), 'pa_context_send_message_to_object': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'char *', 'pa_context_string_cb_t', 'void *']), 'pa_context_set_card_profile_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_card_profile_by_name': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_default_sink': ('pa_operation *', ['pa_context *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_default_source': ('pa_operation *', ['pa_context *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_event_callback': ('void', ['pa_context *', 'pa_context_event_cb_t', 'void *']), 'pa_context_set_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_port_latency_offset': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'int64_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_input_mute': ('pa_operation *', ['pa_context *', 'uint32_t', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_input_volume': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_cvolume *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_mute_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_mute_by_name': ('pa_operation *', ['pa_context *', 'char *', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_port_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_port_by_name': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_volume_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_cvolume *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_sink_volume_by_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_cvolume *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_mute_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_mute_by_name': ('pa_operation *', ['pa_context *', 'char *', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_output_mute': ('pa_operation *', ['pa_context *', 'uint32_t', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_output_volume': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_cvolume *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_port_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_port_by_name': ('pa_operation *', ['pa_context *', 'char *', 'char *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_volume_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_cvolume *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_source_volume_by_name': ('pa_operation *', ['pa_context *', 'char *', 'pa_cvolume *', 'pa_context_success_cb_t', 'void *']), 'pa_context_set_state_callback': ('void', ['pa_context *', 'pa_context_notify_cb_t', 'void *']), 'pa_context_set_subscribe_callback': ('void', ['pa_context *', 'pa_context_subscribe_cb_t', 'void *']), 'pa_context_stat': ('pa_operation *', ['pa_context *', 'pa_stat_info_cb_t', 'void *']), 'pa_context_subscribe': ('pa_operation *', ['pa_context *', 'pa_subscription_mask_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_suspend_sink_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_suspend_sink_by_name': ('pa_operation *', ['pa_context *', 'char *', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_suspend_source_by_index': ('pa_operation *', ['pa_context *', 'uint32_t', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_suspend_source_by_name': ('pa_operation *', ['pa_context *', 'char *', 'int', 'pa_context_success_cb_t', 'void *']), 'pa_context_unload_module': ('pa_operation *', ['pa_context *', 'uint32_t', 'pa_context_success_cb_t', 'void *']), 'pa_context_unref': ('void', ['pa_context *']), 'pa_cvolume_dec': ('pa_cvolume *', ['pa_cvolume *', 'pa_volume_t']), 'pa_cvolume_inc': ('pa_cvolume *', ['pa_cvolume *', 'pa_volume_t']), 'pa_cvolume_inc_clamp': ('pa_cvolume *', ['pa_cvolume *', 'pa_volume_t', 'pa_volume_t']), 'pa_cvolume_init': ('pa_cvolume *', ['pa_cvolume *']), 'pa_cvolume_merge': ('pa_cvolume *', ['pa_cvolume *', 'pa_cvolume *', 'pa_cvolume *']), 'pa_cvolume_remap': ('pa_cvolume *', ['pa_cvolume *', 'pa_channel_map *', 'pa_channel_map *']), 'pa_cvolume_scale': ('pa_cvolume *', ['pa_cvolume *', 'pa_volume_t']), 'pa_cvolume_scale_mask': ('pa_cvolume *', ['pa_cvolume *', 'pa_volume_t', 'pa_channel_map *', 'pa_channel_position_mask_t']), 'pa_cvolume_set': ('pa_cvolume *', ['pa_cvolume *', 'unsigned', 'pa_volume_t']), 'pa_cvolume_set_balance': ('pa_cvolume *', ['pa_cvolume *', 'pa_channel_map *', 'float']), 'pa_cvolume_set_fade': ('pa_cvolume *', ['pa_cvolume *', 'pa_channel_map *', 'float']), 'pa_cvolume_set_lfe_balance': ('pa_cvolume *', ['pa_cvolume *', 'pa_channel_map *', 'float']), 'pa_cvolume_set_position': ('pa_cvolume *', ['pa_cvolume *', 'pa_channel_map *', 'pa_channel_position_t', 'pa_volume_t']), 'pa_cvolume_snprint': ('char *', ['char *', 'size_t', 'pa_cvolume *']), 'pa_cvolume_snprint_verbose': ('char *', ['char *', 'size_t', 'pa_cvolume *', 'pa_channel_map *', 'int']), 'pa_direction_to_string': ('char *', ['pa_direction_t']), 'pa_encoding_from_string': ('pa_encoding_t', ['char *']), 'pa_format_info_copy': ('pa_format_info *', ['pa_format_info *']), 'pa_format_info_free': ('void', ['pa_format_info *']), 'pa_format_info_free_string_array': ('void', ['char * *', 'int']), 'pa_format_info_from_sample_spec': ('pa_format_info *', ['pa_sample_spec *', 'pa_channel_map *']), 'pa_format_info_from_string': ('pa_format_info *', ['char *']), 'pa_format_info_get_channel_map': ('int', ['pa_format_info *', 'pa_channel_map *']), 'pa_format_info_get_channels': ('int', ['pa_format_info *', 'uint8_t *']), 'pa_format_info_get_prop_int': ('int', ['pa_format_info *', 'char *', 'int *']), 'pa_format_info_get_prop_int_array': ('int', ['pa_format_info *', 'char *', 'int * *', 'int *']), 'pa_format_info_get_prop_int_range': ('int', ['pa_format_info *', 'char *', 'int *', 'int *']), 'pa_format_info_get_prop_string': ('int', ['pa_format_info *', 'char *', 'char * *']), 'pa_format_info_get_prop_string_array': ('int', ['pa_format_info *', 'char *', 'char * * *', 'int *']), 'pa_format_info_get_prop_type': ('pa_prop_type_t', ['pa_format_info *', 'char *']), 'pa_format_info_get_rate': ('int', ['pa_format_info *', 'uint32_t *']), 'pa_format_info_get_sample_format': ('int', ['pa_format_info *', 'pa_sample_format_t *']), 'pa_format_info_is_compatible': ('int', ['pa_format_info *', 'pa_format_info *']), 'pa_format_info_is_pcm': ('int', ['pa_format_info *']), 'pa_format_info_new': ('pa_format_info *', ['void']), 'pa_format_info_set_channel_map': ('void', ['pa_format_info *', 'pa_channel_map *']), 'pa_format_info_set_channels': ('void', ['pa_format_info *', 'int']), 'pa_format_info_set_prop_int': ('void', ['pa_format_info *', 'char *', 'int']), 'pa_format_info_set_prop_int_array': ('void', ['pa_format_info *', 'char *', 'int *', 'int']), 'pa_format_info_set_prop_int_range': ('void', ['pa_format_info *', 'char *', 'int', 'int']), 'pa_format_info_set_prop_string': ('void', ['pa_format_info *', 'char *', 'char *']), 'pa_format_info_set_prop_string_array': ('void', ['pa_format_info *', 'char *', 'char * *', 'int']), 'pa_format_info_set_rate': ('void', ['pa_format_info *', 'int']), 'pa_format_info_set_sample_format': ('void', ['pa_format_info *', 'pa_sample_format_t']), 'pa_format_info_snprint': ('char *', ['char *', 'size_t', 'pa_format_info *']), 'pa_format_info_to_sample_spec': ('int', ['pa_format_info *', 'pa_sample_spec *', 'pa_channel_map *']), 'pa_format_info_valid': ('int', ['pa_format_info *']), 'pa_get_binary_name': ('char *', ['char *', 'size_t']), 'pa_get_fqdn': ('char *', ['char *', 'size_t']), 'pa_get_home_dir': ('char *', ['char *', 'size_t']), 'pa_get_host_name': ('char *', ['char *', 'size_t']), 'pa_get_library_version': ('char *', ['void']), 'pa_get_user_name': ('char *', ['char *', 'size_t']), 'pa_gettimeofday': ('struct timeval *', ['struct timeval *']), 'pa_locale_to_utf8': ('char *', ['char *']), 'pa_mainloop_api_once': ('void', ['pa_mainloop_api *', ('void', ['pa_mainloop_api *', 'void *']), 'void *']), 'pa_mainloop_dispatch': ('int', ['pa_mainloop *']), 'pa_mainloop_free': ('void', ['pa_mainloop *']), 'pa_mainloop_get_api': ('pa_mainloop_api *', ['pa_mainloop *']), 'pa_mainloop_get_retval': ('int', ['pa_mainloop *']), 'pa_mainloop_iterate': ('int', ['pa_mainloop *', 'int', 'int *']), 'pa_mainloop_new': ('pa_mainloop *', ['void']), 'pa_mainloop_poll': ('int', ['pa_mainloop *']), 'pa_mainloop_prepare': ('int', ['pa_mainloop *', 'int']), 'pa_mainloop_quit': ('void', ['pa_mainloop *', 'int']), 'pa_mainloop_run': ('int', ['pa_mainloop *', 'int *']), 'pa_mainloop_set_poll_func': ('void', ['pa_mainloop *', 'pa_poll_func', 'void *']), 'pa_mainloop_wakeup': ('void', ['pa_mainloop *']), 'pa_msleep': ('int', ['unsigned long']), 'pa_operation_cancel': ('void', ['pa_operation *']), 'pa_operation_get_state': ('pa_operation_state_t', ['pa_operation *']), 'pa_operation_ref': ('pa_operation *', ['pa_operation *']), 'pa_operation_set_state_callback': ('void', ['pa_operation *', 'pa_operation_notify_cb_t', 'void *']), 'pa_operation_unref': ('void', ['pa_operation *']), 'pa_path_get_filename': ('char *', ['char *']), 'pa_proplist_clear': ('void', ['pa_proplist *']), 'pa_proplist_contains': ('int', ['pa_proplist *', 'char *']), 'pa_proplist_copy': ('pa_proplist *', ['pa_proplist *']), 'pa_proplist_equal': ('int', ['pa_proplist *', 'pa_proplist *']), 'pa_proplist_free': ('void', ['pa_proplist *']), 'pa_proplist_from_string': ('pa_proplist *', ['char *']), 'pa_proplist_get': ('int', ['pa_proplist *', 'char *', 'void * *', 'size_t *']), 'pa_proplist_gets': ('char *', ['pa_proplist *', 'char *']), 'pa_proplist_isempty': ('int', ['pa_proplist *']), 'pa_proplist_iterate': ('char *', ['pa_proplist *', 'void * *']), 'pa_proplist_key_valid': ('int', ['char *']), 'pa_proplist_new': ('pa_proplist *', ['void']), 'pa_proplist_set': ('int', ['pa_proplist *', 'char *', 'void *', 'size_t']), 'pa_proplist_setp': ('int', ['pa_proplist *', 'char *']), 'pa_proplist_sets': ('int', ['pa_proplist *', 'char *', 'char *']), 'pa_proplist_size': ('unsigned', ['pa_proplist *']), 'pa_proplist_to_string': ('char *', ['pa_proplist *']), 'pa_proplist_to_string_sep': ('char *', ['pa_proplist *', 'char *']), 'pa_proplist_unset': ('int', ['pa_proplist *', 'char *']), 'pa_proplist_unset_many': ('int', ['pa_proplist *', 'char * *']), 'pa_proplist_update': ('void', ['pa_proplist *', 'pa_update_mode_t', 'pa_proplist *']), 'pa_rtclock_now': ('pa_usec_t', ['void']), 'pa_sample_spec_init': ('pa_sample_spec *', ['pa_sample_spec *']), 'pa_sample_spec_snprint': ('char *', ['char *', 'size_t', 'pa_sample_spec *']), 'pa_signal_done': ('void', ['void']), 'pa_signal_free': ('void', ['pa_signal_event *']), 'pa_signal_init': ('int', ['pa_mainloop_api *']), 'pa_signal_new': ('pa_signal_event *', ['int', 'pa_signal_cb_t', 'void *']), 'pa_signal_set_destroy': ('void', ['pa_signal_event *', 'pa_signal_destroy_cb_t']), 'pa_stream_begin_write': ('int', ['pa_stream *', 'void * *', 'size_t *']), 'pa_stream_cancel_write': ('int', ['pa_stream *']), 'pa_stream_connect_playback': ('int', ['pa_stream *', 'char *', 'pa_buffer_attr *', 'pa_stream_flags_t', 'pa_cvolume *', 'pa_stream *']), 'pa_stream_connect_record': ('int', ['pa_stream *', 'char *', 'pa_buffer_attr *', 'pa_stream_flags_t']), 'pa_stream_connect_upload': ('int', ['pa_stream *', 'size_t']), 'pa_stream_cork': ('pa_operation *', ['pa_stream *', 'int', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_disconnect': ('int', ['pa_stream *']), 'pa_stream_drain': ('pa_operation *', ['pa_stream *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_drop': ('int', ['pa_stream *']), 'pa_stream_finish_upload': ('int', ['pa_stream *']), 'pa_stream_flush': ('pa_operation *', ['pa_stream *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_get_buffer_attr': ('pa_buffer_attr *', ['pa_stream *']), 'pa_stream_get_channel_map': ('pa_channel_map *', ['pa_stream *']), 'pa_stream_get_context': ('pa_context *', ['pa_stream *']), 'pa_stream_get_device_index': ('uint32_t', ['pa_stream *']), 'pa_stream_get_device_name': ('char *', ['pa_stream *']), 'pa_stream_get_format_info': ('pa_format_info *', ['pa_stream *']), 'pa_stream_get_index': ('uint32_t', ['pa_stream *']), 'pa_stream_get_latency': ('int', ['pa_stream *', 'pa_usec_t *', 'int *']), 'pa_stream_get_monitor_stream': ('uint32_t', ['pa_stream *']), 'pa_stream_get_sample_spec': ('pa_sample_spec *', ['pa_stream *']), 'pa_stream_get_state': ('pa_stream_state_t', ['pa_stream *']), 'pa_stream_get_time': ('int', ['pa_stream *', 'pa_usec_t *']), 'pa_stream_get_timing_info': ('pa_timing_info *', ['pa_stream *']), 'pa_stream_get_underflow_index': ('int64_t', ['pa_stream *']), 'pa_stream_is_corked': ('int', ['pa_stream *']), 'pa_stream_is_suspended': ('int', ['pa_stream *']), 'pa_stream_new': ('pa_stream *', ['pa_context *', 'char *', 'pa_sample_spec *', 'pa_channel_map *']), 'pa_stream_new_extended': ('pa_stream *', ['pa_context *', 'char *', 'pa_format_info * *', 'unsigned int', 'pa_proplist *']), 'pa_stream_new_with_proplist': ('pa_stream *', ['pa_context *', 'char *', 'pa_sample_spec *', 'pa_channel_map *', 'pa_proplist *']), 'pa_stream_peek': ('int', ['pa_stream *', 'void * *', 'size_t *']), 'pa_stream_prebuf': ('pa_operation *', ['pa_stream *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_proplist_remove': ('pa_operation *', ['pa_stream *', 'char * *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_proplist_update': ('pa_operation *', ['pa_stream *', 'pa_update_mode_t', 'pa_proplist *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_readable_size': ('size_t', ['pa_stream *']), 'pa_stream_ref': ('pa_stream *', ['pa_stream *']), 'pa_stream_set_buffer_attr': ('pa_operation *', ['pa_stream *', 'pa_buffer_attr *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_set_buffer_attr_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_event_callback': ('void', ['pa_stream *', 'pa_stream_event_cb_t', 'void *']), 'pa_stream_set_latency_update_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_monitor_stream': ('int', ['pa_stream *', 'uint32_t']), 'pa_stream_set_moved_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_name': ('pa_operation *', ['pa_stream *', 'char *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_set_overflow_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_read_callback': ('void', ['pa_stream *', 'pa_stream_request_cb_t', 'void *']), 'pa_stream_set_started_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_state_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_suspended_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_underflow_callback': ('void', ['pa_stream *', 'pa_stream_notify_cb_t', 'void *']), 'pa_stream_set_write_callback': ('void', ['pa_stream *', 'pa_stream_request_cb_t', 'void *']), 'pa_stream_trigger': ('pa_operation *', ['pa_stream *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_unref': ('void', ['pa_stream *']), 'pa_stream_update_sample_rate': ('pa_operation *', ['pa_stream *', 'uint32_t', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_update_timing_info': ('pa_operation *', ['pa_stream *', 'pa_stream_success_cb_t', 'void *']), 'pa_stream_writable_size': ('size_t', ['pa_stream *']), 'pa_stream_write': ('int', ['pa_stream *', 'void *', 'size_t', 'pa_free_cb_t', 'int64_t', 'pa_seek_mode_t']), 'pa_stream_write_ext_free': ('int', ['pa_stream *', 'void *', 'size_t', 'pa_free_cb_t', 'void *', 'int64_t', 'pa_seek_mode_t']), 'pa_strerror': ('char *', ['int']), 'pa_sw_cvolume_divide': ('pa_cvolume *', ['pa_cvolume *', 'pa_cvolume *', 'pa_cvolume *']), 'pa_sw_cvolume_divide_scalar': ('pa_cvolume *', ['pa_cvolume *', 'pa_cvolume *', 'pa_volume_t']), 'pa_sw_cvolume_multiply': ('pa_cvolume *', ['pa_cvolume *', 'pa_cvolume *', 'pa_cvolume *']), 'pa_sw_cvolume_multiply_scalar': ('pa_cvolume *', ['pa_cvolume *', 'pa_cvolume *', 'pa_volume_t']), 'pa_sw_cvolume_snprint_dB': ('char *', ['char *', 'size_t', 'pa_cvolume *']), 'pa_sw_volume_snprint_dB': ('char *', ['char *', 'size_t', 'pa_volume_t']), 'pa_thread_make_realtime': ('int', ['int']), 'pa_threaded_mainloop_accept': ('void', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_free': ('void', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_get_api': ('pa_mainloop_api *', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_get_retval': ('int', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_in_thread': ('int', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_lock': ('void', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_new': ('pa_threaded_mainloop *', ['void']), 'pa_threaded_mainloop_once_unlocked': ('void', ['pa_threaded_mainloop ' '*', ('void', ['pa_threaded_mainloop ' '*', 'void *']), 'void *']), 'pa_threaded_mainloop_set_name': ('void', ['pa_threaded_mainloop *', 'char *']), 'pa_threaded_mainloop_signal': ('void', ['pa_threaded_mainloop *', 'int']), 'pa_threaded_mainloop_start': ('int', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_stop': ('void', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_unlock': ('void', ['pa_threaded_mainloop *']), 'pa_threaded_mainloop_wait': ('void', ['pa_threaded_mainloop *']), 'pa_timeval_add': ('struct timeval *', ['struct timeval *', 'pa_usec_t']), 'pa_timeval_age': ('pa_usec_t', ['struct timeval *']), 'pa_timeval_load': ('pa_usec_t', ['struct timeval *']), 'pa_timeval_store': ('struct timeval *', ['struct timeval *', 'pa_usec_t']), 'pa_timeval_sub': ('struct timeval *', ['struct timeval *', 'pa_usec_t']), 'pa_utf8_filter': ('char *', ['char *']), 'pa_utf8_to_locale': ('char *', ['char *']), 'pa_volume_snprint': ('char *', ['char *', 'size_t', 'pa_volume_t']), 'pa_volume_snprint_verbose': ('char *', ['char *', 'size_t', 'pa_volume_t', 'int']), 'pa_xfree': ('void', ['void *']), 'pa_xrealloc': ('void *', ['void *', 'size_t'])}} ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1718093606.29251 libpulse-0.2/libpulse/pulse_structs.py0000644000000000000000000002362414632003446015241 0ustar00"""File pulse_structs. This file has been generated by libpulse_parser.py - DO NOT MODIFY. """ pulse_structs = \ {'pa_autoload_info': (('index', 'uint32_t'), ('name', 'char *'), ('type', 'pa_autoload_type_t'), ('module', 'char *'), ('argument', 'char *')), 'pa_buffer_attr': (('maxlength', 'uint32_t'), ('tlength', 'uint32_t'), ('prebuf', 'uint32_t'), ('minreq', 'uint32_t'), ('fragsize', 'uint32_t')), 'pa_card_info': (('index', 'uint32_t'), ('name', 'char *'), ('owner_module', 'uint32_t'), ('driver', 'char *'), ('n_profiles', 'uint32_t'), ('profiles', 'pa_card_profile_info *'), ('active_profile', 'pa_card_profile_info *'), ('proplist', 'pa_proplist *'), ('n_ports', 'uint32_t'), ('ports', 'pa_card_port_info * *'), ('profiles2', 'pa_card_profile_info2 * *'), ('active_profile2', 'pa_card_profile_info2 *')), 'pa_card_port_info': (('name', 'char *'), ('description', 'char *'), ('priority', 'uint32_t'), ('available', 'int'), ('direction', 'int'), ('n_profiles', 'uint32_t'), ('profiles', 'pa_card_profile_info * *'), ('proplist', 'pa_proplist *'), ('latency_offset', 'int64_t'), ('profiles2', 'pa_card_profile_info2 * *'), ('availability_group', 'char *'), ('type', 'uint32_t')), 'pa_card_profile_info': (('name', 'char *'), ('description', 'char *'), ('n_sinks', 'uint32_t'), ('n_sources', 'uint32_t'), ('priority', 'uint32_t')), 'pa_card_profile_info2': (('name', 'char *'), ('description', 'char *'), ('n_sinks', 'uint32_t'), ('n_sources', 'uint32_t'), ('priority', 'uint32_t'), ('available', 'int')), 'pa_channel_map': (('channels', 'uint8_t'), ('map', 'pa_channel_position_t * 32')), 'pa_client_info': (('index', 'uint32_t'), ('name', 'char *'), ('owner_module', 'uint32_t'), ('driver', 'char *'), ('proplist', 'pa_proplist *')), 'pa_context': (), 'pa_cvolume': (('channels', 'uint8_t'), ('values', 'pa_volume_t * 32')), 'pa_defer_event': (), 'pa_format_info': (('encoding', 'pa_encoding_t'), ('plist', 'pa_proplist *')), 'pa_io_event': (), 'pa_mainloop': (), 'pa_module_info': (('index', 'uint32_t'), ('name', 'char *'), ('argument', 'char *'), ('n_used', 'uint32_t'), ('auto_unload', 'int'), ('proplist', 'pa_proplist *')), 'pa_operation': (), 'pa_proplist': (), 'pa_sample_info': (('index', 'uint32_t'), ('name', 'char *'), ('volume', 'pa_cvolume'), ('sample_spec', 'pa_sample_spec'), ('channel_map', 'pa_channel_map'), ('duration', 'pa_usec_t'), ('bytes', 'uint32_t'), ('lazy', 'int'), ('filename', 'char *'), ('proplist', 'pa_proplist *')), 'pa_sample_spec': (('format', 'pa_sample_format_t'), ('rate', 'uint32_t'), ('channels', 'uint8_t')), 'pa_server_info': (('user_name', 'char *'), ('host_name', 'char *'), ('server_version', 'char *'), ('server_name', 'char *'), ('sample_spec', 'pa_sample_spec'), ('default_sink_name', 'char *'), ('default_source_name', 'char *'), ('cookie', 'uint32_t'), ('channel_map', 'pa_channel_map')), 'pa_signal_event': (), 'pa_sink_info': (('name', 'char *'), ('index', 'uint32_t'), ('description', 'char *'), ('sample_spec', 'pa_sample_spec'), ('channel_map', 'pa_channel_map'), ('owner_module', 'uint32_t'), ('volume', 'pa_cvolume'), ('mute', 'int'), ('monitor_source', 'uint32_t'), ('monitor_source_name', 'char *'), ('latency', 'pa_usec_t'), ('driver', 'char *'), ('flags', 'pa_sink_flags_t'), ('proplist', 'pa_proplist *'), ('configured_latency', 'pa_usec_t'), ('base_volume', 'pa_volume_t'), ('state', 'pa_sink_state_t'), ('n_volume_steps', 'uint32_t'), ('card', 'uint32_t'), ('n_ports', 'uint32_t'), ('ports', 'pa_sink_port_info * *'), ('active_port', 'pa_sink_port_info *'), ('n_formats', 'uint8_t'), ('formats', 'pa_format_info * *')), 'pa_sink_input_info': (('index', 'uint32_t'), ('name', 'char *'), ('owner_module', 'uint32_t'), ('client', 'uint32_t'), ('sink', 'uint32_t'), ('sample_spec', 'pa_sample_spec'), ('channel_map', 'pa_channel_map'), ('volume', 'pa_cvolume'), ('buffer_usec', 'pa_usec_t'), ('sink_usec', 'pa_usec_t'), ('resample_method', 'char *'), ('driver', 'char *'), ('mute', 'int'), ('proplist', 'pa_proplist *'), ('corked', 'int'), ('has_volume', 'int'), ('volume_writable', 'int'), ('format', 'pa_format_info *')), 'pa_sink_port_info': (('name', 'char *'), ('description', 'char *'), ('priority', 'uint32_t'), ('available', 'int'), ('availability_group', 'char *'), ('type', 'uint32_t')), 'pa_source_info': (('name', 'char *'), ('index', 'uint32_t'), ('description', 'char *'), ('sample_spec', 'pa_sample_spec'), ('channel_map', 'pa_channel_map'), ('owner_module', 'uint32_t'), ('volume', 'pa_cvolume'), ('mute', 'int'), ('monitor_of_sink', 'uint32_t'), ('monitor_of_sink_name', 'char *'), ('latency', 'pa_usec_t'), ('driver', 'char *'), ('flags', 'pa_source_flags_t'), ('proplist', 'pa_proplist *'), ('configured_latency', 'pa_usec_t'), ('base_volume', 'pa_volume_t'), ('state', 'pa_source_state_t'), ('n_volume_steps', 'uint32_t'), ('card', 'uint32_t'), ('n_ports', 'uint32_t'), ('ports', 'pa_source_port_info * *'), ('active_port', 'pa_source_port_info *'), ('n_formats', 'uint8_t'), ('formats', 'pa_format_info * *')), 'pa_source_output_info': (('index', 'uint32_t'), ('name', 'char *'), ('owner_module', 'uint32_t'), ('client', 'uint32_t'), ('source', 'uint32_t'), ('sample_spec', 'pa_sample_spec'), ('channel_map', 'pa_channel_map'), ('buffer_usec', 'pa_usec_t'), ('source_usec', 'pa_usec_t'), ('resample_method', 'char *'), ('driver', 'char *'), ('proplist', 'pa_proplist *'), ('corked', 'int'), ('volume', 'pa_cvolume'), ('mute', 'int'), ('has_volume', 'int'), ('volume_writable', 'int'), ('format', 'pa_format_info *')), 'pa_source_port_info': (('name', 'char *'), ('description', 'char *'), ('priority', 'uint32_t'), ('available', 'int'), ('availability_group', 'char *'), ('type', 'uint32_t')), 'pa_stat_info': (('memblock_total', 'uint32_t'), ('memblock_total_size', 'uint32_t'), ('memblock_allocated', 'uint32_t'), ('memblock_allocated_size', 'uint32_t'), ('scache_size', 'uint32_t')), 'pa_stream': (), 'pa_threaded_mainloop': (), 'pa_time_event': (), 'pa_timing_info': (('timestamp', 'struct timeval'), ('synchronized_clocks', 'int'), ('sink_usec', 'pa_usec_t'), ('source_usec', 'pa_usec_t'), ('transport_usec', 'pa_usec_t'), ('playing', 'int'), ('write_index_corrupt', 'int'), ('write_index', 'int64_t'), ('read_index_corrupt', 'int'), ('read_index', 'int64_t'), ('configured_sink_usec', 'pa_usec_t'), ('configured_source_usec', 'pa_usec_t'), ('since_underrun', 'int64_t'))} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716643049.5882926 libpulse-0.2/libpulse/pulse_types.py0000644000000000000000000000563114624362352014701 0ustar00"""File pulse_types. This file has been generated by libpulse_parser.py - DO NOT MODIFY. """ pulse_types = \ {'pa_autoload_info': 'struct pa_autoload_info', 'pa_autoload_type_t': 'enum pa_autoload_type', 'pa_buffer_attr': 'struct pa_buffer_attr', 'pa_card_info': 'struct pa_card_info', 'pa_card_port_info': 'struct pa_card_port_info', 'pa_card_profile_info': 'struct pa_card_profile_info', 'pa_card_profile_info2': 'struct pa_card_profile_info2', 'pa_channel_map': 'struct pa_channel_map', 'pa_channel_map_def_t': 'enum pa_channel_map_def', 'pa_channel_position_mask_t': 'uint64_t', 'pa_channel_position_t': 'enum pa_channel_position', 'pa_client_info': 'struct pa_client_info', 'pa_context': 'struct pa_context', 'pa_context_flags_t': 'enum pa_context_flags', 'pa_context_state_t': 'enum pa_context_state', 'pa_cvolume': 'struct pa_cvolume', 'pa_defer_event': 'struct pa_defer_event', 'pa_device_port_type_t': 'enum pa_device_port_type', 'pa_device_type_t': 'enum pa_device_type', 'pa_direction_t': 'enum pa_direction', 'pa_encoding_t': 'enum pa_encoding', 'pa_error_code_t': 'enum pa_error_code', 'pa_format_info': 'struct pa_format_info', 'pa_io_event': 'struct pa_io_event', 'pa_io_event_flags_t': 'enum pa_io_event_flags', 'pa_mainloop': 'struct pa_mainloop', 'pa_mainloop_api': 'struct pa_mainloop_api', 'pa_module_info': 'struct pa_module_info', 'pa_operation': 'struct pa_operation', 'pa_operation_state_t': 'enum pa_operation_state', 'pa_port_available_t': 'enum pa_port_available', 'pa_prop_type_t': 'enum pa_prop_type_t', 'pa_proplist': 'struct pa_proplist', 'pa_sample_format_t': 'enum pa_sample_format', 'pa_sample_info': 'struct pa_sample_info', 'pa_sample_spec': 'struct pa_sample_spec', 'pa_seek_mode_t': 'enum pa_seek_mode', 'pa_server_info': 'struct pa_server_info', 'pa_signal_event': 'struct pa_signal_event', 'pa_sink_flags_t': 'enum pa_sink_flags', 'pa_sink_info': 'struct pa_sink_info', 'pa_sink_input_info': 'struct pa_sink_input_info', 'pa_sink_port_info': 'struct pa_sink_port_info', 'pa_sink_state_t': 'enum pa_sink_state', 'pa_source_flags_t': 'enum pa_source_flags', 'pa_source_info': 'struct pa_source_info', 'pa_source_output_info': 'struct pa_source_output_info', 'pa_source_port_info': 'struct pa_source_port_info', 'pa_source_state_t': 'enum pa_source_state', 'pa_spawn_api': 'struct pa_spawn_api', 'pa_stat_info': 'struct pa_stat_info', 'pa_stream': 'struct pa_stream', 'pa_stream_direction_t': 'enum pa_stream_direction', 'pa_stream_flags_t': 'enum pa_stream_flags', 'pa_stream_state_t': 'enum pa_stream_state', 'pa_subscription_event_type_t': 'enum pa_subscription_event_type', 'pa_subscription_mask_t': 'enum pa_subscription_mask', 'pa_threaded_mainloop': 'struct pa_threaded_mainloop', 'pa_time_event': 'struct pa_time_event', 'pa_timing_info': 'struct pa_timing_info', 'pa_update_mode_t': 'enum pa_update_mode', 'pa_usec_t': 'uint64_t', 'pa_volume_t': 'uint32_t'} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1716638522.346292 libpulse-0.2/libpulse/tests/__init__.py0000644000000000000000000000472614624351472015233 0ustar00import os import sys import subprocess import unittest import functools if sys.version_info >= (3, 9): functools_cache = functools.cache else: functools_cache = functools.lru_cache def _id(obj): return obj @functools_cache def requires_resources(resources): """Skip the test when one of the resource is not available. 'resources' is a string or a tuple instance (MUST be hashable). """ resources = [resources] if isinstance(resources, str) else resources for res in resources: try: if res == 'os.devnull': # Check that os.devnull is writable. with open(os.devnull, 'w'): pass elif res == 'libpulse': # Check that pulseaudio or pipewire-pulse is running. subprocess.run(['pactl', 'info'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) else: # Otherwise check that the module can be imported. exec(f'import {res}') except Exception: return unittest.skip(f"'{res}' is not available") else: return _id def load_ordered_tests(loader, standard_tests, pattern): """Keep the tests in the order they were declared in the class. Thanks to https://stackoverflow.com/a/62073640 """ ordered_cases = [] for test_suite in standard_tests: ordered = [] for test_case in test_suite: test_case_type = type(test_case) method_name = test_case._testMethodName testMethod = getattr(test_case, method_name) line = testMethod.__code__.co_firstlineno ordered.append( (line, test_case_type, method_name) ) ordered.sort() for line, case_type, name in ordered: ordered_cases.append(case_type(name)) return unittest.TestSuite(ordered_cases) def find_in_logs(logs, logger, msg): """Return True if 'msg' from 'logger' is in 'logs'.""" for log in (log.split(':', maxsplit=2) for log in logs): if len(log) == 3 and log[1] == logger and log[2] == msg: return True return False def search_in_logs(logs, logger, matcher): """Return True if the matcher's pattern is found in a message in 'logs'.""" for log in (log.split(':', maxsplit=2) for log in logs): if (len(log) == 3 and log[1] == logger and matcher.search(log[2]) is not None): return True return False ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1717953453.9297345 libpulse-0.2/libpulse/tests/test_libpulse.py0000644000000000000000000002475314631361656016357 0ustar00"""libPulse test cases.""" import re import asyncio import logging from unittest import TestCase, IsolatedAsyncioTestCase, mock # Load the tests in the order they are declared. from . import requires_resources, load_ordered_tests, search_in_logs import libpulse.libpulse as libpulse_module from ..libpulse import * from ..pulse_functions import pulse_functions SINK_NAME= 'foo' MODULE_ARG = (f'sink_name="{SINK_NAME}" sink_properties=device.description=' fr'"{SINK_NAME}\ description"') async def get_event(facility, type, lib_pulse, ready): try: await lib_pulse.pa_context_subscribe(PA_SUBSCRIPTION_MASK_ALL) iterator = lib_pulse.get_events() ready.set_result(True) index = None async for event in iterator: if event.facility == facility and event.type == type: iterator.close() index = event.index return index except asyncio.CancelledError: print('get_event(): CancelledError') except LibPulseError as e: return e def signature(funcname): #Return the signature of a function listed in the pulse_functions module. sig = pulse_functions['signatures'].get(funcname) if sig is None: sig = pulse_functions['callbacks'][funcname] return sig class LoadModule: def __init__(self, lib_pulse, name, argument): self.lib_pulse = lib_pulse self.name = name self.argument = argument self.module_index = PA_INVALID_INDEX async def __aenter__(self): self.module_index = await self.lib_pulse.pa_context_load_module( self.name, self.argument) return self async def __aexit__(self, exc_type, exc_value, traceback): if self.module_index != PA_INVALID_INDEX: await self.lib_pulse.pa_context_unload_module(self.module_index) @requires_resources('libpulse') class LibPulseTests(IsolatedAsyncioTestCase): async def test_log_server_info(self): with self.assertLogs(level=logging.DEBUG) as m_logs: async with LibPulse('libpulse-test') as lib_pulse: await lib_pulse.log_server_info() self.assertTrue(search_in_logs(m_logs.output, 'libpuls', re.compile(fr'Server: [Pp]ulse[Aa]udio.* \d+\.\d'))) async def test_list_sinks(self): async with LibPulse('libpulse-test') as lib_pulse: async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG) as loaded_module: for sink in \ await lib_pulse.pa_context_get_sink_info_list(): if sink.name == SINK_NAME: break else: self.fail(f"'{SINK_NAME}' is not listed in the sink" f' list') async def test_sink_by_name(self): async with LibPulse('libpulse-test') as lib_pulse: async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG) as loaded_module: sink = (await lib_pulse.pa_context_get_sink_info_by_name(SINK_NAME)) self.assertEqual(sink.name, SINK_NAME) self.assertEqual(sink.channel_map.channels, 2) self.assertEqual(len(sink.channel_map.map), 32) async def test_sink_proplist(self): async with LibPulse('libpulse-test') as lib_pulse: async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG) as loaded_module: sink = (await lib_pulse.pa_context_get_sink_info_by_name(SINK_NAME)) self.assertTrue(re.match(fr'{SINK_NAME}\\? description', sink.proplist['device.description'])) async def test_events(self): async with LibPulse('libpulse-test') as lib_pulse: ready = lib_pulse.loop.create_future() evt_task = asyncio.create_task(get_event('module', 'new', lib_pulse, ready)) await ready async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG) as loaded_module: await asyncio.wait_for(evt_task, 1) self.assertEqual(evt_task.result(), loaded_module.module_index) async def test_abort_iterator(self): async def main(): try: async with LibPulse('libpulse-test') as lib_pulse: # Run the asynchronous iterator loop until it is aborted # or cancelled. ready = lib_pulse.loop.create_future() evt_task = asyncio.create_task(get_event('invalid', 'new', lib_pulse, ready)) await ready # Raise an exception to force the closing of the LibPulse # instance and the iterator abort. 1/0 except ZeroDivisionError: pass await evt_task return evt_task.result() main_task = asyncio.create_task(main()) await main_task self.assertTrue(isinstance(main_task.result(), LibPulseClosedIteratorError)) async def test_excep_ctx_mgr(self): with mock.patch.object(libpulse_module, 'pa_context_connect') as connect,\ self.assertRaises(LibPulseStateError): connect.side_effect = LibPulseStateError() async with LibPulse('libpulse-test') as lib_pulse: pass async def test_cancel_ctx_mgr(self): with mock.patch.object(libpulse_module, 'pa_context_connect') as connect,\ self.assertLogs(level=logging.DEBUG) as m_logs: connect.side_effect = asyncio.CancelledError() try: async with LibPulse('libpulse-test') as lib_pulse: pass except LibPulseStateError as e: self.assertEqual(e.args[0], ('PA_CONTEXT_UNCONNECTED', 'PA_OK')) else: self.fail('LibPulseStateError has not been raised') async def test_cancel_main(self): async def main(main_ready): try: async with LibPulse('libpulse-test') as lib_pulse: main_ready.set_result(True) ready = lib_pulse.loop.create_future() try: await ready except asyncio.CancelledError: lib_pulse.state = error_state raise except LibPulseStateError as e: return e except Exception: return None error_state = ('PA_CONTEXT_FAILED', 'PA_ERR_KILLED') loop = asyncio.get_running_loop() main_ready = loop.create_future() main_task = asyncio.create_task(main(main_ready)) await main_ready main_task.cancel() await main_task result = main_task.result() self.assertTrue(isinstance(result, LibPulseStateError)) self.assertEqual(result.args[0], error_state) async def test_fail_instance(self): with self.assertLogs(level=logging.DEBUG) as m_logs,\ self.assertRaises(LibPulseClosedError): async with LibPulse('libpulse-test') as lib_pulse: LibPulse.ASYNCIO_LOOPS = dict() async with LoadModule(lib_pulse, 'module-null-sink', MODULE_ARG): pass async def test_fail_connect(self): # This test assumes that the libpulse library calls # _context_state_callback() at least twice when connecting to the # library. with mock.patch.object(libpulse_module, 'pa_context_get_state') as connect,\ self.assertLogs(level=logging.DEBUG) as m_logs: connect.side_effect = [ PA_CONTEXT_READY, # connected PA_CONTEXT_READY, # ignored state PA_CONTEXT_READY, # idem PA_CONTEXT_FAILED, # connection failure ] async with LibPulse('libpulse-test') as lib_pulse: wait_forever = lib_pulse.loop.create_future() try: await wait_forever except asyncio.CancelledError: # Ensure that lib_pulse._close() does call # pa_context_disconnect(). lib_pulse.state = ('PA_CONTEXT_READY', 'PA_OK') else: self.fail('wait_forever has not been cancelled as expected') self.assertTrue(search_in_logs(m_logs.output, 'libpuls', re.compile('LibPulse instance .* aborted:.*PA_CONTEXT_FAILED'))) async def test_bad_args(self): with self.assertRaises(LibPulseArgumentError): async with LibPulse('my libpulse') as lib_pulse: await lib_pulse.pa_context_get_server_info('bad arg') class LibPulseClassTests(TestCase): """Check that the async methods signatures and their callbacks's match.""" def check_method(self, method): sig = signature(method) self.assertEqual(sig[0], 'pa_operation *') self.assertEqual(sig[1][-1], 'void *') self.assertTrue(sig[1][-2].endswith('_cb_t')) # The callback. callback_sig = signature(sig[1][-2]) self.assertEqual(callback_sig[0], 'void') self.assertEqual(callback_sig[1][-1], 'void *') return sig, callback_sig def test_context_methods(self): for method in LibPulse.context_methods: self.check_method(method) def test_context_success_methods(self): for method in LibPulse.context_success_methods: sig, callback_sig = self.check_method(method) self.assertEqual(sig[1][-2], 'pa_context_success_cb_t') def test_context_list_methods(self): for method in LibPulse.context_list_methods: sig, callback_sig = self.check_method(method) self.assertEqual(callback_sig[1][-2], 'int') self.assertTrue(callback_sig[1][-3].endswith(' *')) def test_stream_success_methods(self): for method in LibPulse.stream_success_methods: sig, callback_sig = self.check_method(method) self.assertEqual(sig[1][-2], 'pa_stream_success_cb_t') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716564704.5961783 libpulse-0.2/libpulse/tests/test_libpulse_ctypes.py0000644000000000000000000000220114624131341017712 0ustar00"""libpulse_ctypes test cases.""" import io from unittest import TestCase, mock from contextlib import redirect_stdout # Load the tests in the order they are declared. from . import requires_resources, load_ordered_tests import libpulse.libpulse_ctypes as libpulse_ctypes_module from ..libpulse_ctypes import PulseCTypes, PulseCTypesLibError @requires_resources('libpulse') class LibPulseCTypesTestCase(TestCase): def test_print_types(self): with redirect_stdout(io.StringIO()) as output: libpulse_ctypes_module.print_types(['types', 'structs', 'callbacks', 'prototypes']) output = output.getvalue() self.assertIn('pa_io_event_flags_t', output) self.assertIn('pa_server_info', output) self.assertIn('io_new', output) self.assertIn('pa_context_new', output) def test_missing_lib(self): with mock.patch.object(libpulse_ctypes_module, 'find_library') as find_library,\ self.assertRaises(PulseCTypesLibError): find_library.return_value = None PulseCTypes() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716642430.3448575 libpulse-0.2/pyproject.toml0000644000000000000000000000153014624361176013045 0ustar00[build-system] requires = ["flit_core >=3.2,<4"] build-backend = "flit_core.buildapi" [project] name = "libpulse" readme = "README.rst" requires-python = ">=3.8,<4" license = {file = "LICENSE"} authors = [{name = "Xavier de Gaye", email = "xdegaye@gmail.com"}] keywords = ["pulseaudio", "pipewire", "asyncio", "ctypes"] classifiers = [ "Development Status :: 2 - Pre-Alpha", "Framework :: AsyncIO", "License :: OSI Approved :: MIT License", "Operating System :: Unix", "Programming Language :: Python :: 3", "Topic :: Multimedia :: Sound/Audio :: Players", ] dynamic = ["version", "description"] [project.urls] Documentation = "https://libpulse.readthedocs.io/en/stable/" Source = "https://gitlab.com/xdegaye/libpulse" Changelog = "https://libpulse.readthedocs.io/en/stable/history.html" [tool.flit.module] name = "libpulse" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1667661560.3930001 libpulse-0.2/tools/__init__.py0000644000000000000000000000000014331477370013370 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716476301.9211593 libpulse-0.2/tools/libpulse_parser.py0000644000000000000000000001563714623654616015056 0ustar00""" Preprocess and parse the libpulse headers. Function signatures are found when parsing: - 'types' (callbacks). - 'structs' (arrays of function pointers). - 'functions'. - as argument of a function when this is a function pointer itself, for example in 'pa_mainloop_api_once'. """ import os import sys import textwrap import shutil import subprocess import pprint from pyclibrary.c_library import CParser PULSEAUDIO_H = '/usr/include/pulse/pulseaudio.h' class ParseError(Exception): pass def dedent(txt): """A dedent that does not use the first line to compute the margin.""" lines = txt.splitlines(keepends=True) # Find the first non empty line, skipping the first line. idx = 1 for i, l in enumerate(lines[1:]): if l != '\n': idx = i + 1 break return ''.join(lines[:idx]) + textwrap.dedent(''.join(lines[idx:])) def preprocess(header, pathname): with open(pathname, 'w') as f: proc = subprocess.run(['gcc', '-E', '-P', header], stdout=f, text=True) print(f"'{pathname}' created") def get_parser(): if shutil.which('gcc') is None: print('*** Error: GNU gcc is required', file=sys.stderr) sys.exit(1) if not os.path.exists(PULSEAUDIO_H): print(f'*** Error: {PULSEAUDIO_H} does not exist', file=sys.stderr) sys.exit(1) dirname = os.path.dirname(__file__) pulse_cpp = os.path.join(dirname, 'libpulse.cpp') pulse_cache = os.path.join(dirname, 'libpulse.cache') if not os.path.exists(pulse_cpp): preprocess(PULSEAUDIO_H, pulse_cpp) cache_built = False if not os.path.exists(pulse_cache): print(f"Building PyCLibrary cache file '{pulse_cache}'.") print('Please wait, this may take a while ...') cache_built = True parser = CParser([pulse_cpp], cache=pulse_cache) if cache_built: print(f"'{pulse_cache}' created") return parser def lib_generator(parser, type): return ((name, item) for (name, item) in parser.defs[type].items() if name.startswith('pa_')) def signature_index(type_instance): # A Type instance is a function signature when one of its members is a # tuple. sig_idx = 0 for i, item in enumerate(type_instance): if i == 0: # The type name. continue if isinstance(item, tuple): sig_idx = i break return sig_idx def get_type(type_instance): items = [] for item in type_instance: # An array. if (isinstance(item, list) and len(item) == 1 and isinstance(item[0], int)): count = int(item[0]) if count > 0: item = f'* {count}' else: item = '*' items.append(item) return ' '.join(items) def parse_types(parser): types = {} callbacks = {} for name, type_instance in lib_generator(parser, 'types'): if signature_index(type_instance): # Signature of a function pointer. callbacks[name] = (type_instance[0], list(get_type(s[1]) for s in type_instance[1])) else: types[name] = type_instance[0] return types, callbacks def parse_enums(parser): enums = {} for name, enum in lib_generator(parser, 'enums'): enums[name] = enum return enums def parse_array(struct): array = {} for member in struct.members: name = member[0] type_instance = member[1] sig_idx = signature_index(type_instance) if sig_idx: # A structure member as a function pointer. # The signature has a variable length as the return type is not a # Type instance as usual but a variable number of str elements. restype = ' '.join(type_instance[:sig_idx]) arg_types = list(get_type(s[1]) for s in type_instance[sig_idx]) array[name] = (restype, arg_types) else: # A structure member as a plain type. array[name] = get_type(type_instance) continue return array def parse_structs(parser): structs ={} arrays = {} try: for name, struct in lib_generator(parser, 'structs'): # An array of function pointers. if name in ('pa_mainloop_api', 'pa_spawn_api'): arrays[name] = parse_array(struct) continue result = [] for member in struct.members: result.append((member[0], get_type(member[1]))) structs[name] = tuple(result) except Exception as e: raise ParseError(f"Structure '{name}': {struct}") from e return structs, arrays def parse_functions(parser): functions = {} for name, func in lib_generator(parser, 'functions'): assert signature_index(func) try: restype = " ".join(func[0]) arg_types = [] for arg in func[1]: type_instance = arg[1] if signature_index(type_instance): # Signature of a function pointer. arg_types.append((type_instance[0], list(get_type(s[1]) for s in type_instance[1]))) else: arg_types.append(get_type(type_instance)) functions[name] = (restype, arg_types) except Exception as e: raise ParseError(f"Function '{name}': {func}") from e return functions def main(): parser = get_parser() types, callbacks = parse_types(parser) enums = parse_enums(parser) structs, arrays = parse_structs(parser) signatures = parse_functions(parser) # The files of the parsed sections are written to 'dirname'. dirname = '.' if len(sys.argv) > 1: dirname = sys.argv[1] if not os.path.isdir(dirname): print(f"*** Error: '{dirname}' is not a directory", file=sys.stderr) sys.exit(1) # Merge all signatures into a 'functions' dictionary. functions = {} functions['signatures'] = {} functions['signatures'].update(signatures) functions['callbacks'] = callbacks for name, signature in arrays['pa_mainloop_api'].items(): if name != 'userdata': functions['callbacks'][name] = signature # Create the parsed sections files. for name in ('types', 'enums', 'structs', 'functions'): pulse_name = 'pulse_' + name doc = f'''"""File {pulse_name}. This file has been generated by libpulse_parser.py - DO NOT MODIFY. """ ''' pathname = os.path.join(dirname, pulse_name + '.py') with open(pathname, 'w') as f: print(dedent(doc), file=f) print(f'{pulse_name} = ', file=f, end='\\\n') pprint.pprint(eval(name), stream=f) print(f"'{pathname}' created") if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1717074252.1919267 libpulse-0.2/tools/set_devpt_version_name.py0000644000000000000000000000134514626074514016410 0ustar00"""Update the version using 'git describe'.""" import sys import re import subprocess INIT_FILE = 'libpulse/__init__.py' def main(): regexp = re.compile(r"(__version__\s+=\s+)'([^']+)'") version = subprocess.check_output(['git', 'describe']) version = version.decode().strip() with open(INIT_FILE) as f: txt = f.read() new_txt = regexp.sub(rf"\1'{version}'", txt) if new_txt != txt: with open(INIT_FILE, 'w') as f: f.write(new_txt) print(f"{INIT_FILE} has been updated with __version__ = '{version}'", file=sys.stderr) else: print(f'*** Error: fail to update {INIT_FILE}', file=sys.stderr) sys.exit(1) if __name__ == '__main__': main() libpulse-0.2/PKG-INFO0000644000000000000000000000466600000000000011172 0ustar00Metadata-Version: 2.1 Name: libpulse Version: 0.2 Summary: Asyncio interface to the Pulseaudio and Pipewire pulse library. Keywords: pulseaudio,pipewire,asyncio,ctypes Author-email: Xavier de Gaye Requires-Python: >=3.8,<4 Description-Content-Type: text/x-rst Classifier: Development Status :: 2 - Pre-Alpha Classifier: Framework :: AsyncIO Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: Unix Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Multimedia :: Sound/Audio :: Players Project-URL: Changelog, https://libpulse.readthedocs.io/en/stable/history.html Project-URL: Documentation, https://libpulse.readthedocs.io/en/stable/ Project-URL: Source, https://gitlab.com/xdegaye/libpulse Asyncio interface to the Pulseaudio and Pipewire pulse library. `libpulse`_ is a Python project based on `asyncio`_, that uses `ctypes`_ to interface with the ``pulse`` library of the PulseAudio and PipeWire sound servers. The interface is meant to be complete. That is, all the constants, structures, plain functions and async functions are made available by importing the libpulse module of the libpulse package. Calling an async function is simple: .. code-block:: python import asyncio from libpulse.libpulse import LibPulse async def main(): async with LibPulse('my libpulse') as lib_pulse: server_info = await lib_pulse.pa_context_get_server_info() print(server_info) asyncio.run(main()) Another example processing sink-input events: .. code-block:: python import asyncio from libpulse.libpulse import LibPulse, PA_SUBSCRIPTION_MASK_SINK_INPUT async def main(): async with LibPulse('my libpulse') as lib_pulse: await lib_pulse.pa_context_subscribe( PA_SUBSCRIPTION_MASK_SINK_INPUT) iterator = lib_pulse.get_events() async for event in iterator: some_function_to_process_the_event(event) asyncio.run(main()) See the libpulse `documentation`_. Requirements ============ Python version 3.8 or more recent. Installation ============ Install ``libpulse`` with pip:: $ python -m pip install libpulse .. _libpulse: https://gitlab.com/xdegaye/libpulse .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _ctypes: https://docs.python.org/3/library/ctypes.html .. _documentation: https://libpulse.readthedocs.io/en/stable/