2004-08-17 Marcus Brinkmann <marcus@g10code.de>
[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 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 #include "padlock-keyhole.xpm"
49
50 #define PGMNAME "pinentry-gtk2"
51
52 #ifndef VERSION
53 #  define VERSION
54 #endif
55
56 static pinentry_t pinentry;
57 static int passphrase_ok;
58 static int confirm_yes;
59
60 static GtkWidget *entry;
61 static GtkWidget *insure;
62 static GtkWidget *time_out;
63
64 /* Gnome hig small and large space in pixels.  */
65 #define HIG_SMALL      6
66 #define HIG_LARGE     12
67
68 \f
69 /* Constrain size of the window the window should not shrink beyond
70    the requisition, and should not grow vertically.  */
71 static void
72 constrain_size (GtkWidget *win, GtkRequisition *req, gpointer data)
73 {
74   static gint width, height;
75   GdkGeometry geo;
76
77   if (req->width == width && req->height == height)
78     return;
79   width = req->width;
80   height = req->height;
81   geo.min_width = width;
82   /* This limit is arbitrary, but INT_MAX breaks other things */
83   geo.max_width = 10000;
84   geo.min_height = geo.max_height = height;
85   gtk_window_set_geometry_hints (GTK_WINDOW (win), NULL, &geo,
86                                  GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
87 }
88
89
90 /* Grab the keyboard for maximum security */
91 static void
92 grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
93 {
94   if (!pinentry->grab)
95     return;
96
97   if (gdk_keyboard_grab (win->window, FALSE, gdk_event_get_time (event)))
98     g_error ("could not grab keyboard");
99 }
100
101 /* Remove grab.  */
102 static void
103 ungrab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
104 {
105   gdk_keyboard_ungrab (gdk_event_get_time (event));
106 }
107
108
109 static int
110 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
111 {
112   gtk_main_quit ();
113   return TRUE;
114 }
115
116
117 static void
118 button_clicked (GtkWidget *widget, gpointer data)
119 {
120   if (data)
121     {
122       const char *s;
123
124       /* Okay button or enter used in text field.  */
125
126       if (pinentry->enhanced)
127         printf ("Options: %s\nTimeout: %d\n\n",
128                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (insure))
129                 ? "insure" : "",
130                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (time_out)));
131
132       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry));
133       if (!s)
134         s = "";
135       passphrase_ok = 1;
136       pinentry_setbufferlen (pinentry, strlen (s) + 1);
137       if (pinentry->pin)
138         strcpy (pinentry->pin, s);
139     }
140   gtk_main_quit ();
141 }
142
143
144 static void
145 enter_callback (GtkWidget *widget, GtkWidget *anentry)
146 {
147   button_clicked (widget, "ok");
148 }
149
150
151 static void
152 confirm_button_clicked (GtkWidget *widget, gpointer data)
153 {
154   if (data)
155     /* Okay button.  */
156     confirm_yes = 1;
157
158   gtk_main_quit ();
159 }
160
161
162 static gchar *
163 pinentry_utf8_validate (gchar *text)
164 {
165   gchar *result;
166
167   if (!text)
168     return NULL;
169
170   if (g_utf8_validate (text, -1, NULL))
171     return g_strdup (text);
172
173   result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL);
174   if (!result)
175     {
176       gchar *p;
177
178       result = p = g_strdup (text);
179       while (!g_utf8_validate (p, -1, (const gchar **) &p))
180         *p = '?';
181     }
182   return result;
183 }
184
185
186 static GtkWidget *
187 create_window (int confirm_mode)
188 {
189   GtkWidget *w;
190   GtkWidget *win, *box, *ebox;
191   GtkWidget *wvbox, *chbox, *bbox;
192   GtkAccelGroup *acc;
193   GdkPixbuf *padlock_keyhole;
194   gchar *msg;
195
196   /* FIXME: check the grabbing code against the one we used with the
197      old gpg-agent */
198   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
199   acc = gtk_accel_group_new ();
200
201   g_signal_connect (G_OBJECT (win), "delete_event",
202                     G_CALLBACK (delete_event), NULL);
203
204 #if 0
205   g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (gtk_main_quit),
206                     NULL);
207 #endif
208   g_signal_connect (G_OBJECT (win), "size-request",
209                     G_CALLBACK (constrain_size), NULL);
210   if (!confirm_mode)
211     {
212       g_signal_connect (G_OBJECT (win),
213                         pinentry->grab ? "map-event" : "focus-in-event",
214                         G_CALLBACK (grab_keyboard), NULL);
215       g_signal_connect (G_OBJECT (win),
216                         pinentry->grab ? "unmap-event" : "focus-out-event",
217                         G_CALLBACK (ungrab_keyboard), NULL);
218     }
219   gtk_window_add_accel_group (GTK_WINDOW (win), acc);
220   
221   wvbox = gtk_vbox_new (FALSE, HIG_LARGE * 2);
222   gtk_container_add (GTK_CONTAINER (win), wvbox);
223   gtk_container_set_border_width (GTK_CONTAINER (wvbox), HIG_LARGE);
224
225   chbox = gtk_hbox_new (FALSE, HIG_LARGE);
226   gtk_box_pack_start (GTK_BOX (wvbox), chbox, FALSE, FALSE, 0);
227
228   padlock_keyhole = gdk_pixbuf_new_from_xpm_data (padlock_keyhole_xpm);
229   w = gtk_image_new_from_pixbuf (padlock_keyhole);
230   gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
231   gtk_box_pack_start (GTK_BOX (chbox), w, FALSE, FALSE, 0);
232
233   box = gtk_vbox_new (FALSE, HIG_SMALL);
234   gtk_box_pack_start (GTK_BOX (chbox), box, TRUE, TRUE, 0);
235
236   if (pinentry->description)
237     {
238       msg = pinentry_utf8_validate (pinentry->description);
239       w = gtk_label_new (msg);
240       g_free (msg);
241       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
242       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
243         gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
244     }
245   if (pinentry->error && !confirm_mode)
246     {
247       GdkColor color = { 0, 0xffff, 0, 0 };
248
249       msg = pinentry_utf8_validate (pinentry->error);
250       w = gtk_label_new (msg);
251       g_free (msg);
252       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
253       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
254       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
255       gtk_widget_modify_fg (w, GTK_STATE_NORMAL, &color);
256     }
257
258   ebox = gtk_hbox_new (FALSE, HIG_SMALL);
259   gtk_box_pack_start (GTK_BOX(box), ebox, FALSE, FALSE, 0);
260
261   if (!confirm_mode)
262     {
263       if (pinentry->prompt)
264         {
265           msg = pinentry_utf8_validate (pinentry->prompt);
266           w = gtk_label_new (msg);
267           g_free (msg);
268           gtk_box_pack_start (GTK_BOX (ebox), w, FALSE, FALSE, 0);
269         }
270       entry = gtk_secure_entry_new ();
271       gtk_widget_set_size_request (entry, 200, -1);
272       g_signal_connect (G_OBJECT (entry), "activate",
273                         G_CALLBACK (enter_callback), entry);
274       gtk_box_pack_start (GTK_BOX (ebox), entry, TRUE, TRUE, 0);
275       gtk_widget_grab_focus (entry);
276       gtk_widget_show (entry);
277
278       if (pinentry->enhanced)
279         {
280           GtkWidget *sbox = gtk_hbox_new (FALSE, HIG_SMALL);
281           gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0);
282
283           w = gtk_label_new ("Forget secret after");
284           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
285           gtk_widget_show (w);
286
287           time_out = gtk_spin_button_new
288             (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, HUGE_VAL, 1, 60, 60)),
289              2, 0);
290           gtk_box_pack_start (GTK_BOX (sbox), time_out, FALSE, FALSE, 0);
291           gtk_widget_show (time_out);
292           
293           w = gtk_label_new ("seconds");
294           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
295           gtk_widget_show (w);
296           gtk_widget_show (sbox);
297
298           insure = gtk_check_button_new_with_label ("ask before giving out "
299                                                     "secret");
300           gtk_box_pack_start (GTK_BOX (box), insure, FALSE, FALSE, 0);
301           gtk_widget_show (insure);
302         }
303     }
304
305   bbox = gtk_hbutton_box_new ();
306   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
307   gtk_box_set_spacing (GTK_BOX (bbox), 6);
308   gtk_box_pack_start (GTK_BOX (wvbox), bbox, TRUE, FALSE, 0);
309
310   if (pinentry->cancel)
311     {
312       msg = pinentry_utf8_validate (pinentry->cancel);
313       w = gtk_button_new_with_label (msg);
314       g_free (msg);
315     }
316   else
317     w = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
318   gtk_container_add (GTK_CONTAINER (bbox), w);
319   g_signal_connect (G_OBJECT (w), "clicked",
320                     G_CALLBACK (confirm_mode ? confirm_button_clicked
321                                 : button_clicked), NULL);
322   GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
323   
324   if (pinentry->ok)
325     {
326       msg = pinentry_utf8_validate (pinentry->ok);
327       w = gtk_button_new_with_label (msg);
328       g_free (msg);
329     }
330   else
331     w = gtk_button_new_from_stock (GTK_STOCK_OK);
332   gtk_container_add (GTK_CONTAINER(bbox), w);
333   if (!confirm_mode)
334     {
335       g_signal_connect (G_OBJECT (w), "clicked",
336                         G_CALLBACK (button_clicked), "ok");
337       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
338       gtk_widget_grab_default (w);
339       g_signal_connect_object (G_OBJECT (entry), "focus_in_event",
340                                 G_CALLBACK (gtk_widget_grab_default),
341                                G_OBJECT (w), 0);
342     }
343   else
344     {
345       g_signal_connect (G_OBJECT (w), "clicked",
346                         G_CALLBACK(confirm_button_clicked), "ok");
347       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
348     }
349
350   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
351   
352   gtk_widget_show_all(win);
353   
354   return win;
355 }
356
357
358 static int
359 gtk_cmd_handler (pinentry_t pe)
360 {
361   GtkWidget *w;
362   int want_pass = !!pe->pin;
363
364   pinentry = pe;
365   confirm_yes = 0;
366   passphrase_ok = 0;
367   w = create_window (want_pass ? 0 : 1);
368   gtk_main ();
369   gtk_widget_destroy (w);
370   while (gtk_events_pending ())
371     gtk_main_iteration ();
372
373   pinentry = NULL;
374   if (want_pass)
375     {
376       if (passphrase_ok && pe->pin)
377         return strlen (pe->pin);
378       else
379         return -1;
380     }
381   else
382     return confirm_yes;
383 }
384
385
386 pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler;
387
388
389 int
390 main (int argc, char *argv[])
391 {
392   pinentry_init (PGMNAME);
393     
394 #ifdef FALLBACK_CURSES
395   if (pinentry_have_display (argc, argv))
396     gtk_init (&argc, &argv);
397   else
398     pinentry_cmd_handler = curses_cmd_handler;
399 #else
400   gtk_init (&argc, &argv);
401 #endif
402
403   /* Consumes all arguments.  */
404   if (pinentry_parse_opts (argc, argv))
405     {
406       printf(PGMNAME " " VERSION "\n");
407       exit(EXIT_SUCCESS);
408     }
409   
410   if (pinentry_loop ())
411     return 1;
412   
413   return 0;
414 }