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