]> git.neil.brown.name Git - freerunner.git/commitdiff
More random stuff
authorNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 10:43:10 +0000 (21:43 +1100)
committerNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 10:43:10 +0000 (21:43 +1100)
Signed-off-by: NeilBrown <neilb@suse.de>
29 files changed:
contacts/contacts.py [new file with mode: 0644]
launch/cmd.py [new file with mode: 0644]
launch/grouptypes.py [new file with mode: 0644]
launch/internal.py [new file with mode: 0644]
launch/launch.py [new file with mode: 0644]
lib/fingerscroll.py [moved from launcher/fingerscroll.py with 99% similarity]
lib/listselect.py [new file with mode: 0644]
lib/scrawl.py [new file with mode: 0644]
lib/tapboard.py [new file with mode: 0644]
network/apm.usbnet [new file with mode: 0644]
network/apm.wifi [new file with mode: 0644]
network/dnsmasq.conf [new file with mode: 0644]
network/interfaces [new file with mode: 0644]
network/wpa_action_updown [new file with mode: 0644]
notes/Control [new file with mode: 0644]
notes/Network [new file with mode: 0644]
scenario/Makefile [new file with mode: 0644]
scenario/aconfig.h [new file with mode: 0644]
scenario/alsactl.c [new file with mode: 0644]
scenario/alsactl.h [new file with mode: 0644]
scenario/init_parse.c [new file with mode: 0644]
scenario/init_sysdeps.c [new file with mode: 0644]
scenario/init_sysfs.c [new file with mode: 0644]
scenario/init_utils_run.c [new file with mode: 0644]
scenario/init_utils_string.c [new file with mode: 0644]
scenario/list.h [new file with mode: 0644]
scenario/state.c [new file with mode: 0644]
scenario/utils.c [new file with mode: 0644]
scenario/version.h [new file with mode: 0644]

diff --git a/contacts/contacts.py b/contacts/contacts.py
new file mode 100644 (file)
index 0000000..a222d4c
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+
+#
+# Contacts manager
+#  Currently a 'contact' is a name, a number, and a speed-dial letter
+#
+# We have a content pane and a row of buttons at the bottom.
+# The content is either:
+#   - list of contacts (name/number, one line each)
+#   - detailed contact info that can be editted.
+#
+# When list of contacts are displayed, typing a char adds that char to
+# a (hidden) substring and only contacts containing that substring are listed.
+# The substring is highlighted.
+# An extra entry at the start is given which matches exactly the substring
+# and can be used to create a new entry.
+#
+# Buttons for contact list are:
+#  When nothing selected:
+#     New ALL Undelete
+#  When contact selected:
+#     Call SMS Open ALL
+#  When new-entry selected:
+#    Create ALL
+#
+# When the detailed contact info is displayed all fields can be
+# edited and change background colour if they differ from stored value
+# Button for details are:
+#  When nothing is changed:
+#     Call SMS Close Delete
+#  When something is changed on new entry
+#     Discard  Create Duplicate
+#  When something is changed on old entry
+#     Restore Change Delete
+#
+# 'delete' doesn't actually delete, but adds ' - deleted' to the name which
+# causes most lookups to ignore the entry.
+#
+from scrawl import Scrawl
+from listselect import ListSelect
+
+
+class Contacts(gtk.Window):
+    def __init__(self):
+        gtk.Window.__init__(self)
+        self.set_default_size(480,640)
+        self.set_title("Contacts")
+        self.connect('destroy', self.close_win)
+
+        self.make_ui()
+
+
+    def make_ui(self):
+        # UI consists of:
+        #   list of contacts -or-
+        #   editable field
+        #   -and-
+        #   variable list of buttons.
+        #
+        v = gtk.VBox(); v.show()
+        self.add(v)
+
+        s = ListSelect()
+        self.sel = s
+        s.callout = self.selected
+
+        v.pack_end(s, expand = True)
+        s.show()
+
+
+if __name__ == "__main__":
+
+    c = Contacts()
+    gtk.main()
+    
diff --git a/launch/cmd.py b/launch/cmd.py
new file mode 100644 (file)
index 0000000..28bce7b
--- /dev/null
@@ -0,0 +1,131 @@
+#
+# Support for running commands from the Laucher
+#
+# Once a command is run, we watch for it to finish
+# and update status when it does.
+# We also maintain window list and for commands that appear
+# in windows, we associate the window with the command.
+# ShellTask() runs a command and captures output in a text buffer
+#  that can be displayed in a FingerScroll
+# WinTask() runs a command in a window
+
+import os,fcntl, gobject
+from internal import Task
+from subprocess import Popen, PIPE
+from FingerScroll import FingerScroll
+
+class ShellTask(Task):
+    # Normally there is one button : "Run"
+    # When this is pressed we create a 'FingerScroll' text buffer
+    # to hold the output.
+    # The button then becomes 'ReRun'
+    # When we get deselected, the buffer gets hidden and we get
+    # new button 'Display'
+    def __init__(self, name, line):
+        Task.__init__(self)
+        self.name = name
+        self.type = "cmd"
+        self.append = False
+        self.cmd = line[1:]
+        if line[1] == '+':
+            self.append = True
+            self.cmd = line[2:]
+        self.buffer = None
+        self.displayed = False
+        self.job = None
+
+    def buttons(self):
+        if self.displayed:
+            if self.job:
+                return ['-','Kill']
+            else:
+                return ['ReRun', '-']
+        if self.buffer != None:
+            if self.job:
+                return ['-', 'Display']
+            else:
+                return ['Run', 'Display']
+        return ['Run']
+
+    def embedded(self):
+        if self.displayed:
+            return self.buffer
+        return None
+
+    def press(self, num):
+        if num == 1:
+            if self.displayed and self.job:
+                # must be a 'kill' request'
+                os.kill(self.job.pid, 15)
+                return
+            # Display the buffer
+            self.displayed = True
+            self.callback(self)
+            return
+
+        if self.job:
+            return
+
+        if self.buffer == None:
+            self.buffer = FingerScroll()
+            self.buffer.show()
+            self.buffer.connect('hide', self.unmap_buff)
+        self.buff = self.buffer.get_buffer()
+        if not self.append:
+            self.buff.delete(self.buff.get_start_iter(), self.buff.get_end_iter())
+        # run the program
+        self.job = Popen(self.cmd, shell=True, close_fds = True,
+                         stdout=PIPE, stderr=PIPE)
+
+        def set_noblock(f):
+            flg = fcntl.fcntl(f, fcntl.F_GETFL, 0)
+            fcntl.fcntl(f, fcntl.F_SETFL, flg | os.O_NONBLOCK)
+        set_noblock(self.job.stdout)
+        set_noblock(self.job.stderr)
+        self.wc = gobject.child_watch_add(self.job.pid, self.done)
+        self.wout = gobject.io_add_watch(self.job.stdout, gobject.IO_IN, self.read)
+        self.werr = gobject.io_add_watch(self.job.stderr, gobject.IO_IN, self.read)
+
+        self.displayed = True
+        
+    def read(self, f, dir):
+        l = f.read()
+        self.buff.insert(self.buff.get_end_iter(), l)
+        gobject.idle_add(self.adjust)
+        if l == "":
+            return False
+        return True
+
+    def adjust(self):
+        adj = self.buffer.vadj
+        adj.set_value(adj.upper - adj.page_size)
+
+    def done(self, *a):
+        self.read(self.job.stdout, None)
+        self.read(self.job.stderr, None)
+        gobject.source_remove(self.wout)
+        gobject.source_remove(self.werr)
+        self.job.stdout.close()
+        self.job.stderr.close()
+        self.job = None
+        self.callback(self)
+
+    def unmap_buff(self, widget):
+        if self.job == None:
+            self.displayed = False
+
+
+class WinTask(Task):
+    def __init__(self, name, window, line):
+        Task.__init__(self)
+        self.name = name
+        self.type = "win"
+
+    def buttons(self):
+        return ['run']
+
+    def press(self, num):
+        print "Thankyou for pressing", num 
+
+    def embedded(self):
+        return 'tapboard'
diff --git a/launch/grouptypes.py b/launch/grouptypes.py
new file mode 100644 (file)
index 0000000..29bb959
--- /dev/null
@@ -0,0 +1,120 @@
+#
+# Generic code for group types
+#
+# A 'group' must provide:
+#
+# - 'parse' to take a line from the config file and interpret it.
+# - [0] and [1] which are a  'name' and 'type' usable by ListSelect
+#    the type will normally be 'group'.
+# - 'tasklist' which provides a task list to display in the other
+#    ListSelect.
+# - 'embedded' which optionally provides a widget to display in
+#    the 'embedded widget' position.  This widget may also be
+#    displayed full-screen
+#
+# A 'group' can generate the signal:
+# - 'new-task-list' to report that the task list has changed
+# ???
+#
+
+import cmd, re
+
+class TaskGroup:
+    def __init__(self, name):
+        self.name = name
+        self.type = 'group'
+
+    def __getitem__(self, key):
+        if key == 0:
+            return self.name
+        if key == 1:
+            return self.type
+
+    def __len__(self):
+        return 1
+
+    def __iter__(self):
+        return (self.__getitem__(x) for x in range(2))
+
+    def embedded(self):
+        return None
+
+    def buttons(self):
+        return []
+
+class IgnoreType(TaskGroup):
+    def __init__(self, name):
+        TaskGroup.__init__(self, name)
+
+    def parse(self, line):
+        pass
+
+    def tasklist(self):
+        return []
+
+class ListType(TaskGroup):
+    """A ListType group parses a list of tasks out of
+    the config file and provides them as a static list.
+    Tasks can be:
+      !command   - run command and capture text output
+      (window)command - run command and expect it to appear as 'window'
+      command.command - run an internal command from the given module
+    In each case, arguments can follow and are treated as you would expect.
+    """
+
+    def __init__(self, name):
+        TaskGroup.__init__(self,name)
+        self.tasks = []
+
+    def parse(self, line):
+        line = line.strip()
+        m = re.match('^([A-Za-z0-9_ ]+)=(.*)', line)
+        if m:
+            name = m.groups()[0].strip()
+            line = m.groups()[1].strip()
+        else:
+            name = None
+        if line[0] == '!':
+            self.parse_txt(name, line)
+        elif line[0] == '(':
+            self.parse_win(name, line)
+        else:
+            self.parse_internal(name, line)
+
+    def tasklist(self):
+        return self.tasks
+
+    def parse_txt(self, name, line):
+        if name == None:
+            name = line[1:]
+        task = cmd.ShellTask(name, line)
+        if task.name:
+            self.tasks.append(task)
+
+    def parse_win(self, name, line):
+        f = line[1:].split(')', 1)
+        if len(f) != 2:
+            return
+        if name == None:
+            name = f[0]
+        task = cmd.WinTask(name, f[0], f[1])
+        if task:
+            self.tasks.append(task)
+
+    def parse_internal(self, name, line):
+        # split in to words, honouring quotes
+        words = map(lambda a: a[0].strip('"')+a[1].strip("'")+a[2],
+                    re.findall('("[^"]*")?(\'[^\']*\')?([^ "\'][^ "\']*)? *',
+                               line))[:-1]
+        
+        cmd = words[0].split('.', 1)
+        if len(cmd) != 2:
+            return
+
+        exec "import " + cmd[0]
+        fn = eval(words[0])
+        if name == None:
+            name = words[0]
+        task = fn(name, *words[1:])
+        if task:
+            self.tasks.append(task)
diff --git a/launch/internal.py b/launch/internal.py
new file mode 100644 (file)
index 0000000..cc86c6d
--- /dev/null
@@ -0,0 +1,107 @@
+#
+# internal commands for 'launch'
+# some of these affect launch directly, some are just
+# ad hoc simple things.
+
+import time as _time
+import gobject
+import dnotify, os
+
+class Task:
+    def __init__(self):
+        self.callback = None
+
+    def __getitem__(self, ind):
+        if ind == 0:
+            self.update()
+            return self.name
+        if ind == 1:
+            return self.type
+
+    def __len__(self):
+        return 2
+
+    def __iter__(self):
+        return (self.__getitem__(x) for x in range(2))
+
+    def buttons(self):
+        return []
+    def embedded(self):
+        return None
+    
+    def update(self):
+        pass
+
+class date(Task):
+    def __init__(self, name):
+        Task.__init__(self)
+        self.name = 'date'
+        self.type = 'cmd'
+        self.update()
+
+    def update(self):
+        self.name = _time.strftime('%d-%b-%Y',
+                                   _time.localtime(_time.time()))
+
+    def on_change(self, obj, ind):
+        now = _time.time()
+        next_hour = int(now/60/60)+1
+        gobject.timeout_add(int (((next_hour*3600) - now) * 1000),
+                            lambda : obj.item_changed(ind, self))
+
+
+class time(Task):
+    def __init__(self, name):
+        Task.__init__(self)
+        self.name = 'time'
+        self.type = 'cmd'
+        self.update()
+
+    def update(self):
+        self.name = _time.strftime('%H:%M',
+                                   _time.localtime(_time.time()))
+
+    def on_change(self, obj, ind):
+        now = _time.time()
+        next_min = int(now/60)+1
+        gobject.timeout_add(int (((next_min*60) - now) * 1000),
+                            lambda : obj.item_changed(ind, self))
+
+
+class file(Task):
+    def __init__(self, name, path):
+        Task.__init__(self)
+        self.path = path
+        self.name = 'file ' + path
+        self.type = 'cmd'
+        try:
+            self.watch = dnotify.dir(os.path.dirname(path))
+        except OSError:
+            self.watch = None
+        self.fwatch = None
+        self.update()
+
+    def update(self):
+        try:
+            f = open(self.path)
+            l = f.readline().strip()
+            f.close()
+        except:
+            l = "-"
+
+        self.name = l
+        if self.fwatch:
+            self.fwatch.cancel()
+            self.fwatch = None
+
+    def on_change(self, obj, ind):
+        if self.watch == None:
+            try:
+                self.watch = dnotify.dir(os.path.dirname(name))
+            except OSError:
+                self.watch = None
+        if self.watch == None:
+            return
+        if self.fwatch == None:
+            self.fwatch = self.watch.watch(os.path.basename(self.path),
+                                           lambda f: obj.item_changed(ind, self))
diff --git a/launch/launch.py b/launch/launch.py
new file mode 100644 (file)
index 0000000..7d6b070
--- /dev/null
@@ -0,0 +1,360 @@
+#!/usr/bin/env python
+
+#TODO
+# aux button
+# calculator
+# sms
+#   entry shows number of new messages
+#   embed shows latest new message if there is one
+#   buttons allow 'read' or 'open'
+# phone calls
+#   number of missed calls, or incoming number
+#   embed shows call log, or tap board
+#   buttons allow 'call' or 'answer' or 'open' ....
+# embed calendar
+#   selected by 'date'
+#   tapping can select a date
+# run windowed program
+# monitor list of windows
+# address list
+#   this is a 'group' where 'tasks' are contacts
+
+
+# Launcher, version 2.
+# This module just define the UI and plugin interface
+# All tasks etc go in loaded modules.
+#
+# The elements of the UI are:
+#
+#   ----------------------------------
+#   |  Text Entry box (one line)     |
+#   +--------------------------------+
+#   |                |               |
+#   |    group       |      task     |
+#   |   selection    |     selection |
+#   |    list        |      list     |
+#   |                |               |
+#   |                |               |
+#   +----------------+               |
+#   |   optional     |               |
+#   |   task-        |               |
+#   |     specific   |               |
+#   |   widget       +---------------|
+#   |                | secondary     |
+#   |                | button row    |
+#   +----------------+---------------+
+#   |         Main Button Row        |
+#   +--------------------------------+
+#
+# If the optional widget is present, then the Main Button Row
+# disappears and the secondary button row is used instead.
+#
+# The selection lists auto-scroll when an item near top or bottom
+# is selected.  Selecting an entry only updates other parts of
+# the UI and does not perform any significant action.
+# Actions are performed by buttons which appear in a button
+# row.
+# The optional widget can be anything provided by the group
+# or task, for example:
+#  - tap-board for entering text
+#  - calendar for selecting a date
+#  - display area for small messages (e.g. SMS)
+#  - preview during file selection
+#  - map of current location
+#
+# Entered text alway appears in the Text Entry box and
+# is made available to the current group and task
+# That object may use the text and may choose to delete it.
+# e.g.
+#   A calculator task might display an evaluation of the text
+#   A call task might offer to call the displayed number, and
+#     delete it when the call completes
+#   An address-book group might restrict displayed tasks
+#     to those which match the text
+#   A 'todo' task might set the text to match the task content,
+#     then update the content as the text changes.
+
+import sys, gtk, os, pango
+
+if __name__ == '__main__':
+    sys.path.insert(1, '/home/neilb/home/freerunner/lib')
+    sys.path.insert(1, '/root/lib')
+
+from tapboard import TapBoard
+import grouptypes, listselect, scrawl
+from grouptypes import *
+
+
+class LaunchWin(gtk.Window):
+    def __init__(self):
+        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
+        self.set_default_size(480, 640)
+        self.connect('destroy', lambda w: gtk.main_quit())
+
+        self.embeded_widget = None
+        self.widgets = {}
+        self.buttons = []
+        self.create_ui()
+        self.load_config()
+
+    def create_ui(self):
+        # Create the UI framework, first the components
+
+        # The Entry
+        e1 = gtk.Entry()
+        e1.set_alignment(0.5) ; # Center text
+        e1.connect('changed', self.entry_changed)
+        e1.connect('backspace', self.entry_changed)
+        e1.show()
+        self.entry = e1
+
+        # The group list
+        l1 = listselect.ListSelect(center = False)
+        l1.connect('selected', self.group_select)
+        # set appearance here
+        l1.set_colour('group','blue')
+        self.grouplist = l1
+        l1.set_zoom(40)
+        l1.show()
+
+        # The task list
+        l2 = listselect.ListSelect(center = True)
+        l2.connect('selected', self.task_select)
+        l2.set_colour('cmd', 'black')
+        l2.set_colour('win', 'blue')
+        l2.set_zoom(35)
+        # set appearance
+        self.tasklist = l2
+        l2.show()
+
+        # The embedded widget: provide a VBox as a place holder
+        v1 = gtk.VBox()
+        self.embed_box = v1
+
+        # The Main button box - buttons are added later
+        h1 = gtk.HBox(True)
+        self.main_buttons = h1
+        # HACK
+        h1.set_size_request(-1, 80)
+        h1.show()
+
+        # The Secondary button box
+        h2 = gtk.HBox(True)
+        h2.set_size_request(-1, 60)
+        self.secondary_buttons = h2
+
+        # Now make the two columns
+
+        v2 = gtk.VBox(True)
+        v2.pack_start(self.grouplist, expand = True)
+        v2.pack_end(self.embed_box, expand = False)
+        v2.show()
+
+        v3 = gtk.VBox()
+        v3.pack_start(self.tasklist, expand = True)
+        v3.pack_end(self.secondary_buttons, expand = False)
+        v3.show()
+
+        # and bind them together
+        h3 = gtk.HBox(True)
+        h3.pack_start(v2, expand=True)
+        h3.pack_end(v3, expand=True)
+        h3.show()
+
+        # And now one big vbox to hold it all
+        v4 = gtk.VBox()
+        v4.pack_start(e1, expand=False)
+        v4.pack_end(self.main_buttons, expand=False)
+        v4.pack_end(h3, expand=True)
+        v4.show()
+        self.add(v4)
+        self.show()
+
+        ## We want writing recognistion to work
+        ## over the whole middle section.  Only that
+        ## turns out to be too hard for my lowly gtk
+        ## skills.  So we do recognition separately
+        ## on each selection box only.
+        s1 = scrawl.Scrawl(l1, self.getsym, lambda p: l1.tap(p[0],p[1]))
+        s2 = scrawl.Scrawl(l2, self.getsym, lambda p: l2.tap(p[0],p[1]))
+        s1.set_colour('red')
+        s2.set_colour('blue')
+
+
+        ctx = self.get_pango_context()
+        fd = ctx.get_font_description()
+        fd.set_absolute_size(30 * pango.SCALE)
+        self.button_font = fd;
+        self.entry.modify_font(fd)
+
+    def load_config(self):
+        fname = os.path.join(os.environ['HOME'], ".launch2rc")
+        types = {}
+        types['ignore'] = IgnoreType
+        types['list' ] = ListType
+        groups = []
+        f = open(fname)
+        gobj = None
+        for line in f:
+            l = line.strip()
+            if not l:
+                continue
+            if l[0] == '[':
+                l = l.strip('[]')
+                f = l.split('/', 1)
+                group = f[0]
+                if len(f) > 1:
+                    group_type = f[1]
+                else:
+                    group_type = "list"
+                if group_type in types:
+                    gobj = types[group_type](group)
+                else:
+                    gobj = types['ignore'](group)
+                groups.append(gobj)
+            elif gobj != None:
+                gobj.parse(l)
+        self.grouplist.list = groups
+        self.grouplist.list_changed()
+
+
+    def entry_changed(self, entry):
+        print "fixme", entry.get_text()
+
+    def group_select(self, list, item):
+        print "select group", item
+        g = list.list[item]
+        self.tasklist.list = g.tasklist()
+        self.tasklist.list_changed()
+        if self.tasklist.list != None:
+            self.task_select(self.tasklist,
+                             self.tasklist.selected)
+
+    def task_select(self, list, item):
+        if item == None:
+            self.set_buttons(None)
+            self.set_embed(None)
+        else:
+            task = self.tasklist.list[item]
+            task.callback = self.update
+            self.set_buttons(task.buttons())
+            self.set_embed(task.embedded())
+
+    def update(self, task):
+        if self.tasklist.selected != None and \
+           self.tasklist.list[self.tasklist.selected] == task:
+            self.set_buttons(task.buttons())
+            self.set_embed(task.embedded())
+    
+    def set_buttons(self, list):
+        if not list:
+            # hide the button boxes
+            self.secondary_buttons.hide()
+            self.main_buttons.hide()
+            self.buttons = []
+            return
+        if self.same_buttons(list):
+            return
+
+        self.buttons = list
+        self.update_buttons(self.main_buttons)
+        self.update_buttons(self.secondary_buttons)
+        if self.embeded_widget:
+            self.main_buttons.hide()
+            self.secondary_buttons.show()
+        else:
+            self.secondary_buttons.hide()
+            self.main_buttons.show()
+
+    def same_buttons(self, list):
+        if len(list) != len(self.buttons):
+            return False
+        for i in range(len(list)):
+            if list[i] != self.buttons[i]:
+                return False
+        return True
+
+    def update_buttons(self, box):
+        # make sure there are enough buttons
+        have = len(box.get_children())
+        need = len(self.buttons) - have
+        if need > 0:
+            for i in range(need):
+                b = gtk.Button("?")
+                b.child.modify_font(self.button_font)
+                b.set_property('can-focus', False)
+                box.add(b)
+                b.connect('clicked', self.button_pressed, have + i)
+            have += need
+        b = box.get_children()
+        # hide extra buttons
+        if need < 0:
+            for i in range(-need):
+                b[have-i-1].hide()
+        # update each button
+        print have, need, self.buttons
+        for i in range(len(self.buttons)):
+            print i, 'is', self.buttons[i]
+            b[i].child.set_text(self.buttons[i])
+            b[i].show()
+
+    def button_pressed(self, widget, num):
+        self.tasklist.list[self.tasklist.selected].press(num)
+        self.task_select(self.tasklist,
+                         self.tasklist.selected)
+
+    def set_embed(self, widget):
+        if type(widget) == str:
+            widget = self.make_widget(widget)
+            
+        if widget == self.embeded_widget:
+            return
+        if self.embeded_widget:
+            self.embeded_widget.hide()
+            self.embed_box.remove(self.embeded_widget)
+            self.embeded_widget = None
+        if widget:
+            self.embed_box.add(widget)
+            self.embeded_widget = widget
+            widget.show()
+            self.main_buttons.hide()
+            self.embed_box.show()
+            if self.buttons:
+                self.secondary_buttons.show()
+        else:
+            self.embed_box.hide()
+            self.secondary_buttons.hide()
+            if self.buttons:
+                self.main_buttons.show()
+
+    def make_widget(self, name):
+        if name in self.widgets:
+            return self.widgets[name]
+        if name == "tapboard":
+            w = TapBoard()
+            def key(w, k):
+                if k == '\b':
+                    self.entry.emit('backspace')
+                elif k == 'Return':
+                    self.entry.emit('activate')
+                elif len(k) == 1:
+                    self.entry.emit('insert-at-cursor', k)
+            w.connect('key', key)
+            self.widgets[name] = w
+            return w
+        return None
+
+    def getsym(self, sym):
+        print "gotsym", sym
+        if sym == '<BS>':
+            self.entry.emit('backspace')
+        elif sym == '<newline>':
+            self.entry.emit('activate')
+        elif len(sym) == 1:
+            self.entry.emit('insert-at-cursor', sym)
+
+if __name__ == '__main__':
+    sys.path.insert(1, '/home/neilb/home/freerunner/lib')
+    sys.path.insert(1, '/root/lib')
+    l = LaunchWin()
+    gtk.main()
similarity index 99%
rename from launcher/fingerscroll.py
rename to lib/fingerscroll.py
index 662484f2dd51d9e6efb38445f6f8f12a53a3506f..b69fa80a151deeebaaa3613569f5a3f8603c3d11 100644 (file)
@@ -83,3 +83,5 @@ if __name__ == "__main__":
         l = f.readline()
 
     gtk.main()
+
+    
diff --git a/lib/listselect.py b/lib/listselect.py
new file mode 100644 (file)
index 0000000..6d2d6a1
--- /dev/null
@@ -0,0 +1,430 @@
+#!/usr/bin/env python
+
+#TODO
+# - centering
+# - test variable-length list
+
+# This module provides the "Select" widget which can be used to
+# selected one item from a list, such as a command, and file, or
+# anything else.  Selecting an object should not have any significant
+# effect (though the control of that is outside this module).  That is
+# because this widget is intended for a finger-touch display and
+# precision might not be very good - a wrong selection should be
+# easily changed.
+#
+# A scale factor is available (though external controls must be used
+# to change it). With small scale factors, the display might use
+# multiple columns to get more entries on the display.
+#
+# There is no direct control of scolling.  Rather the list is
+# automatically scrolled to ensure that the selected item is displayed
+# and is not to close to either end.  If the selected item would be
+# near the end, it is scrolled to be near the beginning, and similarly
+# if it is near the beginning, the list is scrolled so that the
+# selected item is near the end of the display
+#
+# However we never display blank space before the list and try to
+# avoid displaying more than one blank space after the list.
+#
+# Each entry is a short text.  It can have a number of highlights
+# including:
+#  - foreground colour
+#  - background colour
+#  - underline
+#  - leading bullet
+#
+# The text is either centered in the column or left justified
+# (possibly leaving space for a bullet).
+#
+# This widget only provides display of the list and selection.
+# It does not process input events directly.  Rather some other
+# module must take events from this window (or elsewhere) and send
+# 'tap' events to this widget as appropriate.  They are converted
+# to selections.  This allows e.g. a writing-recognition widget
+# to process all input and keep strokes to itself, only sending
+# taps to us.
+#
+# The list of elements is passed as an array.  However it could be
+# changed at any time.  This widget assumes that it will be told
+# whenever the list changes so it doesn't have to poll the list
+# at all.
+#
+# When the list does change, we try to preserve the currently selected
+# position based on the text of the entry.
+#
+# We support arrays with an apparent size of 0.  In this case we
+# don't try to preserve location on a change, and might display
+# more white space at the end of the array (which should appear
+# as containing None).
+#
+# It is possible to ask the "Select" to move the the "next" or
+# "previous" item.  This can have a function which tests candidates
+# for suitability.
+#
+# An entry in the list has two parts: the string and the highlight
+# It must look like a tuple:  e[0] is the string. e[1] is the highlight
+# The highlight can be just a string, in which case it is a colour name,
+# or a tuple of (colour underline bullet background selected-background)
+# missing fields default to (black False False grey white)
+# Also a mapping from string to type can be created.
+
+import gtk, pango, gobject
+
+
+class ListSelect(gtk.DrawingArea):
+    __gsignals__ = {
+        'selected' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+                      (gobject.TYPE_INT,))
+        }
+    def __init__(self, center = False):
+        gtk.DrawingArea.__init__(self)
+
+        # Index of first entry displayed
+        self.top = 0
+        # Index of currently selected entry 
+        self.selected = None
+        # string value of current selection
+        self.selected_str = None
+        self.list = []
+        self.center = center
+
+        self.callout = None
+        
+        self.fd = self.get_pango_context().get_font_description()
+        # zoom level: 20..50
+        self.zoom = 0
+        self.width = 1
+        self.height = 1
+        self.rows = 1
+        self.cols = 1
+        self.bullet_space = True
+
+        self.format_list = {}
+        self.colours = {}
+        self.to_draw = []
+        self.set_zoom(30)
+        self.connect("expose-event", self.draw)
+        self.connect("configure-event", self.reconfig)
+
+        self.connect_after("button_press_event", self.press)
+        self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+
+
+    def press(self, c, ev):
+        #print "press"
+        self.tap(ev.x, ev.y)
+        
+    def draw(self, w, ev):
+        # draw any field that is in the area
+        (x,y,w,h) = ev.area
+        for c in range(self.cols):
+            if (c+1) * self.colwidth < x:
+                continue
+            if x + w < c * self.colwidth:
+                break
+            for r in range(self.rows):
+                if (r+1) * self.lineheight < y:
+                    continue
+                if y + h < r * self.lineheight:
+                    break
+                if (r,c) not in self.to_draw:
+                    self.to_draw.append((r,c))
+        if ev.count == 0:
+            for r,c in self.to_draw:
+                self.draw_one(r,c)
+        self.to_draw = []
+
+    def draw_one(self, r, c, task = None):
+        ind = r + c * self.rows + self.top
+        if len(self.list) >= 0 and ind >= len(self.list):
+            val = None
+        else:
+            val = self.list[ind]
+        if task != None and task != val:
+            return
+        if val == None:
+            strng,fmt = "", "blank"
+        else:
+            strng,fmt = val
+            try:
+                val.on_change(self, ind)
+            except:
+                pass
+
+        if type(fmt) == str:
+            fmt = self.get_format(fmt)
+
+        if len(fmt) == 5:
+            (col, under, bullet, back, sel) = fmt
+            bold=(0,0)
+        if len(fmt) == 6:
+            (col, under, bullet, back, sel, bold) = fmt
+        # draw background rectangle
+        if ind == self.selected:
+            self.window.draw_rectangle(self.get_colour(sel), True,
+                                c*self.colwidth, r*self.lineheight,
+                                self.colwidth, self.lineheight)
+        else:
+            self.window.draw_rectangle(self.get_colour(back), True,
+                                       c*self.colwidth, r*self.lineheight,
+                                       self.colwidth, self.lineheight)
+        if bullet:
+            w = int(self.lineheight * 0.4)
+            vo = (self.lineheight - w)/2
+            ho = 0
+            self.window.draw_rectangle(self.get_colour(col), True,
+                                       c*self.colwidth+ho, r*self.lineheight + vo,
+                                       w, w)
+
+        # draw text
+        layout = self.create_pango_layout(strng)
+        a = pango.AttrList()
+        if under:
+            a.insert(pango.AttrUnderline(pango.UNDERLINE_SINGLE, 0, len(strng)))
+        if bold[0] < bold[1]:
+            a.insert(pango.AttrWeight(pango.WEIGHT_BOLD,bold[0], bold[1]))
+        layout.set_attributes(a)
+
+        offset = self.offset
+        if self.center:
+            ink, (ex,ey,ew,eh) = layout.get_pixel_extents()
+            offset = int((self.colwidth - ew) / 2)
+        if offset < 0:
+            offset = 0
+
+        # FIXME
+        self.window.draw_layout(self.get_colour(col),
+                                c*self.colwidth + offset,
+                                r*self.lineheight,
+                                layout)
+        
+
+    def set_colour(self, name, col):
+        self.colours[name] = col
+    def get_colour(self, col):
+        # col is either a colour name, or a pre-set colour.
+        # so if it isn't in the list, add it
+        if col == None:
+            return self.get_style().bg_gc[gtk.STATE_NORMAL]
+        if col not in self.colours:
+            self.set_colour(col, col)
+        if type(self.colours[col]) == str:
+            gc = self.window.new_gc()
+            gc.set_foreground(self.get_colormap().
+                              alloc_color(gtk.gdk.color_parse(self.colours[col])))
+            self.colours[col] = gc;
+        return self.colours[col]
+
+    
+    def set_format(self, name, colour, underline=False, bullet=False,
+                background=None, selected="white"):
+        self.format_list[name] = (colour, underline, bullet, background, selected)
+
+    def get_format(self, name):
+        if name in self.format_list:
+            return self.format_list[name]
+        if name == "blank":
+            return (None, False, False, None, None)
+        return (name, False, False, None, "white")
+
+    def calc_layout(self):
+        # The zoom or size or list has changed.
+        # We need to calculate lineheight and colwidth
+        # and from those, rows and cols.
+        # If the list is of indefinite length we cannot check the
+        # width of every entry so we just check until we have enough
+        # to fill the page
+
+        i = 0
+        n = len(self.list)
+        indefinite = (n < 0)
+        maxw = 1; maxh = 1;
+        while n < 0 or i < n:
+            e = self.list[i]
+            if e == None:
+                break
+            strng, fmt = e
+            layout = self.create_pango_layout(strng)
+            ink, (ex,ey,ew,eh) = layout.get_pixel_extents()
+            if ew > maxw: maxw = ew
+            if eh > maxh: maxh = eh
+
+            if indefinite:
+                rs = int(self.height / maxh)
+                cs = int(self.width / maxw)
+                if rs < 1: rs = 1
+                if cs < 1: cs = 1
+                n = self.top + rs * cs
+            i += 1
+
+        real_maxw = maxw
+        if self.bullet_space:
+            maxw = maxw + maxh
+        self.rows = int(self.height / maxh)
+        self.cols = int(self.width / maxw)
+        if i == 0 or self.rows == 0:
+            self.rows = 1
+        if self.cols > int((i + self.rows-1) / self.rows):
+            self.cols = int((i + self.rows-1) / self.rows)
+        if self.cols == 0:
+            self.cols = 1
+        self.lineheight = maxh
+        self.colwidth = int(self.width / self.cols)
+        self.offset = (self.colwidth - real_maxw) / 2
+
+    def check_scroll(self):
+        # the top and/or selected have changed, or maybe the layout has
+        # changed.
+        # We need to make sure 'top' is still appropriate.
+        oldtop = self.top
+        if self.selected == None:
+            self.top = 0
+        else:
+            margin = self.rows / 3
+            if margin < 1:
+                margin = 1
+            remainder = self.rows * self.cols - margin
+            if self.selected < self.top + margin:
+                self.top = self.selected - (remainder - 1)
+                if self.top < 0:
+                    self.top = 0
+            if self.selected >= self.top + remainder:
+                self.top = self.selected - margin
+                l = len(self.list)
+                if l >= 0 and self.top + self.rows * self.cols > l:
+                    self.top = l - self.rows * self.cols + 1
+
+        return self.top != oldtop
+
+    def reconfig(self, w, ev):
+        alloc = w.get_allocation()
+        if alloc.width != self.width or alloc.height != self.height:
+            self.width, self.height = alloc.width, alloc.height
+            self.calc_layout()
+            self.check_scroll()
+            self.queue_draw()
+
+    def set_zoom(self, zoom):
+        if zoom > 50:
+            zoom = 50
+        if zoom < 20:
+            zoom = 20
+        if zoom == self.zoom:
+            return
+        self.zoom = zoom
+        s = pango.SCALE
+        for i in range(zoom):
+            s = s * 11 / 10
+        self.fd.set_absolute_size(s)
+        self.modify_font(self.fd)
+
+        self.calc_layout()
+        self.check_scroll()
+        self.queue_draw()
+
+    def list_changed(self):
+        l = len(self.list)
+        if l >= 0:
+            for i in range(l):
+                if self.list[i][0] == self.selected_str:
+                    self.selected = i
+                    break
+            if self.selected >= l:
+                self.selected = None
+            elif self.selected != None:
+                self.selected_str = self.list[self.selected][0]
+        self.calc_layout()
+        self.check_scroll()
+        self.queue_draw()
+
+    def item_changed(self, ind, task = None):
+        # only changed if it is still 'task'
+        col = (ind - self.top) / self.rows
+        row = (ind - self.top) - (col * self.rows)
+        self.draw_one(row, col, task)
+
+
+    def map_pos(self, x, y):
+        row = int(y / self.lineheight)
+        col = int(x / self.colwidth)
+        ind = row + col * self.rows + self.top
+        l = len(self.list)
+        if l >= 0 and ind >= l:
+            return None
+        if l < 0 and self.list[ind] == None:
+            return None
+        return ind
+
+    def tap(self, x, y):
+        ind = self.map_pos(x,y)
+        if ind != None:
+            self.select(ind)
+
+    def select(self, ind):
+        if self.selected == ind:
+            return
+        old = self.selected
+        self.selected = ind
+        self.selected_str = self.list[ind][0]
+        if self.window == None or self.check_scroll():
+            self.queue_draw()
+        else:
+            col = (ind - self.top) / self.rows
+            row = (ind - self.top) - (col * self.rows)
+            self.draw_one(row, col)
+            if old != None:
+                col = (old - self.top) / self.rows
+                row = (old - self.top) - (col * self.rows)
+                self.draw_one(row, col)
+        if self.callout:
+            self.callout(ind, self.list[ind])
+        self.emit('selected', ind)
+
+if __name__ == "__main__":
+
+    # demo app using this widget
+    w = gtk.Window(gtk.WINDOW_TOPLEVEL)
+    w.connect("destroy", lambda w: gtk.main_quit())
+    w.set_title("ListSelect Test")
+
+    s = ListSelect()
+    list = [ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
+               "nine", "ten", "eleven", "twelve", "thirteen", "forteen"]
+    el = []
+    for a in list:
+        el.append((a, "blue"))
+    el[9] = (el[9][0], ("red",True,True,"black","white"))
+    el[13] = (el[13][0], ("black",False,False,"yellow","white",(4,8)))
+    def sel(n, i):
+        s,f = i
+        print n, s, "selected"
+    s.callout = sel
+
+    s.list = el
+    s.select(12)
+    w.add(s)
+    s.show()
+    w.show()
+
+    def key(c, ev):
+        print "key"
+        if ev.string == '+':
+            s.set_zoom(c.zoom+1)
+        if ev.string == '-':
+            s.set_zoom(c.zoom-1)
+    w.connect("key_press_event", key)
+    w.add_events(gtk.gdk.KEY_PRESS_MASK)
+    #w.set_property('can-focus', True)
+    #s.grab_focus()
+
+    han = 0
+    def tap(c, ev):
+        print "tap", ev.send_event
+        #s.tap(ev.x, ev.y)
+        c.handler_block(han)
+        c.event(ev)
+        c.handler_unblock(han)
+        c.stop_emission("button_press_event")
+
+    han = s.connect("button_press_event", tap)
+    gtk.main()
diff --git a/lib/scrawl.py b/lib/scrawl.py
new file mode 100644 (file)
index 0000000..7d514f8
--- /dev/null
@@ -0,0 +1,860 @@
+#! /usr/bin/env python
+
+#TODO
+# - Check combinations of not-enabled
+
+
+# "Scrawl" is a module for processing mouse movements and converting
+# them to ascii character.
+#
+# Given a widget-window it collects mouse events and reports them
+# as some of the following:
+#  'tap' - press/release with minimal movement is a tap
+#  'sym' - press-draw-release is interpreted as a sym where possible
+#  'drag' - press-hold-move is interpretted as a drag.  A callout provides
+#         start and current points, and a 'finish' signal
+#  'select' press-draw-hold is interpretted as a selection.  On the final
+#         release, a list of points is passed to the callout
+#
+# Each of these can be disabled by clearing the relevant '*call' handler
+# If 'sym' is None, it is passed to 'select'
+# If 'drag' is none, an initial hold is ignored
+#
+
+import gtk, gobject, time, math
+
+class Scrawl:
+    def __init__(self, win, sym = None, tap=None, drag=None, select=None):
+        self.window = win
+        self.symcall = sym
+        self.tapcall = tap
+        self.dragcall = drag
+        self.selectcall = select
+
+        self.dragtime = 500
+        self.selecttime = 500
+        self.timer = None
+
+        self.line = None
+        self.colour = None
+
+        self.dict = Dictionary()
+        LoadDict(self.dict)
+
+        win.add_events(gtk.gdk.POINTER_MOTION_MASK
+                       | gtk.gdk.BUTTON_PRESS_MASK
+                       | gtk.gdk.BUTTON_RELEASE_MASK
+                       )
+
+        self.presshan = win.connect("button_press_event", self.press)
+        self.releasehan = win.connect("button_release_event", self.release)
+        self.motionhan = win.connect("motion_notify_event", self.motion)
+
+    def press(self, c, ev):
+        # Start new line
+        c.stop_emission("button_press_event")
+        self.line = [ [int(ev.x), int(ev.y)] ]
+        self.bbox = BBox(Point(ev.x,ev.y))
+        self.dragging = False
+        self.selecting = False
+        if self.timer:
+            gobject.source_remove(self.timer)
+        if self.dragcall:
+            self.timer = gobject.timeout_add(self.dragtime, self.set_drag)
+        
+    def set_drag(self):
+        self.dragging = True
+        self.dragcall(self.line[0], self.line[-1], False)
+    def set_select(self):
+        self.selecting = True
+        self.selectcall(self.line, False)
+
+    def release(self, c, ev):
+        c.stop_emission("button_release_event")
+        if self.timer:
+            gobject.source_remove(self.timer)
+            self.timer = None
+
+        line = self.line
+        self.line = None
+        if line == None:
+            return
+        if self.dragging:
+            if len(line) == 1:
+                # this must be treated like a select, but close the drag
+                self.dragcall(line[0], line[0], True)
+                if self.selectcall:
+                    self.selectcall(line, False)
+                    self.selectcall(line, True)
+                elif self.tapcall:
+                    c.handler_block(self.presshan)
+                    c.handler_block(self.releasehan)
+                    self.tapcall(line[0])
+                    c.handler_unblock(self.presshan)
+                    c.handler_unblock(self.releasehan)
+                return
+            self.dragcall(line[0], line[-1], True)
+            return
+
+        # send an expose event to clean up the line
+        bb = self.bbox
+        self.window.window.invalidate_rect(gtk.gdk.Rectangle(bb.minx, bb.miny,
+                                                             bb.width()+1, bb.height()+1),
+                                           True)
+        
+        if len(line) == 1 and self.tapcall:
+            c.handler_block(self.presshan)
+            c.handler_block(self.releasehan)
+            self.tapcall(line[0])
+            c.handler_unblock(self.presshan)
+            c.handler_unblock(self.releasehan)
+            return
+        if self.selectcall and self.selecting:
+            self.selectcall(line, True)
+            return
+
+        if not self.symcall:
+            if self.selectcall:
+                self.selectcall(line)
+            return
+
+        # look for a symbol match
+        alloc = self.window.get_allocation()
+        pagebb = BBox(Point(0,0))
+        pagebb.add(Point(alloc.width, alloc.height))
+        pagebb.finish(div = 2)
+
+        p = PPath(line[1][0], line[1][1])
+        for pp in line[1:]:
+            p.add(pp[0], pp[1])
+        p.close()
+        patn = p.text()
+        pos = pagebb.relpos(p.bbox)
+        tpos = "mid"
+        if pos < 3:
+            tpos = "top"
+        if pos >= 6:
+            tpos = "bot"
+        sym = self.dict.match(patn, tpos)
+        if sym == None:
+            print "Failed to match pattern:", patn
+        else:
+            self.symcall(sym)
+
+
+    def motion(self, c, ev):
+        if self.line:
+            if ev.is_hint:
+                x, y, state = ev.window.get_pointer()
+            else:
+                x = ev.x
+                y = ev.y
+            x = int(x)
+            y = int(y)
+
+            self.bbox.add(Point(x,y))
+            prev = self.line[-1]
+            if abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10:
+                return
+            if self.timer:
+                gobject.source_remove(self.timer)
+            self.timer = None
+            if self.colour:
+                c.window.draw_line(self.colour, prev[0], prev[1], x, y)
+            self.line.append([x,y])
+            if self.dragging:
+                self.dragcall(self.line[0], self.line[-1], False)
+            else:
+                if not self.symcall:
+                    self.selecting = True
+                if self.selecting:
+                    self.selectcall(self.line, False)
+                else:
+                    self.timer = gobject.timeout_add(self.selecttime, self.set_select)
+
+    def set_colour(self, col):
+        if type(col) == str:
+            c = gtk.gdk.color_parse(col)
+            gc = self.window.window.new_gc()
+            gc.set_foreground(self.window.get_colormap().alloc_color(c))
+            self.colour = gc
+        else:
+            self.colour = col
+
+def LoadDict(dict):
+    # Upper case.
+    # Where they are like lowercase, we either double
+    # the last stroke (L, J, I) or draw backwards (S, Z, X)
+    # U V are a special case
+
+    dict.add('A', "R(4)6,8")
+    dict.add('B', "R(4)6,4.R(7)1,6")
+    dict.add('B', "R(4)6,4.L(4)2,8.R(7)1,6")
+    dict.add('B', "S(6)7,1.R(4)6,4.R(7)0,6")
+    dict.add('C', "R(4)8,2")
+    dict.add('D', "R(4)6,6")
+    dict.add('E', "L(1)2,8.L(7)2,8")
+    # double the stem for F
+    dict.add('F', "L(4)2,6.S(3)7,1")
+    dict.add('F', "S(1)5,3.S(3)1,7.S(3)7,1")
+
+    dict.add('G', "L(4)2,5.S(8)1,7")
+    dict.add('G', "L(4)2,5.R(8)6,8")
+    # FIXME I need better straight-curve alignment
+    dict.add('H', "S(3)1,7.R(7)6,8.S(5)7,1")
+    dict.add('H', "L(3)0,5.R(7)6,8.S(5)7,1")
+    # capital I is down/up
+    dict.add('I', "S(4)1,7.S(4)7,1")
+
+    # Capital J has a left/right tail
+    dict.add('J', "R(4)1,6.S(7)3,5")
+
+    dict.add('K', "L(4)0,2.R(4)6,6.L(4)2,8")
+
+    # Capital L, like J, doubles the foot
+    dict.add('L', "L(4)0,8.S(7)4,3")
+
+    dict.add('M', "R(3)6,5.R(5)3,8")
+    dict.add('M', "R(3)6,5.L(1)0,2.R(5)3,8")
+
+    dict.add('N', "R(3)6,8.L(5)0,2")
+
+    # Capital O is CW, but can be CCW in special dict
+    dict.add('O', "R(4)1,1", bot='0')
+
+    dict.add('P', "R(4)6,3")
+    dict.add('Q', "R(4)7,7.S(8)0,8")
+
+    dict.add('R', "R(4)6,4.S(8)0,8")
+
+    # S is drawn bottom to top.
+    dict.add('S', "L(7)6,1.R(1)7,2")
+
+    # Double the stem for capital T
+    dict.add('T', "R(4)0,8.S(5)7,1")
+
+    # U is L to R, V is R to L for now
+    dict.add('U', "L(4)0,2")
+    dict.add('V', "R(4)2,0")
+
+    dict.add('W', "R(5)2,3.L(7)8,6.R(3)5,0")
+    dict.add('W', "R(5)2,3.R(3)5,0")
+
+    dict.add('X', "R(4)6,0")
+
+    dict.add('Y',"L(1)0,2.R(5)4,6.S(5)6,2")
+    dict.add('Y',"L(1)0,2.S(5)2,7.S(5)7,2")
+
+    dict.add('Z', "R(4)8,2.L(4)6,0")
+
+    # Lower case
+    dict.add('a', "L(4)2,2.L(5)1,7")
+    dict.add('a', "L(4)2,2.L(5)0,8")
+    dict.add('a', "L(4)2,2.S(5)0,8")
+    dict.add('b', "S(3)1,7.R(7)6,3")
+    dict.add('c', "L(4)2,8", top='C')
+    dict.add('d', "L(4)5,2.S(5)1,7")
+    dict.add('d', "L(4)5,2.L(5)0,8")
+    dict.add('e', "S(4)3,5.L(4)5,8")
+    dict.add('e', "L(4)3,8")
+    dict.add('f', "L(4)2,6", top='F')
+    dict.add('f', "S(1)5,3.S(3)1,7", top='F')
+    dict.add('g', "L(1)2,2.R(4)1,6")
+    dict.add('h', "S(3)1,7.R(7)6,8")
+    dict.add('h', "L(3)0,5.R(7)6,8")
+    dict.add('i', "S(4)1,7", top='I', bot='1')
+    dict.add('j', "R(4)1,6", top='J')
+    dict.add('k', "L(3)0,5.L(7)2,8")
+    dict.add('k', "L(4)0,5.R(7)6,6.L(7)1,8")
+    dict.add('l', "L(4)0,8", top='L')
+    dict.add('l', "S(3)1,7.S(7)3,5", top='L')
+    dict.add('m', "S(3)1,7.R(3)6,8.R(5)6,8")
+    dict.add('m', "L(3)0,2.R(3)6,8.R(5)6,8")
+    dict.add('n', "S(3)1,7.R(4)6,8")
+    dict.add('o', "L(4)1,1", top='O', bot='0')
+    dict.add('p', "S(3)1,7.R(4)6,3")
+    dict.add('q', "L(1)2,2.L(5)1,5")
+    dict.add('q', "L(1)2,2.S(5)1,7.R(8)6,2")
+    dict.add('q', "L(1)2,2.S(5)1,7.S(5)1,7")
+    # FIXME this double 1,7 is due to a gentle where the
+    # second looks like a line because it is narrow.??
+    dict.add('r', "S(3)1,7.R(4)6,2")
+    dict.add('s', "L(1)2,7.R(7)1,6", top='S', bot='5')
+    dict.add('t', "R(4)0,8", top='T', bot='7')
+    dict.add('t', "S(1)3,5.S(5)1,7", top='T', bot='7')
+    dict.add('u', "L(4)0,2.S(5)1,7")
+    dict.add('v', "L(4)0,2.L(2)0,2")
+    dict.add('w', "L(3)0,2.L(5)0,2", top='W')
+    dict.add('w', "L(3)0,5.R(7)6,8.L(5)3,2", top='W')
+    dict.add('w', "L(3)0,5.L(5)3,2", top='W')
+    dict.add('x', "L(4)0,6", top='X')
+    dict.add('y', "L(1)0,2.R(5)4,6", top='Y') # if curved
+    dict.add('y', "L(1)0,2.S(5)2,7", top='Y')
+    dict.add('z', "R(4)0,6.L(4)2,8", top='Z', bot='2')
+
+    # Digits
+    dict.add('0', "L(4)7,7")
+    dict.add('0', "R(4)7,7")
+    dict.add('1', "S(4)7,1")
+    dict.add('2', "R(4)0,6.S(7)3,5")
+    dict.add('2', "R(4)3,6.L(4)2,8")
+    dict.add('3', "R(1)0,6.R(7)1,6")
+    dict.add('4', "L(4)7,5")
+    dict.add('5', "L(1)2,6.R(7)0,3")
+    dict.add('5', "L(1)2,6.L(4)0,8.R(7)0,3")
+    dict.add('6', "L(4)2,3")
+    dict.add('7', "S(1)3,5.R(4)1,6")
+    dict.add('7', "R(4)0,6")
+    dict.add('7', "R(4)0,7")
+    dict.add('8', "L(4)2,8.R(4)4,2.L(3)6,1")
+    dict.add('8', "L(1)2,8.R(7)2,0.L(1)6,1")
+    dict.add('8', "L(0)2,6.R(7)0,1.L(2)6,0")
+    dict.add('8', "R(4)2,6.L(4)4,2.R(5)8,1")
+    dict.add('9', "L(1)2,2.S(5)1,7")
+
+    dict.add(' ', "S(4)3,5")
+    dict.add('<BS>', "S(4)5,3")
+    dict.add('-', "S(4)3,5.S(4)5,3")
+    dict.add('_', "S(4)3,5.S(4)5,3.S(4)3,5")
+    dict.add("<left>", "S(4)5,3.S(3)3,5")
+    dict.add("<right>","S(4)3,5.S(5)5,3")
+    dict.add("<left>", "S(4)7,1.S(1)1,7") # "<up>"
+    dict.add("<right>","S(4)1,7.S(7)7,1") # "<down>"
+    dict.add("<newline>", "S(4)2,6")
+
+
+class DictSegment:
+    # Each segment has for elements:
+    #   direction: Right Straight Left (R=cw, L=ccw)
+    #   location: 0-8.
+    #   start: 0-8
+    #   finish: 0-8
+    # Segments match if there difference at each element
+    # is 0, 1, or 3 (RSL coded as 012)
+    # A difference of 1 required both to be same / 3
+    # On a match, return number of 0s
+    # On non-match, return -1
+    def __init__(self, str):
+        # D(L)S,R
+        # 0123456
+        self.e = [0,0,0,0]
+        if len(str) != 7:
+            raise ValueError
+        if str[1] != '(' or str[3] != ')' or str[5] != ',':
+            raise ValueError
+        if str[0] == 'R':
+            self.e[0] = 0
+        elif str[0] == 'L':
+            self.e[0] = 2
+        elif str[0] == 'S':
+            self.e[0] = 1
+        else:
+            raise ValueError
+
+        self.e[1] = int(str[2])
+        self.e[2] = int(str[4])
+        self.e[3] = int(str[6])
+
+    def match(self, other):
+        cnt = 0
+        for i in range(0,4):
+            diff = abs(self.e[i] - other.e[i])
+            if diff == 0:
+                cnt += 1
+            elif diff == 3:
+                pass
+            elif diff == 1 and (self.e[i]/3 == other.e[i]/3):
+                pass
+            else:
+                return -1
+        return cnt
+
+class DictPattern:
+    # A Dict Pattern is a list of segments.
+    # A parsed pattern matches a dict pattern if
+    # the are the same nubmer of segments and they
+    # all match.  The value of the match is the sum
+    # of the individual matches.
+    # A DictPattern is printers as segments joined by periods.
+    #
+    def __init__(self, str):
+        self.segs = map(DictSegment, str.split("."))
+    def match(self,other):
+        if len(self.segs) != len(other.segs):
+            return -1
+        cnt = 0
+        for i in range(0,len(self.segs)):
+            m = self.segs[i].match(other.segs[i])
+            if m < 0:
+                return m
+            cnt += m
+        return cnt
+
+
+class Dictionary:
+    # The dictionary hold all the pattern for symbols and
+    # performs lookup
+    # Each pattern in the directionary can be associated
+    # with  3 symbols.  One when drawing in middle of screen,
+    # one for top of screen, one for bottom.
+    # Often these will all be the same.
+    # This allows e.g. s and S to have the same pattern in different
+    # location on the touchscreen.
+    # A match requires a unique entry with a match that is better
+    # than any other entry.
+    #
+    def __init__(self):
+        self.dict = []
+    def add(self, sym, pat, top = None, bot = None):
+        if top == None: top = sym
+        if bot == None: bot = sym
+        self.dict.append((DictPattern(pat), sym, top, bot))
+
+    def _match(self, p):
+        max = -1
+        val = None
+        for (ptn, sym, top, bot) in self.dict:
+            cnt = ptn.match(p)
+            if cnt > max:
+                max = cnt
+                val = (sym, top, bot)
+            elif cnt == max:
+                val = None
+        return val
+
+    def match(self, str, pos = "mid"):
+        p = DictPattern(str)
+        m = self._match(p)
+        if m == None:
+            return m
+        (mid, top, bot) = self._match(p)
+        if pos == "top": return top
+        if pos == "bot": return bot
+        return mid
+
+
+class Point:
+    # This represents a point in the path and all the points leading
+    # up to it.  It allows us to find the direction and curvature from
+    # one point to another
+    # We store x,y, and sum/cnt of points so far
+    def __init__(self,x,y):
+        x = int(x); y = int(y)
+        self.xsum = x
+        self.ysum = y
+        self.x = x
+        self.y = y
+        self.cnt = 1
+
+    def copy(self):
+        n = Point(0,0)
+        n.xsum = self.xsum
+        n.ysum = self.ysum
+        n.x = self.x
+        n.y = self.y
+        n.cnt = self.cnt
+        return n
+
+    def add(self,x,y):
+        if self.x == x and self.y == y:
+            return
+        self.x = x
+        self.y = y
+        self.xsum += x
+        self.ysum += y
+        self.cnt += 1
+
+    def xlen(self,p):
+        return abs(self.x - p.x)
+    def ylen(self,p):
+        return abs(self.y - p.y)
+    def sqlen(self,p):
+        x = self.x - p.x
+        y = self.y - p.y
+        return x*x + y*y
+
+    def xdir(self,p):
+        if self.x > p.x:
+            return 1
+        if self.x < p.x:
+            return -1
+        return 0
+    def ydir(self,p):
+        if self.y > p.y:
+            return 1
+        if self.y < p.y:
+            return -1
+        return 0
+    def curve(self,p):
+        if self.cnt == p.cnt:
+            return 0
+        x1 = p.x ; y1 = p.y
+        (x2,y2) = self.meanpoint(p)
+        x3 = self.x; y3 = self.y
+
+        curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
+        curve = curve * 100 / ((y3-y1)*(y3-y1)
+                               + (x3-x1)*(x3-x1))
+        if curve > 6:
+            return 1
+        if curve < -6:
+            return -1
+        return 0
+
+    def Vcurve(self,p):
+        if self.cnt == p.cnt:
+            return 0
+        x1 = p.x ; y1 = p.y
+        (x2,y2) = self.meanpoint(p)
+        x3 = self.x; y3 = self.y
+
+        curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
+        curve = curve * 100 / ((y3-y1)*(y3-y1)
+                               + (x3-x1)*(x3-x1))
+        return curve
+
+    def meanpoint(self,p):
+        x = (self.xsum - p.xsum) / (self.cnt - p.cnt)
+        y = (self.ysum - p.ysum) / (self.cnt - p.cnt)
+        return (x,y)
+
+    def is_sharp(self,A,C):
+        # Measure the cosine at self between A and C
+        # as A and C could be curve, we take the mean point on
+        # self.A and self.C as the points to find cosine between
+        (ax,ay) = self.meanpoint(A)
+        (cx,cy) = self.meanpoint(C)
+        a = ax-self.x; b=ay-self.y
+        c = cx-self.x; d=cy-self.y
+        x = a*c + b*d
+        y = a*d - b*c
+        h = math.sqrt(x*x+y*y)
+        if h > 0:
+            cs = x*1000/h
+        else:
+            cs = 0
+        return (cs > 900)
+
+class BBox:
+    # a BBox records min/max x/y of some Points and
+    # can subsequently report row, column, pos of each point
+    # can also locate one bbox in another
+
+    def __init__(self, p):
+        self.minx = p.x
+        self.maxx = p.x
+        self.miny = p.y
+        self.maxy = p.y
+
+    def width(self):
+        return self.maxx - self.minx
+    def height(self):
+        return self.maxy - self.miny
+
+    def add(self, p):
+        if p.x > self.maxx:
+            self.maxx = p.x
+        if p.x < self.minx:
+            self.minx = p.x
+
+        if p.y > self.maxy:
+            self.maxy = p.y
+        if p.y < self.miny:
+            self.miny = p.y
+    def finish(self, div = 3):
+        # if aspect ratio is bad, we adjust max/min accordingly
+        # before setting [xy][12].  We don't change self.min/max
+        # as they are used to place stroke in bigger bbox.
+        # Normally divisions are at 1/3 and 2/3. They can be moved
+        # by setting div e.g. 2 = 1/2 and 1/2
+        (minx,miny,maxx,maxy) = (self.minx,self.miny,self.maxx,self.maxy)
+        if (maxx - minx) * 3 < (maxy - miny) * 2:
+            # too narrow
+            mid = int((maxx + minx)/2)
+            halfwidth = int ((maxy - miny)/3)
+            minx = mid - halfwidth
+            maxx = mid + halfwidth
+        if (maxy - miny) * 3 < (maxx - minx) * 2:
+            # too wide
+            mid = int((maxy + miny)/2)
+            halfheight = int ((maxx - minx)/3)
+            miny = mid - halfheight
+            maxy = mid + halfheight
+
+        div1 = div - 1
+        self.x1 = int((div1*minx + maxx)/div)
+        self.x2 = int((minx + div1*maxx)/div)
+        self.y1 = int((div1*miny + maxy)/div)
+        self.y2 = int((miny + div1*maxy)/div)
+
+    def row(self, p):
+        # 0, 1, 2 - top to bottom
+        if p.y <= self.y1:
+            return 0
+        if p.y < self.y2:
+            return 1
+        return 2
+    def col(self, p):
+        if p.x <= self.x1:
+            return 0
+        if p.x < self.x2:
+            return 1
+        return 2
+    def box(self, p):
+        # 0 to 9
+        return self.row(p) * 3 + self.col(p)
+
+    def relpos(self,b):
+        # b is a box within self.  find location 0-8
+        if b.maxx < self.x2 and b.minx < self.x1:
+            x = 0
+        elif b.minx > self.x1 and b.maxx > self.x2:
+            x = 2
+        else:
+            x = 1
+        if b.maxy < self.y2 and b.miny < self.y1:
+            y = 0
+        elif b.miny > self.y1 and b.maxy > self.y2:
+            y = 2
+        else:
+            y = 1
+        return y*3 + x
+
+
+def different(*args):
+    cur = 0
+    for i in args:
+        if cur != 0 and i != 0 and cur != i:
+            return True
+        if cur == 0:
+            cur = i
+    return False
+
+def maxcurve(*args):
+    for i in args:
+        if i != 0:
+            return i
+    return 0
+
+class PPath:
+    # a PPath refines a list of x,y points into a list of Points
+    # The Points mark out segments which end at significant Points
+    # such as inflections and reversals.
+
+    def __init__(self, x,y):
+
+        self.start = Point(x,y)
+        self.mid = Point(x,y)
+        self.curr = Point(x,y)
+        self.list = [ self.start ]
+
+    def add(self, x, y):
+        self.curr.add(x,y)
+
+        if ( (abs(self.mid.xdir(self.start) - self.curr.xdir(self.mid)) == 2) or
+             (abs(self.mid.ydir(self.start) - self.curr.ydir(self.mid)) == 2) or
+             (abs(self.curr.Vcurve(self.start))+2 < abs(self.mid.Vcurve(self.start)))):
+            pass
+        else:
+            self.mid = self.curr.copy()
+
+        if self.curr.xlen(self.mid) > 4 or self.curr.ylen(self.mid) > 4:
+            self.start = self.mid.copy()
+            self.list.append(self.start)
+            self.mid = self.curr.copy()
+
+    def close(self):
+        self.list.append(self.curr)
+
+    def get_sectlist(self):
+        if len(self.list) <= 2:
+            return [[0,self.list]]
+        l = []
+        A = self.list[0]
+        B = self.list[1]
+        s = [A,B]
+        curcurve = B.curve(A)
+        for C in self.list[2:]:
+            cabc = C.curve(A)
+            cab = B.curve(A)
+            cbc = C.curve(B)
+            if B.is_sharp(A,C) and not different(cabc, cab, cbc, curcurve):
+                # B is too pointy, must break here
+                l.append([curcurve, s])
+                s = [B, C]
+                curcurve = cbc
+            elif not different(cabc, cab, cbc, curcurve):
+                # all happy
+                s.append(C)
+                if curcurve == 0:
+                    curcurve = maxcurve(cab, cbc, cabc)
+            elif not different(cabc, cab, cbc)  :
+                # gentle inflection along AB
+                # was: AB goes in old and new section
+                # now: AB only in old section, but curcurve
+                #      preseved.
+                l.append([curcurve,s])
+                s = [A, B, C]
+                curcurve =maxcurve(cab, cbc, cabc)
+            else:
+                # Change of direction at B
+                l.append([curcurve,s])
+                s = [B, C]
+                curcurve = cbc
+
+            A = B
+            B = C
+        l.append([curcurve,s])
+
+        return l
+
+    def remove_shorts(self, bbox):
+        # in self.list, if a point is close to the previous point,
+        # remove it.
+        if len(self.list) <= 2:
+            return
+        w = bbox.width()/10
+        h = bbox.height()/10
+        n = [self.list[0]]
+        leng = w*h*2*2
+        for p in self.list[1:]:
+            l = p.sqlen(n[-1])
+            if l > leng:
+                n.append(p)
+        self.list = n
+
+    def text(self):
+        # OK, we have a list of points with curvature between.
+        # want to divide this into sections.
+        # for each 3 consectutive points ABC curve of ABC and AB and BC
+        # If all the same, they are all in a section.
+        # If not B starts a new section and the old ends on B or C...
+        BB = BBox(self.list[0])
+        for p in self.list:
+            BB.add(p)
+        BB.finish()
+        self.bbox = BB
+        self.remove_shorts(BB)
+        sectlist = self.get_sectlist()
+        t = ""
+        for c, s in sectlist:
+            if c > 0:
+                dr = "R"  # clockwise is to the Right
+            elif c < 0:
+                dr = "L"  # counterclockwise to the Left
+            else:
+                dr = "S"  # straight
+            bb = BBox(s[0])
+            for p in s:
+                bb.add(p)
+            bb.finish()
+            # If  all points are in some row or column, then
+            # line is S
+            rwdiff = False; cldiff = False
+            rw = bb.row(s[0]); cl=bb.col(s[0])
+            for p in s:
+                if bb.row(p) != rw: rwdiff = True
+                if bb.col(p) != cl: cldiff = True
+            if not rwdiff or not cldiff: dr = "S"
+
+            t1 = dr
+            t1 += "(%d)" % BB.relpos(bb)
+            t1 += "%d,%d" % (bb.box(s[0]), bb.box(s[-1]))
+            t += t1 + '.'
+        return t[:-1]
+
+
+if __name__ == "__main__":
+    # test app for Scrawl.
+    # Create a window with a ListSelect and when a symbol is
+    # entered, select the first element with that letter
+    from listselect import ListSelect
+
+    w = gtk.Window(gtk.WINDOW_TOPLEVEL)
+    w.connect("destroy", lambda w: gtk.main_quit())
+    w.set_title("Scrawl Test")
+    w.show()
+
+    v = gtk.VBox(); v.show()
+    w.add(v)
+
+    s = ListSelect()
+    list = [ "The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", "Dog"]
+    el = []
+    for a in list:
+        el.append((a, ["blue", False, False, None, "white"]))
+    el[4] = (el[4][0], ["red",True,True,"black","white"])
+    s.list = el
+    s.selected = 2
+    v.pack_end(s, expand = True)
+    s.show()
+
+    def sel(n, i):
+        s,f = i
+        print n, s, "selected"
+    s.callout = sel
+
+    global sc
+
+    def gotsym(sym):
+        global sc
+        print "got sym:", sym
+        if sym == '-':
+            s.set_zoom(s.zoom-1)
+        elif sym == '+':
+            s.set_zoom(s.zoom+1)
+        elif sym == '1':
+            sc.symcall = None
+        else:
+            for i in range(len(list)):
+                if list[i].lower().find(sym.lower()) >= 0:
+                    print 'sel', i, list[i].lower(), sym.lower()
+                    s.select(i)
+                    break
+
+    def gottap(p):
+        x,y = p
+        s.tap(x,y)
+
+    global dragsource
+    dragsource = None
+    def gotdrag(start, here, done):
+        global dragsource
+        if dragsource == None:
+            dragsource = s.map_pos(start[0], start[1])
+            if dragsource != None:
+                s.list[dragsource][1][0] = 'black'
+                s.item_changed(dragsource)
+        dragdest = s.map_pos(here[0], here[1])
+        if dragsource != None and dragdest != None and dragsource != dragdest:
+            # swap and update dragsource
+            s.list[dragsource], s.list[dragdest] = \
+                                s.list[dragdest], s.list[dragsource]
+            list[dragsource], list[dragdest] = \
+                              list[dragdest], list[dragsource]
+            dragsource = dragdest
+            s.list_changed()
+        if done and dragsource != None:
+            s.list[dragsource][1][0] = 'blue'
+            s.item_changed(dragsource)
+            dragsource = None
+                                            
+
+    def gotsel(line, done):
+        if not done:
+            # set background to yellow
+            for p in line:
+                ind = s.map_pos(p[0],p[1])
+                if ind != None and s.list[ind][1][3] != 'yellow':
+                    s.list[ind][1][3] = "yellow"
+                    s.item_changed(ind)
+        else:
+            for e in s.list:
+                if e[1][3] == 'yellow':
+                    e[1][3] = None
+                    e[1][2] = not e[1][2]
+            s.list_changed()
+        
+    sc = Scrawl(s, gotsym, gottap, gotdrag, gotsel)
+    sc.set_colour('red')
+    gtk.main()
diff --git a/lib/tapboard.py b/lib/tapboard.py
new file mode 100644 (file)
index 0000000..c5acaec
--- /dev/null
@@ -0,0 +1,419 @@
+
+#
+# a library to draw a widget for tap-input of text
+#
+# Have a 3x4 array of buttons.
+# Enter any symbol by tapping two buttons from the top 3x3,
+# or bottom-middle
+# Other Bottom buttons are:  mode and  cancel/delete  
+# mode cycles : lower, caps, upper, number (abc Abc ABC 123)
+# cancel is effective after a single tap, delete when no pending tap
+#
+# The 3x3 keys normally show a 3x3 matrix of what they enable
+# When one is tapped, all keys change to show a single symbol (after a pause).
+#
+# "123" works in 'single tap' mode.  A single tap makes the symbol in the center
+# of the key appear.  To get other symbols (*, #), hold the relevant key until
+# other symbols appear.  Then tap.
+#
+# The window can be dragged  by touch-and-drag anywhere.
+# If the window is dragged more than 1/2 off the screen, it disappears.
+
+import gtk, pango, gobject
+
+keymap = {}
+
+keymap['lower'] = [
+    ['0','1','Tab','2','3','Delete','?','@','#'],
+    ['b','c','d','f','g','h',' ','Down',' '],
+    ['<','4','5','>','6','7','Return','{','}'],
+    ['j','k','~','l','m','Right','n','p','`'],
+    ['a','e','i','o',' ','u','r','s','t'],
+    ['\\',';',':','Left','\'','"','|','(',')'],
+    ['[',']','Escape','8','9','=','+','-','_'],
+    [' ','Up',' ','q','v','w','x','y','z'],
+    ['!','$','%','^','*','/','&',',','.'],
+    None,
+    [' ',' ',' ',' ',' ',' ','Left','Up','Right',' ','Down',' ']
+    
+    ]
+keymap['UPPER'] = [
+    ['0','1','Tab','2','3',' ','?','@','#'],
+    ['B','C','D','F','G','H',' ','Down',' '],
+    ['<','4','5','>','6','7','Return','{','}'],
+    ['J','K','~','L','M','Right','N','P','`'],
+    ['A','E','I','O',' ','U','R','S','T'],
+    ['\\',';',':','Left','\'','"','|','(',')'],
+    ['[',']','Escape','8','9','=','+','-','_'],
+    [' ','Up',' ','Q','V','W','X','Y','Z'],
+    ['!','$','%','^','*','/','&',',','.'],
+    None,
+    [' ',' ',' ',' ',' ',' ','Left','Up','Right',' ','Down',' ']
+    ]
+keymap['number'] = [
+    ['1',' ',' ',' ',' ',' ',' ',' ',' '],
+    [' ','2',' ',' ',' ',' ',' ',' ',' '],
+    [' ',' ','3',' ',' ',' ',' ',' ',' '],
+    [' ',' ',' ','4',' ',' ',' ',' ',' '],
+    [' ',' ',' ',' ','5',' ',' ',' ',' '],
+    [' ',' ',' ',' ',' ','6',' ',' ',' '],
+    [' ',' ',' ',' ',' ',' ','7',' ',' '],
+    [' ',' ',' ',' ',' ',' ',' ','8',' '],
+    [' ',' ',' ',' ',' ',' ',' ',' ','9'],
+    None,
+    ['+',' ','-','.',' ','/','*',' ','#', ' ', '0', ' ']
+    ]
+
+class TapBoard(gtk.VBox):
+    __gsignals__ = {
+        'key' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+                 (gobject.TYPE_STRING,))
+        }
+    def __init__(self):
+        gtk.VBox.__init__(self)
+        self.keysize = 80
+        self.width = int(3*self.keysize)
+        self.height = int(4.2*self.keysize)
+
+        self.dragx = None
+        self.dragy = None
+        self.moved = False
+
+        self.isize = gtk.icon_size_register("mine", 40, 40)
+
+        self.button_timeout = None
+
+        self.buttons = []
+
+        self.set_homogeneous(True)
+
+        for row in range(3):
+            h = gtk.HBox()
+            h.show()
+            h.set_homogeneous(True)
+            self.add(h)
+            bl = []
+            for col in range(3):
+                b = self.add_button(None, self.tap, row*3+col, h)
+                bl.append(b)
+            self.buttons.append(bl)
+
+        h = gtk.HBox()
+        h.show()
+        h.set_homogeneous(True)
+        self.add(h)
+
+        fd = pango.FontDescription('sans 10')
+        fd.set_absolute_size(30 * pango.SCALE * self.keysize / 80)
+        b = self.add_button('abc', self.nextmode, None, h, fd)
+        self.modebutton = b
+
+        b = self.add_button(None, self.tap, 10, h)
+        self.buttons.append([None, b, None])
+
+        b = self.add_button(gtk.STOCK_UNDO, self.delete, None, h)
+
+        self.mode = 'lower'
+        self.taps = 2
+        self.single = False
+        self.prefix = None
+        self.prefix1 = None
+        self.size = 0
+        self.connect("size-allocate", self.update_buttons)
+        self.connect("realize", self.update_buttons)
+
+
+    def add_button(self, label, click, arg, box, font = None):
+        if not label:
+            b = gtk.Button()
+        elif label[0:4] == 'gtk-':
+            img = gtk.image_new_from_stock(label, self.isize)
+            img.show()
+            b = gtk.Button()
+            b.add(img)
+        else:
+            b = gtk.Button(label)
+        b.show()
+        b.set_property('can-focus', False)
+        if font:
+            b.child.modify_font(font)
+        b.connect('button_press_event', self.press, arg)
+        b.connect('button_release_event', self.release, click, arg)
+        #b.connect('motion_notify_event', self.motion)
+        b.add_events(gtk.gdk.POINTER_MOTION_MASK|
+                     gtk.gdk.POINTER_MOTION_HINT_MASK)
+
+        box.add(b)
+        return b
+
+    def update_buttons(self, *a):
+        if self.window == None:
+            return
+
+        alloc = self.buttons[0][0].get_allocation()
+        w = alloc.width; h = alloc.height
+        if w > h:
+            size = h
+        else:
+            size = w
+        size -= 12
+        if size <= 10 or size == self.size:
+            return
+        #print "update buttons", size
+        self.size = size
+
+        # For each button in 3x3 we need 10 images,
+        # one for initial state, and one for each of the new states
+        # So there are two fonts we want.
+        # First we make the initial images
+        fdsingle = pango.FontDescription('sans 10')
+        fdsingle.set_absolute_size(size / 3.5 * pango.SCALE)
+        fdword = pango.FontDescription('sans 10')
+        fdword.set_absolute_size(size / 4.5 * pango.SCALE)
+
+        bg = self.get_style().bg_gc[gtk.STATE_NORMAL]
+        fg = self.get_style().fg_gc[gtk.STATE_NORMAL]
+        red = self.window.new_gc()
+        red.set_foreground(self.get_colormap().alloc_color(gtk.gdk.color_parse('red')))
+        base_images = {}
+        for mode in keymap.keys():
+            base_images[mode] = 12*[None]
+            for row in range(4):
+                for col in range(3):
+                    if not self.buttons[row][col]:
+                        continue
+                    if row*3+col >= len(keymap[mode]):
+                        continue
+                    syms = keymap[mode][row*3+col]
+                    pm = gtk.gdk.Pixmap(self.window, size, size)
+                    pm.draw_rectangle(bg, True, 0, 0, size, size)
+                    for r in range(4):
+                        for c in range(3):
+                            if r*3+c >= len(syms):
+                                continue
+                            sym = syms[r*3+c]
+                            if sym == ' ':
+                                continue
+                            xpos = ((c-col+1)*2+1)
+                            ypos = ((r-row+1)*2+1)
+                            colour = fg
+                            if xpos != xpos%6:
+                                xpos = xpos%6
+                                colour = red
+                            if ypos != ypos%6:
+                                ypos = ypos%6
+                                colour = red
+                            if len(sym) == 1:
+                                self.modify_font(fdsingle)
+                            else:
+                                self.modify_font(fdword)
+                            layout = self.create_pango_layout(sym[0:3])
+                            (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
+                            pm.draw_layout(colour,
+                                           int(xpos*size/6 - ew/2),
+                                           int(ypos*size/6 - eh/2),
+                                           layout)
+                    im = gtk.Image()
+                    im.set_from_pixmap(pm, None)
+                    base_images[mode][row*3+col] = im
+        self.base_images = base_images
+        fd = pango.FontDescription('sans 10')
+        fd.set_absolute_size(size / 1.5 * pango.SCALE)
+        self.modify_font(fd)
+        sup_images = {}
+        sup_by_sym = {}
+        for mode in keymap.keys():
+            sup_images[mode] = 12*[None]
+            for row in range(4):
+                for col in range(3):
+                    ilist = 12 * [None]
+                    for r in range(4):
+                        for c in range(3):
+                            if r*3+c >= len(keymap[mode]):
+                                continue
+                            if keymap[mode][r*3+c] == None:
+                                continue
+                            if row*3+col >= len(keymap[mode][r*3+c]):
+                                continue
+                            sym = keymap[mode][r*3+c][row*3+col]
+                            if sym == ' ':
+                                continue
+                            if sym in sup_by_sym:
+                                im = sup_by_sym[sym]
+                            else:
+                                pm = gtk.gdk.Pixmap(self.window, size, size)
+                                pm.draw_rectangle(bg, True, 0, 0, size, size)
+                                layout = self.create_pango_layout(sym[0:3])
+                                (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
+                                pm.draw_layout(fg,
+                                               int((size - ew)/2), int((size - eh)/2),
+                                               layout)
+                                im = gtk.Image()
+                                im.set_from_pixmap(pm, None)
+                                sup_by_sym[sym] = im
+                            ilist[r*3+c] = im
+                    sup_images[mode][row*3+col] = ilist
+        self.sup_images = sup_images
+        self.set_button_images()
+
+
+    def set_button_images(self):
+        for row in range(4):
+            for col in range(3):
+                b = self.buttons[row][col]
+                if not b:
+                    continue
+                p = self.prefix
+                if p == None:
+                    p = self.prefix1
+                if p == None:
+                    im = self.base_images[self.mode][row*3+col]
+                else:
+                    im = self.sup_images[self.mode][row*3+col][p]
+                if im:
+                    b.set_image(im)
+        
+
+    def tap(self, rc):
+        if self.prefix == None:
+            self.prefix = rc
+            if self.taps == 2:
+                self.button_timeout = gobject.timeout_add(500, self.do_buttons)
+        else:
+            sym = keymap[self.mode][self.prefix][rc]
+            self.emit('key', sym)
+            self.noprefix()
+
+    def press(self, widget, ev, arg):
+        self.dragx = int(ev.x_root)
+        self.dragy = int(ev.y_root)
+        #self.startx, self.starty  = self.get_position()
+        if arg != None and self.taps == 1 and self.button_timeout == None and self.prefix == None:
+            self.prefix1 = arg
+            if not self.button_timeout:
+                self.button_timeout = gobject.timeout_add(300, self.do_buttons)
+        if arg == None:
+            # press-and-hold makes us disappear
+            if not self.button_timeout:
+                self.button_timeout = gobject.timeout_add(500, self.disappear)
+
+    def release(self, widget, ev, click, arg):
+        self.dragx = None
+        self.dragy = None
+        if arg == None:
+            if self.button_timeout:
+                gobject.source_remove(self.button_timeout)
+                self.button_timeout = None
+        if self.moved:
+            self.moved = False
+            self.noprefix()
+            # If we are half way off the screen, hide
+            root = gtk.gdk.get_default_root_window()
+            (rx,ry,rwidth,rheight,depth) = root.get_geometry()
+            (x,y,width,height,depth) = self.window.get_geometry()
+            xmid = int(x + width / 2)
+            ymid = int(y + height / 2)
+            if xmid < 0 or xmid > rwidth or \
+               ymid < 0 or ymid > rheight:
+                self.hide()
+        elif arg != None and self.taps == 1 and self.button_timeout:
+            # quick tap in single tap mode, just enter the symbol
+            gobject.source_remove(self.button_timeout)
+            self.button_timeout = None
+            num = arg
+            sym = keymap[self.mode][num][num]
+            self.emit('key', sym)
+        else:
+            click(arg)
+
+    def motion(self, widget, ev):
+        if self.dragx == None:
+            return
+        x = int(ev.x_root)
+        y = int(ev.y_root)
+
+        if abs(x-self.dragx)+abs(y-self.dragy) > 40 or self.moved:
+            self.move(self.startx+x-self.dragx,
+                      self.starty+y-self.dragy);
+            self.moved = True
+            if self.button_timeout:
+                gobject.source_remove(self.button_timeout)
+                self.button_timeout = None
+        if ev.is_hint:
+            gtk.gdk.flush()
+            ev.window.get_pointer()
+
+    def disappear(self):
+        self.hide()
+        self.dragx = None
+        self.dragy = None
+
+    def do_buttons(self):
+        self.set_button_images()
+        self.button_timeout = None
+        return False
+
+
+    def nextmode(self, a):
+        if self.prefix:
+            return self.noprefix()
+        if self.prefix1:
+            self.noprefix()
+        if self.mode == 'lower':
+            self.mode = 'UPPER'
+            self.single = True
+            self.modebutton.child.set_text('Abc')
+        elif self.mode == 'UPPER' and self.single:
+            self.single = False
+            self.modebutton.child.set_text('ABC')
+        elif self.mode == 'UPPER' and not self.single:
+            self.mode = 'number'
+            self.taps = 1
+            self.modebutton.child.set_text('123')
+        else:
+            self.mode = 'lower'
+            self.taps = 2
+            self.modebutton.child.set_text('abc')
+        self.set_button_images()
+
+    def delete(self, a):
+        if self.prefix == None:
+            self.emit('key', '\b')
+        else:
+            self.noprefix()
+
+    def noprefix(self):
+        self.prefix = None
+        self.prefix1 = None
+        
+        if self.button_timeout:
+            gobject.source_remove(self.button_timeout)
+            self.button_timeout = None
+        else:
+            self.set_button_images()
+
+        if self.single:
+            self.mode = 'lower'
+            self.single = False
+            self.modebutton.child.set_text('abc')
+            self.set_button_images()
+
+if __name__ == "__main__" :
+    w = gtk.Window()
+    w.connect("destroy", lambda w: gtk.main_quit())
+    ti = TapBoard()
+    w.add(ti)
+    w.set_default_size(ti.width, ti.height)
+    root = gtk.gdk.get_default_root_window()
+    (x,y,width,height,depth) = root.get_geometry()
+    x = int((width-ti.width)/2)
+    y = int((height-ti.width)/2)
+    w.move(x,y)
+    def pkey(ti, str):
+        print 'key', str
+    ti.connect('key', pkey)
+    ti.show()
+    w.show()
+    
+    gtk.main()
+
diff --git a/network/apm.usbnet b/network/apm.usbnet
new file mode 100644 (file)
index 0000000..d9c7820
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+case $1 in 
+ suspend ) ifdown usb0 ;;
+ resume ) ifdown usb0 ; ifup usb0 ;;
+esac
+exit 0
diff --git a/network/apm.wifi b/network/apm.wifi
new file mode 100644 (file)
index 0000000..08743c7
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+case $1 in
+ suspend )
+   ifdown wifi0
+   ifdown eth0
+   ifrename -i wifi0 -n eth0
+   wpa_cli -i eth0 terminate
+   wmiconfig -i eth0 --wlan disable
+   ;;
+
+ resume )
+   ifdown wifi0 ; ifconfig wifi0 down
+   ifrename -i wifi0 -n eth0
+   ifdown eth0
+   (
+     wmiconfig -i eth0 --wlan enable
+     sleep 2
+     ifconfig eth0 0.0.0.0 down
+     wpa_supplicant -B -i eth0 -c /etc/wpa_supplicant.conf -W
+     wpa_cli -B -i eth0 -a /sbin/wpa_action_updown
+     sleep 30
+     # if we don't have a wifi connection, go ad-hoc
+     case `wpa_cli -i eth0 status` in
+       *wpa_state=COMPLETED*ip_address=[1-9]* )
+           exit
+     esac
+     wpa_cli -i eth0 terminate
+     ifdown eth0
+     ifconfig eth0 down
+     ifrename -i eth0 -n wifi0
+     ifup wifi0
+    ) &
+esac
+exit 0
diff --git a/network/dnsmasq.conf b/network/dnsmasq.conf
new file mode 100644 (file)
index 0000000..5688638
--- /dev/null
@@ -0,0 +1,547 @@
+# Configuration file for dnsmasq.
+#
+# Format is one option per line, legal options are the same
+# as the long options legal on the command line. See
+# "/usr/sbin/dnsmasq --help" or "man 8 dnsmasq" for details.
+
+# The following two options make you a better netizen, since they
+# tell dnsmasq to filter out queries which the public DNS cannot
+# answer, and which load the servers (especially the root servers)
+# uneccessarily. If you have a dial-on-demand link they also stop
+# these requests from bringing up the link uneccessarily.
+
+# Never forward plain names (without a dot or domain part)
+#domain-needed
+# Never forward addresses in the non-routed address spaces.
+#bogus-priv
+
+
+# Uncomment this to filter useless windows-originated DNS requests
+# which can trigger dial-on-demand links needlessly.
+# Note that (amongst other things) this blocks all SRV requests,
+# so don't use it if you use eg Kerberos, SIP, XMMP or Google-talk.
+# This option only affects forwarding, SRV records originating for
+# dnsmasq (via srv-host= lines) are not suppressed by it.
+#filterwin2k
+
+# Change this line if you want dns to get its upstream servers from
+# somewhere other that /etc/resolv.conf
+#resolv-file=
+
+# By  default,  dnsmasq  will  send queries to any of the upstream
+# servers it knows about and tries to favour servers to are  known
+# to  be  up.  Uncommenting this forces dnsmasq to try each query
+# with  each  server  strictly  in  the  order  they   appear   in
+# /etc/resolv.conf
+#strict-order
+
+# If you don't want dnsmasq to read /etc/resolv.conf or any other
+# file, getting its servers from this file instead (see below), then
+# uncomment this.
+#no-resolv
+
+# If you don't want dnsmasq to poll /etc/resolv.conf or other resolv
+# files for changes and re-read them then uncomment this.
+#no-poll
+
+# Add other name servers here, with domain specs if they are for
+# non-public domains.
+#server=/localnet/192.168.0.1
+
+# Example of routing PTR queries to nameservers: this will send all 
+# address->name queries for 192.168.3/24 to nameserver 10.1.2.3
+#server=/3.168.192.in-addr.arpa/10.1.2.3
+
+# Add local-only domains here, queries in these domains are answered
+# from /etc/hosts or DHCP only.
+#local=/localnet/
+
+# Add domains which you want to force to an IP address here.
+# The example below send any host in doubleclick.net to a local
+# webserver.
+#address=/doubleclick.net/127.0.0.1
+
+# --address (and --server) work with IPv6 addresses too.
+#address=/www.thekelleys.org.uk/fe80::20d:60ff:fe36:f83
+
+# You can control how dnsmasq talks to a server: this forces 
+# queries to 10.1.2.3 to be routed via eth1
+# --server=10.1.2.3@eth1
+
+# and this sets the source (ie local) address used to talk to
+# 10.1.2.3 to 192.168.1.1 port 55 (there must be a interface with that
+# IP on the machine, obviously).
+# --server=10.1.2.3@192.168.1.1#55
+
+# If you want dnsmasq to change uid and gid to something other
+# than the default, edit the following lines.
+#user=
+#group=
+
+# If you want dnsmasq to listen for DHCP and DNS requests only on
+# specified interfaces (and the loopback) give the name of the
+# interface (eg eth0) here.
+# Repeat the line for more than one interface.
+#interface=
+interface=usb0
+interface=bnep0
+interface=wifi0
+# Or you can specify which interface _not_ to listen on
+#except-interface=
+# Or which to listen on by address (remember to include 127.0.0.1 if
+# you use this.)
+#listen-address=
+# If you want dnsmasq to provide only DNS service on an interface,
+# configure it as shown above, and then use the following line to
+# disable DHCP on it.
+#no-dhcp-interface=
+
+# On systems which support it, dnsmasq binds the wildcard address,
+# even when it is listening on only some interfaces. It then discards
+# requests that it shouldn't reply to. This has the advantage of
+# working even when interfaces come and go and change address. If you
+# want dnsmasq to really bind only the interfaces it is listening on,
+# uncomment this option. About the only time you may need this is when
+# running another nameserver on the same machine.
+#bind-interfaces
+
+# If you don't want dnsmasq to read /etc/hosts, uncomment the
+# following line.
+#no-hosts
+# or if you want it to read another file, as well as /etc/hosts, use
+# this.
+#addn-hosts=/etc/banner_add_hosts
+
+# Set this (and domain: see below) if you want to have a domain
+# automatically added to simple names in a hosts-file.
+#expand-hosts
+
+# Set the domain for dnsmasq. this is optional, but if it is set, it
+# does the following things.
+# 1) Allows DHCP hosts to have fully qualified domain names, as long
+#     as the domain part matches this setting.
+# 2) Sets the "domain" DHCP option thereby potentially setting the
+#    domain of all systems configured by DHCP
+# 3) Provides the domain part for "expand-hosts"
+#domain=thekelleys.org.uk
+
+# Set a different domain for a particular subnet
+#domain=wireless.thekelleys.org.uk,192.168.2.0/24
+
+# Same idea, but range rather then subnet
+#domain=reserved.thekelleys.org.uk,192.68.3.100,192.168.3.200
+
+# Uncomment this to enable the integrated DHCP server, you need
+# to supply the range of addresses available for lease and optionally
+# a lease time. If you have more than one network, you will need to
+# repeat this for each network on which you want to supply DHCP
+# service.
+#dhcp-range=192.168.0.50,192.168.0.150,12h
+dhcp-range=192.168.222.130,192.168.222.158,12h
+dhcp-range=192.168.222.162,192.168.222.190,12h
+dhcp-range=192.168.222.194,192.168.222.222,12h
+
+# This is an example of a DHCP range where the netmask is given. This
+# is needed for networks we reach the dnsmasq DHCP server via a relay
+# agent. If you don't know what a DHCP relay agent is, you probably
+# don't need to worry about this.
+#dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h
+
+# This is an example of a DHCP range with a network-id, so that
+# some DHCP options may be set only for this network.
+#dhcp-range=red,192.168.0.50,192.168.0.150
+
+# Supply parameters for specified hosts using DHCP. There are lots
+# of valid alternatives, so we will give examples of each. Note that
+# IP addresses DO NOT have to be in the range given above, they just
+# need to be on the same network. The order of the parameters in these
+# do not matter, it's permissble to give name,adddress and MAC in any order
+
+# Always allocate the host with ethernet address 11:22:33:44:55:66
+# The IP address 192.168.0.60
+#dhcp-host=11:22:33:44:55:66,192.168.0.60
+#dhcp-host=6a:99:2e:16:45:74 ,192.168.0.202
+
+# Always set the name of the host with hardware address
+# 11:22:33:44:55:66 to be "fred"
+#dhcp-host=11:22:33:44:55:66,fred
+
+# Always give the host with ethernet address 11:22:33:44:55:66
+# the name fred and IP address 192.168.0.60 and lease time 45 minutes
+#dhcp-host=11:22:33:44:55:66,fred,192.168.0.60,45m
+
+# Give a host with ethernet address 11:22:33:44:55:66 or
+# 12:34:56:78:90:12 the IP address 192.168.0.60. Dnsmasq will assume
+# that these two ethernet interfaces will never be in use at the same
+# time, and give the IP address to the second, even if it is already
+# in use by the first. Useful for laptops with wired and wireless
+# addresses.
+#dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60
+
+# Give the machine which says its name is "bert" IP address
+# 192.168.0.70 and an infinite lease
+#dhcp-host=bert,192.168.0.70,infinite
+
+# Always give the host with client identifier 01:02:02:04
+# the IP address 192.168.0.60
+#dhcp-host=id:01:02:02:04,192.168.0.60
+
+# Always give the host with client identifier "marjorie"
+# the IP address 192.168.0.60
+#dhcp-host=id:marjorie,192.168.0.60
+
+# Enable the address given for "judge" in /etc/hosts
+# to be given to a machine presenting the name "judge" when
+# it asks for a DHCP lease.
+#dhcp-host=judge
+
+# Never offer DHCP service to a machine whose ethernet
+# address is 11:22:33:44:55:66
+#dhcp-host=11:22:33:44:55:66,ignore
+
+# Ignore any client-id presented by the machine with ethernet
+# address 11:22:33:44:55:66. This is useful to prevent a machine
+# being treated differently when running under different OS's or
+# between PXE boot and OS boot.
+#dhcp-host=11:22:33:44:55:66,id:*
+
+# Send extra options which are tagged as "red" to
+# the machine with ethernet address 11:22:33:44:55:66
+#dhcp-host=11:22:33:44:55:66,net:red
+
+# Send extra options which are tagged as "red" to
+# any machine with ethernet address starting 11:22:33:
+#dhcp-host=11:22:33:*:*:*,net:red
+
+# Ignore any clients which are specified in dhcp-host lines
+# or /etc/ethers. Equivalent to ISC "deny unkown-clients".
+# This relies on the special "known" tag which is set when 
+# a host is matched.
+#dhcp-ignore=#known
+
+# Send extra options which are tagged as "red" to any machine whose
+# DHCP vendorclass string includes the substring "Linux"
+#dhcp-vendorclass=red,Linux
+
+# Send extra options which are tagged as "red" to any machine one
+# of whose DHCP userclass strings includes the substring "accounts"
+#dhcp-userclass=red,accounts
+
+# Send extra options which are tagged as "red" to any machine whose
+# MAC address matches the pattern.
+#dhcp-mac=red,00:60:8C:*:*:*
+
+# If this line is uncommented, dnsmasq will read /etc/ethers and act
+# on the ethernet-address/IP pairs found there just as if they had
+# been given as --dhcp-host options. Useful if you keep
+# MAC-address/host mappings there for other purposes.
+#read-ethers
+
+# Send options to hosts which ask for a DHCP lease.
+# See RFC 2132 for details of available options.
+# Common options can be given to dnsmasq by name: 
+# run "dnsmasq --help dhcp" to get a list.
+# Note that all the common settings, such as netmask and
+# broadcast address, DNS server and default route, are given
+# sane defaults by dnsmasq. You very likely will not need 
+# any dhcp-options. If you use Windows clients and Samba, there
+# are some options which are recommended, they are detailed at the
+# end of this section.
+
+# Override the default route supplied by dnsmasq, which assumes the
+# router is the same machine as the one running dnsmasq.
+#dhcp-option=3,1.2.3.4
+
+# Do the same thing, but using the option name
+#dhcp-option=option:router,1.2.3.4
+
+# Override the default route supplied by dnsmasq and send no default
+# route at all. Note that this only works for the options sent by
+# default (1, 3, 6, 12, 28) the same line will send a zero-length option 
+# for all other option numbers.
+dhcp-option=3
+
+# Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5
+#dhcp-option=option:ntp-server,192.168.0.4,10.10.0.5
+
+# Set the NTP time server address to be the same machine as
+# is running dnsmasq
+#dhcp-option=42,0.0.0.0
+
+# Set the NIS domain name to "welly"
+#dhcp-option=40,welly
+
+# Set the default time-to-live to 50
+#dhcp-option=23,50
+
+# Set the "all subnets are local" flag
+#dhcp-option=27,1
+
+# Send the etherboot magic flag and then etherboot options (a string).
+#dhcp-option=128,e4:45:74:68:00:00
+#dhcp-option=129,NIC=eepro100
+
+# Specify an option which will only be sent to the "red" network
+# (see dhcp-range for the declaration of the "red" network)
+# Note that the net: part must precede the option: part.
+#dhcp-option = net:red, option:ntp-server, 192.168.1.1
+
+# The following DHCP options set up dnsmasq in the same way as is specified
+# for the ISC dhcpcd in
+# http://www.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt
+# adapted for a typical dnsmasq installation where the host running
+# dnsmasq is also the host running samba.
+# you may want to uncomment some or all of them if you use 
+# Windows clients and Samba.
+#dhcp-option=19,0           # option ip-forwarding off
+#dhcp-option=44,0.0.0.0     # set netbios-over-TCP/IP nameserver(s) aka WINS server(s)
+#dhcp-option=45,0.0.0.0     # netbios datagram distribution server
+#dhcp-option=46,8           # netbios node type
+
+# Send RFC-3397 DNS domain search DHCP option. WARNING: Your DHCP client
+# probably doesn't support this......
+#dhcp-option=option:domain-search,eng.apple.com,marketing.apple.com
+
+# Send RFC-3442 classless static routes (note the netmask encoding)
+#dhcp-option=121,192.168.1.0/24,1.2.3.4,10.0.0.0/8,5.6.7.8
+
+# Send vendor-class specific options encapsulated in DHCP option 43. 
+# The meaning of the options is defined by the vendor-class so
+# options are sent only when the client supplied vendor class
+# matches the class given here. (A substring match is OK, so "MSFT" 
+# matches "MSFT" and "MSFT 5.0"). This example sets the
+# mtftp address to 0.0.0.0 for PXEClients.
+#dhcp-option=vendor:PXEClient,1,0.0.0.0
+
+# Send microsoft-specific option to tell windows to release the DHCP lease
+# when it shuts down. Note the "i" flag, to tell dnsmasq to send the
+# value as a four-byte integer - that's what microsoft wants. See
+# http://technet2.microsoft.com/WindowsServer/en/library/a70f1bb7-d2d4-49f0-96d6-4b7414ecfaae1033.mspx?mfr=true
+#dhcp-option=vendor:MSFT,2,1i
+
+# Send the Encapsulated-vendor-class ID needed by some configurations of
+# Etherboot to allow is to recognise the DHCP server.
+#dhcp-option=vendor:Etherboot,60,"Etherboot"
+
+# Send options to PXELinux. Note that we need to send the options even
+# though they don't appear in the parameter request list, so we need
+# to use dhcp-option-force here. 
+# See http://syslinux.zytor.com/pxe.php#special for details.
+# Magic number - needed before anything else is recognised
+#dhcp-option-force=208,f1:00:74:7e
+# Configuration file name
+#dhcp-option-force=209,configs/common
+# Path prefix
+#dhcp-option-force=210,/tftpboot/pxelinux/files/
+# Reboot time. (Note 'i' to send 32-bit value)
+#dhcp-option-force=211,30i
+
+# Set the boot filename for netboot/PXE. You will only need 
+# this is you want to boot machines over the network and you will need
+# a TFTP server; either dnsmasq's built in TFTP server or an
+# external one. (See below for how to enable the TFTP server.)
+#dhcp-boot=pxelinux.0
+
+# Boot for Etherboot gPXE. The idea is to send two different
+# filenames, the first loads gPXE, and the second tells gPXE what to
+# load. The dhcp-match sets the gpxe tag for requests from gPXE.
+#dhcp-match=gpxe,175 # gPXE sends a 175 option.
+#dhcp-boot=net:#gpxe,undionly.kpxe
+#dhcp-boot=mybootimage
+# Encapsulated options for Etherboot gPXE. All the options are
+# encapsulated within option 175
+#dhcp-option=encap:175, 1, 5b         # priority code
+#dhcp-option=encap:175, 176, 1b       # no-proxydhcp 
+#dhcp-option=encap:175, 177, string   # bus-id 
+#dhcp-option=encap:175, 189, 1b       # BIOS drive code
+#dhcp-option=encap:175, 190, user     # iSCSI username
+#dhcp-option=encap:175, 191, pass     # iSCSI password
+
+# Test for the architecture of a netboot client. PXE clients are
+# supposed to send their architecture as option 93. (See RFC 4578)
+#dhcp-match=peecees, option:client-arch, 0 #x86-32
+#dhcp-match=itanics, option:client-arch, 2 #IA64
+#dhcp-match=hammers, option:client-arch, 6 #x86-64
+#dhcp-match=mactels, option:client-arch, 7 #EFI x86-64 
+
+# Do real PXE, rather than just booting a single file, this is an
+# alternative to dhcp-boot.
+#pxe-prompt="What system shall I netboot?"
+# or with timeout before first available action is taken:
+#pxe-prompt="Press F8 for menu.", 60
+
+# Available boot services. for PXE.
+#pxe-service=x86PC, "Boot from local disk"
+
+# Loads <tftp-root>/pxelinux.0 from dnsmasq TFTP server.
+#pxe-service=x86PC, "Install Linux", pxelinux 
+
+# Loads <tftp-root>/pxelinux.0 from TFTP server at 1.2.3.4.
+# Beware this fails on old PXE ROMS.
+#pxe-service=x86PC, "Install Linux", pxelinux, 1.2.3.4 
+
+# Use bootserver on network, found my multicast or broadcast.
+#pxe-service=x86PC, "Install windows from RIS server", 1
+
+# Use bootserver at a known IP address.
+#pxe-service=x86PC, "Install windows from RIS server", 1, 1.2.3.4
+
+# If you have multicast-FTP available,
+# information for that can be passed in a similar way using options 1
+# to 5. See page 19 of
+# http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf  
+
+  
+# Enable dnsmasq's built-in TFTP server
+#enable-tftp
+
+# Set the root directory for files availble via FTP.
+#tftp-root=/var/ftpd
+
+# Make the TFTP server more secure: with this set, only files owned by
+# the user dnsmasq is running as will be send over the net.
+#tftp-secure
+
+# This option stops dnsmasq from negotiating a larger blocksize for TFTP 
+# transfers. It will slow things down, but may rescue some broken TFTP
+# clients.
+#tftp-no-blocksize
+
+# Set the boot file name only when the "red" tag is set.
+#dhcp-boot=net:red,pxelinux.red-net
+
+# An example of dhcp-boot with an external TFTP server: the name and IP
+# address of the server are given after the filename.
+# Can fail with old PXE ROMS. Overridden by --pxe-service.
+#dhcp-boot=/var/ftpd/pxelinux.0,boothost,192.168.0.3
+
+# Set the limit on DHCP leases, the default is 150
+#dhcp-lease-max=150
+
+# The DHCP server needs somewhere on disk to keep its lease database.
+# This defaults to a sane location, but if you want to change it, use
+# the line below.
+#dhcp-leasefile=/var/lib/misc/dnsmasq.leases
+
+# Set the DHCP server to authoritative mode. In this mode it will barge in
+# and take over the lease for any client which broadcasts on the network,
+# whether it has a record of the lease or not. This avoids long timeouts
+# when a machine wakes up on a new network. DO NOT enable this if there's
+# the slighest chance that you might end up accidentally configuring a DHCP
+# server for your campus/company accidentally. The ISC server uses
+# the same option, and this URL provides more information:
+# http://www.isc.org/index.pl?/sw/dhcp/authoritative.php
+#dhcp-authoritative
+
+# Run an executable when a DHCP lease is created or destroyed.
+# The arguments sent to the script are "add" or "del", 
+# then the MAC address, the IP address and finally the hostname
+# if there is one. 
+#dhcp-script=/bin/echo
+
+# Set the cachesize here.
+#cache-size=150
+
+# If you want to disable negative caching, uncomment this.
+#no-negcache
+
+# Normally responses which come form /etc/hosts and the DHCP lease
+# file have Time-To-Live set as zero, which conventionally means
+# do not cache further. If you are happy to trade lower load on the
+# server for potentially stale date, you can set a time-to-live (in
+# seconds) here.
+#local-ttl=
+
+# If you want dnsmasq to detect attempts by Verisign to send queries
+# to unregistered .com and .net hosts to its sitefinder service and
+# have dnsmasq instead return the correct NXDOMAIN response, uncomment
+# this line. You can add similar lines to do the same for other
+# registries which have implemented wildcard A records.
+#bogus-nxdomain=64.94.110.11
+
+# If you want to fix up DNS results from upstream servers, use the
+# alias option. This only works for IPv4.
+# This alias makes a result of 1.2.3.4 appear as 5.6.7.8
+#alias=1.2.3.4,5.6.7.8
+# and this maps 1.2.3.x to 5.6.7.x
+#alias=1.2.3.0,5.6.7.0,255.255.255.0
+# and this maps 192.168.0.10->192.168.0.40 to 10.0.0.10->10.0.0.40
+#alias=192.168.0.10-192.168.0.40,10.0.0.0,255.255.255.0
+
+# Change these lines if you want dnsmasq to serve MX records.
+
+# Return an MX record named "maildomain.com" with target
+# servermachine.com and preference 50
+#mx-host=maildomain.com,servermachine.com,50
+
+# Set the default target for MX records created using the localmx option.
+#mx-target=servermachine.com
+
+# Return an MX record pointing to the mx-target for all local
+# machines.
+#localmx
+
+# Return an MX record pointing to itself for all local machines.
+#selfmx
+
+# Change the following lines if you want dnsmasq to serve SRV
+# records.  These are useful if you want to serve ldap requests for
+# Active Directory and other windows-originated DNS requests.
+# See RFC 2782.
+# You may add multiple srv-host lines.
+# The fields are <name>,<target>,<port>,<priority>,<weight>
+# If the domain part if missing from the name (so that is just has the
+# service and protocol sections) then the domain given by the domain=
+# config option is used. (Note that expand-hosts does not need to be
+# set for this to work.)
+
+# A SRV record sending LDAP for the example.com domain to
+# ldapserver.example.com port 289
+#srv-host=_ldap._tcp.example.com,ldapserver.example.com,389
+
+# A SRV record sending LDAP for the example.com domain to
+# ldapserver.example.com port 289 (using domain=)
+#domain=example.com
+#srv-host=_ldap._tcp,ldapserver.example.com,389
+
+# Two SRV records for LDAP, each with different priorities
+#srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,1
+#srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,2
+
+# A SRV record indicating that there is no LDAP server for the domain
+# example.com
+#srv-host=_ldap._tcp.example.com
+
+# The following line shows how to make dnsmasq serve an arbitrary PTR
+# record. This is useful for DNS-SD. (Note that the
+# domain-name expansion done for SRV records _does_not
+# occur for PTR records.)
+#ptr-record=_http._tcp.dns-sd-services,"New Employee Page._http._tcp.dns-sd-services"
+
+# Change the following lines to enable dnsmasq to serve TXT records.
+# These are used for things like SPF and zeroconf. (Note that the
+# domain-name expansion done for SRV records _does_not
+# occur for TXT records.)
+
+#Example SPF.
+#txt-record=example.com,"v=spf1 a -all"
+
+#Example zeroconf
+#txt-record=_http._tcp.example.com,name=value,paper=A4
+
+# Provide an alias for a "local" DNS name. Note that this _only_ works
+# for targets which are names from DHCP or /etc/hosts. Give host
+# "bert" another name, bertrand
+#cname=bertand,bert
+
+# For debugging purposes, log each DNS query as it passes through
+# dnsmasq.
+#log-queries
+
+# Log lots of extra information about DHCP transactions.
+#log-dhcp
+
+# Include a another lot of configuration options.
+#conf-file=/etc/dnsmasq.more.conf
+#conf-dir=/etc/dnsmasq.d
diff --git a/network/interfaces b/network/interfaces
new file mode 100644 (file)
index 0000000..5abfa4d
--- /dev/null
@@ -0,0 +1,30 @@
+# /etc/network/interfaces -- configuration file for ifup(8), ifdown(8)
+# The loopback interface
+auto lo
+iface lo inet loopback
+
+# Ethernet/RNDIS gadget (g_ether)
+# ... or on host side, usbnet and random hwaddr
+auto usb0
+iface usb0 inet static
+       address 192.168.222.129
+       netmask 255.255.255.224
+       network 192.168.222.128
+
+# Bluetooth networking
+iface bnep0 inet static
+   address 192.168.222.161
+   netmask 255.255.255.224
+   network 192.168.222.160
+
+iface wifi0 inet static
+ wireless_mode ad-hoc
+ wireless_essid freedom
+ address 192.168.222.193
+ netmask 255.255.255.224
+ network 192.168.222.192
+
+
+# Wireless managed
+iface eth0 inet dhcp
diff --git a/network/wpa_action_updown b/network/wpa_action_updown
new file mode 100644 (file)
index 0000000..be9c75f
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+case $2 in
+ CONNECTED ) ifup $1 ;;
+ DISCONNECTED ) ifdown $1 ;;
+esac
+exit 0
diff --git a/notes/Control b/notes/Control
new file mode 100644 (file)
index 0000000..8c5abd1
--- /dev/null
@@ -0,0 +1,73 @@
+
+Overall control:
+  - blanking
+  - suspend
+  - screen-lock
+  - screen-capture
+  - screen-rotate?
+  - charging current ??
+  - 
+
+Use AUX button??
+
+
+Blanking:
+  Need to know if screen is being used.
+    hint:  if GPS is on, then screen is watched.
+    if touchpad gets input, then screen is watched.
+    if power is on can wait longer.
+  After 10 seconds of no touchpad, check power and gps power
+
+  /sys/devices/platform/s3c2440-i2c/i2c-adapter/i2c-0/0-0073/charger_type
+    starts "none" or "host"
+  /sys/bus/platform/devices/neo1973-pm-gps.0/pwron
+    0 or 1
+
+  First stage blank goes to half brightness and disables keyboard
+   A tap goes full and enables. (so we lose the tap)
+
+  Second stage goes off.  Tap returns to first stage.
+
+Suspend:
+  Need to know when system is being used:
+   - network connections other than 127.0.0.1
+   - active phone call
+   - active screen
+
+So:
+
+  We have a current state:
+      on:  screen is on and accepting input
+      half:  screen is reduced brightness and blocked
+      off:  screen is off, input is diverted
+
+  We have a time of last touchpad input
+
+  We measure:
+      gps power
+      charger type
+      network connections.
+
+  We decide in new state and timeout
+  We change state (if needed) and sleep.
+    sleeping involves reading touchpad so we know time of last input.
+
+
+  switch state:
+   case 'on':
+       if last input < 10 seconds, set time for remaining seconds done
+       if charger and last input < 30 seconds, as above
+       set state to half
+       set time to 50
+
+   case 'half'
+       if < 10 seconds, set state to on, wait 10 seconds
+       if gps power wait for 10 minutes, else 1 minute
+       set state to off
+
+   case 'off'
+       if < 10 seconds, set state to half, wait 10 seconds
+       if gps, wait 30 minutes, else 2 minutes
+       if network or charger, stay at off indefinately
+       if time is up, "apm -s", assume input, set to 'on'.
+
diff --git a/notes/Network b/notes/Network
new file mode 100644 (file)
index 0000000..fbff2c8
--- /dev/null
@@ -0,0 +1,104 @@
+
+Want to have easy config for network:
+
+ Start/stop GPRS, and set AP name
+ Start/stop wireless, and set AP name
+
+So:
+ Big "GPRS" button toggles on/off
+  text entry for AP name
+
+ Big "WLAN" button toggles on/off
+   If on and no ESSID chosen, try to fill in box.
+
+
+AP name is stored in /media/card/gprs
+
+
+--------------------------
+There are 4 network connections:
+ - usb
+ - bluetooth
+ - wifi
+ - GPRS
+
+Each of the first three could be a source or sink of the
+default route.  GPRS can only be a source.
+
+usb:
+   notebook runs dnsmasq.  On 'power' we attempt to connect
+   each creates a low-prio default route through the other
+
+On 'wake' or explicit request we probe for a network:
+ - turn in wifi and wpa_supplicant and see if we manage to connect
+ - turn on bluetooth and try to connect
+ - if neither of those work, allow GPRS??
+
+Once we get a connection on one network we switch the others to
+receive:
+ - wifi goes ad-hoc as 'internet'
+ - bluetooth listens for network
+
+Use cases:
+
+ 1/ development.
+    USB cable connects freerunner with notebook and sets
+    up network.
+    No default route needed, but might be useful if route
+    goes out through notebook incase it is on a wired network
+    or for other reason freerunner cannot see wireless.
+
+ 2/ Internet access for freerunner
+     use wifi if connection can be made,
+      else usb or bluetooth if it has been set up
+      else GPRS
+
+ 3/ Access point for tethering
+    As above but listen on bluetooth and wifi if there aren't
+    in use for Internet access.
+
+
+Notebook always initiates connection whether usb (cable
+plug) or bluetooth.  freerunner runs dhcp.
+If Internet is available, notebook runs dns server
+which freerunner polls for.  If a nameserver is found,
+default route is configured.
+
+Accesspoint mode must be explicitly enabled, possibly by ssh request once local
+connection set up.  That is just setting ip_forward.
+
+So: on wake up and possibly other times:
+
+ - write '0' to ip_forward
+ - wifi: power on, scan for network
+         if we find one, set route and resolv.conf
+         if not, configure ad-hoc mode
+
+ - bluetooth: enable iscan and listen for connection with pand
+ - usb: always have dnsmasq running
+     if there is a bluetooth 
+
+On 'request for internet':
+  If wifi connected, then OK
+  If usb or bluetooth connected, ping dns server.  If found,
+    set resolv.conf and OK
+  Attempt GPRS
+
+On 'request for access point'
+  request internet, enable routing
+
+
+---------------------------
+
+dnsmasq:
+ - listen on usb0 bnep0 and wifi0
+ - each is configured with /5 
+   192.168.222.{129,161,193}
+ - hand out addreses as appropriate
+
+usb:
+  configure when power connected
+    ?? how to enable default route on notebook?
+    Need to restart dnsmasq?
+   Maybe on noteboot us a dhclient hook to avoid
+    over-riding the default route.
\ No newline at end of file
diff --git a/scenario/Makefile b/scenario/Makefile
new file mode 100644 (file)
index 0000000..2673aad
--- /dev/null
@@ -0,0 +1,4 @@
+
+LDLIBS=-lasound
+alsactl : alsactl.o state.o utils.o init_parse.o
+
diff --git a/scenario/aconfig.h b/scenario/aconfig.h
new file mode 100644 (file)
index 0000000..494055a
--- /dev/null
@@ -0,0 +1,135 @@
+/* include/aconfig.h.  Generated from aconfig.h.in by configure.  */
+/* include/aconfig.h.in.  Generated from configure.in by autoheader.  */
+
+/* directory containing alsa configuration */
+#define DATADIR "/usr/share/alsa"
+
+/* Define to 1 if translation of program messages to the user's native
+   language is requested. */
+#define ENABLE_NLS 1
+
+/* Define if curses-based programs can show translated messages. */
+#define ENABLE_NLS_IN_CURSES 1
+
+/* Define to 1 if you have the <alsa/mixer.h> header file. */
+#define HAVE_ALSA_MIXER_H 1
+
+/* Define to 1 if you have the <alsa/pcm.h> header file. */
+#define HAVE_ALSA_PCM_H 1
+
+/* Define to 1 if you have the <alsa/rawmidi.h> header file. */
+#define HAVE_ALSA_RAWMIDI_H 1
+
+/* Define to 1 if you have the <alsa/seq.h> header file. */
+#define HAVE_ALSA_SEQ_H 1
+
+/* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the
+   CoreFoundation framework. */
+/* #undef HAVE_CFLOCALECOPYCURRENT */
+
+/* Define to 1 if you have the MacOS X function CFPreferencesCopyAppValue in
+   the CoreFoundation framework. */
+/* #undef HAVE_CFPREFERENCESCOPYAPPVALUE */
+
+/* Have clock gettime */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Have curses set_escdelay */
+#define HAVE_CURSES_ESCDELAY 1
+
+/* Define if the GNU dcgettext() function is already present or preinstalled.
+   */
+#define HAVE_DCGETTEXT 1
+
+/* Define to 1 if you have the <form.h> header file. */
+#define HAVE_FORM_H 1
+
+/* Define if the GNU gettext() function is already present or preinstalled. */
+#define HAVE_GETTEXT 1
+
+/* Define if you have the iconv() function. */
+/* #undef HAVE_ICONV */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `asound' library (-lasound). */
+#define HAVE_LIBASOUND 1
+
+/* Have librt */
+#define HAVE_LIBRT 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <menu.h> header file. */
+#define HAVE_MENU_H 1
+
+/* Define to 1 if you have the <panel.h> header file. */
+#define HAVE_PANEL_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Name of package */
+#define PACKAGE "alsa-utils"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* directory containing sample data */
+#define SOUNDSDIR "/usr/share/sounds/alsa"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* ALSA util version */
+#define VERSION "1.0.21"
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+   calls it, or to nothing if 'inline' is not supported under any name.  */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
diff --git a/scenario/alsactl.c b/scenario/alsactl.c
new file mode 100644 (file)
index 0000000..02e082f
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *                   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.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "aconfig.h"
+#include "version.h"
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+#include "alsactl.h"
+
+#define SYS_ASOUNDRC "/etc/asound.state"
+
+int debugflag = 0;
+int force_restore = 1;
+int ignore_nocards = 0;
+char *command;
+char *statefile = NULL;
+
+static void help(void)
+{
+       printf("Usage: alsactl <options> command\n");
+       printf("\nAvailable global options:\n");
+       printf("  -h,--help        this help\n");
+       printf("  -d,--debug       debug mode\n");
+       printf("  -v,--version     print version of this program\n");
+       printf("\nAvailable state options:\n");
+       printf("  -f,--file #      configuration file (default " SYS_ASOUNDRC ")\n");
+       printf("  -F,--force       try to restore the matching controls as much as possible\n");
+       printf("                   (default mode)\n");
+       printf("  -g,--ignore      ignore 'No soundcards found' error\n");
+       printf("  -P,--pedantic    do not restore mismatching controls (old default)\n");
+       printf("  -I,--no-init-fallback\n"
+              "                   don't initialize even if restore fails\n");
+       printf("  -r,--runstate #  save restore and init state to this file (only errors)\n");
+       printf("                   default settings is 'no file set'\n");
+       printf("  -R,--remove      remove runstate file at first, otherwise append errors\n");
+       printf("\nAvailable init options:\n");
+       printf("  -E,--env #=#     set environment variable for init phase (NAME=VALUE)\n");
+       printf("  -i,--initfile #  main configuation file for init phase (default " DATADIR "/init/00main)\n");
+       printf("\n");
+       printf("\nAvailable commands:\n");
+       printf("  store   <card #> save current driver setup for one or each soundcards\n");
+       printf("                   to configuration file\n");
+       printf("  restore <card #> load current driver setup for one or each soundcards\n");
+       printf("                   from configuration file\n");
+       printf("  init    <card #> initialize driver to a default state\n");
+       printf("  names   <card #> dump information about all the known present (sub-)devices\n");
+       printf("                   into configuration file (DEPRECATED)\n");
+}
+
+int main(int argc, char *argv[])
+{
+       static const struct option long_option[] =
+       {
+               {"help", 0, NULL, 'h'},
+               {"file", 1, NULL, 'f'},
+               {"env", 1, NULL, 'E'},
+               {"initfile", 1, NULL, 'i'},
+               {"no-init-fallback", 0, NULL, 'I'},
+               {"force", 0, NULL, 'F'},
+               {"ignore", 0, NULL, 'g'},
+               {"pedantic", 0, NULL, 'P'},
+               {"runstate", 0, NULL, 'r'},
+               {"remove", 0, NULL, 'R'},
+               {"debug", 0, NULL, 'd'},
+               {"version", 0, NULL, 'v'},
+               {NULL, 0, NULL, 0},
+       };
+       static const char *const devfiles[] = {
+               "/dev/snd/controlC",
+               "/dev/snd/pcmC",
+               "/dev/snd/midiC",
+               "/dev/snd/hwC",
+               NULL
+       };
+       char *cfgfile = SYS_ASOUNDRC;
+       char *initfile = DATADIR "/init/00main";
+       char *cardname, ncardname[16];
+       const char *const *tmp;
+       int removestate = 0;
+       int init_fallback = 1; /* new default behavior */
+       int res;
+
+       command = argv[0];
+       while (1) {
+               int c;
+
+               if ((c = getopt_long(argc, argv, "hdvf:FgE:i:IPr:R", long_option, NULL)) < 0)
+                       break;
+               switch (c) {
+               case 'h':
+                       help();
+                       return EXIT_SUCCESS;
+               case 'f':
+                       cfgfile = optarg;
+                       break;
+               case 'F':
+                       force_restore = 1;
+                       break;
+               case 'g':
+                       ignore_nocards = 1;
+                       break;
+               case 'E':
+                       if (putenv(optarg)) {
+                               fprintf(stderr, "environment string '%s' is wrong\n", optarg);
+                               return EXIT_FAILURE;
+                       }
+                       break;
+               case 'i':
+                       initfile = optarg;
+                       break;
+               case 'I':
+                       init_fallback = 0;
+                       break;
+               case 'r':
+                       statefile = optarg;
+                       break;
+               case 'R':
+                       removestate = 1;
+                       break;
+               case 'P':
+                       force_restore = 0;
+                       break;
+               case 'd':
+                       debugflag = 1;
+                       break;
+               case 'v':
+                       printf("alsactl version " SND_UTIL_VERSION_STR "\n");
+                       return EXIT_SUCCESS;
+               case '?':               // error msg already printed
+                       help();
+                       return EXIT_FAILURE;
+                       break;
+               default:                // should never happen
+                       fprintf(stderr, 
+                       "Invalid option '%c' (%d) not handled??\n", c, c);
+               }
+       }
+       if (argc - optind <= 0) {
+               fprintf(stderr, "alsactl: Specify command...\n");
+               return 0;
+       }
+
+       cardname = argc - optind > 1 ? argv[optind + 1] : NULL;
+       for (tmp = devfiles; cardname != NULL && *tmp != NULL; tmp++) {
+               int len = strlen(*tmp);
+               if (!strncmp(cardname, *tmp, len)) {
+                       long l = strtol(cardname + len, NULL, 0);
+                       sprintf(ncardname, "%li", l);
+                       cardname = ncardname;
+                       break;
+               }
+       }
+
+       if (!strcmp(argv[optind], "init")) {
+               res = init(initfile, cardname);
+       } else if (!strcmp(argv[optind], "store")) {
+               res = save_state(cfgfile, cardname);
+       } else if (!strcmp(argv[optind], "restore")) {
+               if (removestate)
+                       remove(statefile);
+               res = load_state(cfgfile, initfile, cardname, init_fallback);
+       } else {
+               fprintf(stderr, "alsactl: Unknown command '%s'...\n", 
+                       argv[optind]);
+               res = -ENODEV;
+       }
+
+       snd_config_update_free_global();
+       return res < 0 ? res : 0;
+}
diff --git a/scenario/alsactl.h b/scenario/alsactl.h
new file mode 100644 (file)
index 0000000..89ad295
--- /dev/null
@@ -0,0 +1,94 @@
+extern int debugflag;
+extern int force_restore;
+extern int ignore_nocards;
+extern char *command;
+extern char *statefile;
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define info(...) do {\
+       fprintf(stdout, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stdout, __VA_ARGS__); \
+       putc('\n', stdout); \
+} while (0)
+#else
+#define info(args...) do {\
+       fprintf(stdout, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stdout, ##args); \
+       putc('\n', stdout); \
+} while (0)
+#endif 
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define error(...) do {\
+       fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stderr, __VA_ARGS__); \
+       putc('\n', stderr); \
+} while (0)
+#else
+#define error(args...) do {\
+       fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stderr, ##args); \
+       putc('\n', stderr); \
+} while (0)
+#endif 
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define cerror(cond, ...) do {\
+       if (cond) { \
+               fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+               fprintf(stderr, __VA_ARGS__); \
+               putc('\n', stderr); \
+       } \
+} while (0)
+#else
+#define cerror(cond, args...) do {\
+       if (cond) { \
+               fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+               fprintf(stderr, ##args); \
+               putc('\n', stderr); \
+       } \
+} while (0)
+#endif 
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define dbg(...) do {\
+       if (!debugflag) break; \
+       fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stderr, __VA_ARGS__); \
+       putc('\n', stderr); \
+} while (0)
+#else
+#define dbg(args...) do {\
+       if (!debugflag) break; \
+       fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stderr, ##args); \
+       putc('\n', stderr); \
+} while (0)
+#endif 
+
+int init(const char *file, const char *cardname);
+int save_state(const char *file, const char *cardname);
+int load_state(const char *file, const char *initfile, const char *cardname,
+              int do_init);
+int power(const char *argv[], int argc);
+int generate_names(const char *cfgfile);
+
+/* utils */
+
+int file_map(const char *filename, char **buf, size_t *bufsize);
+void file_unmap(void *buf, size_t bufsize);
+size_t line_width(const char *buf, size_t bufsize, size_t pos);
+void initfailed(int cardnumber, const char *reason);
+
+static inline int hextodigit(int c)
+{
+        if (c >= '0' && c <= '9')
+                c -= '0';
+        else if (c >= 'a' && c <= 'f')
+                c = c - 'a' + 10;
+        else if (c >= 'A' && c <= 'F')
+                c = c - 'A' + 10;
+        else
+                return -1;
+        return c;
+}
diff --git a/scenario/init_parse.c b/scenario/init_parse.c
new file mode 100644 (file)
index 0000000..756cf92
--- /dev/null
@@ -0,0 +1,1757 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Parse initialization files
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *                  Greg Kroah-Hartman <greg@kroah.com>,
+ *                  Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *
+ *   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.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <math.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "alsactl.h"
+#include "list.h"
+
+#define PATH_SIZE      512
+#define NAME_SIZE      128
+#define EJUSTRETURN    0x7fffffff
+
+enum key_op {
+       KEY_OP_UNSET,
+       KEY_OP_MATCH,
+       KEY_OP_NOMATCH,
+       KEY_OP_ADD,
+       KEY_OP_ASSIGN,
+       KEY_OP_ASSIGN_FINAL
+};
+
+struct pair {
+       char *key;
+       char *value;
+       struct pair *next;
+};
+
+struct space {
+       struct pair *pairs;
+       char *rootdir;
+       char *go_to;
+       char *program_result;
+       const char *filename;
+       int linenum;
+       int log_run;
+       int exit_code;
+       int quit;
+       unsigned int ctl_id_changed;
+       snd_hctl_t *ctl_handle;
+       snd_ctl_card_info_t *ctl_card_info;
+       snd_ctl_elem_id_t *ctl_id;
+       snd_ctl_elem_info_t *ctl_info;
+       snd_ctl_elem_value_t *ctl_value;
+};
+
+static void Perror(struct space *space, const char *fmt, ...)
+{
+       va_list arg;
+       va_start(arg, fmt);
+       fprintf(stderr, "%s:%i: ", space->filename, space->linenum);
+       vfprintf(stderr, fmt, arg);
+       putc('\n', stderr);
+       va_end(arg);
+}
+
+#include "init_sysdeps.c"
+#include "init_utils_string.c"
+#include "init_utils_run.c"
+#include "init_sysfs.c"
+
+static void free_space(struct space *space)
+{
+       struct pair *pair = space->pairs;
+       struct pair *next = pair;
+
+       while (next) {
+               pair = next;
+               next = pair->next;
+               free(pair->value);
+               free(pair->key);
+               free(pair);
+       }
+       space->pairs = NULL;
+       if (space->ctl_value) {
+               snd_ctl_elem_value_free(space->ctl_value);
+               space->ctl_value = NULL;
+       }
+       if (space->ctl_info) {
+               snd_ctl_elem_info_free(space->ctl_info);
+               space->ctl_info = NULL;
+       }
+       if (space->ctl_id) {
+               snd_ctl_elem_id_free(space->ctl_id);
+               space->ctl_id = NULL;
+       }
+       if (space->ctl_card_info) {
+               snd_ctl_card_info_free(space->ctl_card_info);
+               space->ctl_card_info = NULL;
+       }
+       if (space->ctl_handle) {
+               snd_hctl_close(space->ctl_handle);
+               space->ctl_handle = NULL;
+       }
+       if (space->rootdir)
+               free(space->rootdir);
+       if (space->program_result)
+               free(space->program_result);
+       if (space->go_to)
+               free(space->go_to);
+       free(space);
+}
+
+static struct pair *value_find(struct space *space, const char *key)
+{
+       struct pair *pair = space->pairs;
+       
+       while (pair && strcmp(pair->key, key) != 0)
+               pair = pair->next;
+       return pair;
+}
+
+static int value_set(struct space *space, const char *key, const char *value)
+{
+       struct pair *pair;
+       
+       pair = value_find(space, key);
+       if (pair) {
+               free(pair->value);
+               pair->value = strdup(value);
+               if (pair->value == NULL)
+                       return -ENOMEM;
+       } else {
+               pair = malloc(sizeof(struct pair));
+               if (pair == NULL)
+                       return -ENOMEM;
+               pair->key = strdup(key);
+               if (pair->key == NULL) {
+                       free(pair);
+                       return -ENOMEM;
+               }
+               pair->value = strdup(value);
+               if (pair->value == NULL) {
+                       free(pair->key);
+                       free(pair);
+                       return -ENOMEM;
+               }
+               pair->next = space->pairs;
+               space->pairs = pair;
+       }
+       return 0;
+}
+
+static int init_space(struct space **space, int card)
+{
+       struct space *res;
+       char device[16];
+       int err;
+
+       res = calloc(1, sizeof(struct space));
+       if (res == NULL)
+               return -ENOMEM;
+       res->ctl_id_changed = ~0;
+       res->linenum = -1;
+       sprintf(device, "hw:%u", card);
+       err = snd_hctl_open(&res->ctl_handle, device, 0);
+       if (err < 0)
+               goto error;
+       err = snd_hctl_load(res->ctl_handle);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_card_info_malloc(&res->ctl_card_info);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_card_info(snd_hctl_ctl(res->ctl_handle), res->ctl_card_info);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_elem_id_malloc(&res->ctl_id);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_elem_info_malloc(&res->ctl_info);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_elem_value_malloc(&res->ctl_value);
+       if (err < 0)
+               goto error;
+       *space = res;
+       return 0;
+ error:
+       free_space(res);
+       return err;
+}
+
+static const char *cardinfo_get(struct space *space, const char *attr)
+{
+       if (strncasecmp(attr, "CARD", 4) == 0) {
+               static char res[16];
+               sprintf(res, "%u", snd_ctl_card_info_get_card(space->ctl_card_info));
+               return res;
+       }
+       if (strncasecmp(attr, "ID", 2) == 0)
+               return snd_ctl_card_info_get_id(space->ctl_card_info);
+       if (strncasecmp(attr, "DRIVER", 6) == 0)
+               return snd_ctl_card_info_get_driver(space->ctl_card_info);
+       if (strncasecmp(attr, "NAME", 4) == 0)
+               return snd_ctl_card_info_get_name(space->ctl_card_info);
+       if (strncasecmp(attr, "LONGNAME", 8) == 0)
+               return snd_ctl_card_info_get_longname(space->ctl_card_info);
+       if (strncasecmp(attr, "MIXERNAME", 9) == 0)
+               return snd_ctl_card_info_get_mixername(space->ctl_card_info);
+       if (strncasecmp(attr, "COMPONENTS", 10) == 0)
+               return snd_ctl_card_info_get_components(space->ctl_card_info);
+       Perror(space, "unknown cardinfo{} attribute '%s'", attr);
+       return NULL;
+}
+
+static int check_id_changed(struct space *space, unsigned int what)
+{
+       snd_hctl_elem_t *elem;
+       int err;
+
+       if ((space->ctl_id_changed & what & 1) != 0) {
+               snd_ctl_elem_id_set_numid(space->ctl_id, 0);
+               elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+               if (!elem)
+                       return -ENOENT;
+               err = snd_hctl_elem_info(elem, space->ctl_info);
+               if (err == 0)
+                       space->ctl_id_changed &= ~1;
+               return err;
+       }
+       if ((space->ctl_id_changed & what & 2) != 0) {
+               snd_ctl_elem_id_set_numid(space->ctl_id, 0);
+               elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+               if (!elem)
+                       return -ENOENT;
+               err = snd_hctl_elem_read(elem, space->ctl_value);
+               if (err == 0)
+                       space->ctl_id_changed &= ~2;
+               return err;
+       }
+       return 0;
+}
+
+static const char *get_ctl_value(struct space *space)
+{
+       snd_ctl_elem_type_t type;
+       unsigned int idx, count;
+       static char res[1024], tmp[16];
+       static const char hex[] = "0123456789abcdef";
+       char *pos;
+       const char *pos1;
+
+       type = snd_ctl_elem_info_get_type(space->ctl_info);
+       count = snd_ctl_elem_info_get_count(space->ctl_info);
+       res[0] = '\0';
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       strlcat(res, snd_ctl_elem_value_get_boolean(space->ctl_value, idx) ? "on" : "off", sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       snprintf(tmp, sizeof(tmp), "%li", snd_ctl_elem_value_get_integer(space->ctl_value, idx));
+                       strlcat(res, tmp, sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       snprintf(tmp, sizeof(tmp), "%lli", snd_ctl_elem_value_get_integer64(space->ctl_value, idx));
+                       strlcat(res, tmp, sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       snprintf(tmp, sizeof(tmp), "%u", snd_ctl_elem_value_get_enumerated(space->ctl_value, idx));
+                       strlcat(res, tmp, sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+               if (type == SND_CTL_ELEM_TYPE_IEC958)
+                       count = sizeof(snd_aes_iec958_t);
+               if (count > (sizeof(res)-1)/2)
+                       count = (sizeof(res)-1/2);
+               pos = res;
+               pos1 = snd_ctl_elem_value_get_bytes(space->ctl_value);
+               while (count > 0) {
+                       idx = *pos1++;
+                       *pos++ = hex[idx >> 4];
+                       *pos++ = hex[idx & 0x0f];
+                       count++;
+               }
+               *pos++ = '\0';
+               break;
+       default:
+               Perror(space, "unknown element type '%i'", type);
+               return NULL;
+       }
+       return res;
+}
+
+/* Function to convert from percentage to volume. val = percentage */
+#define convert_prange1(val, min, max) \
+        ceil((val) * ((max) - (min)) * 0.01 + (min))
+
+static int set_ctl_value(struct space *space, const char *value, int all)
+{
+       snd_ctl_elem_type_t type;
+       unsigned int idx, idx2, count, items;
+       const char *pos, *pos2;
+       snd_hctl_elem_t *elem;
+       int val;
+       long lval;
+
+       type = snd_ctl_elem_info_get_type(space->ctl_info);
+       count = snd_ctl_elem_info_get_count(space->ctl_info);
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       if (*value == '\0')
+                               goto missing;
+                       val = strncasecmp(value, "true", 4) == 0 ||
+                               strncasecmp(value, "yes", 3) == 0 ||
+                               strncasecmp(value, "on", 2) == 0 ||
+                               strncasecmp(value, "1", 1) == 0;
+                       snd_ctl_elem_value_set_boolean(space->ctl_value, idx, val);
+                       if (all)
+                               continue;
+                       pos = strchr(value, ',');
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       pos = strchr(value, ',');
+                       if (pos)
+                               *(char *)pos = '\0';
+                       remove_trailing_chars((char *)value, ' ');
+                       items = pos ? pos - value : strlen(value);
+                       if (items > 1 && value[items-1] == '%') {
+                               val = convert_prange1(strtol(value, NULL, 0), snd_ctl_elem_info_get_min(space->ctl_info), snd_ctl_elem_info_get_max(space->ctl_info));
+                               snd_ctl_elem_value_set_integer(space->ctl_value, idx, val);
+                       } else if (items > 2 && value[items-2] == 'd' && value[items-1] == 'B') {
+                               val = strtol(value, NULL, 0) * 100;
+                               if ((pos2 = strchr(value, '.')) != NULL) {
+                                       if (isdigit(*(pos2-1)) && isdigit(*(pos2-2))) {
+                                               if (val < 0)
+                                                       val -= strtol(pos2 + 1, NULL, 0);
+                                               else
+                                                       val += strtol(pos2 + 1, NULL, 0);
+                                       } else if (isdigit(*(pos2-1))) {
+                                               if (val < 0)
+                                                       val -= strtol(pos2 + 1, NULL, 0) * 10;
+                                               else
+                                                       val += strtol(pos2 + 1, NULL, 0) * 10;
+                                       }
+                               }
+                               val = snd_ctl_convert_from_dB(snd_hctl_ctl(space->ctl_handle), space->ctl_id, val, &lval, -1);
+                               if (val < 0) {
+                                       dbg("unable to convert dB value '%s' to internal integer range", value);
+                                       return val;
+                               }
+                               snd_ctl_elem_value_set_integer(space->ctl_value, idx, lval);
+                       } else {
+                               snd_ctl_elem_value_set_integer(space->ctl_value, idx, strtol(value, NULL, 0));
+                       }
+                       if (all)
+                               continue;
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       snd_ctl_elem_value_set_integer64(space->ctl_value, idx, strtoll(value, NULL, 0));
+                       if (all)
+                               continue;
+                       pos = strchr(value, ',');
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       pos = strchr(value, ',');
+                       if (isdigit(value[0]) || value[0] == '-') {
+                               snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, strtol(value, NULL, 0));
+                       } else {
+                               if (pos)
+                                       *(char *)pos = '\0';
+                               remove_trailing_chars((char *)value, ' ');
+                               items = snd_ctl_elem_info_get_items(space->ctl_info);
+                               for (idx2 = 0; idx2 < items; idx2++) {
+                                       snd_ctl_elem_info_set_item(space->ctl_info, idx2);
+                                       elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+                                       if (elem == NULL)
+                                               return -ENOENT;
+                                       val = snd_hctl_elem_info(elem, space->ctl_info);
+                                       if (val < 0)
+                                               return val;
+                                       if (strcasecmp(snd_ctl_elem_info_get_item_name(space->ctl_info), value) == 0) {
+                                               snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, idx2);
+                                               break;
+                                       }
+                               }
+                               if (idx2 >= items) {
+                                       Perror(space, "wrong enum identifier '%s'", value);
+                                       return -EINVAL;
+                               }
+                       }
+                       if (all)
+                               continue;
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+               if (type == SND_CTL_ELEM_TYPE_IEC958)
+                       count = sizeof(snd_aes_iec958_t);
+               while (*value == ' ')
+                       value++;
+               if (strlen(value) != count * 2) {
+                       Perror(space, "bad ctl value hexa length (should be %u bytes)", count);
+                       return -EINVAL;
+               }
+               for (idx = 0; idx < count; idx += 2) {
+                       val = hextodigit(*(value++)) << 4;
+                       val |= hextodigit(*(value++));
+                       if (val > 255) {
+                               Perror(space, "bad ctl hexa value");
+                               return -EINVAL;
+                       }
+                       snd_ctl_elem_value_set_byte(space->ctl_value, idx, val);
+               }
+               break;
+       default:
+               Perror(space, "unknown element type '%i'", type);
+               return -EINVAL;
+       }
+       return 0;
+  missing:
+       printf("%i %i\n", type, count);
+       Perror(space, "missing some ctl values (line %i)", space->linenum);
+       return -EINVAL;
+}
+
+static const char *elemid_get(struct space *space, const char *attr)
+{
+       long long val;
+       snd_ctl_elem_type_t type;
+       static char res[256];
+
+       if (strncasecmp(attr, "numid", 5) == 0) {
+               val = snd_ctl_elem_id_get_numid(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "iface", 5) == 0 ||
+           strncasecmp(attr, "interface", 9) == 0)
+               return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(space->ctl_id));
+       if (strncasecmp(attr, "device", 6) == 0) {
+               val = snd_ctl_elem_id_get_device(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "subdev", 6) == 0) {
+               val = snd_ctl_elem_id_get_subdevice(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "name", 4) == 0)
+               return snd_ctl_elem_id_get_name(space->ctl_id);
+       if (strncasecmp(attr, "index", 5) == 0) {
+               val = snd_ctl_elem_id_get_index(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "type", 4) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(space->ctl_info));
+       }
+       if (strncasecmp(attr, "attr", 4) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               res[0] = '\0';
+               if (snd_ctl_elem_info_is_readable(space->ctl_info))
+                       strcat(res, "r");
+               if (snd_ctl_elem_info_is_writable(space->ctl_info))
+                       strcat(res, "w");
+               if (snd_ctl_elem_info_is_volatile(space->ctl_info))
+                       strcat(res, "v");
+               if (snd_ctl_elem_info_is_inactive(space->ctl_info))
+                       strcat(res, "i");
+               if (snd_ctl_elem_info_is_locked(space->ctl_info))
+                       strcat(res, "l");
+               if (snd_ctl_elem_info_is_tlv_readable(space->ctl_info))
+                       strcat(res, "R");
+               if (snd_ctl_elem_info_is_tlv_writable(space->ctl_info))
+                       strcat(res, "W");
+               if (snd_ctl_elem_info_is_tlv_commandable(space->ctl_info))
+                       strcat(res, "C");
+               if (snd_ctl_elem_info_is_owner(space->ctl_info))
+                       strcat(res, "o");
+               if (snd_ctl_elem_info_is_user(space->ctl_info))
+                       strcat(res, "u");
+               return res;
+       }
+       if (strncasecmp(attr, "owner", 5) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               val = snd_ctl_elem_info_get_owner(space->ctl_info);
+               goto value;
+       }
+       if (strncasecmp(attr, "count", 5) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               val = snd_ctl_elem_info_get_count(space->ctl_info);
+               goto value;
+       }
+       if (strncasecmp(attr, "min", 3) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               type = snd_ctl_elem_info_get_type(space->ctl_info);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+                       val = snd_ctl_elem_info_get_min64(space->ctl_info);
+               else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+                       val = snd_ctl_elem_info_get_min(space->ctl_info);
+               else
+                       goto empty;
+               goto value;
+       }
+       if (strncasecmp(attr, "max", 3) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               type = snd_ctl_elem_info_get_type(space->ctl_info);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+                       val = snd_ctl_elem_info_get_max64(space->ctl_info);
+               else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+                       val = snd_ctl_elem_info_get_max(space->ctl_info);
+               else
+                       goto empty;
+               goto value;
+       }
+       if (strncasecmp(attr, "step", 3) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               type = snd_ctl_elem_info_get_type(space->ctl_info);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+                       val = snd_ctl_elem_info_get_step64(space->ctl_info);
+               else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+                       val = snd_ctl_elem_info_get_step(space->ctl_info);
+               else
+                       goto empty;
+               goto value;
+       }
+       if (strncasecmp(attr, "items", 5) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               if (snd_ctl_elem_info_get_type(space->ctl_info) == SND_CTL_ELEM_TYPE_ENUMERATED)
+                       val = snd_ctl_elem_info_get_items(space->ctl_info);
+               else {
+                 empty:
+                       res[0] = '\0';
+                       return res;
+               }
+               goto value;
+       }
+       if (strncasecmp(attr, "value", 5) == 0) {
+               if (check_id_changed(space, 3))
+                       return NULL;
+               return get_ctl_value(space);
+       }
+       if (strncasecmp(attr, "dBmin", 5) == 0) {
+               long min, max;
+               if (check_id_changed(space, 1))
+                       return NULL;
+               if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
+                       goto empty;
+               val = min;
+dbvalue:
+               sprintf(res, "%li.%02idB", (long)(val / 100), (int)abs(val % 100));
+               return res;
+       }
+       if (strncasecmp(attr, "dBmax", 5) == 0) {
+               long min, max;
+               if (check_id_changed(space, 1))
+                       return NULL;
+               if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
+                       goto empty;
+               val = max;
+               goto dbvalue;
+       }
+       if (strncasecmp(attr, "enums", 5) == 0) {
+               unsigned int idx, items;
+               snd_hctl_elem_t *elem;
+               if (check_id_changed(space, 1))
+                       return NULL;
+               if (snd_ctl_elem_info_get_type(space->ctl_info) != SND_CTL_ELEM_TYPE_ENUMERATED)
+                       goto empty;
+               items = snd_ctl_elem_info_get_items(space->ctl_info);
+               strcpy(res, "|");
+               for (idx = 0; idx < items; idx++) {
+                       snd_ctl_elem_info_set_item(space->ctl_info, idx);
+                       elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+                       if (elem == NULL)
+                               break;
+                       if (snd_hctl_elem_info(elem, space->ctl_info) < 0)
+                               break;
+                       strlcat(res, snd_ctl_elem_info_get_item_name(space->ctl_info), sizeof(res));
+                       strlcat(res, "|", sizeof(res));
+               }
+               return res;
+       }
+       Perror(space, "unknown ctl{} attribute '%s'", attr);
+       return NULL;
+  value:
+       sprintf(res, "%lli", val);
+       return res;
+}
+
+static int elemid_set(struct space *space, const char *attr, const char *value)
+{
+       unsigned int val;
+       void (*fcn)(snd_ctl_elem_id_t *, unsigned int);
+       snd_ctl_elem_iface_t iface;
+       int err;
+
+       if (strncasecmp(attr, "numid", 5) == 0) {
+               fcn = snd_ctl_elem_id_set_numid;
+               goto value;
+       }
+       if (strncasecmp(attr, "iface", 5) == 0 ||
+           strncasecmp(attr, "interface", 9) == 0 ||
+           strncasecmp(attr, "reset", 5) == 0 ||
+           strncasecmp(attr, "search", 6) == 0) {
+               if (strlen(value) == 0 && strncasecmp(attr, "search", 6) == 0) {
+                       iface = 0;
+                       goto search;
+               }
+               for (iface = 0; iface <= SND_CTL_ELEM_IFACE_LAST; iface++) {
+                       if (strcasecmp(value, snd_ctl_elem_iface_name(iface)) == 0) {
+                               if (strncasecmp(attr, "reset", 5) == 0)
+                                       snd_ctl_elem_id_clear(space->ctl_id);
+                               if (strncasecmp(attr, "search", 5) == 0) {
+                                 search:
+                                       snd_ctl_elem_id_clear(space->ctl_id);
+                                       /* -1 means all */
+                                       snd_ctl_elem_id_set_interface(space->ctl_id, -1);
+                                       snd_ctl_elem_id_set_device(space->ctl_id, -1);
+                                       snd_ctl_elem_id_set_subdevice(space->ctl_id, -1);
+                                       snd_ctl_elem_id_set_name(space->ctl_id, "*");
+                                       snd_ctl_elem_id_set_index(space->ctl_id, -1);
+                                       if (strlen(value) == 0)
+                                               return 0;
+                               }
+                               snd_ctl_elem_id_set_interface(space->ctl_id, iface);
+                               space->ctl_id_changed = ~0;
+                               return 0;
+                       }
+               }
+               Perror(space, "unknown control interface name '%s'", value);
+               return -EINVAL;
+       }
+       if (strncasecmp(attr, "device", 6) == 0) {
+               fcn = snd_ctl_elem_id_set_device;
+               goto value;
+       }
+       if (strncasecmp(attr, "subdev", 6) == 0) {
+               fcn = snd_ctl_elem_id_set_subdevice;
+               goto value;
+       }
+       if (strncasecmp(attr, "name", 4) == 0) {
+               snd_ctl_elem_id_set_name(space->ctl_id, value);
+               space->ctl_id_changed = ~0;
+               return 0;
+       }
+       if (strncasecmp(attr, "index", 5) == 0) {
+               fcn = snd_ctl_elem_id_set_index;
+               goto value;
+       }
+       if (strncasecmp(attr, "values", 6) == 0 ||
+           strncasecmp(attr, "value", 5) == 0) {
+               err = check_id_changed(space, 1);
+               if (err < 0) {
+                       Perror(space, "control element not found");
+                       return err;
+               }
+               err = set_ctl_value(space, value, strncasecmp(attr, "values", 6) == 0);
+               if (err < 0) {
+                       space->ctl_id_changed |= 2;
+               } else {
+                       space->ctl_id_changed &= ~2;
+                       snd_ctl_elem_value_set_id(space->ctl_value, space->ctl_id);
+                       err = snd_ctl_elem_write(snd_hctl_ctl(space->ctl_handle), space->ctl_value);
+                       if (err < 0) {
+                               Perror(space, "value write error: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               return err;
+       }
+       Perror(space, "unknown CTL{} attribute '%s'", attr);
+       return -EINVAL;
+  value:
+       val = (unsigned int)strtol(value, NULL, 0);
+       fcn(space->ctl_id, val);
+       space->ctl_id_changed = ~0;
+       return 0;
+}
+
+static int get_key(char **line, char **key, enum key_op *op, char **value)
+{
+       char *linepos;
+       char *temp;
+
+       linepos = *line;
+       if (linepos == NULL && linepos[0] == '\0')
+               return -EINVAL;
+
+       /* skip whitespace */
+       while (isspace(linepos[0]) || linepos[0] == ',')
+               linepos++;
+
+       /* get the key */
+       if (linepos[0] == '\0')
+               return -EINVAL;
+       *key = linepos;
+
+       while (1) {
+               linepos++;
+               if (linepos[0] == '\0')
+                       return -1;
+               if (isspace(linepos[0]))
+                       break;
+               if (linepos[0] == '=')
+                       break;
+               if (linepos[0] == '+')
+                       break;
+               if (linepos[0] == '!')
+                       break;
+               if (linepos[0] == ':')
+                       break;
+       }
+
+       /* remember end of key */
+       temp = linepos;
+
+       /* skip whitespace after key */
+       while (isspace(linepos[0]))
+               linepos++;
+       if (linepos[0] == '\0')
+               return -EINVAL;
+
+       /* get operation type */
+       if (linepos[0] == '=' && linepos[1] == '=') {
+               *op = KEY_OP_MATCH;
+               linepos += 2;
+               dbg("operator=match");
+       } else if (linepos[0] == '!' && linepos[1] == '=') {
+               *op = KEY_OP_NOMATCH;
+               linepos += 2;
+               dbg("operator=nomatch");
+       } else if (linepos[0] == '+' && linepos[1] == '=') {
+               *op = KEY_OP_ADD;
+               linepos += 2;
+               dbg("operator=add");
+       } else if (linepos[0] == '=') {
+               *op = KEY_OP_ASSIGN;
+               linepos++;
+               dbg("operator=assign");
+       } else if (linepos[0] == ':' && linepos[1] == '=') {
+               *op = KEY_OP_ASSIGN_FINAL;
+               linepos += 2;
+               dbg("operator=assign_final");
+       } else
+               return -EINVAL;
+
+       /* terminate key */
+       temp[0] = '\0';
+       dbg("key='%s'", *key);
+
+       /* skip whitespace after operator */
+       while (isspace(linepos[0]))
+               linepos++;
+       if (linepos[0] == '\0')
+               return -EINVAL;
+
+       /* get the value*/
+       if (linepos[0] != '"')
+               return -EINVAL;
+       linepos++;
+       *value = linepos;
+
+       while (1) {
+               temp = strchr(linepos, '"');
+               if (temp && temp[-1] == '\\') {
+                       linepos = temp + 1;
+                       continue;
+               }
+               break;
+       }
+       if (!temp)
+               return -EINVAL;
+       temp[0] = '\0';
+       temp++;
+       dbg("value='%s'", *value);
+
+       /* move line to next key */
+       *line = temp;
+
+       return 0;
+}
+
+/* extract possible KEY{attr} */
+static char *get_key_attribute(struct space *space, char *str, char *res, size_t ressize)
+{
+       char *pos;
+       char *attr;
+
+       attr = strchr(str, '{');
+       if (attr != NULL) {
+               attr++;
+               pos = strchr(attr, '}');
+               if (pos == NULL) {
+                       Perror(space, "missing closing brace for format");
+                       return NULL;
+               }
+               pos[0] = '\0';
+               strlcpy(res, attr, ressize);
+               pos[0] = '}';
+               dbg("attribute='%s'", res);
+               return res;
+       }
+
+       return NULL;
+}
+
+/* extract possible {attr} and move str behind it */
+static char *get_format_attribute(struct space *space, char **str)
+{
+       char *pos;
+       char *attr = NULL;
+
+       if (*str[0] == '{') {
+               pos = strchr(*str, '}');
+               if (pos == NULL) {
+                       Perror(space, "missing closing brace for format");
+                       return NULL;
+               }
+               pos[0] = '\0';
+               attr = *str+1;
+               *str = pos+1;
+               dbg("attribute='%s', str='%s'", attr, *str);
+       }
+       return attr;
+}
+
+/* extract possible format length and move str behind it*/
+static int get_format_len(struct space *space, char **str)
+{
+       int num;
+       char *tail;
+
+       if (isdigit(*str[0])) {
+               num = (int) strtoul(*str, &tail, 10);
+               if (num > 0) {
+                       *str = tail;
+                       dbg("format length=%i", num);
+                       return num;
+               } else {
+                       Perror(space, "format parsing error '%s'", *str);
+               }
+       }
+       return -1;
+}
+
+static void apply_format(struct space *space, char *string, size_t maxsize)
+{
+       char temp[PATH_SIZE];
+       char temp2[PATH_SIZE];
+       char *head, *tail, *pos, *cpos, *attr, *rest;
+       struct pair *pair;
+       int len;
+       int i;
+       int count;
+       enum subst_type {
+               SUBST_UNKNOWN,
+               SUBST_CARDINFO,
+               SUBST_CTL,
+               SUBST_RESULT,
+               SUBST_ATTR,
+               SUBST_SYSFSROOT,
+               SUBST_ENV,
+               SUBST_CONFIG,
+       };
+       static const struct subst_map {
+               char *name;
+               char fmt;
+               enum subst_type type;
+       } map[] = {
+               { .name = "cardinfo",   .fmt = 'i',     .type = SUBST_CARDINFO },
+               { .name = "ctl",        .fmt = 'C',     .type = SUBST_CTL },
+               { .name = "result",     .fmt = 'c',     .type = SUBST_RESULT },
+               { .name = "attr",       .fmt = 's',     .type = SUBST_ATTR },
+               { .name = "sysfsroot",  .fmt = 'r',     .type = SUBST_SYSFSROOT },
+               { .name = "env",        .fmt = 'E',     .type = SUBST_ENV },
+               { .name = "config",     .fmt = 'g',     .type = SUBST_CONFIG },
+               { NULL, '\0', 0 }
+       };
+       enum subst_type type;
+       const struct subst_map *subst;
+
+       head = string;
+       while (1) {
+               len = -1;
+               while (head[0] != '\0') {
+                       if (head[0] == '$') {
+                               /* substitute named variable */
+                               if (head[1] == '\0')
+                                       break;
+                               if (head[1] == '$') {
+                                       strlcpy(temp, head+2, sizeof(temp));
+                                       strlcpy(head+1, temp, maxsize);
+                                       head++;
+                                       continue;
+                               }
+                               head[0] = '\0';
+                               for (subst = map; subst->name; subst++) {
+                                       if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
+                                               type = subst->type;
+                                               tail = head + strlen(subst->name)+1;
+                                               dbg("will substitute format name '%s'", subst->name);
+                                               goto found;
+                                       }
+                               }
+                       } else if (head[0] == '%') {
+                               /* substitute format char */
+                               if (head[1] == '\0')
+                                       break;
+                               if (head[1] == '%') {
+                                       strlcpy(temp, head+2, sizeof(temp));
+                                       strlcpy(head+1, temp, maxsize);
+                                       head++;
+                                       continue;
+                               }
+                               head[0] = '\0';
+                               tail = head+1;
+                               len = get_format_len(space, &tail);
+                               for (subst = map; subst->name; subst++) {
+                                       if (tail[0] == subst->fmt) {
+                                               type = subst->type;
+                                               tail++;
+                                               dbg("will substitute format char '%c'", subst->fmt);
+                                               goto found;
+                                       }
+                               }
+                       }
+                       head++;
+               }
+               break;
+found:
+               attr = get_format_attribute(space, &tail);
+               strlcpy(temp, tail, sizeof(temp));
+               dbg("format=%i, string='%s', tail='%s'", type ,string, tail);
+
+               switch (type) {
+               case SUBST_CARDINFO:
+                       if (attr == NULL)
+                               Perror(space, "missing identification parametr for cardinfo");
+                       else {
+                               const char *value = cardinfo_get(space, attr);
+                               if (value == NULL)
+                                       break;
+                               strlcat(string, value, maxsize);
+                               dbg("substitute cardinfo{%s} '%s'", attr, value);
+                       }
+                       break;
+               case SUBST_CTL:
+                       if (attr == NULL)
+                               Perror(space, "missing identification parametr for ctl");
+                       else {
+                               const char *value = elemid_get(space, attr);
+                               if (value == NULL)
+                                       break;
+                               strlcat(string, value, maxsize);
+                               dbg("substitute ctl{%s} '%s'", attr, value);
+                       }
+                       break;
+               case SUBST_RESULT:
+                       if (space->program_result == NULL)
+                               break;
+                       /* get part part of the result string */
+                       i = 0;
+                       if (attr != NULL)
+                               i = strtoul(attr, &rest, 10);
+                       if (i > 0) {
+                               dbg("request part #%d of result string", i);
+                               cpos = space->program_result;
+                               while (--i) {
+                                       while (cpos[0] != '\0' && !isspace(cpos[0]))
+                                               cpos++;
+                                       while (isspace(cpos[0]))
+                                               cpos++;
+                               }
+                               if (i > 0) {
+                                       Perror(space, "requested part of result string not found");
+                                       break;
+                               }
+                               strlcpy(temp2, cpos, sizeof(temp2));
+                               /* %{2+}c copies the whole string from the second part on */
+                               if (rest[0] != '+') {
+                                       cpos = strchr(temp2, ' ');
+                                       if (cpos)
+                                               cpos[0] = '\0';
+                               }
+                               strlcat(string, temp2, maxsize);
+                               dbg("substitute part of result string '%s'", temp2);
+                       } else {
+                               strlcat(string, space->program_result, maxsize);
+                               dbg("substitute result string '%s'", space->program_result);
+                       }
+                       break;
+               case SUBST_ATTR:
+                       if (attr == NULL)
+                               Perror(space, "missing file parameter for attr");
+                       else {
+                               const char *value = NULL;
+                               size_t size;
+
+                               pair = value_find(space, "sysfs_device");
+                               if (pair == NULL)
+                                       break;
+                               value = sysfs_attr_get_value(pair->value, attr);
+
+                               if (value == NULL)
+                                       break;
+
+                               /* strip trailing whitespace and replace untrusted characters of sysfs value */
+                               size = strlcpy(temp2, value, sizeof(temp2));
+                               if (size >= sizeof(temp2))
+                                       size = sizeof(temp2)-1;
+                               while (size > 0 && isspace(temp2[size-1]))
+                                       temp2[--size] = '\0';
+                               count = replace_untrusted_chars(temp2);
+                               if (count > 0)
+                                       Perror(space, "%i untrusted character(s) replaced" , count);
+                               strlcat(string, temp2, maxsize);
+                               dbg("substitute sysfs value '%s'", temp2);
+                       }
+                       break;
+               case SUBST_SYSFSROOT:
+                       strlcat(string, sysfs_path, maxsize);
+                       dbg("substitute sysfs_path '%s'", sysfs_path);
+                       break;
+               case SUBST_ENV:
+                       if (attr == NULL) {
+                               dbg("missing attribute");
+                               break;
+                       }
+                       pos = getenv(attr);
+                       if (pos == NULL) {
+                               dbg("env '%s' not available", attr);
+                               break;
+                       }
+                       dbg("substitute env '%s=%s'", attr, pos);
+                       strlcat(string, pos, maxsize);
+                       break;
+               case SUBST_CONFIG:
+                       if (attr == NULL) {
+                               dbg("missing attribute");
+                               break;
+                       }
+                       pair = value_find(space, attr);
+                       if (pair == NULL)
+                               break;
+                       strlcat(string, pair->value, maxsize);
+                       break;
+               default:
+                       Perror(space, "unknown substitution type=%i", type);
+                       break;
+               }
+               /* possibly truncate to format-char specified length */
+               if (len != -1) {
+                       head[len] = '\0';
+                       dbg("truncate to %i chars, subtitution string becomes '%s'", len, head);
+               }
+               strlcat(string, temp, maxsize);
+       }
+       /* unescape strings */
+       head = tail = string;
+       while (*head != '\0') {
+               if (*head == '\\') {
+                       head++;
+                       if (*head == '\0')
+                               break;
+                       switch (*head) {
+                       case 'a': *tail++ = '\a'; break;
+                       case 'b': *tail++ = '\b'; break;
+                       case 'n': *tail++ = '\n'; break;
+                       case 'r': *tail++ = '\r'; break;
+                       case 't': *tail++ = '\t'; break;
+                       case 'v': *tail++ = '\v'; break;
+                       case '\\': *tail++ = '\\'; break;
+                       default: *tail++ = *head; break;
+                       }
+                       head++;
+                       continue;
+               }
+               if (*head)
+                       *tail++ = *head++;
+       }
+       *tail = 0;
+}
+
+static int do_match(const char *key, enum key_op op,
+                   const char *key_value, const char *value)
+{
+       int match;
+
+       if (value == NULL)
+               return 0;
+       dbg("match %s '%s' <-> '%s'", key, key_value, value);
+       match = fnmatch(key_value, value, 0) == 0;
+       if (match && op == KEY_OP_MATCH) {
+               dbg("%s is true (matching value)", key);
+               return 1;
+       }
+       if (!match && op == KEY_OP_NOMATCH) {
+               dbg("%s is true (non-matching value)", key);
+               return 1;
+       }
+       dbg("%s is false", key);
+       return 0;
+}
+
+static int ctl_match(snd_ctl_elem_id_t *pattern, snd_ctl_elem_id_t *id)
+{
+       if (snd_ctl_elem_id_get_interface(pattern) != -1 &&
+           snd_ctl_elem_id_get_interface(pattern) != snd_ctl_elem_id_get_interface(id))
+               return 0;
+       if (snd_ctl_elem_id_get_device(pattern) != -1 &&
+           snd_ctl_elem_id_get_device(pattern) != snd_ctl_elem_id_get_device(id))
+               return 0;
+       if (snd_ctl_elem_id_get_subdevice(pattern) != -1 &&
+           snd_ctl_elem_id_get_subdevice(pattern) != snd_ctl_elem_id_get_subdevice(id))
+               return 0;
+       if (snd_ctl_elem_id_get_index(pattern) != -1 &&
+           snd_ctl_elem_id_get_index(pattern) != snd_ctl_elem_id_get_index(id))
+               return 0;
+       if (fnmatch(snd_ctl_elem_id_get_name(pattern), snd_ctl_elem_id_get_name(id), 0) != 0)
+               return 0;
+       return 1;
+}
+
+static
+int run_program1(struct space *space,
+                const char *command0, char *result,
+                size_t ressize, size_t *reslen, int log)
+{
+       char *pos = strchr(command0, ' ');
+       int cmdlen = pos ? pos - command0 : strlen(command0);
+       int err, index;
+       snd_hctl_elem_t *elem;
+       snd_ctl_elem_id_t *id;
+       
+       if (cmdlen == 12 && strncmp(command0, "__ctl_search", 12) == 0) {
+               index = 0;
+               if (pos)
+                       index = strtol(pos, NULL, 0);
+               err = snd_ctl_elem_id_malloc(&id);
+               if (err < 0)
+                       return EXIT_FAILURE;
+               elem = snd_hctl_first_elem(space->ctl_handle);
+               while (elem) {
+                       snd_hctl_elem_get_id(elem, id);
+                       if (!ctl_match(space->ctl_id, id))
+                               goto next_search;
+                       if (index > 0) {
+                               index--;
+                               goto next_search;
+                       }
+                       strlcpy(result, "0", ressize);
+                       snd_ctl_elem_id_copy(space->ctl_id, id);
+                       snd_ctl_elem_id_free(id);
+                       dbg("__ctl_search found a control");
+                       return EXIT_SUCCESS;
+                     next_search:
+                       elem = snd_hctl_elem_next(elem);
+               }
+               snd_ctl_elem_id_free(id);
+               return EXIT_FAILURE;
+       }
+       if (cmdlen == 11 && strncmp(command0, "__ctl_count", 11) == 0) {
+               index = 0;
+               err = snd_ctl_elem_id_malloc(&id);
+               if (err < 0)
+                       return EXIT_FAILURE;
+               elem = snd_hctl_first_elem(space->ctl_handle);
+               while (elem) {
+                       snd_hctl_elem_get_id(elem, id);
+                       if (!ctl_match(space->ctl_id, id))
+                               goto next_count;
+                       index++;
+                     next_count:
+                       elem = snd_hctl_elem_next(elem);
+               }
+               snd_ctl_elem_id_free(id);
+               if (index > 0) {
+                       snprintf(result, ressize, "%u", index);
+                       dbg("__ctl_count found %s controls", result);
+                       return EXIT_SUCCESS;
+               }
+               dbg("__ctl_count no match");
+               return EXIT_FAILURE;
+       }
+       if (cmdlen == 11 && strncmp(command0, "__ctl_write", 11) == 0) {
+       }
+       Perror(space, "unknown buildin command '%s'", command0);
+       return EXIT_FAILURE;
+}
+
+static int parse(struct space *space, const char *filename);
+
+static char *new_root_dir(const char *filename)
+{
+       char *res, *tmp;
+
+       res = strdup(filename);
+       if (res) {
+               tmp = strrchr(res, '/');
+               if (tmp)
+                       *tmp = '\0';
+       }
+       dbg("new_root_dir '%s' '%s'", filename, res);
+       return res;
+}
+
+static int parse_line(struct space *space, char *line, size_t linesize)
+{
+       char *linepos;
+       char *key, *value, *attr, *temp;
+       struct pair *pair;
+       enum key_op op;
+       int err = 0, count;
+       char string[PATH_SIZE];
+       char result[PATH_SIZE];
+
+       linepos = line;
+       while (*linepos != '\0') {
+               op = KEY_OP_UNSET;
+               
+               err = get_key(&linepos, &key, &op, &value);
+               if (err < 0)
+                       goto invalid;
+
+               if (strncasecmp(key, "LABEL", 5) == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid LABEL operation");
+                               goto invalid;
+                       }
+                       if (space->go_to && strcmp(space->go_to, value) == 0) {
+                               free(space->go_to);
+                               space->go_to = NULL;
+                       }
+                       continue;
+               }
+               
+               if (space->go_to) {
+                       dbg("skip (GOTO '%s')", space->go_to);
+                       break;          /* not for us */
+               }
+
+               if (strncasecmp(key, "CTL{", 4) == 0) {
+                       attr = get_key_attribute(space, key + 3, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing CTL attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_ASSIGN) {
+                               strlcpy(result, value, sizeof(result));
+                               apply_format(space, result, sizeof(result));
+                               dbg("ctl assign: '%s' '%s'", value, attr);
+                               err = elemid_set(space, attr, result);
+                               if (space->program_result) {
+                                       free(space->program_result);
+                                       space->program_result = NULL;
+                               }
+                               snprintf(string, sizeof(string), "%i", err);
+                               space->program_result = strdup(string);
+                               err = 0;
+                               if (space->program_result == NULL)
+                                       break;
+                       } else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               dbg("ctl match: '%s' '%s'", value, attr);
+                               temp = (char *)elemid_get(space, attr);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid CTL{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "RESULT") == 0) {
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               if (!do_match(key, op, value, space->program_result))
+                                       break;
+                       } else if (op == KEY_OP_ASSIGN) {
+                               if (space->program_result) {
+                                       free(space->program_result);
+                                       space->program_result = NULL;
+                               }
+                               strlcpy(string, value, sizeof(string));
+                               apply_format(space, string, sizeof(string));
+                               space->program_result = strdup(string);
+                               if (space->program_result == NULL)
+                                       break;
+                       } else {
+                               Perror(space, "invalid RESULT operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "PROGRAM") == 0) {
+                       if (op == KEY_OP_UNSET)
+                               continue;
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       if (space->program_result) {
+                               free(space->program_result);
+                               space->program_result = NULL;
+                       }
+                       if (run_program(space, string, result, sizeof(result), NULL, space->log_run) != 0) {
+                               dbg("PROGRAM '%s' is false", string);
+                               if (op != KEY_OP_NOMATCH)
+                                       break;
+                       } else {
+                               remove_trailing_chars(result, '\n');
+                               count = replace_untrusted_chars(result);
+                               if (count)
+                                       info("%i untrusted character(s) replaced", count);
+                               dbg("PROGRAM '%s' result is '%s'", string, result);
+                               space->program_result = strdup(result);
+                               if (space->program_result == NULL)
+                                       break;
+                               dbg("PROGRAM returned successful");
+                               if (op == KEY_OP_NOMATCH)
+                                       break;
+                       }
+                       dbg("PROGRAM key is true");
+                       continue;
+               }
+               if (strncasecmp(key, "CARDINFO{", 9) == 0) {
+                       attr = get_key_attribute(space, key + 8, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing CARDINFO attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               dbg("cardinfo: '%s' '%s'", value, attr);
+                               temp = (char *)cardinfo_get(space, attr);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid CARDINFO{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strncasecmp(key, "ATTR{", 5) == 0) {
+                       attr = get_key_attribute(space, key + 4, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing ATTR attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               pair = value_find(space, "sysfs_device");
+                               if (pair == NULL)
+                                       break;
+                               dbg("sysfs_attr: '%s' '%s'", pair->value, attr);
+                               temp = sysfs_attr_get_value(pair->value, attr);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid ATTR{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strncasecmp(key, "ENV{", 4) == 0) {
+                       attr = get_key_attribute(space, key + 3, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing ENV attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               temp = getenv(attr);
+                               dbg("env: '%s' '%s'", attr, temp);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else if (op == KEY_OP_ASSIGN ||
+                                  op == KEY_OP_ASSIGN_FINAL) {
+                               strlcpy(result, value, sizeof(result));
+                               apply_format(space, result, sizeof(result));
+                               dbg("env set: '%s' '%s'", attr, result);
+                               if (setenv(attr, result, op == KEY_OP_ASSIGN_FINAL))
+                                       break;
+                       } else {
+                               Perror(space, "invalid ENV{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "GOTO") == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid GOTO operation");
+                               goto invalid;
+                       }
+                       space->go_to = strdup(value);
+                       if (space->go_to == NULL) {
+                               err = -ENOMEM;
+                               break;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "INCLUDE") == 0) {
+                       char *rootdir, *go_to;
+                       const char *filename;
+                       struct dirent *dirent;
+                       DIR *dir;
+                       int linenum;
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid INCLUDE operation");
+                               goto invalid;
+                       }
+                       if (value[0] == '/')
+                               strlcpy(string, value, sizeof(string));
+                       else {
+                               strlcpy(string, space->rootdir, sizeof(string));
+                               strlcat(string, "/", sizeof(string));
+                               strlcat(string, value, sizeof(string));
+                       }
+                       rootdir = space->rootdir;
+                       go_to = space->go_to;
+                       filename = space->filename;
+                       linenum = space->linenum;
+                       dir = opendir(string);
+                       if (dir) {
+                               count = strlen(string);
+                               while ((dirent = readdir(dir)) != NULL) {
+                                       if (strcmp(dirent->d_name, ".") == 0 ||
+                                           strcmp(dirent->d_name, "..") == 0)
+                                               continue;
+                                       string[count] = '\0';
+                                       strlcat(string, "/", sizeof(string));
+                                       strlcat(string, dirent->d_name, sizeof(string));
+                                       space->go_to = NULL;
+                                       space->rootdir = new_root_dir(string);
+                                       if (space->rootdir) {
+                                               err = parse(space, string);
+                                               free(space->rootdir);
+                                       } else
+                                               err = -ENOMEM;
+                                       if (space->go_to) {
+                                               Perror(space, "unterminated GOTO '%s'", space->go_to);
+                                               free(space->go_to);
+                                       }
+                                       if (err)
+                                               break;
+                               }
+                               closedir(dir);
+                       } else {
+                               space->go_to = NULL;
+                               space->rootdir = new_root_dir(string);
+                               if (space->rootdir) {
+                                       err = parse(space, string);
+                                       free(space->rootdir);
+                               } else
+                                       err = -ENOMEM;
+                               if (space->go_to) {
+                                       Perror(space, "unterminated GOTO '%s'", space->go_to);
+                                       free(space->go_to);
+                               }
+                       }
+                       space->go_to = go_to;
+                       space->rootdir = rootdir;
+                       space->filename = filename;
+                       space->linenum = linenum;
+                       if (space->quit)
+                               break;
+                       if (err)
+                               break;
+                       continue;
+               }
+               if (strncasecmp(key, "ACCESS", 6) == 0) {
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               if (value[0] == '$') {
+                                       strlcpy(string, value, sizeof(string));
+                                       apply_format(space, string, sizeof(string));
+                                       if (string[0] == '/')
+                                               goto __access1;
+                               }
+                               if (value[0] != '/') {
+                                       strlcpy(string, space->rootdir, sizeof(string));
+                                       strlcat(string, "/", sizeof(string));
+                                       strlcat(string, value, sizeof(string));
+                               } else {
+                                       strlcpy(string, value, sizeof(string));
+                               }
+                               apply_format(space, string, sizeof(string));
+                             __access1:
+                               count = access(string, F_OK);
+                               dbg("access(%s) = %i (%s)", string, count, value);
+                               if (op == KEY_OP_MATCH && count != 0)
+                                       break;
+                               if (op == KEY_OP_NOMATCH && count == 0)
+                                       break;
+                       } else {
+                               Perror(space, "invalid ACCESS operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strncasecmp(key, "PRINT", 5) == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid PRINT operation");
+                               goto invalid;
+                       }
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       fwrite(string, strlen(string), 1, stdout);
+                       continue;
+               }
+               if (strncasecmp(key, "ERROR", 5) == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid ERROR operation");
+                               goto invalid;
+                       }
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       fwrite(string, strlen(string), 1, stderr);
+                       continue;
+               }
+               if (strncasecmp(key, "EXIT", 4) == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid EXIT operation");
+                               goto invalid;
+                       }
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       if (strcmp(string, "return") == 0)
+                               return -EJUSTRETURN;
+                       space->exit_code = strtol(string, NULL, 0);
+                       space->quit = 1;
+                       break;
+               }
+               if (strncasecmp(key, "CONFIG{", 7) == 0) {
+                       attr = get_key_attribute(space, key + 6, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing CONFIG attribute");
+                               goto invalid;
+                       }
+                       strlcpy(result, value, sizeof(result));
+                       apply_format(space, result, sizeof(result));
+                       if (op == KEY_OP_ASSIGN) {
+                               err = value_set(space, attr, result);
+                               dbg("CONFIG{%s}='%s'", attr, result);
+                               break;
+                       } else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               pair = value_find(space, attr);
+                               if (pair == NULL)
+                                       break;
+                               if (!do_match(key, op, result, pair->value))
+                                       break;
+                       } else {
+                               Perror(space, "invalid CONFIG{} operation");
+                               goto invalid;
+                       }
+               }
+
+               Perror(space, "unknown key '%s'", key);
+       }
+       return err;
+
+invalid:
+       Perror(space, "invalid rule");
+       return -EINVAL;
+}
+
+static int parse(struct space *space, const char *filename)
+{
+       char *buf, *bufline, *line;
+       size_t bufsize, pos, count, linesize;
+       unsigned int linenum, i, j, linenum_adj;
+       int err;
+
+       dbg("start of file '%s'", filename);
+
+       if (file_map(filename, &buf, &bufsize) != 0) {
+               err = errno;
+               error("Unable to open file '%s': %s", filename, strerror(err));
+               return -err;
+       }
+
+       err = 0;        
+       pos = 0;
+       linenum = 0;
+       linesize = 128;
+       line = malloc(linesize);
+       if (line == NULL)
+               return -ENOMEM;
+       space->filename = filename;
+       while (!err && pos < bufsize && !space->quit) {
+               count = line_width(buf, bufsize, pos);
+               bufline = buf + pos;
+               pos += count + 1;
+               linenum++;
+               
+               /* skip whitespaces */
+               while (count > 0 && isspace(bufline[0])) {
+                       bufline++;
+                       count--;
+               }
+               if (count == 0)
+                       continue;
+                       
+               /* comment check */
+               if (bufline[0] == '#')
+                       continue;
+               
+               if (count > linesize - 1) {
+                       free(line);
+                       linesize = (count + 127 + 1) & ~127;
+                       if (linesize > 2048) {
+                               error("file %s, line %i too long", filename, linenum);
+                               err = -EINVAL;
+                               break;
+                       }
+                       line = malloc(linesize);
+                       if (line == NULL) {
+                               err = -EINVAL;
+                               break;
+                       }
+               }
+               
+               /* skip backslash and newline from multiline rules */
+               linenum_adj = 0;
+               for (i = j = 0; i < count; i++) {
+                       if (bufline[i] == '\\' && bufline[i+1] == '\n') {
+                               linenum_adj++;
+                               continue;
+                       }
+                       line[j++] = bufline[i];
+               }
+               line[j] = '\0';
+
+               dbg("read (%i) '%s'", linenum, line);
+               space->linenum = linenum;
+               err = parse_line(space, line, linesize);
+               if (err == -EJUSTRETURN) {
+                       err = 0;
+                       break;
+               }
+               linenum += linenum_adj;
+       }
+
+       free(line);
+       space->filename = NULL;
+       space->linenum = -1;
+       file_unmap(buf, bufsize);
+       dbg("end of file '%s'", filename);
+       return err ? err : -abs(space->exit_code);
+}
+
+int init(const char *filename, const char *cardname)
+{
+       struct space *space;
+       int err = 0, card, first;
+       
+       sysfs_init();
+       if (!cardname) {
+               first = 1;
+               card = -1;
+               while (1) {
+                       if (snd_card_next(&card) < 0)
+                               break;
+                       if (card < 0) {
+                               if (first) {
+                                       error("No soundcards found...");
+                                       return -ENODEV;
+                               }
+                               break;
+                       }
+                       first = 0;
+                       err = init_space(&space, card);
+                       if (err == 0 &&
+                           (space->rootdir = new_root_dir(filename)) != NULL)
+                               err = parse(space, filename);
+                       free_space(space);
+                       if (err < 0)
+                               break;
+               }
+       } else {
+               card = snd_card_get_index(cardname);
+               if (card < 0) {
+                       error("Cannot find soundcard '%s'...", cardname);
+                       goto error;
+               }
+               memset(&space, 0, sizeof(space));
+               err = init_space(&space, card);
+               if (err == 0 &&
+                   (space->rootdir = new_root_dir(filename)) != NULL)
+                       err = parse(space, filename);
+               free_space(space);
+       }
+  error:
+       sysfs_cleanup();
+       return err;
+}
diff --git a/scenario/init_sysdeps.c b/scenario/init_sysdeps.c
new file mode 100644 (file)
index 0000000..3aca1b4
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     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 version 2 of the License.
+ * 
+ *     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.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#if defined(__GLIBC__) && !(defined(__UCLIBC__) && defined(__USE_BSD))
+static size_t strlcpy(char *dst, const char *src, size_t size)
+{
+       size_t bytes = 0;
+       char *q = dst;
+       const char *p = src;
+       char ch;
+
+       while ((ch = *p++)) {
+               if (bytes+1 < size)
+                       *q++ = ch;
+               bytes++;
+       }
+
+       /* If size == 0 there is no space for a final null... */
+       if (size)
+               *q = '\0';
+       return bytes;
+}
+
+static size_t strlcat(char *dst, const char *src, size_t size)
+{
+       size_t bytes = 0;
+       char *q = dst;
+       const char *p = src;
+       char ch;
+
+       while (bytes < size && *q) {
+               q++;
+               bytes++;
+       }
+       if (bytes == size)
+               return (bytes + strlen(src));
+
+       while ((ch = *p++)) {
+               if (bytes+1 < size)
+               *q++ = ch;
+               bytes++;
+       }
+
+       *q = '\0';
+       return bytes;
+}
+#endif /* __GLIBC__ */
diff --git a/scenario/init_sysfs.c b/scenario/init_sysfs.c
new file mode 100644 (file)
index 0000000..0cbada2
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
+ *                   2008 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 version 2 of the License.
+ * 
+ *     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.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+
+static char sysfs_path[PATH_SIZE];
+
+/* attribute value cache */
+static LIST_HEAD(attr_list);
+struct sysfs_attr {
+       struct list_head node;
+       char path[PATH_SIZE];
+       char *value;                    /* points to value_local if value is cached */
+       char value_local[NAME_SIZE];
+};
+
+static int sysfs_init(void)
+{
+       const char *env;
+       char sysfs_test[PATH_SIZE];
+
+       env = getenv("SYSFS_PATH");
+       if (env) {
+               strlcpy(sysfs_path, env, sizeof(sysfs_path));
+               remove_trailing_chars(sysfs_path, '/');
+       } else
+               strlcpy(sysfs_path, "/sys", sizeof(sysfs_path));
+       dbg("sysfs_path='%s'", sysfs_path);
+
+       strlcpy(sysfs_test, sysfs_path, sizeof(sysfs_test));
+       strlcat(sysfs_test, "/kernel/uevent_helper", sizeof(sysfs_test));
+       if (access(sysfs_test, F_OK)) {
+               error("sysfs path '%s' is invalid\n", sysfs_path);
+               return -errno;
+       }
+
+       INIT_LIST_HEAD(&attr_list);
+       return 0;
+}
+
+static void sysfs_cleanup(void)
+{
+       struct sysfs_attr *attr_loop;
+       struct sysfs_attr *attr_temp;
+
+       list_for_each_entry_safe(attr_loop, attr_temp, &attr_list, node) {
+               list_del(&attr_loop->node);
+               free(attr_loop);
+       }
+}
+
+static char *sysfs_attr_get_value(const char *devpath, const char *attr_name)
+{
+       char path_full[PATH_SIZE];
+       const char *path;
+       char value[NAME_SIZE];
+       struct sysfs_attr *attr_loop;
+       struct sysfs_attr *attr;
+       struct stat statbuf;
+       int fd;
+       ssize_t size;
+       size_t sysfs_len;
+
+       dbg("open '%s'/'%s'", devpath, attr_name);
+       sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
+       path = &path_full[sysfs_len];
+       strlcat(path_full, devpath, sizeof(path_full));
+       strlcat(path_full, "/", sizeof(path_full));
+       strlcat(path_full, attr_name, sizeof(path_full));
+
+       /* look for attribute in cache */
+       list_for_each_entry(attr_loop, &attr_list, node) {
+               if (strcmp(attr_loop->path, path) == 0) {
+                       dbg("found in cache '%s'", attr_loop->path);
+                       return attr_loop->value;
+               }
+       }
+
+       /* store attribute in cache (also negatives are kept in cache) */
+       dbg("new uncached attribute '%s'", path_full);
+       attr = malloc(sizeof(struct sysfs_attr));
+       if (attr == NULL)
+               return NULL;
+       memset(attr, 0x00, sizeof(struct sysfs_attr));
+       strlcpy(attr->path, path, sizeof(attr->path));
+       dbg("add to cache '%s'", path_full);
+       list_add(&attr->node, &attr_list);
+
+       if (lstat(path_full, &statbuf) != 0) {
+               dbg("stat '%s' failed: %s", path_full, strerror(errno));
+               goto out;
+       }
+
+       if (S_ISLNK(statbuf.st_mode)) {
+               /* links return the last element of the target path */
+               char link_target[PATH_SIZE];
+               int len;
+               const char *pos;
+
+               len = readlink(path_full, link_target, sizeof(link_target));
+               if (len > 0) {
+                       link_target[len] = '\0';
+                       pos = strrchr(link_target, '/');
+                       if (pos != NULL) {
+                               dbg("cache '%s' with link value '%s'", path_full, pos+1);
+                               strlcpy(attr->value_local, &pos[1], sizeof(attr->value_local));
+                               attr->value = attr->value_local;
+                       }
+               }
+               goto out;
+       }
+
+       /* skip directories */
+       if (S_ISDIR(statbuf.st_mode))
+               goto out;
+
+       /* skip non-readable files */
+       if ((statbuf.st_mode & S_IRUSR) == 0)
+               goto out;
+
+       /* read attribute value */
+       fd = open(path_full, O_RDONLY);
+       if (fd < 0) {
+               dbg("attribute '%s' does not exist", path_full);
+               goto out;
+       }
+       size = read(fd, value, sizeof(value));
+       close(fd);
+       if (size < 0)
+               goto out;
+       if (size == sizeof(value))
+               goto out;
+
+       /* got a valid value, store and return it */
+       value[size] = '\0';
+       remove_trailing_chars(value, '\n');
+       dbg("cache '%s' with attribute value '%s'", path_full, value);
+       strlcpy(attr->value_local, value, sizeof(attr->value_local));
+       attr->value = attr->value_local;
+
+out:
+       return attr->value;
+}
diff --git a/scenario/init_utils_run.c b/scenario/init_utils_run.c
new file mode 100644 (file)
index 0000000..dde490b
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     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 version 2 of the License.
+ * 
+ *     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.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#define MY_MAX(a,b) ((a) > (b) ? (a) : (b))
+
+#define READ_END       0
+#define WRITE_END      1
+
+static
+int run_program1(struct space *space,
+                const char *command0, char *result,
+                size_t ressize, size_t *reslen, int log);
+
+static
+int run_program0(struct space *space,
+                const char *command0, char *result,
+                size_t ressize, size_t *reslen, int log)
+{
+       int retval = 0;
+       int status;
+       int outpipe[2] = {-1, -1};
+       int errpipe[2] = {-1, -1};
+       pid_t pid;
+       char arg[PATH_SIZE];
+       char program[PATH_SIZE];
+       char *argv[(sizeof(arg) / 2) + 1];
+       int devnull;
+       int i;
+
+       /* build argv from comand */
+       strlcpy(arg, command0, sizeof(arg));
+       i = 0;
+       if (strchr(arg, ' ') != NULL) {
+               char *pos = arg;
+
+               while (pos != NULL) {
+                       if (pos[0] == '\'') {
+                               /* don't separate if in apostrophes */
+                               pos++;
+                               argv[i] = strsep(&pos, "\'");
+                               while (pos != NULL && pos[0] == ' ')
+                                       pos++;
+                       } else {
+                               argv[i] = strsep(&pos, " ");
+                       }
+                       dbg("arg[%i] '%s'", i, argv[i]);
+                       i++;
+               }
+               argv[i] = NULL;
+       } else {
+               argv[0] = arg;
+               argv[1] = NULL;
+       }
+       info("'%s'", command0);
+
+       /* prepare pipes from child to parent */
+       if (result || log) {
+               if (pipe(outpipe) != 0) {
+                       Perror(space, "pipe failed: %s", strerror(errno));
+                       return -1;
+               }
+       }
+       if (log) {
+               if (pipe(errpipe) != 0) {
+                       Perror(space, "pipe failed: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       /* allow programs in /lib/alsa called without the path */
+       if (strchr(argv[0], '/') == NULL) {
+               strlcpy(program, "/lib/alsa/", sizeof(program));
+               strlcat(program, argv[0], sizeof(program));
+               argv[0] = program;
+       }
+
+       pid = fork();
+       switch(pid) {
+       case 0:
+               /* child closes parent ends of pipes */
+               if (outpipe[READ_END] > 0)
+                       close(outpipe[READ_END]);
+               if (errpipe[READ_END] > 0)
+                       close(errpipe[READ_END]);
+
+               /* discard child output or connect to pipe */
+               devnull = open("/dev/null", O_RDWR);
+               if (devnull > 0) {
+                       dup2(devnull, STDIN_FILENO);
+                       if (outpipe[WRITE_END] < 0)
+                               dup2(devnull, STDOUT_FILENO);
+                       if (errpipe[WRITE_END] < 0)
+                               dup2(devnull, STDERR_FILENO);
+                       close(devnull);
+               } else
+                       Perror(space, "open /dev/null failed: %s", strerror(errno));
+               if (outpipe[WRITE_END] > 0) {
+                       dup2(outpipe[WRITE_END], STDOUT_FILENO);
+                       close(outpipe[WRITE_END]);
+               }
+               if (errpipe[WRITE_END] > 0) {
+                       dup2(errpipe[WRITE_END], STDERR_FILENO);
+                       close(errpipe[WRITE_END]);
+               }
+               execv(argv[0], argv);
+
+               /* we should never reach this */
+               Perror(space, "exec of program '%s' failed", argv[0]);
+               _exit(1);
+       case -1:
+               Perror(space, "fork of '%s' failed: %s", argv[0], strerror(errno));
+               return -1;
+       default:
+               /* read from child if requested */
+               if (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+                       ssize_t count;
+                       size_t respos = 0;
+
+                       /* parent closes child ends of pipes */
+                       if (outpipe[WRITE_END] > 0)
+                               close(outpipe[WRITE_END]);
+                       if (errpipe[WRITE_END] > 0)
+                               close(errpipe[WRITE_END]);
+
+                       /* read child output */
+                       while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+                               int fdcount;
+                               fd_set readfds;
+
+                               FD_ZERO(&readfds);
+                               if (outpipe[READ_END] > 0)
+                                       FD_SET(outpipe[READ_END], &readfds);
+                               if (errpipe[READ_END] > 0)
+                                       FD_SET(errpipe[READ_END], &readfds);
+                               fdcount = select(MY_MAX(outpipe[READ_END], errpipe[READ_END])+1, &readfds, NULL, NULL, NULL);
+                               if (fdcount < 0) {
+                                       if (errno == EINTR)
+                                               continue;
+                                       retval = -1;
+                                       break;
+                               }
+
+                               /* get stdout */
+                               if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) {
+                                       char inbuf[1024];
+                                       char *pos;
+                                       char *line;
+
+                                       count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1);
+                                       if (count <= 0) {
+                                               close(outpipe[READ_END]);
+                                               outpipe[READ_END] = -1;
+                                               if (count < 0) {
+                                                       Perror(space, "stdin read failed: %s", strerror(errno));
+                                                       retval = -1;
+                                               }
+                                               continue;
+                                       }
+                                       inbuf[count] = '\0';
+
+                                       /* store result for rule processing */
+                                       if (result) {
+                                               if (respos + count < ressize) {
+                                                       memcpy(&result[respos], inbuf, count);
+                                                       respos += count;
+                                               } else {
+                                                       Perror(space, "ressize %ld too short", (long)ressize);
+                                                       retval = -1;
+                                               }
+                                       }
+                                       pos = inbuf;
+                                       while ((line = strsep(&pos, "\n")))
+                                               if (pos || line[0] != '\0')
+                                                       info("'%s' (stdout) '%s'", argv[0], line);
+                               }
+
+                               /* get stderr */
+                               if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) {
+                                       char errbuf[1024];
+                                       char *pos;
+                                       char *line;
+
+                                       count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1);
+                                       if (count <= 0) {
+                                               close(errpipe[READ_END]);
+                                               errpipe[READ_END] = -1;
+                                               if (count < 0)
+                                                       Perror(space, "stderr read failed: %s", strerror(errno));
+                                               continue;
+                                       }
+                                       errbuf[count] = '\0';
+                                       pos = errbuf;
+                                       while ((line = strsep(&pos, "\n")))
+                                               if (pos || line[0] != '\0')
+                                                       info("'%s' (stderr) '%s'", argv[0], line);
+                               }
+                       }
+                       if (outpipe[READ_END] > 0)
+                               close(outpipe[READ_END]);
+                       if (errpipe[READ_END] > 0)
+                               close(errpipe[READ_END]);
+
+                       /* return the childs stdout string */
+                       if (result) {
+                               result[respos] = '\0';
+                               dbg("result='%s'", result);
+                               if (reslen)
+                                       *reslen = respos;
+                       }
+               }
+               waitpid(pid, &status, 0);
+               if (WIFEXITED(status)) {
+                       info("'%s' returned with status %i", argv[0], WEXITSTATUS(status));
+                       if (WEXITSTATUS(status) != 0)
+                               retval = -1;
+               } else {
+                       Perror(space, "'%s' abnormal exit", argv[0]);
+                       retval = -1;
+               }
+       }
+
+       return retval;
+}
+
+static
+int run_program(struct space *space, const char *command0, char *result,
+               size_t ressize, size_t *reslen, int log)
+{
+       if (command0[0] == '_' && command0[1] == '_')
+               return run_program1(space, command0, result, ressize, reslen, log);
+       return run_program0(space, command0, result, ressize, reslen, log);
+}
diff --git a/scenario/init_utils_string.c b/scenario/init_utils_string.c
new file mode 100644 (file)
index 0000000..01ea800
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     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 version 2 of the License.
+ * 
+ *     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.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+
+static void remove_trailing_chars(char *path, char c)
+{
+       size_t len;
+
+       len = strlen(path);
+       while (len > 0 && path[len-1] == c)
+               path[--len] = '\0';
+}
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+       unsigned char c = (unsigned char)str[0];
+
+       if (c < 0x80)
+               return 1;
+       if ((c & 0xe0) == 0xc0)
+               return 2;
+       if ((c & 0xf0) == 0xe0)
+               return 3;
+       if ((c & 0xf8) == 0xf0)
+               return 4;
+       if ((c & 0xfc) == 0xf8)
+               return 5;
+       if ((c & 0xfe) == 0xfc)
+               return 6;
+       return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+       int unichar;
+       int len;
+       int i;
+
+       len = utf8_encoded_expected_len(str);
+       switch (len) {
+       case 1:
+               return (int)str[0];
+       case 2:
+               unichar = str[0] & 0x1f;
+               break;
+       case 3:
+               unichar = (int)str[0] & 0x0f;
+               break;
+       case 4:
+               unichar = (int)str[0] & 0x07;
+               break;
+       case 5:
+               unichar = (int)str[0] & 0x03;
+               break;
+       case 6:
+               unichar = (int)str[0] & 0x01;
+               break;
+       default:
+               return -1;
+       }
+
+       for (i = 1; i < len; i++) {
+               if (((int)str[i] & 0xc0) != 0x80)
+                       return -1;
+               unichar <<= 6;
+               unichar |= (int)str[i] & 0x3f;
+       }
+
+       return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+       if (unichar < 0x80)
+               return 1;
+       if (unichar < 0x800)
+               return 2;
+       if (unichar < 0x10000)
+               return 3;
+       if (unichar < 0x200000)
+               return 4;
+       if (unichar < 0x4000000)
+               return 5;
+       return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+       if (unichar > 0x10ffff)
+               return 0;
+       if ((unichar & 0xfffff800) == 0xd800)
+               return 0;
+       if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+               return 0;
+       if ((unichar & 0xffff) == 0xffff)
+               return 0;
+       return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+       int len;
+       int unichar;
+       int i;
+
+       len = utf8_encoded_expected_len(str);
+       if (len == 0)
+               return -1;
+
+       /* ascii is valid */
+       if (len == 1)
+               return 1;
+
+       /* check if expected encoded chars are available */
+       for (i = 0; i < len; i++)
+               if ((str[i] & 0x80) != 0x80)
+                       return -1;
+
+       unichar = utf8_encoded_to_unichar(str);
+
+       /* check if encoded length matches encoded value */
+       if (utf8_unichar_to_encoded_len(unichar) != len)
+               return -1;
+
+       /* check if value has valid range */
+       if (!utf8_unichar_valid_range(unichar))
+               return -1;
+
+       return len;
+}
+
+/* replace everything but whitelisted plain ascii and valid utf8 */
+static int replace_untrusted_chars(char *str)
+{
+       size_t i = 0;
+       int replaced = 0;
+
+       while (str[i] != '\0') {
+               int len;
+
+               /* valid printable ascii char */
+               if ((str[i] >= '0' && str[i] <= '9') ||
+                   (str[i] >= 'A' && str[i] <= 'Z') ||
+                   (str[i] >= 'a' && str[i] <= 'z') ||
+                   strchr(" #$%+-./:=?@_,", str[i])) {
+                       i++;
+                       continue;
+               }
+               /* valid utf8 is accepted */
+               len = utf8_encoded_valid_unichar(&str[i]);
+               if (len > 1) {
+                       i += len;
+                       continue;
+               }
+
+               /* everything else is garbage */
+               str[i] = '_';
+               i++;
+               replaced++;
+       }
+
+       return replaced;
+}
diff --git a/scenario/list.h b/scenario/list.h
new file mode 100644 (file)
index 0000000..8626630
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copied from the Linux kernel source tree, version 2.6.0-test1.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+#ifndef _LIST_H
+#define _LIST_H
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:       the pointer to the member.
+ * @type:      the type of the container struct this is embedded in.
+ * @member:    the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({                     \
+       const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
+       (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+                             struct list_head *prev,
+                             struct list_head *next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       entry->next = LIST_POISON1;
+       entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       INIT_LIST_HEAD(entry); 
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+       __list_del(list->prev, list->next);
+       list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+                                 struct list_head *head)
+{
+       __list_del(list->prev, list->next);
+       list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(struct list_head *head)
+{
+       return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+                                struct list_head *head)
+{
+       struct list_head *first = list->next;
+       struct list_head *last = list->prev;
+       struct list_head *at = head->next;
+
+       first->prev = head;
+       head->next = first;
+
+       last->next = at;
+       at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+       if (!list_empty(list))
+               __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+                                   struct list_head *head)
+{
+       if (!list_empty(list)) {
+               __list_splice(list, head);
+               INIT_LIST_HEAD(list);
+       }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:       the &struct list_head pointer.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+       container_of(ptr, type, member)
+
+/**
+ * list_for_each       -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); \
+               pos = pos->next)
+
+/**
+ * __list_for_each     -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev  -       iterate over a list backwards
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+       for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe  -       iterate over a list safe against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry -       iterate over list of given type
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)                         \
+       for (pos = list_entry((head)->next, typeof(*pos), member);      \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)                 \
+       for (pos = list_entry((head)->prev, typeof(*pos), member);      \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)                 \
+       for (pos = list_entry((head)->next, typeof(*pos), member),      \
+               n = list_entry(pos->member.next, typeof(*pos), member); \
+            &pos->member != (head);                                    \
+            pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif /* _LIST_H */
diff --git a/scenario/state.c b/scenario/state.c
new file mode 100644 (file)
index 0000000..635a999
--- /dev/null
@@ -0,0 +1,1653 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *                   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.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "aconfig.h"
+#include "version.h"
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+#include "alsactl.h"
+
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0])
+
+
+static char *id_str(snd_ctl_elem_id_t *id)
+{
+       static char str[128];
+       assert(id);
+       sprintf(str, "%i,%i,%i,%s,%i", 
+               snd_ctl_elem_id_get_interface(id),
+               snd_ctl_elem_id_get_device(id),
+               snd_ctl_elem_id_get_subdevice(id),
+               snd_ctl_elem_id_get_name(id),
+               snd_ctl_elem_id_get_index(id));
+       return str;
+}
+
+static char *num_str(long n)
+{
+       static char str[32];
+       sprintf(str, "%ld", n);
+       return str;
+}
+
+static int snd_config_integer_add(snd_config_t *father, char *id, long integer)
+{
+       int err;
+       snd_config_t *leaf;
+       err = snd_config_make_integer(&leaf, id);
+       if (err < 0)
+               return err;
+       err = snd_config_add(father, leaf);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       err = snd_config_set_integer(leaf, integer);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       return 0;
+}
+
+static int snd_config_integer64_add(snd_config_t *father, char *id, long long integer)
+{
+       int err;
+       snd_config_t *leaf;
+       err = snd_config_make_integer64(&leaf, id);
+       if (err < 0)
+               return err;
+       err = snd_config_add(father, leaf);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       err = snd_config_set_integer64(leaf, integer);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       return 0;
+}
+
+static int snd_config_string_add(snd_config_t *father, const char *id, const char *string)
+{
+       int err;
+       snd_config_t *leaf;
+       err = snd_config_make_string(&leaf, id);
+       if (err < 0)
+               return err;
+       err = snd_config_add(father, leaf);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       err = snd_config_set_string(leaf, string);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       return 0;
+}
+
+static int snd_config_compound_add(snd_config_t *father, const char *id, int join,
+                                  snd_config_t **node)
+{
+       int err;
+       snd_config_t *leaf;
+       err = snd_config_make_compound(&leaf, id, join);
+       if (err < 0)
+               return err;
+       err = snd_config_add(father, leaf);
+       if (err < 0) {
+               snd_config_delete(leaf);
+               return err;
+       }
+       *node = leaf;
+       return 0;
+}
+
+#define MAX_USER_TLV_SIZE      64
+
+static char *tlv_to_str(unsigned int *tlv)
+{
+       int i, len = tlv[1] / 4 + 2;
+       char *s, *p;
+
+       if (len >= MAX_USER_TLV_SIZE)
+               return NULL;
+       s = malloc(len * 8 + 1);
+       if (! s)
+               return NULL;
+       p = s;
+       for (i = 0; i < len; i++) {
+               sprintf(p, "%08x", tlv[i]);
+               p += 8;
+       }
+       return s;
+}
+
+static unsigned int *str_to_tlv(const char *s)
+{
+       int i, j, c, len;
+       unsigned int *tlv;
+                       
+       len = strlen(s);
+       if (len % 8) /* aligned to 4 bytes (= 8 letters) */
+               return NULL;
+       len /= 8;
+       if (len > MAX_USER_TLV_SIZE)
+               return NULL;
+       tlv = malloc(sizeof(int) * len);
+       if (! tlv)
+               return NULL;
+       for (i = 0; i < len; i++) {
+               tlv[i] = 0;
+               for (j = 0; j < 8; j++) {
+                       if ((c = hextodigit(*s++)) < 0) {
+                               free(tlv);
+                               return NULL;
+                       }
+                       tlv[i] = (tlv[i] << 4) | c;
+               }
+       }
+       return tlv;
+}
+
+/*
+ * add the TLV string and dB ranges to comment fields
+ */
+static int add_tlv_comments(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
+                           snd_ctl_elem_info_t *info, snd_config_t *comment)
+{
+       unsigned int tlv[MAX_USER_TLV_SIZE];
+       unsigned int *db;
+       long dbmin, dbmax;
+       int err;
+
+       if (snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)) < 0)
+               return 0; /* ignore error */
+
+       if (snd_ctl_elem_info_is_tlv_writable(info)) {
+               char *s = tlv_to_str(tlv);
+               if (s) {
+                       err = snd_config_string_add(comment, "tlv", s);
+                       if (err < 0) {
+                               error("snd_config_string_add: %s", snd_strerror(err));
+                               return err;
+                       }
+                       free(s);
+               }
+       }
+
+       err = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &db);
+       if (err <= 0)
+               return 0;
+
+       snd_tlv_get_dB_range(db, snd_ctl_elem_info_get_min(info),
+                            snd_ctl_elem_info_get_max(info),
+                            &dbmin, &dbmax);
+       if (err < 0)
+               return err;
+       snd_config_integer_add(comment, "dbmin", dbmin);
+       snd_config_integer_add(comment, "dbmax", dbmax);
+       return 0;
+}
+
+static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *top)
+{
+       snd_ctl_elem_value_t *ctl;
+       snd_ctl_elem_info_t *info;
+       snd_config_t *control, *comment, *item, *value;
+       const char *s;
+       char buf[256];
+       unsigned int idx;
+       int err;
+       unsigned int device, subdevice, index;
+       const char *name;
+       snd_ctl_elem_type_t type;
+       unsigned int count;
+       snd_ctl_elem_value_alloca(&ctl);
+       snd_ctl_elem_info_alloca(&info);
+       snd_ctl_elem_info_set_id(info, id);
+       err = snd_ctl_elem_info(handle, info);
+       if (err < 0) {
+               error("Cannot read control info '%s': %s", id_str(id), snd_strerror(err));
+               return err;
+       }
+
+       if (snd_ctl_elem_info_is_inactive(info) ||
+                               !snd_ctl_elem_info_is_readable(info))
+               return 0;
+       snd_ctl_elem_value_set_id(ctl, id);
+       err = snd_ctl_elem_read(handle, ctl);
+       if (err < 0) {
+               error("Cannot read control '%s': %s", id_str(id), snd_strerror(err));
+               return err;
+       }
+
+       err = snd_config_compound_add(top, num_str(snd_ctl_elem_info_get_numid(info)), 0, &control);
+       if (err < 0) {
+               error("snd_config_compound_add: %s", snd_strerror(err));
+               return err;
+       }
+       err = snd_config_compound_add(control, "comment", 1, &comment);
+       if (err < 0) {
+               error("snd_config_compound_add: %s", snd_strerror(err));
+               return err;
+       }
+
+       buf[0] = '\0';
+       buf[1] = '\0';
+       if (snd_ctl_elem_info_is_readable(info))
+               strcat(buf, " read");
+       if (snd_ctl_elem_info_is_writable(info))
+               strcat(buf, " write");
+       if (snd_ctl_elem_info_is_inactive(info))
+               strcat(buf, " inactive");
+       if (snd_ctl_elem_info_is_volatile(info))
+               strcat(buf, " volatile");
+       if (snd_ctl_elem_info_is_locked(info))
+               strcat(buf, " locked");
+       if (snd_ctl_elem_info_is_user(info))
+               strcat(buf, " user");
+       err = snd_config_string_add(comment, "access", buf + 1);
+       if (err < 0) {
+               error("snd_config_string_add: %s", snd_strerror(err));
+               return err;
+       }
+
+       type = snd_ctl_elem_info_get_type(info);
+       device = snd_ctl_elem_info_get_device(info);
+       subdevice = snd_ctl_elem_info_get_subdevice(info);
+       index = snd_ctl_elem_info_get_index(info);
+       name = snd_ctl_elem_info_get_name(info);
+       count = snd_ctl_elem_info_get_count(info);
+       s = snd_ctl_elem_type_name(type);
+       err = snd_config_string_add(comment, "type", s);
+       if (err < 0) {
+               error("snd_config_string_add: %s", snd_strerror(err));
+               return err;
+       }
+       err = snd_config_integer_add(comment, "count", count);
+       if (err < 0) {
+               error("snd_config_integer_add: %s", snd_strerror(err));
+               return err;
+       }
+
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+       {
+               long min = snd_ctl_elem_info_get_min(info);
+               long max = snd_ctl_elem_info_get_max(info);
+               long step = snd_ctl_elem_info_get_step(info);
+               if (step)
+                       sprintf(buf, "%li - %li (step %li)", min, max, step);
+               else
+                       sprintf(buf, "%li - %li", min, max);
+               err = snd_config_string_add(comment, "range", buf);
+               if (err < 0) {
+                       error("snd_config_string_add: %s", snd_strerror(err));
+                       return err;
+               }
+               if (snd_ctl_elem_info_is_tlv_readable(info)) {
+                       err = add_tlv_comments(handle, id, info, comment);
+                       if (err < 0)
+                               return err;
+               }
+               break;
+       }
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+       {
+               long long min = snd_ctl_elem_info_get_min64(info);
+               long long max = snd_ctl_elem_info_get_max64(info);
+               long long step = snd_ctl_elem_info_get_step64(info);
+               if (step)
+                       sprintf(buf, "%Li - %Li (step %Li)", min, max, step);
+               else
+                       sprintf(buf, "%Li - %Li", min, max);
+               err = snd_config_string_add(comment, "range", buf);
+               if (err < 0) {
+                       error("snd_config_string_add: %s", snd_strerror(err));
+                       return err;
+               }
+               break;
+       }
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+       {
+               unsigned int items;
+               err = snd_config_compound_add(comment, "item", 1, &item);
+               if (err < 0) {
+                       error("snd_config_compound_add: %s", snd_strerror(err));
+                       return err;
+               }
+               items = snd_ctl_elem_info_get_items(info);
+               for (idx = 0; idx < items; idx++) {
+                       snd_ctl_elem_info_set_item(info, idx);
+                       err = snd_ctl_elem_info(handle, info);
+                       if (err < 0) {
+                               error("snd_ctl_card_info: %s", snd_strerror(err));
+                               return err;
+                       }
+                       err = snd_config_string_add(item, num_str(idx), snd_ctl_elem_info_get_item_name(info));
+                       if (err < 0) {
+                               error("snd_config_string_add: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               break;
+       }
+       default:
+               break;
+       }
+       s = snd_ctl_elem_iface_name(snd_ctl_elem_info_get_interface(info));
+       err = snd_config_string_add(control, "iface", s);
+       if (err < 0) {
+               error("snd_config_string_add: %s", snd_strerror(err));
+               return err;
+       }
+       if (device != 0) {
+               err = snd_config_integer_add(control, "device", device);
+               if (err < 0) {
+                       error("snd_config_integer_add: %s", snd_strerror(err));
+                       return err;
+               }
+       }
+       if (subdevice != 0) {
+               err = snd_config_integer_add(control, "subdevice", subdevice);
+               if (err < 0) {
+                       error("snd_config_integer_add: %s", snd_strerror(err));
+                       return err;
+               }
+       }
+       err = snd_config_string_add(control, "name", name);
+       if (err < 0) {
+               error("snd_config_string_add: %s", snd_strerror(err));
+               return err;
+       }
+       if (index != 0) {
+               err = snd_config_integer_add(control, "index", index);
+               if (err < 0) {
+                       error("snd_config_integer_add: %s", snd_strerror(err));
+                       return err;
+               }
+       }
+
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+       {
+               size_t size = type == SND_CTL_ELEM_TYPE_BYTES ?
+                       count : sizeof(snd_aes_iec958_t);
+               char buf[size * 2 + 1];
+               char *p = buf;
+               char *hex = "0123456789abcdef";
+               const unsigned char *bytes = 
+                 (const unsigned char *)snd_ctl_elem_value_get_bytes(ctl);
+               for (idx = 0; idx < size; idx++) {
+                       int v = bytes[idx];
+                       *p++ = hex[v >> 4];
+                       *p++ = hex[v & 0x0f];
+               }
+               *p = '\0';
+               err = snd_config_string_add(control, "value", buf);
+               if (err < 0) {
+                       error("snd_config_string_add: %s", snd_strerror(err));
+                       return err;
+               }
+               return 0;
+       }
+       default:
+               break;
+       }
+
+       if (count == 1) {
+               switch (type) {
+               case SND_CTL_ELEM_TYPE_BOOLEAN:
+                       err = snd_config_string_add(control, "value", snd_ctl_elem_value_get_boolean(ctl, 0) ? "true" : "false");
+                       if (err < 0) {
+                               error("snd_config_string_add: %s", snd_strerror(err));
+                               return err;
+                       }
+                       return 0;
+               case SND_CTL_ELEM_TYPE_INTEGER:
+                       err = snd_config_integer_add(control, "value", snd_ctl_elem_value_get_integer(ctl, 0));
+                       if (err < 0) {
+                               error("snd_config_integer_add: %s", snd_strerror(err));
+                               return err;
+                       }
+                       return 0;
+               case SND_CTL_ELEM_TYPE_INTEGER64:
+                       err = snd_config_integer64_add(control, "value", snd_ctl_elem_value_get_integer64(ctl, 0));
+                       if (err < 0) {
+                               error("snd_config_integer64_add: %s", snd_strerror(err));
+                               return err;
+                       }
+                       return 0;
+               case SND_CTL_ELEM_TYPE_ENUMERATED:
+               {
+                       unsigned int v = snd_ctl_elem_value_get_enumerated(ctl, 0);
+                       snd_config_t *c;
+                       err = snd_config_search(item, num_str(v), &c);
+                       if (err == 0) {
+                               err = snd_config_get_string(c, &s);
+                               assert(err == 0);
+                               err = snd_config_string_add(control, "value", s);
+                       } else {
+                               err = snd_config_integer_add(control, "value", v);
+                       }
+                       if (err < 0)
+                               error("snd_config add: %s", snd_strerror(err));
+                       return 0;
+               }
+               default:
+                       error("Unknown control type: %d\n", type);
+                       return -EINVAL;
+               }
+       }
+
+       err = snd_config_compound_add(control, "value", 1, &value);
+       if (err < 0) {
+               error("snd_config_compound_add: %s", snd_strerror(err));
+               return err;
+       }
+
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               for (idx = 0; idx < count; idx++) {
+                       err = snd_config_string_add(value, num_str(idx), snd_ctl_elem_value_get_boolean(ctl, idx) ? "true" : "false");
+                       if (err < 0) {
+                               error("snd_config_string_add: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               for (idx = 0; idx < count; idx++) {
+                       err = snd_config_integer_add(value, num_str(idx), snd_ctl_elem_value_get_integer(ctl, idx));
+                       if (err < 0) {
+                               error("snd_config_integer_add: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+               for (idx = 0; idx < count; idx++) {
+                       err = snd_config_integer64_add(value, num_str(idx), snd_ctl_elem_value_get_integer64(ctl, idx));
+                       if (err < 0) {
+                               error("snd_config_integer64_add: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+               for (idx = 0; idx < count; idx++) {
+                       unsigned int v = snd_ctl_elem_value_get_enumerated(ctl, idx);
+                       snd_config_t *c;
+                       err = snd_config_search(item, num_str(v), &c);
+                       if (err == 0) {
+                               err = snd_config_get_string(c, &s);
+                               assert(err == 0);
+                               err = snd_config_string_add(value, num_str(idx), s);
+                       } else {
+                               err = snd_config_integer_add(value, num_str(idx), v);
+                       }
+                       if (err < 0) {
+                               error("snd_config add: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               break;
+       default:
+               error("Unknown control type: %d\n", type);
+               return -EINVAL;
+       }
+       
+       return 0;
+}
+       
+static int get_controls(int cardno, snd_config_t *top)
+{
+       snd_ctl_t *handle;
+       snd_ctl_card_info_t *info;
+       snd_config_t *state, *card, *control;
+       snd_ctl_elem_list_t *list;
+       unsigned int idx;
+       int err;
+       char name[32];
+       unsigned int count;
+       const char *id;
+       snd_ctl_card_info_alloca(&info);
+       snd_ctl_elem_list_alloca(&list);
+
+       sprintf(name, "hw:%d", cardno);
+       err = snd_ctl_open(&handle, name, SND_CTL_READONLY);
+       if (err < 0) {
+               error("snd_ctl_open error: %s", snd_strerror(err));
+               return err;
+       }
+       err = snd_ctl_card_info(handle, info);
+       if (err < 0) {
+               error("snd_ctl_card_info error: %s", snd_strerror(err));
+               goto _close;
+       }
+       id = snd_ctl_card_info_get_id(info);
+       err = snd_config_search(top, "state", &state);
+       if (err == 0 &&
+           snd_config_get_type(state) != SND_CONFIG_TYPE_COMPOUND) {
+               error("config state node is not a compound");
+               err = -EINVAL;
+               goto _close;
+       }
+       if (err < 0) {
+               err = snd_config_compound_add(top, "state", 1, &state);
+               if (err < 0) {
+                       error("snd_config_compound_add: %s", snd_strerror(err));
+                       goto _close;
+               }
+       }
+       err = snd_config_search(state, id, &card);
+       if (err == 0 &&
+           snd_config_get_type(card) != SND_CONFIG_TYPE_COMPOUND) {
+               error("config state.%s node is not a compound", id);
+               err = -EINVAL;
+               goto _close;
+       }
+       if (err < 0) {
+               err = snd_config_compound_add(state, id, 0, &card);
+               if (err < 0) {
+                       error("snd_config_compound_add: %s", snd_strerror(err));
+                       goto _close;
+               }
+       }
+       err = snd_config_search(card, "control", &control);
+       if (err == 0) {
+               err = snd_config_delete(control);
+               if (err < 0) {
+                       error("snd_config_delete: %s", snd_strerror(err));
+                       goto _close;
+               }
+       }
+       err = snd_ctl_elem_list(handle, list);
+       if (err < 0) {
+               error("Cannot determine controls: %s", snd_strerror(err));
+               goto _close;
+       }
+       count = snd_ctl_elem_list_get_count(list);
+       err = snd_config_compound_add(card, "control", count > 0, &control);
+       if (err < 0) {
+               error("snd_config_compound_add: %s", snd_strerror(err));
+               goto _close;
+       }
+       if (count == 0) {
+               err = 0;
+               goto _close;
+       }
+       snd_ctl_elem_list_set_offset(list, 0);
+       if (snd_ctl_elem_list_alloc_space(list, count) < 0) {
+               error("No enough memory...");
+               goto _close;
+       }
+       if ((err = snd_ctl_elem_list(handle, list)) < 0) {
+               error("Cannot determine controls (2): %s", snd_strerror(err));
+               goto _free;
+       }
+       for (idx = 0; idx < count; ++idx) {
+               snd_ctl_elem_id_t *id;
+               snd_ctl_elem_id_alloca(&id);
+               snd_ctl_elem_list_get_id(list, idx, id);
+               err = get_control(handle, id, control);
+               if (err < 0)
+                       goto _free;
+       }               
+               
+       err = 0;
+ _free:
+       snd_ctl_elem_list_free_space(list);
+ _close:
+       snd_ctl_close(handle);
+       return err;
+}
+
+static long config_iface(snd_config_t *n)
+{
+       long i;
+       long long li;
+       snd_ctl_elem_iface_t idx; 
+       const char *str;
+       switch (snd_config_get_type(n)) {
+       case SND_CONFIG_TYPE_INTEGER:
+               snd_config_get_integer(n, &i);
+               return i;
+       case SND_CONFIG_TYPE_INTEGER64:
+               snd_config_get_integer64(n, &li);
+               return li;
+       case SND_CONFIG_TYPE_STRING:
+               snd_config_get_string(n, &str);
+               break;
+       default:
+               return -1;
+       }
+       for (idx = 0; idx <= SND_CTL_ELEM_IFACE_LAST; idx++) {
+               if (strcasecmp(snd_ctl_elem_iface_name(idx), str) == 0)
+                       return idx;
+       }
+       return -1;
+}
+
+static int config_bool(snd_config_t *n, int doit)
+{
+       const char *str;
+       long val;
+       long long lval;
+
+       switch (snd_config_get_type(n)) {
+       case SND_CONFIG_TYPE_INTEGER:
+               snd_config_get_integer(n, &val);
+               if (val < 0 || val > 1)
+                       return -1;
+               return val;
+       case SND_CONFIG_TYPE_INTEGER64:
+               snd_config_get_integer64(n, &lval);
+               if (lval < 0 || lval > 1)
+                       return -1;
+               return (int) lval;
+       case SND_CONFIG_TYPE_STRING:
+               snd_config_get_string(n, &str);
+               break;
+       case SND_CONFIG_TYPE_COMPOUND:
+               if (!force_restore || !doit)
+                       return -1;
+               n = snd_config_iterator_entry(snd_config_iterator_first(n));
+               return config_bool(n, doit);
+       default:
+               return -1;
+       }
+       if (strcmp(str, "on") == 0 || strcmp(str, "true") == 0)
+               return 1;
+       if (strcmp(str, "off") == 0 || strcmp(str, "false") == 0)
+               return 0;
+       return -1;
+}
+
+static int config_enumerated(snd_config_t *n, snd_ctl_t *handle,
+                            snd_ctl_elem_info_t *info, int doit)
+{
+       const char *str;
+       long val;
+       long long lval;
+       unsigned int idx, items;
+
+       switch (snd_config_get_type(n)) {
+       case SND_CONFIG_TYPE_INTEGER:
+               snd_config_get_integer(n, &val);
+               return val;
+       case SND_CONFIG_TYPE_INTEGER64:
+               snd_config_get_integer64(n, &lval);
+               return (int) lval;
+       case SND_CONFIG_TYPE_STRING:
+               snd_config_get_string(n, &str);
+               break;
+       case SND_CONFIG_TYPE_COMPOUND:
+               if (!force_restore || !doit)
+                       return -1;
+               n = snd_config_iterator_entry(snd_config_iterator_first(n));
+               return config_enumerated(n, handle, info, doit);
+       default:
+               return -1;
+       }
+       items = snd_ctl_elem_info_get_items(info);
+       for (idx = 0; idx < items; idx++) {
+               int err;
+               snd_ctl_elem_info_set_item(info, idx);
+               err = snd_ctl_elem_info(handle, info);
+               if (err < 0) {
+                       error("snd_ctl_elem_info: %s", snd_strerror(err));
+                       return err;
+               }
+               if (strcmp(str, snd_ctl_elem_info_get_item_name(info)) == 0)
+                       return idx;
+       }
+       return -1;
+}
+
+static int config_integer(snd_config_t *n, long *val, int doit)
+{
+       int err = snd_config_get_integer(n, val);
+       if (err < 0 && force_restore && doit) {
+               if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND)
+                       return err;
+               n = snd_config_iterator_entry(snd_config_iterator_first(n));
+               return config_integer(n, val, doit);
+       }
+       return err;
+}
+
+static int config_integer64(snd_config_t *n, long long *val, int doit)
+{
+       int err = snd_config_get_integer64(n, val);
+       if (err < 0 && force_restore && doit) {
+               if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND)
+                       return err;
+               n = snd_config_iterator_entry(snd_config_iterator_first(n));
+               return config_integer64(n, val, doit);
+       }
+       return err;
+}
+
+static int is_user_control(snd_config_t *conf)
+{
+       snd_config_iterator_t i, next;
+
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *id, *s;
+               if (snd_config_get_id(n, &id) < 0)
+                       continue;
+               if (strcmp(id, "access") == 0) {
+                       if (snd_config_get_string(n, &s) < 0)
+                               return 0;
+                       if (strstr(s, "user"))
+                               return 1;
+               }
+       }
+       return 0;
+}
+
+/*
+ * get the item type from the given comment config
+ */
+static int get_comment_type(snd_config_t *n)
+{
+       static const snd_ctl_elem_type_t types[] = {
+               SND_CTL_ELEM_TYPE_BOOLEAN,
+               SND_CTL_ELEM_TYPE_INTEGER,
+               SND_CTL_ELEM_TYPE_ENUMERATED,
+               SND_CTL_ELEM_TYPE_BYTES,
+               SND_CTL_ELEM_TYPE_IEC958,
+               SND_CTL_ELEM_TYPE_INTEGER64,
+       };
+       const char *type;
+       unsigned int i;
+
+       if (snd_config_get_string(n, &type) < 0)
+               return -EINVAL;
+       for (i = 0; i < ARRAY_SIZE(types); ++i)
+               if (strcmp(type, snd_ctl_elem_type_name(types[i])) == 0)
+                       return types[i];
+       return -EINVAL;
+}
+
+/*
+ * get the value range from the given comment config
+ */
+static int get_comment_range(snd_config_t *n, int ctype,
+                            long *imin, long *imax, long *istep)
+{
+       const char *s;
+       int err;
+
+       if (snd_config_get_string(n, &s) < 0)
+               return -EINVAL;
+       switch (ctype) {
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               err = sscanf(s, "%li - %li (step %li)", imin, imax, istep);
+               if (err != 3) {
+                       istep = 0;
+                       err = sscanf(s, "%li - %li", imin, imax);
+                       if (err != 2)
+                               return -EINVAL;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_config_t *conf)
+{
+       snd_ctl_elem_id_t *id;
+       snd_config_iterator_t i, next;
+       long imin, imax, istep;
+       snd_ctl_elem_type_t ctype;
+       unsigned int count;
+       int err;
+       unsigned int *tlv;
+
+       imin = imax = istep = 0;
+       count = 0;
+       ctype = SND_CTL_ELEM_TYPE_NONE;
+       tlv = NULL;
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *id;
+               if (snd_config_get_id(n, &id) < 0)
+                       continue;
+               if (strcmp(id, "type") == 0) {
+                       err = get_comment_type(n);
+                       if (err < 0)
+                               return err;
+                       ctype = err;
+                       continue;
+               }
+               if (strcmp(id, "range") == 0) {
+                       err = get_comment_range(n, ctype, &imin, &imax, &istep);
+                       if (err < 0)
+                               return err;
+                       continue;
+               }
+               if (strcmp(id, "count") == 0) {
+                       long v;
+                       if ((err = snd_config_get_integer(n, &v)) < 0)
+                               return err;
+                       count = v;
+                       continue;
+               }
+               if (strcmp(id, "tlv") == 0) {
+                       const char *s;
+                       if ((err = snd_config_get_string(n, &s)) < 0)
+                               return -EINVAL;
+                       if (tlv)
+                               free(tlv);
+                       if ((tlv = str_to_tlv(s)) == NULL)
+                               return -EINVAL;
+                       continue;
+               }
+       }
+
+       snd_ctl_elem_id_alloca(&id);
+       snd_ctl_elem_info_get_id(info, id);
+       if (count <= 0)
+               count = 1;
+       switch (ctype) {
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               if (imin > imax || istep > imax - imin)
+                       return -EINVAL;
+               err = snd_ctl_elem_add_integer(handle, id, count, imin, imax, istep);
+               if (err < 0)
+                       goto error;
+               if (tlv)
+                       snd_ctl_elem_tlv_write(handle, id, tlv);
+               break;
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               err = snd_ctl_elem_add_boolean(handle, id, count);
+               break;
+       case SND_CTL_ELEM_TYPE_IEC958:
+               err = snd_ctl_elem_add_iec958(handle, id);
+               break;
+       default:
+               err = -EINVAL;
+               break;
+       }
+
+ error:
+       free(tlv);
+       if (err < 0)
+               return err;
+       return snd_ctl_elem_info(handle, info);
+}
+
+/*
+ * look for a config node with the given item name
+ */
+static snd_config_t *search_comment_item(snd_config_t *conf, const char *name)
+{
+       snd_config_iterator_t i, next;
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *id;
+               if (snd_config_get_id(n, &id) < 0)
+                       continue;
+               if (strcmp(id, name) == 0)
+                       return n;
+       }
+       return NULL;
+}
+
+/*
+ * check whether the config item has the same of compatible type
+ */
+static int check_comment_type(snd_config_t *conf, int type)
+{
+       snd_config_t *n = search_comment_item(conf, "type");
+       int ctype;
+
+       if (!n)
+               return 0; /* not defined */
+       ctype = get_comment_type(n);
+       if (ctype == type)
+               return 0;
+       if ((ctype == SND_CTL_ELEM_TYPE_BOOLEAN ||
+            ctype == SND_CTL_ELEM_TYPE_INTEGER ||
+            ctype == SND_CTL_ELEM_TYPE_INTEGER64 ||
+            ctype == SND_CTL_ELEM_TYPE_ENUMERATED) &&
+           (type == SND_CTL_ELEM_TYPE_BOOLEAN ||
+            type == SND_CTL_ELEM_TYPE_INTEGER ||
+            type == SND_CTL_ELEM_TYPE_INTEGER64 ||
+            type == SND_CTL_ELEM_TYPE_ENUMERATED))
+               return 0; /* OK, compatible */
+       return -EINVAL;
+}
+
+/*
+ * convert from an old value to a new value with the same dB level
+ */
+static int convert_to_new_db(snd_config_t *value, long omin, long omax,
+                            long nmin, long nmax,
+                            long odbmin, long odbmax,
+                            long ndbmin, long ndbmax,
+                            int doit)
+{
+       long val;
+       if (config_integer(value, &val, doit) < 0)
+               return -EINVAL;
+       if (val < omin || val > omax)
+               return -EINVAL;
+       val = ((val - omin) * (odbmax - odbmin)) / (omax - omin) + odbmin;
+       if (val < ndbmin)
+               val = ndbmin;
+       else if (val > ndbmax)
+               val = ndbmax;
+       val = ((val - ndbmin) * (nmax - nmin)) / (ndbmax - ndbmin) + nmin;
+       return snd_config_set_integer(value, val);
+}
+
+/*
+ * compare the current value range with the old range in comments.
+ * also, if dB information is available, try to compare them.
+ * if any change occurs, try to keep the same dB level.
+ */
+static int check_comment_range(snd_ctl_t *handle, snd_config_t *conf,
+                              snd_ctl_elem_info_t *info, snd_config_t *value,
+                              int doit)
+{
+       snd_config_t *n;
+       long omin, omax, ostep;
+       long nmin, nmax;
+       long odbmin, odbmax;
+       long ndbmin, ndbmax;
+       snd_ctl_elem_id_t *id;
+
+       n = search_comment_item(conf, "range");
+       if (!n)
+               return 0;
+       if (get_comment_range(n, SND_CTL_ELEM_TYPE_INTEGER,
+                             &omin, &omax, &ostep) < 0)
+               return 0;
+       nmin = snd_ctl_elem_info_get_min(info);
+       nmax = snd_ctl_elem_info_get_max(info);
+       if (omin != nmin && omax != nmax) {
+               /* Hey, the range mismatches */
+               if (!force_restore || !doit)
+                       return -EINVAL;
+       }
+       if (omin >= omax || nmin >= nmax)
+               return 0; /* invalid values */
+
+       n = search_comment_item(conf, "dbmin");
+       if (!n)
+               return 0;
+       if (config_integer(n, &odbmin, doit) < 0)
+               return 0;
+       n = search_comment_item(conf, "dbmax");
+       if (!n)
+               return 0;
+       if (config_integer(n, &odbmax, doit) < 0)
+               return 0;
+       if (odbmin >= odbmax)
+               return 0; /* invalid values */
+       snd_ctl_elem_id_alloca(&id);
+       snd_ctl_elem_info_get_id(info, id);
+       if (snd_ctl_get_dB_range(handle, id, &ndbmin, &ndbmax) < 0)
+               return 0;
+       if (ndbmin >= ndbmax)
+               return 0; /* invalid values */
+       if (omin == nmin && omax == nmax &&
+           odbmin == ndbmin && odbmax == ndbmax)
+               return 0; /* OK, identical one */
+
+       /* Let's guess the current value from dB range */
+       if (snd_config_get_type(value) == SND_CONFIG_TYPE_COMPOUND) {
+               snd_config_iterator_t i, next;
+               snd_config_for_each(i, next, value) {
+                       snd_config_t *n = snd_config_iterator_entry(i);
+                       convert_to_new_db(n, omin, omax, nmin, nmax,
+                                         odbmin, odbmax, ndbmin, ndbmax, doit);
+               }
+       } else
+               convert_to_new_db(value, omin, omax, nmin, nmax,
+                                 odbmin, odbmax, ndbmin, ndbmax, doit);
+       return 0;
+}
+
+static int restore_config_value(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
+                               snd_ctl_elem_iface_t type,
+                               snd_config_t *value,
+                               snd_ctl_elem_value_t *ctl, int idx,
+                               int doit)
+{
+       long val;
+       long long lval;
+       int err;
+
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               val = config_bool(value, doit);
+               if (val >= 0) {
+                       snd_ctl_elem_value_set_boolean(ctl, idx, val);
+                       return 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               err = config_integer(value, &val, doit);
+               if (err == 0) {
+                       snd_ctl_elem_value_set_integer(ctl, idx, val);
+                       return 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+               err = config_integer64(value, &lval, doit);
+               if (err == 0) {
+                       snd_ctl_elem_value_set_integer64(ctl, idx, lval);
+                       return 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+               val = config_enumerated(value, handle, info, doit);
+               if (val >= 0) {
+                       snd_ctl_elem_value_set_enumerated(ctl, idx, val);
+                       return 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+               break;
+       default:
+               cerror(doit, "Unknow control type: %d", type);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int restore_config_value2(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
+                                snd_ctl_elem_iface_t type,
+                                snd_config_t *value,
+                                snd_ctl_elem_value_t *ctl, int idx,
+                                unsigned int numid, int doit)
+{
+       int err = restore_config_value(handle, info, type, value, ctl, idx, doit);
+       long val;
+
+       if (err != 0)
+               return err;
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+               err = snd_config_get_integer(value, &val);
+               if (err < 0 || val < 0 || val > 255) {
+                       cerror(doit, "bad control.%d.value.%d content", numid, idx);
+                       return force_restore && doit ? 0 : -EINVAL;
+               }
+               snd_ctl_elem_value_set_byte(ctl, idx, val);
+               return 1;
+               break;
+       default:
+               break;
+       }
+       return 0;
+}
+
+static int set_control(snd_ctl_t *handle, snd_config_t *control,
+                      int *maxnumid, int doit)
+{
+       snd_ctl_elem_value_t *ctl;
+       snd_ctl_elem_info_t *info;
+       snd_config_iterator_t i, next;
+       unsigned int numid1;
+       snd_ctl_elem_iface_t iface = -1;
+       int iface1;
+       const char *name1;
+       unsigned int numid;
+       snd_ctl_elem_type_t type;
+       unsigned int count;
+       long device = -1;
+       long device1;
+       long subdevice = -1;
+       long subdevice1;
+       const char *name = NULL;
+       long index1;
+       long index = -1;
+       snd_config_t *value = NULL;
+       snd_config_t *comment = NULL;
+       unsigned int idx;
+       int err;
+       char *set;
+       const char *id;
+       snd_ctl_elem_value_alloca(&ctl);
+       snd_ctl_elem_info_alloca(&info);
+       if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) {
+               cerror(doit, "control is not a compound");
+               return -EINVAL;
+       }
+       err = snd_config_get_id(control, &id);
+       if (err < 0) {
+               cerror(doit, "unable to get id");
+               return -EINVAL;
+       }
+       numid = atoi(id);
+       if ((int)numid > *maxnumid)
+               *maxnumid = numid;
+       snd_config_for_each(i, next, control) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *fld;
+               if (snd_config_get_id(n, &fld) < 0)
+                       continue;
+               if (strcmp(fld, "comment") == 0) {
+                       if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+                               cerror(doit, "control.%d.%s is invalid", numid, fld);
+                               return -EINVAL;
+                       }
+                       comment = n;
+                       continue;
+               }
+               if (strcmp(fld, "iface") == 0) {
+                       iface = (snd_ctl_elem_iface_t)config_iface(n);
+                       continue;
+               }
+               if (strcmp(fld, "device") == 0) {
+                       if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+                               cerror(doit, "control.%d.%s is invalid", numid, fld);
+                               return -EINVAL;
+                       }
+                       snd_config_get_integer(n, &device);
+                       continue;
+               }
+               if (strcmp(fld, "subdevice") == 0) {
+                       if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+                               cerror(doit, "control.%d.%s is invalid", numid, fld);
+                               return -EINVAL;
+                       }
+                       snd_config_get_integer(n, &subdevice);
+                       continue;
+               }
+               if (strcmp(fld, "name") == 0) {
+                       if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
+                               cerror(doit, "control.%d.%s is invalid", numid, fld);
+                               return -EINVAL;
+                       }
+                       snd_config_get_string(n, &name);
+                       continue;
+               }
+               if (strcmp(fld, "index") == 0) {
+                       if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+                               cerror(doit, "control.%d.%s is invalid", numid, fld);
+                               return -EINVAL;
+                       }
+                       snd_config_get_integer(n, &index);
+                       continue;
+               }
+               if (strcmp(fld, "value") == 0) {
+                       value = n;
+                       continue;
+               }
+               cerror(doit, "unknown control.%d.%s field", numid, fld);
+       }
+       if (!value) {
+               cerror(doit, "missing control.%d.value", numid);
+               return -EINVAL;
+       }
+       if (device < 0)
+               device = 0;
+       if (subdevice < 0)
+               subdevice = 0;
+       if (index < 0)
+               index = 0;
+
+       err = -EINVAL;
+       if (!force_restore) {
+               snd_ctl_elem_info_set_numid(info, numid);
+               err = snd_ctl_elem_info(handle, info);
+       }
+       if (err < 0 && name) {
+               snd_ctl_elem_info_set_numid(info, 0);
+               snd_ctl_elem_info_set_interface(info, iface);
+               snd_ctl_elem_info_set_device(info, device);
+               snd_ctl_elem_info_set_subdevice(info, subdevice);
+               snd_ctl_elem_info_set_name(info, name);
+               snd_ctl_elem_info_set_index(info, index);
+               err = snd_ctl_elem_info(handle, info);
+               if (err < 0 && comment && is_user_control(comment)) {
+                       err = add_user_control(handle, info, comment);
+                       if (err < 0) {
+                               cerror(doit, "failed to add user control #%d (%s)",
+                                      numid, snd_strerror(err));
+                               return err;
+                       }
+               }
+       }
+       if (err < 0) {
+               cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err));
+               return -ENOENT;
+       }
+       numid1 = snd_ctl_elem_info_get_numid(info);
+       iface1 = snd_ctl_elem_info_get_interface(info);
+       device1 = snd_ctl_elem_info_get_device(info);
+       subdevice1 = snd_ctl_elem_info_get_subdevice(info);
+       name1 = snd_ctl_elem_info_get_name(info);
+       index1 = snd_ctl_elem_info_get_index(info);
+       count = snd_ctl_elem_info_get_count(info);
+       type = snd_ctl_elem_info_get_type(info);
+       if (err |= numid != numid1 && !force_restore)
+               cerror(doit, "warning: numid mismatch (%d/%d) for control #%d", 
+                     numid, numid1, numid);
+       if (err |= iface != iface1)
+               cerror(doit, "warning: iface mismatch (%d/%d) for control #%d", iface, iface1, numid);
+       if (err |= device != device1)
+               cerror(doit, "warning: device mismatch (%ld/%ld) for control #%d", device, device1, numid);
+       if (err |= subdevice != subdevice1)
+               cerror(doit, "warning: subdevice mismatch (%ld/%ld) for control #%d", subdevice, subdevice1, numid);
+       if (err |= strcmp(name, name1))
+               cerror(doit, "warning: name mismatch (%s/%s) for control #%d", name, name1, numid);
+       if (err |= index != index1)
+               cerror(doit, "warning: index mismatch (%ld/%ld) for control #%d", index, index1, numid);
+       if (err < 0) {
+               cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err));
+               return -ENOENT;
+       }
+
+       if (comment) {
+               if (check_comment_type(comment, type) < 0)
+                       cerror(doit, "incompatible field type for control #%d", numid);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER) {
+                       if (check_comment_range(handle, comment, info, value, doit) < 0) {
+                               cerror(doit, "value range mismatch for control #%d",
+                                     numid);
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       if (snd_ctl_elem_info_is_inactive(info) ||
+                               !snd_ctl_elem_info_is_writable(info))
+               return 0;
+       snd_ctl_elem_value_set_numid(ctl, numid1);
+
+       if (count == 1) {
+               err = restore_config_value(handle, info, type, value, ctl, 0, doit);
+               if (err < 0)
+                       return err;
+               if (err > 0)
+                       goto _ok;
+       }
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+       {
+               const char *buf;
+               err = snd_config_get_string(value, &buf);
+               if (err >= 0) {
+                       int c1 = 0;
+                       int len = strlen(buf);
+                       unsigned int idx = 0;
+                       int size = type == SND_CTL_ELEM_TYPE_BYTES ?
+                               count : sizeof(snd_aes_iec958_t);
+                       if (size * 2 != len) {
+                               cerror(doit, "bad control.%d.value contents\n", numid);
+                               return -EINVAL;
+                       }
+                       while (*buf) {
+                               int c = *buf++;
+                               if ((c = hextodigit(c)) < 0) {
+                                       cerror(doit, "bad control.%d.value contents\n", numid);
+                                       return -EINVAL;
+                               }
+                               if (idx % 2 == 1)
+                                       snd_ctl_elem_value_set_byte(ctl, idx / 2, c1 << 4 | c);
+                               else
+                                       c1 = c;
+                               idx++;
+                       }
+                       goto _ok;
+               }
+       }
+       default:
+               break;
+       }
+       if (snd_config_get_type(value) != SND_CONFIG_TYPE_COMPOUND) {
+               if (!force_restore || !doit) {
+                       cerror(doit, "bad control.%d.value type", numid);
+                       return -EINVAL;
+               }
+               for (idx = 0; idx < count; ++idx) {
+                       err = restore_config_value2(handle, info, type, value,
+                                                   ctl, idx, numid, doit);
+                       if (err < 0)
+                               return err;
+               }
+               goto _ok;
+       }
+
+       set = (char*) alloca(count);
+       memset(set, 0, count);
+       snd_config_for_each(i, next, value) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *id;
+               if (snd_config_get_id(n, &id) < 0)
+                       continue;
+               idx = atoi(id);
+               if (idx >= count || set[idx]) {
+                       cerror(doit, "bad control.%d.value index", numid);
+                       if (!force_restore || !doit)
+                               return -EINVAL;
+                       continue;
+               }
+               err = restore_config_value2(handle, info, type, n,
+                                           ctl, idx, numid, doit);
+               if (err < 0)
+                       return err;
+               if (err > 0)
+                       set[idx] = 1;
+       }
+       for (idx = 0; idx < count; ++idx) {
+               if (!set[idx]) {
+                       cerror(doit, "control.%d.value.%d is not specified", numid, idx);
+                       if (!force_restore || !doit)
+                               return -EINVAL;
+               }
+       }
+
+ _ok:
+       err = doit ? snd_ctl_elem_write(handle, ctl) : 0;
+       if (err < 0) {
+               error("Cannot write control '%d:%ld:%ld:%s:%ld' : %s", (int)iface, device, subdevice, name, index, snd_strerror(err));
+               return err;
+       }
+       return 0;
+}
+
+static int set_controls(int card, snd_config_t *top, int doit)
+{
+       snd_ctl_t *handle;
+       snd_ctl_card_info_t *info;
+       snd_config_t *control;
+       snd_config_iterator_t i, next;
+       int err, maxnumid = -1;
+       char name[32], tmpid[16];
+       const char *id;
+       snd_ctl_card_info_alloca(&info);
+
+       sprintf(name, "hw:%d", card);
+       err = snd_ctl_open(&handle, name, 0);
+       if (err < 0) {
+               error("snd_ctl_open error: %s", snd_strerror(err));
+               return err;
+       }
+       err = snd_ctl_card_info(handle, info);
+       if (err < 0) {
+               error("snd_ctl_card_info error: %s", snd_strerror(err));
+               goto _close;
+       }
+       id = snd_ctl_card_info_get_id(info);
+       err = snd_config_searchv(top, &control, "state", id, "control", 0);
+       if (err < 0) {
+               if (force_restore) {
+                       sprintf(tmpid, "card%d", card);
+                       err = snd_config_searchv(top, &control, "state", tmpid, "control", 0);
+                       if (! err)
+                               id = tmpid;
+               }
+               if (err < 0) {
+                       fprintf(stderr, "No state is present for card %s\n", id);
+                       goto _close;
+               }
+               id = tmpid;
+       }
+       if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) {
+               cerror(doit, "state.%s.control is not a compound\n", id);
+               return -EINVAL;
+       }
+       snd_config_for_each(i, next, control) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               err = set_control(handle, n, &maxnumid, doit);
+               if (err < 0 && (!force_restore || !doit))
+                       goto _close;
+       }
+
+       /* check if we have additional controls in driver */
+       /* in this case we should go through init procedure */
+       if (!doit && maxnumid >= 0) {
+               snd_ctl_elem_id_t *id;
+               snd_ctl_elem_info_t *info;
+               snd_ctl_elem_id_alloca(&id);
+               snd_ctl_elem_info_alloca(&info);
+               snd_ctl_elem_info_set_numid(info, maxnumid+1);
+               if (snd_ctl_elem_info(handle, info) == 0) {
+                       /* not very informative */
+                       /* but value is used for check only */
+                       err = -EAGAIN;
+                       goto _close;
+               }
+       }
+
+ _close:
+       snd_ctl_close(handle);
+       return err;
+}
+
+int save_state(const char *file, const char *cardname)
+{
+       int err;
+       snd_config_t *config;
+       snd_input_t *in;
+       snd_output_t *out;
+       int stdio;
+
+       err = snd_config_top(&config);
+       if (err < 0) {
+               error("snd_config_top error: %s", snd_strerror(err));
+               return err;
+       }
+       stdio = !strcmp(file, "-");
+       if (!stdio && (err = snd_input_stdio_open(&in, file, "r")) >= 0) {
+               err = snd_config_load(config, in);
+               snd_input_close(in);
+#if 0
+               if (err < 0) {
+                       error("snd_config_load error: %s", snd_strerror(err));
+                       return err;
+               }
+#endif
+       }
+
+       if (!cardname) {
+               int card, first = 1;
+
+               card = -1;
+               /* find each installed soundcards */
+               while (1) {
+                       if (snd_card_next(&card) < 0)
+                               break;
+                       if (card < 0) {
+                               if (first) {
+                                       if (ignore_nocards) {
+                                               return 0;
+                                       } else {
+                                               error("No soundcards found...");
+                                               return -ENODEV;
+                                       }
+                               }
+                               break;
+                       }
+                       first = 0;
+                       if ((err = get_controls(card, config)))
+                               return err;
+               }
+       } else {
+               int cardno;
+
+               cardno = snd_card_get_index(cardname);
+               if (cardno < 0) {
+                       error("Cannot find soundcard '%s'...", cardname);
+                       return cardno;
+               }
+               if ((err = get_controls(cardno, config))) {
+                       return err;
+               }
+       }
+       
+       if (stdio)
+               err = snd_output_stdio_attach(&out, stdout, 0);
+       else
+               err = snd_output_stdio_open(&out, file, "w");
+       if (err < 0) {
+               error("Cannot open %s for writing: %s", file, snd_strerror(err));
+               return -errno;
+       }
+       err = snd_config_save(config, out);
+       snd_output_close(out);
+       if (err < 0)
+               error("snd_config_save: %s", snd_strerror(err));
+       return 0;
+}
+
+int load_state(const char *file, const char *initfile, const char *cardname,
+              int do_init)
+{
+       int err, finalerr = 0;
+       snd_config_t *config;
+       snd_input_t *in;
+       int stdio;
+
+       err = snd_config_top(&config);
+       if (err < 0) {
+               error("snd_config_top error: %s", snd_strerror(err));
+               return err;
+       }
+       stdio = !strcmp(file, "-");
+       if (stdio)
+               err = snd_input_stdio_attach(&in, stdin, 0);
+       else
+               err = snd_input_stdio_open(&in, file, "r");
+       if (err >= 0) {
+               err = snd_config_load(config, in);
+               snd_input_close(in);
+               if (err < 0) {
+                       error("snd_config_load error: %s", snd_strerror(err));
+                       return err;
+               }
+       } else {
+               int card, first = 1;
+               char cardname1[16];
+
+               error("Cannot open %s for reading: %s", file, snd_strerror(err));
+               finalerr = err;
+               card = -1;
+               /* find each installed soundcards */
+               while (1) {
+                       if (snd_card_next(&card) < 0)
+                               break;
+                       if (card < 0)
+                               break;
+                       first = 0;
+                       if (!do_init)
+                               break;
+                       sprintf(cardname1, "%i", card);
+                       err = init(initfile, cardname1);
+                       if (err < 0) {
+                               finalerr = err;
+                               initfailed(card, "init");
+                       }
+                       initfailed(card, "restore");
+               }
+               if (first)
+                       finalerr = 0;   /* no cards, no error code */
+               return finalerr;
+       }
+
+       if (!cardname) {
+               int card, first = 1;
+               char cardname1[16];
+
+               card = -1;
+               /* find each installed soundcards */
+               while (1) {
+                       if (snd_card_next(&card) < 0)
+                               break;
+                       if (card < 0) {
+                               if (first) {
+                                       if (ignore_nocards) {
+                                               return 0;
+                                       } else {
+                                               error("No soundcards found...");
+                                               return -ENODEV;
+                                       }
+                               }
+                               break;
+                       }
+                       first = 0;
+                       /* do a check if controls matches state file */
+                       if (do_init && set_controls(card, config, 0)) {
+                               sprintf(cardname1, "%i", card);
+                               err = init(initfile, cardname1);
+                               if (err < 0) {
+                                       initfailed(card, "init");
+                                       finalerr = err;
+                               }
+                       }
+                       if ((err = set_controls(card, config, 1))) {
+                               if (!force_restore)
+                                       finalerr = err;
+                               initfailed(card, "restore");
+                       }
+               }
+       } else {
+               int cardno;
+
+               cardno = snd_card_get_index(cardname);
+               if (cardno < 0) {
+                       error("Cannot find soundcard '%s'...", cardname);
+                       return -ENODEV;
+               }
+               /* do a check if controls matches state file */
+               if (do_init && set_controls(cardno, config, 0)) {
+                       err = init(initfile, cardname);
+                       if (err < 0) {
+                               initfailed(cardno, "init");
+                               return err;
+                       }
+               }
+               if ((err = set_controls(cardno, config, 1))) {
+                       initfailed(cardno, "restore");
+                       if (!force_restore)
+                               return err;
+               }
+       }
+       return finalerr;
+}
diff --git a/scenario/utils.c b/scenario/utils.c
new file mode 100644 (file)
index 0000000..ab4dbd4
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Support routines
+ *  Copyright (c) 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.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <alsa/asoundlib.h>
+#include "alsactl.h"
+
+int file_map(const char *filename, char **buf, size_t *bufsize)
+{
+       struct stat stats;
+       int fd;
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0) {
+               return -1;
+       }
+
+       if (fstat(fd, &stats) < 0) {
+               close(fd);
+               return -1;
+       }
+
+       *buf = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
+       if (*buf == MAP_FAILED) {
+               close(fd);
+               return -1;
+       }
+       *bufsize = stats.st_size;
+
+       close(fd);
+
+       return 0;
+}
+
+void file_unmap(void *buf, size_t bufsize)
+{
+       munmap(buf, bufsize);
+}
+
+size_t line_width(const char *buf, size_t bufsize, size_t pos)
+{
+       int esc = 0;
+       size_t count;
+       
+       for (count = pos; count < bufsize; count++) {
+               if (!esc && buf[count] == '\n')
+                       break;
+               esc = buf[count] == '\\';
+       }
+
+       return count - pos;
+}
+
+void initfailed(int cardnumber, const char *reason)
+{
+       int fp;
+       char *str;
+
+       if (statefile == NULL)
+               return;
+       if (snd_card_get_name(cardnumber, &str) < 0)
+               return;
+       fp = open(statefile, O_WRONLY|O_CREAT|O_APPEND, 0644);
+       write(fp, str, strlen(str));
+       write(fp, ":", 1);
+       write(fp, reason, strlen(reason));
+       write(fp, "\n", 1);
+       close(fp);
+       free(str);
+}
diff --git a/scenario/version.h b/scenario/version.h
new file mode 100644 (file)
index 0000000..73295e0
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ *  version.h
+ */
+
+#define SND_UTIL_MAJOR         1
+#define SND_UTIL_MINOR         0
+#define SND_UTIL_SUBMINOR      21
+#define SND_UTIL_VERSION               ((SND_UTIL_MAJOR<<16)|\
+                                (SND_UTIL_MINOR<<8)|\
+                                 SND_UTIL_SUBMINOR)
+#define SND_UTIL_VERSION_STR   "1.0.21"
+