1 /*
2  * gnome-keyring
3  *
4  * Copyright (C) 2011 Stefan Walter
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Author: Stef Walter <stef@thewalter.net>
20  */
21 
22 #include "config.h"
23 
24 #include "gcr/gcr-prompt.h"
25 
26 #include "gcr-prompt-dialog.h"
27 #include "gcr-secure-entry-buffer.h"
28 
29 #include <gtk/gtk.h>
30 #ifdef GDK_WINDOWING_X11
31 #include <gdk/gdkx.h>
32 #endif
33 #ifdef GDK_WINDOWING_WAYLAND
34 #include <gdk/gdkwayland.h>
35 #endif
36 #include <glib/gi18n.h>
37 
38 /**
39  * SECTION:gcr-prompt-dialog
40  * @title: GcrPromptDialog
41  * @short_description: a GTK+ dialog prompt
42  *
43  * A #GcrPrompt implementation which shows a GTK+ dialog. The dialog will
44  * remain visible (but insensitive) between prompts. If the user cancels the
45  * dialog between prompts, then the dialog will be hidden.
46  */
47 
48 /**
49  * GcrPromptDialog:
50  *
51  * A #GcrPrompt implementation which shows a GTK+ dialog.
52  */
53 
54 /**
55  * GcrPromptDialogClass:
56  * @parent_class: parent class
57  *
58  * The class for #GcrPromptDialog.
59  */
60 
61 #ifdef GCR_DISABLE_GRABS
62 #define GRAB_KEYBOARD 0
63 #else
64 #define GRAB_KEYBOARD 1
65 #endif
66 
67 typedef enum {
68 	PROMPT_NONE,
69 	PROMPT_CONFIRMING,
70 	PROMPT_PASSWORDING
71 } PromptMode;
72 
73 enum {
74 	PROP_0,
75 	PROP_MESSAGE,
76 	PROP_DESCRIPTION,
77 	PROP_WARNING,
78 	PROP_CHOICE_LABEL,
79 	PROP_CHOICE_CHOSEN,
80 	PROP_PASSWORD_NEW,
81 	PROP_PASSWORD_STRENGTH,
82 	PROP_CALLER_WINDOW,
83 	PROP_CONTINUE_LABEL,
84 	PROP_CANCEL_LABEL,
85 
86 	PROP_PASSWORD_VISIBLE,
87 	PROP_CONFIRM_VISIBLE,
88 	PROP_WARNING_VISIBLE,
89 	PROP_CHOICE_VISIBLE,
90 };
91 
92 struct _GcrPromptDialogPrivate {
93 	gchar *title;
94 	gchar *message;
95 	gchar *description;
96 	gchar *warning;
97 	gchar *choice_label;
98 	gboolean choice_chosen;
99 	gboolean password_new;
100 	guint password_strength;
101 	gchar *caller_window;
102 	gchar *continue_label;
103 	gchar *cancel_label;
104 
105 	GSimpleAsyncResult *async_result;
106 	GcrPromptReply last_reply;
107 	GtkWidget *widget_grid;
108 	GtkWidget *continue_button;
109 	GtkWidget *spinner;
110 	GtkWidget *image;
111 	GtkWidget *password_entry;
112 	GtkEntryBuffer *password_buffer;
113 	GtkEntryBuffer *confirm_buffer;
114 	PromptMode mode;
115 #if GTK_CHECK_VERSION (3,20,0)
116 	GdkSeat *grabbed_seat;
117 #else
118 	GdkDevice *grabbed_device;
119 #endif
120 	gulong grab_broken_id;
121 	gboolean grab_disabled;
122 	gboolean was_closed;
123 };
124 
125 static void     gcr_prompt_dialog_prompt_iface       (GcrPromptIface *iface);
126 
127 static gboolean ungrab_keyboard                      (GtkWidget *win,
128                                                       GdkEvent *event,
129                                                       gpointer unused);
130 
131 G_DEFINE_TYPE_WITH_CODE (GcrPromptDialog, gcr_prompt_dialog, GTK_TYPE_DIALOG,
132                          G_ADD_PRIVATE (GcrPromptDialog);
133                          G_IMPLEMENT_INTERFACE (GCR_TYPE_PROMPT, gcr_prompt_dialog_prompt_iface);
134 );
135 
136 #ifdef GDK_WINDOWING_X11
137 static gboolean
update_transient_for_x11(GcrPromptDialog * self,GdkWindow * window)138 update_transient_for_x11 (GcrPromptDialog *self, GdkWindow *window)
139 {
140 	gint64 handle;
141 	gchar *end;
142 	GdkDisplay *display;
143 	GdkWindow *transient_for;
144 
145 	if (!GDK_IS_X11_WINDOW (window))
146 		return FALSE;
147 
148 	handle = g_ascii_strtoll (self->pv->caller_window, &end, 10);
149 	if (!end || *end != '\0') {
150 		g_warning ("couldn't parse caller-window property: %s", self->pv->caller_window);
151 		return FALSE;
152 	}
153 
154 	display = gtk_widget_get_display (GTK_WIDGET (self));
155 	transient_for = gdk_x11_window_foreign_new_for_display (display, (Window)handle);
156 	if (transient_for == NULL) {
157 		g_warning ("caller-window property doesn't represent a window on current display: %s",
158 		           self->pv->caller_window);
159 		return FALSE;
160 	}
161 
162 	gdk_window_set_transient_for (window, transient_for);
163 	g_object_unref (transient_for);
164 	return TRUE;
165 }
166 #endif
167 
168 #ifdef GDK_WINDOWING_WAYLAND
169 static gboolean
update_transient_for_wl(GcrPromptDialog * self,GdkWindow * window)170 update_transient_for_wl (GcrPromptDialog *self, GdkWindow *window)
171 {
172 	if (!GDK_IS_WAYLAND_WINDOW (window))
173 		return FALSE;
174 
175 	if (gdk_wayland_window_set_transient_for_exported (window, self->pv->caller_window)) {
176 		g_debug ("Succesfully set transient for WL window %s", self->pv->caller_window);
177 		return TRUE;
178 	}
179 
180 	g_warning ("caller-window property doesn't represent a window on current display: %s",
181 	           self->pv->caller_window);
182 	return FALSE;
183 }
184 #endif
185 
186 static void
update_transient_for(GcrPromptDialog * self)187 update_transient_for (GcrPromptDialog *self)
188 {
189 	GdkWindow *window;
190 	gboolean success = FALSE;
191 
192 	if (self->pv->caller_window == NULL || g_str_equal (self->pv->caller_window, "")) {
193 		gtk_window_set_modal (GTK_WINDOW (self), FALSE);
194 		return;
195 	}
196 
197 	window = gtk_widget_get_window (GTK_WIDGET (self));
198 	if (window == NULL)
199 		return;
200 
201 #ifdef GDK_WINDOWING_X11
202 	if (!success)
203 		success |= update_transient_for_x11 (self, window);
204 #endif
205 #ifdef GDK_WINDOWING_WAYLAND
206 	if (!success)
207 		success |= update_transient_for_wl (self, window);
208 #endif
209 
210 	if (!success) {
211 		g_warning ("Couldn't set transient to caller window");
212 	}
213 
214 	gtk_window_set_modal (GTK_WINDOW (self), TRUE);
215 }
216 
217 static void
gcr_prompt_dialog_init(GcrPromptDialog * self)218 gcr_prompt_dialog_init (GcrPromptDialog *self)
219 {
220 	self->pv = gcr_prompt_dialog_get_instance_private (self);
221 
222 	/*
223 	 * This is a stupid hack to work around to help the window act like
224 	 * a normal object with regards to reference counting and unref.
225 	 */
226 	gtk_window_set_has_user_ref_count (GTK_WINDOW (self), FALSE);
227 }
228 
229 static void
gcr_prompt_dialog_set_property(GObject * obj,guint prop_id,const GValue * value,GParamSpec * pspec)230 gcr_prompt_dialog_set_property (GObject *obj,
231                                 guint prop_id,
232                                 const GValue *value,
233                                 GParamSpec *pspec)
234 {
235 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (obj);
236 
237 	switch (prop_id) {
238 	case PROP_MESSAGE:
239 		g_free (self->pv->message);
240 		self->pv->message = g_value_dup_string (value);
241 		g_object_notify (obj, "message");
242 		break;
243 	case PROP_DESCRIPTION:
244 		g_free (self->pv->description);
245 		self->pv->description = g_value_dup_string (value);
246 		g_object_notify (obj, "description");
247 		break;
248 	case PROP_WARNING:
249 		g_free (self->pv->warning);
250 		self->pv->warning = g_value_dup_string (value);
251 		if (self->pv->warning && self->pv->warning[0] == '\0') {
252 			g_free (self->pv->warning);
253 			self->pv->warning = NULL;
254 		}
255 		g_object_notify (obj, "warning");
256 		g_object_notify (obj, "warning-visible");
257 		break;
258 	case PROP_CHOICE_LABEL:
259 		g_free (self->pv->choice_label);
260 		self->pv->choice_label = g_value_dup_string (value);
261 		if (self->pv->choice_label && self->pv->choice_label[0] == '\0') {
262 			g_free (self->pv->choice_label);
263 			self->pv->choice_label = NULL;
264 		}
265 		g_object_notify (obj, "choice-label");
266 		g_object_notify (obj, "choice-visible");
267 		break;
268 	case PROP_CHOICE_CHOSEN:
269 		self->pv->choice_chosen = g_value_get_boolean (value);
270 		g_object_notify (obj, "choice-chosen");
271 		break;
272 	case PROP_PASSWORD_NEW:
273 		self->pv->password_new = g_value_get_boolean (value);
274 		g_object_notify (obj, "password-new");
275 		g_object_notify (obj, "confirm-visible");
276 		break;
277 	case PROP_CALLER_WINDOW:
278 		g_free (self->pv->caller_window);
279 		self->pv->caller_window = g_value_dup_string (value);
280 		if (self->pv->caller_window && self->pv->caller_window[0] == '\0') {
281 			g_free (self->pv->caller_window);
282 			self->pv->caller_window = NULL;
283 		}
284 		update_transient_for (self);
285 		g_object_notify (obj, "caller-window");
286 		break;
287 	case PROP_CONTINUE_LABEL:
288 		g_free (self->pv->continue_label);
289 		self->pv->continue_label = g_value_dup_string (value);
290 		g_object_notify (obj, "continue-label");
291 		break;
292 	case PROP_CANCEL_LABEL:
293 		g_free (self->pv->cancel_label);
294 		self->pv->cancel_label = g_value_dup_string (value);
295 		g_object_notify (obj, "cancel-label");
296 		break;
297 	default:
298 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
299 		break;
300 	}
301 }
302 
303 static void
gcr_prompt_dialog_get_property(GObject * obj,guint prop_id,GValue * value,GParamSpec * pspec)304 gcr_prompt_dialog_get_property (GObject *obj,
305                                 guint prop_id,
306                                 GValue *value,
307                                 GParamSpec *pspec)
308 {
309 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (obj);
310 
311 	switch (prop_id) {
312 	case PROP_MESSAGE:
313 		g_value_set_string (value, self->pv->message);
314 		break;
315 	case PROP_DESCRIPTION:
316 		g_value_set_string (value, self->pv->description);
317 		break;
318 	case PROP_WARNING:
319 		g_value_set_string (value, self->pv->warning);
320 		break;
321 	case PROP_CHOICE_LABEL:
322 		g_value_set_string (value, self->pv->choice_label);
323 		break;
324 	case PROP_CHOICE_CHOSEN:
325 		g_value_set_boolean (value, self->pv->choice_chosen);
326 		break;
327 	case PROP_PASSWORD_NEW:
328 		g_value_set_boolean (value, self->pv->password_new);
329 		break;
330 	case PROP_PASSWORD_STRENGTH:
331 		g_value_set_int (value, self->pv->password_strength);
332 		break;
333 	case PROP_CALLER_WINDOW:
334 		g_value_set_string (value, self->pv->caller_window);
335 		break;
336 	case PROP_PASSWORD_VISIBLE:
337 		g_value_set_boolean (value, self->pv->mode == PROMPT_PASSWORDING);
338 		break;
339 	case PROP_CONFIRM_VISIBLE:
340 		g_value_set_boolean (value, self->pv->password_new &&
341 		                            self->pv->mode == PROMPT_PASSWORDING);
342 		break;
343 	case PROP_WARNING_VISIBLE:
344 		g_value_set_boolean (value, self->pv->warning && self->pv->warning[0]);
345 		break;
346 	case PROP_CHOICE_VISIBLE:
347 		g_value_set_boolean (value, self->pv->choice_label && self->pv->choice_label[0]);
348 		break;
349 	case PROP_CONTINUE_LABEL:
350 		g_value_set_string (value, self->pv->continue_label);
351 		break;
352 	case PROP_CANCEL_LABEL:
353 		g_value_set_string (value, self->pv->cancel_label);
354 		break;
355 	default:
356 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
357 		break;
358 	}
359 }
360 
361 
362 static void
on_password_changed(GtkEditable * editable,gpointer user_data)363 on_password_changed (GtkEditable *editable,
364                      gpointer user_data)
365 {
366 	int upper, lower, digit, misc;
367 	const char *password;
368 	gdouble pwstrength;
369 	int length, i;
370 
371 	password = gtk_entry_get_text (GTK_ENTRY (editable));
372 
373 	/*
374 	 * This code is based on the Master Password dialog in Firefox
375 	 * (pref-masterpass.js)
376 	 * Original code triple-licensed under the MPL, GPL, and LGPL
377 	 * so is license-compatible with this file
378 	 */
379 
380 	length = strlen (password);
381 	upper = 0;
382 	lower = 0;
383 	digit = 0;
384 	misc = 0;
385 
386 	for ( i = 0; i < length ; i++) {
387 		if (g_ascii_isdigit (password[i]))
388 			digit++;
389 		else if (g_ascii_islower (password[i]))
390 			lower++;
391 		else if (g_ascii_isupper (password[i]))
392 			upper++;
393 		else
394 			misc++;
395 	}
396 
397 	if (length > 5)
398 		length = 5;
399 	if (digit > 3)
400 		digit = 3;
401 	if (upper > 3)
402 		upper = 3;
403 	if (misc > 3)
404 		misc = 3;
405 
406 	pwstrength = ((length * 0.1) - 0.2) +
407 	              (digit * 0.1) +
408 	              (misc * 0.15) +
409 	              (upper * 0.1);
410 
411 	if (pwstrength < 0.0)
412 		pwstrength = 0.0;
413 	if (pwstrength > 1.0)
414 		pwstrength = 1.0;
415 
416 	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (user_data), pwstrength);
417 }
418 
419 
420 static const gchar*
grab_status_message(GdkGrabStatus status)421 grab_status_message (GdkGrabStatus status)
422 {
423 	switch (status) {
424 	case GDK_GRAB_SUCCESS:
425 		g_return_val_if_reached ("");
426 		break;
427 	case GDK_GRAB_ALREADY_GRABBED:
428 		return "already grabbed";
429 	case GDK_GRAB_INVALID_TIME:
430 		return "invalid time";
431 	case GDK_GRAB_NOT_VIEWABLE:
432 		return "not viewable";
433 	case GDK_GRAB_FROZEN:
434 		return "frozen";
435 	default:
436 		g_message ("unknown grab status: %d", (int)status);
437 		return "unknown";
438 	}
439 }
440 
441 static gboolean
on_grab_broken(GtkWidget * widget,GdkEventGrabBroken * event,gpointer user_data)442 on_grab_broken (GtkWidget *widget,
443                 GdkEventGrabBroken * event,
444                 gpointer user_data)
445 {
446 	ungrab_keyboard (widget, (GdkEvent *)event, user_data);
447 	return TRUE;
448 }
449 
450 #if GTK_CHECK_VERSION (3,20,0)
451 static gboolean
grab_keyboard(GtkWidget * widget,GdkEvent * event,gpointer user_data)452 grab_keyboard (GtkWidget *widget,
453                GdkEvent *event,
454                gpointer user_data)
455 {
456 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (user_data);
457 	GdkGrabStatus status;
458 	GdkSeat *seat;
459 	GdkDisplay *display;
460 
461 	if (self->pv->grabbed_seat || !GRAB_KEYBOARD)
462 		return FALSE;
463 
464 	display = gtk_widget_get_display (widget);
465 	seat = gdk_display_get_default_seat (display);
466 	status = gdk_seat_grab (seat, gtk_widget_get_window (widget),
467 				GDK_SEAT_CAPABILITY_ALL,
468 	                        TRUE, NULL, event, NULL, NULL);
469 	if (status == GDK_GRAB_SUCCESS) {
470 		self->pv->grab_broken_id = g_signal_connect (widget, "grab-broken-event",
471 		                                             G_CALLBACK (on_grab_broken), self);
472 		gtk_grab_add (widget);
473 		self->pv->grabbed_seat = seat;
474 	} else {
475 		g_message ("could not grab keyboard: %s", grab_status_message (status));
476 	}
477 
478 	/* Always return false, so event is handled elsewhere */
479 	return FALSE;
480 }
481 
482 static gboolean
ungrab_keyboard(GtkWidget * widget,GdkEvent * event,gpointer user_data)483 ungrab_keyboard (GtkWidget *widget,
484                  GdkEvent *event,
485                  gpointer user_data)
486 {
487 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (user_data);
488 
489 	if (self->pv->grabbed_seat) {
490 		g_signal_handler_disconnect (widget, self->pv->grab_broken_id);
491 		gdk_seat_ungrab (self->pv->grabbed_seat);
492 		gtk_grab_remove (widget);
493 		self->pv->grabbed_seat = NULL;
494 		self->pv->grab_broken_id = 0;
495 	}
496 
497 	/* Always return false, so event is handled elsewhere */
498 	return FALSE;
499 }
500 #else
501 static gboolean
grab_keyboard(GtkWidget * widget,GdkEvent * event,gpointer user_data)502 grab_keyboard (GtkWidget *widget,
503                GdkEvent *event,
504                gpointer user_data)
505 {
506 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (user_data);
507 	GdkGrabStatus status;
508 	guint32 at;
509 	GdkDevice *device = NULL;
510 	GdkDeviceManager *manager;
511 	GdkDisplay *display;
512 	GList *devices, *l;
513 
514 	if (self->pv->grabbed_device || !GRAB_KEYBOARD)
515 		return FALSE;
516 
517 	display = gtk_widget_get_display (widget);
518 	manager = gdk_display_get_device_manager (display);
519 	devices = gdk_device_manager_list_devices (manager, GDK_DEVICE_TYPE_MASTER);
520 	for (l = devices; l; l = g_list_next (l)) {
521 		device = l->data;
522 		if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
523 			break;
524 	}
525 	g_list_free (devices);
526 
527 	if (!device) {
528 		g_message ("couldn't find device to grab");
529 		return FALSE;
530 	}
531 
532 	at = event ? gdk_event_get_time (event) : GDK_CURRENT_TIME;
533 	status = gdk_device_grab (device, gtk_widget_get_window (widget),
534 	                          GDK_OWNERSHIP_APPLICATION, TRUE,
535 	                          GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, at);
536 	if (status == GDK_GRAB_SUCCESS) {
537 		self->pv->grab_broken_id = g_signal_connect (widget, "grab-broken-event",
538 		                                             G_CALLBACK (on_grab_broken), self);
539 		gtk_device_grab_add (widget, device, TRUE);
540 		self->pv->grabbed_device = device;
541 	} else {
542 		g_message ("could not grab keyboard: %s", grab_status_message (status));
543 	}
544 
545 	/* Always return false, so event is handled elsewhere */
546 	return FALSE;
547 }
548 
549 static gboolean
ungrab_keyboard(GtkWidget * widget,GdkEvent * event,gpointer user_data)550 ungrab_keyboard (GtkWidget *widget,
551                  GdkEvent *event,
552                  gpointer user_data)
553 {
554 	guint32 at = event ? gdk_event_get_time (event) : GDK_CURRENT_TIME;
555 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (user_data);
556 
557 	if (self->pv->grabbed_device) {
558 		g_signal_handler_disconnect (widget, self->pv->grab_broken_id);
559 		gdk_device_ungrab (self->pv->grabbed_device, at);
560 		gtk_device_grab_remove (widget, self->pv->grabbed_device);
561 		self->pv->grabbed_device = NULL;
562 		self->pv->grab_broken_id = 0;
563 	}
564 
565 	/* Always return false, so event is handled elsewhere */
566 	return FALSE;
567 }
568 #endif
569 
570 static gboolean
window_state_changed(GtkWidget * win,GdkEventWindowState * event,gpointer data)571 window_state_changed (GtkWidget *win, GdkEventWindowState *event, gpointer data)
572 {
573 	GdkWindowState state = gdk_window_get_state (gtk_widget_get_window (win));
574 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (data);
575 
576 	if (state & GDK_WINDOW_STATE_WITHDRAWN ||
577 	    state & GDK_WINDOW_STATE_ICONIFIED ||
578 	    state & GDK_WINDOW_STATE_FULLSCREEN ||
579 	    state & GDK_WINDOW_STATE_MAXIMIZED) {
580 		self->pv->grab_disabled = TRUE;
581 		ungrab_keyboard (win, (GdkEvent*)event, data);
582 	} else if (self->pv->grab_disabled) {
583 		self->pv->grab_disabled = FALSE;
584 		grab_keyboard (win, (GdkEvent*)event, data);
585 	}
586 
587 	return FALSE;
588 }
589 
590 static void
gcr_prompt_dialog_constructed(GObject * obj)591 gcr_prompt_dialog_constructed (GObject *obj)
592 {
593 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (obj);
594 	GtkDialog *dialog;
595 	PangoAttrList *attrs;
596 	GtkWidget *widget;
597 	GtkWidget *entry;
598 	GtkWidget *content;
599 	GtkWidget *button;
600 	GtkGrid *grid;
601 
602 	G_OBJECT_CLASS (gcr_prompt_dialog_parent_class)->constructed (obj);
603 
604 	dialog = GTK_DIALOG (self);
605 	button = gtk_dialog_add_button (dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
606 	g_object_bind_property (self, "cancel-label", button, "label", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
607 	gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
608 	button = gtk_dialog_add_button (dialog, _("_OK"), GTK_RESPONSE_OK);
609 	g_object_bind_property (self, "continue-label", button, "label", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
610 	gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
611 	self->pv->continue_button = button;
612 
613 	gtk_window_set_type_hint (GTK_WINDOW (dialog), GDK_WINDOW_TYPE_HINT_NORMAL);
614 	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
615 	gtk_window_set_keep_above (GTK_WINDOW (dialog), TRUE);
616 	gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
617 
618 	content = gtk_dialog_get_content_area (dialog);
619 
620 	grid = GTK_GRID (gtk_grid_new ());
621 	gtk_container_set_border_width (GTK_CONTAINER (grid), 6);
622 	gtk_widget_set_hexpand (GTK_WIDGET (grid), TRUE);
623 	gtk_grid_set_column_homogeneous (grid, FALSE);
624 	gtk_grid_set_column_spacing (grid, 12);
625 	gtk_grid_set_row_spacing (grid, 6);
626 
627 	/* The prompt image */
628 	self->pv->image = gtk_image_new_from_icon_name ("dialog-password", GTK_ICON_SIZE_DIALOG);
629 	gtk_widget_set_valign (self->pv->image, GTK_ALIGN_START);
630 	gtk_grid_attach (grid, self->pv->image, -1, 0, 1, 4);
631 	gtk_widget_show (self->pv->image);
632 
633 	/* The prompt spinner on the continue button */
634 	widget = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog),
635 	                                             GTK_RESPONSE_OK);
636 	self->pv->spinner = gtk_spinner_new ();
637 	gtk_button_set_image (GTK_BUTTON (widget), self->pv->spinner);
638 	gtk_button_set_image_position (GTK_BUTTON (widget), GTK_POS_LEFT);
639 
640 	/* The message label */
641 	widget = gtk_label_new ("");
642 	attrs = pango_attr_list_new ();
643 	pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
644 	pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_LARGE));
645 	gtk_label_set_attributes (GTK_LABEL (widget), attrs);
646 	pango_attr_list_unref (attrs);
647 	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
648 	gtk_widget_set_halign (widget, GTK_ALIGN_START);
649 	gtk_widget_set_hexpand (widget, TRUE);
650 	gtk_widget_set_margin_bottom (widget, 8);
651 	g_object_bind_property (self, "message", widget, "label", G_BINDING_DEFAULT);
652 	gtk_grid_attach (grid, widget, 0, 0, 2, 1);
653 	gtk_widget_show (widget);
654 
655 	/* The description label */
656 	widget = gtk_label_new ("");
657 	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
658 	gtk_widget_set_halign (widget, GTK_ALIGN_START);
659 	gtk_widget_set_hexpand (widget, TRUE);
660 	gtk_widget_set_margin_bottom (widget, 4);
661 	g_object_bind_property (self, "description", widget, "label", G_BINDING_DEFAULT);
662 	gtk_grid_attach (grid, widget, 0, 1, 2, 1);
663 	gtk_widget_show (widget);
664 
665 	/* The password label */
666 	widget = gtk_label_new (_("Password:"));
667 	gtk_widget_set_halign (widget, GTK_ALIGN_START);
668 	gtk_widget_set_hexpand (widget, FALSE);
669 	g_object_bind_property (self, "password-visible", widget, "visible", G_BINDING_DEFAULT);
670 	gtk_grid_attach (grid, widget, 0, 2, 1, 1);
671 
672 	/* The password entry */
673 	self->pv->password_buffer = gcr_secure_entry_buffer_new ();
674 	entry = gtk_entry_new_with_buffer (self->pv->password_buffer);
675 	gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
676 	gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
677 	gtk_widget_set_hexpand (entry, TRUE);
678 	g_object_bind_property (self, "password-visible", entry, "visible", G_BINDING_DEFAULT);
679 	gtk_grid_attach (grid, entry, 1, 2, 1, 1);
680 	self->pv->password_entry = entry;
681 
682 	/* The confirm label */
683 	widget = gtk_label_new (_("Confirm:"));
684 	gtk_widget_set_halign (widget, GTK_ALIGN_START);
685 	gtk_widget_set_hexpand (widget, FALSE);
686 	g_object_bind_property (self, "confirm-visible", widget, "visible", G_BINDING_DEFAULT);
687 	gtk_grid_attach (grid, widget, 0, 3, 1, 1);
688 
689 	/* The confirm entry */
690 	self->pv->confirm_buffer = gcr_secure_entry_buffer_new ();
691 	widget = gtk_entry_new_with_buffer (self->pv->confirm_buffer);
692 	gtk_widget_set_hexpand (widget, TRUE);
693 	gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
694 	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
695 	g_object_bind_property (self, "confirm-visible", widget, "visible", G_BINDING_DEFAULT);
696 	gtk_grid_attach (grid, widget, 1, 3, 1, 1);
697 
698 	/* The quality progress bar */
699 	widget = gtk_progress_bar_new ();
700 	gtk_widget_set_hexpand (widget, TRUE);
701 	g_object_bind_property (self, "confirm-visible", widget, "visible", G_BINDING_DEFAULT);
702 	gtk_grid_attach (grid, widget, 1, 4, 1, 1);
703 	g_signal_connect (entry, "changed", G_CALLBACK (on_password_changed), widget);
704 
705 	/* The warning */
706 	widget = gtk_label_new ("");
707 	attrs = pango_attr_list_new ();
708 	pango_attr_list_insert (attrs, pango_attr_style_new (PANGO_STYLE_ITALIC));
709 	gtk_label_set_attributes (GTK_LABEL (widget), attrs);
710 	pango_attr_list_unref (attrs);
711 	gtk_widget_set_hexpand (widget, FALSE);
712 	g_object_bind_property (self, "warning", widget, "label", G_BINDING_DEFAULT);
713 	g_object_bind_property (self, "warning-visible", widget, "visible", G_BINDING_DEFAULT);
714 	gtk_grid_attach (grid, widget, 0, 5, 2, 1);
715 	gtk_widget_show (widget);
716 
717 	/* The checkbox */
718 	widget = g_object_new (GTK_TYPE_CHECK_BUTTON, "use-underline", TRUE, NULL);
719 	g_object_bind_property (self, "choice-label", widget, "label", G_BINDING_DEFAULT);
720 	g_object_bind_property (self, "choice-visible", widget, "visible", G_BINDING_DEFAULT);
721 	g_object_bind_property (self, "choice-chosen", widget, "active", G_BINDING_BIDIRECTIONAL);
722 	gtk_widget_set_hexpand (widget, FALSE);
723 	gtk_grid_attach (grid, widget, 0, 6, 2, 1);
724 
725 	gtk_container_add (GTK_CONTAINER (content), GTK_WIDGET (grid));
726 	gtk_widget_show (GTK_WIDGET (grid));
727 	self->pv->widget_grid = GTK_WIDGET (grid);
728 
729 	g_signal_connect (self, "map-event", G_CALLBACK (grab_keyboard), self);
730 	g_signal_connect (self, "unmap-event", G_CALLBACK (ungrab_keyboard), self);
731 	g_signal_connect (self, "window-state-event", G_CALLBACK (window_state_changed), self);
732 
733 }
734 
735 static gboolean
handle_password_response(GcrPromptDialog * self)736 handle_password_response (GcrPromptDialog *self)
737 {
738 	const gchar *password;
739 	const gchar *confirm;
740 	const gchar *env;
741 	gint strength;
742 
743 	password = gtk_entry_buffer_get_text (self->pv->password_buffer);
744 
745 	/* Is it a new password? */
746 	if (self->pv->password_new) {
747 		confirm = gtk_entry_buffer_get_text (self->pv->confirm_buffer);
748 
749 		/* Do the passwords match? */
750 		if (!g_str_equal (password, confirm)) {
751 			gcr_prompt_set_warning (GCR_PROMPT (self), _("Passwords do not match."));
752 			return FALSE;
753 		}
754 
755 		/* Don't allow blank passwords if in paranoid mode */
756 		env = g_getenv ("GNOME_KEYRING_PARANOID");
757 		if (env && *env) {
758 			gcr_prompt_set_warning (GCR_PROMPT (self), _("Password cannot be blank"));
759 			return FALSE;
760 		}
761 	}
762 
763 	if (g_str_equal (password, ""))
764 		strength = 0;
765 	else
766 		strength = 1;
767 
768 	self->pv->password_strength = strength;
769 	g_object_notify (G_OBJECT (self), "password-strength");
770 	return TRUE;
771 }
772 
773 static void
gcr_prompt_dialog_response(GtkDialog * dialog,gint response_id)774 gcr_prompt_dialog_response (GtkDialog *dialog,
775                             gint response_id)
776 {
777 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (dialog);
778 	GSimpleAsyncResult *res;
779 
780 	/*
781 	 * If this is called while no prompting is going on, then the dialog
782 	 * is waiting for the caller to perform some action. Close the dialog.
783 	 */
784 
785 	if (self->pv->mode == PROMPT_NONE) {
786 		g_return_if_fail (response_id != GTK_RESPONSE_OK);
787 		gcr_prompt_close (GCR_PROMPT (self));
788 		return;
789 	}
790 
791 	switch (response_id) {
792 	case GTK_RESPONSE_OK:
793 		switch (self->pv->mode) {
794 		case PROMPT_PASSWORDING:
795 			if (!handle_password_response (self))
796 				return;
797 			break;
798 		default:
799 			break;
800 		}
801 		self->pv->last_reply = GCR_PROMPT_REPLY_CONTINUE;
802 		break;
803 
804 	default:
805 		self->pv->last_reply = GCR_PROMPT_REPLY_CANCEL;
806 		break;
807 	}
808 
809 	gtk_widget_set_sensitive (self->pv->continue_button, FALSE);
810 	gtk_widget_set_sensitive (self->pv->widget_grid, FALSE);
811 	gtk_widget_show (self->pv->spinner);
812 	gtk_spinner_start (GTK_SPINNER (self->pv->spinner));
813 	self->pv->mode = PROMPT_NONE;
814 
815 	res = self->pv->async_result;
816 	self->pv->async_result = NULL;
817 
818 	g_simple_async_result_complete (res);
819 	g_object_unref (res);
820 }
821 
822 static void
gcr_prompt_dialog_dispose(GObject * obj)823 gcr_prompt_dialog_dispose (GObject *obj)
824 {
825 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (obj);
826 
827 	gcr_prompt_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_DELETE_EVENT);
828 	g_assert (self->pv->async_result == NULL);
829 
830 	gcr_prompt_close (GCR_PROMPT (self));
831 
832 	ungrab_keyboard (GTK_WIDGET (self), NULL, self);
833 #if GTK_CHECK_VERSION (3,20,0)
834 	g_assert (self->pv->grabbed_seat == NULL);
835 #else
836 	g_assert (self->pv->grabbed_device == NULL);
837 #endif
838 
839 	G_OBJECT_CLASS (gcr_prompt_dialog_parent_class)->dispose (obj);
840 }
841 
842 static void
gcr_prompt_dialog_finalize(GObject * obj)843 gcr_prompt_dialog_finalize (GObject *obj)
844 {
845 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (obj);
846 
847 	g_free (self->pv->title);
848 	g_free (self->pv->message);
849 	g_free (self->pv->description);
850 	g_free (self->pv->warning);
851 	g_free (self->pv->choice_label);
852 	g_free (self->pv->caller_window);
853 
854 	g_object_unref (self->pv->password_buffer);
855 	g_object_unref (self->pv->confirm_buffer);
856 
857 	G_OBJECT_CLASS (gcr_prompt_dialog_parent_class)->finalize (obj);
858 }
859 
860 static void
gcr_prompt_dialog_class_init(GcrPromptDialogClass * klass)861 gcr_prompt_dialog_class_init (GcrPromptDialogClass *klass)
862 {
863 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
864 	GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
865 
866 	gobject_class->constructed = gcr_prompt_dialog_constructed;
867 	gobject_class->get_property = gcr_prompt_dialog_get_property;
868 	gobject_class->set_property = gcr_prompt_dialog_set_property;
869 	gobject_class->dispose = gcr_prompt_dialog_dispose;
870 	gobject_class->finalize = gcr_prompt_dialog_finalize;
871 
872 	dialog_class->response = gcr_prompt_dialog_response;
873 
874 	g_object_class_override_property (gobject_class, PROP_MESSAGE, "message");
875 
876 	g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
877 
878 	g_object_class_override_property (gobject_class, PROP_WARNING, "warning");
879 
880 	g_object_class_override_property (gobject_class, PROP_PASSWORD_NEW, "password-new");
881 
882 	g_object_class_override_property (gobject_class, PROP_PASSWORD_STRENGTH, "password-strength");
883 
884 	g_object_class_override_property (gobject_class, PROP_CHOICE_LABEL, "choice-label");
885 
886 	g_object_class_override_property (gobject_class, PROP_CHOICE_CHOSEN, "choice-chosen");
887 
888 	g_object_class_override_property (gobject_class, PROP_CALLER_WINDOW, "caller-window");
889 
890 	g_object_class_override_property (gobject_class, PROP_CONTINUE_LABEL, "continue-label");
891 
892 	g_object_class_override_property (gobject_class, PROP_CANCEL_LABEL, "cancel-label");
893 
894 	/**
895 	 * GcrPromptDialog:password-visible:
896 	 *
897 	 * Whether the password entry is visible or not.
898 	 */
899 	g_object_class_install_property (gobject_class, PROP_PASSWORD_VISIBLE,
900 	           g_param_spec_boolean ("password-visible", "Password visible", "Password field is visible",
901 	                                 FALSE,
902 	                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
903 
904 	/**
905 	 * GcrPromptDialog:confirm-visible:
906 	 *
907 	 * Whether the password confirm entry is visible or not.
908 	 */
909 	g_object_class_install_property (gobject_class, PROP_CONFIRM_VISIBLE,
910 	           g_param_spec_boolean ("confirm-visible", "Confirm visible", "Confirm field is visible",
911 	                                 FALSE,
912 	                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
913 
914 	/**
915 	 * GcrPromptDialog:warning-visible:
916 	 *
917 	 * Whether the warning label is visible or not.
918 	 */
919 	g_object_class_install_property (gobject_class, PROP_WARNING_VISIBLE,
920 	           g_param_spec_boolean ("warning-visible", "Warning visible", "Warning is visible",
921 	                                 FALSE,
922 	                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
923 
924 	/**
925 	 * GcrPromptDialog:choice-visible:
926 	 *
927 	 * Whether the choice check box is visible or not.
928 	 */
929 	g_object_class_install_property (gobject_class, PROP_CHOICE_VISIBLE,
930 	           g_param_spec_boolean ("choice-visible", "Choice visible", "Choice is visible",
931 	                                 FALSE,
932 	                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
933 }
934 
935 static void
gcr_prompt_dialog_password_async(GcrPrompt * prompt,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)936 gcr_prompt_dialog_password_async (GcrPrompt *prompt,
937                                   GCancellable *cancellable,
938                                   GAsyncReadyCallback callback,
939                                   gpointer user_data)
940 {
941 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (prompt);
942 	GObject *obj;
943 
944 	if (self->pv->async_result != NULL) {
945 		g_warning ("this prompt is already prompting");
946 		return;
947 	}
948 
949 	self->pv->mode = PROMPT_PASSWORDING;
950 	self->pv->async_result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
951 	                                                    gcr_prompt_dialog_password_async);
952 
953 	gtk_entry_buffer_set_text (self->pv->password_buffer, "", 0);
954 	gtk_entry_buffer_set_text (self->pv->confirm_buffer, "", 0);
955 
956 	if (self->pv->was_closed) {
957 		self->pv->last_reply = GCR_PROMPT_REPLY_CANCEL;
958 		g_simple_async_result_complete_in_idle (self->pv->async_result);
959 		return;
960 	}
961 
962 	gtk_image_set_from_icon_name (GTK_IMAGE (self->pv->image),
963 	                              "dialog-password", GTK_ICON_SIZE_DIALOG);
964 	gtk_widget_set_sensitive (self->pv->continue_button, TRUE);
965 	gtk_widget_set_sensitive (self->pv->widget_grid, TRUE);
966 	gtk_widget_hide (self->pv->spinner);
967 	gtk_spinner_stop (GTK_SPINNER (self->pv->spinner));
968 
969 	obj = G_OBJECT (self);
970 	g_object_notify (obj, "password-visible");
971 	g_object_notify (obj, "confirm-visible");
972 	g_object_notify (obj, "warning-visible");
973 	g_object_notify (obj, "choice-visible");
974 
975 	gtk_widget_grab_focus (self->pv->password_entry);
976 	gtk_widget_show (GTK_WIDGET (self));
977 }
978 
979 static const gchar *
gcr_prompt_dialog_password_finish(GcrPrompt * prompt,GAsyncResult * result,GError ** error)980 gcr_prompt_dialog_password_finish (GcrPrompt *prompt,
981                                    GAsyncResult *result,
982                                    GError **error)
983 {
984 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (prompt);
985 
986 	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (prompt),
987 	                      gcr_prompt_dialog_password_async), NULL);
988 
989 	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
990 		return NULL;
991 
992 	if (self->pv->last_reply == GCR_PROMPT_REPLY_CONTINUE)
993 		return gtk_entry_buffer_get_text (self->pv->password_buffer);
994 	return NULL;
995 }
996 
997 static void
gcr_prompt_dialog_confirm_async(GcrPrompt * prompt,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)998 gcr_prompt_dialog_confirm_async (GcrPrompt *prompt,
999                                  GCancellable *cancellable,
1000                                  GAsyncReadyCallback callback,
1001                                  gpointer user_data)
1002 {
1003 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (prompt);
1004 	GtkWidget *button;
1005 	GObject *obj;
1006 
1007 	if (self->pv->async_result != NULL) {
1008 		g_warning ("this prompt is already prompting");
1009 		return;
1010 	}
1011 
1012 	self->pv->mode = PROMPT_CONFIRMING;
1013 	self->pv->async_result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
1014 	                                                    gcr_prompt_dialog_confirm_async);
1015 
1016 	if (self->pv->was_closed) {
1017 		self->pv->last_reply = GCR_PROMPT_REPLY_CANCEL;
1018 		g_simple_async_result_complete_in_idle (self->pv->async_result);
1019 		return;
1020 	}
1021 
1022 	gtk_image_set_from_icon_name (GTK_IMAGE (self->pv->image),
1023 	                              "dialog-question", GTK_ICON_SIZE_DIALOG);
1024 	gtk_widget_set_sensitive (self->pv->continue_button, TRUE);
1025 	gtk_widget_set_sensitive (self->pv->widget_grid, TRUE);
1026 	gtk_widget_hide (self->pv->spinner);
1027 	gtk_spinner_stop (GTK_SPINNER (self->pv->spinner));
1028 
1029 	button = gtk_dialog_get_widget_for_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
1030 	gtk_widget_grab_focus (button);
1031 
1032 	obj = G_OBJECT (self);
1033 	g_object_notify (obj, "password-visible");
1034 	g_object_notify (obj, "confirm-visible");
1035 	g_object_notify (obj, "warning-visible");
1036 	g_object_notify (obj, "choice-visible");
1037 
1038 	gtk_widget_show (GTK_WIDGET (self));
1039 }
1040 
1041 static GcrPromptReply
gcr_prompt_dialog_confirm_finish(GcrPrompt * prompt,GAsyncResult * result,GError ** error)1042 gcr_prompt_dialog_confirm_finish (GcrPrompt *prompt,
1043                                   GAsyncResult *result,
1044                                   GError **error)
1045 {
1046 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (prompt);
1047 
1048 	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (prompt),
1049 	                      gcr_prompt_dialog_confirm_async), GCR_PROMPT_REPLY_CANCEL);
1050 
1051 	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
1052 		return GCR_PROMPT_REPLY_CANCEL;
1053 
1054 	return self->pv->last_reply;
1055 }
1056 
1057 static void
gcr_prompt_dialog_close(GcrPrompt * prompt)1058 gcr_prompt_dialog_close (GcrPrompt *prompt)
1059 {
1060 	GcrPromptDialog *self = GCR_PROMPT_DIALOG (prompt);
1061 	if (!self->pv->was_closed) {
1062 		self->pv->was_closed = TRUE;
1063 		gtk_widget_hide (GTK_WIDGET (self));
1064 	}
1065 }
1066 
1067 static void
gcr_prompt_dialog_prompt_iface(GcrPromptIface * iface)1068 gcr_prompt_dialog_prompt_iface (GcrPromptIface *iface)
1069 {
1070 	iface->prompt_password_async = gcr_prompt_dialog_password_async;
1071 	iface->prompt_password_finish = gcr_prompt_dialog_password_finish;
1072 	iface->prompt_confirm_async = gcr_prompt_dialog_confirm_async;
1073 	iface->prompt_confirm_finish = gcr_prompt_dialog_confirm_finish;
1074 	iface->prompt_close = gcr_prompt_dialog_close;
1075 }
1076