2 * GTK implementation of a GUI password/passphrase prompt.
13 #if !GTK_CHECK_VERSION(3,0,0)
14 #include <gdk/gdkkeysyms.h>
17 #include "gtkcompat.h"
21 #define N_DRAWING_AREAS 3
23 struct drawing_area_ctx {
26 int width, height, current;
30 GtkWidget *dialog, *promptlabel;
31 struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
33 #if GTK_CHECK_VERSION(2,0,0)
39 int passlen, passsize;
42 static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
45 new_active = rand() % (N_DRAWING_AREAS - 1);
46 if (new_active >= ctx->active_area)
48 ctx->drawingareas[ctx->active_area].current = 0;
49 gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
50 ctx->drawingareas[new_active].current = 1;
51 gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
52 ctx->active_area = new_active;
55 static int last_char_len(struct askpass_ctx *ctx)
58 * GTK always encodes in UTF-8, so we can do this in a fixed way.
61 assert(ctx->passlen > 0);
63 while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
68 return ctx->passlen - i;
71 static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
73 int len = strlen(str);
74 if (ctx->passlen + len >= ctx->passsize) {
75 /* Take some care with buffer expansion, because there are
76 * pieces of passphrase in the old buffer so we should ensure
77 * realloc doesn't leave a copy lying around in the address
79 int oldsize = ctx->passsize;
82 ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
83 newbuf = snewn(ctx->passsize, char);
84 memcpy(newbuf, ctx->passphrase, oldsize);
85 smemclr(ctx->passphrase, oldsize);
86 sfree(ctx->passphrase);
87 ctx->passphrase = newbuf;
89 strcpy(ctx->passphrase + ctx->passlen, str);
91 visually_acknowledge_keypress(ctx);
94 static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
96 struct askpass_ctx *ctx = (struct askpass_ctx *)data;
98 if (event->keyval == GDK_Return && event->type == GDK_KEY_PRESS) {
100 } else if (event->keyval == GDK_Escape && event->type == GDK_KEY_PRESS) {
101 smemclr(ctx->passphrase, ctx->passsize);
102 ctx->passphrase = NULL;
105 #if GTK_CHECK_VERSION(2,0,0)
106 if (gtk_im_context_filter_keypress(ctx->imc, event))
110 if (event->type == GDK_KEY_PRESS) {
111 if (!strcmp(event->string, "\x15")) {
112 /* Ctrl-U. Wipe out the whole line */
114 visually_acknowledge_keypress(ctx);
115 } else if (!strcmp(event->string, "\x17")) {
116 /* Ctrl-W. Delete back to the last space->nonspace
117 * boundary. We interpret 'space' in a really simple
118 * way (mimicking terminal drivers), and don't attempt
119 * to second-guess exciting Unicode space
121 while (ctx->passlen > 0) {
123 ctx->passlen -= last_char_len(ctx);
124 deleted = ctx->passphrase[ctx->passlen];
125 prior = (ctx->passlen == 0 ? ' ' :
126 ctx->passphrase[ctx->passlen-1]);
127 if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
130 visually_acknowledge_keypress(ctx);
131 } else if (event->keyval == GDK_BackSpace) {
132 /* Backspace. Delete one character. */
133 if (ctx->passlen > 0)
134 ctx->passlen -= last_char_len(ctx);
135 visually_acknowledge_keypress(ctx);
136 #if !GTK_CHECK_VERSION(2,0,0)
137 } else if (event->string[0]) {
138 add_text_to_passphrase(ctx, event->string);
146 #if GTK_CHECK_VERSION(2,0,0)
147 static void input_method_commit_event(GtkIMContext *imc, gchar *str,
150 struct askpass_ctx *ctx = (struct askpass_ctx *)data;
151 add_text_to_passphrase(ctx, str);
155 static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
158 struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
159 ctx->width = event->width;
160 ctx->height = event->height;
161 gtk_widget_queue_draw(widget);
165 static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
168 struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
170 GdkGC *gc = gdk_gc_new(ctx->area->window);
171 gdk_gc_set_foreground(gc, &ctx->cols[ctx->current]);
172 gdk_draw_rectangle(widget->window, gc, TRUE,
173 0, 0, ctx->width, ctx->height);
178 static int try_grab_keyboard(struct askpass_ctx *ctx)
180 int ret = gdk_keyboard_grab(ctx->dialog->window, FALSE, GDK_CURRENT_TIME);
181 return ret == GDK_GRAB_SUCCESS;
184 typedef int (try_grab_fn_t)(struct askpass_ctx *ctx);
186 static int repeatedly_try_grab(struct askpass_ctx *ctx, try_grab_fn_t fn)
189 * Repeatedly try to grab some aspect of the X server. We have to
190 * do this rather than just trying once, because there is at least
191 * one important situation in which the grab may fail the first
192 * time: any user who is launching an add-key operation off some
193 * kind of window manager hotkey will almost by definition be
194 * running this script with a keyboard grab already active, namely
195 * the one-key grab that the WM (or whatever) uses to detect
196 * presses of the hotkey. So at the very least we have to give the
197 * user time to release that key.
199 const useconds_t ms_limit = 5*1000000; /* try for 5 seconds */
200 const useconds_t ms_step = 1000000/8; /* at 1/8 second intervals */
203 for (ms = 0; ms < ms_limit; ms += ms_step) {
211 static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
212 const char *window_title,
213 const char *prompt_text)
219 ctx->passsize = 2048;
220 ctx->passphrase = snewn(ctx->passsize, char);
225 ctx->dialog = gtk_dialog_new();
226 gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
227 ctx->promptlabel = gtk_label_new(prompt_text);
228 gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), TRUE);
229 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area
230 (GTK_DIALOG(ctx->dialog))),
232 #if GTK_CHECK_VERSION(2,0,0)
233 ctx->imc = gtk_im_multicontext_new();
235 ctx->colmap = gdk_colormap_get_system();
236 ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
237 ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
238 gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
239 FALSE, TRUE, success);
240 if (!success[0] | !success[1])
241 return "unable to allocate colours";
242 for (i = 0; i < N_DRAWING_AREAS; i++) {
243 ctx->drawingareas[i].area = gtk_drawing_area_new();
244 ctx->drawingareas[i].cols = ctx->cols;
245 ctx->drawingareas[i].current = 0;
246 ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
247 /* It would be nice to choose this size in some more
248 * context-sensitive way, like measuring the size of some
249 * piece of template text. */
250 gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
251 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_action_area
252 (GTK_DIALOG(ctx->dialog))),
253 ctx->drawingareas[i].area);
254 gtk_signal_connect(GTK_OBJECT(ctx->drawingareas[i].area),
256 GTK_SIGNAL_FUNC(configure_area),
257 &ctx->drawingareas[i]);
258 gtk_signal_connect(GTK_OBJECT(ctx->drawingareas[i].area),
260 GTK_SIGNAL_FUNC(expose_area),
261 &ctx->drawingareas[i]);
262 gtk_widget_show(ctx->drawingareas[i].area);
264 ctx->active_area = rand() % N_DRAWING_AREAS;
265 ctx->drawingareas[ctx->active_area].current = 1;
268 * Arrange to receive key events. We don't really need to worry
269 * from a UI perspective about which widget gets the events, as
270 * long as we know which it is so we can catch them. So we'll pick
271 * the prompt label at random, and we'll use gtk_grab_add to
272 * ensure key events go to it.
274 gtk_widget_set_sensitive(ctx->promptlabel, TRUE);
276 #if GTK_CHECK_VERSION(2,0,0)
277 gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), TRUE);
281 * Actually show the window, and wait for it to be shown.
283 gtk_widget_show_now(ctx->dialog);
286 * Now that the window is displayed, make it grab the input focus.
288 gtk_grab_add(ctx->promptlabel);
289 if (!repeatedly_try_grab(ctx, try_grab_keyboard))
290 return "unable to grab keyboard";
293 * And now that we've got the keyboard grab, connect up our
294 * keyboard handlers, and display the prompt.
296 #if GTK_CHECK_VERSION(2,0,0)
297 g_signal_connect(G_OBJECT(ctx->imc), "commit",
298 G_CALLBACK(input_method_commit_event), ctx);
300 gtk_signal_connect(GTK_OBJECT(ctx->promptlabel), "key_press_event",
301 GTK_SIGNAL_FUNC(key_event), ctx);
302 gtk_signal_connect(GTK_OBJECT(ctx->promptlabel), "key_release_event",
303 GTK_SIGNAL_FUNC(key_event), ctx);
304 #if GTK_CHECK_VERSION(2,0,0)
305 gtk_im_context_set_client_window(ctx->imc, ctx->dialog->window);
307 gtk_widget_show(ctx->promptlabel);
312 static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
314 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
315 gtk_grab_remove(ctx->promptlabel);
317 if (ctx->passphrase) {
318 assert(ctx->passlen < ctx->passsize);
319 ctx->passphrase[ctx->passlen] = '\0';
322 gtk_widget_destroy(ctx->dialog);
325 static int setup_gtk(const char *display)
327 static int gtk_initialised = FALSE;
330 char **argv = real_argv;
337 argv[argc++] = dupstr("dummy");
338 argv[argc++] = dupprintf("--display=%s", display);
340 ret = gtk_init_check(&argc, &argv);
344 gtk_initialised = ret;
348 char *gtk_askpass_main(const char *display, const char *wintitle,
349 const char *prompt, int *success)
351 struct askpass_ctx actx, *ctx = &actx;
354 /* In case gtk_init hasn't been called yet by the program */
355 if (!setup_gtk(display)) {
357 return dupstr("unable to initialise GTK");
360 if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
362 return dupprintf("%s", err);
365 gtk_askpass_cleanup(ctx);
367 if (ctx->passphrase) {
369 return ctx->passphrase;
372 return dupstr("passphrase input cancelled");
377 void modalfatalbox(const char *p, ...)
380 fprintf(stderr, "FATAL ERROR: ");
382 vfprintf(stderr, p, ap);
388 int main(int argc, char **argv)
390 int success, exitcode;
393 gtk_init(&argc, &argv);
397 ret = dupprintf("usage: %s <prompt text>", argv[0]);
400 ret = gtk_askpass_main(argv[1], &success);
413 smemclr(ret, strlen(ret));