Get rid of getopt_long and improve --help output.
[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 #ifdef ENABLE_ENHANCED
140       if (pinentry->enhanced)
141         {
142           printf("Options: %s\nTimeout: %d\n\n",
143                  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(insure)) ?
144                  "insure"
145                  : "",
146                  gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(time_out)));
147         }
148 #endif
149
150       pinentry->locale_err = 1;
151       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY(entry));
152       if (!s)
153         s = "";
154       s_buffer = secmem_malloc (strlen (s) + 1);
155       if (s_buffer)
156         {
157           strcpy (s_buffer, s);
158           s_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, s_buffer, 1);
159           secmem_free (s_buffer);
160           if (s_utf8)
161             {
162               passphrase_ok = 1;
163               pinentry_setbufferlen (pinentry, strlen (s_utf8) + 1);
164               if (pinentry->pin)
165                 strcpy (pinentry->pin, s_utf8);
166               secmem_free (s_utf8);
167               pinentry->locale_err = 0;
168             }
169         }
170     }
171   gtk_main_quit ();
172 }
173
174
175 static void
176 enter_callback (GtkWidget *widget, GtkWidget *anentry)
177 {
178   button_clicked (widget, "ok");
179 }
180
181
182 static void
183 confirm_button_clicked (GtkWidget *widget, gpointer data)
184 {
185   if (data)
186     { /* okay button */
187       confirm_yes = 1;
188     }
189   gtk_main_quit ();
190 }
191
192
193 static GtkWidget *
194 create_utf8_label (char *text)
195 {
196   GtkWidget *w;
197   char *buf;
198
199   buf = pinentry_utf8_to_local (pinentry->lc_ctype, text);
200   if (buf)
201     {
202       w = gtk_label_new (buf);
203       free (buf);
204     }
205   else /* no core - simply don't convert */
206     w = gtk_label_new (text);
207   return w;
208 }
209
210
211 static GtkWidget *
212 create_window (int confirm_mode)
213 {
214   GtkWidget *w;
215   GtkWidget *win, *box, *ebox;
216   GtkWidget *sbox, *bbox;
217   GtkAccelGroup *acc;
218
219   /* fixme: check the grabbing code against the one we used with the
220      old gpg-agent */
221   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
222   acc = gtk_accel_group_new ();
223
224   gtk_signal_connect (GTK_OBJECT (win), "delete_event",
225                       GTK_SIGNAL_FUNC (delete_event), NULL);
226
227 #if 0
228   gtk_signal_connect (GTK_OBJECT(win), "destroy", gtk_main_quit, NULL);
229 #endif
230   gtk_signal_connect (GTK_OBJECT(win), "size-request", constrain_size, NULL);
231   if (!confirm_mode)
232     {
233       gtk_signal_connect (GTK_OBJECT(win),
234                           pinentry->grab ? "map-event" : "focus-in-event",
235                           grab_keyboard, NULL);
236       gtk_signal_connect (GTK_OBJECT(win),
237                           pinentry->grab ? "unmap-event" : "focus-out-event",
238                           ungrab_keyboard, NULL);
239     }
240   gtk_accel_group_attach(acc, GTK_OBJECT(win));
241
242   box = gtk_vbox_new (FALSE, 5);
243   gtk_container_add (GTK_CONTAINER(win), box);
244   gtk_container_set_border_width (GTK_CONTAINER (box), 5);
245
246   if (pinentry->description)
247     {
248       w = create_utf8_label (pinentry->description);
249       gtk_box_pack_start (GTK_BOX(box), w, TRUE, FALSE, 0);
250     }
251   if (pinentry->error && !confirm_mode)
252     {
253       GtkStyle *style;
254       GdkColormap *cmap;
255       GdkColor color[1] = {{0, 0xffff, 0, 0}};
256       gboolean success[1];
257
258       w = create_utf8_label (pinentry->error);
259       gtk_box_pack_start (GTK_BOX(box), w, TRUE, FALSE, 5);
260
261       /* fixme: Do we need to release something, or is there a more
262          easy way to set a text color? */
263       gtk_widget_realize (win);
264       cmap = gdk_window_get_colormap (win->window);
265       assert (cmap);
266       gdk_colormap_alloc_colors (cmap, color, 1, FALSE, TRUE, success);
267       if (success[0])
268         {
269           gtk_widget_ensure_style(w);
270           style = gtk_style_copy(gtk_widget_get_style(w));
271           style->fg[GTK_STATE_NORMAL] = color[0];
272           gtk_widget_set_style(w, style);
273         }
274     }
275
276   ebox = gtk_hbox_new (FALSE, 5);
277   gtk_box_pack_start (GTK_BOX(box), ebox, FALSE, FALSE, 0);
278   gtk_container_set_border_width (GTK_CONTAINER (ebox), 5);
279
280
281   if (!confirm_mode)
282     {
283       if (pinentry->prompt)
284         {
285           w = create_utf8_label (pinentry->prompt);
286           gtk_box_pack_start (GTK_BOX(ebox), w, FALSE, FALSE, 0);
287         }
288       entry = gtk_secure_entry_new ();
289       gtk_signal_connect (GTK_OBJECT (entry), "activate",
290                           GTK_SIGNAL_FUNC (enter_callback), entry);
291       gtk_box_pack_start (GTK_BOX(ebox), entry, TRUE, TRUE, 0);
292       gtk_secure_entry_set_visibility (GTK_SECURE_ENTRY(entry), FALSE);
293       gtk_signal_connect_after (GTK_OBJECT(entry), "button_press_event",
294                                 unselect, NULL);
295       gtk_widget_grab_focus (entry);
296       gtk_widget_show (entry);
297
298 #ifdef ENABLE_ENHANCED
299       if (pinentry->enhanced)
300         {
301           sbox = gtk_hbox_new(FALSE, 5);
302           gtk_box_pack_start(GTK_BOX(box), sbox, FALSE, FALSE, 0);
303
304           w = gtk_label_new ("Forget secret after");
305           gtk_box_pack_start(GTK_BOX(sbox), w, FALSE, FALSE, 0);
306           gtk_widget_show(w);
307
308           time_out = gtk_spin_button_new
309             (GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, HUGE_VAL,
310                                                1, 60, 60)),2,0);
311           gtk_box_pack_start (GTK_BOX(sbox), time_out, FALSE, FALSE, 0);
312           gtk_widget_show (time_out);
313
314           w = gtk_label_new ("seconds");
315           gtk_box_pack_start (GTK_BOX(sbox), w, FALSE, FALSE, 0);
316           gtk_widget_show (w);
317           gtk_widget_show (sbox);
318
319           insure = gtk_check_button_new_with_label
320             ("ask before giving out secret");
321           gtk_box_pack_start (GTK_BOX(box), insure, FALSE, FALSE, 0);
322           gtk_widget_show (insure);
323         }
324 #endif
325     }
326
327
328   bbox = gtk_hbutton_box_new();
329   gtk_box_pack_start (GTK_BOX(box), bbox, TRUE, FALSE, 0);
330   gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
331
332   w = gtk_button_new_with_label (pinentry->ok ? pinentry->ok : "OK");
333   gtk_container_add (GTK_CONTAINER(bbox), w);
334   if (!confirm_mode)
335     {
336       gtk_signal_connect (GTK_OBJECT(w), "clicked", button_clicked, "ok");
337       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
338       gtk_widget_grab_default (w);
339       gtk_signal_connect_object (GTK_OBJECT (entry), "focus_in_event",
340                                  GTK_SIGNAL_FUNC (gtk_widget_grab_default),
341                                  GTK_OBJECT (w));
342     }
343   else
344     {
345       gtk_signal_connect (GTK_OBJECT(w), "clicked",
346                           confirm_button_clicked, "ok");
347       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
348     }
349
350   gtk_widget_show (w);
351
352   if (!pinentry->one_button)
353     {
354       w = gtk_button_new_with_label (pinentry->cancel
355                                      ? pinentry->cancel : "Cancel");
356       gtk_container_add (GTK_CONTAINER(bbox), w);
357       gtk_accel_group_add (acc, GDK_Escape, 0, 0, GTK_OBJECT(w), "clicked");
358       gtk_signal_connect (GTK_OBJECT(w), "clicked",
359                           confirm_mode? confirm_button_clicked: button_clicked,
360                           NULL);
361       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
362     }
363
364   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
365
366   gtk_widget_show_all (win);
367
368   return win;
369 }
370
371
372
373 static int
374 gtk_cmd_handler (pinentry_t pe)
375 {
376   GtkWidget *w;
377   int want_pass = !!pe->pin;
378
379   pinentry = pe;
380   confirm_yes = 0;
381   passphrase_ok = 0;
382   w = create_window (want_pass ? 0 : 1);
383   gtk_main();
384   gtk_widget_destroy (w);
385   while (gtk_events_pending ())
386     gtk_main_iteration ();
387
388   pinentry = NULL;
389   if (want_pass)
390     {
391       if (passphrase_ok && pe->pin)
392         return strlen (pe->pin);
393       else
394         return -1;
395     }
396   else
397     return confirm_yes;
398 }
399
400 pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler;
401
402 int
403 main (int argc, char *argv[])
404 {
405   pinentry_init (PGMNAME);
406
407 #ifdef FALLBACK_CURSES
408   if (pinentry_have_display (argc, argv))
409     gtk_init (&argc, &argv);
410   else
411     pinentry_cmd_handler = curses_cmd_handler;
412 #else
413   gtk_init (&argc, &argv);
414 #endif
415
416   pinentry_parse_opts (argc, argv);
417
418   if (pinentry_loop ())
419     return 1;
420
421   return 0;
422 }
423