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