diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-08-16 09:06:37 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-08-16 09:06:37 +0000 |
commit | 39d98386f72c65826e162e3e8fd36752ec469252 (patch) | |
tree | 5cec746207c4c892d064beafca1de94568a3aeb9 /extensions/dcopext.py | |
download | pytde-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.py | 723 |
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_) |