* pinentry/pinentry.c (cmd_message): New.
[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 #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 *insure;
60 static GtkWidget *time_out;
61
62 /* Gnome hig small and large space in pixels.  */
63 #define HIG_SMALL      6
64 #define HIG_LARGE     12
65
66 \f
67 /* Constrain size of the window the window should not shrink beyond
68    the requisition, and should not grow vertically.  */
69 static void
70 constrain_size (GtkWidget *win, GtkRequisition *req, gpointer data)
71 {
72   static gint width, height;
73   GdkGeometry geo;
74
75   if (req->width == width && req->height == height)
76     return;
77   width = req->width;
78   height = req->height;
79   geo.min_width = width;
80   /* This limit is arbitrary, but INT_MAX breaks other things */
81   geo.max_width = 10000;
82   geo.min_height = geo.max_height = height;
83   gtk_window_set_geometry_hints (GTK_WINDOW (win), NULL, &geo,
84                                  GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
85 }
86
87
88 /* Grab the keyboard for maximum security */
89 static void
90 grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
91 {
92   if (!pinentry->grab)
93     return;
94
95   if (gdk_keyboard_grab (win->window, FALSE, gdk_event_get_time (event)))
96     g_error ("could not grab keyboard");
97 }
98
99 /* Remove grab.  */
100 static void
101 ungrab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
102 {
103   gdk_keyboard_ungrab (gdk_event_get_time (event));
104 }
105
106
107 static int
108 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
109 {
110   gtk_main_quit ();
111   return TRUE;
112 }
113
114
115 static void
116 button_clicked (GtkWidget *widget, gpointer data)
117 {
118   if (data)
119     {
120       const char *s;
121
122       /* Okay button or enter used in text field.  */
123
124       if (pinentry->enhanced)
125         printf ("Options: %s\nTimeout: %d\n\n",
126                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (insure))
127                 ? "insure" : "",
128                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (time_out)));
129
130       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry));
131       if (!s)
132         s = "";
133       passphrase_ok = 1;
134       pinentry_setbufferlen (pinentry, strlen (s) + 1);
135       if (pinentry->pin)
136         strcpy (pinentry->pin, s);
137     }
138   gtk_main_quit ();
139 }
140
141
142 static void
143 enter_callback (GtkWidget *widget, GtkWidget *anentry)
144 {
145   button_clicked (widget, "ok");
146 }
147
148
149 static void
150 confirm_button_clicked (GtkWidget *widget, gpointer data)
151 {
152   if (data)
153     /* Okay button.  */
154     confirm_yes = 1;
155
156   gtk_main_quit ();
157 }
158
159
160 static gchar *
161 pinentry_utf8_validate (gchar *text)
162 {
163   gchar *result;
164
165   if (!text)
166     return NULL;
167
168   if (g_utf8_validate (text, -1, NULL))
169     return g_strdup (text);
170
171   result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL);
172   if (!result)
173     {
174       gchar *p;
175
176       result = p = g_strdup (text);
177       while (!g_utf8_validate (p, -1, (const gchar **) &p))
178         *p = '?';
179     }
180   return result;
181 }
182
183
184 static GtkWidget *
185 create_window (int confirm_mode)
186 {
187   GtkWidget *w;
188   GtkWidget *win, *box, *ebox;
189   GtkWidget *wvbox, *chbox, *bbox;
190   GtkAccelGroup *acc;
191   gchar *msg;
192
193   /* FIXME: check the grabbing code against the one we used with the
194      old gpg-agent */
195   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
196   acc = gtk_accel_group_new ();
197
198   g_signal_connect (G_OBJECT (win), "delete_event",
199                     G_CALLBACK (delete_event), NULL);
200
201 #if 0
202   g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (gtk_main_quit),
203                     NULL);
204 #endif
205   g_signal_connect (G_OBJECT (win), "size-request",
206                     G_CALLBACK (constrain_size), NULL);
207   if (!confirm_mode)
208     {
209       g_signal_connect (G_OBJECT (win),
210                         pinentry->grab ? "map-event" : "focus-in-event",
211                         G_CALLBACK (grab_keyboard), NULL);
212       g_signal_connect (G_OBJECT (win),
213                         pinentry->grab ? "unmap-event" : "focus-out-event",
214                         G_CALLBACK (ungrab_keyboard), NULL);
215     }
216   gtk_window_add_accel_group (GTK_WINDOW (win), acc);
217   
218   wvbox = gtk_vbox_new (FALSE, HIG_LARGE * 2);
219   gtk_container_add (GTK_CONTAINER (win), wvbox);
220   gtk_container_set_border_width (GTK_CONTAINER (wvbox), HIG_LARGE);
221
222   chbox = gtk_hbox_new (FALSE, HIG_LARGE);
223   gtk_box_pack_start (GTK_BOX (wvbox), chbox, FALSE, FALSE, 0);
224
225   w = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
226                                                GTK_ICON_SIZE_DIALOG);
227   gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
228   gtk_box_pack_start (GTK_BOX (chbox), w, FALSE, FALSE, 0);
229
230   box = gtk_vbox_new (FALSE, HIG_SMALL);
231   gtk_box_pack_start (GTK_BOX (chbox), box, TRUE, TRUE, 0);
232
233   if (pinentry->description)
234     {
235       msg = pinentry_utf8_validate (pinentry->description);
236       w = gtk_label_new (msg);
237       g_free (msg);
238       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
239       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
240         gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
241     }
242   if (pinentry->error && !confirm_mode)
243     {
244       GdkColor color = { 0, 0xffff, 0, 0 };
245
246       msg = pinentry_utf8_validate (pinentry->error);
247       w = gtk_label_new (msg);
248       g_free (msg);
249       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
250       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
251       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
252       gtk_widget_modify_fg (w, GTK_STATE_NORMAL, &color);
253     }
254
255   ebox = gtk_hbox_new (FALSE, HIG_SMALL);
256   gtk_box_pack_start (GTK_BOX(box), ebox, FALSE, FALSE, 0);
257
258   if (!confirm_mode)
259     {
260       if (pinentry->prompt)
261         {
262           msg = pinentry_utf8_validate (pinentry->prompt);
263           w = gtk_label_new (msg);
264           g_free (msg);
265           gtk_box_pack_start (GTK_BOX (ebox), w, FALSE, FALSE, 0);
266         }
267       entry = gtk_secure_entry_new ();
268       gtk_widget_set_size_request (entry, 200, -1);
269       g_signal_connect (G_OBJECT (entry), "activate",
270                         G_CALLBACK (enter_callback), entry);
271       gtk_box_pack_start (GTK_BOX (ebox), entry, TRUE, TRUE, 0);
272       gtk_widget_grab_focus (entry);
273       gtk_widget_show (entry);
274
275       if (pinentry->enhanced)
276         {
277           GtkWidget *sbox = gtk_hbox_new (FALSE, HIG_SMALL);
278           gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0);
279
280           w = gtk_label_new ("Forget secret after");
281           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
282           gtk_widget_show (w);
283
284           time_out = gtk_spin_button_new
285             (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, HUGE_VAL, 1, 60, 60)),
286              2, 0);
287           gtk_box_pack_start (GTK_BOX (sbox), time_out, FALSE, FALSE, 0);
288           gtk_widget_show (time_out);
289           
290           w = gtk_label_new ("seconds");
291           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
292           gtk_widget_show (w);
293           gtk_widget_show (sbox);
294
295           insure = gtk_check_button_new_with_label ("ask before giving out "
296                                                     "secret");
297           gtk_box_pack_start (GTK_BOX (box), insure, FALSE, FALSE, 0);
298           gtk_widget_show (insure);
299         }
300     }
301
302   bbox = gtk_hbutton_box_new ();
303   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
304   gtk_box_set_spacing (GTK_BOX (bbox), 6);
305   gtk_box_pack_start (GTK_BOX (wvbox), bbox, TRUE, FALSE, 0);
306
307   if (!pinentry->one_button)
308     {
309       if (pinentry->cancel)
310         {
311           msg = pinentry_utf8_validate (pinentry->cancel);
312           w = gtk_button_new_with_label (msg);
313           g_free (msg);
314         }
315       else
316         w = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
317       gtk_container_add (GTK_CONTAINER (bbox), w);
318       g_signal_connect (G_OBJECT (w), "clicked",
319                         G_CALLBACK (confirm_mode ? confirm_button_clicked
320                                     : button_clicked), NULL);
321       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
322     }
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 }