diff --git a/extensions/Channel_Type_Contact_Search.xml b/extensions/Channel_Type_Contact_Search.xml
new file mode 100644
index 0000000..fefa77a
--- /dev/null
+++ b/extensions/Channel_Type_Contact_Search.xml
@@ -0,0 +1,462 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Contact_Search" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+  <tp:copyright> Copyright © 2005-2009 Collabora Limited </tp:copyright>
+  <tp:copyright> Copyright © 2005-2009 Nokia Corporation </tp:copyright>
+  <tp:copyright> Copyright © 2006 INdT </tp:copyright>
+  <tp:license xmlns="http://www.w3.org/1999/xhtml">
+    <p>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.</p>
+
+<p>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.</p>
+
+<p>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 Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+  </tp:license>
+  <interface name="org.freedesktop.Telepathy.Channel.Type.ContactSearch">
+    <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+    <tp:added version="0.19.10">
+      as stable API. Changes from draft 2:
+      <tp:type>Contact_Search_Result_Map</tp:type> keys are now identifiers
+      rather than handles; consequently, the values need not include
+      <tt>x-telepathy-identifier</tt>.
+    </tp:added>
+
+    <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+      <p>A channel type for searching server-stored user directories. A new
+        channel should be requested by a client for each search attempt, and
+        closed when the search is completed or the required result has been
+        found. Channels of this type should have <tp:dbus-ref
+        namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>
+        <code>None</code> (and hence <tp:dbus-ref
+        namespace='ofdT.Channel'>TargetHandle</tp:dbus-ref> <code>0</code> and
+        <tp:dbus-ref namespace='ofdT.Channel'>TargetID</tp:dbus-ref>
+        <code>""</code>). Requests for channels of this type need only
+        optionally specify the <tp:member-ref>Server</tp:member-ref> property
+        (if it is an allowed property in the connection's <tp:dbus-ref
+        namespace='ofdT.Connection.Interface.Requests'>RequestableChannelClasses</tp:dbus-ref>).</p>
+
+      <p>Before searching, the
+        <tp:member-ref>AvailableSearchKeys</tp:member-ref> property should be
+        inspected to determine the valid search keys which can be provided to
+        the <tp:member-ref>Search</tp:member-ref> method. A search request is
+        then started by providing some of these terms to the Search method, and
+        the <tp:member-ref>SearchState</tp:member-ref> will change from
+        <code>Not_Started</code> to <code>In_Progress</code>.  As results are
+        returned by the server, the
+        <tp:member-ref>SearchResultReceived</tp:member-ref> signal is emitted
+        for each contact found; when the search is complete, the search state
+        will be set to <code>Completed</code>. If the search fails after Search
+        has been called, the state will change to <code>Failed</code>.  A
+        running search can be cancelled by calling
+        <tp:member-ref>Stop</tp:member-ref>.</p>
+
+      <p>If the protocol supports limiting the number of results returned by a
+        search and subsequently requesting more results, after
+        <tp:member-ref>Limit</tp:member-ref> results have been received the
+        search state will be set to <code>More_Available</code>. Clients may
+        call <tp:member-ref>More</tp:member-ref> to request another
+        <tp:member-ref>Limit</tp:member-ref> results. If allowed by the
+        connection manager, clients may specify the "page size" by specifying
+        <tp:member-ref>Limit</tp:member-ref> when calling
+        <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>.
+        </p>
+
+      <p>The client should call the channel's <tp:dbus-ref
+        namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+        method when it is finished with the channel.</p>
+
+      <p>Each channel can only be used for a single search; a new channel
+        should be requested for each subsequent search. Connection managers
+        MUST support multiple ContactSearch channels being open at once (even
+        to the same server, if applicable).</p>
+
+      <p>It does not make sense to request this channel type using <tp:dbus-ref
+          namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">EnsureChannel</tp:dbus-ref>;
+        clients SHOULD request channels of this type using
+        <tp:dbus-ref
+          namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>
+        instead.</p>
+
+      <tp:rationale>
+        <p>A contact search channel that is already in use for a different
+          search isn't useful.</p>
+      </tp:rationale>
+    </tp:docstring>
+
+    <tp:enum name="Channel_Contact_Search_State" type="u">
+      <tp:enumvalue suffix="Not_Started" value="0">
+        <tp:docstring>The search has not started</tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="In_Progress" value="1">
+        <tp:docstring>The search is in progress</tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="More_Available" value="2">
+        <tp:docstring>The search has paused, but more results can be retrieved
+          by calling <tp:member-ref>More</tp:member-ref>.</tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="Completed" value="3">
+        <tp:docstring>The search has been completed</tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="Failed" value="4">
+        <tp:docstring>The search has failed</tp:docstring>
+      </tp:enumvalue>
+    </tp:enum>
+
+    <property name="SearchState" tp:name-for-bindings="Search_State"
+      access="read" type="u" tp:type="Channel_Contact_Search_State">
+      <tp:docstring>
+        The current state of this search channel object. Change notification
+        is via <tp:member-ref>SearchStateChanged</tp:member-ref>.
+      </tp:docstring>
+    </property>
+
+    <signal name="SearchStateChanged"
+      tp:name-for-bindings="Search_State_Changed">
+      <arg name="State" type="u" tp:type="Channel_Contact_Search_State">
+        <tp:docstring>The new search state</tp:docstring>
+      </arg>
+      <arg name="Error" type="s" tp:type="DBus_Error_Name">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+          If the new state is <code>Failed</code>, the name of a D-Bus error
+          describing what went wrong. Otherwise, the empty string.
+        </tp:docstring>
+      </arg>
+      <arg name="Details" type="a{sv}" tp:type="String_Variant_Map">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+          <p>Additional information about the state transition, which may
+            include the following well-known keys:</p>
+
+          <dl>
+            <dt>debug-message (s)</dt>
+            <dd>Debugging information on the change, corresponding to the
+              message part of a D-Bus error message, which SHOULD NOT be
+              displayed to users under normal circumstances</dd>
+          </dl>
+
+          <tp:rationale>
+            <p>This argument allows for future extensions. For instance,
+              if moving to state <code>Failed</code> because the server
+              rejected one of our search terms, we could define a key
+              that indicates which terms were invalid.</p>
+          </tp:rationale>
+        </tp:docstring>
+      </arg>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+        <p>Emitted when the <tp:member-ref>SearchState</tp:member-ref> property
+          changes. The implementation MUST NOT make transitions other than the
+          following:</p>
+
+        <ul>
+          <li><code>Not_Started</code> → <code>In_Progress</code></li>
+          <li><code>In_Progress</code> → <code>More_Available</code></li>
+          <li><code>More_Available</code> → <code>In_Progress</code></li>
+          <li><code>In_Progress</code> → <code>Completed</code></li>
+          <li><code>In_Progress</code> → <code>Failed</code></li>
+        </ul>
+      </tp:docstring>
+    </signal>
+
+    <tp:simple-type name="Contact_Search_Key" type="s">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+        <p>Any of the following search keys, with the indicated result for
+          the search:</p>
+
+        <dl>
+          <dt>The empty string</dt>
+          <dd>Search for the search term in some implementation-dependent
+            set of fields, using an implementation-dependent algorithm
+            (e.g. searching for each word mentioned)
+            <tp:rationale>
+              The "one big search box" approach to searching, as is familiar
+              from Google. The Sametime plugin to Pidgin appears to search in
+              this way.
+            </tp:rationale>
+          </dd>
+
+          <dt>A <tp:type>VCard_Field</tp:type></dt>
+          <dd>Search for the search term in fields matching that name (for
+            instance, <code>nickname</code> would search nicknames, and
+            <code>tel</code> would search any available phone number,
+            regardless of its work/home/mobile/... status).</dd>
+
+          <dt>A <tp:type>VCard_Field</tp:type> followed by
+            "<code>;</code>" and a
+            <tp:type>VCard_Type_Parameter</tp:type> of the form
+            "<code>type=...</code>"</dt>
+          <dd>Search for the search term in fields of that name and type
+            only (for instance, <code>tel;type=mobile</code>).</dd>
+
+          <dt><code>x-telepathy-identifier</code></dt>
+          <dd>Search for contacts whose identifier in the IM protocol
+            matches the search term (e.g. contains it as a substring)
+            <tp:rationale>
+              Otherwise, starting a search by identifier would require the UI
+              to know the vCard field name corresponding to identifiers in
+              this protocol, which might be non-standard (like
+              <code>x-jabber</code>) or not exist at all.
+            </tp:rationale>
+          </dd>
+
+          <dt><code>x-gender</code></dt>
+          <dd>For the search term "male" or "female", search only for contacts
+            listed as male or female, respectively. The results for other
+            search terms are undefined; it is likely that contacts with
+            unspecified gender will only be matched if this search key
+            is omitted from the request.
+            <tp:rationale>
+              Examples in XEP-0055 suggest this usage, and at least Gadu-Gadu
+              also supports limiting search results by gender.
+            </tp:rationale>
+          </dd>
+
+          <dt><code>x-n-family</code></dt>
+          <dd>Search for the search term in contacts' family names
+            (the first component of the vCard field <code>n</code>).
+            <tp:rationale>
+              Gadu-Gadu and TOC seem to support this mode of searching.
+            </tp:rationale>
+          </dd>
+
+          <dt><code>x-n-given</code></dt>
+          <dd>Search for the search term in contacts' given names
+            (the second component of the vCard field <code>n</code>).
+            <tp:rationale>
+              As for <code>x-n-family</code>.
+            </tp:rationale>
+          </dd>
+
+          <dt><code>x-online</code></dt>
+          <dd>For the search term "yes", search only for contacts who are
+            currently online. The results for other search terms are undefined.
+            <tp:rationale>Gadu-Gadu appears to support this.</tp:rationale>
+          </dd>
+
+          <dt><code>x-adr-locality</code></dt>
+          <dd>Search for the search term as a locality or city (the fourth
+            component of the vCard field <code>adr</code>).
+            <tp:rationale>
+              Gadu-Gadu and TOC appear to support this.
+            </tp:rationale>
+          </dd>
+        </dl>
+      </tp:docstring>
+    </tp:simple-type>
+
+    <property name="Limit" type="u" access="read" tp:name-for-bindings="Limit"
+      tp:immutable='yes' tp:requestable='sometimes'>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+        <p>If supported by the protocol, the maximum number of results that
+          should be returned, where <code>0</code> represents no limit. If the
+          protocol does not support limiting results, this should be
+          <code>0</code>.</p>
+
+        <p>For example, if the terms passed to
+          <tp:member-ref>Search</tp:member-ref> match <i>Antonius</i>,
+          <i>Bridget</i> and <i>Charles</i> and this property is
+          <code>2</code>, the search service SHOULD only return <i>Antonius</i>
+          and <i>Bridget</i>.</p>
+
+        <p>This property SHOULD be requestable if and only if the protocol
+          supports specifying a limit; implementations SHOULD use
+          <code>0</code> as the default if possible, or a protocol-specific
+          sensible default otherwise.</p>
+      </tp:docstring>
+    </property>
+
+    <property name="AvailableSearchKeys" type="as" access="read"
+      tp:name-for-bindings="Available_Search_Keys" tp:immutable='yes'>
+      <tp:docstring>
+        The set of search keys supported by this channel. Example values
+        include [""] (for protocols where several address fields are
+        implicitly searched) or ["x-n-given", "x-n-family", "nickname",
+        "email"] (for XMPP XEP-0055, without extensibility via Data Forms).
+
+        <tp:rationale>
+          It can be in the <tp:dbus-ref
+            namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+          signal for round-trip reduction.
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <tp:mapping name="Contact_Search_Map">
+      <tp:docstring>A map from search keys to search terms.</tp:docstring>
+      <tp:member name="Key" type="s" tp:type="Contact_Search_Key">
+        <tp:docstring>
+          The search key to match against
+        </tp:docstring>
+      </tp:member>
+
+      <tp:member name="Term" type="s">
+        <tp:docstring>
+          The term or terms to be searched for in the search key; depending on
+          the protocol and the server implementation, this may be matched by
+          exact or approximate equality, substring matching, word matching
+          or any other matching algorithm
+        </tp:docstring>
+      </tp:member>
+    </tp:mapping>
+
+    <method name="Search" tp:name-for-bindings="Search">
+      <arg direction="in" name="Terms"
+        type="a{ss}" tp:type="Contact_Search_Map">
+        <tp:docstring>
+          A dictionary mapping search key names to the desired values
+        </tp:docstring>
+      </arg>
+      <tp:docstring>
+        Send a request to start a search for contacts on this connection. This
+        may only be called while the <tp:member-ref>SearchState</tp:member-ref>
+        is Not_Started; a valid search request will cause the
+        <tp:member-ref>SearchStateChanged</tp:member-ref> signal to be emitted
+        with the state In_Progress.
+      </tp:docstring>
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+          <tp:docstring>
+            The <tp:member-ref>SearchState</tp:member-ref> is no longer
+            Not_Started, so this method is no longer available.
+          </tp:docstring>
+        </tp:error>
+        <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+          <tp:docstring>
+            The search terms included something this connection manager cannot
+            search for.
+          </tp:docstring>
+        </tp:error>
+        <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+      </tp:possible-errors>
+    </method>
+
+    <method name="More" tp:name-for-bindings="More">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+        Request that a search in <tp:member-ref>SearchState</tp:member-ref>
+        <code>More_Available</code> move back to state <code>In_Progress</code>
+        and continue listing up to <tp:member-ref>Limit</tp:member-ref> more results.
+      </tp:docstring>
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+          <tp:docstring>
+            The <tp:member-ref>SearchState</tp:member-ref> is not
+            <code>More_Available</code>.
+          </tp:docstring>
+        </tp:error>
+      </tp:possible-errors>
+    </method>
+
+    <method name="Stop" tp:name-for-bindings="Stop">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+        <p>Stop the current search. This may not be called while the
+          <tp:member-ref>SearchState</tp:member-ref> is Not_Started. If called
+          while the SearchState is In_Progress,
+          <tp:member-ref>SearchStateChanged</tp:member-ref> will be emitted,
+          with the state Failed and the error
+          <code>org.freedesktop.Telepathy.Error.<tp:error-ref>Cancelled</tp:error-ref></code>.</p>
+
+        <p>Calling this method on a search in state Completed or Failed
+          succeeds, but has no effect.</p>
+
+        <tp:rationale>
+          <p>Specifying Stop to succeed when the search has finished means that
+            clients who call Stop just before receiving
+            <tp:member-ref>SearchStateChanged</tp:member-ref> don't have to
+            handle a useless error.</p>
+        </tp:rationale>
+
+        <p>Depending on the protocol, the connection manager may not be
+          able to prevent the server from sending further results after this
+          method returns; if this is the case, it MUST ignore any further
+          results.</p>
+      </tp:docstring>
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+          <tp:docstring>
+            The <tp:member-ref>SearchState</tp:member-ref> is Not_Started, so
+            this method is not yet available.
+          </tp:docstring>
+        </tp:error>
+      </tp:possible-errors>
+    </method>
+
+    <tp:mapping name="Contact_Search_Result_Map">
+      <tp:docstring>A map from contact identifier to search result, emitted in
+        the <tp:member-ref>SearchResultReceived</tp:member-ref>
+        signal.</tp:docstring>
+
+      <tp:member name="Contact_Identifier" type="s">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+          The identifier of a contact matching the search terms.
+
+          <tp:rationale>
+            This is an identifier rather than a handle in case we make handles
+            immortal; see <a
+              href="https://bugs.freedesktop.org/show_bug.cgi?id=23155">fd.o#23155</a>
+            and <a
+              href="https://bugs.freedesktop.org/show_bug.cgi?id=13347#c5">fd.o#13347
+            comment 5</a>.
+          </tp:rationale>
+        </tp:docstring>
+      </tp:member>
+
+      <tp:member name="Info" type="a(sasas)" tp:type="Contact_Info_Field[]">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+          <p>An array of fields representing information about this
+            contact, in the same format used in the <tp:dbus-ref
+              namespace="org.freedesktop.Telepathy.Connection.Interface">ContactInfo</tp:dbus-ref>
+            interface. It is possible that a separate call to <tp:dbus-ref
+              namespace="org.freedesktop.Telepathy.Connection.Interface.ContactInfo">RequestContactInfo</tp:dbus-ref>
+            would return more information than this signal provides.</p>
+        </tp:docstring>
+      </tp:member>
+    </tp:mapping>
+
+    <signal name="SearchResultReceived"
+      tp:name-for-bindings="Search_Result_Received">
+      <arg name="Result" type="a{sa(sasas)}" tp:type="Contact_Search_Result_Map">
+        <tp:docstring>A mapping from contact identifier to an array of fields
+          representing information about this contact.</tp:docstring>
+      </arg>
+
+      <tp:docstring>
+        Emitted when a some search results are received from the server.
+        This signal can be fired arbitrarily many times so clients MUST NOT
+        assume they'll get only one signal.
+      </tp:docstring>
+    </signal>
+
+    <property name="Server" tp:name-for-bindings="Server"
+      type="s" access="read" tp:requestable='sometimes' tp:immutable='yes'>
+      <tp:docstring>
+        <p>For protocols which support searching for contacts on multiple
+          servers with different DNS names (like XMPP), the DNS name of the
+          server being searched by this channel, e.g.
+          "characters.shakespeare.lit".  Otherwise, the empty string.</p>
+
+        <tp:rationale>
+          <p>XEP 0055 defines a mechanism for XMPP clients to search services
+            of their choice for contacts, such as users.jabber.org (the "Jabber
+            User Directory").</p>
+        </tp:rationale>
+
+        <p>This property SHOULD be requestable if and only if the
+          protocol supports querying multiple different servers;
+          implementations SHOULD use a sensible default if possible if this
+          property is not specified in a channel request.</p>
+
+        <tp:rationale>
+          <p>This allows a client to perform searches on a protocol it knows
+            nothing about without requiring the user to guess a valid server's
+            hostname.</p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+  </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index ef8b751..928ea5c 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -13,6 +13,7 @@ EXTRA_DIST = \
     generic-types.xml \
     misc.xml \
     Debug.xml \
+    Channel_Type_Contact_Search.xml \
     Logger.xml \
     Connection_Interface_Renaming.xml \
     Authentication_TLS_Certificate.xml \
diff --git a/extensions/all.xml b/extensions/all.xml
index d0b9173..877bf57 100644
--- a/extensions/all.xml
+++ b/extensions/all.xml
@@ -18,5 +18,17 @@
     from="Telepathy specification"/>
 </tp:generic-types>
 
+<!-- types for Channel.Type.ContactSearch -->
+<tp:generic-types>
+  <tp:external-type name="Requestable_Channel_Class" type="a{sv}as"
+    from="Telepathy specification"/>
+  <tp:external-type name="VCard_Field" type="s"
+    from="Telepathy specification"/>
+  <tp:external-type name="VCard_Type_Parameter" type="s"
+    from="Telepathy specification"/>
+  <tp:external-type name="Contact_Info_Field" type="(sasas)"
+    from="Telepathy specification"/>
+</tp:generic-types>
+
 
 </tp:spec>
diff --git a/extensions/misc.xml b/extensions/misc.xml
index ad0d59e..4529c0c 100644
--- a/extensions/misc.xml
+++ b/extensions/misc.xml
@@ -5,6 +5,7 @@
 <tp:title>Misc extensions for Empathy</tp:title>
 
 <xi:include href="Debug.xml" />
+<xi:include href="Channel_Type_Contact_Search.xml" />
 <xi:include href="Logger.xml" />
 <xi:include href="Connection_Interface_Renaming.xml" />
 <xi:include href="Authentication_TLS_Certificate.xml" />
diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
index 423e417..b6d6804 100644
--- a/libempathy-gtk/Makefile.am
+++ b/libempathy-gtk/Makefile.am
@@ -47,6 +47,7 @@ libempathy_gtk_handwritten_source =            	\
 	empathy-contact-menu.c			\
 	empathy-linking-dialog.c		\
 	empathy-live-search.c			\
+	empathy-contact-search-dialog.c		\
 	empathy-contact-selector.c		\
 	empathy-contact-selector-dialog.c \
 	empathy-contact-widget.c		\
@@ -109,6 +110,7 @@ libempathy_gtk_headers =			\
 	empathy-contact-menu.h			\
 	empathy-linking-dialog.h		\
 	empathy-live-search.h			\
+	empathy-contact-search-dialog.h		\
 	empathy-contact-selector.h		\
 	empathy-contact-selector-dialog.h \
 	empathy-contact-widget.h		\
diff --git a/libempathy-gtk/empathy-contact-search-dialog.c b/libempathy-gtk/empathy-contact-search-dialog.c
new file mode 100644
index 0000000..0a172cc
--- /dev/null
+++ b/libempathy-gtk/empathy-contact-search-dialog.c
@@ -0,0 +1,431 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * empathy-contact-search-dialog.c
+ *
+ * Copyright (C) 2010-2011 Collabora Ltd.
+ *
+ * The code contained in this file 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 file 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 code; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *     Danielle Madeley <danielle.madeley@collabora.co.uk>
+ *     Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
+ */
+
+#include <glib/gi18n.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-tp-contact-factory.h>
+
+#include <libempathy-gtk/empathy-account-chooser.h>
+#include <libempathy-gtk/empathy-cell-renderer-text.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
+#include <libempathy/empathy-debug.h>
+
+#include "empathy-contact-search-dialog.h"
+
+#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CONTACT_SEARCH_DIALOG, EmpathyContactSearchDialogPrivate))
+
+G_DEFINE_TYPE (EmpathyContactSearchDialog, empathy_contact_search_dialog, GTK_TYPE_DIALOG);
+
+enum
+{
+   NAME_COLUMN,
+   LOGIN_COLUMN,
+   N_COLUMNS
+};
+
+typedef struct _EmpathyContactSearchDialogPrivate EmpathyContactSearchDialogPrivate;
+struct _EmpathyContactSearchDialogPrivate
+{
+  TpContactSearch *searcher;
+  GtkListStore *store;
+
+  GtkWidget *chooser;
+  GtkWidget *tree_view;
+  GtkWidget *spinner;
+  GtkWidget *find_button;
+  GtkWidget *search_entry;
+  //GtkWidget *server_entry;
+};
+
+static void
+empathy_contact_search_dialog_dispose (GObject *self)
+{
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+
+  tp_clear_object (&priv->searcher);
+
+  G_OBJECT_CLASS (empathy_contact_search_dialog_parent_class)->dispose (self);
+}
+
+static void
+on_searcher_reset (GObject *source_object,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyContactSearchDialog *self = EMPATHY_CONTACT_SEARCH_DIALOG (user_data);
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+  TpContactSearch *searcher = TP_CONTACT_SEARCH (source_object);
+  GError *error = NULL;
+  GHashTable *search;
+  const gchar *search_criteria;
+
+  tp_contact_search_reset_finish (searcher, result, &error);
+  if (error != NULL)
+    {
+      DEBUG ("Failed to reset the TpContactSearch: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  search = g_hash_table_new (g_str_hash, g_str_equal);
+
+  search_criteria = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
+  g_hash_table_insert (search, "fn", (gpointer) search_criteria);
+
+  gtk_list_store_clear (priv->store);
+  tp_contact_search_start (priv->searcher, search);
+
+  g_hash_table_destroy (search);
+}
+
+static void
+empathy_contact_search_dialog_do_search (EmpathyContactSearchDialog *self)
+{
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+
+  tp_contact_search_reset_async (priv->searcher,
+      "", //gtk_entry_get_text (GTK_ENTRY (priv->server_entry)),
+      0,
+      on_searcher_reset,
+      self);
+}
+
+static void
+on_get_contact_factory_get_from_id_cb (TpConnection *connection,
+    EmpathyContact *contact,
+    const GError *error,
+    gpointer user_data,
+    GObject *object)
+{
+    EmpathyContactManager *manager = empathy_contact_manager_dup_singleton ();
+
+    if (error)
+      {
+        g_warning ("Error while getting the contact: %s", error->message);
+        return;
+      }
+
+    empathy_contact_list_add (EMPATHY_CONTACT_LIST (manager), contact, "");
+}
+
+static void
+add_selected_contact (EmpathyContactSearchDialog *self)
+{
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
+  TpConnection *conn;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  gboolean sel;
+  gchar *id;
+
+  conn = empathy_account_chooser_get_connection (EMPATHY_ACCOUNT_CHOOSER (priv->chooser));
+
+  sel = gtk_tree_selection_get_selected (selection, &model, &iter);
+  g_return_if_fail (sel == TRUE);
+
+  gtk_tree_model_get (model, &iter, LOGIN_COLUMN, &id, -1);
+
+  DEBUG ("Requested to add contact: %s", id);
+
+  empathy_tp_contact_factory_get_from_id (conn, id,
+      on_get_contact_factory_get_from_id_cb, NULL,
+      NULL, NULL);
+}
+
+static void
+empathy_contact_search_dialog_response (GtkDialog *self,
+    gint response)
+{
+  switch (response)
+    {
+      case GTK_RESPONSE_APPLY:
+        add_selected_contact (EMPATHY_CONTACT_SEARCH_DIALOG (self));
+        break;
+      default:
+        gtk_widget_destroy (GTK_WIDGET (self));
+        break;
+    }
+}
+
+static void
+empathy_contact_search_dialog_class_init (
+    EmpathyContactSearchDialogClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
+
+  gobject_class->dispose = empathy_contact_search_dialog_dispose;
+
+  dialog_class->response = empathy_contact_search_dialog_response;
+
+  g_type_class_add_private (gobject_class,
+      sizeof (EmpathyContactSearchDialogPrivate));
+}
+
+static void
+_on_search_state_changed_cb (TpContactSearch *searcher,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyContactSearchDialog *self = EMPATHY_CONTACT_SEARCH_DIALOG (user_data);
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+  TpChannelContactSearchState state;
+
+  g_object_get (searcher, "state", &state, NULL);
+
+  DEBUG ("new search status: %d", state);
+
+  if (state == TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS)
+    gtk_spinner_start (GTK_SPINNER (priv->spinner));
+  else
+    gtk_spinner_stop (GTK_SPINNER (priv->spinner));
+}
+
+static void
+_search_results_received (TpContactSearch *searcher,
+    GList *results,
+    EmpathyContactSearchDialog *self)
+{
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+  const TpContactInfoField *name;
+  GList *l;
+
+  for (l = results; l != NULL; l = l->next)
+    {
+      TpContactSearchResult *result = l->data;
+      GtkTreeIter iter;
+
+      gtk_list_store_append (priv->store, &iter);
+
+      name = tp_contact_search_result_get_field (result, "fn");
+
+      gtk_list_store_set (priv->store, &iter,
+          NAME_COLUMN, name ? name->field_value[0] : NULL,
+          LOGIN_COLUMN, tp_contact_search_result_get_identifier (result),
+          -1);
+    }
+}
+
+static void
+on_searcher_created (GObject *source_object,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyContactSearchDialog *self;
+  EmpathyContactSearchDialogPrivate *priv;
+  GError *error = NULL;
+
+  if (EMPATHY_IS_CONTACT_SEARCH_DIALOG (user_data) == FALSE)
+    /* This happens if the dialog is closed before the callback is called */
+    return;
+
+  self = EMPATHY_CONTACT_SEARCH_DIALOG (user_data);
+  priv = GET_PRIVATE (self);
+
+  priv->searcher = tp_contact_search_new_finish (result, &error);
+  if (error)
+    {
+      DEBUG ("Failed to create a TpContactSearch: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  g_signal_connect (priv->searcher, "search-results-received",
+      G_CALLBACK (_search_results_received), self);
+  g_signal_connect (priv->searcher, "notify::state",
+      G_CALLBACK (_on_search_state_changed_cb), self);
+
+  gtk_widget_set_sensitive (priv->find_button, TRUE);
+}
+
+static void
+_account_chooser_changed (EmpathyAccountChooser *chooser,
+    EmpathyContactSearchDialog *self)
+{
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+  TpAccount *account = empathy_account_chooser_get_account (chooser);
+  TpConnection *conn = empathy_account_chooser_get_connection (chooser);
+  TpCapabilities *caps = tp_connection_get_capabilities (conn);
+  gboolean can_cs, can_set_limit, can_set_server;
+
+  can_cs = tp_capabilities_supports_contact_search (caps,
+      &can_set_limit, &can_set_server);
+  DEBUG ("The server supports cs|limit|server: %s|%s|%s",
+      can_cs ? "yes" : "no",
+      can_set_limit ? "yes" : "no",
+      can_set_server ? "yes" : "no");
+
+//  gtk_widget_set_sensitive (priv->server_entry, can_set_server);
+  gtk_widget_set_sensitive (priv->find_button, FALSE);
+
+  DEBUG ("New account is %s", tp_proxy_get_object_path (account));
+
+  tp_clear_object (&priv->searcher);
+  tp_contact_search_new_async (account,
+      "", //gtk_entry_get_text (GTK_ENTRY (priv->server_entry)),
+      0,
+      on_searcher_created, self);
+}
+
+static void
+_on_button_search_clicked (GtkButton *button,
+    EmpathyContactSearchDialog *self)
+{
+  empathy_contact_search_dialog_do_search (self);
+}
+
+#if 0
+static void
+on_server_changed_cb (GtkEditable *editable,
+    gpointer user_data)
+{
+  EmpathyContactSearchDialog *self = EMPATHY_CONTACT_SEARCH_DIALOG (user_data);
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+
+  g_return_if_fail (priv->searcher != NULL);
+
+  tp_contact_search_reset_async (priv->searcher,
+      gtk_entry_get_text (GTK_ENTRY (editable)),
+      0,
+      on_searcher_reset,
+      self);
+}
+#endif
+
+static void
+empathy_contact_search_dialog_init (EmpathyContactSearchDialog *self)
+{
+  EmpathyContactSearchDialogPrivate *priv = GET_PRIVATE (self);
+  GtkWidget *vbox, *hbox, *scrolled_window, *label;
+  GtkCellRenderer *cell;
+  GtkTreeViewColumn *col;
+  GtkTreeSelection *selection;
+
+  vbox = gtk_vbox_new (FALSE, 6);
+
+  /* Account chooser */
+  hbox = gtk_hbox_new (FALSE, 6);
+  label = gtk_label_new (_("Account:"));
+  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
+
+  priv->chooser = empathy_account_chooser_new ();
+  empathy_account_chooser_set_filter (EMPATHY_ACCOUNT_CHOOSER (priv->chooser),
+      empathy_account_chooser_filter_is_connected, NULL);
+  gtk_box_pack_start (GTK_BOX (hbox), priv->chooser, FALSE, TRUE, 0);
+  g_signal_connect (priv->chooser, "changed",
+      G_CALLBACK (_account_chooser_changed), self);
+
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 6);
+
+#if 0
+  /* Server entry */
+  priv->server_entry = gtk_entry_new ();
+  gtk_box_pack_start (GTK_BOX (vbox), priv->server_entry, FALSE, TRUE, 0);
+  g_signal_connect (GTK_EDITABLE (priv->server_entry), "changed",
+      G_CALLBACK (on_server_changed_cb), self);
+#endif
+
+  /* Search input */
+  hbox = gtk_hbox_new (FALSE, 6);
+
+  label = gtk_label_new (_("Enter search criteria"));
+  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
+  priv->spinner = gtk_spinner_new ();
+  gtk_box_pack_end (GTK_BOX (hbox), priv->spinner, FALSE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  priv->search_entry = gtk_entry_new ();
+  gtk_box_pack_start (GTK_BOX (hbox), priv->search_entry, TRUE, TRUE, 0);
+
+  priv->find_button = gtk_button_new_from_stock (GTK_STOCK_FIND);
+  g_signal_connect (priv->find_button, "clicked", G_CALLBACK (_on_button_search_clicked), self);
+  gtk_box_pack_end (GTK_BOX (hbox), priv->find_button, FALSE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 10);
+
+  /* Search results */
+  priv->store = gtk_list_store_new (N_COLUMNS,
+                                    G_TYPE_STRING,  /* Name */
+                                    G_TYPE_STRING); /* Login */
+
+  priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (priv->store));
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
+  gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
+
+  col = gtk_tree_view_column_new ();
+  cell = empathy_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (col, cell, TRUE);
+  /* EmpathyCellRendererText displays "name" above and "status" below.
+   * We want the login above since it'll always be available, and the
+   * name below since we won't always have it. */
+  gtk_tree_view_column_add_attribute (col, cell,
+      "name", LOGIN_COLUMN);
+  gtk_tree_view_column_add_attribute (col, cell,
+      "status", NAME_COLUMN);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), col);
+
+  gtk_dialog_add_buttons (GTK_DIALOG (self),
+      GTK_STOCK_ADD, GTK_RESPONSE_APPLY,
+      NULL);
+
+  /* Pack the dialog */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+      GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+  gtk_container_add (GTK_CONTAINER (scrolled_window), priv->tree_view);
+  gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
+
+  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (
+          GTK_DIALOG (self))), vbox, TRUE, TRUE, 3);
+
+  gtk_window_set_default_size (GTK_WINDOW (self), 200, 400);
+  gtk_widget_show_all (vbox);
+}
+
+GtkWidget *
+empathy_contact_search_dialog_new (GtkWindow *parent)
+{
+  GtkWidget *self;
+
+  g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL);
+
+  self = g_object_new (EMPATHY_TYPE_CONTACT_SEARCH_DIALOG, NULL);
+
+  if (parent != NULL)
+    {
+      gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+    }
+
+  return self;
+}
diff --git a/libempathy-gtk/empathy-contact-search-dialog.h b/libempathy-gtk/empathy-contact-search-dialog.h
new file mode 100644
index 0000000..1421286
--- /dev/null
+++ b/libempathy-gtk/empathy-contact-search-dialog.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * empathy-contact-search-dialog.h
+ *
+ * Copyright (C) 2010-2011 Collabora Ltd.
+ *
+ * The code contained in this file 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 file 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 code; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *     Danielle Madeley <danielle.madeley@collabora.co.uk>
+ *     Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_CONTACT_SEARCH_DIALOG_H__
+#define __EMPATHY_CONTACT_SEARCH_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CONTACT_SEARCH_DIALOG	(empathy_contact_search_dialog_get_type ())
+#define EMPATHY_CONTACT_SEARCH_DIALOG(obj)	(G_TYPE_CHECK_INSTANCE_CAST ((obj), EMPATHY_TYPE_CONTACT_SEARCH_DIALOG, EmpathyContactSearchDialog))
+#define EMPATHY_CONTACT_SEARCH_DIALOG_CLASS(obj)	(G_TYPE_CHECK_CLASS_CAST ((obj), EMPATHY_TYPE_CONTACT_SEARCH_DIALOG, EmpathyContactSearchDialogClass))
+#define EMPATHY_IS_CONTACT_SEARCH_DIALOG(obj)	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), EMPATHY_TYPE_CONTACT_SEARCH_DIALOG))
+#define EMPATHY_IS_CONTACT_SEARCH_DIALOG_CLASS(obj)	(G_TYPE_CHECK_CLASS_TYPE ((obj), EMPATHY_TYPE_CONTACT_SEARCH_DIALOG))
+#define EMPATHY_CONTACT_SEARCH_DIALOG_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CONTACT_SEARCH_DIALOG, EmpathyContactSearchDialogClass))
+
+typedef struct _EmpathyContactSearchDialog EmpathyContactSearchDialog;
+typedef struct _EmpathyContactSearchDialogClass EmpathyContactSearchDialogClass;
+
+struct _EmpathyContactSearchDialog
+{
+  GtkDialog parent;
+};
+
+struct _EmpathyContactSearchDialogClass
+{
+  GtkDialogClass parent_class;
+};
+
+GType empathy_contact_search_dialog_get_type (void);
+GtkWidget *empathy_contact_search_dialog_new (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif
diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c
index 740220b..c198e53 100644
--- a/src/empathy-main-window.c
+++ b/src/empathy-main-window.c
@@ -51,6 +51,7 @@
 #include <libempathy-gtk/empathy-contact-list-store.h>
 #include <libempathy-gtk/empathy-contact-list-view.h>
 #include <libempathy-gtk/empathy-live-search.h>
+#include <libempathy-gtk/empathy-contact-search-dialog.h>
 #include <libempathy-gtk/empathy-geometry.h>
 #include <libempathy-gtk/empathy-gtk-enum-types.h>
 #include <libempathy-gtk/empathy-individual-dialogs.h>
@@ -936,6 +937,15 @@ main_window_chat_add_contact_cb (GtkAction         *action,
 }
 
 static void
+main_window_chat_search_contacts_cb (GtkAction         *action,
+				     EmpathyMainWindow *window)
+{
+	GtkWidget *dialog = empathy_contact_search_dialog_new (
+			GTK_WINDOW (window));
+	gtk_widget_show (dialog);
+}
+
+static void
 main_window_view_show_ft_manager (GtkAction         *action,
 				  EmpathyMainWindow *window)
 {
@@ -1779,6 +1789,7 @@ empathy_main_window_init (EmpathyMainWindow *window)
 			      "room_join_favorites", "activate", main_window_room_join_favorites_cb,
 			      "room_manage_favorites", "activate", main_window_room_manage_favorites_cb,
 			      "chat_add_contact", "activate", main_window_chat_add_contact_cb,
+			      "chat_search_contacts", "activate", main_window_chat_search_contacts_cb,
 			      "view_show_ft_manager", "activate", main_window_view_show_ft_manager,
 			      "view_show_offline", "toggled", main_window_view_show_offline_cb,
 			      "view_show_protocols", "toggled", main_window_view_show_protocols_cb,
diff --git a/src/empathy-main-window.ui b/src/empathy-main-window.ui
index e684c6b..c4c1b79 100644
--- a/src/empathy-main-window.ui
+++ b/src/empathy-main-window.ui
@@ -42,6 +42,13 @@
           </object>
         </child>
         <child>
+          <object class="GtkAction" id="chat_search_contacts">
+            <property name="stock_id">gtk-find</property>
+            <property name="name">chat_search_contacts</property>
+            <property name="label" translatable="yes">_Search for Contacts…</property>
+          </object>
+        </child>
+        <child>
           <object class="GtkAction" id="view_show_ft_manager">
             <property name="icon-name">document-send</property>
             <property name="name">view_show_ft_manager</property>
@@ -232,6 +239,7 @@
           <menuitem action="chat_new_call"/>
           <separator/>
           <menuitem action="chat_add_contact"/>
+          <menuitem action="chat_search_contacts"/>
           <separator/>
           <menuitem action="chat_quit"/>
         </menu>
diff --git a/tools/glib-interfaces-gen.py b/tools/glib-interfaces-gen.py
index 741626c..69c721b 100644
--- a/tools/glib-interfaces-gen.py
+++ b/tools/glib-interfaces-gen.py
@@ -3,36 +3,50 @@
 from sys import argv, stdout, stderr
 import xml.dom.minidom
 
-from libglibcodegen import NS_TP, camelcase_to_upper, get_docstring, \
+from libglibcodegen import NS_TP, get_docstring, \
         get_descendant_text, get_by_path
 
 class Generator(object):
     def __init__(self, prefix, implfile, declfile, dom):
         self.prefix = prefix + '_'
+
+        assert declfile.endswith('.h')
+        docfile = declfile[:-2] + '-gtk-doc.h'
+
         self.impls = open(implfile, 'w')
         self.decls = open(declfile, 'w')
+        self.docs = open(docfile, 'w')
         self.spec = get_by_path(dom, "spec")[0]
 
+    def h(self, code):
+        self.decls.write(code.encode('utf-8'))
+
+    def c(self, code):
+        self.impls.write(code.encode('utf-8'))
+
+    def d(self, code):
+        self.docs.write(code.encode('utf-8'))
+
     def __call__(self):
-        for file in self.decls, self.impls:
-            self.do_header(file)
+        for f in self.h, self.c:
+            self.do_header(f)
         self.do_body()
 
     # Header
-    def do_header(self, file):
-        file.write('/* Generated from: ')
-        file.write(get_descendant_text(get_by_path(self.spec, 'title')))
+    def do_header(self, f):
+        f('/* Generated from: ')
+        f(get_descendant_text(get_by_path(self.spec, 'title')))
         version = get_by_path(self.spec, "version")
         if version:
-            file.write(' version ' + get_descendant_text(version))
-        file.write('\n\n')
+            f(' version ' + get_descendant_text(version))
+        f('\n\n')
         for copyright in get_by_path(self.spec, 'copyright'):
-            stdout.write(get_descendant_text(copyright))
-            stdout.write('\n')
-        file.write('\n')
-        file.write(get_descendant_text(get_by_path(self.spec, 'license')))
-        file.write(get_descendant_text(get_by_path(self.spec, 'docstring')))
-        file.write("""
+            f(get_descendant_text(copyright))
+            f('\n')
+        f('\n')
+        f(get_descendant_text(get_by_path(self.spec, 'license')))
+        f(get_descendant_text(get_by_path(self.spec, 'docstring')))
+        f("""
  */
 
 """)
@@ -44,25 +58,37 @@ class Generator(object):
 
     def do_iface(self, iface):
         parent_name = get_by_path(iface, '../@name')
-        self.decls.write("""\
+        self.d("""\
 /**
  * %(IFACE_DEFINE)s:
- * 
+ *
  * The interface name "%(name)s"
  */
+""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
+            parent_name).upper().replace('/', ''),
+       'name' : iface.getAttribute('name')})
+
+        self.h("""
 #define %(IFACE_DEFINE)s \\
 "%(name)s"
 """ % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
             parent_name).upper().replace('/', ''),
        'name' : iface.getAttribute('name')})
 
-        self.decls.write("""
+        self.d("""
 /**
  * %(IFACE_QUARK_DEFINE)s:
- * 
+ *
  * Expands to a call to a function that returns a quark for the interface \
 name "%(name)s"
  */
+""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \
+            parent_name).upper().replace('/', ''),
+       'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+            parent_name).lower().replace('/', ''),
+       'name' : iface.getAttribute('name')})
+
+        self.h("""
 #define %(IFACE_QUARK_DEFINE)s \\
   (%(iface_quark_func)s ())
 
@@ -74,7 +100,7 @@ GQuark %(iface_quark_func)s (void);
             parent_name).lower().replace('/', ''),
        'name' : iface.getAttribute('name')})
 
-        self.impls.write("""\
+        self.c("""\
 GQuark
 %(iface_quark_func)s (void)
 {
@@ -92,6 +118,80 @@ GQuark
             parent_name).lower().replace('/', ''),
        'name' : iface.getAttribute('name')})
 
+        for prop in iface.getElementsByTagNameNS(None, 'property'):
+            self.d("""
+/**
+ * %(IFACE_PREFIX)s_%(PROP_UC)s:
+ *
+ * The fully-qualified property name "%(name)s.%(prop)s"
+ */
+""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
+                parent_name).upper().replace('/', ''),
+           'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(),
+           'name' : iface.getAttribute('name'),
+           'prop' : prop.getAttribute('name'),
+           })
+
+            self.h("""
+#define %(IFACE_PREFIX)s_%(PROP_UC)s \\
+"%(name)s.%(prop)s"
+""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
+                parent_name).upper().replace('/', ''),
+           'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(),
+           'name' : iface.getAttribute('name'),
+           'prop' : prop.getAttribute('name'),
+           })
+
+
+        for prop in iface.getElementsByTagNameNS(NS_TP, 'contact-attribute'):
+            self.d("""
+/**
+ * %(TOKEN_PREFIX)s_%(TOKEN_UC)s:
+ *
+ * The fully-qualified contact attribute token name "%(name)s/%(prop)s"
+ */
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+                parent_name).upper().replace('/', ''),
+           'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+           'name' : iface.getAttribute('name'),
+           'prop' : prop.getAttribute('name'),
+           })
+
+            self.h("""
+#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\
+"%(name)s/%(prop)s"
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+                parent_name).upper().replace('/', ''),
+           'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+           'name' : iface.getAttribute('name'),
+           'prop' : prop.getAttribute('name'),
+           })
+
+        for prop in iface.getElementsByTagNameNS(NS_TP, 'hct'):
+            if (prop.getAttribute('is-family') != "yes"):
+                self.d("""
+/**
+ * %(TOKEN_PREFIX)s_%(TOKEN_UC)s:
+ *
+ * The fully-qualified capability token name "%(name)s/%(prop)s"
+ */
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+                parent_name).upper().replace('/', ''),
+           'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+           'name' : iface.getAttribute('name'),
+           'prop' : prop.getAttribute('name'),
+           })
+
+                self.h("""
+#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\
+"%(name)s/%(prop)s"
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+                parent_name).upper().replace('/', ''),
+           'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+           'name' : iface.getAttribute('name'),
+           'prop' : prop.getAttribute('name'),
+           })
+
 if __name__ == '__main__':
     argv = argv[1:]
     Generator(argv[0], argv[1], argv[2], xml.dom.minidom.parse(argv[3]))()
