#!/usr/bin/python # (C) 2005 Canonical Ltd. # Author: Martin Pitt # License: GNU General Public License, version 2 or any later version # # Modified by: Thomas Hood, Daniel T Chen # # Get and set configuration parameters in ~/.asoundrc.asoundconf. import sys, re, os.path our_conf_file = os.path.expanduser('~/.asoundrc.asoundconf') asoundrc_file = os.path.expanduser('~/.asoundrc') predefs_file = '/usr/share/alsa/alsa.conf' setting_re_template = '!?\s*%s\s*(?:=|\s)\s*([^;,]+)[;,]?$' our_conf_header = '''# ALSA library configuration file managed by asoundconf(1). # # MANUAL CHANGES TO THIS FILE WILL BE OVERWRITTEN! # # Manual changes to the ALSA library configuration should be implemented # by editing the ~/.asoundrc file, not by editing this file. ''' asoundrc_header = '''# ALSA library configuration file ''' inclusion_comment = '''# Include settings that are under the control of asoundconf(1). # (To disable these settings, comment out this line.)''' usage = '''Usage: asoundconf is-active asoundconf get|delete PARAMETER asoundconf set PARAMETER VALUE asoundconf list Convenience macro functions: asoundconf set-default-card PARAMETER asoundconf reset-default-card asoundconf set-pulseaudio asoundconf unset-pulseaudio asoundconf set-oss PARAMETER asoundconf unset-oss ''' needs_default_card = '''You have omitted a necessary parameter. Please see the output from `asoundconf list`, and use one of those sound card(s) as the parameter. ''' needs_oss_dev = '''You have omitted a necessary parameter. Please specify an OSS device (e.g., /dev/dsp). ''' superuser_warn = '''Please note that you are attempting to run asoundconf as a privileged superuser, which may have unintended consequences. ''' def get_default_predefs(): try: if not os.path.exists(predefs_file): return predefs_file_entire = open(predefs_file).readlines() r = re.compile('^defaults') ## Between these hashes, add additional unique regexps that ## must exist at the end of the user's custom asoundrc. s = re.compile('^defaults.namehint') ## predefs_list = [] must_append_predefs_list = [] for i in predefs_file_entire: if r.match(i) and not s.match(i): predefs_list.append(str(i).strip()) elif s.match(i): must_append_predefs_list.append(str(i).strip()) for i in must_append_predefs_list: predefs_list.append(str(i).strip()) return predefs_list except IOError: pass def ensure_our_conf_exists(): '''If it does not exist then generate a default configuration file with no settings. Return: True on success, False if the file could not be created. ''' if os.path.exists(our_conf_file): return True try: open(our_conf_file, 'w').write(our_conf_header) return True except IOError: print >> sys.stderr, 'Error: could not create', our_conf_file return False def ensure_asound_rc_exists(): '''Generate a default user configuration file with only the inclusion line. Return: True on success, False if the file could not be created. ''' if os.path.exists(asoundrc_file): return True try: open(asoundrc_file, 'w').write('%s\n%s\n<%s>\n\n' % (asoundrc_header, inclusion_comment, our_conf_file)) return True except IOError: print >> sys.stderr, 'Error: could not create', asoundrc_file return False def sds_transition(): '''Replace the magic comments added to the user configuration file by the obsolete set-default-soundcard program with the standard inclusion statement for our configuration file. ''' if not os.path.exists(asoundrc_file): return lines = open(asoundrc_file).readlines() start_marker_re = re.compile('### BEGIN set-default-soundcard') end_marker_re = re.compile('### END set-default-soundcard') userconf_lines = [] our_conf_lines = [] # read up to start comment lineno = 0 found = 0 for l in lines: lineno = lineno+1 if start_marker_re.match(l): found = 1 break userconf_lines.append(l) if found: # replace magic comment section with include userconf_lines.append('%s\n<%s>\n\n' % (inclusion_comment, our_conf_file)) else: # no magic comment return # read magic comment section until end marker and add it to asoundconf found = 0 for l in lines[lineno:]: lineno = lineno+1 if end_marker_re.match(l): found = 1 break if not l.startswith('#'): our_conf_lines.append(l) if not found: # no complete magic comment return # add the rest to user conf userconf_lines = userconf_lines + lines[lineno:] # write our configuration file if not ensure_our_conf_exists(): return try: open(our_conf_file, 'a').writelines(our_conf_lines) except IOError: return # panic out # write user configuration file try: open(asoundrc_file, 'w').writelines(userconf_lines) except IOError: pass def is_active(): '''Check that the user configuration file is either absent, or, if present, that it includifies the asoundconf configuration file; in those cases asoundconf can be used to change the user's ALSA library configuration. Also transition from the legacy set-default-soundcard program. Return True if the above condition is met, False if not. ''' if not os.path.exists(asoundrc_file): return True sds_transition() # check whether or not the file has the inclusion line include_re = re.compile('\s*<\s*%s\s*>' % our_conf_file) for l in open(asoundrc_file): if include_re.match(l): return True return False def get(prmtr): '''Print the value of the given parameter on stdout Also transition from the legacy set-default-soundcard program. Return True on success, and False if the value is not set. ''' sds_transition() if not os.path.exists(our_conf_file): return False setting_re = re.compile(setting_re_template % prmtr) try: for line in open(our_conf_file): m = setting_re.match(line) if m: print m.group(1).strip() return True return False except IOError: return False def list(): '''Get card names from /proc/asound/cards''' cardspath = '/proc/asound/cards' if not os.path.exists(cardspath): return False procfile = open(cardspath, 'rb') cardline = re.compile('^\s*\d+\s*\[') card_lines = [] lines = procfile.readlines() for l in lines: if cardline.match(l): card_lines.append(re.sub(r'^\s*\d+\s*\[(\w+)\s*\].+','\\1',l)) print "Names of available sound cards:" for cardname in card_lines: print cardname.strip() return True def delete(prmtr): '''Delete the given parameter. Also transition from the legacy set-default-soundcard program. Return True on success, and False on an error. ''' sds_transition() if not os.path.exists(our_conf_file): return False setting_re = re.compile(setting_re_template % prmtr) lines = [] try: lines = open(our_conf_file).readlines() except IOError: return False found = 0 for i in xrange(len(lines)): if setting_re.match(lines[i]): del lines[i] found = 1 break if found: # write back file try: f = open(our_conf_file, 'w') except IOError: return False f.writelines(lines) return True def set(prmtr, value): '''Set the given parameter to the given value Also transition from the legacy set-default-soundcard program. Return True on success, and False on an error. ''' sds_transition() setting_re = re.compile(setting_re_template % prmtr) lines = [] ensure_asound_rc_exists() # N.B. We continue even if asoundrc could not be created # and we do NOT ensure that our configuration is "active" if not ensure_our_conf_exists(): return False try: lines = open(our_conf_file).readlines() except IOError: return False newsetting = '%s %s\n' % (prmtr, value) # if setting is already present, change it found = 0 for i in xrange(len(lines)): if setting_re.match(lines[i]): lines[i] = newsetting found = 1 break if not found: lines.append(newsetting) # write back file try: f = open(our_conf_file, 'w') except IOError: return False f.writelines(lines) return True def set_default_card(card): clist = get_default_predefs() sep = re.compile('\s+') r = re.compile('^defaults.pcm.card') s = re.compile('^defaults.ctl.card') ## !defaults.pcm.card and defaults.ctl.card should lead ## the user's custom asoundrc. if set('!defaults.pcm.card', card) and \ set('defaults.ctl.card', card): for i in clist: (j, k) = sep.split(i) if not r.match(j) and not s.match(j): if not set(j, k): return False return True else: return False def reset_default_card(): clist = get_default_predefs() sep = re.compile('\s+') for i in clist: (j, k) = sep.split(i) if not delete(j): return False return True def delete_pcm_default(): return delete('pcm.!default') def delete_ctl_default(): return delete('ctl.!default') def set_pulseaudio(): return set('pcm.!default', '{ type pulse }') and \ set('ctl.!default', '{ type pulse }') def unset_pulseaudio(): return delete_pcm_default() and \ delete_ctl_default() def set_oss(device): endbrace = ' }' return set('pcm.!default { type oss device', device + endbrace) def unset_oss(): return delete_pcm_default() def exit_code(result): '''Exit program with code 0 if result is True, otherwise exit with code 1. ''' if result: sys.exit(0) else: sys.exit(1) ## ## main ## if os.geteuid() == 0: print superuser_warn if len(sys.argv) < 2 or sys.argv[1] == '--help' or sys.argv[1] == '-h': print usage sys.exit(0) if sys.argv[1] == 'is-active': exit_code(is_active()) if sys.argv[1] == 'get': if len(sys.argv) != 3: print usage sys.exit(1) exit_code(get(sys.argv[2])) if sys.argv[1] == 'delete': if len(sys.argv) != 3: print usage sys.exit(1) exit_code(delete(sys.argv[2])) if sys.argv[1] == 'set': if len(sys.argv) != 4: print usage sys.exit(1) exit_code(set(sys.argv[2], sys.argv[3])) if sys.argv[1] == 'list': exit_code(list()) if sys.argv[1] == 'set-default-card': if len(sys.argv) != 3: print needs_default_card sys.exit(1) exit_code(set_default_card(sys.argv[2])) if sys.argv[1] == 'reset-default-card': exit_code(reset_default_card()) if sys.argv[1] == 'set-pulseaudio': exit_code(set_pulseaudio()) if sys.argv[1] == 'unset-pulseaudio': exit_code(unset_pulseaudio()) if sys.argv[1] == 'set-oss': if len(sys.argv) != 3: print needs_oss_dev sys.exit(1) exit_code(set_oss(sys.argv[2])) if sys.argv[1] == 'unset-oss': exit_code(unset_oss()) print usage sys.exit(1)