diff --git a/panels/printers/Makefile.am b/panels/printers/Makefile.am index 0dade8cc3..dab5ae553 100644 --- a/panels/printers/Makefile.am +++ b/panels/printers/Makefile.am @@ -3,6 +3,7 @@ cappletname = printers uidir = $(pkgdatadir)/ui/printers dist_ui_DATA = \ new-printer-dialog.ui \ + ppd-selection-dialog.ui \ printers.ui INCLUDES = \ @@ -18,12 +19,14 @@ ccpanelsdir = $(PANELS_DIR) ccpanels_LTLIBRARIES = libprinters.la libprinters_la_SOURCES = \ - printers-module.c \ - pp-utils.c \ - pp-utils.h \ - pp-new-printer-dialog.c \ - pp-new-printer-dialog.h \ - cc-printers-panel.c \ + printers-module.c \ + pp-utils.c \ + pp-utils.h \ + pp-new-printer-dialog.c \ + pp-new-printer-dialog.h \ + pp-ppd-selection-dialog.c \ + pp-ppd-selection-dialog.h \ + cc-printers-panel.c \ cc-printers-panel.h libprinters_la_LIBADD = $(PRINTERS_PANEL_LIBS) $(PANEL_LIBS) $(CUPS_LIBS) diff --git a/panels/printers/cc-printers-panel.c b/panels/printers/cc-printers-panel.c index 0ea51c48a..63fc803e7 100644 --- a/panels/printers/cc-printers-panel.c +++ b/panels/printers/cc-printers-panel.c @@ -33,6 +33,7 @@ #include "cc-editable-entry.h" #include "pp-new-printer-dialog.h" +#include "pp-ppd-selection-dialog.h" #include "pp-utils.h" G_DEFINE_DYNAMIC_TYPE (CcPrintersPanel, cc_printers_panel, CC_TYPE_PANEL) @@ -83,6 +84,7 @@ struct _CcPrintersPanelPrivate GSettings *lockdown_settings; PpNewPrinterDialog *pp_new_printer_dialog; + PpPPDSelectionDialog *pp_ppd_selection_dialog; GDBusProxy *cups_proxy; GDBusConnection *cups_bus_connection; @@ -91,13 +93,21 @@ struct _CcPrintersPanelPrivate guint cups_status_check_id; guint dbus_subscription_id; + GtkWidget *popup_menu; + GList *driver_change_list; + GCancellable *get_ppd_name_cancellable; + gboolean getting_ppd_names; + PPDList *all_ppds_list; + GHashTable *preferred_drivers; + gboolean getting_all_ppds; + gpointer dummy; }; static void actualize_jobs_list (CcPrintersPanel *self); static void actualize_printers_list (CcPrintersPanel *self); static void actualize_allowed_users_list (CcPrintersPanel *self); -static void actualize_sensitivity (gpointer user_data); +static void update_sensitivity (gpointer user_data); static void printer_disable_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data); static void printer_set_default_cb (GtkToggleButton *button, gpointer user_data); static void detach_from_cups_notifier (gpointer data); @@ -180,7 +190,22 @@ cc_printers_panel_dispose (GObject *object) detach_from_cups_notifier (CC_PRINTERS_PANEL (object)); if (priv->cups_status_check_id > 0) - g_source_remove (priv->cups_status_check_id); + { + g_source_remove (priv->cups_status_check_id); + priv->cups_status_check_id = 0; + } + + if (priv->all_ppds_list) + { + ppd_list_free (priv->all_ppds_list); + priv->all_ppds_list = NULL; + } + + if (priv->preferred_drivers) + { + g_hash_table_unref (priv->preferred_drivers); + priv->preferred_drivers = NULL; + } G_OBJECT_CLASS (cc_printers_panel_parent_class)->dispose (object); } @@ -502,6 +527,8 @@ printer_selection_changed_cb (GtkTreeSelection *selection, cups_ptype_t type = 0; GtkTreeIter iter; GtkWidget *widget; + GtkWidget *model_button; + GtkWidget *model_label; gboolean sensitive; GValue value = G_VALUE_INIT; gchar *printer_make_and_model = NULL; @@ -814,16 +841,23 @@ printer_selection_changed_cb (GtkTreeSelection *selection, cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT); - widget = (GtkWidget*) + model_button = (GtkWidget*) + gtk_builder_get_object (priv->builder, "printer-model-button"); + + model_label = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-model-label"); if (printer_model) { - cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), printer_model); + gtk_button_set_label (GTK_BUTTON (model_button), printer_model); + gtk_label_set_text (GTK_LABEL (model_label), printer_model); g_free (printer_model); } else - cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), EMPTY_TEXT); + { + gtk_button_set_label (GTK_BUTTON (model_button), EMPTY_TEXT); + gtk_label_set_text (GTK_LABEL (model_label), EMPTY_TEXT); + } widget = (GtkWidget*) @@ -917,9 +951,13 @@ printer_selection_changed_cb (GtkTreeSelection *selection, gtk_builder_get_object (priv->builder, "printer-location-label"); cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), ""); + widget = (GtkWidget*) + gtk_builder_get_object (priv->builder, "printer-model-button"); + gtk_button_set_label (GTK_BUTTON (widget), ""); + widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-model-label"); - cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), ""); + gtk_label_set_text (GTK_LABEL (widget), ""); widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-ip-address-label"); @@ -930,7 +968,7 @@ printer_selection_changed_cb (GtkTreeSelection *selection, cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), ""); } - actualize_sensitivity (self); + update_sensitivity (self); } static void @@ -1157,7 +1195,7 @@ actualize_printers_list (CcPrintersPanel *self) g_free (current_printer_instance); g_object_unref (store); - actualize_sensitivity (self); + update_sensitivity (self); } static void @@ -2179,6 +2217,475 @@ printer_location_edit_cb (GtkWidget *entry, actualize_printers_list (self); } +static void +set_ppd_cb (gchar *printer_name, + gboolean success, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + GList *iter; + + priv = PRINTERS_PANEL_PRIVATE (self); + + for (iter = priv->driver_change_list; iter; iter = iter->next) + { + if (g_strcmp0 ((gchar *) iter->data, printer_name) == 0) + { + priv->driver_change_list = g_list_remove_link (priv->driver_change_list, iter); + g_list_free_full (iter, g_free); + break; + } + } + + update_sensitivity (self); + + if (success) + { + actualize_printers_list (self); + } + + g_free (printer_name); +} + +static void +select_ppd_manually (GtkMenuItem *menuitem, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + GtkFileFilter *filter; + GtkWidget *dialog; + gchar *printer_name = NULL; + + priv = PRINTERS_PANEL_PRIVATE (self); + + gtk_menu_shell_cancel (GTK_MENU_SHELL (priv->popup_menu)); + + dialog = gtk_file_chooser_dialog_new (_("Select PPD File"), + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_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) + { + gchar *ppd_filename; + + ppd_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + if (priv->current_dest >= 0 && + priv->current_dest < priv->num_dests && + priv->dests != NULL) + printer_name = priv->dests[priv->current_dest].name; + + if (printer_name && ppd_filename) + { + priv->driver_change_list = + g_list_prepend (priv->driver_change_list, g_strdup (printer_name)); + update_sensitivity (self); + printer_set_ppd_file_async (printer_name, + ppd_filename, + NULL, + set_ppd_cb, + user_data); + } + + g_free (ppd_filename); + } + + gtk_widget_destroy (dialog); +} + +static void +ppd_selection_dialog_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + gchar *printer_name = NULL; + + priv = PRINTERS_PANEL_PRIVATE (self); + + if (response_id == GTK_RESPONSE_OK) + { + gchar *ppd_name; + + ppd_name = pp_ppd_selection_dialog_get_ppd_name (priv->pp_ppd_selection_dialog); + + if (priv->current_dest >= 0 && + priv->current_dest < priv->num_dests && + priv->dests != NULL) + printer_name = priv->dests[priv->current_dest].name; + + if (printer_name && ppd_name) + { + priv->driver_change_list = g_list_prepend (priv->driver_change_list, + g_strdup (printer_name)); + update_sensitivity (self); + printer_set_ppd_async (printer_name, + ppd_name, + NULL, + set_ppd_cb, + user_data); + } + + g_free (ppd_name); + } + + pp_ppd_selection_dialog_free (priv->pp_ppd_selection_dialog); + priv->pp_ppd_selection_dialog = NULL; +} + +static void +select_ppd_in_dialog (GtkMenuItem *menuitem, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + GtkWidget *widget; + gchar *device_id = NULL; + gchar *manufacturer = NULL; + + priv = PRINTERS_PANEL_PRIVATE (self); + + widget = (GtkWidget*) + gtk_builder_get_object (priv->builder, "main-vbox"); + + if (!priv->pp_ppd_selection_dialog) + { + if (priv->current_dest >= 0 && + priv->current_dest < priv->num_dests) + { + device_id = + get_ppd_attribute (priv->ppd_file_names[priv->current_dest], + "1284DeviceID"); + + if (device_id) + { + manufacturer = get_tag_value (device_id, "mfg"); + if (!manufacturer) + manufacturer = get_tag_value (device_id, "manufacturer"); + } + else + { + manufacturer = + get_ppd_attribute (priv->ppd_file_names[priv->current_dest], + "Manufacturer"); + } + } + + priv->pp_ppd_selection_dialog = pp_ppd_selection_dialog_new ( + GTK_WINDOW (gtk_widget_get_toplevel (widget)), + priv->all_ppds_list, + manufacturer, + ppd_selection_dialog_response_cb, + self); + + g_free (manufacturer); + g_free (device_id); + } +} + +static void +set_ppd_from_list (GtkMenuItem *menuitem, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + gchar *printer_name = NULL; + gchar *ppd_name; + + priv = PRINTERS_PANEL_PRIVATE (self); + + ppd_name = (gchar *) g_object_get_data (G_OBJECT (menuitem), "ppd-name"); + + if (priv->current_dest >= 0 && + priv->current_dest < priv->num_dests && + priv->dests != NULL) + printer_name = priv->dests[priv->current_dest].name; + + if (printer_name && ppd_name) + { + priv->driver_change_list = g_list_prepend (priv->driver_change_list, + g_strdup (printer_name)); + update_sensitivity (self); + printer_set_ppd_async (printer_name, + ppd_name, + NULL, + set_ppd_cb, + user_data); + } +} + +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 +get_ppd_names_cb (PPDName **names, + const gchar *printer_name, + gboolean cancelled, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + GtkWidget *informal = NULL; + GtkWidget *placeholders[3]; + GtkWidget *spinner; + gpointer value = NULL; + gboolean found = FALSE; + PPDName **hash_names = NULL; + GList *children, *iter; + gint i; + + priv = PRINTERS_PANEL_PRIVATE (self); + + priv->getting_ppd_names = FALSE; + + for (i = 0; i < 3; i++) + placeholders[i] = NULL; + + children = gtk_container_get_children (GTK_CONTAINER (priv->popup_menu)); + if (children) + { + for (iter = children; iter; iter = iter->next) + { + if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"), + "informal") == 0) + informal = GTK_WIDGET (iter->data); + else if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"), + "placeholder1") == 0) + placeholders[0] = GTK_WIDGET (iter->data); + else if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"), + "placeholder2") == 0) + placeholders[1] = GTK_WIDGET (iter->data); + else if (g_strcmp0 ((gchar *) g_object_get_data (G_OBJECT (iter->data), "purpose"), + "placeholder3") == 0) + placeholders[2] = GTK_WIDGET (iter->data); + } + + g_list_free (children); + } + + if (!priv->preferred_drivers) + { + priv->preferred_drivers = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, ppd_names_free); + } + + if (!cancelled && + !g_hash_table_lookup_extended (priv->preferred_drivers, + printer_name, NULL, NULL)) + g_hash_table_insert (priv->preferred_drivers, g_strdup (printer_name), names); + + if (priv->preferred_drivers && + g_hash_table_lookup_extended (priv->preferred_drivers, + printer_name, NULL, &value)) + { + hash_names = (PPDName **) value; + if (hash_names) + { + for (i = 0; hash_names[i]; i++) + { + if (placeholders[i]) + { + gtk_menu_item_set_label (GTK_MENU_ITEM (placeholders[i]), + hash_names[i]->ppd_display_name); + g_object_set_data_full (G_OBJECT (placeholders[i]), + "ppd-name", + g_strdup (hash_names[i]->ppd_name), + g_free); + g_signal_connect (placeholders[i], + "activate", + G_CALLBACK (set_ppd_from_list), + self); + gtk_widget_set_sensitive (GTK_WIDGET (placeholders[i]), TRUE); + gtk_widget_show (placeholders[i]); + } + } + + found = TRUE; + } + else + { + found = FALSE; + } + } + + if (informal) + { + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (informal), FALSE); + + spinner = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (informal)); + if (spinner) + gtk_spinner_stop (GTK_SPINNER (spinner)); + + if (found) + gtk_widget_hide (informal); + else + gtk_menu_item_set_label (GTK_MENU_ITEM (informal), _("No suitable driver found")); + } + + gtk_widget_show_all (priv->popup_menu); + + update_sensitivity (self); +} + +static void +popup_menu_done (GtkMenuShell *menushell, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + + priv = PRINTERS_PANEL_PRIVATE (self); + + if (priv->get_ppd_name_cancellable) + { + g_cancellable_cancel (priv->get_ppd_name_cancellable); + g_object_unref (priv->get_ppd_name_cancellable); + priv->get_ppd_name_cancellable = NULL; + } +} + +static void +popup_model_menu_cb (GtkButton *button, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + GtkWidget *spinner; + GtkWidget *item; + + priv = PRINTERS_PANEL_PRIVATE (self); + + priv->popup_menu = gtk_menu_new (); + g_signal_connect (priv->popup_menu, + "selection-done", + G_CALLBACK (popup_menu_done), + user_data); + + /* + * These placeholders are a workaround for a situation + * when we want to actually append new menu item in a callback. + * But unfortunately it is not possible to connect to "activate" + * signal of such menu item (appended after gtk_menu_popup()). + */ + item = gtk_image_menu_item_new_with_label (""); + g_object_set_data_full (G_OBJECT (item), "purpose", + g_strdup ("placeholder1"), g_free); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + gtk_widget_set_no_show_all (item, TRUE); + gtk_widget_hide (item); + + item = gtk_image_menu_item_new_with_label (""); + g_object_set_data_full (G_OBJECT (item), "purpose", + g_strdup ("placeholder2"), g_free); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + gtk_widget_set_no_show_all (item, TRUE); + gtk_widget_hide (item); + + item = gtk_image_menu_item_new_with_label (""); + g_object_set_data_full (G_OBJECT (item), "purpose", + g_strdup ("placeholder3"), g_free); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + gtk_widget_set_no_show_all (item, TRUE); + gtk_widget_hide (item); + + item = gtk_image_menu_item_new_with_label (_("Searching for preferred drivers...")); + spinner = gtk_spinner_new (); + gtk_spinner_start (GTK_SPINNER (spinner)); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), spinner); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE); + g_object_set_data_full (G_OBJECT (item), "purpose", + g_strdup ("informal"), g_free); + gtk_widget_set_sensitive (item, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + gtk_widget_set_no_show_all (item, TRUE); + gtk_widget_show (item); + + item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + + item = gtk_menu_item_new_with_label (_("Select from database...")); + g_object_set_data_full (G_OBJECT (item), "purpose", + g_strdup ("ppd-select"), g_free); + g_signal_connect (item, "activate", G_CALLBACK (select_ppd_in_dialog), self); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + + item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + + item = gtk_menu_item_new_with_label (_("Provide PPD File...")); + g_object_set_data_full (G_OBJECT (item), "purpose", + g_strdup ("ppdfile-select"), g_free); + g_signal_connect (item, "activate", G_CALLBACK (select_ppd_manually), self); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item); + + gtk_widget_show_all (priv->popup_menu); + + gtk_menu_popup (GTK_MENU (priv->popup_menu), + NULL, NULL, NULL, NULL, 0, + gtk_get_current_event_time()); + + if (priv->current_dest >= 0 && + priv->current_dest < priv->num_dests && + priv->dests != NULL) + { + if (priv->preferred_drivers && + g_hash_table_lookup_extended (priv->preferred_drivers, + priv->dests[priv->current_dest].name, + NULL, NULL)) + { + get_ppd_names_cb (NULL, + priv->dests[priv->current_dest].name, + FALSE, + user_data); + } + else + { + priv->get_ppd_name_cancellable = g_cancellable_new (); + priv->getting_ppd_names = TRUE; + get_ppd_names_async (priv->dests[priv->current_dest].name, + 3, + priv->get_ppd_name_cancellable, + get_ppd_names_cb, + user_data); + + update_sensitivity (self); + } + } +} + static void test_page_cb (GtkButton *button, gpointer user_data) @@ -2303,7 +2810,7 @@ test_page_cb (GtkButton *button, } static void -actualize_sensitivity (gpointer user_data) +update_sensitivity (gpointer user_data) { CcPrintersPanelPrivate *priv; CcPrintersPanel *self = (CcPrintersPanel*) user_data; @@ -2312,9 +2819,12 @@ actualize_sensitivity (gpointer user_data) GtkWidget *widget; gboolean is_authorized; gboolean is_discovered = FALSE; + gboolean is_class = FALSE; + gboolean is_changing_driver = FALSE; gboolean printer_selected; gboolean local_server = TRUE; gboolean no_cups = FALSE; + GList *iter; gint i; priv = PRINTERS_PANEL_PRIVATE (self); @@ -2330,15 +2840,26 @@ actualize_sensitivity (gpointer user_data) priv->dests != NULL; if (printer_selected) - for (i = 0; i < priv->dests[priv->current_dest].num_options; i++) - { - if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-type") == 0) - { - type = atoi (priv->dests[priv->current_dest].options[i].value); - is_discovered = type & CUPS_PRINTER_DISCOVERED; - break; - } - } + { + for (i = 0; i < priv->dests[priv->current_dest].num_options; i++) + { + if (g_strcmp0 (priv->dests[priv->current_dest].options[i].name, "printer-type") == 0) + { + type = atoi (priv->dests[priv->current_dest].options[i].value); + is_discovered = type & CUPS_PRINTER_DISCOVERED; + is_class = type & CUPS_PRINTER_CLASS; + break; + } + } + + for (iter = priv->driver_change_list; iter; iter = iter->next) + { + if (g_strcmp0 ((gchar *) iter->data, priv->dests[priv->current_dest].name) == 0) + { + is_changing_driver = TRUE; + } + } + } cups_server = cupsServer (); if (cups_server && @@ -2390,6 +2911,19 @@ actualize_sensitivity (gpointer user_data) widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-location-label"); cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), local_server && !is_discovered && is_authorized); + + widget = (GtkWidget*) gtk_builder_get_object (priv->builder, "printer-model-notebook"); + if (is_changing_driver) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 2); + } + else + { + if (local_server && !is_discovered && is_authorized && !is_class && !priv->getting_ppd_names) + gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 0); + else + gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 1); + } } static void @@ -2397,7 +2931,7 @@ on_permission_changed (GPermission *permission, GParamSpec *pspec, gpointer data) { - actualize_sensitivity (data); + update_sensitivity (data); } static void @@ -2490,6 +3024,56 @@ cups_status_check (gpointer user_data) return result; } +static void +get_all_ppds_async_cb (PPDList *ppds, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + + priv = self->priv = PRINTERS_PANEL_PRIVATE (self); + + priv->all_ppds_list = ppds; + + priv->getting_all_ppds = FALSE; + + if (priv->pp_ppd_selection_dialog) + pp_ppd_selection_dialog_set_ppd_list (priv->pp_ppd_selection_dialog, + priv->all_ppds_list); +} + +static void +update_label_padding (GtkWidget *widget, + GtkAllocation *allocation, + gpointer user_data) +{ + CcPrintersPanelPrivate *priv; + CcPrintersPanel *self = (CcPrintersPanel*) user_data; + GtkAllocation allocation1, allocation2; + GtkWidget *label; + GtkWidget *sublabel; + gint offset; + gint pad; + + priv = PRINTERS_PANEL_PRIVATE (self); + + sublabel = gtk_bin_get_child (GTK_BIN (widget)); + if (sublabel) + { + gtk_widget_get_allocation (widget, &allocation1); + gtk_widget_get_allocation (sublabel, &allocation2); + + offset = allocation2.x - allocation1.x; + + label = (GtkWidget*) + gtk_builder_get_object (priv->builder, "printer-model-label"); + + gtk_misc_get_padding (GTK_MISC (label), &pad, NULL); + if (offset != pad) + gtk_misc_set_padding (GTK_MISC (label), offset, 0); + } +} + static void cc_printers_panel_init (CcPrintersPanel *self) { @@ -2532,6 +3116,13 @@ cc_printers_panel_init (CcPrintersPanel *self) priv->permission = NULL; priv->lockdown_settings = NULL; + priv->getting_ppd_names = FALSE; + + priv->all_ppds_list = NULL; + priv->getting_all_ppds = FALSE; + + priv->preferred_drivers = NULL; + builder_result = gtk_builder_add_objects_from_file (priv->builder, DATADIR"/printers.ui", objects, &error); @@ -2628,6 +3219,11 @@ cc_printers_panel_init (CcPrintersPanel *self) G_CALLBACK (on_lockdown_settings_changed), self); + widget = (GtkWidget*) + gtk_builder_get_object (priv->builder, "printer-model-button"); + g_signal_connect (widget, "clicked", G_CALLBACK (popup_model_menu_cb), self); + g_signal_connect (widget, "size-allocate", G_CALLBACK (update_label_padding), self); + /* Set junctions */ widget = (GtkWidget*) @@ -2666,10 +3262,6 @@ cc_printers_panel_init (CcPrintersPanel *self) gtk_builder_get_object (priv->builder, "printer-ip-address-label"); cc_editable_entry_set_selectable (CC_EDITABLE_ENTRY (widget), TRUE); - widget = (GtkWidget*) - gtk_builder_get_object (priv->builder, "printer-model-label"); - cc_editable_entry_set_selectable (CC_EDITABLE_ENTRY (widget), TRUE); - /* Add unlock button */ priv->permission = (GPermission *)polkit_permission_new_sync ( @@ -2694,6 +3286,9 @@ Please check your installation"); populate_allowed_users_list (self); attach_to_cups_notifier (self); + priv->getting_all_ppds = TRUE; + get_all_ppds_async (get_all_ppds_async_cb, self); + http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ()); if (!http) { diff --git a/panels/printers/pp-ppd-selection-dialog.c b/panels/printers/pp-ppd-selection-dialog.c new file mode 100644 index 000000000..2498319b5 --- /dev/null +++ b/panels/printers/pp-ppd-selection-dialog.c @@ -0,0 +1,444 @@ +/* -*- 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 3 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Marek Kasik + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#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 *parent; + GtkWidget *dialog; + + UserResponseCallback user_callback; + gpointer user_data; + + gchar *ppd_name; + GtkResponseType response; + gchar *manufacturer; + + PPDList *list; +}; + +static void +manufacturer_selection_changed_cb (GtkTreeSelection *selection, + gpointer user_data) +{ + PpPPDSelectionDialog *dialog = (PpPPDSelectionDialog *) user_data; + GtkListStore *store; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeView *models_treeview; + gchar *manufacturer_name = NULL; + gint i, index; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, + PPD_MANUFACTURERS_NAMES_COLUMN, &manufacturer_name, + -1); + } + + if (manufacturer_name) + { + index = -1; + for (i = 0; i < dialog->list->num_of_manufacturers; i++) + { + if (g_strcmp0 (manufacturer_name, + dialog->list->manufacturers[i]->manufacturer_name) == 0) + { + index = i; + break; + } + } + + if (index >= 0) + { + models_treeview = (GtkTreeView*) + gtk_builder_get_object (dialog->builder, "ppd-selection-models-treeview"); + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + + for (i = 0; i < dialog->list->manufacturers[index]->num_of_ppds; i++) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + PPD_NAMES_COLUMN, dialog->list->manufacturers[index]->ppds[i]->ppd_name, + PPD_DISPLAY_NAMES_COLUMN, dialog->list->manufacturers[index]->ppds[i]->ppd_display_name, + -1); + } + + gtk_tree_view_set_model (models_treeview, GTK_TREE_MODEL (store)); + g_object_unref (store); + } + + g_free (manufacturer_name); + } +} + +static void +model_selection_changed_cb (GtkTreeSelection *selection, + gpointer user_data) +{ + PpPPDSelectionDialog *dialog = (PpPPDSelectionDialog *) user_data; + GtkTreeModel *model; + GtkTreeIter iter; + GtkWidget *widget; + gchar *model_name = NULL; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, + PPD_NAMES_COLUMN, &model_name, + -1); + } + + widget = (GtkWidget*) + gtk_builder_get_object (dialog->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 *dialog) +{ + GtkTreeSelection *selection; + GtkListStore *store; + GtkTreePath *path; + GtkTreeView *treeview; + GtkTreeIter iter; + GtkTreeIter *preselect_iter = NULL; + GtkWidget *widget; + gint i; + + widget = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "ppd-spinner"); + gtk_widget_hide (widget); + gtk_spinner_stop (GTK_SPINNER (widget)); + + widget = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "progress-label"); + gtk_widget_hide (widget); + + treeview = (GtkTreeView*) + gtk_builder_get_object (dialog->builder, "ppd-selection-manufacturers-treeview"); + + if (dialog->list) + { + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + + for (i = 0; i < dialog->list->num_of_manufacturers; i++) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + PPD_MANUFACTURERS_NAMES_COLUMN, dialog->list->manufacturers[i]->manufacturer_name, + PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN, dialog->list->manufacturers[i]->manufacturer_display_name, + -1); + + if (g_strcmp0 (dialog->manufacturer, + dialog->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 *dialog) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeView *manufacturers_treeview; + GtkTreeView *models_treeview; + GtkWidget *widget; + + manufacturers_treeview = (GtkTreeView*) + gtk_builder_get_object (dialog->builder, "ppd-selection-manufacturers-treeview"); + + renderer = gtk_cell_renderer_text_new (); + + /* Translators: Name of column showing printer manufacturers */ + column = gtk_tree_view_column_new_with_attributes (_("Manufacturers"), renderer, + "text", PPD_MANUFACTURERS_DISPLAY_NAMES_COLUMN, NULL); + gtk_tree_view_column_set_expand (column, TRUE); + gtk_tree_view_append_column (manufacturers_treeview, column); + + + models_treeview = (GtkTreeView*) + gtk_builder_get_object (dialog->builder, "ppd-selection-models-treeview"); + + renderer = gtk_cell_renderer_text_new (); + + /* Translators: Name of column showing printer drivers */ + column = gtk_tree_view_column_new_with_attributes (_("Drivers"), renderer, + "text", PPD_DISPLAY_NAMES_COLUMN, NULL); + gtk_tree_view_column_set_expand (column, TRUE); + 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), dialog); + + g_signal_connect (gtk_tree_view_get_selection (manufacturers_treeview), + "changed", G_CALLBACK (manufacturer_selection_changed_cb), dialog); + + gtk_widget_show_all (dialog->dialog); + + if (!dialog->list) + { + widget = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "ppd-spinner"); + gtk_widget_show (widget); + gtk_spinner_start (GTK_SPINNER (widget)); + + widget = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "progress-label"); + gtk_widget_show (widget); + } + else + { + fill_ppds_list (dialog); + } +} + +static void +ppd_selection_dialog_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + PpPPDSelectionDialog *ppd_selection_dialog = (PpPPDSelectionDialog*) user_data; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeView *models_treeview; + GtkTreeIter iter; + + pp_ppd_selection_dialog_hide (ppd_selection_dialog); + + ppd_selection_dialog->response = response_id; + if (response_id == GTK_RESPONSE_OK) + { + models_treeview = (GtkTreeView*) + gtk_builder_get_object (ppd_selection_dialog->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, &ppd_selection_dialog->ppd_name, + -1); + } + } + } + } + + ppd_selection_dialog->user_callback (GTK_DIALOG (ppd_selection_dialog->dialog), + response_id, + ppd_selection_dialog->user_data); +} + +static void +update_alignment_padding (GtkWidget *widget, + GtkAllocation *allocation, + gpointer user_data) +{ + PpPPDSelectionDialog *dialog = (PpPPDSelectionDialog*) user_data; + GtkAllocation allocation2; + GtkWidget *action_area; + gint offset_left, offset_right; + guint padding_left, padding_right, + padding_top, padding_bottom; + + action_area = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "dialog-action-area1"); + gtk_widget_get_allocation (action_area, &allocation2); + + offset_left = allocation2.x - allocation->x; + offset_right = (allocation->x + allocation->width) - + (allocation2.x + allocation2.width); + + gtk_alignment_get_padding (GTK_ALIGNMENT (widget), + &padding_top, &padding_bottom, + &padding_left, &padding_right); + if (allocation->x >= 0 && allocation2.x >= 0) + { + if (offset_left > 0 && offset_left != padding_left) + gtk_alignment_set_padding (GTK_ALIGNMENT (widget), + padding_top, padding_bottom, + offset_left, padding_right); + + gtk_alignment_get_padding (GTK_ALIGNMENT (widget), + &padding_top, &padding_bottom, + &padding_left, &padding_right); + if (offset_right > 0 && offset_right != padding_right) + gtk_alignment_set_padding (GTK_ALIGNMENT (widget), + padding_top, padding_bottom, + padding_left, offset_right); + } +} + +PpPPDSelectionDialog * +pp_ppd_selection_dialog_new (GtkWindow *parent, + PPDList *ppd_list, + gchar *manufacturer, + UserResponseCallback user_callback, + gpointer user_data) +{ + PpPPDSelectionDialog *dialog; + GtkWidget *widget; + GError *error = NULL; + gchar *objects[] = { "ppd-selection-dialog", NULL }; + guint builder_result; + + dialog = g_new0 (PpPPDSelectionDialog, 1); + + dialog->builder = gtk_builder_new (); + dialog->parent = GTK_WIDGET (parent); + + builder_result = gtk_builder_add_objects_from_file (dialog->builder, + DATADIR"/ppd-selection-dialog.ui", + objects, &error); + + if (builder_result == 0) + { + g_warning ("Could not load ui: %s", error->message); + g_error_free (error); + return NULL; + } + + dialog->dialog = (GtkWidget *) gtk_builder_get_object (dialog->builder, "ppd-selection-dialog"); + dialog->user_callback = user_callback; + dialog->user_data = user_data; + + dialog->response = GTK_RESPONSE_NONE; + dialog->list = ppd_list_copy (ppd_list); + + dialog->manufacturer = get_standard_manufacturers_name (manufacturer); + + /* connect signals */ + g_signal_connect (dialog->dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); + g_signal_connect (dialog->dialog, "response", G_CALLBACK (ppd_selection_dialog_response_cb), dialog); + + widget = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "content-alignment"); + g_signal_connect (widget, "size-allocate", G_CALLBACK (update_alignment_padding), dialog); + + widget = (GtkWidget*) + gtk_builder_get_object (dialog->builder, "ppd-spinner"); + gtk_spinner_start (GTK_SPINNER (widget)); + + populate_dialog (dialog); + + gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), GTK_WINDOW (parent)); + gtk_window_present (GTK_WINDOW (dialog->dialog)); + gtk_widget_show_all (GTK_WIDGET (dialog->dialog)); + + return dialog; +} + +void +pp_ppd_selection_dialog_free (PpPPDSelectionDialog *dialog) +{ + gtk_widget_destroy (GTK_WIDGET (dialog->dialog)); + + g_object_unref (dialog->builder); + + g_free (dialog->ppd_name); + + g_free (dialog->manufacturer); + + g_free (dialog); +} + +gchar * +pp_ppd_selection_dialog_get_ppd_name (PpPPDSelectionDialog *dialog) +{ + return g_strdup (dialog->ppd_name); +} + +void +pp_ppd_selection_dialog_set_ppd_list (PpPPDSelectionDialog *dialog, + PPDList *list) +{ + dialog->list = list; + fill_ppds_list (dialog); +} + +static void +pp_ppd_selection_dialog_hide (PpPPDSelectionDialog *dialog) +{ + gtk_widget_hide (GTK_WIDGET (dialog->dialog)); +} diff --git a/panels/printers/pp-ppd-selection-dialog.h b/panels/printers/pp-ppd-selection-dialog.h new file mode 100644 index 000000000..2ea05d096 --- /dev/null +++ b/panels/printers/pp-ppd-selection-dialog.h @@ -0,0 +1,46 @@ +/* -*- 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 3 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Marek Kasik + */ + +#ifndef __PP_PPD_SELECTION_DIALOG_H__ +#define __PP_PPD_SELECTION_DIALOG_H__ + +#include +#include "pp-utils.h" + +G_BEGIN_DECLS + +typedef struct _PpPPDSelectionDialog PpPPDSelectionDialog; + +typedef void (*UserResponseCallback) (GtkDialog *dialog, gint response_id, gpointer user_data); + +PpPPDSelectionDialog *pp_ppd_selection_dialog_new (GtkWindow *parent, + PPDList *ppd_list, + gchar *manufacturer, + UserResponseCallback user_callback, + gpointer user_data); +gchar *pp_ppd_selection_dialog_get_ppd_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 + +#endif diff --git a/panels/printers/pp-utils.c b/panels/printers/pp-utils.c index 5263af609..c78b70f97 100644 --- a/panels/printers/pp-utils.c +++ b/panels/printers/pp-utils.c @@ -35,6 +35,8 @@ #define SCP_PATH "/org/fedoraproject/Config/Printing" #define SCP_IFACE "org.fedoraproject.Config.Printing" +#define DBUS_TIMEOUT 120000 + gchar * get_tag_value (const gchar *tag_string, const gchar *tag_name) { @@ -2560,3 +2562,1636 @@ printer_set_default_media_size (const gchar *printer_name) g_error_free (error); } } + + +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; + gchar *printer_uri; + 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 (response->request.status.status_code <= IPP_OK_CONFLICT) + { + for (j = 0; j < length; j++) + { + attr = ippFindAttribute (response, requested_attrs[j], IPP_TAG_ZERO); + if (attr && attr->num_values > 0 && attr->value_tag != IPP_TAG_NOVALUE) + { + IPPAttribute *attribute; + + attribute = g_new0 (IPPAttribute, 1); + attribute->attribute_name = g_strdup (requested_attrs[j]); + attribute->attribute_values = g_new0 (IPPAttributeValue, attr->num_values); + attribute->num_of_values = attr->num_values; + + if (attr->value_tag == IPP_TAG_INTEGER || + attr->value_tag == IPP_TAG_ENUM) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_INTEGER; + + for (i = 0; i < attr->num_values; i++) + attribute->attribute_values[i].integer_value = attr->values[i].integer; + } + else if (attr->value_tag == IPP_TAG_NAME || + attr->value_tag == IPP_TAG_STRING || + attr->value_tag == IPP_TAG_TEXT || + attr->value_tag == IPP_TAG_URI || + attr->value_tag == IPP_TAG_KEYWORD || + attr->value_tag == IPP_TAG_URISCHEME) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_STRING; + + for (i = 0; i < attr->num_values; i++) + attribute->attribute_values[i].string_value = g_strdup (attr->values[i].string.text); + } + else if (attr->value_tag == IPP_TAG_RANGE) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_RANGE; + + for (i = 0; i < attr->num_values; i++) + { + attribute->attribute_values[i].lower_range = attr->values[i].range.lower; + attribute->attribute_values[i].upper_range = attr->values[i].range.upper; + } + } + else if (attr->value_tag == IPP_TAG_BOOLEAN) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_BOOLEAN; + + for (i = 0; i < attr->num_values; i++) + attribute->attribute_values[i].boolean_value = attr->values[i].boolean; + } + + 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); + + g_free (printer_uri); + + 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; + 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); + + g_error_free (error); + 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) +{ + GVariant *output; + gboolean result = FALSE; + PSPData *data = (PSPData *) user_data; + 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 ("%s", ret_error); + else + result = TRUE; + + g_variant_unref (output); + } + else + { + if (error->code != G_IO_ERROR_CANCELLED) + g_warning ("%s", error->message); + g_error_free (error); + } + + data->callback (g_strdup (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; + 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); + g_error_free (error); + 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 (g_strdup (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; + 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); + g_error_free (error); + 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); + g_error_free (error); + 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 (g_strdup (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 (g_strdup (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; + gchar *ppd_filename; + gint i; + + data->result = g_new0 (gchar *, g_strv_length (data->ppds_names) + 1); + for (i = 0; data->ppds_names[i]; i++) + { + 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); + g_free (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; + GError *error = NULL; + + if (!ppds_names || !attribute_name) + callback (NULL, user_data); + + 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); + + g_error_free (error); + 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) +{ + GVariant *output; + PPDName *ppd_item; + PPDName **result = NULL; + GPNData *data = (GPNData *) user_data; + 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) + { + GVariant *array; + + g_variant_get (output, "(@a(ss))", + &array); + + if (array) + { + GVariantIter *iter; + GVariant *item; + gchar *driver; + gchar *match; + + for (j = 0; j < G_N_ELEMENTS (match_levels) && n < data->count; j++) + { + g_variant_get (array, + "a(ss)", + &iter); + + while ((item = g_variant_iter_next_value (iter))) + { + g_variant_get (item, + "(ss)", + &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++; + } + + g_free (driver); + g_free (match); + g_variant_unref (item); + } + } + + g_variant_unref (array); + } + + g_variant_unref (output); + } + else + { + if (error->code != G_IO_ERROR_CANCELLED) + g_warning ("%s", error->message); + g_error_free (error); + } + + 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; + 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); + g_error_free (error); + 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, + 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); +} + +static void +get_device_attributes_async_dbus_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) + +{ + GVariant *output; + GDAData *data = (GDAData *) user_data; + 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; + GVariant *devices_variant = NULL; + + g_variant_get (output, "(&s@a{ss})", + &ret_error, + &devices_variant); + + if (ret_error[0] != '\0') + { + g_warning ("%s", ret_error); + } + + if (devices_variant) + { + GVariantIter *iter; + GVariant *item; + gint index = -1; + + if (data->device_uri) + { + gchar *key; + gchar *value; + gchar *number; + gchar *endptr; + gchar *suffix; + + g_variant_get (devices_variant, + "a{ss}", + &iter); + + while ((item = g_variant_iter_next_value (iter))) + { + g_variant_get (item, + "{ss}", + &key, + &value); + + if (g_str_equal (value, data->device_uri)) + { + number = g_strrstr (key, ":"); + if (number != NULL) + { + number++; + index = g_ascii_strtoll (number, &endptr, 10); + if (index == 0 && endptr == (number)) + index = -1; + } + } + + g_free (key); + g_free (value); + g_variant_unref (item); + } + + suffix = g_strdup_printf (":%d", index); + + g_variant_get (devices_variant, + "a{ss}", + &iter); + + while ((item = g_variant_iter_next_value (iter))) + { + gchar *key; + gchar *value; + + g_variant_get (item, + "{ss}", + &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); + } + } + + g_free (key); + g_free (value); + g_variant_unref (item); + } + + g_free (suffix); + } + + g_variant_unref (devices_variant); + } + + g_variant_unref (output); + } + else + { + if (error->code != G_IO_ERROR_CANCELLED) + g_warning ("%s", error->message); + g_error_free (error); + } + + if (!device_id || !device_make_and_model) + { + GVariantBuilder include_scheme_builder; + + 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)) + { + 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_DBUS_CONNECTION (g_object_ref (source_object)), + 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, + user_data); + 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; + GError *error = NULL; + GList *tmp; + gint i; + const gchar *backends[] = + {"hpfax", "ncp", "beh", "bluetooth", "snmp", + "dnssd", "hp", "ipp", "lpd", "parallel", + "serial", "socket", "usb", NULL}; + + 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); + g_error_free (error); + goto out; + } + + for (i = 0; backends[i]; i++) + data->backend_list = g_list_prepend (data->backend_list, g_strdup (backends[i])); + + 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; + GAPCallback callback; + gpointer user_data; + GMainContext *context; +} GAPData; + +static gboolean +get_all_ppds_idle_cb (gpointer user_data) +{ + GAPData *data = (GAPData *) user_data; + + 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); + 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[] = { + { "apollo", "Apollo" }, + { "brother", "Brother" }, + { "canon", "Canon" }, + { "dell", "Dell" }, + { "epson", "Epson" }, + { "gelsprinter", "GelSprinter" }, + { "generic", "Generic" }, + { "gestetner", "Gestetner" }, + { "hewlett packard", "Hewlett-Packard" }, + { "hp", "Hewlett-Packard" }, + { "ibm", "IBM" }, + { "imagistics", "Imagistics" }, + { "infoprint", "InfoPrint" }, + { "infotec", "Infotec" }, + { "konica minolta", "Minolta" }, + { "kyocera", "Kyocera" }, + { "kyocera mita", "Kyocera" }, + { "lanier", "Lanier" }, + { "lexmark international", "Lexmark" }, + { "lexmark", "Lexmark" }, + { "minolta", "Minolta" }, + { "minolta qms", "Minolta" }, + { "nec", "NEC" }, + { "nrg", "NRG" }, + { "oce", "Oce" }, + { "oki", "Oki" }, + { "oki data corp", "Oki" }, + { "panasonic", "Panasonic" }, + { "ricoh", "Ricoh" }, + { "samsung", "Samsung" }, + { "savin", "Savin" }, + { "sharp", "Sharp" }, + { "sony", "Sony" }, + { "tektronix", "Tektronix" }, + { "toshiba tec corp.", "Toshiba" }, + { "xerox", "Xerox" }, +}; + +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 *ppd_make_and_model; + gchar *ppd_device_id; + gchar *ppd_name; + gchar *ppd_product; + gchar *mfg; + gchar *mfg_normalized; + gchar *mdl; + gchar *manufacturer_display_name; + gint i, j; + + request = ippNewRequest (CUPS_GET_PPDS); + response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/"); + + if (response && + response->request.status.status_code <= IPP_OK_CONFLICT) + { + /* + * This hash contains names of manufacturers as keys and + * values are GLists of PPD names. + */ + ppds_hash = g_hash_table_new (g_str_hash, g_str_equal); + + /* + * This hash contains all possible names of manufacturers as keys + * and values are just first occurences 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 = response->attrs; attr != NULL; attr = attr->next) + { + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + ppd_device_id = NULL; + ppd_make_and_model = NULL; + ppd_name = NULL; + ppd_product = NULL; + + while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER) + { + if (g_strcmp0 (attr->name, "ppd-device-id") == 0 && + attr->value_tag == IPP_TAG_TEXT) + ppd_device_id = attr->values[0].string.text; + else if (g_strcmp0 (attr->name, "ppd-make-and-model") == 0 && + attr->value_tag == IPP_TAG_TEXT) + ppd_make_and_model = attr->values[0].string.text; + else if (g_strcmp0 (attr->name, "ppd-name") == 0 && + attr->value_tag == IPP_TAG_NAME) + ppd_name = attr->values[0].string.text; + else if (g_strcmp0 (attr->name, "ppd-product") == 0 && + attr->value_tag == IPP_TAG_TEXT) + ppd_product = attr->values[0].string.text; + + attr = attr->next; + } + + if (ppd_make_and_model && ppd_name && ppd_product && ppd_device_id) + { + mfg = get_tag_value (ppd_device_id, "mfg"); + if (!mfg) + mfg = get_tag_value (ppd_device_id, "manufacturer"); + mfg_normalized = normalize (mfg); + + if (mfg_normalized) + { + manufacturer_display_name = g_hash_table_lookup (manufacturers_hash, mfg_normalized); + if (!manufacturer_display_name) + { + g_hash_table_insert (manufacturers_hash, mfg_normalized, mfg); + } + else + { + g_free (mfg_normalized); + mfg_normalized = normalize (manufacturer_display_name); + } + + item = g_new0 (PPDName, 1); + item->ppd_name = g_strdup (ppd_name); + + mdl = get_tag_value (ppd_device_id, "mdl"); + if (!mdl) + mdl = get_tag_value (ppd_device_id, "model"); + + if (!item->ppd_display_name && + ppd_make_and_model && + ppd_make_and_model[0] != '\0') + { + item->ppd_display_name = g_strdup (ppd_make_and_model); + } + + if (!item->ppd_display_name && + ppd_product && + ppd_product[0] != '\0') + { + item->ppd_display_name = g_strdup (ppd_product); + } + + if (!item->ppd_display_name && + mdl && mdl[0] != '\0') + { + item->ppd_display_name = mdl; + } + else + { + g_free (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, 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 (GAPCallback callback, + gpointer user_data) +{ + GAPData *data; + GThread *thread; + GError *error = NULL; + + data = g_new0 (GAPData, 1); + 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); + + g_error_free (error); + 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 (gchar *name) +{ + gchar *normalized_name; + gchar *result = NULL; + gint i; + + if (name) + { + 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) + { + result = g_strdup (manufacturers_names[i].display_name); + break; + } + } + + g_free (normalized_name); + } + + return result; +} diff --git a/panels/printers/pp-utils.h b/panels/printers/pp-utils.h index f5ea27f38..8eeacedc8 100644 --- a/panels/printers/pp-utils.h +++ b/panels/printers/pp-utils.h @@ -40,9 +40,24 @@ enum 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); @@ -116,6 +131,81 @@ gchar *printer_get_hostname (cups_ptype_t printer_type, void printer_set_default_media_size (const gchar *printer_name); +typedef void (*PSPCallback) (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 (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 (gchar *name); + G_END_DECLS #endif /* __PP_UTILS_H */ diff --git a/panels/printers/ppd-selection-dialog.ui b/panels/printers/ppd-selection-dialog.ui new file mode 100644 index 000000000..e0def830b --- /dev/null +++ b/panels/printers/ppd-selection-dialog.ui @@ -0,0 +1,206 @@ + + + + + 500 + 350 + False + 5 + + True + True + dialog + + + True + False + vertical + 10 + + + True + False + end + + + Cancel + False + True + True + True + False + + + False + True + 0 + + + + + Select + False + True + False + True + True + False + + + False + True + 1 + + + + + True + False + + + 24 + 24 + True + False + + + False + True + + + + + False + True + 0 + + + + + False + True + Loading drivers database... + + + False + True + 10 + 1 + + + + + False + True + 2 + True + + + + + False + True + end + 0 + + + + + True + False + + + True + False + vertical + 10 + + + True + False + + + 140 + True + True + never + in + + + True + True + + + + + + + + False + True + 0 + + + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + True + True + end + 0 + + + + + True + False + 0 + Select Printer Driver + + + + + + False + False + 1 + + + + + + + True + True + 1 + + + + + + ppd-selection-cancel-button + ppd-selection-select-button + + + + diff --git a/panels/printers/printers.ui b/panels/printers/printers.ui index ff9157c52..75b148399 100644 --- a/panels/printers/printers.ui +++ b/panels/printers/printers.ui @@ -315,17 +315,84 @@ - + True - 0 - --- + True + False + + + False + True + True + True + False + none + 0 + + + + + True + False + page 1 + + + False + + + + + True + True + False + 0 + label + + + 1 + + + + + True + False + page 2 + + + 1 + False + + + + + True + False + False + 0 + 10 + Setting new driver... + + + 2 + + + + + True + False + page 3 + + + 2 + False + + 1 3 3 4 - GTK_FILL