/* Gtk selection processing for emacs.
Copyright (C) 1993-1994, 2005-2006, 2008-2018 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 . */
/*
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
#include
#include "lisp.h"
#include "keyboard.h"
#include "pgtkselect.h"
#include "pgtkterm.h"
#include "termhooks.h"
#if 0
static Lisp_Object Vselection_alist;
#endif
#ifndef HAVE_GTK4
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;
#endif
#ifdef HAVE_GTK4
Lisp_Object v_primary_selection;
Lisp_Object selection_tbl;
#endif
/* ==========================================================================
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;
}
#ifndef HAVE_GTK4
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);
}
#endif
#ifndef HAVE_GTK4
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
{
*quark_data = quark_clipboard_data;
*quark_size = quark_clipboard_size;
}
}
#endif
#ifndef HAVE_GTK4
static void
get_func (GtkClipboard *cb, GtkSelectionData *data, guint info,
gpointer user_data_or_owner)
{
#ifndef HAVE_GTK4
PGTK_TRACE ("get_func:");
GObject *obj = G_OBJECT (user_data_or_owner);
const char *str;
int size;
GQuark quark_data, quark_size;
#if defined(GDK_VERSION_3_22) || defined(HAVE_GTK4)
selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
&quark_size);
#else
selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
#endif
str = g_object_get_qdata (obj, quark_data);
size = GPOINTER_TO_SIZE (g_object_get_qdata (obj, quark_size));
PGTK_TRACE ("get_func: str: %s", str);
gtk_selection_data_set_text (data, str, size);
#endif
}
#endif
#ifndef HAVE_GTK4
static void
clear_func (GtkClipboard *cb, gpointer user_data_or_owner)
{
#ifndef HAVE_GTK4
PGTK_TRACE ("clear_func:");
GObject *obj = G_OBJECT (user_data_or_owner);
GQuark quark_data, quark_size;
#ifdef GDK_VERSION_3_22
selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
&quark_size);
#else
selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
#endif
g_object_set_qdata (obj, quark_data, NULL);
g_object_set_qdata (obj, quark_size, 0);
#endif
}
#endif
/* ==========================================================================
Functions used externally
========================================================================== */
void
pgtk_selection_init (void)
{
#ifndef HAVE_GTK4
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");
}
#else
v_primary_selection = Qnil;
#endif
}
#ifndef HAVE_GTK4
#ifndef HAVE_GTK_EVENT_CONTROLLER
void
pgtk_selection_lost (GtkWidget *widget, GdkEventSelection *event,
gpointer user_data)
#else
gboolean
pgtk_selection_lost (GtkWidget *widget, GdkEventSelection *event,
struct frame *frame)
#endif
{
GQuark quark_data, quark_size;
PGTK_TRACE ("pgtk_selection_lost:");
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);
#ifdef HAVE_GTK_EVENT_CONTROLLER
return true;
#endif
}
#endif
#ifndef HAVE_GTK4
static bool
pgtk_selection_usable (void)
{
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;
}
#endif
/* ==========================================================================
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)
{
#ifndef HAVE_GTK4
PGTK_TRACE ("pgtk-own-selection-internal.");
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);
#if defined(GDK_VERSION_3_22) || defined(HAVE_GTK4)
selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
&quark_size);
#else
selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
#endif
/* 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);
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);
PGTK_TRACE ("set_with_owner: owner=%p", FRAME_GTK_WIDGET (f));
if (gtk_clipboard_set_with_owner (cb, targets, n_targets, get_func,
clear_func,
G_OBJECT (FRAME_GTK_WIDGET (f))))
{
PGTK_TRACE ("set_with_owner succeeded..");
successful_p = Qt;
}
else
{
PGTK_TRACE ("set_with_owner failed.");
}
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;
#else
if (EQ (selection, QCLIPBOARD))
{
if (NILP (frame))
frame = selected_frame;
if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
error ("pgtk selection unavailable for this frame");
struct frame *f = XFRAME (frame);
GdkClipboard *clipboard = gdk_display_get_clipboard (FRAME_X_DISPLAY (f));
CHECK_STRING (value);
gdk_clipboard_set_text (clipboard, SSDATA (value));
return value;
}
else if (EQ (selection, QPRIMARY))
{
v_primary_selection = value;
return v_primary_selection;
}
else
{
if (NILP (selection_tbl))
selection_tbl = Fmake_hash_table (0, NULL);
Fputhash (selection, value, selection_tbl);
return selection_tbl;
}
return Qnil;
#endif
}
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)
{
#ifndef HAVE_GTK4
PGTK_TRACE ("pgtk-disown-selection-internal.");
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);
#else
if (EQ (selection, QPRIMARY))
{
GdkClipboard *clib = gdk_display_get_clipboard
(FRAME_X_DISPLAY (frame_for_pgtk_selection (terminal)));
gdk_clipboard_set_value (clib, NULL);
}
else if (EQ (selection, QPRIMARY))
{
v_primary_selection = NULL;
}
else
{
Fremhash (selection, selection_tbl);
}
#endif
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.
On GTK 4, SELECTION is unused. */)
(Lisp_Object selection, Lisp_Object terminal)
{
#ifndef HAVE_GTK4
PGTK_TRACE ("pgtk-selection-exists-p.");
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;
#else
return Qt;
#endif
}
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)
{
#ifndef HAVE_GTK4
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);
#if defined(GDK_VERSION_3_22)
selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
&quark_size);
#else
selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
#endif
obj = gtk_clipboard_get_owner (cb);
return g_object_get_qdata (obj, quark_data) != NULL ? Qt : Qnil;
#else
return Qt;
#endif
}
#ifdef HAVE_GTK4
typedef struct {
Lisp_Object *chp_data;
GdkClipboard *clipboard;
} zz_clipboard_cb_data;
static void
zz_clipboard_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
Lisp_Object clipboard;
GError *e = NULL;
char *stream = gdk_clipboard_read_text_finish
(((zz_clipboard_cb_data *) user_data)->clipboard, res, &e);
if (e || !stream)
{
clipboard = build_pure_c_string ("ERR FETCHING CLIPBOARD");
g_error_free (e);
}
else
{
size_t idx;
size_t idz = 0;
for (idx = 0; idz < strlen (stream);)
{
if (stream[idz] != '\xd')
{
stream[idx] = stream[idz];
++idx;
++idz;
}
else
{
++idz;
}
}
stream[idx] = 0;
clipboard = build_string (stream);
}
*((zz_clipboard_cb_data *) user_data)->chp_data = clipboard;
}
#endif
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)
{
#ifndef HAVE_GTK4
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);
gchar *s = gtk_clipboard_wait_for_text (cb);
if (s == NULL)
return Qnil;
int size = strlen (s);
Lisp_Object str = make_unibyte_string (s, size);
Fput_text_property (make_fixnum (0), make_fixnum (size), Qforeign_selection,
QUTF8_STRING, str);
return str;
#else
if (EQ (selection_symbol, QCLIPBOARD))
{
GdkClipboard *clipboard = gdk_display_get_clipboard
(FRAME_X_DISPLAY (frame_for_pgtk_selection (terminal)));
zz_clipboard_cb_data data;
Lisp_Object object = NULL;
data.clipboard = clipboard;
data.chp_data = &object;
gdk_clipboard_read_text_async (clipboard, NULL,
zz_clipboard_ready, &data);
while (!object)
g_main_context_iteration (NULL, false);
return object;
}
else if (EQ (selection_symbol, QPRIMARY))
{
return v_primary_selection;
}
else
{
if (NILP (selection_tbl))
return Qnil;
return Fgethash (selection_symbol, selection_tbl, Qnil);
}
return Qnil;
#endif
}
void
nxatoms_of_pgtkselect (void)
{
PGTK_TRACE ("nxatoms_of_pgtkselect");
}
void
syms_of_pgtkselect (void)
{
PGTK_TRACE ("syms_of_pgtkselect");
DEFSYM (QCLIPBOARD, "CLIPBOARD");
DEFSYM (QSECONDARY, "SECONDARY");
DEFSYM (QPRIMARY, "PRIMARY")
DEFSYM (QTEXT, "TEXT");
DEFSYM (QFILE_NAME, "FILE_NAME");
DEFSYM (QMULTIPLE, "MULTIPLE");
DEFSYM (Qforeign_selection, "foreign-selection");
DEFSYM (QUTF8_STRING, "UTF8_STRING");
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;
}