diff options
Diffstat (limited to '')
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> |