From 3bdeedd8aca18e449bd3700c4ab65055fa183201 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 7 Jun 2022 13:49:41 +0800 Subject: [PATCH] Improve compatibility with some clients of the Motif drop protocol * lisp/select.el (x-dnd-targets-list): New defvar. (xselect-convert-to-targets): Convert XdndSelection based on the DND targets list. * src/xfns.c (Fx_begin_drag): Pass new argument. * src/xselect.c (struct x_selection_request): New struct. (x_push_current_selection_request): (x_pop_current_selection_request): New functions. (x_selection_request_lisp_error, x_reply_selection_request) (x_handle_selection_request, x_convert_selection) (syms_of_xselect_for_pdumper): Correctly handle recursive requests for MULTIPLE by maintaining a stack of selection requests, converted selections, and other data. * src/xterm.c (x_dnd_begin_drag_and_drop): New argument `selection_target_list'. Bind it to the DND targets list. (syms_of_xterm): New defvar and associated defsym. * src/xterm.h: Update prototypes. --- lisp/select.el | 32 ++++++++++----- src/xfns.c | 2 +- src/xselect.c | 107 +++++++++++++++++++++++++++++++++++-------------- src/xterm.c | 14 ++++++- src/xterm.h | 3 +- 5 files changed, 115 insertions(+), 43 deletions(-) diff --git a/lisp/select.el b/lisp/select.el index 83dc137e239..706197e027e 100644 --- a/lisp/select.el +++ b/lisp/select.el @@ -601,19 +601,29 @@ two markers or an overlay. Otherwise, it is nil." (if len (xselect--int-to-cons len)))) +(defvar x-dnd-targets-list) + (defun xselect-convert-to-targets (selection _type value) ;; Return a vector of atoms, but remove duplicates first. - (apply #'vector - (delete-dups - `( TIMESTAMP MULTIPLE - . ,(delq '_EMACS_INTERNAL - (mapcar (lambda (conv) - (if (or (not (consp (cdr conv))) - (funcall (cadr conv) selection - (car conv) value)) - (car conv) - '_EMACS_INTERNAL)) - selection-converter-alist)))))) + (if (eq selection 'XdndSelection) + ;; This isn't required by the XDND protocol, and sure enough no + ;; clients seem to dependent on it, but Emacs implements the + ;; receiver side of the Motif drop protocol by looking at the + ;; initiator selection's TARGETS target (which Motif provides) + ;; instead of the target table on the drag window, so it seems + ;; plausible for other clients to rely on that as well. + (apply #'vector (mapcar #'intern x-dnd-targets-list)) + (apply #'vector + (delete-dups + `( TIMESTAMP MULTIPLE + . ,(delq '_EMACS_INTERNAL + (mapcar (lambda (conv) + (if (or (not (consp (cdr conv))) + (funcall (cadr conv) selection + (car conv) value)) + (car conv) + '_EMACS_INTERNAL)) + selection-converter-alist))))))) (defun xselect-convert-to-delete (selection _type _value) ;; This should be handled by the caller of `x-begin-drag'. diff --git a/src/xfns.c b/src/xfns.c index cfc6d4c212e..cffb4a5d96c 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -6985,7 +6985,7 @@ that mouse buttons are being held down, such as immediately after a xaction, return_frame, action_list, (const char **) &name_list, nnames, !NILP (allow_current_frame), target_atoms, - ntargets); + ntargets, original); SAFE_FREE (); return lval; diff --git a/src/xselect.c b/src/xselect.c index b920540620b..0271310d042 100644 --- a/src/xselect.c +++ b/src/xselect.c @@ -417,14 +417,6 @@ x_decline_selection_request (struct selection_input_event *event) unblock_input (); } -/* This is the selection request currently being processed. - It is set to zero when the request is fully processed. */ -static struct selection_input_event *x_selection_current_request; - -/* Display info in x_selection_request. */ - -static struct x_display_info *selection_request_dpyinfo; - /* Raw selection data, for sending to a requestor window. */ struct selection_data @@ -442,12 +434,59 @@ struct selection_data struct selection_data *next; }; -/* Linked list of the above (in support of MULTIPLE targets). */ +struct x_selection_request +{ + /* The last element in this stack. */ + struct x_selection_request *last; + + /* Its display info. */ + struct x_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; -static struct selection_data *converted_selections; + /* "Data" to send a requestor for a failed MULTIPLE subtarget. */ + Atom 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 x_selection_request *selection_request_stack; + +static void +x_push_current_selection_request (struct selection_input_event *se, + struct x_display_info *dpyinfo) +{ + struct x_selection_request *frame; -/* "Data" to send a requestor for a failed MULTIPLE subtarget. */ -static Atom conversion_fail_tag; + 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 = None; + + selection_request_stack = frame; +} + +static void +x_pop_current_selection_request (void) +{ + struct x_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 @@ -457,19 +496,21 @@ static void x_selection_request_lisp_error (void) { struct selection_data *cs, *next; + struct x_selection_request *frame; + + frame = selection_request_stack; - for (cs = converted_selections; cs; cs = next) + for (cs = frame->converted_selections; cs; cs = next) { next = cs->next; if (! cs->nofree && cs->data) xfree (cs->data); xfree (cs); } - converted_selections = NULL; + frame->converted_selections = NULL; - if (x_selection_current_request != 0 - && selection_request_dpyinfo->display) - x_decline_selection_request (x_selection_current_request); + if (!frame->converted && frame->dpyinfo->display) + x_decline_selection_request (frame->request); } static void @@ -535,6 +576,9 @@ x_reply_selection_request (struct selection_input_event *event, int max_bytes = selection_quantum (display); specpdl_ref count = SPECPDL_INDEX (); struct selection_data *cs; + struct x_selection_request *frame; + + frame = selection_request_stack; reply->type = SelectionNotify; reply->display = display; @@ -558,7 +602,7 @@ x_reply_selection_request (struct selection_input_event *event, (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 = converted_selections; cs; cs = cs->next) + for (cs = frame->converted_selections; cs; cs = cs->next) { if (cs->property == None) continue; @@ -613,7 +657,7 @@ x_reply_selection_request (struct selection_input_event *event, 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 = converted_selections; cs; cs = cs->next) + for (cs = frame->converted_selections; cs; cs = cs->next) if (cs->wait_object) { int format_bytes = cs->format / 8; @@ -749,9 +793,11 @@ x_handle_selection_request (struct selection_input_event *event) && local_selection_time > SELECTION_EVENT_TIME (event)) goto DONE; - x_selection_current_request = event; - selection_request_dpyinfo = dpyinfo; + block_input (); + x_push_current_selection_request (event, dpyinfo); + record_unwind_protect_void (x_pop_current_selection_request); record_unwind_protect_void (x_selection_request_lisp_error); + unblock_input (); TRACE2 ("x_handle_selection_request: selection=%s, target=%s", SDATA (SYMBOL_NAME (selection_symbol)), @@ -808,11 +854,12 @@ x_handle_selection_request (struct selection_input_event *event) DONE: + selection_request_stack->converted = true; + if (success) x_reply_selection_request (event, dpyinfo); else x_decline_selection_request (event); - x_selection_current_request = 0; /* Run the `x-sent-selection-functions' abnormal hook. */ if (!NILP (Vx_sent_selection_functions) @@ -837,11 +884,14 @@ x_convert_selection (Lisp_Object selection_symbol, { Lisp_Object lisp_selection; struct selection_data *cs; + struct x_selection_request *frame; lisp_selection = x_get_local_selection (selection_symbol, target_symbol, false, dpyinfo); + frame = selection_request_stack; + /* A nil return value means we can't perform the conversion. */ if (NILP (lisp_selection) || (CONSP (lisp_selection) && NILP (XCDR (lisp_selection)))) @@ -849,15 +899,16 @@ x_convert_selection (Lisp_Object selection_symbol, if (for_multiple) { cs = xmalloc (sizeof *cs); - cs->data = (unsigned char *) &conversion_fail_tag; + cs->data = ((unsigned char *) + &selection_request_stack->conversion_fail_tag); cs->size = 1; cs->format = 32; cs->type = XA_ATOM; cs->nofree = true; cs->property = property; cs->wait_object = NULL; - cs->next = converted_selections; - converted_selections = cs; + cs->next = frame->converted_selections; + frame->converted_selections = cs; } return false; @@ -869,8 +920,8 @@ x_convert_selection (Lisp_Object selection_symbol, cs->nofree = true; cs->property = property; cs->wait_object = NULL; - cs->next = converted_selections; - converted_selections = cs; + cs->next = frame->converted_selections; + frame->converted_selections = cs; lisp_data_to_selection_data (dpyinfo, lisp_selection, cs); return true; } @@ -2777,6 +2828,4 @@ syms_of_xselect_for_pdumper (void) property_change_wait_list = 0; prop_location_identifier = 0; property_change_reply = Fcons (Qnil, Qnil); - converted_selections = NULL; - conversion_fail_tag = None; } diff --git a/src/xterm.c b/src/xterm.c index 4dce24104d6..ae46453eb60 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -10645,7 +10645,7 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, Lisp_Object return_frame, Atom *ask_action_list, const char **ask_action_names, size_t n_ask_actions, bool allow_current_frame, Atom *target_atoms, - int ntargets) + int ntargets, Lisp_Object selection_target_list) { #ifndef USE_GTK XEvent next_event; @@ -10674,6 +10674,10 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, base = SPECPDL_INDEX (); + /* Bind this here to avoid juggling bindings and SAFE_FREE in + Fx_begin_drag. */ + specbind (Qx_dnd_targets_list, selection_target_list); + /* Before starting drag-and-drop, walk through the keyboard buffer to see if there are any UNSUPPORTED_DROP_EVENTs, and run them now if they exist, to prevent race conditions from happening due to @@ -26516,6 +26520,7 @@ syms_of_xterm (void) DEFSYM (Qvendor_specific_keysyms, "vendor-specific-keysyms"); DEFSYM (Qlatin_1, "latin-1"); DEFSYM (Qnow, "now"); + DEFSYM (Qx_dnd_targets_list, "x-dnd-targets-list"); #ifdef USE_GTK xg_default_icon_file = build_pure_c_string ("icons/hicolor/scalable/apps/emacs.svg"); @@ -26752,4 +26757,11 @@ operation, and TIME is the X server time when the drop happened. */); doc: /* Max number of buckets allowed per display in the internal color cache. Values less than 1 mean 128. This option is for debugging only. */); x_color_cache_bucket_size = 128; + + DEFVAR_LISP ("x-dnd-targets-list", Vx_dnd_targets_list, + doc: /* List of drag-and-drop targets. +This variable contains the list of drag-and-drop selection targets +during a drag-and-drop operation, in the same format as the TARGET +argument to `x-begin-drag'. */); + Vx_dnd_targets_list = Qnil; } diff --git a/src/xterm.h b/src/xterm.h index c6be30d73ef..1ab65f15d10 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1460,7 +1460,8 @@ extern void x_handle_pending_selection_requests (void); extern bool x_detect_pending_selection_requests (void); extern Lisp_Object x_dnd_begin_drag_and_drop (struct frame *, Time, Atom, Lisp_Object, Atom *, const char **, - size_t, bool, Atom *, int); + size_t, bool, Atom *, int, + Lisp_Object); extern void x_dnd_do_unsupported_drop (struct x_display_info *, Lisp_Object, Lisp_Object, Lisp_Object, Window, int, int, Time); -- 2.39.2