Hide keyboard shortcuts

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. 

6 

7import sys 

8import os.path 

9 

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 

13 

14from xpra.log import Logger 

15log = Logger("sound") 

16 

17 

18pactl_bin = None 

19has_pulseaudio = None 

20 

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 

29 

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 

57 

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) 

62 

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 

68 

69 

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 

74 

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 

79 

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 

97 

98def get_default_sink(): 

99 return get_pactl_info_line("Default Sink:") 

100 

101def get_pactl_server(): 

102 return get_pactl_info_line("Server String:") 

103 

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"" 

112 

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() 

118 

119def get_pulse_id(): 

120 return get_pulse_id_x11_property() 

121 

122 

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) 

139 

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 

176 

177 

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 

202 

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 

218 

219 i = get_info() 

220 print_nested_dict(i) 

221 

222if __name__ == "__main__": 

223 main()