# -*- coding: utf-8 -*- from __future__ import print_function, unicode_literals import itertools as it, operator as op, functools as ft import re lookup_types = { 'sink': 'sink_list', 'source': 'source_list', 'sink-input': 'sink_input_list', 'source-output': 'source_output_list' } lookup_types.update(it.chain.from_iterable( ((v, lookup_types[k]) for v in v) for k,v in { 'source': ['src'], 'sink-input': ['si', 'playback', 'play'], 'source-output': ['so', 'record', 'rec', 'mic'] }.items() )) lookup_key_defaults = dict( # No default keys for type = no implicit matches for that type sink_input_list=[ # match sink_input_list objects with these keys by default 'media.name', 'media.icon_name', 'media.role', 'application.name', 'application.process.binary', 'application.icon_name' ] ) def pulse_obj_lookup(pulse, obj_lookup, prop_default=None): '''Return set of pulse object(s) with proplist values matching lookup-string. Pattern syntax: [ { 'sink' | 'source' | 'sink-input' | 'source-output' } [ / ... ] ':' ] [ proplist-key-name (non-empty) [ / ... ] ':' ] [ ':' (for regexp match) ] [ proplist-key-value ] Examples: - sink:alsa.driver_name:snd_hda_intel Match sink(s) with alsa.driver_name=snd_hda_intel (exact match!). - sink/source:device.bus:pci Match all sinks and sources with device.bus=pci. - myprop:somevalue Match any object (of all 4 supported types) that has myprop=somevalue. - mpv Match any object with any of the "default lookup props" (!!!) being equal to "mpv". "default lookup props" are specified per-type in lookup_key_defaults above. For example, sink input will be looked-up by media.name, application.name, etc. - sink-input/source-output:mpv Same as above, but lookup streams only (not sinks/sources). Note that "sink-input/source-output" matches type spec, and parsed as such, not as key. - si/so:mpv Same as above - see aliases for types in lookup_types. - application.binary/application.icon:mpv Lookup by multiple keys with "any match" logic, same as with multiple object types. - key\/with\/slashes\:and\:colons:somevalue Lookup by key that has slashes and colons in it. "/" and ":" must only be escaped in the proplist key part, used as-is in values. Backslash itself can be escaped as well, i.e. as "\\". - module-stream-restore.id:sink-input-by-media-role:music Value has ":" in it, but there's no need to escape it in any way. - device.description::Analog Value lookup starting with : is interpreted as a regexp, i.e. any object with device.description *containing* "Analog" in this case. - si/so:application.name::^mpv\b Return all sink-inputs/source-outputs ("si/so") where "application.name" proplist value matches regexp "^mpv\b". - :^mpv\b Regexp lookup (stuff starting with "mpv" word) without type or key specification. For python2, lookup string should be unicode type. "prop_default" keyword arg can be used to specify default proplist value for when key is not found there.''' # \ue000-\uf8ff - private use area, never assigned to symbols obj_lookup = obj_lookup.replace('\\\\', '\ue000').replace('\\:', '\ue001') obj_types_re = '({0})(/({0}))*'.format('|'.join(lookup_types)) m = re.search( ( r'^((?P{}):)?'.format(obj_types_re) + r'((?P.+?):)?' r'(?P.*)$' ), obj_lookup, re.IGNORECASE ) if not m: raise ValueError(obj_lookup) lookup_type, lookup_keys, lookup_re = op.itemgetter('t', 'k', 'v')(m.groupdict()) if lookup_keys: lookup_keys = list( v.replace('\ue000', '\\\\').replace('\ue001', ':').replace('\ue002', '/') for v in lookup_keys.replace('\\/', '\ue002').split('/') ) lookup_re = lookup_re.replace('\ue000', '\\\\').replace('\ue001', '\\:') obj_list_res, lookup_re = list(), re.compile( lookup_re[1:] if lookup_re.startswith(':') else '^{}$'.format(re.escape(lookup_re)) ) for k in set( lookup_types[k] for k in (lookup_type.split('/') if lookup_type else lookup_types.keys()) ): if not lookup_keys: lookup_keys = lookup_key_defaults.get(k) if not lookup_keys: continue obj_list = getattr(pulse, k)() if not obj_list: continue for obj, k in it.product(obj_list, lookup_keys): v = obj.proplist.get(k, prop_default) if v is None: continue if lookup_re.search(v): obj_list_res.append(obj) return set(obj_list_res)