diff -Nru gtk+3.0-3.12.2/debian/changelog gtk+3.0-3.12.2/debian/changelog --- gtk+3.0-3.12.2/debian/changelog 2014-09-18 13:54:25.000000000 -0300 +++ gtk+3.0-3.12.2/debian/changelog 2014-09-25 08:55:17.000000000 -0300 @@ -1,3 +1,10 @@ +gtk+3.0 (3.12.2-0ubuntu8) utopic; urgency=medium + + * debian/patches/use-secrets-service-for-cups-auth_info.patch + - Stores auth_info in secrets service for printing (LP: #445333) + + -- Dariusz Gadomski Thu, 25 Sep 2014 13:54:57 +0200 + gtk+3.0 (3.12.2-0ubuntu7) utopic; urgency=medium * debian/patches/0001-Disable-deprecation-warnings-in-tests.patch diff -Nru gtk+3.0-3.12.2/debian/patches/series gtk+3.0-3.12.2/debian/patches/series --- gtk+3.0-3.12.2/debian/patches/series 2014-09-18 13:54:25.000000000 -0300 +++ gtk+3.0-3.12.2/debian/patches/series 2014-09-25 08:59:58.000000000 -0300 @@ -25,3 +25,4 @@ mir-backend.patch 0001-Disable-deprecation-warnings-in-tests.patch 0001-Fight-deprecation-warnings-in-test.patch +use-secrets-service-for-cups-auth_info.patch diff -Nru gtk+3.0-3.12.2/debian/patches/use-secrets-service-for-cups-auth_info.patch gtk+3.0-3.12.2/debian/patches/use-secrets-service-for-cups-auth_info.patch --- gtk+3.0-3.12.2/debian/patches/use-secrets-service-for-cups-auth_info.patch 1969-12-31 21:00:00.000000000 -0300 +++ gtk+3.0-3.12.2/debian/patches/use-secrets-service-for-cups-auth_info.patch 2014-09-25 09:02:40.000000000 -0300 @@ -0,0 +1,1555 @@ +Description: Use secrets service for cups auth_info + When a printer requires auth_info (e.g. a printer connected + over the samba protocol) it is now possible to save the + credentials necessary for printing if a secrets service + is available over dbus. + The auth_info is then stored / loaded from the default + collection of that secrets service. + If no such service is available the user is not shown + the option to remember the password and the behavior + remains the same as before. + . + gtk+3.0 (3.12.2-0ubuntu8) utopic; urgency=medium + . + * debian/patches/use-secrets-service-for-cups-auth_info.patch + - Stores auth_info in secrets service for printing (LP: #445333) +Author: Andre Heinecke +Origin: upstream, https://git.gnome.org/browse/gtk+/commit/?id=382d68ff8e5701a10aa8d4f879c46c63c21e075f +Bug-Ubuntu: https://bugs.launchpad.net/bugs/445333 +Bug: https://bugzilla.gnome.org/show_bug.cgi?id=674264 + +--- gtk+3.0-3.12.2.orig/gtk/gtkmarshalers.list ++++ gtk+3.0-3.12.2/gtk/gtkmarshalers.list +@@ -123,7 +123,6 @@ VOID:UINT,STRING,UINT + VOID:UINT,UINT + VOID:VOID + OBJECT:OBJECT,INT,INT +-VOID:POINTER,POINTER,POINTER,POINTER,STRING + VOID:OBJECT,STRING,POINTER,POINTER + INT:INT + VOID:POINTER,STRING,INT +--- gtk+3.0-3.12.2.orig/gtk/gtkprintbackend.c ++++ gtk+3.0-3.12.2/gtk/gtkprintbackend.c +@@ -47,6 +47,7 @@ struct _GtkPrintBackendPrivate + GtkPrintBackendStatus status; + char **auth_info_required; + char **auth_info; ++ gboolean store_auth_info; + }; + + enum { +@@ -360,7 +361,8 @@ static void request_pass + gpointer auth_info_default, + gpointer auth_info_display, + gpointer auth_info_visible, +- const gchar *prompt); ++ const gchar *prompt, ++ gboolean can_store_auth_info); + + static void + gtk_print_backend_class_init (GtkPrintBackendClass *class) +@@ -437,9 +439,9 @@ gtk_print_backend_class_init (GtkPrintBa + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkPrintBackendClass, request_password), +- NULL, NULL, +- _gtk_marshal_VOID__POINTER_POINTER_POINTER_POINTER_STRING, +- G_TYPE_NONE, 5, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING); ++ NULL, NULL, NULL, ++ G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, ++ G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN); + } + + static void +@@ -666,12 +668,24 @@ gtk_print_backend_print_stream (GtkPrint + void + gtk_print_backend_set_password (GtkPrintBackend *backend, + gchar **auth_info_required, +- gchar **auth_info) ++ gchar **auth_info, ++ gboolean store_auth_info) + { + g_return_if_fail (GTK_IS_PRINT_BACKEND (backend)); + + if (GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password) +- GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend, auth_info_required, auth_info); ++ GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend, ++ auth_info_required, ++ auth_info, ++ store_auth_info); ++} ++ ++static void ++store_auth_info_toggled (GtkCheckButton *chkbtn, ++ gpointer user_data) ++{ ++ gboolean *data = (gboolean *) user_data; ++ *data = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chkbtn)); + } + + static void +@@ -698,9 +712,9 @@ password_dialog_response (GtkWidget + gint i; + + if (response_id == GTK_RESPONSE_OK) +- gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info); ++ gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info, priv->store_auth_info); + else +- gtk_print_backend_set_password (backend, priv->auth_info_required, NULL); ++ gtk_print_backend_set_password (backend, priv->auth_info_required, NULL, FALSE); + + for (i = 0; i < g_strv_length (priv->auth_info_required); i++) + if (priv->auth_info[i] != NULL) +@@ -725,10 +739,11 @@ request_password (GtkPrintBackend *back + gpointer auth_info_default, + gpointer auth_info_display, + gpointer auth_info_visible, +- const gchar *prompt) ++ const gchar *prompt, ++ gboolean can_store_auth_info) + { + GtkPrintBackendPrivate *priv = backend->priv; +- GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry; ++ GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry, *chkbtn; + GtkWidget *focus = NULL; + GtkWidget *content_area; + gchar *markup; +@@ -742,6 +757,7 @@ request_password (GtkPrintBackend *back + priv->auth_info_required = g_strdupv (ai_required); + length = g_strv_length (ai_required); + priv->auth_info = g_new0 (gchar *, length); ++ priv->store_auth_info = FALSE; + + dialog = gtk_dialog_new_with_buttons ( _("Authentication"), NULL, GTK_DIALOG_MODAL, + _("_Cancel"), GTK_RESPONSE_CANCEL, +@@ -812,6 +828,16 @@ request_password (GtkPrintBackend *back + } + } + ++ if (can_store_auth_info) ++ { ++ chkbtn = gtk_check_button_new_with_mnemonic (_("_Remember password")); ++ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chkbtn), FALSE); ++ gtk_box_pack_start (GTK_BOX (vbox), chkbtn, FALSE, FALSE, 6); ++ g_signal_connect (chkbtn, "toggled", ++ G_CALLBACK (store_auth_info_toggled), ++ &(priv->store_auth_info)); ++ } ++ + if (focus != NULL) + { + gtk_widget_grab_focus (focus); +--- gtk+3.0-3.12.2.orig/gtk/gtkprintbackend.h ++++ gtk+3.0-3.12.2/gtk/gtkprintbackend.h +@@ -124,12 +124,14 @@ struct _GtkPrintBackendClass + gpointer auth_info_default, + gpointer auth_info_display, + gpointer auth_info_visible, +- const gchar *prompt); ++ const gchar *prompt, ++ gboolean can_store_auth_info); + + /* not a signal */ + void (*set_password) (GtkPrintBackend *backend, + gchar **auth_info_required, +- gchar **auth_info); ++ gchar **auth_info, ++ gboolean store_auth_info); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); +@@ -162,7 +164,8 @@ void gtk_print_backend_destroy + GDK_AVAILABLE_IN_ALL + void gtk_print_backend_set_password (GtkPrintBackend *backend, + gchar **auth_info_required, +- gchar **auth_info); ++ gchar **auth_info, ++ gboolean can_store_auth_info); + + /* Backend-only functions for GtkPrintBackend */ + +--- gtk+3.0-3.12.2.orig/modules/printbackends/cups/Makefile.am ++++ gtk+3.0-3.12.2/modules/printbackends/cups/Makefile.am +@@ -29,12 +29,14 @@ backend_LTLIBRARIES = libprintbackend-cu + libprintbackend_cups_la_SOURCES = \ + gtkprintbackendcups.c \ + gtkprintercups.c \ +- gtkcupsutils.c ++ gtkcupsutils.c \ ++ gtkcupssecretsutils.c + + noinst_HEADERS = \ + gtkprintbackendcups.h \ + gtkprintercups.h \ +- gtkcupsutils.h ++ gtkcupsutils.h \ ++ gtkcupssecretsutils.h + + libprintbackend_cups_la_LDFLAGS = -avoid-version -module $(no_undefined) + libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS) +--- /dev/null ++++ gtk+3.0-3.12.2/modules/printbackends/cups/gtkcupssecretsutils.c +@@ -0,0 +1,1044 @@ ++/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords ++ * Copyright (C) 2014, Intevation GmbH ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library. If not, see . ++ */ ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "gtkcupssecretsutils.h" ++ ++#define SECRETS_BUS "org.freedesktop.secrets" ++#define SECRETS_IFACE(interface) "org.freedesktop.Secret."interface ++#define SECRETS_PATH "/org/freedesktop/secrets" ++#define SECRETS_TIMEOUT 5000 ++ ++typedef enum ++{ ++ SECRETS_SERVICE_ACTION_QUERY, ++ SECRETS_SERVICE_ACTION_STORE ++} SecretsServiceAction; ++ ++typedef struct ++{ ++ GDBusConnection *dbus_connection; ++ SecretsServiceAction action; ++ gchar **auth_info, ++ **auth_info_labels, ++ **auth_info_required, ++ *printer_uri, ++ *session_path, ++ *collection_path; ++ GDBusProxy *item_proxy; ++ guint prompt_subscription; ++} SecretsServiceData; ++ ++/** ++ * create_attributes: ++ * @printer_uri: URI for the printer ++ * @additional_labels: Optional labels for additional attributes ++ * @additional_attrs: Optional additional attributes ++ * ++ * Creates a GVariant dictionary with key / value pairs that ++ * can be used to identify a secret item. ++ * ++ * Returns: A GVariant dictionary of string pairs or NULL on error. ++ */ ++static GVariant * ++create_attributes (const gchar *printer_uri, ++ gchar **additional_attrs, ++ gchar **additional_labels) ++{ ++ GVariantBuilder *attr_builder = NULL; ++ GVariant *ret = NULL; ++ ++ if (printer_uri == NULL) ++ { ++ GTK_NOTE (PRINTING, ++ g_print ("create_attributes called with invalid parameters.\n")); ++ return NULL; ++ } ++ ++ attr_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY); ++ /* The printer uri is the main identifying part */ ++ g_variant_builder_add (attr_builder, "{ss}", "uri", printer_uri); ++ ++ if (additional_labels != NULL) ++ { ++ int i; ++ for (i = 0; additional_labels[i] != NULL; i++) ++ { ++ g_variant_builder_add (attr_builder, "{ss}", ++ additional_labels[i], ++ additional_attrs[i]); ++ } ++ } ++ ++ ret = g_variant_builder_end (attr_builder); ++ g_variant_builder_unref (attr_builder); ++ ++ return ret; ++} ++ ++static void ++get_secret_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ GVariant *output, ++ *attributes; ++ gchar **auth_info = NULL, ++ *key = NULL, ++ *value = NULL; ++ GVariantIter *iter = NULL; ++ guint i; ++ gint pw_field = -1; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ output = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ attributes = g_dbus_proxy_get_cached_property (task_data->item_proxy, ++ "Attributes"); ++ if (attributes == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Failed to lookup attributes.\n")); ++ g_variant_unref (output); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ /* Iterate over the attributes to fill the auth info */ ++ g_variant_get (attributes, "a{ss}", &iter); ++ ++ auth_info = g_new0 (gchar *, ++ g_strv_length (task_data->auth_info_required) + 1); ++ ++ while (g_variant_iter_loop (iter, "{ss}", &key, &value)) ++ { ++ /* Match attributes with required auth info */ ++ for (i = 0; task_data->auth_info_required[i] != NULL; i++) ++ { ++ if ((strcmp (key, "user") == 0 || ++ strcmp (key, "username") == 0) && ++ strcmp (task_data->auth_info_required[i], ++ "username") == 0) ++ { ++ auth_info[i] = g_strdup (value); ++ } ++ else if (strcmp (key, "domain") == 0 && ++ strcmp (task_data->auth_info_required[i], "domain") == 0) ++ { ++ auth_info[i] = g_strdup (value); ++ } ++ else if ((strcmp (key, "hostname") == 0 || ++ strcmp (key, "server") == 0 ) && ++ strcmp (task_data->auth_info_required[i], "hostname") == 0) ++ { ++ auth_info[i] = g_strdup (value); ++ } ++ else if (strcmp (task_data->auth_info_required[i], "password") == 0) ++ { ++ pw_field = i; ++ } ++ } ++ } ++ ++ if (pw_field == -1) ++ { ++ /* should not happen... */ ++ GTK_NOTE (PRINTING, g_print ("No password required?.\n")); ++ g_variant_unref (output); ++ goto fail; ++ } ++ else ++ { ++ GVariant *secret, ++ *s_value; ++ gconstpointer ba_passwd = NULL; ++ gsize len = 0; ++ ++ secret = g_variant_get_child_value (output, 0); ++ g_variant_unref (output); ++ if (secret == NULL || g_variant_n_children (secret) != 4) ++ { ++ GTK_NOTE (PRINTING, g_print ("Get secret response invalid.\n")); ++ if (secret != NULL) ++ g_variant_unref (secret); ++ goto fail; ++ } ++ s_value = g_variant_get_child_value (secret, 2); ++ ba_passwd = g_variant_get_fixed_array (s_value, ++ &len, ++ sizeof (guchar)); ++ ++ g_variant_unref (secret); ++ ++ if (ba_passwd == NULL || strlen (ba_passwd) > len + 1) ++ { ++ /* No secret or the secret is not a zero terminated value */ ++ GTK_NOTE (PRINTING, g_print ("Invalid secret.\n")); ++ g_variant_unref (s_value); ++ goto fail; ++ } ++ ++ auth_info[pw_field] = g_strndup (ba_passwd, len); ++ g_variant_unref (s_value); ++ } ++ ++ for (i = 0; task_data->auth_info_required[i] != NULL; i++) ++ { ++ if (auth_info[i] == NULL) ++ { ++ /* Error out if we did not find everything */ ++ GTK_NOTE (PRINTING, g_print ("Failed to lookup required attribute: %s.\n", ++ task_data->auth_info_required[i])); ++ goto fail; ++ } ++ } ++ ++ g_task_return_pointer (task, auth_info, NULL); ++ return; ++ ++fail: ++ /* Error out */ ++ GTK_NOTE (PRINTING, g_print ("Failed to lookup secret.\n")); ++ for (i = 0; i < g_strv_length (task_data->auth_info_required); i++) ++ { ++ /* Not all fields of auth_info are neccessarily written so we can not ++ use strfreev here */ ++ g_free (auth_info[i]); ++ } ++ g_free (auth_info); ++ g_task_return_pointer (task, NULL, NULL); ++} ++ ++static void ++create_item_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ GError *error = NULL; ++ GVariant *output; ++ gchar *item = NULL; ++ ++ task = user_data; ++ ++ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ g_variant_get (output, "(&o&o)", &item, NULL); ++ if (item != NULL && strlen (item) > 1) ++ { ++ GTK_NOTE (PRINTING, g_print ("Successfully stored auth info.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ g_variant_unref (output); ++} ++ ++static void ++do_store_auth_info (GTask *task) ++{ ++ GVariant *attributes = NULL, ++ *properties = NULL, ++ *secret = NULL; ++ gchar **additional_attrs = NULL, ++ **additional_labels = NULL, ++ *password = NULL; ++ SecretsServiceData *task_data = g_task_get_task_data (task); ++ guint i, ++ length, ++ additional_count = 0; ++ GVariantBuilder *prop_builder = NULL; ++ ++ length = g_strv_length (task_data->auth_info_labels); ++ ++ additional_attrs = g_new0 (gchar *, length + 1); ++ additional_labels = g_new0 (gchar *, length + 1); ++ /* The labels user and server are chosen to be compatible with ++ the attributes used by system-config-printer */ ++ for (i = 0; task_data->auth_info_labels[i] != NULL; i++) ++ { ++ if (g_strcmp0 (task_data->auth_info_labels[i], "username") == 0) ++ { ++ additional_attrs[additional_count] = task_data->auth_info[i]; ++ additional_labels[additional_count++] = "user"; ++ } ++ else if (g_strcmp0 (task_data->auth_info_labels[i], "hostname") == 0) ++ { ++ additional_attrs[additional_count] = task_data->auth_info[i]; ++ additional_labels[additional_count++] = "server"; ++ } ++ else if (g_strcmp0 (task_data->auth_info_labels[i], "password") == 0) ++ { ++ password = task_data->auth_info[i]; ++ } ++ } ++ ++ attributes = create_attributes (task_data->printer_uri, ++ additional_attrs, ++ additional_labels); ++ g_free (additional_labels); ++ g_free (additional_attrs); ++ if (attributes == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ if (password == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("No secret to store.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ prop_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY); ++ ++ g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Label"), ++ g_variant_new_string (task_data->printer_uri)); ++ g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Attributes"), ++ attributes); ++ ++ properties = g_variant_builder_end (prop_builder); ++ ++ g_variant_builder_unref (prop_builder); ++ ++ secret = g_variant_new ("(oay@ays)", ++ task_data->session_path, ++ NULL, ++ g_variant_new_bytestring (password), ++ "text/plain"); ++ ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ task_data->collection_path, ++ SECRETS_IFACE ("Collection"), ++ "CreateItem", ++ g_variant_new ("(@a{sv}@(oayays)b)", ++ properties, ++ secret, ++ TRUE), ++ G_VARIANT_TYPE ("(oo)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ create_item_cb, ++ task); ++} ++ ++static void ++prompt_completed_cb (GDBusConnection *connection, ++ const gchar *sender_name, ++ const gchar *object_path, ++ const gchar *interface_name, ++ const gchar *signal_name, ++ GVariant *parameters, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GVariant *dismissed; ++ gboolean is_dismissed = TRUE; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ g_dbus_connection_signal_unsubscribe (task_data->dbus_connection, ++ task_data->prompt_subscription); ++ task_data->prompt_subscription = 0; ++ ++ dismissed = g_variant_get_child_value (parameters, 0); ++ ++ if (dismissed == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Invalid prompt signal.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ g_variant_get (dismissed, "b", &is_dismissed); ++ g_variant_unref (dismissed); ++ ++ if (is_dismissed) ++ { ++ GTK_NOTE (PRINTING, g_print ("Collection unlock dismissed.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ /* Prompt successfull, proceed to get or store secret */ ++ switch (task_data->action) ++ { ++ case SECRETS_SERVICE_ACTION_STORE: ++ do_store_auth_info (task); ++ break; ++ ++ case SECRETS_SERVICE_ACTION_QUERY: ++ g_dbus_proxy_call (task_data->item_proxy, ++ "GetSecret", ++ g_variant_new ("(o)", ++ task_data->session_path), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ get_secret_cb, ++ task); ++ break; ++ } ++} ++ ++static void ++prompt_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ GVariant *output; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ g_variant_unref (output); ++ ++ /* Connect to the prompt's completed signal */ ++ task_data->prompt_subscription = ++ g_dbus_connection_signal_subscribe (task_data->dbus_connection, ++ NULL, ++ SECRETS_IFACE ("Prompt"), ++ "Completed", ++ NULL, ++ NULL, ++ G_DBUS_SIGNAL_FLAGS_NONE, ++ prompt_completed_cb, ++ task, ++ NULL); ++} ++ ++static void ++unlock_collection_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ GVariant *output; ++ const gchar *prompt_path; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ g_variant_get (output, "(@ao&o)", NULL, &prompt_path); ++ ++ if (prompt_path != NULL && strlen (prompt_path) > 1) ++ { ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ prompt_path, ++ SECRETS_IFACE ("Prompt"), ++ "Prompt", ++ g_variant_new ("(s)", "0"), ++ G_VARIANT_TYPE ("()"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ prompt_cb, ++ task); ++ } ++ else ++ { ++ switch (task_data->action) ++ { ++ case SECRETS_SERVICE_ACTION_STORE: ++ do_store_auth_info (task); ++ break; ++ ++ case SECRETS_SERVICE_ACTION_QUERY: ++ /* Prompt successfull proceed to get secret */ ++ g_dbus_proxy_call (task_data->item_proxy, ++ "GetSecret", ++ g_variant_new ("(o)", ++ task_data->session_path), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ get_secret_cb, ++ task); ++ break; ++ } ++ } ++ g_variant_unref (output); ++} ++ ++static void ++unlock_read_alias_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ GVariant *output, ++ *subresult; ++ gsize path_len = 0; ++ const gchar *collection_path; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ subresult = g_variant_get_child_value (output, 0); ++ g_variant_unref (output); ++ ++ if (subresult == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Invalid ReadAlias response.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ collection_path = g_variant_get_string (subresult, &path_len); ++ ++ const gchar * const to_unlock[] = ++ { ++ collection_path, NULL ++ }; ++ ++ task_data->collection_path = g_strdup (collection_path); ++ ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ SECRETS_PATH, ++ SECRETS_IFACE ("Service"), ++ "Unlock", ++ g_variant_new ("(^ao)", to_unlock), ++ G_VARIANT_TYPE ("(aoo)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ unlock_collection_cb, ++ task); ++ ++ g_variant_unref (subresult); ++} ++ ++static void ++item_proxy_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ GDBusProxy *item_proxy; ++ GVariant *locked; ++ gboolean is_locked; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ item_proxy = g_dbus_proxy_new_finish (res, ++ &error); ++ if (item_proxy == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ task_data->item_proxy = item_proxy; ++ ++ locked = g_dbus_proxy_get_cached_property (item_proxy, "Locked"); ++ ++ if (locked == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Failed to look up \"Locked\" property on item.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ g_variant_get (locked, "b", &is_locked); ++ g_variant_unref (locked); ++ ++ if (is_locked) ++ { ++ /* Go down the unlock -> lookup path */ ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ SECRETS_PATH, ++ SECRETS_IFACE ("Service"), ++ "ReadAlias", ++ g_variant_new ("(s)", "default"), ++ G_VARIANT_TYPE ("(o)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ unlock_read_alias_cb, ++ task); ++ return; ++ } ++ ++ /* Unlocked proceed to get or store secret */ ++ switch (task_data->action) ++ { ++ case SECRETS_SERVICE_ACTION_STORE: ++ do_store_auth_info (task); ++ break; ++ ++ case SECRETS_SERVICE_ACTION_QUERY: ++ g_dbus_proxy_call (item_proxy, ++ "GetSecret", ++ g_variant_new ("(o)", ++ task_data->session_path), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ get_secret_cb, ++ task); ++ break; ++ } ++} ++ ++static void ++search_items_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ GVariant *output; ++ gsize array_cnt, ++ i; ++ gboolean found_item = FALSE; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ array_cnt = g_variant_n_children (output); ++ ++ for (i = 0; i < array_cnt; i++) ++ { ++ GVariant * const item_paths = g_variant_get_child_value (output, i); ++ const gchar **items = NULL; ++ ++ if (item_paths == NULL) ++ { ++ GTK_NOTE (PRINTING, ++ g_print ("SearchItems returned invalid result.\n")); ++ continue; ++ } ++ ++ items = g_variant_get_objv (item_paths, NULL); ++ ++ if (*items == NULL) ++ { ++ g_variant_unref (item_paths); ++ g_free ((gpointer) items); ++ continue; ++ } ++ ++ /* Access the first found item. */ ++ found_item = TRUE; ++ g_dbus_proxy_new (task_data->dbus_connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ NULL, ++ SECRETS_BUS, ++ *items, ++ SECRETS_IFACE ("Item"), ++ g_task_get_cancellable (task), ++ item_proxy_cb, ++ task); ++ g_free ((gpointer) items); ++ g_variant_unref (item_paths); ++ break; ++ } ++ g_variant_unref (output); ++ ++ if (!found_item) ++ { ++ GTK_NOTE (PRINTING, g_print ("No match found in secrets service.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++} ++ ++static void ++open_session_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ GVariant *output, ++ *session_variant; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), ++ res, ++ &error); ++ if (output == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ session_variant = g_variant_get_child_value (output, 1); ++ ++ if (session_variant == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Invalid session path response.\n")); ++ g_variant_unref (output); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ task_data->session_path = g_variant_dup_string (session_variant, NULL); ++ ++ if (task_data->session_path == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Invalid session path string value.\n")); ++ g_variant_unref (session_variant); ++ g_variant_unref (output); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ g_variant_unref (session_variant); ++ g_variant_unref (output); ++ ++ switch (task_data->action) ++ { ++ case SECRETS_SERVICE_ACTION_QUERY: ++ { ++ /* Search for the secret item */ ++ GVariant *secrets_attrs; ++ ++ secrets_attrs = create_attributes (task_data->printer_uri, NULL, NULL); ++ if (secrets_attrs == NULL) ++ { ++ GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n")); ++ g_task_return_pointer (task, NULL, NULL); ++ return; ++ } ++ ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ SECRETS_PATH, ++ SECRETS_IFACE ("Service"), ++ "SearchItems", ++ g_variant_new ("(@a{ss})", secrets_attrs), ++ G_VARIANT_TYPE ("(aoao)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ search_items_cb, ++ task); ++ break; ++ } ++ ++ case SECRETS_SERVICE_ACTION_STORE: ++ { ++ /* Look up / unlock the default collection for storing */ ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ SECRETS_PATH, ++ SECRETS_IFACE ("Service"), ++ "ReadAlias", ++ g_variant_new ("(s)", "default"), ++ G_VARIANT_TYPE ("(o)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ unlock_read_alias_cb, ++ task); ++ break; ++ } ++ } ++} ++ ++static void ++get_connection_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ GError *error = NULL; ++ ++ task = user_data; ++ task_data = g_task_get_task_data (task); ++ ++ task_data->dbus_connection = g_bus_get_finish (res, &error); ++ if (task_data->dbus_connection == NULL) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ ++ /* Now open a session */ ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ SECRETS_PATH, ++ SECRETS_IFACE ("Service"), ++ "OpenSession", ++ g_variant_new ("(sv)", "plain", ++ g_variant_new_string ("")), ++ G_VARIANT_TYPE ("(vo)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ g_task_get_cancellable (task), ++ open_session_cb, ++ task); ++} ++ ++/** ++ * gtk_cups_secrets_service_watch: ++ * @appeared: The callback to call when the service interface appears ++ * @vanished: The callback to call when the service interface vanishes ++ * @user_data: A reference to the watching printbackend ++ * ++ * Registers a watch for the secrets service interface. ++ * ++ * Returns: The watcher id ++ */ ++guint ++gtk_cups_secrets_service_watch (GBusNameAppearedCallback appeared, ++ GBusNameVanishedCallback vanished, ++ gpointer user_data) ++{ ++ return g_bus_watch_name (G_BUS_TYPE_SESSION, ++ SECRETS_BUS, ++ G_BUS_NAME_WATCHER_FLAGS_AUTO_START, ++ appeared, ++ vanished, ++ user_data, ++ NULL); ++} ++ ++void ++cleanup_task_data (gpointer data) ++{ ++ gint i; ++ SecretsServiceData *task_data = data; ++ ++ g_free (task_data->collection_path); ++ g_strfreev (task_data->auth_info_labels); ++ g_strfreev (task_data->auth_info_required); ++ g_free (task_data->printer_uri); ++ ++ if (task_data->auth_info != NULL) ++ { ++ for (i = 0; task_data->auth_info[i] != NULL; i++) ++ { ++ memset (task_data->auth_info[i], 0, strlen (task_data->auth_info[i])); ++ g_clear_pointer (&task_data->auth_info[i], g_free); ++ } ++ g_clear_pointer (&task_data->auth_info, g_free); ++ } ++ ++ if (task_data->prompt_subscription != 0) ++ { ++ g_dbus_connection_signal_unsubscribe (task_data->dbus_connection, ++ task_data->prompt_subscription); ++ task_data->prompt_subscription = 0; ++ } ++ ++ if (task_data->session_path != NULL) ++ { ++ g_dbus_connection_call (task_data->dbus_connection, ++ SECRETS_BUS, ++ task_data->session_path, ++ SECRETS_IFACE ("Session"), ++ "Close", ++ NULL, ++ G_VARIANT_TYPE ("()"), ++ G_DBUS_CALL_FLAGS_NONE, ++ SECRETS_TIMEOUT, ++ NULL, ++ NULL, ++ NULL); ++ } ++ ++ g_clear_object (&task_data->dbus_connection); ++ g_clear_pointer (&task_data->session_path, g_free); ++ g_clear_object (&task_data->item_proxy); ++} ++ ++/** ++ * gtk_cups_secrets_service_query_task: ++ * @source_object: Source object for this task ++ * @cancellable: Cancellable to cancel this task ++ * @callback: Callback to call once the query is finished ++ * @user_data: The user_data passed to the callback ++ * @printer_uri: URI of the printer ++ * @auth_info_required: Info required for authentication ++ * ++ * Checks if a secrets service as described by the secrets-service standard ++ * is available and if so it tries to find the authentication info in the ++ * default collection of the service. ++ * ++ * This is the entry point to a chain of async calls to open a session, ++ * search the secret, unlock the collection (if necessary) and finally ++ * to lookup the secret. ++ * ++ * See: http://standards.freedesktop.org/secret-service/ for documentation ++ * of the used API. ++ */ ++void ++gtk_cups_secrets_service_query_task (gpointer source_object, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data, ++ const gchar *printer_uri, ++ gchar **auth_info_required) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ ++ task_data = g_new0 (SecretsServiceData, 1); ++ task_data->action = SECRETS_SERVICE_ACTION_QUERY; ++ task_data->printer_uri = g_strdup (printer_uri); ++ task_data->auth_info_required = g_strdupv (auth_info_required); ++ ++ task = g_task_new (source_object, cancellable, callback, user_data); ++ ++ g_task_set_task_data (task, task_data, cleanup_task_data); ++ ++ g_bus_get (G_BUS_TYPE_SESSION, cancellable, ++ get_connection_cb, task); ++} ++ ++static void ++store_done_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task = (GTask *) res; ++ GError *error = NULL; ++ ++ g_task_propagate_pointer (task, &error); ++ ++ if (error != NULL) ++ { ++ GTK_NOTE (PRINTING, ++ g_print ("Failed to store auth info: %s\n", error->message)); ++ g_error_free (error); ++ } ++ ++ g_object_unref (task); ++ GTK_NOTE (PRINTING, ++ g_print ("gtk_cups_secrets_service_store finished.\n")); ++} ++ ++/** ++ * gtk_cups_secrets_service_store: ++ * @auth_info: Auth info that should be stored ++ * @auth_info_labels: The keys to use for the auth info ++ * @printer_uri: URI of the printer ++ * ++ * Tries to store the auth_info in a secrets service. ++ */ ++void ++gtk_cups_secrets_service_store (gchar **auth_info, ++ gchar **auth_info_labels, ++ const gchar *printer_uri) ++{ ++ GTask *task; ++ SecretsServiceData *task_data; ++ ++ if (auth_info == NULL || auth_info_labels == NULL || printer_uri == NULL) ++ { ++ GTK_NOTE (PRINTING, ++ g_print ("Invalid call to gtk_cups_secrets_service_store.\n")); ++ return; ++ } ++ ++ task_data = g_new0 (SecretsServiceData, 1); ++ task_data->action = SECRETS_SERVICE_ACTION_STORE; ++ task_data->printer_uri = g_strdup (printer_uri); ++ task_data->auth_info = g_strdupv (auth_info); ++ task_data->auth_info_labels = g_strdupv (auth_info_labels); ++ ++ task = g_task_new (NULL, NULL, store_done_cb, NULL); ++ ++ g_task_set_task_data (task, task_data, cleanup_task_data); ++ ++ g_bus_get (G_BUS_TYPE_SESSION, NULL, ++ get_connection_cb, task); ++} +--- /dev/null ++++ gtk+3.0-3.12.2/modules/printbackends/cups/gtkcupssecretsutils.h +@@ -0,0 +1,41 @@ ++/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords ++ * Copyright (C) 2014 Intevation GmbH ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library. If not, see . ++ */ ++#ifndef __GTK_SECRETS_UTILS_H__ ++#define __GTK_SECRETS_UTILS_H__ ++ ++#include ++ ++#include "gtkcupsutils.h" ++ ++G_BEGIN_DECLS ++ ++void gtk_cups_secrets_service_query_task (gpointer source_object, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data, ++ const gchar *printer_uri, ++ gchar **auth_info_required); ++guint gtk_cups_secrets_service_watch (GBusNameAppearedCallback appeared, ++ GBusNameVanishedCallback vanished, ++ gpointer user_data); ++void gtk_cups_secrets_service_store (gchar **auth_info, ++ gchar **auth_info_labels, ++ const gchar *printer_uri); ++ ++G_END_DECLS ++ ++#endif /* __GTK_SECRETS_UTILS_H__ */ +--- gtk+3.0-3.12.2.orig/modules/printbackends/cups/gtkprintbackendcups.c ++++ gtk+3.0-3.12.2/modules/printbackends/cups/gtkprintbackendcups.c +@@ -58,6 +58,7 @@ + #include "gtkprintercups.h" + + #include "gtkcupsutils.h" ++#include "gtkcupssecretsutils.h" + + #ifdef HAVE_COLORD + #include +@@ -157,6 +158,9 @@ struct _GtkPrintBackendCups + gchar *avahi_service_browser_paths[2]; + GCancellable *avahi_cancellable; + #endif ++ gboolean secrets_service_available; ++ guint secrets_service_watch_id; ++ GCancellable *secrets_service_cancellable; + }; + + static GObjectClass *backend_parent_class; +@@ -217,16 +221,26 @@ static cairo_surface_t * cups_printer + + static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend, + gchar **auth_info_required, +- gchar **auth_info); ++ gchar **auth_info, ++ gboolean store_auth_info); + + void overwrite_and_free (gpointer data); + static gboolean is_address_local (const gchar *address); + static gboolean request_auth_info (gpointer data); ++static void lookup_auth_info (gpointer data); + + #ifdef HAVE_CUPS_API_1_6 + static void avahi_request_printer_list (GtkPrintBackendCups *cups_backend); + #endif + ++static void secrets_service_appeared_cb (GDBusConnection *connection, ++ const gchar *name, ++ const gchar *name_owner, ++ gpointer user_data); ++static void secrets_service_vanished_cb (GDBusConnection *connection, ++ const gchar *name, ++ gpointer user_data); ++ + static void + gtk_print_backend_cups_register_type (GTypeModule *module) + { +@@ -784,6 +798,13 @@ gtk_print_backend_cups_init (GtkPrintBac + #endif + + cups_get_local_default_printer (backend_cups); ++ ++ backend_cups->secrets_service_available = FALSE; ++ backend_cups->secrets_service_cancellable = g_cancellable_new (); ++ backend_cups->secrets_service_watch_id = ++ gtk_cups_secrets_service_watch (secrets_service_appeared_cb, ++ secrets_service_vanished_cb, ++ backend_cups); + } + + static void +@@ -819,6 +840,12 @@ gtk_print_backend_cups_finalize (GObject + g_clear_object (&backend_cups->dbus_connection); + #endif + ++ g_clear_object (&backend_cups->secrets_service_cancellable); ++ if (backend_cups->secrets_service_watch_id != 0) ++ { ++ g_bus_unwatch_name (backend_cups->secrets_service_watch_id); ++ } ++ + backend_parent_class->finalize (object); + } + +@@ -899,7 +926,8 @@ is_address_local (const gchar *address) + static void + gtk_print_backend_cups_set_password (GtkPrintBackend *backend, + gchar **auth_info_required, +- gchar **auth_info) ++ gchar **auth_info, ++ gboolean store_auth_info) + { + GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend); + GList *l; +@@ -928,7 +956,7 @@ gtk_print_backend_cups_set_password (Gtk + gchar *key = g_strconcat (username, "@", hostname, NULL); + g_hash_table_insert (cups_backend->auth, key, g_strdup (password)); + GTK_NOTE (PRINTING, +- g_print ("CUPS backend: storing password for %s\n", key)); ++ g_print ("CUPS backend: caching password for %s\n", key)); + } + + g_free (cups_backend->username); +@@ -951,6 +979,17 @@ gtk_print_backend_cups_set_password (Gtk + for (i = 0; i < length; i++) + dispatch->request->auth_info[i] = g_strdup (auth_info[i]); + } ++ /* Save the password if the user requested it */ ++ if (password != NULL && store_auth_info) ++ { ++ const gchar *printer_uri = ++ gtk_cups_request_ipp_get_string (dispatch->request, ++ IPP_TAG_URI, ++ "printer-uri"); ++ ++ gtk_cups_secrets_service_store (auth_info, auth_info_required, ++ printer_uri); ++ } + dispatch->backend->authentication_lock = FALSE; + dispatch->request->need_auth_info = FALSE; + } +@@ -1078,7 +1117,9 @@ request_password (gpointer data) + g_free (printer_name); + + g_signal_emit_by_name (dispatch->backend, "request-password", +- auth_info_required, auth_info_default, auth_info_display, auth_info_visible, prompt); ++ auth_info_required, auth_info_default, ++ auth_info_display, auth_info_visible, prompt, ++ FALSE); /* Cups password is only cached not stored. */ + + g_free (prompt); + } +@@ -1182,6 +1223,98 @@ check_auth_info (gpointer user_data) + return G_SOURCE_CONTINUE; + } + ++static void ++lookup_auth_info_cb (GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GTask *task; ++ GtkPrintCupsDispatchWatch *dispatch; ++ gchar **auth_info; ++ GError *error = NULL; ++ gint i; ++ ++ task = (GTask *) res; ++ dispatch = user_data; ++ auth_info = g_task_propagate_pointer (task, &error); ++ ++ if (auth_info == NULL) ++ { ++ if (error != NULL) ++ { ++ GTK_NOTE (PRINTING, ++ g_print ("Failed to look up auth info: %s\n", error->message)); ++ g_error_free (error); ++ } ++ else ++ { ++ /* Error note should have been shown by the function causing this */ ++ GTK_NOTE (PRINTING, g_print ("Failed to look up auth info.\n")); ++ } ++ dispatch->backend->authentication_lock = FALSE; ++ g_object_unref (task); ++ request_auth_info (dispatch); ++ return; ++ } ++ ++ gtk_print_backend_cups_set_password (GTK_PRINT_BACKEND (dispatch->backend), ++ dispatch->request->auth_info_required, auth_info, ++ FALSE); ++ for (i = 0; auth_info[i] != NULL; i++) ++ { ++ overwrite_and_free (auth_info[i]); ++ auth_info[i] = NULL; ++ } ++ g_clear_pointer (auth_info, g_free); ++ ++ g_object_unref (task); ++} ++ ++static void ++lookup_auth_info (gpointer user_data) ++{ ++ GtkPrintCupsDispatchWatch *dispatch; ++ gsize length, ++ i; ++ gboolean need_secret_auth_info = FALSE; ++ const gchar *printer_uri; ++ ++ dispatch = user_data; ++ ++ if (dispatch->backend->authentication_lock) ++ return; ++ ++ length = g_strv_length (dispatch->request->auth_info_required); ++ ++ for (i = 0; i < length; i++) ++ { ++ if (g_strcmp0 (dispatch->request->auth_info_required[i], "password") == 0) ++ { ++ need_secret_auth_info = TRUE; ++ break; ++ } ++ } ++ ++ g_idle_add (check_auth_info, user_data); ++ ++ if (dispatch->backend->secrets_service_available && need_secret_auth_info) ++ { ++ dispatch->backend->authentication_lock = TRUE; ++ printer_uri = gtk_cups_request_ipp_get_string (dispatch->request, ++ IPP_TAG_URI, ++ "printer-uri"); ++ gtk_cups_secrets_service_query_task (dispatch->backend, ++ dispatch->backend->secrets_service_cancellable, ++ lookup_auth_info_cb, ++ dispatch, ++ printer_uri, ++ dispatch->request->auth_info_required); ++ return; ++ } ++ ++ request_auth_info (user_data); ++} ++ + static gboolean + request_auth_info (gpointer user_data) + { +@@ -1258,7 +1391,8 @@ request_auth_info (gpointer user_data) + auth_info_default, + auth_info_display, + auth_info_visible, +- prompt); ++ prompt, ++ dispatch->backend->secrets_service_available); + + for (i = 0; i < length; i++) + { +@@ -1271,8 +1405,6 @@ request_auth_info (gpointer user_data) + g_free (printer_name); + g_free (prompt); + +- g_idle_add (check_auth_info, user_data); +- + return FALSE; + } + +@@ -1473,7 +1605,7 @@ cups_request_execute (GtkPrintBackendCup + { + dispatch->callback = callback; + dispatch->callback_data = user_data; +- request_auth_info (dispatch); ++ lookup_auth_info (dispatch); + } + else + { +@@ -5934,3 +6066,24 @@ cups_printer_get_capabilities (GtkPrinte + + return capabilities; + } ++ ++static void ++secrets_service_appeared_cb (GDBusConnection *connection, ++ const gchar *name, ++ const gchar *name_owner, ++ gpointer user_data) ++{ ++ GtkPrintBackendCups *backend = GTK_PRINT_BACKEND_CUPS (user_data); ++ ++ backend->secrets_service_available = TRUE; ++} ++ ++static void ++secrets_service_vanished_cb (GDBusConnection *connection, ++ const gchar *name, ++ gpointer user_data) ++{ ++ GtkPrintBackendCups *backend = GTK_PRINT_BACKEND_CUPS (user_data); ++ ++ backend->secrets_service_available = FALSE; ++}