summaryrefslogtreecommitdiffstats
path: root/extensions/dcopext.py
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-08-16 09:06:37 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-08-16 09:06:37 +0000
commit39d98386f72c65826e162e3e8fd36752ec469252 (patch)
tree5cec746207c4c892d064beafca1de94568a3aeb9 /extensions/dcopext.py
downloadpytde-39d98386f72c65826e162e3e8fd36752ec469252.tar.gz
pytde-39d98386f72c65826e162e3e8fd36752ec469252.zip
Move python-kde3 to the more correct python-trinity
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/libraries/python-trinity@1247483 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'extensions/dcopext.py')
-rw-r--r--extensions/dcopext.py723
1 files changed, 723 insertions, 0 deletions
diff --git a/extensions/dcopext.py b/extensions/dcopext.py
new file mode 100644
index 0000000..ba13871
--- /dev/null
+++ b/extensions/dcopext.py
@@ -0,0 +1,723 @@
+# -*- coding: ISO-8859-1 -*-
+
+"""
+Copyright 2004 Jim Bublitz (original author)
+ 2006 Mathias Panzenböck (panzi) <grosser.meister.morti@gmx.net>
+
+Terms and Conditions
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Except as contained in this notice, the name of the copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in this Software without prior written authorization from the
+copyright holder.
+"""
+
+import re
+from dcop import DCOPClient
+from qt import QString, QCString, QByteArray, QDataStream, IO_ReadOnly, IO_WriteOnly
+from kdecore import dcop_add, dcop_next
+
+# XXX: 64 bit integers might be handeld wrong! pythons int is AFAIK 32 bit,
+# but pythons long is a arbitrary-precision integer. how to handle that?
+#
+# I think 64 bit types would be:
+# long long, unsigned long long, long long int, unsigned long long int,
+# Q_LLONG, Q_ULLONG, Q_INT64, Q_UINT64
+#
+# and on some (most?) systems:
+# QtOffset
+
+# add complex? complex is c99, not c++
+# but python has a complex type
+POD = set(['char','short','int','long','float','double'])
+typedefIntTypes = set(["uchar", "ushort", "uint", "ulong",
+ "Q_INT8", "Q_INT16", "Q_INT32", "Q_LONG",
+ "Q_UINT8", "Q_UINT16", "Q_UINT32", "Q_ULONG",
+ "sitze_t", "ssize_t", "int8_t", "int16_t", "int32_t",
+ "uint8_t", "uint16_t", "uint32_t", "pid_t", "uid_t",
+ "off_t"])
+# XXX string and std::string too?
+stringTypes = set(["QString", "QCString"])
+pythonStringTypes = set([QString, QCString, str])
+stringTypesDict = {"QString":QString,"QCString":QCString,"str":str,"unicode":unicode}
+
+VOID = 0
+BOOLEAN = 1 # XXX bool is not supported by dcop_add, but maybe some time...
+INTEGER = 2
+FLOAT = 3
+STRING = 4
+CLASS = 5
+
+"""
+(Most of this code is adapted from pydcop in kde-bindings, written by
+Torben Weis and Julian Rockey)
+
+The three classes below (DCOPApp, DCOPObj and DCOPMeth)
+allow transparent Python calls to DCOP methods. For example:
+
+ d = DCOPApp ("kicker", dcop)
+
+(where "kicker" is the complete name of an application and 'dcop' is
+the dcopClient instance owned by the KApplication creating the DCOPApp
+instance) creates a DCOPApp instance. All of the classes in this
+file "borrow" a DCOPClient instance from the calling application.
+
+ d.objects
+
+will return a list of the DCOP objects the application supplies.
+
+ o = d.object ("Panel")
+
+will return a DCOPObj corresponding to applications "Panel" DCOP object.
+
+Similarly:
+
+ o.methods
+
+will return a list of the methods the object supplies and
+
+ m = o.method ("panelSize")
+
+will return a DCOPMeth corresponding to Panel's panelSize() method.
+The m instance also holds the methods return type, list of argument types
+(argtypes) and argument names (argnames).
+
+ m.valid
+
+is a boolean which indicates if the method encapsulated by m is a valid
+method for the application/object specified.
+
+However it isn't necessary to explicitly create the DCOPObj and DCOPMeth.
+
+ d.Panel.panelSize.valid
+
+for example, will also indicate if the method is valid without creating the
+intermediate 'o' and 'm' instances explicitly.
+
+ d = DCOPApp ("kicker", dcop)
+ ok, res = d.Panel.panelSize ()
+
+is all the code necessary to perform the indicated DCOP call and return the
+value the call returns. In this case, panelSize takes no arguments and
+returns an int. 'ok' returns the status of the DCOP call (success = True,
+failure = False).
+
+ ok = d.Panel.addURLButton (QString ("http://www.kde.org"))
+
+would call addURLButton with the required argument, and return nothing but the DCOP call
+status(since its return type is 'void').
+
+Note that to instantiate a DCOPObj directly, you need to have a valid DCOPApp
+to pass to DCOPObj's __init__ method. Similarly, DCOPMeth requires a valid DCOPOBject.
+For example:
+
+ d = DCOPApp ("kicker", dcop)
+ o = DCOPObj (d, "Panel")
+ m = DCOPMeth (o, "panelSize")
+
+or
+
+ m = DCOPMeth (DCOPObj (DCOPApp ("kicker", dcop), "Panel"), "panelSize")
+
+"""
+
+# support stuff:
+def _xiter(*seqences):
+ iters = [iter(seq) for seq in seqences]
+
+ try:
+ while True:
+ yield [it.next() for it in iters]
+
+ except StopIteration:
+ pass
+
+def isStringType(s):
+ for stringType in pythonStringTypes:
+ if isinstance(s,stringType):
+ return True
+ return False
+
+# method syntax:
+# --------------
+# method ::= rtype identifier( args )
+# rtype ::= "void" | type
+# identifier ::= [_a-zA-Z][_a-zA-Z0-9]*
+# args ::= ( arg ("," arg)* )?
+# arg ::= type identifier?
+# type ::= namespace typespec | POD
+# POD ::= ( "unsigned" | "signed" )? identifier
+# namespace ::= (identifier "::")* | "::"
+# typespec ::= identifier ( "<" tpyelist ">" )?
+# typelist ::= (type | int) ("," (type | int) )*
+# int ::= "0x" [0-9a-fA-F]+ | [0-9]+
+
+class MethodParser(object):
+ ident_r = re.compile("[_a-zA-Z][_a-zA-Z0-9]*")
+ num_r = re.compile("0x[0-0a-fA-F]+|[0-9]+")
+
+ def __init__(self,method):
+ self.method = str(method)
+ self.rtype = None
+ self.name = None
+ self.args = []
+
+ self.parseMethod()
+
+ def __repr__(self):
+ return "%s(%s)" % (self.__class__.__name__, repr(self.method))
+
+ def getDecl(self):
+ return ''.join([self.name, '(', ','.join(argtp for (argtp, kind), argname in self.args), ')'])
+
+ def parseMethod(self):
+ i = self.parseRtype(self.method,0)
+ i, self.name = self.parseIdentifier(self.method,i)
+ i = self.parseArgs(self.method,i)
+
+ if i != len(self.method):
+ raise SyntaxError, "invalid function definition: %s" % self.method
+
+ @staticmethod
+ def skipws(s,i):
+ while s[i:i+1].isspace():
+ i += 1
+ return i
+
+ def parseArg(self,s,i):
+ i, tp = self.parseType(s,i)
+ name = self.parseIdentifier(s,i)
+
+ if name:
+ i, name = name
+ else:
+ name = None
+
+ return i, (tp, name)
+
+ def parseIdentifier(self,s,i):
+ i = MethodParser.skipws(s,i)
+ m = MethodParser.ident_r.match(s,i)
+
+ if m:
+ return m.end(), s[i:m.end()]
+ else:
+ return False
+
+ def parseInteger(self,s,i):
+ i = MethodParser.skipws(s,i)
+ m = MethodParser.num_r.match(s,i)
+
+ if m:
+ return m.end(), s[i:m.end()]
+ else:
+ return False
+
+ def parseArgs(self,s,i):
+ i = MethodParser.skipws(s,i)
+
+ if s[i:i+1] == '(':
+ i += 1
+ i = MethodParser.skipws(s,i)
+
+ while i < len(s) and s[i:i+1] != ')':
+ i, arg = self.parseArg(s,i)
+ i = MethodParser.skipws(s,i)
+
+ self.args.append(arg)
+
+ if s[i:i+1] == ',':
+ i += 1
+
+ else:
+ break
+
+ if s[i:i+1] == ')':
+ i += 1
+ else:
+ raise SyntaxError, "missing ')'."
+ else:
+ raise SyntaxError, "missing '('."
+
+ return i
+
+ def parseType(self,s,i):
+ num = self.parseNumberType(s,i)
+
+ if num:
+ return num
+
+ i, ns = self.parseNamespace(s,i)
+ i, tp = self.parseTypespec(s,i)
+
+ tp = ns + tp
+
+ if tp in stringTypes:
+ return i, (tp, STRING)
+
+ else:
+ return i, (tp, CLASS)
+
+ def parseTypespec(self,s,i):
+ i, tp = self.parseIdentifier(s,i)
+ i, tplst = self.parseTypelist(s,i)
+
+ return i, tp + tplst
+
+ def parseTypelist(self,s,i):
+ L = []
+ newi = MethodParser.skipws(s,i)
+
+ if s[newi:newi+1] == '<':
+ i = newi + 1
+ i = MethodParser.skipws(s,i)
+
+ L.append('<')
+
+ while i < len(s) and s[i:i+1] != '>':
+ # template-parameter can be integers!!
+
+ num = self.parseInteger(s,i)
+
+ if num:
+ i, tp = num
+
+ else:
+ i, (tp, kind) = self.parseType(s,i)
+
+ i = MethodParser.skipws(s,i)
+
+ L.append(tp)
+
+ if s[i:i+1] == ',':
+ i += 1
+ L.append(',')
+
+ else:
+ break
+
+
+ if s[i:i+1] == '>':
+ i += 1
+ L.append('>')
+
+ else:
+ raise SyntaxError, "missing '>'."
+
+ return i, ''.join(L)
+
+ def parseNumberType(self,s,i):
+ i, tp = self.parseIdentifier(s,i)
+ L = []
+
+ if tp == 'bool':
+ return i, (tp, BOOLEAN)
+
+ elif tp in typedefIntTypes:
+ return i, (tp, INTEGER)
+
+ elif tp in ('signed','unsigned'):
+ L.append(tp)
+ next = self.parseIdentifier(s,i)
+
+ if next and next[1] in POD:
+ i, tp = next
+
+ else:
+ # type can be fully quallyfied here!
+ return i, (tp, INTEGER)
+
+ if tp in POD:
+ L.append(tp)
+
+ else:
+ # else no number-type at all!
+
+ return False
+
+ # long
+ # long int
+ # long long
+ # long long int
+ # long double
+ # short
+ # short int
+
+ if tp == 'short':
+ # short
+
+ next = self.parseIdentifier(s,i)
+
+ if next and next[1] == 'int':
+ # short int
+
+ i, tp = next
+ L.append(tp)
+
+ elif tp == 'long':
+ # long
+
+ next = self.parseIdentifier(s,i)
+
+ if next:
+ if next[1] in ('int', 'double'):
+ # long int
+ # long double
+
+ i, tp = next
+ L.append(tp)
+
+ elif next[1] == 'long':
+ # long long
+ # XXX: this is 64bit! how should I handle this?
+
+ i, tp = next
+ L.append(tp)
+
+ next = self.parseIdentifier(s,i)
+
+ if next and next[1] == 'int':
+ # long long int
+
+ i, tp = next
+ L.append(tp)
+
+ if tp in ('float', 'double'):
+ return i, (' '.join(L), FLOAT)
+
+ else:
+ return i, (' '.join(L), INTEGER)
+
+ #
+ # ::
+ # foo::
+ # ::foo::
+ # foo::bar::
+ # ::foo::bar::
+ # ...
+ def parseNamespace(self,s,i):
+ L = []
+ i = MethodParser.skipws(s,i)
+
+ if s[i:i+2] == "::":
+ i += 2
+ L.append("::")
+
+ while i < len(s):
+ ns = self.parseIdentifier(s,i)
+
+ if not ns:
+ break
+
+ newi, ns = ns
+ newi = MethodParser.skipws(s,newi)
+
+ if s[newi:newi+2] != "::":
+ break
+
+ i = newi + 2
+
+ L.append( ns )
+ L.append( "::" )
+
+ return i, ''.join(L)
+
+
+ def parseRtype(self,s,i):
+ tp = self.parseIdentifier(s,i)
+
+ if tp and tp[1] == 'void':
+ i, tp = tp
+ self.rtype = (tp,VOID)
+
+ else:
+ i, self.rtype = self.parseType(s,i)
+
+ return i
+
+def DCOPAppsIter(client):
+ for app in client.registeredApplications():
+ yield str(app)
+
+class DCOPApp(object):
+ """
+ An object corresponding to an application with a DCOP interface
+
+ Can return a list of the DCOP objects the application exposes,
+ or create and return an instance of a specific DCOP object.
+ """
+ def __init__ (self, name, client):
+ self.appname = name
+ self.appclient = client
+
+ def __getattr__ (self, item ):
+ if item == "objects":
+ objs, ok = self.appclient.remoteObjects(self.appname)
+
+ if ok:
+ return objs
+ else:
+ return None
+
+ return DCOPObj(self, item)
+
+ def __iter__(self):
+ objs, ok = self.appclient.remoteObjects(self.appname)
+
+ if ok:
+ for obj in objs:
+ yield str(obj)
+
+ # sometimes a object-name is not a valid python identifier.
+ # in that case you can use dcopapp['non-valid::object/name']
+ def __getitem__(self,name):
+ return DCOPObj(self, name)
+
+ def object (self, object):
+ return DCOPObj (self, object)
+
+ def __repr__(self):
+ return '%s(%s,%s)' % (self.__class__.__name__,repr(self.appname),repr(self.appclient))
+
+ def __str__(self):
+ return repr(self)
+
+class DCOPObj(object):
+ """
+ An object corresponding to a specific DCOP object owned by a
+ specific application with a DCOP interface
+
+ Can return a list of the DCOP methods the object exposes,
+ or create and return an instance of a specific DCOP method.
+ """
+
+ def __init__ (self, *args):
+ if isStringType(args[0]):
+ self.appname = args [0]
+ self.objclient = args [1]
+ self.objname = args [2]
+ else:
+ self.appname = args [0].appname
+ self.objname = args [1]
+ self.objclient = args [0].appclient
+
+ self.objmethods = self.getMethods()
+
+ def __repr__( self ):
+ return "%s(%s,%s)" % (self.__class__.__name__,repr(self.appname), repr(self.objname))
+
+ def __str__( self ):
+ return repr(self)
+
+ def __getattr__( self, item ):
+ if item == "methods":
+ return self.objmethods
+
+ return DCOPMeth(self, item)
+
+ def __getitem__(self,name):
+ return DCOPMeth(self, name)
+
+ def getMethods(self):
+ flist, ok = self.objclient.remoteFunctions(self.appname, self.objname)
+
+ if ok:
+ return flist
+ else:
+ return None
+
+ def __iter__(self):
+ flist, ok = self.objclient.remoteFunctions(self.appname, self.objname)
+
+ if ok:
+ for meth in flist:
+ yield str(meth)
+
+ def getMethodNames(self):
+ return [MethodParser(meth).name for meth in self.objmethods]
+
+ def getParsedMethods(self):
+ return [MethodParser(meth) for meth in self.objmethods]
+
+ def method(self, method):
+ return DCOPMeth(self, method)
+
+class DCOPMeth(object):
+ """
+ An object corresponding to a specific DCOP method owned by a
+ specific DCOP object.
+ """
+ def __init__(self, dcopObj, name):
+ self.argtypes = []
+ self.argnames = []
+ self.fcnname = []
+ self.rtype = []
+ self.appname = dcopObj.appname
+ self.objname = dcopObj.objname
+ self.methname = name
+ self.client = dcopObj.objclient
+ try:
+ self.methods = [str(meth) for meth in dcopObj.objmethods]
+ except TypeError:
+ self.methods = []
+ self.valid = self.findMethod()
+#
+# if not self.valid:
+# self.fcnname = self.rtype = self.argtypes = self.argnames = None
+
+ def __repr__( self ):
+ return "%s(%s,%s,%s)" % (self.__class__.__name__,repr(self.appname),repr(self.objname),repr(self.methname))
+
+ def __str__(self):
+ return repr(self)
+
+ def __call__(self, *args):
+ return self.dcop_call(args)
+
+ def __iter__(self):
+ return iter(self.fcnname)
+
+ def dcop_call(self, args):
+ # method valid?
+ if not self.valid:
+ return False, None
+
+ found = self.getMatchingMethod(args)
+
+ if found is None:
+ return False, None
+
+ meth, argtypes = found
+
+ ok, replyType, replyData = self.client.call(self.appname, self.objname, meth, self.__marshall(args,argtypes))
+
+ if ok:
+ return ok, self.__unmarshall(replyData, replyType)
+ else:
+ return ok, None
+
+ def getMatchingMethod(self,args):
+ count = len(args)
+
+ for funct, argtypes in _xiter(self.fcnname, self.argtypes):
+ if len(argtypes) == count:
+ match = True
+
+ for (wanttp, wantkind), have in _xiter(argtypes,args):
+ if wantkind == BOOLEAN:
+ if not isinstance(have, bool):
+ match = False
+ break
+
+ elif wantkind == INTEGER:
+ if not isinstance(have, int):
+ match = False
+ break
+
+ elif wantkind == FLOAT:
+ if not isinstance(have, float):
+ match = False
+ break
+
+ elif wantkind == STRING:
+ if not isStringType(have):
+ match = False
+ break
+
+ elif wanttp != have.__class__.__name__:
+ match = False
+ break
+
+ if match:
+ return funct, argtypes
+ return None
+
+ def findMethod(self):
+ has = False
+
+ for meth in self.methods:
+ fun = MethodParser(meth)
+
+ if fun.name == self.methname:
+ self.argtypes.append([argtp for argtp, argname in fun.args])
+ self.argnames.append([argname for argtp, argname in fun.args])
+ self.rtype.append(fun.rtype)
+ self.fcnname.append(fun.getDecl())
+
+ has = True
+
+ return has
+
+ def __marshall(self, args, argtypes):
+ data = QByteArray()
+ if argtypes == []:
+ return data
+
+ params = QDataStream (data, IO_WriteOnly)
+
+ for arg, (argtype, argkind) in _xiter(args, argtypes):
+ if argkind == BOOLEAN:
+ # XXX for now, let bools be handelt like int
+ dcop_add(params, int(arg), 'int')
+
+ elif argkind in (INTEGER, FLOAT):
+ dcop_add(params, arg, argtype)
+
+ elif argkind == STRING:
+ # convert it to the right string type:
+ if argtype != arg.__class__.__name__:
+ arg = stringTypesDict[argtype](arg)
+
+ dcop_add(params, arg)
+
+ elif argtype.startswith("QMap") or argtype.startswith("QValueList"):
+ dcop_add(params, arg, argtype)
+
+ # XXX:
+ # Is 'isinstance(arg, eval(argtype))' really good?
+ # What if 'argtype' is located in some modul? Like 'qt.QString'.
+ # Then this will fail (but it should not!).
+ # And the worst thing: the eval() will raise a NameError!
+ #
+ # On the other hand 'arg.__class__.__name__ == argtype' has the
+ # disadvantage that it can't be a derived class!
+ #
+ # Would no check at all be better??
+ #
+ # But I doubt a derived class would be ok anyway. I have to check
+ # this in the DCOP-docu, but I think a derived class would not be
+ # correctly unmarshalled, because a derived class could be marshalled
+ # in a total different way to it's super-class.
+ elif arg.__class__.__name__ == argtype:
+ dcop_add(params, arg)
+
+ else:
+ raise TypeError, "expected type %s, got type %s." % (argtype, arg.__class__.__name__)
+
+ return data
+
+ def __unmarshall(self, data, type_):
+ s = QDataStream(data, IO_ReadOnly)
+
+ if str(type_) in stringTypes:
+ return unicode(dcop_next(s, type_))
+ else:
+ return dcop_next(s, type_)