2002-04-05 Marcus Brinkmann <marcus@g10code.de>
[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 <assert.h>
24 #include <limits.h>
25 #include <math.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtk.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
43 #define PGMNAME "pinentry-gtk"
44
45 static pinentry_t pinentry;
46 static int passphrase_ok = 0;
47 static int confirm_yes = 0;
48
49 static GtkWidget *entry, *insure, *timeout;
50
51 /* ok - Return to the command handler routine.  */
52 static void 
53 ok (GtkWidget *w, gpointer data)
54 {
55   gtk_main_quit ();
56 }
57
58
59 /* unselect - work around a bug in GTK+ that permits word-selection to
60    work on the invisible passphrase */
61 static void 
62 unselect(GtkWidget *w, GdkEventButton *e)
63 {
64   static gint lastpos;
65
66   if (e->button == 1) {
67     if (e->type == GDK_BUTTON_PRESS) {
68       lastpos = gtk_editable_get_position(GTK_EDITABLE(entry));
69     } else if (e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) {
70       gtk_secure_entry_set_position(GTK_SECURE_ENTRY(w), lastpos);
71       gtk_secure_entry_select_region(GTK_SECURE_ENTRY(w), 0, 0);
72     }
73   }
74 }
75
76 /* constrain_size - constrain size of the window the window should not
77    shrink beyond 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   geo.max_width = 10000;        /* this limit is arbitrary,
90                                    but INT_MAX breaks other things */
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 /* grab_keyboard - grab the keyboard for maximum security */
97 static void 
98 grab_keyboard(GtkWidget *win, GdkEvent *event, gpointer data)
99 {
100   if (!pinentry->grab)
101     return;
102   if (gdk_keyboard_grab(win->window, FALSE, gdk_event_get_time(event))) 
103     {
104       g_error("could not grab keyboard");
105     }
106 }
107
108 /* ungrab_keyboard - remove grab */
109 static void 
110 ungrab_keyboard(GtkWidget *win, GdkEvent *event, gpointer data)
111 {
112   gdk_keyboard_ungrab(gdk_event_get_time(event));
113 }
114
115
116 static int
117 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
118 {
119   gtk_main_quit ();
120   return TRUE;
121 }
122
123 static void
124 button_clicked (GtkWidget *widget, gpointer data)
125 {
126   if (data)
127     { /* okay button or enter used inntext field */
128       const char *s;
129       
130       if (pinentry->enhanced)
131         {
132           printf("Options: %s\nTimeout: %d\n\n",
133                  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(insure)) ?
134                  "insure"
135                  : "",
136                  gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(timeout)));
137         }
138
139       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY(entry));
140       if (!s)
141         s = "";
142       passphrase_ok = 1;
143       pinentry_setbufferlen (pinentry, strlen(s)+1);
144       if (pinentry->pin)
145         strcpy (pinentry->pin, s);
146     }
147   gtk_main_quit ();
148 }
149
150
151 static void 
152 enter_callback (GtkWidget *widget, GtkWidget *entry)
153 {
154   button_clicked (widget, "ok");
155 }
156
157
158 static void
159 confirm_button_clicked (GtkWidget *widget, gpointer data)
160 {
161   if (data)
162     { /* okay button */
163       confirm_yes = 1;
164     }
165   gtk_main_quit ();
166 }
167
168
169 static GtkWidget *
170 create_window (int confirm_mode)
171 {
172   GtkWidget *w;
173   GtkWidget *win, *box, *ebox;
174   GtkWidget *sbox, *bbox;
175   GtkAccelGroup *acc;
176   int i;
177
178   /* fixme: check the grabbing code against the one we used with the
179      old gpg-agent */
180   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
181   acc = gtk_accel_group_new ();
182
183   gtk_signal_connect (GTK_OBJECT (win), "delete_event",
184                       GTK_SIGNAL_FUNC (delete_event), NULL);
185
186 #if 0
187   gtk_signal_connect (GTK_OBJECT(win), "destroy", gtk_main_quit, NULL);
188 #endif
189   gtk_signal_connect (GTK_OBJECT(win), "size-request", constrain_size, NULL);
190   if (!confirm_mode)
191     {
192       gtk_signal_connect (GTK_OBJECT(win),
193                           pinentry->grab ? "map-event" : "focus-in-event",
194                           grab_keyboard, NULL);
195       gtk_signal_connect (GTK_OBJECT(win),
196                           pinentry->grab ? "unmap-event" : "focus-out-event",
197                           ungrab_keyboard, NULL);
198     }
199   gtk_accel_group_attach(acc, GTK_OBJECT(win));
200
201   box = gtk_vbox_new (FALSE, 5);
202   gtk_container_add (GTK_CONTAINER(win), box);
203   gtk_container_set_border_width (GTK_CONTAINER (box), 5);
204
205   if (pinentry->description)
206     {
207       w = gtk_label_new (pinentry->description);
208       gtk_box_pack_start (GTK_BOX(box), w, TRUE, FALSE, 0);
209     }
210   if (pinentry->error && !confirm_mode)
211     {
212       GtkStyle *style;
213       GdkColormap *cmap;
214       GdkColor color[1] = {{0, 0xffff, 0, 0}};
215       gboolean success[1];
216
217       w = gtk_label_new (pinentry->error);
218       gtk_box_pack_start (GTK_BOX(box), w, TRUE, FALSE, 5);
219
220       /* fixme: Do we need to release something, or is there a more
221          easy way to set a text color? */
222       gtk_widget_realize (win);
223       cmap = gdk_window_get_colormap (win->window);
224       assert (cmap);
225       gdk_colormap_alloc_colors (cmap, color, 1, FALSE, TRUE, success);
226       if (success[0])
227         {
228           gtk_widget_ensure_style(w);
229           style = gtk_style_copy(gtk_widget_get_style(w));
230           style->fg[GTK_STATE_NORMAL] = color[0];
231           gtk_widget_set_style(w, style);
232         } 
233     }
234
235   ebox = gtk_hbox_new (FALSE, 5);
236   gtk_box_pack_start (GTK_BOX(box), ebox, FALSE, FALSE, 0);
237   gtk_container_set_border_width (GTK_CONTAINER (ebox), 5);
238
239
240   if (!confirm_mode)
241     {
242       if (pinentry->prompt)
243         {
244           w = gtk_label_new (pinentry->prompt);
245           gtk_box_pack_start (GTK_BOX(ebox), w, FALSE, FALSE, 0);
246         }
247       entry = gtk_secure_entry_new ();
248       gtk_signal_connect (GTK_OBJECT (entry), "activate",
249                           GTK_SIGNAL_FUNC (enter_callback), entry);
250       gtk_box_pack_start (GTK_BOX(ebox), entry, TRUE, TRUE, 0);
251       gtk_secure_entry_set_visibility (GTK_SECURE_ENTRY(entry), FALSE);
252       gtk_signal_connect_after (GTK_OBJECT(entry), "button_press_event",
253                                 unselect, NULL);
254       gtk_widget_grab_focus (entry);
255       gtk_widget_show (entry);
256
257       if (pinentry->enhanced)
258         {
259           sbox = gtk_hbox_new(FALSE, 5);
260           gtk_box_pack_start(GTK_BOX(box), sbox, FALSE, FALSE, 0);
261           
262           w = gtk_label_new ("Forget secret after");
263           gtk_box_pack_start(GTK_BOX(sbox), w, FALSE, FALSE, 0);
264           gtk_widget_show(w);
265           
266           timeout = gtk_spin_button_new
267             (GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, HUGE_VAL,
268                                                1, 60, 60)),2,0);
269           gtk_box_pack_start (GTK_BOX(sbox), timeout, FALSE, FALSE, 0);
270           gtk_widget_show (timeout);
271         
272           w = gtk_label_new ("seconds");
273           gtk_box_pack_start (GTK_BOX(sbox), w, FALSE, FALSE, 0); 
274           gtk_widget_show (w);
275           gtk_widget_show (sbox);
276           
277           insure = gtk_check_button_new_with_label
278             ("ask before giving out secret");
279           gtk_box_pack_start (GTK_BOX(box), insure, FALSE, FALSE, 0);
280           gtk_widget_show (insure);
281         }
282     }
283
284
285   bbox = gtk_hbutton_box_new();
286   gtk_box_pack_start (GTK_BOX(box), bbox, TRUE, FALSE, 0);
287   gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
288   
289   w = gtk_button_new_with_label ("OK");
290   gtk_container_add (GTK_CONTAINER(bbox), w);
291   if (!confirm_mode)
292     {
293       gtk_signal_connect (GTK_OBJECT(w), "clicked", button_clicked, "ok");
294       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
295       gtk_widget_grab_default (w);
296       gtk_signal_connect_object (GTK_OBJECT (entry), "focus_in_event",
297                                  GTK_SIGNAL_FUNC (gtk_widget_grab_default),
298                                  GTK_OBJECT (w));
299     }
300   else
301     {
302       gtk_signal_connect (GTK_OBJECT(w), "clicked",
303                           confirm_button_clicked, "ok");
304       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
305     }
306
307   gtk_widget_show (w);
308   
309   w = gtk_button_new_with_label ("Cancel");
310   gtk_container_add (GTK_CONTAINER(bbox), w);
311   gtk_accel_group_add (acc, GDK_Escape, 0, 0, GTK_OBJECT(w), "clicked");
312   gtk_signal_connect (GTK_OBJECT(w), "clicked", 
313                       confirm_mode? confirm_button_clicked: button_clicked,
314                       NULL);
315   GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
316
317   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
318
319   gtk_widget_show_all (win);
320
321   return win;
322 }
323
324
325
326 static int
327 cmd_handler (pinentry_t pe)
328 {
329   GtkWidget *w;
330   int want_pass = !!pe->pin;
331
332   pinentry = pe;
333   confirm_yes = 0;
334   passphrase_ok = 0;
335   w = create_window (want_pass ? 0 : 1);
336   gtk_main();
337   gtk_widget_destroy (w);
338   while (gtk_events_pending ())
339     gtk_main_iteration ();
340
341   pinentry = NULL;
342   if (want_pass)
343     {
344       if (passphrase_ok && pe->pin)
345         return strlen (pe->pin);
346       else
347         return -1;
348     }
349   else
350     return confirm_yes;
351 }
352
353 pinentry_cmd_handler_t pinentry_cmd_handler = cmd_handler;
354
355 int 
356 main (int argc, char *argv[])
357 {
358   pinentry_init ();
359
360   /* FIXME: Initialize gtk_init only if DISPLAY is set, if it is not
361      set, a curses based dialog should be used.  */
362   gtk_init (&argc, &argv);
363
364   /* Consumes all arguments.  */
365   if (pinentry_parse_opts (argc, argv))
366     {
367       printf ("pinentry-gtk " VERSION "\n");
368       exit (EXIT_SUCCESS);
369     }
370
371   if (pinentry_loop ())
372     return 1;
373
374   return 0;
375 }