]> git.eshelyaron.com Git - emacs.git/commitdiff
Add support for dragging text from Emacs to other programs
authorPo Lu <luangruo@yahoo.com>
Wed, 16 Mar 2022 03:29:36 +0000 (11:29 +0800)
committerPo Lu <luangruo@yahoo.com>
Wed, 16 Mar 2022 03:31:29 +0000 (11:31 +0800)
This still probably needs some more protection from
malfunctioning clients which delete windows at random, but I
don't know if that's a problem in practice.

* doc/emacs/frames.texi (Drag and Drop):
* doc/lispref/frames.texi (Drag and Drop): Document new
features.

* etc/NEWS: Announce new function `x-begin-drag' and new user
option `mouse-drag-and-drop-region-cross-program'.

* lisp/mouse.el (mouse-drag-and-drop-region-cross-program): New
user option.
(x-begin-drag): New variable declaration.
(mouse-drag-and-drop-region): If the mouse moves out of an Emacs
frame, begin a window system drag.
* lisp/x-dnd.el (x-dnd-handle-xdnd): Remove left-over debugging
code.

* src/xfns.c (Fx_set_mouse_absolute_pixel_position): Fix
indentation of opening paren.
(Fx_begin_drag): New function.
(syms_of_xfns): Define new subr.
* src/xselect.c (x_timestamp_for_selection): New function.

* src/xterm.c (X_DND_SUPPORTED_VERSION): New preprocessor
declaration.
(x_dnd_get_window_proto, x_dnd_send_enter, x_dnd_send_position)
(x_dnd_send_leave, x_dnd_send_drop, x_set_dnd_targets)
(x_dnd_begin_drag_and_drop): New functions.
(handle_one_xevent): Handle drag-and-drop motion and button
events when active.
(x_free_frame_resources): If f is the DND source, stop
drag-and-drop.
(x_term_init): Intern new atoms.
(syms_of_xterm): New symbol QXdndSelection.

* src/xterm.h (struct x_display_info): New atoms
Xatom_XdndAware, Xatom_XdndSelection, Xatom_XdndTypeList,
Xatom_XdndActionCopy, Xatom_XdndActionMove,
Xatom_XdndActionLink, Xatom_XdndActionAsk,
Xatom_XdndActionPrivate, Xatom_XdndActionList,
Xatom_XdndActionDescription, Xatom_XdndProxy, Xatom_XdndEnter,
Xatom_XdndPosition, Xatom_XdndStatus, Xatom_XdndLeave,
Xatom_XdndDrop, and Xatom_XdndFinished.

doc/emacs/frames.texi
doc/lispref/frames.texi
etc/NEWS
lisp/mouse.el
lisp/x-dnd.el
src/xfns.c
src/xselect.c
src/xterm.c
src/xterm.h

index e3cfe5f8441432f6ca977e2cfdec38b0ad31e541..7489344cda912726da7b28c8254139f07f51ee22 100644 (file)
@@ -1220,6 +1220,10 @@ cursor during dragging.  To suppress such behavior, set the options
 @code{mouse-drag-and-drop-region-show-tooltip} and/or
 @code{mouse-drag-and-drop-region-show-cursor} to @code{nil}.
 
+@vindex mouse-drag-and-drop-region-cross-program
+To drag text from Emacs to other programs, set the option
+@code{mouse-drag-and-drop-region-cross-program} to a non-@code{nil}
+value.
 
 @node Menu Bars
 @section Menu Bars
index bae8eb3c703b48c236078bc78620300d4abc81d6..38897d6a0b3198772efca11068f3fb2fcc13fbd5 100644 (file)
@@ -4038,6 +4038,35 @@ there is no match there, Emacs looks for a match in
 still no match has been found, the text for the URL is inserted.  If
 you want to alter Emacs behavior, you can customize these variables.
 
+@cindex initiating drag-and-drop
+  On some window systems, Emacs also supports dragging contents from
+itself to other frames.
+
+@defun x-begin-drag targets action &optional frame
+This function begins a drag from @var{frame}, and returns when the
+session ends, either because the drop was successful, or because the
+drop was rejected.  The drop occurs when all mouse buttons are
+released on top of an X window other than @var{frame} (the @dfn{drop
+target}).
+
+@var{targets} is a list of strings describing selection targets, much
+like the @var{data-type} argument to @code{gui-get-selection}, that
+the drop target can request from Emacs (@pxref{Window System
+Selections}).
+
+@var{action} is a symbol describing the action recommended to the
+target.  It can either be @code{XdndActionCopy}, which means which
+means to copy the contents of the selection @code{XdndSelection} to
+the drop target, or @code{XdndActionMove}, which means the same as
+@code{XdndActionCopy}, but also means the caller should delete
+whatever was saved into that selection afterwards.
+
+If the drop was rejected or no drop target was found, this function
+returns @code{nil}.  Otherwise, it returns a symbol describing the
+action the target chose to perform, which can differ from @var{action}
+if that isn't supported by the drop target.
+@end defun
+
 @node Color Names
 @section Color Names
 
index d6b5da3902e2ff573c900e5a714e1ad314e7836d..f4d8756950b5c7ecc0fd5aac6805a56c40a10c8d 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -169,6 +169,11 @@ methods instead.
 \f
 * Changes in Emacs 29.1
 
++++
+** New user option 'mouse-drag-and-drop-region-cross-program'.
+If non-nil, this option allows dragging text in the region from Emacs
+to another program.
+
 +++
 ** New function 'command-query'.
 This function makes its argument command prompt the user for
@@ -1203,6 +1208,11 @@ functions.
 \f
 * Lisp Changes in Emacs 29.1
 
++++
+** New function 'x-begin-drag'.
+This function initiates a drag-and-drop request with the contents of
+the selection 'XdndSelection', and returns when a drop occurs.
+
 ---
 ** New function 'ietf-drums-parse-date-string'.
 This function parses RFC5322 (and RFC822) date strings, and should be
index 1e205283de20d23cbea245961d48475a31381c22..3e2097e761f094f2ceb2c6b1e7778443b56e21fd 100644 (file)
@@ -2974,6 +2974,11 @@ in addition, temporarily highlight the original region with the
   :type 'boolean
   :version "26.1")
 
+(defcustom mouse-drag-and-drop-region-cross-program nil
+  "If non-nil, allow dragging text to other programs."
+  :type 'boolean
+  :version "29.1")
+
 (defface mouse-drag-and-drop-region '((t :inherit region))
   "Face to highlight original text during dragging.
 This face is used by `mouse-drag-and-drop-region' to temporarily
@@ -2984,6 +2989,7 @@ highlight the original region when
 (declare-function rectangle-dimensions "rect" (start end))
 (declare-function rectangle-position-as-coordinates "rect" (position))
 (declare-function rectangle-intersect-p "rect" (pos1 size1 pos2 size2))
+(declare-function x-begin-drag "xfns.c")
 
 (defun mouse-drag-and-drop-region (event)
   "Move text in the region to point where mouse is dragged to.
@@ -3046,114 +3052,132 @@ is copied instead of being cut."
               states))))
 
     (ignore-errors
-      (track-mouse
-        (setq track-mouse 'dropping)
-        ;; When event was "click" instead of "drag", skip loop.
-        (while (progn
-                 (setq event (read-key))      ; read-event or read-key
-                 (or (mouse-movement-p event)
-                     ;; Handle `mouse-autoselect-window'.
-                     (memq (car event) '(select-window switch-frame))))
-          ;; Obtain the dragged text in region.  When the loop was
-          ;; skipped, value-selection remains nil.
-          (unless value-selection
-            (setq value-selection (funcall region-extract-function nil))
-            (when mouse-drag-and-drop-region-show-tooltip
-              (let ((text-size mouse-drag-and-drop-region-show-tooltip))
-                (setq text-tooltip
-                      (if (and (integerp text-size)
-                               (> (length value-selection) text-size))
-                          (concat
-                           (substring value-selection 0 (/ text-size 2))
-                           "\n...\n"
-                           (substring value-selection (- (/ text-size 2)) -1))
-                        value-selection))))
-
-            ;; Check if selected text is read-only.
-            (setq text-from-read-only
-                  (or text-from-read-only
-                      (catch 'loop
-                        (dolist (bound (region-bounds))
-                          (when (text-property-not-all
-                                 (car bound) (cdr bound) 'read-only nil)
-                            (throw 'loop t)))))))
-
-          (setq window-to-paste (posn-window (event-end event)))
-          (setq point-to-paste (posn-point (event-end event)))
-          ;; Set nil when target buffer is minibuffer.
-          (setq buffer-to-paste (let (buf)
-                                  (when (windowp window-to-paste)
-                                    (setq buf (window-buffer window-to-paste))
-                                    (when (not (minibufferp buf))
-                                      buf))))
-          (setq cursor-in-text-area (and window-to-paste
-                                         point-to-paste
-                                         buffer-to-paste))
-
-          (when cursor-in-text-area
-            ;; Check if point under mouse is read-only.
-            (save-window-excursion
-              (select-window window-to-paste)
-              (setq point-to-paste-read-only
-                    (or buffer-read-only
-                        (get-text-property point-to-paste 'read-only))))
-
-            ;; Check if "drag but negligible".  Operation "drag but
-            ;; negligible" is defined as drag-and-drop the text to
-            ;; the original region.  When modifier is pressed, the
-            ;; text will be inserted to inside of the original
-            ;; region.
-            ;;
-            ;; If the region is rectangular, check if the newly inserted
-            ;; rectangular text would intersect the already selected
-            ;; region. If it would, then set "drag-but-negligible" to t.
-            ;; As a special case, allow dragging the region freely anywhere
-            ;; to the left, as this will never trigger its contents to be
-            ;; inserted into the overlays tracking it.
-            (setq drag-but-negligible
-                  (and (eq (overlay-buffer (car mouse-drag-and-drop-overlays))
-                           buffer-to-paste)
-                       (if region-noncontiguous
-                           (let ((dimensions (rectangle-dimensions start end))
-                                 (start-coordinates
-                                  (rectangle-position-as-coordinates start))
-                                 (point-to-paste-coordinates
-                                  (rectangle-position-as-coordinates
-                                   point-to-paste)))
-                             (and (rectangle-intersect-p
-                                   start-coordinates dimensions
-                                   point-to-paste-coordinates dimensions)
-                                  (not (< (car point-to-paste-coordinates)
-                                           (car start-coordinates)))))
-                         (and (<= (overlay-start
-                                   (car mouse-drag-and-drop-overlays))
-                                  point-to-paste)
-                              (<= point-to-paste
-                                  (overlay-end
-                                   (car mouse-drag-and-drop-overlays))))))))
-
-          ;; Show a tooltip.
-          (if mouse-drag-and-drop-region-show-tooltip
-              (tooltip-show text-tooltip)
-            (tooltip-hide))
-
-          ;; Show cursor and highlight the original region.
-          (when mouse-drag-and-drop-region-show-cursor
-            ;; Modify cursor even when point is out of frame.
-            (setq cursor-type (cond
-                               ((not cursor-in-text-area)
-                                nil)
-                               ((or point-to-paste-read-only
-                                    drag-but-negligible)
-                                'hollow)
-                               (t
-                                'bar)))
+      (catch 'cross-program-drag
+        (track-mouse
+          (setq track-mouse 'dropping)
+          ;; When event was "click" instead of "drag", skip loop.
+          (while (progn
+                   (setq event (read-key))      ; read-event or read-key
+                   (or (mouse-movement-p event)
+                       ;; Handle `mouse-autoselect-window'.
+                       (memq (car event) '(select-window switch-frame))))
+            ;; Obtain the dragged text in region.  When the loop was
+            ;; skipped, value-selection remains nil.
+            (unless value-selection
+              (setq value-selection (funcall region-extract-function nil))
+              (when mouse-drag-and-drop-region-show-tooltip
+                (let ((text-size mouse-drag-and-drop-region-show-tooltip))
+                  (setq text-tooltip
+                        (if (and (integerp text-size)
+                                 (> (length value-selection) text-size))
+                            (concat
+                             (substring value-selection 0 (/ text-size 2))
+                             "\n...\n"
+                             (substring value-selection (- (/ text-size 2)) -1))
+                          value-selection))))
+
+              ;; Check if selected text is read-only.
+              (setq text-from-read-only
+                    (or text-from-read-only
+                        (catch 'loop
+                          (dolist (bound (region-bounds))
+                            (when (text-property-not-all
+                                   (car bound) (cdr bound) 'read-only nil)
+                              (throw 'loop t)))))))
+
+            (when (and mouse-drag-and-drop-region-cross-program
+                       (fboundp 'x-begin-drag)
+                       (framep (posn-window (event-end event)))
+                       (let ((location (posn-x-y (event-end event)))
+                             (frame (posn-window (event-end event))))
+                         (or (< (car location) 0)
+                             (< (cdr location) 0)
+                             (> (car location)
+                                (frame-pixel-width frame))
+                             (> (cdr location)
+                                (frame-pixel-height frame)))))
+              (tooltip-hide)
+              (gui-set-selection 'XdndSelection value-selection)
+              (x-begin-drag '("UTF8_STRING" "STRING")
+                            'XdndActionMove (posn-window (event-end event)))
+              (throw 'cross-program-drag nil))
+
+            (setq window-to-paste (posn-window (event-end event)))
+            (setq point-to-paste (posn-point (event-end event)))
+            ;; Set nil when target buffer is minibuffer.
+            (setq buffer-to-paste (let (buf)
+                                    (when (windowp window-to-paste)
+                                      (setq buf (window-buffer window-to-paste))
+                                      (when (not (minibufferp buf))
+                                        buf))))
+            (setq cursor-in-text-area (and window-to-paste
+                                           point-to-paste
+                                           buffer-to-paste))
+
             (when cursor-in-text-area
-              (dolist (overlay mouse-drag-and-drop-overlays)
-                (overlay-put overlay
-                           'face 'mouse-drag-and-drop-region))
-              (deactivate-mark)     ; Maintain region in other window.
-              (mouse-set-point event)))))
+              ;; Check if point under mouse is read-only.
+              (save-window-excursion
+                (select-window window-to-paste)
+                (setq point-to-paste-read-only
+                      (or buffer-read-only
+                          (get-text-property point-to-paste 'read-only))))
+
+              ;; Check if "drag but negligible".  Operation "drag but
+              ;; negligible" is defined as drag-and-drop the text to
+              ;; the original region.  When modifier is pressed, the
+              ;; text will be inserted to inside of the original
+              ;; region.
+              ;;
+              ;; If the region is rectangular, check if the newly inserted
+              ;; rectangular text would intersect the already selected
+              ;; region. If it would, then set "drag-but-negligible" to t.
+              ;; As a special case, allow dragging the region freely anywhere
+              ;; to the left, as this will never trigger its contents to be
+              ;; inserted into the overlays tracking it.
+              (setq drag-but-negligible
+                    (and (eq (overlay-buffer (car mouse-drag-and-drop-overlays))
+                             buffer-to-paste)
+                         (if region-noncontiguous
+                             (let ((dimensions (rectangle-dimensions start end))
+                                   (start-coordinates
+                                    (rectangle-position-as-coordinates start))
+                                   (point-to-paste-coordinates
+                                    (rectangle-position-as-coordinates
+                                     point-to-paste)))
+                               (and (rectangle-intersect-p
+                                     start-coordinates dimensions
+                                     point-to-paste-coordinates dimensions)
+                                    (not (< (car point-to-paste-coordinates)
+                                            (car start-coordinates)))))
+                           (and (<= (overlay-start
+                                     (car mouse-drag-and-drop-overlays))
+                                    point-to-paste)
+                                (<= point-to-paste
+                                    (overlay-end
+                                     (car mouse-drag-and-drop-overlays))))))))
+
+            ;; Show a tooltip.
+            (if mouse-drag-and-drop-region-show-tooltip
+                (tooltip-show text-tooltip)
+              (tooltip-hide))
+
+            ;; Show cursor and highlight the original region.
+            (when mouse-drag-and-drop-region-show-cursor
+              ;; Modify cursor even when point is out of frame.
+              (setq cursor-type (cond
+                                 ((not cursor-in-text-area)
+                                  nil)
+                                 ((or point-to-paste-read-only
+                                      drag-but-negligible)
+                                  'hollow)
+                                 (t
+                                  'bar)))
+              (when cursor-in-text-area
+                (dolist (overlay mouse-drag-and-drop-overlays)
+                  (overlay-put overlay
+                               'face 'mouse-drag-and-drop-region))
+                (deactivate-mark)     ; Maintain region in other window.
+                (mouse-set-point event))))))
 
       ;; Hide a tooltip.
       (when mouse-drag-and-drop-region-show-tooltip (tooltip-hide))
index 559679131bd0ad6f5351054529c5af80f0b24785..0529d223dbe6452daa285b04700b3dd8535d5e36 100644 (file)
@@ -446,7 +446,6 @@ FORMAT is 32 (not used).  MESSAGE is the data part of an XClientMessageEvent."
                (version (x-dnd-version-from-flags flags))
                (more-than-3 (x-dnd-more-than-3-from-flags flags))
                (dnd-source (aref data 0)))
-          (message "%s %s" version more-than-3)
           (if version  ;; If flags is bad, version will be nil.
               (x-dnd-save-state
                window nil nil
index c8aefec8d711c4d815b495c8217dce765b62398a..3e184571a06d2c8a61af11c638fc8be393951565 100644 (file)
@@ -6547,7 +6547,7 @@ DEFUN ("x-set-mouse-absolute-pixel-position", Fx_set_mouse_absolute_pixel_positi
 The coordinates X and Y are interpreted in pixels relative to a position
 \(0, 0) of the selected frame's display.  */)
   (Lisp_Object x, Lisp_Object y)
-  {
+{
   struct frame *f = SELECTED_FRAME ();
 
   if (FRAME_INITIAL_P (f) || !FRAME_X_P (f))
@@ -6582,6 +6582,85 @@ The coordinates X and Y are interpreted in pixels relative to a position
   return Qnil;
 }
 
+DEFUN ("x-begin-drag", Fx_begin_drag, Sx_begin_drag, 1, 3, 0,
+       doc: /* Begin dragging contents on FRAME, with targets TARGETS.
+TARGETS is a list of strings, which defines the X selection targets
+that will be available to the drop target.  Dragging starts when the
+mouse is pressed on FRAME, and the contents of the selection
+`XdndSelection' will be sent to the X window underneath the mouse
+pointer (the drop target) when the mouse button is released.  ACTION
+is a symbol which tells the target what to do, and can be one of the
+following:
+
+ - `XdndActionCopy', which means to copy the contents from the drag
+   source (FRAME) to the drop target.
+
+ - `XdndActionMove', which means to first take the contents of
+   `XdndSelection', and to delete whatever was saved into that
+   selection afterwards.
+
+There are also some other valid values of ACTION that depend on
+details of both the drop target's implementation details and that of
+Emacs.  For that reason, they are not mentioned here.  Consult
+"Drag-and-Drop Protocol for the X Window System" for more details:
+https://freedesktop.org/wiki/Specifications/XDND/.
+
+If ACTION is not specified or nil, `XdndActionCopy' is used instead.
+
+Block until the mouse buttons are released, then return the action
+chosen by the target, or `nil' if the drop was not accepted by the
+drop target.  */)
+  (Lisp_Object targets, Lisp_Object action, Lisp_Object frame)
+{
+  struct frame *f = decode_window_system_frame (frame);
+  int ntargets = 0;
+  char *target_names[2048];
+  Atom *target_atoms;
+  Lisp_Object lval;
+  Atom xaction;
+
+  CHECK_LIST (targets);
+
+  for (; CONSP (targets); targets = XCDR (targets))
+    {
+      CHECK_STRING (XCAR (targets));
+
+      if (ntargets < 2048)
+       {
+         target_names[ntargets] = SSDATA (XCAR (targets));
+         ntargets++;
+       }
+      else
+       error ("Too many targets");
+    }
+
+  if (NILP (action) || EQ (action, QXdndActionCopy))
+    xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionCopy;
+  else if (EQ (action, QXdndActionMove))
+    xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionMove;
+  else if (EQ (action, QXdndActionLink))
+    xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionLink;
+  else if (EQ (action, QXdndActionAsk))
+    xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionAsk;
+  else if (EQ (action, QXdndActionPrivate))
+    xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionPrivate;
+  else
+    signal_error ("Invalid drag-and-drop action", action);
+
+  target_atoms = xmalloc (ntargets * sizeof *target_atoms);
+
+  block_input ();
+  XInternAtoms (FRAME_X_DISPLAY (f), target_names,
+               ntargets, False, target_atoms);
+  unblock_input ();
+
+  x_set_dnd_targets (target_atoms, ntargets);
+  lval = x_dnd_begin_drag_and_drop (f, FRAME_DISPLAY_INFO (f)->last_user_time,
+                                   xaction);
+
+  return lval;
+}
+
 /************************************************************************
                              X Displays
  ************************************************************************/
@@ -9150,6 +9229,12 @@ syms_of_xfns (void)
   DEFSYM (Qreverse_landscape, "reverse-landscape");
 #endif
 
+  DEFSYM (QXdndActionCopy, "XdndActionCopy");
+  DEFSYM (QXdndActionMove, "XdndActionMove");
+  DEFSYM (QXdndActionLink, "XdndActionLink");
+  DEFSYM (QXdndActionAsk, "XdndActionAsk");
+  DEFSYM (QXdndActionPrivate, "XdndActionPrivate");
+
   Fput (Qundefined_color, Qerror_conditions,
        pure_list (Qundefined_color, Qerror));
   Fput (Qundefined_color, Qerror_message,
@@ -9423,6 +9508,7 @@ eliminated in future versions of Emacs.  */);
   defsubr (&Sx_show_tip);
   defsubr (&Sx_hide_tip);
   defsubr (&Sx_double_buffered_p);
+  defsubr (&Sx_begin_drag);
   tip_timer = Qnil;
   staticpro (&tip_timer);
   tip_frame = Qnil;
index a88c15aa95b3df5a99f466b92b065c5551607232..cdc70d3e247a36502a9334abcc4cfe652c1fd115 100644 (file)
@@ -2647,6 +2647,25 @@ x_send_client_event (Lisp_Object display, Lisp_Object dest, Lisp_Object from,
 
 \f
 
+/* Return the timestamp where ownership of SELECTION was asserted, or
+   nil if no local selection is present.  */
+
+Lisp_Object
+x_timestamp_for_selection (struct x_display_info *dpyinfo,
+                          Lisp_Object selection)
+{
+  Lisp_Object value, local_value;
+
+  local_value = LOCAL_SELECTION (selection, dpyinfo);
+
+  if (NILP (local_value))
+    return Qnil;
+
+  value = XCAR (XCDR (XCDR (local_value)));
+
+  return value;
+}
+
 static void syms_of_xselect_for_pdumper (void);
 
 void
index fc90e370430d3f13f43ab36f9b38087ea897732d..8a4344f2a4f260985986160cb52ee406d1b9b858 100644 (file)
@@ -770,6 +770,338 @@ static void x_update_opaque_region (struct frame *, XEvent *);
 static void x_scroll_bar_end_update (struct x_display_info *, struct scroll_bar *);
 #endif
 
+static bool x_dnd_in_progress;
+static Window x_dnd_last_seen_window;
+static int x_dnd_last_protocol_version;
+static Time x_dnd_selection_timestamp;
+
+static Window x_dnd_mouse_rect_target;
+static XRectangle x_dnd_mouse_rect;
+static Atom x_dnd_action;
+static Atom x_dnd_wanted_action;
+
+static Atom *x_dnd_targets = NULL;
+static int x_dnd_n_targets;
+static struct frame *x_dnd_frame;
+
+#define X_DND_SUPPORTED_VERSION 5
+
+static Window
+x_dnd_get_target_window (struct x_display_info *dpyinfo,
+                        int root_x, int root_y)
+{
+  Window child_return, child, dummy, proxy;
+  int dest_x_return, dest_y_return;
+  int rc;
+  int actual_format;
+  unsigned long actual_size, bytes_remaining;
+  unsigned char *tmp_data;
+  XWindowAttributes attrs;
+  Atom actual_type;
+
+  child_return = dpyinfo->root_window;
+  dest_x_return = root_x;
+  dest_y_return = root_y;
+
+  /* Not strictly necessary, but satisfies GCC.  */
+  child = dpyinfo->root_window;
+
+  while (child_return != None)
+    {
+      child = child_return;
+
+      x_catch_errors (dpyinfo->display);
+      rc = XTranslateCoordinates (dpyinfo->display,
+                                 child_return, child_return,
+                                 dest_x_return, dest_y_return,
+                                 &dest_x_return, &dest_y_return,
+                                 &child_return);
+
+      if (x_had_errors_p (dpyinfo->display) || !rc)
+       {
+         x_uncatch_errors_after_check ();
+         break;
+       }
+
+      if (child_return)
+       {
+         rc = XTranslateCoordinates (dpyinfo->display,
+                                     child, child_return,
+                                     dest_x_return, dest_y_return,
+                                     &dest_x_return, &dest_y_return,
+                                     &dummy);
+
+         if (x_had_errors_p (dpyinfo->display) || !rc)
+           {
+             x_uncatch_errors_after_check ();
+             return None;
+           }
+       }
+
+      x_uncatch_errors_after_check ();
+    }
+
+  if (child != None)
+    {
+      x_catch_errors (dpyinfo->display);
+      rc = XGetWindowProperty (dpyinfo->display, child,
+                              dpyinfo->Xatom_XdndProxy,
+                              0, 1, False, XA_WINDOW,
+                              &actual_type, &actual_format,
+                              &actual_size, &bytes_remaining,
+                              &tmp_data);
+
+      if (!x_had_errors_p (dpyinfo->display)
+         && rc == Success
+         && actual_type == XA_WINDOW
+         && actual_format == 32
+         && actual_size == 1)
+       {
+         proxy = *(Window *) tmp_data;
+         XFree (tmp_data);
+
+         /* Verify the proxy window exists.  */
+         XGetWindowAttributes (dpyinfo->display, proxy, &attrs);
+
+         if (!x_had_errors_p (dpyinfo->display))
+           child = proxy;
+       }
+
+      x_uncatch_errors_after_check ();
+    }
+
+  return child;
+}
+
+static int
+x_dnd_get_window_proto (struct x_display_info *dpyinfo, Window wdesc)
+{
+  Atom actual, value;
+  unsigned char *tmp_data;
+  int rc, format;
+  unsigned long n, left;
+  bool had_errors;
+
+  if (wdesc == None || wdesc == FRAME_X_WINDOW (x_dnd_frame))
+    return -1;
+
+  x_catch_errors (dpyinfo->display);
+  rc = XGetWindowProperty (dpyinfo->display, wdesc, dpyinfo->Xatom_XdndAware,
+                          0, 1, False, XA_ATOM, &actual, &format, &n, &left,
+                          &tmp_data);
+  had_errors = x_had_errors_p (dpyinfo->display);
+  x_uncatch_errors_after_check ();
+
+  if (had_errors || rc != Success || actual != XA_ATOM || format != 32 || n < 1)
+    return -1;
+
+  value = (int) *(Atom *) tmp_data;
+  XFree (tmp_data);
+
+  return (int) value;
+}
+
+static void
+x_dnd_send_enter (struct frame *f, Window target, int supported)
+{
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  int i;
+  XEvent msg;
+
+  msg.xclient.type = ClientMessage;
+  msg.xclient.message_type = dpyinfo->Xatom_XdndEnter;
+  msg.xclient.format = 32;
+  msg.xclient.window = target;
+  msg.xclient.data.l[0] = FRAME_X_WINDOW (f);
+  msg.xclient.data.l[1] = (((unsigned int) min (X_DND_SUPPORTED_VERSION,
+                                               supported) << 24)
+                          | (x_dnd_n_targets > 3 ? 1 : 0));
+  msg.xclient.data.l[2] = 0;
+  msg.xclient.data.l[3] = 0;
+  msg.xclient.data.l[4] = 0;
+
+  for (i = 0; i < min (3, x_dnd_n_targets); ++i)
+    msg.xclient.data.l[i + 2] = x_dnd_targets[i];
+
+  if (x_dnd_n_targets > 3)
+    XChangeProperty (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f),
+                    dpyinfo->Xatom_XdndTypeList, XA_ATOM, 32,
+                    PropModeReplace, (unsigned char *) x_dnd_targets,
+                    x_dnd_n_targets);
+
+  XSendEvent (FRAME_X_DISPLAY (f), target, False, 0, &msg);
+}
+
+static void
+x_dnd_send_position (struct frame *f, Window target, int supported,
+                    unsigned short root_x, unsigned short root_y,
+                    Time timestamp, Atom action)
+{
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  XEvent msg;
+
+  if (target == x_dnd_mouse_rect_target
+      && x_dnd_mouse_rect.width
+      && x_dnd_mouse_rect.height)
+    {
+      if (root_x >= x_dnd_mouse_rect.x
+         && root_x < (x_dnd_mouse_rect.x
+                      + x_dnd_mouse_rect.width)
+         && root_y >= x_dnd_mouse_rect.y
+         && root_y < (x_dnd_mouse_rect.y
+                      + x_dnd_mouse_rect.height))
+       return;
+    }
+
+  msg.xclient.type = ClientMessage;
+  msg.xclient.message_type = dpyinfo->Xatom_XdndPosition;
+  msg.xclient.format = 32;
+  msg.xclient.window = target;
+  msg.xclient.data.l[0] = FRAME_X_WINDOW (f);
+  msg.xclient.data.l[1] = 0;
+  msg.xclient.data.l[2] = (root_x << 16) | root_y;
+  msg.xclient.data.l[3] = 0;
+  msg.xclient.data.l[4] = 0;
+
+  if (supported >= 3)
+    msg.xclient.data.l[3] = timestamp;
+
+  if (supported >= 4)
+    msg.xclient.data.l[4] = action;
+
+  XSendEvent (FRAME_X_DISPLAY (f), target, False, 0, &msg);
+}
+
+static void
+x_dnd_send_leave (struct frame *f, Window target)
+{
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  XEvent msg;
+
+  msg.xclient.type = ClientMessage;
+  msg.xclient.message_type = dpyinfo->Xatom_XdndLeave;
+  msg.xclient.format = 32;
+  msg.xclient.window = target;
+  msg.xclient.data.l[0] = FRAME_X_WINDOW (f);
+  msg.xclient.data.l[1] = 0;
+  msg.xclient.data.l[2] = 0;
+  msg.xclient.data.l[3] = 0;
+  msg.xclient.data.l[4] = 0;
+
+  XSendEvent (FRAME_X_DISPLAY (f), target, False, 0, &msg);
+}
+
+static void
+x_dnd_send_drop (struct frame *f, Window target, Time timestamp,
+                int supported)
+{
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  XEvent msg;
+
+  msg.xclient.type = ClientMessage;
+  msg.xclient.message_type = dpyinfo->Xatom_XdndDrop;
+  msg.xclient.format = 32;
+  msg.xclient.window = target;
+  msg.xclient.data.l[0] = FRAME_X_WINDOW (f);
+  msg.xclient.data.l[1] = 0;
+  msg.xclient.data.l[2] = 0;
+  msg.xclient.data.l[3] = 0;
+  msg.xclient.data.l[4] = 0;
+
+  if (supported >= 1)
+    msg.xclient.data.l[2] = timestamp;
+
+  XSendEvent (FRAME_X_DISPLAY (f), target, False, 0, &msg);
+}
+
+void
+x_set_dnd_targets (Atom *targets, int ntargets)
+{
+  if (x_dnd_targets)
+    xfree (x_dnd_targets);
+
+  x_dnd_targets = targets;
+  x_dnd_n_targets = ntargets;
+}
+
+Lisp_Object
+x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction)
+{
+  XEvent next_event;
+  struct input_event hold_quit;
+  int finish;
+  char *atom_name;
+  Lisp_Object action, ltimestamp;
+
+  if (x_dnd_in_progress)
+    error ("A drag-and-drop session is already in progress");
+
+  ltimestamp = x_timestamp_for_selection (FRAME_DISPLAY_INFO (f),
+                                         QXdndSelection);
+
+  if (NILP (ltimestamp))
+    error ("No local value for XdndSelection");
+
+  if (BIGNUMP (ltimestamp))
+    x_dnd_selection_timestamp = bignum_to_intmax (ltimestamp);
+  else
+    x_dnd_selection_timestamp = XFIXNUM (ltimestamp);
+
+  x_dnd_in_progress = true;
+  x_dnd_frame = f;
+  x_dnd_last_seen_window = FRAME_X_WINDOW (f);
+  x_dnd_last_protocol_version = -1;
+  x_dnd_mouse_rect_target = None;
+  x_dnd_action = None;
+  x_dnd_wanted_action = xaction;
+
+  while (x_dnd_in_progress)
+    {
+      hold_quit.kind = NO_EVENT;
+
+      block_input ();
+      XNextEvent (FRAME_X_DISPLAY (f), &next_event);
+
+      handle_one_xevent (FRAME_DISPLAY_INFO (f),
+                        &next_event, &finish, &hold_quit);
+      unblock_input ();
+
+      if (hold_quit.kind != NO_EVENT)
+       {
+         if (x_dnd_in_progress)
+           {
+             block_input ();
+             if (x_dnd_last_seen_window != None
+                 && x_dnd_last_protocol_version != -1)
+               x_dnd_send_leave (f, x_dnd_last_seen_window);
+             unblock_input ();
+
+             x_dnd_in_progress = false;
+             x_dnd_frame = NULL;
+           }
+
+         FRAME_DISPLAY_INFO (f)->grabbed = 0;
+         quit ();
+       }
+    }
+
+  FRAME_DISPLAY_INFO (f)->grabbed = 0;
+
+  if (x_dnd_wanted_action != None)
+    {
+      block_input ();
+      atom_name = XGetAtomName (FRAME_X_DISPLAY (f),
+                               x_dnd_wanted_action);
+      action = intern (atom_name);
+      XFree (atom_name);
+      unblock_input ();
+
+      return action;
+    }
+
+  return Qnil;
+}
+
 /* Flush display of frame F.  */
 
 static void
@@ -10084,6 +10416,42 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     {
     case ClientMessage:
       {
+       if (x_dnd_in_progress
+           && FRAME_DISPLAY_INFO (x_dnd_frame) == dpyinfo
+           && event->xclient.message_type == dpyinfo->Xatom_XdndStatus)
+         {
+           Window target;
+
+           target = event->xclient.data.l[0];
+
+           if (x_dnd_last_protocol_version != -1
+               && target == x_dnd_last_seen_window
+               && event->xclient.data.l[1] & 2)
+             {
+               x_dnd_mouse_rect_target = target;
+               x_dnd_mouse_rect.x = (event->xclient.data.l[2] & 0xffff0000) >> 16;
+               x_dnd_mouse_rect.y = (event->xclient.data.l[2] & 0xffff);
+               x_dnd_mouse_rect.width = (event->xclient.data.l[3] & 0xffff0000) >> 16;
+               x_dnd_mouse_rect.height = (event->xclient.data.l[3] & 0xffff);
+             }
+           else
+             x_dnd_mouse_rect_target = None;
+
+           if (x_dnd_last_protocol_version != -1
+               && target == x_dnd_last_seen_window)
+             {
+               if (event->xclient.data.l[1] & 1)
+                 {
+                   if (x_dnd_last_protocol_version >= 2)
+                     x_dnd_wanted_action = event->xclient.data.l[4];
+                   else
+                     x_dnd_wanted_action = dpyinfo->Xatom_XdndActionCopy;
+                 }
+               else
+                 x_dnd_wanted_action = None;
+             }
+         }
+
         if (event->xclient.message_type == dpyinfo->Xatom_wm_protocols
             && event->xclient.format == 32)
           {
@@ -11222,6 +11590,43 @@ handle_one_xevent (struct x_display_info *dpyinfo,
             clear_mouse_face (hlinfo);
           }
 
+       if (x_dnd_in_progress
+           && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame))
+         {
+           Window target;
+
+           target = x_dnd_get_target_window (dpyinfo,
+                                             event->xmotion.x_root,
+                                             event->xmotion.y_root);
+
+           if (target != x_dnd_last_seen_window)
+             {
+               if (x_dnd_last_seen_window != None
+                   && x_dnd_last_protocol_version != -1
+                   && x_dnd_last_seen_window != FRAME_X_WINDOW (x_dnd_frame))
+                 x_dnd_send_leave (x_dnd_frame, x_dnd_last_seen_window);
+
+               x_dnd_wanted_action = None;
+               x_dnd_last_seen_window = target;
+               x_dnd_last_protocol_version
+                 = x_dnd_get_window_proto (dpyinfo, target);
+
+               if (target != None && x_dnd_last_protocol_version != -1)
+                 x_dnd_send_enter (x_dnd_frame, target,
+                                   x_dnd_last_protocol_version);
+             }
+
+           if (x_dnd_last_protocol_version != -1 && target != None)
+             x_dnd_send_position (x_dnd_frame, target,
+                                  x_dnd_last_protocol_version,
+                                  event->xmotion.x_root,
+                                  event->xmotion.y_root,
+                                  x_dnd_selection_timestamp,
+                                  dpyinfo->Xatom_XdndActionCopy);
+
+           goto OTHER;
+         }
+
        f = mouse_or_wdesc_frame (dpyinfo, event->xmotion.window);
 
 #ifdef USE_GTK
@@ -11573,6 +11978,38 @@ handle_one_xevent (struct x_display_info *dpyinfo,
         Lisp_Object tab_bar_arg = Qnil;
         bool tab_bar_p = false;
         bool tool_bar_p = false;
+       bool dnd_grab = false;
+
+       for (int i = 1; i < 8; ++i)
+         {
+           if (i != event->xbutton.button
+               && event->xbutton.state & (Button1Mask << (i - 1)))
+             dnd_grab = true;
+         }
+
+       if (x_dnd_in_progress
+           && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)
+           && !dnd_grab
+           && event->xbutton.type == ButtonRelease)
+         {
+           x_dnd_in_progress = false;
+
+           if (x_dnd_last_seen_window != None
+               && x_dnd_last_protocol_version != -1)
+             x_dnd_send_drop (x_dnd_frame, x_dnd_last_seen_window,
+                              x_dnd_selection_timestamp,
+                              x_dnd_last_protocol_version);
+
+           x_dnd_last_protocol_version = -1;
+           x_dnd_last_seen_window = None;
+           x_dnd_frame = NULL;
+           x_set_dnd_targets (NULL, 0);
+
+           goto OTHER;
+         }
+
+       if (x_dnd_in_progress)
+         goto OTHER;
 
        memset (&compose_status, 0, sizeof (compose_status));
        dpyinfo->last_mouse_glyph_frame = NULL;
@@ -12372,6 +12809,41 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                  clear_mouse_face (hlinfo);
                }
 
+             if (x_dnd_in_progress
+                 && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame))
+               {
+                 Window target;
+
+                 target = x_dnd_get_target_window (dpyinfo,
+                                                   xev->root_x,
+                                                   xev->root_y);
+
+                 if (target != x_dnd_last_seen_window)
+                   {
+                     if (x_dnd_last_seen_window != None
+                         && x_dnd_last_protocol_version != -1
+                         && x_dnd_last_seen_window != FRAME_X_WINDOW (x_dnd_frame))
+                       x_dnd_send_leave (x_dnd_frame, x_dnd_last_seen_window);
+
+                     x_dnd_last_seen_window = target;
+                     x_dnd_last_protocol_version
+                       = x_dnd_get_window_proto (dpyinfo, target);
+
+                     if (target != None && x_dnd_last_protocol_version != -1)
+                       x_dnd_send_enter (x_dnd_frame, target,
+                                         x_dnd_last_protocol_version);
+                   }
+
+                 if (x_dnd_last_protocol_version != -1 && target != None)
+                   x_dnd_send_position (x_dnd_frame, target,
+                                        x_dnd_last_protocol_version,
+                                        xev->root_x, xev->root_y,
+                                        x_dnd_selection_timestamp,
+                                        dpyinfo->Xatom_XdndActionCopy);
+
+                 goto XI_OTHER;
+               }
+
              f = mouse_or_wdesc_frame (dpyinfo, xev->event);
 
 #ifdef USE_GTK
@@ -12467,6 +12939,37 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #endif
              /* A fake XButtonEvent for x_construct_mouse_click. */
              XButtonEvent bv;
+             bool dnd_grab = false;
+
+             for (int i = 0; i < xev->buttons.mask_len * 8; ++i)
+               {
+                 if (i != xev->detail && XIMaskIsSet (xev->buttons.mask, i))
+                   dnd_grab = true;
+               }
+
+             if (x_dnd_in_progress
+                 && dpyinfo == FRAME_DISPLAY_INFO (x_dnd_frame)
+                 && !dnd_grab
+                 && xev->evtype == XI_ButtonRelease)
+               {
+                 x_dnd_in_progress = false;
+
+                 if (x_dnd_last_seen_window != None
+                     && x_dnd_last_protocol_version != -1)
+                   x_dnd_send_drop (x_dnd_frame, x_dnd_last_seen_window,
+                                    x_dnd_selection_timestamp,
+                                    x_dnd_last_protocol_version);
+
+                 x_dnd_last_protocol_version = -1;
+                 x_dnd_last_seen_window = None;
+                 x_dnd_frame = NULL;
+                 x_set_dnd_targets (NULL, 0);
+
+                 goto XI_OTHER;
+               }
+
+             if (x_dnd_in_progress)
+               goto XI_OTHER;
 
 #ifdef USE_MOTIF
 #ifdef USE_TOOLKIT_SCROLL_BARS
@@ -16554,6 +17057,16 @@ x_free_frame_resources (struct frame *f)
   struct scroll_bar *b;
 #endif
 
+  if (x_dnd_in_progress && f == x_dnd_frame)
+    {
+      if (x_dnd_last_seen_window != None
+         && x_dnd_last_protocol_version != -1)
+       x_dnd_send_leave (f, x_dnd_last_seen_window);
+
+      x_dnd_in_progress = false;
+      x_dnd_frame = NULL;
+    }
+
   block_input ();
 
   /* If a display connection is dead, don't try sending more
@@ -18014,6 +18527,24 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name)
       ATOM_REFS_INIT ("ShiftLock", Xatom_ShiftLock)
       ATOM_REFS_INIT ("Alt", Xatom_Alt)
 #endif
+      /* DND source.  */
+      ATOM_REFS_INIT ("XdndAware", Xatom_XdndAware)
+      ATOM_REFS_INIT ("XdndSelection", Xatom_XdndSelection)
+      ATOM_REFS_INIT ("XdndTypeList", Xatom_XdndTypeList)
+      ATOM_REFS_INIT ("XdndActionCopy", Xatom_XdndActionCopy)
+      ATOM_REFS_INIT ("XdndActionMove", Xatom_XdndActionMove)
+      ATOM_REFS_INIT ("XdndActionLink", Xatom_XdndActionLink)
+      ATOM_REFS_INIT ("XdndActionAsk", Xatom_XdndActionAsk)
+      ATOM_REFS_INIT ("XdndActionPrivate", Xatom_XdndActionPrivate)
+      ATOM_REFS_INIT ("XdndActionList", Xatom_XdndActionList)
+      ATOM_REFS_INIT ("XdndActionDescription", Xatom_XdndActionDescription)
+      ATOM_REFS_INIT ("XdndProxy", Xatom_XdndProxy)
+      ATOM_REFS_INIT ("XdndEnter", Xatom_XdndEnter)
+      ATOM_REFS_INIT ("XdndPosition", Xatom_XdndPosition)
+      ATOM_REFS_INIT ("XdndStatus", Xatom_XdndStatus)
+      ATOM_REFS_INIT ("XdndLeave", Xatom_XdndLeave)
+      ATOM_REFS_INIT ("XdndDrop", Xatom_XdndDrop)
+      ATOM_REFS_INIT ("XdndFinished", Xatom_XdndFinished)
     };
 
     int i;
@@ -18689,6 +19220,7 @@ With MS Windows, Haiku windowing or Nextstep, the value is t.  */);
   Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
   DEFSYM (Qsuper, "super");
   Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
+  DEFSYM (QXdndSelection, "XdndSelection");
 
   DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym,
     doc: /* Which keys Emacs uses for the ctrl modifier.
index 3638f322e51b1658221b5f5e5c97536fa8f0f2b1..225aaf4cad52366d4ea2b89f8b7b7170ac15fa55 100644 (file)
@@ -561,6 +561,14 @@ struct x_display_info
   /* SM */
   Atom Xatom_SM_CLIENT_ID;
 
+  /* DND source.  */
+  Atom Xatom_XdndAware, Xatom_XdndSelection, Xatom_XdndTypeList,
+    Xatom_XdndActionCopy, Xatom_XdndActionMove, Xatom_XdndActionLink,
+    Xatom_XdndActionAsk, Xatom_XdndActionPrivate, Xatom_XdndActionList,
+    Xatom_XdndActionDescription, Xatom_XdndProxy, Xatom_XdndEnter,
+    Xatom_XdndPosition, Xatom_XdndStatus, Xatom_XdndLeave, Xatom_XdndDrop,
+    Xatom_XdndFinished;
+
 #ifdef HAVE_XKB
   /* Virtual modifiers */
   Atom Xatom_Meta, Xatom_Super, Xatom_Hyper, Xatom_ShiftLock, Xatom_Alt;
@@ -1359,6 +1367,9 @@ extern void x_scroll_bar_configure (GdkEvent *);
 
 extern void x_display_set_last_user_time (struct x_display_info *, Time);
 
+extern Lisp_Object x_dnd_begin_drag_and_drop (struct frame *, Time, Atom);
+extern void x_set_dnd_targets (Atom *, int);
+
 INLINE int
 x_display_pixel_height (struct x_display_info *dpyinfo)
 {
@@ -1453,6 +1464,9 @@ extern Lisp_Object x_property_data_to_lisp (struct frame *,
 extern void x_clipboard_manager_save_frame (Lisp_Object);
 extern void x_clipboard_manager_save_all (void);
 
+extern Lisp_Object x_timestamp_for_selection (struct x_display_info *,
+                                             Lisp_Object);
+
 #ifdef USE_GTK
 extern bool xg_set_icon (struct frame *, Lisp_Object);
 extern bool xg_set_icon_from_xpm_data (struct frame *, const char **);