1#!/usr/bin/env python3

2# This file is part of Checkbox.

3#

4# Copyright 2023 Canonical Ltd.

5#

6# Checkbox is free software: you can redistribute it and/or modify

7# it under the terms of the GNU General Public License version 3,

8# as published by the Free Software Foundation.

9#

10# Checkbox is distributed in the hope that it will be useful,

11# but WITHOUT ANY WARRANTY; without even the implied warranty of

12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

13# GNU General Public License for more details.

14#

15# You should have received a copy of the GNU General Public License

16# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.

17

18 import sys

19 import re

20

21 from argparse import ArgumentParser

22 from subprocess import check_output, STDOUT, CalledProcessError

23 from checkbox_support.parsers.modinfo import ModinfoParser

24

25 TYPES = ("source", "sink")

26

27 entries_regex = re.compile(r"index.*?(?=device.icon_name)", re.DOTALL)

28 driver_regex = re.compile(r"(?<=driver_name = )\"(.*)\"")

29 name_regex = re.compile(r"(?<=name:).*")

30

31

32 class PacmdAudioDevice:

33 """

34 Class representing an audio device with information gathered from pacmd

35 """

36

37 def __init__(self, name, driver):

38 self._name = name

39 self._driver = driver

40 self._modinfo = self._modinfo_parser(driver)

41 self._driver_version = self._find_driver_ver()

42

43 def __str__(self):

44 retstr = "Device: %s\n" % self._name

45 if self._driver:

46 retstr += "Driver: %s (%s)" % (self._driver, self._driver_version)

47 else:

48 retstr += "Driver: Unknown"

49 return retstr

50

51 def _modinfo_parser(self, driver):

52 cmd = ["/sbin/modinfo", driver]

53 try:

54 stream = check_output(cmd, stderr=STDOUT, universal_newlines=True)

55 except CalledProcessError as err:

56 print("Error running %s:" % cmd, file=sys.stderr)

57 print(err.output, file=sys.stderr)

58 return None

59 if not stream:

60 print("Error: modinfo returned nothing", file=sys.stderr)

61 return None

62 else:

63 parser = ModinfoParser(stream)

64 modinfo = parser.get_all()

65

66 return modinfo

67

68 def _find_driver_ver(self):

69 # try the version field first, then vermagic second, some audio

70 # drivers don't report version if the driver is in-tree

71 if self._modinfo["version"]:

72 return self._modinfo["version"]

73 else:

74 # vermagic will look like this (below) and we only care about the

75 # first part:

76 # "3.2.0-29-generic SMP mod_unload modversions"

77 return self._modinfo["vermagic"].split()[0]

78

79

80 def list_device_info():

81 """

82 Lists information on audio devices including device driver and version

83 """

84

85 retval = 0

86 for vtype in TYPES:

87 try:

88 pacmd_entries = check_output(

89 ["pacmd", "list-%ss" % vtype], universal_newlines=True

90 )

91 except Exception as e:

92 print(

93 "Error when running pacmd list-%ss: %s" % (vtype, e),

94 file=sys.stderr,

95 )

96 return 1

97

98 entries = entries_regex.findall(pacmd_entries)

99 for entry in entries:

100 name_match = name_regex.search(entry)

101 if name_match:

102 name = name_match.group().strip()

103 else:

104 print(

105 "Unable to determine device bus information from the"

106 " pacmd list-%ss output\npacmd output was: %s"

107 % (vtype, pacmd_entries),

108 file=sys.stderr,

109 )

110 return 1

111

112 driver_name = driver_regex.findall(entry)

113 if driver_name:

114 driver = driver_name[0]

115 else:

116 driver = None

117

118 print("%s\n" % PacmdAudioDevice(name, driver))

119

120 return retval

121

122

123 def main():

124 parser = ArgumentParser("List audio device and driver information")

125 parser.parse_args()

126 return list_device_info()

127

128

129 if __name__ == "__main__":

130 sys.exit(main())