././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1677078815.4854946
libpulse-0.2/.coveragerc 0000644 0000000 0000000 00000000136 14375430437 012253 0 ustar 00 [run]
omit = */tests/*
tools/*
[report]
exclude_lines =
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716476301.9144926
libpulse-0.2/.gitignore 0000644 0000000 0000000 00000000121 14623654616 012117 0 ustar 00 /dist/
/docs/build/
/.coverage
.emacs.desktop*
*.pyc
libpulse.cpp
libpulse.cache
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716641548.6308718
libpulse-0.2/.gitlab-ci.yml 0000644 0000000 0000000 00000003412 14624357415 012567 0 ustar 00 # 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\%)$/'
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716640796.5455117
libpulse-0.2/.gitlab/issue_templates/Default.md 0000644 0000000 0000000 00000000736 14624356035 016572 0 ustar 00 #### Bug report.
### Your environment.
- Pulseaudio version:
- Pipewire version:
### Steps to reproduce.
### Relevant logs or configuration.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1672656718.6084452
libpulse-0.2/.readthedocs.yaml 0000644 0000000 0000000 00000000564 14354533517 013366 0 ustar 00 # .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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1646844371.9250107
libpulse-0.2/LICENSE 0000644 0000000 0000000 00000002071 14212154724 011127 0 ustar 00 The 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.
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1717933276.452922
libpulse-0.2/README.rst 0000644 0000000 0000000 00000003272 14631312334 011613 0 ustar 00 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/
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1681392950.730833
libpulse-0.2/docs/Makefile 0000644 0000000 0000000 00000001176 14416002467 012521 0 ustar 00 # 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)
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1672657372.279913
libpulse-0.2/docs/requirements.txt 0000644 0000000 0000000 00000000046 14354534734 014350 0 ustar 00 sphinx >= 5.3
sphinx_rtd_theme >= 1.1
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1718355901.009433
libpulse-0.2/docs/source/_static/coverage.svg 0000644 0000000 0000000 00000001661 14633003675 016325 0 ustar 00
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1716641754.305358
libpulse-0.2/docs/source/conf.py 0000644 0000000 0000000 00000002500 14624357732 013661 0 ustar 00 # 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 = []
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1717859591.004651
libpulse-0.2/docs/source/development.rst 0000644 0000000 0000000 00000010653 14631072407 015435 0 ustar 00 .. _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`_.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1718263772.6817298
libpulse-0.2/docs/source/history.rst 0000644 0000000 0000000 00000001051 14632517735 014615 0 ustar 00 Release 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.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1717081054.8670363
libpulse-0.2/docs/source/index.rst 0000644 0000000 0000000 00000000544 14626111737 014224 0 ustar 00 .. 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
././@PaxHeader 0000000 0000000 0000000 00000000032 00000000000 010210 x ustar 00 26 mtime=1718290939.19093
libpulse-0.2/docs/source/interface.rst 0000644 0000000 0000000 00000023547 14632604773 015071 0 ustar 00 Interface
=========
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.
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1718265550.993823
libpulse-0.2/examples/pa_context_load_module.py 0000644 0000000 0000000 00000005766 14632523317 017044 0 ustar 00 """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())
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1718291224.8351173
libpulse-0.2/examples/pa_context_subscribe.py 0000644 0000000 0000000 00000004456 14632605431 016532 0 ustar 00 """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())
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1718086413.4566782
libpulse-0.2/examples/pa_stream_new.py 0000644 0000000 0000000 00000004774 14631765415 015164 0 ustar 00 """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())
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1718355824.7361233
libpulse-0.2/libpulse/__init__.py 0000644 0000000 0000000 00000000504 14633003561 014050 0 ustar 00 """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)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1718266917.8170524
libpulse-0.2/libpulse/libpulse.py 0000644 0000000 0000000 00000070374 14632526046 014153 0 ustar 00 """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()}')
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1716476301.917826
libpulse-0.2/libpulse/libpulse_ctypes.py 0000644 0000000 0000000 00000024006 14623654616 015536 0 ustar 00 """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()
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1716476301.917826
libpulse-0.2/libpulse/mainloop.py 0000644 0000000 0000000 00000023102 14623654616 014142 0 ustar 00 """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')
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716643049.5916257
libpulse-0.2/libpulse/pulse_enums.py 0000644 0000000 0000000 00000033560 14624362352 014666 0 ustar 00 """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}}
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716643049.6016257
libpulse-0.2/libpulse/pulse_functions.py 0000644 0000000 0000000 00000211206 14624362352 015542 0 ustar 00 """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'])}}
././@PaxHeader 0000000 0000000 0000000 00000000032 00000000000 010210 x ustar 00 26 mtime=1718093606.29251
libpulse-0.2/libpulse/pulse_structs.py 0000644 0000000 0000000 00000023624 14632003446 015241 0 ustar 00 """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'))}
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716643049.5882926
libpulse-0.2/libpulse/pulse_types.py 0000644 0000000 0000000 00000005631 14624362352 014701 0 ustar 00 """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'}
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1716638522.346292
libpulse-0.2/libpulse/tests/__init__.py 0000644 0000000 0000000 00000004726 14624351472 015233 0 ustar 00 import 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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1717953453.9297345
libpulse-0.2/libpulse/tests/test_libpulse.py 0000644 0000000 0000000 00000024753 14631361656 016357 0 ustar 00 """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')
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716564704.5961783
libpulse-0.2/libpulse/tests/test_libpulse_ctypes.py 0000644 0000000 0000000 00000002201 14624131341 017712 0 ustar 00 """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()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716642430.3448575
libpulse-0.2/pyproject.toml 0000644 0000000 0000000 00000001530 14624361176 013045 0 ustar 00 [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"
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1667661560.3930001
libpulse-0.2/tools/__init__.py 0000644 0000000 0000000 00000000000 14331477370 013370 0 ustar 00 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1716476301.9211593
libpulse-0.2/tools/libpulse_parser.py 0000644 0000000 0000000 00000015637 14623654616 015056 0 ustar 00 """ 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()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1717074252.1919267
libpulse-0.2/tools/set_devpt_version_name.py 0000644 0000000 0000000 00000001345 14626074514 016410 0 ustar 00 """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-INFO 0000644 0000000 0000000 00000004666 00000000000 011172 0 ustar 00 Metadata-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/