dropping multiple files, then the first file will be used instead.
@end defun
+@defun dnd-direct-save file name &optional frame allow-same-frame
+This function is similar to @code{dnd-begin-file-drag} (with the
+default action of copy), but instead of specifying the action you
+specify the name of the copy created by the target program in
+@code{name}.
+@end defun
+
@cindex initiating drag-and-drop, low-level
The high-level interfaces described above are implemented on top of
a lower-level primitive. If you need to drag content other than files
mouse pointer.
+++
-** New functions 'x-begin-drag', 'dnd-begin-text-drag' and 'dnd-begin-file-drag'.
-These functions allow dragging contents (such as files and text) from
-Emacs to other programs.
+** New functions for dragging items from Emacs to other programs.
+The new functions 'x-begin-drag', 'dnd-begin-file-drag',
+'dnd-begin-drag-files', and 'dnd-direct-save' allow dragging contents
+(such as files and text) from Emacs to other programs.
---
** New function 'ietf-drums-parse-date-string'.
:version "29.1"
:group 'dnd)
+(defcustom dnd-direct-save-remote-files 'x
+ "Whether or not to perform a direct save of remote files.
+This is compatible with less programs, but means dropped files
+will be saved with their actual file names, and not a temporary
+file name provided by TRAMP.
+
+This defaults to `x', which means only to drop that way on X
+Windows."
+ :type '(choice (const :tag "Only use direct save on X Windows" x)
+ (const :tag "Use direct save everywhere" t)
+ (const :tag "Don't use direct save")))
+
;; Functions
(defun dnd-handle-movement (posn)
(dnd-remove-last-dragged-remote-file)
(unless action
(setq action 'copy))
- (let ((original-file file))
- (when (file-remote-p file)
- (if (eq action 'link)
- (error "Cannot create symbolic link to remote file")
- (setq file (file-local-copy file))
- (setq dnd-last-dragged-remote-file file)
- (add-hook 'kill-emacs-hook
- #'dnd-remove-last-dragged-remote-file)))
- (gui-set-selection 'XdndSelection
- (propertize (expand-file-name file) 'text/uri-list
- (concat "file://"
- (expand-file-name file))))
- (let ((return-value
- (x-begin-drag '(;; Xdnd types used by GTK, Qt, and most other
- ;; modern programs that expect filenames to
- ;; be supplied as URIs.
- "text/uri-list" "text/x-xdnd-username"
- ;; Traditional X selection targets used by
- ;; programs supporting the Motif
- ;; drag-and-drop protocols. Also used by NS
- ;; and Haiku.
- "FILE_NAME" "FILE" "HOST_NAME"
- ;; ToolTalk filename. Mostly used by CDE
- ;; programs.
- "_DT_NETFILE")
- (cl-ecase action
- ('copy 'XdndActionCopy)
- ('move 'XdndActionMove)
- ('link 'XdndActionLink))
- frame nil allow-same-frame)))
- (cond
- ((eq return-value 'XdndActionCopy) 'copy)
- ((eq return-value 'XdndActionMove)
- (prog1 'move
- ;; If original-file is a remote file, delete it from the
- ;; remote as well.
- (when (file-remote-p original-file)
- (ignore-errors
- (delete-file original-file)))))
- ((eq return-value 'XdndActionLink) 'link)
- ((not return-value) nil)
- (t 'private)))))
+ (if (and (or (and (eq dnd-direct-save-remote-files 'x)
+ (eq (framep (or frame
+ (selected-frame)))
+ 'x))
+ (and dnd-direct-save-remote-files
+ (not (eq dnd-direct-save-remote-files 'x))))
+ (eq action 'copy)
+ (file-remote-p file))
+ (dnd-direct-save file (file-name-nondirectory file)
+ frame allow-same-frame)
+ (let ((original-file file))
+ (when (file-remote-p file)
+ (if (eq action 'link)
+ (error "Cannot create symbolic link to remote file")
+ (setq file (file-local-copy file))
+ (setq dnd-last-dragged-remote-file file)
+ (add-hook 'kill-emacs-hook
+ #'dnd-remove-last-dragged-remote-file)))
+ (gui-set-selection 'XdndSelection
+ (propertize (expand-file-name file) 'text/uri-list
+ (concat "file://"
+ (expand-file-name file))))
+ (let ((return-value
+ (x-begin-drag '(;; Xdnd types used by GTK, Qt, and most other
+ ;; modern programs that expect filenames to
+ ;; be supplied as URIs.
+ "text/uri-list" "text/x-xdnd-username"
+ ;; Traditional X selection targets used by
+ ;; programs supporting the Motif
+ ;; drag-and-drop protocols. Also used by NS
+ ;; and Haiku.
+ "FILE_NAME" "FILE" "HOST_NAME"
+ ;; ToolTalk filename. Mostly used by CDE
+ ;; programs.
+ "_DT_NETFILE")
+ (cl-ecase action
+ ('copy 'XdndActionCopy)
+ ('move 'XdndActionMove)
+ ('link 'XdndActionLink))
+ frame nil allow-same-frame)))
+ (cond
+ ((eq return-value 'XdndActionCopy) 'copy)
+ ((eq return-value 'XdndActionMove)
+ (prog1 'move
+ ;; If original-file is a remote file, delete it from the
+ ;; remote as well.
+ (when (file-remote-p original-file)
+ (ignore-errors
+ (delete-file original-file)))))
+ ((eq return-value 'XdndActionLink) 'link)
+ ((not return-value) nil)
+ (t 'private))))))
(defun dnd-begin-drag-files (files &optional frame action allow-same-frame)
"Begin dragging FILES from FRAME.
(error (message "Failed to download file: %s" error)
(setcar tem nil))))
(setq tem (cdr tem)))
+ (when dnd-last-dragged-remote-file
+ (add-hook 'kill-emacs-hook
+ #'dnd-remove-last-dragged-remote-file))
;; Remove any files that failed to download from a remote host.
(setq new-files (delq nil new-files))
(unless new-files
((not return-value) nil)
(t 'private)))))
+(declare-function x-dnd-do-direct-save "x-dnd.el")
+
+(defun dnd-direct-save (file name &optional frame allow-same-frame)
+ "Drag FILE from FRAME, but do not treat it as an actual file.
+Instead, ask the target window to insert the file with NAME.
+File managers will create a file in the displayed directory with
+the contents of FILE and the name NAME, while text editors will
+insert the contents of FILE in a new document named
+NAME.
+
+ALLOW-SAME-FRAME means the same as in `dnd-begin-file-drag'.
+Return `copy' if the drop was successful, else nil."
+ (setq file (expand-file-name file))
+ (cond ((eq window-system 'x)
+ (when (x-dnd-do-direct-save file name frame
+ allow-same-frame)
+ 'copy))
+ ;; Avoid infinite recursion.
+ (t (let ((dnd-direct-save-remote-files nil))
+ (dnd-begin-file-drag file frame nil allow-same-frame)))))
+
(provide 'dnd)
;;; dnd.el ends here
(defcustom x-dnd-known-types
(mapcar 'purecopy
- '("text/uri-list"
- "text/x-moz-url"
- "_NETSCAPE_URL"
- "FILE_NAME"
- "UTF8_STRING"
- "text/plain;charset=UTF-8"
- "text/plain;charset=utf-8"
- "text/unicode"
- "text/plain"
- "COMPOUND_TEXT"
- "STRING"
- "TEXT"
- "DndTypeFile"
- "DndTypeText"))
+ '("text/uri-list"
+ "text/x-moz-url"
+ "_NETSCAPE_URL"
+ "FILE_NAME"
+ "UTF8_STRING"
+ "text/plain;charset=UTF-8"
+ "text/plain;charset=utf-8"
+ "text/unicode"
+ "text/plain"
+ "COMPOUND_TEXT"
+ "STRING"
+ "TEXT"
+ "DndTypeFile"
+ "DndTypeText"))
"The types accepted by default for dropped data.
The types are chosen in the order they appear in the list."
:version "22.1"
(progn
(let ((action (cdr (assoc (symbol-name (cadr client-message))
x-dnd-xdnd-to-action)))
- (targets (cddr client-message)))
+ (targets (cddr client-message))
+ (local-value (nth 2 client-message)))
(x-dnd-save-state window nil nil
(apply #'vector targets))
(x-dnd-maybe-call-test-function window action)
(x-dnd-drop-data event (if (framep window) window
(window-frame window))
window
- (x-get-selection-internal
- 'XdndSelection
+ (x-get-local-selection
+ local-value
(intern (x-dnd-current-type window)))
(x-dnd-current-type window))
(x-dnd-forget-drop window))))
(setq x-dnd-native-test-function #'x-dnd-handle-native-drop)
+;;; XDS protocol support.
+
+(declare-function x-begin-drag "xfns.c")
+
+(defvar x-dnd-xds-current-file nil
+ "The file name for which a direct save is currently being performed.")
+
+(defvar x-dnd-xds-source-frame nil
+ "The frame from which a direct save is currently being performed.")
+
+(defun x-dnd-handle-direct-save (_selection _type _value)
+ "Handle a selection request for `XdndDirectSave'."
+ (let* ((uri (x-window-property "XdndDirectSave0"
+ x-dnd-xds-source-frame
+ "AnyPropertyType" nil t))
+ (local-name (dnd-get-local-file-name uri nil)))
+ (if (not local-name)
+ '(STRING . "F")
+ (condition-case nil
+ (progn
+ (rename-file x-dnd-xds-current-file
+ local-name t)
+ (when (equal x-dnd-xds-current-file
+ dnd-last-dragged-remote-file)
+ (dnd-remove-last-dragged-remote-file)))
+ (:success '(STRING . "S"))
+ (error '(STRING . "F"))))))
+
+(defun x-dnd-do-direct-save (file name frame allow-same-frame)
+ "Perform a direct save operation on FILE, from FRAME.
+FILE is the file containing the contents to drop.
+NAME is the name that should be given to the file after dropping.
+FRAME is the frame from which the drop will originate.
+ALLOW-SAME-FRAME means whether or not dropping will be allowed
+on FRAME.
+
+Return the action taken by the drop target, or nil."
+ (dnd-remove-last-dragged-remote-file)
+ (let ((file-name file)
+ (original-file-name file)
+ (selection-converter-alist
+ (cons (cons 'XdndDirectSave0
+ #'x-dnd-handle-direct-save)
+ selection-converter-alist))
+ (x-dnd-xds-current-file nil)
+ (x-dnd-xds-source-frame frame)
+ encoded-name)
+ (unwind-protect
+ (progn
+ (when (file-remote-p file)
+ (setq file-name (file-local-copy file))
+ (setq dnd-last-dragged-remote-file file-name)
+ (add-hook 'kill-emacs-hook
+ #'dnd-remove-last-dragged-remote-file))
+ (setq encoded-name
+ (encode-coding-string name
+ (or file-name-coding-system
+ default-file-name-coding-system)))
+ (setq x-dnd-xds-current-file file-name)
+ (x-change-window-property "XdndDirectSave0" encoded-name
+ frame "text/plain" 8 nil)
+ (gui-set-selection 'XdndSelection (concat "file://" file-name))
+ ;; FIXME: this does not work with GTK file managers, since
+ ;; they always reach for `text/uri-list' first, contrary to
+ ;; the spec.
+ (x-begin-drag '("XdndDirectSave0" "text/uri-list")
+ 'XdndActionDirectSave
+ frame nil allow-same-frame))
+ ;; TODO: check for failure and implement selection-based file
+ ;; transfer.
+ (x-delete-window-property "XdndDirectSave0" frame)
+ ;; Delete any remote copy that was made.
+ (when (not (equal file-name original-file-name))
+ (delete-file file-name)))))
+
(provide 'x-dnd)
;;; x-dnd.el ends here
xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionPrivate;
else if (EQ (action, QXdndActionAsk))
xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionAsk;
+ else if (SYMBOLP (action))
+ /* This is to accommodate non-standard DND protocols such as XDS
+ that are explictly implemented by Emacs, and is not documented
+ for that reason. */
+ xaction = symbol_to_x_atom (FRAME_DISPLAY_INFO (f), action);
else if (CONSP (action))
{
xaction = FRAME_DISPLAY_INFO (f)->Xatom_XdndActionAsk;
/* This converts a Lisp symbol to a server Atom, avoiding a server
roundtrip whenever possible. */
-static Atom
+Atom
symbol_to_x_atom (struct x_display_info *dpyinfo, Lisp_Object sym)
{
Atom val;
XFree (atom_names[i - 1]);
}
+ lval = Fcons (assq_no_quit (QXdndSelection,
+ FRAME_TERMINAL (f)->Vselection_alist),
+ lval);
lval = Fcons (intern (name), lval);
lval = Fcons (QXdndSelection, lval);
ie.arg = lval;
{
/* There is no point in making this extra sync if all requests
are known to have been fully processed. */
- if ((LastKnownRequestProcessed (x_error_message->dpy)
- != NextRequest (x_error_message->dpy) - 1))
+ if ((LastKnownRequestProcessed (dpyinfo->display)
+ != NextRequest (dpyinfo->display) - 1))
XSync (dpyinfo->display, False);
x_clean_failable_requests (dpyinfo);
extern void x_handle_selection_event (struct selection_input_event *);
extern void x_clear_frame_selections (struct frame *);
extern Lisp_Object x_atom_to_symbol (struct x_display_info *, Atom);
+extern Atom symbol_to_x_atom (struct x_display_info *, Lisp_Object);
extern bool x_handle_dnd_message (struct frame *,
const XClientMessageEvent *,