diff --git a/backend/pdf/ev-poppler.c b/backend/pdf/ev-poppler.c index 5c4c0edd0c5e948debcc497c4b240c9e900e6a44..845bedfe458ac1b6ab567c5c7b4c3f4cd502cf42 100644 --- a/backend/pdf/ev-poppler.c +++ b/backend/pdf/ev-poppler.c @@ -55,6 +55,7 @@ #include "ev-document-annotations.h" #include "ev-document-attachments.h" #include "ev-document-text.h" +#include "ev-document-signatures.h" #include "ev-form-field-private.h" #include "ev-selection.h" #include "ev-transition-effect.h" @@ -119,6 +120,7 @@ static void pdf_document_document_print_iface_init (EvDocumentPrintInterfa static void pdf_document_document_annotations_iface_init (EvDocumentAnnotationsInterface *iface); static void pdf_document_document_attachments_iface_init (EvDocumentAttachmentsInterface *iface); static void pdf_document_document_media_iface_init (EvDocumentMediaInterface *iface); +static void pdf_document_signatures_iface_init (EvDocumentSignaturesInterface *iface); static void pdf_document_find_iface_init (EvDocumentFindInterface *iface); static void pdf_document_file_exporter_iface_init (EvFileExporterInterface *iface); static void pdf_selection_iface_init (EvSelectionInterface *iface); @@ -168,6 +170,8 @@ EV_BACKEND_REGISTER_WITH_CODE (PdfDocument, pdf_document, pdf_document_page_transition_iface_init); EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_TEXT, pdf_document_text_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_SIGNATURES, + pdf_document_signatures_iface_init); }); static void @@ -4075,3 +4079,160 @@ pdf_document_document_layers_iface_init (EvDocumentLayersInterface *iface) iface->hide_layer = pdf_document_layers_hide_layer; iface->layer_is_visible = pdf_document_layers_layer_is_visible; } + +static PopplerCertificateInfo * +find_poppler_certificate_info_by_id (EvCertificateInfo *ev_cert_info) +{ + GList *certs = poppler_get_available_signing_certificates (); + + for (GList *list = certs; list != NULL; list = list->next) { + PopplerCertificateInfo *cinfo = list->data; + + if (g_strcmp0 (ev_certificate_info_get_id (ev_cert_info), poppler_certificate_info_get_id (cinfo)) == 0) + return cinfo; + } + + return NULL; +} + +/* Signatures */ +static void +pdf_document_signatures_sign (EvDocumentSignatures *document, + EvSignaturesData *data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + PopplerColor *color; + PopplerSigningData *signing_data = poppler_signing_data_new (); + PopplerCertificateInfo *cert_info = find_poppler_certificate_info_by_id (data->certificate_info); + + if (!cert_info) + return; + + poppler_signing_data_set_certificate_info (signing_data, cert_info); + poppler_signing_data_set_page (signing_data, data->page); + poppler_signing_data_set_field_partial_name(signing_data, g_uuid_string_random()); + poppler_signing_data_set_destination_filename (signing_data, data->destination_file); + + if (data->password) + poppler_signing_data_set_password (signing_data, data->password); + poppler_signing_data_set_signature_text (signing_data, data->signature); + poppler_signing_data_set_signature_text_left (signing_data, data->signature_left); + + color = poppler_color_new(); + color->red = data->font_color.red * 255; + color->green = data->font_color.green * 255; + color->blue = data->font_color.blue * 255; + poppler_signing_data_set_font_color (signing_data, color); + g_clear_pointer (&color, poppler_color_free); + + poppler_signing_data_set_font_size (signing_data, data->font_size); + poppler_signing_data_set_left_font_size (signing_data, data->left_font_size); + poppler_signing_data_set_border_width (signing_data, data->border_width); + + color = poppler_color_new(); + color->red = data->border_color.red * 255; + color->green = data->border_color.green * 255; + color->blue = data->border_color.blue * 255; + poppler_signing_data_set_border_color (signing_data, color); + g_clear_pointer (&color, poppler_color_free); + + color = poppler_color_new(); + color->red = data->background_color.red * 255; + color->green = data->background_color.green * 255; + color->blue = data->background_color.blue * 255; + poppler_signing_data_set_background_color (signing_data, color); + g_clear_pointer (&color, poppler_color_free); + + if (data->document_owner_password) + poppler_signing_data_set_document_owner_password (signing_data, data->document_owner_password); + + if (data->document_user_password) + poppler_signing_data_set_document_user_password (signing_data, data->document_user_password); + + gdouble height; + ev_document_get_page_size (EV_DOCUMENT (document), data->page, NULL, &height); + + PopplerRectangle *signing_rect = poppler_rectangle_new (); + signing_rect->x1 = data->rect->x1; + signing_rect->y1 = height - data->rect->y1; + signing_rect->x2 = data->rect->x2; + signing_rect->y2 = height - data->rect->y2; + + poppler_signing_data_set_signature_rectangle (signing_data, signing_rect); + + poppler_document_sign (POPPLER_DOCUMENT (pdf_document->document), signing_data, cancellable, callback, user_data); +} + +static gboolean +pdf_document_signatures_can_sign (EvDocumentSignatures *document) +{ + return TRUE; +} + +static void +pdf_document_set_password_callback (EvDocumentSignatures *document, + EvSignaturePasswordCallback cb) +{ + poppler_set_nss_password_callback (cb); +} + +static GList * +pdf_document_get_available_signing_certifcates (EvDocumentSignatures *document) +{ + GList *certs = poppler_get_available_signing_certificates(); + GList *ev_certs = NULL; + + if (!certs) + return NULL; + + for (GList *list = certs; list != NULL; list = list->next) { + PopplerCertificateInfo *info = list->data; + EvCertificateInfo *cert_info = ev_certificate_info_new (poppler_certificate_info_get_id (info), poppler_certificate_info_get_subject_common_name (info)); + + ev_certs = g_list_append (ev_certs, cert_info); + } + + g_list_free_full (certs, (GDestroyNotify) poppler_certificate_info_free); + + return ev_certs; +} + +static EvCertificateInfo * +pdf_document_get_certificate_info (EvDocumentSignatures *document, + const char *id) +{ + GList *list; + EvCertificateInfo *info = NULL; + + if (!id || strlen (id) == 0) + return NULL; + + for (list = pdf_document_get_available_signing_certifcates (document); list != NULL; list = list->next) { + EvCertificateInfo *cert_info = list->data; + + if (g_strcmp0 (ev_certificate_info_get_id (cert_info), id) == 0) { + info = ev_certificate_info_copy (cert_info); + break; + } + } + + g_list_free_full (list, (GDestroyNotify) ev_certificate_info_free); + + return info; +} + +static void +pdf_document_signatures_iface_init (EvDocumentSignaturesInterface *iface) +{ +#if POPPLER_CHECK_VERSION(22, 2, 0) + iface->set_password_callback = pdf_document_set_password_callback; + iface->get_available_signing_certificates = pdf_document_get_available_signing_certifcates; + iface->get_certificate_info = pdf_document_get_certificate_info; + iface->sign = pdf_document_signatures_sign; + iface->can_sign = pdf_document_signatures_can_sign; +#endif +} + diff --git a/libdocument/ev-document-signatures.c b/libdocument/ev-document-signatures.c new file mode 100644 index 0000000000000000000000000000000000000000..db242698c1231dc83eec4f2f88d3468e954ab491 --- /dev/null +++ b/libdocument/ev-document-signatures.c @@ -0,0 +1,232 @@ +/* ev-document-signatures.h + * this file is part of evince, a gnome document_links viewer + * + * Copyright (C) 2022 Jan-Michael Brummer + * + * Evince 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. + * + * Evince is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include "ev-document-signatures.h" + +G_DEFINE_INTERFACE (EvDocumentSignatures, ev_document_signatures, 0) + +static void +ev_document_signatures_default_init (EvDocumentSignaturesInterface *klass) +{ +} + +/** + * ev_document_signatures_set_password_callback: + * @document_signatures: an #EvDocumentSignatures + * @cb: (scope call): an #EvSignaturePasswordCallback + */ +void +ev_document_signatures_set_password_callback (EvDocumentSignatures *document_signatures, EvSignaturePasswordCallback cb) +{ + EvDocumentSignaturesInterface *iface = EV_DOCUMENT_SIGNATURES_GET_IFACE (document_signatures); + + if (iface->set_password_callback) + iface->set_password_callback (document_signatures, cb); +} + +/** + * ev_document_signatures_get_available_signing_certificates: + * @document_signatures: an #EvDocumentSignatures + * + * Returns: (transfer full) (element-type EvCertificateInfo): a list of #EvCertificateInfo objects or %NULL + */ +GList * +ev_document_signatures_get_available_signing_certificates (EvDocumentSignatures *document_signatures) +{ + EvDocumentSignaturesInterface *iface = EV_DOCUMENT_SIGNATURES_GET_IFACE (document_signatures); + + if (iface->get_available_signing_certificates) + return iface->get_available_signing_certificates (document_signatures); + + return NULL; +} + +/** + * ev_document_signature_get_certificate: (skip) + * @document_signatures: an #EvDocumentSignatures + * @nick_name: certificate nick name + * + * Returns: (transfer full): a new #EvCertificateInfo, or %NULL + */ +EvCertificateInfo * +ev_document_signature_get_certificate_info (EvDocumentSignatures *document_signatures, + const char *id) +{ + EvDocumentSignaturesInterface *iface = EV_DOCUMENT_SIGNATURES_GET_IFACE (document_signatures); + + if (iface->get_certificate_info) + return iface->get_certificate_info (document_signatures, id); + + return NULL; +} + +gboolean +ev_document_signatures_sign (EvDocumentSignatures *document_signatures, + EvSignaturesData *data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EvDocumentSignaturesInterface *iface = EV_DOCUMENT_SIGNATURES_GET_IFACE (document_signatures); + + if (iface->sign) + iface->sign (document_signatures, data, cancellable, callback, user_data); + return TRUE; + + return FALSE; +} + +gboolean +ev_document_signatures_can_sign (EvDocumentSignatures *document_signatures) +{ + EvDocumentSignaturesInterface *iface = EV_DOCUMENT_SIGNATURES_GET_IFACE (document_signatures); + + if (iface->can_sign) + return iface->can_sign (document_signatures); + + return FALSE; +} + +/** + * ev_document_signatures_data_new: (skip) + * + * Returns: (transfer full): a new #EvSignaturesData + */ +EvSignaturesData * +ev_document_signatures_data_new (void) +{ + EvSignaturesData *data = g_malloc0 (sizeof (EvSignaturesData)); + + gdk_rgba_parse (&data->font_color, "#000000"); + gdk_rgba_parse (&data->border_color, "#000000"); + gdk_rgba_parse (&data->background_color, "#F0F0F0"); + + data->font_size = 10.0; + data->left_font_size = 20.0; + data->border_width = 1.5; + + return data; +} + +void +ev_document_signatures_data_free (EvSignaturesData *data) +{ + g_clear_object (&data->certificate_info); + g_clear_pointer (&data->destination_file, g_free); + g_clear_pointer (&data->password, g_free); + g_clear_pointer (&data->signature, g_free); + g_clear_pointer (&data->signature_left, g_free); + g_clear_pointer (&data->rect, ev_rectangle_free); + g_clear_pointer (&data->document_owner_password, g_free); + g_clear_pointer (&data->document_user_password, g_free); + + g_free (data); +} + +void +ev_document_signatures_data_set_certificate_info (EvSignaturesData *data, + const EvCertificateInfo *certificate_info) +{ + g_clear_object (&data->certificate_info); + data->certificate_info = ev_certificate_info_copy (certificate_info); +} + +void +ev_document_signatures_data_set_destination_file (EvSignaturesData *data, + const char *file) +{ + g_clear_pointer (&data->destination_file, g_free); + data->destination_file = g_strdup (file); +} + +void +ev_document_signatures_data_set_page (EvSignaturesData *data, + guint page) +{ + data->page = page; +} + +void +ev_document_signatures_data_set_rect (EvSignaturesData *data, + const EvRectangle *rectangle) +{ + g_clear_pointer (&data->rect, ev_rectangle_free); + data->rect = ev_rectangle_copy ((EvRectangle*)rectangle); +} + +void +ev_document_signatures_data_set_signature (EvSignaturesData *data, + const char *signature) +{ + g_clear_pointer (&data->signature, g_free); + data->signature = g_strdup (signature); +} + +void +ev_document_signatures_data_set_signature_left (EvSignaturesData *data, + const char *signature_left) +{ + g_clear_pointer (&data->signature_left, g_free); + data->signature_left = g_strdup (signature_left); +} + +G_DEFINE_BOXED_TYPE(EvCertificateInfo, ev_certificate_info, ev_certificate_info_copy, ev_certificate_info_free) + +EvCertificateInfo * +ev_certificate_info_new(const char *id, const char *subject_common_name) +{ + EvCertificateInfo *info = (EvCertificateInfo *)g_malloc0(sizeof(EvCertificateInfo)); + + info->id = g_strdup (id); + info->subject_common_name = g_strdup (subject_common_name); + return info; +} + +EvCertificateInfo * +ev_certificate_info_copy(const EvCertificateInfo *info) + +{ + EvCertificateInfo *info_copy = (EvCertificateInfo *)g_malloc0(sizeof(EvCertificateInfo)); + + info_copy->id = g_strdup (info->id); + info_copy->subject_common_name = g_strdup (info->subject_common_name); + return info_copy; +} + +void +ev_certificate_info_free(EvCertificateInfo *info) +{ + g_clear_pointer (&info->id, g_free); + g_clear_pointer (&info->subject_common_name, g_free); +} + +const char * +ev_certificate_info_get_id (const EvCertificateInfo *info) +{ + return info->id; +} + +const char * +ev_certificate_info_get_subject_common_name (const EvCertificateInfo *info) +{ + return info->subject_common_name; +} diff --git a/libdocument/ev-document-signatures.h b/libdocument/ev-document-signatures.h new file mode 100644 index 0000000000000000000000000000000000000000..165346d283117eee9d74fd35d792c0597b83b645 --- /dev/null +++ b/libdocument/ev-document-signatures.h @@ -0,0 +1,151 @@ +/* ev-document-signatures.h + * this file is part of evince, a gnome document viewer + * + * Copyright © 2022 Jan-Michael Brummer can be included directly." +#endif + +#ifndef EV_DOCUMENT_SIGNATURES_H +#define EV_DOCUMENT_SIGNATURES_H + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define EV_TYPE_DOCUMENT_SIGNATURES (ev_document_signatures_get_type ()) +#define EV_DOCUMENT_SIGNATURES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EV_TYPE_DOCUMENT_SIGNATURES, EvDocumentSignatures)) +#define EV_DOCUMENT_SIGNATURES_IFACE(k) (G_TYPE_CHECK_CLASS_CAST((k), EV_TYPE_DOCUMENT_SIGNATURES, EvDocumentSignaturesInterface)) +#define EV_IS_DOCUMENT_SIGNATURES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EV_TYPE_DOCUMENT_SIGNATURES)) +#define EV_IS_DOCUMENT_SIGNATURES_IFACE(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EV_TYPE_DOCUMENT_SIGNATURES)) +#define EV_DOCUMENT_SIGNATURES_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), EV_TYPE_DOCUMENT_SIGNATURES, EvDocumentSignaturesInterface)) + +typedef struct _EvDocumentSignatures EvDocumentSignatures; +typedef struct _EvDocumentSignaturesInterface EvDocumentSignaturesInterface; + +typedef struct +{ + char *id; + char *subject_common_name; +} EvCertificateInfo; + +typedef struct { + char *destination_file; + EvCertificateInfo *certificate_info; + char *password; + int page; + char *signature; + char *signature_left; + + EvRectangle *rect; + GdkRGBA font_color; + GdkRGBA border_color; + GdkRGBA background_color; + gdouble font_size; + gdouble left_font_size; + gdouble border_width; + char *document_owner_password; + char *document_user_password; +} EvSignaturesData; + +typedef char * (*EvSignaturePasswordCallback)(const gchar *text); + +struct _EvDocumentSignaturesInterface +{ + GTypeInterface base_iface; + + /* Methods */ + void (* set_password_callback) (EvDocumentSignatures *document_signatures, EvSignaturePasswordCallback cb); + GList *(* get_available_signing_certificates) (EvDocumentSignatures *document_signatures); + EvCertificateInfo *(* get_certificate_info) (EvDocumentSignatures *document_signatures, const char *nick_name); + void (* sign) (EvDocumentSignatures *document_signatures, EvSignaturesData *data, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); + gboolean (* can_sign) (EvDocumentSignatures *document_signatures); +}; + +/* Certificate Information */ +#define EV_TYPE_CERTIFICATE_INFO (ev_certificate_info_get_type()) + +EV_PUBLIC +GType ev_certificate_info_get_type(void) G_GNUC_CONST; + +EV_PUBLIC +EvCertificateInfo *ev_certificate_info_new (const char *id, const char *subject_common_name); + +EV_PUBLIC +EvCertificateInfo *ev_certificate_info_copy(const EvCertificateInfo *certificate_info); + +EV_PUBLIC +void ev_certificate_info_free(EvCertificateInfo *certificate_info); + +EV_PUBLIC +const char *ev_certificate_info_get_id (const EvCertificateInfo *certificate_info); + +EV_PUBLIC +const char *ev_certificate_info_get_subject_common_name (const EvCertificateInfo *certificate_info); + +EV_PUBLIC +GType ev_document_signatures_get_type (void) G_GNUC_CONST; + +EV_PUBLIC +GList *ev_document_signatures_get_available_signing_certificates (EvDocumentSignatures *document_signatures); + +EV_PUBLIC +void ev_document_signatures_set_password_callback (EvDocumentSignatures *document_signatures, EvSignaturePasswordCallback cb); + +EV_PUBLIC +EvCertificateInfo *ev_document_signature_get_certificate_info (EvDocumentSignatures *document_signatures, const char *id); + +EV_PUBLIC +gboolean +ev_document_signatures_sign (EvDocumentSignatures *document_signatures, + EvSignaturesData *data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +EV_PUBLIC +gboolean ev_document_signatures_can_sign (EvDocumentSignatures *document_signatures); + +EV_PUBLIC +EvSignaturesData *ev_document_signatures_data_new (void); + +EV_PUBLIC +void ev_document_signatures_data_set_certificate_info (EvSignaturesData *data, const EvCertificateInfo *certificate); + +EV_PUBLIC +void ev_document_signatures_data_set_destination_file (EvSignaturesData *data, const char *file); + +EV_PUBLIC +void ev_document_signatures_data_set_page (EvSignaturesData *data, guint page); + +EV_PUBLIC +void ev_document_signatures_data_free (EvSignaturesData *data); + +EV_PUBLIC +void ev_document_signatures_data_set_rect (EvSignaturesData *data, const EvRectangle *rect); + +EV_PUBLIC +void ev_document_signatures_data_set_signature (EvSignaturesData *data, const char *signature); + +EV_PUBLIC +void ev_document_signatures_data_set_signature_left (EvSignaturesData *data, const char *signature_left); + +G_END_DECLS + +#endif /* EV_DOCUMENT_SIGNATURES_H */ diff --git a/libdocument/meson.build b/libdocument/meson.build index a1e292cdc7fb49161ff00855c4691919b1fa5fa3..33314a465ebe14bb79702c188bcaefdd54d5f06b 100644 --- a/libdocument/meson.build +++ b/libdocument/meson.build @@ -21,6 +21,7 @@ headers = files( 'ev-document-misc.h', 'ev-document-print.h', 'ev-document-security.h', + 'ev-document-signatures.h', 'ev-document-text.h', 'ev-document-transition.h', 'ev-document.h', @@ -69,6 +70,7 @@ sources = files( 'ev-document-misc.c', 'ev-document-print.c', 'ev-document-security.c', + 'ev-document-signatures.c', 'ev-document-text.c', 'ev-document-transition.c', 'ev-file-exporter.c', diff --git a/libview/ev-view-private.h b/libview/ev-view-private.h index 15d56a614352675e2a54e8770606eb624836f1c1..7cc2f3f8efc5cbe12be082043e77d398442be4bc 100644 --- a/libview/ev-view-private.h +++ b/libview/ev-view-private.h @@ -140,6 +140,13 @@ typedef struct { guint delay_timeout_id; } EvLinkPreview; +typedef struct { + gboolean active; + gboolean in_selection; + GdkPoint start; + GdkPoint stop; +} SignatureRectInfo; + struct _EvView { GtkContainer layout; @@ -275,6 +282,9 @@ struct _EvView { /* Link preview */ EvLinkPreview link_preview; + + /* Signature Rect */ + SignatureRectInfo signature_rect_info; }; struct _EvViewClass { @@ -306,6 +316,9 @@ struct _EvViewClass { gint count, gboolean extend_selection); void (*activate) (EvView *view); + void (*signature_rect) (EvView *view, + guint page, + EvRectangle *rectangle); }; void _get_page_size_for_scale_and_rotation (EvDocument *document, diff --git a/libview/ev-view.c b/libview/ev-view.c index 4949ecbf5ee5b3cdce6bec7f209ad3ad696a4aad..fa778341e059ebc0ba8da4cc90950c1671eaa35a 100644 --- a/libview/ev-view.c +++ b/libview/ev-view.c @@ -66,6 +66,7 @@ enum { SIGNAL_MOVE_CURSOR, SIGNAL_CURSOR_MOVED, SIGNAL_ACTIVATE, + SIGNAL_SIGNATURE_RECT, N_SIGNALS }; @@ -343,6 +344,9 @@ static void ev_view_update_primary_selection (EvView /*** Caret navigation ***/ static void ev_view_check_cursor_blink (EvView *ev_view); +void +ev_view_stop_signature_rect (EvView *view); + G_DEFINE_TYPE_WITH_CODE (EvView, ev_view, GTK_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) @@ -5044,6 +5048,44 @@ draw_debug_borders (EvView *view, } #endif +static void +draw_selection_rect (EvView *view, + cairo_t *cr, + gint page) +{ + gint pos_x1, pos_x2, pos_y1, pos_y2; + gint x, y, w, h; + + if (!view->signature_rect_info.active || !view->signature_rect_info.in_selection) + return; + + pos_x1 = view->signature_rect_info.start.x - view->scroll_x; + pos_y1 = view->signature_rect_info.start.y - view->scroll_y; + pos_x2 = view->signature_rect_info.stop.x - view->scroll_x; + pos_y2 = view->signature_rect_info.stop.y - view->scroll_y; + + x = MIN(pos_x1, pos_x2); + y = MIN(pos_y1, pos_y2); + w = ABS(pos_x1 - pos_x2); + h = ABS(pos_y1 - pos_y2); + + if (w <= 0 || h <= 0) + return; + + cairo_save (cr); + + cairo_rectangle(cr, x + 1, y + 1, w - 2, h - 2); + cairo_set_source_rgba (cr, 0.2, 0.6, 0.8, 0.2); + cairo_fill(cr); + + cairo_rectangle(cr, x + 0.5, y + 0.5, w - 1, h - 1); + cairo_set_source_rgba (cr, 0.2, 0.6, 0.8, 0.35); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + + cairo_restore (cr); +} + static gboolean ev_view_draw (GtkWidget *widget, cairo_t *cr) @@ -5088,6 +5130,8 @@ ev_view_draw (GtkWidget *widget, draw_focus (view, cr, i, &clip_rect); if (page_ready && view->synctex_result) highlight_forward_search_results (view, cr, i); + if (page_ready && view->signature_rect_info.active) + draw_selection_rect (view, cr, i); #ifdef EV_ENABLE_DEBUG if (page_ready) draw_debug_borders (view, cr, i, &clip_rect); @@ -5643,11 +5687,24 @@ ev_view_button_press_event (GtkWidget *widget, } view->pressed_button = event->button; + view->selection_info.in_drag = FALSE; if (view->scroll_info.autoscrolling) return TRUE; + if (view->signature_rect_info.active && !view->signature_rect_info.in_selection) { + if (view->pressed_button != 1) + return TRUE; + + view->signature_rect_info.start.x = event->x + view->scroll_x; + view->signature_rect_info.start.y = event->y + view->scroll_y; + view->signature_rect_info.stop = view->signature_rect_info.start; + + view->signature_rect_info.in_selection = TRUE; + return TRUE; + } + if (view->adding_annot_info.adding_annot && !view->adding_annot_info.annot) { if (event->button != 1) return TRUE; @@ -6080,6 +6137,14 @@ ev_view_motion_notify_event (GtkWidget *widget, if (view->rotation != 0) return FALSE; + + if (view->signature_rect_info.active && view->signature_rect_info.in_selection) { + view->signature_rect_info.stop.x = event->x + view->scroll_x; + view->signature_rect_info.stop.y = event->y + view->scroll_y; + gtk_widget_queue_draw (widget); + return TRUE; + } + if (view->adding_annot_info.adding_annot) { EvRectangle rect; EvRectangle current_area; @@ -6440,6 +6505,14 @@ ev_view_button_release_event (GtkWidget *widget, view->drag_info.in_drag = FALSE; + if (view->signature_rect_info.in_selection) { + view->signature_rect_info.stop.x = event->x + view->scroll_x; + view->signature_rect_info.stop.y = event->y + view->scroll_y; + view->signature_rect_info.in_selection = FALSE; + ev_view_stop_signature_rect (view); + return TRUE; + } + if (view->adding_annot_info.adding_annot && !view->selection_scroll_id) { gboolean annot_added = TRUE; @@ -8437,6 +8510,15 @@ ev_view_class_init (EvViewClass *class) G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + signals[SIGNAL_SIGNATURE_RECT] = g_signal_new ("signature-rect", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EvViewClass, signature_rect), + NULL, NULL, + g_cclosure_marshal_VOID__UINT_POINTER, + G_TYPE_NONE, 2, + G_TYPE_INT, + EV_TYPE_RECTANGLE); signals[SIGNAL_ACTIVATE] = g_signal_new ("activate", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, @@ -10458,3 +10540,62 @@ ev_view_get_allow_links_change_zoom (EvView *view) return view->allow_links_change_zoom; } + +void +ev_view_start_signature_rect (EvView *view) +{ + ev_view_set_cursor (view, EV_VIEW_CURSOR_ADD); + + view->signature_rect_info.active = TRUE; +} + +void +ev_view_cancel_signature_rect (EvView *view) +{ + ev_view_set_cursor (view, EV_VIEW_CURSOR_IBEAM); + view->signature_rect_info.in_selection = FALSE; + view->signature_rect_info.active = FALSE; +} + +void +ev_view_stop_signature_rect (EvView *view) +{ + EvRectangle *rect = ev_rectangle_new (); + EvPoint start; + EvPoint end; + gint signature_page; + gint offset; + GdkRectangle page_area; + GtkBorder border; + + ev_view_set_cursor (view, EV_VIEW_CURSOR_IBEAM); + + find_page_at_location (view, view->signature_rect_info.start.x, view->signature_rect_info.start.y, &signature_page, &offset, &offset); + if (signature_page == -1) { + g_warning ("%s: Invalid signature page", __FUNCTION__); + return; + } + + ev_view_get_page_extents (view, signature_page, &page_area, &border); + _ev_view_transform_view_point_to_doc_point (view, &view->signature_rect_info.start, &page_area, &border, + &start.x, &start.y); + _ev_view_transform_view_point_to_doc_point (view, &view->signature_rect_info.stop, &page_area, &border, + &end.x, &end.y); + + rect->x1 = MIN (view->signature_rect_info.start.x, view->signature_rect_info.stop.x); + rect->y1 = MIN (view->signature_rect_info.start.y, view->signature_rect_info.stop.y); + rect->x2 = MAX (view->signature_rect_info.start.x, view->signature_rect_info.stop.x); + rect->y2 = MAX (view->signature_rect_info.start.y, view->signature_rect_info.stop.y); + + rect->x1 = MIN (start.x, end.x); + rect->y1 = MIN (start.y, end.y); + rect->x2 = MAX (start.x, end.x); + rect->y2 = MAX (start.y, end.y); + + view->signature_rect_info.in_selection = FALSE; + view->signature_rect_info.active = FALSE; + + g_signal_emit (view, signals[SIGNAL_SIGNATURE_RECT], 0, signature_page, rect); + ev_rectangle_free (rect); + gtk_widget_queue_draw (GTK_WIDGET (view)); +} diff --git a/libview/ev-view.h b/libview/ev-view.h index 74959c90d8925115a29063fb8b5db32121e6c70c..b64c9a7af30d89a082bae13a4377fa618b1fb055 100644 --- a/libview/ev-view.h +++ b/libview/ev-view.h @@ -194,4 +194,13 @@ void ev_view_set_caret_cursor_position (EvView *view, EV_PUBLIC gboolean ev_view_current_event_is_type (EvView *view, GdkEventType type); + +typedef void (*EvUserRectangleCallback)(EvRectangle *rect); + +EV_PUBLIC +void ev_view_start_signature_rect (EvView *view); + +EV_PUBLIC +void ev_view_cancel_signature_rect (EvView *view); + G_END_DECLS diff --git a/meson.build b/meson.build index b22011813522251305c3e841b41a79130a0a6e11..4449a8523005d5c1f6efd7e8a98b6808e2f37a4d 100644 --- a/meson.build +++ b/meson.build @@ -393,7 +393,7 @@ elif get_option('dvi').auto() endif # *** PDF *** -poppler_req_version = '>= 22.05.0' +poppler_req_version = '>= 23.07.0' poppler_glib_dep = dependency('poppler-glib', version: poppler_req_version, required: get_option('pdf')) enable_pdf = poppler_glib_dep.found() diff --git a/shell/ev-window.c b/shell/ev-window.c index d8970e33b6a7e285ec19d2b28c19e5ce36fb8120..55db26c5caf9766180063e476d7121a35948f67f 100644 --- a/shell/ev-window.c +++ b/shell/ev-window.c @@ -53,6 +53,7 @@ #include "ev-document-links.h" #include "ev-document-annotations.h" #include "ev-document-misc.h" +#include "ev-document-signatures.h" #include "ev-file-exporter.h" #include "ev-file-helpers.h" #include "ev-file-monitor.h" @@ -188,6 +189,12 @@ typedef struct { /* For bookshelf view of recent items*/ EvRecentView *recent_view; + /* Digital signing */ + GtkWidget *certificate_listbox; + guint signature_page; + EvRectangle *signature_bounding_box; + EvCertificateInfo *signature_certificate_info; + /* Document */ EvDocumentModel *model; char *uri; @@ -398,6 +405,7 @@ static void ev_window_cmd_toggle_edit_annots (GSimpleAction *action, GVariant *state, gpointer user_data); +static char *ev_window_signature_password_callback (const char *text); static gchar *nautilus_sendto = NULL; G_DEFINE_TYPE_WITH_PRIVATE (EvWindow, ev_window, HDY_TYPE_APPLICATION_WINDOW) @@ -441,6 +449,7 @@ ev_window_update_actions_sensitivity (EvWindow *ev_window) gboolean recent_view_mode; gboolean dual_mode = FALSE; gboolean has_pages = FALSE; + gboolean can_sign = FALSE; int n_pages = 0, page = -1; if (document) { @@ -468,6 +477,10 @@ ev_window_update_actions_sensitivity (EvWindow *ev_window) can_annotate = ev_document_annotations_can_add_annotation (EV_DOCUMENT_ANNOTATIONS (document)); } + if (has_document && EV_IS_DOCUMENT_SIGNATURES (document)) { + can_sign = ev_document_signatures_can_sign (EV_DOCUMENT_SIGNATURES (document)); + } + if (has_document && priv->settings) { override_restrictions = g_settings_get_boolean (priv->settings, @@ -533,6 +546,7 @@ ev_window_update_actions_sensitivity (EvWindow *ev_window) !recent_view_mode); ev_window_set_action_enabled (ev_window, "rotate-right", has_pages && !recent_view_mode); + ev_window_set_action_enabled (ev_window, "digital-signing", can_sign); /* View menu */ ev_window_set_action_enabled (ev_window, "continuous", has_pages && @@ -763,6 +777,10 @@ ev_window_message_area_response_cb (EvMessageArea *area, gint response_id, EvWindow *window) { + EvWindowPrivate *priv = GET_PRIVATE (window); + EvView *view = EV_VIEW (priv->view); + + ev_view_cancel_signature_rect (view); ev_window_set_message_area (window, NULL); } @@ -1744,6 +1762,10 @@ ev_window_set_document (EvWindow *ev_window, EvDocument *document) g_object_unref (priv->document); priv->document = g_object_ref (document); + /* Set password callback */ + if (EV_IS_DOCUMENT_SIGNATURES (priv->document)) + ev_document_signatures_set_password_callback (EV_DOCUMENT_SIGNATURES (priv->document), ev_window_signature_password_callback); + ev_window_set_message_area (ev_window, NULL); ev_window_set_document_metadata (ev_window); @@ -3222,6 +3244,290 @@ ev_window_cmd_save_as (GSimpleAction *action, ev_window_save_as (window); } +static char * +ev_window_signature_password_callback (const char *text) +{ + GtkWidget *dialog; + GtkWidget *box; + GtkWidget *entry; + char *ret; + GtkWindow *parent = NULL; + + parent = gtk_application_get_active_window (GTK_APPLICATION (g_application_get_default())); + + dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Enter password")); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("Cancel"), GTK_RESPONSE_CANCEL); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("Unlock"), GTK_RESPONSE_OK); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Enter password to open: %s"), text); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + box = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog)); + entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); + gtk_box_pack_end (GTK_BOX (box), entry, TRUE, TRUE, 6); + gtk_widget_show_all (box); + + gtk_dialog_run (GTK_DIALOG (dialog)); + ret = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + gtk_widget_destroy (dialog); + + return ret; +} + +static void +on_document_signed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + char *file = user_data; + g_autofree char *uri = g_strdup_printf ("file://%s", file); + GtkWidget *new_window; + + new_window = ev_window_new (); + ev_window_open_uri (EV_WINDOW (new_window), uri, NULL, EV_WINDOW_MODE_NORMAL, NULL); + gtk_widget_show_all (new_window); + g_free (file); +} + +static void +ev_window_certificate_save_file (EvWindow *window, + const EvCertificateInfo *certificate_info, + const char *filename) +{ + EvWindowPrivate *priv = GET_PRIVATE (window); + EvSignaturesData *data; + time_t t; + g_autofree char *tmp = NULL; + + data = ev_document_signatures_data_new (); + ev_document_signatures_data_set_certificate_info (data, certificate_info); + ev_document_signatures_data_set_destination_file (data, filename); + ev_document_signatures_data_set_page (data, priv->signature_page); + + ev_document_signatures_data_set_rect (data, priv->signature_bounding_box); + + time (&t); + tmp = g_strdup_printf (_("Digitally signed by %s\nDate: %s"), ev_certificate_info_get_subject_common_name (certificate_info), ctime (&t)); + ev_document_signatures_data_set_signature (data, tmp); + ev_document_signatures_data_set_signature_left (data, ev_certificate_info_get_subject_common_name (certificate_info)); + + ev_document_signatures_sign (EV_DOCUMENT_SIGNATURES (priv->document), data, NULL, on_document_signed, g_strdup (filename)); +} + +static void +ev_window_on_save_signed_file_response (GtkWidget *dialog, + guint response, + gpointer user_data) +{ + EvWindow *window = EV_WINDOW (user_data); + EvWindowPrivate *priv = GET_PRIVATE (window); + + if (response == GTK_RESPONSE_ACCEPT) { + g_autofree char *filename = NULL; + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + + filename = gtk_file_chooser_get_filename (chooser); + + ev_window_certificate_save_file (window, priv->signature_certificate_info, filename); + } else { + g_clear_pointer (&priv->signature_certificate_info, ev_certificate_info_free); + } + + gtk_widget_destroy (dialog); +} + +static void +ev_window_certificate_save_as_dialog (EvWindow *window) +{ + GtkWidget *dialog; + GtkFileChooser *chooser; + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; + GtkWindow *parent_window = GTK_WINDOW (window); + EvWindowPrivate *priv = GET_PRIVATE (window); + + dialog = gtk_file_chooser_dialog_new (_("Save Signed File"), + parent_window, + action, + _("_Cancel"), + GTK_RESPONSE_CANCEL, + _("_Save"), + GTK_RESPONSE_ACCEPT, + NULL); + chooser = GTK_FILE_CHOOSER (dialog); + + gtk_file_chooser_set_current_name (chooser, priv->edit_name); + + gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE); + g_signal_connect (dialog, "response", G_CALLBACK (ev_window_on_save_signed_file_response), window); + gtk_widget_show_all (dialog); +} + +static void +ev_window_certificate_selection_response (GtkWidget *dialog, + guint response, + gpointer user_data) +{ + const char *nick; + EvWindow *window = EV_WINDOW (user_data); + EvWindowPrivate *priv = GET_PRIVATE (window); + HdyActionRow *action_row; + + if (response != GTK_RESPONSE_OK) { + gtk_widget_destroy (dialog); + return; + } + + action_row = HDY_ACTION_ROW (gtk_list_box_get_selected_row (GTK_LIST_BOX (priv->certificate_listbox))); + nick = hdy_preferences_row_get_title (HDY_PREFERENCES_ROW (action_row)); + priv->signature_certificate_info = ev_document_signature_get_certificate_info (EV_DOCUMENT_SIGNATURES (priv->document), (const char *)nick); + gtk_widget_destroy (dialog); + + if (!priv->signature_certificate_info) + return; + + ev_window_certificate_save_as_dialog (window); +} + +static void +ev_window_create_certificate_selection (EvWindow *window) +{ + GtkWidget *dialog; + GtkWidget *box; + EvWindowPrivate *priv = GET_PRIVATE (window); + GtkWindow *parent = NULL; + GList *certificates; + GList *list; + + certificates = ev_document_signatures_get_available_signing_certificates (EV_DOCUMENT_SIGNATURES (priv->document)); + + parent = gtk_application_get_active_window (GTK_APPLICATION (g_application_get_default())); + + dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Certificate required")); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("Cancel"), GTK_RESPONSE_CANCEL); + + if (certificates != NULL) { + GtkStyleContext *context; + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Select signing certificate")); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("Select"), GTK_RESPONSE_OK); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + box = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog)); + priv->certificate_listbox = gtk_list_box_new (); + context = gtk_widget_get_style_context(priv->certificate_listbox); + gtk_style_context_add_class(context, "content"); + for (list = certificates; list; list = list->next) { + EvCertificateInfo *certificate_info = list->data; + GtkWidget *row = hdy_action_row_new (); + + hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (row), ev_certificate_info_get_id (certificate_info)); + hdy_action_row_set_subtitle (HDY_ACTION_ROW (row), ev_certificate_info_get_subject_common_name (certificate_info)); + gtk_list_box_insert (GTK_LIST_BOX (priv->certificate_listbox), row, -1); + } + + gtk_list_box_select_row (GTK_LIST_BOX (priv->certificate_listbox), gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->certificate_listbox), 0)); + gtk_box_pack_end (GTK_BOX (box), priv->certificate_listbox, TRUE, TRUE, 6); + gtk_widget_show_all (box); + g_list_free_full (certificates, (GDestroyNotify) ev_certificate_info_free); + } else { + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("No certificates found!")); + } + + g_signal_connect (dialog, "response", G_CALLBACK (ev_window_certificate_selection_response), window); + gtk_widget_show_all (dialog); +} + +static void +ev_window_on_signature_rect_too_small_response (GtkWidget *dialog, + guint response, + gpointer user_data) +{ + EvWindow *window = EV_WINDOW (user_data); + GAction *action; + + gtk_widget_destroy (dialog); + + if (response == GTK_RESPONSE_OK) { + ev_window_create_certificate_selection (window); + return; + } + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "digital-signing"); + g_action_activate (action, NULL); +} + +static void +ev_window_show_signature_rect_too_small_warning (EvWindow *window) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Selection too small")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("A signature of this size may be too small to read. If you would like to create a potentially more readable signature, press 'Start over' and draw a bigger rectangle.")); + + gtk_dialog_add_button (GTK_DIALOG (dialog), _("Start over"), GTK_RESPONSE_CANCEL); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("Sign"), GTK_RESPONSE_OK); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + g_signal_connect (dialog, "response", G_CALLBACK (ev_window_on_signature_rect_too_small_response), window); + gtk_widget_show_all (dialog); +} + +static void +ev_window_on_signature_rect (EvView *view, + guint page, + EvRectangle *rect, + gpointer user_data) +{ + EvWindow *window = user_data; + EvWindowPrivate *priv = GET_PRIVATE (window); + gdouble width, height; + + if (priv->message_area) + gtk_widget_destroy (priv->message_area); + + if (!rect) + return; + + ev_document_get_page_size (priv->document, page, &width, &height); + + priv->signature_page = page; + + g_clear_pointer (&priv->signature_bounding_box, ev_rectangle_free); + priv->signature_bounding_box = ev_rectangle_copy (rect); + + // FIXME this is a bit arbitrary, try to figure out a better rule, potentially based in cm and not pixels? + if ((ABS(rect->x1 - rect->x2) / width < 0.1) || (ABS(rect->y1 - rect->y2) / height < 0.1)) + ev_window_show_signature_rect_too_small_warning (window); + else + ev_window_create_certificate_selection (window); +} + +static void +ev_window_cmd_digital_signing (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + EvWindow *window = user_data; + EvWindowPrivate *priv = GET_PRIVATE (window); + EvView *view = EV_VIEW (priv->view); + GtkWidget *area; + + area = ev_message_area_new (GTK_MESSAGE_INFO, + _("Draw a rectangle to insert a signature field"), + NULL); + + g_signal_connect (ev_message_area_get_info_bar (EV_MESSAGE_AREA (area)), "response", + G_CALLBACK (ev_window_message_area_response_cb), + window); + gtk_widget_show (area); + ev_window_set_message_area (window, area); + + ev_view_start_signature_rect (view); +} + static void ev_window_cmd_send_to (GSimpleAction *action, GVariant *parameter, @@ -6341,7 +6647,8 @@ static const GActionEntry actions[] = { { "open-attachment", ev_window_popup_cmd_open_attachment }, { "save-attachment", ev_window_popup_cmd_save_attachment_as }, { "annot-properties", ev_window_popup_cmd_annot_properties }, - { "remove-annot", ev_window_popup_cmd_remove_annotation } + { "remove-annot", ev_window_popup_cmd_remove_annotation }, + { "digital-signing", ev_window_cmd_digital_signing } }; static void @@ -7652,6 +7959,8 @@ ev_window_init (EvWindow *ev_window) gtk_widget_show (priv->view_box); priv->view = ev_view_new (); + g_signal_connect (G_OBJECT (priv->view), "signature-rect", G_CALLBACK (ev_window_on_signature_rect), ev_window); + page_cache_mb = g_settings_get_uint (ev_window_ensure_settings (ev_window), GS_PAGE_CACHE_SIZE); ev_view_set_page_cache_size (EV_VIEW (priv->view), diff --git a/shell/evince-toolbar.ui b/shell/evince-toolbar.ui index 4add3ecde84ba9ff49d35f062dffd7c9f1e3bbac..df47b15b0c304045927a1ee54114eb491576d216 100644 --- a/shell/evince-toolbar.ui +++ b/shell/evince-toolbar.ui @@ -166,6 +166,12 @@ win.presentation +
+ + D_igital Signing… + win.digital-signing + +
_Continuous