File indexing completed on 2024-04-21 05:01:51

0001 #!/usr/bin/python
0002 #
0003 # Copyright (C) 2008 Collabora Limited <http://www.collabora.co.uk>
0004 # Copyright (C) 2008 Nokia Corporation
0005 #
0006 # This library is free software; you can redistribute it and/or
0007 # modify it under the terms of the GNU Lesser General Public
0008 # License as published by the Free Software Foundation; either
0009 # version 2.1 of the License, or (at your option) any later version.
0010 #
0011 # This library is distributed in the hope that it will be useful,
0012 # but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014 # Lesser General Public License for more details.
0015 #
0016 # You should have received a copy of the GNU Lesser General Public
0017 # License along with this library; if not, write to the Free Software
0018 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0019 
0020 from sys import argv
0021 import xml.dom.minidom
0022 import codecs
0023 from getopt import gnu_getopt
0024 import functools
0025 
0026 from libtpcodegen import NS_TP, get_descendant_text, get_by_path
0027 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
0028 
0029 class Generator(object):
0030     def __init__(self, opts):
0031         try:
0032             self.group = opts.get('--group', '')
0033             self.headerfile = opts['--headerfile']
0034             self.implfile = opts['--implfile']
0035             self.namespace = opts['--namespace']
0036             self.typesnamespace = opts['--typesnamespace']
0037             self.realinclude = opts['--realinclude']
0038             self.prettyinclude = opts.get('--prettyinclude')
0039             self.extraincludes = opts.get('--extraincludes', None)
0040             self.mainiface = opts.get('--mainiface', None)
0041             self.must_define = opts.get('--must-define', None)
0042             self.dbus_proxy = opts.get('--dbus-proxy',
0043                     'Tp::DBusProxy')
0044             self.visibility = opts.get('--visibility', '')
0045             ifacedom = xml.dom.minidom.parse(opts['--ifacexml'])
0046             specdom = xml.dom.minidom.parse(opts['--specxml'])
0047         except KeyError as k:
0048             assert False, 'Missing required parameter %s' % k.args[0]
0049 
0050         self.hs = []
0051         self.bs = []
0052         self.ifacenodes = ifacedom.getElementsByTagName('node')
0053         self.spec, = get_by_path(specdom, "spec")
0054         self.custom_lists = gather_custom_lists(self.spec, self.typesnamespace)
0055         self.externals = gather_externals(self.spec)
0056         self.refs = RefRegistry(self.spec)
0057 
0058     def __call__(self):
0059         # Output info header and includes
0060         self.h("""\
0061 /*
0062  * This file contains D-Bus client proxy classes generated by qt-client-gen.py.
0063  *
0064  * This file can be distributed under the same terms as the specification from
0065  * which it was generated.
0066  */
0067 """)
0068 
0069         if self.must_define:
0070             self.h('\n')
0071             self.h('#ifndef %s\n' % self.must_define)
0072             self.h('#error %s\n' % self.must_define)
0073             self.h('#endif\n')
0074 
0075         self.h('\n')
0076 
0077         if self.extraincludes:
0078             for include in self.extraincludes.split(','):
0079                 self.h('#include %s\n' % include)
0080 
0081         self.h("""
0082 #include <QtGlobal>
0083 
0084 #include <QString>
0085 #include <QObject>
0086 #include <QVariant>
0087 
0088 #include <QDBusPendingReply>
0089 
0090 #include <TelepathyQt/AbstractInterface>
0091 #include <TelepathyQt/DBusProxy>
0092 #include <TelepathyQt/Global>
0093 
0094 namespace Tp
0095 {
0096 class PendingVariant;
0097 class PendingOperation;
0098 }
0099 
0100 """)
0101 
0102         if self.must_define:
0103             self.b("""#define %s\n""" % (self.must_define))
0104 
0105         self.b("""#include "%s"
0106 
0107 """ % self.realinclude)
0108 
0109         # Begin namespace
0110         for ns in self.namespace.split('::'):
0111             self.hb("""\
0112 namespace %s
0113 {
0114 """ % ns)
0115 
0116         # Output interface proxies
0117         def ifacenodecmp(x, y):
0118             xname, yname = [self.namespace + '::' + node.getAttribute('name').replace('/', '').replace('_', '') + 'Interface' for node in (x, y)]
0119 
0120             if xname == self.mainiface:
0121                 return -1
0122             elif yname == self.mainiface:
0123                 return 1
0124             else:
0125                 return (xname > yname) - (xname < yname)
0126 
0127         self.ifacenodes.sort(key=functools.cmp_to_key(ifacenodecmp))
0128         for ifacenode in self.ifacenodes:
0129             self.do_ifacenode(ifacenode)
0130 
0131         # End namespace
0132         self.hb(''.join(['}\n' for ns in self.namespace.split('::')]))
0133 
0134         # Add metatype declaration - otherwise QTBUG #2151 might be triggered
0135         for ifacenode in self.ifacenodes:
0136             classname = ifacenode.getAttribute('name').replace('/', '').replace('_', '') + 'Interface'
0137             self.h("Q_DECLARE_METATYPE(" + self.namespace + "::" + classname + "*)\n")
0138 
0139         # Write output to files
0140         (codecs.getwriter('utf-8')(open(self.headerfile, 'wb'))).write(''.join(self.hs))
0141         (codecs.getwriter('utf-8')(open(self.implfile, 'wb'))).write(''.join(self.bs))
0142 
0143     def do_ifacenode(self, ifacenode):
0144         # Extract info
0145         name = ifacenode.getAttribute('name').replace('/', '').replace('_', '') + 'Interface'
0146         iface, = get_by_path(ifacenode, 'interface')
0147         dbusname = iface.getAttribute('name')
0148 
0149         # Begin class, constructors
0150         self.h("""
0151 /**
0152  * \\class %(name)s
0153 %(headercmd)s\
0154 %(groupcmd)s\
0155  *
0156  * Proxy class providing a 1:1 mapping of the D-Bus interface "%(dbusname)s".
0157  */
0158 class %(visibility)s %(name)s : public Tp::AbstractInterface
0159 {
0160     Q_OBJECT
0161 
0162 public:
0163     /**
0164      * Returns the name of the interface "%(dbusname)s", which this class
0165      * represents.
0166      *
0167      * \\return The D-Bus interface name.
0168      */
0169     static inline QLatin1String staticInterfaceName()
0170     {
0171         return QLatin1String("%(dbusname)s");
0172     }
0173 
0174     /**
0175      * Creates a %(name)s associated with the given object on the session bus.
0176      *
0177      * \\param busName Name of the service the object is on.
0178      * \\param objectPath Path to the object on the service.
0179      * \\param parent Passed to the parent class constructor.
0180      */
0181     %(name)s(
0182         const QString& busName,
0183         const QString& objectPath,
0184         QObject* parent = 0
0185     );
0186 
0187     /**
0188      * Creates a %(name)s associated with the given object on the given bus.
0189      *
0190      * \\param connection The bus via which the object can be reached.
0191      * \\param busName Name of the service the object is on.
0192      * \\param objectPath Path to the object on the service.
0193      * \\param parent Passed to the parent class constructor.
0194      */
0195     %(name)s(
0196         const QDBusConnection& connection,
0197         const QString& busName,
0198         const QString& objectPath,
0199         QObject* parent = 0
0200     );
0201 """ % {'name' : name,
0202        'headercmd' : get_headerfile_cmd(self.realinclude, self.prettyinclude),
0203        'groupcmd' : self.group and (' * \\ingroup %s\n' % self.group),
0204        'dbusname' : dbusname,
0205        'visibility': self.visibility,
0206        })
0207 
0208         self.b("""
0209 %(name)s::%(name)s(const QString& busName, const QString& objectPath, QObject *parent)
0210     : Tp::AbstractInterface(busName, objectPath, staticInterfaceName(), QDBusConnection::sessionBus(), parent)
0211 {
0212 }
0213 
0214 %(name)s::%(name)s(const QDBusConnection& connection, const QString& busName, const QString& objectPath, QObject *parent)
0215     : Tp::AbstractInterface(busName, objectPath, staticInterfaceName(), connection, parent)
0216 {
0217 }
0218 """ % {'name' : name})
0219 
0220         # Construct from DBusProxy subclass
0221         self.h("""
0222     /**
0223      * Creates a %(name)s associated with the same object as the given proxy.
0224      *
0225      * \\param proxy The proxy to use. It will also be the QObject::parent()
0226      *               for this object.
0227      */
0228     %(name)s(%(dbus_proxy)s *proxy);
0229 """ % {'name' : name,
0230        'dbus_proxy' : self.dbus_proxy})
0231 
0232         self.b("""
0233 %(name)s::%(name)s(%(dbus_proxy)s *proxy)
0234     : Tp::AbstractInterface(proxy, staticInterfaceName())
0235 {
0236 }
0237 """ % {'name' : name,
0238        'dbus_proxy' : self.dbus_proxy})
0239 
0240         # Main interface
0241         mainiface = self.mainiface or 'Tp::AbstractInterface'
0242 
0243         if mainiface != self.namespace + '::' + name:
0244             self.h("""
0245     /**
0246      * Creates a %(name)s associated with the same object as the given proxy.
0247      * Additionally, the created proxy will have the same parent as the given
0248      * proxy.
0249      *
0250      * \\param mainInterface The proxy to use.
0251      */
0252     explicit %(name)s(const %(mainiface)s& mainInterface);
0253 
0254     /**
0255      * Creates a %(name)s associated with the same object as the given proxy.
0256      * However, a different parent object can be specified.
0257      *
0258      * \\param mainInterface The proxy to use.
0259      * \\param parent Passed to the parent class constructor.
0260      */
0261     %(name)s(const %(mainiface)s& mainInterface, QObject* parent);
0262 """ % {'name' : name,
0263        'mainiface' : mainiface})
0264 
0265             self.b("""
0266 %(name)s::%(name)s(const %(mainiface)s& mainInterface)
0267     : Tp::AbstractInterface(mainInterface.service(), mainInterface.path(), staticInterfaceName(), mainInterface.connection(), mainInterface.parent())
0268 {
0269 }
0270 
0271 %(name)s::%(name)s(const %(mainiface)s& mainInterface, QObject *parent)
0272     : Tp::AbstractInterface(mainInterface.service(), mainInterface.path(), staticInterfaceName(), mainInterface.connection(), parent)
0273 {
0274 }
0275 """ % {'name' : name,
0276        'mainiface' : mainiface})
0277 
0278         # Properties
0279         has_props = False
0280         for prop in get_by_path(iface, 'property'):
0281             # Skip tp:properties
0282             if not prop.namespaceURI:
0283                 self.do_prop(prop)
0284                 has_props = True
0285 
0286         self.h("""
0287     /**
0288      * Request all of the DBus properties on the interface.
0289      *
0290      * \\return A pending variant map which will emit finished when the properties have
0291      *          been retrieved.
0292      */
0293     Tp::PendingVariantMap *requestAllProperties() const
0294     {
0295         return internalRequestAllProperties();
0296     }
0297 """)
0298 
0299         # Methods
0300         methods = get_by_path(iface, 'method')
0301 
0302         if methods:
0303             self.h("""
0304 public Q_SLOTS:\
0305 """)
0306 
0307             for method in methods:
0308                 self.do_method(method)
0309 
0310         # Signals
0311         signals = get_by_path(iface, 'signal')
0312 
0313         if signals:
0314             self.h("""
0315 Q_SIGNALS:\
0316 """)
0317 
0318             for signal in signals:
0319                 self.do_signal(signal)
0320 
0321         # invalidated handler (already a slot in the superclass)
0322         # we can't just use disconnect(this, NULL, NULL, NULL) because
0323         # (a) that would disconnect QObject::destroyed() and other non-D-Bus
0324         # signals, and (b) QtDBus doesn't support that usage anyway (it needs
0325         # specific signals in order to remove its signal match rules)
0326         self.h("""
0327 protected:
0328     virtual void invalidate(Tp::DBusProxy *, const QString &, const QString &);
0329 """)
0330 
0331         self.b("""
0332 void %(name)s::invalidate(Tp::DBusProxy *proxy,
0333         const QString &error, const QString &message)
0334 {
0335 """ % {'name' : name})
0336 
0337         for signal in signals:
0338             self.do_signal_disconnect(signal)
0339 
0340         self.b("""
0341     Tp::AbstractInterface::invalidate(proxy, error, message);
0342 }
0343 """)
0344 
0345         # Close class
0346         self.h("""\
0347 };
0348 """)
0349 
0350     def do_prop(self, prop):
0351         name = prop.getAttribute('name')
0352         access = prop.getAttribute('access')
0353         gettername = name
0354         settername = None
0355         docstring = format_docstring(prop, self.refs, '     * ').replace('*/', '&#42;&#47;')
0356 
0357         sig = prop.getAttribute('type')
0358         tptype = prop.getAttributeNS(NS_TP, 'type')
0359         binding = binding_from_usage(sig, tptype, self.custom_lists, (sig, tptype) in self.externals, self.typesnamespace)
0360 
0361         if 'write' in access:
0362             settername = 'set' + name
0363 
0364         if 'read' in access:
0365             self.h("""
0366     /**
0367      * Asynchronous getter for the remote object property \\c %(name)s of type \\c %(val)s.
0368      *
0369 %(docstring)s\
0370      *
0371      * \\return A pending variant which will emit finished when the property has been
0372      *          retrieved.
0373      */
0374     inline Tp::PendingVariant *%(gettername)s() const
0375     {
0376         return internalRequestProperty(QLatin1String("%(name)s"));
0377     }
0378 """ % {'name' : name,
0379        'docstring' : docstring,
0380        'val' : binding.val,
0381        'gettername' : 'requestProperty' + name})
0382 
0383         if 'write' in access:
0384             self.h("""
0385     /**
0386      * Asynchronous setter for the remote object property \\c %(name)s of type \\c %(type)s.
0387      *
0388 %(docstring)s\
0389      *
0390      * \\return A pending operation which will emit finished when the property has been
0391      *          set.
0392      */
0393     inline Tp::PendingOperation *%(settername)s(%(type)s newValue)
0394     {
0395         return internalSetProperty(QLatin1String("%(name)s"), QVariant::fromValue(newValue));
0396     }
0397 """ % {'name' : name,
0398        'docstring' : docstring,
0399        'type' : binding.val,
0400        'name' : name,
0401        'settername' : 'setProperty' + name})
0402 
0403     def do_method(self, method):
0404         name = method.getAttribute('name')
0405         args = get_by_path(method, 'arg')
0406         argnames, argdocstrings, argbindings = extract_arg_or_member_info(args, self.custom_lists,
0407                 self.externals, self.typesnamespace, self.refs, '     *     ')
0408 
0409         inargs = []
0410         outargs = []
0411 
0412         for i in range(len(args)):
0413             if args[i].getAttribute('direction') == 'out':
0414                 outargs.append(i)
0415             else:
0416                 inargs.append(i)
0417                 assert argnames[i] != None, 'No argument name for input argument at index %d for method %s' % (i, name)
0418 
0419         rettypes = ', '.join([argbindings[i].val for i in outargs])
0420         params = ', '.join([argbindings[i].inarg + ' ' + argnames[i] for i in inargs])
0421         if params:
0422             params += ', int timeout = -1'
0423         else:
0424             params = 'int timeout = -1'
0425 
0426         self.h("""
0427     /**
0428      * Begins a call to the D-Bus method \\c %s on the remote object.
0429 %s\
0430      *
0431      * Note that \\a timeout is ignored as of now. It will be used once
0432      * http://bugreports.qt.nokia.com/browse/QTBUG-11775 is fixed.
0433      *
0434 """ % (name, format_docstring(method, self.refs, '     * ')))
0435 
0436         for i in inargs:
0437             if argdocstrings[i]:
0438                 self.h("""\
0439      *
0440      * \\param %s
0441 %s\
0442 """ % (argnames[i], argdocstrings[i]))
0443 
0444         self.h("""\
0445      * \\param timeout The timeout in milliseconds.
0446 """)
0447 
0448         for i in outargs:
0449             if argdocstrings[i]:
0450                 self.h("""\
0451      *
0452      * \\return
0453 %s\
0454 """ % argdocstrings[i])
0455 
0456         self.h("""\
0457      */
0458     inline QDBusPendingReply<%(rettypes)s> %(name)s(%(params)s)
0459     {
0460         if (!invalidationReason().isEmpty()) {
0461             return QDBusPendingReply<%(rettypes)s>(QDBusMessage::createError(
0462                 invalidationReason(),
0463                 invalidationMessage()
0464             ));
0465         }
0466 """ % {'rettypes' : rettypes,
0467        'name' : name,
0468        'params' : params})
0469 
0470         if inargs:
0471             self.h("""
0472         QDBusMessage callMessage = QDBusMessage::createMethodCall(this->service(), this->path(),
0473                 this->staticInterfaceName(), QLatin1String("%s"));
0474         callMessage << %s;
0475         return this->connection().asyncCall(callMessage, timeout);
0476     }
0477 """ % (name, ' << '.join(['QVariant::fromValue(%s)' % argnames[i] for i in inargs])))
0478         else:
0479             self.h("""
0480         QDBusMessage callMessage = QDBusMessage::createMethodCall(this->service(), this->path(),
0481                 this->staticInterfaceName(), QLatin1String("%s"));
0482         return this->connection().asyncCall(callMessage, timeout);
0483     }
0484 """ % name)
0485 
0486     def do_signal(self, signal):
0487         name = signal.getAttribute('name')
0488         argnames, argdocstrings, argbindings = extract_arg_or_member_info(get_by_path(signal,
0489             'arg'), self.custom_lists, self.externals, self.typesnamespace, self.refs, '     *     ')
0490 
0491         self.h("""
0492     /**
0493      * Represents the signal \\c %s on the remote object.
0494 %s\
0495 """ % (name, format_docstring(signal, self.refs, '     * ')))
0496 
0497         for i in range(len(argnames)):
0498             assert argnames[i] != None, 'Name missing from argument at index %d for signal %s' % (i, name)
0499             if argdocstrings[i]:
0500                 self.h("""\
0501      *
0502      * \\param %s
0503 %s\
0504 """ % (argnames[i], argdocstrings[i]))
0505 
0506         self.h("""\
0507      */
0508     void %s(%s);
0509 """ % (name, ', '.join(['%s %s' % (binding.inarg, name) for binding, name in zip(argbindings, argnames)])))
0510 
0511     def do_signal_disconnect(self, signal):
0512         name = signal.getAttribute('name')
0513         _, _, argbindings = extract_arg_or_member_info(get_by_path(signal, 'arg'),
0514                 self.custom_lists, self.externals, self.typesnamespace, self.refs, '     *     ')
0515 
0516         self.b("""\
0517     disconnect(this, SIGNAL(%s(%s)), NULL, NULL);
0518 """ % (name, ', '.join([binding.inarg for binding in argbindings])))
0519 
0520     def h(self, str):
0521         self.hs.append(str)
0522 
0523     def b(self, str):
0524         self.bs.append(str)
0525 
0526     def hb(self, str):
0527         self.h(str)
0528         self.b(str)
0529 
0530 
0531 if __name__ == '__main__':
0532     options, argv = gnu_getopt(argv[1:], '',
0533             ['group=',
0534              'namespace=',
0535              'typesnamespace=',
0536              'headerfile=',
0537              'implfile=',
0538              'ifacexml=',
0539              'specxml=',
0540              'realinclude=',
0541              'prettyinclude=',
0542              'extraincludes=',
0543              'mainiface=',
0544              'must-define=',
0545              'dbus-proxy=',
0546              'visibility='])
0547 
0548     Generator(dict(options))()