Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-pulsectl for openSUSE:Factory
checked in at 2021-04-17 00:02:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pulsectl (Old)
and /work/SRC/openSUSE:Factory/.python-pulsectl.new.12324 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pulsectl"
Sat Apr 17 00:02:13 2021 rev:7 rq:886021 version:21.3.4
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pulsectl/python-pulsectl.changes
2021-03-05 13:45:15.799621485 +0100
+++
/work/SRC/openSUSE:Factory/.python-pulsectl.new.12324/python-pulsectl.changes
2021-04-17 00:02:31.141665414 +0200
@@ -1,0 +2,7 @@
+Fri Apr 16 13:32:15 UTC 2021 - Mark??ta Machov?? <[email protected]>
+
+- update to 21.3.4
+ * Add timeout= option for connect() method [#48].
+ * Expose "base_volume" (alsa volume level) attribute for sinks/sources [#47].
+
+-------------------------------------------------------------------
Old:
----
pulsectl-20.2.4.tar.gz
New:
----
pulsectl-21.3.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pulsectl.spec ++++++
--- /var/tmp/diff_new_pack.nERxGN/_old 2021-04-17 00:02:31.637666199 +0200
+++ /var/tmp/diff_new_pack.nERxGN/_new 2021-04-17 00:02:31.641666205 +0200
@@ -19,23 +19,23 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%bcond_without test
Name: python-pulsectl
-Version: 20.2.4
+Version: 21.3.4
Release: 0
Summary: Python high-level interface and ctypes-based bindings for
PulseAudio (libpulse)
License: MIT
Group: Development/Languages/Python
-URL: http://github.com/mk-fg/python-pulse-control
+URL: https://github.com/mk-fg/python-pulse-control
Source:
https://files.pythonhosted.org/packages/source/p/pulsectl/pulsectl-%{version}.tar.gz
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+Requires: python-setuptools
+BuildArch: noarch
%if 0%{?sle_version} && 0%{?sle_version} < 150300
Requires: pulseaudio
%else
Requires: pulseaudio-daemon
%endif
-Requires: python-setuptools
-BuildArch: noarch
%if %{with test}
BuildRequires: libpulse-devel
BuildRequires: pulseaudio
@@ -61,7 +61,7 @@
%if %{with test}
%check
-%python_exec -m unittest pulsectl.tests.all
+%pyunittest pulsectl.tests.all
%endif
%files %{python_files}
++++++ pulsectl-20.2.4.tar.gz -> pulsectl-21.3.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/CHANGES.rst
new/pulsectl-21.3.4/CHANGES.rst
--- old/pulsectl-20.2.4/CHANGES.rst 2020-02-27 16:31:58.000000000 +0100
+++ new/pulsectl-21.3.4/CHANGES.rst 2021-03-08 06:31:56.000000000 +0100
@@ -8,10 +8,18 @@
Each entry is a package version which change first appears in, followed by
description of the change itself.
-Last synced/updated: 20.2.4
+Last synced/updated: 21.3.4
---------------------------------------------------------------------------
+- 21.3.4: Add timeout= option for connect() method [#48].
+
+- 21.3.2: Expose "base_volume" (alsa volume level) attribute for sinks/sources
[#47].
+
+- 21.3.1: There is now https://pypi.org/project/pulsectl-asyncio/ module [#46].
+
+ It is maintained separately, and should provide similar bindings to use with
async apps.
+
- 20.2.4: Add pulse.get_card_by_name() wrapper [#38].
- 20.2.2: Expose "corked" bool attr in PulseSinkInputInfo and
PulseSourceOutputInfo [#37].
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/PKG-INFO new/pulsectl-21.3.4/PKG-INFO
--- old/pulsectl-20.2.4/PKG-INFO 2020-02-27 17:20:20.519531700 +0100
+++ new/pulsectl-21.3.4/PKG-INFO 2021-03-08 06:38:22.525997900 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: pulsectl
-Version: 20.2.4
+Version: 21.3.4
Summary: Python high-level interface and ctypes-based bindings for PulseAudio
(libpulse)
Home-page: http://github.com/mk-fg/python-pulse-control
Author: George Filipkin, Mike Kazantsev
@@ -9,14 +9,19 @@
Description: python-pulse-control (pulsectl module)
======================================
- Python (3.x and 2.x) high-level interface and ctypes-based bindings for
- PulseAudio_ (libpulse), mostly focused on mixer-like controls and
- introspection-related operations (as opposed to e.g. submitting sound
samples to
- play, player-like client).
+ Python (3.x and 2.x) blocking high-level interface and ctypes-based
bindings
+ for PulseAudio_ (libpulse), to use in a simple synchronous code.
+
+ Wrappers are mostly for mixer-like controls and introspection-related
operations,
+ as opposed to e.g. submitting sound samples to play and player-like
client.
+
+ For async version to use with asyncio_, see `pulsectl-asyncio`_
project instead.
Originally forked from pulsemixer_ project, which had this code
bundled.
.. _PulseAudio: https://wiki.freedesktop.org/www/Software/PulseAudio/
+ .. _asyncio: https://docs.python.org/3/library/asyncio.html
+ .. _pulsectl-asyncio: https://pypi.org/project/pulsectl-asyncio/
.. _pulsemixer: https://github.com/GeorgeFilipkin/pulsemixer/
|
@@ -259,7 +264,7 @@
````````````````````````````
libpulse clients always work as an event loop, though this module
kinda hides
- it, presenting a more conventional blocking interface.
+ it, presenting a more old-style blocking interface.
So what happens on any call (e.g. ``pulse.mute(...)``) is:
@@ -271,8 +276,8 @@
and second step here.
Which means that any pulse calls from callback function can't be used
when
- ``event_listen()`` (or any other pulse call through this module, for
that
- matter) waits for return value and runs libpulse loop already.
+ ``event_listen()`` (or any other pulse call through this module, for
that matter)
+ waits for return value and runs libpulse loop already.
One can raise PulseLoopStop exception there to make ``event_listen()``
return,
run whatever pulse calls after that, then re-start the
``event_listen()`` thing.
@@ -287,15 +292,13 @@
to create a mutex around step-2 (run event loop) from the list above,
so
multiple threads won't do it at the same time.
- For proper eventloop integration (think twisted or asyncio),
``_pulse_get_list``
- / ``_pulse_method_call`` wrappers should be overidden to not run pulse
loop, but
- rather return "future" object and register a set of fd's (as passed to
- ``set_poll_func`` callback) with eventloop.
- Never needed that, so not implemented in the module, but should be
rather easy
- to implement on top of it, as described.
+ For proper python eventloop integration (think twisted or asyncio),
+ use `pulsectl-asyncio`_ module instead.
- See more info/discussion and code examples in `github #11
- <https://github.com/mk-fg/python-pulse-control/issues/11>`_.
+ There are also some tricks mentioned in `github #11
+ <https://github.com/mk-fg/python-pulse-control/issues/11>`_ to
shoehorn this
+ module into async apps, but even with non-asyncio eventloop, starting
from
+ pulsectl-asyncio would probably be much easier.
Tests
@@ -408,6 +411,8 @@
* pulsemixer_ - initial source for this project (embedded in the tool).
+ * `pulsectl-asyncio`_ - similar libpulse wrapper to this one, but for
async python code.
+
* `libpulseaudio
<https://github.com/thelinuxdude/python-pulseaudio/>`_ -
different libpulse bindings module, more low-level, auto-generated
from
pulseaudio header files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/README.rst
new/pulsectl-21.3.4/README.rst
--- old/pulsectl-20.2.4/README.rst 2019-09-24 01:03:43.000000000 +0200
+++ new/pulsectl-21.3.4/README.rst 2021-02-26 17:12:09.000000000 +0100
@@ -1,14 +1,19 @@
python-pulse-control (pulsectl module)
======================================
-Python (3.x and 2.x) high-level interface and ctypes-based bindings for
-PulseAudio_ (libpulse), mostly focused on mixer-like controls and
-introspection-related operations (as opposed to e.g. submitting sound samples
to
-play, player-like client).
+Python (3.x and 2.x) blocking high-level interface and ctypes-based bindings
+for PulseAudio_ (libpulse), to use in a simple synchronous code.
+
+Wrappers are mostly for mixer-like controls and introspection-related
operations,
+as opposed to e.g. submitting sound samples to play and player-like client.
+
+For async version to use with asyncio_, see `pulsectl-asyncio`_ project
instead.
Originally forked from pulsemixer_ project, which had this code bundled.
.. _PulseAudio: https://wiki.freedesktop.org/www/Software/PulseAudio/
+.. _asyncio: https://docs.python.org/3/library/asyncio.html
+.. _pulsectl-asyncio: https://pypi.org/project/pulsectl-asyncio/
.. _pulsemixer: https://github.com/GeorgeFilipkin/pulsemixer/
|
@@ -251,7 +256,7 @@
````````````````````````````
libpulse clients always work as an event loop, though this module kinda hides
-it, presenting a more conventional blocking interface.
+it, presenting a more old-style blocking interface.
So what happens on any call (e.g. ``pulse.mute(...)``) is:
@@ -263,8 +268,8 @@
and second step here.
Which means that any pulse calls from callback function can't be used when
-``event_listen()`` (or any other pulse call through this module, for that
-matter) waits for return value and runs libpulse loop already.
+``event_listen()`` (or any other pulse call through this module, for that
matter)
+waits for return value and runs libpulse loop already.
One can raise PulseLoopStop exception there to make ``event_listen()`` return,
run whatever pulse calls after that, then re-start the ``event_listen()``
thing.
@@ -279,15 +284,13 @@
to create a mutex around step-2 (run event loop) from the list above, so
multiple threads won't do it at the same time.
-For proper eventloop integration (think twisted or asyncio),
``_pulse_get_list``
-/ ``_pulse_method_call`` wrappers should be overidden to not run pulse loop,
but
-rather return "future" object and register a set of fd's (as passed to
-``set_poll_func`` callback) with eventloop.
-Never needed that, so not implemented in the module, but should be rather easy
-to implement on top of it, as described.
+For proper python eventloop integration (think twisted or asyncio),
+use `pulsectl-asyncio`_ module instead.
-See more info/discussion and code examples in `github #11
-<https://github.com/mk-fg/python-pulse-control/issues/11>`_.
+There are also some tricks mentioned in `github #11
+<https://github.com/mk-fg/python-pulse-control/issues/11>`_ to shoehorn this
+module into async apps, but even with non-asyncio eventloop, starting from
+pulsectl-asyncio would probably be much easier.
Tests
@@ -400,6 +403,8 @@
* pulsemixer_ - initial source for this project (embedded in the tool).
+* `pulsectl-asyncio`_ - similar libpulse wrapper to this one, but for async
python code.
+
* `libpulseaudio <https://github.com/thelinuxdude/python-pulseaudio/>`_ -
different libpulse bindings module, more low-level, auto-generated from
pulseaudio header files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl/_pulsectl.py
new/pulsectl-21.3.4/pulsectl/_pulsectl.py
--- old/pulsectl-20.2.4/pulsectl/_pulsectl.py 2020-02-27 16:25:08.000000000
+0100
+++ new/pulsectl-21.3.4/pulsectl/_pulsectl.py 2021-03-04 19:56:51.000000000
+0100
@@ -209,7 +209,7 @@
('flags', c_int),
('proplist', POINTER(PA_PROPLIST)),
('configured_latency', PA_USEC_T),
- ('base_volume', c_int),
+ ('base_volume', c_uint32),
('state', c_int),
('n_volume_steps', c_int),
('card', c_uint32),
@@ -256,7 +256,7 @@
('flags', c_int),
('proplist', POINTER(PA_PROPLIST)),
('configured_latency', PA_USEC_T),
- ('base_volume', c_int),
+ ('base_volume', c_uint32),
('state', c_int),
('n_volume_steps', c_int),
('card', c_uint32),
@@ -479,6 +479,8 @@
func_defs = dict(
pa_strerror=([c_int], c_str_p),
pa_runtime_path=([c_str_p], (c_char_p, 'not_null')),
+ pa_operation_unref=[POINTER(PA_OPERATION)],
+
pa_mainloop_new=(POINTER(PA_MAINLOOP)),
pa_mainloop_get_api=([POINTER(PA_MAINLOOP)],
POINTER(PA_MAINLOOP_API)),
pa_mainloop_run=([POINTER(PA_MAINLOOP), POINTER(c_int)], c_int),
@@ -490,9 +492,11 @@
pa_mainloop_set_poll_func=[POINTER(PA_MAINLOOP),
PA_POLL_FUNC_T, c_void_p],
pa_mainloop_quit=([POINTER(PA_MAINLOOP), c_int]),
pa_mainloop_free=[POINTER(PA_MAINLOOP)],
+
pa_signal_init=([POINTER(PA_MAINLOOP_API)], 'int_check_ge0'),
pa_signal_new=([c_int, PA_SIGNAL_CB_T,
POINTER(PA_SIGNAL_EVENT)]),
pa_signal_done=None,
+
pa_context_errno=([POINTER(PA_CONTEXT)], c_int),
pa_context_new=([POINTER(PA_MAINLOOP_API), c_str_p],
POINTER(PA_CONTEXT)),
pa_context_set_state_callback=([POINTER(PA_CONTEXT),
PA_STATE_CB_T, c_void_p]),
@@ -562,7 +566,6 @@
[POINTER(PA_CONTEXT), c_uint32, PA_CLIENT_INFO_CB_T,
c_void_p] ),
pa_context_get_server_info=( 'pa_op',
[POINTER(PA_CONTEXT), PA_SERVER_INFO_CB_T, c_void_p] ),
- pa_operation_unref=[POINTER(PA_OPERATION)],
pa_context_get_card_info_by_index=( 'pa_op',
[POINTER(PA_CONTEXT), c_uint32, PA_CARD_INFO_CB_T,
c_void_p] ),
pa_context_get_card_info_by_name=( 'pa_op',
@@ -581,6 +584,13 @@
[POINTER(PA_CONTEXT), c_uint32,
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
pa_context_subscribe=( 'pa_op',
[POINTER(PA_CONTEXT), c_int, PA_CONTEXT_SUCCESS_CB_T,
c_void_p] ),
+ pa_context_set_subscribe_callback=[POINTER(PA_CONTEXT),
PA_SUBSCRIBE_CB_T, c_void_p],
+ pa_context_play_sample=( 'pa_op',
+ [POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32,
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
+ pa_context_play_sample_with_proplist=( 'pa_op',
+ [ POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32,
+ POINTER(PA_PROPLIST), PA_CONTEXT_SUCCESS_CB_T,
c_void_p ] ),
+
pa_ext_stream_restore_test=( 'pa_op',
[POINTER(PA_CONTEXT), PA_EXT_STREAM_RESTORE_TEST_CB_T,
c_void_p] ),
pa_ext_stream_restore_read=( 'pa_op',
@@ -590,9 +600,12 @@
c_uint, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p ] ),
pa_ext_stream_restore_delete=( 'pa_op',
[POINTER(PA_CONTEXT), POINTER(c_char_p),
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
- pa_context_set_subscribe_callback=[POINTER(PA_CONTEXT),
PA_SUBSCRIBE_CB_T, c_void_p],
+
+ pa_proplist_from_string=([c_str_p], POINTER(PA_PROPLIST)),
pa_proplist_iterate=([POINTER(PA_PROPLIST), POINTER(c_void_p)],
c_str_p),
pa_proplist_gets=([POINTER(PA_PROPLIST), c_str_p], c_str_p),
+ pa_proplist_free=[POINTER(PA_PROPLIST)],
+
pa_channel_map_init_mono=(
[POINTER(PA_CHANNEL_MAP)], (POINTER(PA_CHANNEL_MAP),
'not_null') ),
pa_channel_map_init_stereo=(
@@ -600,8 +613,7 @@
pa_channel_map_snprint=([c_str_p, c_int,
POINTER(PA_CHANNEL_MAP)], c_str_p),
pa_channel_map_parse=(
[POINTER(PA_CHANNEL_MAP), c_str_p],
(POINTER(PA_CHANNEL_MAP), 'not_null') ),
- pa_proplist_from_string=([c_str_p], POINTER(PA_PROPLIST)),
- pa_proplist_free=[POINTER(PA_PROPLIST)],
+
pa_stream_new_with_proplist=(
[ POINTER(PA_CONTEXT), c_str_p,
POINTER(PA_SAMPLE_SPEC),
POINTER(PA_CHANNEL_MAP), POINTER(PA_PROPLIST) ],
@@ -614,12 +626,7 @@
pa_stream_peek=(
[POINTER(PA_STREAM), POINTER(c_void_p),
POINTER(c_int)], 'int_check_ge0' ),
pa_stream_drop=([POINTER(PA_STREAM)], 'int_check_ge0'),
- pa_stream_disconnect=([POINTER(PA_STREAM)], 'int_check_ge0'),
- pa_context_play_sample=( 'pa_op',
- [POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32,
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
- pa_context_play_sample_with_proplist=( 'pa_op',
- [ POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32,
- POINTER(PA_PROPLIST), PA_CONTEXT_SUCCESS_CB_T,
c_void_p ] ) )
+ pa_stream_disconnect=([POINTER(PA_STREAM)], 'int_check_ge0') )
class CallError(Exception): pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl/pulsectl.py
new/pulsectl-21.3.4/pulsectl/pulsectl.py
--- old/pulsectl-20.2.4/pulsectl/pulsectl.py 2020-02-27 16:27:49.000000000
+0100
+++ new/pulsectl-21.3.4/pulsectl/pulsectl.py 2021-03-08 06:20:11.000000000
+0100
@@ -127,6 +127,8 @@
self.proplist[c.force_str(k)] =
c.force_str(c.pa.proplist_gets(struct.proplist, k))
if hasattr(struct, 'volume'):
self.volume =
self._get_wrapper(PulseVolumeInfo)(struct.volume)
+ if hasattr(struct, 'base_volume'):
+ self.base_volume = struct.base_volume /
c.PA_VOLUME_NORM
if hasattr(struct, 'n_ports'):
cls_port = self._get_wrapper(PulsePortInfo)
self.port_list = list(
@@ -385,10 +387,11 @@
c.pa.context_set_state_callback(self._ctx, self._pa_state_cb,
None)
c.pa.context_set_subscribe_callback(self._ctx,
self._pa_subscribe_cb, None)
- def connect(self, autospawn=False, wait=False):
+ def connect(self, autospawn=False, wait=False, timeout=None):
'''Connect to pulseaudio server.
"autospawn" option will start new pulse daemon, if
necessary.
- Specifying "wait" option will make function block until
pulseaudio server appears.'''
+ Specifying "wait" option will make function block until
pulseaudio server appears.
+ "timeout" (in seconds) will raise PulseError if
connection not established within it.'''
if self._loop_closed:
raise PulseError('Eventloop object was already'
' destroyed and cannot be reused from this
instance.')
@@ -398,7 +401,19 @@
if wait: flags |= c.PA_CONTEXT_NOFAIL
try: c.pa.context_connect(self._ctx, self.server, flags, None)
except c.pa.CallError: self.connected = False
- while self.connected is None: self._pulse_iterate()
+ if not timeout: # simplier process
+ while self.connected is None: self._pulse_iterate()
+ else:
+ self._loop_stop, delta, ts_deadline = True, 1,
c.mono_time() + timeout
+ while self.connected is None:
+ delta = ts_deadline - c.mono_time()
+ self._pulse_poll(delta)
+ if delta <= 0: break
+ self._loop_stop = False
+ if not self.connected:
+ c.pa.context_disconnect(self._ctx)
+ while self.connected is not False:
self._pulse_iterate()
+ raise PulseError('Timed-out connecting to
pulseaudio server [{:,.1f}s]'.format(timeout))
if self.connected is False: raise PulseError('Failed to connect
to pulseaudio server')
def disconnect(self):
@@ -490,7 +505,7 @@
ts_deadline = timeout and (ts + timeout)
while True:
delay = max(0, int((ts_deadline - ts) * 1000))
if ts_deadline else -1
- c.pa.mainloop_prepare(loop, delay) # usec
+ c.pa.mainloop_prepare(loop, delay) # delay in ms
c.pa.mainloop_poll(loop)
if self._loop_closed: break # interrupted by
close() or such
c.pa.mainloop_dispatch(loop)
@@ -512,7 +527,7 @@
else: data_list.append(info_cls(info[0]))
def _pulse_get_list(cb_t, pulse_func, info_cls, singleton=False,
index_arg=True):
- def _wrapper(self, index=None):
+ def _wrapper_method(self, index=None):
data = list()
with self._pulse_op_cb(raw=True) as cb:
cb = cb_t(
@@ -525,21 +540,11 @@
if index is not None or singleton:
if not data: raise PulseIndexError(index)
data, = data
- return _wrapper.func(self, data) if _wrapper.func else
data
- _wrapper.func = None
- def _add_wrap_doc(func):
- func.__name__ = '...'
- func.__doc__ = 'Signature: func({})'.format(
- '' if pulse_func.__name__.endswith('_list') or
singleton or not index_arg else 'index' )
- def _decorator_or_method(func_or_self=None, index=None):
- if func_or_self.__class__.__name__ == 'Pulse':
- return _wrapper(func_or_self, index)
- elif func_or_self: _wrapper.func = func_or_self
- assert index is None, index
- return _wrapper
- _add_wrap_doc(_wrapper)
- _add_wrap_doc(_decorator_or_method)
- return _decorator_or_method
+ return data
+ _wrapper_method.__name__ = '...'
+ _wrapper_method.__doc__ = 'Signature: func({})'.format(
+ '' if pulse_func.__name__.endswith('_list') or
singleton or not index_arg else 'index' )
+ return _wrapper_method
get_sink_by_name = _pulse_get_list(
c.PA_SINK_INFO_CB_T,
@@ -780,6 +785,10 @@
self.volume_set(obj, obj.volume)
def volume_get_all_chans(self, obj):
+ # Purpose of this func can be a bit confusing, being here next
to set/change ones
+ '''Get "flat" volume float value for info-object as a mean of
all channel values.
+ Note that this DOES NOT query any kind of updated
values from libpulse,
+ and simply returns value(s) stored in passed
object, i.e. same ones for same object.'''
assert_pulse_object(obj)
return obj.volume.value_flat
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl/tests/dummy_instance.py
new/pulsectl-21.3.4/pulsectl/tests/dummy_instance.py
--- old/pulsectl-20.2.4/pulsectl/tests/dummy_instance.py 2020-02-27
16:30:24.000000000 +0100
+++ new/pulsectl-21.3.4/pulsectl/tests/dummy_instance.py 2021-03-08
06:30:11.000000000 +0100
@@ -2,7 +2,7 @@
from __future__ import unicode_literals, print_function
import itertools as it, operator as op, functools as ft
-import unittest, contextlib, atexit, signal
+import unittest, contextlib, atexit, signal, threading, select, errno
import os, sys, time, subprocess, tempfile, shutil, socket
if sys.version_info.major > 2: unicode = str
@@ -20,6 +20,59 @@
super(adict, self).__init__(*args, **kws)
self.__dict__ = self
+
+def start_sock_delay_thread(*args):
+ # Simple py2/py3 hack to simulate slow network and test conn timeouts
+ thread = threading.Thread(target=_sock_delay_thread, args=args)
+ thread.daemon = True
+ thread.start()
+ return thread
+
+def _sock_delay_thread(
+ ev_ready, ev_done, ev_disco, bind, connect, delay, block=0.1 ):
+ sl = s = c = None
+ try:
+ sl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sl.bind(bind)
+ sl.listen(1)
+ ev_ready.set()
+ sl.settimeout(block)
+ while True:
+ ev_disco.clear()
+ while True:
+ try: s, addr = sl.accept()
+ except socket.timeout: pass
+ else: break
+ if ev_done.is_set(): return
+ ts0 = time.time()
+ c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ c.connect(connect)
+ s.setblocking(False)
+ c.setblocking(False)
+ time.sleep(min(delay, max(0, delay - (time.time() -
ts0))))
+ def _send_data(src, dst, bs=8*2**10):
+ while True:
+ try:
+ buff = src.recv(bs)
+ if not buff: break
+ dst.sendall(buff) # just
assuming it won't get full here
+ except socket.error as err:
+ if err.errno != errno.EAGAIN:
return True
+ break
+ while True:
+ r,w,x = select.select([s,c], [], [s,c], block)
+ if x or ev_done.is_set(): return
+ if ev_disco.is_set(): break
+ if not (r or x): continue
+ if c in r and _send_data(c, s): break
+ if s in r and _send_data(s, c): break
+ s, c = s.close(), c.close()
+ finally:
+ if c: c.close()
+ if s: s.close()
+ if sl: sl.close()
+
+
def dummy_pulse_init(info=None):
if not info: info = adict(proc=None, tmp_dir=None)
try: _dummy_pulse_init(info)
@@ -39,6 +92,7 @@
# t2% PA_TMPDIR=/tmp/pulsectl-tests PA_REUSE=1234,1235 python -m -m
unittest pulsectl.tests.all
env_tmpdir, env_debug, env_reuse = map(
os.environ.get, ['PA_TMPDIR', 'PA_DEBUG', 'PA_REUSE'] )
+ if not os.environ.get('PATH'): os.environ['PATH'] =
'/usr/local/bin:/usr/bin:/bin'
tmp_base = env_tmpdir or info.get('tmp_dir')
if not tmp_base:
@@ -49,8 +103,9 @@
# Pick some random available localhost ports
if not info.get('sock_unix'):
- bind = ( ['127.0.0.1', 0, socket.AF_INET],
- ['::1', 0, socket.AF_INET6], ['127.0.0.1', 0,
socket.AF_INET] )
+ bind = (
+ ['127.0.0.1', 0, socket.AF_INET], ['::1', 0,
socket.AF_INET6],
+ ['127.0.0.1', 0, socket.AF_INET], ['127.0.0.1', 0,
socket.AF_INET] )
for n, spec in enumerate(bind):
if env_reuse:
spec[1] = int(env_reuse.split(':')[n])
@@ -64,15 +119,29 @@
sock_unix='unix:{}'.format(tmp_path('pulse', 'native')),
sock_tcp4='tcp4:{}:{}'.format(bind[0][0], bind[0][1]),
sock_tcp6='tcp6:[{}]:{}'.format(bind[1][0], bind[1][1]),
- sock_tcp_cli=tuple(bind[2][:2]) )
+ sock_tcp_delay='tcp4:{}:{}'.format(bind[2][0],
bind[2][1]),
+ sock_tcp_delay_src=tuple(bind[2][:2]),
+ sock_tcp_delay_dst=tuple(bind[0][:2]),
+ sock_tcp_cli=tuple(bind[3][:2]) )
+
+ if not info.get('sock_delay_thread'):
+ ev_ready, ev_exit, ev_disco = (threading.Event() for n in
range(3))
+ delay = info.sock_delay = 0.5
+ info.sock_delay_thread_ready = ev_ready
+ info.sock_delay_thread_disco = ev_disco
+ info.sock_delay_thread_exit = ev_exit
+ info.sock_delay_thread = start_sock_delay_thread(
+ ev_ready, ev_exit, ev_disco,
+ info.sock_tcp_delay_src, info.sock_tcp_delay_dst, delay
)
if info.proc and info.proc.poll() is not None: info.proc = None
if not env_reuse and not info.get('proc'):
- env = dict(XDG_RUNTIME_DIR=tmp_base, PULSE_STATE_PATH=tmp_base)
+ env = dict( PATH=os.environ['PATH'],
+ XDG_RUNTIME_DIR=tmp_base, PULSE_STATE_PATH=tmp_base )
log_level = 'error' if not env_debug else 'debug'
info.proc = subprocess.Popen(
- [ 'pulseaudio', '--daemonize=no', '--fail',
- '-nF', '/dev/stdin', '--exit-idle-time=-1',
'--log-level={}'.format(log_level) ],
+ ['pulseaudio', '--daemonize=no', '--fail',
+ '-nF', '/dev/stdin', '--exit-idle-time=-1',
'--log-level={}'.format(log_level)],
env=env, stdin=subprocess.PIPE )
bind4, bind6 = info.sock_tcp4.split(':'),
info.sock_tcp6.rsplit(':', 1)
bind4, bind6 = (bind4[1], bind4[2]), (bind6[0].split(':',
1)[1].strip('[]'), bind6[1])
@@ -80,7 +149,6 @@
'module-augment-properties',
'module-default-device-restore',
- 'module-rescue-streams',
'module-always-sink',
'module-intended-roles',
'module-suspend-on-idle',
@@ -126,6 +194,9 @@
except OSError: pass
info.proc.wait()
info.proc = None
+ if info.get('sock_delay_thread'):
+ info.sock_delay_thread_exit.set()
+ info.sock_delay_thread = info.sock_delay_thread.join()
if info.tmp_dir:
shutil.rmtree(info.tmp_dir, ignore_errors=True)
info.tmp_dir = None
@@ -149,6 +220,7 @@
@classmethod
def tearDownClass(cls):
dummy_pulse_cleanup(cls.instance_info)
+ cls.proc = cls.tmp_dir = None
# Fuzzy float comparison is necessary for volume,
@@ -182,6 +254,34 @@
with pulsectl.Pulse('t', server=self.sock_tcp6) as pulse: si6 =
pulse.server_info()
self.assertEqual(vars(si), vars(si6))
+ def test_connect_timeout(self):
+ self.sock_delay_thread_ready.wait(timeout=2)
+ with pulsectl.Pulse('t', server=self.sock_unix) as pulse: si =
pulse.server_info()
+
+ with pulsectl.Pulse('t', server=self.sock_tcp_delay) as pulse:
sid = pulse.server_info()
+ self.assertEqual(vars(si), vars(sid))
+ self.sock_delay_thread_disco.set()
+
+ with pulsectl.Pulse('t', server=self.sock_tcp_delay,
connect=False) as pulse:
+ pulse.connect()
+ sid = pulse.server_info()
+ self.assertEqual(vars(si), vars(sid))
+ self.sock_delay_thread_disco.set()
+
+ with pulsectl.Pulse('t', server=self.sock_tcp_delay,
connect=False) as pulse:
+ pulse.connect(1.0)
+ sid = pulse.server_info()
+ self.assertEqual(vars(si), vars(sid))
+ self.sock_delay_thread_disco.set()
+
+ with pulsectl.Pulse('t', server=self.sock_tcp_delay,
connect=False) as pulse:
+ with self.assertRaises(pulsectl.PulseError):
pulse.connect(timeout=0.1)
+ self.sock_delay_thread_disco.set()
+ pulse.connect(timeout=1.0)
+ sid = pulse.server_info()
+ self.assertEqual(vars(si), vars(sid))
+ self.sock_delay_thread_disco.set()
+
def test_server_info(self):
with pulsectl.Pulse('t', server=self.sock_unix) as pulse:
si, srcs, sinks = pulse.server_info(),
pulse.source_list(), pulse.sink_list()
@@ -344,7 +444,8 @@
pulse.event_callback_set(stream_ev_cb)
paplay = subprocess.Popen(
- ['paplay', '--raw', '/dev/zero'],
env=dict(XDG_RUNTIME_DIR=self.tmp_dir) )
+ ['paplay', '--raw', '/dev/zero'], env=dict(
+ PATH=os.environ['PATH'],
XDG_RUNTIME_DIR=self.tmp_dir ) )
try:
if not stream_started: pulse.event_listen()
self.assertTrue(bool(stream_started))
@@ -443,7 +544,8 @@
pulse.event_callback_set(stream_ev_cb)
paplay = subprocess.Popen(
- ['paplay', '--raw', '/dev/zero'],
env=dict(XDG_RUNTIME_DIR=self.tmp_dir) )
+ ['paplay', '--raw', '/dev/zero'], env=dict(
+ PATH=os.environ['PATH'],
XDG_RUNTIME_DIR=self.tmp_dir ) )
try:
if not stream_started: pulse.event_listen()
stream_idx, = stream_started
@@ -488,7 +590,8 @@
pulse.event_callback_set(stream_ev_cb)
paplay = subprocess.Popen(
- ['paplay', '--raw', '/dev/urandom'],
env=dict(XDG_RUNTIME_DIR=self.tmp_dir) )
+ ['paplay', '--raw', '/dev/urandom'], env=dict(
+ PATH=os.environ['PATH'],
XDG_RUNTIME_DIR=self.tmp_dir ) )
try:
if not stream_started: pulse.event_listen()
stream_idx, = stream_started
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl.egg-info/PKG-INFO
new/pulsectl-21.3.4/pulsectl.egg-info/PKG-INFO
--- old/pulsectl-20.2.4/pulsectl.egg-info/PKG-INFO 2020-02-27
17:20:20.000000000 +0100
+++ new/pulsectl-21.3.4/pulsectl.egg-info/PKG-INFO 2021-03-08
06:38:22.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: pulsectl
-Version: 20.2.4
+Version: 21.3.4
Summary: Python high-level interface and ctypes-based bindings for PulseAudio
(libpulse)
Home-page: http://github.com/mk-fg/python-pulse-control
Author: George Filipkin, Mike Kazantsev
@@ -9,14 +9,19 @@
Description: python-pulse-control (pulsectl module)
======================================
- Python (3.x and 2.x) high-level interface and ctypes-based bindings for
- PulseAudio_ (libpulse), mostly focused on mixer-like controls and
- introspection-related operations (as opposed to e.g. submitting sound
samples to
- play, player-like client).
+ Python (3.x and 2.x) blocking high-level interface and ctypes-based
bindings
+ for PulseAudio_ (libpulse), to use in a simple synchronous code.
+
+ Wrappers are mostly for mixer-like controls and introspection-related
operations,
+ as opposed to e.g. submitting sound samples to play and player-like
client.
+
+ For async version to use with asyncio_, see `pulsectl-asyncio`_
project instead.
Originally forked from pulsemixer_ project, which had this code
bundled.
.. _PulseAudio: https://wiki.freedesktop.org/www/Software/PulseAudio/
+ .. _asyncio: https://docs.python.org/3/library/asyncio.html
+ .. _pulsectl-asyncio: https://pypi.org/project/pulsectl-asyncio/
.. _pulsemixer: https://github.com/GeorgeFilipkin/pulsemixer/
|
@@ -259,7 +264,7 @@
````````````````````````````
libpulse clients always work as an event loop, though this module
kinda hides
- it, presenting a more conventional blocking interface.
+ it, presenting a more old-style blocking interface.
So what happens on any call (e.g. ``pulse.mute(...)``) is:
@@ -271,8 +276,8 @@
and second step here.
Which means that any pulse calls from callback function can't be used
when
- ``event_listen()`` (or any other pulse call through this module, for
that
- matter) waits for return value and runs libpulse loop already.
+ ``event_listen()`` (or any other pulse call through this module, for
that matter)
+ waits for return value and runs libpulse loop already.
One can raise PulseLoopStop exception there to make ``event_listen()``
return,
run whatever pulse calls after that, then re-start the
``event_listen()`` thing.
@@ -287,15 +292,13 @@
to create a mutex around step-2 (run event loop) from the list above,
so
multiple threads won't do it at the same time.
- For proper eventloop integration (think twisted or asyncio),
``_pulse_get_list``
- / ``_pulse_method_call`` wrappers should be overidden to not run pulse
loop, but
- rather return "future" object and register a set of fd's (as passed to
- ``set_poll_func`` callback) with eventloop.
- Never needed that, so not implemented in the module, but should be
rather easy
- to implement on top of it, as described.
+ For proper python eventloop integration (think twisted or asyncio),
+ use `pulsectl-asyncio`_ module instead.
- See more info/discussion and code examples in `github #11
- <https://github.com/mk-fg/python-pulse-control/issues/11>`_.
+ There are also some tricks mentioned in `github #11
+ <https://github.com/mk-fg/python-pulse-control/issues/11>`_ to
shoehorn this
+ module into async apps, but even with non-asyncio eventloop, starting
from
+ pulsectl-asyncio would probably be much easier.
Tests
@@ -408,6 +411,8 @@
* pulsemixer_ - initial source for this project (embedded in the tool).
+ * `pulsectl-asyncio`_ - similar libpulse wrapper to this one, but for
async python code.
+
* `libpulseaudio
<https://github.com/thelinuxdude/python-pulseaudio/>`_ -
different libpulse bindings module, more low-level, auto-generated
from
pulseaudio header files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pulsectl-20.2.4/setup.py new/pulsectl-21.3.4/setup.py
--- old/pulsectl-20.2.4/setup.py 2020-02-27 16:30:38.000000000 +0100
+++ new/pulsectl-21.3.4/setup.py 2021-03-08 06:31:07.000000000 +0100
@@ -13,7 +13,7 @@
setup(
name = 'pulsectl',
- version = '20.2.4',
+ version = '21.3.4',
author = 'George Filipkin, Mike Kazantsev',
author_email = '[email protected]',
license = 'MIT',