You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
724 lines
17 KiB
724 lines
17 KiB
# -*- 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_)
|