telepathy-glib

Implementing GObjects

This section of the manual assumes that you're already familiar with implementing a GObject in the C programming language. If you're not, we recommend reading the FIXME NEED SOME RESOURCES HERE.

The telepathy-glib bindings provide a mechanism for implementing D-Bus interfaces as GInterface interfaces on a GObject. These interfaces begin with the namespace TpSvc… and have GTypes beginning with TP_TYPE_SVC_….

telepathy-glib also provides class mixins that can be used to implement support for functionality such as D-Bus properties and other Telepathy interfaces.

GObjects that implement these interfaces and mixins can be published via D-Bus using the standard dbus-glib function call dbus_g_connection_register_g_object.

12.1.1. Implementing a D-Bus Interface

D-Bus method interfaces are provided by telepathy-glib as regular GInterfaces to be implemented by your class.

For example, we have implemented our own class ExampleObserver which implements the Client and Client.Observer interfaces (also the D-Bus properties mixin, see below). The type definition for this class is presented in Example 12-1.

Example 12-1Defining the Type
G_DEFINE_TYPE_WITH_CODE (ExampleObserver, example_observer, G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
      tp_dbus_properties_mixin_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CLIENT, NULL);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CLIENT_OBSERVER, observer_iface_init);
    );

Complete Source Code

In Example 12-1, interfaces that implement no methods (i.e. Client) do not need to provide initialisation methods and NULL may be passed instead.

Unlike with most GInterfaces, which use a vcall table to map interface methods to implementations, telepathy-glib uses method calls. This is simply to avoid potential ABI problems in the future.

Each method has an "implement" function, of the form tp_svc_interface_name_implement_method_name which accepts a method of the prototype tp_svc_interface_name_method_name_impl. For example, the method call Client.Observer.ObserveChannels is connected using the function tp_svc_client_observer_implement_observe_channels which accepts a function with the prototype of tp_svc_client_observer_observe_channels_impl. This is shown in Example 12-2. Example 12-4 shows the corresponding method implementation.

Example 12-2Initialising the Interface
static void
observer_iface_init (gpointer g_iface, gpointer iface_data)
{
  TpSvcClientObserverClass *klass = (TpSvcClientObserverClass *) g_iface;

  tp_svc_client_observer_implement_observe_channels (klass,
      example_observer_observer_channels);
}

To save excessive typing for interfaces with lots of methods, you can use a temporary IMPLEMENT macro, as demonstrated in Example 12-3 which is functionality identical to Example 12-2.

Example 12-3Initialising the Interface
static void
observer_iface_init (gpointer g_iface, gpointer iface_data)
{
  TpSvcClientObserverClass *klass = (TpSvcClientObserverClass *) g_iface;

#define IMPLEMENT(x) tp_svc_client_observer_implement_##x (klass, \
    example_observer_##x)
  IMPLEMENT (observe_channels);
#undef IMPLEMENT
}

Complete Source Code

Example 12-4Method Implementation for ObserveChannels
static void
example_observer_observe_channels (TpSvcClientObserver   *self,
                                   const char            *account,
                                   const char            *connection,
                                   const GPtrArray       *channels,
                                   const char            *dispatch_op,
                                   const GPtrArray       *requests_satisfied,
                                   GHashTable            *observer_info,
                                   DBusGMethodInvocation *context)
{
  g_print (" > example_observer_observe_channels\n");
  g_print ("     account    = %s\n", account);
  g_print ("     connection = %s\n", connection);
  g_print ("     dispatchop = %s\n", dispatch_op);

  /* channels is of type a(oa{sv}) */
  int i;
  for (i = 0; i < channels->len; i++)
    {
      GValueArray *channel = g_ptr_array_index (channels, i);
      char *path;
      GHashTable *map;

      tp_value_array_unpack (channel, 2,
          &path, &map);

      g_print ("     channel    = %s\n", path);
    }

  tp_svc_client_observer_return_from_observe_channels (context);
}

Complete Source Code

Method implementations may be either synchronous or asynchronous as you desire, however while your mainloop is blocked in a method call your service will be unable to handle any other incoming method calls (or other mainloop events, such as a client UI). When you are ready to return from the method call, call the appropriate return_from function, which will be of the form tp_svc_interface_name_return_from_method_name, e.g. tp_svc_client_observer_return_from_observe_channels (Example 12-4).

12.1.2. Implementing D-Bus Properties (TpDBusPropertiesMixin)

telepathy-glib provides a special class mixin for handling D-Bus properties, TpDBusPropertiesMixin.

Mixins

Mixins are so called because they are mixed into a class to extend its functionality. For example, a class might inherit from GObject or some subclass, but then has the TpDBusPropertiesMixin to add additional functionality without having to inherit from a different class.

It is functionally similar to multiple inheritance, which is not available in GLib.

To add the D-Bus properties mixin to your class, several steps are required:

  • When you register support for the D-Bus properties interface, pass the mixin initialiser, tp_dbus_properties_mixin_iface_init. See Example 12-1.

  • Your class structure needs to include the TpDBusPropertiesMixinClass (Example 12-5).

    Example 12-5Class structure
    typedef struct _ExampleObserverClass ExampleObserverClass;
    struct _ExampleObserverClass
    {
            GObjectClass parent_class;
            TpDBusPropertiesMixinClass dbus_props_class;
    };
    

    Complete Source Code

  • The simplest way to use this mixin is to map D-Bus properties to GObject properties. Your class' class_init method provides the mapping from D-Bus property names to GObject property names.

    Each D-Bus interface requires a NULL-terminated array of TpDBusPropertiesMixinPropImpl structures which provide the mappings between property names. The class also requires a NULL-terminated array of TpDBusPropertiesMixinIfaceImpl, which gives the D-Bus interface name, and getter and setter for each interface as shown in Example 12-6.

    Example 12-6Property Mappings
    /* D-Bus properties are exposed as GObject properties through the
     * TpDBusPropertiesMixin */
    /* properties on the Client interface */
    static TpDBusPropertiesMixinPropImpl client_props[] = {
          { "Interfaces", "interfaces", NULL },
          { NULL }
    };
    
    /* properties on the Client.Observer interface */
    static TpDBusPropertiesMixinPropImpl client_observer_props[] = {
          { "ObserverChannelFilter", "channel-filter", NULL },
          { NULL }
    };
    
    /* complete list of interfaces with properties */
    static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
          { TP_IFACE_CLIENT,
            tp_dbus_properties_mixin_getter_gobject_properties,
            NULL,
            client_props
          },
          { TP_IFACE_CLIENT_OBSERVER,
            tp_dbus_properties_mixin_getter_gobject_properties,
            NULL,
            client_observer_props
          },
          { NULL }
    };
    

    Complete Source Code

    The mixin provides default getter and setter functions, tp_dbus_properties_mixin_getter_gobject_properties and tp_dbus_properties_mixin_setter_gobject_properties for mapping D-Bus properties to GObject properties.

    If you wish, rather than using GObject properties, you may write your own getter and setter functions for handling D-Bus properties.

    The data provided to TpDBusPropertiesMixinPropImpl will be passed as the data to your callback.

  • You must chain up the mixin's class_init method at the end of your class' class_init, as shown in Example 12-7.

    Example 12-7Property Mappings
    /* call our mixin class init */
    klass->dbus_props_class.interfaces = prop_interfaces;
    tp_dbus_properties_mixin_class_init (object_class,
        G_STRUCT_OFFSET (ExampleObserverClass, dbus_props_class));
    

    Complete Source Code

12.1.3. Emitting D-Bus Signals

Emitting a signal is done via calling the function of the form tp_svc_interface_name_emit_signal_name, for instance emitting the Account.AccountPropertyChanged signal is done via the tp_svc_account_emit_account_property_changed function.