diff options
author | guidog <guidog@517b70f8-ed25-0410-8bf6-f5db08f7b76e> | 2009-01-10 13:25:23 +0000 |
---|---|---|
committer | guidog <guidog@517b70f8-ed25-0410-8bf6-f5db08f7b76e> | 2009-01-10 13:25:23 +0000 |
commit | 2128395ed8d11098807c82a39dc5c9717a573bbe (patch) | |
tree | 7b67f54cd569a59d99c158ad67eaf3c052814aee | |
parent | aca6027e6ddb0194baf4da1d7453398042054bc0 (diff) |
add gtksecentry/secmem from gpg pinentry
code is licensed GPLv2
git-svn-id: http://svn.gnome.org/svn/krb5-auth-dialog/trunk@101 517b70f8-ed25-0410-8bf6-f5db08f7b76e
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | acinclude.m4 | 117 | ||||
-rw-r--r-- | configure.ac | 22 | ||||
-rw-r--r-- | gtksecentry/Makefile.am | 7 | ||||
-rw-r--r-- | gtksecentry/gtksecentry.c | 3412 | ||||
-rw-r--r-- | gtksecentry/gtksecentry.h | 181 | ||||
-rw-r--r-- | secmem/Makefile.am | 31 | ||||
-rw-r--r-- | secmem/Manifest | 7 | ||||
-rw-r--r-- | secmem/memory.h | 40 | ||||
-rw-r--r-- | secmem/secmem-util.h | 3 | ||||
-rw-r--r-- | secmem/secmem.c | 448 | ||||
-rw-r--r-- | secmem/util.c | 139 | ||||
-rw-r--r-- | secmem/util.h | 66 | ||||
-rw-r--r-- | src/Makefile.am | 2 |
15 files changed, 4488 insertions, 1 deletions
@@ -1,3 +1,15 @@ +Sun Jan 4 14:39:16 CET 2009 Guido Günther<agx@sigxcpu.org> + + add gtksecentry/secmem from gpg pinentry + * Makefile.am (SUBDIRS): add gtksecentry + * acinclude.m4: add GNUPG_CHECK_TYPEDEF, GNUPG_CHECK_MLOCK + * gtksecentry/{Makefile.am,gtksecentry.c,gtksecentry.h}: new files + * secmem/{Makfile.am,Manifest,memory.h,secmem{-util.h,.c},util.[ch]}: + new files + * configure.ac: checks for secmem + * src/Makefile.am (krb5_auth_dialog_LDADD): add libgtksecentry.a, + libsecmem.a + Sun Jan 4 14:28:06 CET 2009 Guido Günther <agx@sigxcpu.org> add a tray icon diff --git a/Makefile.am b/Makefile.am index ce1e5c4..8db82cc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = src po etpo icons +SUBDIRS = secmem gtksecentry src po etpo icons EXTRA_DIST = \ intltool-extract.in \ diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..7dfad70 --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,117 @@ +dnl Autoconf macros used by PINENTRY +dnl +dnl Copyright (C) 2002 g10 Code GmbH +dnl +dnl +dnl GNUPG_CHECK_TYPEDEF(TYPE, HAVE_NAME) +dnl Check whether a typedef exists and create a #define $2 if it exists +dnl +AC_DEFUN([GNUPG_CHECK_TYPEDEF], + [ AC_MSG_CHECKING(for $1 typedef) + AC_CACHE_VAL(gnupg_cv_typedef_$1, + [AC_TRY_COMPILE([#define _GNU_SOURCE 1 + #include <stdlib.h> + #include <sys/types.h>], [ + #undef $1 + int a = sizeof($1); + ], gnupg_cv_typedef_$1=yes, gnupg_cv_typedef_$1=no )]) + AC_MSG_RESULT($gnupg_cv_typedef_$1) + if test "$gnupg_cv_typedef_$1" = yes; then + AC_DEFINE($2,1,[Defined if a `]$1[' is typedef'd]) + fi + ]) + + +###################################################################### +# Check whether mlock is broken (hpux 10.20 raises a SIGBUS if mlock +# is not called from uid 0 (not tested whether uid 0 works) +# For DECs Tru64 we have also to check whether mlock is in librt +# mlock is there a macro using memlk() +###################################################################### +dnl GNUPG_CHECK_MLOCK +dnl +define([GNUPG_CHECK_MLOCK], + [ AC_CHECK_FUNCS(mlock) + if test "$ac_cv_func_mlock" = "no"; then + AC_CHECK_HEADERS(sys/mman.h) + if test "$ac_cv_header_sys_mman_h" = "yes"; then + # Add librt to LIBS: + AC_CHECK_LIB(rt, memlk) + AC_CACHE_CHECK([whether mlock is in sys/mman.h], + gnupg_cv_mlock_is_in_sys_mman, + [AC_TRY_LINK([ + #include <assert.h> + #ifdef HAVE_SYS_MMAN_H + #include <sys/mman.h> + #endif + ], [ + int i; + + /* glibc defines this for functions which it implements + * to always fail with ENOSYS. Some functions are actually + * named something starting with __ and the normal name + * is an alias. */ + #if defined (__stub_mlock) || defined (__stub___mlock) + choke me + #else + mlock(&i, 4); + #endif + ; return 0; + ], + gnupg_cv_mlock_is_in_sys_mman=yes, + gnupg_cv_mlock_is_in_sys_mman=no)]) + if test "$gnupg_cv_mlock_is_in_sys_mman" = "yes"; then + AC_DEFINE(HAVE_MLOCK,1, + [Defined if the system supports an mlock() call]) + fi + fi + fi + if test "$ac_cv_func_mlock" = "yes"; then + AC_MSG_CHECKING(whether mlock is broken) + AC_CACHE_VAL(gnupg_cv_have_broken_mlock, + AC_TRY_RUN([ + #include <stdlib.h> + #include <unistd.h> + #include <errno.h> + #include <sys/mman.h> + #include <sys/types.h> + #include <fcntl.h> + + int main() + { + char *pool; + int err; + long int pgsize = getpagesize(); + + pool = malloc( 4096 + pgsize ); + if( !pool ) + return 2; + pool += (pgsize - ((long int)pool % pgsize)); + + err = mlock( pool, 4096 ); + if( !err || errno == EPERM ) + return 0; /* okay */ + + return 1; /* hmmm */ + } + + ], + gnupg_cv_have_broken_mlock="no", + gnupg_cv_have_broken_mlock="yes", + gnupg_cv_have_broken_mlock="assume-no" + ) + ) + if test "$gnupg_cv_have_broken_mlock" = "yes"; then + AC_DEFINE(HAVE_BROKEN_MLOCK,1, + [Defined if the mlock() call does not work]) + AC_MSG_RESULT(yes) + AC_CHECK_FUNCS(plock) + else + if test "$gnupg_cv_have_broken_mlock" = "no"; then + AC_MSG_RESULT(no) + else + AC_MSG_RESULT(assuming no) + fi + fi + fi + ]) diff --git a/configure.ac b/configure.ac index 988e9fd..e947e91 100644 --- a/configure.ac +++ b/configure.ac @@ -137,6 +137,26 @@ AM_CONDITIONAL(HAVE_LIBNOTIFY, [test "x$with_libnotify" = "xyes"]) AC_SUBST(LIBNOTIFY_CFLAGS) AC_SUBST(LIBNOTIFY_LIBS) + +dnl secmem +dnl Checks for library functions. +AC_CHECK_FUNCS(seteuid stpcpy mmap) +GNUPG_CHECK_MLOCK +GNUPG_CHECK_TYPEDEF(byte, HAVE_BYTE_TYPEDEF) +GNUPG_CHECK_TYPEDEF(ulong, HAVE_ULONG_TYPEDEF) +dnl Check for libcap +AC_ARG_WITH([libcap], AC_HELP_STRING([--without-libcap], + [Disable support for capabilities library])) +if test "x$with_libcap" != "xno"; then + AC_PATH_PROG(SETCAP, setcap, :, "$PATH:/sbin:/usr/sbin") + AC_CHECK_LIB(cap, cap_set_proc, [ + AC_DEFINE(USE_CAPABILITIES,1,[The capabilities support library is installed]) + LIBCAP=-lcap + ]) +fi +AC_SUBST(LIBCAP) + + check_interval=30 AC_DEFINE_UNQUOTED(CREDENTIAL_CHECK_INTERVAL,[$check_interval], [Define the to number of seconds to wait between checks of @@ -165,6 +185,8 @@ Makefile krb5-auth-dialog.spec src/Makefile src/krb5-auth-dialog.1 +secmem/Makefile +gtksecentry/Makefile icons/Makefile etpo/Makefile po/Makefile.in diff --git a/gtksecentry/Makefile.am b/gtksecentry/Makefile.am new file mode 100644 index 0000000..4c15a5a --- /dev/null +++ b/gtksecentry/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = $(GTK_CFLAGS) \ + -I$(top_srcdir)/secmem -I$(top_srcdir)/gtksecentry + +noinst_LIBRARIES = libgtksecentry.a + +libgtksecentry_a_SOURCES = gtksecentry.c gtksecentry.h + diff --git a/gtksecentry/gtksecentry.c b/gtksecentry/gtksecentry.c new file mode 100644 index 0000000..938479c --- /dev/null +++ b/gtksecentry/gtksecentry.c @@ -0,0 +1,3412 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2004 Albrecht Dreß + * + * 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: + * + * 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. + */ + +#include <stdlib.h> +#include <string.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include "gtksecentry.h" +#include "memory.h" + +#ifndef _ +# include <libintl.h> +# define _(x) gettext(x) +#endif + +#define MIN_SECURE_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 +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/* GObject, GtkObject methods + */ +static void gtk_secure_entry_class_init(GtkSecureEntryClass * klass); +static void gtk_secure_entry_editable_init(GtkEditableClass * iface); +static void gtk_secure_entry_cell_editable_init(GtkCellEditableIface * + iface); +static void gtk_secure_entry_init(GtkSecureEntry * entry); +static void gtk_secure_entry_set_property(GObject * object, + guint prop_id, + const GValue * value, + GParamSpec * pspec); +static void gtk_secure_entry_get_property(GObject * object, + guint prop_id, + GValue * value, + GParamSpec * pspec); +static void gtk_secure_entry_finalize(GObject * object); + +/* GtkWidget methods + */ +static void gtk_secure_entry_realize(GtkWidget * widget); +static void gtk_secure_entry_unrealize(GtkWidget * widget); +static void gtk_secure_entry_size_request(GtkWidget * widget, + GtkRequisition * requisition); +static void gtk_secure_entry_size_allocate(GtkWidget * widget, + GtkAllocation * allocation); +static void gtk_secure_entry_draw_frame(GtkWidget * widget); +static gint gtk_secure_entry_expose(GtkWidget * widget, + GdkEventExpose * event); +static gint gtk_secure_entry_button_press(GtkWidget * widget, + GdkEventButton * event); +static gint gtk_secure_entry_button_release(GtkWidget * widget, + GdkEventButton * event); +static gint gtk_secure_entry_motion_notify(GtkWidget * widget, + GdkEventMotion * event); +static gint gtk_secure_entry_key_press(GtkWidget * widget, + GdkEventKey * event); +static gint gtk_secure_entry_key_release(GtkWidget * widget, + GdkEventKey * event); +static gint gtk_secure_entry_focus_in(GtkWidget * widget, + GdkEventFocus * event); +static gint gtk_secure_entry_focus_out(GtkWidget * widget, + GdkEventFocus * event); +static void gtk_secure_entry_grab_focus(GtkWidget * widget); +static void gtk_secure_entry_style_set(GtkWidget * widget, + GtkStyle * previous_style); +static void gtk_secure_entry_direction_changed(GtkWidget * widget, + GtkTextDirection + previous_dir); +static void gtk_secure_entry_state_changed(GtkWidget * widget, + GtkStateType previous_state); +static void gtk_secure_entry_screen_changed(GtkWidget * widget, + GdkScreen * old_screen); + +/* GtkEditable method implementations + */ +static void gtk_secure_entry_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, + gint * position); +static void gtk_secure_entry_delete_text(GtkEditable * editable, + gint start_pos, gint end_pos); +static void gtk_secure_entry_real_set_position(GtkEditable * editable, + gint position); +static gint gtk_secure_entry_get_position(GtkEditable * editable); +static void gtk_secure_entry_set_selection_bounds(GtkEditable * editable, + gint start, gint end); +static gboolean gtk_secure_entry_get_selection_bounds(GtkEditable * + editable, + gint * start, + gint * end); + +/* GtkCellEditable method implementations + */ +static void gtk_secure_entry_start_editing(GtkCellEditable * cell_editable, + GdkEvent * event); + +/* Default signal handlers + */ +static void gtk_secure_entry_real_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, + gint * position); +static void gtk_secure_entry_real_delete_text(GtkEditable * editable, + gint start_pos, + gint end_pos); +static void gtk_secure_entry_move_cursor(GtkSecureEntry * entry, + GtkMovementStep step, + gint count, + gboolean extend_selection); +static void gtk_secure_entry_insert_at_cursor(GtkSecureEntry * entry, + const gchar * str); +static void gtk_secure_entry_delete_from_cursor(GtkSecureEntry * entry, + GtkDeleteType type, + gint count); +static void gtk_secure_entry_real_activate(GtkSecureEntry * entry); + +static void gtk_secure_entry_keymap_direction_changed(GdkKeymap * keymap, + GtkSecureEntry * + entry); +/* IM Context Callbacks + */ +static void gtk_secure_entry_commit_cb(GtkIMContext * context, + const gchar * str, + GtkSecureEntry * entry); +static void gtk_secure_entry_preedit_changed_cb(GtkIMContext * context, + GtkSecureEntry * entry); +static gboolean gtk_secure_entry_retrieve_surrounding_cb(GtkIMContext * + context, + GtkSecureEntry * + entry); +static gboolean gtk_secure_entry_delete_surrounding_cb(GtkIMContext * + context, + gint offset, + gint n_chars, + GtkSecureEntry * + entry); + +/* Internal routines + */ +static void gtk_secure_entry_enter_text(GtkSecureEntry * entry, + const gchar * str); +static void gtk_secure_entry_set_positions(GtkSecureEntry * entry, + gint current_pos, + gint selection_bound); +static void gtk_secure_entry_draw_text(GtkSecureEntry * entry); +static void gtk_secure_entry_draw_cursor(GtkSecureEntry * entry); +static PangoLayout *gtk_secure_entry_ensure_layout(GtkSecureEntry * entry, + gboolean + include_preedit); +static void gtk_secure_entry_reset_layout(GtkSecureEntry * entry); +static void gtk_secure_entry_queue_draw(GtkSecureEntry * entry); +static void gtk_secure_entry_reset_im_context(GtkSecureEntry * entry); +static void gtk_secure_entry_recompute(GtkSecureEntry * entry); +static gint gtk_secure_entry_find_position(GtkSecureEntry * entry, gint x); +static void gtk_secure_entry_get_cursor_locations(GtkSecureEntry * entry, + gint * strong_x, + gint * weak_x); +static void gtk_secure_entry_adjust_scroll(GtkSecureEntry * entry); +static gint gtk_secure_entry_move_visually(GtkSecureEntry * editable, + gint start, gint count); +static gint gtk_secure_entry_move_logically(GtkSecureEntry * entry, + gint start, gint count); +static gboolean gtk_secure_entry_mnemonic_activate(GtkWidget * widget, + gboolean group_cycling); +static void gtk_secure_entry_state_changed(GtkWidget * widget, + GtkStateType previous_state); +static void gtk_secure_entry_check_cursor_blink(GtkSecureEntry * entry); +static void gtk_secure_entry_pend_cursor_blink(GtkSecureEntry * entry); +static void get_text_area_size(GtkSecureEntry * entry, + gint * x, + gint * y, gint * width, gint * height); +static void get_widget_window_size(GtkSecureEntry * 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; + +gboolean g_use_secure_mem = FALSE; + +# define g_sec_new(type, count) \ + ((type *) g_sec_malloc ((unsigned) sizeof (type) * (count))) + +#define WITH_SECURE_MEM(EXP) do { \ + gboolean tmp = g_use_secure_mem; \ + g_use_secure_mem = TRUE; \ + EXP; \ + g_use_secure_mem = tmp; \ + } while(0) + + +gpointer +g_malloc(gsize size) +{ + gpointer p; + + if (size == 0) + return NULL; + + if (g_use_secure_mem) + p = (gpointer) secmem_malloc(size); + else + p = (gpointer) malloc(size); + if (!p) + g_error("could not allocate %ld bytes", size); + + return p; +} + +gpointer +g_malloc0(gsize size) +{ + gpointer p; + + if (size == 0) + return NULL; + + if (g_use_secure_mem) { + p = (gpointer) secmem_malloc(size); + if (p) + memset(p, 0, size); + } else + p = (gpointer) calloc(size, 1); + if (!p) + g_error("could not allocate %ld bytes", size); + + return p; +} + +gpointer +g_realloc(gpointer mem, gsize size) +{ + gpointer p; + + if (size == 0) { + g_free(mem); + + return NULL; + } + + if (!mem) { + if (g_use_secure_mem) + p = (gpointer) secmem_malloc(size); + else + p = (gpointer) malloc(size); + } else { + if (g_use_secure_mem) { + g_assert(m_is_secure(mem)); + p = (gpointer) secmem_realloc(mem, size); + } else + p = (gpointer) realloc(mem, size); + } + + if (!p) + g_error("could not reallocate %lu bytes", (gulong) size); + + return p; +} + +void +g_free(gpointer mem) +{ + if (mem) { + if (m_is_secure(mem)) + secmem_free(mem); + else + free(mem); + } +} + +GType +gtk_secure_entry_get_type(void) +{ + static GType entry_type = 0; + + if (!entry_type) { + static const GTypeInfo entry_info = { + sizeof(GtkSecureEntryClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_secure_entry_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(GtkSecureEntry), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_secure_entry_init, + }; + + static const GInterfaceInfo editable_info = { + (GInterfaceInitFunc) gtk_secure_entry_editable_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + static const GInterfaceInfo cell_editable_info = { + (GInterfaceInitFunc) gtk_secure_entry_cell_editable_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + entry_type = + g_type_register_static(GTK_TYPE_WIDGET, "GtkSecureEntry", + &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 +gtk_secure_entry_class_init(GtkSecureEntryClass * 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 = gtk_secure_entry_finalize; + gobject_class->set_property = gtk_secure_entry_set_property; + gobject_class->get_property = gtk_secure_entry_get_property; + + widget_class->realize = gtk_secure_entry_realize; + widget_class->unrealize = gtk_secure_entry_unrealize; + widget_class->size_request = gtk_secure_entry_size_request; + widget_class->size_allocate = gtk_secure_entry_size_allocate; + widget_class->expose_event = gtk_secure_entry_expose; + widget_class->button_press_event = gtk_secure_entry_button_press; + widget_class->button_release_event = gtk_secure_entry_button_release; + widget_class->motion_notify_event = gtk_secure_entry_motion_notify; + widget_class->key_press_event = gtk_secure_entry_key_press; + widget_class->key_release_event = gtk_secure_entry_key_release; + widget_class->focus_in_event = gtk_secure_entry_focus_in; + widget_class->focus_out_event = gtk_secure_entry_focus_out; + widget_class->grab_focus = gtk_secure_entry_grab_focus; + widget_class->style_set = gtk_secure_entry_style_set; + widget_class->direction_changed = gtk_secure_entry_direction_changed; + widget_class->state_changed = gtk_secure_entry_state_changed; + widget_class->screen_changed = gtk_secure_entry_screen_changed; + widget_class->mnemonic_activate = gtk_secure_entry_mnemonic_activate; + + class->move_cursor = gtk_secure_entry_move_cursor; + class->insert_at_cursor = gtk_secure_entry_insert_at_cursor; + class->delete_from_cursor = gtk_secure_entry_delete_from_cursor; + class->activate = gtk_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)); + + /* Action signals */ + + signals[ACTIVATE] = + g_signal_new("activate", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkSecureEntryClass, 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(GtkSecureEntryClass, 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(GtkSecureEntryClass, + 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(GtkSecureEntryClass, + 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); + + gtk_settings_install_property(g_param_spec_boolean + ("gtk-entry-select-on-focus", + _("Select on focus"), + _ + ("Whether to select the contents of an entry when it is focused"), + TRUE, G_PARAM_READWRITE)); +} + +static void +gtk_secure_entry_editable_init(GtkEditableClass * iface) +{ + iface->do_insert_text = gtk_secure_entry_insert_text; + iface->do_delete_text = gtk_secure_entry_delete_text; + iface->insert_text = gtk_secure_entry_real_insert_text; + iface->delete_text = gtk_secure_entry_real_delete_text; + iface->set_selection_bounds = gtk_secure_entry_set_selection_bounds; + iface->get_selection_bounds = gtk_secure_entry_get_selection_bounds; + iface->set_position = gtk_secure_entry_real_set_position; + iface->get_position = gtk_secure_entry_get_position; +} + +static void +gtk_secure_entry_cell_editable_init(GtkCellEditableIface * iface) +{ + iface->start_editing = gtk_secure_entry_start_editing; +} + +static void +gtk_secure_entry_set_property(GObject * object, + guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(object); + + switch (prop_id) { + case PROP_MAX_LENGTH: + gtk_secure_entry_set_max_length(entry, g_value_get_int(value)); + break; + + case PROP_HAS_FRAME: + gtk_secure_entry_set_has_frame(entry, g_value_get_boolean(value)); + break; + + case PROP_INVISIBLE_CHAR: + gtk_secure_entry_set_invisible_char(entry, + g_value_get_uint(value)); + break; + + case PROP_ACTIVATES_DEFAULT: + gtk_secure_entry_set_activates_default(entry, + g_value_get_boolean(value)); + break; + + case PROP_WIDTH_CHARS: + gtk_secure_entry_set_width_chars(entry, g_value_get_int(value)); + break; + + case PROP_TEXT: + gtk_secure_entry_set_text(entry, g_value_get_string(value)); + break; + + case PROP_SCROLL_OFFSET: + case PROP_CURSOR_POSITION: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +gtk_secure_entry_get_property(GObject * object, + guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GtkSecureEntry *entry = GTK_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, gtk_secure_entry_get_text(entry)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +gtk_secure_entry_init(GtkSecureEntry * entry) +{ + GTK_WIDGET_SET_FLAGS(entry, GTK_CAN_FOCUS); + + entry->text_size = MIN_SIZE; + WITH_SECURE_MEM(entry->text = g_malloc(entry->text_size)); + entry->text[0] = '\0'; + + entry->invisible_char = '*'; + entry->width_chars = -1; + entry->is_cell_renderer = FALSE; + entry->editing_canceled = FALSE; + entry->has_frame = TRUE; + + /* 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(gtk_secure_entry_commit_cb), entry); + g_signal_connect(entry->im_context, "preedit_changed", + G_CALLBACK(gtk_secure_entry_preedit_changed_cb), + entry); + g_signal_connect(entry->im_context, "retrieve_surrounding", + G_CALLBACK(gtk_secure_entry_retrieve_surrounding_cb), + entry); + g_signal_connect(entry->im_context, "delete_surrounding", + G_CALLBACK(gtk_secure_entry_delete_surrounding_cb), + entry); +} + +static void +gtk_secure_entry_finalize(GObject * object) +{ + GtkSecureEntry *entry = GTK_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) + WITH_SECURE_MEM(g_free(entry->text)); + entry->text = NULL; + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void +gtk_secure_entry_realize(GtkWidget * widget) +{ + GtkSecureEntry *entry; + GtkEditable *editable; + GdkWindowAttr attributes; + gint attributes_mask; + + GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); + entry = GTK_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); + + gtk_secure_entry_adjust_scroll(entry); +} + +static void +gtk_secure_entry_unrealize(GtkWidget * widget) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_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(GtkSecureEntry * 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 +gtk_secure_entry_size_request(GtkWidget * widget, + GtkRequisition * requisition) +{ + GtkSecureEntry *entry = GTK_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_SECURE_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(GtkSecureEntry * 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(GtkSecureEntry * 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 +gtk_secure_entry_size_allocate(GtkWidget * widget, + GtkAllocation * allocation) +{ + GtkSecureEntry *entry = GTK_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); + + gtk_secure_entry_recompute(entry); + } +} + +static void +gtk_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 +gtk_secure_entry_expose(GtkWidget * widget, GdkEventExpose * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + if (widget->window == event->window) + gtk_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) + gtk_secure_entry_draw_cursor(GTK_SECURE_ENTRY(widget)); + + gtk_secure_entry_draw_text(GTK_SECURE_ENTRY(widget)); + } + + return FALSE; +} + +static gint +gtk_secure_entry_button_press(GtkWidget * widget, GdkEventButton * event) +{ + GtkSecureEntry *entry = GTK_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 = + gtk_secure_entry_find_position(entry, + event->x + entry->scroll_offset); + + if (event->button == 1) { + switch (event->type) { + case GDK_BUTTON_PRESS: + gtk_secure_entry_set_positions(entry, tmp_pos, tmp_pos); + break; + + default: + break; + } + + return TRUE; + } + + return FALSE; +} + +static gint +gtk_secure_entry_button_release(GtkWidget * widget, GdkEventButton * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + if (event->window != entry->text_area + || entry->button != event->button) + return FALSE; + + entry->button = 0; + + return TRUE; +} + +static gint +gtk_secure_entry_motion_notify(GtkWidget * widget, GdkEventMotion * event) +{ + GtkSecureEntry *entry = GTK_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 = + gtk_secure_entry_find_position(entry, + event->x + + entry->scroll_offset); + + gtk_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 +gtk_secure_entry_obscure_mouse_cursor(GtkSecureEntry * entry) +{ + if (entry->mouse_cursor_obscured) + return; + + set_invisible_cursor(entry->text_area); + + entry->mouse_cursor_obscured = TRUE; +} + +static gint +gtk_secure_entry_key_press(GtkWidget * widget, GdkEventKey * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_secure_entry_pend_cursor_blink(entry); + + if (gtk_im_context_filter_keypress(entry->im_context, event)) { + gtk_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 +gtk_secure_entry_key_release(GtkWidget * widget, GdkEventKey * event) +{ + GtkSecureEntry *entry = GTK_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 +gtk_secure_entry_focus_in(GtkWidget * widget, GdkEventFocus * event) +{ + GtkSecureEntry *entry = GTK_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(gtk_secure_entry_keymap_direction_changed), + entry); + + gtk_secure_entry_check_cursor_blink(entry); + + return FALSE; +} + +static gint +gtk_secure_entry_focus_out(GtkWidget * widget, GdkEventFocus * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_widget_queue_draw(widget); + + entry->need_im_reset = TRUE; + gtk_im_context_focus_out(entry->im_context); + + gtk_secure_entry_check_cursor_blink(entry); + + g_signal_handlers_disconnect_by_func(gdk_keymap_get_for_display + (gtk_widget_get_display(widget)), + gtk_secure_entry_keymap_direction_changed, + entry); + + return FALSE; +} + +static void +gtk_secure_entry_grab_focus(GtkWidget * widget) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + gboolean select_on_focus; + + GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_DEFAULT); + GTK_WIDGET_CLASS(parent_class)->grab_focus(widget); + + g_object_get(gtk_widget_get_settings(widget), + "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 +gtk_secure_entry_direction_changed(GtkWidget * widget, + GtkTextDirection previous_dir) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_secure_entry_recompute(entry); + + GTK_WIDGET_CLASS(parent_class)->direction_changed(widget, + previous_dir); +} + +static void +gtk_secure_entry_state_changed(GtkWidget * widget, + GtkStateType previous_state) +{ + GtkSecureEntry *entry = GTK_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 +gtk_secure_entry_screen_changed(GtkWidget * widget, GdkScreen * old_screen) +{ + gtk_secure_entry_recompute(GTK_SECURE_ENTRY(widget)); +} + +/* GtkEditable method implementations + */ +static void +gtk_secure_entry_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, gint * position) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + gchar *text; + + if (*position < 0 || *position > entry->text_length) + *position = entry->text_length; + + g_object_ref(editable); + + WITH_SECURE_MEM(text = g_new(gchar, new_text_length + 1)); + + text[new_text_length] = '\0'; + strncpy(text, new_text, new_text_length); + + g_signal_emit_by_name(editable, "insert_text", text, new_text_length, + position); + + WITH_SECURE_MEM(g_free(text)); + + g_object_unref(editable); +} + +static void +gtk_secure_entry_delete_text(GtkEditable * editable, + gint start_pos, gint end_pos) +{ + GtkSecureEntry *entry = GTK_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 +gtk_secure_entry_set_position_internal(GtkSecureEntry * 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) + gtk_secure_entry_reset_im_context(entry); + gtk_secure_entry_set_positions(entry, position, position); + } +} + +static void +gtk_secure_entry_real_set_position(GtkEditable * editable, gint position) +{ + gtk_secure_entry_set_position_internal(GTK_SECURE_ENTRY(editable), + position, TRUE); +} + +static gint +gtk_secure_entry_get_position(GtkEditable * editable) +{ + return GTK_SECURE_ENTRY(editable)->current_pos; +} + +static void +gtk_secure_entry_set_selection_bounds(GtkEditable * editable, + gint start, gint end) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + if (start < 0) + start = entry->text_length; + if (end < 0) + end = entry->text_length; + + gtk_secure_entry_reset_im_context(entry); + + gtk_secure_entry_set_positions(entry, + MIN(end, entry->text_length), + MIN(start, entry->text_length)); +} + +static gboolean +gtk_secure_entry_get_selection_bounds(GtkEditable * editable, + gint * start, gint * end) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + *start = entry->selection_bound; + *end = entry->current_pos; + + return (entry->selection_bound != entry->current_pos); +} + +static void +gtk_secure_entry_style_set(GtkWidget * widget, GtkStyle * previous_style) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_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(GtkSecureEntry * 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(GtkSecureEntry * 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 +gtk_secure_entry_start_editing(GtkCellEditable * cell_editable, + GdkEvent * event) +{ + GTK_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 +gtk_secure_entry_real_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, gint * position) +{ + gint _index; + gint n_chars; + + GtkSecureEntry *entry = GTK_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; + } + } + } + + WITH_SECURE_MEM(entry->text = + g_realloc(entry->text, entry->text_size)); + } + + _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; + + gtk_secure_entry_recompute(entry); + + g_signal_emit_by_name(editable, "changed"); + g_object_notify(G_OBJECT(editable), "text"); +} + +static void +gtk_secure_entry_real_delete_text(GtkEditable * editable, + gint start_pos, gint end_pos) +{ + GtkSecureEntry *entry = GTK_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; + + gtk_secure_entry_set_positions(entry, current_pos, + selection_bound); + + gtk_secure_entry_recompute(entry); + + 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(GtkSecureEntry * 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 = gtk_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 +gtk_secure_entry_move_cursor(GtkSecureEntry * entry, + GtkMovementStep step, + gint count, gboolean extend_selection) +{ + gint new_pos = entry->current_pos; + + gtk_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 = + gtk_secure_entry_move_logically(entry, new_pos, count); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + new_pos = + gtk_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); + + gtk_secure_entry_pend_cursor_blink(entry); +} + +static void +gtk_secure_entry_insert_at_cursor(GtkSecureEntry * entry, + const gchar * str) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint pos = entry->current_pos; + + gtk_secure_entry_reset_im_context(entry); + + gtk_editable_insert_text(editable, str, -1, &pos); + gtk_editable_set_position(editable, pos); +} + +static void +gtk_secure_entry_delete_from_cursor(GtkSecureEntry * entry, + GtkDeleteType type, gint count) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint start_pos = entry->current_pos; + gint end_pos = entry->current_pos; + + gtk_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 = + gtk_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; + } + + gtk_secure_entry_pend_cursor_blink(entry); +} + +static void +gtk_secure_entry_delete_cb(GtkSecureEntry * entry) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint start, end; + + if (gtk_editable_get_selection_bounds(editable, &start, &end)) + gtk_editable_delete_text(editable, start, end); +} + +static void +gtk_secure_entry_toggle_overwrite(GtkSecureEntry * entry) +{ + entry->overwrite_mode = !entry->overwrite_mode; +} + +static void +gtk_secure_entry_real_activate(GtkSecureEntry * 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 +gtk_secure_entry_keymap_direction_changed(GdkKeymap * keymap, + GtkSecureEntry * entry) +{ + gtk_secure_entry_recompute(entry); +} + +/* IM Context Callbacks + */ + +static void +gtk_secure_entry_commit_cb(GtkIMContext * context, + const gchar * str, GtkSecureEntry * entry) +{ + gtk_secure_entry_enter_text(entry, str); +} + +static void +gtk_secure_entry_preedit_changed_cb(GtkIMContext * context, + GtkSecureEntry * 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); + + gtk_secure_entry_recompute(entry); +} + +static gboolean +gtk_secure_entry_retrieve_surrounding_cb(GtkIMContext * context, + GtkSecureEntry * 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 +gtk_secure_entry_delete_surrounding_cb(GtkIMContext * slave, + gint offset, + gint n_chars, + GtkSecureEntry * 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 +gtk_secure_entry_enter_text(GtkSecureEntry * 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) + gtk_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); + gtk_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 +gtk_secure_entry_set_positions(GtkSecureEntry * 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) + gtk_secure_entry_recompute(entry); +} + +static void +gtk_secure_entry_reset_layout(GtkSecureEntry * entry) +{ + if (entry->cached_layout) { + g_object_unref(entry->cached_layout); + entry->cached_layout = NULL; + } +} + +static void +update_im_cursor_location(GtkSecureEntry * entry) +{ + GdkRectangle area; + gint strong_x; + gint strong_xoffset; + gint area_width, area_height; + + gtk_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) +{ + GtkSecureEntry *entry; + + GDK_THREADS_ENTER(); + + entry = GTK_SECURE_ENTRY(data); + + entry->recompute_idle = 0; + + if (gtk_widget_has_screen(GTK_WIDGET(entry))) { + gtk_secure_entry_adjust_scroll(entry); + gtk_secure_entry_queue_draw(entry); + + update_im_cursor_location(entry); + } + + GDK_THREADS_LEAVE(); + + return FALSE; +} + +static void +gtk_secure_entry_recompute(GtkSecureEntry * entry) +{ + gtk_secure_entry_reset_layout(entry); + gtk_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 void +append_char(GString * str, gunichar ch, gint count) +{ + gint i; + gint char_len; + gchar buf[7]; + + char_len = g_unichar_to_utf8(ch, buf); + + i = 0; + while (i < count) { + g_string_append_len(str, buf, char_len); + ++i; + } +} + +static PangoLayout * +gtk_secure_entry_create_layout(GtkSecureEntry * 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 ch_len; + gint preedit_len_chars; + gunichar invisible_char; + + ch_len = g_utf8_strlen(entry->text, entry->n_bytes); + preedit_len_chars = g_utf8_strlen(preedit_string, -1); + ch_len += preedit_len_chars; + + if (entry->invisible_char != 0) + invisible_char = entry->invisible_char; + else + invisible_char = ' '; /* just pick a char */ + + append_char(tmp_string, invisible_char, ch_len); + + /* 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); + gunichar invisible_char; + + if (entry->invisible_char != 0) + invisible_char = entry->invisible_char; + else + invisible_char = ' '; /* just pick a char */ + + append_char(str, invisible_char, entry->text_length); + 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 * +gtk_secure_entry_ensure_layout(GtkSecureEntry * entry, + gboolean include_preedit) +{ + if (entry->preedit_length > 0 && + !include_preedit != !entry->cache_includes_preedit) + gtk_secure_entry_reset_layout(entry); + + if (!entry->cached_layout) { + entry->cached_layout = + gtk_secure_entry_create_layout(entry, include_preedit); + entry->cache_includes_preedit = include_preedit; + } + + return entry->cached_layout; +} + +static void +get_layout_position(GtkSecureEntry * entry, gint * x, gint * y) +{ + PangoLayout *layout; + PangoRectangle logical_rect; + gint area_width, area_height; + gint y_pos; + PangoLayoutLine *line; + + layout = gtk_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 +gtk_secure_entry_draw_text(GtkSecureEntry * entry) +{ + GtkWidget *widget; + PangoLayoutLine *line; + + if (entry->invisible_char == 0) + return; + + if (GTK_WIDGET_DRAWABLE(entry)) { + PangoLayout *layout = gtk_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(GtkSecureEntry * 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 +gtk_secure_entry_draw_cursor(GtkSecureEntry * 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); + + gtk_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 +gtk_secure_entry_queue_draw(GtkSecureEntry * entry) +{ + if (GTK_WIDGET_REALIZED(entry)) + gdk_window_invalidate_rect(entry->text_area, NULL, FALSE); +} + +static void +gtk_secure_entry_reset_im_context(GtkSecureEntry * entry) +{ + if (entry->need_im_reset) { + entry->need_im_reset = 0; + gtk_im_context_reset(entry->im_context); + } +} + +static gint +gtk_secure_entry_find_position(GtkSecureEntry * entry, gint x) +{ + PangoLayout *layout; + PangoLayoutLine *line; + gint _index; + gint pos; + gboolean trailing; + const gchar *text; + gint cursor_index; + + layout = gtk_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 +gtk_secure_entry_get_cursor_locations(GtkSecureEntry * 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 = gtk_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 +gtk_secure_entry_adjust_scroll(GtkSecureEntry * 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 = gtk_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. + */ + + gtk_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 +gtk_secure_entry_move_visually(GtkSecureEntry * entry, + gint start, gint count) +{ + gint _index; + PangoLayout *layout = gtk_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 +gtk_secure_entry_move_logically(GtkSecureEntry * 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 * +gtk_secure_entry_new(void) +{ + return g_object_new(GTK_TYPE_SECURE_ENTRY, NULL); +} + +/** + * gtk_secure_entry_new_with_max_length: + * @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. + * + * Creates a new #GtkSecureEntry widget with the given maximum length. + * + * Note: the existence of this function is inconsistent + * with the rest of the GTK+ API. The normal setup would + * be to just require the user to make an extra call + * to gtk_secure_entry_set_max_length() instead. It is not + * expected that this function will be removed, but + * it would be better practice not to use it. + * + * Return value: a new #GtkSecureEntry. + **/ +GtkWidget * +gtk_secure_entry_new_with_max_length(gint max) +{ + GtkSecureEntry *entry; + + max = CLAMP(max, 0, MAX_SIZE); + + entry = g_object_new(GTK_TYPE_SECURE_ENTRY, NULL); + entry->text_max_length = max; + + return GTK_WIDGET(entry); +} + +void +gtk_secure_entry_set_text(GtkSecureEntry * entry, const gchar * text) +{ + gint tmp_pos; + + g_return_if_fail(GTK_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 +gtk_secure_entry_append_text(GtkSecureEntry * entry, const gchar * text) +{ + gint tmp_pos; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + g_return_if_fail(text != NULL); + + tmp_pos = entry->text_length; + gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &tmp_pos); +} + +void +gtk_secure_entry_prepend_text(GtkSecureEntry * entry, const gchar * text) +{ + gint tmp_pos; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + g_return_if_fail(text != NULL); + + tmp_pos = 0; + gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &tmp_pos); +} + +void +gtk_secure_entry_set_position(GtkSecureEntry * entry, gint position) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + gtk_editable_set_position(GTK_EDITABLE(entry), position); +} + +/** + * gtk_secure_entry_set_invisible_char: + * @entry: a #GtkSecureEntry + * @ch: a Unicode character + * + * Sets the character to use in place of the actual text when + * gtk_secure_entry_set_visibility() has been called to set text visibility + * 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 +gtk_secure_entry_set_invisible_char(GtkSecureEntry * entry, gunichar ch) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + if (ch == entry->invisible_char) + return; + + entry->invisible_char = ch; + g_object_notify(G_OBJECT(entry), "invisible_char"); + gtk_secure_entry_recompute(entry); +} + +/** + * gtk_secure_entry_get_invisible_char: + * @entry: a #GtkSecureEntry + * + * Retrieves the character displayed in place of the real characters + * for entries with visisbility set to false. See gtk_secure_entry_set_invisible_char(). + * + * Return value: the current invisible char, or 0, if the entry does not + * show invisible text at all. + **/ +gunichar +gtk_secure_entry_get_invisible_char(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + return entry->invisible_char; +} + +/** + * gtk_secure_entry_get_text: + * @entry: a #GtkSecureEntry + * + * 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 * +gtk_secure_entry_get_text(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), NULL); + + return entry->text; +} + +void +gtk_secure_entry_select_region(GtkSecureEntry * entry, + gint start, gint end) +{ + gtk_editable_select_region(GTK_EDITABLE(entry), start, end); +} + +/** + * gtk_secure_entry_set_max_length: + * @entry: a #GtkSecureEntry. + * @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 +gtk_secure_entry_set_max_length(GtkSecureEntry * entry, gint max) +{ + g_return_if_fail(GTK_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"); +} + +/** + * gtk_secure_entry_get_max_length: + * @entry: a #GtkSecureEntry + * + * Retrieves the maximum allowed length of the text in + * @entry. See gtk_secure_entry_set_max_length(). + * + * Return value: the maximum allowed number of characters + * in #GtkSecureEntry, or 0 if there is no maximum. + **/ +gint +gtk_secure_entry_get_max_length(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + return entry->text_max_length; +} + +/** + * gtk_secure_entry_set_activates_default: + * @entry: a #GtkSecureEntry + * @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 +gtk_secure_entry_set_activates_default(GtkSecureEntry * entry, + gboolean setting) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + setting = setting != FALSE; + + if (setting != entry->activates_default) { + entry->activates_default = setting; + g_object_notify(G_OBJECT(entry), "activates_default"); + } +} + +/** + * gtk_secure_entry_get_activates_default: + * @entry: a #GtkSecureEntry + * + * Retrieves the value set by gtk_secure_entry_set_activates_default(). + * + * Return value: %TRUE if the entry will activate the default widget + **/ +gboolean +gtk_secure_entry_get_activates_default(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), FALSE); + + return entry->activates_default; +} + +/** + * gtk_secure_entry_set_width_chars: + * @entry: a #GtkSecureEntry + * @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 +gtk_secure_entry_set_width_chars(GtkSecureEntry * entry, gint n_chars) +{ + g_return_if_fail(GTK_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)); + } +} + +/** + * gtk_secure_entry_get_width_chars: + * @entry: a #GtkSecureEntry + * + * Gets the value set by gtk_secure_entry_set_width_chars(). + * + * Return value: number of chars to request space for, or negative if unset + **/ +gint +gtk_secure_entry_get_width_chars(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + return entry->width_chars; +} + +/** + * gtk_secure_entry_set_has_frame: + * @entry: a #GtkSecureEntry + * @setting: new value + * + * Sets whether the entry has a beveled frame around it. + **/ +void +gtk_secure_entry_set_has_frame(GtkSecureEntry * entry, gboolean setting) +{ + g_return_if_fail(GTK_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"); +} + +/** + * gtk_secure_entry_get_has_frame: + * @entry: a #GtkSecureEntry + * + * Gets the value set by gtk_secure_entry_set_has_frame(). + * + * Return value: whether the entry has a beveled frame + **/ +gboolean +gtk_secure_entry_get_has_frame(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), FALSE); + + return entry->has_frame; +} + + +/** + * gtk_secure_entry_get_layout: + * @entry: a #GtkSecureEntry + * + * Gets the #PangoLayout used to display the entry. + * The layout is useful to e.g. convert text positions to + * pixel positions, in combination with gtk_secure_entry_get_layout_offsets(). + * The returned layout is owned by the entry so need not be + * freed by the caller. + * + * Keep in mind that the layout text may contain a preedit string, so + * gtk_secure_entry_layout_index_to_text_index() and + * gtk_secure_entry_text_index_to_layout_index() are needed to convert byte + * indices in the layout to byte indices in the entry contents. + * + * Return value: the #PangoLayout for this entry + **/ +PangoLayout * +gtk_secure_entry_get_layout(GtkSecureEntry * entry) +{ + PangoLayout *layout; + + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), NULL); + + layout = gtk_secure_entry_ensure_layout(entry, TRUE); + + return layout; +} + + +/** + * gtk_secure_entry_layout_index_to_text_index: + * @entry: a #GtkSecureEntry + * @layout_index: byte index into the entry layout text + * + * Converts from a position in the entry contents (returned + * by gtk_secure_entry_get_text()) to a position in the + * entry's #PangoLayout (returned by gtk_secure_entry_get_layout(), + * with text retrieved via pango_layout_get_text()). + * + * Return value: byte index into the entry contents + **/ +gint +gtk_secure_entry_layout_index_to_text_index(GtkSecureEntry * entry, + gint layout_index) +{ + PangoLayout *layout; + const gchar *text; + gint cursor_index; + + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + layout = gtk_secure_entry_ensure_layout(entry, TRUE); + text = pango_layout_get_text(layout); + cursor_index = + g_utf8_offset_to_pointer(text, entry->current_pos) - text; + + if (layout_index >= cursor_index && entry->preedit_length) { + if (layout_index >= cursor_index + entry->preedit_length) + layout_index -= entry->preedit_length; + else + layout_index = cursor_index; + } + + return layout_index; +} + +/** + * gtk_secure_entry_text_index_to_layout_index: + * @entry: a #GtkSecureEntry + * @text_index: byte index into the entry contents + * + * Converts from a position in the entry's #PangoLayout(returned by + * gtk_secure_entry_get_layout()) to a position in the entry contents + * (returned by gtk_secure_entry_get_text()). + * + * Return value: byte index into the entry layout text + **/ +gint +gtk_secure_entry_text_index_to_layout_index(GtkSecureEntry * entry, + gint text_index) +{ + PangoLayout *layout; + const gchar *text; + gint cursor_index; + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + layout = gtk_secure_entry_ensure_layout(entry, TRUE); + text = pango_layout_get_text(layout); + cursor_index = + g_utf8_offset_to_pointer(text, entry->current_pos) - text; + + if (text_index > cursor_index) + text_index += entry->preedit_length; + + return text_index; +} + +/** + * gtk_secure_entry_get_layout_offsets: + * @entry: a #GtkSecureEntry + * @x: location to store X offset of layout, or %NULL + * @y: location to store Y offset of layout, or %NULL + * + * + * Obtains the position of the #PangoLayout used to render text + * in the entry, in widget coordinates. Useful if you want to line + * up the text in an entry with some other text, e.g. when using the + * entry to implement editable cells in a sheet widget. + * + * Also useful to convert mouse events into coordinates inside the + * #PangoLayout, e.g. to take some action if some part of the entry text + * is clicked. + * + * Note that as the user scrolls around in the entry the offsets will + * change; you'll need to connect to the "notify::scroll_offset" + * signal to track this. Remember when using the #PangoLayout + * functions you need to convert to and from pixels using + * PANGO_PIXELS() or #PANGO_SCALE. + * + * Keep in mind that the layout text may contain a preedit string, so + * gtk_secure_entry_layout_index_to_text_index() and + * gtk_secure_entry_text_index_to_layout_index() are needed to convert byte + * indices in the layout to byte indices in the entry contents. + * + **/ +void +gtk_secure_entry_get_layout_offsets(GtkSecureEntry * entry, + gint * x, gint * y) +{ + gint text_area_x, text_area_y; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + /* this gets coords relative to text area */ + get_layout_position(entry, x, y); + + /* convert to widget coords */ + get_text_area_size(entry, &text_area_x, &text_area_y, NULL, NULL); + + if (x) + *x += text_area_x; + + if (y) + *y += text_area_y; +} + + +/* Quick hack of a popup menu + */ +static void +activate_cb(GtkWidget * menuitem, GtkSecureEntry * entry) +{ + const gchar *signal = + g_object_get_data(G_OBJECT(menuitem), "gtk-signal"); + g_signal_emit_by_name(entry, signal); +} + + +static gboolean +gtk_secure_entry_mnemonic_activate(GtkWidget * widget, + gboolean group_cycling) +{ + gtk_widget_grab_focus(widget); + return TRUE; +} + + +static void +unichar_chosen_func(const char *text, gpointer data) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(data); + + gtk_secure_entry_enter_text(entry, text); +} + +/* 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(GtkSecureEntry * 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(GtkSecureEntry * entry) +{ + GtkSettings *settings = gtk_widget_get_settings(GTK_WIDGET(entry)); + gint blinktime; + + g_object_get(settings, "gtk-cursor-blink-time", &blinktime, NULL); + + return blinktime; +} + +static void +show_cursor(GtkSecureEntry * 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(GtkSecureEntry * 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) +{ + GtkSecureEntry *entry; + + GDK_THREADS_ENTER(); + + entry = GTK_SECURE_ENTRY(data); + + if (!GTK_WIDGET_HAS_FOCUS(entry)) { + g_warning + ("GtkSecureEntry - 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 +gtk_secure_entry_check_cursor_blink(GtkSecureEntry * 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 +gtk_secure_entry_pend_cursor_blink(GtkSecureEntry * 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/gtksecentry/gtksecentry.h b/gtksecentry/gtksecentry.h new file mode 100644 index 0000000..12736cd --- /dev/null +++ b/gtksecentry/gtksecentry.h @@ -0,0 +1,181 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2004 Albrecht Dreß + * + * 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: + * + * 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. + */ + +#ifndef __GTK_SECURE_ENTRY_H__ +#define __GTK_SECURE_ENTRY_H__ + + +#include <gtk/gtk.h> + +#ifdef __cplusplus +extern "C" { +#ifdef MAKE_EMACS_HAPPY +} +#endif /* MAKE_EMACS_HAPPY */ +#endif /* __cplusplus */ +#define GTK_TYPE_SECURE_ENTRY (gtk_secure_entry_get_type ()) +#define GTK_SECURE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SECURE_ENTRY, GtkSecureEntry)) +#define GTK_SECURE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SECURE_ENTRY, GtkSecureEntryClass)) +#define GTK_IS_SECURE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SECURE_ENTRY)) +#define GTK_IS_SECURE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SECURE_ENTRY)) +#define GTK_SECURE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SECURE_ENTRY, GtkSecureEntryClass)) +typedef struct _GtkSecureEntry GtkSecureEntry; +typedef struct _GtkSecureEntryClass GtkSecureEntryClass; + +struct _GtkSecureEntry { + GtkWidget widget; + + gchar *text; + + guint overwrite_mode:1; + + guint16 text_length; /* length in use, in chars */ + 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; + + guint in_click:1; /* Flag so we don't select all when clicking in entry to focus in */ + + guint is_cell_renderer:1; + guint editing_canceled:1; /* Only used by GtkCellRendererText */ + + guint mouse_cursor_obscured:1; + + guint resolved_dir : 4; /* PangoDirection */ + + 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; +}; + +struct _GtkSecureEntryClass { + GtkWidgetClass parent_class; + + /* Action signals + */ + void (*activate) (GtkSecureEntry * entry); + void (*move_cursor) (GtkSecureEntry * entry, + GtkMovementStep step, + gint count, gboolean extend_selection); + void (*insert_at_cursor) (GtkSecureEntry * entry, const gchar * str); + void (*delete_from_cursor) (GtkSecureEntry * 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 +gtk_secure_entry_get_type(void) + G_GNUC_CONST; +GtkWidget * +gtk_secure_entry_new(void); +void +gtk_secure_entry_set_invisible_char(GtkSecureEntry * entry, gunichar ch); +gunichar +gtk_secure_entry_get_invisible_char(GtkSecureEntry * entry); +void +gtk_secure_entry_set_has_frame(GtkSecureEntry * entry, gboolean setting); +gboolean +gtk_secure_entry_get_has_frame(GtkSecureEntry * entry); +/* text is truncated if needed */ +void +gtk_secure_entry_set_max_length(GtkSecureEntry * entry, gint max); +gint +gtk_secure_entry_get_max_length(GtkSecureEntry * entry); +void +gtk_secure_entry_set_activates_default(GtkSecureEntry * entry, + gboolean setting); +gboolean +gtk_secure_entry_get_activates_default(GtkSecureEntry * entry); + +void +gtk_secure_entry_set_width_chars(GtkSecureEntry * entry, gint n_chars); +gint +gtk_secure_entry_get_width_chars(GtkSecureEntry * entry); + +/* Somewhat more convenient than the GtkEditable generic functions + */ +void +gtk_secure_entry_set_text(GtkSecureEntry * entry, const gchar * text); +/* returns a reference to the text */ +G_CONST_RETURN gchar * +gtk_secure_entry_get_text(GtkSecureEntry * entry); + +PangoLayout * +gtk_secure_entry_get_layout(GtkSecureEntry * entry); +void +gtk_secure_entry_get_layout_offsets(GtkSecureEntry * entry, + gint * x, gint * y); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_SECURE_ENTRY_H__ */ diff --git a/secmem/Makefile.am b/secmem/Makefile.am new file mode 100644 index 0000000..dfdbed0 --- /dev/null +++ b/secmem/Makefile.am @@ -0,0 +1,31 @@ +# Secure Memory Makefile +# Copyright (C) 2002 g10 Code GmbH +# +# This file is part of PINENTRY. +# +# PINENTRY is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PINENTRY 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 General Public License for more details. +# +# You should have received a copy of the GNU 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 + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = Manifest + +noinst_LIBRARIES = libsecmem.a + +libsecmem_a_SOURCES = \ + memory.h \ + secmem-util.h \ + util.h \ + secmem.c \ + util.c diff --git a/secmem/Manifest b/secmem/Manifest new file mode 100644 index 0000000..a37366d --- /dev/null +++ b/secmem/Manifest @@ -0,0 +1,7 @@ +Makefile.am +memory.h +secmem-util.h +secmem.c +util.c +util.h +$names$ iQCVAwUAP+f/RDEAnp832S/7AQIbRQQAzR7UvGOTMl8AWyVgHGQjW5A5fGzRlaEABl+5UpGmzoFGFdP9upHv3Tj0MKETHNRkdOAA5k5QzamDypAr5RINz9rdZPkNPIAtg4csN7Yb6ITJZaH7yLDJcBmhM49a8ZNpDpQeImzpE05cM6TuGVO6NSIrlt9OBhaHfbkpzgr1tI0==6Tyw diff --git a/secmem/memory.h b/secmem/memory.h new file mode 100644 index 0000000..c6d04b8 --- /dev/null +++ b/secmem/memory.h @@ -0,0 +1,40 @@ +/* Quintuple Agent secure memory allocation + * Copyright (C) 1998,1999 Free Software Foundation, Inc. + * Copyright (C) 1999,2000 Robert Bihlmeyer <robbe@orcus.priv.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _MEMORY_H +#define _MEMORY_H + +#include <sys/types.h> + +/* values for flags, hardcoded in secmem.c */ +#define SECMEM_WARN 0 +#define SECMEM_DONT_WARN 1 +#define SECMEM_SUSPEND_WARN 2 + +void secmem_init( size_t npool ); +void secmem_term( void ); +void *secmem_malloc( size_t size ); +void *secmem_realloc( void *a, size_t newsize ); +void secmem_free( void *a ); +int m_is_secure( const void *p ); +void secmem_dump_stats(void); +void secmem_set_flags( unsigned flags ); +unsigned secmem_get_flags(void); + +#endif /* _MEMORY_H */ diff --git a/secmem/secmem-util.h b/secmem/secmem-util.h new file mode 100644 index 0000000..b422182 --- /dev/null +++ b/secmem/secmem-util.h @@ -0,0 +1,3 @@ +/* This file exists because "util.h" is such a generic name that it is + likely to clash with other such files. */ +#include "util.h" diff --git a/secmem/secmem.c b/secmem/secmem.c new file mode 100644 index 0000000..fc9ac2f --- /dev/null +++ b/secmem/secmem.c @@ -0,0 +1,448 @@ +/* secmem.c - memory allocation from a secure heap + * Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <stdarg.h> +#include <unistd.h> +#if defined(HAVE_MLOCK) || defined(HAVE_MMAP) +# include <sys/mman.h> +# include <sys/types.h> +# include <fcntl.h> +# ifdef USE_CAPABILITIES +# include <sys/capability.h> +# endif +#endif +#include <string.h> + +#include "memory.h" + +#ifdef ORIGINAL_GPG_VERSION +#include "types.h" +#include "util.h" +#else /* ORIGINAL_GPG_VERSION */ + +#include "util.h" + +typedef union { + int a; + short b; + char c[1]; + long d; +#ifdef HAVE_U64_TYPEDEF + u64 e; +#endif + float f; + double g; +} PROPERLY_ALIGNED_TYPE; + +#define log_error log_info +#define log_bug log_fatal + +void +log_info(char *template, ...) +{ + va_list args; + + va_start(args, template); + vfprintf(stderr, template, args); + va_end(args); +} + +void +log_fatal(char *template, ...) +{ + va_list args; + + va_start(args, template); + vfprintf(stderr, template, args); + va_end(args); + exit(EXIT_FAILURE); +} + +#endif /* ORIGINAL_GPG_VERSION */ + +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +# define MAP_ANONYMOUS MAP_ANON +#endif + +#define DEFAULT_POOLSIZE 16384 + +typedef struct memblock_struct MEMBLOCK; +struct memblock_struct { + unsigned size; + union { + MEMBLOCK *next; + PROPERLY_ALIGNED_TYPE aligned; + } u; +}; + + + +static void *pool; +static volatile int pool_okay; /* may be checked in an atexit function */ +static int pool_is_mmapped; +static size_t poolsize; /* allocated length */ +static size_t poollen; /* used length */ +static MEMBLOCK *unused_blocks; +static unsigned max_alloced; +static unsigned cur_alloced; +static unsigned max_blocks; +static unsigned cur_blocks; +static int disable_secmem; +static int show_warning; +static int no_warning; +static int suspend_warning; + + +static void +print_warn(void) +{ + if( !no_warning ) + log_info("Warning: using insecure memory!\n"); +} + + +static void +lock_pool( void *p, size_t n ) +{ +#if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK) + int err; + + cap_set_proc( cap_from_text("cap_ipc_lock+ep") ); + err = mlock( p, n ); + if( err && errno ) + err = errno; + cap_set_proc( cap_from_text("cap_ipc_lock+p") ); + + if( err ) { + if( errno != EPERM + #ifdef EAGAIN /* OpenBSD returns this */ + && errno != EAGAIN + #endif + ) + log_error("can´t lock memory: %s\n", strerror(err)); + show_warning = 1; + } + +#elif defined(HAVE_MLOCK) + uid_t uid; + int err; + + uid = getuid(); + +#ifdef HAVE_BROKEN_MLOCK + if( uid ) { + errno = EPERM; + err = errno; + } + else { + err = mlock( p, n ); + if( err && errno ) + err = errno; + } +#else + err = mlock( p, n ); + if( err && errno ) + err = errno; +#endif + + if( uid && !geteuid() ) { + if( setuid( uid ) || getuid() != geteuid() ) + log_fatal("failed to reset uid: %s\n", strerror(errno)); + } + + if( err ) { + if( errno != EPERM +#ifdef EAGAIN /* OpenBSD returns this */ + && errno != EAGAIN +#endif + ) + log_error("can´t lock memory: %s\n", strerror(err)); + show_warning = 1; + } + +#else + log_info("Please note that you don't have secure memory on this system\n"); +#endif +} + + +static void +init_pool( size_t n) +{ + size_t pgsize; + + poolsize = n; + + if( disable_secmem ) + log_bug("secure memory is disabled"); + +#ifdef HAVE_GETPAGESIZE + pgsize = getpagesize(); +#else + pgsize = 4096; +#endif + +#if HAVE_MMAP + poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1); +# ifdef MAP_ANONYMOUS + pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +# else /* map /dev/zero instead */ + { int fd; + + fd = open("/dev/zero", O_RDWR); + if( fd == -1 ) { + log_error("can't open /dev/zero: %s\n", strerror(errno) ); + pool = (void*)-1; + } + else { + pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE, fd, 0); + close (fd); + } + } +# endif + if( pool == (void*)-1 ) + log_info("can't mmap pool of %u bytes: %s - using malloc\n", + (unsigned)poolsize, strerror(errno)); + else { + pool_is_mmapped = 1; + pool_okay = 1; + } + +#endif + if( !pool_okay ) { + pool = malloc( poolsize ); + if( !pool ) + log_fatal("can't allocate memory pool of %u bytes\n", + (unsigned)poolsize); + else + pool_okay = 1; + } + lock_pool( pool, poolsize ); + poollen = 0; +} + + +/* concatenate unused blocks */ +static void +compress_pool(void) +{ + /* fixme: we really should do this */ +} + +void +secmem_set_flags( unsigned flags ) +{ + int was_susp = suspend_warning; + + no_warning = flags & 1; + suspend_warning = flags & 2; + + /* and now issue the warning if it is not longer suspended */ + if( was_susp && !suspend_warning && show_warning ) { + show_warning = 0; + print_warn(); + } +} + +unsigned +secmem_get_flags(void) +{ + unsigned flags; + + flags = no_warning ? 1:0; + flags |= suspend_warning ? 2:0; + return flags; +} + +void +secmem_init( size_t n ) +{ + if( !n ) { +#ifdef USE_CAPABILITIES + /* drop all capabilities */ + cap_set_proc( cap_from_text("all-eip") ); + +#elif !defined(HAVE_DOSISH_SYSTEM) + uid_t uid; + + disable_secmem=1; + uid = getuid(); + if( uid != geteuid() ) { + if( setuid( uid ) || getuid() != geteuid() ) + log_fatal("failed to drop setuid\n" ); + } +#endif + } + else { + if( n < DEFAULT_POOLSIZE ) + n = DEFAULT_POOLSIZE; + if( !pool_okay ) + init_pool(n); + else + log_error("Oops, secure memory pool already initialized\n"); + } +} + + +void * +secmem_malloc( size_t size ) +{ + MEMBLOCK *mb, *mb2; + int compressed=0; + + if( !pool_okay ) { + log_info( + "operation is not possible without initialized secure memory\n"); + log_info("(you may have used the wrong program for this task)\n"); + exit(2); + } + if( show_warning && !suspend_warning ) { + show_warning = 0; + print_warn(); + } + + /* blocks are always a multiple of 32 */ + size += sizeof(MEMBLOCK); + size = ((size + 31) / 32) * 32; + + retry: + /* try to get it from the used blocks */ + for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next ) + if( mb->size >= size ) { + if( mb2 ) + mb2->u.next = mb->u.next; + else + unused_blocks = mb->u.next; + goto leave; + } + /* allocate a new block */ + if( (poollen + size <= poolsize) ) { + mb = (void*)((char*)pool + poollen); + poollen += size; + mb->size = size; + } + else if( !compressed ) { + compressed=1; + compress_pool(); + goto retry; + } + else + return NULL; + + leave: + cur_alloced += mb->size; + cur_blocks++; + if( cur_alloced > max_alloced ) + max_alloced = cur_alloced; + if( cur_blocks > max_blocks ) + max_blocks = cur_blocks; + + return &mb->u.aligned.c; +} + + +void * +secmem_realloc( void *p, size_t newsize ) +{ + MEMBLOCK *mb; + size_t size; + void *a; + + mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c)); + size = mb->size; + if( newsize < size ) + return p; /* it is easier not to shrink the memory */ + a = secmem_malloc( newsize ); + memcpy(a, p, size); + memset((char*)a+size, 0, newsize-size); + secmem_free(p); + return a; +} + + +void +secmem_free( void *a ) +{ + MEMBLOCK *mb; + size_t size; + + if( !a ) + return; + + mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c)); + size = mb->size; + /* This does not make much sense: probably this memory is held in the + * cache. We do it anyway: */ + wipememory2(mb, 0xff, size ); + wipememory2(mb, 0xaa, size ); + wipememory2(mb, 0x55, size ); + wipememory2(mb, 0x00, size ); + mb->size = size; + mb->u.next = unused_blocks; + unused_blocks = mb; + cur_blocks--; + cur_alloced -= size; +} + +int +m_is_secure( const void *p ) +{ + return p >= pool && p < (void*)((char*)pool+poolsize); +} + +void +secmem_term() +{ + if( !pool_okay ) + return; + + wipememory2( pool, 0xff, poolsize); + wipememory2( pool, 0xaa, poolsize); + wipememory2( pool, 0x55, poolsize); + wipememory2( pool, 0x00, poolsize); +#if HAVE_MMAP + if( pool_is_mmapped ) + munmap( pool, poolsize ); +#endif + pool = NULL; + pool_okay = 0; + poolsize=0; + poollen=0; + unused_blocks=NULL; +} + + +void +secmem_dump_stats() +{ + if( disable_secmem ) + return; + fprintf(stderr, + "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n", + cur_alloced, max_alloced, cur_blocks, max_blocks, + (ulong)poollen, (ulong)poolsize ); +} + diff --git a/secmem/util.c b/secmem/util.c new file mode 100644 index 0000000..580fd34 --- /dev/null +++ b/secmem/util.c @@ -0,0 +1,139 @@ +/* Quintuple Agent + * Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define _GNU_SOURCE 1 + +#include <unistd.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "util.h" + +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) +#endif + +#ifndef HAVE_DOSISH_SYSTEM +static int uid_set = 0; +static uid_t real_uid, file_uid; +#endif /*!HAVE_DOSISH_SYSTEM*/ + +/* write DATA of size BYTES to FD, until all is written or an error occurs */ +ssize_t xwrite(int fd, const void *data, size_t bytes) +{ + char *ptr; + size_t todo; + ssize_t written = 0; + + for (ptr = (char *)data, todo = bytes; todo; ptr += written, todo -= written) + if ((written = TEMP_FAILURE_RETRY(write(fd, ptr, todo))) < 0) + break; + return written; +} + +#if 0 +extern int debug; + +int debugmsg(const char *fmt, ...) +{ + va_list va; + int ret; + + if (debug) { + va_start(va, fmt); + fprintf(stderr, "\e[4m"); + ret = vfprintf(stderr, fmt, va); + fprintf(stderr, "\e[24m"); + va_end(va); + return ret; + } else + return 0; +} +#endif + +/* initialize uid variables */ +#ifndef HAVE_DOSISH_SYSTEM +static void init_uids(void) +{ + real_uid = getuid(); + file_uid = geteuid(); + uid_set = 1; +} +#endif + + +#if 0 /* Not used. */ +/* lower privileges to the real user's */ +void lower_privs() +{ + if (!uid_set) + init_uids(); + if (real_uid != file_uid) { +#ifdef HAVE_SETEUID + if (seteuid(real_uid) < 0) { + perror("lowering privileges failed"); + exit(EXIT_FAILURE); + } +#else + fprintf(stderr, _("Warning: running q-agent setuid on this system is dangerous\n")); +#endif /* HAVE_SETEUID */ + } +} +#endif /* if 0 */ + +#if 0 /* Not used. */ +/* raise privileges to the effective user's */ +void raise_privs() +{ + assert(real_uid >= 0); /* lower_privs() must be called before this */ +#ifdef HAVE_SETEUID + if (real_uid != file_uid && seteuid(file_uid) < 0) { + perror("Warning: raising privileges failed"); + } +#endif /* HAVE_SETEUID */ +} +#endif /* if 0 */ + +/* drop all additional privileges */ +void drop_privs() +{ +#ifndef HAVE_DOSISH_SYSTEM + if (!uid_set) + init_uids(); + if (real_uid != file_uid) { + if (setuid(real_uid) < 0) { + perror("dropping privileges failed"); + exit(EXIT_FAILURE); + } + file_uid = real_uid; + } +#endif +} diff --git a/secmem/util.h b/secmem/util.h new file mode 100644 index 0000000..5d28925 --- /dev/null +++ b/secmem/util.h @@ -0,0 +1,66 @@ +/* Quintuple Agent utilities + * Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at> + * Copyright (C) 2003 g10 Code GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _UTIL_H +#define _UTIL_H + +#include <sys/types.h> + +#ifndef HAVE_BYTE_TYPEDEF +# undef byte +# ifdef __riscos__ + /* Norcroft treats char == unsigned char but char* != unsigned char* */ + typedef char byte; +# else + typedef unsigned char byte; +# endif +# define HAVE_BYTE_TYPEDEF +#endif + +#ifndef HAVE_ULONG_TYPEDEF +# undef ulong + typedef unsigned long ulong; +# define HAVE_ULONG_TYPEDEF +#endif + + +ssize_t xwrite(int, const void *, size_t); /* write until finished */ +int debugmsg(const char *, ...); /* output a debug message if debugging==on */ +void drop_privs(void); /* finally drop privileges */ + + +/* To avoid that a compiler optimizes certain memset calls away, these + macros may be used instead. */ +#define wipememory2(_ptr,_set,_len) do { \ + volatile char *_vptr=(volatile char *)(_ptr); \ + size_t _vlen=(_len); \ + while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ + } while(0) +#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) +#define wipe(_ptr,_len) wipememory2(_ptr,0,_len) + + + + +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index aaf09d9..cff044e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,8 @@ krb5_auth_dialog_SOURCES = \ dummy-strings.c krb5_auth_dialog_LDADD = \ + ../gtksecentry/libgtksecentry.a \ + ../secmem/libsecmem.a \ @NETWORK_MANAGER_LIBS@ \ @KRB5_LIBS@ \ @LIBNOTIFY_LIBS@ \ |