aboutsummaryrefslogtreecommitdiff
path: root/python/ContextKit
diff options
context:
space:
mode:
authorAkos PASZTORY <ext-akos.pasztory@nokia.com>2009-05-14 14:31:13 +0300
committerGergely Risko <ext-risko.gergely@nokia.com>2009-05-26 15:17:19 +0300
commit9efba78ba665662fb73cf810c6b7e146a992fd16 (patch)
tree3ae2f2fb72ef283800b34a9bb423f1cbebe6fd2a /python/ContextKit
parent338e88b325d0dbf3df8bb271bc0d168c21ce1ee6 (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.py65
-rw-r--r--python/ContextKit/ContextProvider.py60
-rw-r--r--python/ContextKit/__init__.py0
-rw-r--r--python/ContextKit/flexiprovider.py209
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')