gtkask.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. /*
  2. * GTK implementation of a GUI password/passphrase prompt.
  3. */
  4. #include <assert.h>
  5. #include <time.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8. #include <gtk/gtk.h>
  9. #include <gdk/gdk.h>
  10. #if !GTK_CHECK_VERSION(3,0,0)
  11. #include <gdk/gdkkeysyms.h>
  12. #endif
  13. #include "gtkfont.h"
  14. #include "gtkcompat.h"
  15. #include "gtkmisc.h"
  16. #include "misc.h"
  17. #define N_DRAWING_AREAS 3
  18. struct drawing_area_ctx {
  19. GtkWidget *area;
  20. #ifndef DRAW_DEFAULT_CAIRO
  21. GdkColor *cols;
  22. #endif
  23. int width, height, current;
  24. };
  25. struct askpass_ctx {
  26. GtkWidget *dialog, *promptlabel;
  27. struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
  28. int active_area;
  29. #if GTK_CHECK_VERSION(2,0,0)
  30. GtkIMContext *imc;
  31. #endif
  32. #ifndef DRAW_DEFAULT_CAIRO
  33. GdkColormap *colmap;
  34. GdkColor cols[2];
  35. #endif
  36. char *passphrase;
  37. int passlen, passsize;
  38. #if GTK_CHECK_VERSION(3,20,0)
  39. GdkSeat *seat; /* for gdk_seat_grab */
  40. #elif GTK_CHECK_VERSION(3,0,0)
  41. GdkDevice *keyboard; /* for gdk_device_grab */
  42. #endif
  43. };
  44. static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
  45. {
  46. int new_active;
  47. new_active = rand() % (N_DRAWING_AREAS - 1);
  48. if (new_active >= ctx->active_area)
  49. new_active++;
  50. ctx->drawingareas[ctx->active_area].current = 0;
  51. gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
  52. ctx->drawingareas[new_active].current = 1;
  53. gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
  54. ctx->active_area = new_active;
  55. }
  56. static int last_char_len(struct askpass_ctx *ctx)
  57. {
  58. /*
  59. * GTK always encodes in UTF-8, so we can do this in a fixed way.
  60. */
  61. int i;
  62. assert(ctx->passlen > 0);
  63. i = ctx->passlen - 1;
  64. while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
  65. if (i == 0)
  66. break;
  67. i--;
  68. }
  69. return ctx->passlen - i;
  70. }
  71. static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
  72. {
  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
  78. * space. */
  79. int oldsize = ctx->passsize;
  80. char *newbuf;
  81. ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
  82. newbuf = snewn(ctx->passsize, char);
  83. memcpy(newbuf, ctx->passphrase, oldsize);
  84. smemclr(ctx->passphrase, oldsize);
  85. sfree(ctx->passphrase);
  86. ctx->passphrase = newbuf;
  87. }
  88. strcpy(ctx->passphrase + ctx->passlen, str);
  89. ctx->passlen += len;
  90. visually_acknowledge_keypress(ctx);
  91. }
  92. static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
  93. {
  94. struct askpass_ctx *ctx = (struct askpass_ctx *)data;
  95. if (event->keyval == GDK_KEY_Return &&
  96. event->type == GDK_KEY_PRESS) {
  97. gtk_main_quit();
  98. } else if (event->keyval == GDK_KEY_Escape &&
  99. event->type == GDK_KEY_PRESS) {
  100. smemclr(ctx->passphrase, ctx->passsize);
  101. ctx->passphrase = NULL;
  102. gtk_main_quit();
  103. } else {
  104. #if GTK_CHECK_VERSION(2,0,0)
  105. if (gtk_im_context_filter_keypress(ctx->imc, event))
  106. return TRUE;
  107. #endif
  108. if (event->type == GDK_KEY_PRESS) {
  109. if (!strcmp(event->string, "\x15")) {
  110. /* Ctrl-U. Wipe out the whole line */
  111. ctx->passlen = 0;
  112. visually_acknowledge_keypress(ctx);
  113. } else if (!strcmp(event->string, "\x17")) {
  114. /* Ctrl-W. Delete back to the last space->nonspace
  115. * boundary. We interpret 'space' in a really simple
  116. * way (mimicking terminal drivers), and don't attempt
  117. * to second-guess exciting Unicode space
  118. * characters. */
  119. while (ctx->passlen > 0) {
  120. char deleted, prior;
  121. ctx->passlen -= last_char_len(ctx);
  122. deleted = ctx->passphrase[ctx->passlen];
  123. prior = (ctx->passlen == 0 ? ' ' :
  124. ctx->passphrase[ctx->passlen-1]);
  125. if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
  126. break;
  127. }
  128. visually_acknowledge_keypress(ctx);
  129. } else if (event->keyval == GDK_KEY_BackSpace) {
  130. /* Backspace. Delete one character. */
  131. if (ctx->passlen > 0)
  132. ctx->passlen -= last_char_len(ctx);
  133. visually_acknowledge_keypress(ctx);
  134. #if !GTK_CHECK_VERSION(2,0,0)
  135. } else if (event->string[0]) {
  136. add_text_to_passphrase(ctx, event->string);
  137. #endif
  138. }
  139. }
  140. }
  141. return TRUE;
  142. }
  143. #if GTK_CHECK_VERSION(2,0,0)
  144. static void input_method_commit_event(GtkIMContext *imc, gchar *str,
  145. gpointer data)
  146. {
  147. struct askpass_ctx *ctx = (struct askpass_ctx *)data;
  148. add_text_to_passphrase(ctx, str);
  149. }
  150. #endif
  151. static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
  152. gpointer data)
  153. {
  154. struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
  155. ctx->width = event->width;
  156. ctx->height = event->height;
  157. gtk_widget_queue_draw(widget);
  158. return TRUE;
  159. }
  160. #ifdef DRAW_DEFAULT_CAIRO
  161. static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
  162. {
  163. cairo_set_source_rgb(cr, 1-ctx->current, 1-ctx->current, 1-ctx->current);
  164. cairo_paint(cr);
  165. }
  166. #else
  167. static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
  168. {
  169. GdkGC *gc = gdk_gc_new(win);
  170. gdk_gc_set_foreground(gc, &ctx->cols[ctx->current]);
  171. gdk_draw_rectangle(win, gc, TRUE, 0, 0, ctx->width, ctx->height);
  172. gdk_gc_unref(gc);
  173. }
  174. #endif
  175. #if GTK_CHECK_VERSION(3,0,0)
  176. static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
  177. {
  178. struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
  179. askpass_redraw_cairo(cr, ctx);
  180. return TRUE;
  181. }
  182. #else
  183. static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
  184. gpointer data)
  185. {
  186. struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
  187. #ifdef DRAW_DEFAULT_CAIRO
  188. cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
  189. askpass_redraw_cairo(cr, ctx);
  190. cairo_destroy(cr);
  191. #else
  192. askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
  193. #endif
  194. return TRUE;
  195. }
  196. #endif
  197. static int try_grab_keyboard(struct askpass_ctx *ctx)
  198. {
  199. int ret;
  200. #if GTK_CHECK_VERSION(3,20,0)
  201. /*
  202. * Grabbing the keyboard in GTK 3.20 requires the new notion of
  203. * GdkSeat.
  204. */
  205. GdkSeat *seat;
  206. seat = gdk_display_get_default_seat
  207. (gtk_widget_get_display(ctx->dialog));
  208. if (!seat)
  209. return FALSE;
  210. ctx->seat = seat;
  211. ret = gdk_seat_grab(seat, gtk_widget_get_window(ctx->dialog),
  212. GDK_SEAT_CAPABILITY_KEYBOARD,
  213. TRUE, NULL, NULL, NULL, NULL);
  214. #elif GTK_CHECK_VERSION(3,0,0)
  215. /*
  216. * And it has to be done differently again prior to GTK 3.20.
  217. */
  218. GdkDeviceManager *dm;
  219. GdkDevice *pointer, *keyboard;
  220. dm = gdk_display_get_device_manager
  221. (gtk_widget_get_display(ctx->dialog));
  222. if (!dm)
  223. return FALSE;
  224. pointer = gdk_device_manager_get_client_pointer(dm);
  225. if (!pointer)
  226. return FALSE;
  227. keyboard = gdk_device_get_associated_device(pointer);
  228. if (!keyboard)
  229. return FALSE;
  230. if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
  231. return FALSE;
  232. ctx->keyboard = keyboard;
  233. ret = gdk_device_grab(ctx->keyboard,
  234. gtk_widget_get_window(ctx->dialog),
  235. GDK_OWNERSHIP_NONE,
  236. TRUE,
  237. GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
  238. NULL,
  239. GDK_CURRENT_TIME);
  240. #else
  241. /*
  242. * It's much simpler in GTK 1 and 2!
  243. */
  244. ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
  245. FALSE, GDK_CURRENT_TIME);
  246. #endif
  247. return ret == GDK_GRAB_SUCCESS;
  248. }
  249. typedef int (try_grab_fn_t)(struct askpass_ctx *ctx);
  250. static int repeatedly_try_grab(struct askpass_ctx *ctx, try_grab_fn_t fn)
  251. {
  252. /*
  253. * Repeatedly try to grab some aspect of the X server. We have to
  254. * do this rather than just trying once, because there is at least
  255. * one important situation in which the grab may fail the first
  256. * time: any user who is launching an add-key operation off some
  257. * kind of window manager hotkey will almost by definition be
  258. * running this script with a keyboard grab already active, namely
  259. * the one-key grab that the WM (or whatever) uses to detect
  260. * presses of the hotkey. So at the very least we have to give the
  261. * user time to release that key.
  262. */
  263. const useconds_t ms_limit = 5*1000000; /* try for 5 seconds */
  264. const useconds_t ms_step = 1000000/8; /* at 1/8 second intervals */
  265. useconds_t ms;
  266. for (ms = 0; ms < ms_limit; ms += ms_step) {
  267. if (fn(ctx))
  268. return TRUE;
  269. usleep(ms_step);
  270. }
  271. return FALSE;
  272. }
  273. static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
  274. const char *window_title,
  275. const char *prompt_text)
  276. {
  277. int i;
  278. GtkBox *action_area;
  279. ctx->passlen = 0;
  280. ctx->passsize = 2048;
  281. ctx->passphrase = snewn(ctx->passsize, char);
  282. /*
  283. * Create widgets.
  284. */
  285. ctx->dialog = our_dialog_new();
  286. gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
  287. gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
  288. ctx->promptlabel = gtk_label_new(prompt_text);
  289. align_label_left(GTK_LABEL(ctx->promptlabel));
  290. gtk_widget_show(ctx->promptlabel);
  291. gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), TRUE);
  292. #if GTK_CHECK_VERSION(3,0,0)
  293. gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
  294. #endif
  295. our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
  296. ctx->promptlabel, TRUE, TRUE, 0);
  297. #if GTK_CHECK_VERSION(2,0,0)
  298. ctx->imc = gtk_im_multicontext_new();
  299. #endif
  300. #ifndef DRAW_DEFAULT_CAIRO
  301. {
  302. gboolean success[2];
  303. ctx->colmap = gdk_colormap_get_system();
  304. ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
  305. ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
  306. gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
  307. FALSE, TRUE, success);
  308. if (!success[0] | !success[1])
  309. return "unable to allocate colours";
  310. }
  311. #endif
  312. action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
  313. for (i = 0; i < N_DRAWING_AREAS; i++) {
  314. ctx->drawingareas[i].area = gtk_drawing_area_new();
  315. #ifndef DRAW_DEFAULT_CAIRO
  316. ctx->drawingareas[i].cols = ctx->cols;
  317. #endif
  318. ctx->drawingareas[i].current = 0;
  319. ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
  320. /* It would be nice to choose this size in some more
  321. * context-sensitive way, like measuring the size of some
  322. * piece of template text. */
  323. gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
  324. gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
  325. TRUE, TRUE, 5);
  326. g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
  327. "configure_event",
  328. G_CALLBACK(configure_area),
  329. &ctx->drawingareas[i]);
  330. #if GTK_CHECK_VERSION(3,0,0)
  331. g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
  332. "draw",
  333. G_CALLBACK(draw_area),
  334. &ctx->drawingareas[i]);
  335. #else
  336. g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
  337. "expose_event",
  338. G_CALLBACK(expose_area),
  339. &ctx->drawingareas[i]);
  340. #endif
  341. #if GTK_CHECK_VERSION(3,0,0)
  342. g_object_set(G_OBJECT(ctx->drawingareas[i].area),
  343. "margin-bottom", 8, (const char *)NULL);
  344. #endif
  345. gtk_widget_show(ctx->drawingareas[i].area);
  346. }
  347. ctx->active_area = rand() % N_DRAWING_AREAS;
  348. ctx->drawingareas[ctx->active_area].current = 1;
  349. /*
  350. * Arrange to receive key events. We don't really need to worry
  351. * from a UI perspective about which widget gets the events, as
  352. * long as we know which it is so we can catch them. So we'll pick
  353. * the prompt label at random, and we'll use gtk_grab_add to
  354. * ensure key events go to it.
  355. */
  356. gtk_widget_set_sensitive(ctx->promptlabel, TRUE);
  357. #if GTK_CHECK_VERSION(2,0,0)
  358. gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), TRUE);
  359. #endif
  360. /*
  361. * Actually show the window, and wait for it to be shown.
  362. */
  363. gtk_widget_show_now(ctx->dialog);
  364. /*
  365. * Now that the window is displayed, make it grab the input focus.
  366. */
  367. gtk_grab_add(ctx->promptlabel);
  368. if (!repeatedly_try_grab(ctx, try_grab_keyboard))
  369. return "unable to grab keyboard";
  370. /*
  371. * And now that we've got the keyboard grab, connect up our
  372. * keyboard handlers.
  373. */
  374. #if GTK_CHECK_VERSION(2,0,0)
  375. g_signal_connect(G_OBJECT(ctx->imc), "commit",
  376. G_CALLBACK(input_method_commit_event), ctx);
  377. #endif
  378. g_signal_connect(G_OBJECT(ctx->promptlabel), "key_press_event",
  379. G_CALLBACK(key_event), ctx);
  380. g_signal_connect(G_OBJECT(ctx->promptlabel), "key_release_event",
  381. G_CALLBACK(key_event), ctx);
  382. #if GTK_CHECK_VERSION(2,0,0)
  383. gtk_im_context_set_client_window(ctx->imc,
  384. gtk_widget_get_window(ctx->dialog));
  385. #endif
  386. return NULL;
  387. }
  388. static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
  389. {
  390. #if GTK_CHECK_VERSION(3,20,0)
  391. gdk_seat_ungrab(ctx->seat);
  392. #elif GTK_CHECK_VERSION(3,0,0)
  393. gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
  394. #else
  395. gdk_keyboard_ungrab(GDK_CURRENT_TIME);
  396. #endif
  397. gtk_grab_remove(ctx->promptlabel);
  398. if (ctx->passphrase) {
  399. assert(ctx->passlen < ctx->passsize);
  400. ctx->passphrase[ctx->passlen] = '\0';
  401. }
  402. gtk_widget_destroy(ctx->dialog);
  403. }
  404. static int setup_gtk(const char *display)
  405. {
  406. static int gtk_initialised = FALSE;
  407. int argc;
  408. char *real_argv[3];
  409. char **argv = real_argv;
  410. int ret;
  411. if (gtk_initialised)
  412. return TRUE;
  413. argc = 0;
  414. argv[argc++] = dupstr("dummy");
  415. argv[argc++] = dupprintf("--display=%s", display);
  416. argv[argc] = NULL;
  417. ret = gtk_init_check(&argc, &argv);
  418. while (argc > 0)
  419. sfree(argv[--argc]);
  420. gtk_initialised = ret;
  421. return ret;
  422. }
  423. char *gtk_askpass_main(const char *display, const char *wintitle,
  424. const char *prompt, int *success)
  425. {
  426. struct askpass_ctx actx, *ctx = &actx;
  427. const char *err;
  428. /* In case gtk_init hasn't been called yet by the program */
  429. if (!setup_gtk(display)) {
  430. *success = FALSE;
  431. return dupstr("unable to initialise GTK");
  432. }
  433. if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
  434. *success = FALSE;
  435. return dupprintf("%s", err);
  436. }
  437. gtk_main();
  438. gtk_askpass_cleanup(ctx);
  439. if (ctx->passphrase) {
  440. *success = TRUE;
  441. return ctx->passphrase;
  442. } else {
  443. *success = FALSE;
  444. return dupstr("passphrase input cancelled");
  445. }
  446. }
  447. #ifdef TEST_ASKPASS
  448. void modalfatalbox(const char *p, ...)
  449. {
  450. va_list ap;
  451. fprintf(stderr, "FATAL ERROR: ");
  452. va_start(ap, p);
  453. vfprintf(stderr, p, ap);
  454. va_end(ap);
  455. fputc('\n', stderr);
  456. exit(1);
  457. }
  458. int main(int argc, char **argv)
  459. {
  460. int success, exitcode;
  461. char *ret;
  462. gtk_init(&argc, &argv);
  463. if (argc != 2) {
  464. success = FALSE;
  465. ret = dupprintf("usage: %s <prompt text>", argv[0]);
  466. } else {
  467. srand(time(NULL));
  468. ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
  469. }
  470. if (!success) {
  471. fputs(ret, stderr);
  472. fputc('\n', stderr);
  473. exitcode = 1;
  474. } else {
  475. fputs(ret, stdout);
  476. fputc('\n', stdout);
  477. exitcode = 0;
  478. }
  479. smemclr(ret, strlen(ret));
  480. return exitcode;
  481. }
  482. #endif