[Telepathy] tp-qt4 ContactCapabilities and RequestableChannelClasses API proposal

Simon McVittie simon.mcvittie at collabora.co.uk
Thu Sep 17 07:18:14 PDT 2009


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Here are some sketches of how I think capabilities-discovery
(ContactCapabilities and RequestableChannelClasses) should look in
telepathy-qt4; see my contact-caps-sketch branch for a starting point for
implementation.

Thoughts?

Subject: [PATCH 1/2] Add ensureAudioCall(), ensureVideoCall() (bindings for the InitialAudio/InitialVideo properties)

- ---
 TelepathyQt4/account.cpp |  134 +++++++++++++++++++++++++++++++++++++++++++++-
 TelepathyQt4/account.h   |   20 +++++++
 2 files changed, 152 insertions(+), 2 deletions(-)

diff --git a/TelepathyQt4/account.cpp b/TelepathyQt4/account.cpp
index 2d82469..c8c166d 100644
- --- a/TelepathyQt4/account.cpp
+++ b/TelepathyQt4/account.cpp
@@ -828,11 +828,49 @@ PendingChannelRequest *Account::ensureMediaCall(
 }
 
 /**
- - * Start a request to ensure that a media channel with the given
+ * Start a request to ensure that an audio call with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ *                       if this channel request is for some reason not
+ *                       involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ *                         org.freedesktop.Telepathy.Client.) of the preferred
+ *                         handler for this channel, or an empty string to
+ *                         indicate that any handler would be acceptable.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureAudioCall(
+        const QString &contactIdentifier,
+        QDateTime userActionTime,
+        const QString &preferredHandler)
+{
+    QVariantMap request;
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+                   TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+                   (uint) Tp::HandleTypeContact);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitialAudio"),
+                   true);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+                   contactIdentifier);
+    return new PendingChannelRequest(dbusConnection(), objectPath(),
+            request, userActionTime, preferredHandler, false, this);
+}
+
+/**
+ * Start a request to ensure that an audio call with the given
  * contact \a contact exists, creating it if necessary.
  *
  * See ensureChannel() for more details.
  *
+ * This will only work on relatively modern connection managers.
+ *
  * \param contact The contact to call.
  * \param userActionTime The time at which user action occurred, or QDateTime()
  *                       if this channel request is for some reason not
@@ -843,8 +881,92 @@ PendingChannelRequest *Account::ensureMediaCall(
  *                         indicate that any handler would be acceptable.
  * \sa ensureChannel(), createChannel()
  */
- -PendingChannelRequest *Account::ensureMediaCall(
+PendingChannelRequest *Account::ensureAudioCall(
+        const ContactPtr &contact,
+        QDateTime userActionTime,
+        const QString &preferredHandler)
+{
+    QVariantMap request;
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+                   TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+                   (uint) Tp::HandleTypeContact);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitialAudio"),
+                   true);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+                   contact ? contact->handle().at(0) : (uint) 0);
+    return new PendingChannelRequest(dbusConnection(), objectPath(),
+            request, userActionTime, preferredHandler, false, this);
+}
+
+/**
+ * Start a request to ensure that a video call with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param withAudio true if both audio and video are required, false for a
+ *                  video-only call
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ *                       if this channel request is for some reason not
+ *                       involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ *                         org.freedesktop.Telepathy.Client.) of the preferred
+ *                         handler for this channel, or an empty string to
+ *                         indicate that any handler would be acceptable.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureAudioCall(
+        const QString &contactIdentifier,
+        bool withAudio,
+        QDateTime userActionTime,
+        const QString &preferredHandler)
+{
+    QVariantMap request;
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+                   TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+                   (uint) Tp::HandleTypeContact);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitialVideo"),
+                   true);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+                   contactIdentifier);
+
+    if (withAudio) {
+        request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitialAudio"),
+                       true);
+    }
+
+    return new PendingChannelRequest(dbusConnection(), objectPath(),
+            request, userActionTime, preferredHandler, false, this);
+}
+
+/**
+ * Start a request to ensure that a video call with the given
+ * contact \a contact exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers.
+ *
+ * \param contact The contact to call.
+ * \param withAudio true if both audio and video are required, false for a
+ *                  video-only call
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ *                       if this channel request is for some reason not
+ *                       involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ *                         org.freedesktop.Telepathy.Client.) of the preferred
+ *                         handler for this channel, or an empty string to
+ *                         indicate that any handler would be acceptable.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureVideoCall(
         const ContactPtr &contact,
+        bool withAudio,
         QDateTime userActionTime,
         const QString &preferredHandler)
 {
@@ -853,8 +975,16 @@ PendingChannelRequest *Account::ensureMediaCall(
                    TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA);
     request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
                    (uint) Tp::HandleTypeContact);
+    request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitialVideo"),
+                   true);
     request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
                    contact ? contact->handle().at(0) : (uint) 0);
+
+    if (withAudio) {
+        request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitialAudio"),
+                       true);
+    }
+
     return new PendingChannelRequest(dbusConnection(), objectPath(),
             request, userActionTime, preferredHandler, false, this);
 }
diff --git a/TelepathyQt4/account.h b/TelepathyQt4/account.h
index 3630540..4debaa0 100644
- --- a/TelepathyQt4/account.h
+++ b/TelepathyQt4/account.h
@@ -159,6 +159,26 @@ public:
             QDateTime userActionTime = QDateTime::currentDateTime(),
             const QString &preferredHandler = QString());
 
+    PendingChannelRequest *ensureAudioCall(
+            const QString &contactIdentifier,
+            QDateTime userActionTime = QDateTime::currentDateTime(),
+            const QString &preferredHandler = QString());
+    PendingChannelRequest *ensureAudioCall(
+            const ContactPtr &contact,
+            QDateTime userActionTime = QDateTime::currentDateTime(),
+            const QString &preferredHandler = QString());
+
+    PendingChannelRequest *ensureVideoCall(
+            const QString &contactIdentifier,
+            bool withAudio = true,
+            QDateTime userActionTime = QDateTime::currentDateTime(),
+            const QString &preferredHandler = QString());
+    PendingChannelRequest *ensureVideoCall(
+            const ContactPtr &contact,
+            bool withAudio = true,
+            QDateTime userActionTime = QDateTime::currentDateTime(),
+            const QString &preferredHandler = QString());
+
     // advanced
     PendingChannelRequest *createChannel(
             const QVariantMap &requestedProperties,

Subject: [PATCH 2/2] Sketch an API for connection and contact capabilities, based on the Requests and ContactCapabilities D-Bus APIs

- ---
 TelepathyQt4/ConnectionCapabilities |   13 +++
 TelepathyQt4/ContactCapabilities    |   13 +++
 TelepathyQt4/capabilities.cpp       |  184 +++++++++++++++++++++++++++++++++++
 TelepathyQt4/capabilities.h         |  111 +++++++++++++++++++++
 TelepathyQt4/connection.h           |    8 ++
 TelepathyQt4/contact.h              |   12 +++
 6 files changed, 341 insertions(+), 0 deletions(-)
 create mode 100644 TelepathyQt4/ConnectionCapabilities
 create mode 100644 TelepathyQt4/ContactCapabilities
 create mode 100644 TelepathyQt4/capabilities.cpp
 create mode 100644 TelepathyQt4/capabilities.h

diff --git a/TelepathyQt4/ConnectionCapabilities b/TelepathyQt4/ConnectionCapabilities
new file mode 100644
index 0000000..c2ebc24
- --- /dev/null
+++ b/TelepathyQt4/ConnectionCapabilities
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionCapabilities_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionCapabilities_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/capabilities.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/TelepathyQt4/ContactCapabilities b/TelepathyQt4/ContactCapabilities
new file mode 100644
index 0000000..d6a3e8a
- --- /dev/null
+++ b/TelepathyQt4/ContactCapabilities
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ContactCapabilities_HEADER_GUARD_
+#define _TelepathyQt4_ContactCapabilities_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/capabilities.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/TelepathyQt4/capabilities.cpp b/TelepathyQt4/capabilities.cpp
new file mode 100644
index 0000000..5f60734
- --- /dev/null
+++ b/TelepathyQt4/capabilities.cpp
@@ -0,0 +1,184 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009 Nokia Corporation
[... standard LGPL header elided ...]
+ */
+
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ContactCapabilities>
+
+/**
+ * Return true if private text channels can be established by providing
+ * a contact identifier.
+ *
+ * If the protocol is such that text chats can be established, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * \return true if Tp::Account::ensureTextChat() can be expected to work
+ */
+bool CapabilitiesBase::supportsTextChats()
+{
+    return FIXME;
+}
+
+/**
+ * Return true if private audio and/or video calls can be established by
+ * providing a contact identifier.
+ *
+ * If the protocol is such that these calls can be established, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * \return true if Tp::Account::ensureMediaCall() can be expected to work
+ */
+bool CapabilitiesBase::supportsMediaCalls()
+{
+    return FIXME;
+}
+
+/**
+ * Return true if private audio calls can be established by providing a
+ * contact identifier.
+ *
+ * Call mediaCallsHaveImmutableStreams() to determine whether such calls are
+ * likely to be upgradable to have a video stream later.
+ *
+ * If the protocol is such that these calls can be established, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * In some older connection managers, supportsAudioCalls() and
+ * supportsVideoCalls() might both return false, even though
+ * supportsMediaCalls() returns true. This indicates that only an older
+ * API is supported - clients of these connection managers must call
+ * ensureMediaCall() to get an empty call, then add audio and/or video
+ * streams to it.
+ *
+ * \return true if Tp::Account::ensureAudioCall() can be expected to work
+ */
+bool CapabilitiesBase::supportsAudioCalls()
+{
+    return FIXME;
+}
+
+/**
+ * Return true if private video calls can be established by providing a
+ * contact identifier.
+ *
+ * The same comments as for supportsAudioCalls() apply to this method.
+ *
+ * \param withAudio If true (the default), check for the ability to make calls
+ *      with both audio and video; if false, do not require the ability to
+ *      include an audio stream
+ * \return true if Tp::Account::ensureVideoCall() can be expected to work,
+ *      if given the same withAudio parameter
+ */
+bool CapabilitiesBase::supportsVideoCalls(bool withAudio)
+{
+    return withAudio ? FIXME : FIXME;
+}
+
+/**
+ * Return whether the protocol supports adding streams of a different type
+ * to ongoing media calls.
+ *
+ * In some protocols and clients (such as XMPP Jingle), all calls potentially
+ * support both audio and video. This is indicated by returning true.
+ *
+ * In other protocols and clients (such as MSN, and the variant of XMPP Jingle
+ * used by Google clients), the streams are fixed at the time the call is
+ * started, so if you will ever want video, you have to ask for it at the
+ * beginning, for instance with ensureVideoCall(). This is indicated by
+ * returning false.
+ *
+ * User interfaces can use this method as a UI hint. If it returns false,
+ * then a UI wishing to support both audio and video calls will have to
+ * provide separate "audio call" and "video call" buttons or menu items;
+ * if it returns true, a single button that makes an audio call is sufficient,
+ * because video can be added later.
+ *
+ * (The underlying Telepathy feature is the ImmutableStreams property; if this
+ * method returns true, then ImmutableStreams is false, and vice versa).
+ *
+ * \return true if audio calls can be upgraded to audio + video
+ */
+bool CapabilitiesBase::supportsUpgradingCalls()
+{
+    return FIXME;
+}
+
+/**
+ * Return true if this object accurately describes the capabilities of a
+ * particular Tp::Contact, or false if it's only a guess based on the
+ * capabilities of the underlying Tp::Connection.
+ *
+ * In protocols like XMPP where each contact advertises their capabilities
+ * to others, Tp::Contact::capabilities will generally return an object where
+ * this method returns true.
+ *
+ * In protocols like SIP where contacts' capabilities are not known,
+ * Tp::Contact::Capabilities will return an object where this method returns
+ * false, whose methods supportsTextChats() etc. are based on what the
+ * underlying Tp::Connection supports.
+ *
+ * This reflects the fact that the best assumption an application can make is
+ * that every contact supports every channel type supported by the connection,
+ * while indicating that requests to communicate might fail if the contact
+ * does not actually have the necessary functionality.
+ */
+bool ContactCapabilities::isSpecificToContact() const
+{
+    return FIXME;
+}
+
+/**
+ * Return true if named text chatrooms can be joined by providing a
+ * chatroom identifier.
+ *
+ * If the protocol is such that chatrooms can be joined, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * \return true if Tp::Account::ensureTextChatroom() can be expected to work
+ */
+bool ConnectionCapabilities::supportsTextChatrooms() const
+{
+    return FIXME;
+}
+
+/**
+ * Return the underlying data structure used by Telepathy to represent
+ * the requests that can succeed.
+ *
+ * This can be used by advanced clients to determine whether an unusually
+ * complex request would succeed. See the Telepathy D-Bus API Specification
+ * for details of how to interpret the returned list of QVariantMap objects.
+ *
+ * The higher-level methods like supportsTextChats() are likely to be more
+ * useful to the majority of clients.
+ *
+ * \return a RequestableChannelClassList indicating the parameters to
+ *      Tp::Account::createChannel, Tp::Account::ensureChannel,
+ *      Tp::Connection::createChannel and Tp::Connection::ensureChannel
+ *      that can be expected to work
+ */
+RequestableChannelClassList CapabilitiesBase::requestableChannelClasses() const
+{
+    return mPriv->classes;
+}
diff --git a/TelepathyQt4/capabilities.h b/TelepathyQt4/capabilities.h
new file mode 100644
index 0000000..0982cb6
- --- /dev/null
+++ b/TelepathyQt4/capabilities.h
@@ -0,0 +1,111 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009 Nokia Corporation
+ *
[... standard LGPL header elided ...]
+ */
+
+#ifndef _TelepathyQt4_capabilities_h_HEADER_GUARD_
+#define _TelepathyQt4_capabilities_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class Connection;
+class Contact;
+
+// This is the part common to contacts (for things a particular contact
+// can do) and connections (for things contacts in general might be able to
+// do)
+class CapabilitiesBase
+{
+public:
+    virtual ~CapabilitiesBase();
+
+    RequestableChannelClassList requestableChannelClasses() const;
+
+    bool supportsTextChats() const;
+    bool supportsMediaCalls() const;
+    bool supportsAudioCalls() const;
+    bool supportsVideoCalls(bool withAudio = true) const;
+    bool supportsUpgradingCalls() const;
+
+    // later:
+    // bool supportsFileTransfers() const;
+    // QList<FileTransferHashType> fileTransfersRequireHash() const;
+    //
+    // bool supportsStreamTubes() const;
+    // bool supportsDBusTubes() const;
+
+protected:
+    CapabilitiesBase(const RequestableChannelClassList &classes);
+
+private:
+    friend class Connection;
+    friend class Contact;
+
+    struct Private;
+    friend struct Private;
+    Private *mPriv;
+};
+
+class ContactCapabilities : public CapabilitiesBase
+{
+public:
+    virtual ~ContactCapabilities();
+
+    bool isSpecificToContact() const;
+
+    // later:
+    // QStringList supportedStreamTubeServices() const;
+    // QStringList supportedDBusTubeServices() const;
+    // (those don't make much sense for ConnectionCapabilities)
+
+private:
+    friend class Connection;
+    friend class Contact;
+
+    CapabilitiesBase(const RequestableChannelClassList &classes,
+            bool isSpecificToContact);
+
+    struct Private;
+    friend struct Private;
+    Private *mPriv;
+};
+
+class ConnectionCapabilities : public CapabilitiesBase
+{
+public:
+    virtual ~ConnectionCapabilities();
+
+    bool supportsTextChatrooms() const;
+
+private:
+    struct Private;
+    friend struct Private;
+    Private *mPriv;
+};
+
+} // Tp namespace
+
+#endif
diff --git a/TelepathyQt4/connection.h b/TelepathyQt4/connection.h
index 6382b67..baea9eb 100644
- --- a/TelepathyQt4/connection.h
+++ b/TelepathyQt4/connection.h
@@ -28,6 +28,7 @@
 
 #include <TelepathyQt4/_gen/cli-connection.h>
 
+#include <TelepathyQt4/ConnectionCapabilities>
 #include <TelepathyQt4/Contact>
 #include <TelepathyQt4/DBus>
 #include <TelepathyQt4/DBusProxy>
@@ -71,6 +72,7 @@ public:
     static const Feature FeatureSimplePresence;
     static const Feature FeatureRoster;
     static const Feature FeatureRosterGroups;
+    static const Feature FeatureCapabilities;
 
     enum Status {
         StatusDisconnected = ConnectionStatusDisconnected,
@@ -95,6 +97,11 @@ public:
     SimpleStatusSpecMap allowedPresenceStatuses() const;
     PendingOperation *setSelfPresence(const QString &status, const QString &statusMessage);
 
+    // all the requests that could potentially succeed on this Connection
+    // (in the case of contacts: all the requests that could possibly succeed,
+    // to any contact)
+    ConnectionCapabilities capabilities() const;
+
     PendingChannel *createChannel(const QVariantMap &request);
     PendingChannel *ensureChannel(const QVariantMap &request);
 
@@ -155,6 +162,7 @@ Q_SIGNALS:
     void selfHandleChanged(uint newHandle);
     // FIXME: might not need this when Renaming is fixed and mapped to Contacts
     void selfContactChanged();
+    void capabilitiesChanged(const ConnectionCapabilities &capabilities);
 
 protected:
     Connection(const QString &busName, const QString &objectPath);
diff --git a/TelepathyQt4/contact.h b/TelepathyQt4/contact.h
index a0ea274..ed079d3 100644
- --- a/TelepathyQt4/contact.h
+++ b/TelepathyQt4/contact.h
@@ -31,6 +31,7 @@
 #include <QSharedPointer>
 #include <QVariantMap>
 
+#include <TelepathyQt4/ContactCapabilities>
 #include <TelepathyQt4/Types>
 
 namespace Tp
@@ -50,6 +51,7 @@ public:
         FeatureAlias,
         FeatureAvatarToken,
         FeatureSimplePresence,
+        FeatureCapabilities,
         _Padding = 0xFFFFFFFF
     };
 
@@ -79,6 +81,14 @@ public:
     PresenceState subscriptionState() const;
     PresenceState publishState() const;
 
+    // all the requests that are likely to succeed for this specific Contact.
+    // If possible, this is from the ContactCapabilities interface,
+    // and capabilities()->isSpecificToContact() will be true; if that
+    // interface isn't present, this is the subset of
+    // manager()->connection()->capabilities() that have handle type Contact,
+    // and capabilities()->isSpecificToContact() will be false.
+    ContactCapabilities capabilities() const;
+
     PendingOperation *requestPresenceSubscription(const QString &message = QString());
     PendingOperation *removePresenceSubscription(const QString &message = QString());
     PendingOperation *authorizePresencePublication(const QString &message = QString());
@@ -105,6 +115,8 @@ Q_SIGNALS:
     void addedToGroup(const QString &group);
     void removedFromGroup(const QString &group);
 
+    void capabilitiesChanged(const ContactCapabilities &capabilities);
+
     // TODO: consider how the Renaming interface should work and map to Contacts
     // I guess it would be something like:
     // void renamedTo(Tp::ContactPtr)
-----BEGIN PGP SIGNATURE-----

iQIVAwUBSrJFHU3o/ypjx8yQAQj8Aw//S9qC8djUyry1OZEiLBPkmf64WfzkOecl
OWcwcb7E+3TfkQmhFjTHhj8bqjzdA8IIeaYln0ReTXBm6FrIzejCT4SyGQLWsp4h
I5wn/byATqKwCJdLWfk6Zryr3382Fyv96oE2DEOm5miYe3aSYtk+IoESj4atXjfc
PdAFHcvPMfUoPxnQZe8n4IDE7HPYjmU4DnFnbm0mBEMofPlqcmXbo9icSyQ8cxxC
PX4gliIl4hHKca8UlUMWPta126qVHGS+dfB5YwXFerywEOa9n6/iOZyGaaIgBM8l
AwL0L6/XsvmYDOWNfR2lhq6vUlyE7vEfsihY1EKm6L9zz5WZOUEXFsKqrIdxNxxB
ArdZPTzuXvXgvEaYdtw2aWRGIOzTFDzlJvqy4D0+EOMimdJo5kawLA2GWM+VpqoK
WCBNaLwFFTOj5MjFB9dwMk2AIugAOxGCaBZ7lnAqHa+7U9b1dn+EJX7QsYMutJgp
ixnREuAT3sjLPZxH4QmwvoyDGhbYu1eUcR/FZ+bshzyU5HpJHd/87uXR7/eY/XRQ
/eys9OweVk1WQayTF44be8VduQv4+YlIgMbR3Bk+nEWR6GHO+TLMF5ZkJ/im97Dr
jDmzrxq0wkIiOfB8Z2I4LjknQ9F+QiCg+LcRT38QaoIvQo++175UFoUeV6A9zolY
66NFoz+5vGk=
=F0aC
-----END PGP SIGNATURE-----


More information about the telepathy mailing list