From 2bee3ad510928181d396a7df3d7d90646d6366a2 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sat, 17 Jul 2010 08:24:35 +0200 Subject: [PATCH] hda-analyzer: fix mixer (control API) checks Signed-off-by: Jaroslav Kysela --- hda-analyzer/hda_codec.py | 27 ++++- hda-analyzer/hda_guilib.py | 92 +++++++++++++++- hda-analyzer/hda_mixer.py | 210 +++++++++++++++++++++++++++++++++++++ hda-analyzer/hda_proc.py | 4 + hda-analyzer/run.py | 2 +- 5 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 hda-analyzer/hda_mixer.py diff --git a/hda-analyzer/hda_codec.py b/hda-analyzer/hda_codec.py index 061227c..ae4028d 100644 --- a/hda-analyzer/hda_codec.py +++ b/hda-analyzer/hda_codec.py @@ -15,6 +15,7 @@ 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 @@ -274,7 +275,7 @@ class HDAAmpCaps: 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 @@ -755,6 +756,16 @@ class HDANode: 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 = [] @@ -851,8 +862,10 @@ class HDACard: 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) @@ -862,6 +875,10 @@ class HDACard: 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 @@ -873,10 +890,12 @@ class HDACodec: 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 @@ -894,6 +913,7 @@ class HDACodec: 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): @@ -1600,6 +1620,7 @@ def HDA_card_list(): 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__': diff --git a/hda-analyzer/hda_guilib.py b/hda-analyzer/hda_guilib.py index cdd12cb..5df14cd 100644 --- a/hda-analyzer/hda_guilib.py +++ b/hda-analyzer/hda_guilib.py @@ -76,6 +76,8 @@ class NodeGui(gtk.ScrolledWindow): 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: @@ -105,6 +107,57 @@ class NodeGui(gtk.ScrolledWindow): 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) @@ -214,8 +267,17 @@ class NodeGui(gtk.ScrolledWindow): 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)' @@ -233,6 +295,8 @@ class NodeGui(gtk.ScrolledWindow): 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() @@ -259,6 +323,31 @@ class NodeGui(gtk.ScrolledWindow): 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: @@ -269,6 +358,7 @@ class NodeGui(gtk.ScrolledWindow): 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, diff --git a/hda-analyzer/hda_mixer.py b/hda-analyzer/hda_mixer.py new file mode 100644 index 0000000..af12d5c --- /dev/null +++ b/hda-analyzer/hda_mixer.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# 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() diff --git a/hda-analyzer/hda_proc.py b/hda-analyzer/hda_proc.py index e188590..6e7f259 100644 --- a/hda-analyzer/hda_proc.py +++ b/hda-analyzer/hda_proc.py @@ -166,6 +166,10 @@ class HDApcmControl: str += ' ControlAmp: chs=%s, dir=%s, idx=%s, ofs=%s\n' % (self.amp_chs, self.amp_dir, self.amp_idx, self.amp_ofs) return str + def amp_index_match(self, idx): + count = (self.amp_chs & 1) + ((self.amp_chs >> 1) & 1) + return idx >= self.amp_idx and idx < self.amp_idx + count + class ProcNode(HDABaseProc): def __init__(self, codec, nid, wcaps): diff --git a/hda-analyzer/run.py b/hda-analyzer/run.py index 59def3d..0456bf2 100755 --- a/hda-analyzer/run.py +++ b/hda-analyzer/run.py @@ -2,7 +2,7 @@ URL="http://git.alsa-project.org/?p=alsa.git;a=blob_plain;f=hda-analyzer/" FILES=["hda_analyzer.py", "hda_guilib.py", "hda_codec.py", "hda_proc.py", - "hda_graph.py"] + "hda_graph.py", "hda_mixer.py"] try: import gobject -- 2.47.1