diff options
author | Akos PASZTORY <ext-akos.pasztory@nokia.com> | 2009-05-14 14:31:13 +0300 |
---|---|---|
committer | Gergely Risko <ext-risko.gergely@nokia.com> | 2009-05-26 15:17:19 +0300 |
commit | 9efba78ba665662fb73cf810c6b7e146a992fd16 (patch) | |
tree | 3ae2f2fb72ef283800b34a9bb423f1cbebe6fd2a /python/ContextKit | |
parent | 338e88b325d0dbf3df8bb271bc0d168c21ce1ee6 (diff) |
Bringing the python stuff alive.
Moved from tools/flexiprovider to python/ContextKit, added into the build
infrastructure and adapted scripts.
Diffstat (limited to 'python/ContextKit')
-rw-r--r-- | python/ContextKit/CTypesHelpers.py | 65 | ||||
-rw-r--r-- | python/ContextKit/ContextProvider.py | 60 | ||||
-rw-r--r-- | python/ContextKit/__init__.py | 0 | ||||
-rw-r--r-- | python/ContextKit/flexiprovider.py | 209 |
4 files changed, 334 insertions, 0 deletions
diff --git a/python/ContextKit/CTypesHelpers.py b/python/ContextKit/CTypesHelpers.py new file mode 100644 index 00000000..bada4e0d --- /dev/null +++ b/python/ContextKit/CTypesHelpers.py @@ -0,0 +1,65 @@ +# From http://wwwx.cs.unc.edu/~gb/wp/blog/2007/02/11/ctypes-tricks/ + +from ctypes import * + +def cfunc(name, dll, result, *args): + '''build and apply a ctypes prototype complete with parameter flags''' + atypes = [] + aflags = [] + for arg in args: + atypes.append(arg[1]) + aflags.append((arg[2], arg[0]) + arg[3:]) + return CFUNCTYPE(result, *atypes)((name, dll), tuple(aflags)) + +class ListPOINTER(object): + '''Just like a POINTER but accept a list of ctype as an argument''' + def __init__(self, etype): + self.etype = etype + + def from_param(self, param): + if isinstance(param, (list,tuple)): + return (self.etype * len(param))(*param) + +class ListPOINTER2(object): + '''Just like POINTER(POINTER(ctype)) but accept a list of lists of ctype''' + def __init__(self, etype): + self.etype = etype + + def from_param(self, param): + if isinstance(param, (list,tuple)): + val = (POINTER(self.etype) * len(param))() + for i,v in enumerate(param): + if isinstance(v, (list,tuple)): + val[i] = (self.etype * len(v))(*v) + else: + raise TypeError, 'nested list or tuple required at %d' % i + return val + else: + raise TypeError, 'list or tuple required' + + +class ByRefArg(object): + '''Just like a POINTER but accept an argument and pass it byref''' + def __init__(self, atype): + self.atype = atype + + def from_param(self, param): + return byref(self.atype(param)) + +# hack the ctypes.Structure class to include printing the fields +class _Structure(Structure): + def __repr__(self): + '''Print the fields''' + res = [] + for field in self._fields_: + res.append('%s=%s' % (field[0], repr(getattr(self, field[0])))) + return self.__class__.__name__ + '(' + ','.join(res) + ')' + @classmethod + def from_param(cls, obj): + '''Magically construct from a tuple''' + if isinstance(obj, cls): + return obj + if isinstance(obj, tuple): + return cls(*obj) + raise TypeError + diff --git a/python/ContextKit/ContextProvider.py b/python/ContextKit/ContextProvider.py new file mode 100644 index 00000000..6e8f61fd --- /dev/null +++ b/python/ContextKit/ContextProvider.py @@ -0,0 +1,60 @@ +from ctypes import * +from CTypesHelpers import * + + +_dll = CDLL("libcontextprovider.so") + +class ContextProvider: + SUBSCRIPTION_CHANGED_CALLBACK = CFUNCTYPE(None, c_int, c_void_p) + + init = cfunc('context_provider_init', _dll, None, + ('bus_type', c_int, 1), + ('bus_name', c_char_p, 1)) + install_group = cfunc('context_provider_install_group', _dll, None, + ('key_group', ListPOINTER (c_char_p), 1), + ('clear_values_on_subscribe', c_int, 1), + ('subscription_changed_cb', SUBSCRIPTION_CHANGED_CALLBACK, 1), + ('last_cb_target', c_void_p, 1)) + install_key = cfunc('context_provider_install_key', _dll, None, + ('key', c_char_p, 1), + ('clear_values_on_subscribe', c_int, 1), + ('subscription_changed_cb', SUBSCRIPTION_CHANGED_CALLBACK, 1), + ('last_cb_target', c_void_p, 1)) + + set_integer = cfunc('context_provider_set_integer', _dll, None, + ('key', c_char_p, 1), + ('val', c_int, 1)) + set_double = cfunc('context_provider_set_double', _dll, None, + ('key', c_char_p, 1), + ('val', c_double, 1)) + set_boolean = cfunc('context_provider_set_boolean', _dll, None, + ('key', c_char_p, 1), + ('val', c_int, 1)) + set_string = cfunc('context_provider_set_string', _dll, None, + ('key', c_char_p, 1), + ('val', c_char_p, 1)) + set_null = cfunc('context_provider_set_null', _dll, None, + ('key', c_char_p, 1)) + +if __name__ == "__main__": + import gobject + import dbus + import dbus.service + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + loop = gobject.MainLoop() + + def py_subscription_changed_cb (subscribe, d): + if subscribe: + print "group subscribed" + else: + print "group unsubscribed" + + + ContextProvider.init(0, "org.freedesktop.ContextKit.Testing.Provider") + subscription_changed_cb = ContextProvider.SUBSCRIPTION_CHANGED_CALLBACK(py_subscription_changed_cb) + p = ContextProvider.install_group(["foo.bar", "foo.baz"], 1, + subscription_changed_cb, None) + ContextProvider.set_integer("foo.bar", 1) + ContextProvider.set_null("foo.baz") + loop.run() diff --git a/python/ContextKit/__init__.py b/python/ContextKit/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/python/ContextKit/__init__.py diff --git a/python/ContextKit/flexiprovider.py b/python/ContextKit/flexiprovider.py new file mode 100644 index 00000000..fd9a56ec --- /dev/null +++ b/python/ContextKit/flexiprovider.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +import os +import sys +import tempfile +import subprocess +import glib, gobject +import dbus, dbus.service, dbus.mainloop.glib + +def pkgconfig(*args): + """Runs `pkg-config $args` and returns the Popen object, augmented + with an `output' attribute containing stdout.""" + cmd = ['pkg-config'] + list(args) + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + process.output = process.communicate()[0].strip() + return process + +# The following kludge is needed if we want this to be usable without +# installing libcontextprovider. If pkg-config reports it as +# uninstalled, we extend PYTHONPATH, then we try to import the module. +# If that fails, extend LD_LIBRARY_PATH and re-exec ourselves. + +if pkgconfig('--exists', 'contextprovider-1.0').returncode != 0 or \ + pkgconfig('--exists', 'duivaluespace-1.0').returncode != 0: + raise RuntimeError("You need to make pkg-config find " + "contextprovider-1.0 and duivaluespace-1.0 " + "somehow.\nTry setting $PKG_CONFIG_PATH.") + +if pkgconfig('--uninstalled', 'contextprovider-1.0').returncode == 0: + sys.path.append(pkgconfig('--variable=pythondir', 'contextprovider-1.0').output) +try: + import ContextProvider as CP +except ImportError: + raise +except: + # Failed, probably because LD_LIBRARY_PATH is not right. Set it and + # re-exec ourselves. To avoid an infinite loop, we try this only + # when LD_LIBRARY_PATH doesn't yet contain what we want to add. + libdir = pkgconfig('--variable=libdir', 'contextprovider-1.0').output + ldpath = [d for d in os.environ.get('LD_LIBRARY_PATH', '').split(':') if d != ''] + if libdir in ldpath: + raise + ldpath += [libdir, libdir + '/.libs'] + env = dict(os.environ) + env.update(LD_LIBRARY_PATH=':'.join(ldpath)) + os.execve(sys.argv[0], sys.argv, env) + +class _property(object): + """Kind-of a template for property types.""" + def __init__(self, datatype, default): + self.datatype = datatype + self.default = default + self.name = None + def __call__(self, name, initial_value=None): + """Clones self, setting its name and initial value.""" + if initial_value is None: + initial_value = self.default + prop = _property(self.datatype, initial_value) + prop.name = name + return prop + +# Pre-defined types and their defaults. +INT = _property('INT', 0) +STRING = _property('STRING', '') +DOUBLE = _property('DOUBLE', 0.0) +TRUTH = _property('TRUTH', False) + +def xmlfor(busname='ctx.flexiprovider', bus='session', *props): + """Generates a provider .xml document for the given properties + (which are instances of _property).""" + xml = ['<?xml version="1.0"?>\n' + '<provider bus="%s" service="%s">' % (bus, busname)] + for p in props: + node, _, key = p.name.partition('.') + xml.append(' <node name="%s">\n' + ' <key name="%s" type="%s">\n' + ' <doc>A phony but very flexible property.</doc>\n' + ' </key>\n' + ' </node>' % (node, key, p.datatype)) + xml.append('</provider>') + return '\n'.join(xml) + +# Location of the update-context-providers script, initialized lazily +# in the function below. +U_C_P = None +def update_context_providers(xml, dir='.'): + """Runs the update-context-provider script in $dir after writing + $xml to a temporary file.""" + global U_C_P + if U_C_P is None: + U_C_P = '' + if pkgconfig('--uninstalled', 'duivaluespace-1.0').returncode == 0: + U_C_P = '%s/registry/' % pkgconfig('--variable=pcfiledir', + 'duivaluespace-1.0').output + U_C_P += 'update-context-providers' + tmpf = tempfile.NamedTemporaryFile('w+b', -1, '.xml', 'flexi', dir) + print >>tmpf, xml + tmpf.flush() + subprocess.call([U_C_P, dir]) + +class Flexiprovider(object): + def stdin_ready(self, fd, cond): + if cond & (glib.IO_ERR | glib.IO_HUP): + self.loop.quit() + return False + # We assume that stdin is line-buffered (ie. readline() doesn't + # block (too much)). It's true if it's a tty. If piping from + # another program, do an entire line. + # TODO maybe we should rather fix ourselves + l = sys.stdin.readline().strip() + if l == '': + self.loop.quit() + return False + try: + # TODO something more sophisticated? + code = compile(l, '<input from stdin>', 'single') + eval(code, {}, dict(INT=INT, + STRING=STRING, + TRUTH=TRUTH, + DOUBLE=DOUBLE, + set=self.set, + get=self.get, + reset=self.reset, + quit=self.loop.quit, + add=self.add_and_update, + info=self.info)) + except: + # We are ignoring errors. + import traceback + traceback.print_exc() + return True + + def __init__(self, properties, name='ctx.flexiprovider', bus='session'): + self.busname = name + self.bus = bus + self.loop = gobject.MainLoop() + self.props = {} + self.values = {} + # Hook into libcontextprovider. + self.setters = dict(INT=CP.ContextProvider.set_integer, + STRING=CP.ContextProvider.set_string, + DOUBLE=CP.ContextProvider.set_double, + TRUTH=CP.ContextProvider.set_boolean) + self.subscribed_cb = CP.ContextProvider.SUBSCRIPTION_CHANGED_CALLBACK(lambda x, y: None) + CP.ContextProvider.init(dict(session=0, system=1)[self.bus], self.busname) + # Add properties and set the initial values. + for p in properties: + self.addproperty(p) + self.update_providers() + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + def get(self, prop): + return self.values.get(prop, None) + + def set(self, prop, val): + if prop not in self.props: + return + self.values[prop] = val + if val is None: + CP.ContextProvider.set_null(prop) + else: + self.setters[self.props[prop].datatype](prop, val) + + def reset(self, prop): + if prop not in self.props: + return + self.set(prop, self.props[prop].default) + + def addproperty(self, prop): + self.props[prop.name] = prop + self.values[prop.name] = prop.default + CP.ContextProvider.install_key(prop.name, 0, self.subscribed_cb, None) + self.reset(prop.name) + + def add_and_update(self, prop): + self.addproperty(prop) + self.update_providers() + + def update_providers(self): + update_context_providers(xmlfor(self.busname, self.bus, *self.props.values())) + + def info(self): + """Dumps information about currently provided properties.""" + for p in self.props.values(): + print p.datatype.ljust(8), p.name.ljust(20), \ + self.values[p.name], '(', p.default, ')' + + def interactive(self): + """Runs the provider interactively, accepting the following + commands from the standard input (note that this is Python + syntax, and it gets eval()ed): + + set(property, value): sets the given property + get(property): prints the value of the property + reset(property): resets the property + add(propertydesc): adds a new property + info(): prints properties and their values + quit(): stops the whole thing + + The provider will stop also if stdin is closed. + """ + glib.io_add_watch(sys.stdin.fileno(), + glib.IO_IN | glib.IO_HUP | glib.IO_ERR, + self.stdin_ready) + self.loop.run() + + def run(self): + self.loop.run() + +__all__ = ('INT', 'STRING', 'DOUBLE', 'TRUTH', 'Flexiprovider') |