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