import os
import struct
from fcntl import ioctl
+from hda_mixer import AlsaMixer, AlsaMixerElem, AlsaMixerElemId
def __ioctl_val(val):
# workaround for OverFlow bug in python 2.4
return -999999
range = (self.stepsize + 1) * 25
off = -self.ofs * range
- if val >= self.nsteps:
+ if val > self.nsteps:
db = off + self.nsteps * range
if val != 0 or self.nsteps != 0:
print "val > nsteps? for nid 0x%02x" % self.nid, val, self.nsteps
def get_controls(self):
return self.codec.get_controls(self.nid)
+ def get_mixercontrols(self):
+ ctls = self.get_controls()
+ res = []
+ for ctl in ctls:
+ id = AlsaMixerElemId(name=ctl.name, index=ctl.index, device=ctl.device)
+ e = AlsaMixerElem(self.codec.mixer, id)
+ e.hdactl = ctl
+ res.append(e)
+ return res
+
def get_conn_amp_vals_str(self, dst_node):
# return amp values for connection between this and dst_node
res = []
def __init__(self, card, ctl_fd=None):
self.card = card
- if not ctl_fd:
- ctl_fd = os.open("/dev/snd/controlC%i" % card, os.O_RDONLY)
+ if ctl_fd is None:
+ self.fd = ctl_fd = os.open("/dev/snd/controlC%i" % card, os.O_RDONLY)
+ else:
+ self.fd = os.dup(ctl_fd)
info = struct.pack('ii16s16s32s80s16s80s128s', 0, 0, '', '', '', '', '', '', '')
res = ioctl(ctl_fd, CTL_IOCTL_CARD_INFO, info)
a = struct.unpack('ii16s16s32s80s16s80s128s', res)
self.longname = a[5].replace('\x00', '')
self.components = a[8].replace('\x00', '')
+ def __del__(self):
+ if not self.fd is None:
+ os.close(self.fd)
+
class HDACodec:
afg = None
def __init__(self, card=0, device=0, clonefd=None):
self.fd = None
self.hwaccess = True
+ ctl_fd = None
if type(1) == type(card):
self.device = device
self.card = card
self.mcard = HDACard(card)
+ ctl_fd = self.mcard.fd
else:
self.device = device
self.mcard = card
self.version = struct.unpack('I', res)
if self.version < 0x00010000: # 1.0.0
raise IOError, "unknown HDA hwdep version"
+ self.mixer = AlsaMixer(self.card, ctl_fd=ctl_fd)
self.parse_proc()
def __del__(self):
components = a[8].replace('\x00', '')
if components.find('HDA:') >= 0:
result.append(HDACard(card, ctl_fd=fd))
+ os.close(fd)
return result
if __name__ == '__main__':
self.read_all = self.__read_all_none
self.node = None
self.codec = None
+ self.popups = []
+ self.tooltips = gtk.Tooltips()
if card and not codec and not node:
self.__build_card(card, doframe)
elif codec and not card and not node:
if self.read_all and self.node == node:
self.read_all()
+ def show_popup(self, text):
+ screen_width = gtk.gdk.screen_width()
+ screen_height = gtk.gdk.screen_height()
+
+ popup_win = gtk.Window(gtk.WINDOW_POPUP)
+ popup_win.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0xffff, 0xd700, 0))
+ frame = gtk.Frame()
+ popup_win.add(frame)
+ label = gtk.Label()
+ label.modify_font(get_fixed_font())
+ if text[-1] == '\n':
+ text = text[:-1]
+ label.set_text(text)
+ frame.add(label)
+ popup_win.move(screen_width + 10, screen_height + 10)
+ popup_win.show_all()
+ popup_width, popup_height = popup_win.get_size()
+
+ rootwin = self.get_screen().get_root_window()
+ x, y, mods = rootwin.get_pointer()
+
+ pos_x = x - popup_width/2
+ if pos_x < 0:
+ pos_x = 0
+ if pos_x + popup_width > screen_width:
+ pos_x = screen_width - popup_width
+ pos_y = y + 16
+ if pos_y < 0:
+ pox_y = 0
+ if pos_y + popup_height > screen_height:
+ pos_y = screen_height - popup_height
+
+ popup_win.move(int(pos_x), int(pos_y))
+ return popup_win
+
+ def __popup_motion_notify(self, widget, event=None):
+ for popup in self.popups:
+ if popup[1] == widget and not popup[0]:
+ popup[0] = self.show_popup(popup[2](*popup[3]))
+
+ def __popup_leave_notify(self, widget, event=None):
+ for popup in self.popups:
+ if popup[1] == widget and popup[0]:
+ popup[0].destroy()
+ popup[0] = None
+
+ def make_popup(self, widget, gettext, data):
+ widget.connect("motion-notify-event", self.__popup_motion_notify)
+ widget.connect("leave-notify-event", self.__popup_leave_notify)
+ self.popups.append([None, widget, gettext, data])
+
def __create_text(self, callback):
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
HDA_SIGNAL.emit("hda-node-changed", self, vals.node)
adj.set_value(vals.vals[idx] & 0x7f)
- def __build_amps(self, node):
+ def __ctl_mute_toggled(self, adj, data):
+ ctl, idx = data
+
+ def __ctl_value_changed(self, adj, data):
+ ctl, idx = data
+ def __popup_show_ctl(self, ctl, idx):
+ return ctl.get_text_info(idx - ctl.hdactl.amp_idx)
+
+ def __build_amps(self, node):
+
def build_caps(title, caps, vals):
if caps and caps.cloned:
title += ' (Global)'
vbox1 = None
self.amp_checkbuttons[caps.dir] = []
self.amp_adjs[caps.dir] = []
+ self.mixer_elems[caps.dir] = []
+ ctls = node.get_mixercontrols()
for val in vals.vals:
if vals.stereo and idx & 1 == 0:
frame1 = gtk.Frame()
hbox.pack_start(scale, True, True)
else:
self.amp_adjs[caps.dir].append(None)
+ sep = False
+ for ctl in ctls:
+ if ctl.hdactl.amp_index_match(idx):
+ if ctl.stype == 'boolean':
+ if not sep:
+ hbox.pack_start(gtk.VSeparator(), False, False)
+ hbox.pack_start(gtk.Label('CTLIFC'), False, False)
+ sep = True
+ checkbutton = gtk.CheckButton('Mute')
+ checkbutton.connect("toggled", self.__ctl_mute_toggled, (ctl, idx))
+ self.make_popup(checkbutton, self.__popup_show_ctl, (ctl, idx))
+ hbox.pack_start(checkbutton, False, False)
+ for ctl in ctls:
+ if ctl.hdactl.amp_index_match(idx):
+ if ctl.stype.startswith('integer'):
+ if not sep:
+ hbox.pack_start(gtk.VSeparator(), False, False)
+ sep = True
+ adj = gtk.Adjustment(0, ctl.min, ctl.max, ctl.step, ctl.step, ctl.step)
+ scale = gtk.HScale(adj)
+ scale.set_digits(0)
+ scale.set_value_pos(gtk.POS_RIGHT)
+ adj.connect("value_changed", self.__ctl_value_changed, (ctl, idx))
+ self.make_popup(scale, self.__popup_show_ctl, (ctl, idx))
+ hbox.pack_start(scale, True, True)
if vbox1:
vbox1.pack_start(hbox, False, False)
else:
self.amp_checkbuttons = {}
self.amp_adjs = {}
+ self.mixer_elems = {}
hbox = gtk.HBox(False, 0)
c = build_caps('Input Amplifier',
node.in_amp and node.amp_caps_in or None,
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import os
+import struct
+from fcntl import ioctl
+
+def __ioctl_val1(val):
+ # workaround for OverFlow bug in python 2.4
+ if val & 0x80000000:
+ return -((val^0xffffffff)+1)
+ return val
+
+CTL_IOCTL_CARD_INFO = __ioctl_val1(0x81785501)
+CTL_IOCTL_ELEM_INFO = __ioctl_val1(0xc1105511)
+CTL_IOCTL_ELEM_READ = __ioctl_val1(0xc4c85512)
+CTL_IOCTL_ELEM_WRITE = __ioctl_val1(0xc4c85513)
+
+CTL_ELEM_TYPE_BOOLEAN = 1
+CTL_ELEM_TYPE_INTEGER = 2
+CTL_ELEM_TYPE_ENUMERATED = 3
+CTL_ELEM_TYPE_BYTES = 4
+CTL_ELEM_TYPE_IEC958 = 5
+CTL_ELEM_TYPE_INTEGER64 = 6
+
+CTL_ELEM_TYPEs = {
+ 'boolean': CTL_ELEM_TYPE_BOOLEAN,
+ 'integer': CTL_ELEM_TYPE_INTEGER,
+ 'enumerated': CTL_ELEM_TYPE_ENUMERATED,
+ 'bytes':CTL_ELEM_TYPE_BYTES,
+ 'iec958':CTL_ELEM_TYPE_IEC958,
+ 'integer64':CTL_ELEM_TYPE_INTEGER64
+}
+CTL_ELEM_RTYPEs = {}
+for i in CTL_ELEM_TYPEs:
+ CTL_ELEM_RTYPEs[CTL_ELEM_TYPEs[i]] = i
+
+CTL_ELEM_IFACE_MIXER = 2
+CTL_ELEM_IFACEs = {
+ "mixer": 2,
+}
+CTL_ELEM_RIFACEs = {}
+for i in CTL_ELEM_IFACEs:
+ CTL_ELEM_RIFACEs[CTL_ELEM_IFACEs[i]] = i
+
+CTL_ELEM_ACCESS_READ = (1<<0)
+CTL_ELEM_ACCESS_WRITE = (1<<1)
+CTL_ELEM_ACCESS_VOLATILE = (1<<2)
+CTL_ELEM_ACCESS_TIMESTAMP = (1<<3)
+CTL_ELEM_ACCESS_TLV_READ = (1<<4)
+CTL_ELEM_ACCESS_TLV_WRITE = (1<<5)
+CTL_ELEM_ACCESS_TLV_COMMAND = (1<<6)
+CTL_ELEM_ACCESS_INACTIVE = (1<<8)
+CTL_ELEM_ACCESS_LOCK = (1<<9)
+CTL_ELEM_ACCESS_OWNER = (1<<10)
+
+CTL_ELEM_ACCESSs = {
+ 'read': CTL_ELEM_ACCESS_READ,
+ 'write': CTL_ELEM_ACCESS_WRITE,
+ 'volatile': CTL_ELEM_ACCESS_VOLATILE,
+ 'timestamp': CTL_ELEM_ACCESS_TIMESTAMP,
+ 'tlv_read': CTL_ELEM_ACCESS_TLV_READ,
+ 'tlv_write': CTL_ELEM_ACCESS_TLV_WRITE,
+ 'tlv_command': CTL_ELEM_ACCESS_TLV_COMMAND,
+ 'inactive': CTL_ELEM_ACCESS_INACTIVE,
+ 'lock': CTL_ELEM_ACCESS_LOCK,
+ 'owner': CTL_ELEM_ACCESS_OWNER
+}
+
+UINTSIZE = len(struct.pack("I", 0))
+LONGSIZE = len(struct.pack("l", 0))
+LONGLONGSIZE = len(struct.pack("q", 0))
+
+class AlsaMixerElemId:
+
+ def __init__(self,
+ numid=0,
+ iface=CTL_ELEM_IFACE_MIXER,
+ device=0,
+ subdevice=0,
+ name=None,
+ index=0):
+ self.numid = numid
+ self.iface = iface
+ self.device = device
+ self.subdevice = subdevice
+ self.name = name
+ self.index = index
+ self.binsize = len(self.pack())
+
+ def pack(self):
+ return struct.pack('IiII44sI',
+ self.numid, self.iface, self.device, self.subdevice,
+ self.name, self.index)
+
+ def unpack(self, binid):
+ self.numid, self.iface, self.device, self.subdevice, \
+ self.name, self.index = struct.unpack('IiII44sI', binid)
+ self.name = self.name.replace('\x00', '')
+
+ def get_text_info(self):
+ return 'iface="%s",name="%s",index=%s,device=%s,subdevice=%s' % \
+ (CTL_ELEM_RIFACEs[self.iface], self.name, self.index,
+ self.device, self.subdevice)
+
+class AlsaMixerElem:
+
+ def __init__(self, mixer, id):
+ self.mixer = mixer
+ self.id = id
+ info = self.__info()
+ self.type = info['type']
+ self.stype = CTL_ELEM_RTYPEs[self.type]
+ self.access = info['access']
+ self.count = info['count']
+ self.owner = info['owner']
+ if info['type'] in [CTL_ELEM_TYPE_INTEGER, CTL_ELEM_TYPE_INTEGER64]:
+ self.min = info['min']
+ self.max = info['max']
+ self.step = info['step']
+ elif info['type'] == CTL_ELEM_TYPE_ENUMERATED:
+ self.items = info['items']
+ self.dimen = info['dimen']
+
+ def __info(self):
+ bin = self.id.pack()+struct.pack('iIIi128s8s64s', 0, 0, 0, 0, '', '', '')
+ res = ioctl(self.mixer.fd, CTL_IOCTL_ELEM_INFO, bin)
+ self.id.unpack(res[:self.id.binsize])
+ a = struct.unpack('iIIi128s8s64s', res[self.id.binsize:])
+ b = {}
+ b['id'] = self.id
+ b['type'] = a[0]
+ b['access'] = []
+ for i in CTL_ELEM_ACCESSs:
+ if CTL_ELEM_ACCESSs[i] & a[1]:
+ b['access'].append(i)
+ b['count'] = a[2]
+ b['owner'] = a[3]
+ if b['type'] == CTL_ELEM_TYPE_INTEGER:
+ b['min'], b['max'], b['step'] = \
+ struct.unpack("lll", a[4][:LONGSIZE*3])
+ elif b['type'] == CTL_ELEM_TYPE_INTEGER64:
+ b['min'], b['max'], b['step'] = \
+ struct.unpack("qqq", a[4][:LONGLONGSIZE*3])
+ elif b['type'] == CTL_ELEM_TYPE_ENUMERATED:
+ b['items'], b['item'], b['name'] = \
+ struct.unpack("II64s", a[4][:UINTSIZE*2+64])
+ b['dimen'] = struct.unpack("HHHH", a[5])
+ return b
+
+ def read(self):
+ bin = self.id.pack() + struct.pack('I512s128s', 0, '', '')
+ startoff = self.id.binsize + UINTSIZE
+ if LONGSIZE == 8:
+ bin += '\x00\x00\x00\x00'
+ startoff += 4
+ res = ioctl(self.mixer.fd, CTL_IOCTL_ELEM_READ, bin)
+ if self.type == CTL_ELEM_TYPE_BOOLEAN:
+ return map(lambda x: x != 0, struct.unpack("l"*self.count, res[startoff:startoff+self.count*LONGSIZE]))
+ elif self.type == CTL_ELEM_TYPE_INTEGER:
+ return struct.unpack("l"*self.count, res[startoff:startoff+self.count*LONGSIZE])
+ elif self.type == CTL_ELEM_TYPE_INTEGER64:
+ return struct.unpack("q"*self.count, res[startoff:startoff+self.count*LONGLONGSIZE])
+ elif self.type == CTL_ELEM_TYPE_ENUMERATED:
+ return struct.unpack("I"*self.count, res[startoff:startoff+self.count*UINTSIZE])
+ elif self.type == CTL_ELEM_TYPE_BYTES:
+ return res[startoff:startoff+self.count]
+ else:
+ raise ValueError, "Unsupported type %s" % CTL_ELEM_RTYPEs[self.type]
+
+ def get_text_info(self, idx=None):
+ res = self.id.get_text_info() + '\n'
+ res += ' type="%s",access=%s,count=%s,owner=%s,dimen=%s\n' % \
+ (self.stype, repr(self.access), self.count, self.owner, self.dimen)
+ if self.stype.startswith('integer'):
+ res += ' min=%s,max=%s,step=%s\n' % (self.min, self.max, self.step)
+ elif self.stype == 'enumerated':
+ res += ' items=%s\n' % (self.items)
+ return res
+
+class AlsaMixer:
+
+ def __init__(self, card, ctl_fd=None):
+ self.card = card
+ if ctl_fd is None:
+ self.fd = os.open("/dev/snd/controlC%s" % card, os.O_RDONLY)
+ else:
+ self.fd = os.dup(ctl_fd)
+
+ def __del__(self):
+ if not self.fd is None:
+ os.close(self.fd)
+
+if __name__ == '__main__':
+ mixer = AlsaMixer(0)
+ elem = AlsaMixerElem(mixer, AlsaMixerElemId(name="Mic Boost"))
+ print elem.read()
+ elem = AlsaMixerElem(mixer, AlsaMixerElemId(name="Capture Volume"))
+ print elem.read()