--- /dev/null
+#!/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()
+
--- /dev/null
+#
+# 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'
--- /dev/null
+#
+# 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)
--- /dev/null
+#
+# 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))
--- /dev/null
+#!/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()
l = f.readline()
gtk.main()
+
+
--- /dev/null
+#!/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()
--- /dev/null
+#! /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()
--- /dev/null
+
+#
+# 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()
+
--- /dev/null
+#!/bin/sh
+
+case $1 in
+ suspend ) ifdown usb0 ;;
+ resume ) ifdown usb0 ; ifup usb0 ;;
+esac
+exit 0
--- /dev/null
+#!/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
--- /dev/null
+# 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
--- /dev/null
+# /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
--- /dev/null
+#!/bin/sh
+
+case $2 in
+ CONNECTED ) ifup $1 ;;
+ DISCONNECTED ) ifdown $1 ;;
+esac
+exit 0
--- /dev/null
+
+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'.
+
--- /dev/null
+
+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
--- /dev/null
+
+LDLIBS=-lasound
+alsactl : alsactl.o state.o utils.o init_parse.o
+
--- /dev/null
+/* 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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"
+