From be35c92c90d455739a6ff9d4beefa2b35d044852 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 21 Jun 2022 22:03:42 +0800 Subject: [PATCH] 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. --- src/frame.c | 11 + src/keyboard.c | 14 +- src/keyboard.h | 6 +- src/pgtkselect.c | 1871 +++++++++++++++++++++++++++++++++++++++------- src/pgtkselect.h | 31 - src/pgtkterm.c | 129 +++- src/pgtkterm.h | 61 +- 7 files changed, 1794 insertions(+), 329 deletions(-) delete mode 100644 src/pgtkselect.h 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,14 +4003,19 @@ 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 (©); +#else + pgtk_handle_selection_event (©); +#endif #else /* We're getting selection request events, but we don't have a window system. */ @@ -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,7 +4411,12 @@ 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 (©); +#else + pgtk_handle_selection_event (©); +#endif #else /* We're getting selection request events, but we don't have a window system. */ 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 . */ # 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 . */ -/* 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 @@ -35,21 +25,37 @@ along with GNU Emacs. If not, see . */ #include "pgtkterm.h" #include "termhooks.h" #include "keyboard.h" -#include "pgtkselect.h" -#include - -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 + . ) 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 . */ - - -#include "dispextern.h" -#include "frame.h" - -#ifdef HAVE_PGTK - -#include - -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 . */ #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, -- 2.39.2