|
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 |
|