You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
6.6 KiB
162 lines
6.6 KiB
from collections import OrderedDict
|
|
|
|
import pulsectl
|
|
import logging
|
|
from ..cycle import OrderedDictCycleAction
|
|
from .naming_map import description
|
|
from .sink_filter import all as sink_filter_all
|
|
from .sink_input_filter import all as sink_input_filter_all
|
|
from functools import partial
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
__all__ = ['PulseCtlDefaultSinkCycleAction']
|
|
|
|
|
|
class PulseProxy:
|
|
class PulseServerInfo:
|
|
def __init__(self, realobj, proxy):
|
|
self.__realobj = realobj
|
|
self.__proxy = proxy
|
|
|
|
@property
|
|
def default_sink_name(self):
|
|
if self.__proxy._fake_default_sink_name is None:
|
|
return self.__realobj.default_sink_name
|
|
elif self.__realobj.default_sink_name == self.__proxy._real_default_sink_name:
|
|
return self.__proxy._fake_default_sink_name
|
|
else:
|
|
return self.__realobj.default_sink_name
|
|
|
|
def __init__(self, name, sink_filter: callable, sink_input_filter: callable):
|
|
self.__pulse = pulsectl.Pulse(name)
|
|
self.__sink_filter = partial(sink_filter, pulse=self)
|
|
self.__sink_input_filter = partial(sink_input_filter, pulse=self)
|
|
self._fake_default_sink_name = None
|
|
self._real_default_sink_name = None
|
|
|
|
def server_info(self):
|
|
return self.PulseServerInfo(self.__pulse.server_info(), self)
|
|
|
|
@property
|
|
def __real_default_sink(self):
|
|
default_sink_name = self.__pulse.server_info().default_sink_name
|
|
return self.__find_sink_by(lambda sink: sink.name == default_sink_name)
|
|
|
|
def sink_default_set(self, value: pulsectl.PulseSinkInfo):
|
|
real_default_sink = self.__real_default_sink
|
|
if not self.__sink_filter(real_default_sink): # Current default sink is filtered out
|
|
self._fake_default_sink_name = value.name # Fake setting of default sink
|
|
self._real_default_sink_name = real_default_sink.name # Save real default sink
|
|
else: # Current default sink is not filtered out
|
|
self.__pulse.sink_default_set(value)
|
|
self._fake_default_sink_name = None
|
|
self._real_default_sink_name = None
|
|
|
|
def sink_list(self):
|
|
return list(filter(self.__sink_filter, self.__pulse.sink_list()))
|
|
|
|
def sink_info(self, *a, **k):
|
|
return self.__pulse.sink_info(*a, **k)
|
|
|
|
def sink_input_list(self):
|
|
return list(filter(self.__sink_input_filter, self.__pulse.sink_input_list()))
|
|
|
|
def sink_input_move(self, *a, **k):
|
|
return self.__pulse.sink_input_move(*a, **k)
|
|
|
|
def __find_sink_by(self, filter_: callable):
|
|
return next(filter(filter_, self.__pulse.sink_list()))
|
|
|
|
|
|
class PulseCtlDefaultSinkCycleAction(OrderedDictCycleAction):
|
|
"""
|
|
A cycle action that allows to select the pulseaudio fallback sink,
|
|
and also moves active sink inputs to that sink.
|
|
"""
|
|
def __init__(self, naming_map: callable = description, sink_filter: callable = sink_filter_all,
|
|
sink_input_filter: callable = sink_input_filter_all):
|
|
"""
|
|
:param naming_map: A function that maps a sink object to a visual representation. Must accept an arbitrary number of keyword arguments
|
|
:param sink_filter: A filter that is applied on all sinks to select the ones that should be shown.
|
|
Must accept an arbitrary number of keyword arguments
|
|
The fallback sink will only be changed when the current fallback sink is not filtered out.
|
|
:param sink_input_filter: A filter that is applied on all sink inputs to select the ones that should be moved to the new fallback sink.
|
|
Must accept an arbitrary number of keyword arguments
|
|
Sink inputs that do not match the filter are never moved
|
|
"""
|
|
self.__od = OrderedDict()
|
|
super().__init__(self.__od)
|
|
self.__pulse = PulseProxy(self.__class__.__name__, sink_filter=sink_filter,
|
|
sink_input_filter=partial(sink_input_filter, sink_filter=sink_filter))
|
|
self.__naming_func = partial(naming_map, pulse=self.__pulse)
|
|
self.__update_items()
|
|
self.current = self.__pulse.server_info().default_sink_name
|
|
|
|
@OrderedDictCycleAction.current.setter
|
|
def current(self, value):
|
|
# if self.current == value:
|
|
# return
|
|
if value not in self.__od:
|
|
return
|
|
|
|
while self.current != value:
|
|
super().next()
|
|
|
|
self.__update_default_sink()
|
|
|
|
def __update_default_sink(self):
|
|
default_sink = self.__od[self.current]
|
|
|
|
self.__pulse.sink_default_set(default_sink)
|
|
logger.debug('%s.__update_default_sink: Set default sink to %r', self.__class__.__name__, default_sink)
|
|
for sink_input in self.__pulse.sink_input_list():
|
|
try:
|
|
self.__pulse.sink_input_move(sink_input.index, default_sink.index)
|
|
logger.debug('%s.__move_sink_inputs: Moved sink input %r to sink %r', self.__class__.__name__,
|
|
sink_input, default_sink)
|
|
except pulsectl.PulseOperationFailed:
|
|
logger.exception('Failed moving sink input %d to sink %d', sink_input.index, default_sink.index)
|
|
|
|
def __update_items(self):
|
|
changed = False
|
|
sinks = self.__pulse.sink_list()
|
|
for sink in sinks:
|
|
if sink.name not in self.__od:
|
|
logger.debug('%s.__update_items: Added sink %r', self.__class__.__name__, sink)
|
|
changed = True
|
|
self.__od[sink.name] = sink
|
|
to_delete = []
|
|
for k, sink in self.__od.items():
|
|
if len(list(filter(lambda s: s.name == sink.name, sinks))) == 0: # Sink from dict not in sinks list
|
|
logger.debug('%s.__update_items: Removed sink %r', self.__class__.__name__, sink)
|
|
to_delete.append(k)
|
|
changed = True
|
|
for k in to_delete:
|
|
del self.__od[k]
|
|
|
|
default_name = self.__pulse.server_info().default_sink_name
|
|
if self.current != default_name:
|
|
logger.debug('%.__update_items: Updated default sink from %s to %s', self.__class__.__name__, self.current, default_name)
|
|
self.current = default_name
|
|
changed = True
|
|
return changed
|
|
|
|
def periodic(self):
|
|
return self.__update_items()
|
|
|
|
@property
|
|
def items(self):
|
|
self.__update_items()
|
|
return map(self.__naming_func, super().items())
|
|
|
|
def prev(self):
|
|
super().prev()
|
|
self.__update_default_sink()
|
|
|
|
def next(self):
|
|
super().next()
|
|
self.__update_default_sink()
|
|
|
|
def __str__(self):
|
|
return self.__naming_func(self.__od[self.current])
|
|
|