Printers: Add authentication dialog for print jobs

This commit adds popup dialog to jobs dialog which can be accessed
if there is a print job which needs authentication.
Clicking "Authenticate" button will reveal this dialog
where user can enter credential info for the actual printer
and clicking the "Authenticate" in it will authenticate all
print jobs of this printer which needs authentication info
and will send them for printing.

https://bugzilla.gnome.org/show_bug.cgi?id=758170
This commit is contained in:
Marek Kasik 2018-02-19 01:02:32 +01:00
parent 90e306df20
commit 1be3901b67
2 changed files with 579 additions and 24 deletions

View file

@ -1,7 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.20.2 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<requires lib="gtk+" version="3.22"/>
<object class="GtkPopover" id="authentication_popover">
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">30</property>
<property name="margin_right">30</property>
<property name="margin_top">20</property>
<property name="margin_bottom">20</property>
<property name="orientation">vertical</property>
<property name="spacing">20</property>
<child>
<object class="GtkLabel" id="authentication-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label" translatable="no">Enter credentials to print from %s.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">10</property>
<property name="column_spacing">15</property>
<child>
<object class="GtkLabel" id="domain-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes" comments="Translators: This is a windows domain used with SMB protocol.">Domain</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="username-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes" comments="Translators: This is a username on a print server.">Username</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="password-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes" comments="Translators: This is a password needed for printing.">Password</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="domain-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="username-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="password-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="visibility">False</property>
<property name="invisible_char">*</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="authenticate-button">
<property name="label" translatable="yes" comments="Translators: This button authenticates all print jobs and send them for printing.">A_uthenticate</property>
<property name="use_underline">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkSizeGroup">
<widgets>
<widget name="domain-label"/>
<widget name="username-label"/>
<widget name="password-label"/>
</widgets>
</object>
<object class="GtkDialog" id="jobs-dialog">
<property name="width_request">600</property>
<property name="height_request">500</property>
@ -39,9 +180,125 @@
<style>
<class name="view"/>
</style>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="authentication-infobar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">0</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="margin-end">2</property>
<child>
<object class="GtkMenuButton" id="authenticate-jobs-button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">end</property>
<property name="popover">authentication_popover</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="label" translatable="yes" comments="Translators: This button pop up authentication dialog for print jobs which need credentials.">_Authenticate</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkArrow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">down</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<property name="margin-start">12</property>
<child>
<object class="GtkLabel" id="authenticate-jobs-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">False</property>
<property name="label" translatable="no">2 Jobs Require Authentication</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow">
<property name="visible">True</property>
@ -93,23 +350,35 @@
<child>
<object class="GtkLabel" id="no-printer-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Translators: this label describes the dialog empty state, with no jobs listed.">No Active Printer Jobs</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">no-jobs-page</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child>
<placeholder/>
</child>
</object>
</interface>

View file

@ -58,9 +58,141 @@ struct _PpJobsDialog {
gchar *printer_name;
gchar **actual_auth_info_required;
GCancellable *get_jobs_cancellable;
};
static gboolean
is_info_required (PpJobsDialog *dialog,
const gchar *info)
{
gboolean required = FALSE;
gint i;
if (dialog != NULL && dialog->actual_auth_info_required != NULL)
{
for (i = 0; dialog->actual_auth_info_required[i] != NULL; i++)
{
if (g_strcmp0 (dialog->actual_auth_info_required[i], info) == 0)
{
required = TRUE;
break;
}
}
}
return required;
}
static gboolean
is_domain_required (PpJobsDialog *dialog)
{
return is_info_required (dialog, "domain");
}
static gboolean
is_username_required (PpJobsDialog *dialog)
{
return is_info_required (dialog, "username");
}
static gboolean
is_password_required (PpJobsDialog *dialog)
{
return is_info_required (dialog, "password");
}
static gboolean
auth_popup_filled (PpJobsDialog *dialog)
{
gboolean domain_required;
gboolean username_required;
gboolean password_required;
guint16 domain_length;
guint16 username_length;
guint16 password_length;
domain_required = is_domain_required (dialog);
username_required = is_username_required (dialog);
password_required = is_password_required (dialog);
domain_length = gtk_entry_get_text_length (GTK_ENTRY (gtk_builder_get_object (dialog->builder, "domain-entry")));
username_length = gtk_entry_get_text_length (GTK_ENTRY (gtk_builder_get_object (dialog->builder, "username-entry")));
password_length = gtk_entry_get_text_length (GTK_ENTRY (gtk_builder_get_object (dialog->builder, "password-entry")));
return (!domain_required || domain_length > 0) &&
(!username_required || username_length > 0) &&
(!password_required || password_length > 0);
}
static void
auth_entries_changed (GtkEntry *entry,
PpJobsDialog *dialog)
{
GtkWidget *widget;
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "authenticate-button"));
gtk_widget_set_sensitive (widget, auth_popup_filled (dialog));
}
static void
auth_entries_activated (GtkEntry *entry,
PpJobsDialog *dialog)
{
GtkWidget *widget;
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "authenticate-button"));
if (auth_popup_filled (dialog))
gtk_button_clicked (GTK_BUTTON (widget));
}
static void
authenticate_popover_update (PpJobsDialog *dialog)
{
GtkWidget *widget;
gboolean domain_required;
gboolean username_required;
gboolean password_required;
domain_required = is_domain_required (dialog);
username_required = is_username_required (dialog);
password_required = is_password_required (dialog);
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "domain-label"));
gtk_widget_set_visible (widget, domain_required);
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "domain-entry"));
gtk_widget_set_visible (widget, domain_required);
if (domain_required)
{
gtk_entry_set_text (GTK_ENTRY (widget), "");
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (widget));
}
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "username-label"));
gtk_widget_set_visible (widget, username_required);
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "username-entry"));
gtk_widget_set_visible (widget, username_required);
if (username_required)
{
gtk_entry_set_text (GTK_ENTRY (widget), cupsUser ());
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (widget));
}
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "password-label"));
gtk_widget_set_visible (widget, password_required);
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "password-entry"));
gtk_widget_set_visible (widget, password_required);
if (password_required)
{
gtk_entry_set_text (GTK_ENTRY (widget), "");
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (widget));
}
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "authenticate-button"));
gtk_widget_set_sensitive (widget, FALSE);
}
static void
job_stop_cb (GtkButton *button,
PpJob *job)
@ -88,14 +220,19 @@ static GtkWidget *
create_listbox_row (gpointer item,
gpointer user_data)
{
PpJob *job = (PpJob *)item;
GtkWidget *box;
GtkWidget *widget;
gchar *title;
gchar *state_string = NULL;
gint job_state;
GtkWidget *widget;
GtkWidget *box;
PpJob *job = (PpJob *)item;
gchar **auth_info_required;
gchar *title;
gchar *state_string = NULL;
gint job_state;
g_object_get (job, "title", &title, "state", &job_state, NULL);
g_object_get (job,
"title", &title,
"state", &job_state,
"auth-info-required", &auth_info_required,
NULL);
switch (job_state)
{
@ -104,8 +241,16 @@ create_listbox_row (gpointer item,
state_string = g_strdup (C_("print job", "Pending"));
break;
case IPP_JOB_HELD:
/* Translators: Job's state (job is held for printing) */
state_string = g_strdup (C_("print job", "Paused"));
if (auth_info_required == NULL)
{
/* Translators: Job's state (job is held for printing) */
state_string = g_strdup (C_("print job", "Paused"));
}
else
{
/* Translators: Job's state (job needs authentication to proceed further) */
state_string = g_strdup_printf ("<span foreground=\"#ff0000\">%s</span>", C_("print job", "Authentication required"));
}
break;
case IPP_JOB_PROCESSING:
/* Translators: Job's state (job is currently printing) */
@ -137,14 +282,17 @@ create_listbox_row (gpointer item,
gtk_widget_set_halign (widget, GTK_ALIGN_START);
gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 10);
widget = gtk_label_new (state_string);
widget = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (widget), state_string);
gtk_widget_set_halign (widget, GTK_ALIGN_END);
gtk_widget_set_margin_end (widget, 64);
gtk_widget_set_margin_start (widget, 64);
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 10);
widget = gtk_button_new_from_icon_name (job_state == IPP_JOB_HELD ? "media-playback-start-symbolic" : "media-playback-pause-symbolic",
GTK_ICON_SIZE_SMALL_TOOLBAR);
g_signal_connect (widget, "clicked", G_CALLBACK (job_pause_cb), item);
gtk_widget_set_sensitive (widget, auth_info_required == NULL);
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 4);
widget = gtk_button_new_from_icon_name ("edit-delete-symbolic",
@ -162,13 +310,18 @@ update_jobs_list_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
PpJobsDialog *dialog = user_data;
PpPrinter *printer = PP_PRINTER (source_object);
GtkWidget *clear_all_button;
GtkStack *stack;
GError *error = NULL;
GList *jobs, *l;
gint num_of_jobs;
PpJobsDialog *dialog = user_data;
PpPrinter *printer = PP_PRINTER (source_object);
GtkWidget *clear_all_button;
GtkWidget *infobar;
GtkWidget *label;
GtkStack *stack;
GError *error = NULL;
GList *jobs, *l;
PpJob *job;
gchar **auth_info_required = NULL;
gchar *text;
gint num_of_jobs, num_of_auth_jobs = 0;
g_list_store_remove_all (dialog->store);
@ -201,9 +354,45 @@ update_jobs_list_cb (GObject *source_object,
for (l = jobs; l != NULL; l = l->next)
{
g_list_store_append (dialog->store, l->data);
job = PP_JOB (l->data);
g_list_store_append (dialog->store, job);
g_object_get (G_OBJECT (job),
"auth-info-required", &auth_info_required,
NULL);
if (auth_info_required != NULL)
{
num_of_auth_jobs++;
if (dialog->actual_auth_info_required == NULL)
dialog->actual_auth_info_required = auth_info_required;
else
g_strfreev (auth_info_required);
auth_info_required = NULL;
}
}
infobar = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "authentication-infobar"));
if (num_of_auth_jobs > 0)
{
label = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "authenticate-jobs-label"));
/* Translators: This label shows how many jobs of this printer needs to be authenticated to be printed. */
text = g_strdup_printf (ngettext ("%u Job Requires Authentication", "%u Jobs Require Authentication", num_of_auth_jobs), num_of_auth_jobs);
gtk_label_set_text (GTK_LABEL (label), text);
g_free (text);
gtk_widget_show (infobar);
}
else
{
gtk_widget_hide (infobar);
}
authenticate_popover_update (dialog);
g_list_free (jobs);
g_clear_object (&dialog->get_jobs_cancellable);
}
@ -265,6 +454,78 @@ on_clear_all_button_clicked (GtkButton *button,
}
}
static void
pp_job_authenticate_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PpJobsDialog *dialog = user_data;
gboolean result;
GError *error = NULL;
PpJob *job = PP_JOB (source_object);
result = pp_job_authenticate_finish (job, res, &error);
if (result)
{
pp_jobs_dialog_update (dialog);
}
else if (error != NULL)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_warning ("Could not authenticate job: %s", error->message);
}
g_error_free (error);
}
}
static void
authenticate_button_clicked (GtkWidget *button,
gpointer user_data)
{
PpJobsDialog *dialog = user_data;
GtkWidget *widget;
PpJob *job;
gchar **auth_info_required = NULL;
gchar **auth_info;
guint num_items;
gint i;
auth_info = g_new0 (gchar *, g_strv_length (dialog->actual_auth_info_required) + 1);
for (i = 0; dialog->actual_auth_info_required[i] != NULL; i++)
{
if (g_strcmp0 (dialog->actual_auth_info_required[i], "domain") == 0)
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "domain-entry"));
else if (g_strcmp0 (dialog->actual_auth_info_required[i], "username") == 0)
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "username-entry"));
else if (g_strcmp0 (dialog->actual_auth_info_required[i], "password") == 0)
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "password-entry"));
else
widget = NULL;
if (widget != NULL)
auth_info[i] = g_strdup (gtk_entry_get_text (GTK_ENTRY (widget)));
}
num_items = g_list_model_get_n_items (G_LIST_MODEL (dialog->store));
for (i = 0; i < num_items; i++)
{
job = PP_JOB (g_list_model_get_item (G_LIST_MODEL (dialog->store), i));
g_object_get (job, "auth-info-required", &auth_info_required, NULL);
if (auth_info_required != NULL)
{
pp_job_authenticate_async (job, auth_info, NULL, pp_job_authenticate_cb, dialog);
g_strfreev (auth_info_required);
auth_info_required = NULL;
}
}
g_strfreev (auth_info);
}
PpJobsDialog *
pp_jobs_dialog_new (GtkWindow *parent,
UserResponseCallback user_callback,
@ -272,9 +533,10 @@ pp_jobs_dialog_new (GtkWindow *parent,
gchar *printer_name)
{
PpJobsDialog *dialog;
GtkButton *clear_all_button;
GtkWidget *widget;
GError *error = NULL;
gchar *objects[] = { "jobs-dialog", NULL };
gchar *objects[] = { "jobs-dialog", "authentication_popover", NULL };
gchar *text;
guint builder_result;
gchar *title;
@ -298,19 +560,41 @@ pp_jobs_dialog_new (GtkWindow *parent,
dialog->user_callback = user_callback;
dialog->user_data = user_data;
dialog->printer_name = g_strdup (printer_name);
dialog->actual_auth_info_required = NULL;
/* connect signals */
g_signal_connect (dialog->dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
g_signal_connect (dialog->dialog, "response", G_CALLBACK (jobs_dialog_response_cb), dialog);
clear_all_button = GTK_BUTTON (gtk_builder_get_object (dialog->builder, "jobs-clear-all-button"));
g_signal_connect (clear_all_button, "clicked", G_CALLBACK (on_clear_all_button_clicked), dialog);
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "jobs-clear-all-button"));
g_signal_connect (widget, "clicked", G_CALLBACK (on_clear_all_button_clicked), dialog);
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "authenticate-button"));
g_signal_connect (widget, "clicked", G_CALLBACK (authenticate_button_clicked), dialog);
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "domain-entry"));
g_signal_connect (widget, "changed", G_CALLBACK (auth_entries_changed), dialog);
g_signal_connect (widget, "activate", G_CALLBACK (auth_entries_activated), dialog);
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "username-entry"));
g_signal_connect (widget, "changed", G_CALLBACK (auth_entries_changed), dialog);
g_signal_connect (widget, "activate", G_CALLBACK (auth_entries_activated), dialog);
widget = GTK_WIDGET (gtk_builder_get_object (dialog->builder, "password-entry"));
g_signal_connect (widget, "changed", G_CALLBACK (auth_entries_changed), dialog);
g_signal_connect (widget, "activate", G_CALLBACK (auth_entries_activated), dialog);
/* Translators: This is the printer name for which we are showing the active jobs */
title = g_strdup_printf (C_("Printer jobs dialog title", "%s — Active Jobs"), printer_name);
gtk_window_set_title (GTK_WINDOW (dialog->dialog), title);
g_free (title);
/* Translators: The printer needs authentication info to print. */
text = g_strdup_printf (_("Enter credentials to print from %s."), printer_name);
widget = GTK_WIDGET (gtk_builder_get_object (GTK_BUILDER (dialog->builder), "authentication-label"));
gtk_label_set_text (GTK_LABEL (widget), text);
g_free (text);
dialog->listbox = GTK_LIST_BOX (gtk_builder_get_object (dialog->builder, "jobs-listbox"));
gtk_list_box_set_header_func (dialog->listbox,
cc_list_box_update_header_func, NULL, NULL);
@ -357,6 +641,8 @@ pp_jobs_dialog_free (PpJobsDialog *dialog)
gtk_widget_destroy (GTK_WIDGET (dialog->dialog));
dialog->dialog = NULL;
g_strfreev (dialog->actual_auth_info_required);
g_clear_object (&dialog->builder);
g_free (dialog->printer_name);
g_free (dialog);