2
# This file is part of Checkbox.
4
# Copyright 2014 Canonical Ltd.
6
# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
8
# Checkbox is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License version 3,
10
# as published by the Free Software Foundation.
12
# Checkbox is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
20
pulse-active-port-change
21
========================
23
This script checks if the active port on either sinks (speakers or headphones)
24
or sources (microphones, webcams) is changed after an appropriate device is
25
plugged into the DUT. The script is fully automatic and either times out after
26
30 seconds or returns as soon as the change is detected.
28
The script monitors pulse audio events with `pactl subscribe`. Any changes to
29
sinks (or sources, depending on the mode) are treated as a possible match. A
30
match is verified by running `pactl list sinks` (or `pactl list sources`) and
31
constructing a set of tuples (sink-source-name, sink-source-active-port,
32
sink-source-availability). Any change to the computed set, as compared to the
33
initially computed set, is considered a match.
35
Due to the algorithm used, it will also detect things like USB headsets, HDMI
36
monitors/speakers, webcams, etc.
38
The script depends on:
39
python3-checkbox-support
49
from checkbox_support.parsers.pactl import parse_pactl_output
52
class AudioPlugDetection:
54
def __init__(self, timeout, mode):
56
self.timeout = timeout
58
# get the un-localized environment
59
env = dict(os.environb)
61
env[b'LANGUAGE'] = b''
62
env[b'LC_ALL'] = b'C.UTF-8'
63
self.unlocalized_env = env
65
signal.signal(signal.SIGALRM, self.on_timeout)
67
def get_sound_config(self):
68
text = subprocess.check_output(
69
["pactl", "list", self.mode], # either 'sources' or 'sinks'
70
env=self.unlocalized_env, universal_newlines=True)
71
doc = parse_pactl_output(text)
73
for record in doc.record_list:
75
port_availability = None
76
# We go through the attribute list once to try to find an active port
77
for attr in record.attribute_list:
78
if attr.name == "Active Port":
79
active_port = attr.value
80
# If there is one, we retrieve its availability flag
82
for attr in record.attribute_list:
83
if attr.name == "Ports":
84
for port in attr.value:
85
if port.name == active_port:
86
port_availability = port.availability
87
cfg.add((record.name, active_port, port_availability))
90
def on_timeout(self, signum, frame):
96
parser = argparse.ArgumentParser(
97
description=__doc__.split("")[0],
98
epilog=__doc__.split("")[1],
99
formatter_class=argparse.RawDescriptionHelpFormatter)
101
'mode', choices=['sinks', 'sources'],
102
help='Monitor either sinks or sources')
104
'-t', '--timeout', type=int, default=30,
105
help='Timeout after which the script fails')
106
ns = parser.parse_args()
107
return cls(ns.timeout, ns.mode).run()
111
if self.mode == 'sinks':
112
look_for = "Event 'change' on sink #"
113
look_for2 = "Event 'change' on server #"
114
elif self.mode == 'sources':
115
look_for = "Event 'change' on source #"
116
look_for2 = "Event 'change' on server #"
119
# Get the initial / baseline configuration
120
initial_cfg = self.get_sound_config()
121
print("Starting with config: {}".format(initial_cfg))
122
print("You have {} seconds to plug something".format(self.timeout))
124
signal.alarm(self.timeout)
125
# run subscribe in a pty as it doesn't fflush() after every event
126
pid, master_fd = pty.fork()
128
os.execlpe("pactl", "pactl", "subscribe", self.unlocalized_env)
130
child_stream = os.fdopen(master_fd, "rt", encoding='UTF-8')
132
for line in child_stream:
133
if line.startswith(look_for) or line.startswith(look_for2):
134
new_cfg = self.get_sound_config()
135
print("Now using config: {}".format(new_cfg))
136
if new_cfg != initial_cfg:
137
print("It seems to work!")
140
except KeyboardInterrupt:
143
os.kill(pid, signal.SIGTERM)
145
return 0 if found else 1
148
if __name__ == "__main__":
149
raise SystemExit(AudioPlugDetection.main())