#!/usr/bin/python # # Copyright (C) 2012 Collabora Limited # Copyright (C) 2012 Nokia Corporation # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from sys import argv import xml.dom.minidom import codecs from getopt import gnu_getopt import functools from libtpcodegen import NS_TP, get_descendant_text, get_by_path from libqtcodegen import binding_from_usage, extract_arg_or_member_info, format_docstring, gather_externals, gather_custom_lists, get_headerfile_cmd, get_qt_name, qt_identifier_escape, RefRegistry # TODO generate docstrings def to_lower_camel_case(s): if len(s) <= 1: return s.lower() i = 0 for c in s: if c == '_': break i += 1 ret = s if i == len(s): return s.lower() else: ret = s[0:i].lower() + s[i:] ret = ret.replace('_', '') return ret class Generator(object): def __init__(self, opts): try: self.group = opts.get('--group', '') self.headerfile = opts['--headerfile'] self.implfile = opts['--implfile'] self.namespace = opts['--namespace'] self.typesnamespace = opts['--typesnamespace'] self.realinclude = opts.get('--realinclude', None) self.mocinclude = opts.get('--mocinclude', None) self.prettyinclude = opts.get('--prettyinclude') self.extraincludes = opts.get('--extraincludes', None) self.must_define = opts.get('--must-define', None) self.visibility = opts.get('--visibility', '') ifacedom = xml.dom.minidom.parse(opts['--ifacexml']) specdom = xml.dom.minidom.parse(opts['--specxml']) except KeyError as k: assert False, 'Missing required parameter %s' % k.args[0] if not self.realinclude: self.realinclude = self.headerfile self.hs = [] self.bs = [] self.ifacenodes = ifacedom.getElementsByTagName('node') self.spec, = get_by_path(specdom, "spec") self.custom_lists = gather_custom_lists(self.spec, self.typesnamespace) self.externals = gather_externals(self.spec) self.refs = RefRegistry(self.spec) def __call__(self): # Output info header and includes self.h("""\ /* * This file contains D-Bus adaptor classes generated by qt-svc-gen.py. * * This file can be distributed under the same terms as the specification from * which it was generated. */ """) if self.must_define: self.h('\n') self.h('#ifndef %s\n' % self.must_define) self.h('#error %s\n' % self.must_define) self.h('#endif\n') self.h('\n') if self.extraincludes: for include in self.extraincludes.split(','): self.h('#include %s\n' % include) self.h("""\ #include #include #include #include #include """) if self.must_define: self.b("""#define %s\n""" % (self.must_define)) self.b("""#include "%s" """ % self.realinclude) if self.mocinclude: self.b("""#include "%s" """ % self.mocinclude) self.b("""\ #include #include """) # Begin namespace for ns in self.namespace.split('::'): self.hb("""\ namespace %s { """ % ns) # Output interface proxies def ifacenodecmp(x, y): xname, yname = [self.namespace + '::' + node.getAttribute('name').replace('/', '').replace('_', '') + 'Adaptor' for node in (x, y)] return (xname > yname) - (xname < yname) self.ifacenodes.sort(key=functools.cmp_to_key(ifacenodecmp)) for ifacenode in self.ifacenodes: self.do_ifacenode(ifacenode) # End namespace self.hb(''.join(['\n}' for ns in self.namespace.split('::')])) # Write output to files (codecs.getwriter('utf-8')(open(self.headerfile, 'wb'))).write(''.join(self.hs)) (codecs.getwriter('utf-8')(open(self.implfile, 'wb'))).write(''.join(self.bs)) def do_ifacenode(self, ifacenode): # Extract info name = ifacenode.getAttribute('name').replace('/', '').replace('_', '') + 'Adaptor' iface, = get_by_path(ifacenode, 'interface') dbusname = iface.getAttribute('name') props = get_by_path(iface, 'property') methods = get_by_path(iface, 'method') signals = get_by_path(iface, 'signal') # Begin class, constructors self.h(""" /** * \\class %(name)s %(headercmd)s\ %(groupcmd)s\ * * Adaptor class providing a 1:1 mapping of the D-Bus interface "%(dbusname)s". */ class %(visibility)s %(name)s : public Tp::AbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "%(dbusname)s") Q_CLASSINFO("D-Bus Introspection", "" " \\n" """ % {'name': name, 'headercmd': get_headerfile_cmd(self.realinclude, self.prettyinclude), 'groupcmd': self.group and (' * \\ingroup %s\n' % self.group), 'dbusname': dbusname, 'visibility': self.visibility, }) self.do_introspection(props, methods, signals) self.h("""\ " \\n" "") """) self.do_qprops(props) self.h(""" public: %(name)s(const QDBusConnection& dbusConnection, QObject* adaptee, QObject* parent); virtual ~%(name)s(); """ % {'name': name}) self.do_mic_typedefs(methods) self.b(""" %(name)s::%(name)s(const QDBusConnection& bus, QObject* adaptee, QObject* parent) : Tp::AbstractAdaptor(bus, adaptee, parent) { """ % {'name': name}) self.do_signals_connect(signals) self.b("""\ } %(name)s::~%(name)s() { } """ % {'name': name}) # Properties has_props = False if props: self.h(""" public: // PROPERTIES """) for prop in props: # Skip tp:properties if not prop.namespaceURI: self.do_prop(name, prop) has_props = True # Methods if methods: self.h(""" public Q_SLOTS: // METHODS """) for method in methods: self.do_method(name, method) # Signals if signals: self.h(""" Q_SIGNALS: // SIGNALS """) for signal in signals: self.do_signal(signal) # Close class self.h("""\ }; """) def do_introspection(self, props, methods, signals): self.do_prop_introspection(props) self.do_method_introspection(methods) self.do_signal_introspection(signals) def do_prop_introspection(self, props): for prop in props: if prop.namespaceURI: continue name = prop.getAttribute('name') access = prop.getAttribute('access') sig = prop.getAttribute('type') tptype = prop.getAttributeNS(NS_TP, 'type') binding = binding_from_usage(sig, tptype, self.custom_lists, (sig, tptype) in self.externals, self.typesnamespace) if not binding.custom_type: self.h("""\ " \\n" """ % {'access': access, 'sig': sig, 'name': name, }) else: self.h("""\ " \\n" " \\n" " \\n" """ % {'access': access, 'sig': sig, 'name': name, 'type': binding.val, }) def do_method_introspection(self, methods): for method in methods: name = method.getAttribute('name') args = get_by_path(method, 'arg') argnames, argdocstrings, argbindings = extract_arg_or_member_info(args, self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ') if not argnames: self.h("""\ " \\n" """ % {'name': name}) else: self.h("""\ " \\n" """ % {'name': name}) outindex = 0 inindex = 0 for i in range(len(argnames)): assert argnames[i] != None, 'Name missing from argument at index %d for signal %s' % (i, name) argbinding = argbindings[i] argname = argnames[i] argsig = args[i].getAttribute('type') argdirection = args[i].getAttribute('direction') # QtDBus requires annotating a{sv} if argsig == 'a{sv}': argbinding.custom_type = True if not argbinding.custom_type: self.h("""\ " \\n" """ % {'direction': argdirection, 'sig': argsig, 'name': argname}) else: self.h("""\ " \\n" " \\n" " \\n" """ % {'direction': argdirection, 'sig': argsig, 'name': argname, 'type': argbinding.val, 'index': 'In' + str(inindex) if argdirection == 'in' else 'Out' + str(outindex), }) if argdirection == 'out': outindex += 1 else: inindex += 1 self.h("""\ " \\n" """) def do_signal_introspection(self, signals): for signal in signals: name = signal.getAttribute('name') args = get_by_path(signal, 'arg') argnames, argdocstrings, argbindings = extract_arg_or_member_info(args, self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ') if not argnames: self.h("""\ " \\n" """ % {'name': name}) else: self.h("""\ " \\n" """ % {'name': name}) for i in range(len(argnames)): assert argnames[i] != None, 'Name missing from argument at index %d for signal %s' % (i, name) argbinding = argbindings[i] argname = argnames[i] argsig = args[i].getAttribute('type') if not argbinding.custom_type: self.h("""\ " \\n" """ % {'sig': argsig, 'name': argname}) else: self.h("""\ " \\n" " \\n" " \\n" """ % {'sig': argsig, 'name': argname, 'type': argbinding.val, 'index': i, }) self.h("""\ " \\n" """) def do_mic_typedefs(self, methods): for method in methods: name = method.getAttribute('name') args = get_by_path(method, 'arg') argnames, argdocstrings, argbindings = extract_arg_or_member_info(args, self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ') outargs = [] for i in range(len(args)): if args[i].getAttribute('direction') == 'out': outargs.append(i) if outargs: outargtypes = ', '.join([argbindings[i].val for i in outargs]) else: outargtypes = '' self.h("""\ typedef Tp::MethodInvocationContextPtr< %(outargtypes)s > %(name)sContextPtr; """ % {'name': name, 'outargtypes': outargtypes, }) def do_qprops(self, props): for prop in props: # Skip tp:properties if not prop.namespaceURI: self.do_qprop(prop) def do_qprop(self, prop): name = prop.getAttribute('name') access = prop.getAttribute('access') gettername = name settername = None if 'write' in access: settername = 'Set' + name sig = prop.getAttribute('type') tptype = prop.getAttributeNS(NS_TP, 'type') binding = binding_from_usage(sig, tptype, self.custom_lists, (sig, tptype) in self.externals, self.typesnamespace) self.h("""\ Q_PROPERTY(%(type)s %(name)s %(getter)s %(setter)s) """ % {'type': binding.val, 'name': name, 'getter': 'READ ' + gettername if ('read' in access) else '', 'setter': 'WRITE ' + settername if ('write' in access) else '', }) def do_prop(self, ifacename, prop): name = prop.getAttribute('name') adaptee_name = to_lower_camel_case(prop.getAttribute('tp:name-for-bindings')) access = prop.getAttribute('access') gettername = name settername = None if 'write' in access: settername = 'Set' + name docstring = format_docstring(prop, self.refs, ' * ').replace('*/', '*/') sig = prop.getAttribute('type') tptype = prop.getAttributeNS(NS_TP, 'type') binding = binding_from_usage(sig, tptype, self.custom_lists, (sig, tptype) in self.externals, self.typesnamespace) if 'read' in access: self.h("""\ /** * Return the value of the exported D-Bus object property \\c %(name)s of type \\c %(type)s. * * Adaptees should export this property as a Qt property named * '%(adaptee_name)s' with type %(type)s. * %(docstring)s\ * * \\return The value of exported property \\c %(name)s. */ %(type)s %(gettername)s() const; """ % {'name': name, 'adaptee_name': adaptee_name, 'docstring': docstring, 'type': binding.val, 'gettername': gettername, }) self.b(""" %(type)s %(ifacename)s::%(gettername)s() const { return qvariant_cast< %(type)s >(adaptee()->property("%(adaptee_name)s")); } """ % {'type': binding.val, 'ifacename': ifacename, 'gettername': gettername, 'adaptee_name': adaptee_name, }) if 'write' in access: self.h("""\ /** * Set the value of the exported D-Bus object property \\c %(name)s of type \\c %(type)s. * * Adaptees should export this property as a writable Qt property named * '%(adaptee_name)s' with type %(type)s. * %(docstring)s\ */ void %(settername)s(const %(type)s &newValue); """ % {'name': name, 'adaptee_name': adaptee_name, 'docstring': docstring, 'settername': settername, 'type': binding.val, }) self.b(""" void %(ifacename)s::%(settername)s(const %(type)s &newValue) { adaptee()->setProperty("%(adaptee_name)s", QVariant::fromValue(newValue)); } """ % {'ifacename': ifacename, 'settername': settername, 'type': binding.val, 'adaptee_name': adaptee_name, }) def do_method(self, ifacename, method): name = method.getAttribute('name') adaptee_name = to_lower_camel_case(method.getAttribute('tp:name-for-bindings')) args = get_by_path(method, 'arg') argnames, argdocstrings, argbindings = extract_arg_or_member_info(args, self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ') docstring = format_docstring(method, self.refs, ' * ').replace('*/', '*/') inargs = [] outargs = [] for i in range(len(args)): if args[i].getAttribute('direction') == 'out': outargs.append(i) else: inargs.append(i) assert argnames[i] != None, 'No argument name for input argument at index %d for method %s' % (i, name) if outargs: rettype = argbindings[outargs[0]].val else: rettype = 'void' params = [argbindings[i].inarg + ' ' + argnames[i] for i in inargs] params.append('const QDBusMessage& dbusMessage') params += [argbindings[i].outarg + ' ' + argnames[i] for i in outargs[1:]] params = ', '.join(params) if outargs: outargtypes = ', '.join([argbindings[i].val for i in outargs]) else: outargtypes = '' invokemethodargs = ', '.join(['Q_ARG(' + argbindings[i].val + ', ' + argnames[i] + ')' for i in inargs]) inparams = [argbindings[i].val for i in inargs] inparams.append("%s::%s::%sContextPtr" % (self.namespace, ifacename, name)) normalized_adaptee_params = ','.join(inparams) adaptee_params = [argbindings[i].inarg + ' ' + argnames[i] for i in inargs] adaptee_params.append('const %(namespace)s::%(ifacename)s::%(name)sContextPtr &context' % {'namespace': self.namespace, 'ifacename': ifacename, 'name': name}) adaptee_params = ', '.join(adaptee_params) self.h("""\ /** * Begins a call to the exported D-Bus method \\c %(name)s on this object. * * Adaptees should export this method as a Qt slot with the following signature: * void %(adaptee_name)s(%(adaptee_params)s); * * Implementations should call MethodInvocationContext::setFinished (or setFinishedWithError * accordingly) on the received \\a context object once the method has finished processing. * %(docstring)s\ * """ % {'name': name, 'adaptee_name': adaptee_name, 'adaptee_params': adaptee_params, 'rettype': rettype, 'docstring': docstring }) for i in inargs: if argdocstrings[i]: self.h("""\ * \\param %s %s\ """ % (argnames[i], argdocstrings[i])) for i in outargs[1:]: if argdocstrings[i]: self.h("""\ * \\param %s Output parameter %s\ """ % (argnames[i], argdocstrings[i])) if outargs: self.h("""\ * \\return %s\ """ % argdocstrings[outargs[0]]) self.h("""\ */ %(rettype)s %(name)s(%(params)s); """ % {'rettype': rettype, 'name': name, 'params': params }) self.b(""" %(rettype)s %(ifacename)s::%(name)s(%(params)s) { if (adaptee()->metaObject()->indexOfMethod("%(adaptee_name)s(%(normalized_adaptee_params)s)") < 0) { dbusConnection().send(dbusMessage.createErrorReply(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"))); """ % {'rettype': rettype, 'ifacename': ifacename, 'name': name, 'adaptee_name': adaptee_name, 'normalized_adaptee_params': normalized_adaptee_params, 'params': params, }) if rettype != 'void': self.b("""\ return %(rettype)s(); """ % {'rettype': rettype}) else: self.b("""\ return; """) self.b("""\ } %(name)sContextPtr ctx = %(name)sContextPtr( new Tp::MethodInvocationContext< %(outargtypes)s >(dbusConnection(), dbusMessage)); """ % {'name': name, 'outargtypes': outargtypes, }) if invokemethodargs: self.b("""\ QMetaObject::invokeMethod(adaptee(), "%(adaptee_name)s", %(invokemethodargs)s, Q_ARG(%(namespace)s::%(ifacename)s::%(name)sContextPtr, ctx)); """ % {'namespace': self.namespace, 'ifacename': ifacename, 'name': name, 'adaptee_name': adaptee_name, 'invokemethodargs': invokemethodargs, }) else: self.b("""\ QMetaObject::invokeMethod(adaptee(), "%(lname)s", Q_ARG(%(namespace)s::%(ifacename)s::%(name)sContextPtr, ctx)); """ % {'namespace': self.namespace, 'ifacename': ifacename, 'name': name, 'lname': (name[0].lower() + name[1:]), }) if rettype != 'void': self.b("""\ return %(rettype)s(); """ % {'rettype': rettype}) self.b("}\n") def do_signal(self, signal): name = signal.getAttribute('name') adaptee_name = to_lower_camel_case(signal.getAttribute('tp:name-for-bindings')) argnames, argdocstrings, argbindings = extract_arg_or_member_info(get_by_path(signal, 'arg'), self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ') params = ', '.join(['%s %s' % (binding.inarg, param_name) for binding, param_name in zip(argbindings, argnames)]) for i in range(len(argnames)): assert argnames[i] != None, 'Name missing from argument at index %d for signal %s' % (i, name) self.h("""\ /** * Represents the exported D-Bus signal \\c %(name)s on this object. * * Adaptees should export this signal as a Qt signal with the following signature: * void %(adaptee_name)s(%(params)s); * * The adaptee signal will be automatically relayed as a D-Bus signal once emitted. * """ % {'name': name, 'adaptee_name': adaptee_name, 'params': params }) for i in range(len(argnames)): assert argnames[i] != None, 'Name missing from argument at index %d for signal %s' % (i, name) if argdocstrings[i]: self.h("""\ * \\param %s %s\ """ % (argnames[i], argdocstrings[i])) self.h("""\ */ void %(name)s(%(params)s); """ % {'name': name, 'params': params }) def do_signals_connect(self, signals): for signal in signals: name = signal.getAttribute('name') adaptee_name = to_lower_camel_case(signal.getAttribute('tp:name-for-bindings')) _, _, argbindings = extract_arg_or_member_info(get_by_path(signal, 'arg'), self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ') self.b("""\ connect(adaptee, SIGNAL(%(adaptee_name)s(%(params)s)), SIGNAL(%(name)s(%(params)s))); """ % {'name': name, 'adaptee_name': adaptee_name, 'params': ', '.join([binding.inarg for binding in argbindings]) }) def h(self, str): self.hs.append(str) def b(self, str): self.bs.append(str) def hb(self, str): self.h(str) self.b(str) if __name__ == '__main__': options, argv = gnu_getopt(argv[1:], '', ['group=', 'headerfile=', 'implfile=', 'namespace=', 'typesnamespace=', 'realinclude=', 'mocinclude=', 'prettyinclude=', 'extraincludes=', 'must-define=', 'visibility=', 'ifacexml=', 'specxml=']) Generator(dict(options))()