4d8570416982561fe54a61d4195142ca0e7337f0
[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 passphrase_ok;
56 typedef enum { CONFIRM_CANCEL, CONFIRM_OK, CONFIRM_NOTOK } confirm_value_t;
57 static confirm_value_t confirm_value;
58
59 static GtkWidget *entry;
60 static GtkWidget *qualitybar;
61 static GtkWidget *insure;
62 static GtkWidget *time_out;
63 static GtkTooltips *tooltips;
64
65 /* Gnome hig small and large space in pixels.  */
66 #define HIG_SMALL      6
67 #define HIG_LARGE     12
68
69 /* The text shown in the quality bar when no text is shown.  This is not
70  * the empty string, becase because with an empty string the height of
71  * the quality bar is less than with a non-empty string.  This results
72  * in ugly layout changes when the text changes from non-empty to empty
73  * and vice versa */
74 #define QUALITYBAR_EMPTY_TEXT " "
75
76 \f
77 /* Constrain size of the window the window should not shrink beyond
78    the requisition, and should not grow vertically.  */
79 static void
80 constrain_size (GtkWidget *win, GtkRequisition *req, gpointer data)
81 {
82   static gint width, height;
83   GdkGeometry geo;
84
85   if (req->width == width && req->height == height)
86     return;
87   width = req->width;
88   height = req->height;
89   geo.min_width = width;
90   /* This limit is arbitrary, but INT_MAX breaks other things */
91   geo.max_width = 10000;
92   geo.min_height = geo.max_height = height;
93   gtk_window_set_geometry_hints (GTK_WINDOW (win), NULL, &geo,
94                                  GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
95 }
96
97  
98 /* Realize the window as transient if we grab the keyboard.  This
99    makes the window a modal dialog to the root window, which helps the
100    window manager.  See the following quote from:
101    http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2512420
102
103    Implementing enhanced support for application transient windows
104
105    If the WM_TRANSIENT_FOR property is set to None or Root window, the
106    window should be treated as a transient for all other windows in
107    the same group. It has been noted that this is a slight ICCCM
108    violation, but as this behavior is pretty standard for many
109    toolkits and window managers, and is extremely unlikely to break
110    anything, it seems reasonable to document it as standard.  */
111
112 static void
113 make_transient (GtkWidget *win, GdkEvent *event, gpointer data)
114 {
115   GdkScreen *screen;
116   GdkWindow *root;
117
118   if (! pinentry->grab)
119     return;
120
121   /* Make window transient for the root window.  */
122   screen = gdk_screen_get_default ();
123   root = gdk_screen_get_root_window (screen);
124   gdk_window_set_transient_for (win->window, root);
125 }
126
127
128 /* Grab the keyboard for maximum security */
129 static void
130 grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
131 {
132   if (! pinentry->grab)
133     return;
134
135   if (gdk_keyboard_grab (win->window, FALSE, gdk_event_get_time (event)))
136     g_error ("could not grab keyboard");
137 }
138
139
140 /* Remove grab.  */
141 static void
142 ungrab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
143 {
144   gdk_keyboard_ungrab (gdk_event_get_time (event));
145   /* Unmake window transient for the root window.  */
146   gdk_window_set_transient_for (win->window, NULL);
147 }
148
149
150 static int
151 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
152 {
153   gtk_main_quit ();
154   return TRUE;
155 }
156
157
158 static void
159 button_clicked (GtkWidget *widget, gpointer data)
160 {
161   if (data)
162     {
163       const char *s;
164
165       /* Okay button or enter used in text field.  */
166
167       if (pinentry->enhanced)
168         printf ("Options: %s\nTimeout: %d\n\n",
169                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (insure))
170                 ? "insure" : "",
171                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (time_out)));
172
173       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry));
174       if (!s)
175         s = "";
176       passphrase_ok = 1;
177       pinentry_setbufferlen (pinentry, strlen (s) + 1);
178       if (pinentry->pin)
179         strcpy (pinentry->pin, s);
180     }
181   gtk_main_quit ();
182 }
183
184
185 static void
186 enter_callback (GtkWidget *widget, GtkWidget *anentry)
187 {
188   button_clicked (widget, "ok");
189 }
190
191
192 static void
193 confirm_button_clicked (GtkWidget *widget, gpointer data)
194 {
195   confirm_value = (int) data;
196   gtk_main_quit ();
197 }
198
199
200 static gchar *
201 pinentry_utf8_validate (gchar *text)
202 {
203   gchar *result;
204
205   if (!text)
206     return NULL;
207
208   if (g_utf8_validate (text, -1, NULL))
209     return g_strdup (text);
210
211   result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL);
212   if (!result)
213     {
214       gchar *p;
215
216       result = p = g_strdup (text);
217       while (!g_utf8_validate (p, -1, (const gchar **) &p))
218         *p = '?';
219     }
220   return result;
221 }
222
223
224 /* Handler called for "changed".   We use it to update the quality
225    indicator.  */
226 static void
227 changed_text_handler (GtkWidget *widget)
228 {
229   char textbuf[50];
230   const char *s;
231   int length;
232   int percent;
233   GdkColor color = { 0, 0, 0, 0};
234
235   if (!qualitybar || !pinentry->quality_bar)
236     return;
237
238   s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (widget));
239   if (!s)
240     s = "";
241   length = strlen (s);
242   percent = length? pinentry_inq_quality (pinentry, s, length) : 0;
243   if (!length)
244     {
245       strcpy(textbuf, QUALITYBAR_EMPTY_TEXT);
246       color.red = 0xffff;
247     }
248   else if (percent < 0)
249     {
250       snprintf (textbuf, sizeof textbuf, "(%d%%)", -percent);
251       color.red = 0xffff;
252       percent = -percent;
253     }
254   else
255     {
256       snprintf (textbuf, sizeof textbuf, "%d%%", percent);
257       color.green = 0xffff;
258     }
259   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 
260                                  (double)percent/100.0);
261   gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar), textbuf);
262   gtk_widget_modify_bg (qualitybar, GTK_STATE_PRELIGHT, &color);
263 }
264
265
266
267 static GtkWidget *
268 create_window (int confirm_mode)
269 {
270   GtkWidget *w;
271   GtkWidget *win, *box;
272   GtkWidget *wvbox, *chbox, *bbox;
273   GtkAccelGroup *acc;
274   gchar *msg;
275
276   tooltips = gtk_tooltips_new ();
277
278   /* FIXME: check the grabbing code against the one we used with the
279      old gpg-agent */
280   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
281   acc = gtk_accel_group_new ();
282
283   g_signal_connect (G_OBJECT (win), "delete_event",
284                     G_CALLBACK (delete_event), NULL);
285
286 #if 0
287   g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (gtk_main_quit),
288                     NULL);
289 #endif
290   g_signal_connect (G_OBJECT (win), "size-request",
291                     G_CALLBACK (constrain_size), NULL);
292   if (!confirm_mode)
293     {
294       if (pinentry->grab)
295         g_signal_connect (G_OBJECT (win),
296                           "realize", G_CALLBACK (make_transient), NULL);
297
298       g_signal_connect (G_OBJECT (win),
299                         pinentry->grab ? "map-event" : "focus-in-event",
300                         G_CALLBACK (grab_keyboard), NULL);
301       g_signal_connect (G_OBJECT (win),
302                         pinentry->grab ? "unmap-event" : "focus-out-event",
303                         G_CALLBACK (ungrab_keyboard), NULL);
304     }
305   gtk_window_add_accel_group (GTK_WINDOW (win), acc);
306   
307   wvbox = gtk_vbox_new (FALSE, HIG_LARGE * 2);
308   gtk_container_add (GTK_CONTAINER (win), wvbox);
309   gtk_container_set_border_width (GTK_CONTAINER (wvbox), HIG_LARGE);
310
311   chbox = gtk_hbox_new (FALSE, HIG_LARGE);
312   gtk_box_pack_start (GTK_BOX (wvbox), chbox, FALSE, FALSE, 0);
313
314   w = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
315                                                GTK_ICON_SIZE_DIALOG);
316   gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
317   gtk_box_pack_start (GTK_BOX (chbox), w, FALSE, FALSE, 0);
318
319   box = gtk_vbox_new (FALSE, HIG_SMALL);
320   gtk_box_pack_start (GTK_BOX (chbox), box, TRUE, TRUE, 0);
321
322   if (pinentry->title)
323     {
324       msg = pinentry_utf8_validate (pinentry->title);
325       gtk_window_set_title (GTK_WINDOW(win), msg);
326     }
327   if (pinentry->description)
328     {
329       msg = pinentry_utf8_validate (pinentry->description);
330       w = gtk_label_new (msg);
331       g_free (msg);
332       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
333       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
334       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
335     }
336   if (pinentry->error && !confirm_mode)
337     {
338       GdkColor color = { 0, 0xffff, 0, 0 };
339
340       msg = pinentry_utf8_validate (pinentry->error);
341       w = gtk_label_new (msg);
342       g_free (msg);
343       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
344       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
345       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
346       gtk_widget_modify_fg (w, GTK_STATE_NORMAL, &color);
347     }
348
349   qualitybar = NULL;
350
351   if (!confirm_mode)
352     {
353       GtkWidget* table = gtk_table_new (pinentry->quality_bar ? 2 : 1, 2,
354                                         FALSE);
355       gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 0);
356
357       if (pinentry->prompt)
358         {
359           msg = pinentry_utf8_validate (pinentry->prompt);
360           w = gtk_label_new (msg);
361           g_free (msg);
362           gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
363           gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1,
364                             GTK_FILL, GTK_FILL, 4, 0);
365         }
366
367       entry = gtk_secure_entry_new ();
368       gtk_widget_set_size_request (entry, 200, -1);
369       g_signal_connect (G_OBJECT (entry), "activate",
370                         G_CALLBACK (enter_callback), entry);
371       g_signal_connect (G_OBJECT (entry), "changed",
372                         G_CALLBACK (changed_text_handler), entry);
373       gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1,
374                         GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
375       gtk_widget_grab_focus (entry);
376       gtk_widget_show (entry);
377
378       if (pinentry->quality_bar)
379         {
380           msg = pinentry_utf8_validate (pinentry->quality_bar);
381           w = gtk_label_new (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, 1, 2,
385                             GTK_FILL, GTK_FILL, 4, 0);
386           qualitybar = gtk_progress_bar_new();
387           gtk_widget_add_events (qualitybar,
388                                  GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
389           gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar),
390                                      QUALITYBAR_EMPTY_TEXT);
391           gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 0.0);
392           if (pinentry->quality_bar_tt)
393             gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), qualitybar,
394                                   pinentry->quality_bar_tt, "");
395           gtk_table_attach (GTK_TABLE (table), qualitybar, 1, 2, 1, 2,
396                             GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
397         }
398
399       if (pinentry->enhanced)
400         {
401           GtkWidget *sbox = gtk_hbox_new (FALSE, HIG_SMALL);
402           gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0);
403
404           w = gtk_label_new ("Forget secret after");
405           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
406           gtk_widget_show (w);
407
408           time_out = gtk_spin_button_new
409             (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, HUGE_VAL, 1, 60, 60)),
410              2, 0);
411           gtk_box_pack_start (GTK_BOX (sbox), time_out, FALSE, FALSE, 0);
412           gtk_widget_show (time_out);
413           
414           w = gtk_label_new ("seconds");
415           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
416           gtk_widget_show (w);
417           gtk_widget_show (sbox);
418
419           insure = gtk_check_button_new_with_label ("ask before giving out "
420                                                     "secret");
421           gtk_box_pack_start (GTK_BOX (box), insure, FALSE, FALSE, 0);
422           gtk_widget_show (insure);
423         }
424     }
425
426   bbox = gtk_hbutton_box_new ();
427   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
428   gtk_box_set_spacing (GTK_BOX (bbox), 6);
429   gtk_box_pack_start (GTK_BOX (wvbox), bbox, TRUE, FALSE, 0);
430
431   if (!pinentry->one_button)
432     {
433       if (pinentry->cancel)
434         {
435           msg = pinentry_utf8_validate (pinentry->cancel);
436           w = gtk_button_new_with_label (msg);
437           g_free (msg);
438         }
439       else
440         w = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
441       gtk_container_add (GTK_CONTAINER (bbox), w);
442       g_signal_connect (G_OBJECT (w), "clicked",
443                         G_CALLBACK (confirm_mode ? confirm_button_clicked
444                                     : button_clicked),
445                         (gpointer) CONFIRM_CANCEL);
446       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
447     }
448   
449   if (confirm_mode && !pinentry->one_button && pinentry->notok)
450     {
451       msg = pinentry_utf8_validate (pinentry->notok);
452       w = gtk_button_new_with_label (msg);
453       g_free (msg);
454
455       gtk_container_add (GTK_CONTAINER (bbox), w);
456       g_signal_connect (G_OBJECT (w), "clicked",
457                         G_CALLBACK (confirm_button_clicked),
458                         (gpointer) CONFIRM_NOTOK);
459       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
460     }
461   
462   if (pinentry->ok)
463     {
464       msg = pinentry_utf8_validate (pinentry->ok);
465       w = gtk_button_new_with_label (msg);
466       g_free (msg);
467     }
468   else
469     w = gtk_button_new_from_stock (GTK_STOCK_OK);
470   gtk_container_add (GTK_CONTAINER(bbox), w);
471   if (!confirm_mode)
472     {
473       g_signal_connect (G_OBJECT (w), "clicked",
474                         G_CALLBACK (button_clicked), "ok");
475       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
476       gtk_widget_grab_default (w);
477       g_signal_connect_object (G_OBJECT (entry), "focus_in_event",
478                                 G_CALLBACK (gtk_widget_grab_default),
479                                G_OBJECT (w), 0);
480     }
481   else
482     {
483       g_signal_connect (G_OBJECT (w), "clicked",
484                         G_CALLBACK(confirm_button_clicked),
485                         (gpointer) CONFIRM_OK);
486       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
487     }
488
489   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
490   
491   gtk_widget_show_all(win);
492   
493   return win;
494 }
495
496
497 static int
498 gtk_cmd_handler (pinentry_t pe)
499 {
500   GtkWidget *w;
501   int want_pass = !!pe->pin;
502
503   pinentry = pe;
504   confirm_value = CONFIRM_CANCEL;
505   passphrase_ok = 0;
506   w = create_window (want_pass ? 0 : 1);
507   gtk_main ();
508   gtk_widget_destroy (w);
509   while (gtk_events_pending ())
510     gtk_main_iteration ();
511
512   if (confirm_value == CONFIRM_CANCEL)
513     pe->canceled = 1;
514
515   pinentry = NULL;
516   if (want_pass)
517     {
518       if (passphrase_ok && pe->pin)
519         return strlen (pe->pin);
520       else
521         return -1;
522     }
523   else
524     return (confirm_value == CONFIRM_OK) ? 1 : 0;
525 }
526
527
528 pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler;
529
530
531 int
532 main (int argc, char *argv[])
533 {
534   pinentry_init (PGMNAME);
535     
536 #ifdef FALLBACK_CURSES
537   if (pinentry_have_display (argc, argv))
538     gtk_init (&argc, &argv);
539   else
540     pinentry_cmd_handler = curses_cmd_handler;
541 #else
542   gtk_init (&argc, &argv);
543 #endif
544
545   /* Consumes all arguments.  */
546   if (pinentry_parse_opts (argc, argv))
547     {
548       printf(PGMNAME " " VERSION "\n");
549       exit(EXIT_SUCCESS);
550     }
551   
552   if (pinentry_loop ())
553     return 1;
554   
555   return 0;
556 }