summaryrefslogtreecommitdiff
path: root/src/pgtkselect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pgtkselect.c')
-rw-r--r--src/pgtkselect.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/src/pgtkselect.c b/src/pgtkselect.c
new file mode 100644
index 00000000000..4c87aaa7ea6
--- /dev/null
+++ b/src/pgtkselect.c
@@ -0,0 +1,536 @@
+/* Gtk selection processing for emacs.
+ Copyright (C) 1993-1994, 2005-2006, 2008-2022 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/>. */
+
+/* FIXME: this file needs a major rewrite to replace the use of GTK's
+ own high-level GtkClipboard API with the GDK selection API:
+
+ https://developer-old.gnome.org/gdk3/stable/gdk3-Selections.html
+
+ That way, most of the code can be shared with X, and non-text
+ targets along with drag-and-drop can be supported. GDK implements
+ selections according to the ICCCM, as on X, but its selection API
+ will work on any supported window system. */
+
+/* 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>
+
+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;
+
+ /* Gdk uses `gdk_display_get_default' when handling selections, so
+ selections don't work properly when Emacs is connected to
+ multiple displays. */
+
+ 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, 2, 0,
+ doc: /* If we own the selection SELECTION, disown it.
+Disowning it means there is no such selection.
+
+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. */)
+ (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);
+
+ 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, 3, 0,
+ doc: /* Return text selected from some program.
+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'.
+
+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 display. */)
+ (Lisp_Object selection_symbol, Lisp_Object target_type,
+ 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
+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);
+
+ DEFVAR_LISP ("pgtk-sent-selection-hooks", Vpgtk_sent_selection_hooks,
+ doc: /* A list of functions to be called when Emacs answers a selection request
+The functions are called with four arguments:
+ - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');
+ - the selection-type which Emacs was asked to convert the
+ selection into before sending (for example, `STRING' or `LENGTH');
+ - a flag indicating success or failure for responding to the request.
+We might have failed (and declined the request) for any number of reasons,
+including being asked for a selection that we no longer own, or being asked
+to convert into a type that we don't know about or that is inappropriate.
+This hook doesn't let you change the behavior of Emacs's selection replies,
+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 selections when connected to multiple displays.
+This may cause crashes due to a GTK bug, which assumes that clients
+will connect to a single display. It might also cause selections to
+not arrive at the correct display. */);
+ pgtk_enable_selection_on_multi_display = false;
+}