From 08cda286c3f5ccf4a898516204884bd7daaae971 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 8 Apr 2023 18:36:23 +0300 Subject: [PATCH] Improve the documentation of the XDS support * doc/lispref/frames.texi (Drag and Drop): Rephrase and rearrange the documentation of XDS support. Add indexing. Document 'x-dnd-save-direct' and 'x-dnd-save-direct-immediately'. Original patch from Po Lu . * lisp/x-dnd.el (x-dnd-types-alist, x-dnd-test-function) (x-dnd-default-test-function, x-dnd-direct-save-function): Doc fixes. (x-dnd-save-direct, x-dnd-save-direct-immediately): Rename the second argument to FILENAME. Doc fix. --- doc/lispref/frames.texi | 88 +++++++++++++++++++++-------- lisp/x-dnd.el | 120 +++++++++++++++++++++++----------------- 2 files changed, 133 insertions(+), 75 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 7cae94d2627..c78ab1c34ba 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -4112,7 +4112,7 @@ has the same meaning as the @var{action} argument to Emacs implements receiving text and URLs individually for each window system, and does not by default support receiving other kinds of data as drops. To support receiving other kinds of data, use the -X-specific interface described below: +X-specific interface described below. @vindex x-dnd-test-function @vindex x-dnd-known-types @@ -4141,29 +4141,71 @@ depending on the specific drag-and-drop protocol being used. For example, the data type used for plain text may be either @code{"STRING"} or @code{"text/plain"}. +@cindex XDS +@cindex direct save protocol @vindex x-dnd-direct-save-function -@c FIXME: This description is overly-complicated and confusing. In -@c particular, the two calls to the function basically sound -@c identical, so it is unclear how should the function distinguish -@c between the first and the second one. The description of who asks -@c whom to do what is also very hard to understand. Needs rewording, -@c and needs shorter sentences. Perhaps examples could help. - However, @code{x-dnd-types-alist} does not handle a special kind of -drop sent by a program that wants Emacs to tell it where to save a -file in a specific location determined by the user. These drops are -instead handled by a function that is the value of the variable -@code{x-dnd-direct-save-function}. This function should accept two arguments. -If the first argument is non-@code{nil}, then the second argument is a -file name to save (with leading directories) that the other -program recommends, and the -function should return the full file name under which it should be -saved. After the function completes, Emacs will ask the other program -to save the file under the name that was returned, and if the file was -successfully saved, call the function again with the first argument -set to a non-@code{nil} value and the second argument set to the file -name that was returned. The function should then perform whatever -action is appropriate (i.e., opening the file or refreshing a -directory listing.) + When Emacs runs on X window system, it supports the X Direct Save +(@acronym{XDS}) protocol, which allows users to save a file by +dragging and dropping it onto an Emacs window, such as a Dired window. +To comply with the unique requirements of @acronym{XDS}, these +drag-and-drop requests are processed specially: instead of being +handled according to @code{x-dnd-types-alist}, they are handled by the +@dfn{direct-save function} that is the value of the variable +@code{x-dnd-direct-save-function}. The value should be a function of +two arguments, @var{need-name} and @var{filename}. The @acronym{XDS} +protocol uses a two-step procedure for dragging files: + +@enumerate 1 +@item +The application from which the file is dragged asks Emacs to provide +the full file name under which to save the file. For this purpose, +the direct-save function is called with its first argument +@var{need-name} non-@code{nil}, and the second argument @var{filename} +set to the basename of the file to be saved. It should return the +fully-expanded absolute file name under which to save the file. For +example, if a file is dragged to a Dired window, the natural directory +for the file is the directory of the file shown at location of the +drop. If saving the file is not possible for some reason, the +function should return @code{nil}, which will cancel the drag-and-drop +operation. + +@item +The application from which the file is dragged saves the file under +the name returned by the first call to the direct-save function. If +it succeeds in saving the file, the direct-save function is called +again, this time with the first argument @var{need-name} set to +@code{nil} and the second argument @var{filename} set to the full +absolute name of the saved file. The function is then expected to do +whatever is needed given the fact that file was saved. For example, +Dired should update the directory on display by showing the new file +there. +@end enumerate + +The default value of @code{x-dnd-direct-save-function} is +@code{x-dnd-save-direct}. + +@defun x-dnd-save-direct need-name filename +When called with the @var{need-name} argument non-@code{nil}, this +function prompts the user for the absolute file name under which it +should be saved. If the specified file already exists, it +additionally asks the user whether to overwrite it, and returns the +absolute file name only if the user confirms the overwriting. + +When called with the @var{need-name} argument @code{nil}, it reverts +the Dired listing if the current buffer is in Dired mode or one of its +descendants, and otherwise visits the file by calling @code{find-file} +(@pxref{Visiting Functions}). +@end defun + +@defun x-dnd-save-direct-immediately need-name filename +This function works like @code{x-dnd-save-direct}, but when called +with its @var{need-name} argument non-@code{nil}, it doesn't prompt +the user for the full name of the file to be saved; instead, it +returns its argument @var{filename} expanded against the current +buffer's default directory (@pxref{File Name Expansion}). (It still +asks for confirmation if a file by that name already exists in the +default directory.) +@end defun @cindex initiating drag-and-drop On capable window systems, Emacs also supports dragging contents diff --git a/lisp/x-dnd.el b/lisp/x-dnd.el index b213b155249..9286a1858cf 100644 --- a/lisp/x-dnd.el +++ b/lisp/x-dnd.el @@ -34,20 +34,20 @@ ;;; Customizable variables (defcustom x-dnd-test-function #'x-dnd-default-test-function - "The function drag and drop uses to determine if to accept or reject a drop. -The function takes three arguments, WINDOW, ACTION and TYPES. -WINDOW is where the mouse is when the function is called. WINDOW -may be a frame if the mouse isn't over a real window (i.e. menu -bar, tool bar or scroll bar). ACTION is the suggested action -from the drag and drop source, one of the symbols move, copy, -link or ask. TYPES is a vector of available types for the drop. - -Each element of TYPE should either be a string (containing the + "Function to be used by drag-and-drop to determine whether to accept a drop. +The function takes three arguments: WINDOW, ACTION, and TYPES. +WINDOW is where the window under the mouse is when the function is called. +WINDOW may be a frame if the mouse isn't over a real window (e.g., menu +bar, tool bar, scroll bar, etc.). +ACTION is the suggested action from the drag and drop source, one of the +symbols `move', `copy', `link' or `ask'. +TYPES is a vector of available types for the drop. +Each element of TYPES should either be a string (containing the name of the type's X atom), or a symbol, whose name will be used. The function shall return nil to reject the drop or a cons with -two values, the wanted action as car and the wanted type as cdr. -The wanted action can be copy, move, link, ask or private. +two values, the wanted action as `car' and the wanted type as `cdr'. +The wanted action can be `copy', `move', `link', `ask' or `private'. The default value for this variable is `x-dnd-default-test-function'." :version "22.1" @@ -70,14 +70,18 @@ The default value for this variable is `x-dnd-default-test-function'." (,(purecopy "DndTypeFile") . x-dnd-handle-offix-file) (,(purecopy "DndTypeFiles") . x-dnd-handle-offix-files) (,(purecopy "DndTypeText") . dnd-insert-text)) - "Which function to call to handle a drop of that type. -If the type for the drop is not present, or the function is nil, -the drop is rejected. The function takes three arguments, WINDOW, ACTION -and DATA. WINDOW is where the drop occurred, ACTION is the action for -this drop (copy, move, link, private or ask) as determined by a previous -call to `x-dnd-test-function'. DATA is the drop data. -The function shall return the action used (copy, move, link or private) -if drop is successful, nil if not." + "Functions to call to handle drag-and-drop of known types. +If the type of the drop is not present in the alist, or the +function corresponding to the type is nil, the drop of that +type will be rejected. + +Each function takes three arguments: WINDOW, ACTION, and DATA. +WINDOW is the window where the drop occurred. +ACTION is the action for this drop (`copy', `move', `link', `private' +or `ask'), as determined by a previous call to `x-dnd-test-function'. +DATA is the drop data. +The function shall return the action it used (one of the above, +excluding `ask') if drop is successful, nil if not." :version "22.1" :type 'alist :group 'x) @@ -122,22 +126,27 @@ like xterm) for text." :group 'x) (defcustom x-dnd-direct-save-function #'x-dnd-save-direct - "Function called when a file is dropped that Emacs must save. -It is called with two arguments: the first is either nil or t, -and the second is a string. - -If the first argument is t, the second argument is the name the -dropped file should be saved under. The function should return a -complete file name describing where the file should be saved. - -It can also return nil, which means to cancel the drop. - -If the first argument is nil, the second is the name of the file -that was dropped." + "Function called when a file is dropped via XDS protocol. +The value should be a function of two arguments that supports +the X Direct Save (XDS) protocol. The function will be called +twice during the protocol execution. + +When the function is called with the first argument non-nil, +it should return an absolute file name whose base name is +the value of the second argument, a string. The return value +is the file name for the dragged file to be saved. The function +can also return nil if saving the file should be refused for some +reason; in that case the drop will be canceled. + +When the function is called with the first argument nil, the +second argument specifies the file name where the file was saved; +the function should then do whatever is appropriate when such a +file is saved, like show the file in the Dired buffer or visit +the file." :version "29.1" - :type '(choice (const :tag "Prompt for name before saving" + :type '(choice (const :tag "Prompt for file name to save" x-dnd-save-direct) - (const :tag "Save and open immediately without prompting" + (const :tag "Save in `default-directory' without prompting" x-dnd-save-direct-immediately) (function :tag "Other function")) :group 'x) @@ -222,14 +231,14 @@ any protocol specific data.") (cdr (x-dnd-get-state-cons-for-frame frame-or-window))) (defun x-dnd-default-test-function (_window _action types) - "The default test function for drag and drop. + "The default test function for drag-and-drop. WINDOW is where the mouse is when this function is called. It may be a frame if the mouse is over the menu bar, scroll bar or tool bar. ACTION is the suggested action from the source, and TYPES are the types the drop data can have. This function only accepts drops with types in `x-dnd-known-types'. It always returns the action `private', unless `types' contains a value -inside `x-dnd-copy-types'." +inside `x-dnd-copy-types', in which case it may return `copy'." (let ((type (x-dnd-choose-type types))) (when type (let ((list x-dnd-copy-types)) (catch 'out @@ -1564,17 +1573,24 @@ was taken, or the direct save failed." (when (not (equal file-name original-file-name)) (delete-file file-name))))) -(defun x-dnd-save-direct (need-name name) - "Handle dropping a file that should be saved immediately. -NEED-NAME tells whether or not the file was not yet saved. NAME -is either the name of the file, or the name the drop source wants -us to save under. +(defun x-dnd-save-direct (need-name filename) + "Handle dropping a file FILENAME that should be saved first, asking the user. +NEED-NAME non-nil means the caller requests the full absolute +file name of FILENAME under which to save it; FILENAME is just +the base name in that case. The function then prompts the user +for where to save to file and returns the result to the caller. + +NEED-NAME nil means the file was saved as FILENAME (which should +be the full absolute file name in that case). The function then +refreshes the Dired display, if the current buffer is in Dired +mode, or visits the file otherwise. -Prompt the user for a file name, then open it." +This function is intended to be the value of `x-dnd-direct-save-function', +which see." (if need-name (let ((file-name (read-file-name "Write file: " default-directory - nil nil name))) + nil nil filename))) (when (file-exists-p file-name) (unless (y-or-n-p (format-message "File `%s' exists; overwrite? " file-name)) @@ -1584,18 +1600,18 @@ Prompt the user for a file name, then open it." ;; interface can be found. (if (derived-mode-p 'dired-mode) (revert-buffer) - (find-file name)))) + (find-file filename)))) -(defun x-dnd-save-direct-immediately (need-name name) - "Save and open a dropped file, like `x-dnd-save-direct'. -NEED-NAME tells whether or not the file was not yet saved. NAME -is either the name of the file, or the name the drop source wants -us to save under. +(defun x-dnd-save-direct-immediately (need-name filename) + "Handle dropping a file FILENAME that should be saved first. +Like `x-dnd-save-direct', but do not prompt for the file name; +instead, return its absolute file name for saving in the current +directory. -Unlike `x-dnd-save-direct', do not prompt for the name by which -to save the file. Simply save it in the current directory." +This function is intended to be the value of `x-dnd-direct-save-function', +which see." (if need-name - (let ((file-name (expand-file-name name))) + (let ((file-name (expand-file-name filename))) (when (file-exists-p file-name) (unless (y-or-n-p (format-message "File `%s' exists; overwrite? " file-name)) @@ -1605,7 +1621,7 @@ to save the file. Simply save it in the current directory." ;; interface can be found. (if (derived-mode-p 'dired-mode) (revert-buffer) - (find-file name)))) + (find-file filename)))) (defun x-dnd-handle-octet-stream-for-drop (save-to) "Save the contents of the XDS selection to SAVE-TO. -- 2.39.2