As mentioned in Section 2.2 ― Using D-Bus, many programming languages have their own generic ways of using D-Bus APIs. In addition, there are some Telepathy-specific APIs to make the use of Telepathy easier. For instance, telepathy-glib provides an API that aims to be more familiar to users of Glib and GTK+. Likewise, Telepathy-Qt provides a Qt API for Telepathy and Telepathy-Python for Python.
Remember that, like raw use of D-Bus from these programming languages, the Telepathy language bindings only create proxies to the D-Bus objects, providing a way to use their API. The actual objects are instantiated in the service's process.
telepathy-python builds on top of the standard dbus module for Python. The telepathy module provides some convenience classes, as well as enumerations and interface names. If you're planning on using the GLib mainloop you'll also need the dbus.mainloop.glib module.
The telepathy.client provides the client D-Bus proxy objects. The telepathy.interfaces provides the names of Telepathy interfaces (e.g. CONNECTION_MANAGER, CONNECTION_INTERFACE_REQUESTS, CHANNEL_TYPE_CONTACT_LIST, etc.). The telepathy.constants module provides named constants in the Telepathy spec (e.g. CONNECTION_STATUS_CONNECTED, HANDLE_TYPE_GROUP).
Example 2-1 shows how to configure D-Bus to use the GLib mainloop for its event dispatching and then makes a connection to the session bus.
import dbus.mainloop.glib dbus.mainloop.glib.DBusGMainLoop(set_as_default = True)
In order to interact with a D-Bus object (i.e. call methods or connect signals) we need to create a proxy object for it. A proxy object is a Python object that we call methods on in our application. In telepathy-python they are provided in the telepathy.client module.
The proxy objects you will use are Connection and Channel.
connection = telepathy.client.Connection(bus_name, object_path) channel = telepathy.client.Channel(connection.service_name, object_path)
telepathy-python proxies differ from regular Python dbus proxies in that they aren't created with a given D-Bus interface. Instead the interface is specified when calling a method or connecting a signal similar to how you might look up a key in a dictionary (see Example 2-3 below). Interface names are available from the module telepathy.interfaces.
D-Bus method calls on a proxy object look like any other Python method call. Python's D-Bus support can automatically convert native Python types into D-Bus types and back, so no complicated type marshalling is required. Example 2-3 shows calling the RequestConnection method (more on this in Section 5.2.2 ― From a Connection Manager).
The reply_handler and error_handler keywords are important to make your method call asynchronous. Method calls in D-Bus should always be made asynchronously, for the reasons outlined in Section 2.2 ― Using D-Bus. Python features such as inline functions and lambdas are permitted as callback functions. The parameters to a callback are specified by the D-Bus return type, e.g. Example 2-4.
cm[CONNECTION_MANAGER].RequestConnection('jabber', { 'account': account, 'password': password, }, reply_handler = self.request_connection_cb, error_handler = self.error_cb)
D-Bus properties are available via the org.freedesktop.DBus.Properties interface, which provides the Get, Set and GetAll methods to get or set property values for the object's other interfaces.
Python D-Bus currently has no specific API for dealing with properties, so they must be accessed through the D-Bus method calls. Example 2-5 is therefore very similar to Example 2-3 presented above.
D-Bus signal handlers may be specified in Python with the proxy object's connect_to_signal method. The specified callback function will then be called when the signal is emitted. Example 2-6 connects to the NewChannels signal in Telepathy.
The C examples in this book will use telepathy-glib, which provides a GObject-based API for the Telepathy framework.
telepathy-glib uses what it calls TpProxy objects to make D-Bus method calls. TpProxy objects are GObjects that expose common Telepathy features (like the interfaces they implement) as GObject properties and struct members.
There are several common TpProxy subclasses that you will use when programming with telepathy-glib: TpAccount, TpAccountManager, TpConnectionManager, TpConnection and TpChannel.
Proxy objects need to make D-Bus method calls to acquire additional information (e.g. available interfaces) to populate the properties and structures of the object. When they have received this information they are considered "ready". You can prepare a proxy using the method call tp_proxy_prepare_async. This method call takes a NULL-terminated array of features to prepare on the proxy (you can pass NULL to indicate you would like just the core features prepared); and a callback to call when preparation has succeeded or failed.
Proxy preparation may potentially fail. In the callback you must use tp_proxy_prepare_finish to check whether preparation was successful. See Example 2-7 and Example 2-8.
Features have names such as TP_CONNECTION_FEATURE_CONNECTED and TP_CHANNEL_FEATURE_GROUP.
/* get the account */ account = tp_account_new (dbus, account_path, &error); if (account == NULL) handle_error (error); /* prepare the core account features */ tp_proxy_prepare_async (account, NULL, _account_ready, user_data);
static void _account_ready (GObject *account, GAsyncResult *res, gpointer user_data) { GError *error = NULL; if (!tp_proxy_prepare_finish (account, res, &error)) handle_error (error); ... }
TpChannel *channel = ...; GQuark features[] = { TP_CHANNEL_FEATURE_CORE, TP_CHANNEL_FEATURE_GROUP, 0 }; tp_proxy_prepare_async (channel, features, callback, user_data);
If TP_CONNECTION_FEATURE_CONNECTED is requested on a TpConnection, you still have to call the Connect method. This feature will block preparation until the connection has entered the CONNECTED state.
See Section 5.2 ― Obtaining a Connection for more details on setting up a Connection.
telepathy-glib provides the Telepathy D-Bus API method calls as generated functions. The generated functions have the following prefixes:
These functions are used for client applications. For instance, tp_cli_connection_call_connect.
These functions are used for service implementations, such as connection managers. For instance, tp_svc_connection_implement_connect.
For each Telepathy D-Bus method, telepathy-glib generally provides both a "run" and a "call" function. For instance, tp_cli_connection_run_connect and tp_cli_connection_call_connect.
The "run" functions are synchronous, and blocks until the D-Bus service has returned the value. They have been deprecated and should never be used in Telepathy programs (see Section 2.2 ― Using D-Bus). Instead use the "call" functions, which are asynchronous; immediately returning and later providing the result to a callback function.
The generated client functions have the following general signature:
Each method call has its own unique callback. The general type signature for a method callback is:
Example 2-10 shows a the construction of a telepathy-glib method call, in this case to RequestConnection.
/* method call for ConnectionManager.RequestConnection */ tp_cli_connection_manager_call_request_connection ( cm, /* TpProxy */ -1, /* timeout */ "jabber", /* method arguments */ parameters, request_connection_cb, /* callback */ NULL, /* user data */ NULL, /* destroy notify */ NULL); /* weak object */ /* callback for ConnectionManager.RequestConnection */ static void request_connection_cb (TpConnectionManager *cm, /* TpProxy */ const char *bus_name, /* return args */ const char *object_path, const GError *in_error, /* error condition */ gpointer user_data, /* user data */ GObject *weak_object) /* weak object */
telepathy-glib provides a convenience API for accessing the D-Bus properties of any TpProxy proxy: tp_cli_dbus_properties_call_get, tp_cli_dbus_properties_call_get_all and tp_cli_dbus_properties_call_set. These functions work similar to all other TpProxy method calls.
For tp_cli_dbus_properties_call_get, the return argument is a GValue of the type specified by the property (see Section 2.3.2.4 ― Types). For tp_cli_dbus_properties_call_get_all the return value is an a{sv} map. Example 2-11 demonstates how to access a D-Bus property.
tp_cli_dbus_properties_call_get (conn, -1, TP_IFACE_CONNECTION_INTERFACE_REQUESTS, "Channels", get_channels_cb, NULL, NULL, NULL); static void get_channels_cb (TpProxy *proxy, const GValue *value, const GError *in_error, gpointer user_data, GObject *weak_obj) { /* handle error */ GPtrArray *channels = g_value_get_boxed (value); /* ... */ }
The handling of variant types in telepathy-glib is done using GLib's built-in GValue type system.
GType types for simple D-Bus types are given in Table 2-1. Telepathy's complex types are all mapped to generated boxed GTypes (see the telepathy-glib manual). For example Channel_Details maps to the GType TP_STRUCT_TYPE_CHANNEL_DETAILS.
To check that a given GValue is of the correct type, use the macro G_VALUE_HOLDS. To retrieve the type of a GValue use the macro G_VALUE_TYPE.
Single Values | Lists/Arrays | ||||
---|---|---|---|---|---|
D-Bus Type | GType | C type | D-Bus Type | GType | C type |
o | DBUS_TYPE_G_OBJECT_PATH | String (char *) | ao | TP_ARRAY_TYPE_OBJECT_PATH_LIST | GPtrArray of Strings (char *) |
y | G_TYPE_UCHAR | guchar | ay | DBUS_TYPE_G_UCHAR_ARRAY | GArray of guchar |
u | G_TYPE_UINT | guint | au | DBUS_TYPE_G_UINT_ARRAY | GArray of guint |
i | G_TYPE_INT | int | ai | DBUS_TYPE_G_INT_ARRAY | GArray of int |
t | G_TYPE_UINT64 | guint64 | at | DBUS_TYPE_G_UINT64_ARRAY | GArray of guint64 |
x | G_TYPE_INT64 | gint64 | ax | DBUS_TYPE_G_INT64_ARRAY | GArray of gint64 |
d | G_TYPE_DOUBLE | double | ad | DBUS_TYPE_G_DOUBLE_ARRAY | GArray of double |
b | G_TYPE_BOOLEAN | gboolean | ab | DBUS_TYPE_G_BOOLEAN_ARRAY | GArray of gboolean |
s | G_TYPE_STRING | char * | as | G_TYPE_STRV | char ** |
Decoding the contents of the GValue can be done either from the manual or from the D-Bus type signature. For example the property Requests.Channels is of the type Channel_Details_List (a(oa{sv})). From the manual, this is a GPtrArray of TP_STRUCT_TYPE_CHANNEL_DETAILS, which in turn is a GValueArray containing a object path and an a{sv} map (hashtable). The complete unpacking is shown in Example 2-14.
More generally, the structure of the type can be determined from its D-Bus type signature by following several rules:
For example, the D-Bus type a{sa(usuu)} is a GHashTable of string keys (char *) to GPtrArrays containing GValueArrays. This is shown graphically in Figure 2-4.
The macro G_VALUE_TYPE_NAME can also be useful for unpacking Telepathy types.
For example the listed type name of TP_STRUCT_TYPE_CHANNEL_DETAILS_LIST is given as GPtrArray_GValueArray_DBusGObjectPath+GHashTable_gchararray+GValue___.
Telepathy makes extensive use of a{sv} maps (i.e. a map of string keys to variant types) for passing and returning properties from calls (this allows for something similar to polymorphic functions via D-Bus). Unforunately, C and GLib do not offer many functions for convieniently handling this type of map.
To make handling these maps easier, Telepathy provides a number of functions, tp_asv_* for manipulating these maps.
Telepathy uses a standard GHashTable that is set up to use string keys, and GSlice-allocated GValue values. The easiest way to create a new map with tp_asv_new. This function will automatically destroy values when they are overwritten, removed or the hash table is destroyed. The standard GHashTable functions g_hash_table_destroy, g_hash_table_ref, etc. can all be used with this map.
tp_asv_new can optionally take a NULL-terminated list of initial values as (key, type, value) tuples, as shown in Example 2-12.
GHashTable *parameters = tp_asv_new ( "account", G_TYPE_STRING, "bob@mcbadgers.com" "password", G_TYPE_STRING, password, NULL);
The map can be further edited or appended to with the tp_asv_set_* functions (e.g. tp_asv_set_string).
To read values from the map, use the tp_asv_get_* functions (e.g. tp_asv_get_string). These functions return the value on success, or NULL/False/0 if the key is unavailable. Some accessor functions (e.g. tp_asv_get_boolean, tp_asv_get_double, tp_asv_get_int32, etc.) take an optional parameter *valid, which can be used to determine, without ambiguity, if the key exists in the map.
The function tp_asv_dump can be used to dump a string representation of the map out to the debugging console.
Structs (e.g. ussu) are stored as a GValueArray. To make these easier to create use the utility function tp_value_array_build. This function requires a number of struct entries, followed by (GType, value) pairs, and terminated with the type G_TYPE_INVALID, as shown in Example 2-13.
GValueArray *entry = tp_value_array_build (4, G_TYPE_UINT, 12, G_TYPE_STRING, "subject", G_TYPE_STRING, "Building a GValueArray", G_TYPE_UINT, 0, G_TYPE_INVALID);
tp_value_array_build takes copies/references of all of the supplied data, which can be freed immediately. These copies/references are released when the GValueArray is freed.
Structs can be unpacked using the utility function tp_value_array_unpack. This function takes a number of values to unpack (may be less than the number of values in the array) followed by that number of pointers to appropriate variables (NULL may be passed to skip a value you're not interested in). Example 2-14 shows unpacking the object path, and properties for an array of channels.
Values are not copied out of the array, and these pointers will become invalid when the array is freed. If you want to keep a copy of a value you should copy/reference it explicitly.
g_return_if_fail (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST)); GPtrArray *channels = g_value_get_boxed (value); int i; for (i = 0; i < channels->len; i++) { GValueArray *channel = g_ptr_array_index (channels, i); char *object_path; GHashTable *map; tp_value_unpack (channel, 2, &object_path, &map); const char *type = tp_asv_get_string (map, TP_IFACE_CHANNEL ".ChannelType"); g_print ("Path: %s\n", object_path); g_print ("Type: %s\n", type); }
Individual values can be retrieved from a structure using g_value_array_get_nth plus the appropriate g_value_get_... function for the returned GValue. Example 2-15 shows how this is done. Similar to using tp_value_array_unpack, this value is not copied or referenced, however GLib provides several g_value_dup_... functions that do make copies.
Variant types are stored as a GValue of type given by the specification. For example, Example 2-16 shows how to unpack the type a(uv).
int i; for (i = 0; i < properties->len; i++) { GValueArray *property = g_ptr_array_index (properties, i); /* the id is a GValue<UINT> * the variant is a GValue<GValue<??> */ guint id = g_value_get_uint (g_value_array_get_nth (property, 0)); GValue *value = g_value_get_boxed (g_value_array_get_nth (property, 1)); /* get a string representation of value */ char *str = g_strdup_value_contents (value); g_print ("Property %i: %s\n", id, str); g_free (str); }
GPtrArray *array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); /* pack structs into array */ GValue value = { 0, }; g_value_init (&value, G_TYPE_STRING); g_value_set_static_string (&value, "Test Subject"); g_ptr_array_add (array, tp_value_array_build (2, G_TYPE_UINT, id, G_TYPE_VALUE, value, G_TYPE_INVALID); g_value_unset (&value); ... /* free array */ g_ptr_array_free (array, TRUE);
To use the telepathy-glib API, you must include the headers for the library, and link to its shared library. The necessary compiler and linker commands can be obtained from the pkg-config utiltity like so:
pkg-config telepathy-glib --cflags pkg-config telepathy-glib --libs
However, if you are using the "autotools" (automake, autoconf, etc) build system, you will find it more convenient to use the PKG_CHECK_MODULES macro in your configure.ac file.
PKG_CHECK_MODULES(EXAMPLE, telepathy-glib) AC_SUBST(EXAMPLE_CFLAGS) AC_SUBST(EXAMPLE_LIBS)
You should then use the generated _CFLAGS and _LIBS definitions in your Makefile.am files.