From 6bc530b6c88a7bd457c8d546e07f4205cc903909 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 21 Oct 2013 17:09:59 +0100 Subject: [PATCH 03/16] Add TpExportable, an introspectable way for mixins to implement methods The main problems with our current mixins, from an introspection point of view, are: * needing to be able to call functions during class initialization; * needing to be able to implement a parent object's D-Bus methods, currently done with G_IMPLEMENT_INTERFACE, which is not introspectable The first can be avoided by calling similar functions during instance initialization, if necessary. The second is somewhat harder, but we can use a signal as our hook. --- docs/reference/telepathy-glib-docs.sgml | 1 + docs/reference/telepathy-glib-sections.txt | 18 +++ telepathy-glib/Makefile.am | 2 + telepathy-glib/codegen.am | 1 + telepathy-glib/exportable.c | 207 +++++++++++++++++++++++++++++ telepathy-glib/exportable.h | 85 ++++++++++++ telepathy-glib/introspection.am | 1 + tools/glib-ginterface-gen.py | 99 ++++++++++---- 8 files changed, 388 insertions(+), 26 deletions(-) create mode 100644 telepathy-glib/exportable.c create mode 100644 telepathy-glib/exportable.h diff --git a/docs/reference/telepathy-glib-docs.sgml b/docs/reference/telepathy-glib-docs.sgml index fe4bb90..cd90562 100644 --- a/docs/reference/telepathy-glib-docs.sgml +++ b/docs/reference/telepathy-glib-docs.sgml @@ -146,6 +146,7 @@ Service-side implementation + diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt index d8f3a15..88bad34 100644 --- a/docs/reference/telepathy-glib-sections.txt +++ b/docs/reference/telepathy-glib-sections.txt @@ -7531,3 +7531,21 @@ tp_tls_certificate_rejection_get_type TpTLSCertificateRejectionPriv + +
+telepathy-glib/telepathy-glib.h +exportable +exportable +TpExportable +TpExportableInterface +TpExportableInvocation +tp_exportable_call_method +tp_exportable_invocation_from_dbus_glib +tp_exportable_invocation_to_dbus_glib + +TP_EXPORTABLE +TP_EXPORTABLE_GET_INTERFACE +TP_IS_EXPORTABLE +TP_TYPE_EXPORTABLE +tp_exportable_get_type +
diff --git a/telepathy-glib/Makefile.am b/telepathy-glib/Makefile.am index ce60d7d..51523e5 100644 --- a/telepathy-glib/Makefile.am +++ b/telepathy-glib/Makefile.am @@ -84,6 +84,7 @@ our_headers = \ dtmf.h \ enums.h \ errors.h \ + exportable.h \ exportable-channel.h \ file-transfer-channel.h \ gnio-util.h \ @@ -258,6 +259,7 @@ libtelepathy_glib_internal_la_SOURCES = \ interfaces.c \ debug-internal.h \ errors.c \ + exportable.c \ exportable-channel.c \ file-transfer-channel.c \ gnio-util.c \ diff --git a/telepathy-glib/codegen.am b/telepathy-glib/codegen.am index a564a2a..7cafc7c 100644 --- a/telepathy-glib/codegen.am +++ b/telepathy-glib/codegen.am @@ -216,6 +216,7 @@ _gen/tp-svc-%.c: _gen/tp-spec-%.xml \ $(tools_dir)/glib-ginterface-gen.py \ codegen.am $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \ + --tp-api=0.23 \ --filename=_gen/tp-svc-$* \ --signal-marshal-prefix=_tp \ --include='' \ diff --git a/telepathy-glib/exportable.c b/telepathy-glib/exportable.c new file mode 100644 index 0000000..b610eac --- /dev/null +++ b/telepathy-glib/exportable.c @@ -0,0 +1,207 @@ +/* + * Glue for D-Bus interface implementors + * + * Copyright © 2013 Collabora Ltd. + * + * 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 + */ + +#include +#include + +#define DEBUG_FLAG TP_DEBUG_MISC +#include "telepathy-glib/debug-internal.h" + +/** + * SECTION:exportable + * @title: TpExportable + * @short_description: interface to allow mixins to export objects on D-Bus + * + * This interface provides a mechanism for introspectable mixins to export + * objects on D-Bus using dbus-glib or, eventually, GDBus. + */ + +/** + * TpExportable: + * + * Typedef representing an implementation of #TpExportableInterface. + */ + +/** + * TpExportableInterface: + * @parent: The parent interface + * @call_method: Default handler for #TpExportable::call-method. + * There is a default implementation, which returns %FALSE. + * + * The interface for #TpExportable objects. + */ + +G_DEFINE_INTERFACE (TpExportable, tp_exportable, G_TYPE_OBJECT) + +enum { + S_CALL_METHOD, + N_SIGNALS +}; +static guint signals[N_SIGNALS] = { 0 }; + +static gboolean +tp_exportable_stub_call_method (TpExportable *self, + const gchar *interface, + const gchar *method, + GVariant *parameters, + TpExportableInvocation *context) +{ + DEBUG ("called"); + /* unhandled */ + return FALSE; +} + +static void +tp_exportable_default_init (TpExportableInterface *iface) +{ + /** + * TpExportable::call-method: + * @self: the exportable object + * @object_path: the path at which this object was exported + * @interface_name: the interface on which the method was called + * @method_name: the method that was called + * @parameters: parameters for the method + * @invocation: used to reply to the method call if %TRUE is returned + * + * Emitted when a D-Bus method is to be handled by a mixin. + * The detail string for this signal is the interface name, followed + * by ".", followed by the method name. + * + * Returns: %TRUE if this implementation will reply to the method call + * by using @invocation; %FALSE if this implementation does not + * implement that method. + */ + signals[S_CALL_METHOD] = g_signal_new ("call-method", + G_OBJECT_CLASS_TYPE (iface), + G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TpExportableInterface, call_method), + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 4, + G_TYPE_STRING, /* arg 1: interface name */ + G_TYPE_STRING, /* arg 2: method name */ + G_TYPE_VARIANT, /* arg 3: parameters */ + G_TYPE_POINTER); /* arg 4: invocation */ + + iface->call_method = tp_exportable_stub_call_method; +} + +/** + * TpExportableInvocation: + * + * A data structure representing a method call which has not yet had a reply. + * Analogous to #GDBusMethodInvocation. + */ + +/** + * tp_exportable_call_method: + * @self: an exportable object + * @interface_name: the D-Bus interface name + * @method_name: the D-Bus method name + * @parameters: (allow-none): parameters for the method, encapsulated in + * a tuple, or %NULL as a synonym for the empty tuple + * @invocation: used to send the reply if %TRUE is returned + * + * Call a method on this object. + * + * Returns: %TRUE if a mixin or other user of #TpExportable has taken + * responsibility for replying to the method call, %FALSE if the caller + * must still reply + */ +gboolean +tp_exportable_call_method (TpExportable *self, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + TpExportableInvocation *invocation) +{ + gboolean handled; + gchar *detail; + + DEBUG ("%s.%s", interface_name, method_name); + + if (parameters != NULL) + g_variant_ref_sink (parameters); + else + parameters = g_variant_ref_sink (g_variant_new_tuple (NULL, 0)); + + detail = g_strdup_printf ("%s.%s", interface_name, method_name); + + g_signal_emit (self, signals[S_CALL_METHOD], g_quark_from_string (detail), + interface_name, method_name, parameters, invocation, &handled); + g_variant_unref (parameters); + g_free (detail); + + if (!handled) + DEBUG ("... unhandled"); + + return handled; +} + +static const gchar magic[] = "TpExportableInvocation"; + +struct _TpExportableInvocation { + gpointer magic; + DBusGMethodInvocation *context; +}; + +/** + * tp_exportable_invocation_from_dbus_glib: (skip) + * @context: a dbus-glib #DBusGMethodInvocation + * + * Create a new #TpExportableInvocation which wraps @context. + * + * Returns: a new #TpExportableInvocation which is responsible for + * replying to, and freeing, @context. You must not use @context + * after calling this function. + */ +TpExportableInvocation * +tp_exportable_invocation_from_dbus_glib (DBusGMethodInvocation *context) +{ + TpExportableInvocation *ret = g_slice_new0 (TpExportableInvocation); + + ret->magic = &magic; + ret->context = context; + return ret; +} + +/** + * tp_exportable_invocation_to_dbus_glib: (skip) + * @invocation: (transfer full): a #TpExportableInvocation + * + * Convert @invocation back into a dbus-glib #DBusGMethodInvocation. + * The caller becomes responsible for calling dbus_g_method_return() or + * dbus_g_method_return_error(), but must not use @invocation any more. + * + * Returns: the #DBusGMethodInvocation + */ +DBusGMethodInvocation * +tp_exportable_invocation_to_dbus_glib (TpExportableInvocation *invocation) +{ + DBusGMethodInvocation *context; + + g_return_val_if_fail (invocation->magic == &magic, NULL); + g_return_val_if_fail (invocation->context != NULL, NULL); + + context = invocation->context; + invocation->magic = NULL; + invocation->context = NULL; + g_slice_free (TpExportableInvocation, invocation); + return context; +} diff --git a/telepathy-glib/exportable.h b/telepathy-glib/exportable.h new file mode 100644 index 0000000..90b4b72 --- /dev/null +++ b/telepathy-glib/exportable.h @@ -0,0 +1,85 @@ +/* + * Glue for D-Bus interface implementors + * + * Copyright © 2013 Collabora Ltd. + * + * 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 + */ + +#if !defined (_TP_IN_META_HEADER) && !defined (_TP_COMPILATION) +#error "Only and can be included directly." +#endif + +#ifndef TP_EXPORTABLE_H +#define TP_EXPORTABLE_H + +#include + +#include + +#include + +G_BEGIN_DECLS + +#define TP_TYPE_EXPORTABLE (tp_exportable_get_type ()) + +#define TP_EXPORTABLE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TP_TYPE_EXPORTABLE, TpExportable)) + +#define TP_IS_EXPORTABLE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + TP_TYPE_EXPORTABLE)) + +#define TP_EXPORTABLE_GET_INTERFACE(obj) \ + (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \ + TP_TYPE_EXPORTABLE, TpExportableInterface)) + +typedef struct _TpExportable TpExportable; +typedef struct _TpExportableInterface TpExportableInterface; + +typedef struct _TpExportableInvocation TpExportableInvocation; + +struct _TpExportableInterface { + GTypeInterface parent; + + gboolean (*call_method) (TpExportable *self, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + TpExportableInvocation *invocation); +}; + +_TP_AVAILABLE_IN_UNRELEASED +GType tp_exportable_get_type (void); + +_TP_AVAILABLE_IN_UNRELEASED +gboolean tp_exportable_call_method (TpExportable *self, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + TpExportableInvocation *invocation); + +_TP_AVAILABLE_IN_UNRELEASED +TpExportableInvocation *tp_exportable_invocation_from_dbus_glib ( + DBusGMethodInvocation *context); + +_TP_AVAILABLE_IN_UNRELEASED +DBusGMethodInvocation *tp_exportable_invocation_to_dbus_glib ( + TpExportableInvocation *invocation); + +G_END_DECLS + +#endif diff --git a/telepathy-glib/introspection.am b/telepathy-glib/introspection.am index ad23841..c41a7ab 100644 --- a/telepathy-glib/introspection.am +++ b/telepathy-glib/introspection.am @@ -33,6 +33,7 @@ TelepathyGLib_0_12_gir_FILES = \ $(srcdir)/handle.c $(srcdir)/handle.h \ $(srcdir)/handle-channels-context.c $(srcdir)/handle-channels-context.h \ $(srcdir)/dbus-daemon.c $(srcdir)/dbus-daemon.h \ + $(srcdir)/exportable.c $(srcdir)/exportable.h \ $(srcdir)/interfaces.c \ $(srcdir)/intset.c $(srcdir)/intset.h \ $(srcdir)/dbus.c $(srcdir)/dbus.h \ diff --git a/tools/glib-ginterface-gen.py b/tools/glib-ginterface-gen.py index c0ce20d..507d4b5 100644 --- a/tools/glib-ginterface-gen.py +++ b/tools/glib-ginterface-gen.py @@ -27,8 +27,8 @@ import os.path import xml.dom.minidom from libtpcodegen import file_set_contents, key_by_name, u -from libglibcodegen import Signature, type_to_gtype, \ - NS_TP, dbus_gutils_wincaps_to_uscore +from libglibcodegen import (Signature, type_to_gtype, + NS_TP, dbus_gutils_wincaps_to_uscore, copy_into_gvalue) NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" @@ -43,15 +43,31 @@ def get_emits_changed(node): except IndexError: return None +class Argument(object): + def __init__(self, name, dtype): + self.name = name + self.dtype = dtype + ctype, gtype, marshaller, pointer = type_to_gtype(dtype) + self.ctype = ctype + self.gtype = gtype + self.marshaller = marshaller + self.pointer = pointer + + if pointer: + self.const_ctype = 'const ' + ctype + else: + self.const_ctype = ctype + class Generator(object): def __init__(self, dom, prefix, basename, signal_marshal_prefix, headers, end_headers, not_implemented_func, - allow_havoc): + allow_havoc, tp_api): self.dom = dom self.__header = [] self.__body = [] self.__docs = [] + self.tp_api = tuple(map(int, tp_api.split('.'))) assert prefix.endswith('_') assert not signal_marshal_prefix.endswith('_') @@ -490,25 +506,20 @@ class Generator(object): else: name = direction + str(len(out_args)) - ctype, gtype, marshaller, pointer = type_to_gtype(dtype) - - if pointer: - ctype = 'const ' + ctype - - struct = (ctype, name) + arg = Argument(name, dtype) if direction == 'in': - in_args.append(struct) + in_args.append(arg) else: - out_args.append(struct) + out_args.append(arg) # Implementation type declaration (in header, docs separated) self.d('/**') self.d(' * %s:' % impl_name) self.d(' * @self: The object implementing this interface') - for (ctype, name) in in_args: + for arg in in_args: self.d(' * @%s: %s (FIXME, generate documentation)' - % (name, ctype)) + % (arg.name, arg.ctype)) self.d(' * @context: Used to return values or throw an error') self.d(' *') self.d(' * The signature of an implementation of the D-Bus method') @@ -517,8 +528,8 @@ class Generator(object): self.h('typedef void (*%s) (%s%s *self,' % (impl_name, self.Prefix, self.node_name_mixed)) - for (ctype, name) in in_args: - self.h(' %s%s,' % (ctype, name)) + for arg in in_args: + self.h(' %s%s,' % (arg.const_ctype, arg.name)) self.h(' DBusGMethodInvocation *context);') # Class member (in class definition) @@ -528,19 +539,53 @@ class Generator(object): self.b('static void') self.b('%s (%s%s *self,' % (stub_name, self.Prefix, self.node_name_mixed)) - for (ctype, name) in in_args: - self.b(' %s%s,' % (ctype, name)) + for arg in in_args: + self.b(' %s%s,' % (arg.const_ctype, arg.name)) self.b(' DBusGMethodInvocation *context)') self.b('{') self.b(' %s impl = (%s%s_GET_CLASS (self)->%s_cb);' % (impl_name, self.PREFIX_, self.node_name_uc, class_member_name)) self.b('') self.b(' if (impl != NULL)') - tmp = ['self'] + [name for (ctype, name) in in_args] + ['context'] + tmp = ['self'] + [arg.name for arg in in_args] + ['context'] self.b(' {') self.b(' (impl) (%s);' % ',\n '.join(tmp)) + self.b(' return;') self.b(' }') - self.b(' else') + self.b('') + + if self.tp_api >= (0, 23): + self.b(' if (TP_IS_EXPORTABLE (self))') + self.b(' {') + self.b(' GVariantBuilder builder;') + + if in_args: + self.b(' GValue value = G_VALUE_INIT;') + + self.b('') + self.b(' g_variant_builder_init (&builder,') + self.b(' G_VARIANT_TYPE_TUPLE);') + self.b('') + + for arg in in_args: + self.b(' g_value_init (&value, %s);' % arg.gtype) + self.b(' ' + copy_into_gvalue('&value', arg.gtype, + arg.marshaller, arg.name)) + self.b(' g_variant_builder_add_value (&builder,') + self.b(' dbus_g_value_build_g_variant (&value));') + self.b(' g_value_unset (&value);') + self.b('') + + self.b(' if (tp_exportable_call_method ((TpExportable *) self,') + self.b(' "%s",' % self.iface_name) + self.b(' "%s",' % dbus_method_name) + self.b(' g_variant_builder_end (&builder),') + self.b(' tp_exportable_invocation_from_dbus_glib (context)))') + self.b(' return;') + self.b(' }') + self.b('') + + self.b(' /* else */') self.b(' {') if self.not_implemented_func: self.b(' %s (context);' % self.not_implemented_func) @@ -585,9 +630,9 @@ class Generator(object): self.d('/**') self.d(' * %s:' % ret_name) self.d(' * @context: The D-Bus method invocation context') - for (ctype, name) in out_args: + for arg in out_args: self.d(' * @%s: %s (FIXME, generate documentation)' - % (name, ctype)) + % (arg.name, arg.const_ctype)) self.d(' *') self.d(' * Return successfully by calling dbus_g_method_return().') self.d(' * This inline function exists only to provide type-safety.') @@ -595,14 +640,14 @@ class Generator(object): self.d('') tmp = (['DBusGMethodInvocation *context'] + - [ctype + name for (ctype, name) in out_args]) + [arg.const_ctype + arg.name for arg in out_args]) self.h('static inline') self.h('/* this comment is to stop gtkdoc realising this is static */') self.h(('void %s (' % ret_name) + (',\n '.join(tmp)) + ');') self.h('static inline void') self.h(('%s (' % ret_name) + (',\n '.join(tmp)) + ')') self.h('{') - tmp = ['context'] + [name for (ctype, name) in out_args] + tmp = ['context'] + [arg.name for arg in out_args] self.h(' dbus_g_method_return (' + ',\n '.join(tmp) + ');') self.h('}') self.h('') @@ -791,7 +836,7 @@ if __name__ == '__main__': ['filename=', 'signal-marshal-prefix=', 'include=', 'include-end=', 'allow-unstable', - 'not-implemented-func=']) + 'not-implemented-func=', 'tp-api=']) try: prefix = argv[1] @@ -804,6 +849,7 @@ if __name__ == '__main__': end_headers = [] not_implemented_func = '' allow_havoc = False + tp_api = '0.0' for option, value in options: if option == '--filename': @@ -822,11 +868,12 @@ if __name__ == '__main__': not_implemented_func = value elif option == '--allow-unstable': allow_havoc = True - + elif option == '--tp-api': + tp_api = value try: dom = xml.dom.minidom.parse(argv[0]) except IndexError: cmdline_error() Generator(dom, prefix, basename, signal_marshal_prefix, headers, - end_headers, not_implemented_func, allow_havoc)() + end_headers, not_implemented_func, allow_havoc, tp_api)() -- 1.8.4.rc3