/* Gtk selection processing for emacs.
   Copyright (C) 1993-1994, 2005-2006, 2008-2020 Free Software
   Foundation, Inc.

This file is part of GNU Emacs.

GNU Emacs 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.

GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */

/*
Originally by Carl Edman
Updated by Christian Limpach (chris@nice.ch)
OpenStep/Rhapsody port by Scott Bender (sbender@harmony-ds.com)
macOS/Aqua port by Christophe de Dinechin (descubes@earthlink.net)
GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
*/

/* This should be the first include, as it may set up #defines affecting
   interpretation of even the system includes.  */
#include <config.h>

#include "lisp.h"
#include "pgtkterm.h"
#include "termhooks.h"
#include "keyboard.h"
#include "pgtkselect.h"
#include <gdk/gdk.h>

#if 0
static Lisp_Object Vselection_alist;
#endif

static GQuark quark_primary_data = 0;
static GQuark quark_primary_size = 0;
static GQuark quark_secondary_data = 0;
static GQuark quark_secondary_size = 0;
static GQuark quark_clipboard_data = 0;
static GQuark quark_clipboard_size = 0;

/* ==========================================================================

    Internal utility functions

   ========================================================================== */

/* From a Lisp_Object, return a suitable frame for selection
   operations.  OBJECT may be a frame, a terminal object, or nil
   (which stands for the selected frame--or, if that is not an pgtk
   frame, the first pgtk display on the list).  If no suitable frame can
   be found, return NULL.  */

static struct frame *
frame_for_pgtk_selection (Lisp_Object object)
{
  Lisp_Object tail, frame;
  struct frame *f;

  if (NILP (object))
    {
      f = XFRAME (selected_frame);
      if (FRAME_PGTK_P (f) && FRAME_LIVE_P (f))
	return f;

      FOR_EACH_FRAME (tail, frame)
      {
	f = XFRAME (frame);
	if (FRAME_PGTK_P (f) && FRAME_LIVE_P (f))
	  return f;
      }
    }
  else if (TERMINALP (object))
    {
      struct terminal *t = decode_live_terminal (object);

      if (t->type == output_pgtk)
	FOR_EACH_FRAME (tail, frame)
	{
	  f = XFRAME (frame);
	  if (FRAME_LIVE_P (f) && f->terminal == t)
	    return f;
	}
    }
  else if (FRAMEP (object))
    {
      f = XFRAME (object);
      if (FRAME_PGTK_P (f) && FRAME_LIVE_P (f))
	return f;
    }

  return NULL;
}

static GtkClipboard *
symbol_to_gtk_clipboard (GtkWidget * widget, Lisp_Object symbol)
{
  GdkAtom atom;

  CHECK_SYMBOL (symbol);
  if (NILP (symbol))
    {
      atom = GDK_SELECTION_PRIMARY;
    }
  else if (EQ (symbol, QCLIPBOARD))
    {
      atom = GDK_SELECTION_CLIPBOARD;
    }
  else if (EQ (symbol, QPRIMARY))
    {
      atom = GDK_SELECTION_PRIMARY;
    }
  else if (EQ (symbol, QSECONDARY))
    {
      atom = GDK_SELECTION_SECONDARY;
    }
  else if (EQ (symbol, Qt))
    {
      atom = GDK_SELECTION_SECONDARY;
    }
  else
    {
      atom = 0;
      error ("Bad selection");
    }

  return gtk_widget_get_clipboard (widget, atom);
}

static void
selection_type_to_quarks (GdkAtom type, GQuark * quark_data,
			  GQuark * quark_size)
{
  if (type == GDK_SELECTION_PRIMARY)
    {
      *quark_data = quark_primary_data;
      *quark_size = quark_primary_size;
    }
  else if (type == GDK_SELECTION_SECONDARY)
    {
      *quark_data = quark_secondary_data;
      *quark_size = quark_secondary_size;
    }
  else if (type == GDK_SELECTION_CLIPBOARD)
    {
      *quark_data = quark_clipboard_data;
      *quark_size = quark_clipboard_size;
    }
  else
    {
      /* fixme: Is it safe to use 'error' here? */
      error ("Unknown selection type.");
    }
}

static void
get_func (GtkClipboard * cb, GtkSelectionData * data, guint info,
	  gpointer user_data_or_owner)
{
  GObject *obj = G_OBJECT (user_data_or_owner);
  const char *str;
  int size;
  GQuark quark_data, quark_size;

  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
			    &quark_size);

  str = g_object_get_qdata (obj, quark_data);
  size = GPOINTER_TO_SIZE (g_object_get_qdata (obj, quark_size));
  gtk_selection_data_set_text (data, str, size);
}

static void
clear_func (GtkClipboard * cb, gpointer user_data_or_owner)
{
  GObject *obj = G_OBJECT (user_data_or_owner);
  GQuark quark_data, quark_size;

  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
			    &quark_size);

  g_object_set_qdata (obj, quark_data, NULL);
  g_object_set_qdata (obj, quark_size, 0);
}


/* ==========================================================================

    Functions used externally

   ========================================================================== */

void
pgtk_selection_init (void)
{
  if (quark_primary_data == 0)
    {
      quark_primary_data = g_quark_from_static_string ("pgtk-primary-data");
      quark_primary_size = g_quark_from_static_string ("pgtk-primary-size");
      quark_secondary_data =
	g_quark_from_static_string ("pgtk-secondary-data");
      quark_secondary_size =
	g_quark_from_static_string ("pgtk-secondary-size");
      quark_clipboard_data =
	g_quark_from_static_string ("pgtk-clipboard-data");
      quark_clipboard_size =
	g_quark_from_static_string ("pgtk-clipboard-size");
    }
}

void
pgtk_selection_lost (GtkWidget * widget, GdkEventSelection * event,
		     gpointer user_data)
{
  GQuark quark_data, quark_size;

  selection_type_to_quarks (event->selection, &quark_data, &quark_size);

  g_object_set_qdata (G_OBJECT (widget), quark_data, NULL);
  g_object_set_qdata (G_OBJECT (widget), quark_size, 0);
}

static bool
pgtk_selection_usable (void)
{
  if (pgtk_enable_selection_on_multi_display)
    return true;

  /*
   * https://github.com/GNOME/gtk/blob/gtk-3-24/gdk/wayland/gdkselection-wayland.c#L1033
   *
   * Gdk uses gdk_display_get_default() when handling selections, so
   * selections don't work properly on multi-display environment.
   *
   * ----------------
   * #include <gtk/gtk.h>
   *
   * static GtkWidget *top1, *top2;
   *
   * int main (int argc, char **argv)
   * {
   *     GtkWidget *w;
   *     GtkTextBuffer *buf;
   *
   *     gtk_init (&argc, &argv);
   *
   *     static char *text = "\
   * It is fine today.\n\
   * It will be fine tomorrow too.\n\
   * It is too hot.";
   *
   *     top1 = gtk_window_new (GTK_WINDOW_TOPLEVEL);
   *     gtk_window_set_title (GTK_WINDOW (top1), "default");
   *     gtk_widget_show (top1);
   *     w = gtk_text_view_new ();
   *     gtk_container_add (GTK_CONTAINER (top1), w);
   *     gtk_widget_show (w);
   *     buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
   *     gtk_text_buffer_insert_at_cursor (buf, text, strlen (text));
   *     gtk_text_buffer_add_selection_clipboard (buf, gtk_widget_get_clipboard (w, GDK_SELECTION_PRIMARY));
   *
   *     unsetenv ("GDK_BACKEND");
   *     GdkDisplay *gdpy;
   *     const char *dpyname2;
   *     if (strcmp (G_OBJECT_TYPE_NAME (gtk_widget_get_window (top1)), "GdkWaylandWindow") == 0)
   *         dpyname2 = ":0";
   *     else
   *         dpyname2 = "wayland-0";
   *     gdpy = gdk_display_open (dpyname2);
   *     top2 = gtk_window_new (GTK_WINDOW_TOPLEVEL);
   *     gtk_window_set_title (GTK_WINDOW (top2), dpyname2);
   *     gtk_window_set_screen (GTK_WINDOW (top2), gdk_display_get_default_screen (gdpy));
   *     gtk_widget_show (top2);
   *     w = gtk_text_view_new ();
   *     gtk_container_add (GTK_CONTAINER (top2), w);
   *     gtk_widget_show (w);
   *     buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
   *     gtk_text_buffer_insert_at_cursor (buf, text, strlen (text));
   *     gtk_text_buffer_add_selection_clipboard (buf, gtk_widget_get_clipboard (w, GDK_SELECTION_PRIMARY));
   *
   *     gtk_main ();
   *
   *     return 0;
   * }
   * ----------------
   *
   * This code fails if
   *   GDK_BACKEND=x11 ./test
   * and select on both of windows.
   *
   * ----------------
   * (test:15345): GLib-GObject-CRITICAL **: 01:56:38.041: g_object_ref: assertion 'G_IS_OBJECT (object)' failed
   *
   * (test:15345): GLib-GObject-CRITICAL **: 01:56:38.042: g_object_ref: assertion 'G_IS_OBJECT (object)' failed
   *
   * (test:15345): GLib-GObject-CRITICAL **: 01:56:39.113: g_object_ref: assertion 'G_IS_OBJECT (object)' failed
   *
   * (test:15345): GLib-GObject-CRITICAL **: 01:56:39.113: g_object_ref: assertion 'G_IS_OBJECT (object)' failed
   * ----------------
   * (gtk-3.24.10)
   *
   * This function checks whether selections work by the number of displays.
   * If you use more than 2 displays, then selection is disabled.
   */

  GdkDisplayManager *dpyman = gdk_display_manager_get ();
  GSList *list = gdk_display_manager_list_displays (dpyman);
  int len = g_slist_length (list);
  g_slist_free (list);
  return len < 2;
}

/* ==========================================================================

    Lisp Defuns

   ========================================================================== */


DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal, Spgtk_own_selection_internal, 2, 3, 0,
       doc: /* Assert an X selection of type SELECTION and value VALUE.
SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
\(Those are literal upper-case symbol names, since that's what X expects.)
VALUE is typically a string, or a cons of two markers, but may be
anything that the functions on `selection-converter-alist' know about.

FRAME should be a frame that should own the selection.  If omitted or
nil, it defaults to the selected frame. */)
  (Lisp_Object selection, Lisp_Object value, Lisp_Object frame)
{
  Lisp_Object successful_p = Qnil;
  Lisp_Object target_symbol, rest;
  GtkClipboard *cb;
  struct frame *f;
  GQuark quark_data, quark_size;

  check_window_system (NULL);

  if (!pgtk_selection_usable ())
    return Qnil;

  if (NILP (frame))
    frame = selected_frame;
  if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
    error ("pgtk selection unavailable for this frame");
  f = XFRAME (frame);

  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
			    &quark_size);

  /* We only support copy of text.  */
  target_symbol = QTEXT;
  if (STRINGP (value))
    {
      GtkTargetList *list;
      GtkTargetEntry *targets;
      gint n_targets;
      GtkWidget *widget;

      list = gtk_target_list_new (NULL, 0);
      gtk_target_list_add_text_targets (list, 0);

      {
	/* text/plain: Strings encoded by Gtk are not correctly decoded by Chromium(Wayland). */
	GdkAtom atom_text_plain = gdk_atom_intern ("text/plain", false);
	gtk_target_list_remove (list, atom_text_plain);
      }

      targets = gtk_target_table_new_from_list (list, &n_targets);

      int size = SBYTES (value);
      gchar *str = xmalloc (size + 1);
      memcpy (str, SSDATA (value), size);
      str[size] = '\0';

      widget = FRAME_GTK_WIDGET (f);
      g_object_set_qdata_full (G_OBJECT (widget), quark_data, str, xfree);
      g_object_set_qdata_full (G_OBJECT (widget), quark_size,
			       GSIZE_TO_POINTER (size), NULL);

      if (gtk_clipboard_set_with_owner (cb,
					targets, n_targets,
					get_func, clear_func,
					G_OBJECT (FRAME_GTK_WIDGET (f))))
	{
	  successful_p = Qt;
	}
      gtk_clipboard_set_can_store (cb, NULL, 0);

      gtk_target_table_free (targets, n_targets);
      gtk_target_list_unref (list);
    }

  if (!EQ (Vpgtk_sent_selection_hooks, Qunbound))
    {
      /* FIXME: Use run-hook-with-args!  */
      for (rest = Vpgtk_sent_selection_hooks; CONSP (rest);
	   rest = Fcdr (rest))
	call3 (Fcar (rest), selection, target_symbol, successful_p);
    }

  return value;
}


DEFUN ("pgtk-disown-selection-internal", Fpgtk_disown_selection_internal, Spgtk_disown_selection_internal, 1, 3, 0,
       doc: /* If we own the selection SELECTION, disown it.
Disowning it means there is no such selection.

Sets the last-change time for the selection to TIME-OBJECT (by default
the time of the last event).

TERMINAL should be a terminal object or a frame specifying the X
server to query.  If omitted or nil, that stands for the selected
frame's display, or the first available X display.

On Nextstep, the TIME-OBJECT and TERMINAL arguments are unused.
On MS-DOS, all this does is return non-nil if we own the selection.
On PGTK, the TIME-OBJECT is unused.  */)
  (Lisp_Object selection, Lisp_Object time_object, Lisp_Object terminal)
{
  struct frame *f = frame_for_pgtk_selection (terminal);
  GtkClipboard *cb;

  if (!pgtk_selection_usable ())
    return Qnil;

  if (!f)
    return Qnil;

  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);

  gtk_clipboard_clear (cb);

  return Qt;
}


DEFUN ("pgtk-selection-exists-p", Fpgtk_selection_exists_p, Spgtk_selection_exists_p, 0, 2, 0,
       doc: /* Whether there is an owner for the given X selection.
SELECTION should be the name of the selection in question, typically
one of the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.  (X expects
these literal upper-case names.)  The symbol nil is the same as
`PRIMARY', and t is the same as `SECONDARY'.

TERMINAL should be a terminal object or a frame specifying the X
server to query.  If omitted or nil, that stands for the selected
frame's display, or the first available X display.

On Nextstep, TERMINAL is unused.  */)
  (Lisp_Object selection, Lisp_Object terminal)
{
  struct frame *f = frame_for_pgtk_selection (terminal);
  GtkClipboard *cb;

  if (!pgtk_selection_usable ())
    return Qnil;

  if (!f)
    return Qnil;

  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);

  return gtk_clipboard_wait_is_text_available (cb) ? Qt : Qnil;
}


DEFUN ("pgtk-selection-owner-p", Fpgtk_selection_owner_p, Spgtk_selection_owner_p, 0, 2, 0,
       doc: /* Whether the current Emacs process owns the given X Selection.
The arg should be the name of the selection in question, typically one of
the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.
\(Those are literal upper-case symbol names, since that's what X expects.)
For convenience, the symbol nil is the same as `PRIMARY',
and t is the same as `SECONDARY'.

TERMINAL should be a terminal object or a frame specifying the X
server to query.  If omitted or nil, that stands for the selected
frame's display, or the first available X display.

On Nextstep, TERMINAL is unused.  */)
  (Lisp_Object selection, Lisp_Object terminal)
{
  struct frame *f = frame_for_pgtk_selection (terminal);
  GtkClipboard *cb;
  GObject *obj;
  GQuark quark_data, quark_size;

  if (!pgtk_selection_usable ())
    return Qnil;

  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
			    &quark_size);

  obj = gtk_clipboard_get_owner (cb);

  return obj && g_object_get_qdata (obj, quark_data) != NULL ? Qt : Qnil;
}


DEFUN ("pgtk-get-selection-internal", Fpgtk_get_selection_internal, Spgtk_get_selection_internal, 2, 4, 0,
       doc: /* Return text selected from some X window.
SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
\(Those are literal upper-case symbol names, since that's what X expects.)
TARGET-TYPE is the type of data desired, typically `STRING'.

TIME-STAMP is the time to use in the XConvertSelection call for foreign
selections.  If omitted, defaults to the time for the last event.

TERMINAL should be a terminal object or a frame specifying the X
server to query.  If omitted or nil, that stands for the selected
frame's display, or the first available X display.

On Nextstep, TIME-STAMP and TERMINAL are unused.
On PGTK, TIME-STAMP is unused.  */)
  (Lisp_Object selection_symbol, Lisp_Object target_type,
   Lisp_Object time_stamp, Lisp_Object terminal)
{
  struct frame *f = frame_for_pgtk_selection (terminal);
  GtkClipboard *cb;

  CHECK_SYMBOL (selection_symbol);
  CHECK_SYMBOL (target_type);
  if (EQ (target_type, QMULTIPLE))
    error ("Retrieving MULTIPLE selections is currently unimplemented");
  if (!f)
    error ("PGTK selection unavailable for this frame");

  if (!pgtk_selection_usable ())
    return Qnil;

  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection_symbol);

  GdkAtom target_atom = gdk_atom_intern (SSDATA (SYMBOL_NAME (target_type)), false);
  GtkSelectionData *seldata = gtk_clipboard_wait_for_contents (cb, target_atom);

  if (seldata == NULL)
    return Qnil;

  const guchar *sd_data = gtk_selection_data_get_data (seldata);
  int sd_len = gtk_selection_data_get_length (seldata);
  int sd_format = gtk_selection_data_get_format (seldata);
  GdkAtom sd_type = gtk_selection_data_get_data_type (seldata);

  if (sd_format == 8)
    {
      Lisp_Object str, lispy_type;

      str = make_unibyte_string ((char *) sd_data, sd_len);
      /* Indicate that this string is from foreign selection by a text
	 property `foreign-selection' so that the caller of
	 x-get-selection-internal (usually x-get-selection) can know
	 that the string must be decode.  */
      if (sd_type == gdk_atom_intern ("COMPOUND_TEXT", false))
	lispy_type = QCOMPOUND_TEXT;
      else if (sd_type == gdk_atom_intern ("UTF8_STRING", false))
	lispy_type = QUTF8_STRING;
      else if (sd_type == gdk_atom_intern ("text/plain;charset=utf-8", false))
	lispy_type = Qtext_plain_charset_utf_8;
      else
	lispy_type = QSTRING;
      Fput_text_property (make_fixnum (0), make_fixnum (sd_len),
			  Qforeign_selection, lispy_type, str);

      gtk_selection_data_free (seldata);
      return str;
    }

  gtk_selection_data_free (seldata);
  return Qnil;
}


void
nxatoms_of_pgtkselect (void)
{
}

void
syms_of_pgtkselect (void)
{
  DEFSYM (QCLIPBOARD, "CLIPBOARD");
  DEFSYM (QSECONDARY, "SECONDARY");
  DEFSYM (QTEXT, "TEXT");
  DEFSYM (QFILE_NAME, "FILE_NAME");
  DEFSYM (QMULTIPLE, "MULTIPLE");

  DEFSYM (Qforeign_selection, "foreign-selection");
  DEFSYM (QUTF8_STRING, "UTF8_STRING");
  DEFSYM (QSTRING, "STRING");
  DEFSYM (QCOMPOUND_TEXT, "COMPOUND_TEXT");
  DEFSYM (Qtext_plain_charset_utf_8, "text/plain;charset=utf-8");

  defsubr (&Spgtk_disown_selection_internal);
  defsubr (&Spgtk_get_selection_internal);
  defsubr (&Spgtk_own_selection_internal);
  defsubr (&Spgtk_selection_exists_p);
  defsubr (&Spgtk_selection_owner_p);

#if 0
  Vselection_alist = Qnil;
  staticpro (&Vselection_alist);
#endif

  DEFVAR_LISP ("pgtk-sent-selection-hooks", Vpgtk_sent_selection_hooks,
	       "A list of functions to be called when Emacs answers a selection request.\n\
The functions are called with four arguments:\n\
  - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');\n\
  - the selection-type which Emacs was asked to convert the\n\
    selection into before sending (for example, `STRING' or `LENGTH');\n\
  - a flag indicating success or failure for responding to the request.\n\
We might have failed (and declined the request) for any number of reasons,\n\
including being asked for a selection that we no longer own, or being asked\n\
to convert into a type that we don't know about or that is inappropriate.\n\
This hook doesn't let you change the behavior of Emacs's selection replies,\n\
it merely informs you that they have happened.");
  Vpgtk_sent_selection_hooks = Qnil;

  DEFVAR_BOOL ("pgtk-enable-selection-on-multi-display", pgtk_enable_selection_on_multi_display,
	       doc: /* Enable selection on multi display environment.
This may cause crash.  */);
  pgtk_enable_selection_on_multi_display = false;
}