import subprocess, re # These are not namedtuples because we want them mutable class Section: def __init__(self, name, items): self.name = name # string self.items = items # [Item] class Item: def __init__(self, index, default, ch): self.index = index # int self.default = default # bool self.ch = ch # {key: Node} class Node: def __init__(self, value, ch): self.value = value # None | string | [string] self.ch = ch # {key: Node} class Pacmd: def __init__(self, full_output): lines = full_output.decode("utf-8").split("\n") self.infos = [] # [string] self.sections = [] # [Section] currsect = None curritem = None currpath = [] def close_curritem(): nonlocal curritem, currsect, currpath if curritem: currsect.items.append(curritem) curritem = None currpath = [] def close_currsect(): nonlocal self, currsect if currsect: close_curritem() self.sections.append(currsect) def q_is_key_value(ln): return ln.find("=") != -1 or ln.find(": ") != -1 def q_get_key_value(ln): if ln.find("=") != -1: m = re.search(r'^\s*([^=]*?)\s*=\s*"(.*)"$', ln) else: m = re.search(r"^\s*([^:]*):\s*(.*)$", ln) return m.group(1), m.group(2) for line in lines: line = line.rstrip() if len(line) == 0: continue line = line.replace("\t", " ") m = re.match(r"^( *(\* )?)([^ ].*)", line) is_default = m.group(2) is not None indent = len(m.group(1)) line = m.group(3) # print(currpath) # print(indent, line) if indent == 0: if line[0].isdigit(): close_currsect() name = re.search(r" ([^ ]*)\(s\)", line).group(1) currsect = Section(name, []) else: self.infos.append(line) elif indent == 4: close_curritem() index = re.search(r"index: ([0-9]*)$", line).group(1) curritem = Item(int(index), is_default, {}) elif indent >= 8: assert curritem != None is_aligned = indent % 8 == 0 indent_depth = (indent + 7) // 8 - 1 if is_aligned and indent_depth < len(currpath): currpath = currpath[:indent_depth] parentval, thisval = None, curritem for k in currpath: parentval, thisval = thisval, thisval.ch[k] if q_is_key_value(line): key, value = q_get_key_value(line) thisval.ch[key] = Node(value, {}) currpath.append(key) elif re.search(r":$", line) is not None: key = line[:-1] thisval.ch[key] = Node(None, {}) currpath.append(key) elif indent_depth >= len(currpath): if type(thisval.value) != list: thisval.value = [thisval.value] thisval.value.append(line) else: print("line = " + line) assert False else: print("indent = " + str(indent)) assert False close_currsect() class PacmdError(Exception): pass def pacmd(*args): try: return subprocess.check_output(["pacmd"] + list(args), stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: raise PacmdError(e.output) def _pacmd_parsed(*args): return Pacmd(pacmd(*args)) def list_all(): return _pacmd_parsed("list") def list_sinks(): return _pacmd_parsed("list-sinks") def list_sink_inputs(): return _pacmd_parsed("list-sink-inputs") def list_sources(): return _pacmd_parsed("list-sources") def list_source_outputs(): return _pacmd_parsed("list-source-outputs") def _make_indent(n): return " " * n def dump_section(sect): print("Section " + sect.name + ":") for item in sect.items: print(_make_indent(1) + "Item " + str(item.index) + ":") for key, node in item.ch.items(): dump_node(node, key, 2); def dump_node(node, key, indent): print(_make_indent(indent) + key + ":" + ("" if node.value is None else " " + str(node.value))) for k2, n2 in node.ch.items(): dump_node(n2, k2, indent + 1)