summaryrefslogtreecommitdiffstats
path: root/panels/printers
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--panels/printers/authentication-dialog.ui193
-rw-r--r--panels/printers/cc-printers-panel.c1361
-rw-r--r--panels/printers/cc-printers-panel.h28
-rw-r--r--panels/printers/gnome-printers-panel.desktop.in.in17
-rw-r--r--panels/printers/meson.build75
-rw-r--r--panels/printers/new-printer-dialog.ui423
-rw-r--r--panels/printers/pp-cups.c320
-rw-r--r--panels/printers/pp-cups.h76
-rw-r--r--panels/printers/pp-details-dialog.c394
-rw-r--r--panels/printers/pp-details-dialog.h41
-rw-r--r--panels/printers/pp-details-dialog.ui227
-rw-r--r--panels/printers/pp-host.c747
-rw-r--r--panels/printers/pp-host.h80
-rw-r--r--panels/printers/pp-ipp-option-widget.c567
-rw-r--r--panels/printers/pp-ipp-option-widget.h39
-rw-r--r--panels/printers/pp-job.c470
-rw-r--r--panels/printers/pp-job.h59
-rw-r--r--panels/printers/pp-jobs-dialog.c603
-rw-r--r--panels/printers/pp-jobs-dialog.h35
-rw-r--r--panels/printers/pp-jobs-dialog.ui387
-rw-r--r--panels/printers/pp-maintenance-command.c394
-rw-r--r--panels/printers/pp-maintenance-command.h52
-rw-r--r--panels/printers/pp-new-printer-dialog.c2106
-rw-r--r--panels/printers/pp-new-printer-dialog.h36
-rw-r--r--panels/printers/pp-new-printer.c1310
-rw-r--r--panels/printers/pp-new-printer.h42
-rw-r--r--panels/printers/pp-options-dialog.c959
-rw-r--r--panels/printers/pp-options-dialog.h34
-rw-r--r--panels/printers/pp-options-dialog.ui107
-rw-r--r--panels/printers/pp-ppd-option-widget.c591
-rw-r--r--panels/printers/pp-ppd-option-widget.h35
-rw-r--r--panels/printers/pp-ppd-selection-dialog.c416
-rw-r--r--panels/printers/pp-ppd-selection-dialog.h41
-rw-r--r--panels/printers/pp-print-device.c450
-rw-r--r--panels/printers/pp-print-device.h48
-rw-r--r--panels/printers/pp-printer-entry.c1082
-rw-r--r--panels/printers/pp-printer-entry.h41
-rw-r--r--panels/printers/pp-printer.c631
-rw-r--r--panels/printers/pp-printer.h78
-rw-r--r--panels/printers/pp-samba.c414
-rw-r--r--panels/printers/pp-samba.h47
-rw-r--r--panels/printers/pp-utils.c3643
-rw-r--r--panels/printers/pp-utils.h260
-rw-r--r--panels/printers/ppd-selection-dialog.ui163
-rw-r--r--panels/printers/printer-entry.ui338
-rw-r--r--panels/printers/printers.gresource.xml13
-rw-r--r--panels/printers/printers.ui288
47 files changed, 19761 insertions, 0 deletions
diff --git a/panels/printers/authentication-dialog.ui b/panels/printers/authentication-dialog.ui
new file mode 100644
index 0000000..965a2b4
--- /dev/null
+++ b/panels/printers/authentication-dialog.ui
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkDialog" id="authentication-dialog">
+ <property name="width_request">430</property>
+ <property name="height_request">270</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes"> </property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="main-vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action-area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="authentication-button">
+ <property name="label" translatable="yes">Authenticate</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">5</property>
+ <property name="margin_end">5</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">15</property>
+ <child>
+ <object class="GtkLabel" id="username-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Username</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Password</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</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>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</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="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="authentication-title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Authentication Required</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="pixel_size">48</property>
+ <property name="icon_name">dialog-password-symbolic</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="authentication-text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_bottom">15</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">36</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button1</action-widget>
+ <action-widget response="-5">authentication-button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/panels/printers/cc-printers-panel.c b/panels/printers/cc-printers-panel.c
new file mode 100644
index 0000000..7364575
--- /dev/null
+++ b/panels/printers/cc-printers-panel.c
@@ -0,0 +1,1361 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include "shell/cc-object-storage.h"
+
+#include "cc-printers-panel.h"
+#include "cc-printers-resources.h"
+#include "pp-printer.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <polkit/polkit.h>
+#include <gdesktop-enums.h>
+
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include <math.h>
+
+#include "pp-new-printer-dialog.h"
+#include "pp-ppd-selection-dialog.h"
+#include "pp-utils.h"
+#include "pp-cups.h"
+#include "pp-printer-entry.h"
+#include "pp-job.h"
+
+#include "cc-permission-infobar.h"
+#include "cc-util.h"
+
+#define RENEW_INTERVAL 500
+#define SUBSCRIPTION_DURATION 600
+
+#define CUPS_DBUS_NAME "org.cups.cupsd.Notifier"
+#define CUPS_DBUS_PATH "/org/cups/cupsd/Notifier"
+#define CUPS_DBUS_INTERFACE "org.cups.cupsd.Notifier"
+
+#define CUPS_STATUS_CHECK_INTERVAL 5
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetState(ipp) ipp->state
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetString(attr, element, language) attr->values[element].string.text
+#endif
+
+struct _CcPrintersPanel
+{
+ CcPanel parent_instance;
+
+ GtkBuilder *builder;
+
+ cups_dest_t *dests;
+ int num_dests;
+
+ GPermission *permission;
+ gboolean is_authorized;
+
+ GSettings *lockdown_settings;
+ CcPermissionInfobar *permission_infobar;
+
+ PpNewPrinterDialog *pp_new_printer_dialog;
+ PpPPDSelectionDialog *pp_ppd_selection_dialog;
+
+ GDBusProxy *cups_proxy;
+ GDBusConnection *cups_bus_connection;
+ gint subscription_id;
+ guint subscription_renewal_id;
+ guint cups_status_check_id;
+ guint dbus_subscription_id;
+ guint remove_printer_timeout_id;
+
+ GtkRevealer *notification;
+ PPDList *all_ppds_list;
+
+ gchar *new_printer_name;
+
+ gchar *renamed_printer_name;
+ gchar *old_printer_name;
+ gchar *deleted_printer_name;
+ GList *deleted_printers;
+ GObject *reference;
+
+ GHashTable *printer_entries;
+ gboolean entries_filled;
+ GVariant *action;
+
+ GtkSizeGroup *size_group;
+};
+
+CC_PANEL_REGISTER (CcPrintersPanel, cc_printers_panel)
+
+typedef struct
+{
+ gchar *printer_name;
+ GCancellable *cancellable;
+} SetPPDItem;
+
+enum {
+ PROP_0,
+ PROP_PARAMETERS
+};
+
+static void actualize_printers_list (CcPrintersPanel *self);
+static void update_sensitivity (gpointer user_data);
+static void detach_from_cups_notifier (gpointer data);
+static void free_dests (CcPrintersPanel *self);
+
+static void
+execute_action (CcPrintersPanel *self,
+ GVariant *action)
+{
+ PpPrinterEntry *printer_entry;
+ const gchar *action_name;
+ const gchar *printer_name;
+ gint count;
+
+ count = g_variant_n_children (action);
+ if (count == 2)
+ {
+ g_autoptr(GVariant) action_variant = NULL;
+
+ g_variant_get_child (action, 0, "v", &action_variant);
+ action_name = g_variant_get_string (action_variant, NULL);
+
+ /* authenticate-jobs printer-name */
+ if (g_strcmp0 (action_name, "authenticate-jobs") == 0)
+ {
+ g_autoptr(GVariant) variant = NULL;
+
+ g_variant_get_child (action, 1, "v", &variant);
+ printer_name = g_variant_get_string (variant, NULL);
+
+ printer_entry = PP_PRINTER_ENTRY (g_hash_table_lookup (self->printer_entries, printer_name));
+ if (printer_entry != NULL)
+ pp_printer_entry_authenticate_jobs (printer_entry);
+ else
+ g_warning ("Could not find printer \"%s\"!", printer_name);
+ }
+ /* show-jobs printer-name */
+ else if (g_strcmp0 (action_name, "show-jobs") == 0)
+ {
+ g_autoptr(GVariant) variant = NULL;
+
+ g_variant_get_child (action, 1, "v", &variant);
+ printer_name = g_variant_get_string (variant, NULL);
+
+ printer_entry = PP_PRINTER_ENTRY (g_hash_table_lookup (self->printer_entries, printer_name));
+ if (printer_entry != NULL)
+ pp_printer_entry_show_jobs_dialog (printer_entry);
+ else
+ g_warning ("Could not find printer \"%s\"!", printer_name);
+ }
+ }
+}
+
+static void
+cc_printers_panel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+cc_printers_panel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcPrintersPanel *self = CC_PRINTERS_PANEL (object);
+ GVariant *parameters;
+
+ switch (property_id)
+ {
+ case PROP_PARAMETERS:
+ parameters = g_value_get_variant (value);
+ if (parameters != NULL && g_variant_n_children (parameters) > 0)
+ {
+ if (self->entries_filled)
+ {
+ execute_action (CC_PRINTERS_PANEL (object), parameters);
+ }
+ else
+ {
+ if (self->action != NULL)
+ g_variant_unref (self->action);
+ self->action = g_variant_ref (parameters);
+ }
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+cc_printers_panel_constructed (GObject *object)
+{
+ CcPrintersPanel *self = CC_PRINTERS_PANEL (object);
+ GtkWidget *widget;
+ CcShell *shell;
+
+ G_OBJECT_CLASS (cc_printers_panel_parent_class)->constructed (object);
+
+ shell = cc_panel_get_shell (CC_PANEL (self));
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "top-right-buttons");
+ cc_shell_embed_widget_in_header (shell, widget, GTK_POS_RIGHT);
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "search-bar");
+ g_signal_connect_object (shell,
+ "key-press-event",
+ G_CALLBACK (gtk_search_bar_handle_event),
+ widget,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+printer_removed_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PpPrinter *printer = PP_PRINTER (source_object);
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *printer_name = NULL;
+
+ g_object_get (printer, "printer-name", &printer_name, NULL);
+ pp_printer_delete_finish (printer, result, &error);
+ g_object_unref (source_object);
+
+ if (user_data != NULL)
+ {
+ GObject *reference = G_OBJECT (user_data);
+
+ if (g_object_get_data (reference, "self") != NULL)
+ {
+ CcPrintersPanel *self = CC_PRINTERS_PANEL (g_object_get_data (reference, "self"));
+ GList *iter;
+
+ for (iter = self->deleted_printers; iter != NULL; iter = iter->next)
+ {
+ if (g_strcmp0 (iter->data, printer_name) == 0)
+ {
+ g_free (iter->data);
+ self->deleted_printers = g_list_delete_link (self->deleted_printers, iter);
+ break;
+ }
+ }
+ }
+
+ g_object_unref (reference);
+ }
+
+ if (error != NULL)
+ g_warning ("Printer could not be deleted: %s", error->message);
+}
+
+static void
+cc_printers_panel_dispose (GObject *object)
+{
+ CcPrintersPanel *self = CC_PRINTERS_PANEL (object);
+
+ detach_from_cups_notifier (CC_PRINTERS_PANEL (object));
+
+ if (self->deleted_printer_name != NULL)
+ {
+ PpPrinter *printer = pp_printer_new (self->deleted_printer_name);
+ pp_printer_delete_async (printer,
+ NULL,
+ printer_removed_cb,
+ NULL);
+ }
+
+ g_clear_object (&self->pp_new_printer_dialog);
+ g_clear_pointer (&self->new_printer_name, g_free);
+ g_clear_pointer (&self->renamed_printer_name, g_free);
+ g_clear_pointer (&self->old_printer_name, g_free);
+ g_clear_object (&self->builder);
+ g_clear_object (&self->lockdown_settings);
+ g_clear_object (&self->permission);
+ g_clear_handle_id (&self->cups_status_check_id, g_source_remove);
+ g_clear_handle_id (&self->remove_printer_timeout_id, g_source_remove);
+ g_clear_pointer (&self->deleted_printer_name, g_free);
+ g_clear_pointer (&self->action, g_variant_unref);
+ g_clear_pointer (&self->printer_entries, g_hash_table_destroy);
+ g_clear_pointer (&self->all_ppds_list, ppd_list_free);
+ free_dests (self);
+ g_list_free_full (self->deleted_printers, g_free);
+ self->deleted_printers = NULL;
+ if (self->reference != NULL)
+ g_object_set_data (self->reference, "self", NULL);
+ g_clear_object (&self->reference);
+
+ G_OBJECT_CLASS (cc_printers_panel_parent_class)->dispose (object);
+}
+
+static const char *
+cc_printers_panel_get_help_uri (CcPanel *panel)
+{
+ return "help:gnome-help/printing";
+}
+
+static void
+cc_printers_panel_class_init (CcPrintersPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+
+ object_class->get_property = cc_printers_panel_get_property;
+ object_class->set_property = cc_printers_panel_set_property;
+ object_class->constructed = cc_printers_panel_constructed;
+ object_class->dispose = cc_printers_panel_dispose;
+
+ panel_class->get_help_uri = cc_printers_panel_get_help_uri;
+
+ g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+}
+
+static void
+on_get_job_attributes_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ const gchar *job_originating_user_name;
+ const gchar *job_printer_uri;
+ g_autoptr(GVariant) attributes = NULL;
+ g_autoptr(GError) error = NULL;
+
+ attributes = pp_job_get_attributes_finish (PP_JOB (source_object), res, &error);
+ g_object_unref (source_object);
+
+ if (attributes != NULL)
+ {
+ g_autoptr(GVariant) username = NULL;
+
+ if ((username = g_variant_lookup_value (attributes, "job-originating-user-name", G_VARIANT_TYPE ("as"))) != NULL)
+ {
+ g_autoptr(GVariant) printer_uri = NULL;
+
+ if ((printer_uri = g_variant_lookup_value (attributes, "job-printer-uri", G_VARIANT_TYPE ("as"))) != NULL)
+ {
+ job_originating_user_name = g_variant_get_string (g_variant_get_child_value (username, 0), NULL);
+ job_printer_uri = g_variant_get_string (g_variant_get_child_value (printer_uri, 0), NULL);
+
+ if (job_originating_user_name != NULL && job_printer_uri != NULL &&
+ g_strcmp0 (job_originating_user_name, cupsUser ()) == 0 &&
+ g_strrstr (job_printer_uri, "/") != 0 &&
+ self->dests != NULL)
+ {
+ PpPrinterEntry *printer_entry;
+ gchar *printer_name;
+
+ printer_name = g_strrstr (job_printer_uri, "/") + 1;
+ printer_entry = PP_PRINTER_ENTRY (g_hash_table_lookup (self->printer_entries, printer_name));
+
+ pp_printer_entry_update_jobs_count (printer_entry);
+ }
+ }
+ }
+ }
+}
+
+static void
+on_cups_notification (GDBusConnection *connection,
+ const char *sender_name,
+ const char *object_path,
+ const char *interface_name,
+ const char *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ gboolean printer_is_accepting_jobs;
+ gchar *printer_name = NULL;
+ gchar *text = NULL;
+ gchar *printer_uri = NULL;
+ gchar *printer_state_reasons = NULL;
+ PpJob *job;
+ gchar *job_state_reasons = NULL;
+ gchar *job_name = NULL;
+ guint job_id;
+ gint printer_state;
+ gint job_state;
+ gint job_impressions_completed;
+ static gchar *requested_attrs[] = {
+ "job-printer-uri",
+ "job-originating-user-name",
+ NULL };
+
+ if (g_strcmp0 (signal_name, "PrinterAdded") != 0 &&
+ g_strcmp0 (signal_name, "PrinterDeleted") != 0 &&
+ g_strcmp0 (signal_name, "PrinterStateChanged") != 0 &&
+ g_strcmp0 (signal_name, "PrinterStopped") != 0 &&
+ g_strcmp0 (signal_name, "JobCreated") != 0 &&
+ g_strcmp0 (signal_name, "JobCompleted") != 0)
+ return;
+
+ if (g_variant_n_children (parameters) == 1)
+ g_variant_get (parameters, "(&s)", &text);
+ else if (g_variant_n_children (parameters) == 6)
+ {
+ g_variant_get (parameters, "(&s&s&su&sb)",
+ &text,
+ &printer_uri,
+ &printer_name,
+ &printer_state,
+ &printer_state_reasons,
+ &printer_is_accepting_jobs);
+ }
+ else if (g_variant_n_children (parameters) == 11)
+ {
+ g_variant_get (parameters, "(&s&s&su&sbuu&s&su)",
+ &text,
+ &printer_uri,
+ &printer_name,
+ &printer_state,
+ &printer_state_reasons,
+ &printer_is_accepting_jobs,
+ &job_id,
+ &job_state,
+ &job_state_reasons,
+ &job_name,
+ &job_impressions_completed);
+ }
+
+ if (g_strcmp0 (signal_name, "PrinterAdded") == 0 ||
+ g_strcmp0 (signal_name, "PrinterDeleted") == 0 ||
+ g_strcmp0 (signal_name, "PrinterStateChanged") == 0 ||
+ g_strcmp0 (signal_name, "PrinterStopped") == 0)
+ actualize_printers_list (self);
+ else if (g_strcmp0 (signal_name, "JobCreated") == 0 ||
+ g_strcmp0 (signal_name, "JobCompleted") == 0)
+ {
+ job = g_object_new (PP_TYPE_JOB, "id", job_id, NULL);
+ pp_job_get_attributes_async (job,
+ requested_attrs,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ on_get_job_attributes_cb,
+ self);
+ }
+}
+
+static gchar *subscription_events[] = {
+ "printer-added",
+ "printer-deleted",
+ "printer-stopped",
+ "printer-state-changed",
+ "job-created",
+ "job-completed",
+ NULL};
+
+static void
+renew_subscription_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ PpCups *cups = PP_CUPS (source_object);
+ gint subscription_id;
+
+ subscription_id = pp_cups_renew_subscription_finish (cups, result);
+ g_object_unref (source_object);
+
+ if (subscription_id > 0)
+ self->subscription_id = subscription_id;
+}
+
+static gboolean
+renew_subscription (gpointer data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) data;
+ PpCups *cups;
+
+ cups = pp_cups_new ();
+ pp_cups_renew_subscription_async (cups,
+ self->subscription_id,
+ subscription_events,
+ SUBSCRIPTION_DURATION,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ renew_subscription_cb,
+ data);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+attach_to_cups_notifier_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ PpCups *cups = PP_CUPS (source_object);
+ g_autoptr(GError) error = NULL;
+ gint subscription_id;
+
+ subscription_id = pp_cups_renew_subscription_finish (cups, result);
+ g_object_unref (source_object);
+
+ if (subscription_id > 0)
+ {
+ self->subscription_id = subscription_id;
+
+ self->subscription_renewal_id =
+ g_timeout_add_seconds (RENEW_INTERVAL, renew_subscription, self);
+
+ self->cups_proxy = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ CUPS_DBUS_NAME,
+ CUPS_DBUS_PATH,
+ CUPS_DBUS_INTERFACE,
+ NULL,
+ &error);
+
+ if (!self->cups_proxy)
+ {
+ g_warning ("%s", error->message);
+ return;
+ }
+
+ self->cups_bus_connection = g_dbus_proxy_get_connection (self->cups_proxy);
+
+ self->dbus_subscription_id =
+ g_dbus_connection_signal_subscribe (self->cups_bus_connection,
+ NULL,
+ CUPS_DBUS_INTERFACE,
+ NULL,
+ CUPS_DBUS_PATH,
+ NULL,
+ 0,
+ on_cups_notification,
+ self,
+ NULL);
+ }
+}
+
+static void
+attach_to_cups_notifier (gpointer data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) data;
+ PpCups *cups;
+
+ cups = pp_cups_new ();
+ pp_cups_renew_subscription_async (cups,
+ self->subscription_id,
+ subscription_events,
+ SUBSCRIPTION_DURATION,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ attach_to_cups_notifier_cb,
+ data);
+}
+
+static void
+subscription_cancel_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PpCups *cups = PP_CUPS (source_object);
+
+ pp_cups_cancel_subscription_finish (cups, result);
+ g_object_unref (source_object);
+}
+
+static void
+detach_from_cups_notifier (gpointer data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) data;
+ PpCups *cups;
+
+ if (self->dbus_subscription_id != 0) {
+ g_dbus_connection_signal_unsubscribe (self->cups_bus_connection,
+ self->dbus_subscription_id);
+ self->dbus_subscription_id = 0;
+ }
+
+ cups = pp_cups_new ();
+ pp_cups_cancel_subscription_async (cups,
+ self->subscription_id,
+ subscription_cancel_cb,
+ NULL);
+
+ self->subscription_id = 0;
+
+ if (self->subscription_renewal_id != 0) {
+ g_source_remove (self->subscription_renewal_id);
+ self->subscription_renewal_id = 0;
+ }
+
+ if (self->cups_proxy != NULL) {
+ g_object_unref (self->cups_proxy);
+ self->cups_proxy = NULL;
+ }
+}
+
+static void
+free_dests (CcPrintersPanel *self)
+{
+ if (self->num_dests > 0)
+ {
+ cupsFreeDests (self->num_dests, self->dests);
+ }
+ self->dests = NULL;
+ self->num_dests = 0;
+}
+
+static void
+on_printer_deletion_undone (CcPrintersPanel *self)
+{
+ GtkWidget *widget;
+
+ gtk_revealer_set_reveal_child (self->notification, FALSE);
+
+ g_clear_pointer (&self->deleted_printer_name, g_free);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "content");
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (widget));
+
+ g_clear_handle_id (&self->remove_printer_timeout_id, g_source_remove);
+}
+
+static void
+on_notification_dismissed (CcPrintersPanel *self)
+{
+ g_clear_handle_id (&self->remove_printer_timeout_id, g_source_remove);
+
+ if (self->deleted_printer_name != NULL)
+ {
+ PpPrinter *printer;
+
+ printer = pp_printer_new (self->deleted_printer_name);
+ /* The reference tells to the callback whether
+ printers panel was already destroyed so
+ it knows whether it can access the list
+ of deleted printers in it (see below).
+ */
+ pp_printer_delete_async (printer,
+ NULL,
+ printer_removed_cb,
+ g_object_ref (self->reference));
+
+ /* List of printers which were recently deleted but are still available
+ in CUPS due to async nature of the method (e.g. quick deletion
+ of several printers).
+ */
+ self->deleted_printers = g_list_prepend (self->deleted_printers, self->deleted_printer_name);
+ self->deleted_printer_name = NULL;
+ }
+
+ gtk_revealer_set_reveal_child (self->notification, FALSE);
+}
+
+static gboolean
+on_remove_printer_timeout (CcPrintersPanel *self)
+{
+ self->remove_printer_timeout_id = 0;
+
+ on_notification_dismissed (self);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+on_printer_deleted (CcPrintersPanel *self,
+ PpPrinterEntry *printer_entry)
+{
+ GtkLabel *label;
+ g_autofree gchar *notification_message = NULL;
+ g_autofree gchar *printer_name = NULL;
+ GtkWidget *widget;
+
+ on_notification_dismissed (self);
+
+ g_object_get (printer_entry,
+ "printer-name", &printer_name,
+ NULL);
+
+ /* Translators: %s is the printer name */
+ notification_message = g_strdup_printf (_("Printer “%s” has been deleted"),
+ printer_name);
+ label = (GtkLabel*)
+ gtk_builder_get_object (self->builder, "notification-label");
+ gtk_label_set_label (label, notification_message);
+
+ self->deleted_printer_name = g_strdup (printer_name);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "content");
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (widget));
+
+ gtk_revealer_set_reveal_child (self->notification, TRUE);
+
+ self->remove_printer_timeout_id = g_timeout_add_seconds (10, G_SOURCE_FUNC (on_remove_printer_timeout), self);
+}
+
+static void
+on_printer_renamed (CcPrintersPanel *self,
+ gchar *new_name,
+ PpPrinterEntry *printer_entry)
+{
+ g_object_get (printer_entry,
+ "printer-name",
+ &self->old_printer_name,
+ NULL);
+ self->renamed_printer_name = g_strdup (new_name);
+}
+
+static void
+on_printer_changed (CcPrintersPanel *self)
+{
+ actualize_printers_list (self);
+}
+
+static void
+add_printer_entry (CcPrintersPanel *self,
+ cups_dest_t printer)
+{
+ PpPrinterEntry *printer_entry;
+ GtkWidget *content;
+ GSList *widgets, *l;
+
+ content = (GtkWidget*) gtk_builder_get_object (self->builder, "content");
+
+ printer_entry = pp_printer_entry_new (printer, self->is_authorized);
+
+ widgets = pp_printer_entry_get_size_group_widgets (printer_entry);
+ for (l = widgets; l != NULL; l = l->next)
+ gtk_size_group_add_widget (self->size_group, GTK_WIDGET (l->data));
+ g_slist_free (widgets);
+
+ g_signal_connect_object (printer_entry,
+ "printer-changed",
+ G_CALLBACK (on_printer_changed),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (printer_entry,
+ "printer-delete",
+ G_CALLBACK (on_printer_deleted),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (printer_entry,
+ "printer-renamed",
+ G_CALLBACK (on_printer_renamed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ gtk_list_box_insert (GTK_LIST_BOX (content), GTK_WIDGET (printer_entry), -1);
+ gtk_widget_show_all (content);
+
+ g_hash_table_insert (self->printer_entries, g_strdup (printer.name), printer_entry);
+}
+
+static void
+set_current_page (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel *) user_data;
+ GtkWidget *widget;
+ PpCups *cups = PP_CUPS (source_object);
+ gboolean success;
+
+ success = pp_cups_connection_test_finish (cups, result, NULL);
+ g_object_unref (source_object);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "main-vbox");
+ if (success)
+ gtk_stack_set_visible_child_name (GTK_STACK (widget), "empty-state");
+ else
+ gtk_stack_set_visible_child_name (GTK_STACK (widget), "no-cups-page");
+
+ update_sensitivity (user_data);
+}
+
+static void
+destroy_nonexisting_entries (PpPrinterEntry *entry,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel *) user_data;
+ g_autofree gchar *printer_name = NULL;
+ gboolean exists = FALSE;
+ gint i;
+
+ g_object_get (G_OBJECT (entry), "printer-name", &printer_name, NULL);
+
+ for (i = 0; i < self->num_dests; i++)
+ {
+ if (g_strcmp0 (self->dests[i].name, printer_name) == 0)
+ {
+ exists = TRUE;
+ break;
+ }
+ }
+
+ if (!exists)
+ {
+ gtk_widget_destroy (GTK_WIDGET (entry));
+ g_hash_table_remove (self->printer_entries, printer_name);
+ }
+}
+
+static void
+actualize_printers_list_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ GtkWidget *widget;
+ PpCups *cups = PP_CUPS (source_object);
+ PpCupsDests *cups_dests;
+ gboolean new_printer_available = FALSE;
+ g_autoptr(GError) error = NULL;
+ gpointer item;
+ int i;
+
+ cups_dests = pp_cups_get_dests_finish (cups, result, &error);
+
+ if (cups_dests == NULL && error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Could not get dests: %s", error->message);
+ }
+
+ g_object_unref (cups);
+ return;
+ }
+
+ free_dests (self);
+ self->dests = cups_dests->dests;
+ self->num_dests = cups_dests->num_of_dests;
+ g_free (cups_dests);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "main-vbox");
+ if (self->num_dests == 0 && !self->new_printer_name)
+ pp_cups_connection_test_async (g_object_ref (cups), NULL, set_current_page, self);
+ else
+ gtk_stack_set_visible_child_name (GTK_STACK (widget), "printers-list");
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "content");
+ gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) destroy_nonexisting_entries, self);
+
+ for (i = 0; i < self->num_dests; i++)
+ {
+ new_printer_available = g_strcmp0 (self->dests[i].name, self->renamed_printer_name) == 0;
+ if (new_printer_available)
+ break;
+ }
+
+ for (i = 0; i < self->num_dests; i++)
+ {
+ if (new_printer_available && g_strcmp0 (self->dests[i].name, self->old_printer_name) == 0)
+ continue;
+
+ item = g_hash_table_lookup (self->printer_entries, self->dests[i].name);
+ if (item != NULL)
+ pp_printer_entry_update (PP_PRINTER_ENTRY (item), self->dests[i], self->is_authorized);
+ else
+ add_printer_entry (self, self->dests[i]);
+ }
+
+ if (!self->entries_filled)
+ {
+ if (self->action != NULL)
+ {
+ execute_action (self, self->action);
+ g_variant_unref (self->action);
+ self->action = NULL;
+ }
+
+ self->entries_filled = TRUE;
+ }
+
+ update_sensitivity (user_data);
+
+ g_object_unref (cups);
+
+ if (self->new_printer_name != NULL)
+ {
+ GtkScrolledWindow *scrolled_window;
+ GtkAllocation allocation;
+ GtkAdjustment *adjustment;
+ GtkWidget *printer_entry;
+
+ /* Scroll the view to show the newly added printer-entry. */
+ scrolled_window = GTK_SCROLLED_WINDOW (gtk_builder_get_object (self->builder,
+ "scrolled-window"));
+ adjustment = gtk_scrolled_window_get_vadjustment (scrolled_window);
+
+ printer_entry = GTK_WIDGET (g_hash_table_lookup (self->printer_entries,
+ self->new_printer_name));
+ if (printer_entry != NULL)
+ {
+ gtk_widget_get_allocation (printer_entry, &allocation);
+ g_clear_pointer (&self->new_printer_name, g_free);
+
+ gtk_adjustment_set_value (adjustment,
+ allocation.y - gtk_widget_get_margin_top (printer_entry));
+ }
+ }
+}
+
+static void
+actualize_printers_list (CcPrintersPanel *self)
+{
+ PpCups *cups;
+
+ cups = pp_cups_new ();
+ pp_cups_get_dests_async (cups,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ actualize_printers_list_cb,
+ self);
+}
+
+static void
+new_printer_dialog_pre_response_cb (CcPrintersPanel *self,
+ const gchar *device_name,
+ const gchar *device_location,
+ const gchar *device_make_and_model,
+ gboolean is_network_device)
+{
+ self->new_printer_name = g_strdup (device_name);
+
+ actualize_printers_list (self);
+}
+
+static void
+new_printer_dialog_response_cb (CcPrintersPanel *self,
+ gint response_id)
+{
+ if (self->pp_new_printer_dialog)
+ g_clear_object (&self->pp_new_printer_dialog);
+
+ if (response_id == GTK_RESPONSE_REJECT)
+ {
+ GtkWidget *message_dialog;
+
+ message_dialog = gtk_message_dialog_new (NULL,
+ 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ /* Translators: Addition of the new printer failed. */
+ _("Failed to add new printer."));
+ g_signal_connect (message_dialog,
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+ gtk_widget_show (message_dialog);
+ }
+
+ actualize_printers_list (self);
+}
+
+static void
+printer_add_cb (CcPrintersPanel *self)
+{
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ self->pp_new_printer_dialog = PP_NEW_PRINTER_DIALOG (
+ pp_new_printer_dialog_new (GTK_WINDOW (toplevel),
+ self->all_ppds_list));
+
+ g_signal_connect_object (self->pp_new_printer_dialog,
+ "pre-response",
+ G_CALLBACK (new_printer_dialog_pre_response_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->pp_new_printer_dialog,
+ "response",
+ G_CALLBACK (new_printer_dialog_response_cb),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+update_sensitivity (gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ const char *cups_server = NULL;
+ GtkWidget *widget;
+ gboolean local_server = TRUE;
+ gboolean no_cups = FALSE;
+
+ self->is_authorized =
+ self->permission &&
+ g_permission_get_allowed (G_PERMISSION (self->permission)) &&
+ self->lockdown_settings &&
+ !g_settings_get_boolean (self->lockdown_settings, "disable-print-setup");
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "main-vbox");
+ if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (widget)), "no-cups-page") == 0)
+ no_cups = TRUE;
+
+ cups_server = cupsServer ();
+ if (cups_server &&
+ g_ascii_strncasecmp (cups_server, "localhost", 9) != 0 &&
+ g_ascii_strncasecmp (cups_server, "127.0.0.1", 9) != 0 &&
+ g_ascii_strncasecmp (cups_server, "::1", 3) != 0 &&
+ cups_server[0] != '/')
+ local_server = FALSE;
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "search-button");
+ gtk_widget_set_visible (widget, !no_cups);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "search-bar");
+ gtk_widget_set_visible (widget, !no_cups);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "printer-add-button");
+ gtk_widget_set_visible (widget, local_server && self->is_authorized && !no_cups && !self->new_printer_name);
+
+ widget = (GtkWidget*) gtk_builder_get_object (self->builder, "printer-add-button2");
+ gtk_widget_set_sensitive (widget, local_server && self->is_authorized && !no_cups && !self->new_printer_name);
+}
+
+static void
+on_permission_changed (CcPrintersPanel *self)
+{
+ actualize_printers_list (self);
+ update_sensitivity (self);
+}
+
+static void
+on_lockdown_settings_changed (CcPrintersPanel *self,
+ const char *key)
+{
+ if (g_str_equal (key, "disable-print-setup") == FALSE)
+ return;
+
+#if 0
+ /* FIXME */
+ gtk_widget_set_sensitive (self->lock_button,
+ !g_settings_get_boolean (self->lockdown_settings, "disable-print-setup"));
+#endif
+
+ on_permission_changed (self);
+}
+
+static void
+cups_status_check_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ gboolean success;
+ PpCups *cups = PP_CUPS (source_object);
+
+ success = pp_cups_connection_test_finish (cups, result, NULL);
+ if (success)
+ {
+ actualize_printers_list (self);
+ attach_to_cups_notifier (self);
+
+ g_source_remove (self->cups_status_check_id);
+ self->cups_status_check_id = 0;
+ }
+
+ g_object_unref (cups);
+}
+
+static gboolean
+cups_status_check (gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ PpCups *cups;
+
+ cups = pp_cups_new ();
+ pp_cups_connection_test_async (cups, NULL, cups_status_check_cb, self);
+
+ return self->cups_status_check_id != 0;
+}
+
+static void
+connection_test_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcPrintersPanel *self;
+ gboolean success;
+ PpCups *cups = PP_CUPS (source_object);
+ g_autoptr(GError) error = NULL;
+
+ success = pp_cups_connection_test_finish (cups, result, &error);
+ g_object_unref (cups);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Could not test connection: %s", error->message);
+ }
+
+ return;
+ }
+
+ self = CC_PRINTERS_PANEL (user_data);
+
+ if (!success)
+ {
+ self->cups_status_check_id =
+ g_timeout_add_seconds (CUPS_STATUS_CHECK_INTERVAL, cups_status_check, self);
+ }
+}
+
+static void
+get_all_ppds_async_cb (PPDList *ppds,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+
+ self->all_ppds_list = ppds;
+
+ if (self->pp_ppd_selection_dialog)
+ pp_ppd_selection_dialog_set_ppd_list (self->pp_ppd_selection_dialog,
+ self->all_ppds_list);
+
+ if (self->pp_new_printer_dialog)
+ pp_new_printer_dialog_set_ppd_list (self->pp_new_printer_dialog,
+ self->all_ppds_list);
+}
+
+static gboolean
+filter_function (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ CcPrintersPanel *self = (CcPrintersPanel*) user_data;
+ GtkWidget *search_entry;
+ gboolean retval;
+ g_autofree gchar *search = NULL;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *location = NULL;
+ g_autofree gchar *printer_name = NULL;
+ g_autofree gchar *printer_location = NULL;
+ GList *iter;
+
+ g_object_get (G_OBJECT (row),
+ "printer-name", &printer_name,
+ "printer-location", &printer_location,
+ NULL);
+
+ search_entry = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "search-entry");
+
+ if (gtk_entry_get_text_length (GTK_ENTRY (search_entry)) == 0)
+ {
+ retval = TRUE;
+ }
+ else
+ {
+ name = cc_util_normalize_casefold_and_unaccent (printer_name);
+ location = cc_util_normalize_casefold_and_unaccent (printer_location);
+
+ search = cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (search_entry)));
+
+ retval = strstr (name, search) != NULL;
+ if (location != NULL)
+ retval = retval || (strstr (location, search) != NULL);
+ }
+
+ if (self->deleted_printer_name != NULL &&
+ g_strcmp0 (self->deleted_printer_name, printer_name) == 0)
+ {
+ retval = FALSE;
+ }
+
+ if (self->deleted_printers != NULL)
+ {
+ for (iter = self->deleted_printers; iter != NULL; iter = iter->next)
+ {
+ if (g_strcmp0 (iter->data, printer_name) == 0)
+ {
+ retval = FALSE;
+ break;
+ }
+ }
+ }
+
+ return retval;
+}
+
+static gint
+sort_function (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ g_autofree gchar *printer_name1 = NULL;
+ g_autofree gchar *printer_name2 = NULL;
+
+ g_object_get (G_OBJECT (row1),
+ "printer-name", &printer_name1,
+ NULL);
+
+ g_object_get (G_OBJECT (row2),
+ "printer-name", &printer_name2,
+ NULL);
+
+ if (printer_name1 != NULL)
+ {
+ if (printer_name2 != NULL)
+ return g_ascii_strcasecmp (printer_name1, printer_name2);
+ else
+ return 1;
+ }
+ else
+ {
+ if (printer_name2 != NULL)
+ return -1;
+ else
+ return 0;
+ }
+}
+
+static void
+cc_printers_panel_init (CcPrintersPanel *self)
+{
+ GtkWidget *top_widget;
+ GtkWidget *widget;
+ PpCups *cups;
+ g_autoptr(GError) error = NULL;
+ gchar *objects[] = { "overlay", "permission-infobar", "top-right-buttons", "printer-add-button", "search-button", NULL };
+ guint builder_result;
+
+ g_resources_register (cc_printers_get_resource ());
+
+ /* initialize main data structure */
+ self->builder = gtk_builder_new ();
+ self->reference = g_object_new (G_TYPE_OBJECT, NULL);
+
+ self->printer_entries = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ g_type_ensure (CC_TYPE_PERMISSION_INFOBAR);
+
+ g_object_set_data_full (self->reference, "self", self, NULL);
+
+ builder_result = gtk_builder_add_objects_from_resource (self->builder,
+ "/org/gnome/control-center/printers/printers.ui",
+ objects, &error);
+
+ if (builder_result == 0)
+ {
+ /* Translators: The XML file containing user interface can not be loaded */
+ g_warning (_("Could not load ui: %s"), error->message);
+ return;
+ }
+
+ self->notification = (GtkRevealer*)
+ gtk_builder_get_object (self->builder, "notification");
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "notification-undo-button");
+ g_signal_connect_object (widget, "clicked", G_CALLBACK (on_printer_deletion_undone), self, G_CONNECT_SWAPPED);
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "notification-dismiss-button");
+ g_signal_connect_object (widget, "clicked", G_CALLBACK (on_notification_dismissed), self, G_CONNECT_SWAPPED);
+
+ self->permission_infobar = (CcPermissionInfobar*)
+ gtk_builder_get_object (self->builder, "permission-infobar");
+
+ /* add the top level widget */
+ top_widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "overlay");
+
+ /* connect signals */
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "printer-add-button");
+ g_signal_connect_object (widget, "clicked", G_CALLBACK (printer_add_cb), self, G_CONNECT_SWAPPED);
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "printer-add-button2");
+ g_signal_connect_object (widget, "clicked", G_CALLBACK (printer_add_cb), self, G_CONNECT_SWAPPED);
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "content");
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (widget),
+ filter_function,
+ self,
+ NULL);
+ g_signal_connect_swapped (gtk_builder_get_object (self->builder, "search-entry"),
+ "search-changed",
+ G_CALLBACK (gtk_list_box_invalidate_filter),
+ widget);
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (widget),
+ sort_function,
+ NULL,
+ NULL);
+
+ self->lockdown_settings = g_settings_new ("org.gnome.desktop.lockdown");
+ if (self->lockdown_settings)
+ g_signal_connect_object (self->lockdown_settings,
+ "changed",
+ G_CALLBACK (on_lockdown_settings_changed),
+ self,
+ G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+ /* Add unlock button */
+ self->permission = (GPermission *)polkit_permission_new_sync (
+ "org.opensuse.cupspkhelper.mechanism.all-edit", NULL, NULL, NULL);
+ if (self->permission != NULL)
+ {
+ g_signal_connect_object (self->permission,
+ "notify",
+ G_CALLBACK (on_permission_changed),
+ self,
+ G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+ cc_permission_infobar_set_permission (self->permission_infobar,
+ self->permission);
+
+ on_permission_changed (self);
+ }
+ else
+ g_warning ("Your system does not have the cups-pk-helper's policy \
+\"org.opensuse.cupspkhelper.mechanism.all-edit\" installed. \
+Please check your installation");
+
+ self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ actualize_printers_list (self);
+ attach_to_cups_notifier (self);
+
+ get_all_ppds_async (cc_panel_get_cancellable (CC_PANEL (self)),
+ get_all_ppds_async_cb,
+ self);
+
+ cups = pp_cups_new ();
+ pp_cups_connection_test_async (cups, cc_panel_get_cancellable (CC_PANEL (self)), connection_test_cb, self);
+ gtk_container_add (GTK_CONTAINER (self), top_widget);
+ gtk_widget_show_all (GTK_WIDGET (self));
+}
diff --git a/panels/printers/cc-printers-panel.h b/panels/printers/cc-printers-panel.h
new file mode 100644
index 0000000..18e93a4
--- /dev/null
+++ b/panels/printers/cc-printers-panel.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_PRINTERS_PANEL (cc_printers_panel_get_type ())
+G_DECLARE_FINAL_TYPE (CcPrintersPanel, cc_printers_panel, CC, PRINTERS_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/printers/gnome-printers-panel.desktop.in.in b/panels/printers/gnome-printers-panel.desktop.in.in
new file mode 100644
index 0000000..96ed1d2
--- /dev/null
+++ b/panels/printers/gnome-printers-panel.desktop.in.in
@@ -0,0 +1,17 @@
+[Desktop Entry]
+Name=Printers
+Comment=Add printers, view printer jobs and decide how you want to print
+Exec=gnome-control-center printers
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=printer
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+# The X-GNOME-Settings-Panel is necessary to show in the main shell UI
+Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-DevicesSettings;
+OnlyShowIn=GNOME;Unity;
+# Translators: Search terms to find the Printers panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Printer;Queue;Print;Paper;Ink;Toner;
+# Notifications are emitted by gnome-settings-daemon
+X-GNOME-UsesNotifications=true \ No newline at end of file
diff --git a/panels/printers/meson.build b/panels/printers/meson.build
new file mode 100644
index 0000000..f887625
--- /dev/null
+++ b/panels/printers/meson.build
@@ -0,0 +1,75 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+)
+
+i18n.merge_file(
+ desktop,
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+sources = files(
+ 'cc-printers-panel.c',
+ 'pp-cups.c',
+ 'pp-details-dialog.c',
+ 'pp-host.c',
+ 'pp-ipp-option-widget.c',
+ 'pp-job.c',
+ 'pp-jobs-dialog.c',
+ 'pp-maintenance-command.c',
+ 'pp-new-printer-dialog.c',
+ 'pp-new-printer.c',
+ 'pp-options-dialog.c',
+ 'pp-ppd-option-widget.c',
+ 'pp-ppd-selection-dialog.c',
+ 'pp-print-device.c',
+ 'pp-printer-entry.c',
+ 'pp-printer.c',
+ 'pp-samba.c',
+ 'pp-utils.c'
+)
+
+resource_data = files(
+ 'authentication-dialog.ui',
+ 'new-printer-dialog.ui',
+ 'ppd-selection-dialog.ui',
+ 'pp-details-dialog.ui',
+ 'pp-jobs-dialog.ui',
+ 'pp-options-dialog.ui',
+ 'printer-entry.ui',
+ 'printers.ui'
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+deps = common_deps + [
+ cups_dep,
+ m_dep,
+ polkit_gobject_dep,
+ dependency('smbclient')
+]
+
+printers_panel_lib = static_library(
+ cappletname,
+ sources: sources,
+ include_directories: [top_inc, common_inc, shell_inc],
+ dependencies: deps,
+ c_args: cflags + cups_cflags,
+)
+panels_libs += [ printers_panel_lib ]
+
diff --git a/panels/printers/new-printer-dialog.ui b/panels/printers/new-printer-dialog.ui
new file mode 100644
index 0000000..b011f31
--- /dev/null
+++ b/panels/printers/new-printer-dialog.ui
@@ -0,0 +1,423 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.14"/>
+ <object class="GtkListStore" id="devices-liststore">
+ <columns>
+ <!-- column-name device_gicon -->
+ <column type="GIcon"/>
+ <!-- column-name device_name -->
+ <column type="gchararray"/>
+ <!-- column-name device_display_name -->
+ <column type="gchararray"/>
+ <!-- column-name device_description -->
+ <column type="gchararray"/>
+ <!-- column-name server_needs_authentication -->
+ <column type="gboolean"/>
+ <!-- column-name device_visible -->
+ <column type="gboolean"/>
+ <!-- column-name device -->
+ <column type="PpPrintDevice"/>
+ </columns>
+ </object>
+ <object class="GtkTreeModelFilter" id="devices-model-filter">
+ <property name="child_model">devices-liststore</property>
+ </object>
+ <object class="GtkDialog" id="dialog">
+ <property name="width_request">480</property>
+ <property name="height_request">490</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes" comments="Translators: This is the title presented at top of the dialog.">Add Printer</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use_header_bar">1</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar" id="headerbar">
+ <property name="visible">True</property>
+ <property name="show-close-button">False</property>
+ <child>
+ <object class="GtkStack" id="headerbar-topleft-buttons">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkButton" id="new-printer-cancel-button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="name">addprinter-page</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="go-back-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">authentication-page</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="headerbar-topright-buttons">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkButton" id="new-printer-add-button">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">addprinter-page</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="unlock-button">
+ <property name="label" translatable="yes" comments="Translators: This button opens authentication dialog for selected server.">_Unlock</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">unlock-button</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="authenticate-button">
+ <property name="label" translatable="yes" comments="Translators: This buttons submits the credentials for the selected server.">_Unlock</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">authentication-page</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <property name="border_width">0</property>
+ <child>
+ <object class="GtkStack" id="dialog-stack">
+ <property name="visible">True</property>
+ <property name="border_width">0</property>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="transition-type">none</property>
+ <property name="has_focus">True</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkTreeView" id="devices-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">devices-model-filter</property>
+ <property name="headers_visible">False</property>
+ <property name="enable-grid-lines">GTK_TREE_VIEW_GRID_LINES_HORIZONTAL</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">standard-page</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <property name="valign">GTK_ALIGN_CENTER</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">GTK_ALIGN_START</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">printer-symbolic</property>
+ <property name="opacity">0.6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="no-devices-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes" comments="Translators: No printers were detected">No Printers Found</property>
+ <property name="opacity">0.6</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">no-printers-page</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="visible">True</property>
+ <property name="active">True</property>
+ <property name="expand">True</property>
+ <property name="sensitive">False</property>
+ <property name="opacity">0.6</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">loading-page</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="toolbar_style">icons</property>
+ <property name="icon_size">1</property>
+ <style>
+ <class name="toolbar"/>
+ </style>
+ <child>
+ <object class="GtkToolItem" id="toolbutton1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">4</property>
+ <child>
+ <object class="GtkSearchEntry" id="search-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_tooltip">True</property>
+ <property name="invisible_char">●</property>
+ <property name="truncate_multiline">True</property>
+ <property name="invisible_char_set">True</property>
+ <property name="placeholder_text" translatable="yes" comments="Translators: The entered text should contain network address of a printer or a text which will filter found devices (their names and locations)">Enter a network address or search for a printer</property>
+ <property name="margin_start">40</property>
+ <property name="margin_end">40</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">addprinter-page</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="margin">20</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">15</property>
+ <property name="expand">True</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="pixel_size">48</property>
+ <property name="icon_name">dialog-password-symbolic</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="authentication-title">
+ <property name="label" translatable="yes">Authentication Required</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="authentication-text">
+ <property name="wrap">True</property>
+ <property name="max_width_chars">36</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Enter username and password to view printers on Print Server.</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Username</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username-entry">
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Password</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password-entry">
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">authentication-page</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">new-printer-cancel-button</action-widget>
+ <action-widget response="-5">new-printer-add-button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkSizeGroup">
+ <widgets>
+ <widget name="new-printer-cancel-button"/>
+ <widget name="headerbar-topright-buttons"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/printers/pp-cups.c b/panels/printers/pp-cups.c
new file mode 100644
index 0000000..96ace83
--- /dev/null
+++ b/panels/printers/pp-cups.c
@@ -0,0 +1,320 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+
+#include "pp-cups.h"
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#endif
+
+struct _PpCups
+{
+ GObject parent_instance;
+};
+
+G_DEFINE_TYPE (PpCups, pp_cups, G_TYPE_OBJECT);
+
+static void
+pp_cups_class_init (PpCupsClass *klass)
+{
+}
+
+static void
+pp_cups_init (PpCups *self)
+{
+}
+
+PpCups *
+pp_cups_new ()
+{
+ return g_object_new (PP_TYPE_CUPS, NULL);
+}
+
+static void
+pp_cups_dests_free (PpCupsDests *dests)
+{
+ cupsFreeDests (dests->num_of_dests, dests->dests);
+}
+
+static void
+_pp_cups_get_dests_thread (GTask *task,
+ gpointer *object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpCupsDests *dests;
+
+ dests = g_new0 (PpCupsDests, 1);
+ dests->num_of_dests = cupsGetDests (&dests->dests);
+
+ if (g_task_set_return_on_cancel (task, FALSE))
+ {
+ g_task_return_pointer (task, dests, (GDestroyNotify) pp_cups_dests_free);
+ }
+ else
+ {
+ pp_cups_dests_free (dests);
+ }
+}
+
+void
+pp_cups_get_dests_async (PpCups *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_return_on_cancel (task, TRUE);
+ g_task_run_in_thread (task, (GTaskThreadFunc) _pp_cups_get_dests_thread);
+ g_object_unref (task);
+}
+
+PpCupsDests *
+pp_cups_get_dests_finish (PpCups *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+connection_test_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ http_t *http;
+
+#ifdef HAVE_CUPS_HTTPCONNECT2
+ http = httpConnect2 (cupsServer (), ippPort (), NULL, AF_UNSPEC,
+ cupsEncryption (), 1, 30000, NULL);
+#else
+ http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
+#endif
+ httpClose (http);
+
+ if (g_task_set_return_on_cancel (task, FALSE))
+ {
+ g_task_return_boolean (task, http != NULL);
+ }
+}
+
+void
+pp_cups_connection_test_async (PpCups *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_return_on_cancel (task, TRUE);
+ g_task_run_in_thread (task, connection_test_thread);
+
+ g_object_unref (task);
+}
+
+gboolean
+pp_cups_connection_test_finish (PpCups *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/* Cancels subscription of given id */
+static void
+cancel_subscription_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ipp_t *request;
+ ipp_t *response = NULL;
+ gint id = GPOINTER_TO_INT (task_data);
+
+ if (id >= 0)
+ {
+ request = ippNewRequest (IPP_CANCEL_SUBSCRIPTION);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, "/");
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+ "notify-subscription-id", id);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+ }
+
+ g_task_return_boolean (task, response != NULL && ippGetStatusCode (response) <= IPP_OK);
+
+ ippDelete (response);
+}
+
+void
+pp_cups_cancel_subscription_async (PpCups *self,
+ gint subscription_id,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GINT_TO_POINTER (subscription_id), NULL);
+ g_task_run_in_thread (task, cancel_subscription_thread);
+
+ g_object_unref (task);
+}
+
+gboolean
+pp_cups_cancel_subscription_finish (PpCups *self,
+ GAsyncResult *result)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+typedef struct {
+ gint id;
+ gchar **events;
+ int lease_duration;
+} CRSData;
+
+static void
+crs_data_free (CRSData *data)
+{
+ g_strfreev (data->events);
+ g_slice_free (CRSData, data);
+}
+
+static void
+renew_subscription_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ipp_attribute_t *attr = NULL;
+ CRSData *subscription_data = task_data;
+ ipp_t *request;
+ ipp_t *response = NULL;
+ gint result = -1;
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
+
+ if (subscription_data->id > 0)
+ {
+ request = ippNewRequest (IPP_RENEW_SUBSCRIPTION);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, "/");
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+ "notify-subscription-id", subscription_data->id);
+ ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
+ "notify-lease-duration", subscription_data->lease_duration);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+ if (response != NULL && ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ if ((attr = ippFindAttribute (response, "notify-lease-duration", IPP_TAG_INTEGER)) == NULL)
+ g_debug ("No notify-lease-duration in response!\n");
+ else if (ippGetInteger (attr, 0) == subscription_data->lease_duration)
+ result = subscription_data->id;
+ }
+ }
+
+ if (result < 0)
+ {
+ request = ippNewRequest (IPP_CREATE_PRINTER_SUBSCRIPTION);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, "/");
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddStrings (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
+ "notify-events", g_strv_length (subscription_data->events), NULL,
+ (const char * const *) subscription_data->events);
+ ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
+ "notify-pull-method", NULL, "ippget");
+ ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
+ "notify-recipient-uri", NULL, "dbus://");
+ ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
+ "notify-lease-duration", subscription_data->lease_duration);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+
+ if (response != NULL && ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ if ((attr = ippFindAttribute (response, "notify-subscription-id", IPP_TAG_INTEGER)) == NULL)
+ g_debug ("No notify-subscription-id in response!\n");
+ else
+ result = ippGetInteger (attr, 0);
+ }
+ }
+
+ ippDelete (response);
+
+ g_task_return_int (task, result);
+}
+
+void
+pp_cups_renew_subscription_async (PpCups *self,
+ gint subscription_id,
+ gchar **events,
+ gint lease_duration,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CRSData *subscription_data;
+ GTask *task;
+
+ subscription_data = g_slice_new (CRSData);
+ subscription_data->id = subscription_id;
+ subscription_data->events = g_strdupv (events);
+ subscription_data->lease_duration = lease_duration;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, subscription_data, (GDestroyNotify) crs_data_free);
+ g_task_run_in_thread (task, renew_subscription_thread);
+
+ g_object_unref (task);
+}
+
+/* Returns id of renewed subscription or new id */
+gint
+pp_cups_renew_subscription_finish (PpCups *self,
+ GAsyncResult *result)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_int (G_TASK (result), NULL);
+}
diff --git a/panels/printers/pp-cups.h b/panels/printers/pp-cups.h
new file mode 100644
index 0000000..04063bb
--- /dev/null
+++ b/panels/printers/pp-cups.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_CUPS (pp_cups_get_type ())
+G_DECLARE_FINAL_TYPE (PpCups, pp_cups, PP, CUPS, GObject)
+
+typedef struct{
+ cups_dest_t *dests;
+ gint num_of_dests;
+} PpCupsDests;
+
+PpCups *pp_cups_new (void);
+
+void pp_cups_get_dests_async (PpCups *cups,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+PpCupsDests *pp_cups_get_dests_finish (PpCups *cups,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_cups_connection_test_async (PpCups *cups,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_cups_connection_test_finish (PpCups *cups,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_cups_cancel_subscription_async (PpCups *cups,
+ gint subscription_id,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_cups_cancel_subscription_finish (PpCups *cups,
+ GAsyncResult *result);
+
+void pp_cups_renew_subscription_async (PpCups *cups,
+ gint subscription_id,
+ gchar **events,
+ gint lease_duration,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gint pp_cups_renew_subscription_finish (PpCups *cups,
+ GAsyncResult *result);
+
+G_END_DECLS
diff --git a/panels/printers/pp-details-dialog.c b/panels/printers/pp-details-dialog.c
new file mode 100644
index 0000000..7957819
--- /dev/null
+++ b/panels/printers/pp-details-dialog.c
@@ -0,0 +1,394 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2016 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Felipe Borges <feborges@redhat.com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-details-dialog.h"
+#include "pp-ppd-selection-dialog.h"
+#include "pp-printer.h"
+#include "pp-utils.h"
+
+struct _PpDetailsDialog {
+ GtkDialog parent_instance;
+
+ GtkLabel *dialog_title;
+ GtkButtonBox *driver_buttons;
+ GtkBox *loading_box;
+ GtkLabel *printer_address_label;
+ GtkEntry *printer_location_entry;
+ GtkLabel *printer_model_label;
+ GtkStack *printer_model_stack;
+ GtkEntry *printer_name_entry;
+ GtkButton *search_for_drivers_button;
+
+ gchar *printer_name;
+ gchar *ppd_file_name;
+ PPDList *all_ppds_list;
+ GCancellable *cancellable;
+
+ /* Dialogs */
+ PpPPDSelectionDialog *pp_ppd_selection_dialog;
+};
+
+G_DEFINE_TYPE (PpDetailsDialog, pp_details_dialog, GTK_TYPE_DIALOG)
+
+static void
+printer_name_changed (PpDetailsDialog *self)
+{
+ const gchar *name;
+ g_autofree gchar *title = NULL;
+
+ name = pp_details_dialog_get_printer_name (self);
+
+ /* Translators: This is the title of the dialog. %s is the printer name. */
+ title = g_strdup_printf (_("%s Details"), name);
+ gtk_label_set_label (self->dialog_title, title);
+}
+
+static void
+ppd_names_free (gpointer user_data)
+{
+ PPDName **names = (PPDName **) user_data;
+ gint i;
+
+ if (names)
+ {
+ for (i = 0; names[i]; i++)
+ {
+ g_free (names[i]->ppd_name);
+ g_free (names[i]->ppd_display_name);
+ g_free (names[i]);
+ }
+
+ g_free (names);
+ }
+}
+
+static void set_ppd_cb (const gchar *printer_name, gboolean success, gpointer user_data);
+
+static void
+get_ppd_names_cb (PPDName **names,
+ const gchar *printer_name,
+ gboolean cancelled,
+ gpointer user_data)
+{
+ PpDetailsDialog *self = (PpDetailsDialog*) user_data;
+
+ if (!cancelled)
+ {
+ if (names != NULL)
+ {
+ gtk_label_set_text (self->printer_model_label, names[0]->ppd_display_name);
+ printer_set_ppd_async (printer_name,
+ names[0]->ppd_name,
+ self->cancellable,
+ set_ppd_cb,
+ self);
+ ppd_names_free (names);
+ }
+ else
+ {
+ gtk_label_set_text (self->printer_model_label, _("No suitable driver found"));
+ }
+
+ gtk_stack_set_visible_child (self->printer_model_stack, GTK_WIDGET (self->printer_model_label));
+ }
+}
+
+static void
+search_for_drivers (PpDetailsDialog *self)
+{
+ gtk_stack_set_visible_child (self->printer_model_stack, GTK_WIDGET (self->loading_box));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->search_for_drivers_button), FALSE);
+
+ get_ppd_names_async (self->printer_name,
+ 1,
+ self->cancellable,
+ get_ppd_names_cb,
+ self);
+}
+
+static void
+set_ppd_cb (const gchar *printer_name,
+ gboolean success,
+ gpointer user_data)
+{
+ PpDetailsDialog *self = (PpDetailsDialog*) user_data;
+
+ gtk_label_set_text (GTK_LABEL (self->printer_model_label), self->ppd_file_name);
+}
+
+static void
+ppd_selection_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ PpDetailsDialog *self = (PpDetailsDialog*) user_data;
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ g_autofree gchar *ppd_name = NULL;
+
+ ppd_name = pp_ppd_selection_dialog_get_ppd_name (self->pp_ppd_selection_dialog);
+
+ if (self->printer_name && ppd_name)
+ {
+ printer_set_ppd_async (self->printer_name,
+ ppd_name,
+ self->cancellable,
+ set_ppd_cb,
+ self);
+
+ g_clear_pointer (&self->ppd_file_name, g_free);
+ self->ppd_file_name = g_strdup (ppd_name);
+ }
+ }
+
+ pp_ppd_selection_dialog_free (self->pp_ppd_selection_dialog);
+ self->pp_ppd_selection_dialog = NULL;
+}
+
+static void
+get_all_ppds_async_cb (PPDList *ppds,
+ gpointer user_data)
+{
+ PpDetailsDialog *self = user_data;
+
+ self->all_ppds_list = ppds;
+
+ if (self->pp_ppd_selection_dialog)
+ pp_ppd_selection_dialog_set_ppd_list (self->pp_ppd_selection_dialog,
+ self->all_ppds_list);
+}
+
+static void
+select_ppd_in_dialog (PpDetailsDialog *self)
+{
+ g_autofree gchar *device_id = NULL;
+ g_autofree gchar *manufacturer = NULL;
+
+ g_clear_pointer (&self->ppd_file_name, g_free);
+ self->ppd_file_name = g_strdup (cupsGetPPD (self->printer_name));
+
+ if (!self->pp_ppd_selection_dialog)
+ {
+ device_id =
+ get_ppd_attribute (self->ppd_file_name,
+ "1284DeviceID");
+
+ if (device_id)
+ {
+ manufacturer = get_tag_value (device_id, "mfg");
+ if (!manufacturer)
+ manufacturer = get_tag_value (device_id, "manufacturer");
+ }
+
+ if (manufacturer == NULL)
+ {
+ manufacturer =
+ get_ppd_attribute (self->ppd_file_name,
+ "Manufacturer");
+ }
+
+ if (manufacturer == NULL)
+ {
+ manufacturer = g_strdup ("Raw");
+ }
+
+ if (self->all_ppds_list == NULL)
+ {
+ get_all_ppds_async (self->cancellable, get_all_ppds_async_cb, self);
+ }
+
+ self->pp_ppd_selection_dialog = pp_ppd_selection_dialog_new (
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
+ self->all_ppds_list,
+ manufacturer,
+ ppd_selection_dialog_response_cb,
+ self);
+ }
+}
+
+static void
+select_ppd_manually (PpDetailsDialog *self)
+{
+ GtkFileFilter *filter;
+ GtkWidget *dialog;
+
+ dialog = gtk_file_chooser_dialog_new (_("Select PPD File"),
+ GTK_WINDOW (self),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter,
+ _("PostScript Printer Description files (*.ppd, *.PPD, *.ppd.gz, *.PPD.gz, *.PPD.GZ)"));
+ gtk_file_filter_add_pattern (filter, "*.ppd");
+ gtk_file_filter_add_pattern (filter, "*.PPD");
+ gtk_file_filter_add_pattern (filter, "*.ppd.gz");
+ gtk_file_filter_add_pattern (filter, "*.PPD.gz");
+ gtk_file_filter_add_pattern (filter, "*.PPD.GZ");
+
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+ {
+ g_autofree gchar *ppd_filename = NULL;
+
+ ppd_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+
+ if (self->printer_name && ppd_filename)
+ {
+ printer_set_ppd_file_async (self->printer_name,
+ ppd_filename,
+ self->cancellable,
+ set_ppd_cb,
+ self);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+update_sensitivity (PpDetailsDialog *self,
+ gboolean sensitive)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (self->printer_name_entry), sensitive);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->printer_location_entry), sensitive);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->driver_buttons), sensitive);
+}
+
+static void
+pp_details_dialog_init (PpDetailsDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->cancellable = g_cancellable_new ();
+}
+
+static void
+pp_details_dialog_dispose (GObject *object)
+{
+ PpDetailsDialog *self = PP_DETAILS_DIALOG (object);
+
+ g_clear_pointer (&self->printer_name, g_free);
+ g_clear_pointer (&self->ppd_file_name, g_free);
+
+ if (self->all_ppds_list != NULL)
+ {
+ ppd_list_free (self->all_ppds_list);
+ self->all_ppds_list = NULL;
+ }
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (pp_details_dialog_parent_class)->dispose (object);
+}
+
+static void
+pp_details_dialog_class_init (PpDetailsDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = pp_details_dialog_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/printers/pp-details-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, dialog_title);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, driver_buttons);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, loading_box);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, printer_address_label);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, printer_location_entry);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, printer_model_label);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, printer_model_stack);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, printer_name_entry);
+ gtk_widget_class_bind_template_child (widget_class, PpDetailsDialog, search_for_drivers_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, printer_name_changed);
+ gtk_widget_class_bind_template_callback (widget_class, search_for_drivers);
+ gtk_widget_class_bind_template_callback (widget_class, select_ppd_in_dialog);
+ gtk_widget_class_bind_template_callback (widget_class, select_ppd_manually);
+}
+
+PpDetailsDialog *
+pp_details_dialog_new (gchar *printer_name,
+ gchar *printer_location,
+ gchar *printer_address,
+ gchar *printer_make_and_model,
+ gboolean sensitive)
+{
+ PpDetailsDialog *self;
+ g_autofree gchar *title = NULL;
+ g_autofree gchar *printer_url = NULL;
+
+ self = g_object_new (PP_DETAILS_DIALOG_TYPE,
+ "use-header-bar", TRUE,
+ NULL);
+
+ self->printer_name = g_strdup (printer_name);
+ self->ppd_file_name = NULL;
+
+ /* Translators: This is the title of the dialog. %s is the printer name. */
+ title = g_strdup_printf (_("%s Details"), printer_name);
+ gtk_label_set_label (self->dialog_title, title);
+
+ printer_url = g_strdup_printf ("<a href=\"http://%s:%d\">%s</a>", printer_address, ippPort (), printer_address);
+ gtk_label_set_markup (GTK_LABEL (self->printer_address_label), printer_url);
+
+ gtk_entry_set_text (GTK_ENTRY (self->printer_name_entry), printer_name);
+ gtk_entry_set_text (GTK_ENTRY (self->printer_location_entry), printer_location);
+ gtk_label_set_text (GTK_LABEL (self->printer_model_label), printer_make_and_model);
+
+ update_sensitivity (self, sensitive);
+
+ return self;
+}
+
+const gchar *
+pp_details_dialog_get_printer_name (PpDetailsDialog *self)
+{
+ g_return_val_if_fail (PP_IS_DETAILS_DIALOG (self), NULL);
+ return gtk_entry_get_text (GTK_ENTRY (self->printer_name_entry));
+}
+
+const gchar *
+pp_details_dialog_get_printer_location (PpDetailsDialog *self)
+{
+ g_return_val_if_fail (PP_IS_DETAILS_DIALOG (self), NULL);
+ return gtk_entry_get_text (GTK_ENTRY (self->printer_location_entry));
+}
diff --git a/panels/printers/pp-details-dialog.h b/panels/printers/pp-details-dialog.h
new file mode 100644
index 0000000..8bb5a65
--- /dev/null
+++ b/panels/printers/pp-details-dialog.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2016 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Felipe Borges <feborges@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_DETAILS_DIALOG_TYPE (pp_details_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (PpDetailsDialog, pp_details_dialog, PP, DETAILS_DIALOG, GtkDialog)
+
+PpDetailsDialog *pp_details_dialog_new (gchar *printer_name,
+ gchar *printer_location,
+ gchar *printer_address,
+ gchar *printer_make_and_model,
+ gboolean sensitive);
+
+const gchar *pp_details_dialog_get_printer_name (PpDetailsDialog *dialog);
+
+const gchar *pp_details_dialog_get_printer_location (PpDetailsDialog *dialog);
+
+G_END_DECLS
diff --git a/panels/printers/pp-details-dialog.ui b/panels/printers/pp-details-dialog.ui
new file mode 100644
index 0000000..0d93c13
--- /dev/null
+++ b/panels/printers/pp-details-dialog.ui
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="PpDetailsDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">0</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="show_close_button">True</property>
+ <child type="title">
+ <object class="GtkLabel" id="dialog_title">
+ <property name="visible">True</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">30</property>
+ <property name="ellipsize">middle</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="margin">20</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="row-spacing">10</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Name</property>
+ <property name="halign">end</property>
+ <property name="mnemonic_widget">printer_name_entry</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="printer_name_entry">
+ <property name="visible">True</property>
+ <property name="halign">fill</property>
+ <property name="width_request">320</property>
+ <signal name="changed" handler="printer_name_changed" object="PpDetailsDialog" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Location</property>
+ <property name="halign">end</property>
+ <property name="mnemonic_widget">printer_location_entry</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="printer_location_entry">
+ <property name="visible">True</property>
+ <property name="width_request">320</property>
+ <property name="halign">fill</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Address</property>
+ <property name="halign">end</property>
+ <property name="mnemonic_widget">printer_address_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="printer_address_label">
+ <property name="visible">True</property>
+ <property name="label">192.168.0.1</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Driver</property>
+ <property name="halign">end</property>
+ <property name="mnemonic_widget">printer_model_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="printer_model_stack">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <child>
+ <object class="GtkLabel" id="printer_model_label">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label">HP Inkjet Delux 9000</property>
+ <property name="selectable">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="loading_box">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkSpinner">
+ <property name="visible">True</property>
+ <property name="active">True</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Searching for preferred drivers…</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkButtonBox" id="driver_buttons">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <property name="halign">start</property>
+ <child>
+ <object class="GtkButton" id="search_for_drivers_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Search for Drivers</property>
+ <property name="halign">fill</property>
+ <signal name="clicked" handler="search_for_drivers" object="PpDetailsDialog" swapped="yes"/>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="select_from_database_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select from Database…</property>
+ <property name="halign">fill</property>
+ <signal name="clicked" handler="select_ppd_in_dialog" object="PpDetailsDialog" swapped="yes"/>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="install_ppd_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Install PPD File…</property>
+ <property name="halign">fill</property>
+ <signal name="clicked" handler="select_ppd_manually" object="PpDetailsDialog" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="search_for_drivers_button"/>
+ <widget name="select_from_database_button"/>
+ <widget name="install_ppd_button"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/printers/pp-host.c b/panels/printers/pp-host.c
new file mode 100644
index 0000000..a31a606
--- /dev/null
+++ b/panels/printers/pp-host.c
@@ -0,0 +1,747 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+
+#include "pp-host.h"
+
+#include <glib/gi18n.h>
+
+#define BUFFER_LENGTH 1024
+
+typedef struct
+{
+ gchar *hostname;
+ gint port;
+} PpHostPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PpHost, pp_host, G_TYPE_OBJECT);
+
+enum {
+ PROP_0 = 0,
+ PROP_HOSTNAME,
+ PROP_PORT,
+};
+
+enum {
+ AUTHENTICATION_REQUIRED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+pp_host_finalize (GObject *object)
+{
+ PpHost *self = PP_HOST (object);
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+
+ g_clear_pointer (&priv->hostname, g_free);
+
+ G_OBJECT_CLASS (pp_host_parent_class)->finalize (object);
+}
+
+static void
+pp_host_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *param_spec)
+{
+ PpHost *self = PP_HOST (object);
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_HOSTNAME:
+ g_value_set_string (value, priv->hostname);
+ break;
+ case PROP_PORT:
+ g_value_set_int (value, priv->port);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_host_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *param_spec)
+{
+ PpHost *self = PP_HOST (object);
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_HOSTNAME:
+ g_free (priv->hostname);
+ priv->hostname = g_value_dup_string (value);
+ break;
+ case PROP_PORT:
+ priv->port = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_host_class_init (PpHostClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pp_host_set_property;
+ gobject_class->get_property = pp_host_get_property;
+ gobject_class->finalize = pp_host_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_HOSTNAME,
+ g_param_spec_string ("hostname",
+ "Hostname",
+ "The hostname",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_PORT,
+ g_param_spec_int ("port",
+ "Port",
+ "The port",
+ -1, G_MAXINT32, PP_HOST_UNSET_PORT,
+ G_PARAM_READWRITE));
+
+ signals[AUTHENTICATION_REQUIRED] =
+ g_signal_new ("authentication-required",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+pp_host_init (PpHost *self)
+{
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+ priv->port = PP_HOST_UNSET_PORT;
+}
+
+PpHost *
+pp_host_new (const gchar *hostname)
+{
+ return g_object_new (PP_TYPE_HOST,
+ "hostname", hostname,
+ NULL);
+}
+
+static gchar **
+line_split (gchar *line)
+{
+ gboolean escaped = FALSE;
+ gboolean quoted = FALSE;
+ gboolean in_word = FALSE;
+ gchar **words = NULL;
+ gchar **result = NULL;
+ g_autofree gchar *buffer = NULL;
+ gchar ch;
+ gint n = 0;
+ gint i, j = 0, k = 0;
+
+ if (line)
+ {
+ n = strlen (line);
+ words = g_new0 (gchar *, n + 1);
+ buffer = g_new0 (gchar, n + 1);
+
+ for (i = 0; i < n; i++)
+ {
+ ch = line[i];
+
+ if (escaped)
+ {
+ buffer[k++] = ch;
+ escaped = FALSE;
+ continue;
+ }
+
+ if (ch == '\\')
+ {
+ in_word = TRUE;
+ escaped = TRUE;
+ continue;
+ }
+
+ if (in_word)
+ {
+ if (quoted)
+ {
+ if (ch == '"')
+ quoted = FALSE;
+ else
+ buffer[k++] = ch;
+ }
+ else if (g_ascii_isspace (ch))
+ {
+ words[j++] = g_strdup (buffer);
+ memset (buffer, 0, n + 1);
+ k = 0;
+ in_word = FALSE;
+ }
+ else if (ch == '"')
+ quoted = TRUE;
+ else
+ buffer[k++] = ch;
+ }
+ else
+ {
+ if (ch == '"')
+ {
+ in_word = TRUE;
+ quoted = TRUE;
+ }
+ else if (!g_ascii_isspace (ch))
+ {
+ in_word = TRUE;
+ buffer[k++] = ch;
+ }
+ }
+ }
+ }
+
+ if (buffer && buffer[0] != '\0')
+ words[j++] = g_strdup (buffer);
+
+ result = g_strdupv (words);
+ g_strfreev (words);
+
+ return result;
+}
+
+static void
+_pp_host_get_snmp_devices_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpHost *self = source_object;
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GError) error = NULL;
+ g_auto(GStrv) argv = NULL;
+ g_autofree gchar *stdout_string = NULL;
+ gint exit_status;
+
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+
+ argv = g_new0 (gchar *, 3);
+ argv[0] = g_strdup ("/usr/lib/cups/backend/snmp");
+ argv[1] = g_strdup (priv->hostname);
+
+ /* Use SNMP to get printer's informations */
+ g_spawn_sync (NULL,
+ argv,
+ NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL,
+ NULL,
+ &stdout_string,
+ NULL,
+ &exit_status,
+ &error);
+
+ if (exit_status == 0 && stdout_string)
+ {
+ g_auto(GStrv) printer_informations = NULL;
+ gint length;
+
+ printer_informations = line_split (stdout_string);
+ length = g_strv_length (printer_informations);
+
+ if (length >= 4)
+ {
+ g_autofree gchar *device_name = NULL;
+ gboolean is_network_device;
+ PpPrintDevice *device;
+
+ device_name = g_strdup (printer_informations[3]);
+ g_strcanon (device_name, ALLOWED_CHARACTERS, '-');
+ is_network_device = g_strcmp0 (printer_informations[0], "network") == 0;
+
+ device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "is-network-device", is_network_device,
+ "device-uri", printer_informations[1],
+ "device-make-and-model", printer_informations[2],
+ "device-info", printer_informations[3],
+ "acquisition-method", ACQUISITION_METHOD_SNMP,
+ "device-name", device_name,
+ NULL);
+
+ if (length >= 5 && printer_informations[4][0] != '\0')
+ g_object_set (device, "device-id", printer_informations[4], NULL);
+
+ if (length >= 6 && printer_informations[5][0] != '\0')
+ g_object_set (device, "device-location", printer_informations[5], NULL);
+
+ g_ptr_array_add (devices, device);
+ }
+ }
+
+ g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
+}
+
+void
+pp_host_get_snmp_devices_async (PpHost *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_run_in_thread (task, _pp_host_get_snmp_devices_thread);
+}
+
+GPtrArray *
+pp_host_get_snmp_devices_finish (PpHost *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+_pp_host_get_remote_cups_devices_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ cups_dest_t *dests = NULL;
+ PpHost *self = (PpHost *) source_object;
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+ g_autoptr(GPtrArray) devices = NULL;
+ http_t *http;
+ gint num_of_devices = 0;
+ gint port;
+ gint i;
+
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+
+ if (priv->port == PP_HOST_UNSET_PORT)
+ port = PP_HOST_DEFAULT_IPP_PORT;
+ else
+ port = priv->port;
+
+ /* Connect to remote CUPS server and get its devices */
+#ifdef HAVE_CUPS_HTTPCONNECT2
+ http = httpConnect2 (priv->hostname, port, NULL, AF_UNSPEC,
+ HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL);
+#else
+ http = httpConnect (priv->hostname, port);
+#endif
+ if (http)
+ {
+ num_of_devices = cupsGetDests2 (http, &dests);
+ if (num_of_devices > 0)
+ {
+ for (i = 0; i < num_of_devices; i++)
+ {
+ g_autofree gchar *device_uri = NULL;
+ const char *device_location;
+ PpPrintDevice *device;
+
+ device_uri = g_strdup_printf ("ipp://%s:%d/printers/%s",
+ priv->hostname,
+ port,
+ dests[i].name);
+
+ device_location = cupsGetOption ("printer-location",
+ dests[i].num_options,
+ dests[i].options);
+
+ device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "is-network-device", TRUE,
+ "device-uri", device_uri,
+ "device-name", dests[i].name,
+ "device-location", device_location,
+ "host-name", priv->hostname,
+ "host-port", port,
+ "acquisition-method", ACQUISITION_METHOD_REMOTE_CUPS_SERVER,
+ NULL);
+ g_ptr_array_add (devices, device);
+ }
+ }
+
+ httpClose (http);
+ }
+
+ g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
+}
+
+void
+pp_host_get_remote_cups_devices_async (PpHost *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_run_in_thread (task, _pp_host_get_remote_cups_devices_thread);
+}
+
+GPtrArray *
+pp_host_get_remote_cups_devices_finish (PpHost *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+typedef struct
+{
+ PpHost *host;
+ gint port;
+} JetDirectData;
+
+static void
+jetdirect_data_free (JetDirectData *data)
+{
+ if (data != NULL)
+ {
+ g_clear_object (&data->host);
+ g_free (data);
+ }
+}
+
+static void
+jetdirect_connection_test_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GSocketConnection) connection = NULL;
+ PpHostPrivate *priv;
+ JetDirectData *data;
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GTask) task = G_TASK (user_data);
+
+ data = g_task_get_task_data (task);
+ priv = pp_host_get_instance_private (data->host);
+
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+
+ connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source_object),
+ res,
+ &error);
+
+ if (connection != NULL)
+ {
+ g_autofree gchar *device_uri = NULL;
+ PpPrintDevice *device;
+
+ g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+
+ device_uri = g_strdup_printf ("socket://%s:%d",
+ priv->hostname,
+ data->port);
+
+ device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "is-network-device", TRUE,
+ "device-uri", device_uri,
+ /* Translators: The found device is a JetDirect printer */
+ "device-name", _("JetDirect Printer"),
+ "host-name", priv->hostname,
+ "host-port", data->port,
+ "acquisition-method", ACQUISITION_METHOD_JETDIRECT,
+ NULL);
+ g_ptr_array_add (devices, device);
+ }
+
+ g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
+}
+
+/* Test whether given host has an AppSocket/HP JetDirect printer connected.
+ See http://en.wikipedia.org/wiki/JetDirect
+ http://www.cups.org/documentation.php/network.html */
+void
+pp_host_get_jetdirect_devices_async (PpHost *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+ JetDirectData *data;
+ g_autoptr(GTask) task = NULL;
+ g_autofree gchar *address = NULL;
+
+ data = g_new0 (JetDirectData, 1);
+ data->host = g_object_ref (self);
+
+ if (priv->port == PP_HOST_UNSET_PORT)
+ data->port = PP_HOST_DEFAULT_JETDIRECT_PORT;
+ else
+ data->port = priv->port;
+
+ task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+ g_task_set_task_data (task, data, (GDestroyNotify) jetdirect_data_free);
+
+ address = g_strdup_printf ("%s:%d", priv->hostname, data->port);
+ if (address != NULL && address[0] != '/')
+ {
+ g_autoptr(GSocketClient) client = NULL;
+
+ client = g_socket_client_new ();
+
+ g_socket_client_connect_to_host_async (client,
+ address,
+ data->port,
+ cancellable,
+ jetdirect_connection_test_cb,
+ g_steal_pointer (&task));
+ }
+ else
+ {
+ GPtrArray *devices = g_ptr_array_new_with_free_func (g_object_unref);
+ g_task_return_pointer (task, devices, (GDestroyNotify) g_ptr_array_unref);
+ }
+}
+
+GPtrArray *
+pp_host_get_jetdirect_devices_finish (PpHost *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static gboolean
+test_lpd_queue (GSocketClient *client,
+ gchar *address,
+ gint port,
+ GCancellable *cancellable,
+ gchar *queue_name)
+{
+ g_autoptr(GSocketConnection) connection = NULL;
+ gboolean result = FALSE;
+ g_autoptr(GError) error = NULL;
+
+ connection = g_socket_client_connect_to_host (client,
+ address,
+ port,
+ cancellable,
+ &error);
+
+ if (connection != NULL)
+ {
+ if (G_IS_TCP_CONNECTION (connection))
+ {
+ GOutputStream *output;
+ GInputStream *input;
+ gssize bytes_read, bytes_written;
+ gchar buffer[BUFFER_LENGTH];
+ gint length;
+
+ output = g_io_stream_get_output_stream (G_IO_STREAM (connection));
+ input = g_io_stream_get_input_stream (G_IO_STREAM (connection));
+
+ /* This LPD command is explained in RFC 1179, section 5.2 */
+ length = g_snprintf (buffer, BUFFER_LENGTH, "\2%s\n", queue_name);
+
+ bytes_written = g_output_stream_write (output,
+ buffer,
+ length,
+ NULL,
+ &error);
+
+ if (bytes_written != -1)
+ {
+ bytes_read = g_input_stream_read (input,
+ buffer,
+ BUFFER_LENGTH,
+ NULL,
+ &error);
+
+ if (bytes_read != -1)
+ {
+ if (bytes_read > 0 && buffer[0] == 0)
+ {
+ /* This LPD command is explained in RFC 1179, section 6.1 */
+ length = g_snprintf (buffer, BUFFER_LENGTH, "\1\n");
+
+ bytes_written = g_output_stream_write (output,
+ buffer,
+ length,
+ NULL,
+ &error);
+
+ result = TRUE;
+ }
+ }
+ }
+ }
+
+ g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+ }
+
+ return result;
+}
+
+static void
+_pp_host_get_lpd_devices_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GSocketConnection) connection = NULL;
+ PpHost *self = source_object;
+ PpHostPrivate *priv = pp_host_get_instance_private (self);
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GSocketClient) client = NULL;
+ g_autoptr(GError) error = NULL;
+ GList *candidates = NULL;
+ GList *iter;
+ gchar *found_queue = NULL;
+ gchar *candidate;
+ g_autofree gchar *address = NULL;
+ gint port;
+ gint i;
+
+ if (priv->port == PP_HOST_UNSET_PORT)
+ port = PP_HOST_DEFAULT_LPD_PORT;
+ else
+ port = priv->port;
+
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+
+ address = g_strdup_printf ("%s:%d", priv->hostname, port);
+ if (address == NULL || address[0] == '/')
+ {
+ g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
+ return;
+ }
+
+ client = g_socket_client_new ();
+
+ connection = g_socket_client_connect_to_host (client,
+ address,
+ port,
+ cancellable,
+ &error);
+
+ if (connection != NULL)
+ {
+ g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+
+ /* Most of this list is taken from system-config-printer */
+ candidates = g_list_append (candidates, g_strdup ("PASSTHRU"));
+ candidates = g_list_append (candidates, g_strdup ("AUTO"));
+ candidates = g_list_append (candidates, g_strdup ("BINPS"));
+ candidates = g_list_append (candidates, g_strdup ("RAW"));
+ candidates = g_list_append (candidates, g_strdup ("TEXT"));
+ candidates = g_list_append (candidates, g_strdup ("ps"));
+ candidates = g_list_append (candidates, g_strdup ("lp"));
+ candidates = g_list_append (candidates, g_strdup ("PORT1"));
+
+ for (i = 0; i < 8; i++)
+ {
+ candidates = g_list_append (candidates, g_strdup_printf ("LPT%d", i));
+ candidates = g_list_append (candidates, g_strdup_printf ("LPT%d_PASSTHRU", i));
+ candidates = g_list_append (candidates, g_strdup_printf ("COM%d", i));
+ candidates = g_list_append (candidates, g_strdup_printf ("COM%d_PASSTHRU", i));
+ }
+
+ for (i = 0; i < 50; i++)
+ candidates = g_list_append (candidates, g_strdup_printf ("pr%d", i));
+
+ for (iter = candidates; iter != NULL; iter = iter->next)
+ {
+ candidate = (gchar *) iter->data;
+
+ if (test_lpd_queue (client,
+ address,
+ port,
+ cancellable,
+ candidate))
+ {
+ found_queue = g_strdup (candidate);
+ break;
+ }
+ }
+
+ if (found_queue != NULL)
+ {
+ g_autofree gchar *device_uri = NULL;
+ PpPrintDevice *device;
+
+ device_uri = g_strdup_printf ("lpd://%s:%d/%s",
+ priv->hostname,
+ port,
+ found_queue);
+
+ device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "is-network-device", TRUE,
+ "device-uri", device_uri,
+ /* Translators: The found device is a Line Printer Daemon printer */
+ "device-name", _("LPD Printer"),
+ "host-name", priv->hostname,
+ "host-port", port,
+ "acquisition-method", ACQUISITION_METHOD_LPD,
+ NULL);
+ g_ptr_array_add (devices, device);
+ }
+
+ g_list_free_full (candidates, g_free);
+ }
+
+ g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
+}
+
+void
+pp_host_get_lpd_devices_async (PpHost *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+ g_task_run_in_thread (task, _pp_host_get_lpd_devices_thread);
+}
+
+GPtrArray *
+pp_host_get_lpd_devices_finish (PpHost *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
diff --git a/panels/printers/pp-host.h b/panels/printers/pp-host.h
new file mode 100644
index 0000000..3e29217
--- /dev/null
+++ b/panels/printers/pp-host.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_HOST (pp_host_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PpHost, pp_host, PP, HOST, GObject)
+
+struct _PpHostClass
+{
+ GObjectClass parent_class;
+};
+
+#define PP_HOST_UNSET_PORT -1
+#define PP_HOST_DEFAULT_IPP_PORT 631
+#define PP_HOST_DEFAULT_JETDIRECT_PORT 9100
+#define PP_HOST_DEFAULT_LPD_PORT 515
+
+PpHost *pp_host_new (const gchar *hostname);
+
+void pp_host_get_snmp_devices_async (PpHost *host,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *pp_host_get_snmp_devices_finish (PpHost *host,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_host_get_remote_cups_devices_async (PpHost *host,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *pp_host_get_remote_cups_devices_finish (PpHost *host,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_host_get_jetdirect_devices_async (PpHost *host,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *pp_host_get_jetdirect_devices_finish (PpHost *host,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_host_get_lpd_devices_async (PpHost *host,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *pp_host_get_lpd_devices_finish (PpHost *host,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/panels/printers/pp-ipp-option-widget.c b/panels/printers/pp-ipp-option-widget.c
new file mode 100644
index 0000000..d9c79d3
--- /dev/null
+++ b/panels/printers/pp-ipp-option-widget.c
@@ -0,0 +1,567 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <glib/gi18n-lib.h>
+
+#include "pp-ipp-option-widget.h"
+#include "pp-utils.h"
+
+static void pp_ipp_option_widget_finalize (GObject *object);
+
+static gboolean construct_widget (PpIPPOptionWidget *self);
+static void update_widget (PpIPPOptionWidget *self);
+static void update_widget_real (PpIPPOptionWidget *self);
+
+struct _PpIPPOptionWidget
+{
+ GtkBox parent_instance;
+
+ GtkWidget *switch_button;
+ GtkWidget *spin_button;
+ GtkWidget *combo;
+
+ IPPAttribute *option_supported;
+ IPPAttribute *option_default;
+
+ gchar *printer_name;
+ gchar *option_name;
+
+ GHashTable *ipp_attribute;
+
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (PpIPPOptionWidget, pp_ipp_option_widget, GTK_TYPE_BOX)
+
+static const struct {
+ const char *keyword;
+ const char *choice;
+ const char *translation;
+} ipp_choice_translations[] = {
+ /* Translators: this is an option of "Two Sided" */
+ { "sides", "one-sided", N_("One Sided") },
+ /* Translators: this is an option of "Two Sided" */
+ { "sides", "two-sided-long-edge", N_("Long Edge (Standard)") },
+ /* Translators: this is an option of "Two Sided" */
+ { "sides", "two-sided-short-edge", N_("Short Edge (Flip)") },
+ /* Translators: this is an option of "Orientation" */
+ { "orientation-requested", "3", N_("Portrait") },
+ /* Translators: this is an option of "Orientation" */
+ { "orientation-requested", "4", N_("Landscape") },
+ /* Translators: this is an option of "Orientation" */
+ { "orientation-requested", "5", N_("Reverse landscape") },
+ /* Translators: this is an option of "Orientation" */
+ { "orientation-requested", "6", N_("Reverse portrait") },
+};
+
+static const gchar *
+ipp_choice_translate (const gchar *option,
+ const gchar *choice)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ipp_choice_translations); i++)
+ {
+ if (g_strcmp0 (ipp_choice_translations[i].keyword, option) == 0 &&
+ g_strcmp0 (ipp_choice_translations[i].choice, choice) == 0)
+ return _(ipp_choice_translations[i].translation);
+ }
+
+ return choice;
+}
+
+static void
+pp_ipp_option_widget_class_init (PpIPPOptionWidgetClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pp_ipp_option_widget_finalize;
+}
+
+static void
+pp_ipp_option_widget_init (PpIPPOptionWidget *self)
+{
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_HORIZONTAL);
+}
+
+static void
+pp_ipp_option_widget_finalize (GObject *object)
+{
+ PpIPPOptionWidget *self = PP_IPP_OPTION_WIDGET (object);
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_pointer (&self->option_name, g_free);
+ g_clear_pointer (&self->printer_name, g_free);
+ g_clear_pointer (&self->option_supported, ipp_attribute_free);
+ g_clear_pointer (&self->option_default, ipp_attribute_free);
+ g_clear_pointer (&self->ipp_attribute, g_hash_table_unref);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (pp_ipp_option_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+pp_ipp_option_widget_new (IPPAttribute *attr_supported,
+ IPPAttribute *attr_default,
+ const gchar *option_name,
+ const gchar *printer)
+{
+ PpIPPOptionWidget *self = NULL;
+
+ if (attr_supported && option_name && printer)
+ {
+ self = g_object_new (PP_TYPE_IPP_OPTION_WIDGET, NULL);
+
+ self->printer_name = g_strdup (printer);
+ self->option_name = g_strdup (option_name);
+ self->option_supported = ipp_attribute_copy (attr_supported);
+ self->option_default = ipp_attribute_copy (attr_default);
+
+ if (construct_widget (self))
+ {
+ update_widget_real (self);
+ }
+ else
+ {
+ g_object_ref_sink (self);
+ g_object_unref (self);
+ self = NULL;
+ }
+ }
+
+ return (GtkWidget *) self;
+}
+
+enum {
+ NAME_COLUMN,
+ VALUE_COLUMN,
+ N_COLUMNS
+};
+
+static GtkWidget *
+combo_box_new (void)
+{
+ GtkCellRenderer *cell;
+ GtkListStore *store;
+ GtkWidget *combo_box;
+
+ combo_box = gtk_combo_box_new ();
+
+ store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
+ "text", NAME_COLUMN,
+ NULL);
+
+ return combo_box;
+}
+
+static void
+combo_box_append (GtkWidget *combo,
+ const gchar *display_text,
+ const gchar *value)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+ store = GTK_LIST_STORE (model);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, display_text,
+ VALUE_COLUMN, value,
+ -1);
+}
+
+struct ComboSet {
+ GtkComboBox *combo;
+ const gchar *value;
+};
+
+static gboolean
+set_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ struct ComboSet *set_data = data;
+ g_autofree gchar *value = NULL;
+
+ gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
+ if (strcmp (value, set_data->value) == 0)
+ {
+ gtk_combo_box_set_active_iter (set_data->combo, iter);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+combo_box_set (GtkWidget *combo,
+ const gchar *value)
+{
+ struct ComboSet set_data;
+ GtkTreeModel *model;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ set_data.combo = GTK_COMBO_BOX (combo);
+ set_data.value = value;
+ gtk_tree_model_foreach (model, set_cb, &set_data);
+}
+
+static char *
+combo_box_get (GtkWidget *combo)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *value = NULL;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+ gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
+
+ return value;
+}
+
+static void
+printer_add_option_async_cb (gboolean success,
+ gpointer user_data)
+{
+ PpIPPOptionWidget *self = user_data;
+
+ update_widget (user_data);
+ g_clear_object (&self->cancellable);
+}
+
+static void
+switch_changed_cb (PpIPPOptionWidget *self)
+{
+ gchar **values;
+
+ values = g_new0 (gchar *, 2);
+
+ if (gtk_switch_get_active (GTK_SWITCH (self->switch_button)))
+ values[0] = g_strdup ("True");
+ else
+ values[0] = g_strdup ("False");
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_object_unref (self->cancellable);
+ }
+
+ self->cancellable = g_cancellable_new ();
+ printer_add_option_async (self->printer_name,
+ self->option_name,
+ values,
+ TRUE,
+ self->cancellable,
+ printer_add_option_async_cb,
+ self);
+
+ g_strfreev (values);
+}
+
+static void
+combo_changed_cb (PpIPPOptionWidget *self)
+{
+ gchar **values;
+
+ values = g_new0 (gchar *, 2);
+ values[0] = combo_box_get (self->combo);
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_object_unref (self->cancellable);
+ }
+
+ self->cancellable = g_cancellable_new ();
+ printer_add_option_async (self->printer_name,
+ self->option_name,
+ values,
+ TRUE,
+ self->cancellable,
+ printer_add_option_async_cb,
+ self);
+
+ g_strfreev (values);
+}
+
+static void
+spin_button_changed_cb (PpIPPOptionWidget *self)
+{
+ gchar **values;
+
+ values = g_new0 (gchar *, 2);
+ values[0] = g_strdup_printf ("%d", gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->spin_button)));
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_object_unref (self->cancellable);
+ }
+
+ self->cancellable = g_cancellable_new ();
+ printer_add_option_async (self->printer_name,
+ self->option_name,
+ values,
+ TRUE,
+ self->cancellable,
+ printer_add_option_async_cb,
+ self);
+
+ g_strfreev (values);
+}
+
+static gboolean
+construct_widget (PpIPPOptionWidget *self)
+{
+ gboolean trivial_option = FALSE;
+ gboolean result = FALSE;
+ gint i;
+
+ if (self->option_supported)
+ {
+ switch (self->option_supported->attribute_type)
+ {
+ case IPP_ATTRIBUTE_TYPE_INTEGER:
+ if (self->option_supported->num_of_values <= 1)
+ trivial_option = TRUE;
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_STRING:
+ if (self->option_supported->num_of_values <= 1)
+ trivial_option = TRUE;
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_RANGE:
+ if (self->option_supported->attribute_values[0].lower_range ==
+ self->option_supported->attribute_values[0].upper_range)
+ trivial_option = TRUE;
+ break;
+ }
+
+ if (!trivial_option)
+ {
+ switch (self->option_supported->attribute_type)
+ {
+ case IPP_ATTRIBUTE_TYPE_BOOLEAN:
+ self->switch_button = gtk_switch_new ();
+
+ gtk_box_pack_start (GTK_BOX (self), self->switch_button, FALSE, FALSE, 0);
+ g_signal_connect_object (self->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), self, G_CONNECT_SWAPPED);
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_INTEGER:
+ self->combo = combo_box_new ();
+
+ for (i = 0; i < self->option_supported->num_of_values; i++)
+ {
+ g_autofree gchar *value = NULL;
+
+ value = g_strdup_printf ("%d", self->option_supported->attribute_values[i].integer_value);
+ combo_box_append (self->combo,
+ ipp_choice_translate (self->option_name,
+ value),
+ value);
+ }
+
+ gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
+ g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_STRING:
+ self->combo = combo_box_new ();
+
+ for (i = 0; i < self->option_supported->num_of_values; i++)
+ combo_box_append (self->combo,
+ ipp_choice_translate (self->option_name,
+ self->option_supported->attribute_values[i].string_value),
+ self->option_supported->attribute_values[i].string_value);
+
+ gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
+ g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_RANGE:
+ self->spin_button = gtk_spin_button_new_with_range (
+ self->option_supported->attribute_values[0].lower_range,
+ self->option_supported->attribute_values[0].upper_range,
+ 1);
+
+ gtk_box_pack_start (GTK_BOX (self), self->spin_button, FALSE, FALSE, 0);
+ g_signal_connect_object (self->spin_button, "value-changed", G_CALLBACK (spin_button_changed_cb), self, G_CONNECT_SWAPPED);
+ break;
+
+ default:
+ break;
+ }
+
+ result = TRUE;
+ }
+ }
+
+ return result;
+}
+
+static void
+update_widget_real (PpIPPOptionWidget *self)
+{
+ IPPAttribute *attr = NULL;
+
+ if (self->option_default)
+ {
+ attr = ipp_attribute_copy (self->option_default);
+
+ ipp_attribute_free (self->option_default);
+ self->option_default = NULL;
+ }
+ else if (self->ipp_attribute)
+ {
+ g_autofree gchar *attr_name = g_strdup_printf ("%s-default", self->option_name);
+ attr = ipp_attribute_copy (g_hash_table_lookup (self->ipp_attribute, attr_name));
+
+ g_hash_table_unref (self->ipp_attribute);
+ self->ipp_attribute = NULL;
+ }
+
+ switch (self->option_supported->attribute_type)
+ {
+ case IPP_ATTRIBUTE_TYPE_BOOLEAN:
+ g_signal_handlers_block_by_func (self->switch_button, switch_changed_cb, self);
+
+ if (attr && attr->num_of_values > 0 &&
+ attr->attribute_type == IPP_ATTRIBUTE_TYPE_BOOLEAN)
+ {
+ gtk_switch_set_active (GTK_SWITCH (self->switch_button),
+ attr->attribute_values[0].boolean_value);
+ }
+
+ g_signal_handlers_unblock_by_func (self->switch_button, switch_changed_cb, self);
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_INTEGER:
+ g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
+
+ if (attr && attr->num_of_values > 0 &&
+ attr->attribute_type == IPP_ATTRIBUTE_TYPE_INTEGER)
+ {
+ g_autofree gchar *value = g_strdup_printf ("%d", attr->attribute_values[0].integer_value);
+ combo_box_set (self->combo, value);
+ }
+ else
+ {
+ g_autofree gchar *value = g_strdup_printf ("%d", self->option_supported->attribute_values[0].integer_value);
+ combo_box_set (self->combo, value);
+ }
+
+ g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_STRING:
+ g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
+
+ if (attr && attr->num_of_values > 0 &&
+ attr->attribute_type == IPP_ATTRIBUTE_TYPE_STRING)
+ {
+ combo_box_set (self->combo, attr->attribute_values[0].string_value);
+ }
+ else
+ {
+ combo_box_set (self->combo, self->option_supported->attribute_values[0].string_value);
+ }
+
+ g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
+ break;
+
+ case IPP_ATTRIBUTE_TYPE_RANGE:
+ g_signal_handlers_block_by_func (self->spin_button, spin_button_changed_cb, self);
+
+ if (attr && attr->num_of_values > 0 &&
+ attr->attribute_type == IPP_ATTRIBUTE_TYPE_INTEGER)
+ {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->spin_button),
+ attr->attribute_values[0].integer_value);
+ }
+ else
+ {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->spin_button),
+ self->option_supported->attribute_values[0].lower_range);
+ }
+
+ g_signal_handlers_unblock_by_func (self->spin_button, spin_button_changed_cb, self);
+ break;
+
+ default:
+ break;
+ }
+
+ ipp_attribute_free (attr);
+}
+
+static void
+get_ipp_attributes_cb (GHashTable *table,
+ gpointer user_data)
+{
+ PpIPPOptionWidget *self = user_data;
+
+ if (self->ipp_attribute)
+ g_hash_table_unref (self->ipp_attribute);
+
+ self->ipp_attribute = table;
+
+ update_widget_real (self);
+}
+
+static void
+update_widget (PpIPPOptionWidget *self)
+{
+ gchar **attributes_names;
+
+ attributes_names = g_new0 (gchar *, 2);
+ attributes_names[0] = g_strdup_printf ("%s-default", self->option_name);
+
+ get_ipp_attributes_async (self->printer_name,
+ attributes_names,
+ get_ipp_attributes_cb,
+ self);
+
+ g_strfreev (attributes_names);
+}
diff --git a/panels/printers/pp-ipp-option-widget.h b/panels/printers/pp-ipp-option-widget.h
new file mode 100644
index 0000000..a5d0b84
--- /dev/null
+++ b/panels/printers/pp-ipp-option-widget.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_IPP_OPTION_WIDGET (pp_ipp_option_widget_get_type ())
+G_DECLARE_FINAL_TYPE (PpIPPOptionWidget, pp_ipp_option_widget, PP, IPP_OPTION_WIDGET, GtkBox)
+
+GtkWidget *pp_ipp_option_widget_new (IPPAttribute *attr_supported,
+ IPPAttribute *attr_default,
+ const gchar *option_name,
+ const gchar *printer);
+
+G_END_DECLS
diff --git a/panels/printers/pp-job.c b/panels/printers/pp-job.c
new file mode 100644
index 0000000..81e4341
--- /dev/null
+++ b/panels/printers/pp-job.c
@@ -0,0 +1,470 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2015 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Felipe Borges <feborges@redhat.com>
+ */
+
+#include "pp-job.h"
+
+#include <gio/gio.h>
+#include <cups/cups.h>
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetBoolean(attr, element) attr->values[element].boolean
+#define ippGetCount(attr) attr->num_values
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+#define ippGetValueTag(attr) attr->value_tag
+static int
+ippGetRange (ipp_attribute_t *attr,
+ int element,
+ int *upper)
+{
+ *upper = attr->values[element].range.upper;
+ return (attr->values[element].range.lower);
+}
+#endif
+
+struct _PpJob
+{
+ GObject parent_instance;
+
+ gint id;
+ gchar *title;
+ gint state;
+ gchar **auth_info_required;
+};
+
+G_DEFINE_TYPE (PpJob, pp_job, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_ID,
+ PROP_TITLE,
+ PROP_STATE,
+ PROP_AUTH_INFO_REQUIRED,
+ LAST_PROPERTY
+};
+
+static GParamSpec *properties[LAST_PROPERTY];
+
+static void
+pp_job_cancel_purge_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ NULL);
+ g_object_unref (source_object);
+}
+
+void
+pp_job_cancel_purge_async (PpJob *self,
+ gboolean job_purge)
+{
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+ gint *job_id;
+
+ g_object_get (self, "id", &job_id, NULL);
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get session bus: %s", error->message);
+ return;
+ }
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "JobCancelPurge",
+ g_variant_new ("(ib)",
+ job_id,
+ job_purge),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ pp_job_cancel_purge_async_dbus_cb,
+ NULL);
+}
+
+static void
+pp_job_set_hold_until_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ NULL);
+ g_object_unref (source_object);
+}
+
+void
+pp_job_set_hold_until_async (PpJob *self,
+ const gchar *job_hold_until)
+{
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+ gint *job_id;
+
+ g_object_get (self, "id", &job_id, NULL);
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get session bus: %s", error->message);
+ return;
+ }
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "JobSetHoldUntil",
+ g_variant_new ("(is)",
+ job_id,
+ job_hold_until),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ pp_job_set_hold_until_async_dbus_cb,
+ NULL);
+}
+
+static void
+pp_job_init (PpJob *obj)
+{
+}
+
+static void
+pp_job_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PpJob *self = PP_JOB (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_int (value, self->id);
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+ case PROP_STATE:
+ g_value_set_int (value, self->state);
+ break;
+ case PROP_AUTH_INFO_REQUIRED:
+ g_value_set_pointer (value, g_strdupv (self->auth_info_required));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pp_job_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PpJob *self = PP_JOB (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ self->id = g_value_get_int (value);
+ break;
+ case PROP_TITLE:
+ g_free (self->title);
+ self->title = g_value_dup_string (value);
+ break;
+ case PROP_STATE:
+ self->state = g_value_get_int (value);
+ break;
+ case PROP_AUTH_INFO_REQUIRED:
+ self->auth_info_required = g_strdupv (g_value_get_pointer (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pp_job_finalize (GObject *object)
+{
+ PpJob *self = PP_JOB (object);
+
+ g_clear_pointer (&self->title, g_free);
+ g_clear_pointer (&self->auth_info_required, g_strfreev);
+
+ G_OBJECT_CLASS (pp_job_parent_class)->finalize (object);
+}
+
+static void
+pp_job_class_init (PpJobClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->get_property = pp_job_get_property;
+ object_class->set_property = pp_job_set_property;
+ object_class->finalize = pp_job_finalize;
+
+ properties[PROP_ID] = g_param_spec_int ("id",
+ "Id",
+ "Job id",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE);
+ properties[PROP_TITLE] = g_param_spec_string ("title",
+ "Title",
+ "Title of this print job",
+ NULL,
+ G_PARAM_READWRITE);
+ properties[PROP_STATE] = g_param_spec_int ("state",
+ "State",
+ "State of this print job (Paused, Completed, Cancelled,...)",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE);
+ properties[PROP_AUTH_INFO_REQUIRED] = g_param_spec_pointer ("auth-info-required",
+ "Authentication info required",
+ "Which authentication info is required for this print job",
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
+}
+
+static void
+_pp_job_get_attributes_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpJob *self = PP_JOB (source_object);
+ ipp_attribute_t *attr = NULL;
+ GVariantBuilder builder;
+ GVariant *attributes = NULL;
+ gchar **attributes_names = task_data;
+ ipp_t *request;
+ ipp_t *response = NULL;
+ g_autofree gchar *job_uri = NULL;
+ gint i, j, length = 0, n_attrs = 0;
+
+ job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", self->id);
+
+ if (attributes_names != NULL)
+ {
+ length = g_strv_length (attributes_names);
+
+ request = ippNewRequest (IPP_GET_JOB_ATTRIBUTES);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "job-uri", NULL, job_uri);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", length, NULL, (const char **) attributes_names);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+ }
+
+ if (response != NULL)
+ {
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ for (j = 0; j < length; j++)
+ {
+ attr = ippFindAttribute (response, attributes_names[j], IPP_TAG_ZERO);
+ n_attrs = ippGetCount (attr);
+ if (attr != NULL && n_attrs > 0 && ippGetValueTag (attr) != IPP_TAG_NOVALUE)
+ {
+ const GVariantType *type = NULL;
+ GVariant **values;
+ GVariant *range[2];
+ gint range_uppervalue;
+
+ values = g_new (GVariant*, n_attrs);
+
+ switch (ippGetValueTag (attr))
+ {
+ case IPP_TAG_INTEGER:
+ case IPP_TAG_ENUM:
+ type = G_VARIANT_TYPE_INT32;
+
+ for (i = 0; i < n_attrs; i++)
+ values[i] = g_variant_new_int32 (ippGetInteger (attr, i));
+ break;
+
+ case IPP_TAG_NAME:
+ case IPP_TAG_STRING:
+ case IPP_TAG_TEXT:
+ case IPP_TAG_URI:
+ case IPP_TAG_KEYWORD:
+ case IPP_TAG_URISCHEME:
+ type = G_VARIANT_TYPE_STRING;
+
+ for (i = 0; i < n_attrs; i++)
+ values[i] = g_variant_new_string (ippGetString (attr, i, NULL));
+ break;
+
+ case IPP_TAG_RANGE:
+ type = G_VARIANT_TYPE_TUPLE;
+
+ for (i = 0; i < n_attrs; i++)
+ {
+ range[0] = g_variant_new_int32 (ippGetRange (attr, i, &(range_uppervalue)));
+ range[1] = g_variant_new_int32 (range_uppervalue);
+
+ values[i] = g_variant_new_tuple (range, 2);
+ }
+ break;
+
+ case IPP_TAG_BOOLEAN:
+ type = G_VARIANT_TYPE_BOOLEAN;
+
+ for (i = 0; i < n_attrs; i++)
+ values[i] = g_variant_new_boolean (ippGetBoolean (attr, i));
+ break;
+
+ default:
+ /* do nothing (switch w/ enumeration type) */
+ break;
+ }
+
+ if (type != NULL)
+ {
+ g_variant_builder_add (&builder, "{sv}",
+ attributes_names[j],
+ g_variant_new_array (type, values, n_attrs));
+ }
+
+ g_free (values);
+ }
+ }
+
+ attributes = g_variant_builder_end (&builder);
+ }
+
+ g_task_return_pointer (task, attributes, (GDestroyNotify) g_variant_unref);
+}
+
+void
+pp_job_get_attributes_async (PpJob *self,
+ gchar **attributes_names,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, g_strdupv (attributes_names), (GDestroyNotify) g_strfreev);
+ g_task_run_in_thread (task, _pp_job_get_attributes_thread);
+
+ g_object_unref (task);
+}
+
+GVariant *
+pp_job_get_attributes_finish (PpJob *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+_pp_job_authenticate_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpJob *self = source_object;
+ gboolean result = FALSE;
+ gchar **auth_info = task_data;
+ ipp_t *request;
+ ipp_t *response = NULL;
+ gint length;
+
+ if (auth_info != NULL)
+ {
+ g_autofree gchar *job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", self->id);
+
+ length = g_strv_length (auth_info);
+
+ request = ippNewRequest (IPP_OP_CUPS_AUTHENTICATE_JOB);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "job-uri", NULL, job_uri);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
+ "auth-info", length, NULL, (const char **) auth_info);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+
+ result = response != NULL && ippGetStatusCode (response) <= IPP_OK;
+
+ if (response != NULL)
+ ippDelete (response);
+ }
+
+ g_task_return_boolean (task, result);
+}
+
+void
+pp_job_authenticate_async (PpJob *self,
+ gchar **auth_info,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, g_strdupv (auth_info), (GDestroyNotify) g_strfreev);
+ g_task_run_in_thread (task, _pp_job_authenticate_thread);
+
+ g_object_unref (task);
+}
+
+gboolean
+pp_job_authenticate_finish (PpJob *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/panels/printers/pp-job.h b/panels/printers/pp-job.h
new file mode 100644
index 0000000..fb4105f
--- /dev/null
+++ b/panels/printers/pp-job.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2015 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Felipe Borges <feborges@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_JOB (pp_job_get_type ())
+G_DECLARE_FINAL_TYPE (PpJob, pp_job, PP, JOB, GObject)
+
+void pp_job_set_hold_until_async (PpJob *job,
+ const gchar *job_hold_until);
+
+void pp_job_cancel_purge_async (PpJob *job,
+ gboolean job_purge);
+
+void pp_job_get_attributes_async (PpJob *job,
+ gchar **attributes_names,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GVariant *pp_job_get_attributes_finish (PpJob *job,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_job_authenticate_async (PpJob *job,
+ gchar **auth_info,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_job_authenticate_finish (PpJob *job,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/panels/printers/pp-jobs-dialog.c b/panels/printers/pp-jobs-dialog.c
new file mode 100644
index 0000000..2235ffc
--- /dev/null
+++ b/panels/printers/pp-jobs-dialog.c
@@ -0,0 +1,603 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <gdesktop-enums.h>
+
+#include <cups/cups.h>
+
+#include "list-box-helper.h"
+#include "pp-jobs-dialog.h"
+#include "pp-utils.h"
+#include "pp-job.h"
+#include "pp-cups.h"
+#include "pp-printer.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+#define CLOCK_SCHEMA "org.gnome.desktop.interface"
+#define CLOCK_FORMAT_KEY "clock-format"
+
+struct _PpJobsDialog {
+ GtkDialog parent_instance;
+
+ GtkButton *authenticate_button;
+ GtkMenuButton *authenticate_jobs_button;
+ GtkLabel *authenticate_jobs_label;
+ GtkInfoBar *authentication_infobar;
+ GtkLabel *authentication_label;
+ GtkEntry *domain_entry;
+ GtkLabel *domain_label;
+ GtkButton *jobs_clear_all_button;
+ GtkListBox *jobs_listbox;
+ GtkScrolledWindow *list_jobs_page;
+ GtkBox *no_jobs_page;
+ GtkEntry *password_entry;
+ GtkLabel *password_label;
+ GtkStack *stack;
+ GListStore *store;
+ GtkEntry *username_entry;
+ GtkLabel *username_label;
+
+ gchar *printer_name;
+
+ gchar **actual_auth_info_required;
+ gboolean jobs_filled;
+ gboolean pop_up_authentication_popup;
+
+ GCancellable *get_jobs_cancellable;
+};
+
+G_DEFINE_TYPE (PpJobsDialog, pp_jobs_dialog, GTK_TYPE_DIALOG)
+
+static gboolean
+is_info_required (PpJobsDialog *self,
+ const gchar *info)
+{
+ gint i;
+
+ if (self->actual_auth_info_required == NULL)
+ return FALSE;
+
+ for (i = 0; self->actual_auth_info_required[i] != NULL; i++)
+ if (g_strcmp0 (self->actual_auth_info_required[i], info) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+is_domain_required (PpJobsDialog *self)
+{
+ return is_info_required (self, "domain");
+}
+
+static gboolean
+is_username_required (PpJobsDialog *self)
+{
+ return is_info_required (self, "username");
+}
+
+static gboolean
+is_password_required (PpJobsDialog *self)
+{
+ return is_info_required (self, "password");
+}
+
+static gboolean
+auth_popup_filled (PpJobsDialog *self)
+{
+ gboolean domain_required;
+ gboolean username_required;
+ gboolean password_required;
+ guint16 domain_length;
+ guint16 username_length;
+ guint16 password_length;
+
+ domain_required = is_domain_required (self);
+ username_required = is_username_required (self);
+ password_required = is_password_required (self);
+
+ domain_length = gtk_entry_get_text_length (self->domain_entry);
+ username_length = gtk_entry_get_text_length (self->username_entry);
+ password_length = gtk_entry_get_text_length (self->password_entry);
+
+ return (!domain_required || domain_length > 0) &&
+ (!username_required || username_length > 0) &&
+ (!password_required || password_length > 0);
+}
+
+static void
+auth_entries_changed (PpJobsDialog *self)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (self->authenticate_button), auth_popup_filled (self));
+}
+
+static void
+auth_entries_activated (PpJobsDialog *self)
+{
+ if (auth_popup_filled (self))
+ gtk_button_clicked (self->authenticate_button);
+}
+
+static void
+authenticate_popover_update (PpJobsDialog *self)
+{
+ gboolean domain_required;
+ gboolean username_required;
+ gboolean password_required;
+
+ domain_required = is_domain_required (self);
+ username_required = is_username_required (self);
+ password_required = is_password_required (self);
+
+ gtk_widget_set_visible (GTK_WIDGET (self->domain_label), domain_required);
+ gtk_widget_set_visible (GTK_WIDGET (self->domain_entry), domain_required);
+ if (domain_required)
+ gtk_entry_set_text (self->domain_entry, "");
+
+ gtk_widget_set_visible (GTK_WIDGET (self->username_label), username_required);
+ gtk_widget_set_visible (GTK_WIDGET (self->username_entry), username_required);
+ if (username_required)
+ gtk_entry_set_text (self->username_entry, cupsUser ());
+
+ gtk_widget_set_visible (GTK_WIDGET (self->password_label), password_required);
+ gtk_widget_set_visible (GTK_WIDGET (self->password_entry), password_required);
+ if (password_required)
+ gtk_entry_set_text (self->password_entry, "");
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->authenticate_button), FALSE);
+}
+
+static void
+job_stop_cb (GtkButton *button,
+ PpJob *job)
+{
+ pp_job_cancel_purge_async (job, FALSE);
+}
+
+static void
+job_pause_cb (GtkButton *button,
+ PpJob *job)
+{
+ gint job_state;
+
+ g_object_get (job, "state", &job_state, NULL);
+
+ pp_job_set_hold_until_async (job, job_state == IPP_JOB_HELD ? "no-hold" : "indefinite");
+
+ gtk_button_set_image (button,
+ gtk_image_new_from_icon_name (job_state == IPP_JOB_HELD ?
+ "media-playback-pause-symbolic" : "media-playback-start-symbolic",
+ GTK_ICON_SIZE_SMALL_TOOLBAR));
+}
+
+static GtkWidget *
+create_listbox_row (gpointer item,
+ gpointer user_data)
+{
+ 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,
+ "auth-info-required", &auth_info_required,
+ NULL);
+
+ switch (job_state)
+ {
+ case IPP_JOB_PENDING:
+ /* Translators: Job's state (job is waiting to be printed) */
+ state_string = g_strdup (C_("print job", "Pending"));
+ break;
+ case IPP_JOB_HELD:
+ 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) */
+ state_string = g_strdup (C_("print job", "Processing"));
+ break;
+ case IPP_JOB_STOPPED:
+ /* Translators: Job's state (job has been stopped) */
+ state_string = g_strdup (C_("print job", "Stopped"));
+ break;
+ case IPP_JOB_CANCELED:
+ /* Translators: Job's state (job has been canceled) */
+ state_string = g_strdup (C_("print job", "Canceled"));
+ break;
+ case IPP_JOB_ABORTED:
+ /* Translators: Job's state (job has aborted due to error) */
+ state_string = g_strdup (C_("print job", "Aborted"));
+ break;
+ case IPP_JOB_COMPLETED:
+ /* Translators: Job's state (job has completed successfully) */
+ state_string = g_strdup (C_("print job", "Completed"));
+ break;
+ }
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ g_object_set (box, "margin", 6, NULL);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 2);
+
+ widget = gtk_label_new (title);
+ gtk_label_set_max_width_chars (GTK_LABEL (widget), 40);
+ gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+ gtk_widget_set_halign (widget, GTK_ALIGN_START);
+ gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 10);
+
+ 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",
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_signal_connect (widget, "clicked", G_CALLBACK (job_stop_cb), item);
+ gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 4);
+
+ gtk_widget_show_all (box);
+
+ return box;
+}
+
+static void
+pop_up_authentication_popup (PpJobsDialog *self)
+{
+ if (self->actual_auth_info_required != NULL)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->authenticate_jobs_button), TRUE);
+}
+
+static void
+update_jobs_list_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PpJobsDialog *self = user_data;
+ PpPrinter *printer = PP_PRINTER (source_object);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) jobs;
+ PpJob *job;
+ gchar **auth_info_required = NULL;
+ gint num_of_auth_jobs = 0;
+ guint i;
+
+ g_list_store_remove_all (self->store);
+
+ jobs = pp_printer_get_jobs_finish (printer, result, &error);
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Could not get jobs: %s", error->message);
+ }
+
+ return;
+ }
+
+ if (jobs->len > 0)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self->jobs_clear_all_button), TRUE);
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->list_jobs_page));
+ }
+ else
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self->jobs_clear_all_button), FALSE);
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->no_jobs_page));
+ }
+
+ for (i = 0; i < jobs->len; i++)
+ {
+ job = PP_JOB (g_ptr_array_index (jobs, i));
+
+ g_list_store_append (self->store, g_object_ref (job));
+
+ g_object_get (G_OBJECT (job),
+ "auth-info-required", &auth_info_required,
+ NULL);
+ if (auth_info_required != NULL)
+ {
+ num_of_auth_jobs++;
+
+ if (self->actual_auth_info_required == NULL)
+ self->actual_auth_info_required = auth_info_required;
+ else
+ g_strfreev (auth_info_required);
+
+ auth_info_required = NULL;
+ }
+ }
+
+ if (num_of_auth_jobs > 0)
+ {
+ g_autofree gchar *text = NULL;
+
+ /* 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 (self->authenticate_jobs_label, text);
+
+ gtk_widget_show (GTK_WIDGET (self->authentication_infobar));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self->authentication_infobar));
+ }
+
+ authenticate_popover_update (self);
+
+ g_clear_object (&self->get_jobs_cancellable);
+
+ if (!self->jobs_filled)
+ {
+ if (self->pop_up_authentication_popup)
+ {
+ pop_up_authentication_popup (self);
+ self->pop_up_authentication_popup = FALSE;
+ }
+
+ self->jobs_filled = TRUE;
+ }
+}
+
+static void
+update_jobs_list (PpJobsDialog *self)
+{
+ PpPrinter *printer;
+
+ if (self->printer_name != NULL)
+ {
+ g_cancellable_cancel (self->get_jobs_cancellable);
+ g_clear_object (&self->get_jobs_cancellable);
+
+ self->get_jobs_cancellable = g_cancellable_new ();
+
+ printer = pp_printer_new (self->printer_name);
+ pp_printer_get_jobs_async (printer,
+ TRUE,
+ CUPS_WHICHJOBS_ACTIVE,
+ self->get_jobs_cancellable,
+ update_jobs_list_cb,
+ self);
+ }
+}
+
+static void
+on_clear_all_button_clicked (PpJobsDialog *self)
+{
+ guint num_items;
+ guint i;
+
+ num_items = g_list_model_get_n_items (G_LIST_MODEL (self->store));
+
+ for (i = 0; i < num_items; i++)
+ {
+ PpJob *job = PP_JOB (g_list_model_get_item (G_LIST_MODEL (self->store), i));
+
+ pp_job_cancel_purge_async (job, FALSE);
+ }
+}
+
+static void
+pp_job_authenticate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpJobsDialog *self = user_data;
+ gboolean result;
+ g_autoptr(GError) error = NULL;
+ PpJob *job = PP_JOB (source_object);
+
+ result = pp_job_authenticate_finish (job, res, &error);
+ if (result)
+ {
+ pp_jobs_dialog_update (self);
+ }
+ 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);
+ }
+ }
+}
+
+static void
+authenticate_button_clicked (PpJobsDialog *self)
+{
+ PpJob *job;
+ gchar **auth_info_required = NULL;
+ gchar **auth_info;
+ guint num_items;
+ gint i;
+
+ auth_info = g_new0 (gchar *, g_strv_length (self->actual_auth_info_required) + 1);
+ for (i = 0; self->actual_auth_info_required[i] != NULL; i++)
+ {
+ if (g_strcmp0 (self->actual_auth_info_required[i], "domain") == 0)
+ auth_info[i] = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->domain_entry)));
+ else if (g_strcmp0 (self->actual_auth_info_required[i], "username") == 0)
+ auth_info[i] = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->username_entry)));
+ else if (g_strcmp0 (self->actual_auth_info_required[i], "password") == 0)
+ auth_info[i] = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->password_entry)));
+ }
+
+ num_items = g_list_model_get_n_items (G_LIST_MODEL (self->store));
+ for (i = 0; i < num_items; i++)
+ {
+ job = PP_JOB (g_list_model_get_item (G_LIST_MODEL (self->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, self);
+
+ g_strfreev (auth_info_required);
+ auth_info_required = NULL;
+ }
+ }
+
+ g_strfreev (auth_info);
+}
+
+static gboolean
+key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_KEY_Escape)
+ gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_CLOSE);
+
+ return FALSE;
+}
+
+PpJobsDialog *
+pp_jobs_dialog_new (const gchar *printer_name)
+{
+ PpJobsDialog *self;
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *title = NULL;
+
+ self = g_object_new (PP_TYPE_JOBS_DIALOG,
+ "use-header-bar", 1,
+ NULL);
+
+ self->printer_name = g_strdup (printer_name);
+ self->actual_auth_info_required = NULL;
+ self->jobs_filled = FALSE;
+ self->pop_up_authentication_popup = FALSE;
+
+ /* connect signals */
+ g_signal_connect (self, "key-press-event", G_CALLBACK (key_press_event_cb), NULL);
+
+ /* 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 (self), title);
+
+ /* Translators: The printer needs authentication info to print. */
+ text = g_strdup_printf (_("Enter credentials to print from %s."), printer_name);
+ gtk_label_set_text (self->authentication_label, text);
+
+ gtk_list_box_set_header_func (self->jobs_listbox,
+ cc_list_box_update_header_func, NULL, NULL);
+ self->store = g_list_store_new (pp_job_get_type ());
+ gtk_list_box_bind_model (self->jobs_listbox, G_LIST_MODEL (self->store),
+ create_listbox_row, NULL, NULL);
+
+ update_jobs_list (self);
+
+ return self;
+}
+
+void
+pp_jobs_dialog_update (PpJobsDialog *self)
+{
+ update_jobs_list (self);
+}
+
+void
+pp_jobs_dialog_authenticate_jobs (PpJobsDialog *self)
+{
+ if (self->jobs_filled)
+ pop_up_authentication_popup (self);
+ else
+ self->pop_up_authentication_popup = TRUE;
+}
+
+static void
+pp_jobs_dialog_init (PpJobsDialog *dialog)
+{
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+}
+
+static void
+pp_jobs_dialog_dispose (GObject *object)
+{
+ PpJobsDialog *self = PP_JOBS_DIALOG (object);
+
+ g_cancellable_cancel (self->get_jobs_cancellable);
+ g_clear_object (&self->get_jobs_cancellable);
+ g_clear_pointer (&self->actual_auth_info_required, g_strfreev);
+ g_clear_pointer (&self->printer_name, g_free);
+
+ G_OBJECT_CLASS (pp_jobs_dialog_parent_class)->dispose (object);
+}
+
+static void
+pp_jobs_dialog_class_init (PpJobsDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/printers/pp-jobs-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, authenticate_button);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, authenticate_jobs_button);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, authenticate_jobs_label);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, authentication_infobar);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, authentication_label);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, domain_entry);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, domain_label);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, jobs_clear_all_button);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, jobs_listbox);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, list_jobs_page);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, no_jobs_page);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, password_label);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, stack);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, username_entry);
+ gtk_widget_class_bind_template_child (widget_class, PpJobsDialog, username_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, authenticate_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_clear_all_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, auth_entries_activated);
+ gtk_widget_class_bind_template_callback (widget_class, auth_entries_changed);
+
+ object_class->dispose = pp_jobs_dialog_dispose;
+}
diff --git a/panels/printers/pp-jobs-dialog.h b/panels/printers/pp-jobs-dialog.h
new file mode 100644
index 0000000..c61354b
--- /dev/null
+++ b/panels/printers/pp-jobs-dialog.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_JOBS_DIALOG (pp_jobs_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (PpJobsDialog, pp_jobs_dialog, PP, JOBS_DIALOG, GtkDialog)
+
+PpJobsDialog *pp_jobs_dialog_new (const gchar *printer_name);
+void pp_jobs_dialog_update (PpJobsDialog *dialog);
+void pp_jobs_dialog_authenticate_jobs (PpJobsDialog *dialog);
+
+G_END_DECLS
diff --git a/panels/printers/pp-jobs-dialog.ui b/panels/printers/pp-jobs-dialog.ui
new file mode 100644
index 0000000..660580e
--- /dev/null
+++ b/panels/printers/pp-jobs-dialog.ui
@@ -0,0 +1,387 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.2 -->
+<interface>
+ <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>
+ <signal name="changed" handler="auth_entries_changed" swapped="yes"/>
+ <signal name="activate" handler="auth_entries_activated" swapped="yes"/>
+ </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>
+ <signal name="changed" handler="auth_entries_changed" swapped="yes"/>
+ <signal name="activate" handler="auth_entries_activated" swapped="yes"/>
+ </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>
+ <signal name="changed" handler="auth_entries_changed" swapped="yes"/>
+ <signal name="activate" handler="auth_entries_activated" swapped="yes"/>
+ </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>
+ <signal name="clicked" handler="authenticate_button_clicked" swapped="yes"/>
+ </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>
+ <template class="PpJobsDialog" parent="GtkDialog">
+ <property name="width_request">720</property>
+ <property name="height_request">500</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">0</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkButton" id="jobs_clear_all_button">
+ <property name="label" translatable="yes" comments="Translators: this action removes (purges) all the listed jobs from the list.">Clear All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="on_clear_all_button_clicked" swapped="yes"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="border_width">0</property>
+ <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="list_jobs_page">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="vexpand">True</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkListBox" id="jobs_listbox">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">fill</property>
+ <property name="valign">fill</property>
+ <property name="selection-mode">none</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="no_jobs_page">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="pixel_size">64</property>
+ <property name="icon_name">printer-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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="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>
+ </template>
+</interface>
diff --git a/panels/printers/pp-maintenance-command.c b/panels/printers/pp-maintenance-command.c
new file mode 100644
index 0000000..feb144c
--- /dev/null
+++ b/panels/printers/pp-maintenance-command.c
@@ -0,0 +1,394 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include <glib/gstdio.h>
+
+#include "pp-maintenance-command.h"
+
+#include "pp-utils.h"
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetCount(attr) attr->num_values
+#define ippGetValueTag(attr) attr->value_tag
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetString(attr, element, language) attr->values[element].string.text
+#endif
+
+struct _PpMaintenanceCommand
+{
+ GObject parent_instance;
+
+ gchar *printer_name;
+ gchar *command;
+ gchar *parameters;
+ gchar *title;
+};
+
+G_DEFINE_TYPE (PpMaintenanceCommand, pp_maintenance_command, G_TYPE_OBJECT);
+
+enum {
+ PROP_0 = 0,
+ PROP_PRINTER_NAME,
+ PROP_COMMAND,
+ PROP_PARAMETERS,
+ PROP_TITLE
+};
+
+static void
+pp_maintenance_command_finalize (GObject *object)
+{
+ PpMaintenanceCommand *self = PP_MAINTENANCE_COMMAND (object);
+
+ g_clear_pointer (&self->printer_name, g_free);
+ g_clear_pointer (&self->command, g_free);
+ g_clear_pointer (&self->parameters, g_free);
+ g_clear_pointer (&self->title, g_free);
+
+ G_OBJECT_CLASS (pp_maintenance_command_parent_class)->finalize (object);
+}
+
+static void
+pp_maintenance_command_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *param_spec)
+{
+ PpMaintenanceCommand *self = PP_MAINTENANCE_COMMAND (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRINTER_NAME:
+ g_value_set_string (value, self->printer_name);
+ break;
+ case PROP_COMMAND:
+ g_value_set_string (value, self->command);
+ break;
+ case PROP_PARAMETERS:
+ g_value_set_string (value, self->parameters);
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_maintenance_command_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *param_spec)
+{
+ PpMaintenanceCommand *self = PP_MAINTENANCE_COMMAND (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRINTER_NAME:
+ g_free (self->printer_name);
+ self->printer_name = g_value_dup_string (value);
+ break;
+ case PROP_COMMAND:
+ g_free (self->command);
+ self->command = g_value_dup_string (value);
+ break;
+ case PROP_PARAMETERS:
+ g_free (self->parameters);
+ self->parameters = g_value_dup_string (value);
+ break;
+ case PROP_TITLE:
+ g_free (self->title);
+ self->title = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_maintenance_command_class_init (PpMaintenanceCommandClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pp_maintenance_command_set_property;
+ gobject_class->get_property = pp_maintenance_command_get_property;
+ gobject_class->finalize = pp_maintenance_command_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_PRINTER_NAME,
+ g_param_spec_string ("printer-name",
+ "Printer name",
+ "Name of the printer",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_COMMAND,
+ g_param_spec_string ("command",
+ "Maintenance command",
+ "Command to execute",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_PARAMETERS,
+ g_param_spec_string ("parameters",
+ "Optional parameters",
+ "Optional parameters for the maintenance command",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_TITLE,
+ g_param_spec_string ("title",
+ "Command title",
+ "Title of the job by which the command will be executed",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+pp_maintenance_command_init (PpMaintenanceCommand *self)
+{
+}
+
+PpMaintenanceCommand *
+pp_maintenance_command_new (const gchar *printer_name,
+ const gchar *command,
+ const gchar *parameters,
+ const gchar *title)
+{
+ return g_object_new (PP_TYPE_MAINTENANCE_COMMAND,
+ "printer-name", printer_name,
+ "command", command,
+ "parameters", parameters,
+ "title", title,
+ NULL);
+}
+
+static gboolean _pp_maintenance_command_is_supported (const gchar *printer_name,
+ const gchar *command);
+
+static void
+_pp_maintenance_command_execute_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpMaintenanceCommand *self = PP_MAINTENANCE_COMMAND (source_object);
+ gboolean success = FALSE;
+ GError *error = NULL;
+
+ if (_pp_maintenance_command_is_supported (self->printer_name, self->command))
+ {
+ ipp_t *request;
+ ipp_t *response = NULL;
+ g_autofree gchar *printer_uri = NULL;
+ g_autofree gchar *file_name = NULL;
+ int fd = -1;
+
+ printer_uri = g_strdup_printf ("ipp://localhost/printers/%s",
+ self->printer_name);
+
+ request = ippNewRequest (IPP_PRINT_JOB);
+
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, printer_uri);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "job-name", NULL, self->title);
+ ippAddString (request, IPP_TAG_JOB, IPP_TAG_MIMETYPE,
+ "document-format", NULL, "application/vnd.cups-command");
+
+ fd = g_file_open_tmp ("ccXXXXXX", &file_name, &error);
+
+ if (fd != -1)
+ {
+ FILE *file;
+
+ file = fdopen (fd, "w");
+ fprintf (file, "#CUPS-COMMAND\n");
+ fprintf (file, "%s", self->command);
+ if (self->parameters)
+ fprintf (file, " %s", self->parameters);
+ fprintf (file, "\n");
+ fclose (file);
+
+ response = cupsDoFileRequest (CUPS_HTTP_DEFAULT, request, "/", file_name);
+ g_unlink (file_name);
+
+ if (response != NULL)
+ {
+ if (ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ success = TRUE;
+ }
+
+ ippDelete (response);
+ }
+ }
+ }
+ else
+ {
+ success = TRUE;
+ }
+
+ if (!success)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Execution of maintenance command failed.");
+ }
+
+ g_task_return_boolean (task, success);
+}
+
+void
+pp_maintenance_command_execute_async (PpMaintenanceCommand *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_check_cancellable (task, TRUE);
+ g_task_run_in_thread (task, _pp_maintenance_command_execute_thread);
+
+ g_object_unref (task);
+}
+
+gboolean
+pp_maintenance_command_execute_finish (PpMaintenanceCommand *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+_pp_maintenance_command_is_supported (const gchar *printer_name,
+ const gchar *command)
+{
+ ipp_attribute_t *attr = NULL;
+ gboolean is_supported = FALSE;
+ ipp_t *request;
+ ipp_t *response = NULL;
+ g_autofree gchar *printer_uri = NULL;
+ GPtrArray *available_commands = NULL;
+ int i;
+
+ printer_uri = g_strdup_printf ("ipp://localhost/printers/%s",
+ printer_name);
+
+ request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, printer_uri);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", NULL, "printer-commands");
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+ if (response != NULL)
+ {
+ if (ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ int commands_count;
+
+ attr = ippFindAttribute (response, "printer-commands", IPP_TAG_ZERO);
+ commands_count = attr != NULL ? ippGetCount (attr) : 0;
+ if (commands_count > 0 &&
+ ippGetValueTag (attr) != IPP_TAG_NOVALUE &&
+ (ippGetValueTag (attr) == IPP_TAG_KEYWORD))
+ {
+ available_commands = g_ptr_array_new_full (commands_count, g_free);
+ for (i = 0; i < commands_count; ++i)
+ {
+ /* Array gains ownership of the lower-cased string */
+ g_ptr_array_add (available_commands, g_ascii_strdown (ippGetString (attr, i, NULL), -1));
+ }
+ }
+ }
+
+ ippDelete (response);
+ }
+
+ if (available_commands != NULL)
+ {
+ g_autofree gchar *command_lowercase = g_ascii_strdown (command, -1);
+ for (i = 0; i < available_commands->len; ++i)
+ {
+ const gchar *available_command = g_ptr_array_index (available_commands, i);
+ if (g_strcmp0 (available_command, command_lowercase) == 0)
+ {
+ is_supported = TRUE;
+ break;
+ }
+ }
+
+ g_ptr_array_free (available_commands, TRUE);
+ }
+
+ return is_supported;
+}
+
+static void
+_pp_maintenance_command_is_supported_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpMaintenanceCommand *self = PP_MAINTENANCE_COMMAND (source_object);
+ gboolean success = FALSE;
+
+ success = _pp_maintenance_command_is_supported (self->printer_name, self->command);
+ g_task_return_boolean (task, success);
+}
+
+void
+pp_maintenance_command_is_supported_async (PpMaintenanceCommand *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_check_cancellable (task, TRUE);
+ g_task_run_in_thread (task, _pp_maintenance_command_is_supported_thread);
+
+ g_object_unref (task);
+}
+
+gboolean
+pp_maintenance_command_is_supported_finish (PpMaintenanceCommand *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/panels/printers/pp-maintenance-command.h b/panels/printers/pp-maintenance-command.h
new file mode 100644
index 0000000..b5bacfe
--- /dev/null
+++ b/panels/printers/pp-maintenance-command.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_MAINTENANCE_COMMAND (pp_maintenance_command_get_type ())
+G_DECLARE_FINAL_TYPE (PpMaintenanceCommand, pp_maintenance_command, PP, MAINTENANCE_COMMAND, GObject)
+
+PpMaintenanceCommand *pp_maintenance_command_new (const gchar *printer_name,
+ const gchar *command,
+ const gchar *parameters,
+ const gchar *title);
+
+void pp_maintenance_command_execute_async (PpMaintenanceCommand *command,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_maintenance_command_execute_finish (PpMaintenanceCommand *command,
+ GAsyncResult *result,
+ GError **error);
+void pp_maintenance_command_is_supported_async (PpMaintenanceCommand *command,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_maintenance_command_is_supported_finish (PpMaintenanceCommand *command,
+ GAsyncResult *result,
+ GError **error);
+G_END_DECLS
diff --git a/panels/printers/pp-new-printer-dialog.c b/panels/printers/pp-new-printer-dialog.c
new file mode 100644
index 0000000..7ace4d7
--- /dev/null
+++ b/panels/printers/pp-new-printer-dialog.c
@@ -0,0 +1,2106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include <cups/cups.h>
+
+#include "pp-new-printer-dialog.h"
+#include "pp-ppd-selection-dialog.h"
+#include "pp-utils.h"
+#include "pp-host.h"
+#include "pp-cups.h"
+#include "pp-samba.h"
+#include "pp-new-printer.h"
+
+#include <gdk/gdkx.h>
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetState(ipp) ipp->state
+#endif
+
+/*
+ * Additional delay to the default 150ms delay in GtkSearchEntry
+ * resulting in total delay of 500ms.
+ */
+#define HOST_SEARCH_DELAY (500 - 150)
+
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (self->builder, s))
+
+#define AUTHENTICATION_PAGE "authentication-page"
+#define ADDPRINTER_PAGE "addprinter-page"
+
+static void set_device (PpNewPrinterDialog *self,
+ PpPrintDevice *device,
+ GtkTreeIter *iter);
+static void replace_device (PpNewPrinterDialog *self,
+ PpPrintDevice *old_device,
+ PpPrintDevice *new_device);
+static void populate_devices_list (PpNewPrinterDialog *self);
+static void search_entry_activated_cb (PpNewPrinterDialog *self);
+static void search_entry_changed_cb (PpNewPrinterDialog *self);
+static void new_printer_dialog_response_cb (PpNewPrinterDialog *self,
+ gint response_id);
+static void update_dialog_state (PpNewPrinterDialog *self);
+static void add_devices_to_list (PpNewPrinterDialog *self,
+ GPtrArray *devices);
+static void remove_device_from_list (PpNewPrinterDialog *self,
+ const gchar *device_name);
+
+enum
+{
+ DEVICE_GICON_COLUMN = 0,
+ DEVICE_NAME_COLUMN,
+ DEVICE_DISPLAY_NAME_COLUMN,
+ DEVICE_DESCRIPTION_COLUMN,
+ SERVER_NEEDS_AUTHENTICATION_COLUMN,
+ DEVICE_VISIBLE_COLUMN,
+ DEVICE_COLUMN,
+ DEVICE_N_COLUMNS
+};
+
+struct _PpNewPrinterDialog
+{
+ GObject parent_instance;
+
+ GtkBuilder *builder;
+
+ GPtrArray *local_cups_devices;
+
+ GtkListStore *store;
+ GtkTreeModelFilter *filter;
+ GtkTreeView *treeview;
+
+ cups_dest_t *dests;
+ gint num_of_dests;
+
+ GCancellable *cancellable;
+ GCancellable *remote_host_cancellable;
+
+ gboolean cups_searching;
+ gboolean samba_authenticated_searching;
+ gboolean samba_searching;
+
+ PpPPDSelectionDialog *ppd_selection_dialog;
+
+ PpPrintDevice *new_device;
+
+ PPDList *list;
+
+ GtkWidget *dialog;
+ GtkWindow *parent;
+
+ GIcon *local_printer_icon;
+ GIcon *remote_printer_icon;
+ GIcon *authenticated_server_icon;
+
+ PpHost *snmp_host;
+ PpHost *socket_host;
+ PpHost *lpd_host;
+ PpHost *remote_cups_host;
+ PpSamba *samba_host;
+ guint host_search_timeout_id;
+};
+
+G_DEFINE_TYPE (PpNewPrinterDialog, pp_new_printer_dialog, G_TYPE_OBJECT)
+
+static void pp_new_printer_dialog_finalize (GObject *object);
+
+enum {
+ PRE_RESPONSE,
+ RESPONSE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+pp_new_printer_dialog_class_init (PpNewPrinterDialogClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = pp_new_printer_dialog_finalize;
+
+ /**
+ * PpNewPrinterDialog::pre-response:
+ * @device: the device that is being added
+ *
+ * The signal which gets emitted when the new printer dialog is closed.
+ */
+ signals[PRE_RESPONSE] =
+ g_signal_new ("pre-response",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+ /**
+ * PpNewPrinterDialog::response:
+ * @response-id: response id of dialog
+ *
+ * The signal which gets emitted after the printer is added and configured.
+ */
+ signals[RESPONSE] =
+ g_signal_new ("response",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, G_TYPE_INT);
+}
+
+
+PpNewPrinterDialog *
+pp_new_printer_dialog_new (GtkWindow *parent,
+ PPDList *ppd_list)
+{
+ PpNewPrinterDialog *self;
+
+ self = g_object_new (PP_TYPE_NEW_PRINTER_DIALOG, NULL);
+
+ self->list = ppd_list_copy (ppd_list);
+ self->parent = parent;
+
+ gtk_window_set_transient_for (GTK_WINDOW (self->dialog), GTK_WINDOW (parent));
+
+ gtk_widget_show_all (self->dialog);
+
+ return PP_NEW_PRINTER_DIALOG (self);
+}
+
+void
+pp_new_printer_dialog_set_ppd_list (PpNewPrinterDialog *self,
+ PPDList *list)
+{
+ self->list = ppd_list_copy (list);
+
+ if (self->ppd_selection_dialog)
+ pp_ppd_selection_dialog_set_ppd_list (self->ppd_selection_dialog, self->list);
+}
+
+static void
+emit_pre_response (PpNewPrinterDialog *self,
+ const gchar *device_name,
+ const gchar *device_location,
+ const gchar *device_make_and_model,
+ gboolean network_device)
+{
+ g_signal_emit (self,
+ signals[PRE_RESPONSE],
+ 0,
+ device_name,
+ device_location,
+ device_make_and_model,
+ network_device);
+}
+
+static void
+emit_response (PpNewPrinterDialog *self,
+ gint response_id)
+{
+ g_signal_emit (self, signals[RESPONSE], 0, response_id);
+}
+
+typedef struct
+{
+ gchar *server_name;
+ gpointer dialog;
+} AuthSMBData;
+
+static void
+get_authenticated_samba_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ AuthSMBData *data = user_data;
+ PpNewPrinterDialog *self = PP_NEW_PRINTER_DIALOG (data->dialog);
+ g_autoptr(GPtrArray) devices = NULL;
+ gboolean cancelled = FALSE;
+ PpSamba *samba = (PpSamba *) source_object;
+ g_autoptr(GError) error = NULL;
+
+ g_object_ref (samba);
+
+ devices = pp_samba_get_devices_finish (samba, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ self->samba_authenticated_searching = FALSE;
+
+ for (guint i = 0; i < devices->len; i++)
+ {
+ PpPrintDevice *device = g_ptr_array_index (devices, i);
+
+ if (pp_print_device_is_authenticated_server (device))
+ {
+ cancelled = TRUE;
+ break;
+ }
+ }
+
+ if (!cancelled)
+ {
+ if (devices != NULL)
+ {
+ add_devices_to_list (self, devices);
+
+ if (devices->len > 0)
+ {
+ gtk_entry_set_text (GTK_ENTRY (WID ("search-entry")), pp_print_device_get_device_location (g_ptr_array_index (devices, 0)));
+ search_entry_activated_cb (self);
+ }
+ }
+ }
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ self->samba_authenticated_searching = FALSE;
+ update_dialog_state (self);
+ }
+ }
+
+ g_free (data->server_name);
+ g_free (data);
+}
+
+static void
+go_to_page (PpNewPrinterDialog *self,
+ const gchar *page)
+{
+ GtkStack *stack;
+
+ stack = GTK_STACK (WID ("dialog-stack"));
+ gtk_stack_set_visible_child_name (stack, page);
+
+ stack = GTK_STACK (WID ("headerbar-topright-buttons"));
+ gtk_stack_set_visible_child_name (stack, page);
+
+ stack = GTK_STACK (WID ("headerbar-topleft-buttons"));
+ gtk_stack_set_visible_child_name (stack, page);
+}
+
+static gchar *
+get_entry_text (const gchar *object_name,
+ PpNewPrinterDialog *self)
+{
+ return g_strdup (gtk_entry_get_text (GTK_ENTRY (WID (object_name))));
+}
+
+static void
+on_authenticate (PpNewPrinterDialog *self)
+{
+ gchar *hostname = NULL;
+ gchar *username = NULL;
+ gchar *password = NULL;
+
+ username = get_entry_text ("username-entry", self);
+ password = get_entry_text ("password-entry", self);
+
+ if ((username == NULL) || (username[0] == '\0') ||
+ (password == NULL) || (password[0] == '\0'))
+ {
+ g_clear_pointer (&username, g_free);
+ g_clear_pointer (&password, g_free);
+ return;
+ }
+
+ pp_samba_set_auth_info (PP_SAMBA (self->samba_host), username, password);
+
+ gtk_header_bar_set_title (GTK_HEADER_BAR (WID ("headerbar")), _("Add Printer"));
+ go_to_page (self, ADDPRINTER_PAGE);
+
+ g_object_get (PP_HOST (self->samba_host), "hostname", &hostname, NULL);
+ remove_device_from_list (self, hostname);
+}
+
+static void
+on_authentication_required (PpNewPrinterDialog *self)
+{
+ g_autofree gchar *hostname = NULL;
+ g_autofree gchar *title = NULL;
+ g_autofree gchar *text = NULL;
+
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (WID ("headerbar")), NULL);
+ gtk_header_bar_set_title (GTK_HEADER_BAR (WID ("headerbar")), _("Unlock Print Server"));
+
+ g_object_get (self->samba_host, "hostname", &hostname, NULL);
+ /* Translators: Samba server needs authentication of the user to show list of its printers. */
+ title = g_strdup_printf (_("Unlock %s."), hostname);
+ gtk_label_set_text (GTK_LABEL (WID ("authentication-title")), title);
+
+ /* Translators: Samba server needs authentication of the user to show list of its printers. */
+ text = g_strdup_printf (_("Enter username and password to view printers on %s."), hostname);
+ gtk_label_set_text (GTK_LABEL (WID ("authentication-text")), text);
+
+ go_to_page (self, AUTHENTICATION_PAGE);
+
+ g_signal_connect_object (WID ("authenticate-button"), "clicked", G_CALLBACK (on_authenticate), self, G_CONNECT_SWAPPED);
+}
+
+static void
+auth_entries_changed (PpNewPrinterDialog *self)
+{
+ gboolean can_authenticate = FALSE;
+ gchar *username = NULL;
+ gchar *password = NULL;
+
+ username = get_entry_text ("username-entry", self);
+ password = get_entry_text ("password-entry", self);
+
+ can_authenticate = (username != NULL && username[0] != '\0' &&
+ password != NULL && password[0] != '\0');
+
+ gtk_widget_set_sensitive (WID ("authenticate-button"), can_authenticate);
+
+ g_clear_pointer (&username, g_free);
+ g_clear_pointer (&password, g_free);
+}
+
+static void
+on_go_back_button_clicked (PpNewPrinterDialog *self)
+{
+ pp_samba_set_auth_info (self->samba_host, NULL, NULL);
+ g_clear_object (&self->samba_host);
+
+ go_to_page (self, ADDPRINTER_PAGE);
+ gtk_header_bar_set_title (GTK_HEADER_BAR (WID ("headerbar")), _("Add Printer"));
+ gtk_widget_set_sensitive (WID ("new-printer-add-button"), FALSE);
+
+ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (self->treeview));
+}
+
+static void
+authenticate_samba_server (PpNewPrinterDialog *self)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ AuthSMBData *data;
+ gchar *server_name = NULL;
+
+ gtk_widget_set_sensitive (WID ("unlock-button"), FALSE);
+ gtk_widget_set_sensitive (WID ("authenticate-button"), FALSE);
+ gtk_widget_grab_focus (WID ("username-entry"));
+
+ if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (self->treeview), &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ DEVICE_NAME_COLUMN, &server_name,
+ -1);
+
+ if (server_name != NULL)
+ {
+ g_clear_object (&self->samba_host);
+
+ self->samba_host = pp_samba_new (server_name);
+ g_signal_connect_object (self->samba_host,
+ "authentication-required",
+ G_CALLBACK (on_authentication_required),
+ self, G_CONNECT_SWAPPED);
+
+ self->samba_authenticated_searching = TRUE;
+ update_dialog_state (self);
+
+ data = g_new (AuthSMBData, 1);
+ data->server_name = server_name;
+ data->dialog = self;
+
+ pp_samba_get_devices_async (self->samba_host,
+ TRUE,
+ self->cancellable,
+ get_authenticated_samba_devices_cb,
+ data);
+ }
+ }
+}
+
+static gboolean
+stack_key_press_cb (PpNewPrinterDialog *self,
+ GdkEvent *event)
+{
+ gtk_widget_grab_focus (WID ("search-entry"));
+ gtk_main_do_event (event);
+
+ return TRUE;
+}
+
+static void
+pp_new_printer_dialog_init (PpNewPrinterDialog *self)
+{
+ GtkStyleContext *context;
+ GtkWidget *widget;
+ g_autoptr(GError) error = NULL;
+ gchar *objects[] = { "dialog",
+ "devices-liststore",
+ "devices-model-filter",
+ NULL };
+ guint builder_result;
+
+ self->builder = gtk_builder_new ();
+
+ builder_result = gtk_builder_add_objects_from_resource (self->builder,
+ "/org/gnome/control-center/printers/new-printer-dialog.ui",
+ objects, &error);
+
+ if (builder_result == 0)
+ {
+ g_warning ("Could not load ui: %s", error->message);
+ }
+
+ self->local_cups_devices = g_ptr_array_new_with_free_func (g_object_unref);
+
+ /* GCancellable for cancelling of async operations */
+ self->cancellable = g_cancellable_new ();
+
+ /* Construct dialog */
+ self->dialog = WID ("dialog");
+
+ self->treeview = GTK_TREE_VIEW (WID ("devices-treeview"));
+
+ self->store = GTK_LIST_STORE (gtk_builder_get_object (self->builder, "devices-liststore"));
+
+ self->filter = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (self->builder, "devices-model-filter"));
+
+ /* Connect signals */
+ g_signal_connect_object (self->dialog, "response", G_CALLBACK (new_printer_dialog_response_cb), self, G_CONNECT_SWAPPED);
+
+ widget = WID ("search-entry");
+ g_signal_connect_object (widget, "activate", G_CALLBACK (search_entry_activated_cb), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "search-changed", G_CALLBACK (search_entry_changed_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (WID ("unlock-button"), "clicked", G_CALLBACK (authenticate_samba_server), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (WID ("stack"), "key-press-event", G_CALLBACK (stack_key_press_cb), self, G_CONNECT_SWAPPED);
+
+ /* Authentication form widgets */
+ g_signal_connect_object (WID ("username-entry"), "changed", G_CALLBACK (auth_entries_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (WID ("password-entry"), "changed", G_CALLBACK (auth_entries_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (WID ("go-back-button"), "clicked", G_CALLBACK (on_go_back_button_clicked), self, G_CONNECT_SWAPPED);
+
+ /* Set junctions */
+ widget = WID ("scrolledwindow1");
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
+
+ widget = WID ("toolbar1");
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
+
+ /* Fill with data */
+ populate_devices_list (self);
+}
+
+static void
+pp_new_printer_dialog_finalize (GObject *object)
+{
+ PpNewPrinterDialog *self = PP_NEW_PRINTER_DIALOG (object);
+
+ g_cancellable_cancel (self->remote_host_cancellable);
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_handle_id (&self->host_search_timeout_id, g_source_remove);
+ g_clear_object (&self->remote_host_cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_pointer (&self->dialog, gtk_widget_destroy);
+ g_clear_pointer (&self->list, ppd_list_free);
+ g_clear_object (&self->builder);
+ g_clear_pointer (&self->local_cups_devices, g_ptr_array_unref);
+ g_clear_object (&self->local_printer_icon);
+ g_clear_object (&self->remote_printer_icon);
+ g_clear_object (&self->authenticated_server_icon);
+
+ if (self->num_of_dests > 0)
+ {
+ cupsFreeDests (self->num_of_dests, self->dests);
+ self->num_of_dests = 0;
+ self->dests = NULL;
+ }
+
+ G_OBJECT_CLASS (pp_new_printer_dialog_parent_class)->finalize (object);
+}
+
+static void
+device_selection_changed_cb (PpNewPrinterDialog *self)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkWidget *widget;
+ GtkWidget *stack;
+ gboolean authentication_needed;
+ gboolean selected;
+
+ selected = gtk_tree_selection_get_selected (gtk_tree_view_get_selection (self->treeview),
+ &model,
+ &iter);
+
+ if (selected)
+ {
+ gtk_tree_model_get (model, &iter,
+ SERVER_NEEDS_AUTHENTICATION_COLUMN, &authentication_needed,
+ -1);
+
+ widget = WID ("new-printer-add-button");
+ gtk_widget_set_sensitive (widget, selected);
+
+ widget = WID ("unlock-button");
+ gtk_widget_set_sensitive (widget, authentication_needed);
+
+ stack = WID ("headerbar-topright-buttons");
+
+ if (authentication_needed)
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), "unlock-button");
+ else
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), ADDPRINTER_PAGE);
+ }
+}
+
+static void
+remove_device_from_list (PpNewPrinterDialog *self,
+ const gchar *device_name)
+{
+ PpPrintDevice *device;
+ GtkTreeIter iter;
+ gboolean cont;
+
+ cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
+ while (cont)
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store), &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ if (g_strcmp0 (pp_print_device_get_device_name (device), device_name) == 0)
+ {
+ gtk_list_store_remove (self->store, &iter);
+ g_object_unref (device);
+ break;
+ }
+
+ g_object_unref (device);
+
+ cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
+ }
+
+ update_dialog_state (self);
+}
+
+static gboolean
+prepend_original_name (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ PpPrintDevice *device;
+ GList **list = data;
+
+ gtk_tree_model_get (model, iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ *list = g_list_prepend (*list, g_strdup (pp_print_device_get_device_original_name (device)));
+
+ g_object_unref (device);
+
+ return FALSE;
+}
+
+static void
+add_device_to_list (PpNewPrinterDialog *self,
+ PpPrintDevice *device)
+{
+ PpPrintDevice *store_device;
+ GList *original_names_list = NULL;
+ gint acquisition_method;
+
+ if (device)
+ {
+ if (pp_print_device_get_host_name (device) == NULL)
+ {
+ g_autofree gchar *host_name = guess_device_hostname (device);
+ g_object_set (device, "host-name", host_name, NULL);
+ }
+
+ acquisition_method = pp_print_device_get_acquisition_method (device);
+ if (pp_print_device_get_device_id (device) ||
+ pp_print_device_get_device_ppd (device) ||
+ (pp_print_device_get_host_name (device) &&
+ acquisition_method == ACQUISITION_METHOD_REMOTE_CUPS_SERVER) ||
+ acquisition_method == ACQUISITION_METHOD_SAMBA_HOST ||
+ acquisition_method == ACQUISITION_METHOD_SAMBA ||
+ (pp_print_device_get_device_uri (device) &&
+ (acquisition_method == ACQUISITION_METHOD_JETDIRECT ||
+ acquisition_method == ACQUISITION_METHOD_LPD)))
+ {
+ g_autofree gchar *canonicalized_name = NULL;
+
+ g_object_set (device,
+ "device-original-name", pp_print_device_get_device_name (device),
+ NULL);
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (self->store),
+ prepend_original_name,
+ &original_names_list);
+
+ original_names_list = g_list_reverse (original_names_list);
+
+ canonicalized_name = canonicalize_device_name (original_names_list,
+ self->local_cups_devices,
+ self->dests,
+ self->num_of_dests,
+ device);
+
+ g_list_free_full (original_names_list, g_free);
+
+ g_object_set (device,
+ "display-name", canonicalized_name,
+ "device-name", canonicalized_name,
+ NULL);
+
+ if (pp_print_device_get_acquisition_method (device) == ACQUISITION_METHOD_DEFAULT_CUPS_SERVER)
+ g_ptr_array_add (self->local_cups_devices, g_object_ref (device));
+ else
+ set_device (self, device, NULL);
+ }
+ else if (pp_print_device_is_authenticated_server (device) &&
+ pp_print_device_get_host_name (device) != NULL)
+ {
+ store_device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "device-name", pp_print_device_get_host_name (device),
+ "host-name", pp_print_device_get_host_name (device),
+ "is-authenticated-server", pp_print_device_is_authenticated_server (device),
+ NULL);
+
+ set_device (self, store_device, NULL);
+
+ g_object_unref (store_device);
+ }
+ }
+}
+
+static void
+add_devices_to_list (PpNewPrinterDialog *self,
+ GPtrArray *devices)
+{
+ for (guint i = 0; i < devices->len; i++)
+ add_device_to_list (self, g_ptr_array_index (devices, i));
+}
+
+static PpPrintDevice *
+device_in_list (gchar *device_uri,
+ GPtrArray *device_list)
+{
+ for (guint i = 0; i < device_list->len; i++)
+ {
+ PpPrintDevice *device = g_ptr_array_index (device_list, i);
+ /* GroupPhysicalDevices returns uris without port numbers */
+ if (pp_print_device_get_device_uri (device) != NULL &&
+ g_str_has_prefix (pp_print_device_get_device_uri (device), device_uri))
+ return g_object_ref (device);
+ }
+
+ return NULL;
+}
+
+static PpPrintDevice *
+device_in_liststore (gchar *device_uri,
+ GtkListStore *device_liststore)
+{
+ PpPrintDevice *device;
+ GtkTreeIter iter;
+ gboolean cont;
+
+ cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (device_liststore), &iter);
+ while (cont)
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (device_liststore), &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ /* GroupPhysicalDevices returns uris without port numbers */
+ if (pp_print_device_get_device_uri (device) != NULL &&
+ g_str_has_prefix (pp_print_device_get_device_uri (device), device_uri))
+ {
+ return device;
+ }
+
+ g_object_unref (device);
+
+ cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (device_liststore), &iter);
+ }
+
+ return NULL;
+}
+
+static void
+update_dialog_state (PpNewPrinterDialog *self)
+{
+ GtkTreeIter iter;
+ GtkWidget *header;
+ GtkWidget *stack;
+ gboolean searching;
+
+ searching = self->cups_searching ||
+ self->remote_cups_host != NULL ||
+ self->snmp_host != NULL ||
+ self->socket_host != NULL ||
+ self->lpd_host != NULL ||
+ self->samba_host != NULL ||
+ self->samba_authenticated_searching ||
+ self->samba_searching;
+
+ header = WID ("headerbar");
+ stack = WID ("stack");
+
+ if (searching)
+ {
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (header), _("Searching for Printers"));
+ }
+ else
+ {
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (header), NULL);
+ }
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter))
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), "standard-page");
+ else
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), searching ? "loading-page" : "no-printers-page");
+}
+
+static void
+group_physical_devices_cb (gchar ***device_uris,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpPrintDevice *device, *better_device;
+ gint i, j;
+
+ if (device_uris != NULL)
+ {
+ for (i = 0; device_uris[i] != NULL; i++)
+ {
+ /* Is there any device in this sublist? */
+ if (device_uris[i][0] != NULL)
+ {
+ device = NULL;
+ for (j = 0; device_uris[i][j] != NULL; j++)
+ {
+ device = device_in_liststore (device_uris[i][j], self->store);
+ if (device != NULL)
+ break;
+ }
+
+ /* Is this sublist represented in the current list of devices? */
+ if (device != NULL)
+ {
+ /* Is there better device in the sublist? */
+ if (j != 0)
+ {
+ better_device = device_in_list (device_uris[i][0], self->local_cups_devices);
+ replace_device (self, device, better_device);
+ g_object_unref (better_device);
+ }
+
+ g_object_unref (device);
+ }
+ else
+ {
+ device = device_in_list (device_uris[i][0], self->local_cups_devices);
+ if (device != NULL)
+ {
+ set_device (self, device, NULL);
+ g_object_unref (device);
+ }
+ }
+ }
+ }
+
+ for (i = 0; device_uris[i] != NULL; i++)
+ g_strfreev (device_uris[i]);
+
+ g_free (device_uris);
+ }
+ else
+ {
+ for (i = 0; i < self->local_cups_devices->len; i++)
+ set_device (self, g_ptr_array_index (self->local_cups_devices, i), NULL);
+ g_ptr_array_set_size (self->local_cups_devices, 0);
+ }
+
+ update_dialog_state (self);
+}
+
+static void
+group_physical_devices_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ g_autoptr(GError) error = NULL;
+ gchar ***result = NULL;
+ gint i;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ g_autoptr(GVariant) array = NULL;
+
+ g_variant_get (output, "(@aas)", &array);
+
+ if (array)
+ {
+ g_autoptr(GVariantIter) iter = NULL;
+ GStrv device_uris;
+
+ result = g_new0 (gchar **, g_variant_n_children (array) + 1);
+ g_variant_get (array, "aas", &iter);
+ i = 0;
+ while (g_variant_iter_next (iter, "^as", &device_uris))
+ {
+ result[i] = device_uris;
+ i++;
+ }
+ }
+ }
+ else if (error &&
+ error->domain == G_DBUS_ERROR &&
+ (error->code == G_DBUS_ERROR_SERVICE_UNKNOWN ||
+ error->code == G_DBUS_ERROR_UNKNOWN_METHOD))
+ {
+ g_warning ("Install system-config-printer which provides \
+DBus method \"GroupPhysicalDevices\" to group duplicates in device list.");
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ group_physical_devices_cb (result, user_data);
+}
+
+static void
+get_cups_devices_cb (GPtrArray *devices,
+ gboolean finished,
+ gboolean cancelled,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ GDBusConnection *bus;
+ GVariantBuilder device_list;
+ GVariantBuilder device_hash;
+ PpPrintDevice **all_devices;
+ PpPrintDevice *device;
+ const gchar *device_class;
+ GtkTreeIter iter;
+ gboolean cont;
+ g_autoptr(GError) error = NULL;
+ gint length, i;
+
+
+ if (!cancelled)
+ {
+ if (finished)
+ {
+ self->cups_searching = FALSE;
+ }
+
+ if (devices != NULL)
+ {
+ add_devices_to_list (self, devices);
+
+ length = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (self->store), NULL) + self->local_cups_devices->len;
+ if (length > 0)
+ {
+ all_devices = g_new0 (PpPrintDevice *, length);
+
+ i = 0;
+ cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
+ while (cont)
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store), &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ all_devices[i] = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "device-id", pp_print_device_get_device_id (device),
+ "device-make-and-model", pp_print_device_get_device_make_and_model (device),
+ "is-network-device", pp_print_device_is_network_device (device),
+ "device-uri", pp_print_device_get_device_uri (device),
+ NULL);
+ i++;
+
+ g_object_unref (device);
+
+ cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
+ }
+
+ for (guint j = 0; j < self->local_cups_devices->len; j++)
+ {
+ PpPrintDevice *pp_device = g_ptr_array_index (self->local_cups_devices, j);
+ all_devices[i] = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "device-id", pp_print_device_get_device_id (pp_device),
+ "device-make-and-model", pp_print_device_get_device_make_and_model (pp_device),
+ "is-network-device", pp_print_device_is_network_device (pp_device),
+ "device-uri", pp_print_device_get_device_uri (pp_device),
+ NULL);
+ i++;
+ }
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (bus)
+ {
+ g_variant_builder_init (&device_list, G_VARIANT_TYPE ("a{sv}"));
+
+ for (i = 0; i < length; i++)
+ {
+ if (pp_print_device_get_device_uri (all_devices[i]))
+ {
+ g_variant_builder_init (&device_hash, G_VARIANT_TYPE ("a{ss}"));
+
+ if (pp_print_device_get_device_id (all_devices[i]))
+ g_variant_builder_add (&device_hash,
+ "{ss}",
+ "device-id",
+ pp_print_device_get_device_id (all_devices[i]));
+
+ if (pp_print_device_get_device_make_and_model (all_devices[i]))
+ g_variant_builder_add (&device_hash,
+ "{ss}",
+ "device-make-and-model",
+ pp_print_device_get_device_make_and_model (all_devices[i]));
+
+ if (pp_print_device_is_network_device (all_devices[i]))
+ device_class = "network";
+ else
+ device_class = "direct";
+
+ g_variant_builder_add (&device_hash,
+ "{ss}",
+ "device-class",
+ device_class);
+
+ g_variant_builder_add (&device_list,
+ "{sv}",
+ pp_print_device_get_device_uri (all_devices[i]),
+ g_variant_builder_end (&device_hash));
+ }
+ }
+
+ g_dbus_connection_call (bus,
+ SCP_BUS,
+ SCP_PATH,
+ SCP_IFACE,
+ "GroupPhysicalDevices",
+ g_variant_new ("(v)", g_variant_builder_end (&device_list)),
+ G_VARIANT_TYPE ("(aas)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->cancellable,
+ group_physical_devices_dbus_cb,
+ self);
+ }
+ else
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ group_physical_devices_cb (NULL, user_data);
+ }
+
+ for (i = 0; i < length; i++)
+ g_object_unref (all_devices[i]);
+ g_free (all_devices);
+ }
+ else
+ {
+ update_dialog_state (self);
+ }
+ }
+ else
+ {
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_snmp_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpHost *host = (PpHost *) source_object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+
+ devices = pp_host_get_snmp_devices_finish (host, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ if ((gpointer) source_object == (gpointer) self->snmp_host)
+ self->snmp_host = NULL;
+
+ add_devices_to_list (self, devices);
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ if ((gpointer) source_object == (gpointer) self->snmp_host)
+ self->snmp_host = NULL;
+
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_remote_cups_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpHost *host = (PpHost *) source_object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+
+ devices = pp_host_get_remote_cups_devices_finish (host, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ if ((gpointer) source_object == (gpointer) self->remote_cups_host)
+ self->remote_cups_host = NULL;
+
+ add_devices_to_list (self, devices);
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ if ((gpointer) source_object == (gpointer) self->remote_cups_host)
+ self->remote_cups_host = NULL;
+
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_samba_host_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ g_autoptr(GPtrArray) devices = NULL;
+ PpSamba *samba = (PpSamba *) source_object;
+ g_autoptr(GError) error = NULL;
+
+ devices = pp_samba_get_devices_finish (samba, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ if ((gpointer) source_object == (gpointer) self->samba_host)
+ self->samba_host = NULL;
+
+ add_devices_to_list (self, devices);
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ if ((gpointer) source_object == (gpointer) self->samba_host)
+ self->samba_host = NULL;
+
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_samba_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ g_autoptr(GPtrArray) devices = NULL;
+ PpSamba *samba = (PpSamba *) source_object;
+ g_autoptr(GError) error = NULL;
+
+ devices = pp_samba_get_devices_finish (samba, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ self->samba_searching = FALSE;
+
+ add_devices_to_list (self, devices);
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ self->samba_searching = FALSE;
+
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_jetdirect_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpHost *host = (PpHost *) source_object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+
+ devices = pp_host_get_jetdirect_devices_finish (host, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ if ((gpointer) source_object == (gpointer) self->socket_host)
+ self->socket_host = NULL;
+
+ add_devices_to_list (self, devices);
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ if ((gpointer) source_object == (gpointer) self->socket_host)
+ self->socket_host = NULL;
+
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_lpd_devices_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpHost *host = (PpHost *) source_object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+
+ devices = pp_host_get_lpd_devices_finish (host, res, &error);
+ g_object_unref (source_object);
+
+ if (devices != NULL)
+ {
+ if ((gpointer) source_object == (gpointer) self->lpd_host)
+ self->lpd_host = NULL;
+
+ add_devices_to_list (self, devices);
+
+ update_dialog_state (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ if ((gpointer) source_object == (gpointer) self->lpd_host)
+ self->lpd_host = NULL;
+
+ update_dialog_state (self);
+ }
+ }
+}
+
+static void
+get_cups_devices (PpNewPrinterDialog *self)
+{
+ self->cups_searching = TRUE;
+ update_dialog_state (self);
+
+ get_cups_devices_async (self->cancellable,
+ get_cups_devices_cb,
+ self);
+}
+
+static gboolean
+parse_uri (const gchar *uri,
+ gchar **scheme,
+ gchar **host,
+ gint *port)
+{
+ const gchar *tmp = NULL;
+ g_autofree gchar *resulting_host = NULL;
+ gchar *position;
+
+ *port = PP_HOST_UNSET_PORT;
+
+ position = g_strrstr (uri, "://");
+ if (position != NULL)
+ {
+ *scheme = g_strndup (uri, position - uri);
+ tmp = position + 3;
+ }
+ else
+ {
+ tmp = uri;
+ }
+
+ if (g_strrstr (tmp, "@"))
+ tmp = g_strrstr (tmp, "@") + 1;
+
+ if ((position = g_strrstr (tmp, "/")))
+ {
+ *position = '\0';
+ resulting_host = g_strdup (tmp);
+ *position = '/';
+ }
+ else
+ {
+ resulting_host = g_strdup (tmp);
+ }
+
+ if ((position = g_strrstr (resulting_host, ":")))
+ {
+ *position = '\0';
+ *port = atoi (position + 1);
+ }
+
+ *host = g_uri_unescape_string (resulting_host,
+ G_URI_RESERVED_CHARS_GENERIC_DELIMITERS
+ G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS);
+
+ return TRUE;
+}
+
+typedef struct
+{
+ PpNewPrinterDialog *dialog;
+ gchar *host_scheme;
+ gchar *host_name;
+ gint host_port;
+} THostSearchData;
+
+static void
+search_for_remote_printers_free (THostSearchData *data)
+{
+ g_free (data->host_scheme);
+ g_free (data->host_name);
+ g_free (data);
+}
+
+static gboolean
+search_for_remote_printers (THostSearchData *data)
+{
+ PpNewPrinterDialog *self = data->dialog;
+
+ g_cancellable_cancel (self->remote_host_cancellable);
+ g_clear_object (&self->remote_host_cancellable);
+
+ self->remote_host_cancellable = g_cancellable_new ();
+
+ self->remote_cups_host = pp_host_new (data->host_name);
+ self->snmp_host = pp_host_new (data->host_name);
+ self->socket_host = pp_host_new (data->host_name);
+ self->lpd_host = pp_host_new (data->host_name);
+
+ if (data->host_port != PP_HOST_UNSET_PORT)
+ {
+ g_object_set (self->remote_cups_host, "port", data->host_port, NULL);
+ g_object_set (self->snmp_host, "port", data->host_port, NULL);
+
+ /* Accept port different from the default one only if user specifies
+ * scheme (for socket and lpd printers).
+ */
+ if (data->host_scheme != NULL &&
+ g_ascii_strcasecmp (data->host_scheme, "socket") == 0)
+ g_object_set (self->socket_host, "port", data->host_port, NULL);
+
+ if (data->host_scheme != NULL &&
+ g_ascii_strcasecmp (data->host_scheme, "lpd") == 0)
+ g_object_set (self->lpd_host, "port", data->host_port, NULL);
+ }
+
+ self->samba_host = pp_samba_new (data->host_name);
+
+ update_dialog_state (data->dialog);
+
+ pp_host_get_remote_cups_devices_async (self->remote_cups_host,
+ self->remote_host_cancellable,
+ get_remote_cups_devices_cb,
+ data->dialog);
+
+ pp_host_get_snmp_devices_async (self->snmp_host,
+ self->remote_host_cancellable,
+ get_snmp_devices_cb,
+ data->dialog);
+
+ pp_host_get_jetdirect_devices_async (self->socket_host,
+ self->remote_host_cancellable,
+ get_jetdirect_devices_cb,
+ data->dialog);
+
+ pp_host_get_lpd_devices_async (self->lpd_host,
+ self->remote_host_cancellable,
+ get_lpd_devices_cb,
+ data->dialog);
+
+ pp_samba_get_devices_async (self->samba_host,
+ FALSE,
+ self->remote_host_cancellable,
+ get_samba_host_devices_cb,
+ data->dialog);
+
+ self->host_search_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+search_address (const gchar *text,
+ PpNewPrinterDialog *self,
+ gboolean delay_search)
+{
+ PpPrintDevice *device;
+ GtkTreeIter iter;
+ gboolean found = FALSE;
+ gboolean subfound;
+ gboolean next_set;
+ gboolean cont;
+ g_autofree gchar *lowercase_text = NULL;
+ gchar **words;
+ gint words_length = 0;
+ gint i;
+ gint acquisition_method;
+
+ lowercase_text = g_ascii_strdown (text, -1);
+ words = g_strsplit_set (lowercase_text, " ", -1);
+
+ if (words)
+ {
+ words_length = g_strv_length (words);
+
+ cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
+ while (cont)
+ {
+ g_autofree gchar *lowercase_name = NULL;
+ g_autofree gchar *lowercase_location = NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store), &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ lowercase_name = g_ascii_strdown (pp_print_device_get_device_name (device), -1);
+ if (pp_print_device_get_device_location (device))
+ lowercase_location = g_ascii_strdown (pp_print_device_get_device_location (device), -1);
+ else
+ lowercase_location = NULL;
+
+ subfound = TRUE;
+ for (i = 0; words[i]; i++)
+ {
+ if (!g_strrstr (lowercase_name, words[i]) &&
+ (!lowercase_location || !g_strrstr (lowercase_location, words[i])))
+ subfound = FALSE;
+ }
+
+ if (subfound)
+ found = TRUE;
+
+ gtk_list_store_set (GTK_LIST_STORE (self->store), &iter,
+ DEVICE_VISIBLE_COLUMN, subfound,
+ -1);
+
+ g_object_unref (device);
+
+ cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
+ }
+
+ g_strfreev (words);
+ }
+
+ /*
+ * The given word is probably an address since it was not found among
+ * already present devices.
+ */
+ if (!found && words_length == 1)
+ {
+ cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
+ while (cont)
+ {
+ next_set = FALSE;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store), &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ gtk_list_store_set (GTK_LIST_STORE (self->store), &iter,
+ DEVICE_VISIBLE_COLUMN, TRUE,
+ -1);
+
+ acquisition_method = pp_print_device_get_acquisition_method (device);
+ g_object_unref (device);
+ if (acquisition_method == ACQUISITION_METHOD_REMOTE_CUPS_SERVER ||
+ acquisition_method == ACQUISITION_METHOD_SNMP ||
+ acquisition_method == ACQUISITION_METHOD_JETDIRECT ||
+ acquisition_method == ACQUISITION_METHOD_LPD ||
+ acquisition_method == ACQUISITION_METHOD_SAMBA_HOST)
+ {
+ if (!gtk_list_store_remove (self->store, &iter))
+ break;
+ else
+ next_set = TRUE;
+ }
+
+ if (!next_set)
+ cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
+ }
+
+ if (text && text[0] != '\0')
+ {
+ g_autoptr(GSocketConnectable) conn = NULL;
+ g_autofree gchar *test_uri = NULL;
+ g_autofree gchar *test_port = NULL;
+ gchar *scheme = NULL;
+ gchar *host = NULL;
+ gint port;
+
+ parse_uri (text, &scheme, &host, &port);
+
+ if (host != NULL)
+ {
+ if (port >= 0)
+ test_port = g_strdup_printf (":%d", port);
+ else
+ test_port = g_strdup ("");
+
+ test_uri = g_strdup_printf ("%s://%s%s",
+ scheme != NULL && scheme[0] != '\0' ? scheme : "none",
+ host,
+ test_port);
+
+ conn = g_network_address_parse_uri (test_uri, 0, NULL);
+ if (conn != NULL)
+ {
+ THostSearchData *search_data;
+
+ search_data = g_new (THostSearchData, 1);
+ search_data->host_scheme = scheme;
+ search_data->host_name = host;
+ search_data->host_port = port;
+ search_data->dialog = self;
+
+ if (self->host_search_timeout_id != 0)
+ {
+ g_source_remove (self->host_search_timeout_id);
+ self->host_search_timeout_id = 0;
+ }
+
+ if (delay_search)
+ {
+ self->host_search_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT,
+ HOST_SEARCH_DELAY,
+ (GSourceFunc) search_for_remote_printers,
+ search_data,
+ (GDestroyNotify) search_for_remote_printers_free);
+ }
+ else
+ {
+ search_for_remote_printers (search_data);
+ search_for_remote_printers_free (search_data);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void
+search_entry_activated_cb (PpNewPrinterDialog *self)
+{
+ search_address (gtk_entry_get_text (GTK_ENTRY (WID ("search-entry"))),
+ self,
+ FALSE);
+}
+
+static void
+search_entry_changed_cb (PpNewPrinterDialog *self)
+{
+ search_address (gtk_entry_get_text (GTK_ENTRY (WID ("search-entry"))),
+ self,
+ TRUE);
+}
+
+static gchar *
+get_local_scheme_description_from_uri (gchar *device_uri)
+{
+ gchar *description = NULL;
+
+ if (device_uri != NULL)
+ {
+ if (g_str_has_prefix (device_uri, "usb") ||
+ g_str_has_prefix (device_uri, "hp:/usb/") ||
+ g_str_has_prefix (device_uri, "hpfax:/usb/"))
+ {
+ /* Translators: The found device is a printer connected via USB */
+ description = g_strdup (_("USB"));
+ }
+ else if (g_str_has_prefix (device_uri, "serial"))
+ {
+ /* Translators: The found device is a printer connected via serial port */
+ description = g_strdup (_("Serial Port"));
+ }
+ else if (g_str_has_prefix (device_uri, "parallel") ||
+ g_str_has_prefix (device_uri, "hp:/par/") ||
+ g_str_has_prefix (device_uri, "hpfax:/par/"))
+ {
+ /* Translators: The found device is a printer connected via parallel port */
+ description = g_strdup (_("Parallel Port"));
+ }
+ else if (g_str_has_prefix (device_uri, "bluetooth"))
+ {
+ /* Translators: The found device is a printer connected via Bluetooth */
+ description = g_strdup (_("Bluetooth"));
+ }
+ }
+
+ return description;
+}
+
+static void
+set_device (PpNewPrinterDialog *self,
+ PpPrintDevice *device,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter titer;
+ gint acquisition_method;
+
+ if (device != NULL)
+ {
+ acquisition_method = pp_print_device_get_acquisition_method (device);
+ if (pp_print_device_get_display_name (device) &&
+ (pp_print_device_get_device_id (device) ||
+ pp_print_device_get_device_ppd (device) ||
+ (pp_print_device_get_host_name (device) &&
+ acquisition_method == ACQUISITION_METHOD_REMOTE_CUPS_SERVER) ||
+ (pp_print_device_get_device_uri (device) &&
+ (acquisition_method == ACQUISITION_METHOD_JETDIRECT ||
+ acquisition_method == ACQUISITION_METHOD_LPD)) ||
+ acquisition_method == ACQUISITION_METHOD_SAMBA_HOST ||
+ acquisition_method == ACQUISITION_METHOD_SAMBA))
+ {
+ g_autofree gchar *description = NULL;
+
+ description = get_local_scheme_description_from_uri (pp_print_device_get_device_uri (device));
+ if (description == NULL)
+ {
+ if (pp_print_device_get_device_location (device) != NULL && pp_print_device_get_device_location (device)[0] != '\0')
+ {
+ /* Translators: Location of found network printer (e.g. Kitchen, Reception) */
+ description = g_strdup_printf (_("Location: %s"), pp_print_device_get_device_location (device));
+ }
+ else if (pp_print_device_get_host_name (device) != NULL && pp_print_device_get_host_name (device)[0] != '\0')
+ {
+ /* Translators: Network address of found printer */
+ description = g_strdup_printf (_("Address: %s"), pp_print_device_get_host_name (device));
+ }
+ }
+
+ if (iter == NULL)
+ gtk_list_store_append (self->store, &titer);
+
+ gtk_list_store_set (self->store, iter == NULL ? &titer : iter,
+ DEVICE_GICON_COLUMN, pp_print_device_is_network_device (device) ? self->remote_printer_icon : self->local_printer_icon,
+ DEVICE_NAME_COLUMN, pp_print_device_get_device_name (device),
+ DEVICE_DISPLAY_NAME_COLUMN, pp_print_device_get_display_name (device),
+ DEVICE_DESCRIPTION_COLUMN, description,
+ DEVICE_VISIBLE_COLUMN, TRUE,
+ DEVICE_COLUMN, device,
+ -1);
+ }
+ else if (pp_print_device_is_authenticated_server (device) &&
+ pp_print_device_get_host_name (device) != NULL)
+ {
+ if (iter == NULL)
+ gtk_list_store_append (self->store, &titer);
+
+ gtk_list_store_set (self->store, iter == NULL ? &titer : iter,
+ DEVICE_GICON_COLUMN, self->authenticated_server_icon,
+ DEVICE_NAME_COLUMN, pp_print_device_get_host_name (device),
+ DEVICE_DISPLAY_NAME_COLUMN, pp_print_device_get_host_name (device),
+ /* Translators: This item is a server which needs authentication to show its printers */
+ DEVICE_DESCRIPTION_COLUMN, _("Server requires authentication"),
+ SERVER_NEEDS_AUTHENTICATION_COLUMN, TRUE,
+ DEVICE_VISIBLE_COLUMN, TRUE,
+ DEVICE_COLUMN, device,
+ -1);
+ }
+ }
+}
+
+static void
+replace_device (PpNewPrinterDialog *self,
+ PpPrintDevice *old_device,
+ PpPrintDevice *new_device)
+{
+ PpPrintDevice *device;
+ GtkTreeIter iter;
+ gboolean cont;
+
+ if (old_device != NULL && new_device != NULL)
+ {
+ cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
+ while (cont)
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store), &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+
+ if (old_device == device)
+ {
+ set_device (self, new_device, &iter);
+ g_object_unref (device);
+ break;
+ }
+
+ g_object_unref (device);
+
+ cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
+ }
+ }
+}
+
+static void
+cups_get_dests_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpCupsDests *dests;
+ PpCups *cups = (PpCups *) source_object;
+ g_autoptr(GError) error = NULL;
+
+ dests = pp_cups_get_dests_finish (cups, res, &error);
+ g_object_unref (source_object);
+
+ if (dests)
+ {
+ self->dests = dests->dests;
+ self->num_of_dests = dests->num_of_dests;
+
+ get_cups_devices (self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ get_cups_devices (self);
+ }
+ }
+}
+
+static void
+row_activated_cb (PpNewPrinterDialog *self)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean authentication_needed;
+ gboolean selected;
+
+ selected = gtk_tree_selection_get_selected (gtk_tree_view_get_selection (self->treeview),
+ &model,
+ &iter);
+
+ if (selected)
+ {
+ gtk_tree_model_get (model, &iter, SERVER_NEEDS_AUTHENTICATION_COLUMN, &authentication_needed, -1);
+
+ if (authentication_needed)
+ {
+ authenticate_samba_server (self);
+ }
+ else
+ {
+ gtk_dialog_response (GTK_DIALOG (self->dialog), GTK_RESPONSE_OK);
+ }
+ }
+}
+
+static void
+cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ gboolean selected = FALSE;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *description = NULL;
+
+ selected = gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (self->treeview), iter);
+
+ gtk_tree_model_get (tree_model, iter,
+ DEVICE_DISPLAY_NAME_COLUMN, &name,
+ DEVICE_DESCRIPTION_COLUMN, &description,
+ -1);
+
+ if (name != NULL)
+ {
+ g_autofree gchar *text = NULL;
+
+ if (description != NULL)
+ {
+ if (selected)
+ text = g_markup_printf_escaped ("<b>%s</b>\n<small>%s</small>",
+ name,
+ description);
+ else
+ text = g_markup_printf_escaped ("<b>%s</b>\n<small><span foreground=\"#555555\">%s</span></small>",
+ name,
+ description);
+ }
+ else
+ {
+ text = g_markup_printf_escaped ("<b>%s</b>\n ",
+ name);
+ }
+
+ g_object_set (G_OBJECT (cell),
+ "markup", text,
+ NULL);
+ }
+}
+
+static void
+populate_devices_list (PpNewPrinterDialog *self)
+{
+ GtkTreeViewColumn *column;
+ PpSamba *samba;
+ GEmblem *emblem;
+ PpCups *cups;
+ GIcon *icon, *emblem_icon;
+ GtkCellRenderer *text_renderer;
+ GtkCellRenderer *icon_renderer;
+
+ g_signal_connect_object (gtk_tree_view_get_selection (self->treeview),
+ "changed", G_CALLBACK (device_selection_changed_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->treeview,
+ "row-activated", G_CALLBACK (row_activated_cb), self, G_CONNECT_SWAPPED);
+
+ self->local_printer_icon = g_themed_icon_new ("printer");
+ self->remote_printer_icon = g_themed_icon_new ("printer-network");
+
+ icon = g_themed_icon_new ("network-server");
+ emblem_icon = g_themed_icon_new ("changes-prevent");
+ emblem = g_emblem_new (emblem_icon);
+
+ self->authenticated_server_icon = g_emblemed_icon_new (icon, emblem);
+
+ g_object_unref (icon);
+ g_object_unref (emblem_icon);
+ g_object_unref (emblem);
+
+ icon_renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (icon_renderer, "stock-size", GTK_ICON_SIZE_DIALOG, NULL);
+ gtk_cell_renderer_set_alignment (icon_renderer, 1.0, 0.5);
+ gtk_cell_renderer_set_padding (icon_renderer, 4, 4);
+ column = gtk_tree_view_column_new_with_attributes ("Icon", icon_renderer,
+ "gicon", DEVICE_GICON_COLUMN, NULL);
+ gtk_tree_view_column_set_max_width (column, -1);
+ gtk_tree_view_column_set_min_width (column, 80);
+ gtk_tree_view_append_column (self->treeview, column);
+
+
+ text_renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Devices", text_renderer,
+ NULL);
+ gtk_tree_view_column_set_cell_data_func (column, text_renderer, cell_data_func,
+ self, NULL);
+ gtk_tree_view_append_column (self->treeview, column);
+
+ gtk_tree_model_filter_set_visible_column (self->filter, DEVICE_VISIBLE_COLUMN);
+
+ cups = pp_cups_new ();
+ pp_cups_get_dests_async (cups, self->cancellable, cups_get_dests_cb, self);
+
+ self->samba_searching = TRUE;
+ update_dialog_state (self);
+
+ samba = pp_samba_new (NULL);
+ pp_samba_get_devices_async (samba, FALSE, self->cancellable, get_samba_devices_cb, self);
+}
+
+static void
+printer_add_async_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ GtkResponseType response_id = GTK_RESPONSE_OK;
+ PpNewPrinter *new_printer = (PpNewPrinter *) source_object;
+ gboolean success;
+ g_autoptr(GError) error = NULL;
+
+ success = pp_new_printer_add_finish (new_printer, res, &error);
+ g_object_unref (source_object);
+
+ if (success)
+ {
+ emit_response (self, response_id);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("%s", error->message);
+
+ response_id = GTK_RESPONSE_REJECT;
+
+ emit_response (self, response_id);
+ }
+ }
+}
+
+static void
+ppd_selection_cb (GtkDialog *_dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ PpNewPrinterDialog *self = user_data;
+ PpNewPrinter *new_printer;
+ GList *original_names_list = NULL;
+ g_autofree gchar *ppd_name = NULL;
+ g_autofree gchar *ppd_display_name = NULL;
+ guint window_id = 0;
+ gint acquisition_method;
+
+ ppd_name = pp_ppd_selection_dialog_get_ppd_name (self->ppd_selection_dialog);
+ ppd_display_name = pp_ppd_selection_dialog_get_ppd_display_name (self->ppd_selection_dialog);
+ pp_ppd_selection_dialog_free (self->ppd_selection_dialog);
+ self->ppd_selection_dialog = NULL;
+
+ if (ppd_name)
+ {
+ g_object_set (self->new_device, "device-ppd", ppd_name, NULL);
+
+ acquisition_method = pp_print_device_get_acquisition_method (self->new_device);
+ if ((acquisition_method == ACQUISITION_METHOD_JETDIRECT ||
+ acquisition_method == ACQUISITION_METHOD_LPD) &&
+ ppd_display_name != NULL)
+ {
+ g_autofree gchar *printer_name = NULL;
+
+ g_object_set (self->new_device,
+ "device-name", ppd_display_name,
+ "device-original-name", ppd_display_name,
+ NULL);
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (self->store),
+ prepend_original_name,
+ &original_names_list);
+
+ original_names_list = g_list_reverse (original_names_list);
+
+ printer_name = canonicalize_device_name (original_names_list,
+ self->local_cups_devices,
+ self->dests,
+ self->num_of_dests,
+ self->new_device);
+
+ g_list_free_full (original_names_list, g_free);
+
+ g_object_set (self->new_device,
+ "device-name", printer_name,
+ "device-original-name", printer_name,
+ NULL);
+ }
+
+ emit_pre_response (self,
+ pp_print_device_get_device_name (self->new_device),
+ pp_print_device_get_device_location (self->new_device),
+ pp_print_device_get_device_make_and_model (self->new_device),
+ pp_print_device_is_network_device (self->new_device));
+
+ window_id = (guint) GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (gtk_window_get_transient_for (GTK_WINDOW (self->dialog)))));
+
+ new_printer = pp_new_printer_new ();
+ g_object_set (new_printer,
+ "name", pp_print_device_get_device_name (self->new_device),
+ "original-name", pp_print_device_get_device_original_name (self->new_device),
+ "device-uri", pp_print_device_get_device_uri (self->new_device),
+ "device-id", pp_print_device_get_device_id (self->new_device),
+ "ppd-name", pp_print_device_get_device_ppd (self->new_device),
+ "ppd-file-name", pp_print_device_get_device_ppd (self->new_device),
+ "info", pp_print_device_get_device_info (self->new_device),
+ "location", pp_print_device_get_device_location (self->new_device),
+ "make-and-model", pp_print_device_get_device_make_and_model (self->new_device),
+ "host-name", pp_print_device_get_host_name (self->new_device),
+ "host-port", pp_print_device_get_host_port (self->new_device),
+ "is-network-device", pp_print_device_is_network_device (self->new_device),
+ "window-id", window_id,
+ NULL);
+ self->cancellable = g_cancellable_new ();
+
+ pp_new_printer_add_async (new_printer,
+ self->cancellable,
+ printer_add_async_cb,
+ self);
+
+ g_clear_object (&self->new_device);
+ }
+}
+
+static void
+new_printer_dialog_response_cb (PpNewPrinterDialog *self,
+ gint response_id)
+{
+ PpPrintDevice *device = NULL;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint acquisition_method;
+
+ gtk_widget_hide (GTK_WIDGET (self->dialog));
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (self->treeview), &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ DEVICE_COLUMN, &device,
+ -1);
+ }
+
+ if (device)
+ {
+ PpNewPrinter *new_printer;
+ guint window_id = 0;
+
+ acquisition_method = pp_print_device_get_acquisition_method (device);
+ if (acquisition_method == ACQUISITION_METHOD_SAMBA ||
+ acquisition_method == ACQUISITION_METHOD_SAMBA_HOST ||
+ acquisition_method == ACQUISITION_METHOD_JETDIRECT ||
+ acquisition_method == ACQUISITION_METHOD_LPD)
+ {
+ self->new_device = pp_print_device_copy (device);
+ self->ppd_selection_dialog =
+ pp_ppd_selection_dialog_new (self->parent,
+ self->list,
+ NULL,
+ ppd_selection_cb,
+ self);
+ }
+ else
+ {
+ emit_pre_response (self,
+ pp_print_device_get_device_name (device),
+ pp_print_device_get_device_location (device),
+ pp_print_device_get_device_make_and_model (device),
+ pp_print_device_is_network_device (device));
+
+ window_id = (guint) GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (gtk_window_get_transient_for (GTK_WINDOW (self->dialog)))));
+
+ new_printer = pp_new_printer_new ();
+ g_object_set (new_printer,
+ "name", pp_print_device_get_device_name (device),
+ "original-name", pp_print_device_get_device_original_name (device),
+ "device-uri", pp_print_device_get_device_uri (device),
+ "device-id", pp_print_device_get_device_id (device),
+ "ppd-name", pp_print_device_get_device_ppd (device),
+ "ppd-file-name", pp_print_device_get_device_ppd (device),
+ "info", pp_print_device_get_device_info (device),
+ "location", pp_print_device_get_device_location (device),
+ "make-and-model", pp_print_device_get_device_make_and_model (device),
+ "host-name", pp_print_device_get_host_name (device),
+ "host-port", pp_print_device_get_host_port (device),
+ "is-network-device", pp_print_device_is_network_device (device),
+ "window-id", window_id,
+ NULL);
+
+ self->cancellable = g_cancellable_new ();
+
+ pp_new_printer_add_async (new_printer,
+ self->cancellable,
+ printer_add_async_cb,
+ self);
+ }
+
+ g_object_unref (device);
+ }
+ }
+ else
+ {
+ emit_response (self, GTK_RESPONSE_CANCEL);
+ }
+}
diff --git a/panels/printers/pp-new-printer-dialog.h b/panels/printers/pp-new-printer-dialog.h
new file mode 100644
index 0000000..0a7d84b
--- /dev/null
+++ b/panels/printers/pp-new-printer-dialog.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include "pp-utils.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_NEW_PRINTER_DIALOG (pp_new_printer_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (PpNewPrinterDialog, pp_new_printer_dialog, PP, NEW_PRINTER_DIALOG, GObject)
+
+PpNewPrinterDialog *pp_new_printer_dialog_new (GtkWindow *parent,
+ PPDList *ppd_list);
+void pp_new_printer_dialog_set_ppd_list (PpNewPrinterDialog *dialog,
+ PPDList *list);
+
+G_END_DECLS
diff --git a/panels/printers/pp-new-printer.c b/panels/printers/pp-new-printer.c
new file mode 100644
index 0000000..1c325b4
--- /dev/null
+++ b/panels/printers/pp-new-printer.c
@@ -0,0 +1,1310 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "pp-new-printer.h"
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+
+#include "pp-utils.h"
+#include "pp-maintenance-command.h"
+
+#define PACKAGE_KIT_BUS "org.freedesktop.PackageKit"
+#define PACKAGE_KIT_PATH "/org/freedesktop/PackageKit"
+#define PACKAGE_KIT_MODIFY_IFACE "org.freedesktop.PackageKit.Modify"
+#define PACKAGE_KIT_QUERY_IFACE "org.freedesktop.PackageKit.Query"
+
+#define DBUS_TIMEOUT 120000
+#define DBUS_TIMEOUT_LONG 600000
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetState(ipp) ipp->state
+#endif
+
+struct _PpNewPrinter
+{
+ GObject parent_instance;
+
+ gchar *name;
+ gchar *original_name;
+ gchar *device_uri;
+ gchar *device_id;
+ gchar *ppd_name;
+ gchar *ppd_file_name;
+ gchar *info;
+ gchar *location;
+ gchar *make_and_model;
+ gchar *host_name;
+ gint host_port;
+ gboolean is_network_device;
+ guint window_id;
+ gboolean unlink_ppd_file;
+
+ GTask *task;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (PpNewPrinter, pp_new_printer, G_TYPE_OBJECT);
+
+enum {
+ PROP_0 = 0,
+ PROP_NAME,
+ PROP_ORIGINAL_NAME,
+ PROP_DEVICE_URI,
+ PROP_DEVICE_ID,
+ PROP_PPD_NAME,
+ PROP_PPD_FILE_NAME,
+ PROP_INFO,
+ PROP_LOCATION,
+ PROP_MAKE_AND_MODEL,
+ PROP_HOST_NAME,
+ PROP_HOST_PORT,
+ PROP_IS_NETWORK_DEVICE,
+ PROP_WINDOW_ID
+};
+
+static void
+pp_new_printer_finalize (GObject *object)
+{
+ PpNewPrinter *self = PP_NEW_PRINTER (object);
+
+ if (self->unlink_ppd_file && self->ppd_file_name)
+ g_unlink (self->ppd_file_name);
+
+ g_clear_pointer (&self->name, g_free);
+ g_clear_pointer (&self->original_name, g_free);
+ g_clear_pointer (&self->device_uri, g_free);
+ g_clear_pointer (&self->device_id, g_free);
+ g_clear_pointer (&self->ppd_name, g_free);
+ g_clear_pointer (&self->ppd_file_name, g_free);
+ g_clear_pointer (&self->info, g_free);
+ g_clear_pointer (&self->location, g_free);
+ g_clear_pointer (&self->make_and_model, g_free);
+ g_clear_pointer (&self->host_name, g_free);
+ g_clear_object (&self->task);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (pp_new_printer_parent_class)->finalize (object);
+}
+
+static void
+pp_new_printer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *param_spec)
+{
+ PpNewPrinter *self = PP_NEW_PRINTER (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, self->name);
+ break;
+ case PROP_ORIGINAL_NAME:
+ g_value_set_string (value, self->original_name);
+ break;
+ case PROP_DEVICE_URI:
+ g_value_set_string (value, self->device_uri);
+ break;
+ case PROP_DEVICE_ID:
+ g_value_set_string (value, self->device_id);
+ break;
+ case PROP_PPD_NAME:
+ g_value_set_string (value, self->ppd_name);
+ break;
+ case PROP_PPD_FILE_NAME:
+ g_value_set_string (value, self->ppd_file_name);
+ break;
+ case PROP_INFO:
+ g_value_set_string (value, self->info);
+ break;
+ case PROP_LOCATION:
+ g_value_set_string (value, self->location);
+ break;
+ case PROP_MAKE_AND_MODEL:
+ g_value_set_string (value, self->make_and_model);
+ break;
+ case PROP_HOST_NAME:
+ g_value_set_string (value, self->host_name);
+ break;
+ case PROP_HOST_PORT:
+ g_value_set_int (value, self->host_port);
+ break;
+ case PROP_IS_NETWORK_DEVICE:
+ g_value_set_boolean (value, self->is_network_device);
+ break;
+ case PROP_WINDOW_ID:
+ g_value_set_uint (value, self->window_id);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_new_printer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *param_spec)
+{
+ PpNewPrinter *self = PP_NEW_PRINTER (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_free (self->name);
+ self->name = g_value_dup_string (value);
+ break;
+ case PROP_ORIGINAL_NAME:
+ g_free (self->original_name);
+ self->original_name = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_URI:
+ g_free (self->device_uri);
+ self->device_uri = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_ID:
+ g_free (self->device_id);
+ self->device_id = g_value_dup_string (value);
+ break;
+ case PROP_PPD_NAME:
+ g_free (self->ppd_name);
+ self->ppd_name = g_value_dup_string (value);
+ break;
+ case PROP_PPD_FILE_NAME:
+ g_free (self->ppd_file_name);
+ self->ppd_file_name = g_value_dup_string (value);
+ break;
+ case PROP_INFO:
+ g_free (self->info);
+ self->info = g_value_dup_string (value);
+ break;
+ case PROP_LOCATION:
+ g_free (self->location);
+ self->location = g_value_dup_string (value);
+ break;
+ case PROP_MAKE_AND_MODEL:
+ g_free (self->make_and_model);
+ self->make_and_model = g_value_dup_string (value);
+ break;
+ case PROP_HOST_NAME:
+ g_free (self->host_name);
+ self->host_name = g_value_dup_string (value);
+ break;
+ case PROP_HOST_PORT:
+ self->host_port = g_value_get_int (value);
+ break;
+ case PROP_IS_NETWORK_DEVICE:
+ self->is_network_device = g_value_get_boolean (value);
+ break;
+ case PROP_WINDOW_ID:
+ self->window_id = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_new_printer_class_init (PpNewPrinterClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pp_new_printer_set_property;
+ gobject_class->get_property = pp_new_printer_get_property;
+
+ gobject_class->finalize = pp_new_printer_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "The new printer's name",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_ORIGINAL_NAME,
+ g_param_spec_string ("original-name",
+ "Original name",
+ "Original name of the new printer",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_DEVICE_URI,
+ g_param_spec_string ("device-uri",
+ "Device URI",
+ "The new printer's device URI",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_DEVICE_ID,
+ g_param_spec_string ("device-id",
+ "DeviceID",
+ "The new printer's DeviceID",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_PPD_NAME,
+ g_param_spec_string ("ppd-name",
+ "PPD name",
+ "Name of PPD for the new printer",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_PPD_FILE_NAME,
+ g_param_spec_string ("ppd-file-name",
+ "PPD file name",
+ "PPD file for the new printer",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_INFO,
+ g_param_spec_string ("info",
+ "Printer info",
+ "The new printer's info",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_LOCATION,
+ g_param_spec_string ("location",
+ "Printer location",
+ "The new printer's location",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_MAKE_AND_MODEL,
+ g_param_spec_string ("make-and-model",
+ "Printer make and model",
+ "The new printer's make and model",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_HOST_NAME,
+ g_param_spec_string ("host-name",
+ "Hostname",
+ "The new printer's hostname",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_HOST_PORT,
+ g_param_spec_int ("host-port",
+ "Host port",
+ "The port of the host",
+ 0, G_MAXINT32, 631,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_IS_NETWORK_DEVICE,
+ g_param_spec_boolean ("is-network-device",
+ "Network device",
+ "Whether the new printer is a network device",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_WINDOW_ID,
+ g_param_spec_uint ("window-id",
+ "WindowID",
+ "Window ID of parent window",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+}
+
+static void
+pp_new_printer_init (PpNewPrinter *self)
+{
+}
+
+PpNewPrinter *
+pp_new_printer_new ()
+{
+ return g_object_new (PP_TYPE_NEW_PRINTER, NULL);
+}
+
+static void printer_configure_async (PpNewPrinter *self);
+
+static void
+_pp_new_printer_add_async_cb (gboolean success,
+ PpNewPrinter *self)
+{
+ if (!success)
+ {
+ g_task_return_new_error (self->task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Installation of the new printer failed.");
+ return;
+ }
+
+ g_task_return_boolean (self->task, success);
+}
+
+static void
+printer_add_real_async_cb (cups_dest_t *destination,
+ gpointer user_data)
+{
+ PpNewPrinter *self = user_data;
+ gboolean success = FALSE;
+
+ if (destination)
+ {
+ success = TRUE;
+ cupsFreeDests (1, destination);
+ }
+
+ if (success)
+ {
+ printer_configure_async (self);
+ }
+ else
+ {
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+}
+
+static void
+printer_add_real_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinter *self = user_data;
+ g_autoptr(GVariant) output = NULL;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ const gchar *ret_error;
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: addition of printer %s failed: %s", self->name, ret_error);
+ }
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ get_named_dest_async (self->name,
+ printer_add_real_async_cb,
+ self);
+ }
+}
+
+static void
+printer_add_real_async (PpNewPrinter *self)
+{
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+
+ if (!self->ppd_name && !self->ppd_file_name)
+ {
+ _pp_new_printer_add_async_cb (FALSE, self);
+ return;
+ }
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ _pp_new_printer_add_async_cb (FALSE, self);
+ return;
+ }
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ self->ppd_name ? "PrinterAdd" : "PrinterAddWithPpdFile",
+ g_variant_new ("(sssss)",
+ self->name,
+ self->device_uri,
+ self->ppd_name ? self->ppd_name : self->ppd_file_name,
+ self->info ? self->info : "",
+ self->location ? self->location : ""),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ NULL,
+ printer_add_real_async_dbus_cb,
+ self);
+}
+
+static PPDName *
+get_ppd_item_from_output (GVariant *output)
+{
+ g_autoptr(GVariant) array = NULL;
+ gint j;
+ static const char * const match_levels[] = {
+ "exact-cmd",
+ "exact",
+ "close",
+ "generic",
+ "none"};
+
+ if (output == NULL)
+ return NULL;
+
+ g_variant_get (output, "(@a(ss))", &array);
+ for (j = 0; j < G_N_ELEMENTS (match_levels); j++)
+ {
+ g_autoptr(GVariantIter) iter = NULL;
+ const gchar *driver, *match;
+
+ g_variant_get (array, "a(ss)", &iter);
+ while (g_variant_iter_next (iter, "(&s&s)", &driver, &match))
+ {
+ PPDName *ppd_item;
+
+ if (!g_str_equal (match, match_levels[j]))
+ continue;
+
+ ppd_item = g_new0 (PPDName, 1);
+ ppd_item->ppd_name = g_strdup (driver);
+
+ if (g_strcmp0 (match, "exact-cmd") == 0)
+ ppd_item->ppd_match_level = PPD_EXACT_CMD_MATCH;
+ else if (g_strcmp0 (match, "exact") == 0)
+ ppd_item->ppd_match_level = PPD_EXACT_MATCH;
+ else if (g_strcmp0 (match, "close") == 0)
+ ppd_item->ppd_match_level = PPD_CLOSE_MATCH;
+ else if (g_strcmp0 (match, "generic") == 0)
+ ppd_item->ppd_match_level = PPD_GENERIC_MATCH;
+ else if (g_strcmp0 (match, "none") == 0)
+ ppd_item->ppd_match_level = PPD_NO_MATCH;
+
+ return ppd_item;
+ }
+ }
+
+ return NULL;
+}
+
+
+static void
+printer_add_async_scb3 (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinter *self = user_data;
+ g_autoptr(GVariant) output = NULL;
+ PPDName *ppd_item = NULL;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ ppd_item = get_ppd_item_from_output (output);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ ppd_item && ppd_item->ppd_name)
+ {
+ self->ppd_name = g_strdup (ppd_item->ppd_name);
+ printer_add_real_async (self);
+ }
+ else
+ {
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+
+ if (ppd_item)
+ {
+ g_free (ppd_item->ppd_name);
+ g_free (ppd_item);
+ }
+}
+
+static void
+install_printer_drivers_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinter *self = user_data;
+ g_autoptr(GVariant) output = NULL;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ GDBusConnection *bus;
+ g_autoptr(GError) bus_error = NULL;
+
+ /* Try whether CUPS has a driver for the new printer */
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &bus_error);
+ if (bus)
+ {
+ g_dbus_connection_call (bus,
+ SCP_BUS,
+ SCP_PATH,
+ SCP_IFACE,
+ "GetBestDrivers",
+ g_variant_new ("(sss)",
+ self->device_id,
+ self->make_and_model ? self->make_and_model : "",
+ self->device_uri ? self->device_uri : ""),
+ G_VARIANT_TYPE ("(a(ss))"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ self->cancellable,
+ printer_add_async_scb3,
+ self);
+ }
+ else
+ {
+ g_warning ("Failed to get system bus: %s", bus_error->message);
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+ }
+}
+
+static void
+printer_add_async_scb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpNewPrinter *self = user_data;
+ GDBusConnection *bus;
+ GVariantBuilder array_builder;
+ g_autoptr(GVariant) output = NULL;
+ gboolean cancelled = FALSE;
+ PPDName *ppd_item = NULL;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ ppd_item = get_ppd_item_from_output (output);
+ }
+ else
+ {
+ cancelled = g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+
+ if (!cancelled)
+ g_warning ("%s", error->message);
+ }
+
+ if (!cancelled)
+ {
+ if (ppd_item == NULL || ppd_item->ppd_match_level < PPD_EXACT_MATCH)
+ {
+ g_autoptr(GError) bus_error = NULL;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &bus_error);
+ if (bus)
+ {
+ g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&array_builder, "s", self->device_id);
+
+ g_dbus_connection_call (bus,
+ PACKAGE_KIT_BUS,
+ PACKAGE_KIT_PATH,
+ PACKAGE_KIT_MODIFY_IFACE,
+ "InstallPrinterDrivers",
+ g_variant_new ("(uass)",
+ self->window_id,
+ &array_builder,
+ "hide-finished"),
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ NULL,
+ install_printer_drivers_cb,
+ self);
+ }
+ else
+ {
+ g_warning ("Failed to get session bus: %s", bus_error->message);
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+ }
+ else if (ppd_item && ppd_item->ppd_name)
+ {
+ self->ppd_name = g_strdup (ppd_item->ppd_name);
+ printer_add_real_async (self);
+ }
+ else
+ {
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+ }
+
+ if (ppd_item)
+ {
+ g_free (ppd_item->ppd_name);
+ g_free (ppd_item);
+ }
+}
+
+static void
+printer_add_async_scb4 (const gchar *ppd_filename,
+ gpointer user_data)
+{
+ PpNewPrinter *self = user_data;
+
+ self->ppd_file_name = g_strdup (ppd_filename);
+ if (self->ppd_file_name)
+ {
+ self->unlink_ppd_file = TRUE;
+ printer_add_real_async (self);
+ }
+ else
+ {
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+}
+
+static GList *
+glist_uniq (GList *list)
+{
+ GList *result = NULL;
+ GList *iter = NULL;
+ GList *tmp = NULL;
+
+ for (iter = list; iter; iter = iter->next)
+ {
+ if (tmp == NULL ||
+ g_strcmp0 ((gchar *) tmp->data, (gchar *) iter->data) != 0)
+ {
+ tmp = iter;
+ result = g_list_append (result, g_strdup (iter->data));
+ }
+ }
+
+ g_list_free_full (list, g_free);
+
+ return result;
+}
+
+typedef struct
+{
+ PpNewPrinter *new_printer;
+ GCancellable *cancellable;
+ gboolean set_accept_jobs_finished;
+ gboolean set_enabled_finished;
+ gboolean autoconfigure_finished;
+ gboolean set_media_size_finished;
+ gboolean install_missing_executables_finished;
+} PCData;
+
+static void
+printer_configure_async_finish (PCData *data)
+{
+ PpNewPrinter *self = data->new_printer;
+
+ if (data->set_accept_jobs_finished &&
+ data->set_enabled_finished &&
+ (data->autoconfigure_finished || self->is_network_device) &&
+ data->set_media_size_finished &&
+ data->install_missing_executables_finished)
+ {
+ _pp_new_printer_add_async_cb (TRUE, data->new_printer);
+
+ g_clear_object (&data->cancellable);
+ g_free (data);
+ }
+}
+
+static void
+pao_cb (gboolean success,
+ gpointer user_data)
+{
+ PCData *data = (PCData *) user_data;
+
+ data->set_media_size_finished = TRUE;
+ printer_configure_async_finish (data);
+}
+
+static void
+printer_set_accepting_jobs_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ PCData *data = (PCData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ data->set_accept_jobs_finished = TRUE;
+ printer_configure_async_finish (data);
+}
+
+static void
+printer_set_enabled_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ PCData *data = (PCData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ data->set_enabled_finished = TRUE;
+ printer_configure_async_finish (data);
+}
+
+typedef struct
+{
+ GList *executables;
+ GList *packages;
+ guint window_id;
+ gchar *ppd_file_name;
+ GCancellable *cancellable;
+ gpointer user_data;
+} IMEData;
+
+static void
+install_missing_executables_cb (IMEData *data)
+{
+ PCData *pc_data = (PCData *) data->user_data;
+
+ pc_data->install_missing_executables_finished = TRUE;
+ printer_configure_async_finish (pc_data);
+
+ if (data->ppd_file_name)
+ {
+ g_unlink (data->ppd_file_name);
+ g_clear_pointer (&data->ppd_file_name, g_free);
+ }
+
+ if (data->executables)
+ {
+ g_list_free_full (data->executables, g_free);
+ data->executables = NULL;
+ }
+
+ if (data->packages)
+ {
+ g_list_free_full (data->packages, g_free);
+ data->packages = NULL;
+ }
+
+ if (data->cancellable)
+ g_clear_object (&data->cancellable);
+
+ g_free (data);
+}
+
+static void
+install_package_names_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ IMEData *data = (IMEData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ install_missing_executables_cb (data);
+}
+
+
+static void
+search_files_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ IMEData *data = (IMEData *) user_data;
+ g_autoptr(GError) error = NULL;
+ GList *item;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ if (output)
+ {
+ gboolean installed;
+ gchar *package;
+
+ g_variant_get (output,
+ "(bs)",
+ &installed,
+ &package);
+
+ if (!installed)
+ data->packages = g_list_append (data->packages, g_strdup (package));
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (data->executables)
+ {
+ item = data->executables;
+ g_dbus_connection_call (G_DBUS_CONNECTION (source_object),
+ PACKAGE_KIT_BUS,
+ PACKAGE_KIT_PATH,
+ PACKAGE_KIT_QUERY_IFACE,
+ "SearchFile",
+ g_variant_new ("(ss)",
+ (gchar *) item->data,
+ ""),
+ G_VARIANT_TYPE ("(bs)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ data->cancellable,
+ search_files_cb,
+ data);
+
+ data->executables = g_list_remove_link (data->executables, item);
+ g_list_free_full (item, g_free);
+ }
+ else
+ {
+ GVariantBuilder array_builder;
+ GList *pkg_iter;
+
+ data->packages = g_list_sort (data->packages, (GCompareFunc) g_strcmp0);
+ data->packages = glist_uniq (data->packages);
+
+ if (data->packages)
+ {
+ g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
+
+ for (pkg_iter = data->packages; pkg_iter; pkg_iter = pkg_iter->next)
+ g_variant_builder_add (&array_builder,
+ "s",
+ (gchar *) pkg_iter->data);
+
+ g_dbus_connection_call (G_DBUS_CONNECTION (source_object),
+ PACKAGE_KIT_BUS,
+ PACKAGE_KIT_PATH,
+ PACKAGE_KIT_MODIFY_IFACE,
+ "InstallPackageNames",
+ g_variant_new ("(uass)",
+ data->window_id,
+ &array_builder,
+ "hide-finished"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ data->cancellable,
+ install_package_names_cb,
+ data);
+
+ g_list_free_full (data->packages, g_free);
+ data->packages = NULL;
+ }
+ else
+ {
+ g_object_unref (source_object);
+ install_missing_executables_cb (data);
+ }
+ }
+}
+
+static void
+get_missing_executables_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ IMEData *data = (IMEData *) user_data;
+ g_autoptr(GError) error = NULL;
+ GList *executables = NULL;
+ GList *item;
+
+ if (data->ppd_file_name)
+ {
+ g_unlink (data->ppd_file_name);
+ g_clear_pointer (&data->ppd_file_name, g_free);
+ }
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+
+ if (output)
+ {
+ g_autoptr(GVariant) array = NULL;
+ g_autoptr(GVariantIter) iter = NULL;
+ const gchar *executable;
+
+ g_variant_get (output, "(@as)", &array);
+
+ g_variant_get (array, "as", &iter);
+ while (g_variant_iter_next (iter, "&s", &executable))
+ executables = g_list_append (executables, g_strdup (executable));
+ }
+ else if (error->domain == G_DBUS_ERROR &&
+ (error->code == G_DBUS_ERROR_SERVICE_UNKNOWN ||
+ error->code == G_DBUS_ERROR_UNKNOWN_METHOD))
+ {
+ g_warning ("Install system-config-printer which provides \
+DBus method \"MissingExecutables\" to find missing executables and filters.");
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ executables = g_list_sort (executables, (GCompareFunc) g_strcmp0);
+ executables = glist_uniq (executables);
+
+ if (executables)
+ {
+ data->executables = executables;
+
+ item = data->executables;
+ g_dbus_connection_call (g_object_ref (G_DBUS_CONNECTION (source_object)),
+ PACKAGE_KIT_BUS,
+ PACKAGE_KIT_PATH,
+ PACKAGE_KIT_QUERY_IFACE,
+ "SearchFile",
+ g_variant_new ("(ss)",
+ (gchar *) item->data,
+ ""),
+ G_VARIANT_TYPE ("(bs)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ data->cancellable,
+ search_files_cb,
+ data);
+
+ data->executables = g_list_remove_link (data->executables, item);
+ g_list_free_full (item, g_free);
+ }
+ else
+ {
+ g_object_unref (source_object);
+ install_missing_executables_cb (data);
+ }
+}
+
+static void
+printer_get_ppd_cb (const gchar *ppd_filename,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ IMEData *data = (IMEData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ data->ppd_file_name = g_strdup (ppd_filename);
+ if (data->ppd_file_name)
+ {
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("%s", error->message);
+ }
+ else
+ {
+ g_dbus_connection_call (bus,
+ SCP_BUS,
+ SCP_PATH,
+ SCP_IFACE,
+ "MissingExecutables",
+ g_variant_new ("(s)", data->ppd_file_name),
+ G_VARIANT_TYPE ("(as)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ data->cancellable,
+ get_missing_executables_cb,
+ data);
+ return;
+ }
+ }
+
+ install_missing_executables_cb (data);
+}
+
+static void
+pp_maintenance_command_execute_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpMaintenanceCommand *command = (PpMaintenanceCommand *) source_object;
+ g_autoptr(GError) error = NULL;
+ PCData *data;
+ gboolean result;
+
+ result = pp_maintenance_command_execute_finish (command, res, &error);
+ g_object_unref (source_object);
+
+ if (result)
+ {
+ data = (PCData *) user_data;
+
+ data->autoconfigure_finished = TRUE;
+ printer_configure_async_finish (data);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ data = (PCData *) user_data;
+
+ g_warning ("%s", error->message);
+
+ data->autoconfigure_finished = TRUE;
+ printer_configure_async_finish (data);
+ }
+ }
+}
+
+static void
+printer_configure_async (PpNewPrinter *self)
+{
+ GDBusConnection *bus;
+ PCData *data;
+ IMEData *ime_data;
+ gchar **values;
+ g_autoptr(GError) error = NULL;
+
+ data = g_new0 (PCData, 1);
+ data->new_printer = self;
+ data->set_accept_jobs_finished = FALSE;
+ data->set_enabled_finished = FALSE;
+ data->autoconfigure_finished = FALSE;
+ data->set_media_size_finished = FALSE;
+ data->install_missing_executables_finished = FALSE;
+
+ /* Enable printer and make it accept jobs */
+ if (self->name)
+ {
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (bus)
+ {
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetAcceptJobs",
+ g_variant_new ("(sbs)",
+ self->name,
+ TRUE,
+ ""),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ printer_set_accepting_jobs_cb,
+ data);
+
+ g_dbus_connection_call (g_object_ref (bus),
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetEnabled",
+ g_variant_new ("(sb)",
+ self->name,
+ TRUE),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ printer_set_enabled_cb,
+ data);
+ }
+ else
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ data->set_accept_jobs_finished = TRUE;
+ data->set_enabled_finished = TRUE;
+ }
+ }
+ else
+ {
+ data->set_accept_jobs_finished = TRUE;
+ data->set_enabled_finished = TRUE;
+ }
+
+ /* Run autoconfiguration of printer */
+ if (!self->is_network_device)
+ {
+ PpMaintenanceCommand *command;
+ command = pp_maintenance_command_new (self->name,
+ "autoconfigure",
+ NULL,
+ /* Translators: Name of job which makes printer to autoconfigure itself */
+ _("Automatic configuration"));
+
+ pp_maintenance_command_execute_async (command,
+ NULL,
+ pp_maintenance_command_execute_cb,
+ data);
+ }
+
+ /* Set media size for printer */
+ values = g_new0 (gchar *, 2);
+ values[0] = g_strdup (get_page_size_from_locale ());
+
+ printer_add_option_async (self->name, "PageSize", values, FALSE, NULL, pao_cb, data);
+
+ g_strfreev (values);
+
+ /* Install missing executables for printer */
+ ime_data = g_new0 (IMEData, 1);
+ ime_data->window_id = self->window_id;
+ if (data->cancellable)
+ ime_data->cancellable = g_object_ref (data->cancellable);
+ ime_data->user_data = data;
+
+ printer_get_ppd_async (self->name,
+ NULL,
+ 0,
+ printer_get_ppd_cb,
+ ime_data);
+}
+
+void
+pp_new_printer_add_async (PpNewPrinter *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ self->task = g_task_new (self, cancellable, callback, user_data);
+ self->cancellable = g_object_ref (cancellable);
+
+ if (self->ppd_name || self->ppd_file_name)
+ {
+ /* We have everything we need */
+ printer_add_real_async (self);
+ }
+ else if (self->device_id)
+ {
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+
+ /* Try whether CUPS has a driver for the new printer */
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (bus)
+ {
+ g_dbus_connection_call (bus,
+ SCP_BUS,
+ SCP_PATH,
+ SCP_IFACE,
+ "GetBestDrivers",
+ g_variant_new ("(sss)",
+ self->device_id,
+ self->make_and_model ? self->make_and_model : "",
+ self->device_uri ? self->device_uri : ""),
+ G_VARIANT_TYPE ("(a(ss))"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ cancellable,
+ printer_add_async_scb,
+ self);
+ }
+ else
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+ }
+ else if (self->original_name && self->host_name)
+ {
+ /* Try to get PPD from remote CUPS */
+ printer_get_ppd_async (self->original_name,
+ self->host_name,
+ self->host_port,
+ printer_add_async_scb4,
+ self);
+ }
+ else
+ {
+ _pp_new_printer_add_async_cb (FALSE, self);
+ }
+}
+
+gboolean
+pp_new_printer_add_finish (PpNewPrinter *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
diff --git a/panels/printers/pp-new-printer.h b/panels/printers/pp-new-printer.h
new file mode 100644
index 0000000..207bac4
--- /dev/null
+++ b/panels/printers/pp-new-printer.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_NEW_PRINTER (pp_new_printer_get_type ())
+G_DECLARE_FINAL_TYPE (PpNewPrinter, pp_new_printer, PP, NEW_PRINTER, GObject)
+
+PpNewPrinter *pp_new_printer_new (void);
+
+void pp_new_printer_add_async (PpNewPrinter *host,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_new_printer_add_finish (PpNewPrinter *host,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/panels/printers/pp-options-dialog.c b/panels/printers/pp-options-dialog.c
new file mode 100644
index 0000000..05f8e72
--- /dev/null
+++ b/panels/printers/pp-options-dialog.c
@@ -0,0 +1,959 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-options-dialog.h"
+#include "pp-maintenance-command.h"
+#include "pp-ppd-option-widget.h"
+#include "pp-ipp-option-widget.h"
+#include "pp-utils.h"
+#include "pp-printer.h"
+
+struct _PpOptionsDialog {
+ GtkDialog parent_instance;
+
+ GtkTreeSelection *categories_selection;
+ GtkTreeView *categories_treeview;
+ GtkBox *main_box;
+ GtkNotebook *notebook;
+ GtkSpinner *spinner;
+ GtkStack *stack;
+
+ gchar *printer_name;
+
+ gchar *ppd_filename;
+ gboolean ppd_filename_set;
+
+ cups_dest_t *destination;
+ gboolean destination_set;
+
+ GHashTable *ipp_attributes;
+ gboolean ipp_attributes_set;
+
+ gboolean sensitive;
+};
+
+G_DEFINE_TYPE (PpOptionsDialog, pp_options_dialog, GTK_TYPE_DIALOG)
+
+enum
+{
+ CATEGORY_IDS_COLUMN = 0,
+ CATEGORY_NAMES_COLUMN
+};
+
+/* These lists come from Gtk+ */
+/* TODO: Only "Resolution" currently has a context to disambiguate it from
+ * the display settings. All of these should have contexts, but it
+ * was late in the release cycle and this partial solution was
+ * preferable. See:
+ * https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/414#note_446778
+ */
+static const struct {
+ const char *keyword;
+ const char *translation_context;
+ const char *translation;
+} ppd_option_translations[] = {
+ { "Duplex", NULL, N_("Two Sided") },
+ { "MediaType", NULL, N_("Paper Type") },
+ { "InputSlot", NULL, N_("Paper Source") },
+ { "OutputBin", NULL, N_("Output Tray") },
+ { "Resolution", "printing option", NC_("printing option", "Resolution") },
+ { "PreFilter", NULL, N_("GhostScript pre-filtering") },
+};
+
+/* keep sorted when changing */
+static const char *allowed_page_setup_options[] = {
+ "InputSlot",
+ "MediaType",
+ "OutputBin",
+ "PageSize",
+};
+
+/* keep sorted when changing */
+static const char *allowed_color_options[] = {
+ "BRColorEnhancement",
+ "BRColorMatching",
+ "BRColorMatching",
+ "BRColorMode",
+ "BRGammaValue",
+ "BRImprovedGray",
+ "BlackSubstitution",
+ "ColorModel",
+ "HPCMYKInks",
+ "HPCSGraphics",
+ "HPCSImages",
+ "HPCSText",
+ "HPColorSmart",
+ "RPSBlackMode",
+ "RPSBlackOverPrint",
+ "Rcmyksimulation",
+};
+
+/* keep sorted when changing */
+static const char *allowed_color_groups[] = {
+ "Color",
+ "Color1",
+ "Color2",
+ "ColorBalance",
+ "ColorPage",
+ "ColorSettings1",
+ "ColorSettings2",
+ "ColorSettings3",
+ "ColorSettings4",
+ "EPColorSettings",
+ "FPColorWise1",
+ "FPColorWise2",
+ "FPColorWise3",
+ "FPColorWise4",
+ "FPColorWise5",
+ "HPCMYKInksPanel",
+ "HPColorOptions",
+ "HPColorOptionsPanel",
+ "HPColorQualityOptionsPanel",
+ "ManualColor",
+};
+
+/* keep sorted when changing */
+static const char *allowed_image_quality_options[] = {
+ "BRDocument",
+ "BRHalfTonePattern",
+ "BRNormalPrt",
+ "BRPrintQuality",
+ "BitsPerPixel",
+ "Darkness",
+ "Dithering",
+ "EconoMode",
+ "Economode",
+ "HPEconoMode",
+ "HPEdgeControl",
+ "HPGraphicsHalftone",
+ "HPHalftone",
+ "HPImagingOptions",
+ "HPLJDensity",
+ "HPPhotoHalftone",
+ "HPPrintQualityOptions",
+ "HPResolutionOptions",
+ "OutputMode",
+ "REt",
+ "RPSBitsPerPixel",
+ "RPSDitherType",
+ "Resolution",
+ "ScreenLock",
+ "Smoothing",
+ "TonerSaveMode",
+ "UCRGCRForImage",
+};
+
+/* keep sorted when changing */
+static const char *allowed_image_quality_groups[] = {
+ "EPQualitySettings",
+ "FPImageQuality1",
+ "FPImageQuality2",
+ "FPImageQuality3",
+ "ImageQualityPage",
+ "Quality",
+};
+
+/* keep sorted when changing */
+static const char * allowed_finishing_options[] = {
+ "BindColor",
+ "BindEdge",
+ "BindType",
+ "BindWhen",
+ "Booklet",
+ "FoldType",
+ "FoldWhen",
+ "HPStaplerOptions",
+ "Jog",
+ "Slipsheet",
+ "Sorter",
+ "StapleLocation",
+ "StapleOrientation",
+ "StapleWhen",
+ "StapleX",
+ "StapleY",
+};
+
+/* keep sorted when changing */
+static const char *allowed_job_groups[] = {
+ "JobHandling",
+ "JobLog",
+};
+
+/* keep sorted when changing */
+static const char *allowed_finishing_groups[] = {
+ "Booklet",
+ "BookletCover",
+ "BookletModeOptions",
+ "FPFinishing1",
+ "FPFinishing2",
+ "FPFinishing3",
+ "FPFinishing4",
+ "Finishing",
+ "FinishingOptions",
+ "FinishingPage",
+ "HPBookletPanel",
+ "HPFinishing",
+ "HPFinishingOptions",
+ "HPFinishingPanel",
+};
+
+/* keep sorted when changing */
+static const char *allowed_installable_options_groups[] = {
+ "InstallableOptions",
+};
+
+/* keep sorted when changing */
+static const char *allowed_page_setup_groups[] = {
+ "HPMarginAndLayout",
+ "OutputControl",
+ "PaperHandling",
+ "Paper",
+ "Source",
+};
+
+/* keep sorted when changing */
+static const char *disallowed_ppd_options[] = {
+ "Collate",
+ "Copies",
+ "Duplex",
+ "HPManualDuplexOrientation",
+ "HPManualDuplexSwitch",
+ "OutputOrder",
+ "PageRegion"
+};
+
+static int
+strptr_cmp (const void *a,
+ const void *b)
+{
+ char **aa = (char **)a;
+ char **bb = (char **)b;
+ return strcmp (*aa, *bb);
+}
+
+static gboolean
+string_in_table (gchar *str,
+ const gchar *table[],
+ gint table_len)
+{
+ return bsearch (&str, table, table_len, sizeof (char *), (void *)strptr_cmp) != NULL;
+}
+
+#define STRING_IN_TABLE(_str, _table) (string_in_table (_str, _table, G_N_ELEMENTS (_table)))
+
+static const gchar *
+ppd_option_name_translate (ppd_option_t *option)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ppd_option_translations); i++)
+ {
+ if (g_strcmp0 (ppd_option_translations[i].keyword, option->keyword) == 0)
+ {
+ if (ppd_option_translations[i].translation_context)
+ return g_dpgettext2(NULL, ppd_option_translations[i].translation_context, ppd_option_translations[i].translation);
+ else
+ return _(ppd_option_translations[i].translation);
+ }
+ }
+
+ return option->text;
+}
+
+static gint
+grid_get_height (GtkWidget *grid)
+{
+ GList *children;
+ GList *child;
+ gint height = 0;
+ gint top_attach = 0;
+ gint max = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (grid));
+ for (child = children; child; child = g_list_next (child))
+ {
+ gtk_container_child_get (GTK_CONTAINER (grid), child->data,
+ "top-attach", &top_attach,
+ "height", &height,
+ NULL);
+
+ if (height + top_attach > max)
+ max = height + top_attach;
+ }
+
+ g_list_free (children);
+
+ return max;
+}
+
+static gboolean
+grid_is_empty (GtkWidget *grid)
+{
+ GList *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (grid));
+ if (children)
+ {
+ g_list_free (children);
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+}
+
+static GtkWidget *
+ipp_option_add (IPPAttribute *attr_supported,
+ IPPAttribute *attr_default,
+ const gchar *option_name,
+ const gchar *option_display_name,
+ const gchar *printer_name,
+ GtkWidget *grid,
+ gboolean sensitive)
+{
+ GtkStyleContext *context;
+ GtkWidget *widget;
+ GtkWidget *label;
+ gint position;
+
+ widget = (GtkWidget *) pp_ipp_option_widget_new (attr_supported,
+ attr_default,
+ option_name,
+ printer_name);
+ if (widget)
+ {
+ gtk_widget_show_all (widget);
+ gtk_widget_set_sensitive (widget, sensitive);
+ position = grid_get_height (grid);
+
+ label = gtk_label_new (option_display_name);
+ gtk_widget_show (GTK_WIDGET (label));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+ context = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (context, "dim-label");
+ gtk_widget_set_halign (label, GTK_ALIGN_END);
+ gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_start (label, 10);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
+
+ gtk_widget_set_margin_start (widget, 20);
+ gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
+ }
+
+ return widget;
+}
+
+static GtkWidget *
+ppd_option_add (ppd_option_t option,
+ const gchar *printer_name,
+ GtkWidget *grid,
+ gboolean sensitive)
+{
+ GtkStyleContext *context;
+ GtkWidget *widget;
+ GtkWidget *label;
+ gint position;
+
+ widget = (GtkWidget *) pp_ppd_option_widget_new (&option, printer_name);
+ if (widget)
+ {
+ gtk_widget_show_all (widget);
+ gtk_widget_set_sensitive (widget, sensitive);
+ position = grid_get_height (grid);
+
+ label = gtk_label_new (ppd_option_name_translate (&option));
+ gtk_widget_show (GTK_WIDGET (label));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+ context = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (context, "dim-label");
+ gtk_widget_set_halign (label, GTK_ALIGN_END);
+ gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_start (label, 10);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, position, 1, 1);
+
+ gtk_widget_set_margin_start (widget, 20);
+ gtk_grid_attach (GTK_GRID (grid), widget, 1, position, 1, 1);
+ }
+
+ return widget;
+}
+
+static GtkWidget *
+tab_grid_new ()
+{
+ GtkWidget *grid;
+
+ grid = gtk_grid_new ();
+ gtk_widget_show (GTK_WIDGET (grid));
+ gtk_container_set_border_width (GTK_CONTAINER (grid), 20);
+ gtk_grid_set_row_spacing (GTK_GRID (grid), 15);
+
+ return grid;
+}
+
+static void
+tab_add (PpOptionsDialog *self,
+ const gchar *tab_name,
+ GtkWidget *grid)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkWidget *scrolled_window;
+ gboolean unref_store = FALSE;
+ gint id;
+
+ if (!grid_is_empty (grid))
+ {
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_show (GTK_WIDGET (scrolled_window));
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), grid);
+
+ id = gtk_notebook_append_page (self->notebook,
+ scrolled_window,
+ NULL);
+
+ if (id >= 0)
+ {
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (self->categories_treeview));
+ if (!store)
+ {
+ store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+ unref_store = TRUE;
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ CATEGORY_IDS_COLUMN, id,
+ CATEGORY_NAMES_COLUMN, tab_name,
+ -1);
+
+ if (unref_store)
+ {
+ gtk_tree_view_set_model (self->categories_treeview, GTK_TREE_MODEL (store));
+ g_object_unref (store);
+ }
+ }
+ }
+ else
+ {
+ g_object_ref_sink (grid);
+ g_object_unref (grid);
+ }
+}
+
+static void
+category_selection_changed_cb (PpOptionsDialog *self)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint id = -1;
+
+ if (gtk_tree_selection_get_selected (self->categories_selection, &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ CATEGORY_IDS_COLUMN, &id,
+ -1);
+ }
+
+ if (id >= 0)
+ {
+ gtk_notebook_set_current_page (self->notebook, id);
+ }
+}
+
+static void
+populate_options_real (PpOptionsDialog *self)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ ppd_file_t *ppd_file;
+ GtkWidget *grid;
+ GtkWidget *general_tab_grid = tab_grid_new ();
+ GtkWidget *page_setup_tab_grid = tab_grid_new ();
+ GtkWidget *installable_options_tab_grid = tab_grid_new ();
+ GtkWidget *job_tab_grid = tab_grid_new ();
+ GtkWidget *image_quality_tab_grid = tab_grid_new ();
+ GtkWidget *color_tab_grid = tab_grid_new ();
+ GtkWidget *finishing_tab_grid = tab_grid_new ();
+ GtkWidget *advanced_tab_grid = tab_grid_new ();
+ gint i, j;
+
+ gtk_spinner_stop (self->spinner);
+
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->main_box));
+
+ if (self->ipp_attributes)
+ {
+ /* Add number-up option to Page Setup tab */
+ ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
+ "number-up-supported"),
+ g_hash_table_lookup (self->ipp_attributes,
+ "number-up-default"),
+ "number-up",
+ /* Translators: This option sets number of pages printed on one sheet */
+ _("Pages per side"),
+ self->printer_name,
+ page_setup_tab_grid,
+ self->sensitive);
+
+ /* Add sides option to Page Setup tab */
+ ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
+ "sides-supported"),
+ g_hash_table_lookup (self->ipp_attributes,
+ "sides-default"),
+ "sides",
+ /* Translators: This option sets whether to print on both sides of paper */
+ _("Two-sided"),
+ self->printer_name,
+ page_setup_tab_grid,
+ self->sensitive);
+
+ /* Add orientation-requested option to Page Setup tab */
+ ipp_option_add (g_hash_table_lookup (self->ipp_attributes,
+ "orientation-requested-supported"),
+ g_hash_table_lookup (self->ipp_attributes,
+ "orientation-requested-default"),
+ "orientation-requested",
+ /* Translators: This option sets orientation of print (portrait, landscape...) */
+ _("Orientation"),
+ self->printer_name,
+ page_setup_tab_grid,
+ self->sensitive);
+ }
+
+ if (self->destination && self->ppd_filename)
+ {
+ ppd_file = ppdOpenFile (self->ppd_filename);
+ ppdLocalize (ppd_file);
+
+ if (ppd_file)
+ {
+ ppdMarkDefaults (ppd_file);
+ cupsMarkOptions (ppd_file,
+ self->destination->num_options,
+ self->destination->options);
+
+ for (i = 0; i < ppd_file->num_groups; i++)
+ {
+ for (j = 0; j < ppd_file->groups[i].num_options; j++)
+ {
+ grid = NULL;
+
+ if (STRING_IN_TABLE (ppd_file->groups[i].name,
+ allowed_color_groups))
+ grid = color_tab_grid;
+ else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+ allowed_image_quality_groups))
+ grid = image_quality_tab_grid;
+ else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+ allowed_job_groups))
+ grid = job_tab_grid;
+ else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+ allowed_finishing_groups))
+ grid = finishing_tab_grid;
+ else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+ allowed_installable_options_groups))
+ grid = installable_options_tab_grid;
+ else if (STRING_IN_TABLE (ppd_file->groups[i].name,
+ allowed_page_setup_groups))
+ grid = page_setup_tab_grid;
+
+ if (!STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+ disallowed_ppd_options))
+ {
+ if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+ allowed_color_options))
+ grid = color_tab_grid;
+ else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+ allowed_image_quality_options))
+ grid = image_quality_tab_grid;
+ else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+ allowed_finishing_options))
+ grid = finishing_tab_grid;
+ else if (!grid && STRING_IN_TABLE (ppd_file->groups[i].options[j].keyword,
+ allowed_page_setup_options))
+ grid = page_setup_tab_grid;
+
+ if (!grid)
+ grid = advanced_tab_grid;
+
+ ppd_option_add (ppd_file->groups[i].options[j],
+ self->printer_name,
+ grid,
+ self->sensitive);
+ }
+ }
+ }
+
+ ppdClose (ppd_file);
+ }
+ }
+
+ self->ppd_filename_set = FALSE;
+ if (self->ppd_filename)
+ {
+ g_unlink (self->ppd_filename);
+ g_free (self->ppd_filename);
+ self->ppd_filename = NULL;
+ }
+
+ self->destination_set = FALSE;
+ if (self->destination)
+ {
+ cupsFreeDests (1, self->destination);
+ self->destination = NULL;
+ }
+
+ self->ipp_attributes_set = FALSE;
+ if (self->ipp_attributes)
+ {
+ g_hash_table_unref (self->ipp_attributes);
+ self->ipp_attributes = NULL;
+ }
+
+ /* Translators: "General" tab contains general printer options */
+ tab_add (self, C_("Printer Option Group", "General"), general_tab_grid);
+
+ /* Translators: "Page Setup" tab contains settings related to pages (page size, paper source, etc.) */
+ tab_add (self, C_("Printer Option Group", "Page Setup"), page_setup_tab_grid);
+
+ /* Translators: "Installable Options" tab contains settings of presence of installed options (amount of RAM, duplex unit, etc.) */
+ tab_add (self, C_("Printer Option Group", "Installable Options"), installable_options_tab_grid);
+
+ /* Translators: "Job" tab contains settings for jobs */
+ tab_add (self, C_("Printer Option Group", "Job"), job_tab_grid);
+
+ /* Translators: "Image Quality" tab contains settings for quality of output print (e.g. resolution) */
+ tab_add (self, C_("Printer Option Group", "Image Quality"), image_quality_tab_grid);
+
+ /* Translators: "Color" tab contains color settings (e.g. color printing) */
+ tab_add (self, C_("Printer Option Group", "Color"), color_tab_grid);
+
+ /* Translators: "Finishing" tab contains finishing settings (e.g. booklet printing) */
+ tab_add (self, C_("Printer Option Group", "Finishing"), finishing_tab_grid);
+
+ /* Translators: "Advanced" tab contains all others settings */
+ tab_add (self, C_("Printer Option Group", "Advanced"), advanced_tab_grid);
+
+ /* Select the first option group */
+ if ((model = gtk_tree_view_get_model (self->categories_treeview)) != NULL &&
+ gtk_tree_model_get_iter_first (model, &iter))
+ gtk_tree_selection_select_iter (self->categories_selection, &iter);
+}
+
+static void
+printer_get_ppd_cb (const gchar *ppd_filename,
+ gpointer user_data)
+{
+ PpOptionsDialog *self = (PpOptionsDialog *) user_data;
+
+ if (self->ppd_filename)
+ {
+ g_unlink (self->ppd_filename);
+ g_free (self->ppd_filename);
+ }
+
+ self->ppd_filename = g_strdup (ppd_filename);
+ self->ppd_filename_set = TRUE;
+
+ if (self->destination_set &&
+ self->ipp_attributes_set)
+ {
+ populate_options_real (self);
+ }
+}
+
+static void
+get_named_dest_cb (cups_dest_t *dest,
+ gpointer user_data)
+{
+ PpOptionsDialog *self = (PpOptionsDialog *) user_data;
+
+ if (self->destination)
+ cupsFreeDests (1, self->destination);
+
+ self->destination = dest;
+ self->destination_set = TRUE;
+
+ if (self->ppd_filename_set &&
+ self->ipp_attributes_set)
+ {
+ populate_options_real (self);
+ }
+}
+
+static void
+get_ipp_attributes_cb (GHashTable *table,
+ gpointer user_data)
+{
+ PpOptionsDialog *self = (PpOptionsDialog *) user_data;
+
+ if (self->ipp_attributes)
+ g_hash_table_unref (self->ipp_attributes);
+
+ self->ipp_attributes = table;
+ self->ipp_attributes_set = TRUE;
+
+ if (self->ppd_filename_set &&
+ self->destination_set)
+ {
+ populate_options_real (self);
+ }
+}
+
+static void
+populate_options (PpOptionsDialog *self)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ /*
+ * Options which we need to obtain through an IPP request
+ * to be able to fill the options dialog.
+ * *-supported - possible values of the option
+ * *-default - actual value of the option
+ */
+ const gchar *attributes[] =
+ { "number-up-supported",
+ "number-up-default",
+ "sides-supported",
+ "sides-default",
+ "orientation-requested-supported",
+ "orientation-requested-default",
+ NULL};
+
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->spinner));
+
+ renderer = gtk_cell_renderer_text_new ();
+
+ column = gtk_tree_view_column_new_with_attributes ("Categories", renderer,
+ "text", CATEGORY_NAMES_COLUMN, NULL);
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (self->categories_treeview, column);
+
+ gtk_spinner_start (self->spinner);
+
+ printer_get_ppd_async (self->printer_name,
+ NULL,
+ 0,
+ printer_get_ppd_cb,
+ self);
+
+ get_named_dest_async (self->printer_name,
+ get_named_dest_cb,
+ self);
+
+ get_ipp_attributes_async (self->printer_name,
+ (gchar **) attributes,
+ get_ipp_attributes_cb,
+ self);
+}
+
+static void
+pp_maintenance_command_execute_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpMaintenanceCommand *command = (PpMaintenanceCommand *) source_object;
+
+ pp_maintenance_command_execute_finish (command, res, NULL);
+
+ g_object_unref (command);
+}
+
+static gchar *
+get_testprint_filename (const gchar *datadir)
+{
+ const gchar *testprint[] = { "/data/testprint",
+ "/data/testprint.ps",
+ NULL };
+ gchar *filename = NULL;
+ gint i;
+
+ for (i = 0; testprint[i] != NULL; i++)
+ {
+ filename = g_strconcat (datadir, testprint[i], NULL);
+ if (g_access (filename, R_OK) == 0)
+ break;
+
+ g_clear_pointer (&filename, g_free);
+ }
+
+ return filename;
+}
+
+static void
+print_test_page_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ pp_printer_print_file_finish (PP_PRINTER (source_object),
+ result, NULL);
+
+ g_object_unref (source_object);
+}
+
+static void
+test_page_cb (PpOptionsDialog *self)
+{
+ gint i;
+
+ if (self->printer_name)
+ {
+ const gchar *const dirs[] = { "/usr/share/cups",
+ "/usr/local/share/cups",
+ NULL };
+ const gchar *datadir = NULL;
+ g_autofree gchar *filename = NULL;
+
+ datadir = getenv ("CUPS_DATADIR");
+ if (datadir != NULL)
+ {
+ filename = get_testprint_filename (datadir);
+ }
+ else
+ {
+ for (i = 0; dirs[i] != NULL && filename == NULL; i++)
+ filename = get_testprint_filename (dirs[i]);
+ }
+
+ if (filename != NULL)
+ {
+ PpPrinter *printer;
+
+ printer = pp_printer_new (self->printer_name);
+ pp_printer_print_file_async (printer,
+ filename,
+ /* Translators: Name of job which makes printer to print test page */
+ _("Test Page"),
+ NULL,
+ print_test_page_cb,
+ NULL);
+ }
+ else
+ {
+ PpMaintenanceCommand *command;
+
+ command = pp_maintenance_command_new (self->printer_name,
+ "PrintSelfTestPage",
+ NULL,
+ /* Translators: Name of job which makes printer to print test page */
+ _("Test page"));
+
+ pp_maintenance_command_execute_async (command, NULL, pp_maintenance_command_execute_cb, NULL);
+ }
+ }
+}
+
+PpOptionsDialog *
+pp_options_dialog_new (gchar *printer_name,
+ gboolean sensitive)
+{
+ PpOptionsDialog *self;
+
+ self = g_object_new (pp_options_dialog_get_type (),
+ "use-header-bar", 1,
+ NULL);
+
+ self->printer_name = g_strdup (printer_name);
+
+ self->sensitive = sensitive;
+
+ gtk_window_set_title (GTK_WINDOW (self), printer_name);
+
+ populate_options (self);
+
+ return self;
+}
+
+static void
+pp_options_dialog_dispose (GObject *object)
+{
+ PpOptionsDialog *self = PP_OPTIONS_DIALOG (object);
+
+ g_free (self->printer_name);
+ self->printer_name = NULL;
+
+ if (self->ppd_filename)
+ {
+ g_unlink (self->ppd_filename);
+ g_free (self->ppd_filename);
+ self->ppd_filename = NULL;
+ }
+
+ if (self->destination)
+ {
+ cupsFreeDests (1, self->destination);
+ self->destination = NULL;
+ }
+
+ if (self->ipp_attributes)
+ {
+ g_hash_table_unref (self->ipp_attributes);
+ self->ipp_attributes = NULL;
+ }
+
+ G_OBJECT_CLASS (pp_options_dialog_parent_class)->dispose (object);
+}
+
+void
+pp_options_dialog_class_init (PpOptionsDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = pp_options_dialog_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/printers/pp-options-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, categories_selection);
+ gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, categories_treeview);
+ gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, main_box);
+ gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, notebook);
+ gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, spinner);
+ gtk_widget_class_bind_template_child (widget_class, PpOptionsDialog, stack);
+
+ gtk_widget_class_bind_template_callback (widget_class, category_selection_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, test_page_cb);
+}
+
+void
+pp_options_dialog_init (PpOptionsDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/panels/printers/pp-options-dialog.h b/panels/printers/pp-options-dialog.h
new file mode 100644
index 0000000..9c881eb
--- /dev/null
+++ b/panels/printers/pp-options-dialog.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_OPTIONS_DIALOG (pp_options_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (PpOptionsDialog, pp_options_dialog, PP, OPTIONS_DIALOG, GtkDialog)
+
+PpOptionsDialog *pp_options_dialog_new (gchar *printer_name,
+ gboolean sensitive);
+
+G_END_DECLS
diff --git a/panels/printers/pp-options-dialog.ui b/panels/printers/pp-options-dialog.ui
new file mode 100644
index 0000000..79eaf88
--- /dev/null
+++ b/panels/printers/pp-options-dialog.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="PpOptionsDialog" parent="GtkDialog">
+ <property name="width_request">500</property>
+ <property name="height_request">400</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="resizable">False</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes" comments="Translators: This button triggers the printing of a test page.">Test Page</property>
+ <signal name="clicked" handler="test_page_cb" object="PpOptionsDialog" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="pack_type">start</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkBox" id="main_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="width_request">120</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="categories_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="categories_selection">
+ <signal name="changed" handler="category_selection_changed_cb" object="PpOptionsDialog" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tab_pos">left</property>
+ <property name="show_tabs">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="visible">True</property>
+ <property name="width_request">24</property>
+ <property name="height_request">24</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/printers/pp-ppd-option-widget.c b/panels/printers/pp-ppd-option-widget.c
new file mode 100644
index 0000000..56862ad
--- /dev/null
+++ b/panels/printers/pp-ppd-option-widget.c
@@ -0,0 +1,591 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "pp-ppd-option-widget.h"
+#include "pp-utils.h"
+
+static void pp_ppd_option_widget_finalize (GObject *object);
+
+static gboolean construct_widget (PpPPDOptionWidget *self);
+static void update_widget (PpPPDOptionWidget *self);
+static void update_widget_real (PpPPDOptionWidget *self);
+
+struct _PpPPDOptionWidget
+{
+ GtkBox parent_instance;
+
+ GtkWidget *switch_button;
+ GtkWidget *combo;
+ GtkWidget *image;
+ GtkWidget *box;
+
+ ppd_option_t *option;
+
+ gchar *printer_name;
+ gchar *option_name;
+
+ cups_dest_t *destination;
+ gboolean destination_set;
+
+ gchar *ppd_filename;
+ gboolean ppd_filename_set;
+
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (PpPPDOptionWidget, pp_ppd_option_widget, GTK_TYPE_BOX)
+
+/* This list comes from Gtk+ */
+static const struct {
+ const char *keyword;
+ const char *choice;
+ const char *translation;
+} ppd_choice_translations[] = {
+ { "Duplex", "None", N_("One Sided") },
+ /* Translators: this is an option of "Two Sided" */
+ { "Duplex", "DuplexNoTumble", N_("Long Edge (Standard)") },
+ /* Translators: this is an option of "Two Sided" */
+ { "Duplex", "DuplexTumble", N_("Short Edge (Flip)") },
+ /* Translators: this is an option of "Paper Source" */
+ { "InputSlot", "Auto", N_("Auto Select") },
+ /* Translators: this is an option of "Paper Source" */
+ { "InputSlot", "AutoSelect", N_("Auto Select") },
+ /* Translators: this is an option of "Paper Source" */
+ { "InputSlot", "Default", N_("Printer Default") },
+ /* Translators: this is an option of "Paper Source" */
+ { "InputSlot", "None", N_("Printer Default") },
+ /* Translators: this is an option of "Paper Source" */
+ { "InputSlot", "PrinterDefault", N_("Printer Default") },
+ /* Translators: this is an option of "Paper Source" */
+ { "InputSlot", "Unspecified", N_("Auto Select") },
+ /* Translators: this is an option of "Resolution" */
+ { "Resolution", "default", N_("Printer Default") },
+ /* Translators: this is an option of "GhostScript" */
+ { "PreFilter", "EmbedFonts", N_("Embed GhostScript fonts only") },
+ /* Translators: this is an option of "GhostScript" */
+ { "PreFilter", "Level1", N_("Convert to PS level 1") },
+ /* Translators: this is an option of "GhostScript" */
+ { "PreFilter", "Level2", N_("Convert to PS level 2") },
+ /* Translators: this is an option of "GhostScript" */
+ { "PreFilter", "No", N_("No pre-filtering") },
+};
+
+static ppd_option_t *
+cups_option_copy (ppd_option_t *option)
+{
+ ppd_option_t *result;
+ gint i;
+
+ result = g_new0 (ppd_option_t, 1);
+
+ *result = *option;
+
+ result->choices = g_new (ppd_choice_t, result->num_choices);
+ for (i = 0; i < result->num_choices; i++)
+ {
+ result->choices[i] = option->choices[i];
+ result->choices[i].code = g_strdup (option->choices[i].code);
+ result->choices[i].option = result;
+ }
+
+ return result;
+}
+
+static void
+cups_option_free (ppd_option_t *option)
+{
+ gint i;
+
+ if (option)
+ {
+ for (i = 0; i < option->num_choices; i++)
+ g_free (option->choices[i].code);
+
+ g_free (option->choices);
+ g_free (option);
+ }
+}
+
+static void
+pp_ppd_option_widget_class_init (PpPPDOptionWidgetClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pp_ppd_option_widget_finalize;
+}
+
+static void
+pp_ppd_option_widget_init (PpPPDOptionWidget *self)
+{
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_HORIZONTAL);
+}
+
+static void
+pp_ppd_option_widget_finalize (GObject *object)
+{
+ PpPPDOptionWidget *self = PP_PPD_OPTION_WIDGET (object);
+
+ g_cancellable_cancel (self->cancellable);
+ if (self->ppd_filename)
+ g_unlink (self->ppd_filename);
+
+ g_clear_pointer (&self->option, cups_option_free);
+ g_clear_pointer (&self->printer_name, g_free);
+ g_clear_pointer (&self->option_name, g_free);
+ if (self->destination)
+ {
+ cupsFreeDests (1, self->destination);
+ self->destination = NULL;
+ }
+ g_clear_pointer (&self->ppd_filename, g_free);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (pp_ppd_option_widget_parent_class)->finalize (object);
+}
+
+static const gchar *
+ppd_choice_translate (ppd_choice_t *choice)
+{
+ const gchar *keyword = choice->option->keyword;
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ppd_choice_translations); i++)
+ {
+ if (g_strcmp0 (ppd_choice_translations[i].keyword, keyword) == 0 &&
+ g_strcmp0 (ppd_choice_translations[i].choice, choice->choice) == 0)
+ return _(ppd_choice_translations[i].translation);
+ }
+
+ return choice->text;
+}
+
+GtkWidget *
+pp_ppd_option_widget_new (ppd_option_t *option,
+ const gchar *printer_name)
+{
+ PpPPDOptionWidget *self = NULL;
+
+ if (option && printer_name)
+ {
+ self = g_object_new (PP_TYPE_PPD_OPTION_WIDGET, NULL);
+
+ self->printer_name = g_strdup (printer_name);
+ self->option = cups_option_copy (option);
+ self->option_name = g_strdup (option->keyword);
+
+ if (construct_widget (self))
+ {
+ update_widget_real (self);
+ }
+ else
+ {
+ g_object_ref_sink (self);
+ g_object_unref (self);
+ self = NULL;
+ }
+ }
+
+ return (GtkWidget *) self;
+}
+
+enum {
+ NAME_COLUMN,
+ VALUE_COLUMN,
+ N_COLUMNS
+};
+
+static GtkWidget *
+combo_box_new (void)
+{
+ GtkCellRenderer *cell;
+ GtkListStore *store;
+ GtkWidget *combo_box;
+
+ combo_box = gtk_combo_box_new ();
+
+ store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
+ "text", NAME_COLUMN,
+ NULL);
+
+ return combo_box;
+}
+
+static void
+combo_box_append (GtkWidget *combo,
+ const gchar *display_text,
+ const gchar *value)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+ store = GTK_LIST_STORE (model);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, display_text,
+ VALUE_COLUMN, value,
+ -1);
+}
+
+struct ComboSet {
+ GtkComboBox *combo;
+ const gchar *value;
+};
+
+static gboolean
+set_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ struct ComboSet *set_data = data;
+ g_autofree gchar *value = NULL;
+
+ gtk_tree_model_get (model, iter, VALUE_COLUMN, &value, -1);
+
+ if (strcmp (value, set_data->value) == 0)
+ {
+ gtk_combo_box_set_active_iter (set_data->combo, iter);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+combo_box_set (GtkWidget *combo,
+ const gchar *value)
+{
+ struct ComboSet set_data;
+ GtkTreeModel *model;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ set_data.combo = GTK_COMBO_BOX (combo);
+ set_data.value = value;
+ gtk_tree_model_foreach (model, set_cb, &set_data);
+}
+
+static char *
+combo_box_get (GtkWidget *combo)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *value = NULL;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+ gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
+
+ return value;
+}
+
+static void
+printer_add_option_async_cb (gboolean success,
+ gpointer user_data)
+{
+ PpPPDOptionWidget *self = user_data;
+
+ update_widget (user_data);
+ g_clear_object (&self->cancellable);
+}
+
+static void
+switch_changed_cb (PpPPDOptionWidget *self)
+{
+ gchar **values;
+
+ values = g_new0 (gchar *, 2);
+
+ if (gtk_switch_get_active (GTK_SWITCH (self->switch_button)))
+ values[0] = g_strdup ("True");
+ else
+ values[0] = g_strdup ("False");
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_object_unref (self->cancellable);
+ }
+
+ self->cancellable = g_cancellable_new ();
+ printer_add_option_async (self->printer_name,
+ self->option_name,
+ values,
+ FALSE,
+ self->cancellable,
+ printer_add_option_async_cb,
+ self);
+
+ g_strfreev (values);
+}
+
+static void
+combo_changed_cb (PpPPDOptionWidget *self)
+{
+ gchar **values;
+
+ values = g_new0 (gchar *, 2);
+ values[0] = combo_box_get (self->combo);
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_object_unref (self->cancellable);
+ }
+
+ self->cancellable = g_cancellable_new ();
+ printer_add_option_async (self->printer_name,
+ self->option_name,
+ values,
+ FALSE,
+ self->cancellable,
+ printer_add_option_async_cb,
+ self);
+
+ g_strfreev (values);
+}
+
+static gboolean
+construct_widget (PpPPDOptionWidget *self)
+{
+ gint i;
+
+ /* Don't show options which has only one choice */
+ if (self->option && self->option->num_choices > 1)
+ {
+ switch (self->option->ui)
+ {
+ case PPD_UI_BOOLEAN:
+ self->switch_button = gtk_switch_new ();
+ g_signal_connect_object (self->switch_button, "notify::active", G_CALLBACK (switch_changed_cb), self, G_CONNECT_SWAPPED);
+ gtk_box_pack_start (GTK_BOX (self), self->switch_button, FALSE, FALSE, 0);
+ break;
+
+ case PPD_UI_PICKONE:
+ self->combo = combo_box_new ();
+
+ for (i = 0; i < self->option->num_choices; i++)
+ {
+ combo_box_append (self->combo,
+ ppd_choice_translate (&self->option->choices[i]),
+ self->option->choices[i].choice);
+ }
+
+ gtk_box_pack_start (GTK_BOX (self), self->combo, FALSE, FALSE, 0);
+ g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
+ break;
+
+ case PPD_UI_PICKMANY:
+ self->combo = combo_box_new ();
+
+ for (i = 0; i < self->option->num_choices; i++)
+ {
+ combo_box_append (self->combo,
+ ppd_choice_translate (&self->option->choices[i]),
+ self->option->choices[i].choice);
+ }
+
+ gtk_box_pack_start (GTK_BOX (self), self->combo, TRUE, TRUE, 0);
+ g_signal_connect_object (self->combo, "changed", G_CALLBACK (combo_changed_cb), self, G_CONNECT_SWAPPED);
+ break;
+
+ default:
+ break;
+ }
+
+ self->image = gtk_image_new_from_icon_name ("dialog-warning-symbolic", GTK_ICON_SIZE_MENU);
+ if (!self->image)
+ self->image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (self), self->image, FALSE, FALSE, 0);
+ gtk_widget_set_no_show_all (GTK_WIDGET (self->image), TRUE);
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+update_widget_real (PpPPDOptionWidget *self)
+{
+ ppd_option_t *option = NULL, *iter;
+ ppd_file_t *ppd_file;
+ gint i;
+
+ if (self->option)
+ {
+ option = cups_option_copy (self->option);
+ cups_option_free (self->option);
+ self->option = NULL;
+ }
+ else if (self->ppd_filename)
+ {
+ ppd_file = ppdOpenFile (self->ppd_filename);
+ ppdLocalize (ppd_file);
+
+ if (ppd_file)
+ {
+ ppdMarkDefaults (ppd_file);
+
+ for (iter = ppdFirstOption(ppd_file); iter; iter = ppdNextOption(ppd_file))
+ {
+ if (g_str_equal (iter->keyword, self->option_name))
+ {
+ option = cups_option_copy (iter);
+ break;
+ }
+ }
+
+ ppdClose (ppd_file);
+ }
+
+ g_unlink (self->ppd_filename);
+ g_free (self->ppd_filename);
+ self->ppd_filename = NULL;
+ }
+
+ if (option)
+ {
+ g_autofree gchar *value = NULL;
+
+ for (i = 0; i < option->num_choices; i++)
+ if (option->choices[i].marked)
+ value = g_strdup (option->choices[i].choice);
+
+ if (value == NULL)
+ value = g_strdup (option->defchoice);
+
+ if (value)
+ {
+ switch (option->ui)
+ {
+ case PPD_UI_BOOLEAN:
+ g_signal_handlers_block_by_func (self->switch_button, switch_changed_cb, self);
+ if (g_ascii_strcasecmp (value, "True") == 0)
+ gtk_switch_set_active (GTK_SWITCH (self->switch_button), TRUE);
+ else
+ gtk_switch_set_active (GTK_SWITCH (self->switch_button), FALSE);
+ g_signal_handlers_unblock_by_func (self->switch_button, switch_changed_cb, self);
+ break;
+
+ case PPD_UI_PICKONE:
+ g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
+ combo_box_set (self->combo, value);
+ g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
+ break;
+
+ case PPD_UI_PICKMANY:
+ g_signal_handlers_block_by_func (self->combo, combo_changed_cb, self);
+ combo_box_set (self->combo, value);
+ g_signal_handlers_unblock_by_func (self->combo, combo_changed_cb, self);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (option->conflicted)
+ gtk_widget_show (self->image);
+ else
+ gtk_widget_hide (self->image);
+ }
+
+ cups_option_free (option);
+}
+
+static void
+get_named_dest_cb (cups_dest_t *dest,
+ gpointer user_data)
+{
+ PpPPDOptionWidget *self = user_data;
+
+ if (self->destination)
+ cupsFreeDests (1, self->destination);
+
+ self->destination = dest;
+ self->destination_set = TRUE;
+
+ if (self->ppd_filename_set)
+ {
+ update_widget_real (self);
+ }
+}
+
+static void
+printer_get_ppd_cb (const gchar *ppd_filename,
+ gpointer user_data)
+{
+ PpPPDOptionWidget *self = user_data;
+
+ if (self->ppd_filename)
+ {
+ g_unlink (self->ppd_filename);
+ g_free (self->ppd_filename);
+ }
+
+ self->ppd_filename = g_strdup (ppd_filename);
+ self->ppd_filename_set = TRUE;
+
+ if (self->destination_set)
+ {
+ update_widget_real (self);
+ }
+}
+
+static void
+update_widget (PpPPDOptionWidget *self)
+{
+ self->ppd_filename_set = FALSE;
+ self->destination_set = FALSE;
+
+ get_named_dest_async (self->printer_name,
+ get_named_dest_cb,
+ self);
+
+ printer_get_ppd_async (self->printer_name,
+ NULL,
+ 0,
+ printer_get_ppd_cb,
+ self);
+}
diff --git a/panels/printers/pp-ppd-option-widget.h b/panels/printers/pp-ppd-option-widget.h
new file mode 100644
index 0000000..b558e51
--- /dev/null
+++ b/panels/printers/pp-ppd-option-widget.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_PPD_OPTION_WIDGET (pp_ppd_option_widget_get_type ())
+G_DECLARE_FINAL_TYPE (PpPPDOptionWidget, pp_ppd_option_widget, PP, PPD_OPTION_WIDGET, GtkBox)
+
+GtkWidget *pp_ppd_option_widget_new (ppd_option_t *source,
+ const gchar *printer_name);
+
+G_END_DECLS
diff --git a/panels/printers/pp-ppd-selection-dialog.c b/panels/printers/pp-ppd-selection-dialog.c
new file mode 100644
index 0000000..4c6a092
--- /dev/null
+++ b/panels/printers/pp-ppd-selection-dialog.c
@@ -0,0 +1,416 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-ppd-selection-dialog.h"
+
+static void pp_ppd_selection_dialog_hide (PpPPDSelectionDialog *dialog);
+
+enum
+{
+ PPD_NAMES_COLUMN = 0,
+ PPD_DISPLAY_NAMES_COLUMN
+};
+
+enum
+{
+ PPD_MANUFACTURERS_NAMES_COLUMN = 0,
+ PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN
+};
+
+
+struct _PpPPDSelectionDialog {
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+
+ UserResponseCallback user_callback;
+ gpointer user_data;
+
+ gchar *ppd_name;
+ gchar *ppd_display_name;
+ gchar *manufacturer;
+
+ PPDList *list;
+};
+
+static void
+manufacturer_selection_changed_cb (GtkTreeSelection *selection,
+ PpPPDSelectionDialog *self)
+{
+ GtkTreeView *treeview;
+ GtkListStore *store;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreeView *models_treeview;
+ gchar *manufacturer_name = NULL;
+ gint i, index;
+
+ treeview = GTK_TREE_VIEW (gtk_builder_get_object (self->builder, "ppd-selection-manufacturers-treeview"));
+ if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview), &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ PPD_MANUFACTURERS_NAMES_COLUMN, &manufacturer_name,
+ -1);
+ }
+
+ if (manufacturer_name)
+ {
+ index = -1;
+ for (i = 0; i < self->list->num_of_manufacturers; i++)
+ {
+ if (g_strcmp0 (manufacturer_name,
+ self->list->manufacturers[i]->manufacturer_name) == 0)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index >= 0)
+ {
+ models_treeview = (GtkTreeView*)
+ gtk_builder_get_object (self->builder, "ppd-selection-models-treeview");
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+ for (i = 0; i < self->list->manufacturers[index]->num_of_ppds; i++)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ PPD_NAMES_COLUMN, self->list->manufacturers[index]->ppds[i]->ppd_name,
+ PPD_DISPLAY_NAMES_COLUMN, self->list->manufacturers[index]->ppds[i]->ppd_display_name,
+ -1);
+ }
+
+ gtk_tree_view_set_model (models_treeview, GTK_TREE_MODEL (store));
+ g_object_unref (store);
+ gtk_tree_view_columns_autosize (models_treeview);
+ }
+
+ g_free (manufacturer_name);
+ }
+}
+
+static void
+model_selection_changed_cb (GtkTreeSelection *selection,
+ PpPPDSelectionDialog *self)
+{
+ GtkTreeView *treeview;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkWidget *widget;
+ gchar *model_name = NULL;
+
+ treeview = GTK_TREE_VIEW (gtk_builder_get_object (self->builder, "ppd-selection-models-treeview"));
+ if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview), &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ PPD_NAMES_COLUMN, &model_name,
+ -1);
+ }
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "ppd-selection-select-button");
+
+ if (model_name)
+ {
+ gtk_widget_set_sensitive (widget, TRUE);
+ g_free (model_name);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (widget, FALSE);
+ }
+}
+
+static void
+fill_ppds_list (PpPPDSelectionDialog *self)
+{
+ GtkTreeSelection *selection;
+ GtkListStore *store;
+ GtkTreePath *path;
+ GtkTreeView *treeview;
+ GtkTreeIter iter;
+ GtkTreeIter *preselect_iter = NULL;
+ GtkWidget *widget;
+ gint i;
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "ppd-spinner");
+ gtk_widget_hide (widget);
+ gtk_spinner_stop (GTK_SPINNER (widget));
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "progress-label");
+ gtk_widget_hide (widget);
+
+ treeview = (GtkTreeView*)
+ gtk_builder_get_object (self->builder, "ppd-selection-manufacturers-treeview");
+
+ if (self->list)
+ {
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+ for (i = 0; i < self->list->num_of_manufacturers; i++)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ PPD_MANUFACTURERS_NAMES_COLUMN, self->list->manufacturers[i]->manufacturer_name,
+ PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN, self->list->manufacturers[i]->manufacturer_display_name,
+ -1);
+
+ if (g_strcmp0 (self->manufacturer,
+ self->list->manufacturers[i]->manufacturer_display_name) == 0)
+ {
+ preselect_iter = gtk_tree_iter_copy (&iter);
+ }
+ }
+
+ gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (store));
+
+ if (preselect_iter &&
+ (selection = gtk_tree_view_get_selection (treeview)) != NULL)
+ {
+ gtk_tree_selection_select_iter (selection, preselect_iter);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), preselect_iter);
+ gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.0);
+ gtk_tree_path_free (path);
+ gtk_tree_iter_free (preselect_iter);
+ }
+
+ g_object_unref (store);
+ }
+}
+
+static void
+populate_dialog (PpPPDSelectionDialog *self)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkTreeView *manufacturers_treeview;
+ GtkTreeView *models_treeview;
+ GtkWidget *widget;
+ GtkWidget *header;
+
+ manufacturers_treeview = (GtkTreeView*)
+ gtk_builder_get_object (self->builder, "ppd-selection-manufacturers-treeview");
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_renderer_set_padding (renderer, 10, 0);
+
+ /* Translators: Name of column showing printer manufacturers */
+ column = gtk_tree_view_column_new_with_attributes (_("Manufacturer"), renderer,
+ "text", PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN, NULL);
+ gtk_tree_view_column_set_expand (column, TRUE);
+ header = gtk_label_new (gtk_tree_view_column_get_title (column));
+ gtk_widget_set_margin_start (header, 10);
+ gtk_tree_view_column_set_widget (column, header);
+ gtk_widget_show (header);
+ gtk_tree_view_append_column (manufacturers_treeview, column);
+
+
+ models_treeview = (GtkTreeView*)
+ gtk_builder_get_object (self->builder, "ppd-selection-models-treeview");
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_renderer_set_padding (renderer, 10, 0);
+
+ /* Translators: Name of column showing printer drivers */
+ column = gtk_tree_view_column_new_with_attributes (_("Driver"), renderer,
+ "text", PPD_DISPLAY_NAMES_COLUMN,
+ NULL);
+ gtk_tree_view_column_set_expand (column, TRUE);
+ header = gtk_label_new (gtk_tree_view_column_get_title (column));
+ gtk_widget_set_margin_start (header, 10);
+ gtk_tree_view_column_set_widget (column, header);
+ gtk_widget_show (header);
+ gtk_tree_view_append_column (models_treeview, column);
+
+
+ g_signal_connect (gtk_tree_view_get_selection (models_treeview),
+ "changed", G_CALLBACK (model_selection_changed_cb), self);
+
+ g_signal_connect (gtk_tree_view_get_selection (manufacturers_treeview),
+ "changed", G_CALLBACK (manufacturer_selection_changed_cb), self);
+
+ gtk_widget_show_all (self->dialog);
+
+ if (!self->list)
+ {
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "ppd-spinner");
+ gtk_widget_show (widget);
+ gtk_spinner_start (GTK_SPINNER (widget));
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "progress-label");
+ gtk_widget_show (widget);
+ }
+ else
+ {
+ fill_ppds_list (self);
+ }
+}
+
+static void
+ppd_selection_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ PpPPDSelectionDialog *self)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeView *models_treeview;
+ GtkTreeIter iter;
+
+ pp_ppd_selection_dialog_hide (self);
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ models_treeview = (GtkTreeView*)
+ gtk_builder_get_object (self->builder, "ppd-selection-models-treeview");
+
+ if (models_treeview)
+ {
+ selection = gtk_tree_view_get_selection (models_treeview);
+
+ if (selection)
+ {
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ PPD_NAMES_COLUMN, &self->ppd_name,
+ PPD_DISPLAY_NAMES_COLUMN, &self->ppd_display_name,
+ -1);
+ }
+ }
+ }
+ }
+
+ self->user_callback (GTK_DIALOG (self->dialog), response_id, self->user_data);
+}
+
+PpPPDSelectionDialog *
+pp_ppd_selection_dialog_new (GtkWindow *parent,
+ PPDList *ppd_list,
+ const gchar *manufacturer,
+ UserResponseCallback user_callback,
+ gpointer user_data)
+{
+ PpPPDSelectionDialog *self;
+ GtkWidget *widget;
+ g_autoptr(GError) error = NULL;
+ gchar *objects[] = { "ppd-selection-dialog", NULL };
+ guint builder_result;
+
+ self = g_new0 (PpPPDSelectionDialog, 1);
+
+ self->builder = gtk_builder_new ();
+
+ builder_result = gtk_builder_add_objects_from_resource (self->builder,
+ "/org/gnome/control-center/printers/ppd-selection-dialog.ui",
+ objects, &error);
+
+ if (builder_result == 0)
+ {
+ g_warning ("Could not load ui: %s", error->message);
+ return NULL;
+ }
+
+ self->dialog = (GtkWidget *) gtk_builder_get_object (self->builder, "ppd-selection-dialog");
+ self->user_callback = user_callback;
+ self->user_data = user_data;
+
+ self->list = ppd_list_copy (ppd_list);
+
+ self->manufacturer = get_standard_manufacturers_name (manufacturer);
+
+ /* connect signals */
+ g_signal_connect (self->dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ g_signal_connect (self->dialog, "response", G_CALLBACK (ppd_selection_dialog_response_cb), self);
+
+ gtk_window_set_transient_for (GTK_WINDOW (self->dialog), GTK_WINDOW (parent));
+
+ widget = (GtkWidget*)
+ gtk_builder_get_object (self->builder, "ppd-spinner");
+ gtk_spinner_start (GTK_SPINNER (widget));
+
+ populate_dialog (self);
+
+ gtk_window_present (GTK_WINDOW (self->dialog));
+ gtk_widget_show_all (GTK_WIDGET (self->dialog));
+
+ return self;
+}
+
+void
+pp_ppd_selection_dialog_free (PpPPDSelectionDialog *self)
+{
+ gtk_widget_destroy (GTK_WIDGET (self->dialog));
+
+ g_object_unref (self->builder);
+
+ g_free (self->ppd_name);
+
+ g_free (self->ppd_display_name);
+
+ g_free (self->manufacturer);
+
+ g_free (self);
+}
+
+gchar *
+pp_ppd_selection_dialog_get_ppd_name (PpPPDSelectionDialog *self)
+{
+ return g_strdup (self->ppd_name);
+}
+
+gchar *
+pp_ppd_selection_dialog_get_ppd_display_name (PpPPDSelectionDialog *self)
+{
+ return g_strdup (self->ppd_display_name);
+}
+
+void
+pp_ppd_selection_dialog_set_ppd_list (PpPPDSelectionDialog *self,
+ PPDList *list)
+{
+ self->list = list;
+ fill_ppds_list (self);
+}
+
+static void
+pp_ppd_selection_dialog_hide (PpPPDSelectionDialog *self)
+{
+ gtk_widget_hide (GTK_WIDGET (self->dialog));
+}
diff --git a/panels/printers/pp-ppd-selection-dialog.h b/panels/printers/pp-ppd-selection-dialog.h
new file mode 100644
index 0000000..3cca002
--- /dev/null
+++ b/panels/printers/pp-ppd-selection-dialog.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+typedef struct _PpPPDSelectionDialog PpPPDSelectionDialog;
+
+PpPPDSelectionDialog *pp_ppd_selection_dialog_new (GtkWindow *parent,
+ PPDList *ppd_list,
+ const gchar *manufacturer,
+ UserResponseCallback user_callback,
+ gpointer user_data);
+gchar *pp_ppd_selection_dialog_get_ppd_name (PpPPDSelectionDialog *dialog);
+gchar *pp_ppd_selection_dialog_get_ppd_display_name (PpPPDSelectionDialog *dialog);
+void pp_ppd_selection_dialog_set_ppd_list (PpPPDSelectionDialog *dialog,
+ PPDList *list);
+void pp_ppd_selection_dialog_free (PpPPDSelectionDialog *dialog);
+
+G_END_DECLS
diff --git a/panels/printers/pp-print-device.c b/panels/printers/pp-print-device.c
new file mode 100644
index 0000000..3ab0b07
--- /dev/null
+++ b/panels/printers/pp-print-device.c
@@ -0,0 +1,450 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2015 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "pp-print-device.h"
+
+struct _PpPrintDevice
+{
+ GObject parent_instance;
+
+ gchar *device_name;
+ gchar *display_name;
+ gchar *device_original_name;
+ gchar *device_make_and_model;
+ gchar *device_location;
+ gchar *device_info;
+ gchar *device_uri;
+ gchar *device_id;
+ gchar *device_ppd;
+ gchar *host_name;
+ gint host_port;
+ gboolean is_authenticated_server;
+ gint acquisition_method;
+ gboolean is_network_device;
+};
+
+G_DEFINE_TYPE (PpPrintDevice, pp_print_device, G_TYPE_OBJECT);
+
+enum
+{
+ PROP_0 = 0,
+ PROP_DEVICE_NAME,
+ PROP_DISPLAY_NAME,
+ PROP_DEVICE_ORIGINAL_NAME,
+ PROP_DEVICE_MAKE_AND_MODEL,
+ PROP_DEVICE_LOCATION,
+ PROP_DEVICE_INFO,
+ PROP_DEVICE_URI,
+ PROP_DEVICE_ID,
+ PROP_DEVICE_PPD,
+ PROP_HOST_NAME,
+ PROP_HOST_PORT,
+ PROP_IS_AUTHENTICATED_SERVER,
+ PROP_ACQUISITION_METHOD,
+ PROP_IS_NETWORK_DEVICE
+};
+
+static void
+pp_print_device_finalize (GObject *object)
+{
+ PpPrintDevice *self = PP_PRINT_DEVICE (object);
+
+ g_clear_pointer (&self->device_name, g_free);
+ g_clear_pointer (&self->display_name, g_free);
+ g_clear_pointer (&self->device_original_name, g_free);
+ g_clear_pointer (&self->device_make_and_model, g_free);
+ g_clear_pointer (&self->device_location, g_free);
+ g_clear_pointer (&self->device_info, g_free);
+ g_clear_pointer (&self->device_uri, g_free);
+ g_clear_pointer (&self->device_id, g_free);
+ g_clear_pointer (&self->device_ppd, g_free);
+ g_clear_pointer (&self->host_name, g_free);
+
+ G_OBJECT_CLASS (pp_print_device_parent_class)->finalize (object);
+}
+
+static void
+pp_print_device_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *param_spec)
+{
+ PpPrintDevice *self = PP_PRINT_DEVICE (object);
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE_NAME:
+ g_value_set_string (value, self->device_name);
+ break;
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, self->display_name);
+ break;
+ case PROP_DEVICE_ORIGINAL_NAME:
+ g_value_set_string (value, self->device_original_name);
+ break;
+ case PROP_DEVICE_MAKE_AND_MODEL:
+ g_value_set_string (value, self->device_make_and_model);
+ break;
+ case PROP_DEVICE_LOCATION:
+ g_value_set_string (value, self->device_location);
+ break;
+ case PROP_DEVICE_INFO:
+ g_value_set_string (value, self->device_info);
+ break;
+ case PROP_DEVICE_URI:
+ g_value_set_string (value, self->device_uri);
+ break;
+ case PROP_DEVICE_ID:
+ g_value_set_string (value, self->device_id);
+ break;
+ case PROP_DEVICE_PPD:
+ g_value_set_string (value, self->device_ppd);
+ break;
+ case PROP_HOST_NAME:
+ g_value_set_string (value, self->host_name);
+ break;
+ case PROP_HOST_PORT:
+ g_value_set_int (value, self->host_port);
+ break;
+ case PROP_IS_AUTHENTICATED_SERVER:
+ g_value_set_boolean (value, self->is_authenticated_server);
+ break;
+ case PROP_ACQUISITION_METHOD:
+ g_value_set_int (value, self->acquisition_method);
+ break;
+ case PROP_IS_NETWORK_DEVICE:
+ g_value_set_boolean (value, self->is_network_device);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_print_device_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *param_spec)
+{
+ PpPrintDevice *self = PP_PRINT_DEVICE (object);
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE_NAME:
+ g_free (self->device_name);
+ self->device_name = g_value_dup_string (value);
+ break;
+ case PROP_DISPLAY_NAME:
+ g_free (self->display_name);
+ self->display_name = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_ORIGINAL_NAME:
+ g_free (self->device_original_name);
+ self->device_original_name = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_MAKE_AND_MODEL:
+ g_free (self->device_make_and_model);
+ self->device_make_and_model = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_LOCATION:
+ g_free (self->device_location);
+ self->device_location = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_INFO:
+ g_free (self->device_info);
+ self->device_info = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_URI:
+ g_free (self->device_uri);
+ self->device_uri = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_ID:
+ g_free (self->device_id);
+ self->device_id = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_PPD:
+ g_free (self->device_ppd);
+ self->device_ppd = g_value_dup_string (value);
+ break;
+ case PROP_HOST_NAME:
+ g_free (self->host_name);
+ self->host_name = g_value_dup_string (value);
+ break;
+ case PROP_HOST_PORT:
+ self->host_port = g_value_get_int (value);
+ break;
+ case PROP_IS_AUTHENTICATED_SERVER:
+ self->is_authenticated_server = g_value_get_boolean (value);
+ break;
+ case PROP_ACQUISITION_METHOD:
+ self->acquisition_method = g_value_get_int (value);
+ break;
+ case PROP_IS_NETWORK_DEVICE:
+ self->is_network_device = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+pp_print_device_class_init (PpPrintDeviceClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pp_print_device_set_property;
+ gobject_class->get_property = pp_print_device_get_property;
+
+ gobject_class->finalize = pp_print_device_finalize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_NAME,
+ g_param_spec_string ("device-name",
+ "Device name",
+ "Name of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DISPLAY_NAME,
+ g_param_spec_string ("display-name",
+ "Display name",
+ "Name of the device formatted for users",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_ORIGINAL_NAME,
+ g_param_spec_string ("device-original-name",
+ "Device original name",
+ "Original name of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_MAKE_AND_MODEL,
+ g_param_spec_string ("device-make-and-model",
+ "Device make and model",
+ "Make and model of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_LOCATION,
+ g_param_spec_string ("device-location",
+ "Device location",
+ "Locaton of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_INFO,
+ g_param_spec_string ("device-info",
+ "Device info",
+ "Information about the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_URI,
+ g_param_spec_string ("device-uri",
+ "Device URI",
+ "URI of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_ID,
+ g_param_spec_string ("device-id",
+ "DeviceID",
+ "DeviceID of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_PPD,
+ g_param_spec_string ("device-ppd",
+ "Device PPD",
+ "Name of the PPD of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_HOST_NAME,
+ g_param_spec_string ("host-name",
+ "Host name",
+ "Hostname of the device",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_HOST_PORT,
+ g_param_spec_int ("host-port",
+ "Host port",
+ "The port of the host",
+ 0, G_MAXINT32, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_IS_AUTHENTICATED_SERVER,
+ g_param_spec_boolean ("is-authenticated-server",
+ "Is authenticated server",
+ "Whether the device is a server which needs authentication",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_ACQUISITION_METHOD,
+ g_param_spec_int ("acquisition-method",
+ "Acquisition method",
+ "Acquisition method of the device",
+ 0, G_MAXINT32, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_IS_NETWORK_DEVICE,
+ g_param_spec_boolean ("is-network-device",
+ "Network device",
+ "Whether the device is a network device",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static void
+pp_print_device_init (PpPrintDevice *self)
+{
+}
+
+PpPrintDevice *
+pp_print_device_new ()
+{
+ return g_object_new (PP_TYPE_PRINT_DEVICE, NULL);
+}
+
+gchar *
+pp_print_device_get_device_name (PpPrintDevice *self)
+{
+ return self->device_name;
+}
+
+gchar *
+pp_print_device_get_display_name (PpPrintDevice *self)
+{
+ return self->display_name;
+}
+
+gchar *
+pp_print_device_get_device_original_name (PpPrintDevice *self)
+{
+ return self->device_original_name;
+}
+
+gchar *
+pp_print_device_get_device_make_and_model (PpPrintDevice *self)
+{
+ return self->device_make_and_model;
+}
+
+gchar *
+pp_print_device_get_device_location (PpPrintDevice *self)
+{
+ return self->device_location;
+}
+
+gchar *
+pp_print_device_get_device_info (PpPrintDevice *self)
+{
+ return self->device_info;
+}
+
+gchar *
+pp_print_device_get_device_uri (PpPrintDevice *self)
+{
+ return self->device_uri;
+}
+
+gchar *
+pp_print_device_get_device_id (PpPrintDevice *self)
+{
+ return self->device_id;
+}
+
+gchar *
+pp_print_device_get_device_ppd (PpPrintDevice *self)
+{
+ return self->device_ppd;
+}
+
+gchar *
+pp_print_device_get_host_name (PpPrintDevice *self)
+{
+ return self->host_name;
+}
+
+gint
+pp_print_device_get_host_port (PpPrintDevice *self)
+{
+ return self->host_port;
+}
+
+gboolean
+pp_print_device_is_authenticated_server (PpPrintDevice *self)
+{
+ return self->is_authenticated_server;
+}
+
+gint
+pp_print_device_get_acquisition_method (PpPrintDevice *self)
+{
+ return self->acquisition_method;
+}
+
+gboolean
+pp_print_device_is_network_device (PpPrintDevice *self)
+{
+ return self->is_network_device;
+}
+
+PpPrintDevice *
+pp_print_device_copy (PpPrintDevice *self)
+{
+ return g_object_new (PP_TYPE_PRINT_DEVICE,
+ "device-name", pp_print_device_get_device_name (self),
+ "display-name", pp_print_device_get_display_name (self),
+ "device-original-name", pp_print_device_get_device_original_name (self),
+ "device-make-and-model", pp_print_device_get_device_make_and_model (self),
+ "device-location", pp_print_device_get_device_location (self),
+ "device-info", pp_print_device_get_device_info (self),
+ "device-uri", pp_print_device_get_device_uri (self),
+ "device-id", pp_print_device_get_device_id (self),
+ "device-ppd", pp_print_device_get_device_ppd (self),
+ "host-name", pp_print_device_get_host_name (self),
+ "host-port", pp_print_device_get_host_port (self),
+ "is-authenticated-server", pp_print_device_is_authenticated_server (self),
+ "acquisition-method", pp_print_device_get_acquisition_method (self),
+ "is-network-device", pp_print_device_is_network_device (self),
+ NULL);
+}
diff --git a/panels/printers/pp-print-device.h b/panels/printers/pp-print-device.h
new file mode 100644
index 0000000..a271254
--- /dev/null
+++ b/panels/printers/pp-print-device.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2015 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_PRINT_DEVICE (pp_print_device_get_type ())
+G_DECLARE_FINAL_TYPE (PpPrintDevice, pp_print_device, PP, PRINT_DEVICE, GObject)
+
+PpPrintDevice *pp_print_device_new (void);
+PpPrintDevice *pp_print_device_copy (PpPrintDevice *device);
+gchar *pp_print_device_get_device_name (PpPrintDevice *device);
+gchar *pp_print_device_get_display_name (PpPrintDevice *device);
+gchar *pp_print_device_get_device_original_name (PpPrintDevice *device);
+gchar *pp_print_device_get_device_make_and_model (PpPrintDevice *device);
+gchar *pp_print_device_get_device_location (PpPrintDevice *device);
+gchar *pp_print_device_get_device_info (PpPrintDevice *device);
+gchar *pp_print_device_get_device_uri (PpPrintDevice *device);
+gchar *pp_print_device_get_device_id (PpPrintDevice *device);
+gchar *pp_print_device_get_device_ppd (PpPrintDevice *device);
+gchar *pp_print_device_get_host_name (PpPrintDevice *device);
+gint pp_print_device_get_host_port (PpPrintDevice *device);
+gboolean pp_print_device_is_authenticated_server (PpPrintDevice *device);
+gint pp_print_device_get_acquisition_method (PpPrintDevice *device);
+gboolean pp_print_device_is_network_device (PpPrintDevice *device);
+
+G_END_DECLS
diff --git a/panels/printers/pp-printer-entry.c b/panels/printers/pp-printer-entry.c
new file mode 100644
index 0000000..f74a440
--- /dev/null
+++ b/panels/printers/pp-printer-entry.c
@@ -0,0 +1,1082 @@
+/*
+ * Copyright 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Felipe Borges <felipeborges@gnome.org>
+ */
+
+#include <config.h>
+
+#include "pp-printer-entry.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "pp-details-dialog.h"
+#include "pp-maintenance-command.h"
+#include "pp-options-dialog.h"
+#include "pp-jobs-dialog.h"
+#include "pp-printer.h"
+#include "pp-utils.h"
+
+#define SUPPLY_BAR_HEIGHT 8
+
+typedef struct
+{
+ gchar *marker_names;
+ gchar *marker_levels;
+ gchar *marker_colors;
+ gchar *marker_types;
+} InkLevelData;
+
+struct _PpPrinterEntry
+{
+ GtkListBoxRow parent;
+
+ gchar *printer_name;
+ gboolean is_accepting_jobs;
+ gchar *printer_make_and_model;
+ gchar *printer_location;
+ gchar *printer_hostname;
+ gboolean is_authorized;
+ gint printer_state;
+ InkLevelData *inklevel;
+
+ /* Maintenance commands */
+ PpMaintenanceCommand *clean_command;
+ GCancellable *check_clean_heads_cancellable;
+
+ /* Widgets */
+ GtkImage *printer_icon;
+ GtkLabel *printer_status;
+ GtkLabel *printer_name_label;
+ GtkLabel *printer_model_label;
+ GtkLabel *printer_model;
+ GtkLabel *printer_location_label;
+ GtkLabel *printer_location_address_label;
+ GtkLabel *printer_inklevel_label;
+ GtkFrame *supply_frame;
+ GtkDrawingArea *supply_drawing_area;
+ GtkWidget *show_jobs_dialog_button;
+ GtkWidget *clean_heads_menuitem;
+ GtkCheckButton *printer_default_checkbutton;
+ GtkModelButton *remove_printer_menuitem;
+ GtkBox *printer_error;
+ GtkLabel *error_status;
+
+ /* Dialogs */
+ PpJobsDialog *pp_jobs_dialog;
+
+ GCancellable *get_jobs_cancellable;
+};
+
+struct _PpPrinterEntryClass
+{
+ GtkListBoxRowClass parent_class;
+
+ void (*printer_changed) (PpPrinterEntry *printer_entry);
+ void (*printer_delete) (PpPrinterEntry *printer_entry);
+ void (*printer_renamed) (PpPrinterEntry *printer_entry, const gchar *new_name);
+};
+
+G_DEFINE_TYPE (PpPrinterEntry, pp_printer_entry, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+ PROP_0,
+ PROP_PRINTER_NAME,
+ PROP_PRINTER_LOCATION,
+};
+
+enum {
+ IS_DEFAULT_PRINTER,
+ PRINTER_DELETE,
+ PRINTER_RENAMED,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static InkLevelData *
+ink_level_data_new (void)
+{
+ return g_slice_new0 (InkLevelData);
+}
+
+static void
+ink_level_data_free (InkLevelData *data)
+{
+ g_clear_pointer (&data->marker_names, g_free);
+ g_clear_pointer (&data->marker_levels, g_free);
+ g_clear_pointer (&data->marker_colors, g_free);
+ g_clear_pointer (&data->marker_types, g_free);
+ g_slice_free (InkLevelData, data);
+}
+
+static void
+pp_printer_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PpPrinterEntry *self = PP_PRINTER_ENTRY (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRINTER_NAME:
+ g_value_set_string (value, self->printer_name);
+ break;
+ case PROP_PRINTER_LOCATION:
+ g_value_set_string (value, self->printer_location);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pp_printer_entry_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PpPrinterEntry *self = PP_PRINTER_ENTRY (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRINTER_NAME:
+ self->printer_name = g_value_dup_string (value);
+ break;
+ case PROP_PRINTER_LOCATION:
+ self->printer_location = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pp_printer_entry_init (PpPrinterEntry *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+ self->inklevel = ink_level_data_new ();
+}
+
+typedef struct {
+ gchar *color;
+ gchar *type;
+ gchar *name;
+ gint level;
+} MarkerItem;
+
+static gint
+markers_cmp (gconstpointer a,
+ gconstpointer b)
+{
+ MarkerItem *x = (MarkerItem*) a;
+ MarkerItem *y = (MarkerItem*) b;
+
+ if (x->level < y->level)
+ return 1;
+ else if (x->level == y->level)
+ return 0;
+ else
+ return -1;
+}
+
+static gchar *
+sanitize_printer_model (const gchar *printer_make_and_model)
+{
+ gchar *breakpoint = NULL, *tmp2 = NULL;
+ g_autofree gchar *tmp = NULL;
+ gchar backup;
+ size_t length = 0;
+ gchar *forbidden[] = {
+ "foomatic",
+ ",",
+ "hpijs",
+ "hpcups",
+ "(recommended)",
+ "postscript (recommended)",
+ NULL };
+ int i;
+
+ tmp = g_ascii_strdown (printer_make_and_model, -1);
+
+ for (i = 0; i < g_strv_length (forbidden); i++)
+ {
+ tmp2 = g_strrstr (tmp, forbidden[i]);
+ if (breakpoint == NULL ||
+ (tmp2 != NULL && tmp2 < breakpoint))
+ breakpoint = tmp2;
+ }
+
+ if (breakpoint)
+ {
+ backup = *breakpoint;
+ *breakpoint = '\0';
+ length = strlen (tmp);
+ *breakpoint = backup;
+
+ if (length > 0)
+ return g_strndup (printer_make_and_model, length);
+ }
+ else
+ return g_strdup (printer_make_and_model);
+
+ return NULL;
+}
+
+static gboolean
+supply_level_is_empty (PpPrinterEntry *self)
+{
+ return !((self->inklevel->marker_levels != NULL) &&
+ (self->inklevel->marker_colors != NULL) &&
+ (self->inklevel->marker_names != NULL) &&
+ (self->inklevel->marker_types != NULL));
+}
+
+/* To tone down the colors in the supply level bar
+ * we shade them by darkening the hue.
+ *
+ * Obs.: we don't know whether the color is already
+ * shaded.
+ *
+ */
+static void
+tone_down_color (GdkRGBA *color,
+ gdouble hue_ratio,
+ gdouble saturation_ratio,
+ gdouble value_ratio)
+{
+ gdouble h, s, v;
+
+ gtk_rgb_to_hsv (color->red, color->green, color->blue,
+ &h, &s, &v);
+ gtk_hsv_to_rgb (h * hue_ratio, s * saturation_ratio, v * value_ratio,
+ &color->red, &color->green, &color->blue);
+}
+
+static gboolean
+supply_levels_draw_cb (PpPrinterEntry *self,
+ cairo_t *cr)
+{
+ GtkStyleContext *context;
+ gboolean is_empty = TRUE;
+ g_autofree gchar *tooltip_text = NULL;
+ gint width;
+ gint height;
+ int i;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self->supply_drawing_area));
+
+ width = gtk_widget_get_allocated_width (GTK_WIDGET (self->supply_drawing_area));
+ height = gtk_widget_get_allocated_height (GTK_WIDGET (self->supply_drawing_area));
+
+ gtk_render_background (context, cr, 0, 0, width, height);
+
+ if (!supply_level_is_empty (self))
+ {
+ GSList *markers = NULL;
+ GSList *tmp_list = NULL;
+ gchar **marker_levelsv = NULL;
+ gchar **marker_colorsv = NULL;
+ gchar **marker_namesv = NULL;
+ gchar **marker_typesv = NULL;
+
+ gtk_style_context_save (context);
+
+ marker_levelsv = g_strsplit (self->inklevel->marker_levels, ",", -1);
+ marker_colorsv = g_strsplit (self->inklevel->marker_colors, ",", -1);
+ marker_namesv = g_strsplit (self->inklevel->marker_names, ",", -1);
+ marker_typesv = g_strsplit (self->inklevel->marker_types, ",", -1);
+
+ if (g_strv_length (marker_levelsv) == g_strv_length (marker_colorsv) &&
+ g_strv_length (marker_colorsv) == g_strv_length (marker_namesv) &&
+ g_strv_length (marker_namesv) == g_strv_length (marker_typesv))
+ {
+ for (i = 0; i < g_strv_length (marker_levelsv); i++)
+ {
+ MarkerItem *marker;
+
+ if (g_strcmp0 (marker_typesv[i], "ink") == 0 ||
+ g_strcmp0 (marker_typesv[i], "toner") == 0 ||
+ g_strcmp0 (marker_typesv[i], "inkCartridge") == 0 ||
+ g_strcmp0 (marker_typesv[i], "tonerCartridge") == 0)
+ {
+ marker = g_new0 (MarkerItem, 1);
+ marker->type = g_strdup (marker_typesv[i]);
+ marker->name = g_strdup (marker_namesv[i]);
+ marker->color = g_strdup (marker_colorsv[i]);
+ marker->level = atoi (marker_levelsv[i]);
+
+ markers = g_slist_prepend (markers, marker);
+ }
+ }
+
+ markers = g_slist_sort (markers, markers_cmp);
+
+ for (tmp_list = markers; tmp_list; tmp_list = tmp_list->next)
+ {
+ GdkRGBA color = {0.0, 0.0, 0.0, 1.0};
+ double display_value;
+ int value;
+
+ value = ((MarkerItem*) tmp_list->data)->level;
+
+ gdk_rgba_parse (&color, ((MarkerItem*) tmp_list->data)->color);
+ tone_down_color (&color, 1.0, 0.6, 0.9);
+
+ if (value > 0)
+ {
+ display_value = value / 100.0 * (width - 3.0);
+ gdk_cairo_set_source_rgba (cr, &color);
+ cairo_rectangle (cr, 2.0, 2.0, display_value, SUPPLY_BAR_HEIGHT);
+ cairo_fill (cr);
+
+ tone_down_color (&color, 1.0, 1.0, 0.85);
+ gdk_cairo_set_source_rgba (cr, &color);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr, 1.5, 1.5, display_value, SUPPLY_BAR_HEIGHT + 1);
+ cairo_stroke (cr);
+
+ is_empty = FALSE;
+ }
+
+ if (tooltip_text)
+ {
+ g_autofree gchar *old_tooltip_text = g_steal_pointer (&tooltip_text);
+ tooltip_text = g_strdup_printf ("%s\n%s",
+ old_tooltip_text,
+ ((MarkerItem*) tmp_list->data)->name);
+ }
+ else
+ tooltip_text = g_strdup_printf ("%s",
+ ((MarkerItem*) tmp_list->data)->name);
+ }
+
+ gtk_render_frame (context, cr, 1, 1, width - 1, SUPPLY_BAR_HEIGHT);
+
+ for (tmp_list = markers; tmp_list; tmp_list = tmp_list->next)
+ {
+ g_free (((MarkerItem*) tmp_list->data)->name);
+ g_free (((MarkerItem*) tmp_list->data)->type);
+ g_free (((MarkerItem*) tmp_list->data)->color);
+ }
+ g_slist_free_full (markers, g_free);
+ }
+
+ gtk_style_context_restore (context);
+
+ if (tooltip_text)
+ {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->supply_drawing_area), tooltip_text);
+ }
+ else
+ {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->supply_drawing_area), NULL);
+ gtk_widget_set_has_tooltip (GTK_WIDGET (self->supply_drawing_area), FALSE);
+ }
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->printer_inklevel_label), !is_empty);
+ gtk_widget_set_visible (GTK_WIDGET (self->supply_frame), !is_empty);
+
+ return TRUE;
+}
+
+static void
+on_printer_rename_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PpPrinterEntry *self = user_data;
+ g_autofree gchar *printer_name = NULL;
+
+ if (!pp_printer_rename_finish (PP_PRINTER (source_object), result, NULL))
+ return;
+
+ g_object_get (PP_PRINTER (source_object),
+ "printer-name", &printer_name,
+ NULL);
+
+ g_signal_emit_by_name (self, "printer-renamed", printer_name);
+}
+
+static void
+on_show_printer_details_dialog (GtkButton *button,
+ PpPrinterEntry *self)
+{
+ const gchar *new_name;
+ const gchar *new_location;
+
+ PpDetailsDialog *dialog = pp_details_dialog_new (self->printer_name,
+ self->printer_location,
+ self->printer_hostname,
+ self->printer_make_and_model,
+ self->is_authorized);
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ new_location = pp_details_dialog_get_printer_location (dialog);
+ if (g_strcmp0 (self->printer_location, new_location) != 0)
+ printer_set_location (self->printer_name, new_location);
+
+ new_name = pp_details_dialog_get_printer_name (dialog);
+ if (g_strcmp0 (self->printer_name, new_name) != 0)
+ {
+ PpPrinter *printer = pp_printer_new (self->printer_name);
+
+ pp_printer_rename_async (printer,
+ new_name,
+ NULL,
+ on_printer_rename_cb,
+ self);
+ }
+
+ g_signal_emit_by_name (self, "printer-changed");
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+on_show_printer_options_dialog (GtkButton *button,
+ PpPrinterEntry *self)
+{
+ PpOptionsDialog *dialog;
+
+ dialog = pp_options_dialog_new (self->printer_name, self->is_authorized);
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+set_as_default_printer (GtkToggleButton *button,
+ PpPrinterEntry *self)
+{
+ printer_set_default (self->printer_name);
+
+ g_signal_emit_by_name (self, "printer-changed");
+}
+
+static void
+check_clean_heads_maintenance_command_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpPrinterEntry *self = user_data;
+ PpMaintenanceCommand *command = (PpMaintenanceCommand *) source_object;
+ gboolean is_supported = FALSE;
+ g_autoptr(GError) error = NULL;
+
+ is_supported = pp_maintenance_command_is_supported_finish (command, res, &error);
+ if (error != NULL)
+ {
+ g_debug ("Could not check 'Clean' maintenance command: %s", error->message);
+ goto out;
+ }
+
+ if (is_supported)
+ {
+ gtk_widget_show (GTK_WIDGET (self->clean_heads_menuitem));
+ }
+
+ out:
+ g_object_unref (source_object);
+}
+
+static void
+check_clean_heads_maintenance_command (PpPrinterEntry *self)
+{
+ if (self->clean_command == NULL)
+ return;
+
+ g_object_ref (self->clean_command);
+ self->check_clean_heads_cancellable = g_cancellable_new ();
+
+ pp_maintenance_command_is_supported_async (self->clean_command,
+ self->check_clean_heads_cancellable,
+ check_clean_heads_maintenance_command_cb,
+ self);
+}
+
+static void
+clean_heads_maintenance_command_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpPrinterEntry *self = user_data;
+ PpMaintenanceCommand *command = (PpMaintenanceCommand *) source_object;
+ g_autoptr(GError) error = NULL;
+
+ if (!pp_maintenance_command_execute_finish (command, res, &error))
+ {
+ g_warning ("Error cleaning print heads for %s: %s", self->printer_name, error->message);
+ }
+ g_object_unref (source_object);
+}
+
+static void
+clean_heads (GtkButton *button,
+ PpPrinterEntry *self)
+{
+ if (self->clean_command == NULL)
+ return;
+
+ g_object_ref (self->clean_command);
+ pp_maintenance_command_execute_async (self->clean_command,
+ NULL,
+ clean_heads_maintenance_command_cb,
+ self);
+}
+
+static void
+remove_printer (GtkButton *button,
+ PpPrinterEntry *self)
+{
+ g_signal_emit_by_name (self, "printer-delete");
+}
+
+static void
+get_jobs_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PpPrinterEntry *self = user_data;
+ PpPrinter *printer = PP_PRINTER (source_object);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) jobs = NULL;
+ g_autofree gchar *button_label = NULL;
+
+ jobs = pp_printer_get_jobs_finish (printer, result, &error);
+
+ g_object_unref (source_object);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Could not get jobs: %s", error->message);
+ }
+
+ return;
+ }
+
+ if (jobs->len == 0)
+ {
+ /* Translators: This is the label of the button that opens the Jobs Dialog. */
+ button_label = g_strdup (_("No Active Jobs"));
+ }
+ else
+ {
+ /* Translators: This is the label of the button that opens the Jobs Dialog. */
+ button_label = g_strdup_printf (ngettext ("%u Job", "%u Jobs", jobs->len), jobs->len);
+ }
+
+ gtk_button_set_label (GTK_BUTTON (self->show_jobs_dialog_button), button_label);
+ gtk_widget_set_sensitive (self->show_jobs_dialog_button, jobs->len > 0);
+
+ if (self->pp_jobs_dialog != NULL)
+ {
+ pp_jobs_dialog_update (self->pp_jobs_dialog);
+ }
+
+ g_clear_object (&self->get_jobs_cancellable);
+}
+
+void
+pp_printer_entry_update_jobs_count (PpPrinterEntry *self)
+{
+ PpPrinter *printer;
+
+ g_cancellable_cancel (self->get_jobs_cancellable);
+ g_clear_object (&self->get_jobs_cancellable);
+
+ self->get_jobs_cancellable = g_cancellable_new ();
+
+ printer = pp_printer_new (self->printer_name);
+ pp_printer_get_jobs_async (printer,
+ TRUE,
+ CUPS_WHICHJOBS_ACTIVE,
+ self->get_jobs_cancellable,
+ get_jobs_cb,
+ self);
+}
+
+static void
+jobs_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ PpPrinterEntry *self = (PpPrinterEntry*) user_data;
+
+ if (self->pp_jobs_dialog != NULL)
+ {
+ gtk_widget_destroy (GTK_WIDGET (self->pp_jobs_dialog));
+ self->pp_jobs_dialog = NULL;
+ }
+}
+
+void
+pp_printer_entry_show_jobs_dialog (PpPrinterEntry *self)
+{
+ if (self->pp_jobs_dialog == NULL)
+ {
+ self->pp_jobs_dialog = pp_jobs_dialog_new (self->printer_name);
+ g_signal_connect_object (self->pp_jobs_dialog, "response", G_CALLBACK (jobs_dialog_response_cb), self, 0);
+ gtk_window_set_transient_for (GTK_WINDOW (self->pp_jobs_dialog), GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
+ gtk_window_present (GTK_WINDOW (self->pp_jobs_dialog));
+ }
+}
+
+void
+pp_printer_entry_authenticate_jobs (PpPrinterEntry *self)
+{
+ pp_printer_entry_show_jobs_dialog (self);
+ pp_jobs_dialog_authenticate_jobs (self->pp_jobs_dialog);
+}
+
+static void
+show_jobs_dialog (GtkButton *button,
+ gpointer user_data)
+{
+ pp_printer_entry_show_jobs_dialog (PP_PRINTER_ENTRY (user_data));
+}
+
+enum
+{
+ PRINTER_READY = 3,
+ PRINTER_PROCESSING,
+ PRINTER_STOPPED
+};
+
+static void
+restart_printer (GtkButton *button,
+ PpPrinterEntry *self)
+{
+ if (self->printer_state == PRINTER_STOPPED)
+ printer_set_enabled (self->printer_name, TRUE);
+
+ if (!self->is_accepting_jobs)
+ printer_set_accepting_jobs (self->printer_name, TRUE, NULL);
+
+ g_signal_emit_by_name (self, "printer-changed");
+}
+
+GSList *
+pp_printer_entry_get_size_group_widgets (PpPrinterEntry *self)
+{
+ GSList *widgets = NULL;
+
+ widgets = g_slist_prepend (widgets, self->printer_icon);
+ widgets = g_slist_prepend (widgets, self->printer_location_label);
+ widgets = g_slist_prepend (widgets, self->printer_model_label);
+ widgets = g_slist_prepend (widgets, self->printer_inklevel_label);
+
+ return widgets;
+}
+
+PpPrinterEntry *
+pp_printer_entry_new (cups_dest_t printer,
+ gboolean is_authorized)
+{
+ PpPrinterEntry *self;
+
+ self = g_object_new (PP_PRINTER_ENTRY_TYPE, "printer-name", printer.name, NULL);
+
+ self->clean_command = pp_maintenance_command_new (self->printer_name,
+ "Clean",
+ "all",
+ /* Translators: Name of job which makes printer to clean its heads */
+ _("Clean print heads"));
+ check_clean_heads_maintenance_command (self);
+
+ g_signal_connect_object (self->supply_drawing_area, "draw", G_CALLBACK (supply_levels_draw_cb), self, G_CONNECT_SWAPPED);
+
+ pp_printer_entry_update (self, printer, is_authorized);
+
+ return self;
+}
+
+void
+pp_printer_entry_update (PpPrinterEntry *self,
+ cups_dest_t printer,
+ gboolean is_authorized)
+{
+ cups_ptype_t printer_type = 0;
+ gboolean is_accepting_jobs = TRUE;
+ gboolean ink_supply_is_empty;
+ g_autofree gchar *instance = NULL;
+ const gchar *printer_uri = NULL;
+ const gchar *device_uri = NULL;
+ const gchar *location = NULL;
+ g_autofree gchar *printer_icon_name = NULL;
+ const gchar *printer_make_and_model = NULL;
+ const gchar *reason = NULL;
+ gchar **printer_reasons = NULL;
+ g_autofree gchar *status = NULL;
+ g_autofree gchar *printer_status = NULL;
+ int i, j;
+ static const char * const reasons[] =
+ {
+ "toner-low",
+ "toner-empty",
+ "developer-low",
+ "developer-empty",
+ "marker-supply-low",
+ "marker-supply-empty",
+ "cover-open",
+ "door-open",
+ "media-low",
+ "media-empty",
+ "offline",
+ "paused",
+ "marker-waste-almost-full",
+ "marker-waste-full",
+ "opc-near-eol",
+ "opc-life-over"
+ };
+ static const char * statuses[] =
+ {
+ /* Translators: The printer is low on toner */
+ N_("Low on toner"),
+ /* Translators: The printer has no toner left */
+ N_("Out of toner"),
+ /* Translators: "Developer" is a chemical for photo development,
+ * http://en.wikipedia.org/wiki/Photographic_developer */
+ N_("Low on developer"),
+ /* Translators: "Developer" is a chemical for photo development,
+ * http://en.wikipedia.org/wiki/Photographic_developer */
+ N_("Out of developer"),
+ /* Translators: "marker" is one color bin of the printer */
+ N_("Low on a marker supply"),
+ /* Translators: "marker" is one color bin of the printer */
+ N_("Out of a marker supply"),
+ /* Translators: One or more covers on the printer are open */
+ N_("Open cover"),
+ /* Translators: One or more doors on the printer are open */
+ N_("Open door"),
+ /* Translators: At least one input tray is low on media */
+ N_("Low on paper"),
+ /* Translators: At least one input tray is empty */
+ N_("Out of paper"),
+ /* Translators: The printer is offline */
+ NC_("printer state", "Offline"),
+ /* Translators: Someone has stopped the Printer */
+ NC_("printer state", "Stopped"),
+ /* Translators: The printer marker supply waste receptacle is almost full */
+ N_("Waste receptacle almost full"),
+ /* Translators: The printer marker supply waste receptacle is full */
+ N_("Waste receptacle full"),
+ /* Translators: Optical photo conductors are used in laser printers */
+ N_("The optical photo conductor is near end of life"),
+ /* Translators: Optical photo conductors are used in laser printers */
+ N_("The optical photo conductor is no longer functioning")
+ };
+
+ if (printer.instance)
+ {
+ instance = g_strdup_printf ("%s / %s", printer.name, printer.instance);
+ }
+ else
+ {
+ instance = g_strdup (printer.name);
+ }
+
+ self->printer_state = PRINTER_READY;
+
+ for (i = 0; i < printer.num_options; i++)
+ {
+ if (g_strcmp0 (printer.options[i].name, "device-uri") == 0)
+ device_uri = printer.options[i].value;
+ else if (g_strcmp0 (printer.options[i].name, "printer-uri-supported") == 0)
+ printer_uri = printer.options[i].value;
+ else if (g_strcmp0 (printer.options[i].name, "printer-type") == 0)
+ printer_type = atoi (printer.options[i].value);
+ else if (g_strcmp0 (printer.options[i].name, "printer-location") == 0)
+ location = printer.options[i].value;
+ else if (g_strcmp0 (printer.options[i].name, "printer-state-reasons") == 0)
+ reason = printer.options[i].value;
+ else if (g_strcmp0 (printer.options[i].name, "marker-names") == 0)
+ {
+ g_free (self->inklevel->marker_names);
+ self->inklevel->marker_names = g_strcompress (g_strdup (printer.options[i].value));
+ }
+ else if (g_strcmp0 (printer.options[i].name, "marker-levels") == 0)
+ {
+ g_free (self->inklevel->marker_levels);
+ self->inklevel->marker_levels = g_strdup (printer.options[i].value);
+ }
+ else if (g_strcmp0 (printer.options[i].name, "marker-colors") == 0)
+ {
+ g_free (self->inklevel->marker_colors);
+ self->inklevel->marker_colors = g_strdup (printer.options[i].value);
+ }
+ else if (g_strcmp0 (printer.options[i].name, "marker-types") == 0)
+ {
+ g_free (self->inklevel->marker_types);
+ self->inklevel->marker_types = g_strdup (printer.options[i].value);
+ }
+ else if (g_strcmp0 (printer.options[i].name, "printer-make-and-model") == 0)
+ printer_make_and_model = printer.options[i].value;
+ else if (g_strcmp0 (printer.options[i].name, "printer-state") == 0)
+ self->printer_state = atoi (printer.options[i].value);
+ else if (g_strcmp0 (printer.options[i].name, "printer-is-accepting-jobs") == 0)
+ {
+ if (g_strcmp0 (printer.options[i].value, "true") == 0)
+ is_accepting_jobs = TRUE;
+ else
+ is_accepting_jobs = FALSE;
+ }
+ }
+
+ /* Find the first of the most severe reasons
+ * and show it in the status field
+ */
+ if (reason && !g_str_equal (reason, "none"))
+ {
+ int errors = 0, warnings = 0, reports = 0;
+ int error_index = -1, warning_index = -1, report_index = -1;
+
+ printer_reasons = g_strsplit (reason, ",", -1);
+ for (i = 0; i < g_strv_length (printer_reasons); i++)
+ {
+ for (j = 0; j < G_N_ELEMENTS (reasons); j++)
+ if (strncmp (printer_reasons[i], reasons[j], strlen (reasons[j])) == 0)
+ {
+ if (g_str_has_suffix (printer_reasons[i], "-report"))
+ {
+ if (reports == 0)
+ report_index = j;
+ reports++;
+ }
+ else if (g_str_has_suffix (printer_reasons[i], "-warning"))
+ {
+ if (warnings == 0)
+ warning_index = j;
+ warnings++;
+ }
+ else
+ {
+ if (errors == 0)
+ error_index = j;
+ errors++;
+ }
+ }
+ }
+ g_strfreev (printer_reasons);
+
+ if (error_index >= 0)
+ status = g_strdup (_(statuses[error_index]));
+ else if (warning_index >= 0)
+ status = g_strdup (_(statuses[warning_index]));
+ else if (report_index >= 0)
+ status = g_strdup (_(statuses[report_index]));
+ }
+
+ if ((self->printer_state == PRINTER_STOPPED || !is_accepting_jobs) &&
+ status != NULL && status[0] != '\0')
+ {
+ gtk_label_set_label (self->error_status, status);
+ gtk_widget_set_visible (GTK_WIDGET (self->printer_error), TRUE);
+ }
+ else
+ {
+ gtk_label_set_label (self->error_status, "");
+ gtk_widget_set_visible (GTK_WIDGET (self->printer_error), FALSE);
+ }
+
+ switch (self->printer_state)
+ {
+ case PRINTER_READY:
+ if (is_accepting_jobs)
+ {
+ /* Translators: Printer's state (can start new job without waiting) */
+ printer_status = g_strdup ( C_("printer state", "Ready"));
+ }
+ else
+ {
+ /* Translators: Printer's state (printer is ready but doesn't accept new jobs) */
+ printer_status = g_strdup ( C_("printer state", "Does not accept jobs"));
+ }
+ break;
+ case PRINTER_PROCESSING:
+ /* Translators: Printer's state (jobs are processing) */
+ printer_status = g_strdup ( C_("printer state", "Processing"));
+ break;
+ case PRINTER_STOPPED:
+ /* Translators: Printer's state (no jobs can be processed) */
+ printer_status = g_strdup ( C_("printer state", "Stopped"));
+ break;
+ }
+
+ if (printer_is_local (printer_type, device_uri))
+ printer_icon_name = g_strdup ("printer");
+ else
+ printer_icon_name = g_strdup ("printer-network");
+
+ g_object_set (self, "printer-location", location, NULL);
+
+ self->is_accepting_jobs = is_accepting_jobs;
+ self->is_authorized = is_authorized;
+
+ g_free (self->printer_hostname);
+ self->printer_hostname = printer_get_hostname (printer_type, device_uri, printer_uri);
+
+ gtk_image_set_from_icon_name (self->printer_icon, printer_icon_name, GTK_ICON_SIZE_DIALOG);
+ gtk_label_set_text (self->printer_status, printer_status);
+ gtk_label_set_text (self->printer_name_label, instance);
+ g_signal_handlers_block_by_func (self->printer_default_checkbutton, set_as_default_printer, self);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->printer_default_checkbutton), printer.is_default);
+ g_signal_handlers_unblock_by_func (self->printer_default_checkbutton, set_as_default_printer, self);
+
+ self->printer_make_and_model = sanitize_printer_model (printer_make_and_model);
+
+ if (self->printer_make_and_model == NULL || self->printer_make_and_model[0] == '\0')
+ {
+ gtk_widget_hide (GTK_WIDGET (self->printer_model_label));
+ gtk_widget_hide (GTK_WIDGET (self->printer_model));
+ }
+ else
+ {
+ gtk_label_set_text (self->printer_model, self->printer_make_and_model);
+ }
+
+ if (location != NULL && location[0] == '\0')
+ {
+ gtk_widget_hide (GTK_WIDGET (self->printer_location_label));
+ gtk_widget_hide (GTK_WIDGET (self->printer_location_address_label));
+ }
+ else
+ {
+ gtk_label_set_text (self->printer_location_address_label, location);
+ }
+
+ ink_supply_is_empty = supply_level_is_empty (self);
+ gtk_widget_set_visible (GTK_WIDGET (self->printer_inklevel_label), !ink_supply_is_empty);
+ gtk_widget_set_visible (GTK_WIDGET (self->supply_frame), !ink_supply_is_empty);
+
+ pp_printer_entry_update_jobs_count (self);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->printer_default_checkbutton), self->is_authorized);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->remove_printer_menuitem), self->is_authorized);
+}
+
+static void
+pp_printer_entry_dispose (GObject *object)
+{
+ PpPrinterEntry *self = PP_PRINTER_ENTRY (object);
+
+ g_cancellable_cancel (self->get_jobs_cancellable);
+ g_cancellable_cancel (self->check_clean_heads_cancellable);
+
+ g_clear_pointer (&self->printer_name, g_free);
+ g_clear_pointer (&self->printer_location, g_free);
+ g_clear_pointer (&self->printer_make_and_model, g_free);
+ g_clear_pointer (&self->printer_hostname, g_free);
+ g_clear_pointer (&self->inklevel, ink_level_data_free);
+ g_clear_object (&self->get_jobs_cancellable);
+ g_clear_object (&self->check_clean_heads_cancellable);
+ g_clear_object (&self->clean_command);
+
+ G_OBJECT_CLASS (pp_printer_entry_parent_class)->dispose (object);
+}
+
+static void
+pp_printer_entry_class_init (PpPrinterEntryClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/printers/printer-entry.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_icon);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_name_label);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_status);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_model_label);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_model);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_location_label);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_location_address_label);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_inklevel_label);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, supply_frame);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, supply_drawing_area);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_default_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, show_jobs_dialog_button);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, clean_heads_menuitem);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, remove_printer_menuitem);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, error_status);
+ gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_error);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_show_printer_details_dialog);
+ gtk_widget_class_bind_template_callback (widget_class, on_show_printer_options_dialog);
+ gtk_widget_class_bind_template_callback (widget_class, set_as_default_printer);
+ gtk_widget_class_bind_template_callback (widget_class, clean_heads);
+ gtk_widget_class_bind_template_callback (widget_class, remove_printer);
+ gtk_widget_class_bind_template_callback (widget_class, show_jobs_dialog);
+ gtk_widget_class_bind_template_callback (widget_class, restart_printer);
+
+ object_class->get_property = pp_printer_entry_get_property;
+ object_class->set_property = pp_printer_entry_set_property;
+ object_class->dispose = pp_printer_entry_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_PRINTER_NAME,
+ g_param_spec_string ("printer-name",
+ "Printer Name",
+ "The Printer unique name",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_PRINTER_LOCATION,
+ g_param_spec_string ("printer-location",
+ "Printer Location",
+ "Printer location string",
+ NULL,
+ G_PARAM_READWRITE));
+
+ signals[IS_DEFAULT_PRINTER] =
+ g_signal_new ("printer-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[PRINTER_DELETE] =
+ g_signal_new ("printer-delete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[PRINTER_RENAMED] =
+ g_signal_new ("printer-renamed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+}
diff --git a/panels/printers/pp-printer-entry.h b/panels/printers/pp-printer-entry.h
new file mode 100644
index 0000000..5db39d9
--- /dev/null
+++ b/panels/printers/pp-printer-entry.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Felipe Borges <felipeborges@gnome.org>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+
+#define PP_PRINTER_ENTRY_TYPE (pp_printer_entry_get_type ())
+G_DECLARE_FINAL_TYPE (PpPrinterEntry, pp_printer_entry, PP, PRINTER_ENTRY, GtkListBoxRow)
+
+PpPrinterEntry *pp_printer_entry_new (cups_dest_t printer,
+ gboolean is_authorized);
+
+void pp_printer_entry_update_jobs_count (PpPrinterEntry *self);
+
+GSList *pp_printer_entry_get_size_group_widgets (PpPrinterEntry *self);
+
+void pp_printer_entry_show_jobs_dialog (PpPrinterEntry *self);
+
+void pp_printer_entry_authenticate_jobs (PpPrinterEntry *self);
+
+void pp_printer_entry_update (PpPrinterEntry *self,
+ cups_dest_t printer,
+ gboolean is_authorized);
diff --git a/panels/printers/pp-printer.c b/panels/printers/pp-printer.c
new file mode 100644
index 0000000..451f9b9
--- /dev/null
+++ b/panels/printers/pp-printer.c
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Martin Hatina <mhatina@redhat.com>
+ * Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "pp-printer.h"
+
+#include "pp-job.h"
+
+#if (CUPS_VERSION_MAJOR == 1) && (CUPS_VERSION_MINOR <= 6)
+#define IPP_STATE_IDLE IPP_IDLE
+#endif
+
+struct _PpPrinter
+{
+ GObject parent_instance;
+ gchar *printer_name;
+};
+
+G_DEFINE_TYPE (PpPrinter, pp_printer, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0 = 0,
+ PROP_NAME
+};
+
+static void
+pp_printer_dispose (GObject *object)
+{
+ PpPrinter *self = PP_PRINTER (object);
+
+ g_clear_pointer (&self->printer_name, g_free);
+
+ G_OBJECT_CLASS (pp_printer_parent_class)->dispose (object);
+}
+
+static void
+pp_printer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PpPrinter *self = PP_PRINTER (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, self->printer_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pp_printer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PpPrinter *self = PP_PRINTER (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_free (self->printer_name);
+ self->printer_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+
+}
+
+static void
+pp_printer_class_init (PpPrinterClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pp_printer_set_property;
+ gobject_class->get_property = pp_printer_get_property;
+ gobject_class->dispose = pp_printer_dispose;
+
+ g_object_class_install_property (gobject_class, PROP_NAME,
+ g_param_spec_string ("printer-name",
+ "Printer name",
+ "Name of this printer",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+pp_printer_init (PpPrinter *self)
+{
+}
+
+PpPrinter *
+pp_printer_new (const gchar *name)
+{
+ PpPrinter *self = g_object_new (PP_TYPE_PRINTER, "printer-name", name, NULL);
+
+ return self;
+}
+
+static void
+printer_rename_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpPrinter *self = PP_PRINTER (source_object);
+ gboolean result;
+ gchar *new_printer_name = task_data;
+ g_autofree gchar *old_printer_name = NULL;
+
+ g_object_get (self, "printer-name", &old_printer_name, NULL);
+
+ result = printer_rename (old_printer_name, new_printer_name);
+
+ if (result)
+ {
+ g_object_set (self, "printer-name", new_printer_name, NULL);
+ }
+
+ g_task_return_boolean (task, result);
+}
+
+static void
+printer_rename_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PpPrinter *self;
+ g_autoptr(GVariant) output = NULL;
+ gboolean result = FALSE;
+ g_autoptr(GError) error = NULL;
+ GTask *task = user_data;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output != NULL)
+ {
+ g_autofree gchar *old_printer_name = NULL;
+ const gchar *ret_error;
+
+ self = g_task_get_source_object (task);
+ g_object_get (self, "printer-name", &old_printer_name, NULL);
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: renaming of printer %s failed: %s", old_printer_name, ret_error);
+ }
+ else
+ {
+ result = TRUE;
+ g_object_set (self, "printer-name", g_task_get_task_data (task), NULL);
+ }
+
+ g_task_return_boolean (task, result);
+ }
+ else
+ {
+ if (error->domain == G_DBUS_ERROR &&
+ (error->code == G_DBUS_ERROR_SERVICE_UNKNOWN ||
+ error->code == G_DBUS_ERROR_UNKNOWN_METHOD))
+ {
+ g_warning ("Update cups-pk-helper to at least 0.2.6 please to be able to use PrinterRename method.");
+
+ g_task_run_in_thread (task, printer_rename_thread);
+ }
+ else
+ {
+ g_task_return_boolean (task, FALSE);
+ }
+ }
+}
+
+static void
+get_bus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+ GTask *task = user_data;
+
+ bus = g_bus_get_finish (res, &error);
+ if (bus != NULL)
+ {
+ g_autofree gchar *printer_name = NULL;
+
+ g_object_get (g_task_get_source_object (task),
+ "printer-name", &printer_name,
+ NULL);
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterRename",
+ g_variant_new ("(ss)",
+ printer_name,
+ g_task_get_task_data (task)),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ g_task_get_cancellable (task),
+ printer_rename_dbus_cb,
+ task);
+ }
+ else
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ g_task_return_boolean (task, FALSE);
+ }
+}
+
+void
+pp_printer_rename_async (PpPrinter *self,
+ const gchar *new_printer_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (new_printer_name != NULL);
+
+ task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+ g_task_set_task_data (task, g_strdup (new_printer_name), g_free);
+
+ g_bus_get (G_BUS_TYPE_SYSTEM,
+ cancellable,
+ get_bus_cb,
+ task);
+}
+
+gboolean
+pp_printer_rename_finish (PpPrinter *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+ g_object_unref (res);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+typedef struct
+{
+ gboolean myjobs;
+ gint which_jobs;
+} GetJobsData;
+
+static void
+get_jobs_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ipp_attribute_t *attr = NULL;
+ static gchar *printer_attributes[] = { "auth-info-required" };
+ GetJobsData *get_jobs_data = task_data;
+ cups_job_t *jobs = NULL;
+ PpPrinter *self = PP_PRINTER (source_object);
+ gboolean auth_info_is_required;
+ PpJob *job;
+ ipp_t *job_request;
+ ipp_t *job_response;
+ ipp_t *printer_request;
+ ipp_t *printer_response;
+ gchar **auth_info_required = NULL;
+ g_autofree gchar *printer_name = NULL;
+ g_autoptr(GPtrArray) array = NULL;
+ gint num_jobs;
+ gint i, j;
+
+ g_object_get (self, "printer-name", &printer_name, NULL);
+
+ num_jobs = cupsGetJobs (&jobs,
+ printer_name,
+ get_jobs_data->myjobs ? 1 : 0,
+ get_jobs_data->which_jobs);
+
+ array = g_ptr_array_new_with_free_func (g_object_unref);
+ for (i = 0; i < num_jobs; i++)
+ {
+ auth_info_is_required = FALSE;
+ if (jobs[i].state == IPP_JOB_HELD)
+ {
+ g_autofree gchar *job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", jobs[i].id);
+
+ job_request = ippNewRequest (IPP_GET_JOB_ATTRIBUTES);
+ ippAddString (job_request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "job-uri", NULL, job_uri);
+ ippAddString (job_request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddString (job_request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", NULL, "job-hold-until");
+ job_response = cupsDoRequest (CUPS_HTTP_DEFAULT, job_request, "/");
+
+ if (job_response != NULL)
+ {
+ attr = ippFindAttribute (job_response, "job-hold-until", IPP_TAG_ZERO);
+ if (attr != NULL && g_strcmp0 (ippGetString (attr, 0, NULL), "auth-info-required") == 0)
+ {
+ auth_info_is_required = TRUE;
+
+ if (auth_info_required == NULL)
+ {
+ g_autofree gchar *printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", printer_name);
+
+ printer_request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
+ ippAddString (printer_request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, printer_uri);
+ ippAddString (printer_request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddStrings (printer_request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", 1, NULL, (const char **) printer_attributes);
+ printer_response = cupsDoRequest (CUPS_HTTP_DEFAULT, printer_request, "/");
+
+ if (printer_response != NULL)
+ {
+ attr = ippFindAttribute (printer_response, "auth-info-required", IPP_TAG_ZERO);
+ if (attr != NULL)
+ {
+ auth_info_required = g_new0 (gchar *, ippGetCount (attr) + 1);
+ for (j = 0; j < ippGetCount (attr); j++)
+ auth_info_required[j] = g_strdup (ippGetString (attr, j, NULL));
+ }
+
+ ippDelete (printer_response);
+ }
+ }
+ }
+
+ ippDelete (job_response);
+ }
+ }
+
+ job = g_object_new (pp_job_get_type (),
+ "id", jobs[i].id,
+ "title", jobs[i].title,
+ "state", jobs[i].state,
+ "auth-info-required", auth_info_is_required ? auth_info_required : NULL,
+ NULL);
+
+ g_ptr_array_add (array, job);
+ }
+
+ g_strfreev (auth_info_required);
+ cupsFreeJobs (num_jobs, jobs);
+
+ if (g_task_set_return_on_cancel (task, FALSE))
+ {
+ g_task_return_pointer (task, g_steal_pointer (&array), (GDestroyNotify) g_ptr_array_unref);
+ }
+}
+
+void
+pp_printer_get_jobs_async (PpPrinter *self,
+ gboolean myjobs,
+ gint which_jobs,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GetJobsData *get_jobs_data;
+ GTask *task;
+
+ get_jobs_data = g_new (GetJobsData, 1);
+ get_jobs_data->myjobs = myjobs;
+ get_jobs_data->which_jobs = which_jobs;
+
+ task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+ g_task_set_task_data (task, get_jobs_data, g_free);
+ g_task_set_return_on_cancel (task, TRUE);
+ g_task_run_in_thread (task, get_jobs_thread);
+ g_object_unref (task);
+}
+
+GPtrArray *
+pp_printer_get_jobs_finish (PpPrinter *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), NULL);
+
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+pp_printer_delete_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ gboolean result = FALSE;
+ g_autoptr(GError) error = NULL;
+ GTask *task = user_data;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output != NULL)
+ {
+ g_autofree gchar *printer_name = NULL;
+ const gchar *ret_error;
+
+ g_object_get (g_task_get_source_object (task), "printer-name", &printer_name, NULL);
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ g_warning ("cups-pk-helper: removing of printer %s failed: %s", printer_name, ret_error);
+ else
+ result = TRUE;
+
+ g_task_return_boolean (task, result);
+ }
+ else
+ {
+ g_warning ("%s", error->message);
+
+ g_task_return_boolean (task, FALSE);
+ }
+}
+
+static void
+pp_printer_delete_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+ GTask *task = user_data;
+
+ bus = g_bus_get_finish (res, &error);
+ if (bus != NULL)
+ {
+ g_autofree gchar *printer_name = NULL;
+
+ g_object_get (g_task_get_source_object (task),
+ "printer-name", &printer_name,
+ NULL);
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterDelete",
+ g_variant_new ("(s)", printer_name),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ g_task_get_cancellable (task),
+ pp_printer_delete_dbus_cb,
+ task);
+ }
+ else
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ g_task_return_boolean (task, FALSE);
+ }
+}
+
+void
+pp_printer_delete_async (PpPrinter *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+
+ g_bus_get (G_BUS_TYPE_SYSTEM,
+ cancellable,
+ pp_printer_delete_cb,
+ task);
+}
+
+gboolean
+pp_printer_delete_finish (PpPrinter *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+typedef struct
+{
+ gchar *filename;
+ gchar *job_name;
+} PrintFileData;
+
+static void
+print_file_data_free (PrintFileData *print_file_data)
+{
+ g_free (print_file_data->filename);
+ g_free (print_file_data->job_name);
+
+ g_slice_free (PrintFileData, print_file_data);
+}
+
+static void
+print_file_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ PpPrinter *self = PP_PRINTER (source_object);
+ PrintFileData *print_file_data;
+ cups_ptype_t type = 0;
+ cups_dest_t *dest = NULL;
+ const gchar *printer_type = NULL;
+ gboolean ret = FALSE;
+ g_autofree gchar *printer_name = NULL;
+ g_autofree gchar *printer_uri = NULL;
+ g_autofree gchar *resource = NULL;
+ ipp_t *response = NULL;
+ ipp_t *request;
+
+ g_object_get (self, "printer-name", &printer_name, NULL);
+ dest = cupsGetNamedDest (CUPS_HTTP_DEFAULT, printer_name, NULL);
+ if (dest != NULL)
+ {
+ printer_type = cupsGetOption ("printer-type",
+ dest->num_options,
+ dest->options);
+ cupsFreeDests (1, dest);
+
+ if (printer_type)
+ type = atoi (printer_type);
+ }
+
+ if (type & CUPS_PRINTER_CLASS)
+ {
+ printer_uri = g_strdup_printf ("ipp://localhost/classes/%s", printer_name);
+ resource = g_strdup_printf ("/classes/%s", printer_name);
+ }
+ else
+ {
+ printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", printer_name);
+ resource = g_strdup_printf ("/printers/%s", printer_name);
+ }
+
+ print_file_data = g_task_get_task_data (task);
+
+ request = ippNewRequest (IPP_PRINT_JOB);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, printer_uri);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser ());
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "job-name", NULL, print_file_data->job_name);
+ response = cupsDoFileRequest (CUPS_HTTP_DEFAULT, request, resource, print_file_data->filename);
+
+ if (response != NULL)
+ {
+ if (ippGetState (response) == IPP_ERROR)
+ g_warning ("An error has occurred during printing of test page.");
+ if (ippGetState (response) == IPP_STATE_IDLE)
+ ret = TRUE;
+
+ ippDelete (response);
+ }
+
+ if (g_task_set_return_on_cancel (task, FALSE))
+ {
+ g_task_return_boolean (task, ret);
+ }
+}
+
+void
+pp_printer_print_file_async (PpPrinter *self,
+ const gchar *filename,
+ const gchar *job_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PrintFileData *print_file_data;
+ GTask *task;
+
+ print_file_data = g_new (PrintFileData, 1);
+ print_file_data->filename = g_strdup (filename);
+ print_file_data->job_name = g_strdup (job_name);
+
+ task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+
+ g_task_set_return_on_cancel (task, TRUE);
+ g_task_set_task_data (task, print_file_data, (GDestroyNotify) print_file_data_free);
+
+ g_task_run_in_thread (task, print_file_thread);
+ g_object_unref (task);
+}
+
+gboolean
+pp_printer_print_file_finish (PpPrinter *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
diff --git a/panels/printers/pp-printer.h b/panels/printers/pp-printer.h
new file mode 100644
index 0000000..5fac607
--- /dev/null
+++ b/panels/printers/pp-printer.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Martin Hatina <mhatina@redhat.com>
+ * Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_PRINTER (pp_printer_get_type ())
+G_DECLARE_FINAL_TYPE (PpPrinter, pp_printer, PP, PRINTER, GObject)
+
+GType pp_printer_get_type (void) G_GNUC_CONST;
+
+PpPrinter *pp_printer_new (const gchar *name);
+
+void pp_printer_rename_async (PpPrinter *printer,
+ const gchar *new_printer_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_printer_rename_finish (PpPrinter *printer,
+ GAsyncResult *res,
+ GError **error);
+
+void pp_printer_delete_async (PpPrinter *printer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_printer_delete_finish (PpPrinter *printer,
+ GAsyncResult *res,
+ GError **error);
+
+void pp_printer_get_jobs_async (PpPrinter *printer,
+ gboolean myjobs,
+ gint which_jobs,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *pp_printer_get_jobs_finish (PpPrinter *printer,
+ GAsyncResult *res,
+ GError **error);
+
+void pp_printer_print_file_async (PpPrinter *printer,
+ const gchar *filename,
+ const gchar *job_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean pp_printer_print_file_finish (PpPrinter *printer,
+ GAsyncResult *res,
+ GError **error);
+
+G_END_DECLS
diff --git a/panels/printers/pp-samba.c b/panels/printers/pp-samba.c
new file mode 100644
index 0000000..5eaf5b7
--- /dev/null
+++ b/panels/printers/pp-samba.c
@@ -0,0 +1,414 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 - 2013 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#include "pp-samba.h"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libsmbclient.h>
+#include <errno.h>
+
+#define POLL_DELAY 100000
+
+struct _PpSamba
+{
+ PpHost parent_instance;
+
+ /* Auth info */
+ gchar *username;
+ gchar *password;
+ gboolean waiting;
+};
+
+G_DEFINE_TYPE (PpSamba, pp_samba, PP_TYPE_HOST);
+
+static void
+pp_samba_finalize (GObject *object)
+{
+ PpSamba *self = PP_SAMBA (object);
+
+ g_clear_pointer (&self->username, g_free);
+ g_clear_pointer (&self->password, g_free);
+
+ G_OBJECT_CLASS (pp_samba_parent_class)->finalize (object);
+}
+
+static void
+pp_samba_class_init (PpSambaClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = pp_samba_finalize;
+}
+
+static void
+pp_samba_init (PpSamba *samba)
+{
+}
+
+PpSamba *
+pp_samba_new (const gchar *hostname)
+{
+ return g_object_new (PP_TYPE_SAMBA,
+ "hostname", hostname,
+ NULL);
+}
+
+typedef struct
+{
+ PpSamba *samba;
+ GPtrArray *devices;
+ GMainContext *context;
+ gboolean auth_if_needed;
+ gboolean hostname_set;
+ gboolean cancelled;
+} SMBData;
+
+static void
+smb_data_free (SMBData *data)
+{
+ if (data)
+ {
+ g_ptr_array_unref (data->devices);
+
+ g_free (data);
+ }
+}
+
+static gboolean
+get_auth_info (gpointer user_data)
+{
+ SMBData *data = (SMBData *) user_data;
+ PpSamba *samba = PP_SAMBA (data->samba);
+
+ g_signal_emit_by_name (samba, "authentication-required");
+
+ return FALSE;
+}
+
+void
+pp_samba_set_auth_info (PpSamba *samba,
+ const gchar *username,
+ const gchar *password)
+{
+ g_free (samba->username);
+ if ((username != NULL) && (username[0] != '\0'))
+ samba->username = g_strdup (username);
+ else
+ samba->username = NULL;
+
+ g_free (samba->password);
+ if ((password != NULL) && (password[0] != '\0'))
+ samba->password = g_strdup (password);
+ else
+ samba->password = NULL;
+
+ samba->waiting = FALSE;
+}
+
+static void
+auth_fn (SMBCCTX *smb_context,
+ const char *server,
+ const char *share,
+ char *workgroup,
+ int wgmaxlen,
+ char *username,
+ int unmaxlen,
+ char *password,
+ int pwmaxlen)
+{
+ PpSamba *samba;
+ g_autoptr(GSource) source = NULL;
+ SMBData *data;
+
+ data = (SMBData *) smbc_getOptionUserData (smb_context);
+ samba = data->samba;
+
+ if (!data->cancelled)
+ {
+ samba->username = g_strdup (username);
+ samba->password = g_strdup (password);
+
+ source = g_idle_source_new ();
+ g_source_set_callback (source,
+ get_auth_info,
+ data,
+ NULL);
+ g_source_attach (source, data->context);
+
+ samba->waiting = TRUE;
+
+ /*
+ * smbclient needs to get authentication data
+ * from this synchronous callback so we are blocking
+ * until we get them
+ */
+ while (samba->waiting)
+ {
+ g_usleep (POLL_DELAY);
+ }
+
+ /* Samba tries to call the auth_fn again if we just set the values
+ * to NULL when we want to cancel the authentication
+ */
+ if (samba->username == NULL && samba->password == NULL)
+ data->cancelled = TRUE;
+
+ if (samba->username != NULL)
+ {
+ if (g_strcmp0 (username, samba->username) != 0)
+ g_strlcpy (username, samba->username, unmaxlen);
+ }
+ else
+ {
+ username[0] = '\0';
+ }
+
+ if (samba->password != NULL)
+ {
+ if (g_strcmp0 (password, samba->password) != 0)
+ g_strlcpy (password, samba->password, pwmaxlen);
+ }
+ else
+ {
+ password[0] = '\0';
+ }
+
+ }
+}
+
+static void
+anonymous_auth_fn (SMBCCTX *smb_context,
+ const char *server,
+ const char *share,
+ char *workgroup,
+ int wgmaxlen,
+ char *username,
+ int unmaxlen,
+ char *password,
+ int pwmaxlen)
+{
+ username[0] = '\0';
+ password[0] = '\0';
+}
+
+static void
+list_dir (SMBCCTX *smb_context,
+ const gchar *dirname,
+ const gchar *path,
+ GCancellable *cancellable,
+ SMBData *data)
+{
+ struct smbc_dirent *dirent;
+ smbc_closedir_fn smbclient_closedir;
+ smbc_readdir_fn smbclient_readdir;
+ smbc_opendir_fn smbclient_opendir;
+ const gchar *host_name;
+ SMBCFILE *dir;
+
+ if (!g_cancellable_is_cancelled (cancellable))
+ {
+ smbclient_closedir = smbc_getFunctionClosedir (smb_context);
+ smbclient_readdir = smbc_getFunctionReaddir (smb_context);
+ smbclient_opendir = smbc_getFunctionOpendir (smb_context);
+
+ dir = smbclient_opendir (smb_context, dirname);
+ if (!dir && errno == EACCES)
+ {
+ if (g_str_has_prefix (dirname, "smb://"))
+ host_name = dirname + 6;
+ else
+ host_name = dirname;
+
+ if (data->auth_if_needed)
+ {
+ data->cancelled = FALSE;
+ smbc_setFunctionAuthDataWithContext (smb_context, auth_fn);
+ dir = smbclient_opendir (smb_context, dirname);
+ smbc_setFunctionAuthDataWithContext (smb_context, anonymous_auth_fn);
+
+ if (data->cancelled)
+ {
+ PpPrintDevice *device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "host-name", host_name,
+ "is-authenticated-server", TRUE,
+ NULL);
+ g_ptr_array_add (data->devices, device);
+
+ if (dir)
+ smbclient_closedir (smb_context, dir);
+ return;
+ }
+ }
+ else
+ {
+ PpPrintDevice *device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "host-name", host_name,
+ "is-authenticated-server", TRUE,
+ NULL);
+ g_ptr_array_add (data->devices, device);
+ }
+ }
+
+ while (dir && (dirent = smbclient_readdir (smb_context, dir)))
+ {
+ g_autofree gchar *subdirname = NULL;
+ g_autofree gchar *subpath = NULL;
+
+ if (dirent->smbc_type == SMBC_WORKGROUP)
+ {
+ subdirname = g_strdup_printf ("%s%s", dirname, dirent->name);
+ subpath = g_strdup_printf ("%s%s", path, dirent->name);
+ }
+
+ if (dirent->smbc_type == SMBC_SERVER)
+ {
+ subdirname = g_strdup_printf ("smb://%s", dirent->name);
+ subpath = g_strdup_printf ("%s//%s", path, dirent->name);
+ }
+
+ if (dirent->smbc_type == SMBC_PRINTER_SHARE)
+ {
+ g_autofree gchar *uri = NULL;
+ g_autofree gchar *device_name = NULL;
+ g_autofree gchar *device_uri = NULL;
+ PpPrintDevice *device;
+
+ uri = g_strdup_printf ("%s/%s", dirname, dirent->name);
+ device_uri = g_uri_escape_string (uri,
+ G_URI_RESERVED_CHARS_GENERIC_DELIMITERS
+ G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS,
+ FALSE);
+
+ device_name = g_strdup (dirent->name);
+ g_strcanon (device_name, ALLOWED_CHARACTERS, '-');
+
+ device = g_object_new (PP_TYPE_PRINT_DEVICE,
+ "device-uri", device_uri,
+ "is-network-device", TRUE,
+ "device-info", dirent->comment,
+ "device-name", device_name,
+ "acquisition-method", data->hostname_set ? ACQUISITION_METHOD_SAMBA_HOST : ACQUISITION_METHOD_SAMBA,
+ "device-location", path,
+ "host-name", dirname,
+ NULL);
+
+ g_ptr_array_add (data->devices, device);
+ }
+
+ if (subdirname)
+ {
+ list_dir (smb_context,
+ subdirname,
+ subpath,
+ cancellable,
+ data);
+ }
+ }
+
+ if (dir)
+ smbclient_closedir (smb_context, dir);
+ }
+}
+
+static void
+_pp_samba_get_devices_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ static GMutex mutex;
+ SMBData *data = (SMBData *) task_data;
+ SMBCCTX *smb_context;
+
+ data->devices = g_ptr_array_new_with_free_func (g_object_unref);
+ data->samba = PP_SAMBA (source_object);
+
+ g_mutex_lock (&mutex);
+
+ smb_context = smbc_new_context ();
+ if (smb_context)
+ {
+ if (smbc_init_context (smb_context))
+ {
+ g_autofree gchar *hostname = NULL;
+ g_autofree gchar *dirname = NULL;
+ g_autofree gchar *path = NULL;
+
+ smbc_setOptionUserData (smb_context, data);
+
+ g_object_get (source_object, "hostname", &hostname, NULL);
+ if (hostname != NULL)
+ {
+ dirname = g_strdup_printf ("smb://%s", hostname);
+ path = g_strdup_printf ("//%s", hostname);
+ }
+ else
+ {
+ dirname = g_strdup_printf ("smb://");
+ path = g_strdup_printf ("//");
+ }
+
+ smbc_setFunctionAuthDataWithContext (smb_context, anonymous_auth_fn);
+ list_dir (smb_context, dirname, path, cancellable, data);
+ }
+
+ smbc_free_context (smb_context, 1);
+ }
+
+ g_mutex_unlock (&mutex);
+
+ g_task_return_pointer (task, g_ptr_array_ref (data->devices), (GDestroyNotify) g_ptr_array_unref);
+}
+
+void
+pp_samba_get_devices_async (PpSamba *samba,
+ gboolean auth_if_needed,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ SMBData *data;
+ g_autofree gchar *hostname = NULL;
+
+ g_object_get (G_OBJECT (samba), "hostname", &hostname, NULL);
+
+ task = g_task_new (samba, cancellable, callback, user_data);
+ data = g_new0 (SMBData, 1);
+ data->devices = NULL;
+ data->context = g_main_context_default ();
+ data->hostname_set = hostname != NULL;
+ data->auth_if_needed = auth_if_needed;
+
+ g_task_set_task_data (task, data, (GDestroyNotify) smb_data_free);
+ g_task_run_in_thread (task, _pp_samba_get_devices_thread);
+}
+
+GPtrArray *
+pp_samba_get_devices_finish (PpSamba *samba,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, samba), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
diff --git a/panels/printers/pp-samba.h b/panels/printers/pp-samba.h
new file mode 100644
index 0000000..0801317
--- /dev/null
+++ b/panels/printers/pp-samba.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 - 2013 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marek Kasik <mkasik@redhat.com>
+ */
+
+#pragma once
+
+#include "pp-host.h"
+#include "pp-utils.h"
+
+G_BEGIN_DECLS
+
+#define PP_TYPE_SAMBA (pp_samba_get_type ())
+G_DECLARE_FINAL_TYPE (PpSamba, pp_samba, PP, SAMBA, PpHost)
+
+PpSamba *pp_samba_new (const gchar *hostname);
+
+void pp_samba_get_devices_async (PpSamba *samba,
+ gboolean auth_if_needed,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *pp_samba_get_devices_finish (PpSamba *samba,
+ GAsyncResult *result,
+ GError **error);
+
+void pp_samba_set_auth_info (PpSamba *samba,
+ const gchar *username,
+ const gchar *password);
+
+G_END_DECLS
diff --git a/panels/printers/pp-utils.c b/panels/printers/pp-utils.c
new file mode 100644
index 0000000..127660f
--- /dev/null
+++ b/panels/printers/pp-utils.c
@@ -0,0 +1,3643 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+#include "pp-utils.h"
+
+#define DBUS_TIMEOUT 120000
+#define DBUS_TIMEOUT_LONG 600000
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetCount(attr) attr->num_values
+#define ippGetGroupTag(attr) attr->group_tag
+#define ippGetValueTag(attr) attr->value_tag
+#define ippGetName(attr) attr->name
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+#define ippGetBoolean(attr, element) attr->values[element].boolean
+
+static int
+ippGetRange (ipp_attribute_t *attr,
+ int element,
+ int *upper)
+{
+ *upper = attr->values[element].range.upper;
+ return (attr->values[element].range.lower);
+}
+
+static ipp_attribute_t *
+ippFirstAttribute (ipp_t *ipp)
+{
+ if (!ipp)
+ return (NULL);
+ return (ipp->current = ipp->attrs);
+}
+
+static ipp_attribute_t *
+ippNextAttribute (ipp_t *ipp)
+{
+ if (!ipp || !ipp->current)
+ return (NULL);
+ return (ipp->current = ipp->current->next);
+}
+#endif
+
+#if (CUPS_VERSION_MAJOR == 1) && (CUPS_VERSION_MINOR <= 6)
+#define HTTP_URI_STATUS_OK HTTP_URI_OK
+#endif
+
+gchar *
+get_tag_value (const gchar *tag_string, const gchar *tag_name)
+{
+ gchar **tag_string_splitted = NULL;
+ gchar *tag_value = NULL;
+ gint tag_name_length;
+ gint i;
+
+ if (tag_string && tag_name)
+ {
+ tag_name_length = strlen (tag_name);
+ tag_string_splitted = g_strsplit (tag_string, ";", 0);
+ if (tag_string_splitted)
+ {
+ for (i = 0; i < g_strv_length (tag_string_splitted); i++)
+ if (g_ascii_strncasecmp (tag_string_splitted[i], tag_name, tag_name_length) == 0)
+ if (strlen (tag_string_splitted[i]) > tag_name_length + 1)
+ tag_value = g_strdup (tag_string_splitted[i] + tag_name_length + 1);
+
+ g_strfreev (tag_string_splitted);
+ }
+ }
+
+ return tag_value;
+}
+
+
+/*
+ * Normalize given string so that it is lowercase, doesn't
+ * have trailing or leading whitespaces and digits doesn't
+ * neighbour with alphabetic.
+ * (see cupshelpers/ppds.py from system-config-printer)
+ */
+static gchar *
+normalize (const gchar *input_string)
+{
+ gchar *result = NULL;
+ gint i, j = 0, k = -1;
+
+ if (input_string)
+ {
+ g_autofree gchar *tmp = g_strstrip (g_ascii_strdown (input_string, -1));
+ if (tmp)
+ {
+ g_autofree gchar *res = g_new (gchar, 2 * strlen (tmp));
+
+ for (i = 0; i < strlen (tmp); i++)
+ {
+ if ((g_ascii_isalpha (tmp[i]) && k >= 0 && g_ascii_isdigit (res[k])) ||
+ (g_ascii_isdigit (tmp[i]) && k >= 0 && g_ascii_isalpha (res[k])))
+ {
+ res[j] = ' ';
+ k = j++;
+ res[j] = tmp[i];
+ k = j++;
+ }
+ else
+ {
+ if (g_ascii_isspace (tmp[i]) || !g_ascii_isalnum (tmp[i]))
+ {
+ if (!(k >= 0 && res[k] == ' '))
+ {
+ res[j] = ' ';
+ k = j++;
+ }
+ }
+ else
+ {
+ res[j] = tmp[i];
+ k = j++;
+ }
+ }
+ }
+
+ res[j] = '\0';
+
+ result = g_strdup (res);
+ }
+ }
+
+ return result;
+}
+
+
+char *
+get_dest_attr (const char *dest_name,
+ const char *attr)
+{
+ cups_dest_t *dests;
+ int num_dests;
+ cups_dest_t *dest;
+ const char *value;
+ char *ret;
+
+ if (dest_name == NULL)
+ return NULL;
+
+ ret = NULL;
+
+ num_dests = cupsGetDests (&dests);
+ if (num_dests < 1) {
+ g_debug ("Unable to get printer destinations");
+ return NULL;
+ }
+
+ dest = cupsGetDest (dest_name, NULL, num_dests, dests);
+ if (dest == NULL) {
+ g_debug ("Unable to find a printer named '%s'", dest_name);
+ goto out;
+ }
+
+ value = cupsGetOption (attr, dest->num_options, dest->options);
+ if (value == NULL) {
+ g_debug ("Unable to get %s for '%s'", attr, dest_name);
+ goto out;
+ }
+ ret = g_strdup (value);
+out:
+ cupsFreeDests (num_dests, dests);
+
+ return ret;
+}
+
+gchar *
+get_ppd_attribute (const gchar *ppd_file_name,
+ const gchar *attribute_name)
+{
+ ppd_file_t *ppd_file = NULL;
+ ppd_attr_t *ppd_attr = NULL;
+ gchar *result = NULL;
+
+ if (ppd_file_name)
+ {
+ ppd_file = ppdOpenFile (ppd_file_name);
+
+ if (ppd_file)
+ {
+ ppd_attr = ppdFindAttr (ppd_file, attribute_name, NULL);
+ if (ppd_attr != NULL)
+ result = g_strdup (ppd_attr->value);
+ ppdClose (ppd_file);
+ }
+ }
+
+ return result;
+}
+
+/* Set default destination in ~/.cups/lpoptions.
+ * Unset default destination if "dest" is NULL.
+ */
+void
+set_local_default_printer (const gchar *printer_name)
+{
+ cups_dest_t *dests = NULL;
+ int num_dests = 0;
+ int i;
+
+ num_dests = cupsGetDests (&dests);
+
+ for (i = 0; i < num_dests; i ++)
+ {
+ if (printer_name && g_strcmp0 (dests[i].name, printer_name) == 0)
+ dests[i].is_default = 1;
+ else
+ dests[i].is_default = 0;
+ }
+
+ cupsSetDests (num_dests, dests);
+}
+
+/*
+ * This function does something which should be provided by CUPS...
+ * It returns FALSE if the renaming fails.
+ */
+gboolean
+printer_rename (const gchar *old_name,
+ const gchar *new_name)
+{
+ ipp_attribute_t *attr = NULL;
+ cups_ptype_t printer_type = 0;
+ cups_dest_t *dests = NULL;
+ cups_dest_t *dest = NULL;
+ cups_job_t *jobs = NULL;
+ g_autoptr(GDBusConnection) bus = NULL;
+ const gchar *printer_location = NULL;
+ const gchar *printer_info = NULL;
+ const gchar *printer_uri = NULL;
+ const gchar *device_uri = NULL;
+ const gchar *job_sheets = NULL;
+ gboolean result = FALSE;
+ gboolean accepting = TRUE;
+ gboolean printer_paused = FALSE;
+ gboolean default_printer = FALSE;
+ gboolean printer_shared = FALSE;
+ g_autoptr(GError) error = NULL;
+ http_t *http;
+ g_autofree gchar *ppd_link = NULL;
+ g_autofree gchar *ppd_filename = NULL;
+ gchar **sheets = NULL;
+ gchar **users_allowed = NULL;
+ gchar **users_denied = NULL;
+ gchar **member_names = NULL;
+ const gchar *start_sheet = NULL;
+ const gchar *end_sheet = NULL;
+ g_autofree gchar *error_policy = NULL;
+ g_autofree gchar *op_policy = NULL;
+ ipp_t *request;
+ ipp_t *response;
+ gint i;
+ int num_dests = 0;
+ int num_jobs = 0;
+ static const char * const requested_attrs[] = {
+ "printer-error-policy",
+ "printer-op-policy",
+ "requesting-user-name-allowed",
+ "requesting-user-name-denied",
+ "member-names"};
+
+ if (old_name == NULL ||
+ old_name[0] == '\0' ||
+ new_name == NULL ||
+ new_name[0] == '\0' ||
+ g_strcmp0 (old_name, new_name) == 0)
+ return FALSE;
+
+ num_dests = cupsGetDests (&dests);
+
+ dest = cupsGetDest (new_name, NULL, num_dests, dests);
+ if (dest)
+ {
+ cupsFreeDests (num_dests, dests);
+ return FALSE;
+ }
+
+ num_jobs = cupsGetJobs (&jobs, old_name, 0, CUPS_WHICHJOBS_ACTIVE);
+ cupsFreeJobs (num_jobs, jobs);
+ if (num_jobs > 1)
+ {
+ g_warning ("There are queued jobs on printer %s!", old_name);
+ cupsFreeDests (num_dests, dests);
+ return FALSE;
+ }
+
+ /*
+ * Gather some informations about the original printer
+ */
+ dest = cupsGetDest (old_name, NULL, num_dests, dests);
+ if (dest)
+ {
+ for (i = 0; i < dest->num_options; i++)
+ {
+ if (g_strcmp0 (dest->options[i].name, "printer-is-accepting-jobs") == 0)
+ accepting = g_strcmp0 (dest->options[i].value, "true") == 0;
+ else if (g_strcmp0 (dest->options[i].name, "printer-is-shared") == 0)
+ printer_shared = g_strcmp0 (dest->options[i].value, "true") == 0;
+ else if (g_strcmp0 (dest->options[i].name, "device-uri") == 0)
+ device_uri = dest->options[i].value;
+ else if (g_strcmp0 (dest->options[i].name, "printer-uri-supported") == 0)
+ printer_uri = dest->options[i].value;
+ else if (g_strcmp0 (dest->options[i].name, "printer-info") == 0)
+ printer_info = dest->options[i].value;
+ else if (g_strcmp0 (dest->options[i].name, "printer-location") == 0)
+ printer_location = dest->options[i].value;
+ else if (g_strcmp0 (dest->options[i].name, "printer-state") == 0)
+ printer_paused = g_strcmp0 (dest->options[i].value, "5") == 0;
+ else if (g_strcmp0 (dest->options[i].name, "job-sheets") == 0)
+ job_sheets = dest->options[i].value;
+ else if (g_strcmp0 (dest->options[i].name, "printer-type") == 0)
+ printer_type = atoi (dest->options[i].value);
+ }
+ default_printer = dest->is_default;
+ }
+ cupsFreeDests (num_dests, dests);
+
+ if (accepting)
+ {
+ printer_set_accepting_jobs (old_name, FALSE, NULL);
+
+ num_jobs = cupsGetJobs (&jobs, old_name, 0, CUPS_WHICHJOBS_ACTIVE);
+ cupsFreeJobs (num_jobs, jobs);
+ if (num_jobs > 1)
+ {
+ printer_set_accepting_jobs (old_name, accepting, NULL);
+ g_warning ("There are queued jobs on printer %s!", old_name);
+ return FALSE;
+ }
+ }
+
+
+ /*
+ * Gather additional informations about the original printer
+ */
+#ifdef HAVE_CUPS_HTTPCONNECT2
+ http = httpConnect2 (cupsServer (), ippPort (), NULL, AF_UNSPEC,
+ cupsEncryption (), 1, 30000, NULL);
+#else
+ http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ());
+#endif
+ if (http != NULL)
+ {
+ request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, printer_uri);
+ ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", G_N_ELEMENTS (requested_attrs), NULL, requested_attrs);
+ response = cupsDoRequest (http, request, "/");
+
+ if (response)
+ {
+ if (ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ attr = ippFindAttribute (response, "printer-error-policy", IPP_TAG_NAME);
+ if (attr)
+ error_policy = g_strdup (ippGetString (attr, 0, NULL));
+
+ attr = ippFindAttribute (response, "printer-op-policy", IPP_TAG_NAME);
+ if (attr)
+ op_policy = g_strdup (ippGetString (attr, 0, NULL));
+
+ attr = ippFindAttribute (response, "requesting-user-name-allowed", IPP_TAG_NAME);
+ if (attr && ippGetCount (attr) > 0)
+ {
+ users_allowed = g_new0 (gchar *, ippGetCount (attr) + 1);
+ for (i = 0; i < ippGetCount (attr); i++)
+ users_allowed[i] = g_strdup (ippGetString (attr, i, NULL));
+ }
+
+ attr = ippFindAttribute (response, "requesting-user-name-denied", IPP_TAG_NAME);
+ if (attr && ippGetCount (attr) > 0)
+ {
+ users_denied = g_new0 (gchar *, ippGetCount (attr) + 1);
+ for (i = 0; i < ippGetCount (attr); i++)
+ users_denied[i] = g_strdup (ippGetString (attr, i, NULL));
+ }
+
+ attr = ippFindAttribute (response, "member-names", IPP_TAG_NAME);
+ if (attr && ippGetCount (attr) > 0)
+ {
+ member_names = g_new0 (gchar *, ippGetCount (attr) + 1);
+ for (i = 0; i < ippGetCount (attr); i++)
+ member_names[i] = g_strdup (ippGetString (attr, i, NULL));
+ }
+ }
+ ippDelete (response);
+ }
+ httpClose (http);
+ }
+
+ if (job_sheets)
+ {
+ sheets = g_strsplit (job_sheets, ",", 0);
+ if (g_strv_length (sheets) > 1)
+ {
+ start_sheet = sheets[0];
+ end_sheet = sheets[1];
+ }
+ }
+
+ ppd_link = g_strdup (cupsGetPPD (old_name));
+ if (ppd_link)
+ {
+ ppd_filename = g_file_read_link (ppd_link, NULL);
+
+ if (!ppd_filename)
+ ppd_filename = g_strdup (ppd_link);
+ }
+
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ }
+ else
+ {
+ if (printer_type & CUPS_PRINTER_CLASS)
+ {
+ if (member_names)
+ for (i = 0; i < g_strv_length (member_names); i++)
+ class_add_printer (new_name, member_names[i]);
+ }
+ else
+ {
+ g_autoptr(GVariant) output = NULL;
+ g_autoptr(GError) add_error = NULL;
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterAddWithPpdFile",
+ g_variant_new ("(sssss)",
+ new_name,
+ device_uri ? device_uri : "",
+ ppd_filename ? ppd_filename : "",
+ printer_info ? printer_info : "",
+ printer_location ? printer_location : ""),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &add_error);
+
+ if (output)
+ {
+ const gchar *ret_error;
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ g_warning ("cups-pk-helper: rename of printer %s to %s failed: %s", old_name, new_name, ret_error);
+ }
+ else
+ {
+ g_warning ("%s", add_error->message);
+ }
+ }
+ }
+
+ if (ppd_link)
+ {
+ g_unlink (ppd_link);
+ }
+
+ num_dests = cupsGetDests (&dests);
+ dest = cupsGetDest (new_name, NULL, num_dests, dests);
+ if (dest)
+ {
+ printer_set_accepting_jobs (new_name, accepting, NULL);
+ printer_set_enabled (new_name, !printer_paused);
+ printer_set_shared (new_name, printer_shared);
+ printer_set_job_sheets (new_name, start_sheet, end_sheet);
+ printer_set_policy (new_name, op_policy, FALSE);
+ printer_set_policy (new_name, error_policy, TRUE);
+ printer_set_users (new_name, users_allowed, TRUE);
+ printer_set_users (new_name, users_denied, FALSE);
+ if (default_printer)
+ printer_set_default (new_name);
+
+ printer_delete (old_name);
+
+ result = TRUE;
+ }
+ else
+ printer_set_accepting_jobs (old_name, accepting, NULL);
+
+ cupsFreeDests (num_dests, dests);
+ if (sheets)
+ g_strfreev (sheets);
+ if (users_allowed)
+ g_strfreev (users_allowed);
+ if (users_denied)
+ g_strfreev (users_denied);
+
+ return result;
+}
+
+gboolean
+printer_set_location (const gchar *printer_name,
+ const gchar *location)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name || !location)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetLocation",
+ g_variant_new ("(ss)", printer_name, location),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of location for printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_set_accepting_jobs (const gchar *printer_name,
+ gboolean accepting_jobs,
+ const gchar *reason)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetAcceptJobs",
+ g_variant_new ("(sbs)",
+ printer_name,
+ accepting_jobs,
+ reason ? reason : ""),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of acceptance of jobs for printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_set_enabled (const gchar *printer_name,
+ gboolean enabled)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetEnabled",
+ g_variant_new ("(sb)", printer_name, enabled),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of enablement of printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_delete (const gchar *printer_name)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterDelete",
+ g_variant_new ("(s)", printer_name),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: removing of printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_set_default (const gchar *printer_name)
+{
+ const char *cups_server;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name)
+ return TRUE;
+
+ cups_server = cupsServer ();
+ if (g_ascii_strncasecmp (cups_server, "localhost", 9) == 0 ||
+ g_ascii_strncasecmp (cups_server, "127.0.0.1", 9) == 0 ||
+ g_ascii_strncasecmp (cups_server, "::1", 3) == 0 ||
+ cups_server[0] == '/')
+ {
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+
+ /* Clean .cups/lpoptions before setting
+ * default printer on local CUPS server.
+ */
+ set_local_default_printer (NULL);
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return FALSE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetDefault",
+ g_variant_new ("(s)", printer_name),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting default printer to %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else
+ /* Store default printer to .cups/lpoptions
+ * if we are connected to a remote CUPS server.
+ */
+ {
+ set_local_default_printer (printer_name);
+ return TRUE;
+ }
+}
+
+gboolean
+printer_set_shared (const gchar *printer_name,
+ gboolean shared)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetShared",
+ g_variant_new ("(sb)", printer_name, shared),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of sharing of printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_set_job_sheets (const gchar *printer_name,
+ const gchar *start_sheet,
+ const gchar *end_sheet)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name || !start_sheet || !end_sheet)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetJobSheets",
+ g_variant_new ("(sss)", printer_name, start_sheet, end_sheet),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of job sheets for printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_set_policy (const gchar *printer_name,
+ const gchar *policy,
+ gboolean error_policy)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name || !policy)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ if (error_policy)
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetErrorPolicy",
+ g_variant_new ("(ss)", printer_name, policy),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ else
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetOpPolicy",
+ g_variant_new ("(ss)", printer_name, policy),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of a policy for printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_set_users (const gchar *printer_name,
+ gchar **users,
+ gboolean allowed)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ GVariantBuilder array_builder;
+ gint i;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!printer_name || !users)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
+ for (i = 0; users[i]; i++)
+ g_variant_builder_add (&array_builder, "s", users[i]);
+
+ if (allowed)
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetUsersAllowed",
+ g_variant_new ("(sas)", printer_name, &array_builder),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ else
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterSetUsersDenied",
+ g_variant_new ("(sas)", printer_name, &array_builder),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: setting of access list for printer %s failed: %s", printer_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+class_add_printer (const gchar *class_name,
+ const gchar *printer_name)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) output = NULL;
+ const gchar *ret_error;
+ g_autoptr(GError) error = NULL;
+
+ if (!class_name || !printer_name)
+ return TRUE;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ return TRUE;
+ }
+
+ output = g_dbus_connection_call_sync (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "ClassAddPrinter",
+ g_variant_new ("(ss)", class_name, printer_name),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (output == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: adding of printer %s to class %s failed: %s", printer_name, class_name, ret_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+printer_is_local (cups_ptype_t printer_type,
+ const gchar *device_uri)
+{
+ gboolean result = TRUE;
+ char scheme[HTTP_MAX_URI];
+ char username[HTTP_MAX_URI];
+ char hostname[HTTP_MAX_URI];
+ char resource[HTTP_MAX_URI];
+ int port;
+
+ if (printer_type &
+ (CUPS_PRINTER_DISCOVERED |
+ CUPS_PRINTER_REMOTE |
+ CUPS_PRINTER_IMPLICIT))
+ result = FALSE;
+
+ if (device_uri == NULL || !result)
+ return result;
+
+ httpSeparateURI (HTTP_URI_CODING_ALL, device_uri,
+ scheme, sizeof (scheme),
+ username, sizeof (username),
+ hostname, sizeof (hostname),
+ &port,
+ resource, sizeof (resource));
+
+ if (g_str_equal (scheme, "ipp") ||
+ g_str_equal (scheme, "smb") ||
+ g_str_equal (scheme, "socket") ||
+ g_str_equal (scheme, "lpd"))
+ result = FALSE;
+
+ return result;
+}
+
+gchar*
+printer_get_hostname (cups_ptype_t printer_type,
+ const gchar *device_uri,
+ const gchar *printer_uri)
+{
+ gboolean local = TRUE;
+ gchar *result = NULL;
+ char scheme[HTTP_MAX_URI];
+ char username[HTTP_MAX_URI];
+ char hostname[HTTP_MAX_URI];
+ char resource[HTTP_MAX_URI];
+ int port;
+
+ if (device_uri == NULL)
+ return result;
+
+ if (printer_type & (CUPS_PRINTER_DISCOVERED |
+ CUPS_PRINTER_REMOTE |
+ CUPS_PRINTER_IMPLICIT))
+ {
+ if (printer_uri)
+ {
+ httpSeparateURI (HTTP_URI_CODING_ALL, printer_uri,
+ scheme, sizeof (scheme),
+ username, sizeof (username),
+ hostname, sizeof (hostname),
+ &port,
+ resource, sizeof (resource));
+
+ if (hostname[0] != '\0')
+ result = g_strdup (hostname);
+ }
+
+ local = FALSE;
+ }
+
+ if (result == NULL && device_uri)
+ {
+ httpSeparateURI (HTTP_URI_CODING_ALL, device_uri,
+ scheme, sizeof (scheme),
+ username, sizeof (username),
+ hostname, sizeof (hostname),
+ &port,
+ resource, sizeof (resource));
+
+ if (g_str_equal (scheme, "ipp") ||
+ g_str_equal (scheme, "smb") ||
+ g_str_equal (scheme, "socket") ||
+ g_str_equal (scheme, "lpd"))
+ {
+ if (hostname[0] != '\0')
+ result = g_strdup (hostname);
+
+ local = FALSE;
+ }
+ }
+
+ if (local)
+ result = g_strdup ("localhost");
+
+ return result;
+}
+
+/* Returns default page size for current locale */
+const gchar *
+get_page_size_from_locale (void)
+{
+ if (g_str_equal (gtk_paper_size_get_default (), GTK_PAPER_NAME_LETTER))
+ return "Letter";
+ else
+ return "A4";
+}
+
+typedef struct
+{
+ gchar *printer_name;
+ gchar **attributes_names;
+ GHashTable *result;
+ GIACallback callback;
+ gpointer user_data;
+ GMainContext *context;
+} GIAData;
+
+static gboolean
+get_ipp_attributes_idle_cb (gpointer user_data)
+{
+ GIAData *data = (GIAData *) user_data;
+
+ data->callback (data->result, data->user_data);
+
+ return FALSE;
+}
+
+static void
+get_ipp_attributes_data_free (gpointer user_data)
+{
+ GIAData *data = (GIAData *) user_data;
+
+ if (data->context)
+ g_main_context_unref (data->context);
+ g_free (data->printer_name);
+ if (data->attributes_names)
+ g_strfreev (data->attributes_names);
+ g_free (data);
+}
+
+static void
+get_ipp_attributes_cb (gpointer user_data)
+{
+ GIAData *data = (GIAData *) user_data;
+ GSource *idle_source;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source,
+ get_ipp_attributes_idle_cb,
+ data,
+ get_ipp_attributes_data_free);
+ g_source_attach (idle_source, data->context);
+ g_source_unref (idle_source);
+}
+
+static void
+ipp_attribute_free2 (gpointer attr)
+{
+ IPPAttribute *attribute = (IPPAttribute *) attr;
+ ipp_attribute_free (attribute);
+}
+
+static gpointer
+get_ipp_attributes_func (gpointer user_data)
+{
+ ipp_attribute_t *attr = NULL;
+ GIAData *data = (GIAData *) user_data;
+ ipp_t *request;
+ ipp_t *response = NULL;
+ g_autofree gchar *printer_uri = NULL;
+ char **requested_attrs = NULL;
+ gint i, j, length = 0;
+
+ printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", data->printer_name);
+
+ if (data->attributes_names)
+ {
+ length = g_strv_length (data->attributes_names);
+
+ requested_attrs = g_new0 (char *, length);
+ for (i = 0; data->attributes_names[i]; i++)
+ requested_attrs[i] = g_strdup (data->attributes_names[i]);
+
+ request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
+ ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, printer_uri);
+ ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", length, NULL, (const char **) requested_attrs);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+ }
+
+ if (response)
+ {
+ if (ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ for (j = 0; j < length; j++)
+ {
+ attr = ippFindAttribute (response, requested_attrs[j], IPP_TAG_ZERO);
+ if (attr && ippGetCount (attr) > 0 && ippGetValueTag (attr) != IPP_TAG_NOVALUE)
+ {
+ IPPAttribute *attribute;
+
+ attribute = g_new0 (IPPAttribute, 1);
+ attribute->attribute_name = g_strdup (requested_attrs[j]);
+ attribute->attribute_values = g_new0 (IPPAttributeValue, ippGetCount (attr));
+ attribute->num_of_values = ippGetCount (attr);
+
+ if (ippGetValueTag (attr) == IPP_TAG_INTEGER ||
+ ippGetValueTag (attr) == IPP_TAG_ENUM)
+ {
+ attribute->attribute_type = IPP_ATTRIBUTE_TYPE_INTEGER;
+
+ for (i = 0; i < ippGetCount (attr); i++)
+ attribute->attribute_values[i].integer_value = ippGetInteger (attr, i);
+ }
+ else if (ippGetValueTag (attr) == IPP_TAG_NAME ||
+ ippGetValueTag (attr) == IPP_TAG_STRING ||
+ ippGetValueTag (attr) == IPP_TAG_TEXT ||
+ ippGetValueTag (attr) == IPP_TAG_URI ||
+ ippGetValueTag (attr) == IPP_TAG_KEYWORD ||
+ ippGetValueTag (attr) == IPP_TAG_URISCHEME)
+ {
+ attribute->attribute_type = IPP_ATTRIBUTE_TYPE_STRING;
+
+ for (i = 0; i < ippGetCount (attr); i++)
+ attribute->attribute_values[i].string_value = g_strdup (ippGetString (attr, i, NULL));
+ }
+ else if (ippGetValueTag (attr) == IPP_TAG_RANGE)
+ {
+ attribute->attribute_type = IPP_ATTRIBUTE_TYPE_RANGE;
+
+ for (i = 0; i < ippGetCount (attr); i++)
+ {
+ attribute->attribute_values[i].lower_range =
+ ippGetRange (attr, i, &(attribute->attribute_values[i].upper_range));
+ }
+ }
+ else if (ippGetValueTag (attr) == IPP_TAG_BOOLEAN)
+ {
+ attribute->attribute_type = IPP_ATTRIBUTE_TYPE_BOOLEAN;
+
+ for (i = 0; i < ippGetCount (attr); i++)
+ attribute->attribute_values[i].boolean_value = ippGetBoolean (attr, i);
+ }
+
+ if (!data->result)
+ data->result = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, ipp_attribute_free2);
+
+ g_hash_table_insert (data->result, g_strdup (requested_attrs[j]), attribute);
+ }
+ }
+ }
+
+ ippDelete (response);
+ }
+
+
+ for (i = 0; i < length; i++)
+ g_free (requested_attrs[i]);
+ g_free (requested_attrs);
+
+ get_ipp_attributes_cb (data);
+
+ return NULL;
+}
+
+void
+get_ipp_attributes_async (const gchar *printer_name,
+ gchar **attributes_names,
+ GIACallback callback,
+ gpointer user_data)
+{
+ GIAData *data;
+ GThread *thread;
+ g_autoptr(GError) error = NULL;
+
+ data = g_new0 (GIAData, 1);
+ data->printer_name = g_strdup (printer_name);
+ data->attributes_names = g_strdupv (attributes_names);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->context = g_main_context_ref_thread_default ();
+
+ thread = g_thread_try_new ("get-ipp-attributes",
+ get_ipp_attributes_func,
+ data,
+ &error);
+
+ if (!thread)
+ {
+ g_warning ("%s", error->message);
+ callback (NULL, user_data);
+
+ get_ipp_attributes_data_free (data);
+ }
+ else
+ {
+ g_thread_unref (thread);
+ }
+}
+
+IPPAttribute *
+ipp_attribute_copy (IPPAttribute *attr)
+{
+ IPPAttribute *result = NULL;
+ gint i;
+
+ if (attr)
+ {
+ result = g_new0 (IPPAttribute, 1);
+
+ *result = *attr;
+ result->attribute_name = g_strdup (attr->attribute_name);
+ result->attribute_values = g_new0 (IPPAttributeValue, attr->num_of_values);
+ for (i = 0; i < attr->num_of_values; i++)
+ {
+ result->attribute_values[i] = attr->attribute_values[i];
+ if (attr->attribute_values[i].string_value)
+ result->attribute_values[i].string_value = g_strdup (attr->attribute_values[i].string_value);
+ }
+ }
+
+ return result;
+}
+
+void
+ipp_attribute_free (IPPAttribute *attr)
+{
+ gint i;
+
+ if (attr)
+ {
+ for (i = 0; i < attr->num_of_values; i++)
+ g_free (attr->attribute_values[i].string_value);
+
+ g_free (attr->attribute_values);
+ g_free (attr->attribute_name);
+ g_free (attr);
+ }
+}
+
+
+
+typedef struct
+{
+ gchar *printer_name;
+ gchar *ppd_copy;
+ GCancellable *cancellable;
+ PSPCallback callback;
+ gpointer user_data;
+} PSPData;
+
+static void
+printer_set_ppd_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ gboolean result = FALSE;
+ PSPData *data = (PSPData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ const gchar *ret_error;
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ g_warning ("cups-pk-helper: setting of driver for printer %s failed: %s", data->printer_name, ret_error);
+ else
+ result = TRUE;
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ /* Don't call callback if cancelled */
+ if (!data->cancellable ||
+ !g_cancellable_is_cancelled (data->cancellable))
+ data->callback (data->printer_name,
+ result,
+ data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+
+ if (data->ppd_copy)
+ {
+ g_unlink (data->ppd_copy);
+ g_free (data->ppd_copy);
+ }
+
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+/*
+ * Set ppd for given printer.
+ * Don't use this for classes, just for printers.
+ */
+void
+printer_set_ppd_async (const gchar *printer_name,
+ const gchar *ppd_name,
+ GCancellable *cancellable,
+ PSPCallback callback,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ PSPData *data;
+ g_autoptr(GError) error = NULL;
+
+ data = g_new0 (PSPData, 1);
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->printer_name = g_strdup (printer_name);
+
+ if (printer_name == NULL ||
+ printer_name[0] == '\0')
+ {
+ goto out;
+ }
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ goto out;
+ }
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterAdd",
+ g_variant_new ("(sssss)",
+ printer_name,
+ "",
+ ppd_name,
+ "",
+ ""),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ data->cancellable,
+ printer_set_ppd_async_dbus_cb,
+ data);
+
+ return;
+
+out:
+ callback (printer_name, FALSE, user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+static void
+printer_set_ppd_file_async_scb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ gboolean success;
+ PSPData *data = (PSPData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ success = g_file_copy_finish (G_FILE (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (!success)
+ {
+ g_warning ("%s", error->message);
+ goto out;
+ }
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ goto out;
+ }
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "PrinterAddWithPpdFile",
+ g_variant_new ("(sssss)",
+ data->printer_name,
+ "",
+ data->ppd_copy,
+ "",
+ ""),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ data->cancellable,
+ printer_set_ppd_async_dbus_cb,
+ data);
+
+ return;
+
+out:
+ data->callback (data->printer_name, FALSE, data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->printer_name);
+ g_free (data->ppd_copy);
+ g_free (data);
+}
+
+/*
+ * Set ppd for given printer.
+ * Don't use this for classes, just for printers.
+ */
+void
+printer_set_ppd_file_async (const gchar *printer_name,
+ const gchar *ppd_filename,
+ GCancellable *cancellable,
+ PSPCallback callback,
+ gpointer user_data)
+{
+ GFileIOStream *stream;
+ PSPData *data;
+ GFile *source_ppd_file;
+ GFile *destination_ppd_file;
+
+ data = g_new0 (PSPData, 1);
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->printer_name = g_strdup (printer_name);
+
+ if (printer_name == NULL ||
+ printer_name[0] == '\0')
+ {
+ goto out;
+ }
+
+ /*
+ * We need to copy the PPD to temp directory at first.
+ * This is needed because of SELinux.
+ */
+ source_ppd_file = g_file_new_for_path (ppd_filename);
+ destination_ppd_file = g_file_new_tmp ("g-c-c-XXXXXX.ppd", &stream, NULL);
+ g_object_unref (stream);
+ data->ppd_copy = g_strdup (g_file_get_path (destination_ppd_file));
+
+ g_file_copy_async (source_ppd_file,
+ destination_ppd_file,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ NULL,
+ NULL,
+ printer_set_ppd_file_async_scb,
+ data);
+
+ g_object_unref (destination_ppd_file);
+
+ return;
+
+out:
+ callback (printer_name, FALSE, user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+
+
+typedef void (*GPACallback) (gchar **attribute_values,
+ gpointer user_data);
+
+typedef struct
+{
+ gchar *attribute_name;
+ gchar **ppds_names;
+ gchar **result;
+ GPACallback callback;
+ gpointer user_data;
+ GMainContext *context;
+} GPAData;
+
+static gboolean
+get_ppds_attribute_idle_cb (gpointer user_data)
+{
+ GPAData *data = (GPAData *) user_data;
+
+ data->callback (data->result, data->user_data);
+
+ return FALSE;
+}
+
+static void
+get_ppds_attribute_data_free (gpointer user_data)
+{
+ GPAData *data = (GPAData *) user_data;
+
+ if (data->context)
+ g_main_context_unref (data->context);
+ g_free (data->attribute_name);
+ g_strfreev (data->ppds_names);
+ g_free (data);
+}
+
+static void
+get_ppds_attribute_cb (gpointer user_data)
+{
+ GPAData *data = (GPAData *) user_data;
+ GSource *idle_source;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source,
+ get_ppds_attribute_idle_cb,
+ data,
+ get_ppds_attribute_data_free);
+ g_source_attach (idle_source, data->context);
+ g_source_unref (idle_source);
+}
+
+static gpointer
+get_ppds_attribute_func (gpointer user_data)
+{
+ ppd_file_t *ppd_file;
+ ppd_attr_t *ppd_attr;
+ GPAData *data = (GPAData *) user_data;
+ gint i;
+
+ data->result = g_new0 (gchar *, g_strv_length (data->ppds_names) + 1);
+ for (i = 0; data->ppds_names[i]; i++)
+ {
+ g_autofree gchar *ppd_filename = g_strdup (cupsGetServerPPD (CUPS_HTTP_DEFAULT, data->ppds_names[i]));
+ if (ppd_filename)
+ {
+ ppd_file = ppdOpenFile (ppd_filename);
+ if (ppd_file)
+ {
+ ppd_attr = ppdFindAttr (ppd_file, data->attribute_name, NULL);
+ if (ppd_attr != NULL)
+ data->result[i] = g_strdup (ppd_attr->value);
+
+ ppdClose (ppd_file);
+ }
+
+ g_unlink (ppd_filename);
+ }
+ }
+
+ get_ppds_attribute_cb (data);
+
+ return NULL;
+}
+
+/*
+ * Get values of requested PPD attribute for given PPDs.
+ */
+static void
+get_ppds_attribute_async (gchar **ppds_names,
+ gchar *attribute_name,
+ GPACallback callback,
+ gpointer user_data)
+{
+ GPAData *data;
+ GThread *thread;
+ g_autoptr(GError) error = NULL;
+
+ if (!ppds_names || !attribute_name)
+ {
+ callback (NULL, user_data);
+ return;
+ }
+
+ data = g_new0 (GPAData, 1);
+ data->ppds_names = g_strdupv (ppds_names);
+ data->attribute_name = g_strdup (attribute_name);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->context = g_main_context_ref_thread_default ();
+
+ thread = g_thread_try_new ("get-ppds-attribute",
+ get_ppds_attribute_func,
+ data,
+ &error);
+
+ if (!thread)
+ {
+ g_warning ("%s", error->message);
+ callback (NULL, user_data);
+
+ get_ppds_attribute_data_free (data);
+ }
+ else
+ {
+ g_thread_unref (thread);
+ }
+}
+
+
+
+typedef void (*GDACallback) (gchar *device_id,
+ gchar *device_make_and_model,
+ gchar *device_uri,
+ gpointer user_data);
+
+typedef struct
+{
+ gchar *printer_name;
+ gchar *device_uri;
+ GCancellable *cancellable;
+ GList *backend_list;
+ GDACallback callback;
+ gpointer user_data;
+} GDAData;
+
+typedef struct
+{
+ gchar *printer_name;
+ gint count;
+ PPDName **result;
+ GCancellable *cancellable;
+ GPNCallback callback;
+ gpointer user_data;
+} GPNData;
+
+static void
+get_ppd_names_async_cb (gchar **attribute_values,
+ gpointer user_data)
+{
+ GPNData *data = (GPNData *) user_data;
+ gint i;
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ g_strfreev (attribute_values);
+
+ for (i = 0; data->result[i]; i++)
+ {
+ g_free (data->result[i]->ppd_name);
+ g_free (data->result[i]);
+ }
+
+ g_free (data->result);
+ data->result = NULL;
+
+ goto out;
+ }
+
+ if (attribute_values)
+ {
+ for (i = 0; attribute_values[i]; i++)
+ data->result[i]->ppd_display_name = attribute_values[i];
+
+ g_free (attribute_values);
+ }
+
+out:
+ data->callback (data->result,
+ data->printer_name,
+ g_cancellable_is_cancelled (data->cancellable),
+ data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+static void
+get_ppd_names_async_dbus_scb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ PPDName *ppd_item;
+ PPDName **result = NULL;
+ GPNData *data = (GPNData *) user_data;
+ g_autoptr(GError) error = NULL;
+ GList *driver_list = NULL;
+ GList *iter;
+ gint i, j, n = 0;
+ static const char * const match_levels[] = {
+ "exact-cmd",
+ "exact",
+ "close",
+ "generic",
+ "none"};
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ g_autoptr(GVariant) array = NULL;
+
+ g_variant_get (output, "(@a(ss))",
+ &array);
+
+ for (j = 0; j < G_N_ELEMENTS (match_levels) && n < data->count; j++)
+ {
+ g_autoptr(GVariantIter) iter = NULL;
+ const gchar *driver, *match;
+
+ g_variant_get (array,
+ "a(ss)",
+ &iter);
+
+ while (g_variant_iter_next (iter, "(&s&s)", &driver, &match))
+ {
+ if (g_str_equal (match, match_levels[j]) && n < data->count)
+ {
+ ppd_item = g_new0 (PPDName, 1);
+ ppd_item->ppd_name = g_strdup (driver);
+
+ if (g_strcmp0 (match, "exact-cmd") == 0)
+ ppd_item->ppd_match_level = PPD_EXACT_CMD_MATCH;
+ else if (g_strcmp0 (match, "exact") == 0)
+ ppd_item->ppd_match_level = PPD_EXACT_MATCH;
+ else if (g_strcmp0 (match, "close") == 0)
+ ppd_item->ppd_match_level = PPD_CLOSE_MATCH;
+ else if (g_strcmp0 (match, "generic") == 0)
+ ppd_item->ppd_match_level = PPD_GENERIC_MATCH;
+ else if (g_strcmp0 (match, "none") == 0)
+ ppd_item->ppd_match_level = PPD_NO_MATCH;
+
+ driver_list = g_list_append (driver_list, ppd_item);
+
+ n++;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (n > 0)
+ {
+ result = g_new0 (PPDName *, n + 1);
+ i = 0;
+ for (iter = driver_list; iter; iter = iter->next)
+ {
+ result[i] = iter->data;
+ i++;
+ }
+ }
+
+ if (result)
+ {
+ gchar **ppds_names;
+
+ data->result = result;
+
+ ppds_names = g_new0 (gchar *, n + 1);
+ for (i = 0; i < n; i++)
+ ppds_names[i] = g_strdup (result[i]->ppd_name);
+
+ get_ppds_attribute_async (ppds_names,
+ "NickName",
+ get_ppd_names_async_cb,
+ data);
+
+ g_strfreev (ppds_names);
+ }
+ else
+ {
+ data->callback (NULL,
+ data->printer_name,
+ g_cancellable_is_cancelled (data->cancellable),
+ data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->printer_name);
+ g_free (data);
+ }
+}
+
+static void
+get_device_attributes_cb (gchar *device_id,
+ gchar *device_make_and_model,
+ gchar *device_uri,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ g_autoptr(GError) error = NULL;
+ GPNData *data = (GPNData *) user_data;
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ goto out;
+
+ if (!device_id || !device_make_and_model || !device_uri)
+ goto out;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ goto out;
+ }
+
+ g_dbus_connection_call (bus,
+ SCP_BUS,
+ SCP_PATH,
+ SCP_IFACE,
+ "GetBestDrivers",
+ g_variant_new ("(sss)",
+ device_id,
+ device_make_and_model,
+ device_uri),
+ G_VARIANT_TYPE ("(a(ss))"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_LONG,
+ data->cancellable,
+ get_ppd_names_async_dbus_scb,
+ data);
+
+ return;
+
+out:
+ data->callback (NULL,
+ data->printer_name,
+ g_cancellable_is_cancelled (data->cancellable),
+ data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+/*
+ * Special item for the list of backends. It represents
+ * backends not present in the list itself.
+ */
+#define OTHER_BACKENDS "other-backends"
+
+/*
+ * List of CUPS backends sorted according to their speed,
+ * the fastest is the first one. The last item represents
+ * backends not present in the list.
+ */
+const gchar *cups_backends[] = {
+ "usb",
+ "socket",
+ "serial",
+ "parallel",
+ "lpd",
+ "ipp",
+ "hp",
+ "dnssd",
+ "snmp",
+ "bluetooth",
+ "beh",
+ "ncp",
+ "hpfax",
+ OTHER_BACKENDS
+};
+
+static GList *
+create_backends_list ()
+{
+ GList *list = NULL;
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cups_backends); i++)
+ list = g_list_prepend (list, g_strdup (cups_backends[i]));
+ list = g_list_reverse (list);
+
+ return list;
+}
+
+static GVariantBuilder *
+create_other_backends_array ()
+{
+ GVariantBuilder *builder;
+ gint i;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+ for (i = 0; i < G_N_ELEMENTS (cups_backends) - 1; i++)
+ g_variant_builder_add (builder, "s", cups_backends[i]);
+
+ return builder;
+}
+
+static void
+get_device_attributes_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+
+{
+ g_autoptr(GVariant) output = NULL;
+ GDAData *data = (GDAData *) user_data;
+ g_autoptr(GError) error = NULL;
+ GList *tmp;
+ gchar *device_id = NULL;
+ gchar *device_make_and_model = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ const gchar *ret_error;
+ g_autoptr(GVariant) devices_variant = NULL;
+ gint index = -1;
+
+ g_variant_get (output, "(&s@a{ss})",
+ &ret_error,
+ &devices_variant);
+
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: getting of attributes for printer %s failed: %s", data->printer_name, ret_error);
+ }
+
+ if (data->device_uri)
+ {
+ g_autoptr(GVariantIter) iter = NULL;
+ const gchar *key, *value;
+ g_autofree gchar *suffix = NULL;
+
+ g_variant_get (devices_variant,
+ "a{ss}",
+ &iter);
+
+ while (g_variant_iter_next (iter, "{&s&s}", &key, &value))
+ {
+ if (g_str_equal (value, data->device_uri))
+ {
+ gchar *number = g_strrstr (key, ":");
+ if (number != NULL)
+ {
+ gchar *endptr;
+
+ number++;
+ index = g_ascii_strtoll (number, &endptr, 10);
+ if (index == 0 && endptr == (number))
+ index = -1;
+ }
+ }
+ }
+
+ suffix = g_strdup_printf (":%d", index);
+
+ g_variant_get (devices_variant,
+ "a{ss}",
+ &iter);
+
+ while (g_variant_iter_next (iter, "{&s&s}", &key, &value))
+ {
+ if (g_str_has_suffix (key, suffix))
+ {
+ if (g_str_has_prefix (key, "device-id"))
+ {
+ device_id = g_strdup (value);
+ }
+
+ if (g_str_has_prefix (key, "device-make-and-model"))
+ {
+ device_make_and_model = g_strdup (value);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (!device_id || !device_make_and_model)
+ {
+ GVariantBuilder *include_scheme_builder = NULL;
+ GVariantBuilder *exclude_scheme_builder = NULL;
+
+ g_free (device_id);
+ g_free (device_make_and_model);
+
+ device_id = NULL;
+ device_make_and_model = NULL;
+
+ if (data->backend_list && !g_cancellable_is_cancelled (data->cancellable))
+ {
+ const gchar *backend_name;
+
+ backend_name = data->backend_list->data;
+
+ if (g_strcmp0 (backend_name, OTHER_BACKENDS) != 0)
+ {
+ include_scheme_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (include_scheme_builder, "s", backend_name);
+ }
+ else
+ {
+ exclude_scheme_builder = create_other_backends_array ();
+ }
+
+ tmp = data->backend_list;
+ data->backend_list = g_list_remove_link (data->backend_list, tmp);
+ g_list_free_full (tmp, g_free);
+
+ g_dbus_connection_call (G_DBUS_CONNECTION (g_object_ref (source_object)),
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "DevicesGet",
+ g_variant_new ("(iiasas)",
+ 0,
+ 0,
+ include_scheme_builder,
+ exclude_scheme_builder),
+ G_VARIANT_TYPE ("(sa{ss})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ data->cancellable,
+ get_device_attributes_async_dbus_cb,
+ user_data);
+
+ if (include_scheme_builder)
+ g_variant_builder_unref (include_scheme_builder);
+
+ if (exclude_scheme_builder)
+ g_variant_builder_unref (exclude_scheme_builder);
+
+ return;
+ }
+ }
+
+ g_object_unref (source_object);
+
+ if (data->backend_list)
+ {
+ g_list_free_full (data->backend_list, g_free);
+ data->backend_list = NULL;
+ }
+
+ data->callback (device_id,
+ device_make_and_model,
+ data->device_uri,
+ data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->device_uri);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+static void
+get_device_attributes_async_scb (GHashTable *result,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ GVariantBuilder include_scheme_builder;
+ IPPAttribute *attr;
+ GDAData *data = (GDAData *) user_data;
+ g_autoptr(GError) error = NULL;
+ GList *tmp;
+
+ if (result)
+ {
+ attr = g_hash_table_lookup (result, "device-uri");
+ if (attr && attr->attribute_type == IPP_ATTRIBUTE_TYPE_STRING &&
+ attr->num_of_values > 0)
+ data->device_uri = g_strdup (attr->attribute_values[0].string_value);
+ g_hash_table_unref (result);
+ }
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ goto out;
+
+ if (!data->device_uri)
+ goto out;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ goto out;
+ }
+
+ data->backend_list = create_backends_list ();
+
+ g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&include_scheme_builder, "s", data->backend_list->data);
+
+ tmp = data->backend_list;
+ data->backend_list = g_list_remove_link (data->backend_list, tmp);
+ g_list_free_full (tmp, g_free);
+
+ g_dbus_connection_call (g_object_ref (bus),
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "DevicesGet",
+ g_variant_new ("(iiasas)",
+ 0,
+ 0,
+ &include_scheme_builder,
+ NULL),
+ G_VARIANT_TYPE ("(sa{ss})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ data->cancellable,
+ get_device_attributes_async_dbus_cb,
+ data);
+
+ return;
+
+out:
+ data->callback (NULL, NULL, NULL, data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data->device_uri);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+/*
+ * Get device-id, device-make-and-model and device-uri for given printer.
+ */
+static void
+get_device_attributes_async (const gchar *printer_name,
+ GCancellable *cancellable,
+ GDACallback callback,
+ gpointer user_data)
+{
+ GDAData *data;
+ gchar **attributes;
+
+ if (!printer_name)
+ {
+ callback (NULL, NULL, NULL, user_data);
+ return;
+ }
+
+ data = g_new0 (GDAData, 1);
+ data->printer_name = g_strdup (printer_name);
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ attributes = g_new0 (gchar *, 2);
+ attributes[0] = g_strdup ("device-uri");
+
+ get_ipp_attributes_async (printer_name,
+ attributes,
+ get_device_attributes_async_scb,
+ data);
+
+ g_strfreev (attributes);
+}
+
+/*
+ * Return "count" best matching driver names for given printer.
+ */
+void
+get_ppd_names_async (gchar *printer_name,
+ gint count,
+ GCancellable *cancellable,
+ GPNCallback callback,
+ gpointer user_data)
+{
+ GPNData *data;
+
+ if (!printer_name)
+ {
+ callback (NULL, NULL, TRUE, user_data);
+ return;
+ }
+
+ data = g_new0 (GPNData, 1);
+ data->printer_name = g_strdup (printer_name);
+ data->count = count;
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ /*
+ * We have to find out device-id for this printer at first.
+ */
+ get_device_attributes_async (printer_name,
+ cancellable,
+ get_device_attributes_cb,
+ data);
+}
+
+typedef struct
+{
+ PPDList *result;
+ GCancellable *cancellable;
+ GAPCallback callback;
+ gpointer user_data;
+ GMainContext *context;
+} GAPData;
+
+static gboolean
+get_all_ppds_idle_cb (gpointer user_data)
+{
+ GAPData *data = (GAPData *) user_data;
+
+ /* Don't call callback if cancelled */
+ if (data->cancellable &&
+ g_cancellable_is_cancelled (data->cancellable))
+ {
+ ppd_list_free (data->result);
+ data->result = NULL;
+ }
+ else
+ {
+ data->callback (data->result, data->user_data);
+ }
+
+ return FALSE;
+}
+
+static void
+get_all_ppds_data_free (gpointer user_data)
+{
+ GAPData *data = (GAPData *) user_data;
+
+ if (data->context)
+ g_main_context_unref (data->context);
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+static void
+get_all_ppds_cb (gpointer user_data)
+{
+ GAPData *data = (GAPData *) user_data;
+ GSource *idle_source;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source,
+ get_all_ppds_idle_cb,
+ data,
+ get_all_ppds_data_free);
+ g_source_attach (idle_source, data->context);
+ g_source_unref (idle_source);
+}
+
+static const struct {
+ const char *normalized_name;
+ const char *display_name;
+} manufacturers_names[] = {
+ { "alps", "Alps" },
+ { "anitech", "Anitech" },
+ { "apple", "Apple" },
+ { "apollo", "Apollo" },
+ { "brother", "Brother" },
+ { "canon", "Canon" },
+ { "citizen", "Citizen" },
+ { "citoh", "Citoh" },
+ { "compaq", "Compaq" },
+ { "dec", "DEC" },
+ { "dell", "Dell" },
+ { "dnp", "DNP" },
+ { "dymo", "Dymo" },
+ { "epson", "Epson" },
+ { "fujifilm", "Fujifilm" },
+ { "fujitsu", "Fujitsu" },
+ { "gelsprinter", "Ricoh" },
+ { "generic", "Generic" },
+ { "genicom", "Genicom" },
+ { "gestetner", "Gestetner" },
+ { "hewlett packard", "Hewlett-Packard" },
+ { "heidelberg", "Heidelberg" },
+ { "hitachi", "Hitachi" },
+ { "hp", "Hewlett-Packard" },
+ { "ibm", "IBM" },
+ { "imagen", "Imagen" },
+ { "imagistics", "Imagistics" },
+ { "infoprint", "InfoPrint" },
+ { "infotec", "Infotec" },
+ { "intellitech", "Intellitech" },
+ { "kodak", "Kodak" },
+ { "konica minolta", "Minolta" },
+ { "kyocera", "Kyocera" },
+ { "kyocera mita", "Kyocera" },
+ { "lanier", "Lanier" },
+ { "lexmark international", "Lexmark" },
+ { "lexmark", "Lexmark" },
+ { "minolta", "Minolta" },
+ { "minolta qms", "Minolta" },
+ { "mitsubishi", "Mitsubishi" },
+ { "nec", "NEC" },
+ { "nrg", "NRG" },
+ { "oce", "Oce" },
+ { "oki", "Oki" },
+ { "oki data corp", "Oki" },
+ { "olivetti", "Olivetti" },
+ { "olympus", "Olympus" },
+ { "panasonic", "Panasonic" },
+ { "pcpi", "PCPI" },
+ { "pentax", "Pentax" },
+ { "qms", "QMS" },
+ { "raven", "Raven" },
+ { "raw", "Raw" },
+ { "ricoh", "Ricoh" },
+ { "samsung", "Samsung" },
+ { "savin", "Savin" },
+ { "seiko", "Seiko" },
+ { "sharp", "Sharp" },
+ { "shinko", "Shinko" },
+ { "sipix", "SiPix" },
+ { "sony", "Sony" },
+ { "star", "Star" },
+ { "tally", "Tally" },
+ { "tektronix", "Tektronix" },
+ { "texas instruments", "Texas Instruments" },
+ { "toshiba", "Toshiba" },
+ { "toshiba tec corp.", "Toshiba" },
+ { "xante", "Xante" },
+ { "xerox", "Xerox" },
+ { "zebra", "Zebra" },
+};
+
+static gpointer
+get_all_ppds_func (gpointer user_data)
+{
+ ipp_attribute_t *attr;
+ GHashTable *ppds_hash = NULL;
+ GHashTable *manufacturers_hash = NULL;
+ GAPData *data = (GAPData *) user_data;
+ PPDName *item;
+ ipp_t *request;
+ ipp_t *response;
+ GList *list;
+ gchar *manufacturer_display_name;
+ gint i, j;
+
+ request = ippNewRequest (CUPS_GET_PPDS);
+ response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+
+ if (response &&
+ ippGetStatusCode (response) <= IPP_OK_CONFLICT)
+ {
+ /*
+ * This hash contains names of manufacturers as keys and
+ * values are GLists of PPD names.
+ */
+ ppds_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ /*
+ * This hash contains all possible names of manufacturers as keys
+ * and values are just first occurrences of their equivalents.
+ * This is for mapping of e.g. "Hewlett Packard" and "HP" to the same name
+ * (the one which comes first).
+ */
+ manufacturers_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ for (i = 0; i < G_N_ELEMENTS (manufacturers_names); i++)
+ {
+ g_hash_table_insert (manufacturers_hash,
+ g_strdup (manufacturers_names[i].normalized_name),
+ g_strdup (manufacturers_names[i].display_name));
+ }
+
+ for (attr = ippFirstAttribute (response); attr != NULL; attr = ippNextAttribute (response))
+ {
+ const gchar *ppd_device_id = NULL;
+ const gchar *ppd_make_and_model = NULL;
+ const gchar *ppd_name = NULL;
+ const gchar *ppd_product = NULL;
+ const gchar *ppd_make = NULL;
+ g_autofree gchar *mdl = NULL;
+ g_autofree gchar *mfg = NULL;
+ g_autofree gchar *mfg_normalized = NULL;
+
+ while (attr != NULL && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute (response);
+
+ if (attr == NULL)
+ break;
+
+ while (attr != NULL && ippGetGroupTag (attr) == IPP_TAG_PRINTER)
+ {
+ if (g_strcmp0 (ippGetName (attr), "ppd-device-id") == 0 &&
+ ippGetValueTag (attr) == IPP_TAG_TEXT)
+ ppd_device_id = ippGetString (attr, 0, NULL);
+ else if (g_strcmp0 (ippGetName (attr), "ppd-make-and-model") == 0 &&
+ ippGetValueTag (attr) == IPP_TAG_TEXT)
+ ppd_make_and_model = ippGetString (attr, 0, NULL);
+ else if (g_strcmp0 (ippGetName (attr), "ppd-name") == 0 &&
+ ippGetValueTag (attr) == IPP_TAG_NAME)
+ ppd_name = ippGetString (attr, 0, NULL);
+ else if (g_strcmp0 (ippGetName (attr), "ppd-product") == 0 &&
+ ippGetValueTag (attr) == IPP_TAG_TEXT)
+ ppd_product = ippGetString (attr, 0, NULL);
+ else if (g_strcmp0 (ippGetName (attr), "ppd-make") == 0 &&
+ ippGetValueTag (attr) == IPP_TAG_TEXT)
+ ppd_make = ippGetString (attr, 0, NULL);
+
+ attr = ippNextAttribute (response);
+ }
+
+ /* Get manufacturer's name */
+ if (ppd_device_id && ppd_device_id[0] != '\0')
+ {
+ mfg = get_tag_value (ppd_device_id, "mfg");
+ if (!mfg)
+ mfg = get_tag_value (ppd_device_id, "manufacturer");
+ mfg_normalized = normalize (mfg);
+ }
+
+ if (!mfg &&
+ ppd_make &&
+ ppd_make[0] != '\0')
+ {
+ mfg = g_strdup (ppd_make);
+ mfg_normalized = normalize (ppd_make);
+ }
+
+ /* Get model */
+ if (ppd_make_and_model &&
+ ppd_make_and_model[0] != '\0')
+ {
+ mdl = g_strdup (ppd_make_and_model);
+ }
+
+ if (!mdl &&
+ ppd_product &&
+ ppd_product[0] != '\0')
+ {
+ mdl = g_strdup (ppd_product);
+ }
+
+ if (!mdl &&
+ ppd_device_id &&
+ ppd_device_id[0] != '\0')
+ {
+ mdl = get_tag_value (ppd_device_id, "mdl");
+ if (!mdl)
+ mdl = get_tag_value (ppd_device_id, "model");
+ }
+
+ if (ppd_name && ppd_name[0] != '\0' &&
+ mdl && mdl[0] != '\0' &&
+ mfg && mfg[0] != '\0')
+ {
+ manufacturer_display_name = g_hash_table_lookup (manufacturers_hash, mfg_normalized);
+ if (!manufacturer_display_name)
+ {
+ g_hash_table_insert (manufacturers_hash, g_strdup (mfg_normalized), g_strdup (mfg));
+ }
+ else
+ {
+ g_free (mfg_normalized);
+ mfg_normalized = normalize (manufacturer_display_name);
+ }
+
+ item = g_new0 (PPDName, 1);
+ item->ppd_name = g_strdup (ppd_name);
+ item->ppd_display_name = g_strdup (mdl);
+ item->ppd_match_level = -1;
+
+ list = g_hash_table_lookup (ppds_hash, mfg_normalized);
+ if (list)
+ {
+ list = g_list_append (list, item);
+ }
+ else
+ {
+ list = g_list_append (list, item);
+ g_hash_table_insert (ppds_hash, g_strdup (mfg_normalized), list);
+ }
+ }
+
+ if (attr == NULL)
+ break;
+ }
+ }
+
+ if (response)
+ ippDelete(response);
+
+ if (ppds_hash &&
+ manufacturers_hash)
+ {
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+ GList *ppd_item;
+ GList *sort_list = NULL;
+ GList *list_iter;
+ gchar *name;
+
+ data->result = g_new0 (PPDList, 1);
+ data->result->num_of_manufacturers = g_hash_table_size (ppds_hash);
+ data->result->manufacturers = g_new0 (PPDManufacturerItem *, data->result->num_of_manufacturers);
+
+ g_hash_table_iter_init (&iter, ppds_hash);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ sort_list = g_list_append (sort_list, g_strdup (key));
+ }
+
+ /* Sort list of manufacturers */
+ sort_list = g_list_sort (sort_list, (GCompareFunc) g_strcmp0);
+
+ /*
+ * Fill resulting list of lists (list of manufacturers where
+ * each item contains list of PPD names)
+ */
+ i = 0;
+ for (list_iter = sort_list; list_iter; list_iter = list_iter->next)
+ {
+ name = (gchar *) list_iter->data;
+ value = g_hash_table_lookup (ppds_hash, name);
+
+ data->result->manufacturers[i] = g_new0 (PPDManufacturerItem, 1);
+ data->result->manufacturers[i]->manufacturer_name = g_strdup (name);
+ data->result->manufacturers[i]->manufacturer_display_name = g_strdup (g_hash_table_lookup (manufacturers_hash, name));
+ data->result->manufacturers[i]->num_of_ppds = g_list_length ((GList *) value);
+ data->result->manufacturers[i]->ppds = g_new0 (PPDName *, data->result->manufacturers[i]->num_of_ppds);
+
+ for (ppd_item = (GList *) value, j = 0; ppd_item; ppd_item = ppd_item->next, j++)
+ {
+ data->result->manufacturers[i]->ppds[j] = ppd_item->data;
+ }
+
+ g_list_free ((GList *) value);
+
+ i++;
+ }
+
+ g_list_free_full (sort_list, g_free);
+ g_hash_table_destroy (ppds_hash);
+ g_hash_table_destroy (manufacturers_hash);
+ }
+
+ get_all_ppds_cb (data);
+
+ return NULL;
+}
+
+/*
+ * Get names of all installed PPDs sorted by manufacturers names.
+ */
+void
+get_all_ppds_async (GCancellable *cancellable,
+ GAPCallback callback,
+ gpointer user_data)
+{
+ GAPData *data;
+ GThread *thread;
+ g_autoptr(GError) error = NULL;
+
+ data = g_new0 (GAPData, 1);
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->context = g_main_context_ref_thread_default ();
+
+ thread = g_thread_try_new ("get-all-ppds",
+ get_all_ppds_func,
+ data,
+ &error);
+
+ if (!thread)
+ {
+ g_warning ("%s", error->message);
+ callback (NULL, user_data);
+
+ get_all_ppds_data_free (data);
+ }
+ else
+ {
+ g_thread_unref (thread);
+ }
+}
+
+PPDList *
+ppd_list_copy (PPDList *list)
+{
+ PPDList *result = NULL;
+ gint i, j;
+
+ if (list)
+ {
+ result = g_new0 (PPDList, 1);
+ result->num_of_manufacturers = list->num_of_manufacturers;
+ result->manufacturers = g_new0 (PPDManufacturerItem *, list->num_of_manufacturers);
+
+ for (i = 0; i < result->num_of_manufacturers; i++)
+ {
+ result->manufacturers[i] = g_new0 (PPDManufacturerItem, 1);
+ result->manufacturers[i]->num_of_ppds = list->manufacturers[i]->num_of_ppds;
+ result->manufacturers[i]->ppds = g_new0 (PPDName *, result->manufacturers[i]->num_of_ppds);
+
+ result->manufacturers[i]->manufacturer_display_name =
+ g_strdup (list->manufacturers[i]->manufacturer_display_name);
+
+ result->manufacturers[i]->manufacturer_name =
+ g_strdup (list->manufacturers[i]->manufacturer_name);
+
+ for (j = 0; j < result->manufacturers[i]->num_of_ppds; j++)
+ {
+ result->manufacturers[i]->ppds[j] = g_new0 (PPDName, 1);
+
+ result->manufacturers[i]->ppds[j]->ppd_display_name =
+ g_strdup (list->manufacturers[i]->ppds[j]->ppd_display_name);
+
+ result->manufacturers[i]->ppds[j]->ppd_name =
+ g_strdup (list->manufacturers[i]->ppds[j]->ppd_name);
+
+ result->manufacturers[i]->ppds[j]->ppd_match_level =
+ list->manufacturers[i]->ppds[j]->ppd_match_level;
+ }
+ }
+ }
+
+ return result;
+}
+
+void
+ppd_list_free (PPDList *list)
+{
+ gint i, j;
+
+ if (list)
+ {
+ for (i = 0; i < list->num_of_manufacturers; i++)
+ {
+ for (j = 0; j < list->manufacturers[i]->num_of_ppds; j++)
+ {
+ g_free (list->manufacturers[i]->ppds[j]->ppd_name);
+ g_free (list->manufacturers[i]->ppds[j]->ppd_display_name);
+ g_free (list->manufacturers[i]->ppds[j]);
+ }
+
+ g_free (list->manufacturers[i]->manufacturer_name);
+ g_free (list->manufacturers[i]->manufacturer_display_name);
+ g_free (list->manufacturers[i]->ppds);
+ g_free (list->manufacturers[i]);
+ }
+
+ g_free (list->manufacturers);
+ g_free (list);
+ }
+}
+
+gchar *
+get_standard_manufacturers_name (const gchar *name)
+{
+ g_autofree gchar *normalized_name = NULL;
+ gint i;
+
+ if (name == NULL)
+ return NULL;
+
+ normalized_name = normalize (name);
+
+ for (i = 0; i < G_N_ELEMENTS (manufacturers_names); i++)
+ {
+ if (g_strcmp0 (manufacturers_names[i].normalized_name, normalized_name) == 0)
+ {
+ return g_strdup (manufacturers_names[i].display_name);
+ }
+ }
+
+ return NULL;
+}
+
+typedef struct
+{
+ gchar *printer_name;
+ gchar *host_name;
+ gint port;
+ gchar *result;
+ PGPCallback callback;
+ gpointer user_data;
+ GMainContext *context;
+} PGPData;
+
+static gboolean
+printer_get_ppd_idle_cb (gpointer user_data)
+{
+ PGPData *data = (PGPData *) user_data;
+
+ data->callback (data->result, data->user_data);
+
+ return FALSE;
+}
+
+static void
+printer_get_ppd_data_free (gpointer user_data)
+{
+ PGPData *data = (PGPData *) user_data;
+
+ if (data->context)
+ g_main_context_unref (data->context);
+ g_free (data->result);
+ g_free (data->printer_name);
+ g_free (data->host_name);
+ g_free (data);
+}
+
+static void
+printer_get_ppd_cb (gpointer user_data)
+{
+ PGPData *data = (PGPData *) user_data;
+ GSource *idle_source;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source,
+ printer_get_ppd_idle_cb,
+ data,
+ printer_get_ppd_data_free);
+ g_source_attach (idle_source, data->context);
+ g_source_unref (idle_source);
+}
+
+static gpointer
+printer_get_ppd_func (gpointer user_data)
+{
+ PGPData *data = (PGPData *) user_data;
+
+ if (data->host_name)
+ {
+ http_t *http;
+
+#ifdef HAVE_CUPS_HTTPCONNECT2
+ http = httpConnect2 (data->host_name, data->port, NULL, AF_UNSPEC,
+ HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL);
+#else
+ http = httpConnect (data->host_name, data->port);
+#endif
+ if (http)
+ {
+ data->result = g_strdup (cupsGetPPD2 (http, data->printer_name));
+ httpClose (http);
+ }
+ }
+ else
+ {
+ data->result = g_strdup (cupsGetPPD (data->printer_name));
+ }
+
+ printer_get_ppd_cb (data);
+
+ return NULL;
+}
+
+void
+printer_get_ppd_async (const gchar *printer_name,
+ const gchar *host_name,
+ gint port,
+ PGPCallback callback,
+ gpointer user_data)
+{
+ PGPData *data;
+ GThread *thread;
+ g_autoptr(GError) error = NULL;
+
+ data = g_new0 (PGPData, 1);
+ data->printer_name = g_strdup (printer_name);
+ data->host_name = g_strdup (host_name);
+ data->port = port;
+ data->callback = callback;
+ data->user_data = user_data;
+ data->context = g_main_context_ref_thread_default ();
+
+ thread = g_thread_try_new ("printer-get-ppd",
+ printer_get_ppd_func,
+ data,
+ &error);
+
+ if (!thread)
+ {
+ g_warning ("%s", error->message);
+ callback (NULL, user_data);
+
+ printer_get_ppd_data_free (data);
+ }
+ else
+ {
+ g_thread_unref (thread);
+ }
+}
+
+typedef struct
+{
+ gchar *printer_name;
+ cups_dest_t *result;
+ GNDCallback callback;
+ gpointer user_data;
+ GMainContext *context;
+} GNDData;
+
+static gboolean
+get_named_dest_idle_cb (gpointer user_data)
+{
+ GNDData *data = (GNDData *) user_data;
+
+ data->callback (data->result, data->user_data);
+
+ return FALSE;
+}
+
+static void
+get_named_dest_data_free (gpointer user_data)
+{
+ GNDData *data = (GNDData *) user_data;
+
+ if (data->context)
+ g_main_context_unref (data->context);
+ g_free (data->printer_name);
+ g_free (data);
+}
+
+static void
+get_named_dest_cb (gpointer user_data)
+{
+ GNDData *data = (GNDData *) user_data;
+ GSource *idle_source;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source,
+ get_named_dest_idle_cb,
+ data,
+ get_named_dest_data_free);
+ g_source_attach (idle_source, data->context);
+ g_source_unref (idle_source);
+}
+
+static gpointer
+get_named_dest_func (gpointer user_data)
+{
+ GNDData *data = (GNDData *) user_data;
+
+ data->result = cupsGetNamedDest (CUPS_HTTP_DEFAULT, data->printer_name, NULL);
+
+ get_named_dest_cb (data);
+
+ return NULL;
+}
+
+void
+get_named_dest_async (const gchar *printer_name,
+ GNDCallback callback,
+ gpointer user_data)
+{
+ GNDData *data;
+ GThread *thread;
+ g_autoptr(GError) error = NULL;
+
+ data = g_new0 (GNDData, 1);
+ data->printer_name = g_strdup (printer_name);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->context = g_main_context_ref_thread_default ();
+
+ thread = g_thread_try_new ("get-named-dest",
+ get_named_dest_func,
+ data,
+ &error);
+
+ if (!thread)
+ {
+ g_warning ("%s", error->message);
+ callback (NULL, user_data);
+
+ get_named_dest_data_free (data);
+ }
+ else
+ {
+ g_thread_unref (thread);
+ }
+}
+
+typedef struct
+{
+ GCancellable *cancellable;
+ PAOCallback callback;
+ gpointer user_data;
+} PAOData;
+
+static void
+printer_add_option_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) output = NULL;
+ gboolean success = FALSE;
+ PAOData *data = (PAOData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+ g_object_unref (source_object);
+
+ if (output)
+ {
+ const gchar *ret_error;
+
+ g_variant_get (output, "(&s)", &ret_error);
+ if (ret_error[0] != '\0')
+ g_warning ("cups-pk-helper: setting of an option failed: %s", ret_error);
+ else
+ success = TRUE;
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (!g_cancellable_is_cancelled (data->cancellable))
+ data->callback (success, data->user_data);
+
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+void
+printer_add_option_async (const gchar *printer_name,
+ const gchar *option_name,
+ gchar **values,
+ gboolean set_default,
+ GCancellable *cancellable,
+ PAOCallback callback,
+ gpointer user_data)
+{
+ GVariantBuilder array_builder;
+ GDBusConnection *bus;
+ PAOData *data;
+ g_autoptr(GError) error = NULL;
+ gint i;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ callback (FALSE, user_data);
+ return;
+ }
+
+ g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
+ if (values)
+ {
+ for (i = 0; values[i]; i++)
+ g_variant_builder_add (&array_builder, "s", values[i]);
+ }
+
+ data = g_new0 (PAOData, 1);
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ set_default ? "PrinterAddOptionDefault" :
+ "PrinterAddOption",
+ g_variant_new ("(ssas)",
+ printer_name,
+ option_name,
+ &array_builder),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ cancellable,
+ printer_add_option_async_dbus_cb,
+ data);
+}
+
+typedef struct
+{
+ GCancellable *cancellable;
+ GCDCallback callback;
+ gpointer user_data;
+ GList *backend_list;
+} GCDData;
+
+static gint
+get_suffix_index (const gchar *string)
+{
+ gchar *number;
+ gchar *endptr;
+ gint index = -1;
+
+ number = g_strrstr (string, ":");
+ if (number)
+ {
+ number++;
+ index = g_ascii_strtoll (number, &endptr, 10);
+ if (index == 0 && endptr == number)
+ index = -1;
+ }
+
+ return index;
+}
+
+static void
+get_cups_devices_async_dbus_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+
+{
+ g_autoptr(GPtrArray) devices = NULL;
+ g_autoptr(GVariant) output = NULL;
+ GCDData *data = (GCDData *) user_data;
+ g_autoptr(GError) error = NULL;
+ gint num_of_devices = 0;
+
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res,
+ &error);
+
+ if (output)
+ {
+ const gchar *ret_error;
+ g_autoptr(GVariant) devices_variant = NULL;
+ gboolean is_network_device;
+ g_autoptr(GVariantIter) iter = NULL;
+ const gchar *key, *value;
+ gint index = -1, max_index = -1, i;
+
+ g_variant_get (output, "(&s@a{ss})",
+ &ret_error,
+ &devices_variant);
+
+ if (ret_error[0] != '\0')
+ {
+ g_warning ("cups-pk-helper: getting of CUPS devices failed: %s", ret_error);
+ }
+
+ g_variant_get (devices_variant, "a{ss}", &iter);
+ while (g_variant_iter_next (iter, "{&s&s}", &key, &value))
+ {
+ index = get_suffix_index (key);
+ if (index > max_index)
+ max_index = index;
+ }
+
+ if (max_index >= 0)
+ {
+ g_autoptr(GVariantIter) iter2 = NULL;
+
+ num_of_devices = max_index + 1;
+ devices = g_ptr_array_new_with_free_func (g_object_unref);
+ for (i = 0; i < num_of_devices; i++)
+ g_ptr_array_add (devices, pp_print_device_new ());
+
+ g_variant_get (devices_variant, "a{ss}", &iter2);
+ while (g_variant_iter_next (iter2, "{&s&s}", &key, &value))
+ {
+ PpPrintDevice *device;
+
+ index = get_suffix_index (key);
+ if (index >= 0)
+ {
+ device = g_ptr_array_index (devices, index);
+ if (g_str_has_prefix (key, "device-class"))
+ {
+ is_network_device = g_strcmp0 (value, "network") == 0;
+ g_object_set (device, "is-network-device", is_network_device, NULL);
+ }
+ else if (g_str_has_prefix (key, "device-id"))
+ g_object_set (device, "device-id", value, NULL);
+ else if (g_str_has_prefix (key, "device-info"))
+ g_object_set (device, "device-info", value, NULL);
+ else if (g_str_has_prefix (key, "device-make-and-model"))
+ {
+ g_object_set (device,
+ "device-make-and-model", value,
+ "device-name", value,
+ NULL);
+ }
+ else if (g_str_has_prefix (key, "device-uri"))
+ g_object_set (device, "device-uri", value, NULL);
+ else if (g_str_has_prefix (key, "device-location"))
+ g_object_set (device, "device-location", value, NULL);
+
+ g_object_set (device, "acquisition-method", ACQUISITION_METHOD_DEFAULT_CUPS_SERVER, NULL);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+
+ data->callback (devices,
+ TRUE,
+ g_cancellable_is_cancelled (data->cancellable),
+ data->user_data);
+
+ g_list_free_full (data->backend_list, g_free);
+ data->backend_list = NULL;
+ g_object_unref (source_object);
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data);
+
+ return;
+ }
+
+ if (data->backend_list)
+ {
+ if (!g_cancellable_is_cancelled (data->cancellable))
+ {
+ GVariantBuilder *include_scheme_builder = NULL;
+ GVariantBuilder *exclude_scheme_builder = NULL;
+ g_autofree gchar *backend_name = NULL;
+
+ backend_name = data->backend_list->data;
+
+ data->callback (devices,
+ FALSE,
+ FALSE,
+ data->user_data);
+
+ if (g_strcmp0 (backend_name, OTHER_BACKENDS) != 0)
+ {
+ include_scheme_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (include_scheme_builder, "s", backend_name);
+ }
+ else
+ {
+ exclude_scheme_builder = create_other_backends_array ();
+ }
+
+ data->backend_list = g_list_remove_link (data->backend_list, data->backend_list);
+
+ g_dbus_connection_call (G_DBUS_CONNECTION (g_object_ref (source_object)),
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "DevicesGet",
+ g_variant_new ("(iiasas)",
+ 0,
+ 0,
+ include_scheme_builder,
+ exclude_scheme_builder),
+ G_VARIANT_TYPE ("(sa{ss})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ data->cancellable,
+ get_cups_devices_async_dbus_cb,
+ user_data);
+
+ if (include_scheme_builder)
+ g_variant_builder_unref (include_scheme_builder);
+
+ if (exclude_scheme_builder)
+ g_variant_builder_unref (exclude_scheme_builder);
+
+ return;
+ }
+ else
+ {
+ data->callback (devices,
+ TRUE,
+ TRUE,
+ data->user_data);
+
+ g_list_free_full (data->backend_list, g_free);
+ data->backend_list = NULL;
+ }
+ }
+ else
+ {
+ data->callback (devices,
+ TRUE,
+ g_cancellable_is_cancelled (data->cancellable),
+ data->user_data);
+ }
+
+ g_object_unref (source_object);
+ if (data->cancellable)
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+void
+get_cups_devices_async (GCancellable *cancellable,
+ GCDCallback callback,
+ gpointer user_data)
+{
+ GDBusConnection *bus;
+ GVariantBuilder include_scheme_builder;
+ GCDData *data;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *backend_name = NULL;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!bus)
+ {
+ g_warning ("Failed to get system bus: %s", error->message);
+ callback (NULL, TRUE, FALSE, user_data);
+ return;
+ }
+
+ data = g_new0 (GCDData, 1);
+ if (cancellable)
+ data->cancellable = g_object_ref (cancellable);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->backend_list = create_backends_list ();
+
+ backend_name = data->backend_list->data;
+
+ g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&include_scheme_builder, "s", backend_name);
+
+ data->backend_list = g_list_remove_link (data->backend_list, data->backend_list);
+
+ g_dbus_connection_call (bus,
+ MECHANISM_BUS,
+ "/",
+ MECHANISM_BUS,
+ "DevicesGet",
+ g_variant_new ("(iiasas)",
+ 0,
+ 0,
+ &include_scheme_builder,
+ NULL),
+ G_VARIANT_TYPE ("(sa{ss})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ cancellable,
+ get_cups_devices_async_dbus_cb,
+ data);
+}
+
+gchar *
+guess_device_hostname (PpPrintDevice *device)
+{
+ http_uri_status_t status;
+ char scheme[HTTP_MAX_URI];
+ char username[HTTP_MAX_URI];
+ char hostname[HTTP_MAX_URI];
+ char resource[HTTP_MAX_URI];
+ int port;
+ gchar *result = NULL;
+ gchar *hostname_begin;
+ gchar *hostname_end = NULL;
+
+ if (device != NULL && pp_print_device_get_device_uri (device) != NULL)
+ {
+ if (g_str_has_prefix (pp_print_device_get_device_uri (device), "socket") ||
+ g_str_has_prefix (pp_print_device_get_device_uri (device), "lpd") ||
+ g_str_has_prefix (pp_print_device_get_device_uri (device), "ipp") ||
+ g_str_has_prefix (pp_print_device_get_device_uri (device), "smb"))
+ {
+ status = httpSeparateURI (HTTP_URI_CODING_ALL,
+ pp_print_device_get_device_uri (device),
+ scheme, HTTP_MAX_URI,
+ username, HTTP_MAX_URI,
+ hostname, HTTP_MAX_URI,
+ &port,
+ resource, HTTP_MAX_URI);
+
+ if (status >= HTTP_URI_STATUS_OK &&
+ hostname[0] != '\0')
+ result = g_strdup (hostname);
+ }
+ else if ((g_str_has_prefix (pp_print_device_get_device_uri (device), "dnssd") ||
+ g_str_has_prefix (pp_print_device_get_device_uri (device), "mdns")) &&
+ pp_print_device_get_device_info (device) != NULL)
+ {
+ /*
+ * CUPS browses its printers as
+ * "PrinterName @ ComputerName" or "PrinterInfo @ ComputerName"
+ * through DNS-SD.
+ */
+ hostname_begin = g_strrstr (pp_print_device_get_device_info (device), " @ ");
+ if (hostname_begin != NULL)
+ result = g_strdup (hostname_begin + 3);
+ }
+ else if (g_str_has_prefix (pp_print_device_get_device_uri (device), "hp:/net/") ||
+ g_str_has_prefix (pp_print_device_get_device_uri (device), "hpfax:/net/"))
+ {
+ /*
+ * HPLIP printers have URI of form hp:/net/%s?ip=%s&port=%d
+ * or hp:/net/%s?ip=%s.
+ */
+ hostname_begin = g_strrstr (pp_print_device_get_device_uri (device), "ip=");
+ if (hostname_begin != NULL)
+ {
+ hostname_begin += 3;
+ hostname_end = strstr (hostname_begin, "&");
+ }
+
+ if (hostname_end != NULL)
+ result = g_strndup (hostname_begin, hostname_end - hostname_begin);
+ else
+ result = g_strdup (hostname_begin);
+ }
+ }
+
+ return result;
+}
+
+gchar *
+canonicalize_device_name (GList *device_names,
+ GPtrArray *local_cups_devices,
+ cups_dest_t *dests,
+ gint num_of_dests,
+ PpPrintDevice *device)
+{
+ PpPrintDevice *item;
+ gboolean already_present;
+ GList *iter;
+ gsize len;
+ g_autofree gchar *name = NULL;
+ gchar *occurrence;
+ gint name_index, j;
+ static const char * const residues[] = {
+ "-foomatic",
+ "-hpijs",
+ "-hpcups",
+ "-cups",
+ "-gutenprint",
+ "-series",
+ "-label-printer",
+ "-dot-matrix",
+ "-ps3",
+ "-ps2",
+ "-br-script",
+ "-kpdl",
+ "-pcl3",
+ "-pcl",
+ "-zxs",
+ "-pxl"};
+
+ if (pp_print_device_get_device_id (device) != NULL)
+ {
+ name = get_tag_value (pp_print_device_get_device_id (device), "mdl");
+ if (name == NULL)
+ name = get_tag_value (pp_print_device_get_device_id (device), "model");
+ }
+
+ if (name == NULL &&
+ pp_print_device_get_device_make_and_model (device) != NULL &&
+ pp_print_device_get_device_make_and_model (device)[0] != '\0')
+ {
+ name = g_strdup (pp_print_device_get_device_make_and_model (device));
+ }
+
+ if (name == NULL &&
+ pp_print_device_get_device_original_name (device) != NULL &&
+ pp_print_device_get_device_original_name (device)[0] != '\0')
+ {
+ name = g_strdup (pp_print_device_get_device_original_name (device));
+ }
+
+ if (name == NULL &&
+ pp_print_device_get_device_info (device) != NULL &&
+ pp_print_device_get_device_info (device)[0] != '\0')
+ {
+ name = g_strdup (pp_print_device_get_device_info (device));
+ }
+
+ if (name == NULL)
+ return NULL;
+
+ g_strstrip (name);
+ g_strcanon (name, ALLOWED_CHARACTERS, '-');
+
+ /* Remove common strings found in driver names */
+ for (j = 0; j < G_N_ELEMENTS (residues); j++)
+ {
+ g_autofree gchar *lower_name = g_ascii_strdown (name, -1);
+
+ occurrence = g_strrstr (lower_name, residues[j]);
+ if (occurrence != NULL)
+ {
+ occurrence[0] = '\0';
+ name[strlen (lower_name)] = '\0';
+ }
+ }
+
+ /* Remove trailing "-" */
+ len = strlen (name);
+ while (len-- && name[len] == '-')
+ name[len] = '\0';
+
+ /* Merge "--" to "-" */
+ occurrence = g_strrstr (name, "--");
+ while (occurrence != NULL)
+ {
+ shift_string_left (occurrence);
+ occurrence = g_strrstr (name, "--");
+ }
+
+ /* Remove leading "-" */
+ if (name[0] == '-')
+ shift_string_left (name);
+
+ name_index = 2;
+ already_present = FALSE;
+ while (TRUE)
+ {
+ g_autofree gchar *new_name = NULL;
+
+ if (already_present)
+ {
+ new_name = g_strdup_printf ("%s-%d", name, name_index);
+ name_index++;
+ }
+ else
+ {
+ new_name = g_strdup (name);
+ }
+
+ already_present = FALSE;
+ for (j = 0; j < num_of_dests; j++)
+ if (g_strcmp0 (dests[j].name, new_name) == 0)
+ already_present = TRUE;
+
+ for (iter = device_names; iter; iter = iter->next)
+ {
+ gchar *device_original_name = iter->data;
+ if (g_strcmp0 (device_original_name, new_name) == 0)
+ already_present = TRUE;
+ }
+
+ for (guint i = 0; i < local_cups_devices->len; i++)
+ {
+ item = g_ptr_array_index (local_cups_devices, i);
+ if (g_strcmp0 (pp_print_device_get_device_original_name (item), new_name) == 0)
+ already_present = TRUE;
+ }
+
+ if (!already_present)
+ return g_steal_pointer (&new_name);
+ }
+}
+
+void
+shift_string_left (gchar *str)
+{
+ gchar *next;
+
+ if (str != NULL && str[0] != '\0')
+ {
+ next = g_utf8_find_next_char (str, NULL);
+ memmove (str, next, strlen (next) + 1);
+ }
+}
diff --git a/panels/printers/pp-utils.h b/panels/printers/pp-utils.h
new file mode 100644
index 0000000..34762ff
--- /dev/null
+++ b/panels/printers/pp-utils.h
@@ -0,0 +1,260 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+
+#include "pp-print-device.h"
+
+#define ALLOWED_CHARACTERS "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
+
+#define MECHANISM_BUS "org.opensuse.CupsPkHelper.Mechanism"
+
+#define SCP_BUS "org.fedoraproject.Config.Printing"
+#define SCP_PATH "/org/fedoraproject/Config/Printing"
+#define SCP_IFACE "org.fedoraproject.Config.Printing"
+
+G_BEGIN_DECLS
+
+typedef void (*UserResponseCallback) (GtkDialog *dialog, gint response_id, gpointer user_data);
+
+/*
+ * Match level of PPD driver.
+ */
+enum
+{
+ PPD_NO_MATCH = 0,
+ PPD_GENERIC_MATCH,
+ PPD_CLOSE_MATCH,
+ PPD_EXACT_MATCH,
+ PPD_EXACT_CMD_MATCH
+};
+
+enum
+{
+ ACQUISITION_METHOD_DEFAULT_CUPS_SERVER = 0,
+ ACQUISITION_METHOD_REMOTE_CUPS_SERVER,
+ ACQUISITION_METHOD_SNMP,
+ ACQUISITION_METHOD_SAMBA,
+ ACQUISITION_METHOD_SAMBA_HOST,
+ ACQUISITION_METHOD_JETDIRECT,
+ ACQUISITION_METHOD_LPD
+};
+
+typedef struct
+{
+ gchar *ppd_name;
+ gchar *ppd_display_name;
+ gint ppd_match_level;
+} PPDName;
+
+typedef struct
+{
+ gchar *manufacturer_name;
+ gchar *manufacturer_display_name;
+ PPDName **ppds;
+ gsize num_of_ppds;
+} PPDManufacturerItem;
+
+typedef struct
+{
+ PPDManufacturerItem **manufacturers;
+ gsize num_of_manufacturers;
+} PPDList;
+
+gchar *get_tag_value (const gchar *tag_string,
+ const gchar *tag_name);
+
+char *get_dest_attr (const char *dest_name,
+ const char *attr);
+
+gchar *get_ppd_attribute (const gchar *ppd_file_name,
+ const gchar *attribute_name);
+
+void set_local_default_printer (const gchar *printer_name);
+
+gboolean printer_set_location (const gchar *printer_name,
+ const gchar *location);
+
+gboolean printer_set_accepting_jobs (const gchar *printer_name,
+ gboolean accepting_jobs,
+ const gchar *reason);
+
+gboolean printer_set_enabled (const gchar *printer_name,
+ gboolean enabled);
+
+gboolean printer_rename (const gchar *old_name,
+ const gchar *new_name);
+
+gboolean printer_delete (const gchar *printer_name);
+
+gboolean printer_set_default (const gchar *printer_name);
+
+gboolean printer_set_shared (const gchar *printer_name,
+ gboolean shared);
+
+gboolean printer_set_job_sheets (const gchar *printer_name,
+ const gchar *start_sheet,
+ const gchar *end_sheet);
+
+gboolean printer_set_policy (const gchar *printer_name,
+ const gchar *policy,
+ gboolean error_policy);
+
+gboolean printer_set_users (const gchar *printer_name,
+ gchar **users,
+ gboolean allowed);
+
+gboolean class_add_printer (const gchar *class_name,
+ const gchar *printer_name);
+
+gboolean printer_is_local (cups_ptype_t printer_type,
+ const gchar *device_uri);
+
+gchar *printer_get_hostname (cups_ptype_t printer_type,
+ const gchar *device_uri,
+ const gchar *printer_uri);
+
+typedef void (*PSPCallback) (const gchar *printer_name,
+ gboolean success,
+ gpointer user_data);
+
+void printer_set_ppd_async (const gchar *printer_name,
+ const gchar *ppd_name,
+ GCancellable *cancellable,
+ PSPCallback callback,
+ gpointer user_data);
+
+void printer_set_ppd_file_async (const gchar *printer_name,
+ const gchar *ppd_filename,
+ GCancellable *cancellable,
+ PSPCallback callback,
+ gpointer user_data);
+
+typedef void (*GPNCallback) (PPDName **names,
+ const gchar *printer_name,
+ gboolean cancelled,
+ gpointer user_data);
+
+void get_ppd_names_async (gchar *printer_name,
+ gint count,
+ GCancellable *cancellable,
+ GPNCallback callback,
+ gpointer user_data);
+
+typedef void (*GAPCallback) (PPDList *ppds,
+ gpointer user_data);
+
+void get_all_ppds_async (GCancellable *cancellable,
+ GAPCallback callback,
+ gpointer user_data);
+
+PPDList *ppd_list_copy (PPDList *list);
+void ppd_list_free (PPDList *list);
+
+enum
+{
+ IPP_ATTRIBUTE_TYPE_INTEGER = 0,
+ IPP_ATTRIBUTE_TYPE_STRING,
+ IPP_ATTRIBUTE_TYPE_RANGE,
+ IPP_ATTRIBUTE_TYPE_BOOLEAN
+};
+
+typedef struct
+{
+ gboolean boolean_value;
+ gchar *string_value;
+ gint integer_value;
+ gint lower_range;
+ gint upper_range;
+} IPPAttributeValue;
+
+typedef struct
+{
+ gchar *attribute_name;
+ IPPAttributeValue *attribute_values;
+ gint num_of_values;
+ gint attribute_type;
+} IPPAttribute;
+
+typedef void (*GIACallback) (GHashTable *table,
+ gpointer user_data);
+
+void get_ipp_attributes_async (const gchar *printer_name,
+ gchar **attributes_names,
+ GIACallback callback,
+ gpointer user_data);
+
+IPPAttribute *ipp_attribute_copy (IPPAttribute *attr);
+
+void ipp_attribute_free (IPPAttribute *attr);
+
+gchar *get_standard_manufacturers_name (const gchar *name);
+
+typedef void (*PGPCallback) (const gchar *ppd_filename,
+ gpointer user_data);
+
+void printer_get_ppd_async (const gchar *printer_name,
+ const gchar *host_name,
+ gint port,
+ PGPCallback callback,
+ gpointer user_data);
+
+typedef void (*GNDCallback) (cups_dest_t *destination,
+ gpointer user_data);
+
+void get_named_dest_async (const gchar *printer_name,
+ GNDCallback callback,
+ gpointer user_data);
+
+typedef void (*PAOCallback) (gboolean success,
+ gpointer user_data);
+
+void printer_add_option_async (const gchar *printer_name,
+ const gchar *option_name,
+ gchar **values,
+ gboolean set_default,
+ GCancellable *cancellable,
+ PAOCallback callback,
+ gpointer user_data);
+
+const gchar *get_page_size_from_locale (void);
+
+typedef void (*GCDCallback) (GPtrArray *devices,
+ gboolean finished,
+ gboolean cancelled,
+ gpointer user_data);
+
+void get_cups_devices_async (GCancellable *cancellable,
+ GCDCallback callback,
+ gpointer user_data);
+
+gchar *guess_device_hostname (PpPrintDevice *device);
+
+gchar *canonicalize_device_name (GList *device_names,
+ GPtrArray *local_cups_devices,
+ cups_dest_t *dests,
+ gint num_of_dests,
+ PpPrintDevice *device);
+
+void shift_string_left (gchar *str);
+
+G_END_DECLS
diff --git a/panels/printers/ppd-selection-dialog.ui b/panels/printers/ppd-selection-dialog.ui
new file mode 100644
index 0000000..26bd22d
--- /dev/null
+++ b/panels/printers/ppd-selection-dialog.ui
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkDialog" id="ppd-selection-dialog">
+ <property name="width_request">600</property>
+ <property name="height_request">400</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Select Printer Driver</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="main-vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action-area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="ppd-selection-cancel-button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ppd-selection-select-button">
+ <property name="label" translatable="yes">Select</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinner" id="ppd-spinner">
+ <property name="width_request">24</property>
+ <property name="height_request">24</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="progress-label">
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="label" translatable="yes">Loading drivers database…</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">10</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>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="width_request">140</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="ppd-selection-manufacturers-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="ppd-selection-models-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">ppd-selection-cancel-button</action-widget>
+ <action-widget response="-5">ppd-selection-select-button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/panels/printers/printer-entry.ui b/panels/printers/printer-entry.ui
new file mode 100644
index 0000000..71d3e80
--- /dev/null
+++ b/panels/printers/printer-entry.ui
@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.8 -->
+ <object class="GtkPopover" id="printer-menu">
+
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="margin">10</property>
+
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="text" translatable="yes">Printing Options</property>
+ <signal name="clicked" handler="on_show_printer_options_dialog"/>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="text" translatable="yes">Printer Details</property>
+ <signal name="clicked" handler="on_show_printer_details_dialog"/>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="printer_default_checkbutton">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes" comments="Set this printer as default">Use Printer by Default</property>
+ <signal name="toggled" handler="set_as_default_printer"/>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="clean_heads_menuitem">
+ <property name="visible">False</property>
+ <property name="text" translatable="yes" comments="Translators: This button executes command which cleans print heads of the printer.">Clean Print Heads</property>
+ <signal name="clicked" handler="clean_heads"/>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="remove_printer_menuitem">
+ <property name="visible">True</property>
+ <property name="text" translatable="yes">Remove Printer</property>
+ <signal name="clicked" handler="remove_printer"/>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+
+ <template class="PpPrinterEntry" parent="GtkListBoxRow">
+ <property name="valign">center</property>
+ <property name="margin">10</property>
+ <property name="margin-start">60</property>
+ <property name="margin-end">60</property>
+ <property name="activatable">False</property>
+
+ <child>
+ <object class="GtkFrame" id="content_area">
+ <property name="visible">True</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="view"/>
+ </style>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="row-spacing">8</property>
+ <property name="column-spacing">15</property>
+ <property name="margin_start">24</property>
+ <property name="margin_end">20</property>
+ <property name="margin_top">14</property>
+ <property name="margin_bottom">20</property>
+ <property name="no-show-all">True</property>
+
+ <child>
+ <object class="GtkImage" id="printer_icon">
+ <property name="visible">True</property>
+ <property name="pixel-size">48</property>
+ <property name="icon_name">printer</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkGrid" id="printer_name_grid">
+ <property name="visible">True</property>
+ <property name="margin">10</property>
+ <property name="margin_start">0</property>
+ <property name="halign">start</property>
+ <child>
+
+ <object class="GtkLabel" id="printer_name_label">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
+ <property name="width-chars">22</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="printer_status">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <property name="orientation">horizontal</property>
+ <property name="valign">center</property>
+ <child>
+
+ <object class="GtkButton" id="show_jobs_dialog_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">No Active Jobs</property>
+ <signal name="clicked" handler="show_jobs_dialog"/>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkMenuButton">
+ <property name="visible">True</property>
+ <property name="popover">printer-menu</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="printer_model_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Model</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="printer_model">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="printer_location_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Location</property>
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="printer_location_address_label">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="printer_inklevel_label">
+ <property name="label" translatable="yes">Ink Level</property>
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkFrame" id="supply_frame">
+ <property name="valign">center</property>
+ <property name="halign">start</property>
+ <property name="height_request">18</property>
+ <property name="width_request">300</property>
+ <property name="border_width">2</property>
+
+ <child>
+ <object class="GtkDrawingArea" id="supply_drawing_area">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkBox" id="printer_error">
+ <property name="visible">False</property>
+ <property name="spacing">10</property>
+ <property name="margin-top">6</property>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">dialog-warning-symbolic</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="error_status">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" comments="Translators: This is the message which follows the printer error.">Please restart when the problem is resolved.</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes" comments="Translators: This is the button which restarts the printer.">Restart</property>
+ <signal name="clicked" handler="restart_printer"/>
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+</interface>
diff --git a/panels/printers/printers.gresource.xml b/panels/printers/printers.gresource.xml
new file mode 100644
index 0000000..b9a7d4b
--- /dev/null
+++ b/panels/printers/printers.gresource.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/printers">
+ <file preprocess="xml-stripblanks">authentication-dialog.ui</file>
+ <file preprocess="xml-stripblanks">new-printer-dialog.ui</file>
+ <file preprocess="xml-stripblanks">pp-options-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ppd-selection-dialog.ui</file>
+ <file preprocess="xml-stripblanks">pp-details-dialog.ui</file>
+ <file preprocess="xml-stripblanks">pp-jobs-dialog.ui</file>
+ <file preprocess="xml-stripblanks">printer-entry.ui</file>
+ <file preprocess="xml-stripblanks">printers.ui</file>
+ </gresource>
+</gresources>
diff --git a/panels/printers/printers.ui b/panels/printers/printers.ui
new file mode 100644
index 0000000..d8f8af2
--- /dev/null
+++ b/panels/printers/printers.ui
@@ -0,0 +1,288 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkBox" id="top-right-buttons">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="printer-add-button">
+ <property name="visible">True</property>
+ <property name="sensitive">True</property>
+ <property name="use_underline">True</property>
+ <property name="label" translatable="yes" comments="Translators: This button adds new printer.">Add…</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="search-button">
+ <property name="visible">True</property>
+ <property name="margin-end">6</property> <!-- since we don't have access to the shell header bar -->
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">system-search-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+
+<object class="GtkOverlay" id="overlay">
+ <property name="visible">True</property>
+ <child type="overlay">
+ <object class="GtkRevealer" id="notification">
+ <property name="visible">True</property>
+ <property name="halign">GTK_ALIGN_CENTER</property>
+ <property name="valign">GTK_ALIGN_START</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <style>
+ <class name="app-notification"/>
+ </style>
+ <child>
+ <object class="GtkLabel" id="notification-label">
+ <property name="visible">True</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">50</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="notification-undo-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="valign">GTK_ALIGN_CENTER</property>
+ <property name="label" translatable="yes" comments="Translators: This is the button which allows undoing the removal of the printer.">Undo</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="notification-dismiss-button">
+ <property name="visible">True</property>
+ <property name="valign">GTK_ALIGN_CENTER</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+
+<object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <child>
+ <object class="CcPermissionInfobar" id="permission-infobar">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+
+ <object class="GtkStack" id="main-vbox">
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSpinner" id="loading-spinner">
+ <property name="visible">True</property>
+ <property name="active">True</property>
+ <property name="expand">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">loading-page</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <child>
+ <object class="GtkSearchBar" id="search-bar">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="search_mode_enabled" bind-source="search-button" bind-property="active" bind-flags="bidirectional" />
+ <child>
+ <object class="GtkSearchEntry" id="search-entry">
+ <property name="visible">True</property>
+ <property name="width_chars">30</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled-window">
+ <property name="visible">True</property>
+ <property name="hscrollbar-policy">GTK_POLICY_NEVER</property>
+ <property name="min-content-height">490</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkListBox" id="content">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection-mode">GTK_SELECTION_NONE</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <style>
+ <class name="background"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">printers-list</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">printer-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="margin_bottom">15</property>
+ <property name="label" translatable="yes">No printers</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="printer-add-button2">
+ <property name="label" translatable="yes" comments="Translators: This button adds new printer.">Add a Printer…</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="relief">normal</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">empty-state</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Translators: The CUPS server is not running (we can not connect to it).">Sorry! The system printing service
+doesn’t seem to be available.</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">computer-fail-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">no-cups-page</property>
+ </packing>
+ </child>
+ </object>
+</child>
+</object>
+
+ </child>
+</object>
+
+ <object class="GtkSizeGroup" id="sizegroup1">
+ <widgets>
+ <widget name="back-button-1"/>
+ <widget name="back-spacer-label-1"/>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="sizegroup2">
+ <widgets>
+ <widget name="back-button-2"/>
+ <widget name="back-spacer-label-2"/>
+ </widgets>
+ </object>
+</interface>