html.c 11.4 KB
/*
 * This file is part of YAD.
 *
 * YAD 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 3 of the License, or
 * (at your option) any later version.
 *
 * YAD 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 YAD. If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2008-2019, Victor Ananjevsky <ananasik@gmail.com>
 */

#include <limits.h>
#include <stdlib.h>

#include "yad.h"

#include <webkit2/webkit2.h>

static WebKitWebView *view;

static GString *inbuf;

static gboolean is_loaded = FALSE;

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

static void
load_uri (const gchar * uri)
{
  gchar *addr = NULL;

  if (!uri || !uri[0])
    return;

  if (g_file_test (uri, G_FILE_TEST_EXISTS))
    {
      if (g_path_is_absolute (uri))
        addr = g_filename_to_uri (uri, NULL, NULL);
      else
        {
          gchar *afn = realpath (uri, NULL);
          addr = g_filename_to_uri (afn, NULL, NULL);
          g_free (afn);
        }
    }
  else
    {
      if (g_uri_parse_scheme (uri) == NULL)
        addr = g_strdup_printf ("https://%s", uri);
      else
        addr = g_strdup (uri);
    }

  is_loaded = FALSE;
  webkit_web_view_load_uri (view, addr);
  g_free (addr);
}

static void
loaded_cb (WebKitWebView *v, WebKitLoadEvent ev, gpointer d)
{
  if (ev == WEBKIT_LOAD_FINISHED)
    is_loaded = TRUE;
}

static gboolean
policy_cb (WebKitWebView *v, WebKitPolicyDecision *pd, WebKitPolicyDecisionType pt, gpointer d)
{
  const gchar *uri;

  if (!is_loaded)
    return FALSE;

  if (!options.html_data.browser)
    {
      WebKitNavigationAction *act = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (pd));
      webkit_policy_decision_ignore (pd);
      if (webkit_navigation_action_get_navigation_type (act) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
        {
          WebKitURIRequest *r = webkit_navigation_action_get_request (act);

          uri = webkit_uri_request_get_uri (r);
          if (options.html_data.print_uri)
            g_printf ("%s\n", uri);
          else
            g_app_info_launch_default_for_uri (uri, NULL, NULL);
        }
    }
  else if (options.html_data.uri_cmd)
    {
      gchar *v1, *v2, *cmd;
      gint status;

      if (pt == WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
        {
          WebKitURIRequest *r = webkit_response_policy_decision_get_request (WEBKIT_RESPONSE_POLICY_DECISION (pd));
          uri =  webkit_uri_request_get_uri (r);
          v1 = g_strdup ("");
          v2 = g_strdup ("");
        }
      else
        {
          WebKitNavigationAction *act = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (pd));
          WebKitURIRequest *r = webkit_navigation_action_get_request (act);
          uri =  webkit_uri_request_get_uri (r);
          v1 = g_strdup_printf ("%d", webkit_navigation_action_get_mouse_button (act));
          v2 = g_strdup_printf ("%d", webkit_navigation_action_get_modifiers (act));
        }

      g_setenv ("YAD_HTML_BUTTON", v1, TRUE);
      g_setenv ("YAD_HTML_STATE", v2, TRUE);

      cmd = g_strdup_printf ("%s '%s'", options.html_data.uri_cmd, uri);
      status = run_command_sync (cmd, NULL);
      g_free (cmd);

      g_unsetenv ("YAD_HTML_BUTTON");
      g_unsetenv ("YAD_HTML_STATE");

      g_free (v1);
      g_free (v2);

      /* actial exit code in a highest byte */
      switch (status >> 8)
        {
        case 0:
          webkit_policy_decision_use (pd);
          break;
        case 1:
          webkit_policy_decision_ignore (pd);
          break;
        case 2:
          if (pt == WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
            webkit_policy_decision_download (pd);
          else
            webkit_policy_decision_use (pd);
          break;
        default:
          g_printerr ("yad: wrong return code (%d) from uri handler\n", status >> 8);
          return FALSE;
        }
    }
  else
    return FALSE;

  return TRUE;
}

static void
select_file_cb (GtkEntry *entry, GtkEntryIconPosition pos, GdkEventButton *ev, gpointer d)
{
  GtkWidget *dlg;
  static gchar *dir = NULL;

  if (ev->button != 1 || pos != GTK_ENTRY_ICON_SECONDARY)
    return;

  dlg = gtk_file_chooser_dialog_new (_("YAD - Select File"),
                                     GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (entry))),
                                     GTK_FILE_CHOOSER_ACTION_OPEN,
                                     _("OK"), GTK_RESPONSE_ACCEPT,
                                     _("Cancel"), GTK_RESPONSE_CANCEL,
                                     NULL);
  if (dir)
    gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg), dir);

  if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
    {
      gchar *uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
      gtk_entry_set_text (entry, uri);
      g_free (uri);

      /* keep current dir */
      g_free (dir);
      dir = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg));
    }

  gtk_widget_destroy (dlg);
}

static void
do_open_cb (GtkWidget *w, GtkDialog *dlg)
{
  gtk_dialog_response (dlg, GTK_RESPONSE_ACCEPT);
}

static void
open_cb (GSimpleAction *act, GVariant *param, gpointer d)
{
  GtkWidget *dlg, *cnt, *lbl, *entry;

  dlg = gtk_dialog_new_with_buttons (_("Open URI"),
                                     GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
                                     GTK_DIALOG_DESTROY_WITH_PARENT,
                                     _("Cancel"), GTK_RESPONSE_REJECT,
                                     _("Open"), GTK_RESPONSE_ACCEPT,
                                     NULL);
  gtk_window_set_default_size (GTK_WINDOW (dlg), 350, -1);

  cnt = gtk_dialog_get_content_area (GTK_DIALOG (dlg));

  lbl = gtk_label_new (_("Enter URI or file name:"));
  gtk_label_set_xalign (GTK_LABEL (lbl), 0.0);
  gtk_widget_show (lbl);
  gtk_box_pack_start (GTK_BOX (cnt), lbl, TRUE, FALSE, 2);

  entry = gtk_entry_new ();
  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY, "document-open");
  gtk_widget_show (entry);
  gtk_box_pack_start (GTK_BOX (cnt), entry, TRUE, FALSE, 2);

  g_signal_connect (G_OBJECT (entry), "icon-press", G_CALLBACK (select_file_cb), NULL);
  g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (do_open_cb), dlg);

  if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
    load_uri (gtk_entry_get_text (GTK_ENTRY (entry)));

  gtk_widget_destroy (dlg);
}

static void
quit_cb (GSimpleAction *act, GVariant *param, gpointer d)
{
  yad_exit (options.data.def_resp);
}

static gboolean
menu_cb (WebKitWebView *view, WebKitContextMenu *menu, GdkEvent *ev, WebKitHitTestResult *hit, gpointer d)
{
  WebKitContextMenuItem *mi;
  GSimpleAction *act;

  mi = webkit_context_menu_item_new_separator ();
  webkit_context_menu_prepend (menu, mi);

  act = g_simple_action_new ("open", NULL);
  g_signal_connect (G_OBJECT (act), "activate", G_CALLBACK (open_cb), NULL);

  mi = webkit_context_menu_item_new_from_gaction (G_ACTION (act), _("Open URI"), NULL);
  webkit_context_menu_prepend (menu, mi);

  mi = webkit_context_menu_item_new_separator ();
  webkit_context_menu_append (menu, mi);

  act = g_simple_action_new ("quit", NULL);
  g_signal_connect (G_OBJECT (act), "activate", G_CALLBACK (quit_cb), NULL);

  mi = webkit_context_menu_item_new_from_gaction (G_ACTION (act), _("Quit"), NULL);
  webkit_context_menu_append (menu, mi);

  return FALSE;
}

static void
title_cb (GObject *obj, GParamSpec *spec, GtkWindow *dlg)
{
  const gchar *title = webkit_web_view_get_title (view);
  if (title)
    gtk_window_set_title (dlg, title);
}

static void
icon_cb (GObject *obj, GParamSpec *spec, GtkWindow *dlg)
{
  GdkPixbuf *pb = gdk_pixbuf_get_from_surface (webkit_web_view_get_favicon (view), 0, 0, -1, -1);

  if (pb)
    {
      gtk_window_set_icon (dlg, pb);
      g_object_unref (pb);
    }
}

static gboolean
handle_stdin (GIOChannel * ch, GIOCondition cond, gpointer d)
{
  gchar *buf;
  GBytes *data;
  GError *err = NULL;

  switch (g_io_channel_read_line (ch, &buf, NULL, NULL, &err))
    {
    case G_IO_STATUS_NORMAL:
      g_string_append (inbuf, buf);
      return TRUE;

    case G_IO_STATUS_ERROR:
      g_printerr ("yad_html_handle_stdin(): %s\n", err->message);
      g_error_free (err);
      return FALSE;

    case G_IO_STATUS_EOF:
      data = g_bytes_new (inbuf->str, inbuf->len);
      /*g_string_free (inbuf, TRUE); */ /* FIXME: IS THAT NEEDED ??? (and where) */
      webkit_web_view_load_bytes (view, data, options.html_data.mime, options.html_data.encoding, NULL);
      g_bytes_unref (data);
      return FALSE;

    case G_IO_STATUS_AGAIN:
      return TRUE;
    }

  return FALSE;
}

GtkWidget *
html_create_widget (GtkWidget * dlg)
{
  GtkWidget *sw;
  WebKitSettings *settings;

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), options.hscroll_policy, options.vscroll_policy);

  view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
  gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (view));

  settings = webkit_settings_new ();

  g_object_set (G_OBJECT (settings), "user-agent", options.html_data.user_agent, NULL);
  if (options.html_data.user_style)
    {
      gchar *uri = g_filename_to_uri (options.html_data.user_style, NULL, NULL);
      g_object_set (G_OBJECT (settings), "user-stylesheet-uri", uri, NULL);
    }
  webkit_web_view_set_settings (view, settings);

  webkit_settings_set_default_charset (settings, g_get_codeset ());

  g_signal_connect (view, "decide-policy", G_CALLBACK (policy_cb), NULL);
  g_signal_connect (view, "load-changed", G_CALLBACK (loaded_cb), NULL);

  if (options.html_data.browser)
    {
      g_signal_connect (view, "context-menu", G_CALLBACK (menu_cb), NULL);
      if (!options.data.dialog_title)
        g_signal_connect (view, "notify::title", G_CALLBACK (title_cb), dlg);
      if (strcmp (options.data.window_icon, "yad") == 0)
        g_signal_connect (view, "notify::favicon", G_CALLBACK (icon_cb), dlg);
    }
  else
    {
      g_object_set (G_OBJECT(settings), "enable-caret-browsing", FALSE, NULL);
      g_object_set (G_OBJECT(settings), "enable-developer-extras", FALSE, NULL);
      g_object_set (G_OBJECT(settings), "enable-html5-database", FALSE, NULL);
      g_object_set (G_OBJECT(settings), "enable-html5-local-storage", FALSE, NULL);
      g_object_set (G_OBJECT(settings), "enable-offline-web-application-cache", FALSE, NULL);
      g_object_set (G_OBJECT(settings), "enable-page-cache", FALSE, NULL);
      g_object_set (G_OBJECT(settings), "enable-plugins", FALSE, NULL);
      g_object_set (G_OBJECT (settings), "enable-private-browsing", TRUE, NULL);
    }

  gtk_widget_show_all (sw);
  gtk_widget_grab_focus (GTK_WIDGET (view));

  if (options.html_data.uri)
    load_uri (options.html_data.uri);
  else if (!options.html_data.browser)
    {
      GIOChannel *ch;

      inbuf = g_string_new (NULL);
      ch = g_io_channel_unix_new (0);
      g_io_channel_set_encoding (ch, NULL, NULL);
      g_io_channel_set_flags (ch, G_IO_FLAG_NONBLOCK, NULL);
      g_io_add_watch (ch, G_IO_IN | G_IO_HUP, handle_stdin, NULL);
    }
  else if (options.extra_data)
    load_uri (options.extra_data[0]);

  return sw;
}