Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
EvBrowserPlugin.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2014 Igalia S.L.
3  *
4  * Evince is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * Evince is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "config.h"
20 #include "EvBrowserPlugin.h"
21 
22 #include "EvBrowserPluginToolbar.h"
23 #include "npfunctions.h"
24 #include <errno.h>
25 #include <gtk/gtkx.h>
26 #include <limits>
27 #include <string.h>
28 
30  enum Methods {
38 
40  };
41 
42  enum Properties {
50 
52  };
53 
55  {
60  }
61 
62  return static_cast<EvBrowserPlugin *>(NPN_CreateObject(instance, &npClass));
63  }
64 
66 
72 };
73 
75 {
76  return s_pluginClass.createObject(instance);
77 }
78 
80 {
81  return "Evince Browser Plugin";
82 }
83 
85 {
86  return "The <a href=\"http://wiki.gnome.org/Apps/Evince/\">Evince</a> " PACKAGE_VERSION " plugin handles documents inside the browser window.";
87 }
88 
90  : m_NPP(instance)
91  , m_window(nullptr)
92  , m_model(nullptr)
93  , m_view(nullptr)
94  , m_toolbar(nullptr)
95 {
96  m_NPP->pdata = this;
97 }
98 
100 {
101  if (m_window)
102  gtk_widget_destroy(m_window);
103  g_clear_object(&m_model);
104  m_NPP->pdata = nullptr;
105 }
106 
107 template <typename IntegerType>
108 static inline void parseInteger(const char *strValue, IntegerType &intValue)
109 {
110  static const IntegerType intMax = std::numeric_limits<IntegerType>::max();
111  static const bool isSigned = std::numeric_limits<IntegerType>::is_signed;
112 
113  if (!strValue)
114  return;
115 
116  char *endPtr = nullptr;
117  errno = 0;
118  gint64 value = isSigned ? g_ascii_strtoll(strValue, &endPtr, 0) : g_ascii_strtoull(strValue, &endPtr, 0);
119  if (endPtr != strValue && errno == 0 && value <= intMax)
120  intValue = static_cast<IntegerType>(value);
121 }
122 
123 static inline void parseDouble(const char *strValue, double &doubleValue)
124 {
125  if (!strValue)
126  return;
127 
128  char *endPtr = nullptr;
129  errno = 0;
130  double value = g_ascii_strtod(strValue, &endPtr);
131  if (endPtr != strValue && errno == 0)
132  doubleValue = value;
133 }
134 
135 static inline void parseBoolean(const char *strValue, bool &boolValue)
136 {
137  if (!strValue)
138  return;
139 
140  unique_gptr<char> value(g_ascii_strdown(strValue, -1));
141  if (g_ascii_strcasecmp(value.get(), "false") == 0 || g_ascii_strcasecmp(value.get(), "no") == 0)
142  boolValue = false;
143  else if (g_ascii_strcasecmp(value.get(), "true") == 0 || g_ascii_strcasecmp(value.get(), "yes") == 0)
144  boolValue = true;
145  else {
146  int intValue = boolValue;
147  parseInteger<int>(strValue, intValue);
148  boolValue = intValue > 0;
149  }
150 }
151 
152 static inline void parseZoomMode(const char *strValue, EvSizingMode &sizingModeValue)
153 {
154  if (!strValue)
155  return;
156 
157  unique_gptr<char> value(g_ascii_strdown(strValue, -1));
158  if (g_ascii_strcasecmp(value.get(), "none") == 0)
159  sizingModeValue = EV_SIZING_FREE;
160  else if (g_ascii_strcasecmp(value.get(), "fit-page") == 0)
161  sizingModeValue = EV_SIZING_FIT_PAGE;
162  else if (g_ascii_strcasecmp(value.get(), "fit-width") == 0)
163  sizingModeValue = EV_SIZING_FIT_WIDTH;
164  else if (g_ascii_strcasecmp(value.get(), "auto") == 0)
165  sizingModeValue = EV_SIZING_AUTOMATIC;
166 }
167 
168 NPError EvBrowserPlugin::initialize(NPMIMEType, uint16_t mode, int16_t argc, char *argn[], char *argv[], NPSavedData *)
169 {
170  // Default values.
171  bool toolbarVisible = true;
172  unsigned currentPage = 1;
174  bool continuous = true;
175  bool dual = false;
176  double zoom = 0;
177 
178  for (int16_t i = 0; i < argc; ++i) {
179  if (g_ascii_strcasecmp(argn[i], "toolbar") == 0)
180  parseBoolean(argv[i], toolbarVisible);
181  else if (g_ascii_strcasecmp(argn[i], "currentpage") == 0)
182  parseInteger<unsigned>(argv[i], currentPage);
183  else if (g_ascii_strcasecmp(argn[i], "zoom") == 0)
184  parseDouble(argv[i], zoom);
185  else if (g_ascii_strcasecmp(argn[i], "zoommode") == 0)
186  parseZoomMode(argv[i], sizingMode);
187  else if (g_ascii_strcasecmp(argn[i], "continuous") == 0)
188  parseBoolean(argv[i], continuous);
189  else if (g_ascii_strcasecmp(argn[i], "dual") == 0)
190  parseBoolean(argv[i], dual);
191  }
192 
194  if (currentPage > 0)
195  ev_document_model_set_page(m_model, currentPage - 1);
198  if (zoom) {
201  } else
203 
206 
208  if (toolbarVisible)
209  gtk_widget_show(m_toolbar);
210 
211  return NPERR_NO_ERROR;
212 }
213 
215 {
216  if (!m_window) {
217  m_window = gtk_plug_new(reinterpret_cast<Window>(window->window));
218  gtk_widget_realize(m_window);
219 
220  GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
221  gtk_box_pack_start(GTK_BOX(vbox), m_toolbar, FALSE, FALSE, 0);
222 
223  GtkWidget *scrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
224  gtk_container_add(GTK_CONTAINER(scrolledWindow), GTK_WIDGET(m_view));
225  gtk_widget_show(GTK_WIDGET(m_view));
226 
227  gtk_box_pack_start(GTK_BOX(vbox), scrolledWindow, TRUE, TRUE, 0);
228  gtk_widget_show(scrolledWindow);
229 
230  gtk_container_add(GTK_CONTAINER(m_window), vbox);
231  gtk_widget_show(vbox);
232  }
233 
234  gtk_widget_set_size_request(m_window, window->width, window->height);
235  gtk_widget_show(m_window);
236 
237  return NPERR_NO_ERROR;
238 }
239 
240 NPError EvBrowserPlugin::newStream(NPMIMEType, NPStream *stream, NPBool seekable, uint16_t *stype)
241 {
242  m_url.reset(g_strdup(stream->url));
243  *stype = NP_ASFILEONLY;
244  return NPERR_NO_ERROR;
245 }
246 
248 {
249  return NPERR_NO_ERROR;
250 }
251 
252 void EvBrowserPlugin::streamAsFile(NPStream *, const char *fname)
253 {
254  GFile *file = g_file_new_for_commandline_arg(fname);
255  unique_gptr<char> uri(g_file_get_uri(file));
256  g_object_unref(file);
257 
258  // Load the document synchronously here because the temporary file created by the browser
259  // is deleted when this function returns.
260  GError *error = nullptr;
261  EvDocument *document = ev_document_factory_get_document(uri.get(), &error);
262  if (!document) {
263  g_printerr("Error loading document %s: %s\n", uri.get(), error->message);
264  g_error_free(error);
265  } else {
267  g_object_unref(document);
268 
270  }
271 }
272 
274 {
275  return 0;
276 }
277 
278 int32_t EvBrowserPlugin::write(NPStream *, int32_t /*offset*/, int32_t /*len*/, void */*buffer*/)
279 {
280  return 0;
281 }
282 
284 {
285 
286 }
287 
289 {
290  return 0;
291 }
292 
293 void EvBrowserPlugin::urlNotify(const char */*url*/, NPReason, void */*notifyData*/)
294 {
295 
296 }
297 
299 {
300  g_return_val_if_fail(EV_IS_DOCUMENT_MODEL(m_model), 0);
302 }
303 
305 {
306  g_return_val_if_fail(EV_IS_DOCUMENT_MODEL(m_model), 0);
308  return document ? ev_document_get_n_pages(document) : 0;
309 }
310 
311 double EvBrowserPlugin::zoom() const
312 {
313  g_return_val_if_fail(EV_IS_DOCUMENT_MODEL(m_model), 1);
315 }
316 
317 void EvBrowserPlugin::setZoom(double scale)
318 {
319  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
322 }
323 
325 {
326  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
328 }
329 
331 {
332  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
334 }
335 
336 void EvBrowserPlugin::goToPage(unsigned page)
337 {
338  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
340 }
341 
342 void EvBrowserPlugin::goToPage(const char *pageLabel)
343 {
344  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
346 }
347 
349 {
350  g_return_if_fail(EV_IS_VIEW(m_view));
351  g_return_if_fail(EV_IS_LINK(link));
353  gtk_widget_grab_focus(GTK_WIDGET(m_view));
354 }
355 
357 {
358  g_return_val_if_fail(EV_IS_DOCUMENT_MODEL(m_model), false);
360 }
361 
362 void EvBrowserPlugin::setContinuous(bool continuous)
363 {
364  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
366 }
367 
369 {
370  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
372 }
373 
375 {
376  g_return_val_if_fail(EV_IS_DOCUMENT_MODEL(m_model), false);
378 }
379 
381 {
382  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
384 }
385 
387 {
388  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
390 }
391 
393 {
394  g_return_if_fail(EV_IS_VIEW(m_view));
397 }
398 
400 {
401  g_return_if_fail(EV_IS_VIEW(m_view));
404 }
405 
407 {
408  g_return_val_if_fail(EV_IS_DOCUMENT_MODEL(m_model), EV_SIZING_FREE);
410 }
411 
413 {
414  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
416 }
417 
419 {
420  g_return_if_fail(m_url);
421  // Since I don't know how to force a download in the browser, I use
422  // a special frame name here that Epiphany will check in the new window policy
423  // callback to start the download.
424  NPN_GetURL(m_NPP, m_url.get(), "_evince_download");
425 }
426 
428 {
429  g_return_if_fail(EV_IS_DOCUMENT_MODEL(m_model));
430 
432  if (!document)
433  return;
434 
435  EvPrintOperation *printOperation = ev_print_operation_new(document);
436  if (!printOperation)
437  return;
438 
439  unique_gptr<char> outputBasename(g_path_get_basename(m_url.get()));
440  if (char *dot = g_strrstr(outputBasename.get(), "."))
441  dot[0] = '\0';
442 
443  unique_gptr<char> unescapedBasename(g_uri_unescape_string(outputBasename.get(), nullptr));
444  // Set output basename for printing to file.
445  GtkPrintSettings *printSettings = gtk_print_settings_new();
446  gtk_print_settings_set(printSettings, GTK_PRINT_SETTINGS_OUTPUT_BASENAME, unescapedBasename.get());
447 
448  if (const char *title = ev_document_get_title(document))
449  ev_print_operation_set_job_name(printOperation, title);
452  ev_print_operation_set_print_settings(printOperation, printSettings);
453  g_object_unref(printSettings);
454 
455  g_signal_connect(printOperation, "done", G_CALLBACK(g_object_unref), nullptr);
456 
457  GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(m_view));
458  ev_print_operation_run(printOperation, GTK_IS_WINDOW(toplevel) ? GTK_WINDOW(toplevel) : nullptr);
459 }
460 
462 {
463  // Download is only available for Epiphany for now.
464  return g_strrstr(NPN_UserAgent(m_NPP), "Epiphany");
465 }
466 
468 {
469  g_return_val_if_fail(EV_IS_BROWSER_PLUGIN_TOOLBAR(m_toolbar), false);
470  return gtk_widget_get_visible(m_toolbar);
471 }
472 
474 {
475  g_return_if_fail(EV_IS_BROWSER_PLUGIN_TOOLBAR(m_toolbar));
476  if (isVisible)
477  gtk_widget_show(m_toolbar);
478  else
479  gtk_widget_hide(m_toolbar);
480 }
481 
483 {
485 }
486 
488 {
491 }
492 
494 {
495  switch (direction) {
496  case Next:
498  break;
499  case Previous:
501  break;
502  }
503 }
504 
506 {
508  gtk_widget_queue_draw(GTK_WIDGET(m_view));
509 }
510 
512 {
514 }
515 
516 // Scripting interface
518 {
519  return new EvBrowserPlugin(instance);
520 }
521 
523 {
524  delete static_cast<EvBrowserPlugin *>(npObject);
525 }
526 
528 {
529 }
530 
532 {
533  for (unsigned i = 0; i < EvBrowserPluginClass::Methods::NumMethodIdentifiers; ++i) {
534  if (name == s_pluginClass.methodIdentifiers[i]) {
535  if (i == EvBrowserPluginClass::Methods::Download)
536  return static_cast<EvBrowserPlugin *>(npObject)->canDownload();
537  return true;
538  }
539  }
540  return false;
541 }
542 
543 bool EvBrowserPlugin::invoke(NPObject *npObject, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
544 {
545  EvBrowserPlugin *plugin = static_cast<EvBrowserPlugin *>(npObject);
546 
547  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::GoToPage]) {
548  if (argCount != 1)
549  return false;
550 
551  if (NPVARIANT_IS_DOUBLE(args[0]))
552  plugin->goToPage(static_cast<unsigned>(NPVARIANT_TO_DOUBLE(args[0])));
553  else if (NPVARIANT_IS_STRING(args[0])) {
554  unique_gptr<char> pageLabel(g_strndup(NPVARIANT_TO_STRING(args[0]).UTF8Characters, NPVARIANT_TO_STRING(args[0]).UTF8Length));
555  plugin->goToPage(pageLabel.get());
556  } else
557  return false;
558 
559  VOID_TO_NPVARIANT(*result);
560  return true;
561  }
562  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::ToggleContinuous]) {
563  plugin->toggleContinuous();
564  VOID_TO_NPVARIANT(*result);
565  return true;
566  }
567  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::ToggleDual]) {
568  plugin->toggleDual();
569  VOID_TO_NPVARIANT(*result);
570  return true;
571  }
572  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::ZoomIn]) {
573  plugin->zoomIn();
574  VOID_TO_NPVARIANT(*result);
575  return true;
576  }
577  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::ZoomOut]) {
578  plugin->zoomOut();
579  VOID_TO_NPVARIANT(*result);
580  return true;
581  }
582  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::Download]) {
583  plugin->download();
584  VOID_TO_NPVARIANT(*result);
585  return true;
586  }
587  if (name == s_pluginClass.methodIdentifiers[EvBrowserPluginClass::Methods::Print]) {
588  plugin->print();
589  VOID_TO_NPVARIANT(*result);
590  return true;
591  }
592  return false;
593 }
594 
596 {
597  for (unsigned i = 0; i < EvBrowserPluginClass::Properties::NumPropertyIdentifiers; ++i) {
598  if (name == s_pluginClass.propertyIdentifiers[i])
599  return true;
600  }
601  return false;
602 }
603 
605 {
606  EvBrowserPlugin *plugin = static_cast<EvBrowserPlugin *>(npObject);
607 
608  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::CurrentPage]) {
609  INT32_TO_NPVARIANT(plugin->currentPage() + 1, *value);
610  return true;
611  }
612  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::PageCount]) {
613  INT32_TO_NPVARIANT(plugin->pageCount(), *value);
614  return true;
615  }
616  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Zoom]) {
617  DOUBLE_TO_NPVARIANT(plugin->zoom(), *value);
618  return true;
619  }
620  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::ZoomMode]) {
621  const char *zoomMode;
622 
623  switch (plugin->sizingMode()) {
624  case EV_SIZING_FREE:
625  zoomMode = "none";
626  break;
627  case EV_SIZING_FIT_PAGE:
628  zoomMode = "fit-page";
629  break;
630  case EV_SIZING_FIT_WIDTH:
631  zoomMode = "fit-width";
632  break;
633  case EV_SIZING_AUTOMATIC:
634  zoomMode = "auto";
635  break;
636  default:
637  return false;
638  }
639 
640  size_t zoomModeLength = strlen(zoomMode);
641  char *result = static_cast<char *>(NPN_MemAlloc(zoomModeLength + 1));
642  memcpy(result, zoomMode, zoomModeLength);
643  result[zoomModeLength] = '\0';
644 
645  STRINGZ_TO_NPVARIANT(result, *value);
646 
647  return true;
648  }
649  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Continuous]) {
650  BOOLEAN_TO_NPVARIANT(plugin->isContinuous(), *value);
651  return true;
652  }
653  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Dual]) {
654  BOOLEAN_TO_NPVARIANT(plugin->isDual(), *value);
655  return true;
656  }
657  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Toolbar]) {
658  BOOLEAN_TO_NPVARIANT(plugin->toolbarVisible(), *value);
659  return true;
660  }
661 
662  return false;
663 }
664 
666 {
667  EvBrowserPlugin *plugin = static_cast<EvBrowserPlugin *>(npObject);
668 
669  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::CurrentPage]) {
670  plugin->goToPage(static_cast<unsigned>(NPVARIANT_TO_DOUBLE(*value)));
671  return true;
672  }
673  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Zoom]) {
674  plugin->setZoom(NPVARIANT_TO_DOUBLE(*value));
675  return true;
676  }
677  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::ZoomMode]) {
678  unique_gptr<char> zoomMode(g_strndup(NPVARIANT_TO_STRING(*value).UTF8Characters, NPVARIANT_TO_STRING(*value).UTF8Length));
679 
680  if (g_strcmp0(zoomMode.get(), "none") == 0)
681  plugin->setSizingMode(EV_SIZING_FREE);
682  else if (g_strcmp0(zoomMode.get(), "fit-page") == 0)
684  else if (g_strcmp0(zoomMode.get(), "fit-width") == 0)
686  else if (g_strcmp0(zoomMode.get(), "auto") == 0)
688  else
689  return false;
690 
691  return true;
692  }
693  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Continuous]) {
694  plugin->setContinuous(NPVARIANT_TO_BOOLEAN(*value));
695  return true;
696  }
697  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Dual]) {
698  plugin->setDual(NPVARIANT_TO_BOOLEAN(*value));
699  return true;
700  }
701  if (name == s_pluginClass.propertyIdentifiers[EvBrowserPluginClass::Properties::Toolbar]) {
702  plugin->setToolbarVisible(NPVARIANT_TO_BOOLEAN(*value));
703  return true;
704  }
705 
706  return false;
707 }
708 
710  {
712  allocate,
713  deallocate,
714  invalidate,
715  hasMethod,
716  invoke,
717  nullptr, // NPClass::invokeDefault
718  hasProperty,
719  getProperty,
720  setProperty,
721  nullptr, // NPClass::removeProperty
722  nullptr, // NPClass::enumerate
723  nullptr // NPClass::construct
724  },
725  // methodIdentifierNames
726  {
727  "goToPage",
728  "toggleContinuous",
729  "toggleDual",
730  "zoomIn",
731  "zoomOut",
732  "download",
733  "print"
734  },
735  // propertyIdentifierNames
736  {
737  "currentPage",
738  "pageCount",
739  "zoom",
740  "zoomMode",
741  "continuous",
742  "dual",
743  "toolbar"
744  },
745  false // identifiersInitialized
746 };