[gcr/wip/dueno/prompter] system-prompter: Add DBus interface sending secret through FD [ci skip]



commit 03873e4721bf529f3eb952ab4d5a3bc81477d2c2
Author: Daiki Ueno <dueno src gnome org>
Date:   Sun Sep 8 17:52:35 2019 +0200

    system-prompter: Add DBus interface sending secret through FD [ci skip]

 gcr/Makefile.am                     |   3 +-
 gcr/gcr-dbus-constants.h            |   2 +
 gcr/gcr-system-prompt.c             | 101 +++++++++++++++++--------
 gcr/gcr-system-prompter.c           | 144 +++++++++++++++++++++++++++---------
 gcr/org.gnome.keyring.Prompter2.xml |  84 +++++++++++++++++++++
 5 files changed, 267 insertions(+), 67 deletions(-)
---
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 3ad96f4..4913dca 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -155,7 +155,8 @@ gcr/gcr-oids.c: gcr/gcr-oids.list gcr/gcr-mkoids
 gcr/gcr-oids.h: gcr/gcr-oids.c
 
 DBUS_XML_DEFINITIONS = \
-       gcr/org.gnome.keyring.Prompter.xml
+       gcr/org.gnome.keyring.Prompter.xml \
+       gcr/org.gnome.keyring.Prompter2.xml
 
 gcr/gcr-dbus-generated.c: $(DBUS_XML_DEFINITIONS)
        $(AM_V_GEN) $(GDBUS_CODEGEN) --interface-prefix org.gnome.keyring.internal. \
diff --git a/gcr/gcr-dbus-constants.h b/gcr/gcr-dbus-constants.h
index e4303d9..193542c 100644
--- a/gcr/gcr-dbus-constants.h
+++ b/gcr/gcr-dbus-constants.h
@@ -34,12 +34,14 @@ G_BEGIN_DECLS
 #define GCR_DBUS_PROMPT_OBJECT_PREFIX                "/org/gnome/keyring/Prompt"
 
 #define GCR_DBUS_PROMPTER_INTERFACE                  "org.gnome.keyring.internal.Prompter"
+#define GCR_DBUS_PROMPTER2_INTERFACE                 "org.gnome.keyring.internal.Prompter2"
 
 #define GCR_DBUS_PROMPTER_METHOD_BEGIN               "BeginPrompting"
 #define GCR_DBUS_PROMPTER_METHOD_STOP                "StopPrompting"
 #define GCR_DBUS_PROMPTER_METHOD_PERFORM             "PerformPrompt"
 
 #define GCR_DBUS_CALLBACK_INTERFACE                  "org.gnome.keyring.internal.Prompter.Callback"
+#define GCR_DBUS_CALLBACK2_INTERFACE                 "org.gnome.keyring.internal.Prompter.Callback2"
 
 #define GCR_DBUS_PROMPT_ERROR_IN_PROGRESS            "org.gnome.keyring.Prompter.InProgress"
 #define GCR_DBUS_PROMPT_ERROR_FAILED                 "org.gnome.keyring.Prompter.Failed"
diff --git a/gcr/gcr-system-prompt.c b/gcr/gcr-system-prompt.c
index 08a4a4a..7d13881 100644
--- a/gcr/gcr-system-prompt.c
+++ b/gcr/gcr-system-prompt.c
@@ -120,6 +120,7 @@ struct _GcrSystemPromptPrivate {
 
        GSimpleAsyncResult *pending;
        gchar *last_response;
+       gint fd;
 };
 
 static void     gcr_system_prompt_prompt_iface         (GcrPromptIface *iface);
@@ -485,11 +486,15 @@ perform_close (GcrSystemPrompt *self,
 
        if (self->pv->begun_prompting) {
                if (self->pv->connection && self->pv->prompt_path && self->pv->prompt_owner) {
-                       g_debug ("Calling the prompter %s method", GCR_DBUS_PROMPTER_METHOD_STOP);
+                       const gchar *interface_name =
+                               self->pv->exchange ? GCR_DBUS_PROMPTER_INTERFACE : 
GCR_DBUS_PROMPTER2_INTERFACE;
+                       
+                       g_debug ("Calling the prompter %s.%s method",
+                                interface_name, GCR_DBUS_PROMPTER_METHOD_STOP);
                        g_dbus_connection_call (self->pv->connection,
                                                self->pv->prompter_bus_name,
                                                GCR_DBUS_PROMPTER_OBJECT_PATH,
-                                               GCR_DBUS_PROMPTER_INTERFACE,
+                                               interface_name,
                                                GCR_DBUS_PROMPTER_METHOD_STOP,
                                                g_variant_new ("(o)", self->pv->prompt_path),
                                                G_VARIANT_TYPE ("()"),
@@ -647,7 +652,6 @@ prompt_method_ready (GcrSystemPrompt *self,
                      GDBusMethodInvocation *invocation,
                      GVariant *parameters)
 {
-       GcrSecretExchange *exchange;
        GSimpleAsyncResult *res;
        GVariantIter *iter;
        gchar *received;
@@ -664,12 +668,15 @@ prompt_method_ready (GcrSystemPrompt *self,
        update_properties_from_iter (self, iter);
        g_variant_iter_free (iter);
 
-       exchange = gcr_system_prompt_get_secret_exchange (self);
-       if (gcr_secret_exchange_receive (exchange, received))
-               self->pv->received = TRUE;
-       else
-               g_warning ("received invalid secret exchange string");
-       g_free (received);
+       if (self->pv->exchange == NULL) {
+               
+       } else {
+               if (gcr_secret_exchange_receive (exchange, received))
+                       self->pv->received = TRUE;
+               else
+                       g_warning ("received invalid secret exchange string");
+               g_free (received);
+       }
 
        res = g_object_ref (self->pv->pending);
        g_clear_object (&self->pv->pending);
@@ -755,6 +762,7 @@ register_prompt_object (GcrSystemPrompt *self,
                         GError **error)
 {
        GError *lerror = NULL;
+       GDBusInterfaceInfo *interface_info;
        guint id;
 
        /*
@@ -766,9 +774,13 @@ register_prompt_object (GcrSystemPrompt *self,
                g_dbus_connection_unregister_object (self->pv->connection,
                                                     self->pv->prompt_registered);
 
+       interface_info = self->pv->exchange ?
+               _gcr_dbus_prompter_callback_interface_info () :
+               _gcr_dbus_prompter_callback2_interface_info ();
+
        id = g_dbus_connection_register_object (self->pv->connection,
                                                self->pv->prompt_path,
-                                               _gcr_dbus_prompter_callback_interface_info (),
+                                               interface_info,
                                                &prompt_dbus_vtable,
                                                self, NULL, &lerror);
        self->pv->prompt_registered = id;
@@ -1168,10 +1180,8 @@ perform_prompt_async (GcrSystemPrompt *self,
                       gpointer user_data)
 {
        GSimpleAsyncResult *res;
-       GcrSecretExchange *exchange;
        GVariantBuilder *builder;
        CallClosure *closure;
-       gchar *sent;
 
        g_return_if_fail (GCR_IS_SYSTEM_PROMPT (self));
        g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
@@ -1195,12 +1205,6 @@ perform_prompt_async (GcrSystemPrompt *self,
 
        g_debug ("prompting for password");
 
-       exchange = gcr_system_prompt_get_secret_exchange (self);
-       if (self->pv->received)
-               sent = gcr_secret_exchange_send (exchange, NULL, 0);
-       else
-               sent = gcr_secret_exchange_begin (exchange);
-
        closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
                                                            self->pv->prompter_bus_name,
                                                            G_BUS_NAME_WATCHER_FLAGS_NONE,
@@ -1213,22 +1217,57 @@ perform_prompt_async (GcrSystemPrompt *self,
        /* Reregister the prompt object in the current GMainContext */
        register_prompt_object (self, NULL);
 
-       g_dbus_connection_call (self->pv->connection,
-                               self->pv->prompter_bus_name,
-                               GCR_DBUS_PROMPTER_OBJECT_PATH,
-                               GCR_DBUS_PROMPTER_INTERFACE,
-                               GCR_DBUS_PROMPTER_METHOD_PERFORM,
-                               g_variant_new ("(osa{sv}s)", self->pv->prompt_path,
-                                              type, builder, sent),
-                               G_VARIANT_TYPE ("()"),
-                               G_DBUS_CALL_FLAGS_NO_AUTO_START,
-                               -1, cancellable,
-                               on_perform_prompt_complete,
-                               g_object_ref (res));
+       if (self->pv->exchange == NULL) {
+               gint fds[2];
+               GError *error = NULL;
+               
+               if (!g_unix_open_pipe (fds, 0, &error)) {
+                       g_simple_async_result_set_from_error (res, error);
+                       g_simple_async_result_complete (res);
+                       g_object_unref (res);
+                       return;
+               }
+
+               self->pv->fd = fds[0];
+
+               g_dbus_connection_call (self->pv->connection,
+                                       self->pv->prompter_bus_name,
+                                       GCR_DBUS_PROMPTER_OBJECT_PATH,
+                                       GCR_DBUS_PROMPTER2_INTERFACE,
+                                       GCR_DBUS_PROMPTER_METHOD_PERFORM,
+                                       g_variant_new ("(osa{sv}h)", self->pv->prompt_path,
+                                                      type, builder, fds[1]),
+                                       G_VARIANT_TYPE ("()"),
+                                       G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                       -1, cancellable,
+                                       on_perform_prompt_complete,
+                                       g_object_ref (res));
+       } else {
+               gchar *sent;
+
+               if (self->pv->received)
+                       sent = gcr_secret_exchange_send (self->pv->exchange, NULL, 0);
+               else
+                       sent = gcr_secret_exchange_begin (self->pv->exchange);
+
+               g_dbus_connection_call (self->pv->connection,
+                                       self->pv->prompter_bus_name,
+                                       GCR_DBUS_PROMPTER_OBJECT_PATH,
+                                       GCR_DBUS_PROMPTER_INTERFACE,
+                                       GCR_DBUS_PROMPTER_METHOD_PERFORM,
+                                       g_variant_new ("(osa{sv}s)", self->pv->prompt_path,
+                                                      type, builder, sent),
+                                       G_VARIANT_TYPE ("()"),
+                                       G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                       -1, cancellable,
+                                       on_perform_prompt_complete,
+                                       g_object_ref (res));
+               g_free (sent);
+       }
+
        g_variant_builder_unref(builder);
 
        self->pv->pending = res;
-       g_free (sent);
 }
 
 static GcrPromptReply
diff --git a/gcr/gcr-system-prompter.c b/gcr/gcr-system-prompter.c
index 10948cf..be78dad 100644
--- a/gcr/gcr-system-prompter.c
+++ b/gcr/gcr-system-prompter.c
@@ -35,6 +35,8 @@
 
 #include "egg/egg-error.h"
 
+#include <gio/gunixfdlist.h>
+#include <gio/gunixoutputstream.h>
 #include <string.h>
 
 /**
@@ -91,6 +93,7 @@ struct _GcrSystemPrompterPrivate {
        GType prompt_type;
 
        guint prompter_registered;
+       guint prompter2_registered;
        GDBusConnection *connection;
 
        GHashTable *callbacks;       /* Callback -> guint watch_id */
@@ -105,6 +108,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (GcrSystemPrompter, gcr_system_prompter, G_TYPE_OBJEC
 typedef struct {
        const gchar *path;
        const gchar *name;
+       const gchar *interface_name;
 } Callback;
 
 typedef struct {
@@ -120,6 +124,7 @@ typedef struct {
        gboolean received;
        gboolean closed;
        guint close_sig;
+       gint fd;
 } ActivePrompt;
 
 static void    prompt_send_ready               (ActivePrompt *active,
@@ -165,8 +170,10 @@ callback_dup (Callback *original)
        g_assert (original != NULL);
        g_assert (original->path != NULL);
        g_assert (original->name != NULL);
+       g_assert (original->interface_name != NULL);
        callback->path = g_strdup (original->path);
        callback->name = g_strdup (original->name);
+       callback->interface_name = g_strdup (original->interface_name);
        return callback;
 }
 
@@ -176,6 +183,7 @@ callback_free (gpointer data)
        Callback *callback = data;
        g_free ((gchar *)callback->path);
        g_free ((gchar *)callback->name);
+       g_free ((gchar *)callback->interface_name);
        g_slice_free (Callback, callback);
 }
 
@@ -209,6 +217,8 @@ active_prompt_unref (gpointer data)
                        g_signal_handler_disconnect (active->prompt, active->notify_sig);
                if (g_signal_handler_is_connected (active->prompt, active->close_sig))
                        g_signal_handler_disconnect (active->prompt, active->close_sig);
+               if (active->fd >= 0)
+                       close (active->fd);
                g_object_unref (active->prompt);
                g_hash_table_destroy (active->changed);
                if (active->exchange)
@@ -242,6 +252,7 @@ active_prompt_create (GcrSystemPrompter *self,
        active->notify_sig = g_signal_connect (active->prompt, "notify", G_CALLBACK (on_prompt_notify), 
active);
        active->close_sig = g_signal_connect (active->prompt, "prompt-close", G_CALLBACK (on_prompt_close), 
active);
        active->changed = g_hash_table_new (g_direct_hash, g_direct_equal);
+       active->fd = -1;
 
        /* Insert us into the active hash table */
        g_hash_table_replace (self->pv->active, active->callback, active);
@@ -629,39 +640,65 @@ prompt_send_ready (ActivePrompt *active,
 {
        GcrSystemPrompter *self;
        GVariantBuilder *builder;
-       GcrSecretExchange *exchange;
-       gchar *sent;
 
        g_assert (active->ready == FALSE);
 
-       exchange = active_prompt_get_secret_exchange (active);
-       if (!active->received) {
-               g_return_if_fail (secret == NULL);
-               sent = gcr_secret_exchange_begin (exchange);
-       } else {
-               sent = gcr_secret_exchange_send (exchange, secret, -1);
-       }
-
        self = active->prompter;
        builder = prompt_build_properties (active->prompt, active->changed);
 
-       g_debug ("calling the %s method on %s@%s",
-                GCR_DBUS_CALLBACK_METHOD_READY, active->callback->path, active->callback->name);
+       g_debug ("calling the %s.%s method on %s@%s",
+                active->callback->interface_name, GCR_DBUS_CALLBACK_METHOD_READY,
+                active->callback->path, active->callback->name);
+
+       if (g_str_equal (active->callback->interface_name, GCR_DBUS_PROMPTER2_INTERFACE)) {
+               GOutputStream *stream;
+
+               stream = g_unix_output_stream_new (active->fd, TRUE);
+               if (!g_output_stream_write_all (stream, secret, strlen (secret), NULL, NULL, &error)) {
+                       g_object_unref (stream);
+                       g_dbus_method_invocation_take_error (invocation, error);
+               }
 
-       g_dbus_connection_call (self->pv->connection,
-                               active->callback->name,
-                               active->callback->path,
-                               GCR_DBUS_CALLBACK_INTERFACE,
-                               GCR_DBUS_CALLBACK_METHOD_READY,
-                               g_variant_new ("(sa{sv}s)", response, builder, sent),
-                               G_VARIANT_TYPE ("()"),
-                               G_DBUS_CALL_FLAGS_NO_AUTO_START,
-                               -1, active->cancellable,
-                               on_prompt_ready_complete,
-                               active_prompt_ref (active));
+               g_dbus_connection_call (self->pv->connection,
+                                       active->callback->name,
+                                       active->callback->path,
+                                       GCR_DBUS_CALLBACK2_INTERFACE,
+                                       GCR_DBUS_CALLBACK_METHOD_READY,
+                                       g_variant_new ("(sa{sv})", response, builder),
+                                       G_VARIANT_TYPE ("()"),
+                                       G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                       -1, active->cancellable,
+                                       on_prompt_ready_complete,
+                                       active_prompt_ref (active));
+
+       } else {
+               GcrSecretExchange *exchange;
+               gchar *sent;
+
+               exchange = active_prompt_get_secret_exchange (active);
+               if (!active->received) {
+                       g_return_if_fail (secret == NULL);
+                       sent = gcr_secret_exchange_begin (exchange);
+               } else {
+                       sent = gcr_secret_exchange_send (exchange, secret, -1);
+               }
+
+               g_dbus_connection_call (self->pv->connection,
+                                       active->callback->name,
+                                       active->callback->path,
+                                       GCR_DBUS_CALLBACK_INTERFACE,
+                                       GCR_DBUS_CALLBACK_METHOD_READY,
+                                       g_variant_new ("(sa{sv}s)", response, builder, sent),
+                                       G_VARIANT_TYPE ("()"),
+                                       G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                       -1, active->cancellable,
+                                       on_prompt_ready_complete,
+                                       active_prompt_ref (active));
+
+               g_free (sent);
+       }
 
        g_variant_builder_unref (builder);
-       g_free (sent);
 }
 
 static void
@@ -768,6 +805,7 @@ prompter_method_begin_prompting (GcrSystemPrompter *self,
        guint watch_id;
 
        lookup.name = caller = g_dbus_method_invocation_get_sender (invocation);
+       lookup.interface_name = g_dbus_method_invocation_get_interface_name (invocation);
        g_variant_get (parameters, "(&o)", &lookup.path);
 
        g_debug ("received %s call from callback %s@%s",
@@ -881,13 +919,31 @@ prompter_method_perform_prompt (GcrSystemPrompter *self,
        const gchar *type;
        GVariantIter *iter;
        const gchar *received;
+       gint fd;
 
        lookup.name = g_dbus_method_invocation_get_sender (invocation);
-       g_variant_get (parameters, "(&o&sa{sv}&s)",
-                      &lookup.path, &type, &iter, &received);
+       lookup.interface_name = g_dbus_method_invocation_get_interface_name (invocation);
+       if (g_strcmp0 (lookup.interface_name, GCR_DBUS_PROMPTER2_INTERFACE)) {
+               GDBusMessage *message;
+               GUnixFDList *fd_list;
+               gint idx;
 
-       g_debug ("received %s call from callback %s@%s",
-                GCR_DBUS_PROMPTER_METHOD_PERFORM,
+               message = g_dbus_method_invocation_get_message (invocation);
+               fd_list = g_dbus_message_get_unix_fd_list (message);
+
+               g_assert (fd_list != NULL);
+
+               g_variant_get (parameters, "(&o&sa{sv}&h)",
+                              &lookup.path, &type, &iter, &idx);
+
+               fd = g_unix_fd_list_get (fd_list, idx, NULL);
+       } else {
+               g_variant_get (parameters, "(&o&sa{sv}&s)",
+                              &lookup.path, &type, &iter, &received);
+       }
+
+       g_debug ("received %s.%s call from callback %s@%s",
+                lookup.interface_name, GCR_DBUS_PROMPTER_METHOD_PERFORM,
                 lookup.path, lookup.name);
 
        active = g_hash_table_lookup (self->pv->active, &lookup);
@@ -914,13 +970,17 @@ prompter_method_perform_prompt (GcrSystemPrompter *self,
        prompt_update_properties (active->prompt, iter);
        g_variant_iter_free (iter);
 
-       exchange = active_prompt_get_secret_exchange (active);
-       if (!gcr_secret_exchange_receive (exchange, received)) {
-               g_debug ("received invalid secret exchange from callback %s@%s",
-                        lookup.path, lookup.name);
-               g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
-                                                      "Invalid secret exchange received");
-               return;
+       if (g_strcmp0 (lookup.interface_name, GCR_DBUS_PROMPTER2_INTERFACE))
+               active->fd = fd;
+       else {
+               exchange = active_prompt_get_secret_exchange (active);
+               if (!gcr_secret_exchange_receive (exchange, received)) {
+                       g_debug ("received invalid secret exchange from callback %s@%s",
+                                lookup.path, lookup.name);
+                       g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, 
G_DBUS_ERROR_INVALID_ARGS,
+                                                              "Invalid secret exchange received");
+                       return;
+               }
        }
 
        active->received = TRUE;
@@ -1039,6 +1099,16 @@ gcr_system_prompter_register (GcrSystemPrompter *self,
                g_warning ("error registering prompter %s", egg_error_message (error));
                g_clear_error (&error);
        }
+
+       self->pv->prompter2_registered = g_dbus_connection_register_object (connection,
+                                                                           GCR_DBUS_PROMPTER_OBJECT_PATH,
+                                                                           
_gcr_dbus_prompter2_interface_info (),
+                                                                           &prompter_dbus_vtable,
+                                                                           self, NULL, &error);
+       if (error != NULL) {
+               g_warning ("error registering prompter2 %s", egg_error_message (error));
+               g_clear_error (&error);
+       }
 }
 
 /**
@@ -1078,6 +1148,10 @@ gcr_system_prompter_unregister (GcrSystemPrompter *self,
                g_assert_not_reached ();
        self->pv->prompter_registered = 0;
 
+       if (!g_dbus_connection_unregister_object (self->pv->connection, self->pv->prompter2_registered))
+               g_assert_not_reached ();
+       self->pv->prompter2_registered = 0;
+
        g_clear_object (&self->pv->connection);
 }
 
diff --git a/gcr/org.gnome.keyring.Prompter2.xml b/gcr/org.gnome.keyring.Prompter2.xml
new file mode 100644
index 0000000..e378983
--- /dev/null
+++ b/gcr/org.gnome.keyring.Prompter2.xml
@@ -0,0 +1,84 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+
+<node>
+       <!--
+        * WARNING: This is an internal interface, and not a public API. It
+        * can change between releases.
+        *
+        * This can be viewed as an interface for remoting the GcrPrompt
+        * interface.
+        *
+        * We use a callback interface exported from the prompting client.
+        * The prompter calls this rather than emit signals. It does this so
+        * that the prompter can be aware if the client returns errors or is
+        * no longer around.
+       -->
+
+       <interface name="org.gnome.keyring.internal.Prompter2">
+               <!--
+                * Prompts first call BeginPrompting() with the path that. This
+                * returns immediately. The prompter then calls PromptReady()
+                * on the callback when it's ready for that client prompt to
+                * start showing prompts.
+               -->
+               <method name="BeginPrompting">
+                       <!-- DBus path to the client prompt callback -->
+                       <arg name="callback" type="o" direction="in"/>
+               </method>
+
+               <!--
+                * Called by client prompts to actually show a prompt. This can
+                * only be called after the prompter calls PromptReady() on
+                * the callback.
+                *
+                * This returns immediately, and PromptReady() will be called
+                * on the callback when the prompt completes.
+               -->
+               <method name="PerformPrompt">
+                       <!-- DBus path to the client prompt callback -->
+                       <arg name="callback" type="o" direction="in"/>
+
+                       <!-- Type of prompt: 'password' or 'confirm' -->
+                       <arg name="type" type="s" direction="in"/>
+
+                       <!-- GcrPrompt properties which changed since last prompt -->
+                       <arg name="properties" type="a{sv}" direction="in"/>
+
+                       <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+                       <arg type="h" name="fd" direction="in"/>
+               </method>
+
+               <!--
+                * Called by client prompts to stop prompting. If a prompt is
+                * currently being displayed then it will be cancelled as if
+                * the user cancelled it.
+                *
+                * Will call PromptDone() on the client callback.
+               -->
+               <method name="StopPrompting">
+                       <!-- DBus path to the client prompt callback -->
+                       <arg name="callback" type="o" direction="in"/>
+               </method>
+       </interface>
+
+       <!-- Called when ready to prompt or a prompt completes -->
+       <interface name="org.gnome.keyring.internal.Prompter.Callback2">
+
+               <!--
+                * Called by the prompter when ready to show a prompt. This
+                * occurs when the prompter is ready for the first prompt,
+                * and also after prompts complete.
+               -->
+               <method name="PromptReady">
+                       <arg name="reply" type="s" direction="in"/>
+                       <arg name="properties" type="a{sv}" direction="in"/>
+               </method>
+
+               <!--
+                * Called when the prompter stops prompting for this callback
+               -->
+               <method name="PromptDone">
+               </method>
+       </interface>
+</node>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]