aboutsummaryrefslogtreecommitdiff
path: root/pa.py
blob: e09565e2645b1adbbd0a4e095a0eb90dbabae5b7 (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
163
164
from collections import namedtuple
import pacmd

class SinkSource:
    def __init__(self, pitem, kind_of_thing):
        assert type(pitem) == pacmd.Item
        self._i = pitem
        self._kind_of_thing = kind_of_thing

    def kind(self):
        return self._kind_of_thing

    def name(self):
        return self._i.ch["name"].value

    def description(self):
        return self._i.ch["properties"].ch["device.description"].value

    def default(self):
        return self._i.default

    def index(self):
        return self._i.index

    def state(self):
        return self._i.ch["state"].value

    def muted(self):
        return _parse_muted(self._i)

    def volume(self):
        return _parse_volume(self._i)

    def set_volume(self, vol):
        assert type(vol) == float or type(vol) == int
        vol = round(vol * _get_maxvol(self._i))
        pacmd.pacmd("set-{}-volume".format(self._kind_of_thing),
                    str(self.index()), str(vol))

    def set_muted(self, yes):
        pacmd.pacmd("set-{}-mute".format(self._kind_of_thing),
                    str(self.index()), "true" if yes else "false")

    def set_default(self):
        pacmd.pacmd("set-default-{}".format(self._kind_of_thing),
                    str(self.index()))

class Sink(SinkSource):
    def __init__(self, pitem):
        super().__init__(pitem, "sink")

class Source(SinkSource):
    def __init__(self, pitem):
        super().__init__(pitem, "source")

class InputOutput:
    def __init__(self, pitem, kind_of_thing, linked_kind):
        assert type(pitem) == pacmd.Item
        self._i = pitem
        self._kind_of_thing = kind_of_thing
        self._linked_kind = linked_kind

    # sink-input or source-output
    def kind(self):
        return self._kind_of_thing

    # sink or source
    def linked_kind(self):
        return self._linked_kind

    def name(self):
        try:
            name = self._i.ch["properties"].ch["media.name"].value
            # Append the first that is present, if any
            for key in ["application.name", "application.process.binary"]:
                if key in self._i.ch["properties"].ch:
                    name += " (" + self._i.ch["properties"].ch[key].value + ")"
                    break
        except Exception as e:
            return "???"
        return name

    def linked_index(self):
        return int(self._i.ch[self._linked_kind].value.split()[0])

    # Don't know if this is ever true; it wouldn't make much sense to have a
    # 'default' input or output.
    def default(self):
        return self._i.default

    def index(self):
        return self._i.index

    def driver(self):
        return self._i.ch["driver"].value

    def muted(self):
        return _parse_muted(self._i)

    def volume(self):
        return _parse_volume(self._i)

    def set_volume(self, vol):
        assert type(vol) == float or type(vol) == int
        vol = round(vol * _get_maxvol(self._i))
        pacmd.pacmd("set-{}-volume".format(self._kind_of_thing), str(self.index()), str(vol))

    def set_muted(self, yes):
        pacmd.pacmd("set-{}-mute".format(self._kind_of_thing), str(self.index()), "true" if yes else "false")

    def move_to(self, idx):
        assert type(idx) == int
        pacmd.pacmd("move-{}".format(self._kind_of_thing), str(self.index()), str(idx))

class SinkInput(InputOutput):
    def __init__(self, pitem):
        super().__init__(pitem, "sink-input", "sink")

class SourceOutput(InputOutput):
    def __init__(self, pitem):
        super().__init__(pitem, "source-output", "source")


# TODO: How should one really do this? This is just a collection of hacks that
# accidentally works for all my devices, but will probably fail the moment you
# try another.
def _get_maxvol(item):
    if "volume steps" in item.ch:
        return int(item.ch["volume steps"].value.strip()) - 1
    elif "base volume" in item.ch:
        num, perc = item.ch["base volume"].value.split("/")[0:2]
        num = int(num.strip())
        perc = int(num.replace("%", "").strip())
        return num * (100 / perc)
    else:
        return 65536

def _parse_volume(item):
    vol = [int(x.split(":")[1].split("/")[0].strip())
           for x in item.ch["volume"].value[0].split(",")]
    maxvol = _get_maxvol(item)
    return [v / maxvol for v in vol]

def _parse_muted(item):
    if item.ch["muted"].value == "yes": return True
    elif item.ch["muted"].value == "no": return False
    else: assert False

def _list_things(pacmd_obj, section_name, klass):
    assert len(pacmd_obj.sections) == 1
    assert pacmd_obj.sections[0].name == section_name
    return [klass(item) for item in pacmd_obj.sections[0].items]

def list_sinks():
    return _list_things(pacmd.list_sinks(), "sink", Sink)

def list_sink_inputs():
    return _list_things(pacmd.list_sink_inputs(), "input", SinkInput)

def list_sources():
    return _list_things(pacmd.list_sources(), "source", Source)

def list_source_outputs():
    return _list_things(pacmd.list_source_outputs(), "output", SourceOutput)