diff options
author | Stefan Walter <stefw@src.gnome.org> | 2009-01-21 22:08:17 +0000 |
---|---|---|
committer | Stefan Walter <stefw@src.gnome.org> | 2009-01-21 22:08:17 +0000 |
commit | cb90a436ed4e25b02c6df9963d64290b740a2ea9 (patch) | |
tree | 20a0ef15f13bd4f7e35e5c2e05e01be025b9cf0a | |
parent | 21fc592b57f7729ead21efec04a2bfe94f50e296 (diff) | |
download | gcr-cb90a436ed4e25b02c6df9963d64290b740a2ea9.tar.gz |
Complete importer in gcr library. Make 'gnome-keyring import' use the new
* daemon/ui/gkr-ask-entry.c: (moved)
* daemon/ui/gkr-ask-entry.h: (moved)
* daemon/ui/gkr-ask-tool.c:
* daemon/ui/Makefile.am:
* egg/egg-secure-entry.c: (moved from daemon/ui/gkr-ask-entry.c)
* egg/egg-secure-entry.h: (moved from daemon/ui/gkr-ask-entry.h)
* egg/Makefile.am:
* gcr/gcr.h: (added)
* gcr/gcr-import-dialog.c: (added)
* gcr/gcr-import-dialog.glade:
* gcr/gcr-import-dialog.h: (added)
* gcr/gcr-importer.c:
* gcr/gcr-importer.h:
* gcr/gcr-internal.c: (removed)
* gcr/gcr-internal.h:
* gcr/gcr-library.c: (added)
* gcr/gcr-parser.c:
* gcr/gcr-parser.h:
* gcr/gcr-types.h:
* gcr/Makefile.am:
* gcr/tests/Makefile.am:
* gcr/tests/unit-test-parser.c:
* tool/gkr-tool.c:
* tool/gkr-tool-import.c:
* tool/Makefile.am: Complete importer in gcr library. Make 'gnome-keyring import'
use the new importer.
svn path=/trunk/; revision=1472
-rw-r--r-- | egg/Makefile.am | 11 | ||||
-rw-r--r-- | egg/egg-secure-entry.c | 2733 | ||||
-rw-r--r-- | egg/egg-secure-entry.h | 187 | ||||
-rw-r--r-- | gcr/Makefile.am | 50 | ||||
-rw-r--r-- | gcr/gcr-import-dialog.c | 452 | ||||
-rw-r--r-- | gcr/gcr-import-dialog.glade | 185 | ||||
-rw-r--r-- | gcr/gcr-import-dialog.h | 86 | ||||
-rw-r--r-- | gcr/gcr-importer.c | 749 | ||||
-rw-r--r-- | gcr/gcr-importer.h | 59 | ||||
-rw-r--r-- | gcr/gcr-internal.h | 8 | ||||
-rw-r--r-- | gcr/gcr-library.c (renamed from gcr/gcr-internal.c) | 105 | ||||
-rw-r--r-- | gcr/gcr-parser.c | 163 | ||||
-rw-r--r-- | gcr/gcr-parser.h | 44 | ||||
-rw-r--r-- | gcr/gcr-types.h | 19 | ||||
-rw-r--r-- | gcr/gcr.h | 46 | ||||
-rw-r--r-- | gcr/tests/Makefile.am | 4 | ||||
-rw-r--r-- | gcr/tests/unit-test-parser.c | 2 |
17 files changed, 4560 insertions, 343 deletions
diff --git a/egg/Makefile.am b/egg/Makefile.am index ca3637b..a6d0a2d 100644 --- a/egg/Makefile.am +++ b/egg/Makefile.am @@ -3,7 +3,8 @@ noinst_LTLIBRARIES = \ libegg.la \ libegg-buffer.la \ libegg-creds.la \ - libegg-secure.la + libegg-secure.la \ + libegg-secure-entry.la BUILT_SOURCES = \ asn1-def-pk.h asn1-def-pkix.h @@ -45,6 +46,14 @@ DISTCLEANFILES = \ libegg_secure_la_SOURCES = \ egg-secure-memory.c egg-secure-memory.h +libegg_secure_entry_la_SOURCES = \ + egg-secure-entry.c egg-secure-entry.h + +libegg_secure_entry_la_CFLAGS = \ + $(GOBJECT_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GTK_CFLAGS) + libegg_buffer_la_SOURCES = \ egg-buffer.c egg-buffer.h diff --git a/egg/egg-secure-entry.c b/egg/egg-secure-entry.c new file mode 100644 index 0000000..1d92c9f --- /dev/null +++ b/egg/egg-secure-entry.c @@ -0,0 +1,2733 @@ +/* + * GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 of the License, or (at your option) any later version. + * + * 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. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * Heavily stripped down for use in pinentry-gtk-2 by Albrecht Dreß + * <albrecht.dress@arcor.de> Feb. 2004. + * + * (C) by Albrecht Dreß 2004 unter the terms of the GNU Lesser General + * Public License. + * + * The entry is invisible by default, uses secure memory methods to + * allocate the text memory, and all potentially dangerous methods + * (copy & paste, popup, etc.) have been removed. + */ + +/* + * Modified for inclusion into gnome-keyring by Stef Walter + */ + +#include <stdlib.h> +#include <string.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include "egg-secure-entry.h" +#include "egg-secure-memory.h" + +#define MIN_ASK_ENTRY_WIDTH 150 +#define DRAW_TIMEOUT 20 +#define INNER_BORDER 2 + +/* Initial size of buffer, in bytes */ +#define MIN_SIZE 16 + +/* Maximum size of text buffer, in bytes */ +#define MAX_SIZE G_MAXUSHORT + +enum { + ACTIVATE, + MOVE_CURSOR, + INSERT_AT_CURSOR, + DELETE_FROM_CURSOR, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_CURSOR_POSITION, + PROP_SELECTION_BOUND, + PROP_MAX_LENGTH, + PROP_HAS_FRAME, + PROP_INVISIBLE_CHAR, + PROP_ACTIVATES_DEFAULT, + PROP_WIDTH_CHARS, + PROP_SCROLL_OFFSET, + PROP_TEXT, + PROP_VISIBILITY +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/* GObject, GtkObject methods */ +static void egg_secure_entry_class_init (EggSecureEntryClass *klass); +static void egg_secure_entry_editable_init (GtkEditableClass *iface); +static void egg_secure_entry_cell_editable_init (GtkCellEditableIface *iface); +static void egg_secure_entry_init (EggSecureEntry *entry); +static void egg_secure_entry_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void egg_secure_entry_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); +static void egg_secure_entry_finalize (GObject *object); + +/* GtkWidget methods */ +static void egg_secure_entry_realize (GtkWidget *widget); +static void egg_secure_entry_unrealize (GtkWidget *widget); +static void egg_secure_entry_size_request (GtkWidget *widget, GtkRequisition *requisition); +static void egg_secure_entry_size_allocate (GtkWidget *widget, GtkAllocation *allocation); +static void egg_secure_entry_draw_frame (GtkWidget *widget); +static gint egg_secure_entry_expose (GtkWidget *widget, GdkEventExpose *event); +static gint egg_secure_entry_button_press (GtkWidget *widget, GdkEventButton *event); +static gint egg_secure_entry_button_release (GtkWidget *widget, GdkEventButton *event); +static gint egg_secure_entry_motion_notify (GtkWidget *widget, GdkEventMotion *event); +static gint egg_secure_entry_key_press (GtkWidget *widget, GdkEventKey *event); +static gint egg_secure_entry_key_release (GtkWidget *widget, GdkEventKey *event); +static gint egg_secure_entry_focus_in (GtkWidget *widget, GdkEventFocus *event); +static gint egg_secure_entry_focus_out (GtkWidget *widget, GdkEventFocus *event); +static void egg_secure_entry_grab_focus (GtkWidget *widget); +static void egg_secure_entry_style_set (GtkWidget *widget, GtkStyle *previous_style); +static void egg_secure_entry_direction_changed (GtkWidget *widget, GtkTextDirection previous_dir); +static void egg_secure_entry_state_changed (GtkWidget *widget, GtkStateType previous_state); +static void egg_secure_entry_screen_changed (GtkWidget *widget, GdkScreen *old_screen); + +/* GtkEditable method implementations */ +static void egg_secure_entry_insert_text (GtkEditable *editable, const gchar *new_text, + gint new_text_length, gint *position); +static void egg_secure_entry_delete_text (GtkEditable *editable, gint start_pos, gint end_pos); +static void egg_secure_entry_real_set_position (GtkEditable *editable, gint position); +static gint egg_secure_entry_get_position (GtkEditable *editable); +static void egg_secure_entry_set_selection_bounds (GtkEditable *editable, gint start, gint end); +static gboolean egg_secure_entry_get_selection_bounds (GtkEditable *editable, gint *start, gint *end); + +/* GtkCellEditable method implementations */ +static void egg_secure_entry_start_editing (GtkCellEditable *cell_editable, GdkEvent *event); + +/* Default signal handlers */ +static void egg_secure_entry_real_insert_text (GtkEditable *editable, const gchar *new_text, + gint new_text_length, gint *position); +static void egg_secure_entry_real_delete_text (GtkEditable *editable, gint start_pos, gint end_pos); +static void egg_secure_entry_move_cursor (EggSecureEntry *entry, GtkMovementStep step, + gint count, gboolean extend_selection); +static void egg_secure_entry_insert_at_cursor (EggSecureEntry *entry, const gchar *str); +static void egg_secure_entry_delete_from_cursor (EggSecureEntry *entry, GtkDeleteType type, gint count); +static void egg_secure_entry_real_activate (EggSecureEntry *entry); +static void egg_secure_entry_keymap_direction_changed (GdkKeymap *keymap, EggSecureEntry *entry); + +/* IM Context Callbacks */ +static void egg_secure_entry_commit_cb(GtkIMContext *context, const gchar *str, EggSecureEntry *entry); +static void egg_secure_entry_preedit_changed_cb (GtkIMContext * context, EggSecureEntry *entry); +static gboolean egg_secure_entry_retrieve_surrounding_cb (GtkIMContext *context, EggSecureEntry *entry); +static gboolean egg_secure_entry_delete_surrounding_cb (GtkIMContext *context, gint offset, + gint n_chars, EggSecureEntry *entry); + +/* Internal routines */ +static void egg_secure_entry_enter_text (EggSecureEntry *entry, const gchar *str); +static void egg_secure_entry_set_positions (EggSecureEntry *entry, gint current_pos, gint selection_bound); +static void egg_secure_entry_draw_text (EggSecureEntry *entry); +static void egg_secure_entry_draw_cursor (EggSecureEntry *entry); +static PangoLayout *egg_secure_entry_ensure_layout(EggSecureEntry *entry, gboolean include_preedit); +static void egg_secure_entry_reset_layout (EggSecureEntry *entry); +static void egg_secure_entry_queue_draw (EggSecureEntry *entry); +static void egg_secure_entry_reset_im_context (EggSecureEntry *entry); +static void egg_secure_entry_recompute (EggSecureEntry *entry); +static gint egg_secure_entry_find_position (EggSecureEntry *entry, gint x); +static void egg_secure_entry_get_cursor_locations (EggSecureEntry *entry, gint *strong_x, gint *weak_x); +static void egg_secure_entry_adjust_scroll (EggSecureEntry *entry); +static gint egg_secure_entry_move_visually (EggSecureEntry *editable, gint start, gint count); +static gint egg_secure_entry_move_logically (EggSecureEntry *entry, gint start, gint count); +static gboolean egg_secure_entry_mnemonic_activate (GtkWidget *widget, gboolean group_cycling); +static void egg_secure_entry_state_changed (GtkWidget *widget, GtkStateType previous_state); +static void egg_secure_entry_check_cursor_blink (EggSecureEntry *entry); +static void egg_secure_entry_pend_cursor_blink (EggSecureEntry *entry); +static void get_text_area_size (EggSecureEntry *entry, gint *x, gint *y, gint *width, gint *height); +static void get_widget_window_size (EggSecureEntry *entry, gint *x, gint *y, gint *width, gint *height); + +#define _gtk_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID +#define _gtk_marshal_VOID__STRING g_cclosure_marshal_VOID__STRING +static void _gtk_marshal_VOID__ENUM_INT_BOOLEAN (GClosure *closure, GValue *return_value, + guint n_param_values, const GValue *param_values, + gpointer invocation_hint, gpointer marshal_data); +static void _gtk_marshal_VOID__ENUM_INT (GClosure *closure, GValue *return_value, guint n_param_values, + const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); + +static GtkWidgetClass *parent_class = NULL; + +GType +egg_secure_entry_get_type(void) +{ + static GType entry_type = 0; + + if (!entry_type) { + static const GTypeInfo entry_info = { + sizeof(EggSecureEntryClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) egg_secure_entry_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(EggSecureEntry), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_secure_entry_init, + }; + + static const GInterfaceInfo editable_info = { + (GInterfaceInitFunc) egg_secure_entry_editable_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + static const GInterfaceInfo cell_editable_info = { + (GInterfaceInitFunc) egg_secure_entry_cell_editable_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + entry_type = g_type_register_static(GTK_TYPE_WIDGET, "EggSecureEntry", &entry_info, 0); + g_type_add_interface_static(entry_type, GTK_TYPE_EDITABLE, &editable_info); + g_type_add_interface_static(entry_type, GTK_TYPE_CELL_EDITABLE, &cell_editable_info); + } + + return entry_type; +} + +static void +add_move_binding (GtkBindingSet *binding_set, guint keyval, guint modmask, + GtkMovementStep step, gint count) +{ + g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0); + + gtk_binding_entry_add_signal (binding_set, keyval, modmask, "move_cursor", 3, + G_TYPE_ENUM, step, G_TYPE_INT, count, G_TYPE_BOOLEAN, FALSE); + + /* Selection-extending version */ + gtk_binding_entry_add_signal (binding_set, keyval, modmask | GDK_SHIFT_MASK, "move_cursor", + 3, G_TYPE_ENUM, step, G_TYPE_INT, count, G_TYPE_BOOLEAN, TRUE); +} + +static void +egg_secure_entry_class_init(EggSecureEntryClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + GtkWidgetClass *widget_class; + GtkBindingSet *binding_set; + + widget_class = (GtkWidgetClass*) class; + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = egg_secure_entry_finalize; + gobject_class->set_property = egg_secure_entry_set_property; + gobject_class->get_property = egg_secure_entry_get_property; + + widget_class->realize = egg_secure_entry_realize; + widget_class->unrealize = egg_secure_entry_unrealize; + widget_class->size_request = egg_secure_entry_size_request; + widget_class->size_allocate = egg_secure_entry_size_allocate; + widget_class->expose_event = egg_secure_entry_expose; + widget_class->button_press_event = egg_secure_entry_button_press; + widget_class->button_release_event = egg_secure_entry_button_release; + widget_class->motion_notify_event = egg_secure_entry_motion_notify; + widget_class->key_press_event = egg_secure_entry_key_press; + widget_class->key_release_event = egg_secure_entry_key_release; + widget_class->focus_in_event = egg_secure_entry_focus_in; + widget_class->focus_out_event = egg_secure_entry_focus_out; + widget_class->grab_focus = egg_secure_entry_grab_focus; + widget_class->style_set = egg_secure_entry_style_set; + widget_class->direction_changed = egg_secure_entry_direction_changed; + widget_class->state_changed = egg_secure_entry_state_changed; + widget_class->screen_changed = egg_secure_entry_screen_changed; + widget_class->mnemonic_activate = egg_secure_entry_mnemonic_activate; + + class->move_cursor = egg_secure_entry_move_cursor; + class->insert_at_cursor = egg_secure_entry_insert_at_cursor; + class->delete_from_cursor = egg_secure_entry_delete_from_cursor; + class->activate = egg_secure_entry_real_activate; + + g_object_class_install_property (gobject_class, PROP_CURSOR_POSITION, + g_param_spec_int ("cursor_position", "Cursor Position", "The current position of the insertion cursor in chars", + 0, MAX_SIZE, 0, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_SELECTION_BOUND, + g_param_spec_int ("selection_bound", "Selection Bound", "The position of the opposite end of the selection from the cursor in chars", + 0, MAX_SIZE, 0, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_MAX_LENGTH, + g_param_spec_int ("max_length", "Maximum length", "Maximum number of characters for this entry. Zero if no maximum", + 0, MAX_SIZE, 0, G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_HAS_FRAME, + g_param_spec_boolean("has_frame", "Has Frame", "FALSE removes outside bevel from entry", + TRUE, G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_INVISIBLE_CHAR, + g_param_spec_unichar("invisible_char", "Invisible character", "The character to use when masking entry contents (in \"password mode\")", + '*', G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_ACTIVATES_DEFAULT, + g_param_spec_boolean ("activates_default", "Activates default", "Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed", + FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS, + g_param_spec_int ("width_chars", "Width in chars", "Number of characters to leave space for in the entry", + -1, G_MAXINT, -1, G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_SCROLL_OFFSET, + g_param_spec_int("scroll_offset", "Scroll offset", "Number of pixels of the entry scrolled off the screen to the left", + 0, G_MAXINT, 0, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_TEXT, + g_param_spec_string("text", "Text", "The contents of the entry", + "", G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_VISIBILITY, + g_param_spec_boolean ("visibility", "Visibility", "Whether contents are drawn using invisible character", + FALSE, G_PARAM_READWRITE)); + + /* Action signals */ + + signals[ACTIVATE] = g_signal_new ("activate", G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggSecureEntryClass, activate), + NULL, NULL, _gtk_marshal_VOID__VOID, G_TYPE_NONE, 0); + widget_class->activate_signal = signals[ACTIVATE]; + + signals[MOVE_CURSOR] = g_signal_new ("move_cursor", G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggSecureEntryClass, move_cursor), + NULL, NULL, _gtk_marshal_VOID__ENUM_INT_BOOLEAN, + G_TYPE_NONE, 3, GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT, G_TYPE_BOOLEAN); + + signals[INSERT_AT_CURSOR] = g_signal_new("insert_at_cursor", G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggSecureEntryClass, insert_at_cursor), + NULL, NULL, _gtk_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[DELETE_FROM_CURSOR] = g_signal_new("delete_from_cursor", G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggSecureEntryClass, delete_from_cursor), + NULL, NULL, _gtk_marshal_VOID__ENUM_INT, G_TYPE_NONE, 2, + GTK_TYPE_DELETE_TYPE, G_TYPE_INT); + + /* Key bindings */ + + binding_set = gtk_binding_set_by_class(class); + + /* Moving the insertion point */ + add_move_binding(binding_set, GDK_Right, 0, GTK_MOVEMENT_VISUAL_POSITIONS, 1); + add_move_binding(binding_set, GDK_Left, 0, GTK_MOVEMENT_VISUAL_POSITIONS, -1); + add_move_binding(binding_set, GDK_KP_Right, 0, GTK_MOVEMENT_VISUAL_POSITIONS, 1); + add_move_binding(binding_set, GDK_KP_Left, 0, GTK_MOVEMENT_VISUAL_POSITIONS, -1); + add_move_binding(binding_set, GDK_Right, GDK_CONTROL_MASK, GTK_MOVEMENT_WORDS, 1); + add_move_binding(binding_set, GDK_Left, GDK_CONTROL_MASK, GTK_MOVEMENT_WORDS, -1); + add_move_binding(binding_set, GDK_KP_Right, GDK_CONTROL_MASK, GTK_MOVEMENT_WORDS, 1); + add_move_binding(binding_set, GDK_KP_Left, GDK_CONTROL_MASK, GTK_MOVEMENT_WORDS, -1); + add_move_binding(binding_set, GDK_Home, 0, GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + add_move_binding(binding_set, GDK_End, 0, GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + add_move_binding(binding_set, GDK_KP_Home, 0, GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + add_move_binding(binding_set, GDK_KP_End, 0, GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + add_move_binding(binding_set, GDK_Home, GDK_CONTROL_MASK, GTK_MOVEMENT_BUFFER_ENDS, -1); + add_move_binding(binding_set, GDK_End, GDK_CONTROL_MASK, GTK_MOVEMENT_BUFFER_ENDS, 1); + add_move_binding(binding_set, GDK_KP_Home, GDK_CONTROL_MASK, GTK_MOVEMENT_BUFFER_ENDS, -1); + add_move_binding(binding_set, GDK_KP_End, GDK_CONTROL_MASK, GTK_MOVEMENT_BUFFER_ENDS, 1); + + /* Select all */ + gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "move_cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, G_TYPE_INT, -1, + G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "move_cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, G_TYPE_INT, 1, + G_TYPE_BOOLEAN, TRUE); + + /* Activate */ + gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "activate", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "activate", 0); + + /* Deleting text */ + gtk_binding_entry_add_signal (binding_set, GDK_Delete, 0, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, G_TYPE_INT, 1); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, 0, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, G_TYPE_INT, 1); + gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, 0, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, G_TYPE_INT, -1); + /* Make this do the same as Backspace, to help with mis-typing */ + gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, GDK_SHIFT_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, G_TYPE_INT, -1); + gtk_binding_entry_add_signal (binding_set, GDK_Delete, GDK_CONTROL_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, G_TYPE_INT, 1); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Delete, GDK_CONTROL_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, G_TYPE_INT, 1); + gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, GDK_CONTROL_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, G_TYPE_INT, -1); +} + +static void +egg_secure_entry_editable_init (GtkEditableClass *iface) +{ + iface->do_insert_text = egg_secure_entry_insert_text; + iface->do_delete_text = egg_secure_entry_delete_text; + iface->insert_text = egg_secure_entry_real_insert_text; + iface->delete_text = egg_secure_entry_real_delete_text; + iface->set_selection_bounds = egg_secure_entry_set_selection_bounds; + iface->get_selection_bounds = egg_secure_entry_get_selection_bounds; + iface->set_position = egg_secure_entry_real_set_position; + iface->get_position = egg_secure_entry_get_position; +} + +static void +egg_secure_entry_cell_editable_init (GtkCellEditableIface * iface) +{ + iface->start_editing = egg_secure_entry_start_editing; +} + +static void +egg_secure_entry_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(object); + + switch (prop_id) { + case PROP_MAX_LENGTH: + egg_secure_entry_set_max_length(entry, g_value_get_int(value)); + break; + + case PROP_HAS_FRAME: + egg_secure_entry_set_has_frame(entry, g_value_get_boolean(value)); + break; + + case PROP_INVISIBLE_CHAR: + egg_secure_entry_set_invisible_char(entry, g_value_get_uint(value)); + break; + + case PROP_ACTIVATES_DEFAULT: + egg_secure_entry_set_activates_default(entry, g_value_get_boolean(value)); + break; + + case PROP_WIDTH_CHARS: + egg_secure_entry_set_width_chars(entry, g_value_get_int(value)); + break; + + case PROP_TEXT: + egg_secure_entry_set_text(entry, g_value_get_string(value)); + break; + + case PROP_VISIBILITY: + egg_secure_entry_set_visibility (entry, g_value_get_boolean (value)); + break; + + case PROP_SCROLL_OFFSET: + case PROP_CURSOR_POSITION: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +egg_secure_entry_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(object); + + switch (prop_id) { + case PROP_CURSOR_POSITION: + g_value_set_int(value, entry->current_pos); + break; + case PROP_SELECTION_BOUND: + g_value_set_int(value, entry->selection_bound); + break; + case PROP_MAX_LENGTH: + g_value_set_int(value, entry->text_max_length); + break; + case PROP_HAS_FRAME: + g_value_set_boolean(value, entry->has_frame); + break; + case PROP_INVISIBLE_CHAR: + g_value_set_uint(value, entry->invisible_char); + break; + case PROP_ACTIVATES_DEFAULT: + g_value_set_boolean(value, entry->activates_default); + break; + case PROP_WIDTH_CHARS: + g_value_set_int(value, entry->width_chars); + break; + case PROP_SCROLL_OFFSET: + g_value_set_int(value, entry->scroll_offset); + break; + case PROP_TEXT: + g_value_set_string(value, egg_secure_entry_get_text(entry)); + break; + case PROP_VISIBILITY: + g_value_set_boolean (value, egg_secure_entry_get_visibility (entry)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +egg_secure_entry_init (EggSecureEntry *entry) +{ + GtkStyle *style; + GtkWidget *tempent; + + GTK_WIDGET_SET_FLAGS (entry, GTK_CAN_FOCUS); + + /* Get the RC style for a normal GtkEntry */ + style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (GTK_WIDGET (entry)), + NULL, NULL, GTK_TYPE_ENTRY); + gtk_widget_set_style (GTK_WIDGET (entry), style); + + entry->text_size = MIN_SIZE; + entry->text = egg_secure_alloc (entry->text_size + 1); + entry->text[0] = '\0'; + + entry->visibility = FALSE; + entry->width_chars = -1; + entry->is_cell_renderer = FALSE; + entry->editing_canceled = FALSE; + entry->has_frame = TRUE; + + /* Use the invisible_char from GtkEntry */ + tempent = gtk_entry_new (); + entry->invisible_char = gtk_entry_get_invisible_char (GTK_ENTRY (tempent)); + g_object_ref_sink (tempent); + g_object_unref (tempent); + + /* + * This object is completely private. No external entity can gain a reference + * to it; so we create it here and destroy it in finalize(). + */ + entry->im_context = gtk_im_multicontext_new (); + + g_signal_connect (entry->im_context, "commit", + G_CALLBACK (egg_secure_entry_commit_cb), entry); + g_signal_connect (entry->im_context, "preedit_changed", + G_CALLBACK(egg_secure_entry_preedit_changed_cb), entry); + g_signal_connect (entry->im_context, "retrieve_surrounding", + G_CALLBACK(egg_secure_entry_retrieve_surrounding_cb), entry); + g_signal_connect (entry->im_context, "delete_surrounding", + G_CALLBACK(egg_secure_entry_delete_surrounding_cb), entry); +} + +static void +egg_secure_entry_finalize (GObject *object) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (object); + + if (entry->cached_layout) + g_object_unref (entry->cached_layout); + + g_object_unref (entry->im_context); + + if (entry->blink_timeout) + g_source_remove (entry->blink_timeout); + + if (entry->recompute_idle) + g_source_remove (entry->recompute_idle); + + entry->text_size = 0; + + if (entry->text) + egg_secure_free (entry->text); + entry->text = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +egg_secure_entry_realize (GtkWidget *widget) +{ + EggSecureEntry *entry; + GtkEditable *editable; + GdkWindowAttr attributes; + gint attributes_mask; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + entry = EGG_SECURE_ENTRY (widget); + editable = GTK_EDITABLE (widget); + + attributes.window_type = GDK_WINDOW_CHILD; + + get_widget_window_size (entry, &attributes.x, &attributes.y, + &attributes.width, &attributes.height); + + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | + GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, entry); + + get_text_area_size (entry, &attributes.x, &attributes.y, + &attributes.width, &attributes.height); + + attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), + GDK_XTERM); + attributes_mask |= GDK_WA_CURSOR; + + entry->text_area = gdk_window_new (widget->window, &attributes, attributes_mask); + gdk_window_set_user_data (entry->text_area, entry); + + gdk_cursor_unref (attributes.cursor); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gdk_window_set_background (widget->window, + &widget->style->base[GTK_WIDGET_STATE (widget)]); + gdk_window_set_background (entry->text_area, + &widget->style->base[GTK_WIDGET_STATE (widget)]); + + gdk_window_show (entry->text_area); + + gtk_im_context_set_client_window (entry->im_context, entry->text_area); + + egg_secure_entry_adjust_scroll (entry); +} + +static void +egg_secure_entry_unrealize (GtkWidget *widget) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + egg_secure_entry_reset_layout (entry); + + gtk_im_context_set_client_window (entry->im_context, NULL); + + if (entry->text_area) { + gdk_window_set_user_data (entry->text_area, NULL); + gdk_window_destroy (entry->text_area); + entry->text_area = NULL; + } + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (*GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +get_borders (EggSecureEntry *entry, gint *xborder, gint *yborder) +{ + GtkWidget *widget = GTK_WIDGET (entry); + gint focus_width; + gboolean interior_focus; + + gtk_widget_style_get (widget, "interior-focus", &interior_focus, + "focus-line-width", &focus_width, NULL); + + if (entry->has_frame) { + *xborder = widget->style->xthickness; + *yborder = widget->style->ythickness; + } else { + *xborder = 0; + *yborder = 0; + } + + if (!interior_focus) { + *xborder += focus_width; + *yborder += focus_width; + } +} + +static void +egg_secure_entry_size_request (GtkWidget *widget, GtkRequisition *requisition) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + PangoFontMetrics *metrics; + gint xborder, yborder; + PangoContext *context; + + context = gtk_widget_get_pango_context (widget); + metrics = pango_context_get_metrics (context, widget->style->font_desc, + pango_context_get_language (context)); + + entry->ascent = pango_font_metrics_get_ascent (metrics); + entry->descent = pango_font_metrics_get_descent (metrics); + + get_borders (entry, &xborder, &yborder); + + xborder += INNER_BORDER; + yborder += INNER_BORDER; + + if (entry->width_chars < 0) + requisition->width = MIN_ASK_ENTRY_WIDTH + xborder * 2; + else { + gint char_width = pango_font_metrics_get_approximate_char_width (metrics); + gint digit_width = pango_font_metrics_get_approximate_digit_width (metrics); + gint char_pixels = (MAX (char_width, digit_width) + PANGO_SCALE - 1) / PANGO_SCALE; + requisition->width = char_pixels * entry->width_chars + xborder * 2; + } + + requisition->height = PANGO_PIXELS (entry->ascent + entry->descent) + yborder * 2; + pango_font_metrics_unref(metrics); +} + +static void +get_text_area_size(EggSecureEntry *entry, gint *x, gint *y, gint *width, gint *height) +{ + gint xborder, yborder; + GtkRequisition requisition; + GtkWidget *widget = GTK_WIDGET (entry); + + gtk_widget_get_child_requisition (widget, &requisition); + + get_borders (entry, &xborder, &yborder); + + if (x) + *x = xborder; + + if (y) + *y = yborder; + + if (width) + *width = GTK_WIDGET (entry)->allocation.width - xborder * 2; + + if (height) + *height = requisition.height - yborder * 2; +} + +static void +get_widget_window_size (EggSecureEntry *entry, gint *x, gint *y, gint *width, gint *height) +{ + GtkRequisition requisition; + GtkWidget *widget = GTK_WIDGET (entry); + + gtk_widget_get_child_requisition (widget, &requisition); + + if (x) + *x = widget->allocation.x; + + if (y) { + if (entry->is_cell_renderer) + *y = widget->allocation.y; + else + *y = widget->allocation.y + (widget->allocation.height - + requisition.height) / 2; + } + + if (width) + *width = widget->allocation.width; + + if (height) { + if (entry->is_cell_renderer) + *height = widget->allocation.height; + else + *height = requisition.height; + } +} + +static void +egg_secure_entry_size_allocate(GtkWidget *widget, GtkAllocation *allocation) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(widget); + + widget->allocation = *allocation; + + if (GTK_WIDGET_REALIZED (widget)) { + /* + * We call gtk_widget_get_child_requisition, since we want (for + * backwards compatibility reasons) the realization here to + * be affected by the usize of the entry, if set + */ + gint x, y, width, height; + + get_widget_window_size (entry, &x, &y, &width, &height); + + gdk_window_move_resize (widget->window, x, y, width, height); + + get_text_area_size (entry, &x, &y, &width, &height); + + gdk_window_move_resize (entry->text_area, x, y, width, height); + + egg_secure_entry_recompute (entry); + } +} + +static void +egg_secure_entry_draw_frame (GtkWidget *widget) +{ + gint x = 0, y = 0; + gint width, height; + gboolean interior_focus; + gint focus_width; + + gtk_widget_style_get (widget, "interior-focus", &interior_focus, + "focus-line-width", &focus_width, NULL); + + gdk_drawable_get_size (widget->window, &width, &height); + + if (GTK_WIDGET_HAS_FOCUS (widget) && !interior_focus) { + x += focus_width; + y += focus_width; + width -= 2 * focus_width; + height -= 2 * focus_width; + } + + gtk_paint_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, + NULL, widget, "entry", x, y, width, height); + + if (GTK_WIDGET_HAS_FOCUS (widget) && !interior_focus) { + x -= focus_width; + y -= focus_width; + width += 2 * focus_width; + height += 2 * focus_width; + + gtk_paint_focus(widget->style, widget->window, GTK_WIDGET_STATE (widget), + NULL, widget, "entry", 0, 0, width, height); + } +} + +static gint +egg_secure_entry_expose(GtkWidget *widget, GdkEventExpose *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(widget); + + if (widget->window == event->window) + egg_secure_entry_draw_frame(widget); + else if (entry->text_area == event->window) { + gint area_width, area_height; + + get_text_area_size(entry, NULL, NULL, &area_width, &area_height); + + gtk_paint_flat_box(widget->style, entry->text_area, GTK_WIDGET_STATE (widget), GTK_SHADOW_NONE, + NULL, widget, "entry_bg", 0, 0, area_width, area_height); + + if ((entry->invisible_char != 0) && GTK_WIDGET_HAS_FOCUS (widget) && + entry->selection_bound == entry->current_pos && entry->cursor_visible) + egg_secure_entry_draw_cursor (EGG_SECURE_ENTRY (widget)); + + egg_secure_entry_draw_text (EGG_SECURE_ENTRY (widget)); + } + + return FALSE; +} + +static gint +egg_secure_entry_button_press(GtkWidget *widget, GdkEventButton *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + gint tmp_pos; + + if (event->window != entry->text_area || + (entry->button && event->button != entry->button)) + return FALSE; + + entry->button = event->button; + + if (!GTK_WIDGET_HAS_FOCUS (widget)) { + entry->in_click = TRUE; + gtk_widget_grab_focus (widget); + entry->in_click = FALSE; + } + + tmp_pos = egg_secure_entry_find_position (entry, event->x + entry->scroll_offset); + + if (event->button == 1) { + switch (event->type) { + case GDK_BUTTON_PRESS: + egg_secure_entry_set_positions(entry, tmp_pos, tmp_pos); + break; + default: + break; + } + + return TRUE; + } + + return FALSE; +} + +static gint +egg_secure_entry_button_release(GtkWidget *widget, GdkEventButton *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(widget); + + if (event->window != entry->text_area || entry->button != event->button) + return FALSE; + + entry->button = 0; + return TRUE; +} + +static gint +egg_secure_entry_motion_notify(GtkWidget *widget, GdkEventMotion *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(widget); + gint tmp_pos; + + if (entry->mouse_cursor_obscured) { + GdkCursor *cursor; + + cursor = gdk_cursor_new_for_display (gtk_widget_get_display(widget), + GDK_XTERM); + gdk_window_set_cursor (entry->text_area, cursor); + gdk_cursor_unref (cursor); + entry->mouse_cursor_obscured = FALSE; + } + + if (event->window != entry->text_area || entry->button != 1) + return FALSE; + + if (event->is_hint || (entry->text_area != event->window)) + gdk_window_get_pointer (entry->text_area, NULL, NULL, NULL); + + { + gint height; + gdk_drawable_get_size (entry->text_area, NULL, &height); + + if (event->y < 0) + tmp_pos = 0; + else if (event->y >= height) + tmp_pos = entry->text_length; + else + tmp_pos = egg_secure_entry_find_position (entry, + event->x + entry->scroll_offset); + + egg_secure_entry_set_positions (entry, tmp_pos, -1); + } + + return TRUE; +} + +static void +set_invisible_cursor (GdkWindow * window) +{ + GdkBitmap *empty_bitmap; + GdkCursor *cursor; + GdkColor useless; + char invisible_cursor_bits[] = { 0x0 }; + + useless.red = useless.green = useless.blue = 0; + useless.pixel = 0; + + empty_bitmap = gdk_bitmap_create_from_data (window, invisible_cursor_bits, 1, 1); + + cursor = gdk_cursor_new_from_pixmap (empty_bitmap, empty_bitmap, &useless, &useless, 0, 0); + + gdk_window_set_cursor (window, cursor); + + gdk_cursor_unref (cursor); + + g_object_unref (empty_bitmap); +} + +static void +egg_secure_entry_obscure_mouse_cursor (EggSecureEntry * entry) +{ + if (entry->mouse_cursor_obscured) + return; + + set_invisible_cursor (entry->text_area); + + entry->mouse_cursor_obscured = TRUE; +} + +static gint +egg_secure_entry_key_press (GtkWidget *widget, GdkEventKey *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + egg_secure_entry_pend_cursor_blink (entry); + + if (gtk_im_context_filter_keypress (entry->im_context, event)) { + egg_secure_entry_obscure_mouse_cursor (entry); + entry->need_im_reset = TRUE; + return TRUE; + } + + if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event)) + /* Activate key bindings */ + return TRUE; + + return FALSE; +} + +static gint +egg_secure_entry_key_release (GtkWidget *widget, GdkEventKey *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + if (gtk_im_context_filter_keypress (entry->im_context, event)) { + entry->need_im_reset = TRUE; + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->key_release_event (widget, event); +} + +static gint +egg_secure_entry_focus_in (GtkWidget *widget, GdkEventFocus *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + gtk_widget_queue_draw (widget); + + entry->need_im_reset = TRUE; + gtk_im_context_focus_in (entry->im_context); + + g_signal_connect (gdk_keymap_get_for_display (gtk_widget_get_display (widget)), "direction_changed", + G_CALLBACK (egg_secure_entry_keymap_direction_changed), entry); + + egg_secure_entry_check_cursor_blink (entry); + + return FALSE; +} + +static gint +egg_secure_entry_focus_out (GtkWidget *widget, GdkEventFocus *event) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + gtk_widget_queue_draw (widget); + + entry->need_im_reset = TRUE; + gtk_im_context_focus_out (entry->im_context); + + egg_secure_entry_check_cursor_blink (entry); + + g_signal_handlers_disconnect_by_func (gdk_keymap_get_for_display (gtk_widget_get_display (widget)), + egg_secure_entry_keymap_direction_changed, entry); + + return FALSE; +} + +static void +egg_secure_entry_grab_focus (GtkWidget *widget) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + GtkSettings *settings = gtk_widget_get_settings (widget); + gboolean select_on_focus = FALSE; + + GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_DEFAULT); + GTK_WIDGET_CLASS (parent_class)->grab_focus(widget); + + /* Some versions of GTK don't have this property */ + if (g_object_class_find_property (G_OBJECT_CLASS (GTK_SETTINGS_GET_CLASS (settings)), + "gtk-entry-select-on-focus")) + g_object_get (settings, "gtk-entry-select-on-focus", &select_on_focus, NULL); + + if (select_on_focus && !entry->in_click) + gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); +} + +static void +egg_secure_entry_direction_changed(GtkWidget *widget, GtkTextDirection previous_dir) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + egg_secure_entry_recompute (entry); + + GTK_WIDGET_CLASS (parent_class)->direction_changed (widget, previous_dir); +} + +static void +egg_secure_entry_state_changed (GtkWidget *widget, GtkStateType previous_state) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + if (GTK_WIDGET_REALIZED (widget)) { + gdk_window_set_background (widget->window, + &widget->style->base[GTK_WIDGET_STATE(widget)]); + gdk_window_set_background (entry->text_area, + &widget->style->base[GTK_WIDGET_STATE(widget)]); + } + + if (!GTK_WIDGET_IS_SENSITIVE (widget)) { + /* Clear any selection */ + gtk_editable_select_region (GTK_EDITABLE(entry), + entry->current_pos, entry->current_pos); + } + + gtk_widget_queue_draw (widget); +} + +static void +egg_secure_entry_screen_changed (GtkWidget *widget, GdkScreen *old_screen) +{ + egg_secure_entry_recompute (EGG_SECURE_ENTRY (widget)); +} + +/* GtkEditable method implementations */ + +static void +egg_secure_entry_insert_text (GtkEditable *editable, const gchar *new_text, + gint new_text_length, gint * position) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY(editable); + gchar *text; + + if (*position < 0 || *position > entry->text_length) + *position = entry->text_length; + + g_object_ref (editable); + + text = egg_secure_alloc (new_text_length + 1); + + strncpy (text, new_text, new_text_length); + text[new_text_length] = '\0'; + + g_signal_emit_by_name (editable, "insert_text", text, + new_text_length, position); + + egg_secure_free (text); + + g_object_unref (editable); +} + +static void +egg_secure_entry_delete_text (GtkEditable* editable, gint start_pos, + gint end_pos) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (editable); + + if (end_pos < 0 || end_pos > entry->text_length) + end_pos = entry->text_length; + if (start_pos < 0) + start_pos = 0; + if (start_pos > end_pos) + start_pos = end_pos; + + g_object_ref(editable); + + g_signal_emit_by_name (editable, "delete_text", start_pos, end_pos); + + g_object_unref (editable); +} + +static void +egg_secure_entry_set_position_internal (EggSecureEntry *entry, + gint position, gboolean reset_im) +{ + if (position < 0 || position > entry->text_length) + position = entry->text_length; + + if (position != entry->current_pos || position != entry->selection_bound) { + if (reset_im) + egg_secure_entry_reset_im_context (entry); + egg_secure_entry_set_positions (entry, position, position); + } +} + +static void +egg_secure_entry_real_set_position (GtkEditable *editable, gint position) +{ + egg_secure_entry_set_position_internal (EGG_SECURE_ENTRY (editable), + position, TRUE); +} + +static gint +egg_secure_entry_get_position (GtkEditable *editable) +{ + return EGG_SECURE_ENTRY (editable)->current_pos; +} + +static void +egg_secure_entry_set_selection_bounds (GtkEditable *editable, + gint start, gint end) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (editable); + + if (start < 0) + start = entry->text_length; + if (end < 0) + end = entry->text_length; + + egg_secure_entry_reset_im_context (entry); + + egg_secure_entry_set_positions (entry, MIN (end, entry->text_length), + MIN (start, entry->text_length)); +} + +static gboolean +egg_secure_entry_get_selection_bounds (GtkEditable *editable, + gint *start, gint *end) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (editable); + + *start = entry->selection_bound; + *end = entry->current_pos; + + return (entry->selection_bound != entry->current_pos); +} + +static void +egg_secure_entry_style_set (GtkWidget *widget, GtkStyle *previous_style) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (widget); + + egg_secure_entry_recompute (entry); + + if (previous_style && GTK_WIDGET_REALIZED (widget)) { + gdk_window_set_background (widget->window, + &widget->style->base[GTK_WIDGET_STATE (widget)]); + gdk_window_set_background (entry->text_area, + &widget->style-> base[GTK_WIDGET_STATE (widget)]); + } +} + +/* GtkCellEditable method implementations + */ +static void +gtk_cell_editable_secure_entry_activated (EggSecureEntry *entry, gpointer data) +{ + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (entry)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (entry)); +} + +static gboolean +gtk_cell_editable_key_press_event (EggSecureEntry *entry, GdkEventKey *key_event, + gpointer data) +{ + if (key_event->keyval == GDK_Escape) { + entry->editing_canceled = TRUE; + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (entry)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (entry)); + return TRUE; + } + + /* override focus */ + if (key_event->keyval == GDK_Up || key_event->keyval == GDK_Down) { + gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry)); + gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry)); + return TRUE; + } + + return FALSE; +} + +static void +egg_secure_entry_start_editing (GtkCellEditable *cell_editable, + GdkEvent *event) +{ + EGG_SECURE_ENTRY(cell_editable)->is_cell_renderer = TRUE; + + g_signal_connect (cell_editable, "activate", + G_CALLBACK (gtk_cell_editable_secure_entry_activated), NULL); + g_signal_connect (cell_editable, "key_press_event", + G_CALLBACK (gtk_cell_editable_key_press_event), NULL); +} + +/* Default signal handlers */ + +static void +egg_secure_entry_real_insert_text (GtkEditable *editable, const gchar *new_text, + gint new_text_length, gint *position) +{ + gint _index; + gint n_chars; + + EggSecureEntry *entry = EGG_SECURE_ENTRY (editable); + + if (new_text_length < 0) + new_text_length = strlen (new_text); + + n_chars = g_utf8_strlen (new_text, new_text_length); + if (entry->text_max_length > 0 && n_chars + entry->text_length > entry->text_max_length) { + gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (entry))); + n_chars = entry->text_max_length - entry->text_length; + new_text_length = g_utf8_offset_to_pointer (new_text, n_chars) - new_text; + } + + if (new_text_length + entry->n_bytes + 1 > entry->text_size) { + while (new_text_length + entry->n_bytes + 1 > entry->text_size) { + if (entry->text_size == 0) + entry->text_size = MIN_SIZE; + else { + if (2 * (guint) entry->text_size < MAX_SIZE && + 2 * (guint) entry->text_size > entry->text_size) + entry->text_size *= 2; + else { + entry->text_size = MAX_SIZE; + if (new_text_length > (gint) entry->text_size - (gint) entry->n_bytes - 1) { + new_text_length = (gint) entry->text_size - (gint) entry->n_bytes - 1; + new_text_length = g_utf8_find_prev_char (new_text, new_text + new_text_length + 1) - new_text; + n_chars = g_utf8_strlen (new_text, new_text_length); + } + break; + } + } + } + + entry->text = egg_secure_realloc (entry->text, entry->text_size + 1); + } + + _index = g_utf8_offset_to_pointer (entry->text, *position) - entry->text; + + g_memmove (entry->text + _index + new_text_length, entry->text + _index, + entry->n_bytes - _index); + memcpy (entry->text + _index, new_text, new_text_length); + + entry->n_bytes += new_text_length; + entry->text_length += n_chars; + + /* NUL terminate for safety and convenience */ + entry->text[entry->n_bytes] = '\0'; + + if (entry->current_pos > *position) + entry->current_pos += n_chars; + + if (entry->selection_bound > *position) + entry->selection_bound += n_chars; + + *position += n_chars; + + egg_secure_entry_recompute (entry); + + entry->changed = TRUE; + g_signal_emit_by_name (editable, "changed"); + g_object_notify (G_OBJECT (editable), "text"); +} + +static void +egg_secure_entry_real_delete_text (GtkEditable *editable, gint start_pos, + gint end_pos) +{ + EggSecureEntry *entry = EGG_SECURE_ENTRY (editable); + + if (start_pos < 0) + start_pos = 0; + if (end_pos < 0 || end_pos > entry->text_length) + end_pos = entry->text_length; + + if (start_pos < end_pos) { + gint start_index = g_utf8_offset_to_pointer (entry->text, start_pos) - entry->text; + gint end_index = g_utf8_offset_to_pointer(entry->text, end_pos) - entry->text; + gint current_pos; + gint selection_bound; + + g_memmove (entry->text + start_index, entry->text + end_index, + entry->n_bytes + 1 - end_index); + entry->text_length -= (end_pos - start_pos); + entry->n_bytes -= (end_index - start_index); + + current_pos = entry->current_pos; + if (current_pos > start_pos) + current_pos -= MIN(current_pos, end_pos) - start_pos; + + selection_bound = entry->selection_bound; + if (selection_bound > start_pos) + selection_bound -= MIN(selection_bound, end_pos) - start_pos; + + egg_secure_entry_set_positions(entry, current_pos, selection_bound); + + egg_secure_entry_recompute (entry); + + entry->changed = TRUE; + g_signal_emit_by_name (editable, "changed"); + g_object_notify (G_OBJECT (editable), "text"); + } +} + +/* + * Compute the X position for an offset that corresponds to the "more important + * cursor position for that offset. We use this when trying to guess to which + * end of the selection we should go to when the user hits the left or + * right arrow key. + */ +static gint +get_better_cursor_x (EggSecureEntry *entry, gint offset) +{ + GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET(entry))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + gboolean split_cursor; + + PangoLayout *layout = egg_secure_entry_ensure_layout (entry, TRUE); + const gchar *text = pango_layout_get_text(layout); + gint _index = g_utf8_offset_to_pointer (text, offset) - text; + + PangoRectangle strong_pos, weak_pos; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)), + "gtk-split-cursor", &split_cursor, NULL); + + pango_layout_get_cursor_pos (layout, _index, &strong_pos, &weak_pos); + + if (split_cursor) + return strong_pos.x / PANGO_SCALE; + else + return (keymap_direction == entry->resolved_dir) ? + strong_pos.x / PANGO_SCALE : + weak_pos.x / PANGO_SCALE; +} + +static void +egg_secure_entry_move_cursor (EggSecureEntry *entry, GtkMovementStep step, + gint count, gboolean extend_selection) +{ + gint new_pos = entry->current_pos; + + egg_secure_entry_reset_im_context (entry); + + if (entry->current_pos != entry->selection_bound && !extend_selection) { + /* + * If we have a current selection and aren't extending it, move to the + * start/or end of the selection as appropriate + */ + switch (step) { + case GTK_MOVEMENT_VISUAL_POSITIONS: + { + gint current_x = get_better_cursor_x (entry, entry->current_pos); + gint bound_x = get_better_cursor_x (entry, entry->selection_bound); + + if (count < 0) + new_pos = current_x < bound_x ? + entry->current_pos : entry->selection_bound; + else + new_pos = current_x > bound_x ? + entry->current_pos : entry->selection_bound; + break; + } + case GTK_MOVEMENT_LOGICAL_POSITIONS: + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + new_pos = count < 0 ? 0 : entry->text_length; + break; + case GTK_MOVEMENT_WORDS: + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + break; + } + } else { + switch (step) { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + new_pos = egg_secure_entry_move_logically (entry, new_pos, count); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + new_pos = egg_secure_entry_move_visually (entry, new_pos, count); + break; + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + new_pos = count < 0 ? 0 : entry->text_length; + break; + case GTK_MOVEMENT_WORDS: + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + break; + } + } + + if (extend_selection) + gtk_editable_select_region (GTK_EDITABLE (entry), entry->selection_bound, new_pos); + else + gtk_editable_set_position (GTK_EDITABLE (entry), new_pos); + + egg_secure_entry_pend_cursor_blink (entry); +} + +static void +egg_secure_entry_insert_at_cursor(EggSecureEntry *entry, const gchar *str) +{ + GtkEditable *editable = GTK_EDITABLE (entry); + gint pos = entry->current_pos; + + egg_secure_entry_reset_im_context (entry); + + gtk_editable_insert_text (editable, str, -1, &pos); + gtk_editable_set_position (editable, pos); +} + +static void +egg_secure_entry_delete_from_cursor (EggSecureEntry *entry, GtkDeleteType type, + gint count) +{ + GtkEditable *editable = GTK_EDITABLE (entry); + gint start_pos = entry->current_pos; + gint end_pos = entry->current_pos; + + egg_secure_entry_reset_im_context (entry); + + if (entry->selection_bound != entry->current_pos) { + gtk_editable_delete_selection (editable); + return; + } + + switch (type) { + case GTK_DELETE_CHARS: + end_pos = egg_secure_entry_move_logically (entry, entry->current_pos, count); + gtk_editable_delete_text (editable, MIN (start_pos, end_pos), MAX (start_pos, end_pos)); + break; + case GTK_DELETE_DISPLAY_LINE_ENDS: + case GTK_DELETE_PARAGRAPH_ENDS: + if (count < 0) + gtk_editable_delete_text (editable, 0, entry->current_pos); + else + gtk_editable_delete_text (editable, entry->current_pos, -1); + break; + case GTK_DELETE_DISPLAY_LINES: + case GTK_DELETE_PARAGRAPHS: + gtk_editable_delete_text (editable, 0, -1); + break; + default: + break; + } + + egg_secure_entry_pend_cursor_blink (entry); +} + +static void +egg_secure_entry_real_activate (EggSecureEntry *entry) +{ + GtkWindow *window; + GtkWidget *toplevel; + GtkWidget *widget; + + widget = GTK_WIDGET (entry); + + if (entry->activates_default) { + toplevel = gtk_widget_get_toplevel (widget); + if (GTK_IS_WINDOW (toplevel)) { + window = GTK_WINDOW(toplevel); + + if (window && widget != window->default_widget && + !(widget == window->focus_widget && + (!window->default_widget || !GTK_WIDGET_SENSITIVE(window->default_widget)))) + gtk_window_activate_default(window); + } + } +} + +static void +egg_secure_entry_keymap_direction_changed (GdkKeymap *keymap, EggSecureEntry *entry) +{ + egg_secure_entry_recompute (entry); +} + +/* IM Context Callbacks */ + +static void +egg_secure_entry_commit_cb (GtkIMContext *context, const gchar *str, + EggSecureEntry *entry) +{ + egg_secure_entry_enter_text (entry, str); +} + +static void +egg_secure_entry_preedit_changed_cb (GtkIMContext *context, EggSecureEntry *entry) +{ + gchar *preedit_string; + gint cursor_pos; + + gtk_im_context_get_preedit_string (entry->im_context, &preedit_string, NULL, &cursor_pos); + entry->preedit_length = strlen (preedit_string); + cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1)); + entry->preedit_cursor = cursor_pos; + g_free (preedit_string); + + egg_secure_entry_recompute (entry); +} + +static gboolean +egg_secure_entry_retrieve_surrounding_cb (GtkIMContext *context, EggSecureEntry *entry) +{ + gtk_im_context_set_surrounding (context, entry->text, entry->n_bytes, + g_utf8_offset_to_pointer (entry->text, entry->current_pos) - entry->text); + return TRUE; +} + +static gboolean +egg_secure_entry_delete_surrounding_cb (GtkIMContext *slave, gint offset, gint n_chars, + EggSecureEntry *entry) +{ + gtk_editable_delete_text (GTK_EDITABLE (entry), entry->current_pos + offset, + entry->current_pos + offset + n_chars); + return TRUE; +} + +/* Internal functions */ + +/* Used for im_commit_cb and inserting Unicode chars */ +static void +egg_secure_entry_enter_text (EggSecureEntry *entry, const gchar *str) +{ + GtkEditable *editable = GTK_EDITABLE (entry); + gint tmp_pos; + + if (gtk_editable_get_selection_bounds (editable, NULL, NULL)) + gtk_editable_delete_selection (editable); + else { + if (entry->overwrite_mode) + egg_secure_entry_delete_from_cursor (entry, GTK_DELETE_CHARS, 1); + } + + tmp_pos = entry->current_pos; + gtk_editable_insert_text (editable, str, strlen(str), &tmp_pos); + egg_secure_entry_set_position_internal (entry, tmp_pos, FALSE); +} + +/* + * All changes to entry->current_pos and entry->selection_bound + * should go through this function. + */ +static void +egg_secure_entry_set_positions (EggSecureEntry *entry, gint current_pos, + gint selection_bound) +{ + gboolean changed = FALSE; + + g_object_freeze_notify (G_OBJECT (entry)); + + if (current_pos != -1 && entry->current_pos != current_pos) { + entry->current_pos = current_pos; + changed = TRUE; + + g_object_notify (G_OBJECT (entry), "cursor_position"); + } + + if (selection_bound != -1 && entry->selection_bound != selection_bound) { + entry->selection_bound = selection_bound; + changed = TRUE; + + g_object_notify (G_OBJECT (entry), "selection_bound"); + } + + g_object_thaw_notify (G_OBJECT (entry)); + + if (changed) + egg_secure_entry_recompute (entry); +} + +static void +egg_secure_entry_reset_layout (EggSecureEntry *entry) +{ + if (entry->cached_layout) { + g_object_unref (entry->cached_layout); + entry->cached_layout = NULL; + } +} + +static void +update_im_cursor_location (EggSecureEntry *entry) +{ + GdkRectangle area; + gint strong_x; + gint strong_xoffset; + gint area_width, area_height; + + egg_secure_entry_get_cursor_locations (entry, &strong_x, NULL); + get_text_area_size (entry, NULL, NULL, &area_width, &area_height); + + strong_xoffset = strong_x - entry->scroll_offset; + if (strong_xoffset < 0) { + strong_xoffset = 0; + } else if (strong_xoffset > area_width) { + strong_xoffset = area_width; + } + area.x = strong_xoffset; + area.y = 0; + area.width = 0; + area.height = area_height; + + gtk_im_context_set_cursor_location (entry->im_context, &area); +} + +static gboolean +recompute_idle_func (gpointer data) +{ + EggSecureEntry *entry; + + GDK_THREADS_ENTER (); + + entry = EGG_SECURE_ENTRY (data); + + entry->recompute_idle = 0; + + if (gtk_widget_has_screen (GTK_WIDGET (entry))) { + egg_secure_entry_adjust_scroll (entry); + egg_secure_entry_queue_draw (entry); + + update_im_cursor_location (entry); + } + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +egg_secure_entry_recompute (EggSecureEntry *entry) +{ + egg_secure_entry_reset_layout (entry); + egg_secure_entry_check_cursor_blink (entry); + + if (!entry->recompute_idle) { + entry->recompute_idle = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 15, /* between resize and redraw */ + recompute_idle_func, entry, NULL); + } +} + +static gunichar +build_string (EggSecureEntry *entry, GString *str, gint extra) +{ + gint i, count, char_len; + gunichar invisible_char; + gchar buf[7]; + + if (entry->visibility) { + g_string_append_len (str, entry->text, entry->n_bytes); + return 0; + + } else { + + if (entry->invisible_char != 0) + invisible_char = entry->invisible_char; + else + invisible_char = ' '; /* just pick a char */ + + count = g_utf8_strlen (entry->text, entry->n_bytes) + extra; + + char_len = g_unichar_to_utf8 (entry->invisible_char, buf); + for (i = 0; i < count; i++) + g_string_append_len(str, buf, char_len); + + return invisible_char; + } +} + +static PangoLayout * +egg_secure_entry_create_layout (EggSecureEntry * entry, gboolean include_preedit) +{ + GtkWidget *widget = GTK_WIDGET (entry); + PangoLayout *layout = gtk_widget_create_pango_layout (widget, NULL); + PangoAttrList *tmp_attrs = pango_attr_list_new (); + + gchar *preedit_string = NULL; + gint preedit_length = 0; + PangoAttrList *preedit_attrs = NULL; + + pango_layout_set_single_paragraph_mode (layout, TRUE); + + if (include_preedit) { + gtk_im_context_get_preedit_string(entry->im_context, &preedit_string, + &preedit_attrs, NULL); + preedit_length = entry->preedit_length; + } + + if (preedit_length) { + GString *tmp_string = g_string_new(NULL); + + gint cursor_index = g_utf8_offset_to_pointer (entry->text, entry->current_pos) - + entry->text; + + gint preedit_len_chars; + gunichar invisible_char; + + preedit_len_chars = g_utf8_strlen (preedit_string, -1); + invisible_char = build_string (entry, tmp_string, preedit_len_chars); + + /* + * Fix cursor index to point to invisible char corresponding + * to the preedit, fix preedit_length to be the length of + * the invisible chars representing the preedit + */ + cursor_index = + g_utf8_offset_to_pointer (tmp_string->str, entry->current_pos) - tmp_string->str; + preedit_length = preedit_len_chars * g_unichar_to_utf8 (invisible_char, NULL); + + pango_layout_set_text (layout, tmp_string->str, tmp_string->len); + + pango_attr_list_splice (tmp_attrs, preedit_attrs, cursor_index, preedit_length); + + g_string_free (tmp_string, TRUE); + } else { + PangoDirection pango_dir; + + pango_dir = pango_find_base_dir (entry->text, entry->n_bytes); + if (pango_dir == PANGO_DIRECTION_NEUTRAL) { + if (GTK_WIDGET_HAS_FOCUS (widget)) { + GdkDisplay *display = gtk_widget_get_display (widget); + GdkKeymap *keymap = gdk_keymap_get_for_display (display); + pango_dir = gdk_keymap_get_direction (keymap); + } else { + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + pango_dir = PANGO_DIRECTION_LTR; + else + pango_dir = PANGO_DIRECTION_RTL; + } + } + + pango_context_set_base_dir (gtk_widget_get_pango_context(widget), pango_dir); + + pango_layout_set_alignment (layout, pango_dir); + + entry->resolved_dir = pango_dir; + + { + GString *str = g_string_new (NULL); + build_string (entry, str, 0); + pango_layout_set_text (layout, str->str, str->len); + g_string_free (str, TRUE); + } + } + + pango_layout_set_attributes (layout, tmp_attrs); + + if (preedit_string) + g_free (preedit_string); + if (preedit_attrs) + pango_attr_list_unref (preedit_attrs); + + pango_attr_list_unref (tmp_attrs); + + return layout; +} + +static PangoLayout * +egg_secure_entry_ensure_layout (EggSecureEntry *entry, gboolean include_preedit) +{ + if (entry->preedit_length > 0 && !include_preedit != !entry->cache_includes_preedit) + egg_secure_entry_reset_layout (entry); + + if (!entry->cached_layout) { + entry->cached_layout = egg_secure_entry_create_layout (entry, include_preedit); + entry->cache_includes_preedit = include_preedit; + } + + return entry->cached_layout; +} + +static void +get_layout_position (EggSecureEntry *entry, gint *x, gint *y) +{ + PangoLayout *layout; + PangoRectangle logical_rect; + gint area_width, area_height; + gint y_pos; + PangoLayoutLine *line; + + layout = egg_secure_entry_ensure_layout (entry, TRUE); + + get_text_area_size (entry, NULL, NULL, &area_width, &area_height); + + area_height = PANGO_SCALE * (area_height - 2 * INNER_BORDER); + + line = pango_layout_get_lines (layout)->data; + pango_layout_line_get_extents (line, NULL, &logical_rect); + + /* Align primarily for locale's ascent/descent */ + y_pos = ((area_height - entry->ascent - entry->descent) / 2 + + entry->ascent + logical_rect.y); + + /* Now see if we need to adjust to fit in actual drawn string */ + if (logical_rect.height > area_height) + y_pos = (area_height - logical_rect.height) / 2; + else if (y_pos < 0) + y_pos = 0; + else if (y_pos + logical_rect.height > area_height) + y_pos = area_height - logical_rect.height; + + y_pos = INNER_BORDER + y_pos / PANGO_SCALE; + + if (x) + *x = INNER_BORDER - entry->scroll_offset; + + if (y) + *y = y_pos; +} + +static void +egg_secure_entry_draw_text(EggSecureEntry *entry) +{ + GtkWidget *widget; + PangoLayoutLine *line; + + if (entry->invisible_char == 0) + return; + + if (GTK_WIDGET_DRAWABLE (entry)) { + PangoLayout *layout = egg_secure_entry_ensure_layout (entry, TRUE); + gint x, y; + gint start_pos, end_pos; + + widget = GTK_WIDGET(entry); + + get_layout_position(entry, &x, &y); + + gdk_draw_layout(entry->text_area, widget->style->text_gc[widget->state], x, y, layout); + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos)) { + gint *ranges; + gint n_ranges, i; + PangoRectangle logical_rect; + const gchar *text = pango_layout_get_text (layout); + gint start_index = g_utf8_offset_to_pointer (text, start_pos) - text; + gint end_index = g_utf8_offset_to_pointer (text, end_pos) - text; + GdkRegion *clip_region = gdk_region_new (); + GdkGC *text_gc; + GdkGC *selection_gc; + + line = pango_layout_get_lines(layout)->data; + + pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges); + pango_layout_get_extents(layout, NULL, &logical_rect); + + if (GTK_WIDGET_HAS_FOCUS(entry)) { + selection_gc = widget->style->base_gc[GTK_STATE_SELECTED]; + text_gc = widget->style->text_gc[GTK_STATE_SELECTED]; + } else { + selection_gc = widget->style->base_gc[GTK_STATE_ACTIVE]; + text_gc = widget->style->text_gc[GTK_STATE_ACTIVE]; + } + + for (i = 0; i < n_ranges; i++) { + GdkRectangle rect; + + rect.x = INNER_BORDER - entry->scroll_offset + + ranges[2 * i] / PANGO_SCALE; + rect.y = y; + rect.width = (ranges[2 * i + 1] - ranges[2 * i]) / PANGO_SCALE; + rect.height = logical_rect.height / PANGO_SCALE; + + gdk_draw_rectangle (entry->text_area, selection_gc, TRUE, rect.x, + rect.y, rect.width, rect.height); + + gdk_region_union_with_rect(clip_region, &rect); + } + + gdk_gc_set_clip_region(text_gc, clip_region); + gdk_draw_layout(entry->text_area, text_gc, x, y, layout); + gdk_gc_set_clip_region(text_gc, NULL); + + gdk_region_destroy(clip_region); + g_free(ranges); + } + } +} + +static void +draw_insertion_cursor (EggSecureEntry *entry, GdkRectangle *cursor_location, + gboolean is_primary, PangoDirection direction, gboolean draw_arrow) +{ + GtkWidget *widget = GTK_WIDGET (entry); + GtkTextDirection text_dir; + + if (direction == PANGO_DIRECTION_LTR) + text_dir = GTK_TEXT_DIR_LTR; + else + text_dir = GTK_TEXT_DIR_RTL; + + gtk_draw_insertion_cursor (widget, entry->text_area, NULL, cursor_location, + is_primary, text_dir, draw_arrow); +} + +static void +egg_secure_entry_draw_cursor (EggSecureEntry * entry) +{ + GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET(entry))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + + if (GTK_WIDGET_DRAWABLE (entry)) { + GtkWidget *widget = GTK_WIDGET(entry); + GdkRectangle cursor_location; + gboolean split_cursor; + + gint xoffset = INNER_BORDER - entry->scroll_offset; + gint strong_x, weak_x; + gint text_area_height; + PangoDirection dir1 = PANGO_DIRECTION_NEUTRAL; + PangoDirection dir2 = PANGO_DIRECTION_NEUTRAL; + gint x1 = 0; + gint x2 = 0; + + gdk_drawable_get_size (entry->text_area, NULL, &text_area_height); + + egg_secure_entry_get_cursor_locations (entry, &strong_x, &weak_x); + + g_object_get (gtk_widget_get_settings (widget), "gtk-split-cursor", &split_cursor, NULL); + + dir1 = entry->resolved_dir; + + if (split_cursor) { + x1 = strong_x; + + if (weak_x != strong_x) { + dir2 = (entry->resolved_dir == PANGO_DIRECTION_LTR) ? + PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR; + x2 = weak_x; + } + } else { + if (keymap_direction == entry->resolved_dir) + x1 = strong_x; + else + x1 = weak_x; + } + + cursor_location.x = xoffset + x1; + cursor_location.y = INNER_BORDER; + cursor_location.width = 0; + cursor_location.height = text_area_height - 2 * INNER_BORDER; + + draw_insertion_cursor (entry, &cursor_location, TRUE, dir1, + dir2 != PANGO_DIRECTION_NEUTRAL); + + if (dir2 != PANGO_DIRECTION_NEUTRAL) { + cursor_location.x = xoffset + x2; + draw_insertion_cursor(entry, &cursor_location, FALSE, dir2, TRUE); + } + } +} + +static void +egg_secure_entry_queue_draw (EggSecureEntry *entry) +{ + if (GTK_WIDGET_REALIZED (entry)) + gdk_window_invalidate_rect (entry->text_area, NULL, FALSE); +} + +static void +egg_secure_entry_reset_im_context (EggSecureEntry *entry) +{ + if (entry->need_im_reset) { + entry->need_im_reset = 0; + gtk_im_context_reset (entry->im_context); + } +} + +static gint +egg_secure_entry_find_position (EggSecureEntry *entry, gint x) +{ + PangoLayout *layout; + PangoLayoutLine *line; + gint _index; + gint pos; + gboolean trailing; + const gchar *text; + gint cursor_index; + + layout = egg_secure_entry_ensure_layout (entry, TRUE); + text = pango_layout_get_text (layout); + cursor_index = g_utf8_offset_to_pointer (text, entry->current_pos) - text; + + line = pango_layout_get_lines (layout)->data; + pango_layout_line_x_to_index (line, x * PANGO_SCALE, &_index, &trailing); + + if (_index >= cursor_index && entry->preedit_length) { + if (_index >= cursor_index + entry->preedit_length) + _index -= entry->preedit_length; + else { + _index = cursor_index; + trailing = 0; + } + } + + pos = g_utf8_pointer_to_offset (text, text + _index); + pos += trailing; + + return pos; +} + +static void +egg_secure_entry_get_cursor_locations (EggSecureEntry *entry, + gint *strong_x, gint *weak_x) +{ + if (!entry->invisible_char) { + if (strong_x) + *strong_x = 0; + if (weak_x) + *weak_x = 0; + } else { + PangoLayout *layout = egg_secure_entry_ensure_layout (entry, TRUE); + const gchar *text = pango_layout_get_text (layout); + PangoRectangle strong_pos, weak_pos; + gint _index; + + _index = g_utf8_offset_to_pointer (text, entry->current_pos + + entry->preedit_cursor) - text; + + pango_layout_get_cursor_pos (layout, _index, &strong_pos, &weak_pos); + + if (strong_x) + *strong_x = strong_pos.x / PANGO_SCALE; + + if (weak_x) + *weak_x = weak_pos.x / PANGO_SCALE; + } +} + +static void +egg_secure_entry_adjust_scroll (EggSecureEntry *entry) +{ + gint min_offset, max_offset; + gint text_area_width, text_width; + gint strong_x, weak_x; + gint strong_xoffset, weak_xoffset; + PangoLayout *layout; + PangoLayoutLine *line; + PangoRectangle logical_rect; + + if (!GTK_WIDGET_REALIZED(entry)) + return; + + gdk_drawable_get_size (entry->text_area, &text_area_width, NULL); + text_area_width -= 2 * INNER_BORDER; + + layout = egg_secure_entry_ensure_layout (entry, TRUE); + line = pango_layout_get_lines (layout)->data; + + pango_layout_line_get_extents (line, NULL, &logical_rect); + + /* Display as much text as we can */ + + text_width = PANGO_PIXELS (logical_rect.width); + + if (text_width > text_area_width) { + min_offset = 0; + max_offset = text_width - text_area_width; + } else { + min_offset = 0; + max_offset = min_offset; + } + + entry->scroll_offset = CLAMP (entry->scroll_offset, min_offset, max_offset); + + /* + * And make sure cursors are on screen. Note that the cursor is + * actually drawn one pixel into the INNER_BORDER space on + * the right, when the scroll is at the utmost right. This + * looks better to to me than confining the cursor inside the + * border entirely, though it means that the cursor gets one + * pixel closer to the the edge of the widget on the right than + * on the left. This might need changing if one changed + * INNER_BORDER from 2 to 1, as one would do on a + * small-screen-real-estate display. + * + * We always make sure that the strong cursor is on screen, and + * put the weak cursor on screen if possible. + */ + + egg_secure_entry_get_cursor_locations (entry, &strong_x, &weak_x); + + strong_xoffset = strong_x - entry->scroll_offset; + + if (strong_xoffset < 0) { + entry->scroll_offset += strong_xoffset; + strong_xoffset = 0; + } else if (strong_xoffset > text_area_width) { + entry->scroll_offset += strong_xoffset - text_area_width; + strong_xoffset = text_area_width; + } + + weak_xoffset = weak_x - entry->scroll_offset; + + if (weak_xoffset < 0 && strong_xoffset - weak_xoffset <= text_area_width) { + entry->scroll_offset += weak_xoffset; + } else if (weak_xoffset > text_area_width && + strong_xoffset - (weak_xoffset - text_area_width) >= 0) { + entry->scroll_offset += weak_xoffset - text_area_width; + } + + g_object_notify (G_OBJECT (entry), "scroll_offset"); +} + +static gint +egg_secure_entry_move_visually (EggSecureEntry * entry, + gint start, gint count) +{ + gint _index; + PangoLayout *layout = egg_secure_entry_ensure_layout (entry, FALSE); + const gchar *text; + + text = pango_layout_get_text (layout); + + _index = g_utf8_offset_to_pointer (text, start) - text; + + while (count != 0) { + int new_index, new_trailing; + gboolean split_cursor; + gboolean strong; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)), + "gtk-split-cursor", &split_cursor, NULL); + + if (split_cursor) + strong = TRUE; + else { + GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (entry))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + + strong = keymap_direction == entry->resolved_dir; + } + + if (count > 0) { + pango_layout_move_cursor_visually (layout, strong, _index, 0, 1, + &new_index, &new_trailing); + count--; + } else { + pango_layout_move_cursor_visually (layout, strong, _index, 0, -1, + &new_index, &new_trailing); + count++; + } + + if (new_index < 0 || new_index == G_MAXINT) + break; + + _index = new_index; + + while (new_trailing--) + _index = g_utf8_next_char (text + new_index) - text; + } + + return g_utf8_pointer_to_offset (text, text + _index); +} + +static gint +egg_secure_entry_move_logically (EggSecureEntry *entry, + gint start, gint count) +{ + gint new_pos = start; + + /* Prevent any leak of information */ + new_pos = CLAMP (start + count, 0, entry->text_length); + return new_pos; +} + +/* Public API */ + +GtkWidget * +egg_secure_entry_new (void) +{ + return g_object_new (EGG_TYPE_SECURE_ENTRY, NULL); +} + +void +egg_secure_entry_set_text (EggSecureEntry *entry, const gchar *text) +{ + gint tmp_pos; + + g_return_if_fail (EGG_IS_SECURE_ENTRY(entry)); + g_return_if_fail (text != NULL); + + /* + * Actually setting the text will affect the cursor and selection; + * if the contents don't actually change, this will look odd to the user. + */ + if (strcmp (entry->text, text) == 0) + return; + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1); + + tmp_pos = 0; + gtk_editable_insert_text (GTK_EDITABLE (entry), text, strlen (text), &tmp_pos); +} + +void +egg_secure_entry_reset_changed (EggSecureEntry *entry) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + entry->changed = FALSE; +} + +gboolean +egg_secure_entry_get_changed (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), FALSE); + return entry->changed; +} + +void +egg_secure_entry_set_visibility (EggSecureEntry *entry, gboolean setting) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + + if (setting == entry->visibility) + return; + + entry->visibility = setting; + g_object_notify (G_OBJECT (entry), "visibility"); + egg_secure_entry_recompute (entry); +} + +gboolean +egg_secure_entry_get_visibility (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), FALSE); + return entry->visibility; +} + + +/** + * egg_secure_entry_set_invisible_char: + * @entry: a #EggSecureEntry + * @ch: a Unicode character + * + * Sets the character to use in place of the actual text when + * egg_secure_entry_set_visibility() has been called to set text + * to %FALSE. i.e. this is the character used in "password mode" to + * show the user how many characters have been typed. The default + * invisible char is an asterisk ('*'). If you set the invisible char + * to 0, then the user will get no feedback at all; there will be + * no text on the screen as they type. + * + **/ +void +egg_secure_entry_set_invisible_char (EggSecureEntry *entry, gunichar ch) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + + if (ch == entry->invisible_char) + return; + + entry->invisible_char = ch; + g_object_notify (G_OBJECT (entry), "invisible_char"); + egg_secure_entry_recompute (entry); +} + +/** + * egg_secure_entry_get_invisible_char: + * @entry: a #EggSecureEntry + * + * Retrieves the character displayed in place of the real characters + * for entries with visisbility set to false. See egg_secure_entry_set_invisible_char(). + * + * Return value: the current invisible char, or 0, if the entry does not + * show invisible text at all. + **/ +gunichar +egg_secure_entry_get_invisible_char (EggSecureEntry * entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), 0); + + return entry->invisible_char; +} + +/** + * egg_secure_entry_get_text: + * @entry: a #EggSecureEntry + * + * Retrieves the contents of the entry widget. + * See also gtk_editable_get_chars(). + * + * Return value: a pointer to the contents of the widget as a + * string. This string points to internally allocated + * storage in the widget and must not be freed, modified or + * stored. + **/ +G_CONST_RETURN gchar* +egg_secure_entry_get_text (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), NULL); + + return entry->text; +} + +/** + * egg_secure_entry_set_max_length: + * @entry: a #EggSecureEntry. + * @max: the maximum length of the entry, or 0 for no maximum. + * (other than the maximum length of entries.) The value passed in will + * be clamped to the range 0-65536. + * + * Sets the maximum allowed length of the contents of the widget. If + * the current contents are longer than the given length, then they + * will be truncated to fit. + **/ +void +egg_secure_entry_set_max_length(EggSecureEntry *entry, gint max) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + + max = CLAMP (max, 0, MAX_SIZE); + + if (max > 0 && entry->text_length > max) + gtk_editable_delete_text (GTK_EDITABLE (entry), max, -1); + + entry->text_max_length = max; + g_object_notify (G_OBJECT (entry), "max_length"); +} + +/** + * egg_secure_entry_get_max_length: + * @entry: a #EggSecureEntry + * + * Retrieves the maximum allowed length of the text in + * @entry. See egg_secure_entry_set_max_length(). + * + * Return value: the maximum allowed number of characters + * in #EggSecureEntry, or 0 if there is no maximum. + **/ +gint +egg_secure_entry_get_max_length (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), 0); + return entry->text_max_length; +} + +/** + * egg_secure_entry_set_activates_default: + * @entry: a #EggSecureEntry + * @setting: %TRUE to activate window's default widget on Enter keypress + * + * If @setting is %TRUE, pressing Enter in the @entry will activate the default + * widget for the window containing the entry. This usually means that + * the dialog box containing the entry will be closed, since the default + * widget is usually one of the dialog buttons. + * + * (For experts: if @setting is %TRUE, the entry calls + * gtk_window_activate_default() on the window containing the entry, in + * the default handler for the "activate" signal.) + * + **/ +void +egg_secure_entry_set_activates_default (EggSecureEntry *entry, + gboolean setting) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + setting = setting != FALSE; + + if (setting != entry->activates_default) { + entry->activates_default = setting; + g_object_notify (G_OBJECT (entry), "activates_default"); + } +} + +/** + * egg_secure_entry_get_activates_default: + * @entry: a #EggSecureEntry + * + * Retrieves the value set by egg_secure_entry_set_activates_default(). + * + * Return value: %TRUE if the entry will activate the default widget + **/ +gboolean +egg_secure_entry_get_activates_default (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), FALSE); + return entry->activates_default; +} + +/** + * egg_secure_entry_set_width_chars: + * @entry: a #EggSecureEntry + * @n_chars: width in chars + * + * Changes the size request of the entry to be about the right size + * for @n_chars characters. Note that it changes the size + * <emphasis>request</emphasis>, the size can still be affected by + * how you pack the widget into containers. If @n_chars is -1, the + * size reverts to the default entry size. + * + **/ +void +egg_secure_entry_set_width_chars (EggSecureEntry *entry, gint n_chars) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + + if (entry->width_chars != n_chars) { + entry->width_chars = n_chars; + g_object_notify (G_OBJECT (entry), "width_chars"); + gtk_widget_queue_resize (GTK_WIDGET (entry)); + } +} + +/** + * egg_secure_entry_get_width_chars: + * @entry: a #EggSecureEntry + * + * Gets the value set by egg_secure_entry_set_width_chars(). + * + * Return value: number of chars to request space for, or negative if unset + **/ +gint +egg_secure_entry_get_width_chars (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), 0); + return entry->width_chars; +} + +/** + * egg_secure_entry_set_has_frame: + * @entry: a #EggSecureEntry + * @setting: new value + * + * Sets whether the entry has a beveled frame around it. + **/ +void +egg_secure_entry_set_has_frame (EggSecureEntry *entry, gboolean setting) +{ + g_return_if_fail (EGG_IS_SECURE_ENTRY (entry)); + + setting = (setting != FALSE); + + if (entry->has_frame == setting) + return; + + gtk_widget_queue_resize (GTK_WIDGET (entry)); + entry->has_frame = setting; + g_object_notify (G_OBJECT (entry), "has_frame"); +} + +/** + * egg_secure_entry_get_has_frame: + * @entry: a #EggSecureEntry + * + * Gets the value set by egg_secure_entry_set_has_frame(). + * + * Return value: whether the entry has a beveled frame + **/ +gboolean +egg_secure_entry_get_has_frame (EggSecureEntry *entry) +{ + g_return_val_if_fail (EGG_IS_SECURE_ENTRY (entry), FALSE); + return entry->has_frame; +} + +static gboolean +egg_secure_entry_mnemonic_activate (GtkWidget *widget, gboolean group_cycling) +{ + gtk_widget_grab_focus (widget); + return TRUE; +} + +/* We display the cursor when + * + * - the selection is empty, AND + * - the widget has focus + */ + +#define CURSOR_ON_MULTIPLIER 0.66 +#define CURSOR_OFF_MULTIPLIER 0.34 +#define CURSOR_PEND_MULTIPLIER 1.0 + +static gboolean +cursor_blinks (EggSecureEntry *entry) +{ + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (entry)); + gboolean blink; + + if (GTK_WIDGET_HAS_FOCUS (entry) && + entry->selection_bound == entry->current_pos) { + g_object_get (settings, "gtk-cursor-blink", &blink, NULL); + return blink; + } else + return FALSE; +} + +static gint +get_cursor_time (EggSecureEntry *entry) +{ + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (entry)); + gint time; + + g_object_get (settings, "gtk-cursor-blink-time", &time, NULL); + + return time; +} + +static void +show_cursor (EggSecureEntry *entry) +{ + if (!entry->cursor_visible) { + entry->cursor_visible = TRUE; + + if (GTK_WIDGET_HAS_FOCUS (entry) && + entry->selection_bound == entry->current_pos) + gtk_widget_queue_draw (GTK_WIDGET (entry)); + } +} + +static void +hide_cursor(EggSecureEntry * entry) +{ + if (entry->cursor_visible) { + entry->cursor_visible = FALSE; + + if (GTK_WIDGET_HAS_FOCUS (entry) && + entry->selection_bound == entry->current_pos) + gtk_widget_queue_draw (GTK_WIDGET (entry)); + } +} + +/* + * Blink! + */ +static gint +blink_cb (gpointer data) +{ + EggSecureEntry *entry; + + GDK_THREADS_ENTER (); + + entry = EGG_SECURE_ENTRY (data); + + if (!GTK_WIDGET_HAS_FOCUS (entry)) { + g_warning ("EggSecureEntry - did not receive focus-out-event. If you\n" + "connect a handler to this signal, it must return\n" + "FALSE so the entry gets the event as well"); + } + + g_assert (GTK_WIDGET_HAS_FOCUS (entry)); + g_assert (entry->selection_bound == entry->current_pos); + + if (entry->cursor_visible) { + hide_cursor(entry); + entry->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_OFF_MULTIPLIER, + blink_cb, entry); + } else { + show_cursor(entry); + entry->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER, + blink_cb, entry); + } + + GDK_THREADS_LEAVE (); + + /* Remove ourselves */ + return FALSE; +} + +static void +egg_secure_entry_check_cursor_blink (EggSecureEntry *entry) +{ + if (cursor_blinks (entry)) { + if (!entry->blink_timeout) { + entry->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER, + blink_cb, entry); + show_cursor (entry); + } + } else { + if (entry->blink_timeout) { + g_source_remove (entry->blink_timeout); + entry->blink_timeout = 0; + } + + entry->cursor_visible = TRUE; + } + +} + +static void +egg_secure_entry_pend_cursor_blink (EggSecureEntry *entry) +{ + if (cursor_blinks (entry)) { + if (entry->blink_timeout != 0) + g_source_remove(entry->blink_timeout); + + entry->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_PEND_MULTIPLIER, + blink_cb, entry); + show_cursor (entry); + } +} + +static inline gboolean +keyval_is_cursor_move (guint keyval) +{ + if (keyval == GDK_Up || keyval == GDK_KP_Up) + return TRUE; + + if (keyval == GDK_Down || keyval == GDK_KP_Down) + return TRUE; + + if (keyval == GDK_Page_Up) + return TRUE; + + if (keyval == GDK_Page_Down) + return TRUE; + + return FALSE; +} + +/* stolen from gtkmarshalers.c */ + +#define g_marshal_value_peek_enum(v) (v)->data[0].v_int +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int + +/* VOID:ENUM,INT,BOOLEAN (gtkmarshalers.list:64) */ +static void +_gtk_marshal_VOID__ENUM_INT_BOOLEAN(GClosure * closure, GValue * return_value, + guint n_param_values, const GValue * param_values, + gpointer invocation_hint, gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__ENUM_INT_BOOLEAN) (gpointer data1, + gint arg_1, + gint arg_2, + gboolean arg_3, + gpointer data2); + register GMarshalFunc_VOID__ENUM_INT_BOOLEAN callback; + register GCClosure *cc = (GCClosure *) closure; + register gpointer data1, data2; + + g_return_if_fail(n_param_values == 4); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + data2 = g_value_peek_pointer(param_values + 0); + } else { + data1 = g_value_peek_pointer(param_values + 0); + data2 = closure->data; + } + callback = + (GMarshalFunc_VOID__ENUM_INT_BOOLEAN) (marshal_data ? marshal_data + : cc->callback); + + callback(data1, + g_marshal_value_peek_enum(param_values + 1), + g_marshal_value_peek_int(param_values + 2), + g_marshal_value_peek_boolean(param_values + 3), data2); +} + +static void +_gtk_marshal_VOID__ENUM_INT(GClosure * closure, GValue * return_value, + guint n_param_values, const GValue * param_values, + gpointer invocation_hint, gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__ENUM_INT) (gpointer data1, + gint arg_1, + gint arg_2, + gpointer data2); + register GMarshalFunc_VOID__ENUM_INT callback; + register GCClosure *cc = (GCClosure *) closure; + register gpointer data1, data2; + + g_return_if_fail(n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + data2 = g_value_peek_pointer(param_values + 0); + } else { + data1 = g_value_peek_pointer(param_values + 0); + data2 = closure->data; + } + callback = + (GMarshalFunc_VOID__ENUM_INT) (marshal_data ? marshal_data : cc-> + callback); + + callback(data1, + g_marshal_value_peek_enum(param_values + 1), + g_marshal_value_peek_int(param_values + 2), data2); +} diff --git a/egg/egg-secure-entry.h b/egg/egg-secure-entry.h new file mode 100644 index 0000000..35bf8d4 --- /dev/null +++ b/egg/egg-secure-entry.h @@ -0,0 +1,187 @@ +/* + * GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 of the License, or (at your option) any later version. + * + * 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. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * Heavily stripped down for use in pinentry-gtk-2 by Albrecht Dreß + * <albrecht.dress@arcor.de> Feb. 2004. + * + * (C) by Albrecht Dreß 2004 unter the terms of the GNU Lesser General + * Public License. + * + * The entry is now always invisible, uses secure memory methods to + * allocate the text memory, and all potentially dangerous methods + * (copy & paste, popup, etc.) have been removed. + */ + +/* + * Modified for inclusion into gnome-keyring by Stef Walter + */ + +#ifndef __EGG_SECURE_ENTRY_H__ +#define __EGG_SECURE_ENTRY_H__ + + +#include <gtk/gtk.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define EGG_TYPE_SECURE_ENTRY (egg_secure_entry_get_type ()) +#define EGG_SECURE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SECURE_ENTRY, EggSecureEntry)) +#define EGG_SECURE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SECURE_ENTRY, EggSecureEntryClass)) +#define EGG_IS_SECURE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SECURE_ENTRY)) +#define EGG_IS_SECURE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SECURE_ENTRY)) +#define EGG_SECURE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SECURE_ENTRY, EggSecureEntryClass)) + +typedef struct _EggSecureEntry EggSecureEntry; +typedef struct _EggSecureEntryClass EggSecureEntryClass; + +struct _EggSecureEntry { + GtkWidget widget; + + gchar *text; + + guint overwrite_mode : 1; + + /* length in use, in chars */ + guint16 text_length; + guint16 text_max_length; + + /*< private > */ + GdkWindow *text_area; + GtkIMContext *im_context; + + gint current_pos; + gint selection_bound; + + PangoLayout *cached_layout; + guint cache_includes_preedit : 1; + + guint need_im_reset : 1; + + guint has_frame : 1; + + guint activates_default : 1; + + guint cursor_visible : 1; + + /* Flag so we don't select all when clicking in entry to focus in */ + guint in_click : 1; + + /* Only used by GtkCellRendererText */ + guint is_cell_renderer : 1; + guint editing_canceled : 1; + + guint mouse_cursor_obscured : 1; + + /* PangoDirection */ + guint resolved_dir : 4; + + guint button; + guint blink_timeout; + guint recompute_idle; + gint scroll_offset; + gint ascent; /* font ascent, in pango units */ + gint descent; /* font descent, in pango units */ + + guint16 text_size; /* allocated size, in bytes */ + guint16 n_bytes; /* length in use, in bytes */ + + guint16 preedit_length; /* length of preedit string, in bytes */ + guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */ + + gunichar invisible_char; + gint width_chars; + + gboolean visibility; + + /* Keeps track of whether changed between resets */ + gboolean changed; +}; + +struct _EggSecureEntryClass { + GtkWidgetClass parent_class; + + /* Action signals */ + void (*activate) (EggSecureEntry *entry); + void (*move_cursor) (EggSecureEntry *entry, GtkMovementStep step, + gint count, gboolean extend_selection); + void (*insert_at_cursor) (EggSecureEntry *entry, const gchar *str); + void (*delete_from_cursor) (EggSecureEntry *entry, GtkDeleteType type, gint count); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType egg_secure_entry_get_type (void) G_GNUC_CONST; + +GtkWidget* egg_secure_entry_new (void); + +void egg_secure_entry_reset_changed (EggSecureEntry *entry); + +gboolean egg_secure_entry_get_changed (EggSecureEntry *entry); + +void egg_secure_entry_set_visibility (EggSecureEntry *entry, gboolean setting); + +gboolean egg_secure_entry_get_visibility (EggSecureEntry *entry); + +void egg_secure_entry_set_invisible_char (EggSecureEntry *entry, gunichar ch); + +gunichar egg_secure_entry_get_invisible_char (EggSecureEntry *entry); + +void egg_secure_entry_set_has_frame (EggSecureEntry *entry, gboolean setting); + +gboolean egg_secure_entry_get_has_frame (EggSecureEntry *entry); + +/* text is truncated if needed */ +void egg_secure_entry_set_max_length (EggSecureEntry *entry, gint max); + +gint egg_secure_entry_get_max_length (EggSecureEntry *entry); + +void egg_secure_entry_set_activates_default (EggSecureEntry *entry, gboolean setting); + +gboolean egg_secure_entry_get_activates_default (EggSecureEntry *entry); + +void egg_secure_entry_set_width_chars (EggSecureEntry *entry, gint n_chars); + +gint egg_secure_entry_get_width_chars (EggSecureEntry *entry); + +/* Somewhat more convenient than the GtkEditable generic functions */ +void egg_secure_entry_set_text (EggSecureEntry *entry, const gchar *text); + +/* returns a reference to the text */ +const gchar* egg_secure_entry_get_text (EggSecureEntry *entry); + +#ifdef __cplusplus +} +#endif + +#endif /*__EGG_SECURE_ENTRY_H__ */ diff --git a/gcr/Makefile.am b/gcr/Makefile.am index b1cef7d..0edd1de 100644 --- a/gcr/Makefile.am +++ b/gcr/Makefile.am @@ -1,12 +1,24 @@ -incdir = $(includedir)/gcr +# ------------------------------------------------------------------ +# UI BUILDER +# -inc_HEADERS = \ - gcr-parser.h \ - gcr-types.h +uidir = $(datadir)/gcr/ui/ + +GLADE_FILES = \ + gcr-import-dialog.glade + +.glade.ui: + gtk-builder-convert --skip-windows $< $@ + +ui_DATA = $(GLADE_FILES:.glade=.ui) + +# ------------------------------------------------------------------ +# LIBRARY INCLUDES = \ -I$(top_builddir) \ -I$(top_srcdir) \ + $(GTK_CFLAGS) \ $(GOBJECT_CFLAGS) \ $(GLIB_CFLAGS) @@ -16,17 +28,26 @@ BUILT_SOURCES = \ lib_LTLIBRARIES = libgcr.la libgcr_la_SOURCES = \ - gcr-internal.c gcr-internal.h \ + gcr-import-dialog.c gcr-import-dialog.h \ + gcr-importer.c gcr-importer.h \ + gcr-internal.h \ + gcr-library.c \ gcr-parser.c gcr-parser.h \ gcr-types.h \ $(BUILT_SOURCES) +libgcr_la_CFLAGS = \ + -DPKCS11_MODULE_PATH=\""$(libdir)/gnome-keyring/gnome-keyring-pkcs11.so"\" \ + -DGCR_API_SUBJECT_TO_CHANGE \ + -DUIDIR=\""$(uidir)"\" + libgcr_la_LDFLAGS = \ -version-info $(GCR_LT_RELEASE) \ -no-undefined -export-symbols-regex 'gcr_*' libgcr_la_LIBADD = \ $(top_builddir)/egg/libegg.la \ + $(top_builddir)/egg/libegg-secure-entry.la \ $(top_builddir)/gp11/libgp11.la \ $(GOBJECT_LIBS) \ $(GLIB_LIBS) @@ -41,16 +62,24 @@ gcr-marshal.c: gcr-marshal.list $(GLIB_GENMARSHAL) pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = gcr-$(GCR_MAJOR).pc +gcr-$(GCR_MAJOR).pc: gcr.pc + cp gcr.pc gcr-$(GCR_MAJOR).pc + +# ---------------------------------------------------------------- + EXTRA_DIST = \ gcr.pc.in \ gcr-marshal.list \ - gcr-import-dialog.glade + gcr-import-dialog.glade \ + $(GLADE_FILES) -DISTCLEANFILES = \ - gcr-$(GCR_MAJOR).pc +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(ui_DATA) \ + $(pkgconfig_DATA) -gcr-$(GCR_MAJOR).pc: gcr.pc - cp gcr.pc gcr-$(GCR_MAJOR).pc +DISTCLEANFILES = \ + $(pkgconfig_DATA) if WITH_TESTS TESTS_DIR = tests @@ -60,4 +89,3 @@ endif SUBDIRS = . \ $(TESTS_DIR) - diff --git a/gcr/gcr-import-dialog.c b/gcr/gcr-import-dialog.c new file mode 100644 index 0000000..b56b883 --- /dev/null +++ b/gcr/gcr-import-dialog.c @@ -0,0 +1,452 @@ +/* + * 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 "gcr-import-dialog.h" +#include "gcr-internal.h" + +#include "egg/egg-secure-entry.h" + +enum { + PROP_0, + PROP_SELECTED_SLOT, + PROP_PASSWORD, + PROP_PRIMARY_TEXT, + PROP_SECONDARY_TEXT +}; + +enum { + COLUMN_SLOT, + COLUMN_ICON, + COLUMN_LABEL, + N_COLUMNS +}; + +struct _GcrImportDialogPrivate { + GtkBuilder *builder; + EggSecureEntry *entry; + GtkComboBox *combo; + GtkListStore *slots; +}; + +G_DEFINE_TYPE (GcrImportDialog, _gcr_import_dialog, GTK_TYPE_DIALOG); + +/* ----------------------------------------------------------------------------- + * INTERNAL + */ + +static void +populate_slots (GcrImportDialog *self) +{ + GList *modules, *m; + GList *slots, *s; + GtkTreeIter iter; + GP11TokenInfo *info; + gboolean added; + + g_assert (GCR_IS_IMPORT_DIALOG (self)); + + if (self->pv->slots) + return; + + self->pv->slots = gtk_list_store_new (N_COLUMNS, GP11_TYPE_SLOT, G_TYPE_STRING, G_TYPE_STRING); + gtk_combo_box_set_model (self->pv->combo, GTK_TREE_MODEL (self->pv->slots)); + + modules = _gcr_get_pkcs11_modules (); + g_return_if_fail (modules); + + gtk_list_store_clear (self->pv->slots); + + added = FALSE; + for (m = modules; m; m = g_list_next (m)) { + + g_return_if_fail (GP11_IS_MODULE (m->data)); + slots = gp11_module_get_slots (m->data, TRUE); + + for (s = slots; s; s = g_list_next (s)) { + info = gp11_slot_get_token_info (s->data); + if (!(info->flags & CKF_WRITE_PROTECTED)) { + gtk_list_store_append (self->pv->slots, &iter); + gtk_list_store_set (self->pv->slots, &iter, + COLUMN_LABEL, info->label, + COLUMN_SLOT, s->data, + -1); + added = TRUE; + } + } + + gp11_list_unref_free (slots); + } + + if (added) + gtk_combo_box_set_active (self->pv->combo, 0); +} + +/* ----------------------------------------------------------------------------- + * OBJECT + */ + +static void +gcr_import_dialog_real_realize (GtkWidget *base) +{ + GcrImportDialog *self = GCR_IMPORT_DIALOG (base); + if (GTK_WIDGET_VISIBLE (self->pv->combo)) + populate_slots (self); + GTK_WIDGET_CLASS (_gcr_import_dialog_parent_class)->realize (base); +} + +static GObject* +gcr_import_dialog_constructor (GType type, guint n_props, GObjectConstructParam *props) +{ + GcrImportDialog *self = GCR_IMPORT_DIALOG (G_OBJECT_CLASS (_gcr_import_dialog_parent_class)->constructor(type, n_props, props)); + GtkCellRenderer *renderer; + GtkWidget *widget; + + g_return_val_if_fail (self, NULL); + + if (!gtk_builder_add_from_file (self->pv->builder, UIDIR "gcr-import-dialog.ui", NULL)) + g_return_val_if_reached (NULL); + + /* Fill in the dialog from builder */ + widget = GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "import-dialog")); + g_return_val_if_fail (widget, FALSE); + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (self)->vbox), widget); + + /* Add a secure entry */ + self->pv->entry = EGG_SECURE_ENTRY (egg_secure_entry_new ()); + widget = GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "password-area")); + gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (self->pv->entry)); + gtk_widget_show (GTK_WIDGET (self->pv->entry)); + + /* Initialize the combo box */ + self->pv->combo = GTK_COMBO_BOX (gtk_builder_get_object (self->pv->builder, "slot-combo")); + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->pv->combo), renderer, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (self->pv->combo), renderer, "icon-name", COLUMN_ICON); + g_object_set (renderer, "xpad", 3, NULL); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->pv->combo), renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (self->pv->combo), renderer, "text", COLUMN_LABEL); + + /* Add our various buttons */ + gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_OK, GTK_RESPONSE_OK); + + return G_OBJECT (self); +} + +static void +_gcr_import_dialog_init (GcrImportDialog *self) +{ + self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_IMPORT_DIALOG, GcrImportDialogPrivate); + self->pv->builder = gtk_builder_new (); +} + +static void +gcr_import_dialog_dispose (GObject *obj) +{ + G_OBJECT_CLASS (_gcr_import_dialog_parent_class)->dispose (obj); +} + +static void +gcr_import_dialog_finalize (GObject *obj) +{ + GcrImportDialog *self = GCR_IMPORT_DIALOG (obj); + + g_object_unref (self->pv->slots); + self->pv->slots = NULL; + + g_object_unref (self->pv->builder); + self->pv->builder = NULL; + + G_OBJECT_CLASS (_gcr_import_dialog_parent_class)->finalize (obj); +} + +static void +gcr_import_dialog_set_property (GObject *obj, guint prop_id, const GValue *value, + GParamSpec *pspec) +{ + GcrImportDialog *self = GCR_IMPORT_DIALOG (obj); + + switch (prop_id) { + case PROP_SELECTED_SLOT: + _gcr_import_dialog_set_selected_slot (self, g_value_get_object (value)); + break; + case PROP_PASSWORD: + _gcr_import_dialog_set_password (self, g_value_get_pointer (value)); + break; + case PROP_PRIMARY_TEXT: + _gcr_import_dialog_set_primary_text (self, g_value_get_string (value)); + break; + case PROP_SECONDARY_TEXT: + _gcr_import_dialog_set_secondary_text (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; + } +} + +static void +gcr_import_dialog_get_property (GObject *obj, guint prop_id, GValue *value, + GParamSpec *pspec) +{ + GcrImportDialog *self = GCR_IMPORT_DIALOG (obj); + + switch (prop_id) { + case PROP_SELECTED_SLOT: + g_value_set_object (value, _gcr_import_dialog_get_selected_slot (self)); + break; + case PROP_PASSWORD: + g_value_set_pointer (value, (gpointer)_gcr_import_dialog_get_password (self)); + break; + case PROP_PRIMARY_TEXT: + g_value_set_string (value, _gcr_import_dialog_get_primary_text (self)); + break; + case PROP_SECONDARY_TEXT: + g_value_set_string (value, _gcr_import_dialog_get_secondary_text (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; + } +} + +static void +_gcr_import_dialog_class_init (GcrImportDialogClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->constructor = gcr_import_dialog_constructor; + gobject_class->dispose = gcr_import_dialog_dispose; + gobject_class->finalize = gcr_import_dialog_finalize; + gobject_class->set_property = gcr_import_dialog_set_property; + gobject_class->get_property = gcr_import_dialog_get_property; + + widget_class->realize = gcr_import_dialog_real_realize; + + g_type_class_add_private (gobject_class, sizeof (GcrImportDialogPrivate)); + + g_object_class_install_property (gobject_class, PROP_SELECTED_SLOT, + g_param_spec_object ("selected-slot", "Selected Slot", "Selected PKCS#11 slot", + GP11_TYPE_SLOT, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PASSWORD, + g_param_spec_pointer ("password", "Password", "Pointer to password", + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PRIMARY_TEXT, + g_param_spec_string ("primary-text", "Primary Text", "Primary dialog text", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_SECONDARY_TEXT, + g_param_spec_string ("secondary-text", "Secondary Text", "Dialog secondary text", + NULL, G_PARAM_READWRITE)); + + _gcr_initialize (); +} + +/* ----------------------------------------------------------------------------- + * PUBLIC + */ + +GcrImportDialog* +_gcr_import_dialog_new (void) +{ + GcrImportDialog *dialog = g_object_new (GCR_TYPE_IMPORT_DIALOG, NULL); + return g_object_ref_sink (dialog); +} + +gboolean +_gcr_import_dialog_run (GcrImportDialog *self, GtkWindow *parent) +{ + gboolean ret; + + g_return_val_if_fail (GCR_IS_IMPORT_DIALOG (self), FALSE); + + if (parent != NULL) + gtk_window_set_transient_for (GTK_WINDOW (self), parent); + + ret = (gtk_dialog_run (GTK_DIALOG (self)) == GTK_RESPONSE_OK); + + if (parent != NULL) + gtk_window_set_transient_for (GTK_WINDOW (self), NULL); + + gtk_widget_hide (GTK_WIDGET (self)); + return ret; +} + +GP11Slot* +_gcr_import_dialog_get_selected_slot (GcrImportDialog *self) +{ + GtkTreeIter iter; + GP11Slot *slot; + + g_return_val_if_fail (GCR_IMPORT_DIALOG (self), NULL); + + if (GTK_WIDGET_VISIBLE (self->pv->combo)) + populate_slots (self); + else + return NULL; + + if (!gtk_combo_box_get_active_iter (self->pv->combo, &iter)) + return NULL; + + gtk_tree_model_get (GTK_TREE_MODEL (self->pv->slots), &iter, COLUMN_SLOT, &slot, -1); + + /* We hold the reference to this */ + if (slot != NULL) + g_object_unref (slot); + + return slot; +} + +void +_gcr_import_dialog_set_selected_slot (GcrImportDialog *self, GP11Slot *slot) +{ + GtkTreeIter iter; + GP11Slot *it_slot; + gboolean matched; + + g_return_if_fail (GCR_IMPORT_DIALOG (self)); + + if (GTK_WIDGET_VISIBLE (self->pv->combo)) + populate_slots (self); + else + g_return_if_reached (); + + if (slot == NULL) { + gtk_combo_box_set_active (self->pv->combo, -1); + return; + } + + g_return_if_fail (GP11_IS_SLOT (slot)); + + matched = FALSE; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->pv->slots), &iter)) { + do { + gtk_tree_model_get (GTK_TREE_MODEL (self->pv->slots), &iter, COLUMN_SLOT, &it_slot, -1); + if (gp11_slot_equal (it_slot, slot)) + matched = TRUE; + g_object_unref (it_slot); + } while (!matched && gtk_tree_model_iter_next (GTK_TREE_MODEL (self->pv->slots), &iter)); + } + + if (matched) { + gtk_combo_box_set_active_iter (self->pv->combo, &iter); + } else { + gtk_combo_box_set_active (self->pv->combo, -1); + g_return_if_reached (); + } +} + +void +_gcr_import_dialog_show_selected_slot (GcrImportDialog *self) +{ + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "slot-label"))); + gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "slot-area"))); +} + +void +_gcr_import_dialog_hide_selected_slot (GcrImportDialog *self) +{ + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "slot-label"))); + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "slot-area"))); +} + +const gchar* +_gcr_import_dialog_get_password (GcrImportDialog *self) +{ + g_return_val_if_fail (GCR_IS_IMPORT_DIALOG (self), NULL); + return egg_secure_entry_get_text (self->pv->entry); +} + +void +_gcr_import_dialog_set_password (GcrImportDialog *self, const gchar *password) +{ + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + if (password == NULL) + password = ""; + egg_secure_entry_set_text (self->pv->entry, password); +} + +void +_gcr_import_dialog_show_password (GcrImportDialog *self) +{ + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "password-label"))); + gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "password-area"))); +} + +void +_gcr_import_dialog_hide_password (GcrImportDialog *self) +{ + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "password-label"))); + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (self->pv->builder, "password-area"))); +} + +const gchar* +_gcr_import_dialog_get_primary_text (GcrImportDialog *self) +{ + g_return_val_if_fail (GCR_IS_IMPORT_DIALOG (self), NULL); + return gtk_label_get_text (GTK_LABEL (gtk_builder_get_object (self->pv->builder, "primary-text"))); +} + +void +_gcr_import_dialog_set_primary_text (GcrImportDialog *self, const gchar *text) +{ + gchar *label; + + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + + if (text == NULL) + text = ""; + + label = g_markup_printf_escaped ("<span size='large' weight='bold'>%s</span>", text); + gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (self->pv->builder, "primary-text")), label); + g_free (label); + + g_object_notify (G_OBJECT (self), "primary-text"); +} + +const gchar* +_gcr_import_dialog_get_secondary_text (GcrImportDialog *self) +{ + g_return_val_if_fail (GCR_IS_IMPORT_DIALOG (self), NULL); + return gtk_label_get_text (GTK_LABEL (gtk_builder_get_object (self->pv->builder, "secondary-text"))); +} + +void +_gcr_import_dialog_set_secondary_text (GcrImportDialog *self, const gchar *text) +{ + g_return_if_fail (GCR_IS_IMPORT_DIALOG (self)); + + if (text == NULL) + text = ""; + + gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (self->pv->builder, "secondary-text")), text); + g_object_notify (G_OBJECT (self), "primary-text"); +} diff --git a/gcr/gcr-import-dialog.glade b/gcr/gcr-import-dialog.glade index 7da00a1..e91c662 100644 --- a/gcr/gcr-import-dialog.glade +++ b/gcr/gcr-import-dialog.glade @@ -1,164 +1,127 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> -<!--Generated with glade3 3.4.5 on Sat Jan 17 14:53:28 2009 --> +<!--Generated with glade3 3.4.5 on Wed Jan 21 16:01:29 2009 --> <glade-interface> - <widget class="GtkDialog" id="dialog1"> - <property name="border_width">5</property> - <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> - <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> - <property name="has_separator">False</property> - <child internal-child="vbox"> - <widget class="GtkVBox" id="dialog-vbox1"> + <widget class="GtkWindow" id="window1"> + <child> + <widget class="GtkVBox" id="import-dialog"> <property name="visible">True</property> - <property name="spacing">2</property> + <property name="border_width">6</property> + <property name="spacing">6</property> <child> - <widget class="GtkVBox" id="vbox1"> + <widget class="GtkHBox" id="hbox2"> <property name="visible">True</property> - <property name="spacing">6</property> + <property name="spacing">12</property> <child> - <widget class="GtkHBox" id="hbox2"> + <widget class="GtkImage" id="image1"> <property name="visible">True</property> - <property name="spacing">12</property> - <child> - <widget class="GtkImage" id="image1"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="yalign">1</property> - <property name="stock">gtk-dialog-authentication</property> - <property name="icon_size">6</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <widget class="GtkVBox" id="vbox2"> - <property name="visible">True</property> - <property name="spacing">6</property> - <child> - <widget class="GtkLabel" id="primary-text"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><span size='large' weight='bold'>Import Certificates and Keys</span></property> - <property name="use_markup">True</property> - </widget> - </child> - <child> - <widget class="GtkLabel" id="secondary-text"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">Secondary prompt text</property> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> + <property name="xalign">0</property> + <property name="yalign">1</property> + <property name="stock">gtk-dialog-authentication</property> + <property name="icon_size">6</property> </widget> <packing> <property name="expand">False</property> + <property name="fill">False</property> </packing> </child> <child> - <widget class="GtkTable" id="table1"> + <widget class="GtkVBox" id="vbox2"> <property name="visible">True</property> - <property name="n_rows">2</property> - <property name="n_columns">2</property> - <property name="column_spacing">12</property> - <property name="row_spacing">6</property> + <property name="spacing">6</property> <child> - <widget class="GtkEntry" id="password-entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - </packing> - </child> - <child> - <widget class="GtkComboBox" id="location-combo"> - <property name="visible">True</property> - <property name="button_sensitivity">GTK_SENSITIVITY_ON</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label5"> + <widget class="GtkLabel" id="primary-text"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Password:</property> + <property name="label" translatable="yes"><span size='large' weight='bold'>Import Certificates and Keys</span></property> + <property name="use_markup">True</property> + <property name="wrap">True</property> </widget> - <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> - </packing> </child> <child> - <widget class="GtkLabel" id="label4"> + <widget class="GtkLabel" id="secondary-text"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Import Into:</property> + <property name="wrap">True</property> </widget> <packing> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="position">1</property> </packing> </child> </widget> <packing> - <property name="expand">False</property> <property name="position">1</property> </packing> </child> </widget> <packing> - <property name="position">1</property> + <property name="expand">False</property> </packing> </child> - <child internal-child="action_area"> - <widget class="GtkHButtonBox" id="dialog-action_area1"> + <child> + <widget class="GtkTable" id="table1"> <property name="visible">True</property> - <property name="layout_style">GTK_BUTTONBOX_END</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <widget class="GtkAlignment" id="password-area"> + <property name="visible">True</property> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkAlignment" id="slot-area"> + <property name="visible">True</property> + <child> + <widget class="GtkComboBox" id="slot-combo"> + <property name="visible">True</property> + <property name="button_sensitivity">GTK_SENSITIVITY_ON</property> + </widget> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> <child> - <widget class="GtkButton" id="button1"> + <widget class="GtkLabel" id="password-label"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-cancel</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Password:</property> </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> </child> <child> - <widget class="GtkButton" id="button2"> + <widget class="GtkLabel" id="slot-label"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-ok</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Import Into:</property> </widget> <packing> - <property name="position">1</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> </packing> </child> </widget> <packing> <property name="expand">False</property> - <property name="pack_type">GTK_PACK_END</property> + <property name="position">1</property> </packing> </child> </widget> diff --git a/gcr/gcr-import-dialog.h b/gcr/gcr-import-dialog.h new file mode 100644 index 0000000..2465f22 --- /dev/null +++ b/gcr/gcr-import-dialog.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef __GCR_IMPORT_DIALOG_H__ +#define __GCR_IMPORT_DIALOG_H__ + +#include "gcr.h" + +#include "gp11/gp11.h" + +#include <gtk/gtk.h> + +#define GCR_TYPE_IMPORT_DIALOG (_gcr_import_dialog_get_type ()) +#define GCR_IMPORT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_IMPORT_DIALOG, GcrImportDialog)) +#define GCR_IMPORT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_IMPORT_DIALOG, GcrImportDialogClass)) +#define GCR_IS_IMPORT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_IMPORT_DIALOG)) +#define GCR_IS_IMPORT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_IMPORT_DIALOG)) +#define GCR_IMPORT_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_IMPORT_DIALOG, GcrImportDialogClass)) + +typedef struct _GcrImportDialog GcrImportDialog; +typedef struct _GcrImportDialogClass GcrImportDialogClass; +typedef struct _GcrImportDialogPrivate GcrImportDialogPrivate; + +struct _GcrImportDialog { + GtkDialog parent; + GcrImportDialogPrivate *pv; +}; + +struct _GcrImportDialogClass { + GtkDialogClass parent_class; +}; + +GType _gcr_import_dialog_get_type (void); + +GcrImportDialog* _gcr_import_dialog_new (void); + +gboolean _gcr_import_dialog_run (GcrImportDialog *self, + GtkWindow *parent); + +GP11Slot* _gcr_import_dialog_get_selected_slot (GcrImportDialog *self); + +void _gcr_import_dialog_set_selected_slot (GcrImportDialog *self, + GP11Slot *slot); + +void _gcr_import_dialog_show_selected_slot (GcrImportDialog *self); + +void _gcr_import_dialog_hide_selected_slot (GcrImportDialog *self); + +const gchar* _gcr_import_dialog_get_password (GcrImportDialog *self); + +void _gcr_import_dialog_set_password (GcrImportDialog *self, + const gchar *password); + +void _gcr_import_dialog_show_password (GcrImportDialog *self); + +void _gcr_import_dialog_hide_password (GcrImportDialog *self); + +const gchar* _gcr_import_dialog_get_primary_text (GcrImportDialog *self); + +void _gcr_import_dialog_set_primary_text (GcrImportDialog *self, + const gchar *text); + +const gchar* _gcr_import_dialog_get_secondary_text (GcrImportDialog *self); + +void _gcr_import_dialog_set_secondary_text (GcrImportDialog *self, + const gchar *text); + +#endif /* __GCR_IMPORT_DIALOG_H__ */ diff --git a/gcr/gcr-importer.c b/gcr/gcr-importer.c index 35abba8..0fe0809 100644 --- a/gcr/gcr-importer.c +++ b/gcr/gcr-importer.c @@ -21,46 +21,555 @@ #include "config.h" +#include "gcr-import-dialog.h" #include "gcr-importer.h" +#include "gcr-internal.h" +#include "gcr-parser.h" + +#include <glib/gi18n-lib.h> enum { PROP_0, - PROP_IMPORTER + PROP_SLOT, + PROP_PARSER, + PROP_PROMPT_BEHAVIOR }; enum { - SIGNAL, + IMPORTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; -G_DEFINE_TYPE (GcrImporter, gcr_importer, G_TYPE_OBJECT); +struct _GcrImporterPrivate { + GP11Slot *slot; + GcrParser *parser; + GcrImporterPromptBehavior behavior; + + /* Information about last import */ + GError *error; + gboolean succeeded; + + /* State data during import */ + gboolean processing; + GCancellable *cancel; + GInputStream *input; + gboolean prompted; + gboolean async; + GByteArray *buffer; + GP11Session *session; + GQueue queue; + + /* Extra async stuff */ + GAsyncReadyCallback callback; + gpointer user_data; +}; + +/* State forward declarations */ +static void state_cancelled (GcrImporter *self, gboolean async); +static void state_complete (GcrImporter *self, gboolean async); +static void state_create_object (GcrImporter *self, gboolean async); +static void state_open_session (GcrImporter *self, gboolean async); +static void state_parse_buffer (GcrImporter *self, gboolean async); +static void state_read_buffer (GcrImporter *self, gboolean async); + +static void gcr_importer_async_result (GAsyncResultIface *iface); +G_DEFINE_TYPE_WITH_CODE (GcrImporter, gcr_importer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, gcr_importer_async_result)); + +#define BLOCK 4096 /* ----------------------------------------------------------------------------- * INTERNAL */ +static void +cleanup_state_data (GcrImporter *self) +{ + GP11Attributes *attrs; + + if (self->pv->buffer) + g_byte_array_free (self->pv->buffer, TRUE); + self->pv->buffer = NULL; + + if (self->pv->session) + g_object_unref (self->pv->session); + self->pv->session = NULL; + + while ((attrs = g_queue_pop_head (&self->pv->queue)) != NULL) + gp11_attributes_unref (attrs); + g_assert (g_queue_is_empty (&self->pv->queue)); + + if (self->pv->input) + g_object_unref (self->pv->input); + self->pv->input = NULL; + + if (self->pv->cancel) + g_object_unref (self->pv->cancel); + self->pv->cancel = NULL; +} + +static void +cleanup_import_data (GcrImporter *self) +{ + if (self->pv->error) + g_clear_error (&self->pv->error); + self->pv->succeeded = TRUE; +} + +static void +next_state (GcrImporter *self, void (*state) (GcrImporter*, gboolean)) +{ + g_assert (GCR_IS_IMPORTER (self)); + g_assert (self->pv->processing); + g_assert (state); + + if (self->pv->cancel && g_cancellable_is_cancelled (self->pv->cancel)) + state = state_cancelled; + + (state) (self, self->pv->async); +} + +/* --------------------------------------------------------------------------------- + * COMPLETE + */ + +static void +state_complete (GcrImporter *self, gboolean async) +{ + if (async && self->pv->callback != NULL) + (self->pv->callback) (G_OBJECT (self), G_ASYNC_RESULT (self), self->pv->user_data); + + cleanup_state_data (self); + self->pv->processing = FALSE; +} + +static void +state_failure (GcrImporter *self, gboolean async) +{ + self->pv->succeeded = FALSE; + next_state (self, state_complete); +} + +static void +state_cancelled (GcrImporter *self, gboolean async) +{ + if (self->pv->cancel && g_cancellable_is_cancelled (self->pv->cancel)) + g_cancellable_cancel (self->pv->cancel); + if (self->pv->error) + g_error_free (self->pv->error); + self->pv->error = g_error_new_literal (GCR_DATA_ERROR, GCR_ERROR_CANCELLED, _("The operation was cancelled")); + next_state (self, state_failure); +} + +/* --------------------------------------------------------------------------------- + * CREATE OBJECTS + */ + +static void +complete_create_object (GcrImporter *self, GP11Object *object, GError *error) +{ + if (object == NULL) { + g_propagate_error (&self->pv->error, error); + next_state (self, state_failure); + + } else { + g_signal_emit (self, signals[IMPORTED], 0, object); + g_object_unref (object); + next_state (self, state_create_object); + } +} + +static void +on_create_object (GObject *obj, GAsyncResult *res, gpointer user_data) +{ + GError *error = NULL; + GP11Object *object = gp11_session_create_object_finish (GP11_SESSION (obj), res, &error); + complete_create_object (GCR_IMPORTER (user_data), object, error); +} + +static void +state_create_object (GcrImporter *self, gboolean async) +{ + GP11Attributes *attrs; + GP11Object *object; + GError *error = NULL; + + /* No more objects */ + if (g_queue_is_empty (&self->pv->queue)) { + next_state (self, state_complete); + + } else { + + /* Pop first one off the list */ + attrs = g_queue_pop_head (&self->pv->queue); + g_assert (attrs); + + gp11_attributes_add_ulong (attrs, CKA_TOKEN, CK_TRUE); + + if (async) { + gp11_session_create_object_async (self->pv->session, attrs, self->pv->cancel, + on_create_object, self); + } else { + object = gp11_session_create_object_full (self->pv->session, attrs, self->pv->cancel, &error); + complete_create_object (self, object, error); + } + + gp11_attributes_unref (attrs); + } +} + +/* --------------------------------------------------------------------------------- + * OPEN SESSION + */ + +static void +complete_open_session (GcrImporter *self, GP11Session *session, GError *error) +{ + if (!session) { + g_propagate_error (&self->pv->error, error); + next_state (self, state_failure); + } else { + self->pv->session = session; + next_state (self, state_create_object); + } +} + +static void +on_open_session (GObject *obj, GAsyncResult *res, gpointer user_data) +{ + GError *error = NULL; + GP11Session *session = gp11_slot_open_session_finish (GP11_SLOT (obj), res, &error); + complete_open_session (GCR_IMPORTER (user_data), session, error); +} + +static void +state_open_session (GcrImporter *self, gboolean async) +{ + GP11Session *session; + GError *error = NULL; + + if (!self->pv->slot) { + g_set_error (&self->pv->error, GCR_DATA_ERROR, GCR_ERROR_FAILURE, _("No location available to import to")); + next_state (self, state_failure); + + } else { + + if (async) { + gp11_slot_open_session_async (self->pv->slot, CKF_RW_SESSION, + self->pv->cancel, on_open_session, self); + } else { + session = gp11_slot_open_session_full (self->pv->slot, CKF_RW_SESSION, + self->pv->cancel, &error); + complete_open_session (self, session, error); + } + } +} + +/* --------------------------------------------------------------------------------- + * IMPORT PROMPT + */ + +static void +complete_import_prompt (GcrImporter *self, GcrImportDialog *dialog, gint response) +{ + GP11Slot *slot; + + gtk_widget_hide (GTK_WIDGET (dialog)); + self->pv->prompted = TRUE; + + /* No dialog or dialog completed */ + if (response == GTK_RESPONSE_OK) { + + slot = _gcr_import_dialog_get_selected_slot (dialog); + gcr_importer_set_slot (self, slot); + next_state (self, state_open_session); + + /* The dialog was cancelled or closed */ + } else { + next_state (self, state_cancelled); + } +} + +static void +on_prompt_response (GtkDialog *dialog, gint response, gpointer user_data) +{ + complete_import_prompt (GCR_IMPORTER (user_data), GCR_IMPORT_DIALOG (dialog), response); + g_object_unref (dialog); +} + +static void +state_import_prompt (GcrImporter *self, gboolean async) +{ + GcrImportDialog *dialog; + gboolean prompt; + gint response; + + g_assert (GCR_IS_IMPORTER (self)); + + /* No need to prompt */ + if (self->pv->prompted == TRUE) + prompt = FALSE; + else if (self->pv->behavior == GCR_IMPORTER_PROMPT_ALWAYS) + prompt = TRUE; + else if (self->pv->behavior == GCR_IMPORTER_PROMPT_NEVER) + prompt = FALSE; + else + prompt = self->pv->slot ? FALSE : TRUE; + + if (prompt == FALSE) { + next_state (self, state_open_session); + + } else { + + dialog = _gcr_import_dialog_new (); + + _gcr_import_dialog_set_primary_text (dialog, _("Import Certificates/Keys")); + _gcr_import_dialog_hide_password (dialog); + + if (self->pv->slot) { + _gcr_import_dialog_set_selected_slot (dialog, self->pv->slot); + _gcr_import_dialog_hide_selected_slot (dialog); + } else { + _gcr_import_dialog_set_secondary_text (dialog, _("Choose a location to store the imported certificates/keys.")); + } + + /* Prompt without blocking main loop */ + if (async) { + g_signal_connect (dialog, "response", G_CALLBACK (on_prompt_response), self); + gtk_widget_show (GTK_WIDGET (dialog)); + + /* Block mainloop */ + } else { + response = gtk_dialog_run (GTK_DIALOG (dialog)); + complete_import_prompt (self, dialog, response); + g_object_unref (dialog); + } + } +} + +/* --------------------------------------------------------------------------------- + * PARSING + */ + +static const gchar* +prepare_auth_primary (CK_OBJECT_CLASS klass) +{ + if (klass == CKO_PRIVATE_KEY) + return _("Enter password to unlock the private key"); + else if (klass == CKO_CERTIFICATE) + return _("Enter password to unlock the certificate"); + else + return _("Enter password to unlock"); +} + +static gchar* +prepare_auth_secondary (CK_OBJECT_CLASS klass, const gchar *label) +{ + if (label == NULL) { + if (klass == CKO_PRIVATE_KEY) + return g_strdup (_("In order to import the private key, it must be unlocked")); + else if (klass == CKO_CERTIFICATE) + return g_strdup (_("In order to import the certificate, it must be unlocked")); + else + return g_strdup (_("In order to import the data, it must be unlocked")); + } else { + if (klass == CKO_PRIVATE_KEY) + return g_strdup_printf (_("In order to import the private key '%s', it must be unlocked"), label); + else if (klass == CKO_CERTIFICATE) + return g_strdup_printf (_("In order to import the certificate '%s', it must be unlocked"), label); + else + return g_strdup_printf (_("In order to import '%s', it must be unlocked"), label); + } +} + +static void +on_parser_parsed (GcrParser *parser, GcrImporter *self) +{ + GP11Attributes *attrs; + + g_return_if_fail (GCR_IS_PARSER (parser)); + g_return_if_fail (GCR_IS_IMPORTER (self)); + + attrs = gcr_parser_get_parsed_attributes (parser); + g_return_if_fail (attrs); + g_queue_push_tail (&self->pv->queue, gp11_attributes_ref (attrs)); +} + +static gboolean +on_parser_authenticate (GcrParser *parser, gint count, GcrImporter *self) +{ + GcrImportDialog *dialog; + GP11Attributes *attrs; + const gchar *password; + gchar *text, *label; + GP11Slot *slot; + gulong klass; + + dialog = _gcr_import_dialog_new (); + + if (self->pv->slot) + _gcr_import_dialog_set_selected_slot (dialog, self->pv->slot); + + /* Figure out the text for the dialog */ + attrs = gcr_parser_get_parsed_attributes (parser); + g_return_val_if_fail (attrs, FALSE); + + if (!gp11_attributes_find_ulong (attrs, CKA_CLASS, &klass)) + klass = (gulong)-1; + if (!gp11_attributes_find_string (attrs, CKA_LABEL, &label)) + label = NULL; + + text = prepare_auth_secondary (klass, label); + _gcr_import_dialog_set_primary_text (dialog, prepare_auth_primary (klass)); + _gcr_import_dialog_set_secondary_text (dialog, text); + g_free (label); + g_free (text); + + if (!_gcr_import_dialog_run (dialog, NULL)) + return FALSE; + + slot = _gcr_import_dialog_get_selected_slot (dialog); + gcr_importer_set_slot (self, slot); + + password = _gcr_import_dialog_get_password (dialog); + gcr_parser_add_password (parser, password); + + g_object_unref (dialog); + self->pv->prompted = TRUE; + return TRUE; +} + +static void +state_parse_buffer (GcrImporter *self, gboolean async) +{ + GError *error = NULL; + GcrParser *parser; + gulong parsed_conn; + gulong auth_conn; + gboolean ret; + + g_assert (GCR_IS_IMPORTER (self)); + g_assert (self->pv->buffer); + + parser = gcr_importer_get_parser (self); + g_object_ref (parser); + + /* Listen in to the parser */ + parsed_conn = g_signal_connect (parser, "parsed", G_CALLBACK (on_parser_parsed), self); + auth_conn = g_signal_connect (parser, "authenticate", G_CALLBACK (on_parser_authenticate), self); + + ret = gcr_parser_parse_data (parser, self->pv->buffer->data, self->pv->buffer->len, &error); + + /* An optimization to free data early as possible */ + g_byte_array_free (self->pv->buffer, TRUE); + self->pv->buffer = NULL; + + g_signal_handler_disconnect (parser, parsed_conn); + g_signal_handler_disconnect (parser, auth_conn); + g_object_unref (parser); + + if (ret == TRUE) { + next_state (self, state_import_prompt); + } else { + g_propagate_error (&self->pv->error, error); + next_state (self, state_failure); + } +} + +/* --------------------------------------------------------------------------------- + * BUFFER READING + */ + +static void +complete_read_buffer (GcrImporter *self, gssize count, GError *error) +{ + g_assert (GCR_IS_IMPORTER (self)); + g_assert (self->pv->buffer); + + /* A failure */ + if (count == -1) { + g_propagate_error (&self->pv->error, error); + next_state (self, state_failure); + } else { + + g_return_if_fail (count >= 0 && count <= BLOCK); + g_byte_array_set_size (self->pv->buffer, self->pv->buffer->len - (BLOCK - count)); + + /* Finished reading */ + if (count == 0) { + + /* Optimization, unref input early */ + g_object_unref (self->pv->input); + self->pv->input = NULL; + + next_state (self, state_parse_buffer); + + /* Read the next block */ + } else { + next_state (self, state_read_buffer); + } + } + +} + +static void +on_read_buffer (GObject *obj, GAsyncResult *res, gpointer user_data) +{ + GError *error = NULL; + gssize count; + + count = g_input_stream_read_finish (G_INPUT_STREAM (obj), res, &error); + complete_read_buffer (user_data, count, error); +} + +static void +state_read_buffer (GcrImporter *self, gboolean async) +{ + GError *error = NULL; + gssize count; + gsize at; + + g_assert (GCR_IS_IMPORTER (self)); + g_assert (G_IS_INPUT_STREAM (self->pv->input)); + + if (!self->pv->buffer) + self->pv->buffer = g_byte_array_sized_new (BLOCK); + + at = self->pv->buffer->len; + g_byte_array_set_size (self->pv->buffer, at + BLOCK); + + if (async) { + g_input_stream_read_async (self->pv->input, self->pv->buffer->data + at, + BLOCK, G_PRIORITY_DEFAULT, self->pv->cancel, + on_read_buffer, self); + } else { + count = g_input_stream_read (self->pv->input, self->pv->buffer->data + at, + BLOCK, self->pv->cancel, &error); + complete_read_buffer (self, count, error); + } +} + /* ----------------------------------------------------------------------------- * OBJECT */ - static GObject* gcr_importer_constructor (GType type, guint n_props, GObjectConstructParam *props) { GcrImporter *self = GCR_IMPORTER (G_OBJECT_CLASS (gcr_importer_parent_class)->constructor(type, n_props, props)); g_return_val_if_fail (self, NULL); - - return G_OBJECT (self); } static void gcr_importer_init (GcrImporter *self) { - + self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_IMPORTER, GcrImporterPrivate); + self->pv->behavior = GCR_IMPORTER_PROMPT_NEEDED; + g_queue_init (&self->pv->queue); } static void @@ -68,6 +577,17 @@ gcr_importer_dispose (GObject *obj) { GcrImporter *self = GCR_IMPORTER (obj); + cleanup_state_data (self); + cleanup_import_data (self); + + if (self->pv->parser) + g_object_unref (self->pv->parser); + self->pv->parser = NULL; + + if (self->pv->slot) + g_object_unref (self->pv->slot); + self->pv->slot = NULL; + G_OBJECT_CLASS (gcr_importer_parent_class)->dispose (obj); } @@ -76,6 +596,9 @@ gcr_importer_finalize (GObject *obj) { GcrImporter *self = GCR_IMPORTER (obj); + g_assert (!self->pv->parser); + g_assert (!self->pv->slot); + G_OBJECT_CLASS (gcr_importer_parent_class)->finalize (obj); } @@ -86,7 +609,14 @@ gcr_importer_set_property (GObject *obj, guint prop_id, const GValue *value, GcrImporter *self = GCR_IMPORTER (obj); switch (prop_id) { - case PROP_IMPORTER: + case PROP_PARSER: + gcr_importer_set_parser (self, g_value_get_object (value)); + break; + case PROP_SLOT: + gcr_importer_set_slot (self, g_value_get_object (value)); + break; + case PROP_PROMPT_BEHAVIOR: + gcr_importer_set_prompt_behavior (self, (GcrImporterPromptBehavior)g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); @@ -101,7 +631,14 @@ gcr_importer_get_property (GObject *obj, guint prop_id, GValue *value, GcrImporter *self = GCR_IMPORTER (obj); switch (prop_id) { - case PROP_IMPORTER: + case PROP_PARSER: + g_value_set_object (value, gcr_importer_get_parser (self)); + break; + case PROP_SLOT: + g_value_set_object (value, gcr_importer_get_slot (self)); + break; + case PROP_PROMPT_BEHAVIOR: + g_value_set_int (value, gcr_importer_get_prompt_behavior (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); @@ -120,17 +657,49 @@ gcr_importer_class_init (GcrImporterClass *klass) gobject_class->set_property = gcr_importer_set_property; gobject_class->get_property = gcr_importer_get_property; - g_object_class_install_property (gobject_class, PROP_IMPORTER, - g_param_spec_pointer ("importer", "Importer", "Importer.", G_PARAM_READWRITE)); + g_type_class_add_private (gobject_class, sizeof (GcrImporterPrivate)); + + g_object_class_install_property (gobject_class, PROP_PARSER, + g_param_spec_object ("parser", "Parser", "Parser used to parse imported data", + GCR_TYPE_PARSER, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PARSER, + g_param_spec_object ("slot", "Slot", "PKCS#11 slot to import data into", + GP11_TYPE_SLOT, G_PARAM_READWRITE)); - signals[SIGNAL] = g_signal_new ("signal", GCR_TYPE_IMPORTER, - G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GcrImporterClass, signal), + g_object_class_install_property (gobject_class, PROP_PROMPT_BEHAVIOR, + g_param_spec_int ("prompt-behavior", "Prompt Behavior", "Import Prompt Behavior", + 0, G_MAXINT, GCR_IMPORTER_PROMPT_NEEDED, G_PARAM_READWRITE)); + + signals[IMPORTED] = g_signal_new ("imported", GCR_TYPE_IMPORTER, + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GcrImporterClass, imported), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, 0); + G_TYPE_NONE, 1, GP11_TYPE_OBJECT); _gcr_initialize (); } +static gpointer +gcr_importer_real_get_user_data (GAsyncResult *base) +{ + g_return_val_if_fail (GCR_IS_IMPORTER (base), NULL); + return GCR_IMPORTER (base)->pv->user_data; +} + +static GObject* +gcr_importer_real_get_source_object (GAsyncResult *base) +{ + g_return_val_if_fail (GCR_IS_IMPORTER (base), NULL); + return G_OBJECT (base); +} + +static void +gcr_importer_async_result (GAsyncResultIface *iface) +{ + iface->get_source_object = gcr_importer_real_get_source_object; + iface->get_user_data = gcr_importer_real_get_user_data; +} + /* ----------------------------------------------------------------------------- * PUBLIC */ @@ -141,73 +710,133 @@ gcr_importer_new (void) return g_object_new (GCR_TYPE_IMPORTER, NULL); } -gboolean -gcr_importer_import_data (GcrImporter *self, const guchar *data, gsize n_data, - GError *err) +GcrParser* +gcr_importer_get_parser (GcrImporter *self) { - GckParser *parser; - gulong parsed_conn; - gulong auth_conn; - gboolean ret; - - g_return_val_if_fail (GCR_IS_IMPORTER (self), FALSE); - g_return_val_if_fail (data || !n_data, FALSE); - g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GCR_IS_IMPORTER (self), NULL); + if (!self->pv->parser) + self->pv->parser = gcr_parser_new (); + return self->pv->parser; +} +void +gcr_importer_set_parser (GcrImporter *self, GcrParser *parser) +{ + g_return_if_fail (GCR_IS_IMPORTER (self)); + + if (parser) + g_object_ref (parser); + if (self->pv->parser) + g_object_unref (self->pv->parser); + self->pv->parser = parser; + g_object_notify (G_OBJECT (self), "parser"); +} - xxxx; +GP11Slot* +gcr_importer_get_slot (GcrImporter *self) +{ + g_return_val_if_fail (GCR_IS_IMPORTER (self), NULL); + return self->pv->slot; +} +void +gcr_importer_set_slot (GcrImporter *self, GP11Slot *slot) +{ + g_return_if_fail (GCR_IS_IMPORTER (self)); + + if (slot) + g_object_ref (slot); + if (self->pv->slot) + g_object_unref (self->pv->slot); + self->pv->slot = slot; + g_object_notify (G_OBJECT (self), "slot"); +} - /* - * Parse to see if it's something that needs a password - * if we can't prompt, - * return an error - * Possibly prompt, if password needed, with all information necessary - * - */ +GcrImporterPromptBehavior +gcr_importer_get_prompt_behavior (GcrImporter *self) +{ + g_return_val_if_fail (GCR_IS_IMPORTER (self), GCR_IMPORTER_PROMPT_NEEDED); + return self->pv->behavior; +} +void +gcr_importer_set_prompt_behavior (GcrImporter *self, GcrImporterPromptBehavior behavior) +{ + g_return_if_fail (GCR_IMPORTER (self)); + self->pv->behavior = behavior; + g_object_notify (G_OBJECT (self), "prompt-behavior"); +} - g_object_ref (self); +gboolean +gcr_importer_import (GcrImporter *self, GInputStream *input, + GCancellable *cancel, GError **error) +{ + g_return_val_if_fail (GCR_IS_IMPORTER (self), FALSE); + g_return_val_if_fail (G_IS_INPUT_STREAM (input), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (!self->pv->processing, FALSE); - parser = gcr_importer_get_parser (self); + cleanup_import_data (self); - /* Listen in to the parser */ - g_object_ref (parser); - parsed_conn = g_signal_connect (parser, "parsed-item", G_CALLBACK (parser_parsed_item), self); - auth_conn = g_signal_connect (parser, "authenticate", G_CALLBACK (parser_authenticate), self); + self->pv->input = g_object_ref (input); + if (cancel) + self->pv->cancel = g_object_ref (cancel); + self->pv->processing = TRUE; + self->pv->async = FALSE; - /* Feed the parser the data */ - ret = gcr_parser_parse_data (parser, data, n_data, err); + next_state (self, state_read_buffer); - /* Now we should have all the data ready, check if we should prompt... */ - /* Import data one by one into module */ + g_assert (!self->pv->processing); + g_assert (!self->pv->input); + g_assert (!self->pv->cancel); - g_signal_handler_disconnect (parser, parsed_conn); - g_signal_handler_disconnect (parser, auth_conn); - g_object_unref (parser); + if (!self->pv->succeeded) { + g_propagate_error (error, self->pv->error); + self->pv->error = NULL; + return FALSE; + } - g_object_unref (self); + return TRUE; +} - return ret; +void +gcr_importer_import_async (GcrImporter *self, GInputStream *input, GCancellable *cancel, + GAsyncReadyCallback callback, gpointer user_data) +{ + g_return_if_fail (GCR_IS_IMPORTER (self)); + g_return_if_fail (G_IS_INPUT_STREAM (input)); + g_return_if_fail (!self->pv->processing); + + cleanup_import_data (self); + + self->pv->input = g_object_ref (input); + if (cancel) + self->pv->cancel = g_object_ref (cancel); + self->pv->processing = TRUE; + self->pv->async = TRUE; + self->pv->callback = callback; + self->pv->user_data = user_data; + + next_state (self, state_read_buffer); + g_assert (self->pv->processing); } gboolean -gcr_importer_import_file (GcrImporter *self, const gchar *filename, - GError *err) +gcr_importer_import_finish (GcrImporter *self, GAsyncResult *res, GError **error) { - gboolean ret; - gchar *data; - gsize n_data; - g_return_val_if_fail (GCR_IS_IMPORTER (self), FALSE); - g_return_val_if_fail (filename, FALSE); + g_return_val_if_fail (GCR_IMPORTER (res) == self, FALSE); g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (!self->pv->processing, FALSE); - if (!g_file_get_contents (filename, &data, &n_data, err)) - return FALSE; + g_assert (!self->pv->input); + g_assert (!self->pv->cancel); - ret = gcr_importer_import_data (self, (const guchar*)data, n_data, error); - g_free (data); + if (!self->pv->succeeded) { + g_propagate_error (error, self->pv->error); + self->pv->error = NULL; + return FALSE; + } - return ret; + return TRUE; } diff --git a/gcr/gcr-importer.h b/gcr/gcr-importer.h index dac1d51..ed366f3 100644 --- a/gcr/gcr-importer.h +++ b/gcr/gcr-importer.h @@ -22,8 +22,17 @@ #ifndef __GCR_IMPORTER_H__ #define __GCR_IMPORTER_H__ +#include "gcr.h" +#include "gcr-parser.h" + #include <glib-object.h> +typedef enum { + GCR_IMPORTER_PROMPT_NEEDED, + GCR_IMPORTER_PROMPT_ALWAYS, + GCR_IMPORTER_PROMPT_NEVER +} GcrImporterPromptBehavior; + #define GCR_TYPE_IMPORTER (gcr_importer_get_type ()) #define GCR_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_IMPORTER, GcrImporter)) #define GCR_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_IMPORTER, GcrImporterClass)) @@ -33,49 +42,53 @@ typedef struct _GcrImporter GcrImporter; typedef struct _GcrImporterClass GcrImporterClass; +typedef struct _GcrImporterPrivate GcrImporterPrivate; struct _GcrImporter { GObject parent; + GcrImporterPrivate *pv; }; struct _GcrImporterClass { GObjectClass parent_class; - /* signals --------------------------------------------------------- */ + /* signals */ - void (*signal) (GcrImporter *self, GkrImportedItem *item); + void (*imported) (GcrImporter *self, GP11Object *object); }; -GType gcr_importer_get_type (void); +GType gcr_importer_get_type (void); -GcrImporter* gcr_importer_new (void); +GcrImporter* gcr_importer_new (void); -GcrImporter* gcr_importer_new_for_module (GP11Module *module); +GcrParser* gcr_importer_get_parser (GcrImporter *self); -GcrImporter* gcr_importer_new_for_module_funcs (gpointer pkcs11_funcs); +void gcr_importer_set_parser (GcrImporter *self, + GcrParser *parser); -void gcr_importer_set_slot (GcrImporter *self, - GP11Slot *slot); +struct _GP11Slot* gcr_importer_get_slot (GcrImporter *self); -void gcr_importer_set_slot_id (GcrImporter *self, - gulong slot_id); +void gcr_importer_set_slot (GcrImporter *self, + struct _GP11Slot *slot); -void gcr_importer_set_parser (GcrImporter *self, - GcrParser *parser); +GcrImporterPromptBehavior gcr_importer_get_prompt_behavior (GcrImporter *self); -void gcr_importer_set_window (GcrImporter *self, - GtkWindow *window); +void gcr_importer_set_prompt_behavior (GcrImporter *self, + GcrImporterPromptBehavior behavior); -void gcr_importer_set_prompt_behavior (GcrImporter *self, - GcrImporterPromptBehavior behavior); +gboolean gcr_importer_import (GcrImporter *self, + GInputStream *input, + GCancellable *cancel, + GError **error); -gboolean gcr_importer_import_data (GcrImporter *self, - const guchar *data, - gsize n_data, - GError *error); +void gcr_importer_import_async (GcrImporter *self, + GInputStream *input, + GCancellable *cancel, + GAsyncReadyCallback callback, + gpointer user_data); -gboolean gcr_importer_import_file (GcrImporter *self, - const gchar *filename, - GError *error); +gboolean gcr_importer_import_finish (GcrImporter *self, + GAsyncResult *res, + GError **error); #endif /* __GCR_IMPORTER_H__ */ diff --git a/gcr/gcr-internal.h b/gcr/gcr-internal.h index daad990..a8a4651 100644 --- a/gcr/gcr-internal.h +++ b/gcr/gcr-internal.h @@ -1,8 +1,14 @@ #ifndef GCR_INTERNAL_H_ #define GCR_INTERNAL_H_ +#include "gcr.h" + +#include "gp11/gp11.h" + #include <glib.h> -void _gcr_initialize (void); +void _gcr_initialize (void); + +GList* _gcr_get_pkcs11_modules (void); #endif /* GCR_INTERNAL_H_ */ diff --git a/gcr/gcr-internal.c b/gcr/gcr-library.c index 8daddee..41356b5 100644 --- a/gcr/gcr-internal.c +++ b/gcr/gcr-library.c @@ -21,12 +21,95 @@ #include "config.h" +#include "gcr.h" +#include "gcr-types.h" #include "gcr-internal.h" #include "egg/egg-secure-memory.h" #include <gcrypt.h> +static GList *all_modules = NULL; + +GQuark +gcr_data_error_get_domain (void) +{ + static GQuark domain = 0; + if (domain == 0) + domain = g_quark_from_static_string ("gcr-parser-error"); + return domain; +} + +/* ----------------------------------------------------------------------------- + * MEMORY + */ + +static gboolean do_warning = TRUE; +#define WARNING "couldn't allocate secure memory to keep passwords " \ + "and or keys from being written to the disk" + +#define ABORTMSG "The GNOME_KEYRING_PARANOID environment variable was set. " \ + "Exiting..." + +static G_LOCK_DEFINE (memory_lock); + +/* + * These are called from egg-secure-memory.c to provide appropriate + * locking for memory between threads + */ + +void +egg_memory_lock (void) +{ + G_LOCK (memory_lock); +} + +void +egg_memory_unlock (void) +{ + G_UNLOCK (memory_lock); +} + +void* +egg_memory_fallback (void *p, unsigned long sz) +{ + const gchar *env; + + /* We were asked to free memory */ + if (!sz) { + g_free (p); + return NULL; + } + + /* We were asked to allocate */ + if (!p) { + if (do_warning) { + g_message (WARNING); + do_warning = FALSE; + } + + env = g_getenv ("GNOME_KEYRING_PARANOID"); + if (env && *env) + g_error (ABORTMSG); + + return g_malloc0 (sz); + } + + /* + * Reallocation is a bit of a gray area, as we can be asked + * by external libraries (like libgcrypt) to reallocate a + * non-secure block into secure memory. We cannot satisfy + * this request (as we don't know the size of the original + * block) so we just try our best here. + */ + + return g_realloc (p, sz); +} + +/* ------------------------------------------------------------------------------ + * GCRYPT HOOKS + */ + static void log_handler (gpointer unused, int unknown, const gchar *msg, va_list va) { @@ -88,10 +171,12 @@ static struct gcry_thread_cbs glib_thread_cbs = { void _gcr_initialize (void) { - static gsize gcrypt_initialized = FALSE; + static volatile gsize gcr_initialized = 0; + GP11Module *module; + GError *error = NULL; unsigned seed; - if (g_once_init_enter (&gcrypt_initialized)) { + if (g_once_init_enter (&gcr_initialized)) { /* Only initialize libgcrypt if it hasn't already been initialized */ if (!gcry_control (GCRYCTL_INITIALIZATION_FINISHED_P)) { @@ -111,6 +196,20 @@ _gcr_initialize (void) gcry_create_nonce (&seed, sizeof (seed)); srand (seed); - g_once_init_leave (&gcrypt_initialized, 1); + /* TODO: This needs reworking for multiple modules */ + module = gp11_module_initialize (PKCS11_MODULE_PATH, NULL, &error); + if (module) + all_modules = g_list_prepend (all_modules, module); + else + g_warning ("couldn't initialize PKCS#11 module: %s", + error && error->message ? error->message : ""); + + g_once_init_leave (&gcr_initialized, 1); } } + +GList* +_gcr_get_pkcs11_modules (void) +{ + return all_modules; +} diff --git a/gcr/gcr-parser.c b/gcr/gcr-parser.c index 7348788..505f9f9 100644 --- a/gcr/gcr-parser.c +++ b/gcr/gcr-parser.c @@ -263,7 +263,7 @@ enum_next_password (GcrParser *self, PasswordState *state, const gchar **passwor ++state->ask_state; if (!result) - return GCR_PARSE_CANCELLED; + return GCR_ERROR_CANCELLED; /* Return any passwords added */ if (state->seen < self->pv->passwords->len) { @@ -273,7 +273,7 @@ enum_next_password (GcrParser *self, PasswordState *state, const gchar **passwor return SUCCESS; } - return GCR_PARSE_LOCKED; + return GCR_ERROR_LOCKED; } static void @@ -293,7 +293,7 @@ parsed_fire (GcrParser *self) static gint parse_der_private_key_rsa (GcrParser *self, const guchar *data, gsize n_data) { - gint res = GCR_PARSE_UNRECOGNIZED; + gint res = GCR_ERROR_UNRECOGNIZED; ASN1_TYPE asn = ASN1_TYPE_EMPTY; guint version; @@ -303,14 +303,14 @@ parse_der_private_key_rsa (GcrParser *self, const guchar *data, gsize n_data) parsed_clear (self, CKO_PRIVATE_KEY); parsed_ulong (self, CKA_KEY_TYPE, CKK_RSA); - res = GCR_PARSE_FAILURE; + res = GCR_ERROR_FAILURE; if (!egg_asn1_read_uint (asn, "version", &version)) goto done; /* We only support simple version */ if (version != 0) { - res = GCR_PARSE_UNRECOGNIZED; + res = GCR_ERROR_UNRECOGNIZED; g_message ("unsupported version of RSA key: %u", version); goto done; } @@ -330,7 +330,7 @@ done: if (asn) asn1_delete_structure (&asn); - if (res == GCR_PARSE_FAILURE) + if (res == GCR_ERROR_FAILURE) g_message ("invalid RSA key"); return res; @@ -343,7 +343,7 @@ done: static gint parse_der_private_key_dsa (GcrParser *self, const guchar *data, gsize n_data) { - gint ret = GCR_PARSE_UNRECOGNIZED; + gint ret = GCR_ERROR_UNRECOGNIZED; int res; ASN1_TYPE asn; @@ -353,7 +353,7 @@ parse_der_private_key_dsa (GcrParser *self, const guchar *data, gsize n_data) parsed_clear (self, CKO_PRIVATE_KEY); parsed_ulong (self, CKA_KEY_TYPE, CKK_DSA); - res = GCR_PARSE_FAILURE; + res = GCR_ERROR_FAILURE; if (!parsed_asn1_attribute (self, asn, data, n_data, "p", CKA_PRIME) || !parsed_asn1_attribute (self, asn, data, n_data, "q", CKA_SUBPRIME) || @@ -368,7 +368,7 @@ done: if (asn) asn1_delete_structure (&asn); - if (ret == GCR_PARSE_FAILURE) + if (ret == GCR_ERROR_FAILURE) g_message ("invalid DSA key"); return ret; @@ -378,7 +378,7 @@ static gint parse_der_private_key_dsa_parts (GcrParser *self, const guchar *keydata, gsize n_keydata, const guchar *params, gsize n_params) { - gint ret = GCR_PARSE_UNRECOGNIZED; + gint ret = GCR_ERROR_UNRECOGNIZED; int res; ASN1_TYPE asn_params = ASN1_TYPE_EMPTY; ASN1_TYPE asn_key = ASN1_TYPE_EMPTY; @@ -390,7 +390,7 @@ parse_der_private_key_dsa_parts (GcrParser *self, const guchar *keydata, gsize n parsed_clear (self, CKO_PRIVATE_KEY); parsed_ulong (self, CKA_KEY_TYPE, CKK_DSA); - res = GCR_PARSE_FAILURE; + res = GCR_ERROR_FAILURE; if (!parsed_asn1_attribute (self, asn_params, params, n_params, "p", CKA_PRIME) || !parsed_asn1_attribute (self, asn_params, params, n_params, "q", CKA_SUBPRIME) || @@ -407,7 +407,7 @@ done: if (asn_params) asn1_delete_structure (&asn_params); - if (ret == GCR_PARSE_FAILURE) + if (ret == GCR_ERROR_FAILURE) g_message ("invalid DSA key"); return ret; @@ -423,7 +423,7 @@ parse_der_private_key (GcrParser *self, const guchar *data, gsize n_data) gint res; res = parse_der_private_key_rsa (self, data, n_data); - if (res == GCR_PARSE_UNRECOGNIZED) + if (res == GCR_ERROR_UNRECOGNIZED) res = parse_der_private_key_dsa (self, data, n_data); return res; @@ -445,13 +445,13 @@ parse_der_pkcs8_plain (GcrParser *self, const guchar *data, gsize n_data) const guchar *params; gsize n_params; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-8-PrivateKeyInfo", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; key_type = GP11_INVALID; key_algo = egg_asn1_read_oid (asn, "privateKeyAlgorithm.algorithm"); @@ -463,7 +463,7 @@ parse_der_pkcs8_plain (GcrParser *self, const guchar *data, gsize n_data) key_type = CKK_DSA; if (key_type == GP11_INVALID) { - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; goto done; } @@ -487,17 +487,17 @@ done: ret = parse_der_private_key_dsa (self, keydata, n_keydata); /* Otherwise try the two part format that everyone seems to like */ - if (ret == GCR_PARSE_UNRECOGNIZED && params && n_params) + if (ret == GCR_ERROR_UNRECOGNIZED && params && n_params) ret = parse_der_private_key_dsa_parts (self, keydata, n_keydata, params, n_params); break; default: g_message ("invalid or unsupported key type in PKCS#8 key"); - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; break; }; - } else if (ret == GCR_PARSE_FAILURE) { + } else if (ret == GCR_ERROR_FAILURE) { g_message ("invalid PKCS#8 key"); } @@ -521,13 +521,13 @@ parse_der_pkcs8_encrypted (GcrParser *self, const guchar *data, gsize n_data) const gchar *password; gint l; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-8-EncryptedPrivateKeyInfo", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; /* Figure out the type of encryption */ scheme = egg_asn1_read_oid (asn, "encryptionAlgorithm.algorithm"); @@ -576,7 +576,7 @@ parse_der_pkcs8_encrypted (GcrParser *self, const guchar *data, gsize n_data) egg_secure_free (crypted); crypted = NULL; - if (r != GCR_PARSE_UNRECOGNIZED) { + if (r != GCR_ERROR_UNRECOGNIZED) { ret = r; break; } @@ -600,7 +600,7 @@ parse_der_pkcs8 (GcrParser *self, const guchar *data, gsize n_data) gint ret; ret = parse_der_pkcs8_plain (self, data, n_data); - if (ret == GCR_PARSE_UNRECOGNIZED) + if (ret == GCR_ERROR_UNRECOGNIZED) ret = parse_der_pkcs8_encrypted (self, data, n_data); return ret; @@ -618,7 +618,7 @@ parse_der_certificate (GcrParser *self, const guchar *data, gsize n_data) asn = egg_asn1_decode ("PKIX1.Certificate", data, n_data); if (asn == NULL) - return GCR_PARSE_UNRECOGNIZED; + return GCR_ERROR_UNRECOGNIZED; parsed_clear (self, CKO_CERTIFICATE); parsed_ulong (self, CKA_CERTIFICATE_TYPE, CKC_X_509); @@ -651,13 +651,13 @@ handle_pkcs7_signed_data (GcrParser *self, const guchar *data, gsize n_data) gsize n_certificate; int i; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-7-SignedData", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; for (i = 0; TRUE; ++i) { @@ -694,13 +694,13 @@ parse_der_pkcs7 (GcrParser *self, const guchar *data, gsize n_data) gsize n_content; GQuark oid; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-7-ContentInfo", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; oid = egg_asn1_read_oid (asn, "contentType"); if (!oid) @@ -736,13 +736,13 @@ handle_pkcs12_cert_bag (GcrParser *self, const guchar *data, gsize n_data) gsize n_certificate; gint ret; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-12-CertBag", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; certificate = egg_asn1_read_content (asn, data, n_data, "certValue", &n_certificate); if (!certificate) @@ -775,13 +775,13 @@ handle_pkcs12_bag (GcrParser *self, const guchar *data, gsize n_data) const guchar *element; gsize n_element; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-12-SafeContents", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; /* Get the number of elements in this bag */ res = asn1_number_of_elements (asn, "", &count); @@ -822,10 +822,10 @@ handle_pkcs12_bag (GcrParser *self, const guchar *data, gsize n_data) /* TODO: OID_PKCS12_BAG_CRL */ } else { - r = GCR_PARSE_UNRECOGNIZED; + r = GCR_ERROR_UNRECOGNIZED; } - if (r == GCR_PARSE_FAILURE || r == GCR_PARSE_CANCELLED) { + if (r == GCR_ERROR_FAILURE || r == GCR_ERROR_CANCELLED) { ret = r; goto done; } @@ -855,13 +855,13 @@ handle_pkcs12_encrypted_bag (GcrParser *self, const guchar *data, gsize n_data) gint ret, r; gint l; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-7-EncryptedData", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; /* Check the encryption schema OID */ scheme = egg_asn1_read_oid (asn, "encryptedContentInfo.contentEncryptionAlgorithm.algorithm"); @@ -887,7 +887,7 @@ handle_pkcs12_encrypted_bag (GcrParser *self, const guchar *data, gsize n_data) /* Parse the encryption stuff into a cipher. */ if (!egg_symkey_read_cipher (scheme, password, -1, params, n_params, &cih)) { - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; goto done; } @@ -915,7 +915,7 @@ handle_pkcs12_encrypted_bag (GcrParser *self, const guchar *data, gsize n_data) egg_secure_free (crypted); crypted = NULL; - if (r != GCR_PARSE_UNRECOGNIZED) { + if (r != GCR_ERROR_UNRECOGNIZED) { ret = r; break; } @@ -944,13 +944,13 @@ handle_pkcs12_safe (GcrParser *self, const guchar *data, gsize n_data) GQuark oid; guint i; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-12-AuthenticatedSafe", data, n_data); if (!asn) goto done; - ret = GCR_PARSE_FAILURE; + ret = GCR_ERROR_FAILURE; /* * Inside each PKCS12 safe there are multiple bags. @@ -992,10 +992,10 @@ handle_pkcs12_safe (GcrParser *self, const guchar *data, gsize n_data) /* Hmmmm, not sure what this is */ } else { g_warning ("unrecognized type of safe content in pkcs12: %s", g_quark_to_string (oid)); - r = GCR_PARSE_UNRECOGNIZED; + r = GCR_ERROR_UNRECOGNIZED; } - if (r == GCR_PARSE_FAILURE || r == GCR_PARSE_CANCELLED) { + if (r == GCR_ERROR_FAILURE || r == GCR_ERROR_CANCELLED) { ret = r; goto done; } @@ -1019,7 +1019,7 @@ parse_der_pkcs12 (GcrParser *self, const guchar *data, gsize n_data) gsize n_content; GQuark oid; - ret = GCR_PARSE_UNRECOGNIZED; + ret = GCR_ERROR_UNRECOGNIZED; asn = egg_asn1_decode ("PKIX1.pkcs-12-PFX", data, n_data); if (!asn) @@ -1091,14 +1091,14 @@ handle_plain_pem (GcrParser *self, GQuark type, gint subformat, format_id = GCR_FORMAT_DER_PKCS12; else - return GCR_PARSE_UNRECOGNIZED; + return GCR_ERROR_UNRECOGNIZED; if (subformat != 0 && subformat != format_id) - return GCR_PARSE_UNRECOGNIZED; + return GCR_ERROR_UNRECOGNIZED; format = parser_format_lookup (format_id); if (format == NULL) - return GCR_PARSE_UNRECOGNIZED; + return GCR_ERROR_UNRECOGNIZED; return (format->function) (self, data, n_data); } @@ -1143,7 +1143,7 @@ handle_encrypted_pem (GcrParser *self, GQuark type, gint subformat, val = g_hash_table_lookup (headers, "DEK-Info"); if (!val) { g_message ("missing encryption header"); - return GCR_PARSE_FAILURE; + return GCR_ERROR_FAILURE; } /* Fill in information necessary for prompting */ @@ -1162,7 +1162,7 @@ handle_encrypted_pem (GcrParser *self, GQuark type, gint subformat, ret = egg_openssl_decrypt_block (val, password, -1, data, n_data, &decrypted, &n_decrypted); if (!ret) - return GCR_PARSE_FAILURE; + return GCR_ERROR_FAILURE; g_assert (decrypted); @@ -1176,11 +1176,11 @@ handle_encrypted_pem (GcrParser *self, GQuark type, gint subformat, egg_secure_free (decrypted); /* Unrecognized is a bad password */ - if (res != GCR_PARSE_UNRECOGNIZED) + if (res != GCR_ERROR_UNRECOGNIZED) return res; } - return GCR_PARSE_FAILURE; + return GCR_ERROR_FAILURE; } typedef struct { @@ -1194,12 +1194,12 @@ handle_pem_data (GQuark type, const guchar *data, gsize n_data, GHashTable *headers, gpointer user_data) { HandlePemArgs *args = (HandlePemArgs*)user_data; - gint res = GCR_PARSE_FAILURE; + gint res = GCR_ERROR_FAILURE; gboolean encrypted = FALSE; const gchar *val; /* Something already failed to parse */ - if (args->result == GCR_PARSE_FAILURE) + if (args->result == GCR_ERROR_FAILURE) return; /* See if it's encrypted PEM all openssl like*/ @@ -1216,8 +1216,8 @@ handle_pem_data (GQuark type, const guchar *data, gsize n_data, res = handle_plain_pem (args->parser, type, args->subformat, data, n_data); - if (res != GCR_PARSE_UNRECOGNIZED) { - if (args->result == GCR_PARSE_UNRECOGNIZED) + if (res != GCR_ERROR_UNRECOGNIZED) { + if (args->result == GCR_ERROR_UNRECOGNIZED) args->result = res; else if (res > args->result) args->result = res; @@ -1227,16 +1227,16 @@ handle_pem_data (GQuark type, const guchar *data, gsize n_data, static gint handle_pem_format (GcrParser *self, gint subformat, const guchar *data, gsize n_data) { - HandlePemArgs ctx = { self, GCR_PARSE_UNRECOGNIZED, subformat }; + HandlePemArgs ctx = { self, GCR_ERROR_UNRECOGNIZED, subformat }; guint found; if (n_data == 0) - return GCR_PARSE_UNRECOGNIZED; + return GCR_ERROR_UNRECOGNIZED; found = egg_openssl_pem_parse (data, n_data, handle_pem_data, &ctx); if (found == 0) - return GCR_PARSE_UNRECOGNIZED; + return GCR_ERROR_UNRECOGNIZED; return ctx.result; } @@ -1375,7 +1375,7 @@ parser_format_foreach (gpointer key, gpointer value, gpointer data) g_assert (GCR_IS_PARSER (args->parser)); result = (format->function) (args->parser, args->data, args->n_data); - if (result != GCR_PARSE_UNRECOGNIZED) { + if (result != GCR_ERROR_UNRECOGNIZED) { args->result = result; return TRUE; } @@ -1534,15 +1534,6 @@ gcr_parser_new (void) return g_object_new (GCR_TYPE_PARSER, NULL); } -GQuark -gcr_parser_get_error_domain (void) -{ - static GQuark domain = 0; - if (domain == 0) - domain = g_quark_from_static_string ("gcr-parser-error"); - return domain; -} - void gcr_parser_add_password (GcrParser *self, const gchar *password) { @@ -1554,7 +1545,7 @@ gboolean gcr_parser_parse_data (GcrParser *self, const guchar *data, gsize n_data, GError **err) { - ForeachArgs args = { self, data, n_data, GCR_PARSE_UNRECOGNIZED }; + ForeachArgs args = { self, data, n_data, GCR_ERROR_UNRECOGNIZED }; const gchar *message; gint i; @@ -1578,16 +1569,16 @@ gcr_parser_parse_data (GcrParser *self, const guchar *data, switch (args.result) { case SUCCESS: return TRUE; - case GCR_PARSE_CANCELLED: + case GCR_ERROR_CANCELLED: message = _("The operation was cancelled"); break; - case GCR_PARSE_UNRECOGNIZED: + case GCR_ERROR_UNRECOGNIZED: message = _("Unrecognized or unsupported data."); break; - case GCR_PARSE_FAILURE: + case GCR_ERROR_FAILURE: message = _("Could not parse invalid or corrupted data."); break; - case GCR_PARSE_LOCKED: + case GCR_ERROR_LOCKED: message = _("The data is locked"); break; default: @@ -1595,37 +1586,11 @@ gcr_parser_parse_data (GcrParser *self, const guchar *data, break; }; - g_set_error_literal (err, GCR_PARSER_ERROR, args.result, message); + g_set_error_literal (err, GCR_DATA_ERROR, args.result, message); return FALSE; } gboolean -gcr_parser_parse_file (GcrParser *self, const gchar *filename, GError **err) -{ - GMappedFile *mapped; - gboolean ret; - const guchar *data; - gsize n_data; - - g_return_val_if_fail (GCR_IS_PARSER (self), FALSE); - g_return_val_if_fail (filename, FALSE); - g_return_val_if_fail (!err || !*err, FALSE); - - mapped = g_mapped_file_new (filename, FALSE, err); - if (mapped == NULL) - return FALSE; - - data = (const guchar*)g_mapped_file_get_contents (mapped); - n_data = g_mapped_file_get_length (mapped); - - ret = gcr_parser_parse_data (self, data, n_data, err); - - g_mapped_file_free (mapped); - - return ret; -} - -gboolean gcr_parser_format_enable (GcrParser *self, gint format_id) { ParserFormat *format; diff --git a/gcr/gcr-parser.h b/gcr/gcr-parser.h index 4038cf0..2f5be35 100644 --- a/gcr/gcr-parser.h +++ b/gcr/gcr-parser.h @@ -22,12 +22,12 @@ #ifndef __GCR_PARSER_H__ #define __GCR_PARSER_H__ +#include "gcr.h" + #include <glib-object.h> #include "gcr-types.h" -#define GCR_PARSER_ERROR (gcr_parser_get_error_domain ()) - #define GCR_TYPE_PARSER (gcr_parser_get_type ()) #define GCR_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_PARSER, GcrParser)) #define GCR_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_PARSER, GcrParserClass)) @@ -56,37 +56,31 @@ struct _GcrParserClass { void (*parsed) (GcrParser *self); }; -GType gcr_parser_get_type (void); - -GQuark gcr_parser_get_error_domain (void) G_GNUC_CONST; - -GcrParser* gcr_parser_new (void); +GType gcr_parser_get_type (void); -gboolean gcr_parser_format_enable (GcrParser *self, - gint format); +GcrParser* gcr_parser_new (void); -gboolean gcr_parser_format_disable (GcrParser *self, - gint format); +gboolean gcr_parser_format_enable (GcrParser *self, + gint format); -gboolean gcr_parser_format_supported (GcrParser *self, - gint format); +gboolean gcr_parser_format_disable (GcrParser *self, + gint format); -gboolean gcr_parser_parse_data (GcrParser *self, - const guchar *data, - gsize n_data, - GError **err); +gboolean gcr_parser_format_supported (GcrParser *self, + gint format); -gboolean gcr_parser_parse_file (GcrParser *self, - const gchar *filename, - GError **err); +gboolean gcr_parser_parse_data (GcrParser *self, + const guchar *data, + gsize n_data, + GError **err); -void gcr_parser_add_password (GcrParser *self, - const gchar *password); +void gcr_parser_add_password (GcrParser *self, + const gchar *password); -const gchar* gcr_parser_get_parsed_label (GcrParser *self); +const gchar* gcr_parser_get_parsed_label (GcrParser *self); -const gchar* gcr_parser_get_parsed_description (GcrParser *self); +const gchar* gcr_parser_get_parsed_description (GcrParser *self); -GP11Attributes* gcr_parser_get_parsed_attributes (GcrParser *self); +struct _GP11Attributes* gcr_parser_get_parsed_attributes (GcrParser *self); #endif /* __GCR_PARSER_H__ */ diff --git a/gcr/gcr-types.h b/gcr/gcr-types.h index 5f12892..ca2ed91 100644 --- a/gcr/gcr-types.h +++ b/gcr/gcr-types.h @@ -1,11 +1,15 @@ #ifndef GCRTYPES_H_ #define GCRTYPES_H_ +#define GCR_DATA_ERROR (gcr_data_error_get_domain ()) + +GQuark gcr_data_error_get_domain (void) G_GNUC_CONST; + enum { - GCR_PARSE_FAILURE = -1, - GCR_PARSE_UNRECOGNIZED = 1, - GCR_PARSE_CANCELLED = 2, - GCR_PARSE_LOCKED = 3 + GCR_ERROR_FAILURE = -1, + GCR_ERROR_UNRECOGNIZED = 1, + GCR_ERROR_CANCELLED = 2, + GCR_ERROR_LOCKED = 3 }; enum { @@ -35,11 +39,8 @@ enum { GCR_FORMAT_PEM_PKCS12 }; -#ifndef GP11_H - /* Forward declare some of the GP11 objects */ -typedef struct _GP11Attributes GP11Attributes; - -#endif /* GP11_H */ +struct _GP11Attributes; +struct _GP11Slot; #endif /* GCRTYPES_H_ */ diff --git a/gcr/gcr.h b/gcr/gcr.h new file mode 100644 index 0000000..4cf2f23 --- /dev/null +++ b/gcr/gcr.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef __GCR_H__ +#define __GCR_H__ + +#include <glib.h> + +#ifndef GCR_API_SUBJECT_TO_CHANGE +#error "This API has not yet reached stability." +#endif + +struct _GP11Slot; + +#ifdef UNIMPLEMENTED +enum { + GCR_INIT_NO_MODULES = 0x01, +}; + +void gcr_initialize (guint flags); + +void gcr_modules_register_loaded (gpointer funcs); + +gboolean gcr_modules_register_file (const gchar *module_path, + GError *error); +#endif /* UNIMPLEMENTED */ + +#endif /* __GCR_H__ */ diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am index 63b89d8..4306474 100644 --- a/gcr/tests/Makefile.am +++ b/gcr/tests/Makefile.am @@ -8,6 +8,10 @@ UNIT_PROMPT = UNIT_LIBS = \ $(top_builddir)/gcr/libgcr.la \ $(top_builddir)/egg/libegg.la \ + $(top_builddir)/egg/libegg-secure-entry.la \ $(top_builddir)/gp11/libgp11.la +UNIT_FLAGS = \ + -DGCR_API_SUBJECT_TO_CHANGE + include $(top_srcdir)/tests/gtest.make diff --git a/gcr/tests/unit-test-parser.c b/gcr/tests/unit-test-parser.c index 70c11bc..3a60489 100644 --- a/gcr/tests/unit-test-parser.c +++ b/gcr/tests/unit-test-parser.c @@ -29,6 +29,8 @@ #include "gcr/gcr-parser.h" +#include "gp11/gp11.h" + #include <glib.h> #include <gcrypt.h> #include <libtasn1.h> |