]> git.alsa-project.org Git - alsa.git/commitdiff
hda-analyzer: fix mixer (control API) checks
authorJaroslav Kysela <perex@t61.perex-int.cz>
Sat, 17 Jul 2010 06:24:35 +0000 (08:24 +0200)
committerJaroslav Kysela <perex@perex.cz>
Sat, 17 Jul 2010 06:24:35 +0000 (08:24 +0200)
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
hda-analyzer/hda_codec.py
hda-analyzer/hda_guilib.py
hda-analyzer/hda_mixer.py [new file with mode: 0644]
hda-analyzer/hda_proc.py
hda-analyzer/run.py

index 061227c71eb6111fbe82c78bdaed53485a350ba7..ae4028dca2ae1ab0dafd59861f52feb310120b68 100644 (file)
@@ -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__':
index cdd12cb47353341ae511531b59b7ea1059d9beee..5df14cdea715343fd9500809b173790cc9a4beed 100644 (file)
@@ -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 (file)
index 0000000..af12d5c
--- /dev/null
@@ -0,0 +1,210 @@
+#!/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()
index e1885904e34d75648a83f2720630881f2f1cd6f5..6e7f259a664398d5d55f07346d4537f1e2846b87 100644 (file)
@@ -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):
index 59def3d87919674194256f82f9261d70a229d5b0..0456bf2d4fb93f499824a58270f65225f7fa62bc 100755 (executable)
@@ -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