summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPo Lu <luangruo@yahoo.com>2022-06-21 22:03:42 +0800
committerPo Lu <luangruo@yahoo.com>2022-06-21 22:05:21 +0800
commitbe35c92c90d455739a6ff9d4beefa2b35d044852 (patch)
tree19c5be6b9e09ccb45c58a5008f8f956f3ad92d57
parentb1af8c2c00aefe6aa554a468e65b6e07c9f14722 (diff)
downloademacs-be35c92c90d455739a6ff9d4beefa2b35d044852.tar.gz
Rewrite PGTK selection code from scratch
* src/frame.c (delete_frame): Clear selections and swallow special events. * src/keyboard.c (kbd_buffer_get_event, process_special_events): Also handle selection events on PGTK. * src/keyboard.h (union buffered_input_event): Include selection events on PGTK. * src/pgtkselect.c (symbol_to_gtk_clipboard, LOCAL_SELECTION): New functions and macros. (selection_type_to_quarks, get_func, clear_func): Delete functions. (pgtk_selection_init, pgtk_selection_lost): (pgtk_selection_usable): New functions. (Fpgtk_own_selection_internal, Fpgtk_disown_selection_internal) (Fpgtk_selection_exists_p, Fpgtk_selection_owner_p) (Fpgtk_get_selection_internal): Complete rewrite. (syms_of_pgtkselect): Update defsyms and add more hooks. * src/pgtkselect.h: Delete file. * src/pgtkterm.c (evq_enqueue): Set last user time based on the event. (pgtk_any_window_to_frame, button_event): Fix coding style. (pgtk_set_event_handler): Add selection events. (pgtk_find_selection_owner, pgtk_selection_event): New functions. (pgtk_term_init): Remove call to `pgtk_selection_init'. * src/pgtkterm.h (struct pgtk_display_info): New field `display'. (enum selection_input_event): New struct. New macros for accessing its fields.
-rw-r--r--src/frame.c11
-rw-r--r--src/keyboard.c14
-rw-r--r--src/keyboard.h6
-rw-r--r--src/pgtkselect.c1871
-rw-r--r--src/pgtkselect.h31
-rw-r--r--src/pgtkterm.c129
-rw-r--r--src/pgtkterm.h61
7 files changed, 1794 insertions, 329 deletions
diff --git a/src/frame.c b/src/frame.c
index c21461d49fe..c2f2f8e4642 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2176,6 +2176,17 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
x_clear_frame_selections (f);
#endif
+#ifdef HAVE_PGTK
+ if (FRAME_PGTK_P (f))
+ {
+ /* Do special selection events now, in case the window gets
+ destroyed by this deletion. Does this run Lisp code? */
+ swallow_events (false);
+
+ pgtk_clear_frame_selections (f);
+ }
+#endif
+
/* Free glyphs.
This function must be called before the window tree of the
frame is deleted because windows contain dynamically allocated
diff --git a/src/keyboard.c b/src/keyboard.c
index c41727d6c6e..6bc2afd40ac 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -4003,15 +4003,20 @@ kbd_buffer_get_event (KBOARD **kbp,
case SELECTION_REQUEST_EVENT:
case SELECTION_CLEAR_EVENT:
{
-#ifdef HAVE_X11
+#if defined HAVE_X11 || HAVE_PGTK
/* Remove it from the buffer before processing it,
since otherwise swallow_events will see it
and process it again. */
struct selection_input_event copy = event->sie;
kbd_fetch_ptr = next_kbd_event (event);
input_pending = readable_events (0);
+
+#ifdef HAVE_X11
x_handle_selection_event (&copy);
#else
+ pgtk_handle_selection_event (&copy);
+#endif
+#else
/* We're getting selection request events, but we don't have
a window system. */
emacs_abort ();
@@ -4381,7 +4386,7 @@ process_special_events (void)
if (event->kind == SELECTION_REQUEST_EVENT
|| event->kind == SELECTION_CLEAR_EVENT)
{
-#ifdef HAVE_X11
+#if defined HAVE_X11 || defined HAVE_PGTK
/* Remove the event from the fifo buffer before processing;
otherwise swallow_events called recursively could see it
@@ -4406,8 +4411,13 @@ process_special_events (void)
moved_events * sizeof *kbd_fetch_ptr);
kbd_fetch_ptr = next_kbd_event (kbd_fetch_ptr);
input_pending = readable_events (0);
+
+#ifdef HAVE_X11
x_handle_selection_event (&copy);
#else
+ pgtk_handle_selection_event (&copy);
+#endif
+#else
/* We're getting selection request events, but we don't have
a window system. */
emacs_abort ();
diff --git a/src/keyboard.h b/src/keyboard.h
index 6ae2dc9c4c6..507d80c2975 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -27,6 +27,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
# include "xterm.h" /* for struct selection_input_event */
#endif
+#ifdef HAVE_PGTK
+#include "pgtkterm.h" /* for struct selection_input_event */
+#endif
+
INLINE_HEADER_BEGIN
/* Most code should use this macro to access Lisp fields in struct kboard. */
@@ -226,7 +230,7 @@ union buffered_input_event
{
ENUM_BF (event_kind) kind : EVENT_KIND_WIDTH;
struct input_event ie;
-#ifdef HAVE_X11
+#if defined HAVE_X11 || defined HAVE_PGTK
struct selection_input_event sie;
#endif
};
diff --git a/src/pgtkselect.c b/src/pgtkselect.c
index 76901b9eb1d..a0168c9fad8 100644
--- a/src/pgtkselect.c
+++ b/src/pgtkselect.c
@@ -17,16 +17,6 @@ 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>
@@ -35,21 +25,37 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#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
-
- ========================================================================== */
+#include "atimer.h"
+#include "blockinput.h"
+
+/* This file deliberately does not implement INCR, since it adds a
+ bunch of extra code for no real gain, as PGTK isn't supposed to
+ support X11 anyway. */
+
+/* Advance declaration of structs. */
+struct selection_data;
+struct prop_location;
+
+static void pgtk_decline_selection_request (struct selection_input_event *);
+static bool pgtk_convert_selection (Lisp_Object, Lisp_Object, GdkAtom, bool,
+ struct pgtk_display_info *);
+static bool waiting_for_other_props_on_window (GdkDisplay *, GdkWindow *);
+#if 0
+static struct prop_location *expect_property_change (GdkDisplay *, GdkWindow *,
+ GdkAtom, int);
+#endif
+static void unexpect_property_change (struct prop_location *);
+static void wait_for_property_change (struct prop_location *);
+static Lisp_Object pgtk_get_window_property_as_lisp_data (struct pgtk_display_info *,
+ GdkWindow *, GdkAtom,
+ Lisp_Object, GdkAtom, bool);
+static Lisp_Object selection_data_to_lisp_data (struct pgtk_display_info *,
+ const unsigned char *,
+ ptrdiff_t, GdkAtom, int);
+static void lisp_data_to_selection_data (struct pgtk_display_info *, Lisp_Object,
+ struct selection_data *);
+static Lisp_Object pgtk_get_local_selection (Lisp_Object, Lisp_Object,
+ bool, struct pgtk_display_info *);
/* From a Lisp_Object, return a suitable frame for selection
operations. OBJECT may be a frame, a terminal object, or nil
@@ -98,398 +104,1662 @@ frame_for_pgtk_selection (Lisp_Object object)
return NULL;
}
-static GtkClipboard *
-symbol_to_gtk_clipboard (GtkWidget * widget, Lisp_Object symbol)
+#define LOCAL_SELECTION(selection_symbol, dpyinfo) \
+ assq_no_quit (selection_symbol, dpyinfo->terminal->Vselection_alist)
+
+static GdkAtom
+symbol_to_gdk_atom (Lisp_Object sym)
{
- GdkAtom atom;
+ if (NILP (sym))
+ return GDK_NONE;
- CHECK_SYMBOL (symbol);
- if (NILP (symbol))
- {
- atom = GDK_SELECTION_PRIMARY;
- }
- else if (EQ (symbol, QCLIPBOARD))
+ if (EQ (sym, QPRIMARY))
+ return GDK_SELECTION_PRIMARY;
+ if (EQ (sym, QSECONDARY))
+ return GDK_SELECTION_SECONDARY;
+ if (EQ (sym, QCLIPBOARD))
+ return GDK_SELECTION_CLIPBOARD;
+
+ if (!SYMBOLP (sym))
+ emacs_abort ();
+
+ return gdk_atom_intern (SSDATA (SYMBOL_NAME (sym)), FALSE);
+}
+
+static Lisp_Object
+gdk_atom_to_symbol (GdkAtom atom)
+{
+ return intern (gdk_atom_name (atom));
+}
+
+
+
+/* Do protocol to assert ourself as a selection owner.
+ FRAME shall be the owner; it must be a valid GDK frame.
+ Update the Vselection_alist so that we can reply to later requests for
+ our selection. */
+
+static void
+pgtk_own_selection (Lisp_Object selection_name, Lisp_Object selection_value,
+ Lisp_Object frame)
+{
+ struct frame *f = XFRAME (frame);
+ struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+ guint32 timestamp = gtk_get_current_event_time ();
+ GdkAtom selection_atom = symbol_to_gdk_atom (selection_name);
+ Lisp_Object targets;
+ ptrdiff_t i, ntargets;
+ GtkTargetEntry *gtargets;
+
+ if (timestamp == GDK_CURRENT_TIME)
+ timestamp = dpyinfo->last_user_time;
+
+ /* Assert ownership over the selection. Ideally we would use the
+ GDK selection API for this as well, but it just doesn't work on
+ Wayland. */
+
+ if (!gdk_selection_owner_set_for_display (dpyinfo->display,
+ FRAME_GDK_WINDOW (f),
+ selection_atom,
+ timestamp, TRUE))
+ signal_error ("Could not assert ownership over selection", selection_name);
+
+ /* Update the local cache */
+ {
+ Lisp_Object selection_data;
+ Lisp_Object prev_value;
+
+ selection_data = list4 (selection_name, selection_value,
+ INT_TO_INTEGER (timestamp), frame);
+ prev_value = LOCAL_SELECTION (selection_name, dpyinfo);
+
+ tset_selection_alist
+ (dpyinfo->terminal,
+ Fcons (selection_data, dpyinfo->terminal->Vselection_alist));
+
+ /* If we already owned the selection, remove the old selection
+ data. Don't use Fdelq as that may quit. */
+ if (!NILP (prev_value))
+ {
+ /* We know it's not the CAR, so it's easy. */
+ Lisp_Object rest = dpyinfo->terminal->Vselection_alist;
+ for (; CONSP (rest); rest = XCDR (rest))
+ if (EQ (prev_value, Fcar (XCDR (rest))))
+ {
+ XSETCDR (rest, XCDR (XCDR (rest)));
+ break;
+ }
+ }
+ }
+
+ /* Announce the targets to the display server. This isn't required
+ on X, but is on Wayland. */
+
+ targets = pgtk_get_local_selection (selection_name, QTARGETS,
+ true, dpyinfo);
+
+ /* GC must not happen inside this segment. */
+ block_input ();
+ gtk_selection_clear_targets (FRAME_GTK_WIDGET (f), selection_atom);
+
+ if (VECTORP (targets))
{
- atom = GDK_SELECTION_CLIPBOARD;
+ gtargets = xzalloc (sizeof *gtargets * ASIZE (targets));
+ ntargets = 0;
+
+ for (i = 0; i < ASIZE (targets); ++i)
+ {
+ if (SYMBOLP (AREF (targets, i)))
+ gtargets[ntargets++].target
+ = SSDATA (SYMBOL_NAME (AREF (targets, i)));
+ }
+
+ gtk_selection_add_targets (FRAME_GTK_WIDGET (f),
+ selection_atom, gtargets,
+ ntargets);
+
+ xfree (gtargets);
}
- else if (EQ (symbol, QPRIMARY))
+ unblock_input ();
+}
+
+static Lisp_Object
+pgtk_get_local_selection (Lisp_Object selection_symbol, Lisp_Object target_type,
+ bool local_request, struct pgtk_display_info *dpyinfo)
+{
+ Lisp_Object local_value, tem;
+ Lisp_Object handler_fn, value, check;
+
+ local_value = LOCAL_SELECTION (selection_symbol, dpyinfo);
+
+ if (NILP (local_value)) return Qnil;
+
+ /* TIMESTAMP is a special case. */
+ if (EQ (target_type, QTIMESTAMP))
{
- atom = GDK_SELECTION_PRIMARY;
+ handler_fn = Qnil;
+ value = XCAR (XCDR (XCDR (local_value)));
}
- else if (EQ (symbol, QSECONDARY))
+ else
{
- atom = GDK_SELECTION_SECONDARY;
+ /* Don't allow a quit within the converter.
+ When the user types C-g, he would be surprised
+ if by luck it came during a converter. */
+ specpdl_ref count = SPECPDL_INDEX ();
+ specbind (Qinhibit_quit, Qt);
+
+ CHECK_SYMBOL (target_type);
+ handler_fn = Fcdr (Fassq (target_type, Vselection_converter_alist));
+
+ if (CONSP (handler_fn))
+ handler_fn = XCDR (handler_fn);
+
+ tem = XCAR (XCDR (local_value));
+
+ if (STRINGP (tem))
+ {
+ local_value = Fget_text_property (make_fixnum (0),
+ target_type, tem);
+
+ if (!NILP (local_value))
+ tem = local_value;
+ }
+
+ if (!NILP (handler_fn))
+ value = call3 (handler_fn, selection_symbol,
+ (local_request
+ ? Qnil
+ : target_type),
+ tem);
+ else
+ value = Qnil;
+ value = unbind_to (count, value);
}
- else if (EQ (symbol, Qt))
+
+ /* Make sure this value is of a type that we could transmit
+ to another client. */
+
+ check = value;
+ if (CONSP (value)
+ && SYMBOLP (XCAR (value)))
+ check = XCDR (value);
+
+ if (STRINGP (check)
+ || VECTORP (check)
+ || SYMBOLP (check)
+ || INTEGERP (check)
+ || NILP (value))
+ return value;
+ /* Check for a value that CONS_TO_INTEGER could handle. */
+ else if (CONSP (check)
+ && INTEGERP (XCAR (check))
+ && (INTEGERP (XCDR (check))
+ ||
+ (CONSP (XCDR (check))
+ && INTEGERP (XCAR (XCDR (check)))
+ && NILP (XCDR (XCDR (check))))))
+ return value;
+
+ signal_error ("Invalid data returned by selection-conversion function",
+ list2 (handler_fn, value));
+}
+
+static void
+pgtk_decline_selection_request (struct selection_input_event *event)
+{
+ gdk_selection_send_notify (SELECTION_EVENT_REQUESTOR (event),
+ SELECTION_EVENT_SELECTION (event),
+ SELECTION_EVENT_TARGET (event),
+ GDK_NONE, SELECTION_EVENT_TIME (event));
+}
+
+struct selection_data
+{
+ unsigned char *data;
+ ptrdiff_t size;
+ int format;
+ GdkAtom type;
+ bool nofree;
+ GdkAtom property;
+
+ /* This can be set to non-NULL during x_reply_selection_request, if
+ the selection is waiting for an INCR transfer to complete. Don't
+ free these; that's done by unexpect_property_change. */
+ struct prop_location *wait_object;
+ struct selection_data *next;
+};
+
+struct pgtk_selection_request
+{
+ /* The last element in this stack. */
+ struct pgtk_selection_request *last;
+
+ /* Its display info. */
+ struct pgtk_display_info *dpyinfo;
+
+ /* Its selection input event. */
+ struct selection_input_event *request;
+
+ /* Linked list of the above (in support of MULTIPLE targets). */
+ struct selection_data *converted_selections;
+
+ /* "Data" to send a requestor for a failed MULTIPLE subtarget. */
+ GdkAtom conversion_fail_tag;
+
+ /* Whether or not conversion was successful. */
+ bool converted;
+};
+
+/* Stack of selections currently being processed.
+ NULL if all requests have been fully processed. */
+
+struct pgtk_selection_request *selection_request_stack;
+
+static void
+pgtk_push_current_selection_request (struct selection_input_event *se,
+ struct pgtk_display_info *dpyinfo)
+{
+ struct pgtk_selection_request *frame;
+
+ frame = xmalloc (sizeof *frame);
+ frame->converted = false;
+ frame->last = selection_request_stack;
+ frame->request = se;
+ frame->dpyinfo = dpyinfo;
+ frame->converted_selections = NULL;
+ frame->conversion_fail_tag = GDK_NONE;
+
+ selection_request_stack = frame;
+}
+
+static void
+pgtk_pop_current_selection_request (void)
+{
+ struct pgtk_selection_request *tem;
+
+ tem = selection_request_stack;
+ selection_request_stack = selection_request_stack->last;
+
+ xfree (tem);
+}
+
+/* Used as an unwind-protect clause so that, if a selection-converter signals
+ an error, we tell the requestor that we were unable to do what they wanted
+ before we throw to top-level or go into the debugger or whatever. */
+
+static void
+pgtk_selection_request_lisp_error (void)
+{
+ struct selection_data *cs, *next;
+ struct pgtk_selection_request *frame;
+
+ frame = selection_request_stack;
+
+ for (cs = frame->converted_selections; cs; cs = next)
{
- atom = GDK_SELECTION_SECONDARY;
+ next = cs->next;
+ if (! cs->nofree && cs->data)
+ xfree (cs->data);
+ xfree (cs);
}
- else
+ frame->converted_selections = NULL;
+
+ if (!frame->converted && frame->dpyinfo->display)
+ pgtk_decline_selection_request (frame->request);
+}
+
+/* This stuff is so that INCR selections are reentrant (that is, so we can
+ be servicing multiple INCR selection requests simultaneously.) I haven't
+ actually tested that yet. */
+
+/* Keep a list of the property changes that are awaited. */
+
+struct prop_location
+{
+ int identifier;
+ GdkDisplay *display;
+ GdkWindow *window;
+ GdkAtom property;
+ int desired_state;
+ bool arrived;
+ struct prop_location *next;
+};
+
+#if 0
+
+static int prop_location_identifier;
+
+#endif
+
+static Lisp_Object property_change_reply;
+
+static struct prop_location *property_change_reply_object;
+
+static struct prop_location *property_change_wait_list;
+
+static void
+set_property_change_object (struct prop_location *location)
+{
+ /* Input must be blocked so we don't get the event before we set these. */
+ if (!input_blocked_p ())
+ emacs_abort ();
+
+ XSETCAR (property_change_reply, Qnil);
+ property_change_reply_object = location;
+}
+
+
+/* Send the reply to a selection request event EVENT. */
+
+static void
+pgtk_reply_selection_request (struct selection_input_event *event,
+ struct pgtk_display_info *dpyinfo)
+{
+ GdkDisplay *display = SELECTION_EVENT_DISPLAY (event);
+ GdkWindow *window = SELECTION_EVENT_REQUESTOR (event);
+ ptrdiff_t bytes_remaining;
+ struct selection_data *cs;
+ struct pgtk_selection_request *frame;
+
+ frame = selection_request_stack;
+
+ block_input ();
+ /* Loop over converted selections, storing them in the requested
+ properties. If data is large, only store the first N bytes
+ (section 2.7.2 of ICCCM). Note that we store the data for a
+ MULTIPLE request in the opposite order; the ICCM says only that
+ the conversion itself must be done in the same order. */
+ for (cs = frame->converted_selections; cs; cs = cs->next)
{
- atom = 0;
- error ("Bad selection");
+ if (cs->property == GDK_NONE)
+ continue;
+
+ bytes_remaining = cs->size;
+ bytes_remaining *= cs->format >> 3;
+
+ gdk_property_change (window, cs->property,
+ cs->type, cs->format,
+ GDK_PROP_MODE_APPEND,
+ cs->data, cs->size);
}
- return gtk_widget_get_clipboard (widget, atom);
+ /* Now issue the SelectionNotify event. */
+ gdk_selection_send_notify (window,
+ SELECTION_EVENT_SELECTION (event),
+ SELECTION_EVENT_TARGET (event),
+ SELECTION_EVENT_PROPERTY (event),
+ SELECTION_EVENT_TIME (event));
+ gdk_display_flush (display);
+
+ /* Finish sending the rest of each of the INCR values. This should
+ be improved; there's a chance of deadlock if more than one
+ subtarget in a MULTIPLE selection requires an INCR transfer, and
+ the requestor and Emacs loop waiting on different transfers. */
+ for (cs = frame->converted_selections; cs; cs = cs->next)
+ if (cs->wait_object)
+ {
+ int format_bytes = cs->format / 8;
+
+ /* Must set this inside block_input (). unblock_input may read
+ events and setting property_change_reply in
+ wait_for_property_change is then too late. */
+ set_property_change_object (cs->wait_object);
+ unblock_input ();
+
+ bytes_remaining = cs->size;
+ bytes_remaining *= format_bytes;
+
+ /* Wait for the requestor to ack by deleting the property.
+ This can run Lisp code (process handlers) or signal. */
+ wait_for_property_change (cs->wait_object);
+
+ /* Now write a zero-length chunk to the property to tell the
+ requestor that we're done. */
+ block_input ();
+ if (! waiting_for_other_props_on_window (display, window))
+ gdk_window_set_events (window, 0);
+ gdk_property_change (window, cs->property, cs->type, cs->format,
+ GDK_PROP_MODE_REPLACE, cs->data, 0);
+ }
+
+ gdk_display_sync (display);
+ unblock_input ();
}
+
+
+/* Handle a SelectionRequest event EVENT.
+ This is called from keyboard.c when such an event is found in the queue. */
+
static void
-selection_type_to_quarks (GdkAtom type, GQuark * quark_data,
- GQuark * quark_size)
+pgtk_handle_selection_request (struct selection_input_event *event)
{
- if (type == GDK_SELECTION_PRIMARY)
+ guint32 local_selection_time;
+ struct pgtk_display_info *dpyinfo = SELECTION_EVENT_DPYINFO (event);
+ GdkAtom selection = SELECTION_EVENT_SELECTION (event);
+ Lisp_Object selection_symbol = gdk_atom_to_symbol (selection);
+ GdkAtom target = SELECTION_EVENT_TARGET (event);
+ Lisp_Object target_symbol = gdk_atom_to_symbol (target);
+ GdkAtom property = SELECTION_EVENT_PROPERTY (event);
+ Lisp_Object local_selection_data;
+ bool success = false;
+ specpdl_ref count = SPECPDL_INDEX ();
+ bool pushed;
+ Lisp_Object alias, tem;
+
+ alias = Vpgtk_selection_alias_alist;
+
+ FOR_EACH_TAIL_SAFE (alias)
{
- *quark_data = quark_primary_data;
- *quark_size = quark_primary_size;
+ tem = Qnil;
+
+ if (CONSP (alias))
+ tem = XCAR (alias);
+
+ if (CONSP (tem)
+ && EQ (XCAR (tem), selection_symbol)
+ && SYMBOLP (XCDR (tem)))
+ {
+ selection_symbol = XCDR (tem);
+ break;
+ }
}
- else if (type == GDK_SELECTION_SECONDARY)
+
+ pushed = false;
+
+ if (!dpyinfo)
+ goto DONE;
+
+ local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo);
+
+ /* Decline if we don't own any selections. */
+ if (NILP (local_selection_data)) goto DONE;
+
+ /* Decline requests issued prior to our acquiring the selection. */
+ CONS_TO_INTEGER (XCAR (XCDR (XCDR (local_selection_data))),
+ guint32, local_selection_time);
+ if (SELECTION_EVENT_TIME (event) != GDK_CURRENT_TIME
+ && local_selection_time > SELECTION_EVENT_TIME (event))
+ goto DONE;
+
+ block_input ();
+ pushed = true;
+ pgtk_push_current_selection_request (event, dpyinfo);
+ record_unwind_protect_void (pgtk_pop_current_selection_request);
+ record_unwind_protect_void (pgtk_selection_request_lisp_error);
+ unblock_input ();
+
+ if (EQ (target_symbol, QMULTIPLE))
{
- *quark_data = quark_secondary_data;
- *quark_size = quark_secondary_size;
+ /* For MULTIPLE targets, the event property names a list of atom
+ pairs; the first atom names a target and the second names a
+ non-GDK_NONE property. */
+ GdkWindow *requestor = SELECTION_EVENT_REQUESTOR (event);
+ Lisp_Object multprop;
+ ptrdiff_t j, nselections;
+ struct selection_data cs;
+
+ if (property == GDK_NONE)
+ goto DONE;
+
+ multprop = pgtk_get_window_property_as_lisp_data (dpyinfo,
+ requestor,
+ property,
+ QMULTIPLE,
+ selection,
+ true);
+
+ if (!VECTORP (multprop) || ASIZE (multprop) % 2)
+ goto DONE;
+
+ nselections = ASIZE (multprop) / 2;
+ /* Perform conversions. This can signal. */
+ for (j = 0; j < nselections; j++)
+ {
+ Lisp_Object subtarget = AREF (multprop, 2*j);
+ GdkAtom subproperty = symbol_to_gdk_atom (AREF (multprop, 2 * j + 1));
+ bool subsuccess = false;
+
+ if (subproperty != GDK_NONE)
+ subsuccess = pgtk_convert_selection (selection_symbol, subtarget,
+ subproperty, true, dpyinfo);
+ if (!subsuccess)
+ ASET (multprop, 2*j+1, Qnil);
+ }
+ /* Save conversion results */
+ lisp_data_to_selection_data (dpyinfo, multprop, &cs);
+ gdk_property_change (requestor, property,
+ cs.type, cs.format,
+ GDK_PROP_MODE_REPLACE,
+ cs.data, cs.size);
+ success = true;
}
- else if (type == GDK_SELECTION_CLIPBOARD)
+ else
{
- *quark_data = quark_clipboard_data;
- *quark_size = quark_clipboard_size;
+ if (property == GDK_NONE)
+ property = SELECTION_EVENT_TARGET (event);
+
+ success = pgtk_convert_selection (selection_symbol,
+ target_symbol, property,
+ false, dpyinfo);
}
+
+ DONE:
+
+ if (pushed)
+ selection_request_stack->converted = true;
+
+ if (success)
+ pgtk_reply_selection_request (event, dpyinfo);
else
- /* FIXME: Is it safe to use 'error' here? */
- error ("Unknown selection type.");
+ pgtk_decline_selection_request (event);
+
+ /* Run the `pgtk-sent-selection-functions' abnormal hook. */
+ if (!NILP (Vpgtk_sent_selection_functions)
+ && !BASE_EQ (Vpgtk_sent_selection_functions, Qunbound))
+ CALLN (Frun_hook_with_args, Qpgtk_sent_selection_functions,
+ selection_symbol, target_symbol, success ? Qt : Qnil);
+
+ unbind_to (count, Qnil);
}
-static void
-get_func (GtkClipboard * cb, GtkSelectionData * data, guint info,
- gpointer user_data_or_owner)
+/* Perform the requested selection conversion, and write the data to
+ the converted_selections linked list, where it can be accessed by
+ x_reply_selection_request. If FOR_MULTIPLE, write out
+ the data even if conversion fails, using conversion_fail_tag.
+
+ Return true if (and only if) successful. */
+
+static bool
+pgtk_convert_selection (Lisp_Object selection_symbol,
+ Lisp_Object target_symbol, GdkAtom property,
+ bool for_multiple, struct pgtk_display_info *dpyinfo)
{
- GObject *obj = G_OBJECT (user_data_or_owner);
- const char *str;
- int size;
- GQuark quark_data, quark_size;
+ Lisp_Object lisp_selection;
+ struct selection_data *cs;
+ struct pgtk_selection_request *frame;
+
+ lisp_selection
+ = pgtk_get_local_selection (selection_symbol, target_symbol,
+ false, dpyinfo);
- selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
- &quark_size);
+ frame = selection_request_stack;
- 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);
+ /* A nil return value means we can't perform the conversion. */
+ if (NILP (lisp_selection)
+ || (CONSP (lisp_selection) && NILP (XCDR (lisp_selection))))
+ {
+ if (for_multiple)
+ {
+ cs = xmalloc (sizeof *cs);
+ cs->data = ((unsigned char *)
+ &selection_request_stack->conversion_fail_tag);
+ cs->size = 1;
+ cs->format = 32;
+ cs->type = GDK_SELECTION_TYPE_ATOM;
+ cs->nofree = true;
+ cs->property = property;
+ cs->wait_object = NULL;
+ cs->next = frame->converted_selections;
+ frame->converted_selections = cs;
+ }
+
+ return false;
+ }
+
+ /* Otherwise, record the converted selection to binary. */
+ cs = xmalloc (sizeof *cs);
+ cs->data = NULL;
+ cs->nofree = true;
+ cs->property = property;
+ cs->wait_object = NULL;
+ cs->next = frame->converted_selections;
+ frame->converted_selections = cs;
+ lisp_data_to_selection_data (dpyinfo, lisp_selection, cs);
+ return true;
}
+
+
+/* Handle a SelectionClear event EVENT, which indicates that some
+ client cleared out our previously asserted selection.
+ This is called from keyboard.c when such an event is found in the queue. */
+
static void
-clear_func (GtkClipboard * cb, gpointer user_data_or_owner)
+pgtk_handle_selection_clear (struct selection_input_event *event)
{
- GObject *obj = G_OBJECT (user_data_or_owner);
- GQuark quark_data, quark_size;
+ GdkAtom selection = SELECTION_EVENT_SELECTION (event);
+ guint32 changed_owner_time = SELECTION_EVENT_TIME (event);
- selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
- &quark_size);
+ Lisp_Object selection_symbol, local_selection_data;
+ guint32 local_selection_time;
+ struct pgtk_display_info *dpyinfo = SELECTION_EVENT_DPYINFO (event);
+ Lisp_Object Vselection_alist;
- g_object_set_qdata (obj, quark_data, NULL);
- g_object_set_qdata (obj, quark_size, 0);
-}
+ if (!dpyinfo) return;
+ selection_symbol = gdk_atom_to_symbol (selection);
+ local_selection_data = LOCAL_SELECTION (selection_symbol, dpyinfo);
-/* ==========================================================================
+ /* Well, we already believe that we don't own it, so that's just fine. */
+ if (NILP (local_selection_data)) return;
- Functions used externally
+ CONS_TO_INTEGER (XCAR (XCDR (XCDR (local_selection_data))),
+ guint32, local_selection_time);
- ========================================================================== */
+ /* We have reasserted the selection since this SelectionClear was
+ generated, so we can disregard it. */
+ if (changed_owner_time != GDK_CURRENT_TIME
+ && local_selection_time > changed_owner_time)
+ return;
+
+ /* Otherwise, really clear. Don't use Fdelq as that may quit. */
+ Vselection_alist = dpyinfo->terminal->Vselection_alist;
+ if (EQ (local_selection_data, CAR (Vselection_alist)))
+ Vselection_alist = XCDR (Vselection_alist);
+ else
+ {
+ Lisp_Object rest;
+ for (rest = Vselection_alist; CONSP (rest); rest = XCDR (rest))
+ if (EQ (local_selection_data, CAR (XCDR (rest))))
+ {
+ XSETCDR (rest, XCDR (XCDR (rest)));
+ break;
+ }
+ }
+ tset_selection_alist (dpyinfo->terminal, Vselection_alist);
+
+ /* Run the `pgtk-lost-selection-functions' abnormal hook. */
+ CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions, selection_symbol);
+
+ redisplay_preserve_echo_area (20);
+}
+
+void
+pgtk_handle_selection_event (struct selection_input_event *event)
+{
+ if (event->kind != SELECTION_REQUEST_EVENT)
+ pgtk_handle_selection_clear (event);
+ else
+ pgtk_handle_selection_request (event);
+}
+
+/* Clear all selections that were made from frame F.
+ We do this when about to delete a frame. */
void
-pgtk_selection_init (void)
+pgtk_clear_frame_selections (struct frame *f)
+{
+ Lisp_Object frame;
+ Lisp_Object rest;
+ struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+ struct terminal *t = dpyinfo->terminal;
+
+ XSETFRAME (frame, f);
+
+ /* Delete elements from the beginning of Vselection_alist. */
+ while (CONSP (t->Vselection_alist)
+ && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (t->Vselection_alist)))))))
+ {
+ /* Run the `pgtk-lost-selection-functions' abnormal hook. */
+ CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions,
+ Fcar (Fcar (t->Vselection_alist)));
+
+ tset_selection_alist (t, XCDR (t->Vselection_alist));
+ }
+
+ /* Delete elements after the beginning of Vselection_alist. */
+ for (rest = t->Vselection_alist; CONSP (rest); rest = XCDR (rest))
+ if (CONSP (XCDR (rest))
+ && EQ (frame, XCAR (XCDR (XCDR (XCDR (XCAR (XCDR (rest))))))))
+ {
+ CALLN (Frun_hook_with_args, Qpgtk_lost_selection_functions,
+ XCAR (XCAR (XCDR (rest))));
+ XSETCDR (rest, XCDR (XCDR (rest)));
+ break;
+ }
+}
+
+/* True if any properties for DISPLAY and WINDOW
+ are on the list of what we are waiting for. */
+
+static bool
+waiting_for_other_props_on_window (GdkDisplay *display, GdkWindow *window)
+{
+ for (struct prop_location *p = property_change_wait_list; p; p = p->next)
+ if (p->display == display && p->window == window)
+ return true;
+ return false;
+}
+
+/* Add an entry to the list of property changes we are waiting for.
+ DISPLAY, WINDOW, PROPERTY, STATE describe what we will wait for.
+ The return value is a number that uniquely identifies
+ this awaited property change. */
+
+/* Currently unused -- uncomment later if we decide to implement INCR
+ transfer for X. */
+
+#if 0
+
+static struct prop_location *
+expect_property_change (GdkDisplay *display, GdkWindow *window,
+ GdkAtom property, int state)
+{
+ struct prop_location *pl = xmalloc (sizeof *pl);
+ pl->identifier = ++prop_location_identifier;
+ pl->display = display;
+ pl->window = window;
+ pl->property = property;
+ pl->desired_state = state;
+ pl->next = property_change_wait_list;
+ pl->arrived = false;
+ property_change_wait_list = pl;
+ return pl;
+}
+
+#endif
+
+/* Delete an entry from the list of property changes we are waiting for.
+ IDENTIFIER is the number that uniquely identifies the entry. */
+
+static void
+unexpect_property_change (struct prop_location *location)
{
- if (quark_primary_data == 0)
+ struct prop_location *prop, **pprev = &property_change_wait_list;
+
+ for (prop = property_change_wait_list; prop; prop = *pprev)
{
- 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");
+ if (prop == location)
+ {
+ *pprev = prop->next;
+ xfree (prop);
+ break;
+ }
+ else
+ pprev = &prop->next;
}
}
+/* Remove the property change expectation element for IDENTIFIER. */
+
+static void
+wait_for_property_change_unwind (void *loc)
+{
+ struct prop_location *location = loc;
+
+ unexpect_property_change (location);
+ if (location == property_change_reply_object)
+ property_change_reply_object = 0;
+}
+
+/* Actually wait for a property change.
+ IDENTIFIER should be the value that expect_property_change returned. */
+
+static void
+wait_for_property_change (struct prop_location *location)
+{
+ specpdl_ref count = SPECPDL_INDEX ();
+
+ /* Make sure to do unexpect_property_change if we quit or err. */
+ record_unwind_protect_ptr (wait_for_property_change_unwind, location);
+
+ /* See comment in x_reply_selection_request about setting
+ property_change_reply. Do not do it here. */
+
+ /* If the event we are waiting for arrives beyond here, it will set
+ property_change_reply, because property_change_reply_object says so. */
+ if (! location->arrived)
+ {
+ intmax_t timeout = max (0, 5000);
+ intmax_t secs = timeout / 1000;
+ int nsecs = (timeout % 1000) * 1000000;
+
+ wait_reading_process_output (secs, nsecs, 0, false,
+ property_change_reply, NULL, 0);
+
+ if (NILP (XCAR (property_change_reply)))
+ error ("Timed out waiting for property-notify event");
+ }
+
+ unbind_to (count, Qnil);
+}
+
+/* Called from the big filter in response to a PropertyNotify
+ event. */
+
void
-pgtk_selection_lost (GtkWidget * widget, GdkEventSelection * event,
- gpointer user_data)
+pgtk_handle_property_notify (GdkEventProperty *event)
{
- GQuark quark_data, quark_size;
+ struct prop_location *rest;
+ GdkDisplay *dpy;
+
+ dpy = gdk_window_get_display (event->window);
- selection_type_to_quarks (event->selection, &quark_data, &quark_size);
+ for (rest = property_change_wait_list; rest; rest = rest->next)
+ {
+ if (!rest->arrived
+ && rest->property == event->atom
+ && rest->window == event->window
+ && rest->display == dpy
+ && rest->desired_state == event->state)
+ {
+ rest->arrived = true;
- g_object_set_qdata (G_OBJECT (widget), quark_data, NULL);
- g_object_set_qdata (G_OBJECT (widget), quark_size, 0);
+ /* If this is the one wait_for_property_change is waiting for,
+ tell it to wake up. */
+ if (rest == property_change_reply_object)
+ XSETCAR (property_change_reply, Qt);
+
+ return;
+ }
+ }
}
-static bool
-pgtk_selection_usable (void)
+static void
+pgtk_display_selection_waiting_message (struct atimer *timer)
{
- if (pgtk_enable_selection_on_multi_display)
- return true;
+ Lisp_Object val;
- /* Gdk uses `gdk_display_get_default' when handling selections, so
- selections don't work properly when Emacs is connected to
- multiple displays. */
+ val = build_string ("Waiting for reply from selection owner...");
+ message3_nolog (val);
+}
- 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;
+static void
+pgtk_cancel_atimer (void *atimer)
+{
+ cancel_atimer (atimer);
}
-/* ==========================================================================
+
+/* Variables for communication with pgtk_handle_selection_notify. */
+static GdkAtom reading_which_selection;
+static Lisp_Object reading_selection_reply;
+static GdkWindow *reading_selection_window;
- Lisp Defuns
+/* Do protocol to read selection-data from the window server.
+ Converts this to Lisp data and returns it.
+ FRAME is the frame whose window shall request the selection. */
- ========================================================================== */
+static Lisp_Object
+pgtk_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type,
+ Lisp_Object time_stamp, Lisp_Object frame)
+{
+ struct frame *f = XFRAME (frame);
+ struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+ GdkWindow *requestor_window = FRAME_GDK_WINDOW (f);
+ guint32 requestor_time = dpyinfo->last_user_time;
+ GdkAtom selection_atom = symbol_to_gdk_atom (selection_symbol);
+ GdkAtom type_atom = (CONSP (target_type)
+ ? symbol_to_gdk_atom (XCAR (target_type))
+ : symbol_to_gdk_atom (target_type));
+ struct atimer *delayed_message;
+ struct timespec message_interval;
+ specpdl_ref count;
+
+ count = SPECPDL_INDEX ();
+
+ if (!FRAME_LIVE_P (f))
+ return unbind_to (count, Qnil);
+
+ if (!NILP (time_stamp))
+ CONS_TO_INTEGER (time_stamp, guint32, requestor_time);
+
+ block_input ();
+ /* Prepare to block until the reply has been read. */
+ reading_selection_window = requestor_window;
+ reading_which_selection = selection_atom;
+ XSETCAR (reading_selection_reply, Qnil);
+
+ gdk_selection_convert (requestor_window, selection_atom,
+ type_atom, requestor_time);
+ unblock_input ();
+
+ /* It should not be necessary to stop handling selection requests
+ during this time. In fact, the SAVE_TARGETS mechanism requires
+ us to handle a clipboard manager's requests before it returns
+ GDK_SELECTION_NOTIFY. */
+
+ message_interval = make_timespec (1, 0);
+ delayed_message = start_atimer (ATIMER_RELATIVE, message_interval,
+ pgtk_display_selection_waiting_message,
+ NULL);
+ record_unwind_protect_ptr (pgtk_cancel_atimer, delayed_message);
+
+ /* This allows quits. Also, don't wait forever. */
+ intmax_t timeout = max (0, 5000);
+ intmax_t secs = timeout / 1000;
+ int nsecs = (timeout % 1000) * 1000000;
+
+ wait_reading_process_output (secs, nsecs, 0, false,
+ reading_selection_reply, NULL, 0);
+
+ if (NILP (XCAR (reading_selection_reply)))
+ error ("Timed out waiting for reply from selection owner");
+ if (EQ (XCAR (reading_selection_reply), Qlambda))
+ return unbind_to (count, Qnil);
+
+ /* Otherwise, the selection is waiting for us on the requested property. */
+ return unbind_to (count,
+ pgtk_get_window_property_as_lisp_data (dpyinfo,
+ requestor_window,
+ GDK_NONE,
+ target_type,
+ selection_atom,
+ false));
+}
+/* Subroutines of pgtk_get_window_property_as_lisp_data */
-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.
+static ptrdiff_t
+pgtk_size_for_format (gint format)
+{
+ switch (format)
+ {
+ case 8:
+ return sizeof (unsigned char);
+ case 16:
+ return sizeof (unsigned short);
+ case 32:
+ return sizeof (unsigned long);
+
+ default:
+ emacs_abort ();
+ }
+}
-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)
+/* Use xfree, not g_free, to free the data obtained with this function. */
+
+static void
+pgtk_get_window_property (GdkWindow *window, unsigned char **data_ret,
+ ptrdiff_t *bytes_ret, GdkAtom *actual_type_ret,
+ int *actual_format_ret, unsigned long *actual_size_ret)
{
- Lisp_Object successful_p = Qnil;
- Lisp_Object target_symbol, rest;
- GtkClipboard *cb;
- struct frame *f;
- GQuark quark_data, quark_size;
+ gint length, actual_format;
+ unsigned char *data;
+ ptrdiff_t element_size;
+ void *xdata;
+ GdkAtom actual_type;
+ unsigned long i;
+ unsigned int *idata;
+ unsigned long *ldata;
+
+ data = NULL;
+
+ length = gdk_selection_property_get (window, &data,
+ &actual_type,
+ &actual_format);
+
+ if (!data)
+ {
+ *data_ret = NULL;
+ *actual_type_ret = GDK_NONE;
+ *bytes_ret = 0;
+ *actual_format_ret = 8;
+ *actual_size_ret = 0;
- check_window_system (NULL);
+ return;
+ }
- if (!pgtk_selection_usable ())
- return Qnil;
+ if (actual_type == GDK_SELECTION_TYPE_ATOM
+ || actual_type == gdk_atom_intern_static_string ("ATOM_PAIR"))
+ {
+ /* GDK should not allow anything else. */
+ eassert (actual_format == 32);
- 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);
+ length = length / sizeof (GdkAtom);
+ xdata = xmalloc (sizeof (GdkAtom) * length);
+ memcpy (xdata, data, 1 + length * sizeof (GdkAtom));
+
+ g_free (data);
+
+ *data_ret = xdata;
+ *actual_type_ret = actual_type;
+ *bytes_ret = length * sizeof (GdkAtom);
+ *actual_format_ret = 32;
+ *actual_size_ret = length;
- cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
- selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
- &quark_size);
+ return;
+ }
+
+ element_size = pgtk_size_for_format (actual_format);
+ length = length / element_size;
- /* We only support copy of text. */
- target_symbol = QTEXT;
- if (STRINGP (value))
+ /* Add an extra byte on the end. GDK guarantees that it is
+ NULL. */
+ xdata = xmalloc (1 + element_size * length);
+ memcpy (xdata, data, 1 + element_size * length);
+
+ if (actual_format == 32 && LONG_WIDTH > 32)
{
- GtkTargetList *list;
- GtkTargetEntry *targets;
- gint n_targets;
- GtkWidget *widget;
+ ldata = (typeof (ldata)) data;
+ idata = xdata;
- list = gtk_target_list_new (NULL, 0);
- gtk_target_list_add_text_targets (list, 0);
+ for (i = 0; i < length; ++i)
+ idata[i] = ldata[i];
- {
- /* 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);
- }
+ /* There is always enough space in idata. */
+ idata[length] = 0;
+ *bytes_ret = sizeof *idata * length;
+ }
+ else
+ /* I think GDK itself prevents element_size from exceeding the
+ length at which this computation fails. */
+ *bytes_ret = element_size * length;
+
+ /* Now free the original `data' allocated by GDK. */
+ g_free (data);
+
+ *data_ret = xdata;
+ *actual_type_ret = GDK_NONE;
+ *actual_size_ret = length;
+ *actual_format_ret = actual_format;
+ *actual_type_ret = actual_type;
+}
+
+static Lisp_Object
+pgtk_get_window_property_as_lisp_data (struct pgtk_display_info *dpyinfo,
+ GdkWindow *window, GdkAtom property,
+ Lisp_Object target_type, GdkAtom selection_atom,
+ bool for_multiple)
+{
+ GdkAtom actual_type;
+ int actual_format;
+ unsigned long actual_size;
+ unsigned char *data = 0;
+ ptrdiff_t bytes = 0;
+ Lisp_Object val;
+ GdkDisplay *display = dpyinfo->display;
+
+ pgtk_get_window_property (window, &data, &bytes,
+ &actual_type, &actual_format,
+ &actual_size);
+
+ if (!data)
+ {
+ if (for_multiple)
+ return Qnil;
+
+ if (gdk_selection_owner_get_for_display (display, selection_atom))
+ {
+ AUTO_STRING (format, "Selection owner couldn't convert: %s");
+ CALLN (Fmessage, format,
+ actual_type
+ ? list2 (target_type,
+ gdk_atom_to_symbol (actual_type))
+ : target_type);
+ return Qnil;
+ }
+ else
+ {
+ AUTO_STRING (format, "No selection: %s");
+ CALLN (Fmessage, format,
+ gdk_atom_to_symbol (selection_atom));
+ return Qnil;
+ }
+ }
- targets = gtk_target_table_new_from_list (list, &n_targets);
+ if (!for_multiple && property != GDK_NONE)
+ gdk_property_delete (window, property);
- int size = SBYTES (value);
- gchar *str = xmalloc (size + 1);
- memcpy (str, SSDATA (value), size);
- str[size] = '\0';
+ /* It's been read. Now convert it to a lisp object in some semi-rational
+ manner. */
+ val = selection_data_to_lisp_data (dpyinfo, data, bytes,
+ actual_type, actual_format);
- 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);
+ /* Use xfree, not g_free, because pgtk_get_window_property calls
+ xmalloc itself. */
+ xfree (data);
+ return val;
+}
+
+
+
+/* These functions convert from the selection data read from the
+ server into something that we can use from Lisp, and vice versa.
+
+ Type: Format: Size: Lisp Type:
+ ----- ------- ----- -----------
+ * 8 * String
+ ATOM 32 1 Symbol
+ ATOM 32 > 1 Vector of Symbols
+ * 16 1 Integer
+ * 16 > 1 Vector of Integers
+ * 32 1 if small enough: fixnum
+ otherwise: bignum
+ * 32 > 1 Vector of the above
+
+ When converting an object to C, it may be of the form (SYMBOL
+ . <data>) where SYMBOL is what we should claim that the type is.
+ Format and representation are as above.
+
+ Important: When format is 32, data should contain an array of int,
+ not an array of long as GDK returns. Unless TYPE is also
+ GDK_SELECTION_TYPE_ATOM, in which case data should be an array of
+ GdkAtom. This makes a difference when sizeof (long) != sizeof
+ (int). */
+
+static Lisp_Object
+selection_data_to_lisp_data (struct pgtk_display_info *dpyinfo,
+ const unsigned char *data,
+ ptrdiff_t size, GdkAtom type, int format)
+{
+ if (type == gdk_atom_intern_static_string ("NULL"))
+ return QNULL;
+ /* Convert any 8-bit data to a string, for compactness. */
+ else if (format == 8)
+ {
+ Lisp_Object str, lispy_type;
+
+ str = make_unibyte_string ((char *) data, size);
+ /* 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 (type == gdk_atom_intern_static_string ("COMPOUND_TEXT"))
+ lispy_type = QCOMPOUND_TEXT;
+ else if (type == gdk_atom_intern_static_string ("UTF8_STRING"))
+ lispy_type = QUTF8_STRING;
+ else
+ lispy_type = QSTRING;
+
+ Fput_text_property (make_fixnum (0), make_fixnum (size),
+ Qforeign_selection, lispy_type, str);
+ return str;
+ }
+ /* Convert a single atom to a Lisp_Symbol. Convert a set of atoms to
+ a vector of symbols. */
+ else if (format == 32
+ && (type == GDK_SELECTION_TYPE_ATOM
+ /* Treat ATOM_PAIR type similar to list of atoms. */
+ || type == gdk_atom_intern_static_string ("ATOM_PAIR")))
+ {
+ ptrdiff_t i;
+ GdkAtom *idata = (GdkAtom *) data;
- if (gtk_clipboard_set_with_owner (cb,
- targets, n_targets,
- get_func, clear_func,
- G_OBJECT (FRAME_GTK_WIDGET (f))))
+ if (size == sizeof (GdkAtom))
+ return gdk_atom_to_symbol (idata[0]);
+ else
{
- successful_p = Qt;
+ Lisp_Object v = make_nil_vector (size / sizeof (GdkAtom));
+
+ for (i = 0; i < size / sizeof (GdkAtom); i++)
+ ASET (v, i, gdk_atom_to_symbol (idata[i]));
+ return v;
}
- gtk_clipboard_set_can_store (cb, NULL, 0);
+ }
+
+ /* Convert a single 16-bit number or a small 32-bit number to a Lisp_Int.
+ If the number is 32 bits and won't fit in a Lisp_Int, convert it
+ to a bignum.
- gtk_target_table_free (targets, n_targets);
- gtk_target_list_unref (list);
+ INTEGER is a signed type, CARDINAL is unsigned.
+ Assume any other types are unsigned as well.
+ */
+ else if (format == 32 && size == sizeof (int))
+ {
+ if (type == GDK_SELECTION_TYPE_INTEGER)
+ return INT_TO_INTEGER (((int *) data) [0]);
+ else
+ return INT_TO_INTEGER (((unsigned int *) data) [0]);
+ }
+ else if (format == 16 && size == sizeof (short))
+ {
+ if (type == GDK_SELECTION_TYPE_INTEGER)
+ return make_fixnum (((short *) data) [0]);
+ else
+ return make_fixnum (((unsigned short *) data) [0]);
+ }
+ /* Convert any other kind of data to a vector of numbers, represented
+ as above (as an integer, or a cons of two 16 bit integers.)
+ */
+ else if (format == 16)
+ {
+ ptrdiff_t i;
+ Lisp_Object v = make_uninit_vector (size / 2);
+
+ if (type == GDK_SELECTION_TYPE_INTEGER)
+ {
+ for (i = 0; i < size / 2; i++)
+ {
+ short j = ((short *) data) [i];
+ ASET (v, i, make_fixnum (j));
+ }
+ }
+ else
+ {
+ for (i = 0; i < size / 2; i++)
+ {
+ unsigned short j = ((unsigned short *) data) [i];
+ ASET (v, i, make_fixnum (j));
+ }
+ }
+ return v;
}
+ else
+ {
+ ptrdiff_t i;
+ Lisp_Object v = make_nil_vector (size / sizeof (gint));
+
+ if (type == GDK_SELECTION_TYPE_INTEGER)
+ {
+ for (i = 0; i < size / sizeof (gint); i++)
+ {
+ int j = ((gint *) data) [i];
+ ASET (v, i, INT_TO_INTEGER (j));
+ }
+ }
+ else
+ {
+ for (i = 0; i < size / sizeof (gint); i++)
+ {
+ unsigned int j = ((unsigned int *) data) [i];
+ ASET (v, i, INT_TO_INTEGER (j));
+ }
+ }
+ return v;
+ }
+}
+
+/* Convert OBJ to an X long value, and return it as unsigned long.
+ OBJ should be an integer or a cons representing an integer.
+ Treat values in the range X_LONG_MAX + 1 .. X_ULONG_MAX as X
+ unsigned long values: in theory these values are supposed to be
+ signed but in practice unsigned 32-bit data are communicated via X
+ selections and we need to support that. */
+static unsigned long
+cons_to_gdk_long (Lisp_Object obj)
+{
+ if (G_MAXUINT32 <= INTMAX_MAX
+ || NILP (Fnatnump (CONSP (obj) ? XCAR (obj) : obj)))
+ return cons_to_signed (obj, 0, min (G_MAXUINT32, INTMAX_MAX));
+ else
+ return cons_to_unsigned (obj, G_MAXUINT32);
+}
+
+/* Use xfree, not XFree, to free the data obtained with this function. */
+
+static void
+lisp_data_to_selection_data (struct pgtk_display_info *dpyinfo,
+ Lisp_Object obj, struct selection_data *cs)
+{
+ Lisp_Object type = Qnil;
- if (!BASE_EQ (Vpgtk_sent_selection_hooks, Qunbound))
+ eassert (cs != NULL);
+ cs->nofree = false;
+
+ if (CONSP (obj) && SYMBOLP (XCAR (obj)))
{
- /* 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);
+ type = XCAR (obj);
+ obj = XCDR (obj);
+ if (CONSP (obj) && NILP (XCDR (obj)))
+ obj = XCAR (obj);
}
+ if (EQ (obj, QNULL) || (EQ (type, QNULL)))
+ { /* This is not the same as declining */
+ cs->format = 32;
+ cs->size = 0;
+ cs->data = NULL;
+ type = QNULL;
+ }
+ else if (STRINGP (obj))
+ {
+ if (SCHARS (obj) < SBYTES (obj))
+ /* OBJ is a multibyte string containing a non-ASCII char. */
+ signal_error ("Non-ASCII string must be encoded in advance", obj);
+ if (NILP (type))
+ type = QSTRING;
+ cs->format = 8;
+ cs->size = SBYTES (obj);
+ cs->data = SDATA (obj);
+ cs->nofree = true;
+ }
+ else if (SYMBOLP (obj))
+ {
+ void *data = xmalloc (sizeof (GdkAtom) + 1);
+ GdkAtom *x_atom_ptr = data;
+ cs->data = data;
+ cs->format = 32;
+ cs->size = 1;
+ cs->data[sizeof (GdkAtom)] = 0;
+ *x_atom_ptr = symbol_to_gdk_atom (obj);
+ if (NILP (type)) type = QATOM;
+ }
+ else if (RANGED_FIXNUMP (SHRT_MIN, obj, SHRT_MAX))
+ {
+ void *data = xmalloc (sizeof (short) + 1);
+ short *short_ptr = data;
+ cs->data = data;
+ cs->format = 16;
+ cs->size = 1;
+ cs->data[sizeof (short)] = 0;
+ *short_ptr = XFIXNUM (obj);
+ if (NILP (type)) type = QINTEGER;
+ }
+ else if (INTEGERP (obj)
+ || (CONSP (obj) && INTEGERP (XCAR (obj))
+ && (FIXNUMP (XCDR (obj))
+ || (CONSP (XCDR (obj))
+ && FIXNUMP (XCAR (XCDR (obj)))))))
+ {
+ void *data = xmalloc (sizeof (unsigned long) + 1);
+ unsigned long *x_long_ptr = data;
+ cs->data = data;
+ cs->format = 32;
+ cs->size = 1;
+ cs->data[sizeof (unsigned long)] = 0;
+ *x_long_ptr = cons_to_gdk_long (obj);
+ if (NILP (type)) type = QINTEGER;
+ }
+ else if (VECTORP (obj))
+ {
+ /* Lisp_Vectors may represent a set of ATOMs;
+ a set of 16 or 32 bit INTEGERs;
+ or a set of ATOM_PAIRs (represented as [[A1 A2] [A3 A4] ...]
+ */
+ ptrdiff_t i;
+ ptrdiff_t size = ASIZE (obj);
+
+ if (SYMBOLP (AREF (obj, 0)))
+ /* This vector is an ATOM set */
+ {
+ void *data;
+ GdkAtom *x_atoms;
+ if (NILP (type)) type = QATOM;
+ for (i = 0; i < size; i++)
+ if (!SYMBOLP (AREF (obj, i)))
+ signal_error ("All elements of selection vector must have same type", obj);
+
+ cs->data = data = xnmalloc (size, sizeof *x_atoms);
+ x_atoms = data;
+ cs->format = 32;
+ cs->size = size;
+ for (i = 0; i < size; i++)
+ x_atoms[i] = symbol_to_gdk_atom (AREF (obj, i));
+ }
+ else
+ /* This vector is an INTEGER set, or something like it */
+ {
+ int format = 16;
+ int data_size = sizeof (short);
+ void *data;
+ unsigned long *x_atoms;
+ short *shorts;
+ if (NILP (type)) type = QINTEGER;
+ for (i = 0; i < size; i++)
+ {
+ if (! RANGED_FIXNUMP (SHRT_MIN, AREF (obj, i), SHRT_MAX))
+ {
+ /* Use sizeof (long) even if it is more than 32 bits.
+ See comment in x_get_window_property and
+ x_fill_property_data. */
+ data_size = sizeof (long);
+ format = 32;
+ break;
+ }
+ }
+ cs->data = data = xnmalloc (size, data_size);
+ x_atoms = data;
+ shorts = data;
+ cs->format = format;
+ cs->size = size;
+ for (i = 0; i < size; i++)
+ {
+ if (format == 32)
+ x_atoms[i] = cons_to_gdk_long (AREF (obj, i));
+ else
+ shorts[i] = XFIXNUM (AREF (obj, i));
+ }
+ }
+ }
+ else
+ signal_error (/* Qselection_error */ "Unrecognized selection data", obj);
+
+ cs->type = symbol_to_gdk_atom (type);
+}
+
+static Lisp_Object
+clean_local_selection_data (Lisp_Object obj)
+{
+ if (CONSP (obj)
+ && INTEGERP (XCAR (obj))
+ && CONSP (XCDR (obj))
+ && FIXNUMP (XCAR (XCDR (obj)))
+ && NILP (XCDR (XCDR (obj))))
+ obj = Fcons (XCAR (obj), XCDR (obj));
+
+ if (CONSP (obj)
+ && INTEGERP (XCAR (obj))
+ && FIXNUMP (XCDR (obj)))
+ {
+ if (BASE_EQ (XCAR (obj), make_fixnum (0)))
+ return XCDR (obj);
+ if (BASE_EQ (XCAR (obj), make_fixnum (-1)))
+ return make_fixnum (- XFIXNUM (XCDR (obj)));
+ }
+ if (VECTORP (obj))
+ {
+ ptrdiff_t i;
+ ptrdiff_t size = ASIZE (obj);
+ Lisp_Object copy;
+ if (size == 1)
+ return clean_local_selection_data (AREF (obj, 0));
+ copy = make_nil_vector (size);
+ for (i = 0; i < size; i++)
+ ASET (copy, i, clean_local_selection_data (AREF (obj, i)));
+ return copy;
+ }
+ return obj;
+}
+
+DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal,
+ Spgtk_own_selection_internal, 2, 3, 0,
+ doc: /* Assert a 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 GDK 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)
+{
+ if (NILP (frame)) frame = selected_frame;
+ if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
+ error ("GDK selection unavailable for this frame");
+
+ CHECK_SYMBOL (selection);
+ if (NILP (value)) error ("VALUE may not be nil");
+ pgtk_own_selection (selection, value, frame);
return value;
}
+/* Request the selection value from the owner. If we are the owner,
+ simply return our selection value. If we are not the owner, this
+ will block until all of the data has arrived. */
-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.
+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. */)
- (Lisp_Object selection, Lisp_Object terminal)
+ (Lisp_Object selection_symbol, Lisp_Object target_type,
+ Lisp_Object time_stamp, Lisp_Object terminal)
{
+ Lisp_Object val = Qnil;
+ Lisp_Object maybe_alias;
struct frame *f = frame_for_pgtk_selection (terminal);
- GtkClipboard *cb;
- if (!pgtk_selection_usable ())
- return Qnil;
+ CHECK_SYMBOL (selection_symbol);
+ CHECK_SYMBOL (target_type);
+ if (EQ (target_type, QMULTIPLE))
+ error ("Retrieving MULTIPLE selections is currently unimplemented");
if (!f)
- return Qnil;
+ error ("GDK selection unavailable for this frame");
- cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+ /* Quitting inside this function is okay, so we don't have to use
+ FOR_EACH_TAIL_SAFE. */
+ maybe_alias = Fassq (selection_symbol, Vpgtk_selection_alias_alist);
- gtk_clipboard_clear (cb);
+ if (!NILP (maybe_alias))
+ {
+ selection_symbol = XCDR (maybe_alias);
+ CHECK_SYMBOL (selection_symbol);
+ }
- return Qt;
+ val = pgtk_get_local_selection (selection_symbol, target_type, true,
+ FRAME_DISPLAY_INFO (f));
+
+ if (NILP (val) && FRAME_LIVE_P (f))
+ {
+ Lisp_Object frame;
+ XSETFRAME (frame, f);
+ return pgtk_get_foreign_selection (selection_symbol, target_type,
+ time_stamp, frame);
+ }
+
+ if (CONSP (val) && SYMBOLP (XCAR (val)))
+ {
+ val = XCDR (val);
+ if (CONSP (val) && NILP (XCDR (val)))
+ val = XCAR (val);
+ }
+ return clean_local_selection_data (val);
}
+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.
-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'.
+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, TERMINAL is unused. */)
- (Lisp_Object selection, Lisp_Object terminal)
+frame's display, or the first available X display. */)
+ (Lisp_Object selection, Lisp_Object time_object, Lisp_Object terminal)
{
+ guint32 timestamp;
+ GdkAtom selection_atom;
struct frame *f = frame_for_pgtk_selection (terminal);
- GtkClipboard *cb;
+ struct pgtk_display_info *dpyinfo;
- if (!pgtk_selection_usable ())
+ if (!f)
return Qnil;
- if (!f)
+ dpyinfo = FRAME_DISPLAY_INFO (f);
+ CHECK_SYMBOL (selection);
+
+ /* Don't disown the selection when we're not the owner. */
+ if (NILP (LOCAL_SELECTION (selection, dpyinfo)))
return Qnil;
- cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+ selection_atom = symbol_to_gdk_atom (selection);
- return gtk_clipboard_wait_is_text_available (cb) ? Qt : Qnil;
-}
+ block_input ();
+ if (NILP (time_object))
+ timestamp = dpyinfo->last_user_time;
+ else
+ CONS_TO_INTEGER (time_object, guint32, timestamp);
+ gdk_selection_owner_set_for_display (dpyinfo->display, NULL,
+ selection_atom, timestamp,
+ TRUE);
+ unblock_input ();
+ return Qt;
+}
-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.
+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 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.)
+\(Those are literal upper-case symbol names, since that's what GDK 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
+TERMINAL should be a terminal object or a frame specifying the GDK
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. */)
+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;
- 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);
+ CHECK_SYMBOL (selection);
+ if (NILP (selection)) selection = QPRIMARY;
+ if (EQ (selection, Qt)) selection = QSECONDARY;
- obj = gtk_clipboard_get_owner (cb);
-
- return obj && g_object_get_qdata (obj, quark_data) != NULL ? Qt : Qnil;
+ if (f && !NILP (LOCAL_SELECTION (selection, FRAME_DISPLAY_INFO (f))))
+ return Qt;
+ else
+ return Qnil;
}
+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 selection.
+SELECTION should be the name of the selection in question, typically
+one of the symbols `PRIMARY', `SECONDARY', `CLIPBOARD', or
+`CLIPBOARD_MANAGER' (GDK expects these literal upper-case names.) The
+symbol nil is the same as `PRIMARY', and t is the same as `SECONDARY'.
-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
+TERMINAL should be a terminal object or a frame specifying the GDK
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)
+frame's display, or the first available X display. */)
+ (Lisp_Object selection, Lisp_Object terminal)
{
+ GdkWindow *owner;
+ GdkAtom atom;
struct frame *f = frame_for_pgtk_selection (terminal);
- GtkClipboard *cb;
+ struct pgtk_display_info *dpyinfo;
- CHECK_SYMBOL (selection_symbol);
- CHECK_SYMBOL (target_type);
+ CHECK_SYMBOL (selection);
+ if (NILP (selection)) selection = QPRIMARY;
+ if (EQ (selection, Qt)) selection = QSECONDARY;
- 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);
+ dpyinfo = FRAME_DISPLAY_INFO (f);
- if (seldata == NULL)
- return Qnil;
+ if (!NILP (LOCAL_SELECTION (selection, dpyinfo)))
+ return Qt;
- 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);
+ atom = symbol_to_gdk_atom (selection);
+ if (atom == 0) return Qnil;
+ block_input ();
+ owner = gdk_selection_owner_get_for_display (dpyinfo->display, atom);
+ unblock_input ();
+ return (owner ? Qt : Qnil);
+}
- if (sd_format == 8)
- {
- Lisp_Object str, lispy_type;
+/* Called to handle GDK_SELECTION_NOTIFY events.
+ If it's the selection we are waiting for, stop waiting
+ by setting the car of reading_selection_reply to non-nil.
+ We store t there if the reply is successful, lambda if not. */
- 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);
+void
+pgtk_handle_selection_notify (GdkEventSelection *event)
+{
+ /* GDK doesn't populate event->requestor, contrary to what the ICCCM
+ says should be done with SelectionNotify events. */
- gtk_selection_data_free (seldata);
- return str;
- }
+ if (event->selection != reading_which_selection)
+ return;
- gtk_selection_data_free (seldata);
- return Qnil;
+ XSETCAR (reading_selection_reply,
+ (event->property != GDK_NONE ? Qt : Qlambda));
}
void
@@ -499,7 +1769,19 @@ syms_of_pgtkselect (void)
DEFSYM (QSECONDARY, "SECONDARY");
DEFSYM (QTEXT, "TEXT");
DEFSYM (QFILE_NAME, "FILE_NAME");
+ DEFSYM (QSTRING, "STRING");
+ DEFSYM (QINTEGER, "INTEGER");
+ DEFSYM (QTIMESTAMP, "TIMESTAMP");
+ DEFSYM (QTEXT, "TEXT");
DEFSYM (QMULTIPLE, "MULTIPLE");
+ DEFSYM (QNULL, "NULL");
+ DEFSYM (QATOM, "ATOM");
+ DEFSYM (QTARGETS, "TARGETS");
+
+ DEFSYM (Qpgtk_sent_selection_functions,
+ "pgtk-sent-selection-functions");
+ DEFSYM (Qpgtk_lost_selection_functions,
+ "pgtk-lost-selection-functions");
DEFSYM (Qforeign_selection, "foreign-selection");
DEFSYM (QUTF8_STRING, "UTF8_STRING");
@@ -513,6 +1795,32 @@ syms_of_pgtkselect (void)
defsubr (&Spgtk_selection_exists_p);
defsubr (&Spgtk_selection_owner_p);
+ DEFVAR_LISP ("selection-converter-alist", Vselection_converter_alist,
+ doc: /* SKIP: real doc in xselect.c. */);
+ Vselection_converter_alist = Qnil;
+
+ DEFVAR_LISP ("pgtk-lost-selection-functions", Vpgtk_lost_selection_functions,
+ doc: /* A list of functions to be called when Emacs loses a selection.
+\(This happens when some other client makes its own selection
+or when a Lisp program explicitly clears the selection.)
+The functions are called with one argument, the selection type
+\(a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'). */);
+ Vpgtk_lost_selection_functions = Qnil;
+
+ DEFVAR_LISP ("pgtk-sent-selection-functions", Vpgtk_sent_selection_functions,
+ doc: /* A list of functions to be called when Emacs answers a selection request.
+The functions are called with three 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_functions = Qnil;
+
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:
@@ -533,4 +1841,19 @@ 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;
+
+ DEFVAR_LISP ("pgtk-selection-alias-alist", Vpgtk_selection_alias_alist,
+ doc: /* List of selections to alias to another.
+It should be an alist of a selection name to another. When a
+selection request arrives for the first selection, Emacs will respond
+as if the request was meant for the other.
+
+Note that this does not affect setting or owning selections. */);
+ Vpgtk_selection_alias_alist = Qnil;
+
+ reading_selection_reply = Fcons (Qnil, Qnil);
+ staticpro (&reading_selection_reply);
+
+ property_change_reply = Fcons (Qnil, Qnil);
+ staticpro (&property_change_reply);
}
diff --git a/src/pgtkselect.h b/src/pgtkselect.h
deleted file mode 100644
index fd9910b2d18..00000000000
--- a/src/pgtkselect.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Definitions and headers for selection of pure Gtk+3.
- Copyright (C) 1989, 1993, 2005, 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/>. */
-
-
-#include "dispextern.h"
-#include "frame.h"
-
-#ifdef HAVE_PGTK
-
-#include <gtk/gtk.h>
-
-extern void pgtk_selection_init (void);
-extern void pgtk_selection_lost (GtkWidget *, GdkEventSelection *, gpointer);
-
-#endif /* HAVE_PGTK */
diff --git a/src/pgtkterm.c b/src/pgtkterm.c
index da958a6664a..91874ff58a5 100644
--- a/src/pgtkterm.c
+++ b/src/pgtkterm.c
@@ -61,7 +61,6 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "buffer.h"
#include "font.h"
#include "xsettings.h"
-#include "pgtkselect.h"
#include "emacsgtkfixed.h"
#ifdef GDK_WINDOWING_WAYLAND
@@ -290,6 +289,9 @@ static void
evq_enqueue (union buffered_input_event *ev)
{
struct event_queue_t *evq = &event_q;
+ struct frame *frame;
+ struct pgtk_display_info *dpyinfo;
+
if (evq->cap == 0)
{
evq->cap = 4;
@@ -303,6 +305,27 @@ evq_enqueue (union buffered_input_event *ev)
}
evq->q[evq->nr++] = *ev;
+
+ if (ev->ie.kind != SELECTION_REQUEST_EVENT
+ && ev->ie.kind != SELECTION_CLEAR_EVENT)
+ {
+ frame = NULL;
+
+ if (WINDOWP (ev->ie.frame_or_window))
+ frame = WINDOW_XFRAME (XWINDOW (ev->ie.frame_or_window));
+
+ if (FRAMEP (ev->ie.frame_or_window))
+ frame = XFRAME (ev->ie.frame_or_window);
+
+ if (frame)
+ {
+ dpyinfo = FRAME_DISPLAY_INFO (frame);
+
+ if (dpyinfo->last_user_time < ev->ie.timestamp)
+ dpyinfo->last_user_time = ev->ie.timestamp;
+ }
+ }
+
raise (SIGIO);
}
@@ -4809,16 +4832,16 @@ pgtk_any_window_to_frame (GdkWindow *window)
return NULL;
FOR_EACH_FRAME (tail, frame)
- {
- if (found)
- break;
- f = XFRAME (frame);
- if (FRAME_PGTK_P (f))
- {
- if (pgtk_window_is_of_frame (f, window))
- found = f;
- }
- }
+ {
+ if (found)
+ break;
+ f = XFRAME (frame);
+ if (FRAME_PGTK_P (f))
+ {
+ if (pgtk_window_is_of_frame (f, window))
+ found = f;
+ }
+ }
return found;
}
@@ -5868,8 +5891,7 @@ construct_mouse_click (struct input_event *result,
}
static gboolean
-button_event (GtkWidget *widget,
- GdkEvent *event,
+button_event (GtkWidget *widget, GdkEvent *event,
gpointer *user_data)
{
union buffered_input_event inev;
@@ -6174,6 +6196,8 @@ pgtk_monitors_changed_cb (GdkScreen *screen, gpointer user_data)
evq_enqueue (&inev);
}
+static gboolean pgtk_selection_event (GtkWidget *, GdkEvent *, gpointer);
+
void
pgtk_set_event_handler (struct frame *f)
{
@@ -6225,14 +6249,20 @@ pgtk_set_event_handler (struct frame *f)
G_CALLBACK (button_event), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "scroll-event",
G_CALLBACK (scroll_event), NULL);
- g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event",
- G_CALLBACK (pgtk_selection_lost), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "configure-event",
G_CALLBACK (configure_event), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-data-received",
G_CALLBACK (drag_data_received), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "draw",
G_CALLBACK (pgtk_handle_draw), NULL);
+ g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "property-notify-event",
+ G_CALLBACK (pgtk_selection_event), NULL);
+ g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event",
+ G_CALLBACK (pgtk_selection_event), NULL);
+ g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-request-event",
+ G_CALLBACK (pgtk_selection_event), NULL);
+ g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-notify-event",
+ G_CALLBACK (pgtk_selection_event), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "event",
G_CALLBACK (pgtk_handle_event), NULL);
}
@@ -6292,6 +6322,73 @@ same_x_server (const char *name1, const char *name2)
&& (*name2 == '.' || *name2 == '\0'));
}
+static struct frame *
+pgtk_find_selection_owner (GdkWindow *window)
+{
+ Lisp_Object tail, tem;
+ struct frame *f;
+
+ FOR_EACH_FRAME (tail, tem)
+ {
+ f = XFRAME (tem);
+
+ if (FRAME_PGTK_P (f)
+ && (FRAME_GDK_WINDOW (f) == window))
+ return f;
+ }
+
+ return NULL;
+}
+
+static gboolean
+pgtk_selection_event (GtkWidget *widget, GdkEvent *event,
+ gpointer user_data)
+{
+ struct frame *f;
+ union buffered_input_event inev;
+
+ if (event->type == GDK_PROPERTY_NOTIFY)
+ pgtk_handle_property_notify (&event->property);
+ else if (event->type == GDK_SELECTION_CLEAR
+ || event->type == GDK_SELECTION_REQUEST)
+ {
+ f = pgtk_find_selection_owner (event->selection.window);
+
+ if (f)
+ {
+ EVENT_INIT (inev.ie);
+
+ inev.sie.kind = (event->type == GDK_SELECTION_CLEAR
+ ? SELECTION_CLEAR_EVENT
+ : SELECTION_REQUEST_EVENT);
+
+ SELECTION_EVENT_DPYINFO (&inev.sie) = FRAME_DISPLAY_INFO (f);
+ SELECTION_EVENT_SELECTION (&inev.sie) = event->selection.selection;
+ SELECTION_EVENT_TIME (&inev.sie) = event->selection.time;
+
+ if (event->type == GDK_SELECTION_REQUEST)
+ {
+ /* FIXME: when does GDK destroy the requestor GdkWindow
+ object?
+
+ It would make sense to wait for the transfer to
+ complete. But I don't know if GDK actually does
+ that. */
+ SELECTION_EVENT_REQUESTOR (&inev.sie) = event->selection.requestor;
+ SELECTION_EVENT_TARGET (&inev.sie) = event->selection.target;
+ SELECTION_EVENT_PROPERTY (&inev.sie) = event->selection.property;
+ }
+
+ evq_enqueue (&inev);
+ return TRUE;
+ }
+ }
+ else if (event->type == GDK_SELECTION_NOTIFY)
+ pgtk_handle_selection_notify (&event->selection);
+
+ return FALSE;
+}
+
/* Open a connection to X display DISPLAY_NAME, and return
the structure that describes the open display.
If we cannot contact the display, return null. */
@@ -6525,8 +6622,6 @@ pgtk_term_init (Lisp_Object display_name, char *resource_name)
xsettings_initialize (dpyinfo);
- pgtk_selection_init ();
-
pgtk_im_init (dpyinfo);
g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-added",
diff --git a/src/pgtkterm.h b/src/pgtkterm.h
index e31e62ae193..86578be6b56 100644
--- a/src/pgtkterm.h
+++ b/src/pgtkterm.h
@@ -127,8 +127,14 @@ struct pgtk_display_info
/* The generic display parameters corresponding to this PGTK display. */
struct terminal *terminal;
- /* This says how to access this display in Gdk. */
- GdkDisplay *gdpy;
+ union
+ {
+ /* This says how to access this display through GDK. */
+ GdkDisplay *gdpy;
+
+ /* An alias defined to make porting X code easier. */
+ GdkDisplay *display;
+ };
/* This is a cons cell of the form (NAME . FONT-LIST-CACHE). */
Lisp_Object name_list_element;
@@ -210,6 +216,9 @@ struct pgtk_display_info
/* Time of last mouse movement. */
Time last_mouse_movement_time;
+ /* Time of last user interaction. */
+ guint32 last_user_time;
+
/* The scroll bar in which the last motion event occurred. */
void *last_mouse_scroll_bar;
@@ -443,10 +452,11 @@ enum
FRAME_GTK_OUTER_WIDGET (f) : \
FRAME_GTK_WIDGET (f))
-/* aliases */
#define FRAME_PGTK_VIEW(f) FRAME_GTK_WIDGET (f)
#define FRAME_X_WINDOW(f) FRAME_GTK_OUTER_WIDGET (f)
#define FRAME_NATIVE_WINDOW(f) GTK_WINDOW (FRAME_X_WINDOW (f))
+#define FRAME_GDK_WINDOW(f) \
+ (gtk_widget_get_window (FRAME_GTK_WIDGET (f)))
#define FRAME_X_DISPLAY(f) (FRAME_DISPLAY_INFO (f)->gdpy)
@@ -484,6 +494,49 @@ enum
#define FRAME_CR_SURFACE_DESIRED_HEIGHT(f) \
((f)->output_data.pgtk->cr_surface_desired_height)
+
+/* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT
+ or SELECTION_CLEAR_EVENT, then its contents are really described
+ by this structure. */
+
+/* For an event of kind SELECTION_REQUEST_EVENT,
+ this structure really describes the contents. */
+
+struct selection_input_event
+{
+ ENUM_BF (event_kind) kind : EVENT_KIND_WIDTH;
+ struct pgtk_display_info *dpyinfo;
+ /* We spell it with an "o" here because X does. */
+ GdkWindow *requestor;
+ GdkAtom selection, target, property;
+ guint32 time;
+};
+
+/* Unlike macros below, this can't be used as an lvalue. */
+INLINE GdkDisplay *
+SELECTION_EVENT_DISPLAY (struct selection_input_event *ev)
+{
+ return ev->dpyinfo->display;
+}
+#define SELECTION_EVENT_DPYINFO(eventp) \
+ ((eventp)->dpyinfo)
+/* We spell it with an "o" here because X does. */
+#define SELECTION_EVENT_REQUESTOR(eventp) \
+ ((eventp)->requestor)
+#define SELECTION_EVENT_SELECTION(eventp) \
+ ((eventp)->selection)
+#define SELECTION_EVENT_TARGET(eventp) \
+ ((eventp)->target)
+#define SELECTION_EVENT_PROPERTY(eventp) \
+ ((eventp)->property)
+#define SELECTION_EVENT_TIME(eventp) \
+ ((eventp)->time)
+
+extern void pgtk_handle_selection_event (struct selection_input_event *);
+extern void pgtk_clear_frame_selections (struct frame *);
+extern void pgtk_handle_property_notify (GdkEventProperty *);
+extern void pgtk_handle_selection_notify (GdkEventSelection *);
+
/* Display init/shutdown functions implemented in pgtkterm.c */
extern struct pgtk_display_info *pgtk_term_init (Lisp_Object display_name,
char *resource_name);
@@ -493,7 +546,7 @@ extern void pgtk_term_shutdown (int sig);
extern void pgtk_clear_frame (struct frame *f);
extern char *pgtk_xlfd_to_fontname (const char *xlfd);
-/* Implemented in pgtkfns. */
+/* Implemented in pgtkfns.c. */
extern void pgtk_set_doc_edited (void);
extern const char *pgtk_get_defaults_value (const char *key);
extern const char *pgtk_get_string_resource (XrmDatabase rdb,