aboutsummaryrefslogtreecommitdiff
path: root/pacmd.py
blob: 135c34130d2df1c051d170fa50c2803e86705541 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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)