Fix bug#1162. This finally allows me to use the pinentry on my kfreebsd laptop witho...
[pinentry.git] / gtk+-2 / pinentry-gtk-2.c
1 /* pinentry-gtk-2.c
2    Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at>
3    Copyright (C) 2001, 2002, 2007 g10 Code GmbH
4    Copyright (C) 2004 by Albrecht DreƟ <albrecht.dress@arcor.de>
5
6    pinentry-gtk-2 is a pinentry application for the Gtk+-2 widget set.
7    It tries to follow the Gnome Human Interface Guide as close as
8    possible.
9
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14   
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19   
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 #include <gtk/gtk.h>
28 #include <assert.h>
29 #include <math.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #ifdef HAVE_GETOPT_H
35 #include <getopt.h>
36 #else
37 #include "getopt.h"
38 #endif                          /* HAVE_GETOPT_H */
39
40 #include "gtksecentry.h"
41 #include "pinentry.h"
42
43 #ifdef FALLBACK_CURSES
44 #include "pinentry-curses.h"
45 #endif
46
47 \f
48 #define PGMNAME "pinentry-gtk2"
49
50 #ifndef VERSION
51 #  define VERSION
52 #endif
53
54 static pinentry_t pinentry;
55 static int grab_failed;
56 static int passphrase_ok;
57 typedef enum { CONFIRM_CANCEL, CONFIRM_OK, CONFIRM_NOTOK } confirm_value_t;
58 static confirm_value_t confirm_value;
59
60 static GtkWidget *entry;
61 static GtkWidget *qualitybar;
62 #ifdef ENABLE_ENHANCED
63 static GtkWidget *insure;
64 static GtkWidget *time_out;
65 #endif
66 static GtkTooltips *tooltips;
67
68 /* Gnome hig small and large space in pixels.  */
69 #define HIG_SMALL      6
70 #define HIG_LARGE     12
71
72 /* The text shown in the quality bar when no text is shown.  This is not
73  * the empty string, because with an empty string the height of
74  * the quality bar is less than with a non-empty string.  This results
75  * in ugly layout changes when the text changes from non-empty to empty
76  * and vice versa.  */
77 #define QUALITYBAR_EMPTY_TEXT " "
78
79 \f
80 /* Constrain size of the window the window should not shrink beyond
81    the requisition, and should not grow vertically.  */
82 static void
83 constrain_size (GtkWidget *win, GtkRequisition *req, gpointer data)
84 {
85   static gint width, height;
86   GdkGeometry geo;
87
88   if (req->width == width && req->height == height)
89     return;
90   width = req->width;
91   height = req->height;
92   geo.min_width = width;
93   /* This limit is arbitrary, but INT_MAX breaks other things */
94   geo.max_width = 10000;
95   geo.min_height = geo.max_height = height;
96   gtk_window_set_geometry_hints (GTK_WINDOW (win), NULL, &geo,
97                                  GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
98 }
99
100  
101 /* Realize the window as transient if we grab the keyboard.  This
102    makes the window a modal dialog to the root window, which helps the
103    window manager.  See the following quote from:
104    http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2512420
105
106    Implementing enhanced support for application transient windows
107
108    If the WM_TRANSIENT_FOR property is set to None or Root window, the
109    window should be treated as a transient for all other windows in
110    the same group. It has been noted that this is a slight ICCCM
111    violation, but as this behavior is pretty standard for many
112    toolkits and window managers, and is extremely unlikely to break
113    anything, it seems reasonable to document it as standard.  */
114
115 static void
116 make_transient (GtkWidget *win, GdkEvent *event, gpointer data)
117 {
118   GdkScreen *screen;
119   GdkWindow *root;
120
121   if (! pinentry->grab)
122     return;
123
124   /* Make window transient for the root window.  */
125   screen = gdk_screen_get_default ();
126   root = gdk_screen_get_root_window (screen);
127   gdk_window_set_transient_for (win->window, root);
128 }
129
130
131 /* Grab the keyboard for maximum security */
132 static void
133 grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
134 {
135   if (! pinentry->grab)
136     return;
137
138   if (gdk_keyboard_grab (win->window, FALSE, gdk_event_get_time (event)))
139     {
140       g_critical ("could not grab keyboard");
141       grab_failed = 1;
142       gtk_main_quit ();
143     }
144 }
145
146
147 /* Remove grab.  */
148 static void
149 ungrab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
150 {
151   gdk_keyboard_ungrab (gdk_event_get_time (event));
152   /* Unmake window transient for the root window.  */
153   /* gdk_window_set_transient_for cannot be used with parent = NULL to
154      unset transient hint (unlike gtk_ version which can).  Replacement
155      code is taken from gtk_window_transient_parent_unrealized.  */
156   gdk_property_delete (win->window,
157                        gdk_atom_intern_static_string ("WM_TRANSIENT_FOR"));
158 }
159
160
161 static int
162 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
163 {
164   gtk_main_quit ();
165   return TRUE;
166 }
167
168
169 static void
170 button_clicked (GtkWidget *widget, gpointer data)
171 {
172   if (data)
173     {
174       const char *s;
175
176       /* Okay button or enter used in text field.  */
177 #ifdef ENABLE_ENHANCED
178       /* FIXME: This is not compatible with assuan.  We can't just
179          print stuff on stdout.  */
180       if (pinentry->enhanced)
181         printf ("Options: %s\nTimeout: %d\n\n",
182                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (insure))
183                 ? "insure" : "",
184                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (time_out)));
185 #endif
186
187       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry));
188       if (!s)
189         s = "";
190       passphrase_ok = 1;
191       pinentry_setbufferlen (pinentry, strlen (s) + 1);
192       if (pinentry->pin)
193         strcpy (pinentry->pin, s);
194     }
195   gtk_main_quit ();
196 }
197
198
199 static void
200 enter_callback (GtkWidget *widget, GtkWidget *anentry)
201 {
202   button_clicked (widget, "ok");
203 }
204
205
206 static void
207 confirm_button_clicked (GtkWidget *widget, gpointer data)
208 {
209   confirm_value = (int) data;
210   gtk_main_quit ();
211 }
212
213
214 static gchar *
215 pinentry_utf8_validate (gchar *text)
216 {
217   gchar *result;
218
219   if (!text)
220     return NULL;
221
222   if (g_utf8_validate (text, -1, NULL))
223     return g_strdup (text);
224
225   result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL);
226   if (!result)
227     {
228       gchar *p;
229
230       result = p = g_strdup (text);
231       while (!g_utf8_validate (p, -1, (const gchar **) &p))
232         *p = '?';
233     }
234   return result;
235 }
236
237
238 /* Handler called for "changed".   We use it to update the quality
239    indicator.  */
240 static void
241 changed_text_handler (GtkWidget *widget)
242 {
243   char textbuf[50];
244   const char *s;
245   int length;
246   int percent;
247   GdkColor color = { 0, 0, 0, 0};
248
249   if (!qualitybar || !pinentry->quality_bar)
250     return;
251
252   s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (widget));
253   if (!s)
254     s = "";
255   length = strlen (s);
256   percent = length? pinentry_inq_quality (pinentry, s, length) : 0;
257   if (!length)
258     {
259       strcpy(textbuf, QUALITYBAR_EMPTY_TEXT);
260       color.red = 0xffff;
261     }
262   else if (percent < 0)
263     {
264       snprintf (textbuf, sizeof textbuf, "(%d%%)", -percent);
265       color.red = 0xffff;
266       percent = -percent;
267     }
268   else
269     {
270       snprintf (textbuf, sizeof textbuf, "%d%%", percent);
271       color.green = 0xffff;
272     }
273   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 
274                                  (double)percent/100.0);
275   gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar), textbuf);
276   gtk_widget_modify_bg (qualitybar, GTK_STATE_PRELIGHT, &color);
277 }
278
279
280
281 static GtkWidget *
282 create_window (int confirm_mode)
283 {
284   GtkWidget *w;
285   GtkWidget *win, *box;
286   GtkWidget *wvbox, *chbox, *bbox;
287   GtkAccelGroup *acc;
288   gchar *msg;
289
290   tooltips = gtk_tooltips_new ();
291
292   /* FIXME: check the grabbing code against the one we used with the
293      old gpg-agent */
294   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
295   acc = gtk_accel_group_new ();
296
297   g_signal_connect (G_OBJECT (win), "delete_event",
298                     G_CALLBACK (delete_event), NULL);
299
300 #if 0
301   g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (gtk_main_quit),
302                     NULL);
303 #endif
304   g_signal_connect (G_OBJECT (win), "size-request",
305                     G_CALLBACK (constrain_size), NULL);
306   if (!confirm_mode)
307     {
308       if (pinentry->grab)
309         g_signal_connect (G_OBJECT (win),
310                           "realize", G_CALLBACK (make_transient), NULL);
311
312       /* We need to grab the keyboard when its visible! not when its
313          mapped (there is a difference)  */
314       g_object_set (G_OBJECT(win), "events",
315                     GDK_VISIBILITY_NOTIFY_MASK | GDK_STRUCTURE_MASK, NULL);
316
317       g_signal_connect (G_OBJECT (win),
318                         pinentry->grab
319                         ? "visibility-notify-event"
320                         : "focus-in-event",
321                         G_CALLBACK (grab_keyboard), NULL);
322       g_signal_connect (G_OBJECT (win),
323                         pinentry->grab ? "unmap-event" : "focus-out-event",
324                         G_CALLBACK (ungrab_keyboard), NULL);
325     }
326   gtk_window_add_accel_group (GTK_WINDOW (win), acc);
327   
328   wvbox = gtk_vbox_new (FALSE, HIG_LARGE * 2);
329   gtk_container_add (GTK_CONTAINER (win), wvbox);
330   gtk_container_set_border_width (GTK_CONTAINER (wvbox), HIG_LARGE);
331
332   chbox = gtk_hbox_new (FALSE, HIG_LARGE);
333   gtk_box_pack_start (GTK_BOX (wvbox), chbox, FALSE, FALSE, 0);
334
335   w = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
336                                                GTK_ICON_SIZE_DIALOG);
337   gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
338   gtk_box_pack_start (GTK_BOX (chbox), w, FALSE, FALSE, 0);
339
340   box = gtk_vbox_new (FALSE, HIG_SMALL);
341   gtk_box_pack_start (GTK_BOX (chbox), box, TRUE, TRUE, 0);
342
343   if (pinentry->title)
344     {
345       msg = pinentry_utf8_validate (pinentry->title);
346       gtk_window_set_title (GTK_WINDOW(win), msg);
347     }
348   if (pinentry->description)
349     {
350       msg = pinentry_utf8_validate (pinentry->description);
351       w = gtk_label_new (msg);
352       g_free (msg);
353       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
354       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
355       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
356     }
357   if (pinentry->error && !confirm_mode)
358     {
359       GdkColor color = { 0, 0xffff, 0, 0 };
360
361       msg = pinentry_utf8_validate (pinentry->error);
362       w = gtk_label_new (msg);
363       g_free (msg);
364       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
365       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
366       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
367       gtk_widget_modify_fg (w, GTK_STATE_NORMAL, &color);
368     }
369
370   qualitybar = NULL;
371
372   if (!confirm_mode)
373     {
374       GtkWidget* table = gtk_table_new (pinentry->quality_bar ? 2 : 1, 2,
375                                         FALSE);
376       gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 0);
377
378       if (pinentry->prompt)
379         {
380           msg = pinentry_utf8_validate (pinentry->prompt);
381           w = gtk_label_new_with_mnemonic (msg);
382           g_free (msg);
383           gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
384           gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1,
385                             GTK_FILL, GTK_FILL, 4, 0);
386         }
387
388       entry = gtk_secure_entry_new ();
389       gtk_widget_set_size_request (entry, 200, -1);
390       g_signal_connect (G_OBJECT (entry), "activate",
391                         G_CALLBACK (enter_callback), entry);
392       g_signal_connect (G_OBJECT (entry), "changed",
393                         G_CALLBACK (changed_text_handler), entry);
394       gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1,
395                         GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
396       gtk_widget_grab_focus (entry);
397       gtk_widget_show (entry);
398
399       if (pinentry->quality_bar)
400         {
401           msg = pinentry_utf8_validate (pinentry->quality_bar);
402           w = gtk_label_new (msg);
403           g_free (msg);
404           gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
405           gtk_table_attach (GTK_TABLE (table), w, 0, 1, 1, 2,
406                             GTK_FILL, GTK_FILL, 4, 0);
407           qualitybar = gtk_progress_bar_new();
408           gtk_widget_add_events (qualitybar,
409                                  GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
410           gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar),
411                                      QUALITYBAR_EMPTY_TEXT);
412           gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 0.0);
413           if (pinentry->quality_bar_tt)
414             gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), qualitybar,
415                                   pinentry->quality_bar_tt, "");
416           gtk_table_attach (GTK_TABLE (table), qualitybar, 1, 2, 1, 2,
417                             GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
418         }
419
420 #ifdef ENABLE_ENHANCED
421       if (pinentry->enhanced)
422         {
423           GtkWidget *sbox = gtk_hbox_new (FALSE, HIG_SMALL);
424           gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0);
425
426           w = gtk_label_new ("Forget secret after");
427           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
428           gtk_widget_show (w);
429
430           time_out = gtk_spin_button_new
431             (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, HUGE_VAL, 1, 60, 60)),
432              2, 0);
433           gtk_box_pack_start (GTK_BOX (sbox), time_out, FALSE, FALSE, 0);
434           gtk_widget_show (time_out);
435           
436           w = gtk_label_new ("seconds");
437           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
438           gtk_widget_show (w);
439           gtk_widget_show (sbox);
440
441           insure = gtk_check_button_new_with_label ("ask before giving out "
442                                                     "secret");
443           gtk_box_pack_start (GTK_BOX (box), insure, FALSE, FALSE, 0);
444           gtk_widget_show (insure);
445         }
446 #endif
447     }
448
449   bbox = gtk_hbutton_box_new ();
450   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
451   gtk_box_set_spacing (GTK_BOX (bbox), 6);
452   gtk_box_pack_start (GTK_BOX (wvbox), bbox, TRUE, FALSE, 0);
453
454   if (!pinentry->one_button)
455     {
456       if (pinentry->cancel)
457         {
458           msg = pinentry_utf8_validate (pinentry->cancel);
459           w = gtk_button_new_with_mnemonic (msg);
460           g_free (msg);
461         }
462       else
463         w = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
464       gtk_container_add (GTK_CONTAINER (bbox), w);
465       g_signal_connect (G_OBJECT (w), "clicked",
466                         G_CALLBACK (confirm_mode ? confirm_button_clicked
467                                     : button_clicked),
468                         (gpointer) CONFIRM_CANCEL);
469       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
470     }
471   
472   if (confirm_mode && !pinentry->one_button && pinentry->notok)
473     {
474       msg = pinentry_utf8_validate (pinentry->notok);
475       w = gtk_button_new_with_mnemonic (msg);
476       g_free (msg);
477
478       gtk_container_add (GTK_CONTAINER (bbox), w);
479       g_signal_connect (G_OBJECT (w), "clicked",
480                         G_CALLBACK (confirm_button_clicked),
481                         (gpointer) CONFIRM_NOTOK);
482       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
483     }
484   
485   if (pinentry->ok)
486     {
487       msg = pinentry_utf8_validate (pinentry->ok);
488       w = gtk_button_new_with_mnemonic (msg);
489       g_free (msg);
490     }
491   else
492     w = gtk_button_new_from_stock (GTK_STOCK_OK);
493   gtk_container_add (GTK_CONTAINER(bbox), w);
494   if (!confirm_mode)
495     {
496       g_signal_connect (G_OBJECT (w), "clicked",
497                         G_CALLBACK (button_clicked), "ok");
498       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
499       gtk_widget_grab_default (w);
500       g_signal_connect_object (G_OBJECT (entry), "focus_in_event",
501                                 G_CALLBACK (gtk_widget_grab_default),
502                                G_OBJECT (w), 0);
503     }
504   else
505     {
506       g_signal_connect (G_OBJECT (w), "clicked",
507                         G_CALLBACK(confirm_button_clicked),
508                         (gpointer) CONFIRM_OK);
509       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
510     }
511
512   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
513   gtk_window_set_keep_above (GTK_WINDOW (win), TRUE);
514   gtk_widget_show_all (win);
515   gtk_window_present (GTK_WINDOW (win));  /* Make sure it has the focus.  */
516   
517   return win;
518 }
519
520
521 static int
522 gtk_cmd_handler (pinentry_t pe)
523 {
524   GtkWidget *w;
525   int want_pass = !!pe->pin;
526
527   pinentry = pe;
528   confirm_value = CONFIRM_CANCEL;
529   passphrase_ok = 0;
530   w = create_window (want_pass ? 0 : 1);
531   gtk_main ();
532   gtk_widget_destroy (w);
533   while (gtk_events_pending ())
534     gtk_main_iteration ();
535
536   if (confirm_value == CONFIRM_CANCEL || grab_failed)
537     pe->canceled = 1;
538
539   pinentry = NULL;
540   if (want_pass)
541     {
542       if (passphrase_ok && pe->pin)
543         return strlen (pe->pin);
544       else
545         return -1;
546     }
547   else
548     return (confirm_value == CONFIRM_OK) ? 1 : 0;
549 }
550
551
552 pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler;
553
554
555 int
556 main (int argc, char *argv[])
557 {
558   static GMemVTable secure_mem =
559     {
560       secentry_malloc,
561       secentry_realloc,
562       secentry_free,
563       NULL,
564       NULL,
565       NULL
566     };
567
568   g_mem_set_vtable (&secure_mem);
569
570   pinentry_init (PGMNAME);
571     
572 #ifdef FALLBACK_CURSES
573   if (pinentry_have_display (argc, argv))
574     gtk_init (&argc, &argv);
575   else
576     pinentry_cmd_handler = curses_cmd_handler;
577 #else
578   gtk_init (&argc, &argv);
579 #endif
580
581   /* Consumes all arguments.  */
582   if (pinentry_parse_opts (argc, argv))
583     {
584       printf(PGMNAME " " VERSION "\n");
585       exit(EXIT_SUCCESS);
586     }
587   
588   if (pinentry_loop ())
589     return 1;
590   
591   return 0;
592 }