New command SETTITLE.
[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 static GtkTooltips *tooltips;
63
64 /* Gnome hig small and large space in pixels.  */
65 #define HIG_SMALL      6
66 #define HIG_LARGE     12
67
68 /* The text shown in the quality bar when no text is shown.  This is not
69  * the empty string, becase because with an empty string the height of
70  * the quality bar is less than with a non-empty string.  This results
71  * in ugly layout changes when the text changes from non-empty to empty
72  * and vice versa */
73 #define QUALITYBAR_EMPTY_TEXT " "
74
75 \f
76 /* Constrain size of the window the window should not shrink beyond
77    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   /* This limit is arbitrary, but INT_MAX breaks other things */
90   geo.max_width = 10000;
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  
97 /* Realize the window as transient if we grab the keyboard.  This
98    makes the window a modal dialog to the root window, which helps the
99    window manager.  See the following quote from:
100    http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2512420
101
102    Implementing enhanced support for application transient windows
103
104    If the WM_TRANSIENT_FOR property is set to None or Root window, the
105    window should be treated as a transient for all other windows in
106    the same group. It has been noted that this is a slight ICCCM
107    violation, but as this behavior is pretty standard for many
108    toolkits and window managers, and is extremely unlikely to break
109    anything, it seems reasonable to document it as standard.  */
110
111 static void
112 make_transient (GtkWidget *win, GdkEvent *event, gpointer data)
113 {
114   GdkScreen *screen;
115   GdkWindow *root;
116
117   if (! pinentry->grab)
118     return;
119
120   /* Make window transient for the root window.  */
121   screen = gdk_screen_get_default ();
122   root = gdk_screen_get_root_window (screen);
123   gdk_window_set_transient_for (win->window, root);
124 }
125
126
127 /* Grab the keyboard for maximum security */
128 static void
129 grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
130 {
131   if (! pinentry->grab)
132     return;
133
134   if (gdk_keyboard_grab (win->window, FALSE, gdk_event_get_time (event)))
135     g_error ("could not grab keyboard");
136 }
137
138
139 /* Remove grab.  */
140 static void
141 ungrab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data)
142 {
143   gdk_keyboard_ungrab (gdk_event_get_time (event));
144   /* Unmake window transient for the root window.  */
145   gdk_window_set_transient_for (win->window, NULL);
146 }
147
148
149 static int
150 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
151 {
152   gtk_main_quit ();
153   return TRUE;
154 }
155
156
157 static void
158 button_clicked (GtkWidget *widget, gpointer data)
159 {
160   if (data)
161     {
162       const char *s;
163
164       /* Okay button or enter used in text field.  */
165
166       if (pinentry->enhanced)
167         printf ("Options: %s\nTimeout: %d\n\n",
168                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (insure))
169                 ? "insure" : "",
170                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (time_out)));
171
172       s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry));
173       if (!s)
174         s = "";
175       passphrase_ok = 1;
176       pinentry_setbufferlen (pinentry, strlen (s) + 1);
177       if (pinentry->pin)
178         strcpy (pinentry->pin, s);
179     }
180   gtk_main_quit ();
181 }
182
183
184 static void
185 enter_callback (GtkWidget *widget, GtkWidget *anentry)
186 {
187   button_clicked (widget, "ok");
188 }
189
190
191 static void
192 confirm_button_clicked (GtkWidget *widget, gpointer data)
193 {
194   if (data)
195     /* Okay button.  */
196     confirm_yes = 1;
197
198   gtk_main_quit ();
199 }
200
201
202 static gchar *
203 pinentry_utf8_validate (gchar *text)
204 {
205   gchar *result;
206
207   if (!text)
208     return NULL;
209
210   if (g_utf8_validate (text, -1, NULL))
211     return g_strdup (text);
212
213   result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL);
214   if (!result)
215     {
216       gchar *p;
217
218       result = p = g_strdup (text);
219       while (!g_utf8_validate (p, -1, (const gchar **) &p))
220         *p = '?';
221     }
222   return result;
223 }
224
225
226 /* Handler called for "changed".   We use it to update the quality
227    indicator.  */
228 static void
229 changed_text_handler (GtkWidget *widget)
230 {
231   char textbuf[50];
232   const char *s;
233   int length;
234   int percent;
235   GdkColor color = { 0, 0, 0, 0};
236
237   if (!qualitybar || !pinentry->quality_bar)
238     return;
239
240   s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (widget));
241   if (!s)
242     s = "";
243   length = strlen (s);
244   percent = length? pinentry_inq_quality (pinentry, s, length) : 0;
245   if (!length)
246     {
247       strcpy(textbuf, QUALITYBAR_EMPTY_TEXT);
248       color.red = 0xffff;
249     }
250   else if (percent < 0)
251     {
252       snprintf (textbuf, sizeof textbuf, "(%d%%)", -percent);
253       color.red = 0xffff;
254       percent = -percent;
255     }
256   else
257     {
258       snprintf (textbuf, sizeof textbuf, "%d%%", percent);
259       color.green = 0xffff;
260     }
261   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 
262                                  (double)percent/100.0);
263   gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar), textbuf);
264   gtk_widget_modify_bg (qualitybar, GTK_STATE_PRELIGHT, &color);
265 }
266
267
268
269 static GtkWidget *
270 create_window (int confirm_mode)
271 {
272   GtkWidget *w;
273   GtkWidget *win, *box;
274   GtkWidget *wvbox, *chbox, *bbox;
275   GtkAccelGroup *acc;
276   gchar *msg;
277
278   tooltips = gtk_tooltips_new ();
279
280   /* FIXME: check the grabbing code against the one we used with the
281      old gpg-agent */
282   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
283   acc = gtk_accel_group_new ();
284
285   g_signal_connect (G_OBJECT (win), "delete_event",
286                     G_CALLBACK (delete_event), NULL);
287
288 #if 0
289   g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (gtk_main_quit),
290                     NULL);
291 #endif
292   g_signal_connect (G_OBJECT (win), "size-request",
293                     G_CALLBACK (constrain_size), NULL);
294   if (!confirm_mode)
295     {
296       if (pinentry->grab)
297         g_signal_connect (G_OBJECT (win),
298                           "realize", G_CALLBACK (make_transient), NULL);
299
300       g_signal_connect (G_OBJECT (win),
301                         pinentry->grab ? "map-event" : "focus-in-event",
302                         G_CALLBACK (grab_keyboard), NULL);
303       g_signal_connect (G_OBJECT (win),
304                         pinentry->grab ? "unmap-event" : "focus-out-event",
305                         G_CALLBACK (ungrab_keyboard), NULL);
306     }
307   gtk_window_add_accel_group (GTK_WINDOW (win), acc);
308   
309   wvbox = gtk_vbox_new (FALSE, HIG_LARGE * 2);
310   gtk_container_add (GTK_CONTAINER (win), wvbox);
311   gtk_container_set_border_width (GTK_CONTAINER (wvbox), HIG_LARGE);
312
313   chbox = gtk_hbox_new (FALSE, HIG_LARGE);
314   gtk_box_pack_start (GTK_BOX (wvbox), chbox, FALSE, FALSE, 0);
315
316   w = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
317                                                GTK_ICON_SIZE_DIALOG);
318   gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0);
319   gtk_box_pack_start (GTK_BOX (chbox), w, FALSE, FALSE, 0);
320
321   box = gtk_vbox_new (FALSE, HIG_SMALL);
322   gtk_box_pack_start (GTK_BOX (chbox), box, TRUE, TRUE, 0);
323
324   if (pinentry->title)
325     {
326       msg = pinentry_utf8_validate (pinentry->title);
327       gtk_window_set_title (GTK_WINDOW(win), msg);
328     }
329   if (pinentry->description)
330     {
331       msg = pinentry_utf8_validate (pinentry->description);
332       w = gtk_label_new (msg);
333       g_free (msg);
334       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
335       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
336       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
337     }
338   if (pinentry->error && !confirm_mode)
339     {
340       GdkColor color = { 0, 0xffff, 0, 0 };
341
342       msg = pinentry_utf8_validate (pinentry->error);
343       w = gtk_label_new (msg);
344       g_free (msg);
345       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
346       gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
347       gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0);
348       gtk_widget_modify_fg (w, GTK_STATE_NORMAL, &color);
349     }
350
351   qualitybar = NULL;
352
353   if (!confirm_mode)
354     {
355       GtkWidget* table = gtk_table_new (pinentry->quality_bar ? 2 : 1, 2,
356                                         FALSE);
357       gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 0);
358
359       if (pinentry->prompt)
360         {
361           msg = pinentry_utf8_validate (pinentry->prompt);
362           w = gtk_label_new (msg);
363           g_free (msg);
364           gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
365           gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1,
366                             GTK_FILL, GTK_FILL, 4, 0);
367         }
368
369       entry = gtk_secure_entry_new ();
370       gtk_widget_set_size_request (entry, 200, -1);
371       g_signal_connect (G_OBJECT (entry), "activate",
372                         G_CALLBACK (enter_callback), entry);
373       g_signal_connect (G_OBJECT (entry), "changed",
374                         G_CALLBACK (changed_text_handler), entry);
375       gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1,
376                         GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
377       gtk_widget_grab_focus (entry);
378       gtk_widget_show (entry);
379
380       if (pinentry->quality_bar)
381         {
382           msg = pinentry_utf8_validate (pinentry->quality_bar);
383           w = gtk_label_new (msg);
384           g_free (msg);
385           gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
386           gtk_table_attach (GTK_TABLE (table), w, 0, 1, 1, 2,
387                             GTK_FILL, GTK_FILL, 4, 0);
388           qualitybar = gtk_progress_bar_new();
389           gtk_widget_add_events (qualitybar,
390                                  GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
391           gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar),
392                                      QUALITYBAR_EMPTY_TEXT);
393           gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 0.0);
394           if (pinentry->quality_bar_tt)
395             gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), qualitybar,
396                                   pinentry->quality_bar_tt, "");
397           gtk_table_attach (GTK_TABLE (table), qualitybar, 1, 2, 1, 2,
398                             GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
399         }
400
401       if (pinentry->enhanced)
402         {
403           GtkWidget *sbox = gtk_hbox_new (FALSE, HIG_SMALL);
404           gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0);
405
406           w = gtk_label_new ("Forget secret after");
407           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
408           gtk_widget_show (w);
409
410           time_out = gtk_spin_button_new
411             (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, HUGE_VAL, 1, 60, 60)),
412              2, 0);
413           gtk_box_pack_start (GTK_BOX (sbox), time_out, FALSE, FALSE, 0);
414           gtk_widget_show (time_out);
415           
416           w = gtk_label_new ("seconds");
417           gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0);
418           gtk_widget_show (w);
419           gtk_widget_show (sbox);
420
421           insure = gtk_check_button_new_with_label ("ask before giving out "
422                                                     "secret");
423           gtk_box_pack_start (GTK_BOX (box), insure, FALSE, FALSE, 0);
424           gtk_widget_show (insure);
425         }
426     }
427
428   bbox = gtk_hbutton_box_new ();
429   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
430   gtk_box_set_spacing (GTK_BOX (bbox), 6);
431   gtk_box_pack_start (GTK_BOX (wvbox), bbox, TRUE, FALSE, 0);
432
433   if (!pinentry->one_button)
434     {
435       if (pinentry->cancel)
436         {
437           msg = pinentry_utf8_validate (pinentry->cancel);
438           w = gtk_button_new_with_label (msg);
439           g_free (msg);
440         }
441       else
442         w = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
443       gtk_container_add (GTK_CONTAINER (bbox), w);
444       g_signal_connect (G_OBJECT (w), "clicked",
445                         G_CALLBACK (confirm_mode ? confirm_button_clicked
446                                     : button_clicked), NULL);
447       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
448     }
449   
450   if (pinentry->ok)
451     {
452       msg = pinentry_utf8_validate (pinentry->ok);
453       w = gtk_button_new_with_label (msg);
454       g_free (msg);
455     }
456   else
457     w = gtk_button_new_from_stock (GTK_STOCK_OK);
458   gtk_container_add (GTK_CONTAINER(bbox), w);
459   if (!confirm_mode)
460     {
461       g_signal_connect (G_OBJECT (w), "clicked",
462                         G_CALLBACK (button_clicked), "ok");
463       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
464       gtk_widget_grab_default (w);
465       g_signal_connect_object (G_OBJECT (entry), "focus_in_event",
466                                 G_CALLBACK (gtk_widget_grab_default),
467                                G_OBJECT (w), 0);
468     }
469   else
470     {
471       g_signal_connect (G_OBJECT (w), "clicked",
472                         G_CALLBACK(confirm_button_clicked), "ok");
473       GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
474     }
475
476   gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
477   
478   gtk_widget_show_all(win);
479   
480   return win;
481 }
482
483
484 static int
485 gtk_cmd_handler (pinentry_t pe)
486 {
487   GtkWidget *w;
488   int want_pass = !!pe->pin;
489
490   pinentry = pe;
491   confirm_yes = 0;
492   passphrase_ok = 0;
493   w = create_window (want_pass ? 0 : 1);
494   gtk_main ();
495   gtk_widget_destroy (w);
496   while (gtk_events_pending ())
497     gtk_main_iteration ();
498
499   pinentry = NULL;
500   if (want_pass)
501     {
502       if (passphrase_ok && pe->pin)
503         return strlen (pe->pin);
504       else
505         return -1;
506     }
507   else
508     return confirm_yes;
509 }
510
511
512 pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler;
513
514
515 int
516 main (int argc, char *argv[])
517 {
518   pinentry_init (PGMNAME);
519     
520 #ifdef FALLBACK_CURSES
521   if (pinentry_have_display (argc, argv))
522     gtk_init (&argc, &argv);
523   else
524     pinentry_cmd_handler = curses_cmd_handler;
525 #else
526   gtk_init (&argc, &argv);
527 #endif
528
529   /* Consumes all arguments.  */
530   if (pinentry_parse_opts (argc, argv))
531     {
532       printf(PGMNAME " " VERSION "\n");
533       exit(EXIT_SUCCESS);
534     }
535   
536   if (pinentry_loop ())
537     return 1;
538   
539   return 0;
540 }