57e917658624f4d396c6a55f28e84673edc1d081
[pinentry.git] / gtk / pinentry-gtk.c
1 /* gpinentry.c 
2  * Copyright (C) 2001, 2002 g10 Code GmbH
3  * Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtk.h>
25 #include <assert.h>
26 #include <limits.h>
27 #include <math.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <X11/X.h>
32
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 #include "memory.h"
43
44
45 #ifdef FALLBACK_CURSES
46 #include "pinentry-curses.h"
47 #endif
48
49 #define PGMNAME "pinentry-gtk"
50
51 static pinentry_t pinentry;
52 static int passphrase_ok = 0;
53 static int confirm_yes = 0;
54
55 static GtkWidget *entry, *insure, *time_out;
56
57 #if 0 /* not used */
58 /* ok - Return to the command handler routine.  */
59 static void 
60 ok (GtkWidget *w, gpointer data)
61 {
62   gtk_main_quit ();
63 }
64 #endif /* not used */
65
66 /* unselect - work around a bug in GTK+ that permits word-selection to
67    work on the invisible passphrase */
68 static void 
69 unselect(GtkWidget *w, GdkEventButton *e)
70 {
71   static gint lastpos;
72
73   if (e->button == 1) {
74     if (e->type == GDK_BUTTON_PRESS) {
75       lastpos = gtk_editable_get_position(GTK_EDITABLE(entry));
76     } else if (e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) {
77       gtk_secure_entry_set_position(GTK_SECURE_ENTRY(w), lastpos);
78       gtk_secure_entry_select_region(GTK_SECURE_ENTRY(w), 0, 0);
79     }
80   }
81 }
82
83 /* constrain_size - constrain size of the window the window should not
84    shrink beyond the requisition, and should not grow vertically */
85 static void 
86 constrain_size(GtkWidget *win, GtkRequisition *req, gpointer data)
87 {
88   static gint width, height;
89   GdkGeometry geo;
90
91   if (req->width == width && req->height == height)
92     return; 
93   width = req->width;
94   height = req->height;
95   geo.min_width = width;
96   geo.max_width = 10000;        /* this limit is arbitrary,
97                                    but INT_MAX breaks other things */
98   geo.min_height = geo.max_height = height;
99   gtk_window_set_geometry_hints(GTK_WINDOW(win), NULL, &geo,
100                                 GDK_HINT_MIN_SIZE|GDK_HINT_MAX_SIZE);
101 }
102
103 /* grab_keyboard - grab the keyboard for maximum security */
104 static void 
105 grab_keyboard(GtkWidget *win, GdkEvent *event, gpointer data)
106 {
107   if (!pinentry->grab)
108     return;
109   if (gdk_keyboard_grab(win->window, FALSE, gdk_event_get_time(event))) 
110     {
111       g_error("could not grab keyboard");
112     }
113 }
114
115 /* ungrab_keyboard - remove grab */
116 static void 
117 ungrab_keyboard(GtkWidget *win, GdkEvent *event, gpointer data)
118 {
119   gdk_keyboard_ungrab(gdk_event_get_time(event));
120 }
121
122
123 static int
124 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
125 {
126   gtk_main_quit ();
127   return TRUE;
128 }
129
130 static void
131 button_clicked (GtkWidget *widget, gpointer data)
132 {
133   if (data)
134     { /* Okay button hit or Enter used in the text field. */
135       const char *s;
136       char *s_utf8;
137       char *s_buffer;
138
139       if (pinentry->enhanced)
140         {
141           printf("Options: %s\nTimeout: %d\n\n",
142                  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(insure)) ?
143                  "insure"
144                  : "",
145                  gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(time_out)));
146         }
147
148       pinentry->locale_err = 1;
149       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY(entry));
150       if (!s)
151         s = "";
152       s_buffer = secmem_malloc (strlen (s) + 1);
153       if (s_buffer)
154         {
155           strcpy (s_buffer, s);
156           s_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, s_buffer, 1);
157           secmem_free (s_buffer);
158           if (s_utf8)
159             {
160               passphrase_ok = 1;
161               pinentry_setbufferlen (pinentry, strlen (s_utf8) + 1);
162               if (pinentry->pin)
163                 strcpy (pinentry->pin, s_utf8);
164               secmem_free (s_utf8);
165               pinentry->locale_err = 0;
166             }
167         }
168     }
169   gtk_main_quit ();
170 }
171
172
173 static void 
174 enter_callback (GtkWidget *widget, GtkWidget *anentry)
175 {
176   button_clicked (widget, "ok");
177 }
178
179
180 static void
181 confirm_button_clicked (GtkWidget *widget, gpointer data)
182 {
183   if (data)
184     { /* okay button */
185       confirm_yes = 1;
186     }
187   gtk_main_quit ();
188 }
189
190
191 static GtkWidget *
192 create_utf8_label (char *text)
193 {
194   GtkWidget *w;
195   char *buf;
196
197   buf = pinentry_utf8_to_local (pinentry->lc_ctype, text);
198   if (buf)
199     {
200       w = gtk_label_new (buf);
201       free (buf);
202     }
203   else /* no core - simply don't convert */
204     w = gtk_label_new (text);
205   return w;
206 }
207
208
209 static GtkWidget *
210 create_window (int confirm_mode)
211 {
212   GtkWidget *w;
213   GtkWidget *win, *box, *ebox;
214   GtkWidget *sbox, *bbox;
215   GtkAccelGroup *acc;
216
217   /* fixme: check the grabbing code against the one we used with the
218      old gpg-agent */
219   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
220   acc = gtk_accel_group_new ();
221
222   gtk_signal_connect (GTK_OBJECT (win), "delete_event",
223                       GTK_SIGNAL_FUNC (delete_event), NULL);
224
225 #if 0
226   gtk_signal_connect (GTK_OBJECT(win), "destroy", gtk_main_quit, NULL);
227 #endif
228   gtk_signal_connect (GTK_OBJECT(win), "size-request", constrain_size, NULL);
229   if (!confirm_mode)
230     {
231       gtk_signal_connect (GTK_OBJECT(win),
232                           pinentry->grab ? "map-event" : "focus-in-event",
233                           grab_keyboard, NULL);
234       gtk_signal_connect (GTK_OBJECT(win),
235                           pinentry->grab ? "unmap-event" : "focus-out-event",
236                           ungrab_keyboard, NULL);
237     }
238   gtk_accel_group_attach(acc, GTK_OBJECT(win));
239
240   box = gtk_vbox_new (FALSE, 5);
241   gtk_container_add (GTK_CONTAINER(win), box);
242   gtk_container_set_border_width (GTK_CONTAINER (box), 5);
243
244   if (pinentry->description)
245     {
246       w = create_utf8_label (pinentry->description);
247       gtk_box_pack_start (GTK_BOX(box), w, TRUE, FALSE, 0);
248     }
249   if (pinentry->error && !confirm_mode)
250     {
251       GtkStyle *style;
252       GdkColormap *cmap;
253       GdkColor color[1] = {{0, 0xffff, 0, 0}};
254       gboolean success[1];
255
256       w = create_utf8_label (pinentry->error);
257       gtk_box_pack_start (GTK_BOX(box), w, TRUE, FALSE, 5);
258
259       /* fixme: Do we need to release something, or is there a more
260          easy way to set a text color? */
261       gtk_widget_realize (win);
262       cmap = gdk_window_get_colormap (win->window);
263       assert (cmap);
264       gdk_colormap_alloc_colors (cmap, color, 1, FALSE, TRUE, success);
265       if (success[0])
266         {
267           gtk_widget_ensure_style(w);
268           style = gtk_style_copy(gtk_widget_get_style(w));
269           style->fg[GTK_STATE_NORMAL] = color[0];
270           gtk_widget_set_style(w, style);
271         } 
272     }
273
274   ebox = gtk_hbox_new (FALSE, 5);
275   gtk_box_pack_start (GTK_BOX(box), ebox, FALSE, FALSE, 0);
276   gtk_container_set_border_width (GTK_CONTAINER (ebox), 5);
277
278
279   if (!confirm_mode)
280     {
281       if (pinentry->prompt)
282         {
283           w = create_utf8_label (pinentry->prompt);
284           gtk_box_pack_start (GTK_BOX(ebox), w, FALSE, FALSE, 0);
285         }
286       entry = gtk_secure_entry_new ();
287       gtk_signal_connect (GTK_OBJECT (entry), "activate",
288                           GTK_SIGNAL_FUNC (enter_callback), entry);
289       gtk_box_pack_start (GTK_BOX(ebox), entry, TRUE, TRUE, 0);
290       gtk_secure_entry_set_visibility (GTK_SECURE_ENTRY(entry), FALSE);
291       gtk_signal_connect_after (GTK_OBJECT(entry), "button_press_event",
292                                 unselect, NULL);
293       gtk_widget_grab_focus (entry);
294       gtk_widget_show (entry);
295
296       if (pinentry->enhanced)
297         {
298           sbox = gtk_hbox_new(FALSE, 5);
299           gtk_box_pack_start(GTK_BOX(box), sbox, FALSE, FALSE, 0);
300           
301           w = gtk_label_new ("Forget secret after");
302           gtk_box_pack_start(GTK_BOX(sbox), w, FALSE, FALSE, 0);
303           gtk_widget_show(w);
304           
305           time_out = gtk_spin_button_new
306             (GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, HUGE_VAL,
307                                                1, 60, 60)),2,0);
308           gtk_box_pack_start (GTK_BOX(sbox), time_out, FALSE, FALSE, 0);
309           gtk_widget_show (time_out);
310         
311           w = gtk_label_new ("seconds");
312           gtk_box_pack_start (GTK_BOX(sbox), w, FALSE, FALSE, 0); 
313           gtk_widget_show (w);
314           gtk_widget_show (sbox);
315           
316           insure = gtk_check_button_new_with_label
317             ("ask before giving out secret");
318           gtk_box_pack_start (GTK_BOX(box), insure, FALSE, FALSE, 0);
319           gtk_widget_show (insure);
320         }
321     }
322
323
324   bbox = gtk_hbutton_box_new();
325   gtk_box_pack_start (GTK_BOX(box), bbox, TRUE, FALSE, 0);
326   gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
327   
328   w = gtk_button_new_with_label (pinentry->ok ? pinentry->ok : "OK");
329   gtk_container_add (GTK_CONTAINER(bbox), w);
330   if (!confirm_mode)
331     {
332       gtk_signal_connect (GTK_OBJECT(w), "clicked", button_clicked, "ok");
333       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
334       gtk_widget_grab_default (w);
335       gtk_signal_connect_object (GTK_OBJECT (entry), "focus_in_event",
336                                  GTK_SIGNAL_FUNC (gtk_widget_grab_default),
337                                  GTK_OBJECT (w));
338     }
339   else
340     {
341       gtk_signal_connect (GTK_OBJECT(w), "clicked",
342                           confirm_button_clicked, "ok");
343       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
344     }
345
346   gtk_widget_show (w);
347   
348   w = gtk_button_new_with_label (pinentry->cancel ? pinentry->cancel : "Cancel");
349   gtk_container_add (GTK_CONTAINER(bbox), w);
350   gtk_accel_group_add (acc, GDK_Escape, 0, 0, GTK_OBJECT(w), "clicked");
351   gtk_signal_connect (GTK_OBJECT(w), "clicked", 
352                       confirm_mode? confirm_button_clicked: button_clicked,
353                       NULL);
354   GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
355
356   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
357
358   gtk_widget_show_all (win);
359
360   return win;
361 }
362
363
364
365 static int
366 gtk_cmd_handler (pinentry_t pe)
367 {
368   GtkWidget *w;
369   int want_pass = !!pe->pin;
370
371   pinentry = pe;
372   confirm_yes = 0;
373   passphrase_ok = 0;
374   w = create_window (want_pass ? 0 : 1);
375   gtk_main();
376   gtk_widget_destroy (w);
377   while (gtk_events_pending ())
378     gtk_main_iteration ();
379
380   pinentry = NULL;
381   if (want_pass)
382     {
383       if (passphrase_ok && pe->pin)
384         return strlen (pe->pin);
385       else
386         return -1;
387     }
388   else
389     return confirm_yes;
390 }
391
392 pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler;
393
394 int 
395 main (int argc, char *argv[])
396 {
397   pinentry_init (PGMNAME);
398
399 #ifdef FALLBACK_CURSES
400   if (pinentry_have_display (argc, argv))
401     gtk_init (&argc, &argv);
402   else
403     pinentry_cmd_handler = curses_cmd_handler;
404 #else
405   gtk_init (&argc, &argv);
406 #endif
407
408   /* Consumes all arguments.  */
409   if (pinentry_parse_opts (argc, argv))
410     {
411       printf ("pinentry-gtk (pinentry) " VERSION "\n");
412       exit (EXIT_SUCCESS);
413     }
414
415   if (pinentry_loop ())
416     return 1;
417
418   return 0;
419 }
420