diff --git a/panels/printers/cc-printers-panel.c b/panels/printers/cc-printers-panel.c index f4a03a6859d0fbbb56987ea81d01b9b318684064..394189122148237d09fb2622218dd8a9bd7206ee 100644 --- a/panels/printers/cc-printers-panel.c +++ b/panels/printers/cc-printers-panel.c @@ -24,11 +24,13 @@ #include "cc-printers-resources.h" #include "pp-printer.h" -#include +#include +#include +#include #include #include #include -#include +#include #include #include @@ -57,6 +59,19 @@ #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5) #define HAVE_CUPS_1_6 1 +#define OBJ_ATTR_SIZE 1024 +#define AVAHI_IF_UNSPEC -1 +#define AVAHI_PROTO_INET 0 +#define AVAHI_PROTO_INET6 1 +#define AVAHI_PROTO_UNSPEC -1 +#define SYSTEMD1_OBJ "/org/freedesktop/systemd1" +#define SYSTEMD1_BUS "org.freedesktop.systemd1" +#define SYSTEMD1_MANAGER_IFACE "org.freedesktop.systemd1.Manager" +#define SYSTEMD1_SERVICE_IFACE "org.freedesktop.systemd1.Service" +#define AVAHI_BUS "org.freedesktop.Avahi" +#define AVAHI_SERVER_IFACE "org.freedesktop.Avahi.Server" +#define AVAHI_SERVICE_BROWSER_IFACE "org.freedesktop.Avahi.ServiceBrowser" +#define AVAHI_SERVICE_RESOLVER_IFACE "org.freedesktop.Avahi.ServiceResolver" #endif #ifndef HAVE_CUPS_1_6 @@ -65,6 +80,61 @@ #define ippGetString(attr, element, language) attr->values[element].string.text #endif +enum +{ + SYSTEM_OBJECT, + PRINTER_OBJECT, + SCANNER_OBJECT, + PRINTER_QUEUE + +} obj_type; + +typedef struct +{ + ipp_t *response; + gchar *buff; + int buff_size; + cups_dest_t *service; +} add_attribute_data; + +typedef struct +{ + char *avahi_service_browser_path; + guint avahi_service_browser_subscription_id; + guint avahi_service_type_browser_subscription_id; + guint unsubscribe_general_subscription_id; + guint done, done_1, done_2, done_3, done_4; + GDBusConnection *dbus_connection; + GCancellable *avahi_cancellable; + GList *system_objects; + GMainLoop *loop; + gpointer user_data; + char *service_type; +} Avahi; + +typedef struct +{ + GList *services; + gchar *location; + gchar *address; + gchar *hostname; + gchar *name; + gchar *resource_path; + gchar *type; + gchar *domain; + gchar *object_type; + gchar *admin_url; + gchar *uri; + gchar *objAttr; + gchar *printer_type_hex; + gint64 printer_type, + printer_state; + gboolean got_printer_state, + got_printer_type; + int port; + int family; + gpointer user_data; +} AvahiData; struct _CcPrintersPanel { CcPanel parent_instance; @@ -80,8 +150,10 @@ struct _CcPrintersPanel GtkEditable *search_entry; AdwToastOverlay *toast_overlay; AdwToast *toast; + GHashTable *printer_groups; PpCups *cups; + Avahi *printer_device_backend; cups_dest_t *dests; int num_dests; @@ -140,6 +212,7 @@ static void free_dests (CcPrintersPanel *self); static void set_current_page (GObject *source_object, GAsyncResult *result, gpointer user_data); +static gboolean is_local_ip (const char *ip); static void execute_action (CcPrintersPanel *self, @@ -297,6 +370,42 @@ cc_printers_panel_dispose (GObject *object) detach_from_cups_notifier (CC_PRINTERS_PANEL (object)); + Avahi *printer_device_backend = self->printer_device_backend; + + for (int i = 0; i < 4; i++) + { + if (printer_device_backend[i].avahi_service_browser_subscription_id > 0) + { + g_dbus_connection_signal_unsubscribe (printer_device_backend[i].dbus_connection, + printer_device_backend[i].avahi_service_browser_subscription_id); + printer_device_backend[i].avahi_service_browser_subscription_id = 0; + } + + if (printer_device_backend[i].avahi_service_type_browser_subscription_id > 0) + { + g_dbus_connection_signal_unsubscribe (printer_device_backend[i].dbus_connection, + printer_device_backend[i].avahi_service_type_browser_subscription_id); + printer_device_backend[i].avahi_service_type_browser_subscription_id = 0; + } + + if (printer_device_backend[i].avahi_service_browser_path) + { + g_dbus_connection_call (printer_device_backend[i].dbus_connection, + AVAHI_BUS, + printer_device_backend[i].avahi_service_browser_path, + AVAHI_SERVICE_BROWSER_IFACE, + "Free", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); + g_clear_pointer (&printer_device_backend[i].avahi_service_browser_path, g_free); + } + } + if (self->deleted_printer_name != NULL) { g_autoptr(PpPrinter) printer = pp_printer_new (self->deleted_printer_name); @@ -709,43 +818,270 @@ on_printer_changed (CcPrintersPanel *self) actualize_printers_list (self); } +static gint +compare_printer_entries (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const gchar *uri_a = (const gchar *) g_object_get_data (G_OBJECT (PP_PRINTER_ENTRY ((gpointer) a)), "printer-uri"); + const gchar *uri_b = (const gchar *) g_object_get_data (G_OBJECT (PP_PRINTER_ENTRY ((gpointer) b)), "printer-uri"); + + if (uri_a == NULL) + uri_a = ""; + if (uri_b == NULL) + uri_b = ""; + + return g_strcmp0 (uri_a, uri_b); +} + static void -add_printer_entry (CcPrintersPanel *self, - cups_dest_t printer) +sort_printers_in_group (GtkWidget *group_box, const gchar *group_name) { - PpPrinterEntry *printer_entry; - GSList *widgets, *l; + GList *children = NULL; + GtkWidget *child; - printer_entry = pp_printer_entry_new (printer, self->is_authorized); + child = gtk_widget_get_first_child (group_box); + while (child) + { + if (PP_IS_PRINTER_ENTRY (child)) + { + children = g_list_prepend (children, child); + } + child = gtk_widget_get_next_sibling (child); + } + + if (g_list_length (children) > 1) + { + GList *l; + for (l = children; l != NULL; l = l->next) + { + g_object_ref (l->data); + gtk_box_remove (GTK_BOX (group_box), GTK_WIDGET (l->data)); + } + + children = g_list_sort_with_data (children, compare_printer_entries, (gpointer) group_name); + + for (l = children; l != NULL; l = l->next) + { + gtk_box_append (GTK_BOX (group_box), GTK_WIDGET (l->data)); + g_object_unref (l->data); + } + } + + g_list_free (children); +} + +gboolean +is_local_ip (const char *ip) +{ + GInetAddress *addr = g_inet_address_new_from_string (ip); + if (!addr) + { + g_printerr ("Invalid IP address: %s\n", ip); + return FALSE; + } + + if (g_inet_address_get_is_loopback (addr)) + { + g_object_unref (addr); + return TRUE; + } + + if (g_inet_address_get_is_link_local (addr)) + { + g_object_unref (addr); + return TRUE; + } + + GResolver *resolver = g_resolver_get_default (); + GList *addresses = g_resolver_lookup_by_name (resolver, g_get_host_name (), NULL, NULL); + + gboolean is_local = FALSE; + for (GList *l = addresses; l != NULL; l = l->next) + { + GInetAddress *local = G_INET_ADDRESS (l->data); + if (g_inet_address_equal (addr, local)) + { + is_local = TRUE; + break; + } + } + + g_list_free_full (addresses, g_object_unref); + g_object_unref (addr); + + return is_local; +} + +static void +add_printer_entry (CcPrintersPanel *self, cups_dest_t printer) +{ + PpPrinterEntry *printer_entry; + GSList *widgets, *l; + GtkWidget *group_box = NULL, *group_container = NULL; + const gchar *printer_uri = NULL; + gchar *group_key_copy = NULL; + gboolean isprinterapp = TRUE; + const gchar *group_name = NULL; + const gchar *ip_address = NULL; + const gchar *printer_type_hex = NULL; + const gchar *hostname = NULL; + const gchar *port = NULL; + + for (int i = 0; i < printer.num_options; i++) + { + if (g_strcmp0 (printer.options[i].name, "address") == 0) + ip_address = printer.options[i].value; + + if (g_strcmp0 (printer.options[i].name, "printer-type-hex") == 0) + printer_type_hex = printer.options[i].value; + + if (g_strcmp0 (printer.options[i].name, "printer-more-info") == 0) + printer_uri = printer.options[i].value; + + if (g_strcmp0 (printer.options[i].name, "hostname") == 0) + hostname = printer.options[i].value; + + if (g_strcmp0 (printer.options[i].name, "port") == 0) + port = printer.options[i].value; + } + + if (ip_address != NULL && printer_type_hex != NULL && + is_local_ip (ip_address) && + printer_type_hex[0] == '0' && + strncmp (printer_type_hex, "0x", 2) == 0) + { + return; + } + if (!printer_uri || *printer_uri == '\0') + { + printer_uri = printer.name; + isprinterapp = FALSE; + } + + gchar *group_key = NULL; + if (hostname && port) + group_key = g_strdup_printf ("%s:%s", hostname, port); + else + group_key = g_strdup (printer.name); + + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, self->printer_groups); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (g_strcmp0 (group_key, key) == 0) + { + group_container = GTK_WIDGET (value); + group_key_copy = g_strdup (key); + break; + } + } + + if (!group_container || !isprinterapp) + { + + group_key_copy = group_key; + group_container = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + + const gchar *group_title = printer.name; + if (isprinterapp) + { + for (int i = 0; i < printer.num_options; i++) + { + if (g_strcmp0 (printer.options[i].name, "printer-info") == 0) + { + group_title = printer.options[i].value; + break; + } + } + } + + GtkWidget *group_label = gtk_label_new (group_title); + gtk_widget_set_halign (group_label, GTK_ALIGN_START); + group_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_append (GTK_BOX (group_container), group_label); + gtk_widget_set_visible (group_label, FALSE); + gtk_box_append (GTK_BOX (group_container), group_box); + + g_object_set_data_full (G_OBJECT (group_container), + "group-title", + g_strdup (group_title), + g_free); + + gtk_list_box_insert (self->content, group_container, -1); + g_hash_table_insert (self->printer_groups, g_strdup (group_key_copy), group_container); + + group_name = group_title; + } + else + { + GtkWidget *child = gtk_widget_get_first_child (group_container); + while ((child = gtk_widget_get_next_sibling (child))) + { + if (GTK_IS_BOX (child)) + { + group_box = child; + break; + } + } + + group_name = (const gchar *) g_object_get_data (G_OBJECT (group_container), "group-title"); + } + + printer_entry = pp_printer_entry_new (printer, self->is_authorized); widgets = pp_printer_entry_get_size_group_widgets (printer_entry); for (l = widgets; l != NULL; l = l->next) gtk_size_group_add_widget (self->size_group, GTK_WIDGET (l->data)); g_slist_free (widgets); - g_signal_connect_object (printer_entry, - "printer-changed", + g_object_set_data_full (G_OBJECT (printer_entry), "printer-uri", g_strdup (printer_uri), g_free); + + g_signal_connect_object (printer_entry, "printer-changed", G_CALLBACK (on_printer_changed), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (printer_entry, - "printer-delete", + self, G_CONNECT_SWAPPED); + g_signal_connect_object (printer_entry, "printer-delete", G_CALLBACK (on_printer_deleted), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (printer_entry, - "printer-renamed", + self, G_CONNECT_SWAPPED); + g_signal_connect_object (printer_entry, "printer-renamed", G_CALLBACK (on_printer_renamed), - self, - G_CONNECT_SWAPPED); + self, G_CONNECT_SWAPPED); g_object_bind_property (self, "compact", printer_entry, "compact", G_BINDING_SYNC_CREATE); - gtk_list_box_insert (self->content, GTK_WIDGET (printer_entry), -1); + gtk_box_append (GTK_BOX (group_box), GTK_WIDGET (printer_entry)); + g_hash_table_insert (self->printer_entries, + g_strdup (printer.name), + printer_entry); + + if (group_name) + sort_printers_in_group (group_box, group_name); + + { + int count = 0; + GtkWidget *first_child = NULL; + GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (group_box)); + + while (child) + { + if (PP_IS_PRINTER_ENTRY (child)) + { + gtk_widget_remove_css_class (child, "first-in-group"); + if (count == 0) + first_child = child; + count++; + } + child = gtk_widget_get_next_sibling (child); + } + + if (count > 1 && first_child) + { + gtk_widget_add_css_class (first_child, "first-in-group"); + } + } - g_hash_table_insert (self->printer_entries, g_strdup (printer.name), printer_entry); + g_free (group_key); } static void @@ -770,6 +1106,9 @@ static gboolean remove_nonexisting_entry (CcPrintersPanel *self, PpPrinterEntry *entry) { + if (pp_printer_entry_get_web_interface (entry) != NULL) + return FALSE; + gboolean exists = FALSE; gint i; @@ -788,6 +1127,25 @@ remove_nonexisting_entry (CcPrintersPanel *self, return !exists; } +static void +add_ipp_device_cb (AvahiData *data, + cups_dest_t *dest) +{ + CcPrintersPanel *self = (CcPrintersPanel *) data->user_data; + gpointer item; + + g_message ("%d\n", data->user_data == NULL); + + gtk_stack_set_visible_child_name (self->main_stack, "printers-list"); + + item = g_hash_table_lookup (self->printer_entries, dest->name); + + if (item == NULL) + add_printer_entry (self, *dest); + + update_sensitivity (data->user_data); +} + static void actualize_printers_list_cb (GObject *source_object, GAsyncResult *result, @@ -876,7 +1234,6 @@ actualize_printers_list_cb (GObject *source_object, GtkAdjustment *adjustment; GtkWidget *printer_entry; - /* Scroll the view to show the newly added printer-entry. */ adjustment = gtk_scrolled_window_get_vadjustment (self->scrolled_window); printer_entry = GTK_WIDGET (g_hash_table_lookup (self->printer_entries, @@ -901,6 +1258,418 @@ actualize_printers_list (CcPrintersPanel *self) self); } +static int +compare_services (gconstpointer data1, + gconstpointer data2) +{ + AvahiData *data_1 = (AvahiData *) data1; + AvahiData *data_2 = (AvahiData *) data2; + + return g_strcmp0 (data_1->name, data_2->name); +} + +static void +add_option (cups_dest_t *dest, + gchar *attr_name, + gchar *attr_val) +{ + dest->options[dest->num_options].name = g_strdup (attr_name); + dest->options[dest->num_options].value = g_strdup (attr_val); + dest->num_options++; + + return; +} + +static gboolean +avahi_txt_get_key_value_pair (const gchar *entry, + gchar **key, + gchar **value) +{ + const gchar *equal_sign; + + *key = NULL; + *value = NULL; + + if (entry != NULL) + { + equal_sign = strstr (entry, "="); + + if (equal_sign != NULL) + { + *key = g_strndup (entry, equal_sign - entry); + *value = g_strdup (equal_sign + 1); + + return TRUE; + } + } + + return FALSE; +} + +static void +add_device (AvahiData *data) +{ + cups_dest_t *dest = g_new0 (cups_dest_t, 1); + dest->options = g_new0 (cups_option_t, 20); + dest->name = g_strdup (data->name); + add_option (dest, "domain", data->domain); + add_option (dest, "type", data->type); + add_option (dest, "address", data->address); + add_option (dest, "device-uri", data->uri); + add_option (dest, "printer-type-hex", data->printer_type_hex); + + if (data->admin_url != NULL) + add_option (dest, "printer-more-info", data->admin_url); + else + add_option (dest, "printer-more-info", g_strdup_printf ("http://%s:%d", data->hostname, data->port)); + + add_option (dest, "printer-location", data->location); + add_option (dest, "hostname", data->hostname); + add_option (dest, "port", g_strdup_printf ("%d", data->port)); + add_option (dest, "OBJ_TYPE", data->object_type); + + add_ipp_device_cb (data, dest); + return; +} + +static void +avahi_service_resolver_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + AvahiData *data; + Avahi *backend; + const char *name; + const char *hostname; + const char *type; + const char *domain; + const char *address; + char *key; + char *value; + char *tmp; + char *endptr; + GVariant *txt, + *child, + *output; + guint32 flags; + guint16 port; + GError *error = NULL; + GList *iter; + gsize length; + int interface; + int protocol; + int aprotocol; + int i; + + backend = user_data; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + + if (output) + { + + g_variant_get (output, "(ii&s&s&s&si&sq@aayu)", + &interface, + &protocol, + &name, + &type, + &domain, + &hostname, + &aprotocol, + &address, + &port, + &txt, + &flags); + + data = g_new0 (AvahiData, 1); + data->user_data = backend->user_data; + + if (g_strcmp0 (type, "_ipps-system._tcp") == 0 || + g_strcmp0 (type, "_ipp-system._tcp") == 0) + { + data->object_type = g_strdup ("SYSTEM_OBJECT"); + } + else + { + data->object_type = g_strdup ("PRINTER_OBJECT"); + } + + for (i = 0; i < g_variant_n_children (txt); i++) + { + child = g_variant_get_child_value (txt, i); + + length = g_variant_get_size (child); + if (length > 0) + { + tmp = g_strndup (g_variant_get_data (child), length); + g_variant_unref (child); + + if (!avahi_txt_get_key_value_pair (tmp, &key, &value)) + { + g_free (tmp); + continue; + } + + if (g_strcmp0 (key, "rp") == 0) + { + data->resource_path = g_strdup (value); + } + else if (g_strcmp0 (key, "note") == 0) + { + data->location = g_strdup (value); + } + else if (g_strcmp0 (key, "printer-type") == 0) + { + endptr = NULL; + data->printer_type_hex = g_strdup (value); + // data->printer_type = g_ascii_strtoull (value, &endptr, 16); + if (data->printer_type != 0 || endptr != value) + data->got_printer_type = TRUE; + } + else if (g_strcmp0 (key, "printer-state") == 0) + { + endptr = NULL; + data->printer_state = g_ascii_strtoull (value, &endptr, 10); + if (data->printer_state != 0 || endptr != value) + data->got_printer_state = TRUE; + } + else if (g_strcmp0 (key, "adminurl") == 0) + { + if (*value != '\0') + data->admin_url = g_strdup (value); + } + g_clear_pointer (&key, g_free); + g_clear_pointer (&value, g_free); + g_free (tmp); + } + else + { + g_variant_unref (child); + } + } + + data->address = g_strdup (address); + data->hostname = g_strdup (hostname); + data->port = port; + data->family = protocol; + data->name = g_strdup (name); + data->type = g_strdup (type); + data->domain = g_strdup (domain); + data->services = NULL; + + g_variant_unref (txt); + g_variant_unref (output); + + iter = g_list_find_custom (backend->system_objects, (gconstpointer) data, (GCompareFunc) compare_services); + if (iter == NULL) + { + backend->system_objects = g_list_append (backend->system_objects, data); + add_device (data); + } + else + { + g_free (data->location); + g_free (data->address); + g_free (data->hostname); + g_free (data->name); + g_free (data->resource_path); + g_free (data->type); + g_free (data->domain); + g_free (data); + } + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + char *message = g_strdup_printf ("%s", error->message); + g_free (message); + } + g_error_free (error); + } + + return; +} + +static void +avahi_service_browser_signal_handler (GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + Avahi *backend; + char *name; + char *type; + char *domain; + guint flags; + int interface; + int protocol; + + backend = user_data; + + if (g_strcmp0 (signal_name, "ItemNew") == 0) + { + g_variant_get (parameters, "(ii&s&s&su)", + &interface, + &protocol, + &name, + &type, + &domain, + &flags); + + g_dbus_connection_call (backend->dbus_connection, + AVAHI_BUS, + "/", + AVAHI_SERVER_IFACE, + "ResolveService", + g_variant_new ("(iisssiu)", + interface, + protocol, + name, + type, + domain, + AVAHI_PROTO_UNSPEC, + 0), + G_VARIANT_TYPE ("(iissssisqaayu)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + backend->avahi_cancellable, + avahi_service_resolver_cb, + backend); + } + else if (g_strcmp0 (signal_name, "ItemRemove") == 0) + { + g_variant_get (parameters, "(ii&s&s&su)", + &interface, + &protocol, + &name, + &type, + &domain, + &flags); + + GList *iter = g_list_find_custom (backend->system_objects, name, (GCompareFunc) compare_services); + if (iter != NULL) + { + backend->system_objects = g_list_delete_link (backend->system_objects, iter); + g_free (iter->data); + } + } + + return; +} + +static void +avahi_service_browser_new_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Avahi *printer_device_backend; + GError *error = NULL; + GVariant *output = NULL; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + printer_device_backend = user_data; + + if (output) + { + + g_variant_get (output, "(o)", &printer_device_backend->avahi_service_browser_path); + printer_device_backend->avahi_service_type_browser_subscription_id = + g_dbus_connection_signal_subscribe (printer_device_backend->dbus_connection, + NULL, + AVAHI_SERVICE_BROWSER_IFACE, + NULL, + printer_device_backend->avahi_service_browser_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + avahi_service_browser_signal_handler, + printer_device_backend, + NULL); + + if (printer_device_backend->avahi_service_browser_path && + printer_device_backend->avahi_service_type_browser_subscription_id > 0) + { + g_dbus_connection_signal_unsubscribe (printer_device_backend->dbus_connection, + printer_device_backend->avahi_service_browser_subscription_id); + printer_device_backend->avahi_service_browser_subscription_id = 0; + } + + g_variant_unref (output); + } + else + { + + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + + g_error_free (error); + } +} + +static void +cups_get_ipp_devices_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + Avahi *printer_device_backend; + g_autoptr (GError) error = NULL; + pp_cups_get_dests_finish (PP_CUPS (source_object), result, &error); + + CcPrintersPanel *self = (CcPrintersPanel *) user_data; + printer_device_backend = self->printer_device_backend; + for (int i = 0; i < 4; i++) + { + printer_device_backend[i].user_data = user_data; + + printer_device_backend[i].avahi_service_browser_subscription_id = + g_dbus_connection_signal_subscribe (printer_device_backend[i].dbus_connection, + NULL, + AVAHI_SERVICE_BROWSER_IFACE, + NULL, + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + avahi_service_browser_signal_handler, + &printer_device_backend[i], + NULL); + + g_dbus_connection_call (printer_device_backend[i].dbus_connection, + AVAHI_BUS, + "/", + AVAHI_SERVER_IFACE, + "ServiceBrowserNew", + g_variant_new ("(iissu)", + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + printer_device_backend[i].service_type, + "", + 0), + G_VARIANT_TYPE ("(o)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + printer_device_backend[i].avahi_cancellable, + avahi_service_browser_new_cb, + &printer_device_backend[i]); + } + return; +} + +static void +actualize_ipp_device_list (CcPrintersPanel *self) +{ + pp_cups_get_new_dests_async (self->cups, + cc_panel_get_cancellable (CC_PANEL (self)), + cups_get_ipp_devices_cb, + self); +} + static void printer_add_async_cb (GObject *source_object, GAsyncResult *res, @@ -927,6 +1696,7 @@ printer_add_async_cb (GObject *source_object, } actualize_printers_list (self); + actualize_ipp_device_list (self); } static void @@ -1015,6 +1785,7 @@ static void on_permission_changed (CcPrintersPanel *self) { actualize_printers_list (self); + actualize_ipp_device_list (self); update_sensitivity (self); } @@ -1162,21 +1933,25 @@ filter_function (GtkListBoxRow *row, static gint sort_function (GtkListBoxRow *row1, GtkListBoxRow *row2, - gpointer user_data) + gpointer user_data) { - PpPrinterEntry *entry1 = PP_PRINTER_ENTRY (row1); - PpPrinterEntry *entry2 = PP_PRINTER_ENTRY (row2); - if (pp_printer_entry_get_name (entry1) != NULL) + GtkWidget *child1 = gtk_list_box_row_get_child (row1); + GtkWidget *child2 = gtk_list_box_row_get_child (row2); + + const gchar *name1 = (const gchar *) g_object_get_data (G_OBJECT (child1), "group-title"); + const gchar *name2 = (const gchar *) g_object_get_data (G_OBJECT (child2), "group-title"); + + if (name1 != NULL) { - if (pp_printer_entry_get_name (entry2) != NULL) - return g_ascii_strcasecmp (pp_printer_entry_get_name (entry1), pp_printer_entry_get_name (entry2)); + if (name2 != NULL) + return g_ascii_strcasecmp (name1, name2); else return 1; } else { - if (pp_printer_entry_get_name (entry2) != NULL) + if (name2 != NULL) return -1; else return 0; @@ -1230,8 +2005,8 @@ cc_printers_panel_class_init (CcPrintersPanelClass *klass) static void cc_printers_panel_init (CcPrintersPanel *self) { - g_autoptr(GtkCssProvider) provider = NULL; - + g_autoptr (GtkCssProvider) provider = NULL; + self->printer_groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); gtk_widget_init_template (GTK_WIDGET (self)); g_resources_register (cc_printers_get_resource ()); @@ -1278,8 +2053,8 @@ cc_printers_panel_init (CcPrintersPanel *self) G_CONNECT_SWAPPED | G_CONNECT_AFTER); /* Add unlock button */ - self->permission = (GPermission *)polkit_permission_new_sync ( - "org.opensuse.cupspkhelper.mechanism.all-edit", NULL, NULL, NULL); + self->permission = (GPermission *) polkit_permission_new_sync ( + "org.opensuse.cupspkhelper.mechanism.all-edit", NULL, NULL, NULL); if (self->permission != NULL) { g_signal_connect_object (self->permission, @@ -1299,7 +2074,22 @@ Please check your installation"); self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->printer_device_backend = g_new0 (Avahi, 4); + for (int x = 0; x < 4; x++) + { + self->printer_device_backend[x].system_objects = NULL; + self->printer_device_backend[x].avahi_cancellable = g_cancellable_new (); + self->printer_device_backend[x].dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, self->printer_device_backend[x].avahi_cancellable, NULL); + self->printer_device_backend[x].loop = g_main_loop_new (NULL, FALSE); + } + self->printer_device_backend[0].service_type = "_ipps-system._tcp"; + self->printer_device_backend[1].service_type = "_ipp-system._tcp"; + self->printer_device_backend[2].service_type = "_ipps._tcp"; + self->printer_device_backend[3].service_type = "_ipp._tcp"; + + actualize_ipp_device_list (self); actualize_printers_list (self); + attach_to_cups_notifier (self); get_all_ppds_async (cc_panel_get_cancellable (CC_PANEL (self)), diff --git a/panels/printers/pp-cups.c b/panels/printers/pp-cups.c index 9942579371cc220a372a4a318f8dd6b5845da8e2..0017f71f7132b01d9acbe6980709d16e492a644c 100644 --- a/panels/printers/pp-cups.c +++ b/panels/printers/pp-cups.c @@ -18,9 +18,9 @@ * Author: Marek Kasik */ -#include "config.h" - #include "pp-cups.h" +#include "config.h" +#include #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5) #define HAVE_CUPS_1_6 1 @@ -31,12 +31,56 @@ #define ippGetStatusCode(ipp) ipp->request.status.status_code #endif +typedef struct +{ + char *avahi_service_browser_path; + guint avahi_service_browser_subscription_id; + guint avahi_service_browser_subscription_id_ind; + guint unsubscribe_general_subscription_id; + guint done, done_1, done_2, done_3, done_4; + GDBusConnection *dbus_connection; + GCancellable *avahi_cancellable; + GList *system_objects; + GMainLoop *loop; + gpointer user_data; + char *service_type; +} Avahi; + +typedef struct +{ + GList *services; + gchar *location; + gchar *address; + gchar *hostname; + gchar *name; + gchar *resource_path; + gchar *type; + gchar *domain; + gchar *object_type; + gchar *admin_url; + gchar *uri; + gchar *objAttr; + gint64 printer_type, + printer_state; + gboolean got_printer_state, + got_printer_type; + int port; + int family; + gpointer user_data; +} AvahiData; + +typedef struct +{ + cups_dest_t *dests; + int num_of_dests; +} EnumData; struct _PpCups { GObject parent_instance; }; G_DEFINE_TYPE (PpCups, pp_cups, G_TYPE_OBJECT); +static Avahi *cupsGetIPPDevices (gpointer user_data); static void pp_cups_class_init (PpCupsClass *klass) @@ -54,12 +98,39 @@ pp_cups_new () return g_object_new (PP_TYPE_CUPS, NULL); } +// static void +// pp_cups_dests_free (PpCupsDests *dests) +// { +// cupsFreeDests (dests->num_of_dests, dests->dests); +// } + +static int +add_dest_cb (void *user_data, unsigned flags, cups_dest_t *dest) +{ + EnumData *data = (EnumData *) user_data; + + if (flags & CUPS_DEST_FLAGS_REMOVED) + return 1; // skip removed printers + + // Append dest to our list + data->num_of_dests = cupsCopyDest (dest, data->num_of_dests, &data->dests); + + return 1; // continue enumeration +} + static void pp_cups_dests_free (PpCupsDests *dests) { - cupsFreeDests (dests->num_of_dests, dests->dests); + if (dests) + { + if (dests->dests) + cupsFreeDests (dests->num_of_dests, dests->dests); + g_free (dests); + } } + + static void _pp_cups_get_dests_thread (GTask *task, gpointer *object, @@ -67,18 +138,78 @@ _pp_cups_get_dests_thread (GTask *task, GCancellable *cancellable) { PpCupsDests *dests; + EnumData data = { NULL, 0 }; + + // enumerate with 1s timeout + cupsEnumDests (CUPS_DEST_FLAGS_NONE, + 1000, // timeout (ms) + NULL, CUPS_PRINTER_LOCAL, CUPS_PRINTER_DISCOVERED, // no monitor, match-all + (cups_dest_cb_t) add_dest_cb, + &data); dests = g_new0 (PpCupsDests, 1); - dests->num_of_dests = cupsGetDests (&dests->dests); + dests->num_of_dests = data.num_of_dests; + dests->dests = data.dests; + + if (g_task_set_return_on_cancel (task, FALSE)) + g_task_return_pointer (task, dests, (GDestroyNotify) pp_cups_dests_free); + else + pp_cups_dests_free (dests); +} + +static void +_pp_cups_get_new_dests_thread (GTask *task, + gpointer *object, + gpointer task_data, + GCancellable *cancellable) +{ + Avahi *backends; + + backends = cupsGetIPPDevices (NULL); if (g_task_set_return_on_cancel (task, FALSE)) { - g_task_return_pointer (task, dests, (GDestroyNotify) pp_cups_dests_free); + g_task_return_pointer (task, backends, (GDestroyNotify) pp_cups_dests_free); } else { - pp_cups_dests_free (dests); + // pp_cups_dests_free (backends); + g_message ("Cancelled discovery for ipp devices\n"); + } +} + +Avahi * +cupsGetIPPDevices (gpointer user_data) +{ + Avahi *printer_device_backend; + + printer_device_backend = g_new0 (Avahi, 4); + for (int x = 0; x < 4; x++) + { + printer_device_backend[x].system_objects = NULL; + printer_device_backend[x].avahi_cancellable = g_cancellable_new (); + printer_device_backend[x].dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, printer_device_backend[x].avahi_cancellable, NULL); + printer_device_backend[x].loop = g_main_loop_new (NULL, FALSE); } + printer_device_backend[0].service_type = "_ipps-system._tcp"; + printer_device_backend[1].service_type = "_ipp-system._tcp"; + printer_device_backend[2].service_type = "_ipps._tcp"; + printer_device_backend[3].service_type = "_ipp._tcp"; + + return printer_device_backend; +} + +void +pp_cups_get_new_dests_async (PpCups *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr (GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_return_on_cancel (task, TRUE); + g_task_run_in_thread (task, (GTaskThreadFunc) _pp_cups_get_new_dests_thread); } void diff --git a/panels/printers/pp-cups.h b/panels/printers/pp-cups.h index 04063bb1c089fe1b051d83447f7fd743a8f5dde3..1d44ef72b8eccf869a18ddf41b22f18b814813ff 100644 --- a/panels/printers/pp-cups.h +++ b/panels/printers/pp-cups.h @@ -41,6 +41,11 @@ void pp_cups_get_dests_async (PpCups *cups, GAsyncReadyCallback callback, gpointer user_data); +void pp_cups_get_new_dests_async (PpCups *cups, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + PpCupsDests *pp_cups_get_dests_finish (PpCups *cups, GAsyncResult *result, GError **error); diff --git a/panels/printers/pp-printer-entry.blp b/panels/printers/pp-printer-entry.blp index 6f11cd5a5a4f00c90c46ff6044488e618c938107..6e69df1b3d81bb8f3fdf7ea2c5866f65e9cab756 100644 --- a/panels/printers/pp-printer-entry.blp +++ b/panels/printers/pp-printer-entry.blp @@ -106,7 +106,7 @@ template $PpPrinterEntry: ListBoxRow { } } - Label { + Label printer_status_label { label: _("Status"); xalign: 1; mnemonic-widget: printer_status; @@ -218,20 +218,29 @@ template $PpPrinterEntry: ListBoxRow { menu printer_menu { section { + item { + label: _("Web Interface"); + action: "printer.webinterface"; + hidden-when: "action-disabled"; + } + item { label: _("Printing Options"); action: "printer.options"; + hidden-when: "action-disabled"; } item { label: _("Printer Details"); action: "printer.details"; + hidden-when: "action-disabled"; } item { /* Translators: Set this printer as default */ label: _("Use Printer by Default"); action: "printer.default"; + hidden-when: "action-disabled"; } item { @@ -244,6 +253,7 @@ menu printer_menu { item { label: _("Remove Printer"); action: "printer.remove"; + hidden-when: "action-disabled"; } } } diff --git a/panels/printers/pp-printer-entry.c b/panels/printers/pp-printer-entry.c index fbaac9a35a425ca41938f3bd1dc4bd25e1acb532..66ffa3ad0170310ef4c9a696e1f5cc89c39acc1f 100644 --- a/panels/printers/pp-printer-entry.c +++ b/panels/printers/pp-printer-entry.c @@ -49,10 +49,12 @@ struct _PpPrinterEntry gboolean is_accepting_jobs; gchar *printer_make_and_model; gchar *printer_location; + gchar *web_interface; gchar *printer_hostname; gboolean is_authorized; gint printer_state; InkLevelData *inklevel; + gboolean is_system_printer; /* Maintenance commands */ PpMaintenanceCommand *clean_command; @@ -61,6 +63,7 @@ struct _PpPrinterEntry /* Widgets */ GtkOrientable *header_box; GtkLabel *printer_status; + GtkLabel *printer_status_label; GtkLabel *printer_name_label; GtkLabel *printer_model_label; GtkLabel *printer_model; @@ -71,6 +74,9 @@ struct _PpPrinterEntry GtkDrawingArea *supply_drawing_area; GtkWidget *show_jobs_dialog_button; GtkBox *printer_error; + GtkButton *printer_detail_btn; + GtkButton *printer_options_dialog_btn; + GtkLinkButton *web_interface_btn; GtkLabel *error_status; gboolean is_default; @@ -127,10 +133,17 @@ ink_level_data_free (InkLevelData *data) static void pp_printer_entry_init (PpPrinterEntry *self) { + GtkCssProvider *provider; + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.clean-heads", FALSE); gtk_widget_init_template (GTK_WIDGET (self)); self->inklevel = ink_level_data_new (); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_string (provider, ".system-printer { font-size: clamp(1em, 0.08em + 0.7vw, 1.15em); } .first-in-group { border-bottom: 1px solid #c0c0c0; }"); + gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); } typedef struct { @@ -366,6 +379,22 @@ on_printer_rename_cb (GObject *source_object, g_signal_emit_by_name (self, "printer-renamed", pp_printer_get_name (PP_PRINTER (source_object))); } +static void +on_click_web_interface (PpPrinterEntry *self) +{ + GtkUriLauncher *launcher; + launcher = gtk_uri_launcher_new (self->web_interface); + gtk_uri_launcher_launch (launcher, NULL, NULL, NULL, NULL); + g_object_unref (launcher); +} + +static void +on_click_web_interface_action (GtkWidget *widget, const char *action_name, GVariant *parameter) +{ + PpPrinterEntry *self = PP_PRINTER_ENTRY (widget); + on_click_web_interface (self); +} + static void show_printer_details_response_cb (PpPrinterEntry *self, PpDetailsDialog *dialog) @@ -534,18 +563,37 @@ get_jobs_cb (GObject *source_object, return; } - if (jobs->len == 0) + if (self->is_system_printer) { - /* Translators: This is the label of the button that opens the Jobs Dialog. */ - button_label = g_strdup (_("No Active Jobs")); + if (jobs->len == 0) + { + gtk_widget_set_visible (self->show_jobs_dialog_button, FALSE); + } + else + { + /* Translators: This is the label of the button that opens the Jobs Dialog. */ + button_label = g_strdup_printf (ngettext ("%u Job", "%u Jobs", jobs->len), jobs->len); + gtk_widget_set_visible (self->show_jobs_dialog_button, TRUE); + } } else { - /* Translators: This is the label of the button that opens the Jobs Dialog. */ - button_label = g_strdup_printf (ngettext ("%u Job", "%u Jobs", jobs->len), jobs->len); + if (jobs->len == 0) + { + /* Translators: This is the label of the button that opens the Jobs Dialog. */ + button_label = g_strdup (_("No Active Jobs")); + } + else + { + /* Translators: This is the label of the button that opens the Jobs Dialog. */ + button_label = g_strdup_printf (ngettext ("%u Job", "%u Jobs", jobs->len), jobs->len); + } + + gtk_widget_set_visible (self->show_jobs_dialog_button, TRUE); } - gtk_button_set_label (GTK_BUTTON (self->show_jobs_dialog_button), button_label); + if (button_label != NULL) + gtk_button_set_label (GTK_BUTTON (self->show_jobs_dialog_button), button_label); gtk_widget_set_sensitive (self->show_jobs_dialog_button, jobs->len > 0); if (self->pp_jobs_dialog != NULL) @@ -641,6 +689,7 @@ pp_printer_entry_get_size_group_widgets (PpPrinterEntry *self) { GSList *widgets = NULL; + widgets = g_slist_prepend (widgets, self->printer_status_label); widgets = g_slist_prepend (widgets, self->printer_location_label); widgets = g_slist_prepend (widgets, self->printer_model_label); widgets = g_slist_prepend (widgets, self->printer_inklevel_label); @@ -682,6 +731,20 @@ pp_printer_entry_get_name (PpPrinterEntry *self) return self->printer_name; } +const gchar * +pp_printer_entry_get_web_interface (PpPrinterEntry *self) +{ + g_return_val_if_fail (PP_IS_PRINTER_ENTRY (self), NULL); + return self->web_interface; +} + +const gchar * +pp_printer_entry_get_hostname (PpPrinterEntry *self) +{ + g_return_val_if_fail (PP_IS_PRINTER_ENTRY (self), NULL); + return self->printer_hostname; +} + const gchar * pp_printer_entry_get_location (PpPrinterEntry *self) { @@ -697,11 +760,15 @@ pp_printer_entry_update (PpPrinterEntry *self, cups_ptype_t printer_type = 0; gboolean is_accepting_jobs = TRUE; gboolean ink_supply_is_empty; + gboolean sanitize_name = FALSE; g_autofree gchar *instance = NULL; const gchar *printer_uri = NULL; + gchar *web_interface = NULL; + const gchar *dev_type = NULL; const gchar *device_uri = NULL; const gchar *location = NULL; const gchar *printer_make_and_model = NULL; + const gchar *printer_commands = NULL; const gchar *reason = NULL; gchar **printer_reasons = NULL; g_autofree gchar *status = NULL; @@ -777,8 +844,23 @@ pp_printer_entry_update (PpPrinterEntry *self, for (i = 0; i < printer.num_options; i++) { + if (g_strcmp0 (printer.options[i].name, "device-uri") == 0) device_uri = printer.options[i].value; + else if (g_strcmp0 (printer.options[i].name, "printer-more-info") == 0) + { + web_interface = printer.options[i].value, self->web_interface = web_interface; + } + else if (g_strcmp0 (printer.options[i].name, "OBJ_TYPE") == 0) + { + dev_type = printer.options[i].value; + if (g_strcmp0 (dev_type, "SYSTEM_OBJECT") == 0) + self->is_system_printer = TRUE; + } + else if (g_strcmp0 (printer.options[i].name, "sanitize-name") == 0) + sanitize_name = TRUE; + else if (g_strcmp0 (printer.options[i].name, "printer-commands") == 0) + printer_commands = printer.options[i].value; else if (g_strcmp0 (printer.options[i].name, "printer-uri-supported") == 0) printer_uri = printer.options[i].value; else if (g_strcmp0 (printer.options[i].name, "printer-type") == 0) @@ -865,7 +947,8 @@ pp_printer_entry_update (PpPrinterEntry *self, } if ((self->printer_state == PRINTER_STOPPED || !is_accepting_jobs) && - status != NULL && status[0] != '\0') + status != NULL && status[0] != '\0' && + !(self->web_interface != NULL && *self->web_interface != '\0' && self->is_system_printer)) { gtk_label_set_label (self->error_status, status); gtk_widget_set_visible (GTK_WIDGET (self->printer_error), TRUE); @@ -909,11 +992,32 @@ pp_printer_entry_update (PpPrinterEntry *self, g_free (self->printer_hostname); self->printer_hostname = printer_get_hostname (printer_type, device_uri, printer_uri); - gtk_label_set_text (self->printer_status, printer_status); + if (self->web_interface != NULL && *self->web_interface != '\0' && self->is_system_printer) + { + gtk_widget_set_visible (GTK_WIDGET (self->printer_status_label), FALSE); + gtk_widget_set_visible (GTK_WIDGET (self->printer_status), FALSE); + gtk_label_set_text (self->printer_status, ""); + } + else + { + gtk_widget_set_visible (GTK_WIDGET (self->printer_status_label), TRUE); + gtk_widget_set_visible (GTK_WIDGET (self->printer_status), TRUE); + gtk_label_set_text (self->printer_status, printer_status); + } gtk_label_set_text (self->printer_name_label, instance); self->is_default = printer.is_default; g_object_notify (G_OBJECT (self), "default"); + if (dev_type == NULL || sanitize_name == TRUE) + self->printer_make_and_model = sanitize_printer_model (printer_make_and_model); + else + { + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.options", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.details", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.remove", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.default", FALSE); + } + self->printer_make_and_model = sanitize_printer_model (printer_make_and_model); if (self->printer_make_and_model == NULL || self->printer_make_and_model[0] == '\0') @@ -926,24 +1030,42 @@ pp_printer_entry_update (PpPrinterEntry *self, gtk_label_set_text (self->printer_model, self->printer_make_and_model); } - if (location != NULL && location[0] == '\0') + if ((location != NULL && location[0] == '\0') || (self->web_interface != NULL && *self->web_interface != '\0' && self->is_system_printer)) { gtk_widget_set_visible (GTK_WIDGET (self->printer_location_label), FALSE); gtk_widget_set_visible (GTK_WIDGET (self->printer_location_address_label), FALSE); + gtk_label_set_text (self->printer_location_address_label, ""); } else { + gtk_widget_set_visible (GTK_WIDGET (self->printer_location_label), TRUE); + gtk_widget_set_visible (GTK_WIDGET (self->printer_location_address_label), TRUE); gtk_label_set_text (self->printer_location_address_label, location); } + if (web_interface != NULL || device_uri == NULL) + { + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.options", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.details", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.remove", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.default", FALSE); + } + + if (printer_commands != NULL) + { + gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.webinterface", FALSE); + } + ink_supply_is_empty = supply_level_is_empty (self); gtk_widget_set_visible (GTK_WIDGET (self->printer_inklevel_label), !ink_supply_is_empty); gtk_widget_set_visible (GTK_WIDGET (self->supply_frame), !ink_supply_is_empty); - pp_printer_entry_update_jobs_count (self); + if (self->is_system_printer) + gtk_widget_add_css_class (GTK_WIDGET (self), "system-printer"); + else + gtk_widget_remove_css_class (GTK_WIDGET (self), "system-printer"); - gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.default", self->is_authorized); - gtk_widget_action_set_enabled (GTK_WIDGET (self), "printer.remove", self->is_authorized); + pp_printer_entry_update_jobs_count (self); } static void @@ -1036,6 +1158,7 @@ pp_printer_entry_class_init (PpPrinterEntryClass *klass) gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, header_box); gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_name_label); gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_status); + gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_status_label); gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_model_label); gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_model); gtk_widget_class_bind_template_child (widget_class, PpPrinterEntry, printer_location_label); @@ -1104,4 +1227,6 @@ pp_printer_entry_class_init (PpPrinterEntryClass *klass) (GtkWidgetActionActivateFunc) printer_clean_heads_cb); gtk_widget_class_install_action (widget_class, "printer.remove", NULL, (GtkWidgetActionActivateFunc) printer_remove_cb); + gtk_widget_class_install_action (widget_class, "printer.webinterface", NULL, + on_click_web_interface_action); } diff --git a/panels/printers/pp-printer-entry.h b/panels/printers/pp-printer-entry.h index 1c6b3dced428f23e99224632fe195b64f39c3f98..a4225e6e003dd9bed7e42c15d7c87a1b140ba076 100644 --- a/panels/printers/pp-printer-entry.h +++ b/panels/printers/pp-printer-entry.h @@ -30,6 +30,10 @@ PpPrinterEntry *pp_printer_entry_new (cups_dest_t printer, const gchar *pp_printer_entry_get_name (PpPrinterEntry *self); +const gchar *pp_printer_entry_get_hostname (PpPrinterEntry *self); + +const gchar *pp_printer_entry_get_web_interface (PpPrinterEntry *self); + const gchar *pp_printer_entry_get_location (PpPrinterEntry *self); void pp_printer_entry_update_jobs_count (PpPrinterEntry *self);