/* * gnome-keyring * * Copyright (C) 2008 Stefan Walter * * This program 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 program 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 program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "gku-prompt.h" #include "gku-prompt-marshal.h" #include "egg/egg-cleanup.h" #include "egg/egg-dh.h" #include "egg/egg-error.h" #include "egg/egg-hex.h" #include "egg/egg-hkdf.h" #include "egg/egg-libgcrypt.h" #include "egg/egg-secure-memory.h" #include "egg/egg-spawn.h" #include "gcr/gcr-base.h" #include "pkcs11/pkcs11i.h" #include #include #ifdef _DEBUG #define DEBUG_PROMPT 0 #define DEBUG_STDERR 0 #endif enum { PROP_0, PROP_EXCHANGE }; enum { RESPONDED, COMPLETED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; struct _GkuPromptPrivate { GKeyFile *input; GKeyFile *output; gchar *executable; gboolean completed; gboolean failure; /* Transport crypto */ GcrSecretExchange *exchange; /* Information about child */ GPid pid; /* Input and output */ gchar *in_data; gsize in_offset; gsize in_length; GString *out_data; GString *err_data; guint io_tag; }; G_DEFINE_TYPE (GkuPrompt, gku_prompt, G_TYPE_OBJECT); /* Forward declaration*/ static void display_async_prompt (GkuPrompt *); /* User choices we transfer over during a soft prompt reset */ const struct { const gchar *section; const gchar *name; } SOFT_RESET[] = { { "unlock-options", "choice" }, { "unlock-options", "ttl" }, { "details", "expanded" }, }; EGG_SECURE_DECLARE (prompt); /* ----------------------------------------------------------------------------- * INTERNAL */ static void kill_process (GkuPrompt *self) { if (self->pv->pid) kill (self->pv->pid, SIGHUP); } static void mark_completed (GkuPrompt *self) { g_assert (GKU_IS_PROMPT (self)); g_assert (!self->pv->completed); self->pv->completed = TRUE; g_signal_emit (self, signals[COMPLETED], 0); } static void mark_failed (GkuPrompt *self) { g_assert (GKU_IS_PROMPT (self)); g_assert (!self->pv->failure); self->pv->failure = TRUE; if (!self->pv->completed) mark_completed (self); } static void mark_responded (GkuPrompt *self) { gboolean continu = FALSE; g_assert (GKU_IS_PROMPT (self)); g_signal_emit (self, signals[RESPONDED], 0, &continu); g_assert (GKU_IS_PROMPT (self)); /* The prompt gets displayed again */ if (continu) { display_async_prompt (self); /* The prompt has completed */ } else { mark_completed (self); } } static gboolean on_standard_input (int fd, gpointer user_data) { GkuPrompt *self = GKU_PROMPT (user_data); gssize ret; g_return_val_if_fail (GKU_IS_PROMPT (self), FALSE); if (self->pv->in_offset >= self->pv->in_length) return FALSE; g_assert (self->pv->in_data); ret = egg_spawn_write_input (fd, self->pv->in_data + self->pv->in_offset, self->pv->in_length - self->pv->in_offset); if (ret <= 0) { g_warning ("couldn't write all input to prompt process"); mark_failed (self); return FALSE; } self->pv->in_offset += ret; return TRUE; } static gboolean on_standard_output (int fd, gpointer user_data) { GkuPrompt *self = GKU_PROMPT (user_data); gchar buffer[1024]; gssize ret; g_return_val_if_fail (GKU_IS_PROMPT (self), FALSE); ret = egg_spawn_read_output (fd, buffer, sizeof (buffer)); if (ret < 0) { g_warning ("couldn't read output data from prompt process"); mark_failed (self); return FALSE; } if (!self->pv->out_data) self->pv->out_data = g_string_new_len (buffer, ret); else g_string_append_len (self->pv->out_data, buffer, ret); return (ret > 0); } static gboolean on_standard_error (int fd, gpointer user_data) { GkuPrompt *self = GKU_PROMPT (user_data); gchar buffer[1024]; gssize ret; gchar *ptr; g_return_val_if_fail (GKU_IS_PROMPT (self), FALSE); ret = egg_spawn_read_output (fd, buffer, sizeof (buffer)); if (ret < 0) { g_warning ("couldn't read error data from prompt process"); mark_failed (self); return FALSE; } if (!self->pv->err_data) self->pv->err_data = g_string_new_len (buffer, ret); else g_string_append_len (self->pv->err_data, buffer, ret); /* Print all stderr lines as messages */ while ((ptr = strchr (self->pv->err_data->str, '\n')) != NULL) { *ptr = '\0'; g_printerr ("%s\n", self->pv->err_data->str); g_string_erase (self->pv->err_data, 0, ptr - self->pv->err_data->str + 1); } return ret > 0; } static void on_io_completed (gpointer user_data) { GkuPrompt *self = GKU_PROMPT (user_data); GError *error = NULL; g_return_if_fail (GKU_IS_PROMPT (self)); g_assert (!self->pv->output); g_assert (self->pv->io_tag != 0); g_assert (!self->pv->completed); /* Should be the last call we receive */ self->pv->io_tag = 0; /* Print out any remaining errors */ if (self->pv->err_data && self->pv->err_data->len) g_message ("%s", self->pv->err_data->str); /* Parse the output data properly */ if (!self->pv->failure) { if (!self->pv->out_data->len) { g_warning ("no data returned from the prompt"); mark_failed (self); } } if (!self->pv->failure) { #if DEBUG_PROMPT g_printerr ("PROMPT OUTPUT:\n%s\n", self->pv->out_data->str); #endif self->pv->output = g_key_file_new (); if (!g_key_file_load_from_data (self->pv->output, self->pv->out_data->str, self->pv->out_data->len, G_KEY_FILE_NONE, &error)) { g_key_file_free (self->pv->output); g_warning ("couldn't parse output from prompt: %s", egg_error_message (error)); g_clear_error (&error); mark_failed (self); } else { mark_responded (self); } } } static void on_child_exited (GPid pid, gint status, gpointer user_data) { GkuPrompt *self = GKU_PROMPT (user_data); gint code; if (pid == self->pv->pid) { self->pv->pid = 0; if (!self->pv->failure) { if (WIFEXITED (status)) { code = WEXITSTATUS (status); if (code != 0) { g_message ("prompt process exited with failure code: %d", code); mark_failed (self); } } else if (WIFSIGNALED (status)) { code = WTERMSIG (status); g_message ("prompt process was killed with signal: %d", code); mark_failed (self); } } } g_spawn_close_pid (pid); } static void prepare_transport_crypto (GkuPrompt *self) { gchar *request; if (!g_key_file_has_group (self->pv->input, "exchange")) { if (self->pv->exchange == NULL) self->pv->exchange = gcr_secret_exchange_new (NULL); request = gcr_secret_exchange_begin (self->pv->exchange); g_key_file_set_string (self->pv->input, "transport", "exchange", request); g_free (request); } } static gboolean prepare_input_data (GkuPrompt *self) { GError *error = NULL; g_assert (self->pv->input); prepare_transport_crypto (self); self->pv->in_data = g_key_file_to_data (self->pv->input, &self->pv->in_length, &error); if (!self->pv->in_data) { g_warning ("couldn't encode data for prompt: %s", egg_error_message (error)); g_clear_error (&error); mark_failed (self); return FALSE; } #if DEBUG_PROMPT g_printerr ("PROMPT INPUT:\n%s\n", self->pv->in_data); #endif /* No further modifications to input are possible */ g_key_file_free (self->pv->input); self->pv->input = NULL; return TRUE; } static void display_async_prompt (GkuPrompt *self) { EggSpawnCallbacks callbacks; GError *error = NULL; gchar **names, **envp; int i, n; char *argv[] = { self->pv->executable, NULL, }; g_assert (!self->pv->pid); /* Fires completed event when fails */ if (!prepare_input_data (self)) return; /* Any environment we have */ names = g_listenv (); for (n = 0; names && names[n]; ++n); envp = g_new (char*, n + 2); for (i = 0; i < n; i++) envp[i] = g_strdup_printf ("%s=%s", names[i], g_getenv (names[i])); envp[i++] = NULL; g_strfreev (names); memset (&callbacks, 0, sizeof (callbacks)); callbacks.standard_input = on_standard_input; callbacks.standard_output = on_standard_output; callbacks.standard_error = on_standard_error; callbacks.completed = on_io_completed; callbacks.finalize_func = g_object_unref; #ifdef DEBUG_STDERR /* Let stderr show through if desired */ callbacks.standard_error = NULL; #endif self->pv->io_tag = egg_spawn_async_with_callbacks (NULL, argv, envp, G_SPAWN_DO_NOT_REAP_CHILD, &self->pv->pid, &callbacks, g_object_ref (self), NULL, &error); if (!self->pv->io_tag) { g_warning ("couldn't spawn prompt tool: %s", egg_error_message (error)); g_clear_error (&error); self->pv->pid = 0; mark_failed (self); return; } g_child_watch_add_full (G_PRIORITY_DEFAULT, self->pv->pid, on_child_exited, g_object_ref (self), g_object_unref); } static void display_dummy_prompt (GkuPrompt *self, const gchar *response) { GError *error = NULL; gchar *title = NULL; g_assert (GKU_IS_PROMPT (self)); if (!response && self->pv->input) title = g_key_file_get_string (self->pv->input, "prompt", "title", NULL); /* Fires completed event when fails */ if (!prepare_input_data (self)) { g_free (title); return; } if (response) { #if DEBUG_PROMPT g_printerr ("PROMPT OUTPUT:\n%s\n", response); #endif self->pv->output = g_key_file_new (); if (!g_key_file_load_from_data (self->pv->output, response, strlen (response), G_KEY_FILE_NONE, &error)) { g_key_file_free (self->pv->output); g_warning ("couldn't parse output from prompt: %s", egg_error_message (error)); g_clear_error (&error); mark_failed (self); } else { mark_responded (self); } /* No response, problem! */ } else { g_warning ("unexpected prompt without response: %s", title ? title : "unknown"); mark_failed (self); } g_free (title); } static void clear_prompt_data (GkuPrompt *self) { if (self->pv->input) g_key_file_free (self->pv->input); self->pv->input = NULL; if (self->pv->output) g_key_file_free (self->pv->output); self->pv->output = NULL; self->pv->failure = FALSE; self->pv->completed = FALSE; g_free (self->pv->in_data); self->pv->in_data = NULL; self->pv->in_length = 0; self->pv->in_offset = 0; if (self->pv->out_data) g_string_free (self->pv->out_data, TRUE); self->pv->out_data = NULL; if (self->pv->err_data) g_string_free (self->pv->err_data, TRUE); self->pv->err_data = NULL; if (self->pv->io_tag) g_source_remove (self->pv->io_tag); self->pv->io_tag = 0; } /* ----------------------------------------------------------------------------- * OBJECT */ static gboolean gku_prompt_real_responded (GkuPrompt *self) { /* The prompt is done, if nobody overrode this signal and returned TRUE */ return FALSE; } static void gku_prompt_real_completed (GkuPrompt *self) { /* Nothing to do */ } static void gku_prompt_constructed (GObject *obj) { GkuPrompt *self = GKU_PROMPT (obj); G_OBJECT_CLASS (gku_prompt_parent_class)->constructed (obj); if (!self->pv->executable) self->pv->executable = g_strdup (PROMPTEXEC); } static void gku_prompt_init (GkuPrompt *self) { self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GKU_TYPE_PROMPT, GkuPromptPrivate); gku_prompt_reset (self, TRUE); } static void gku_prompt_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { GkuPrompt *self = GKU_PROMPT (obj); switch (prop_id) { case PROP_EXCHANGE: g_return_if_fail (self->pv->exchange == NULL); self->pv->exchange = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void gku_prompt_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { GkuPrompt *self = GKU_PROMPT (obj); switch (prop_id) { case PROP_EXCHANGE: g_value_set_object (value, self->pv->exchange); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void gku_prompt_dispose (GObject *obj) { GkuPrompt *self = GKU_PROMPT (obj); kill_process (self); clear_prompt_data (self); g_clear_object (&self->pv->exchange); G_OBJECT_CLASS (gku_prompt_parent_class)->dispose (obj); } static void gku_prompt_finalize (GObject *obj) { GkuPrompt *self = GKU_PROMPT (obj); g_assert (self->pv->pid == 0); g_assert (!self->pv->input); g_assert (!self->pv->output); g_assert (!self->pv->in_data); g_assert (!self->pv->out_data); g_assert (!self->pv->err_data); g_assert (!self->pv->io_tag); g_assert (!self->pv->exchange); g_free (self->pv->executable); self->pv->executable = NULL; G_OBJECT_CLASS (gku_prompt_parent_class)->finalize (obj); } static void gku_prompt_class_init (GkuPromptClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->constructed = gku_prompt_constructed; gobject_class->get_property = gku_prompt_get_property; gobject_class->set_property = gku_prompt_set_property; gobject_class->dispose = gku_prompt_dispose; gobject_class->finalize = gku_prompt_finalize; klass->responded = gku_prompt_real_responded; klass->completed = gku_prompt_real_completed; g_type_class_add_private (klass, sizeof (GkuPromptPrivate)); g_object_class_install_property (gobject_class, PROP_EXCHANGE, g_param_spec_object ("exchange", "Exchange", "Secret Exchange", GCR_TYPE_SECRET_EXCHANGE, G_PARAM_READWRITE)); signals[COMPLETED] = g_signal_new ("completed", GKU_TYPE_PROMPT, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GkuPromptClass, completed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[RESPONDED] = g_signal_new ("responded", GKU_TYPE_PROMPT, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GkuPromptClass, responded), g_signal_accumulator_true_handled, NULL, gku_prompt_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN, 0); egg_libgcrypt_initialize (); } /* ----------------------------------------------------------------------------- * PUBLIC */ GkuPrompt* gku_prompt_new (void) { return g_object_new (GKU_TYPE_PROMPT, NULL); } void gku_prompt_set_title (GkuPrompt *self, const gchar *title) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_string (self->pv->input, "prompt", "title", title); } void gku_prompt_set_primary_text (GkuPrompt *self, const gchar *primary) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_string (self->pv->input, "prompt", "primary", primary); } void gku_prompt_set_secondary_text (GkuPrompt *self, const gchar *secondary) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_string (self->pv->input, "prompt", "secondary", secondary); } void gku_prompt_show_widget (GkuPrompt *self, const gchar *widget) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_boolean (self->pv->input, "visibility", widget, TRUE); } void gku_prompt_hide_widget (GkuPrompt *self, const gchar *widget) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_boolean (self->pv->input, "visibility", widget, FALSE); } void gku_prompt_select_widget (GkuPrompt *self, const gchar *widget) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_boolean (self->pv->input, "selected", widget, TRUE); } gboolean gku_prompt_has_response (GkuPrompt *self) { g_return_val_if_fail (GKU_IS_PROMPT (self), FALSE); return self->pv->output ? TRUE : FALSE; } gint gku_prompt_get_response (GkuPrompt *self) { gchar *response; guint ret; g_return_val_if_fail (GKU_IS_PROMPT (self), GKU_RESPONSE_FAILURE); if (self->pv->failure) return GKU_RESPONSE_FAILURE; g_return_val_if_fail (self->pv->output, GKU_RESPONSE_FAILURE); response = g_key_file_get_string (self->pv->output, "prompt", "response", NULL); if (!response || g_str_equal (response, "")) { ret = GKU_RESPONSE_NONE; } else if (g_str_equal (response, "ok")) { ret = GKU_RESPONSE_OK; } else if (g_str_equal (response, "no")) { ret = GKU_RESPONSE_NO; } else if (g_str_equal (response, "other")) { ret = GKU_RESPONSE_OTHER; } else { g_warning ("invalid response field received from prompt: %s", response); ret = GKU_RESPONSE_NONE; } g_free (response); return ret; } gchar * gku_prompt_get_password (GkuPrompt *self, const gchar *password_type) { gchar *received; gboolean receive_success; g_return_val_if_fail (GKU_IS_PROMPT (self), NULL); received = g_key_file_get_string (self->pv->output, password_type, "value", NULL); if (received == NULL) return NULL; /* Not encrypted */ if (!g_key_file_get_boolean (self->pv->output, password_type, "encrypted", NULL)) return received; g_return_val_if_fail (self->pv->exchange, NULL); receive_success = gcr_secret_exchange_receive (self->pv->exchange, received); g_free (received); if (!receive_success) return NULL; return egg_secure_strdup (gcr_secret_exchange_get_secret (self->pv->exchange, NULL)); } gboolean gku_prompt_is_widget_selected (GkuPrompt *self, const gchar *widget) { g_return_val_if_fail (GKU_IS_PROMPT (self), FALSE); g_return_val_if_fail (self->pv->output, FALSE); if (!self->pv->failure) return FALSE; g_assert (self->pv->output); return g_key_file_get_boolean (self->pv->output, "selected", widget, NULL); } void gku_prompt_set_window_id (GkuPrompt *self, const gchar *window_id) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); if (!window_id) g_key_file_remove_key (self->pv->input, "prompt", "window-id", NULL); else g_key_file_set_string (self->pv->input, "prompt", "window-id", window_id); } void gku_prompt_set_warning (GkuPrompt *self, const gchar *warning) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); if (!warning) g_key_file_remove_key (self->pv->input, "prompt", "warning", NULL); else g_key_file_set_string (self->pv->input, "prompt", "warning", warning); } void gku_prompt_reset (GkuPrompt *self, gboolean hard) { GKeyFile *input; gchar *value; gint i; g_return_if_fail (GKU_IS_PROMPT (self)); kill_process (self); self->pv->pid = 0; input = g_key_file_new (); /* If not a hard reset, copy over some user data */ if (!hard && self->pv->output) { for (i = 0; i < G_N_ELEMENTS (SOFT_RESET); ++i) { value = g_key_file_get_value (self->pv->output, SOFT_RESET[i].section, SOFT_RESET[i].name, NULL); if (value != NULL) g_key_file_set_value (input, SOFT_RESET[i].section, SOFT_RESET[i].name, value); g_free (value); } } clear_prompt_data (self); self->pv->input = input; } const gchar* gku_prompt_get_unlock_choice (GkuPrompt *self) { const gchar *result; gchar *value; g_return_val_if_fail (GKU_IS_PROMPT (self), NULL); g_return_val_if_fail (self->pv->output, NULL); value = g_key_file_get_string (self->pv->output, "unlock-options", "choice", NULL); if (value == NULL) return ""; result = g_intern_string (value); g_free (value); return result; } void gku_prompt_set_unlock_choice (GkuPrompt *self, const gchar *option) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (option); g_return_if_fail (self->pv->input); g_key_file_set_string (self->pv->input, "unlock-options", "choice", option); /* Expand if anything but session unlock is chosen */ if (!g_str_equal (option, GCR_UNLOCK_OPTION_SESSION) && !g_key_file_has_key (self->pv->input, "details", "expanded", NULL)) g_key_file_set_boolean (self->pv->input, "details", "expanded", TRUE); } guint gku_prompt_get_unlock_ttl (GkuPrompt *self) { g_return_val_if_fail (GKU_IS_PROMPT (self), 0); g_return_val_if_fail (self->pv->output, 0); return g_key_file_get_integer (self->pv->output, "unlock-options", "ttl", NULL); } void gku_prompt_set_unlock_ttl (GkuPrompt *self, guint ttl) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_integer (self->pv->input, "unlock-options", "ttl", ttl); } void gku_prompt_set_unlock_sensitive (GkuPrompt *self, const gchar *option, gboolean sensitive, const gchar *reason) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (self->pv->input); g_key_file_set_boolean (self->pv->input, option, "sensitive", sensitive); if (reason) g_key_file_set_string (self->pv->input, option, "reason", reason); } void gku_prompt_set_unlock_label (GkuPrompt *self, const gchar *option, const gchar *label) { g_return_if_fail (GKU_IS_PROMPT (self)); g_return_if_fail (option); g_return_if_fail (label); g_return_if_fail (self->pv->input); g_key_file_set_string (self->pv->input, option, "label", label); } /* ---------------------------------------------------------------------------------- * ATTENTION QUEUES */ /* Forward declaration */ static void next_attention_req (const gchar *); static void free_attention_req (gpointer data); typedef struct _Attention { gchar *window_id; GkuPromptAttentionFunc callback; GDestroyNotify destroy; gpointer user_data; GkuPrompt *prompt; gboolean active; GCond *cond; } AttentionReq; static GStaticMutex attention_mutex = G_STATIC_MUTEX_INIT; static GHashTable *attention_reqs = NULL; /* Used for testing, to queue responses without showing prompt */ static gboolean dummy_responses = FALSE; static GQueue queued_responses = G_QUEUE_INIT; static void done_attention_req (GkuPrompt *prompt, gpointer user_data) { AttentionReq *att = user_data; g_assert (att); g_assert (GKU_IS_PROMPT (att->prompt)); if (att->active) { att->active = FALSE; next_attention_req (att->window_id); } if (att->cond) { g_static_mutex_lock (&attention_mutex); g_cond_broadcast (att->cond); g_static_mutex_unlock (&attention_mutex); att->cond = NULL; } free_attention_req (att); } static void clear_attention_reqs (gpointer unused) { g_assert (attention_reqs); g_hash_table_destroy (attention_reqs); } static AttentionReq* alloc_attention_req (const gchar *window_id) { AttentionReq *att; g_assert (window_id); att = g_slice_new0 (AttentionReq); att->window_id = g_strdup (window_id); return att; } static void free_attention_req (gpointer data) { AttentionReq *att = data; if (att) { att->cond = NULL; if (att->destroy) (att->destroy) (att->user_data); if (att->prompt) { g_signal_handlers_disconnect_by_func (att->prompt, done_attention_req, att); g_object_unref (att->prompt); } g_free (att->window_id); g_slice_free (AttentionReq, att); } } static void free_attention_queue (gpointer data) { GQueue *queue = data; AttentionReq *att; if (queue) { while (!g_queue_is_empty (queue)) { att = g_queue_pop_head (queue); att->active = FALSE; free_attention_req (att); } g_queue_free (queue); } } static GQueue* alloc_attention_queue (void) { return g_queue_new (); } static void next_attention_req (const gchar *window_id) { gchar *response; AttentionReq *att; GQueue *queue; g_assert (window_id); g_assert (attention_reqs); queue = g_hash_table_lookup (attention_reqs, window_id); g_return_if_fail (queue); /* Nothing more to process for this window */ if (g_queue_is_empty (queue)) { g_hash_table_remove (attention_reqs, window_id); return; } /* Get the next one out */ att = g_queue_pop_head (queue); g_assert (att); g_assert (att->window_id); g_assert (g_str_equal (att->window_id, window_id)); g_assert (!att->prompt); g_assert (att->callback); /* Callback populates the prompt */ att->prompt = (att->callback) (att->user_data); /* Don't show the prompt */ if (att->prompt == NULL) { free_attention_req (att); next_attention_req (window_id); return; } att->active = TRUE; g_signal_connect_data (att->prompt, "completed", G_CALLBACK (done_attention_req), att, NULL, G_CONNECT_AFTER); gku_prompt_set_window_id (att->prompt, window_id); /* Fake display the prompt */ if (dummy_responses) { g_static_mutex_lock (&attention_mutex); response = g_queue_pop_head (&queued_responses); g_static_mutex_unlock (&attention_mutex); display_dummy_prompt (att->prompt, response); g_free (response); /* Actually display the prompt, "completed" signal will fire */ } else { display_async_prompt (att->prompt); } /* att can be freed here so beware! */ } static gboolean service_attention_req (gpointer user_data) { AttentionReq *att = user_data; gboolean now = FALSE; GQueue *queue; g_assert (att); if (!attention_reqs) { attention_reqs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_attention_queue); egg_cleanup_register (clear_attention_reqs, NULL); } queue = g_hash_table_lookup (attention_reqs, att->window_id); if (queue == NULL) { queue = alloc_attention_queue (); g_hash_table_insert (attention_reqs, g_strdup (att->window_id), queue); now = TRUE; } g_queue_push_tail (queue, att); if (now == TRUE) next_attention_req (att->window_id); /* Remove this timeout handler after one call */ return FALSE; } static AttentionReq* prepare_attention_req (const gchar *window_id, GkuPromptAttentionFunc callback, gpointer user_data, GDestroyNotify destroy_notify) { AttentionReq *att; g_return_val_if_fail (callback, NULL); if (!window_id) window_id = ""; att = alloc_attention_req (window_id); att->callback = callback; att->user_data = user_data; att->destroy = destroy_notify; return att; } void gku_prompt_request_attention_async (const gchar *window_id, GkuPromptAttentionFunc callback, gpointer user_data, GDestroyNotify destroy_notify) { AttentionReq *att = prepare_attention_req (window_id, callback, user_data, destroy_notify); g_return_if_fail (att); g_timeout_add (0, service_attention_req, att); } void gku_prompt_request_attention_sync (const gchar *window_id, GkuPromptAttentionFunc callback, gpointer user_data, GDestroyNotify destroy_notify) { AttentionReq *att = prepare_attention_req (window_id, callback, user_data, destroy_notify); GCond *cond = g_cond_new (); g_return_if_fail (att); att->cond = cond; g_static_mutex_lock (&attention_mutex); g_timeout_add (0, service_attention_req, att); /* WARNING: att may have been destroyed past this point */ g_cond_wait (cond, g_static_mutex_get_mutex (&attention_mutex)); g_static_mutex_unlock (&attention_mutex); g_cond_free (cond); } #ifdef WITH_TESTABLE void gku_prompt_dummy_prepare_response (void) { g_static_mutex_lock (&attention_mutex); dummy_responses = TRUE; while (!g_queue_is_empty (&queued_responses)) g_free (g_queue_pop_head (&queued_responses)); g_static_mutex_unlock (&attention_mutex); } gboolean gku_prompt_dummy_have_response (void) { gboolean ret; g_static_mutex_lock (&attention_mutex); ret = !g_queue_is_empty (&queued_responses); g_static_mutex_unlock (&attention_mutex); return ret; } static void queue_dummy_response (gchar *response) { g_assert (response); g_static_mutex_lock (&attention_mutex); dummy_responses = TRUE; g_queue_push_tail (&queued_responses, response); g_static_mutex_unlock (&attention_mutex); } void gku_prompt_dummy_queue_response (const gchar *response) { g_return_if_fail (response); queue_dummy_response (g_strdup (response)); } void gku_prompt_dummy_queue_ok_password (const gchar *password) { const static gchar *RESPONSE = "[password]\nencrypted=FALSE\nvalue=%s\n[prompt]\nresponse=ok\n"; g_return_if_fail (password); queue_dummy_response (g_strdup_printf (RESPONSE, password)); } void gku_prompt_dummy_queue_ok_passwords (const gchar *original, const gchar *password) { const static gchar *RESPONSE = "[password]\nencrypted=FALSE\nvalue=%s\n" "[original]\nencrypted=FALSE\nvalue=%s\n" "[prompt]\nresponse=ok\n"; g_return_if_fail (password); queue_dummy_response (g_strdup_printf (RESPONSE, password, original)); } void gku_prompt_dummy_queue_auto_password (const gchar *password) { const static gchar *RESPONSE = "[password]\nencrypted=FALSE\nvalue=%s\n[prompt]\nresponse=ok\n" "[unlock-options]\nchoice=always\n"; g_return_if_fail (password); queue_dummy_response (g_strdup_printf (RESPONSE, password)); } void gku_prompt_dummy_queue_no (void) { const static gchar *RESPONSE = "[prompt]\nresponse=no\n"; gku_prompt_dummy_queue_response (RESPONSE); } #endif /* WITH_TESTABLE */