Blame hwmixvolume/hwmixvolume

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