summaryrefslogtreecommitdiffstats
path: root/plugins/print-notifications
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/print-notifications')
-rw-r--r--plugins/print-notifications/gsd-print-notifications-manager.c1725
-rw-r--r--plugins/print-notifications/gsd-print-notifications-manager.h38
-rw-r--r--plugins/print-notifications/gsd-printer.c1403
-rw-r--r--plugins/print-notifications/main.c7
-rw-r--r--plugins/print-notifications/meson.build37
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
+)