diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/print-notifications/gsd-print-notifications-manager.c | 1725 | ||||
-rw-r--r-- | plugins/print-notifications/gsd-print-notifications-manager.h | 38 | ||||
-rw-r--r-- | plugins/print-notifications/gsd-printer.c | 1403 | ||||
-rw-r--r-- | plugins/print-notifications/main.c | 7 | ||||
-rw-r--r-- | plugins/print-notifications/meson.build | 37 |
5 files changed, 3210 insertions, 0 deletions
diff --git a/plugins/print-notifications/gsd-print-notifications-manager.c b/plugins/print-notifications/gsd-print-notifications-manager.c new file mode 100644 index 0000000..4e0b3ab --- /dev/null +++ b/plugins/print-notifications/gsd-print-notifications-manager.c @@ -0,0 +1,1725 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 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 <sys/types.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <locale.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include <cups/cups.h> +#include <cups/ppd.h> +#include <libnotify/notify.h> + +#include "gnome-settings-profile.h" +#include "gsd-print-notifications-manager.h" + +#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 RENEW_INTERVAL 3500 +#define SUBSCRIPTION_DURATION 3600 +#define CONNECTING_TIMEOUT 60 +#define REASON_TIMEOUT 15000 +#define CUPS_CONNECTION_TEST_INTERVAL 300 +#define CHECK_INTERVAL 60 /* secs */ +#define AUTHENTICATION_CHECK_TIMEOUT 3 + +#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5) +#define HAVE_CUPS_1_6 1 +#endif + +#ifndef HAVE_CUPS_1_6 +#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 ippGetName(attr) attr->name +#define ippGetCount(attr) attr->num_values +#define ippGetBoolean(attr, index) attr->values[index].boolean + +static ipp_attribute_t * +ippNextAttribute (ipp_t *ipp) +{ + if (!ipp || !ipp->current) + return (NULL); + return (ipp->current = ipp->current->next); +} +#endif + +struct _GsdPrintNotificationsManager +{ + GObject parent; + + GDBusConnection *cups_bus_connection; + gint subscription_id; + cups_dest_t *dests; + gint num_dests; + gboolean scp_handler_spawned; + GPid scp_handler_pid; + GList *timeouts; + GHashTable *printing_printers; + GList *active_notifications; + guint cups_connection_timeout_id; + guint check_source_id; + guint cups_dbus_subscription_id; + guint renew_source_id; + gint last_notify_sequence_number; + guint start_idle_id; + GList *held_jobs; +}; + +static void gsd_print_notifications_manager_class_init (GsdPrintNotificationsManagerClass *klass); +static void gsd_print_notifications_manager_init (GsdPrintNotificationsManager *print_notifications_manager); +static void gsd_print_notifications_manager_finalize (GObject *object); +static gboolean cups_connection_test (gpointer user_data); +static gboolean process_new_notifications (gpointer user_data); + +G_DEFINE_TYPE (GsdPrintNotificationsManager, gsd_print_notifications_manager, G_TYPE_OBJECT) + +static gpointer manager_object = NULL; + +static const char * +password_cb (const char *prompt, + http_t *http, + const char *method, + const char *resource, + void *user_data) +{ + return NULL; +} + +static 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; + + 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: + return ret; +} + +static gboolean +is_cupsbrowsed_dest (const char *name) +{ + const char *val = NULL; + gboolean is_cupsbrowsed = FALSE; + cups_dest_t *found_dest = NULL; + + found_dest = cupsGetNamedDest (CUPS_HTTP_DEFAULT, name, NULL); + if (found_dest == NULL) { + goto out; + } + + val = cupsGetOption ("cups-browsed", found_dest->num_options, found_dest->options); + if (val == NULL) { + goto out; + } + + if (g_str_equal (val, "yes") || g_str_equal (val, "on") || g_str_equal (val, "true")) { + is_cupsbrowsed = TRUE; + } +out: + if (found_dest != NULL) { + cupsFreeDests (1, found_dest); + } + + return is_cupsbrowsed; +} + +static gboolean +is_local_dest (const char *name, + cups_dest_t *dests, + int num_dests) +{ + char *type_str; + cups_ptype_t type; + gboolean is_remote; + + is_remote = TRUE; + + type_str = get_dest_attr (name, "printer-type", dests, num_dests); + if (type_str == NULL) { + goto out; + } + + type = atoi (type_str); + is_remote = type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT); + g_free (type_str); + out: + return !is_remote; +} + +static gboolean +server_is_local (const gchar *server_name) +{ + if (server_name != NULL && + (g_ascii_strncasecmp (server_name, "localhost", 9) == 0 || + g_ascii_strncasecmp (server_name, "127.0.0.1", 9) == 0 || + g_ascii_strncasecmp (server_name, "::1", 3) == 0 || + server_name[0] == '/')) { + return TRUE; + } else { + return FALSE; + } +} + +static int +strcmp0(const void *a, const void *b) +{ + return g_strcmp0 (*((gchar **) a), *((gchar **) b)); +} + +struct +{ + gchar *printer_name; + gchar *primary_text; + gchar *secondary_text; + guint timeout_id; + GsdPrintNotificationsManager *manager; +} typedef TimeoutData; + +struct +{ + gchar *printer_name; + gchar *reason; + NotifyNotification *notification; + gulong notification_close_id; + GsdPrintNotificationsManager *manager; +} typedef ReasonData; + +struct +{ + guint job_id; + gchar *printer_name; + guint timeout_id; +} typedef HeldJob; + +static void +free_timeout_data (gpointer user_data) +{ + TimeoutData *data = (TimeoutData *) user_data; + + if (data) { + g_free (data->printer_name); + g_free (data->primary_text); + g_free (data->secondary_text); + g_free (data); + } +} + +static void +free_reason_data (gpointer user_data) +{ + ReasonData *data = (ReasonData *) user_data; + + if (data) { + if (data->notification_close_id > 0 && + g_signal_handler_is_connected (data->notification, + data->notification_close_id)) + g_signal_handler_disconnect (data->notification, data->notification_close_id); + + g_object_unref (data->notification); + + g_free (data->printer_name); + g_free (data->reason); + + g_free (data); + } +} + +static void +free_held_job (gpointer user_data) +{ + HeldJob *job = (HeldJob *) user_data; + + if (job != NULL) { + g_free (job->printer_name); + g_free (job); + } +} + +static void +notification_closed_cb (NotifyNotification *notification, + gpointer user_data) +{ + ReasonData *data = (ReasonData *) user_data; + + if (data) { + data->manager->active_notifications = + g_list_remove (data->manager->active_notifications, data); + + free_reason_data (data); + } +} + +static gboolean +show_notification (gpointer user_data) +{ + NotifyNotification *notification; + TimeoutData *data = (TimeoutData *) user_data; + ReasonData *reason_data; + GList *tmp; + + if (!data) + return FALSE; + + notification = notify_notification_new (data->primary_text, + data->secondary_text, + "printer-symbolic"); + + notify_notification_set_app_name (notification, _("Printers")); + notify_notification_set_hint (notification, + "resident", + g_variant_new_boolean (TRUE)); + notify_notification_set_timeout (notification, REASON_TIMEOUT); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel"); + + reason_data = g_new0 (ReasonData, 1); + reason_data->printer_name = g_strdup (data->printer_name); + reason_data->reason = g_strdup ("connecting-to-device"); + reason_data->notification = notification; + reason_data->manager = data->manager; + + reason_data->notification_close_id = + g_signal_connect (notification, + "closed", + G_CALLBACK (notification_closed_cb), + reason_data); + + reason_data->manager->active_notifications = + g_list_append (reason_data->manager->active_notifications, reason_data); + + notify_notification_show (notification, NULL); + + tmp = g_list_find (data->manager->timeouts, data); + if (tmp) { + data->manager->timeouts = g_list_remove_link (data->manager->timeouts, tmp); + g_list_free_full (tmp, free_timeout_data); + } + + return FALSE; +} + +static gboolean +reason_is_blacklisted (const gchar *reason) +{ + if (g_str_equal (reason, "none")) + return TRUE; + + if (g_str_equal (reason, "other")) + return TRUE; + + if (g_str_equal (reason, "com.apple.print.recoverable")) + return TRUE; + + /* https://bugzilla.redhat.com/show_bug.cgi?id=883401 */ + if (g_str_has_prefix (reason, "cups-remote-")) + return TRUE; + + /* https://bugzilla.redhat.com/show_bug.cgi?id=1207154 */ + if (g_str_equal (reason, "cups-waiting-for-job-completed")) + return TRUE; + + return FALSE; +} + +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) +{ + /* Ignore any signal starting with Server*. This has caused a message + * storm through ServerAudit messages in the past, see + * https://gitlab.gnome.org/GNOME/gnome-settings-daemon/issues/62 + */ + if (!signal_name || (strncmp (signal_name, "Server", 6) == 0)) + return; + + process_new_notifications (user_data); +} + +static gchar * +get_statuses_second (guint i, + const gchar *printer_name) +{ + gchar *status; + + switch (i) { + case 0: + /* Translators: The printer is low on toner (same as in system-config-printer) */ + status = g_strdup_printf (_("Printer “%s” is low on toner."), printer_name); + break; + case 1: + /* Translators: The printer has no toner left (same as in system-config-printer) */ + status = g_strdup_printf (_("Printer “%s” has no toner left."), printer_name); + break; + case 2: + /* Translators: The printer is in the process of connecting to a shared network output device (same as in system-config-printer) */ + status = g_strdup_printf (_("Printer “%s” may not be connected."), printer_name); + break; + case 3: + /* Translators: One or more covers on the printer are open (same as in system-config-printer) */ + status = g_strdup_printf (_("The cover is open on printer “%s”."), printer_name); + break; + case 4: + /* Translators: A filter or backend is not installed (same as in system-config-printer) */ + status = g_strdup_printf (_("There is a missing print filter for " + "printer “%s”."), printer_name); + break; + case 5: + /* Translators: One or more doors on the printer are open (same as in system-config-printer) */ + status = g_strdup_printf (_("The door is open on printer “%s”."), printer_name); + break; + case 6: + /* Translators: "marker" is one color bin of the printer */ + status = g_strdup_printf (_("Printer “%s” is low on a marker supply."), printer_name); + break; + case 7: + /* Translators: "marker" is one color bin of the printer */ + status = g_strdup_printf (_("Printer “%s” is out of a marker supply."), printer_name); + break; + case 8: + /* Translators: At least one input tray is low on media (same as in system-config-printer) */ + status = g_strdup_printf (_("Printer “%s” is low on paper."), printer_name); + break; + case 9: + /* Translators: At least one input tray is empty (same as in system-config-printer) */ + status = g_strdup_printf (_("Printer “%s” is out of paper."), printer_name); + break; + case 10: + /* Translators: The printer is offline (same as in system-config-printer) */ + status = g_strdup_printf (_("Printer “%s” is currently off-line."), printer_name); + break; + case 11: + /* Translators: The printer has detected an error (same as in system-config-printer) */ + status = g_strdup_printf (_("There is a problem on printer “%s”."), printer_name); + break; + default: + g_assert_not_reached (); + } + + return status; +} + +static void +authenticate_cb (NotifyNotification *notification, + gchar *action, + gpointer user_data) +{ + GAppInfo *app_info; + gboolean ret; + GError *error = NULL; + gchar *commandline; + gchar *printer_name = user_data; + + if (g_strcmp0 (action, "default") == 0) { + notify_notification_close (notification, NULL); + + commandline = g_strdup_printf (BINDIR "/gnome-control-center printers show-jobs %s", printer_name); + app_info = g_app_info_create_from_commandline (commandline, + "gnome-control-center", + G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION, + &error); + g_free (commandline); + + if (app_info != NULL) { + ret = g_app_info_launch (app_info, + NULL, + NULL, + &error); + + if (!ret) { + g_warning ("failed to launch gnome-control-center: %s", error->message); + g_error_free (error); + } + + g_object_unref (app_info); + } else { + g_warning ("failed to create application info: %s", error->message); + g_error_free (error); + } + } +} + +static void +unref_notification (NotifyNotification *notification, + gpointer data) +{ + g_object_unref (notification); +} + +static gint +check_job_for_authentication (gpointer userdata) +{ + GsdPrintNotificationsManager *manager = userdata; + ipp_attribute_t *attr; + static gchar *requested_attributes[] = { "job-state-reasons", "job-hold-until", NULL }; + gboolean needs_authentication = FALSE; + HeldJob *job; + gchar *primary_text; + gchar *secondary_text; + gchar *job_uri; + ipp_t *request, *response; + gint i; + + if (manager->held_jobs != NULL) { + job = (HeldJob *) manager->held_jobs->data; + + manager->held_jobs = g_list_delete_link (manager->held_jobs, + manager->held_jobs); + + request = ippNewRequest (IPP_GET_JOB_ATTRIBUTES); + + job_uri = g_strdup_printf ("ipp://localhost/jobs/%u", job->job_id); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "job-uri", NULL, job_uri); + g_free (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", 2, NULL, (const char **) requested_attributes); + + response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/"); + if (response != NULL) { + if (ippGetStatusCode (response) <= IPP_OK_CONFLICT) { + if ((attr = ippFindAttribute (response, "job-state-reasons", IPP_TAG_ZERO)) != NULL) { + for (i = 0; i < ippGetCount (attr); i++) { + if (g_strcmp0 (ippGetString (attr, i, NULL), "cups-held-for-authentication") == 0) { + needs_authentication = TRUE; + break; + } + } + } + + if (!needs_authentication && (attr = ippFindAttribute (response, "job-hold-until", IPP_TAG_ZERO)) != NULL) { + if (g_strcmp0 (ippGetString (attr, 0, NULL), "auth-info-required") == 0) + needs_authentication = TRUE; + } + } + + ippDelete (response); + } + + if (needs_authentication) { + NotifyNotification *notification; + + /* Translators: The printer has a job to print but the printer needs authentication to continue with the print */ + primary_text = g_strdup_printf (_("%s Requires Authentication"), job->printer_name); + /* Translators: A printer needs credentials to continue printing a job */ + secondary_text = g_strdup_printf (_("Credentials required in order to print")); + + notification = notify_notification_new (primary_text, + secondary_text, + "printer-symbolic"); + notify_notification_set_app_name (notification, _("Printers")); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel"); + notify_notification_add_action (notification, + "default", + /* This is a default action so the label won't be shown */ + "Authenticate", + authenticate_cb, + g_strdup (job->printer_name), g_free); + g_signal_connect (notification, "closed", G_CALLBACK (unref_notification), NULL); + + notify_notification_show (notification, NULL); + + g_free (primary_text); + g_free (secondary_text); + } + + free_held_job (job); + } + + return G_SOURCE_REMOVE; +} + +static void +process_cups_notification (GsdPrintNotificationsManager *manager, + const char *notify_subscribed_event, + const char *notify_text, + const char *notify_printer_uri, + const char *printer_name, + gint printer_state, + const char *printer_state_reasons, + gboolean printer_is_accepting_jobs, + guint notify_job_id, + gint job_state, + const char *job_state_reasons, + const char *job_name, + gint job_impressions_completed) +{ + ipp_attribute_t *attr; + gboolean my_job = FALSE; + gboolean known_reason; + HeldJob *held_job; + http_t *http; + gchar *primary_text = NULL; + gchar *secondary_text = NULL; + gchar *job_uri = NULL; + ipp_t *request, *response; + static const char * const reasons[] = { + "toner-low", + "toner-empty", + "connecting-to-device", + "cover-open", + "cups-missing-filter", + "door-open", + "marker-supply-low", + "marker-supply-empty", + "media-low", + "media-empty", + "offline", + "other"}; + + static const char * statuses_first[] = { + /* Translators: The printer is low on toner (same as in system-config-printer) */ + N_("Toner low"), + /* Translators: The printer has no toner left (same as in system-config-printer) */ + N_("Toner empty"), + /* Translators: The printer is in the process of connecting to a shared network output device (same as in system-config-printer) */ + N_("Not connected?"), + /* Translators: One or more covers on the printer are open (same as in system-config-printer) */ + N_("Cover open"), + /* Translators: A filter or backend is not installed (same as in system-config-printer) */ + N_("Printer configuration error"), + /* Translators: One or more doors on the printer are open (same as in system-config-printer) */ + N_("Door open"), + /* Translators: "marker" is one color bin of the printer */ + N_("Marker supply low"), + /* Translators: "marker" is one color bin of the printer */ + N_("Out of a marker supply"), + /* Translators: At least one input tray is low on media (same as in system-config-printer) */ + N_("Paper low"), + /* Translators: At least one input tray is empty (same as in system-config-printer) */ + N_("Out of paper"), + /* Translators: The printer is offline (same as in system-config-printer) */ + N_("Printer off-line"), + /* Translators: The printer has detected an error (same as in system-config-printer) */ + N_("Printer error") }; + + if (g_strcmp0 (notify_subscribed_event, "printer-added") != 0 && + g_strcmp0 (notify_subscribed_event, "printer-deleted") != 0 && + g_strcmp0 (notify_subscribed_event, "printer-state-changed") != 0 && + g_strcmp0 (notify_subscribed_event, "job-completed") != 0 && + g_strcmp0 (notify_subscribed_event, "job-state-changed") != 0 && + g_strcmp0 (notify_subscribed_event, "job-created") != 0) + return; + + if (notify_job_id > 0) { + if ((http = httpConnectEncrypt (cupsServer (), ippPort (), + cupsEncryption ())) == NULL) { + g_debug ("Connection to CUPS server \'%s\' failed.", cupsServer ()); + } else { + job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", notify_job_id); + + 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 ()); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", NULL, "job-originating-user-name"); + response = cupsDoRequest (http, request, "/"); + + if (response) { + if (ippGetStatusCode (response) <= IPP_OK_CONFLICT && + (attr = ippFindAttribute(response, "job-originating-user-name", + IPP_TAG_NAME))) { + if (g_strcmp0 (ippGetString (attr, 0, NULL), cupsUser ()) == 0) + my_job = TRUE; + } + ippDelete(response); + } + g_free (job_uri); + httpClose (http); + } + } + + if (g_strcmp0 (notify_subscribed_event, "printer-added") == 0) { + cupsFreeDests (manager->num_dests, manager->dests); + manager->num_dests = cupsGetDests (&manager->dests); + + if (is_local_dest (printer_name, + manager->dests, + manager->num_dests) && + !is_cupsbrowsed_dest (printer_name)) { + /* Translators: New printer has been added */ + primary_text = g_strdup (_("Printer added")); + secondary_text = g_strdup (printer_name); + } + } else if (g_strcmp0 (notify_subscribed_event, "printer-deleted") == 0) { + cupsFreeDests (manager->num_dests, manager->dests); + manager->num_dests = cupsGetDests (&manager->dests); + } else if (g_strcmp0 (notify_subscribed_event, "job-completed") == 0 && my_job) { + g_hash_table_remove (manager->printing_printers, + printer_name); + + switch (job_state) { + case IPP_JOB_PENDING: + case IPP_JOB_HELD: + case IPP_JOB_PROCESSING: + break; + case IPP_JOB_STOPPED: + /* Translators: A print job has been stopped */ + primary_text = g_strdup (C_("print job state", "Printing stopped")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_CANCELED: + /* Translators: A print job has been canceled */ + primary_text = g_strdup (C_("print job state", "Printing canceled")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_ABORTED: + /* Translators: A print job has been aborted */ + primary_text = g_strdup (C_("print job state", "Printing aborted")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_COMPLETED: + /* Translators: A print job has been completed */ + primary_text = g_strdup (C_("print job state", "Printing completed")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + } + } else if (g_strcmp0 (notify_subscribed_event, "job-state-changed") == 0 && my_job) { + switch (job_state) { + case IPP_JOB_PROCESSING: + g_hash_table_insert (manager->printing_printers, + g_strdup (printer_name), NULL); + + /* Translators: A job is printing */ + primary_text = g_strdup (C_("print job state", "Printing")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_STOPPED: + g_hash_table_remove (manager->printing_printers, + printer_name); + /* Translators: A print job has been stopped */ + primary_text = g_strdup (C_("print job state", "Printing stopped")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_CANCELED: + g_hash_table_remove (manager->printing_printers, + printer_name); + /* Translators: A print job has been canceled */ + primary_text = g_strdup (C_("print job state", "Printing canceled")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_ABORTED: + g_hash_table_remove (manager->printing_printers, + printer_name); + /* Translators: A print job has been aborted */ + primary_text = g_strdup (C_("print job state", "Printing aborted")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_COMPLETED: + g_hash_table_remove (manager->printing_printers, + printer_name); + /* Translators: A print job has been completed */ + primary_text = g_strdup (C_("print job state", "Printing completed")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + break; + case IPP_JOB_HELD: + held_job = g_new (HeldJob, 1); + held_job->job_id = notify_job_id; + held_job->printer_name = g_strdup (printer_name); + /* CUPS takes sometime to change the "job-state-reasons" to "cups-held-for-authentication" + after the job changes job-state to "held" state but this change is not signalized + by any event so we just check the job-state-reason (or job-hold-until) after some timeout */ + held_job->timeout_id = g_timeout_add_seconds (AUTHENTICATION_CHECK_TIMEOUT, check_job_for_authentication, manager); + + manager->held_jobs = g_list_append (manager->held_jobs, held_job); + break; + default: + break; + } + } else if (g_strcmp0 (notify_subscribed_event, "job-created") == 0 && my_job) { + if (job_state == IPP_JOB_PROCESSING) { + g_hash_table_insert (manager->printing_printers, + g_strdup (printer_name), NULL); + + /* Translators: A job is printing */ + primary_text = g_strdup (C_("print job state", "Printing")); + /* Translators: "print-job xy" on a printer */ + secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name); + } + } else if (g_strcmp0 (notify_subscribed_event, "printer-state-changed") == 0) { + cups_dest_t *dest = NULL; + const gchar *tmp_printer_state_reasons = NULL; + GSList *added_reasons = NULL; + GSList *tmp_list = NULL; + GList *tmp; + gchar **old_state_reasons = NULL; + gchar **new_state_reasons = NULL; + gint i, j; + + /* Remove timeout which shows notification about possible disconnection of printer + * if "connecting-to-device" has vanished. + */ + if (printer_state_reasons == NULL || + g_strrstr (printer_state_reasons, "connecting-to-device") == NULL) { + TimeoutData *data; + + for (tmp = manager->timeouts; tmp; tmp = g_list_next (tmp)) { + data = (TimeoutData *) tmp->data; + if (g_strcmp0 (printer_name, data->printer_name) == 0) { + g_source_remove (data->timeout_id); + manager->timeouts = g_list_remove_link (manager->timeouts, tmp); + g_list_free_full (tmp, free_timeout_data); + break; + } + } + } + + for (tmp = manager->active_notifications; tmp; tmp = g_list_next (tmp)) { + ReasonData *reason_data = (ReasonData *) tmp->data; + GList *remove_list; + + if (printer_state_reasons == NULL || + (g_strcmp0 (printer_name, reason_data->printer_name) == 0 && + g_strrstr (printer_state_reasons, reason_data->reason) == NULL)) { + + if (reason_data->notification_close_id > 0 && + g_signal_handler_is_connected (reason_data->notification, + reason_data->notification_close_id)) { + g_signal_handler_disconnect (reason_data->notification, + reason_data->notification_close_id); + reason_data->notification_close_id = 0; + } + + notify_notification_close (reason_data->notification, NULL); + + remove_list = tmp; + tmp = g_list_next (tmp); + manager->active_notifications = + g_list_remove_link (manager->active_notifications, remove_list); + + g_list_free_full (remove_list, free_reason_data); + } + } + + /* Check whether we are printing on this printer right now. */ + if (g_hash_table_lookup_extended (manager->printing_printers, printer_name, NULL, NULL)) { + dest = cupsGetDest (printer_name, + NULL, + manager->num_dests, + manager->dests); + if (dest) + tmp_printer_state_reasons = cupsGetOption ("printer-state-reasons", + dest->num_options, + dest->options); + + if (tmp_printer_state_reasons) + old_state_reasons = g_strsplit (tmp_printer_state_reasons, ",", -1); + + cupsFreeDests (manager->num_dests, manager->dests); + manager->num_dests = cupsGetDests (&manager->dests); + + dest = cupsGetDest (printer_name, + NULL, + manager->num_dests, + manager->dests); + if (dest) + tmp_printer_state_reasons = cupsGetOption ("printer-state-reasons", + dest->num_options, + dest->options); + + if (tmp_printer_state_reasons) + new_state_reasons = g_strsplit (tmp_printer_state_reasons, ",", -1); + + if (new_state_reasons) + qsort (new_state_reasons, + g_strv_length (new_state_reasons), + sizeof (gchar *), + strcmp0); + + if (old_state_reasons) { + qsort (old_state_reasons, + g_strv_length (old_state_reasons), + sizeof (gchar *), + strcmp0); + + j = 0; + for (i = 0; new_state_reasons && i < g_strv_length (new_state_reasons); i++) { + while (old_state_reasons[j] && + g_strcmp0 (old_state_reasons[j], new_state_reasons[i]) < 0) + j++; + + if (old_state_reasons[j] == NULL || + g_strcmp0 (old_state_reasons[j], new_state_reasons[i]) != 0) + added_reasons = g_slist_append (added_reasons, + new_state_reasons[i]); + } + } else { + for (i = 0; new_state_reasons && i < g_strv_length (new_state_reasons); i++) { + added_reasons = g_slist_append (added_reasons, + new_state_reasons[i]); + } + } + + for (tmp_list = added_reasons; tmp_list; tmp_list = tmp_list->next) { + gchar *data = (gchar *) tmp_list->data; + known_reason = FALSE; + for (j = 0; j < G_N_ELEMENTS (reasons); j++) { + if (strncmp (data, + reasons[j], + strlen (reasons[j])) == 0) { + NotifyNotification *notification; + known_reason = TRUE; + + if (g_strcmp0 (reasons[j], "connecting-to-device") == 0) { + TimeoutData *data; + + data = g_new0 (TimeoutData, 1); + data->printer_name = g_strdup (printer_name); + data->primary_text = g_strdup ( _(statuses_first[j])); + data->secondary_text = get_statuses_second (j, printer_name); + data->manager = manager; + + data->timeout_id = g_timeout_add_seconds (CONNECTING_TIMEOUT, show_notification, data); + g_source_set_name_by_id (data->timeout_id, "[gnome-settings-daemon] show_notification"); + manager->timeouts = g_list_append (manager->timeouts, data); + } else { + ReasonData *reason_data; + gchar *second_row = get_statuses_second (j, printer_name); + + notification = notify_notification_new ( _(statuses_first[j]), + second_row, + "printer-symbolic"); + notify_notification_set_app_name (notification, _("Printers")); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel"); + notify_notification_set_hint (notification, + "resident", + g_variant_new_boolean (TRUE)); + notify_notification_set_timeout (notification, REASON_TIMEOUT); + + reason_data = g_new0 (ReasonData, 1); + reason_data->printer_name = g_strdup (printer_name); + reason_data->reason = g_strdup (reasons[j]); + reason_data->notification = notification; + reason_data->manager = manager; + + reason_data->notification_close_id = + g_signal_connect (notification, + "closed", + G_CALLBACK (notification_closed_cb), + reason_data); + + manager->active_notifications = + g_list_append (manager->active_notifications, reason_data); + + notify_notification_show (notification, NULL); + + g_free (second_row); + } + } + } + + if (!known_reason && + !reason_is_blacklisted (data)) { + NotifyNotification *notification; + ReasonData *reason_data; + gchar *first_row; + gchar *second_row; + gchar *text = NULL; + gchar *ppd_file_name; + ppd_file_t *ppd_file; + char buffer[8192]; + + ppd_file_name = g_strdup (cupsGetPPD (printer_name)); + if (ppd_file_name) { + ppd_file = ppdOpenFile (ppd_file_name); + if (ppd_file) { + gchar **tmpv; + static const char * const schemes[] = { + "text", "http", "help", "file" + }; + + tmpv = g_new0 (gchar *, G_N_ELEMENTS (schemes) + 1); + i = 0; + for (j = 0; j < G_N_ELEMENTS (schemes); j++) { + if (ppdLocalizeIPPReason (ppd_file, data, schemes[j], buffer, sizeof (buffer))) { + tmpv[i++] = g_strdup (buffer); + } + } + + if (i > 0) + text = g_strjoinv (", ", tmpv); + g_strfreev (tmpv); + + ppdClose (ppd_file); + } + + g_unlink (ppd_file_name); + g_free (ppd_file_name); + } + + + if (g_str_has_suffix (data, "-report")) + /* Translators: This is a title of a report notification for a printer */ + first_row = g_strdup (_("Printer report")); + else if (g_str_has_suffix (data, "-warning")) + /* Translators: This is a title of a warning notification for a printer */ + first_row = g_strdup (_("Printer warning")); + else + /* Translators: This is a title of an error notification for a printer */ + first_row = g_strdup (_("Printer error")); + + + if (text == NULL) + text = g_strdup (data); + + /* Translators: "Printer 'MyPrinterName': 'Description of the report/warning/error from a PPD file'." */ + second_row = g_strdup_printf (_("Printer “%s”: “%s”."), printer_name, text); + g_free (text); + + + notification = notify_notification_new (first_row, + second_row, + "printer-symbolic"); + notify_notification_set_app_name (notification, _("Printers")); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel"); + notify_notification_set_hint (notification, + "resident", + g_variant_new_boolean (TRUE)); + notify_notification_set_timeout (notification, REASON_TIMEOUT); + + reason_data = g_new0 (ReasonData, 1); + reason_data->printer_name = g_strdup (printer_name); + reason_data->reason = g_strdup (data); + reason_data->notification = notification; + reason_data->manager = manager; + + reason_data->notification_close_id = + g_signal_connect (notification, + "closed", + G_CALLBACK (notification_closed_cb), + reason_data); + + manager->active_notifications = + g_list_append (manager->active_notifications, reason_data); + + notify_notification_show (notification, NULL); + + g_free (first_row); + g_free (second_row); + } + } + g_slist_free (added_reasons); + } + + if (new_state_reasons) + g_strfreev (new_state_reasons); + + if (old_state_reasons) + g_strfreev (old_state_reasons); + } + + + if (primary_text) { + NotifyNotification *notification; + notification = notify_notification_new (primary_text, + secondary_text, + "printer-symbolic"); + notify_notification_set_app_name (notification, _("Printers")); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel"); + notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE)); + notify_notification_show (notification, NULL); + g_object_unref (notification); + g_free (primary_text); + g_free (secondary_text); + } +} + +static gboolean +process_new_notifications (gpointer user_data) +{ + GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data; + ipp_attribute_t *attr; + const gchar *notify_subscribed_event = NULL; + const gchar *printer_name = NULL; + const gchar *notify_text = NULL; + const gchar *notify_printer_uri = NULL; + gchar *job_state_reasons = NULL; + const gchar *job_name = NULL; + const char *attr_name; + gboolean printer_is_accepting_jobs = FALSE; + gchar *printer_state_reasons = NULL; + gchar **reasons; + guint notify_job_id = 0; + ipp_t *request; + ipp_t *response; + gint printer_state = -1; + gint job_state = -1; + gint job_impressions_completed = -1; + gint notify_sequence_number = -1; + gint i; + + request = ippNewRequest (IPP_GET_NOTIFICATIONS); + + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, cupsUser ()); + + ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, + "notify-subscription-ids", manager->subscription_id); + + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, + "/printers/"); + + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, + "/jobs/"); + + ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, + "notify-sequence-numbers", + manager->last_notify_sequence_number + 1); + + + response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/"); + + + for (attr = ippFindAttribute (response, "notify-sequence-number", IPP_TAG_INTEGER); + attr != NULL; + attr = ippNextAttribute (response)) { + + attr_name = ippGetName (attr); + if (g_strcmp0 (attr_name, "notify-sequence-number") == 0) { + notify_sequence_number = ippGetInteger (attr, 0); + + if (notify_sequence_number > manager->last_notify_sequence_number) + manager->last_notify_sequence_number = notify_sequence_number; + + if (notify_subscribed_event != NULL) { + process_cups_notification (manager, + notify_subscribed_event, + notify_text, + notify_printer_uri, + printer_name, + printer_state, + printer_state_reasons, + printer_is_accepting_jobs, + notify_job_id, + job_state, + job_state_reasons, + job_name, + job_impressions_completed); + + g_clear_pointer (&printer_state_reasons, g_free); + g_clear_pointer (&job_state_reasons, g_free); + } + + notify_subscribed_event = NULL; + notify_text = NULL; + notify_printer_uri = NULL; + printer_name = NULL; + printer_state = -1; + printer_state_reasons = NULL; + printer_is_accepting_jobs = FALSE; + notify_job_id = 0; + job_state = -1; + job_state_reasons = NULL; + job_name = NULL; + job_impressions_completed = -1; + } else if (g_strcmp0 (attr_name, "notify-subscribed-event") == 0) { + notify_subscribed_event = ippGetString (attr, 0, NULL); + } else if (g_strcmp0 (attr_name, "notify-text") == 0) { + notify_text = ippGetString (attr, 0, NULL); + } else if (g_strcmp0 (attr_name, "notify-printer-uri") == 0) { + notify_printer_uri = ippGetString (attr, 0, NULL); + } else if (g_strcmp0 (attr_name, "printer-name") == 0) { + printer_name = ippGetString (attr, 0, NULL); + } else if (g_strcmp0 (attr_name, "printer-state") == 0) { + printer_state = ippGetInteger (attr, 0); + } else if (g_strcmp0 (attr_name, "printer-state-reasons") == 0) { + reasons = g_new0 (gchar *, ippGetCount (attr) + 1); + for (i = 0; i < ippGetCount (attr); i++) + reasons[i] = g_strdup (ippGetString (attr, i, NULL)); + printer_state_reasons = g_strjoinv (",", reasons); + g_strfreev (reasons); + } else if (g_strcmp0 (attr_name, "printer-is-accepting-jobs") == 0) { + printer_is_accepting_jobs = ippGetBoolean (attr, 0); + } else if (g_strcmp0 (attr_name, "notify-job-id") == 0) { + notify_job_id = ippGetInteger (attr, 0); + } else if (g_strcmp0 (attr_name, "job-state") == 0) { + job_state = ippGetInteger (attr, 0); + } else if (g_strcmp0 (attr_name, "job-state-reasons") == 0) { + reasons = g_new0 (gchar *, ippGetCount (attr) + 1); + for (i = 0; i < ippGetCount (attr); i++) + reasons[i] = g_strdup (ippGetString (attr, i, NULL)); + job_state_reasons = g_strjoinv (",", reasons); + g_strfreev (reasons); + } else if (g_strcmp0 (attr_name, "job-name") == 0) { + job_name = ippGetString (attr, 0, NULL); + } else if (g_strcmp0 (attr_name, "job-impressions-completed") == 0) { + job_impressions_completed = ippGetInteger (attr, 0); + } + } + + if (notify_subscribed_event != NULL) { + process_cups_notification (manager, + notify_subscribed_event, + notify_text, + notify_printer_uri, + printer_name, + printer_state, + printer_state_reasons, + printer_is_accepting_jobs, + notify_job_id, + job_state, + job_state_reasons, + job_name, + job_impressions_completed); + + g_clear_pointer (&printer_state_reasons, g_free); + g_clear_pointer (&job_state_reasons, g_free); + } + + if (response != NULL) + ippDelete (response); + + return TRUE; +} + +static void +scp_handler (GsdPrintNotificationsManager *manager, + gboolean start) +{ + if (start) { + GError *error = NULL; + char *args[2]; + + if (manager->scp_handler_spawned) + return; + + args[0] = LIBEXECDIR "/gsd-printer"; + args[1] = NULL; + + g_spawn_async (NULL, args, NULL, + 0, NULL, NULL, + &manager->scp_handler_pid, &error); + + manager->scp_handler_spawned = (error == NULL); + + if (error) { + g_warning ("Could not execute system-config-printer-udev handler: %s", + error->message); + g_error_free (error); + } + } else if (manager->scp_handler_spawned) { + kill (manager->scp_handler_pid, SIGHUP); + g_spawn_close_pid (manager->scp_handler_pid); + manager->scp_handler_spawned = FALSE; + } +} + +static void +cancel_subscription (gint id) +{ + http_t *http; + ipp_t *request; + + if (id >= 0 && + ((http = httpConnectEncrypt (cupsServer (), ippPort (), + cupsEncryption ())) != NULL)) { + 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); + ippDelete (cupsDoRequest (http, request, "/")); + httpClose (http); + } +} + +static gboolean +renew_subscription (gpointer data) +{ + GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) data; + ipp_attribute_t *attr = NULL; + http_t *http; + ipp_t *request; + ipp_t *response; + gint num_events = 7; + static const char * const events[] = { + "job-created", + "job-completed", + "job-state-changed", + "job-state", + "printer-added", + "printer-deleted", + "printer-state-changed"}; + + if ((http = httpConnectEncrypt (cupsServer (), ippPort (), + cupsEncryption ())) == NULL) { + g_debug ("Connection to CUPS server \'%s\' failed.", cupsServer ()); + } else { + if (manager->subscription_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", manager->subscription_id); + ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-lease-duration", SUBSCRIPTION_DURATION); + ippDelete (cupsDoRequest (http, request, "/")); + } else { + 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", num_events, NULL, events); + ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, + "notify-pull-method", NULL, "ippget"); + if (server_is_local (cupsServer ())) { + 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_DURATION); + response = cupsDoRequest (http, 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 + manager->subscription_id = ippGetInteger (attr, 0); + } + + if (response) + ippDelete (response); + } + httpClose (http); + } + return TRUE; +} + +static void +renew_subscription_with_connection_test_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GSocketConnection *connection; + GError *error = NULL; + + connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source_object), + res, + &error); + + if (connection) { + g_debug ("Test connection to CUPS server \'%s:%d\' succeeded.", cupsServer (), ippPort ()); + + g_io_stream_close (G_IO_STREAM (connection), NULL, NULL); + g_object_unref (connection); + + renew_subscription (user_data); + } else { + g_debug ("Test connection to CUPS server \'%s:%d\' failed.", cupsServer (), ippPort ()); + } +} + +static gboolean +renew_subscription_with_connection_test (gpointer user_data) +{ + GSocketClient *client; + gchar *address; + int port; + + port = ippPort (); + + address = g_strdup_printf ("%s:%d", cupsServer (), port); + + if (address && address[0] != '/') { + client = g_socket_client_new (); + + g_debug ("Initiating test connection to CUPS server \'%s:%d\'.", cupsServer (), port); + + g_socket_client_connect_to_host_async (client, + address, + port, + NULL, + renew_subscription_with_connection_test_cb, + user_data); + + g_object_unref (client); + } else { + renew_subscription (user_data); + } + + g_free (address); + + return TRUE; +} + +static void +renew_subscription_timeout_enable (GsdPrintNotificationsManager *manager, + gboolean enable, + gboolean with_connection_test) +{ + if (manager->renew_source_id > 0) + g_source_remove (manager->renew_source_id); + + if (enable) { + renew_subscription (manager); + if (with_connection_test) { + manager->renew_source_id = + g_timeout_add_seconds (RENEW_INTERVAL, + renew_subscription_with_connection_test, + manager); + g_source_set_name_by_id (manager->renew_source_id, "[gnome-settings-daemon] renew_subscription_with_connection_test"); + } else { + manager->renew_source_id = + g_timeout_add_seconds (RENEW_INTERVAL, + renew_subscription, + manager); + g_source_set_name_by_id (manager->renew_source_id, "[gnome-settings-daemon] renew_subscription"); + } + } else { + manager->renew_source_id = 0; + } +} + +static void +cups_connection_test_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data; + GSocketConnection *connection; + GError *error = NULL; + + connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source_object), + res, + &error); + + if (connection) { + g_debug ("Test connection to CUPS server \'%s:%d\' succeeded.", cupsServer (), ippPort ()); + + g_io_stream_close (G_IO_STREAM (connection), NULL, NULL); + g_object_unref (connection); + + manager->num_dests = cupsGetDests (&manager->dests); + g_debug ("Got dests from remote CUPS server."); + + renew_subscription_timeout_enable (manager, TRUE, TRUE); + manager->check_source_id = g_timeout_add_seconds (CHECK_INTERVAL, process_new_notifications, manager); + g_source_set_name_by_id (manager->check_source_id, "[gnome-settings-daemon] process_new_notifications"); + } else { + g_debug ("Test connection to CUPS server \'%s:%d\' failed.", cupsServer (), ippPort ()); + if (manager->cups_connection_timeout_id == 0) { + manager->cups_connection_timeout_id = + g_timeout_add_seconds (CUPS_CONNECTION_TEST_INTERVAL, cups_connection_test, manager); + g_source_set_name_by_id (manager->cups_connection_timeout_id, "[gnome-settings-daemon] cups_connection_test"); + } + } +} + +static gboolean +cups_connection_test (gpointer user_data) +{ + GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data; + GSocketClient *client; + gchar *address; + int port = ippPort (); + + if (!manager->dests) { + address = g_strdup_printf ("%s:%d", cupsServer (), port); + + client = g_socket_client_new (); + + g_debug ("Initiating test connection to CUPS server \'%s:%d\'.", cupsServer (), port); + + g_socket_client_connect_to_host_async (client, + address, + port, + NULL, + cups_connection_test_cb, + manager); + + g_object_unref (client); + g_free (address); + } + + if (manager->dests) { + manager->cups_connection_timeout_id = 0; + + return FALSE; + } else { + return TRUE; + } +} + +static void +gsd_print_notifications_manager_got_dbus_connection (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data; + GError *error = NULL; + + manager->cups_bus_connection = g_bus_get_finish (res, &error); + + if (manager->cups_bus_connection != NULL) { + manager->cups_dbus_subscription_id = + g_dbus_connection_signal_subscribe (manager->cups_bus_connection, + NULL, + CUPS_DBUS_INTERFACE, + NULL, + CUPS_DBUS_PATH, + NULL, + 0, + on_cups_notification, + manager, + NULL); + } else { + g_warning ("Connection to message bus failed: %s", error->message); + g_error_free (error); + } +} + +static gboolean +gsd_print_notifications_manager_start_idle (gpointer data) +{ + GsdPrintNotificationsManager *manager = data; + + gnome_settings_profile_start (NULL); + + manager->printing_printers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + /* + * Set a password callback which cancels authentication + * before we prepare a correct solution (see bug #725440). + */ + cupsSetPasswordCB2 (password_cb, NULL); + + if (server_is_local (cupsServer ())) { + manager->num_dests = cupsGetDests (&manager->dests); + g_debug ("Got dests from local CUPS server."); + + renew_subscription_timeout_enable (manager, TRUE, FALSE); + + g_bus_get (G_BUS_TYPE_SYSTEM, + NULL, + gsd_print_notifications_manager_got_dbus_connection, + data); + } else { + cups_connection_test (manager); + } + + scp_handler (manager, TRUE); + + gnome_settings_profile_end (NULL); + + manager->start_idle_id = 0; + return G_SOURCE_REMOVE; +} + +gboolean +gsd_print_notifications_manager_start (GsdPrintNotificationsManager *manager, + GError **error) +{ + g_debug ("Starting print-notifications manager"); + + gnome_settings_profile_start (NULL); + + manager->subscription_id = -1; + manager->dests = NULL; + manager->num_dests = 0; + manager->scp_handler_spawned = FALSE; + manager->timeouts = NULL; + manager->printing_printers = NULL; + manager->active_notifications = NULL; + manager->cups_bus_connection = NULL; + manager->cups_connection_timeout_id = 0; + manager->last_notify_sequence_number = -1; + manager->held_jobs = NULL; + + manager->start_idle_id = g_idle_add (gsd_print_notifications_manager_start_idle, manager); + g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] gsd_print_notifications_manager_start_idle"); + + gnome_settings_profile_end (NULL); + + return TRUE; +} + +void +gsd_print_notifications_manager_stop (GsdPrintNotificationsManager *manager) +{ + TimeoutData *data; + ReasonData *reason_data; + HeldJob *job; + GList *tmp; + + g_debug ("Stopping print-notifications manager"); + + cupsFreeDests (manager->num_dests, manager->dests); + manager->num_dests = 0; + manager->dests = NULL; + + if (manager->cups_dbus_subscription_id > 0 && + manager->cups_bus_connection != NULL) { + g_dbus_connection_signal_unsubscribe (manager->cups_bus_connection, + manager->cups_dbus_subscription_id); + manager->cups_dbus_subscription_id = 0; + } + + renew_subscription_timeout_enable (manager, FALSE, FALSE); + + if (manager->check_source_id > 0) { + g_source_remove (manager->check_source_id); + manager->check_source_id = 0; + } + + if (manager->subscription_id >= 0) + cancel_subscription (manager->subscription_id); + + g_clear_pointer (&manager->printing_printers, g_hash_table_destroy); + + g_clear_object (&manager->cups_bus_connection); + + for (tmp = manager->timeouts; tmp; tmp = g_list_next (tmp)) { + data = (TimeoutData *) tmp->data; + if (data) + g_source_remove (data->timeout_id); + } + g_list_free_full (manager->timeouts, free_timeout_data); + + for (tmp = manager->active_notifications; tmp; tmp = g_list_next (tmp)) { + reason_data = (ReasonData *) tmp->data; + if (reason_data) { + if (reason_data->notification_close_id > 0 && + g_signal_handler_is_connected (reason_data->notification, + reason_data->notification_close_id)) { + g_signal_handler_disconnect (reason_data->notification, + reason_data->notification_close_id); + reason_data->notification_close_id = 0; + } + + notify_notification_close (reason_data->notification, NULL); + } + } + g_list_free_full (manager->active_notifications, free_reason_data); + + for (tmp = manager->held_jobs; tmp; tmp = g_list_next (tmp)) { + job = (HeldJob *) tmp->data; + g_source_remove (job->timeout_id); + } + g_list_free_full (manager->held_jobs, free_held_job); + + scp_handler (manager, FALSE); +} + +static void +gsd_print_notifications_manager_class_init (GsdPrintNotificationsManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_print_notifications_manager_finalize; + + notify_init ("gnome-settings-daemon"); +} + +static void +gsd_print_notifications_manager_init (GsdPrintNotificationsManager *manager) +{ +} + +static void +gsd_print_notifications_manager_finalize (GObject *object) +{ + GsdPrintNotificationsManager *manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_PRINT_NOTIFICATIONS_MANAGER (object)); + + manager = GSD_PRINT_NOTIFICATIONS_MANAGER (object); + + g_return_if_fail (manager != NULL); + + gsd_print_notifications_manager_stop (manager); + + if (manager->start_idle_id != 0) + g_source_remove (manager->start_idle_id); + + G_OBJECT_CLASS (gsd_print_notifications_manager_parent_class)->finalize (object); +} + +GsdPrintNotificationsManager * +gsd_print_notifications_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + manager_object = g_object_new (GSD_TYPE_PRINT_NOTIFICATIONS_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + } + + return GSD_PRINT_NOTIFICATIONS_MANAGER (manager_object); +} diff --git a/plugins/print-notifications/gsd-print-notifications-manager.h b/plugins/print-notifications/gsd-print-notifications-manager.h new file mode 100644 index 0000000..ec1dc72 --- /dev/null +++ b/plugins/print-notifications/gsd-print-notifications-manager.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * 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/>. + * + */ + +#ifndef __GSD_PRINT_NOTIFICATIONS_MANAGER_H +#define __GSD_PRINT_NOTIFICATIONS_MANAGER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GSD_TYPE_PRINT_NOTIFICATIONS_MANAGER (gsd_print_notifications_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (GsdPrintNotificationsManager, gsd_print_notifications_manager, GSD, PRINT_NOTIFICATIONS_MANAGER, GObject) + +GsdPrintNotificationsManager *gsd_print_notifications_manager_new (void); +gboolean gsd_print_notifications_manager_start (GsdPrintNotificationsManager *manager, + GError **error); +void gsd_print_notifications_manager_stop (GsdPrintNotificationsManager *manager); + +G_END_DECLS + +#endif /* __GSD_PRINT_NOTIFICATIONS_MANAGER_H */ diff --git a/plugins/print-notifications/gsd-printer.c b/plugins/print-notifications/gsd-printer.c new file mode 100644 index 0000000..573129b --- /dev/null +++ b/plugins/print-notifications/gsd-printer.c @@ -0,0 +1,1403 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 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, 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 <gio/gio.h> +#include <stdlib.h> +#include <libnotify/notify.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> +#include <cups/cups.h> +#include <cups/ppd.h> + +static GDBusNodeInfo *npn_introspection_data = NULL; +static GDBusNodeInfo *pdi_introspection_data = NULL; + +#define SCP_DBUS_NPN_NAME "com.redhat.NewPrinterNotification" +#define SCP_DBUS_NPN_PATH "/com/redhat/NewPrinterNotification" +#define SCP_DBUS_NPN_INTERFACE "com.redhat.NewPrinterNotification" + +#define SCP_DBUS_PDI_NAME "com.redhat.PrinterDriversInstaller" +#define SCP_DBUS_PDI_PATH "/com/redhat/PrinterDriversInstaller" +#define SCP_DBUS_PDI_INTERFACE "com.redhat.PrinterDriversInstaller" + +#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 SCP_BUS "org.fedoraproject.Config.Printing" +#define SCP_PATH "/org/fedoraproject/Config/Printing" +#define SCP_IFACE "org.fedoraproject.Config.Printing" + +#define MECHANISM_BUS "org.opensuse.CupsPkHelper.Mechanism" + +#define ALLOWED_CHARACTERS "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" + +#define DBUS_TIMEOUT 60000 +#define DBUS_INSTALL_TIMEOUT 3600000 + +#define GNOME_SESSION_DBUS_NAME "org.gnome.SessionManager" +#define GNOME_SESSION_DBUS_PATH "/org/gnome/SessionManager" +#define GNOME_SESSION_DBUS_IFACE "org.gnome.SessionManager" +#define GNOME_SESSION_CLIENT_PRIVATE_DBUS_IFACE "org.gnome.SessionManager.ClientPrivate" + +#define GNOME_SESSION_PRESENCE_DBUS_PATH "/org/gnome/SessionManager/Presence" +#define GNOME_SESSION_PRESENCE_DBUS_IFACE "org.gnome.SessionManager.Presence" + +#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 + +enum { + PRESENCE_STATUS_AVAILABLE = 0, + PRESENCE_STATUS_INVISIBLE, + PRESENCE_STATUS_BUSY, + PRESENCE_STATUS_IDLE, + PRESENCE_STATUS_UNKNOWN +}; + +static const gchar npn_introspection_xml[] = + "<node name='/com/redhat/NewPrinterNotification'>" + " <interface name='com.redhat.NewPrinterNotification'>" + " <method name='GetReady'>" + " </method>" + " <method name='NewPrinter'>" + " <arg type='i' name='status' direction='in'/>" + " <arg type='s' name='name' direction='in'/>" + " <arg type='s' name='mfg' direction='in'/>" + " <arg type='s' name='mdl' direction='in'/>" + " <arg type='s' name='des' direction='in'/>" + " <arg type='s' name='cmd' direction='in'/>" + " </method>" + " </interface>" + "</node>"; + +static const gchar pdi_introspection_xml[] = + "<node name='/com/redhat/PrinterDriversInstaller'>" + " <interface name='com.redhat.PrinterDriversInstaller'>" + " <method name='InstallDrivers'>" + " <arg type='s' name='mfg' direction='in'/>" + " <arg type='s' name='mdl' direction='in'/>" + " <arg type='s' name='cmd' direction='in'/>" + " </method>" + " </interface>" + "</node>"; + +static GMainLoop *main_loop; +static guint npn_registration_id; +static guint pdi_registration_id; +static guint npn_owner_id; +static guint pdi_owner_id; + +static GHashTable * +get_missing_executables (const gchar *ppd_file_name) +{ + GHashTable *executables = NULL; + GDBusProxy *proxy; + GVariant *output; + GVariant *array; + GError *error = NULL; + gint i; + + if (!ppd_file_name) + return NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SCP_BUS, + SCP_PATH, + SCP_IFACE, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return NULL; + } + + output = g_dbus_proxy_call_sync (proxy, + "MissingExecutables", + g_variant_new ("(s)", + ppd_file_name), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output && g_variant_n_children (output) == 1) { + array = g_variant_get_child_value (output, 0); + if (array) { + executables = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + for (i = 0; i < g_variant_n_children (array); i++) { + g_hash_table_insert (executables, + g_strdup (g_variant_get_string ( + g_variant_get_child_value (array, i), + NULL)), + NULL); + } + } + } + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); + + return executables; +} + +static GHashTable * +find_packages_for_executables (GHashTable *executables) +{ + GHashTableIter exec_iter; + GHashTable *packages = NULL; + GDBusProxy *proxy; + GVariant *output; + gpointer key, value; + GError *error = NULL; + + if (!executables || g_hash_table_size (executables) <= 0) + return NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + PACKAGE_KIT_BUS, + PACKAGE_KIT_PATH, + PACKAGE_KIT_QUERY_IFACE, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return NULL; + } + + packages = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + g_hash_table_iter_init (&exec_iter, executables); + while (g_hash_table_iter_next (&exec_iter, &key, &value)) { + output = g_dbus_proxy_call_sync (proxy, + "SearchFile", + g_variant_new ("(ss)", + (gchar *) key, + ""), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output) { + gboolean installed; + gchar *package; + + g_variant_get (output, + "(bs)", + &installed, + &package); + if (!installed) + g_hash_table_insert (packages, g_strdup (package), NULL); + + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + } + + g_object_unref (proxy); + + return packages; +} + +static void +install_packages (GHashTable *packages) +{ + GVariantBuilder array_builder; + GHashTableIter pkg_iter; + GDBusProxy *proxy; + GVariant *output; + gpointer key, value; + GError *error = NULL; + + if (!packages || g_hash_table_size (packages) <= 0) + return; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + PACKAGE_KIT_BUS, + PACKAGE_KIT_PATH, + PACKAGE_KIT_MODIFY_IFACE, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return; + } + + g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as")); + + g_hash_table_iter_init (&pkg_iter, packages); + while (g_hash_table_iter_next (&pkg_iter, &key, &value)) { + g_variant_builder_add (&array_builder, + "s", + (gchar *) key); + } + + output = g_dbus_proxy_call_sync (proxy, + "InstallPackageNames", + g_variant_new ("(uass)", + 0, + &array_builder, + "hide-finished"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_INSTALL_TIMEOUT, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); +} + +static gchar * +get_best_ppd (gchar *device_id, + gchar *device_make_and_model, + gchar *device_uri) +{ + GDBusProxy *proxy; + GVariant *output; + GVariant *array; + GVariant *tuple; + GError *error = NULL; + gchar *ppd_name = NULL; + gint i, j; + static const char * const match_levels[] = { + "exact-cmd", + "exact", + "close", + "generic", + "none"}; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SCP_BUS, + SCP_PATH, + SCP_IFACE, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return NULL; + } + + output = g_dbus_proxy_call_sync (proxy, + "GetBestDrivers", + g_variant_new ("(sss)", + device_id ? device_id : "", + device_make_and_model ? device_make_and_model : "", + device_uri ? device_uri : ""), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output && g_variant_n_children (output) >= 1) { + array = g_variant_get_child_value (output, 0); + if (array) + for (j = 0; j < G_N_ELEMENTS (match_levels) && ppd_name == NULL; j++) + for (i = 0; i < g_variant_n_children (array) && ppd_name == NULL; i++) { + tuple = g_variant_get_child_value (array, i); + if (tuple && g_variant_n_children (tuple) == 2) { + if (g_strcmp0 (g_variant_get_string ( + g_variant_get_child_value (tuple, 1), + NULL), match_levels[j]) == 0) + ppd_name = g_strdup (g_variant_get_string ( + g_variant_get_child_value (tuple, 0), + NULL)); + } + } + } + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); + + return ppd_name; +} + +static gchar * +get_tag_value (const gchar *tag_string, + const gchar *tag_name) +{ + gchar **tag_string_splitted; + gchar *tag_value = NULL; + gint tag_name_length; + gint i; + + if (!tag_string || + !tag_name) + return NULL; + + 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; +} + +static gchar * +create_name (gchar *device_id) +{ + cups_dest_t *dests; + gboolean already_present = FALSE; + gchar *name = NULL; + gchar *new_name = NULL; + gint num_dests; + gint name_index = 2; + gint j; + + g_return_val_if_fail (device_id != NULL, NULL); + + name = get_tag_value (device_id, "mdl"); + if (!name) + name = get_tag_value (device_id, "model"); + + if (name) + name = g_strcanon (name, ALLOWED_CHARACTERS, '-'); + + num_dests = cupsGetDests (&dests); + do { + 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_dests; j++) + if (g_strcmp0 (dests[j].name, new_name) == 0) + already_present = TRUE; + + if (already_present) { + g_free (new_name); + } else { + g_free (name); + name = new_name; + } + } while (already_present); + cupsFreeDests (num_dests, dests); + + return name; +} + +static gboolean +add_printer (gchar *printer_name, + gchar *device_uri, + gchar *ppd_name, + gchar *info, + gchar *location) +{ + cups_dest_t *dests; + GDBusProxy *proxy; + gboolean success = FALSE; + GVariant *output; + GError *error = NULL; + gint num_dests; + gint i; + + if (!printer_name || !device_uri || !ppd_name) + return FALSE; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return FALSE; + } + + output = g_dbus_proxy_call_sync (proxy, + "PrinterAdd", + g_variant_new ("(sssss)", + printer_name, + device_uri, + ppd_name, + info ? info : "", + location ? location : ""), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); + + num_dests = cupsGetDests (&dests); + for (i = 0; i < num_dests; i++) + if (g_strcmp0 (dests[i].name, printer_name) == 0) + success = TRUE; + cupsFreeDests (num_dests, dests); + + return success; +} + +static gboolean +printer_set_enabled (const gchar *printer_name, + gboolean enabled) +{ + GDBusProxy *proxy; + gboolean result = TRUE; + GVariant *output; + GError *error = NULL; + + if (!printer_name) + return FALSE; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return FALSE; + } + + output = g_dbus_proxy_call_sync (proxy, + "PrinterSetEnabled", + g_variant_new ("(sb)", + printer_name, + enabled), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + result = FALSE; + } + + g_object_unref (proxy); + + return result; +} + +static gboolean +printer_set_accepting_jobs (const gchar *printer_name, + gboolean accepting_jobs, + const gchar *reason) +{ + GDBusProxy *proxy; + gboolean result = TRUE; + GVariant *output; + GError *error = NULL; + + if (!printer_name) + return FALSE; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return FALSE; + } + + output = g_dbus_proxy_call_sync (proxy, + "PrinterSetAcceptJobs", + g_variant_new ("(sbs)", + printer_name, + accepting_jobs, + reason ? reason : ""), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + result = FALSE; + } + + g_object_unref (proxy); + + return result; +} + +static ipp_t * +execute_maintenance_command (const char *printer_name, + const char *command, + const char *title) +{ + http_t *http; + GError *error = NULL; + ipp_t *request = NULL; + ipp_t *response = NULL; + gchar *file_name = NULL; + char *uri; + int fd = -1; + + http = httpConnectEncrypt (cupsServer (), + ippPort (), + cupsEncryption ()); + + if (!http) + return NULL; + + request = ippNewRequest (IPP_PRINT_JOB); + + uri = g_strdup_printf ("ipp://localhost/printers/%s", + printer_name); + + ippAddString (request, + IPP_TAG_OPERATION, + IPP_TAG_URI, + "printer-uri", + NULL, + uri); + + g_free (uri); + + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", + NULL, 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\n", command); + fclose (file); + + response = cupsDoFileRequest (http, request, "/", file_name); + g_unlink (file_name); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_free (file_name); + httpClose (http); + + return response; +} + +static 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; +} + +static void +printer_autoconfigure (gchar *printer_name) +{ + gchar *commands; + gchar *commands_lowercase; + ipp_t *response = NULL; + + if (!printer_name) + return; + + commands = get_dest_attr (printer_name, "printer-commands"); + commands_lowercase = g_ascii_strdown (commands, -1); + + if (g_strrstr (commands_lowercase, "autoconfigure")) { + response = execute_maintenance_command (printer_name, + "AutoConfigure", + ("Automatic configuration")); + if (response) { + if (ippGetState (response) == IPP_ERROR) + g_warning ("An error has occured during automatic configuration of new printer."); + ippDelete (response); + } + } + g_free (commands); + g_free (commands_lowercase); +} + +/* Returns default page size for current locale */ +static 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"; +} + +static void +set_default_paper_size (const gchar *printer_name, + const gchar *ppd_file_name) +{ + GDBusProxy *proxy; + GVariant *output; + GError *error = NULL; + GVariantBuilder *builder; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + return; + } + + /* Set default media size according to the locale + * FIXME: Handle more than A4 and Letter: + * https://bugzilla.gnome.org/show_bug.cgi?id=660769 */ + builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + g_variant_builder_add (builder, "s", get_page_size_from_locale ()); + + output = g_dbus_proxy_call_sync (proxy, + "PrinterAddOption", + g_variant_new ("(ssas)", + printer_name ? printer_name : "", + "PageSize", + builder), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } else { + if (!(error->domain == G_DBUS_ERROR && + (error->code == G_DBUS_ERROR_SERVICE_UNKNOWN || + error->code == G_DBUS_ERROR_UNKNOWN_METHOD))) + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); +} + +/* + * Setup new printer and returns TRUE if successful. + */ +static gboolean +setup_printer (gchar *device_id, + gchar *device_make_and_model, + gchar *device_uri) +{ + gboolean success = FALSE; + gchar *ppd_name; + gchar *printer_name; + + ppd_name = get_best_ppd (device_id, device_make_and_model, device_uri); + printer_name = create_name (device_id); + + if (!ppd_name || !printer_name || !device_uri) { + g_free (ppd_name); + g_free (printer_name); + return FALSE; + } + + success = add_printer (printer_name, device_uri, + ppd_name, NULL, NULL); + + /* Set some options of the new printer */ + if (success) { + const char *ppd_file_name; + + printer_set_accepting_jobs (printer_name, TRUE, NULL); + printer_set_enabled (printer_name, TRUE); + printer_autoconfigure (printer_name); + + ppd_file_name = cupsGetPPD (printer_name); + + if (ppd_file_name) { + GHashTable *executables; + GHashTable *packages; + + set_default_paper_size (printer_name, ppd_file_name); + + executables = get_missing_executables (ppd_file_name); + packages = find_packages_for_executables (executables); + install_packages (packages); + + if (executables) + g_hash_table_destroy (executables); + if (packages) + g_hash_table_destroy (packages); + g_unlink (ppd_file_name); + } + } + + g_free (printer_name); + g_free (ppd_name); + + return success; +} + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + gchar *primary_text = NULL; + gchar *secondary_text = NULL; + gchar *name = NULL; + gchar *mfg = NULL; + gchar *mdl = NULL; + gchar *des = NULL; + gchar *cmd = NULL; + gchar *device = NULL; + gchar *device_id; + gchar *make_and_model; + gint status = 0; + + if (g_strcmp0 (method_name, "GetReady") == 0) { + /* Translators: We are configuring new printer */ + primary_text = g_strdup (_("Configuring new printer")); + /* Translators: Just wait */ + secondary_text = g_strdup (_("Please wait…")); + + g_dbus_method_invocation_return_value (invocation, + NULL); + } + else if (g_strcmp0 (method_name, "NewPrinter") == 0) { + if (g_variant_n_children (parameters) == 6) { + g_variant_get (parameters, "(i&s&s&s&s&s)", + &status, + &name, + &mfg, + &mdl, + &des, + &cmd); + } + + if (g_strrstr (name, "/")) { + /* name is a URI, no queue was generated, because no suitable + * driver was found + */ + + device_id = g_strdup_printf ("MFG:%s;MDL:%s;DES:%s;CMD:%s;", mfg, mdl, des, cmd); + make_and_model = g_strdup_printf ("%s %s", mfg, mdl); + + if (!setup_printer (device_id, make_and_model, name)) { + + /* Translators: We have no driver installed for this printer */ + primary_text = g_strdup (_("Missing printer driver")); + + if ((mfg && mdl) || des) { + if (mfg && mdl) + device = g_strdup_printf ("%s %s", mfg, mdl); + else + device = g_strdup (des); + + /* Translators: We have no driver installed for the device */ + secondary_text = g_strdup_printf (_("No printer driver for %s."), device); + g_free (device); + } + else + /* Translators: We have no driver installed for this printer */ + secondary_text = g_strdup (_("No driver for this printer.")); + } + + g_free (make_and_model); + g_free (device_id); + } + else { + /* name is the name of the queue which hal_lpadmin has set up + * automatically. + */ + + const char *ppd_file_name; + + ppd_file_name = cupsGetPPD (name); + if (ppd_file_name) { + GHashTable *executables; + GHashTable *packages; + + executables = get_missing_executables (ppd_file_name); + packages = find_packages_for_executables (executables); + install_packages (packages); + + if (executables) + g_hash_table_destroy (executables); + if (packages) + g_hash_table_destroy (packages); + g_unlink (ppd_file_name); + } + } + + g_dbus_method_invocation_return_value (invocation, + NULL); + } + else if (g_strcmp0 (method_name, "InstallDrivers") == 0) { + GDBusProxy *proxy; + GError *error = NULL; + + if (g_variant_n_children (parameters) == 3) { + g_variant_get (parameters, "(&s&s&s)", + &mfg, + &mdl, + &cmd); + } + + if (mfg && mdl) + device = g_strdup_printf ("MFG:%s;MDL:%s;", mfg, mdl); + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + PACKAGE_KIT_BUS, + PACKAGE_KIT_PATH, + PACKAGE_KIT_MODIFY_IFACE, + NULL, + &error); + + if (!proxy) { + g_warning ("%s", error->message); + g_error_free (error); + } + + if (proxy && device) { + GVariantBuilder *builder; + GVariant *output; + + builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + g_variant_builder_add (builder, "s", device); + + output = g_dbus_proxy_call_sync (proxy, + "InstallPrinterDrivers", + g_variant_new ("(uass)", + 0, + builder, + "hide-finished"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_INSTALL_TIMEOUT, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); + } + + g_dbus_method_invocation_return_value (invocation, + NULL); + } + + if (primary_text) { + NotifyNotification *notification; + notification = notify_notification_new (primary_text, + secondary_text, + "printer-symbolic"); + notify_notification_set_app_name (notification, _("Printers")); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel"); + notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE)); + + notify_notification_show (notification, NULL); + g_object_unref (notification); + g_free (primary_text); + g_free (secondary_text); + } +} + +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + NULL, + NULL +}; + +static void +unregister_objects () +{ + GDBusConnection *system_connection; + GError *error = NULL; + + system_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + + if (npn_registration_id > 0) { + g_dbus_connection_unregister_object (system_connection, npn_registration_id); + npn_registration_id = 0; + } + + if (pdi_registration_id > 0) { + g_dbus_connection_unregister_object (system_connection, pdi_registration_id); + pdi_registration_id = 0; + } +} + +static void +unown_names () +{ + if (npn_owner_id > 0) { + g_bus_unown_name (npn_owner_id); + npn_owner_id = 0; + } + + if (pdi_owner_id > 0) { + g_bus_unown_name (pdi_owner_id); + pdi_owner_id = 0; + } +} + +static void +on_npn_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GError *error = NULL; + + npn_registration_id = g_dbus_connection_register_object (connection, + SCP_DBUS_NPN_PATH, + npn_introspection_data->interfaces[0], + &interface_vtable, + NULL, + NULL, + &error); + + if (npn_registration_id == 0) { + g_warning ("Failed to register object: %s\n", error->message); + g_error_free (error); + } +} + +static void +on_pdi_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GError *error = NULL; + + pdi_registration_id = g_dbus_connection_register_object (connection, + SCP_DBUS_PDI_PATH, + pdi_introspection_data->interfaces[0], + &interface_vtable, + NULL, + NULL, + &error); + + if (pdi_registration_id == 0) { + g_warning ("Failed to register object: %s\n", error->message); + g_error_free (error); + } +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + unregister_objects (); +} + +static void +session_signal_handler (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + guint new_status; + + g_variant_get (parameters, "(u)", &new_status); + + if (new_status == PRESENCE_STATUS_IDLE || + new_status == PRESENCE_STATUS_AVAILABLE) { + unregister_objects (); + unown_names (); + + if (new_status == PRESENCE_STATUS_AVAILABLE) { + npn_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, + SCP_DBUS_NPN_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_npn_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + pdi_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, + SCP_DBUS_PDI_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_pdi_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + } + } +} + +static void +client_signal_handler (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GDBusProxy *proxy; + GError *error = NULL; + GVariant *output; + + if (g_strcmp0 (signal_name, "QueryEndSession") == 0 || + g_strcmp0 (signal_name, "EndSession") == 0) { + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + sender_name, + object_path, + interface_name, + NULL, + &error); + + if (proxy) { + output = g_dbus_proxy_call_sync (proxy, + "EndSessionResponse", + g_variant_new ("(bs)", TRUE, ""), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output) { + g_variant_unref (output); + } + else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); + } + else { + g_warning ("%s", error->message); + g_error_free (error); + } + + if (g_strcmp0 (signal_name, "EndSession") == 0) { + g_main_loop_quit (main_loop); + g_debug ("Exiting gsd-printer"); + } + } +} + +static gchar * +register_gnome_session_client (const gchar *app_id, + const gchar *client_startup_id) +{ + GDBusProxy *proxy; + GVariant *output = NULL; + GError *error = NULL; + const gchar *client_id = NULL; + gchar *result = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + GNOME_SESSION_DBUS_NAME, + GNOME_SESSION_DBUS_PATH, + GNOME_SESSION_DBUS_IFACE, + NULL, + &error); + + if (proxy) { + output = g_dbus_proxy_call_sync (proxy, + "RegisterClient", + g_variant_new ("(ss)", app_id, client_startup_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output) { + g_variant_get (output, "(o)", &client_id); + if (client_id) + result = g_strdup (client_id); + g_variant_unref (output); + } + else { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_object_unref (proxy); + } + else { + g_warning ("%s", error->message); + g_error_free (error); + } + + return result; +} + +int +main (int argc, char *argv[]) +{ + GDBusConnection *connection; + gboolean client_signal_subscription_set = FALSE; + GError *error = NULL; + guint client_signal_subscription_id; + guint session_signal_subscription_id; + gchar *object_path; + + bindtextdomain (GETTEXT_PACKAGE, GNOME_SETTINGS_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + npn_registration_id = 0; + pdi_registration_id = 0; + npn_owner_id = 0; + pdi_owner_id = 0; + + notify_init ("gnome-settings-daemon-printer"); + + npn_introspection_data = + g_dbus_node_info_new_for_xml (npn_introspection_xml, &error); + + if (npn_introspection_data == NULL) { + g_warning ("Error parsing introspection XML: %s\n", error->message); + g_error_free (error); + goto error; + } + + pdi_introspection_data = + g_dbus_node_info_new_for_xml (pdi_introspection_xml, &error); + + if (pdi_introspection_data == NULL) { + g_warning ("Error parsing introspection XML: %s\n", error->message); + g_error_free (error); + goto error; + } + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + session_signal_subscription_id = + g_dbus_connection_signal_subscribe (connection, + NULL, + GNOME_SESSION_PRESENCE_DBUS_IFACE, + "StatusChanged", + GNOME_SESSION_PRESENCE_DBUS_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + session_signal_handler, + NULL, + NULL); + + object_path = register_gnome_session_client ("gsd-printer", ""); + if (object_path) { + client_signal_subscription_id = + g_dbus_connection_signal_subscribe (connection, + NULL, + GNOME_SESSION_CLIENT_PRIVATE_DBUS_IFACE, + NULL, + object_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + client_signal_handler, + NULL, + NULL); + client_signal_subscription_set = TRUE; + } + + if (npn_owner_id == 0) + npn_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, + SCP_DBUS_NPN_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_npn_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + if (pdi_owner_id == 0) + pdi_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, + SCP_DBUS_PDI_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_pdi_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + main_loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (main_loop); + + unregister_objects (); + unown_names (); + + if (client_signal_subscription_set) + g_dbus_connection_signal_unsubscribe (connection, client_signal_subscription_id); + g_dbus_connection_signal_unsubscribe (connection, session_signal_subscription_id); + + g_free (object_path); + + g_dbus_node_info_unref (npn_introspection_data); + g_dbus_node_info_unref (pdi_introspection_data); + + return 0; + +error: + + if (npn_introspection_data) + g_dbus_node_info_unref (npn_introspection_data); + + if (pdi_introspection_data) + g_dbus_node_info_unref (pdi_introspection_data); + + return 1; +} diff --git a/plugins/print-notifications/main.c b/plugins/print-notifications/main.c new file mode 100644 index 0000000..a0dd406 --- /dev/null +++ b/plugins/print-notifications/main.c @@ -0,0 +1,7 @@ +#define NEW gsd_print_notifications_manager_new +#define START gsd_print_notifications_manager_start +#define STOP gsd_print_notifications_manager_stop +#define MANAGER GsdPrintNotificationsManager +#include "gsd-print-notifications-manager.h" + +#include "daemon-skeleton.h" diff --git a/plugins/print-notifications/meson.build b/plugins/print-notifications/meson.build new file mode 100644 index 0000000..1e1c614 --- /dev/null +++ b/plugins/print-notifications/meson.build @@ -0,0 +1,37 @@ +sources = files( + 'gsd-print-notifications-manager.c', + 'main.c' +) + +deps = plugins_deps + [ + cups_dep, + gtk_dep, + libnotify_dep +] + +cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)] +cflags += ['-DBINDIR="@0@"'.format(gsd_bindir)] + +executable( + 'gsd-' + plugin_name, + sources, + include_directories: [top_inc, common_inc], + dependencies: deps, + c_args: cflags, + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir +) + +program = 'gsd-printer' + +executable( + program, + program + '.c', + include_directories: top_inc, + dependencies: deps, + c_args: '-DGNOME_SETTINGS_LOCALEDIR="@0@"'.format(gsd_localedir), + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir +) |