Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/sound/pulseaudio/pulseaudio_pactl_util.py : 73%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# This file is part of Xpra.
3# Copyright (C) 2010-2018 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7import sys
8import os.path
10from xpra.sound.pulseaudio.pulseaudio_common_util import get_pulse_server_x11_property, get_pulse_id_x11_property
11from xpra.util import print_nested_dict
12from xpra.os_util import which, WIN32, OSX, bytestostr, strtobytes
14from xpra.log import Logger
15log = Logger("sound")
18pactl_bin = None
19has_pulseaudio = None
21def get_pactl_bin():
22 global pactl_bin
23 if pactl_bin is None:
24 if WIN32 or OSX:
25 pactl_bin = ""
26 else:
27 pactl_bin = which("pactl")
28 return pactl_bin
30def pactl_output(log_errors=True, *pactl_args):
31 pactl_bin = get_pactl_bin()
32 if not pactl_bin:
33 return -1, None, None
34 #ie: "pactl list"
35 cmd = [pactl_bin] + list(pactl_args)
36 #force "C" locale so that we can parse the output as expected
37 env = os.environ.copy()
38 env["LC_ALL"] = "C"
39 try:
40 import subprocess
41 log("running %s", cmd)
42 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
43 from xpra.child_reaper import getChildReaper
44 procinfo = getChildReaper().add_process(process, "pactl", cmd, True, True)
45 log("waiting for %s output", cmd)
46 out, err = process.communicate()
47 getChildReaper().add_dead_process(procinfo)
48 code = process.poll()
49 log("pactl_output%s returned %s", pactl_args, code)
50 return code, out, err
51 except Exception as e:
52 if log_errors:
53 log.error("failed to execute %s: %s", cmd, e)
54 else:
55 log("failed to execute %s: %s", cmd, e)
56 return -1, None, None
58def is_pa_installed():
59 pactl_bin = get_pactl_bin()
60 log("is_pa_installed() pactl_bin=%s", pactl_bin)
61 return bool(pactl_bin)
63def has_pa():
64 global has_pulseaudio
65 if has_pulseaudio is None:
66 has_pulseaudio = get_pulse_server_x11_property() or is_pa_installed()
67 return has_pulseaudio
70def set_source_mute(device, mute=False):
71 code, out, err = pactl_output(True, "set-source-mute", device, str(int(mute)))
72 log("set_source_mute: output=%s, err=%s", out, err)
73 return code==0
75def set_sink_mute(device, mute=False):
76 code, out, err = pactl_output(True, "set-sink-mute", device, str(int(mute)))
77 log("set_sink_mute: output=%s, err=%s", out, err)
78 return code==0
80def get_pactl_info_line(prefix):
81 if not has_pa():
82 return ""
83 code, out, err = pactl_output(False, "info")
84 if code!=0:
85 log.warn("Warning: failed to query pulseaudio using 'pactl info'")
86 if err:
87 for x in err.splitlines():
88 log.warn(" %s", bytestostr(x))
89 return ""
90 stat = ""
91 for line in bytestostr(out).splitlines():
92 if line.startswith(prefix):
93 stat = line[len(prefix):].strip()
94 break
95 log("get_pactl_info_line(%s)=%s", prefix, stat)
96 return stat
98def get_default_sink():
99 return get_pactl_info_line("Default Sink:")
101def get_pactl_server():
102 return get_pactl_info_line("Server String:")
104def get_pulse_cookie_hash():
105 v = get_pactl_info_line("Cookie:")
106 try:
107 import hashlib
108 return strtobytes(hashlib.sha256(strtobytes(v)).hexdigest())
109 except:
110 pass
111 return b""
113def get_pulse_server(may_start_it=True):
114 xp = get_pulse_server_x11_property()
115 if xp or not may_start_it:
116 return xp
117 return get_pactl_server()
119def get_pulse_id():
120 return get_pulse_id_x11_property()
123def get_pa_device_options(monitors=False, input_or_output=None, ignored_devices=("bell-window-system",)):
124 """
125 Finds the list of devices, monitors=False allows us to filter out monitors
126 (which could create sound loops if we use them)
127 set input_or_output=True to get inputs only
128 set input_or_output=False to get outputs only
129 set input_or_output=None to get both
130 Same goes for monitors (False|True|None)
131 Returns the a dict() with the PulseAudio name as key and a description as value
132 """
133 if WIN32 or OSX:
134 return {}
135 status, out, _ = pactl_output(False, "list")
136 if status!=0 or not out:
137 return {}
138 return do_get_pa_device_options(out, monitors, input_or_output, ignored_devices)
140def do_get_pa_device_options(pactl_list_output, monitors=False, input_or_output=None, ignored_devices=("bell-window-system",)):
141 device_class = None
142 device_description = None
143 name = None
144 devices = {}
145 for line in pactl_list_output.splitlines():
146 if not line.startswith(b" ") and not line.startswith(b"\t"): #clear vars when we encounter a new section
147 if name and device_class:
148 if name in ignored_devices:
149 continue
150 #Verify against monitor flag if set:
151 if monitors is not None:
152 is_monitor = device_class==b'"monitor"'
153 if is_monitor!=monitors:
154 continue
155 #Verify against input flag (if set):
156 if input_or_output is not None:
157 is_input = name.lower().find(b"input")>=0
158 if is_input is True and input_or_output is False:
159 continue
160 is_output = name.lower().find(b"output")>=0
161 if is_output is True and input_or_output is True:
162 continue
163 if not device_description:
164 device_description = name
165 devices[name] = device_description
166 name = None
167 device_class = None
168 line = line.strip()
169 if line.startswith(b"Name: "):
170 name = line[len(b"Name: "):]
171 if line.startswith(b"device.class = "):
172 device_class = line[len(b"device-class = "):]
173 if line.startswith(b"device.description = "):
174 device_description = line[len(b"device.description = "):].strip(b'"')
175 return devices
178def get_info():
179 i = 0
180 dinfo = {}
181 status, out, _ = pactl_output(False, "list")
182 if status==0 and out:
183 for monitors in (True, False):
184 for io in (True, False):
185 devices = do_get_pa_device_options(out, monitors, io)
186 for d,name in devices.items():
187 dinfo[bytestostr(d)] = bytestostr(name)
188 i += 1
189 info = {
190 "device" : dinfo,
191 "devices" : i,
192 "pulseaudio" : {
193 "wrapper" : "pactl",
194 "found" : bool(has_pa()),
195 "id" : get_pulse_id(),
196 "server" : get_pulse_server(False),
197 "cookie-hash" : get_pulse_cookie_hash(),
198 }
199 }
200 log("pulseaudio_pactl_util.get_info()=%s", info)
201 return info
203def main():
204 from xpra.os_util import load_binary_file
205 if "-v" in sys.argv:
206 log.enable_debug()
207 sys.argv.remove("-v")
208 if len(sys.argv)>1:
209 for filename in sys.argv[1:]:
210 if not os.path.exists(filename):
211 log.warn("file argument '%s' does not exist, ignoring", filename)
212 continue
213 data = load_binary_file(filename)
214 devices = do_get_pa_device_options(data, True, False)
215 log.info("%s devices found in '%s'", len(devices), filename)
216 print_nested_dict(devices)
217 return
219 i = get_info()
220 print_nested_dict(i)
222if __name__ == "__main__":
223 main()