Blame hwmixvolume/hwmixvolume

Packit Service b98cfc
#!/usr/bin/python2
Packit Service b98cfc
Packit Service b98cfc
# hwmixvolume - ALSA hardware mixer volume control applet
Packit Service b98cfc
# Copyright (c) 2009-2010 Clemens Ladisch
Packit Service b98cfc
#
Packit Service b98cfc
# Permission to use, copy, modify, and/or distribute this software for any
Packit Service b98cfc
# purpose with or without fee is hereby granted, provided that the above
Packit Service b98cfc
# copyright notice and this permission notice appear in all copies.
Packit Service b98cfc
#
Packit Service b98cfc
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
Packit Service b98cfc
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Packit Service b98cfc
# AND FITNESS.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
Packit Service b98cfc
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Packit Service b98cfc
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
Packit Service b98cfc
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Packit Service b98cfc
# PERFORMANCE OF THIS SOFTWARE.
Packit Service b98cfc
Packit Service b98cfc
import gobject, gtk
Packit Service b98cfc
from pyalsa import alsacard, alsahcontrol
Packit Service b98cfc
Packit Service b98cfc
INTF_PCM = alsahcontrol.interface_id['PCM']
Packit Service b98cfc
INTF_MIXER = alsahcontrol.interface_id['MIXER']
Packit Service b98cfc
TYPE_INTEGER = alsahcontrol.element_type['INTEGER']
Packit Service b98cfc
EVENT_VALUE = alsahcontrol.event_mask['VALUE']
Packit Service b98cfc
EVENT_INFO = alsahcontrol.event_mask['INFO']
Packit Service b98cfc
EVENT_REMOVE = alsahcontrol.event_mask_remove
Packit Service b98cfc
Packit Service b98cfc
class Stream:
Packit Service b98cfc
	def __init__(self, element, parent):
Packit Service b98cfc
		self.element = element
Packit Service b98cfc
		self.element.set_callback(self)
Packit Service b98cfc
		self.parent = parent
Packit Service b98cfc
		self.label = None
Packit Service b98cfc
		self.scales = []
Packit Service b98cfc
		self.adjustments = []
Packit Service b98cfc
		self.callback(self.element, EVENT_INFO)
Packit Service b98cfc
Packit Service b98cfc
	def destroy(self):
Packit Service b98cfc
		self.deactivate()
Packit Service b98cfc
Packit Service b98cfc
	def callback(self, e, mask):
Packit Service b98cfc
		if mask == EVENT_REMOVE:
Packit Service b98cfc
			self.deactivate()
Packit Service b98cfc
		elif (mask & EVENT_INFO) != 0:
Packit Service b98cfc
			info = alsahcontrol.Info(self.element)
Packit Service b98cfc
			if info.is_inactive:
Packit Service b98cfc
				self.deactivate()
Packit Service b98cfc
			else:
Packit Service b98cfc
				self.activate()
Packit Service b98cfc
		elif (mask & EVENT_VALUE) != 0:
Packit Service b98cfc
			self.update_scales_from_ctl()
Packit Service b98cfc
Packit Service b98cfc
	def activate(self):
Packit Service b98cfc
		if self.label:
Packit Service b98cfc
			return
Packit Service b98cfc
		info = alsahcontrol.Info(self.element)
Packit Service b98cfc
		value = alsahcontrol.Value(self.element)
Packit Service b98cfc
		value.read()
Packit Service b98cfc
		values = value.get_tuple(TYPE_INTEGER, info.count)
Packit Service b98cfc
		self.label = gtk.Label(self.get_label(info))
Packit Service b98cfc
		self.label.set_single_line_mode(True)
Packit Service b98cfc
		self.parent.scales_vbox.pack_start(self.label, expand=False)
Packit Service b98cfc
		for i in range(info.count):
Packit Service b98cfc
			adj = gtk.Adjustment(value=values[i],
Packit Service b98cfc
					lower=info.min, upper=info.max,
Packit Service b98cfc
					step_incr=1,
Packit Service b98cfc
					page_incr=(info.max-info.min+1)/8)
Packit Service b98cfc
			adj.connect('value-changed', self.update_ctl_from_scale, i)
Packit Service b98cfc
			scale = gtk.HScale(adj)
Packit Service b98cfc
			scale.set_draw_value(False)
Packit Service b98cfc
			self.parent.scales_vbox.pack_start(scale, expand=False)
Packit Service b98cfc
			self.scales.append(scale)
Packit Service b98cfc
			self.adjustments.append(adj)
Packit Service b98cfc
		self.parent.scales_vbox.show_all()
Packit Service b98cfc
		self.parent.update_msg_label()
Packit Service b98cfc
Packit Service b98cfc
	def deactivate(self):
Packit Service b98cfc
		if not self.label:
Packit Service b98cfc
			return
Packit Service b98cfc
		self.label.destroy()
Packit Service b98cfc
		for s in self.scales:
Packit Service b98cfc
			s.destroy()
Packit Service b98cfc
		self.label = None
Packit Service b98cfc
		self.scales = []
Packit Service b98cfc
		self.adjustments = []
Packit Service b98cfc
		self.parent.update_msg_label()
Packit Service b98cfc
Packit Service b98cfc
	def update_scales_from_ctl(self):
Packit Service b98cfc
		if not self.label:
Packit Service b98cfc
			return
Packit Service b98cfc
		count = len(self.adjustments)
Packit Service b98cfc
		value = alsahcontrol.Value(self.element)
Packit Service b98cfc
		value.read()
Packit Service b98cfc
		values = value.get_tuple(TYPE_INTEGER, count)
Packit Service b98cfc
		for i in range(count):
Packit Service b98cfc
			self.adjustments[i].set_value(values[i])
Packit Service b98cfc
Packit Service b98cfc
	def update_ctl_from_scale(self, adj, index):
Packit Service b98cfc
		scale_value = adj.get_value()
Packit Service b98cfc
		value_to_set = int(round(adj.get_value()))
Packit Service b98cfc
		count = len(self.adjustments)
Packit Service b98cfc
		value = alsahcontrol.Value(self.element)
Packit Service b98cfc
		if self.parent.lock_check.get_active():
Packit Service b98cfc
			values = [value_to_set  for i in range(count)]
Packit Service b98cfc
		else:
Packit Service b98cfc
			value.read()
Packit Service b98cfc
			values = value.get_array(TYPE_INTEGER, count)
Packit Service b98cfc
			values[index] = value_to_set
Packit Service b98cfc
		value.set_array(TYPE_INTEGER, values)
Packit Service b98cfc
		value.write()
Packit Service b98cfc
		if value_to_set != scale_value:
Packit Service b98cfc
			adj.set_value(value_to_set)
Packit Service b98cfc
Packit Service b98cfc
	def get_label(self, info):
Packit Service b98cfc
		pid = self.get_pid(info)
Packit Service b98cfc
		if pid:
Packit Service b98cfc
			cmdline = self.get_pid_cmdline(pid)
Packit Service b98cfc
			if cmdline:
Packit Service b98cfc
				return cmdline
Packit Service b98cfc
			else:
Packit Service b98cfc
				return "PID %d" % pid
Packit Service b98cfc
		else:
Packit Service b98cfc
			name = info.name
Packit Service b98cfc
			if name[-7:] == " Volume":
Packit Service b98cfc
				name = name[:-7]
Packit Service b98cfc
			if name[-9:] == " Playback":
Packit Service b98cfc
				name = name[:-9]
Packit Service b98cfc
			return name
Packit Service b98cfc
Packit Service b98cfc
	def get_pid(self, info):
Packit Service b98cfc
		card = self.parent.current_card
Packit Service b98cfc
		device = info.device
Packit Service b98cfc
		subdevice = info.subdevice
Packit Service b98cfc
		if subdevice == 0:
Packit Service b98cfc
			subdevice = info.index
Packit Service b98cfc
		filename = "/proc/asound/card%d/pcm%dp/sub%d/status" % (card, device, subdevice)
Packit Service b98cfc
		try:
Packit Service b98cfc
			f = open(filename, "r")
Packit Service b98cfc
		except IOError:
Packit Service b98cfc
			return None
Packit Service b98cfc
		try:
Packit Service b98cfc
			for line in f.readlines():
Packit Service b98cfc
				if line[:9] == "owner_pid":
Packit Service b98cfc
					return int(line.split(':')[1].strip())
Packit Service b98cfc
		finally:
Packit Service b98cfc
			f.close()
Packit Service b98cfc
		return None
Packit Service b98cfc
Packit Service b98cfc
	def get_pid_cmdline(self, pid):
Packit Service b98cfc
		try:
Packit Service b98cfc
			f = open("/proc/%d/cmdline" % pid, "r")
Packit Service b98cfc
		except IOError:
Packit Service b98cfc
			return None
Packit Service b98cfc
		try:
Packit Service b98cfc
			cmdline = f.read()
Packit Service b98cfc
		finally:
Packit Service b98cfc
			f.close()
Packit Service b98cfc
		return cmdline.replace('\x00', ' ').strip()
Packit Service b98cfc
Packit Service b98cfc
class MixerWindow(gtk.Window):
Packit Service b98cfc
	card_numbers = alsacard.card_list()
Packit Service b98cfc
	current_card = -1
Packit Service b98cfc
	hcontrol = None
Packit Service b98cfc
	scales_vbox = None
Packit Service b98cfc
	msg_label = None
Packit Service b98cfc
	streams = []
Packit Service b98cfc
	hctl_sources = []
Packit Service b98cfc
Packit Service b98cfc
	def __init__(self):
Packit Service b98cfc
		gtk.Window.__init__(self)
Packit Service b98cfc
		self.connect('destroy', lambda w: gtk.main_quit())
Packit Service b98cfc
		self.set_title("Hardware Mixer Volumes")
Packit Service b98cfc
Packit Service b98cfc
		vbox = gtk.VBox()
Packit Service b98cfc
		self.add(vbox)
Packit Service b98cfc
Packit Service b98cfc
		hbox = gtk.HBox()
Packit Service b98cfc
		vbox.pack_start(hbox, expand=False)
Packit Service b98cfc
Packit Service b98cfc
		label = gtk.Label("_Sound Card: ")
Packit Service b98cfc
		label.set_use_underline(True)
Packit Service b98cfc
		hbox.pack_start(label, expand=False)
Packit Service b98cfc
Packit Service b98cfc
		combo = gtk.combo_box_new_text()
Packit Service b98cfc
		for i in self.card_numbers:
Packit Service b98cfc
			str = "%d: %s" % (i, alsacard.card_get_name(i))
Packit Service b98cfc
			combo.append_text(str)
Packit Service b98cfc
		if len(self.card_numbers) > 0:
Packit Service b98cfc
			combo.set_active(0)
Packit Service b98cfc
		combo.connect('changed', lambda c: self.change_card(self.card_numbers[combo.get_active()]))
Packit Service b98cfc
		hbox.pack_start(combo)
Packit Service b98cfc
		label.set_mnemonic_widget(combo)
Packit Service b98cfc
Packit Service b98cfc
		self.lock_check = gtk.CheckButton(label="_Lock Channels")
Packit Service b98cfc
		self.lock_check.set_active(True)
Packit Service b98cfc
		vbox.pack_start(self.lock_check, expand=False)
Packit Service b98cfc
Packit Service b98cfc
		scrollwin = gtk.ScrolledWindow()
Packit Service b98cfc
		scrollwin.set_policy(hscrollbar_policy=gtk.POLICY_NEVER, vscrollbar_policy=gtk.POLICY_AUTOMATIC)
Packit Service b98cfc
		scrollwin.set_shadow_type(gtk.SHADOW_NONE)
Packit Service b98cfc
		vbox.pack_start(scrollwin)
Packit Service b98cfc
Packit Service b98cfc
		self.scales_vbox = gtk.VBox()
Packit Service b98cfc
		scrollwin.add_with_viewport(self.scales_vbox)
Packit Service b98cfc
Packit Service b98cfc
		label = gtk.Label()
Packit Service b98cfc
		label.set_single_line_mode(True)
Packit Service b98cfc
		line_height = label.size_request()[1]
Packit Service b98cfc
		label.destroy()
Packit Service b98cfc
		scale = gtk.HScale()
Packit Service b98cfc
		scale.set_draw_value(False)
Packit Service b98cfc
		line_height += scale.size_request()[1]
Packit Service b98cfc
		scale.destroy()
Packit Service b98cfc
		# always have space for at least four sliders
Packit Service b98cfc
		scrollwin.set_size_request(width=-1, height=line_height*4+4)
Packit Service b98cfc
Packit Service b98cfc
		# TODO: select the default card or the first card with stream controls
Packit Service b98cfc
		if len(self.card_numbers) > 0:
Packit Service b98cfc
			self.change_card(self.card_numbers[0])
Packit Service b98cfc
		self.update_msg_label()
Packit Service b98cfc
Packit Service b98cfc
		self.show_all()
Packit Service b98cfc
Packit Service b98cfc
	def change_card(self, cardnum):
Packit Service b98cfc
		for s in self.hctl_sources:
Packit Service b98cfc
			gobject.source_remove(s)
Packit Service b98cfc
		self.hctl_sources = []
Packit Service b98cfc
Packit Service b98cfc
		self.hcontrol = self.open_hcontrol_for_card(cardnum)
Packit Service b98cfc
Packit Service b98cfc
		for s in self.streams:
Packit Service b98cfc
			s.destroy()
Packit Service b98cfc
		self.streams = []
Packit Service b98cfc
Packit Service b98cfc
		self.current_card = cardnum
Packit Service b98cfc
Packit Service b98cfc
		if not self.hcontrol:
Packit Service b98cfc
			self.update_msg_label()
Packit Service b98cfc
			return
Packit Service b98cfc
Packit Service b98cfc
		for id in self.hcontrol.list():
Packit Service b98cfc
			if not self.is_stream_elem(id):
Packit Service b98cfc
				continue
Packit Service b98cfc
			elem = alsahcontrol.Element(self.hcontrol, id[0])
Packit Service b98cfc
			info = alsahcontrol.Info(elem)
Packit Service b98cfc
			if not self.is_stream_info(info):
Packit Service b98cfc
				continue
Packit Service b98cfc
			stream = Stream(elem, self)
Packit Service b98cfc
			self.streams.append(stream)
Packit Service b98cfc
Packit Service b98cfc
		for fd,condition in self.hcontrol.poll_fds:
Packit Service b98cfc
			self.hctl_sources.append(gobject.io_add_watch(fd, condition, self.hctl_io_callback))
Packit Service b98cfc
Packit Service b98cfc
		self.update_msg_label()
Packit Service b98cfc
Packit Service b98cfc
		self.scales_vbox.show_all()
Packit Service b98cfc
Packit Service b98cfc
	def update_msg_label(self):
Packit Service b98cfc
		needs_msg = len(self.scales_vbox.get_children()) < 2
Packit Service b98cfc
		has_msg = self.msg_label
Packit Service b98cfc
		if has_msg and not needs_msg:
Packit Service b98cfc
			self.msg_label.destroy()
Packit Service b98cfc
			self.msg_label = None
Packit Service b98cfc
		elif needs_msg:
Packit Service b98cfc
			if len(self.streams) > 0:
Packit Service b98cfc
				msg = "There are no open streams."
Packit Service b98cfc
			else:
Packit Service b98cfc
				msg = "This card does not have stream controls."
Packit Service b98cfc
			if not has_msg:
Packit Service b98cfc
				self.msg_label = gtk.Label(msg)
Packit Service b98cfc
				self.scales_vbox.pack_start(self.msg_label)
Packit Service b98cfc
				self.scales_vbox.show_all()
Packit Service b98cfc
			elif self.msg_label.get_text() != msg:
Packit Service b98cfc
				self.msg_label.set_text(msg)
Packit Service b98cfc
Packit Service b98cfc
	def open_hcontrol_for_card(self, cardnum):
Packit Service b98cfc
		devname = "hw:CARD=" + str(cardnum)
Packit Service b98cfc
		try:
Packit Service b98cfc
			hc = alsahcontrol.HControl(name=devname,
Packit Service b98cfc
					mode=alsahcontrol.open_mode['NONBLOCK'])
Packit Service b98cfc
		except:
Packit Service b98cfc
			# TODO: alsa error msg
Packit Service b98cfc
			dlg = gtk.MessageDialog(self,
Packit Service b98cfc
					gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
Packit Service b98cfc
					gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
Packit Service b98cfc
					"Cannot open sound card control device.")
Packit Service b98cfc
			dlg.run()
Packit Service b98cfc
			dlg.destroy()
Packit Service b98cfc
			return None
Packit Service b98cfc
		return hc
Packit Service b98cfc
Packit Service b98cfc
	def is_stream_elem(self, id):
Packit Service b98cfc
		return ((id[1] == INTF_PCM and
Packit Service b98cfc
			 id[4] in ("PCM Playback Volume", "EMU10K1 PCM Volume")) or
Packit Service b98cfc
			(id[1] == INTF_MIXER and
Packit Service b98cfc
			 id[4] == "VIA DXS Playback Volume"))
Packit Service b98cfc
Packit Service b98cfc
	def is_stream_info(self, info):
Packit Service b98cfc
		return info.is_readable and info.is_writable and info.type == TYPE_INTEGER
Packit Service b98cfc
Packit Service b98cfc
	def hctl_io_callback(self, source, condition):
Packit Service b98cfc
		self.hcontrol.handle_events()
Packit Service b98cfc
		return True
Packit Service b98cfc
Packit Service b98cfc
def main():
Packit Service b98cfc
	MixerWindow()
Packit Service b98cfc
	gtk.main()
Packit Service b98cfc
Packit Service b98cfc
main()
Packit Service b98cfc