From: Visuwesh <visuweshm@gmail.com>
To: Ihor Radchenko <yantar92@posteo.net>
Cc: Po Lu <luangruo@yahoo.com>, emacs-orgmode@gnu.org
Subject: Re: [BUG] [PATCH] Add yank-media and DND handler [9.6.7 (9.6.7-g6eb773 @ /home/viz/lib/emacs/straight/build/org/)]
Date: Tue, 05 Dec 2023 18:48:14 +0530 [thread overview]
Message-ID: <87fs0gwzxl.fsf@gmail.com> (raw)
In-Reply-To: <87cywo5rwy.fsf@localhost> (Ihor Radchenko's message of "Sun, 05 Nov 2023 12:02:53 +0000")
[-- Attachment #1: Type: text/plain, Size: 513 bytes --]
[ஞாயிறு நவம்பர் 05, 2023] Ihor Radchenko wrote:
> Po Lu <luangruo@yahoo.com> writes:
>
>> I have now installed an interface to this end into Emacs 30. The
>> pertinent documentation is under the ``Drag and Drop'' node in the Lisp
>> reference manual.
>
> Thanks!
>
> [...]
>
> Visuwesh, do you want to update your patch accordingly?
I have now adjusted the patch to insert a space between links when the
Emacs version in <30, and when more than one file is dropped.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-support-for-yank-media-and-DND.patch --]
[-- Type: text/x-diff, Size: 16263 bytes --]
From 93db10f9f0c618be5512fbd67cd7b7676d6b1b0c Mon Sep 17 00:00:00 2001
From: Visuwesh <visuweshm@gmail.com>
Date: Fri, 22 Sep 2023 20:11:41 +0530
Subject: [PATCH] Add support for yank-media and DND
* lisp/org.el (org-mode): Call the setup function for yank-media and
DND.
(org-setup-yank-dnd-handlers): Register yank-media-handler and DND
handler.
(org-yank-image-save-method, org-yank-image-file-name-function)
(org-yank-dnd-method, org-yank-dnd-default-attach-method): New
defcustoms.
(org--image-yank-media-handler, org--copied-files-yank-media-handler)
(org--dnd-rmc, org--dnd-multi-local-file-handler)
(org--dnd-local-file-handler, org--dnd-attach-file, org--dnd-xds-method)
(org--dnd-xds-function): Add yank-media and DND handlers.
* doc/org-manual.org: (Drag and Drop & ~yank-media~): Describe the new
feature in the manual.
* etc/ORG-NEWS: Advertise the new features.
---
doc/org-manual.org | 45 ++++++++
etc/ORG-NEWS | 24 +++++
lisp/org.el | 248 ++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 316 insertions(+), 1 deletion(-)
diff --git a/doc/org-manual.org b/doc/org-manual.org
index 3e9d42f55..7b14eb937 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -21095,6 +21095,51 @@ most recent since the mobile application searches files that were last
pulled. To get an updated agenda view with changes since the last
pull, pull again.
+** Drag and Drop & ~yank-media~
+:PROPERTIES:
+:DESCRIPTION: Dropping and pasting files and images
+:END:
+
+#+cindex: dropping files
+#+cindex: dragging files
+#+cindex: drag and drop
+#+cindex: dnd
+#+vindex: org-yank-dnd-method
+Org mode supports drag and drop (DnD) of files. By default, Org asks
+the user what must be done with the dropped file: attach it, insert
+=file:= link, or open the file. Customize ~org-yank-dnd-method~ to
+set the default DnD action.
+
+When DnD method is "attach", Org mode first consults DnD metadata to
+decide the attach method. For example, when file/files are dragged
+from a file manager, Org may attach by copying or by moving.
+
+#+vindex: org-yank-dnd-default-attach-method
+If Org cannot figure out which attachment method to use from the
+metadata, it defaults to ~org-yank-dnd-default-attach-method~ [fn::By
+default, ~org-yank-dnd-default-attach-method~ is set to nil -- use the same
+value as ~org-attach-method~ (~cp~ by default).]
+
+#+cindex: pasting files, images from clipboard
+Starting from Emacs 29, Org mode supports ~yank-media~ command to yank
+images from the clipboard and files from a file manager.
+
+#+vindex: org-yank-image-save-method
+When yanking images from clipboard, Org saves the image on disk and
+inserts the image link to Org buffer. Images are either saved as
+attachments to heading (default) or to a globally defined directory.
+The save location is controlled by ~org-yank-image-save-method~.
+
+#+vindex: org-yank-image-file-name-function
+The yanked images are saved under automatically generated name. You
+can customize ~org-yank-image-file-name-function~ to make Org query
+the image names or change the naming scheme.
+
+When yanking files copied from a file manager, Org respects the value
+of ~org-yank-dnd-method~. Image files pasted this way also respect
+the value of ~org-yank-image-save-method~ when the action to perform
+is =attach=.
+
* Hacking
:PROPERTIES:
:DESCRIPTION: How to hack your way around.
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 252c5a9f9..7c40ec0fe 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -596,6 +596,30 @@ return a matplotlib Figure object to plot. For output results, the
current figure (as returned by =pyplot.gcf()=) is cleared before
evaluation, and then plotted afterwards.
+*** Images and files in clipboard can be pasted
+
+Org asks the user what must be done when pasting images and files
+copied to the clipboard from a file manager using the ~yank-media~
+command. The default action can be set by customizing
+~org-yank-dnd-method~. The ~yank-media~ command was added in Emacs
+29.
+
+Images can be saved to a separate directory instead of being attached,
+customize ~org-yank-image-save-method~.
+
+Image filename chosen can be customized by setting
+~org-yank-image-file-name-function~ which by default autogenerates a
+filename based on the current time.
+
+*** Files and images can be attached by dropping onto Emacs
+
+By default, Org asks the user what to do with the dropped file like
+for pasted files. The same user option ~org-yank-dnd-method~ is
+respected.
+
+Images dropped also respect the value of ~org-yank-image-save-method~
+when ~org-yank-dnd-method~ is =attach=.
+
** New functions and changes in function arguments
*** =TYPES= argument in ~org-element-lineage~ can now be a symbol
diff --git a/lisp/org.el b/lisp/org.el
index d0b2355ea..086b3dff5 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -4999,7 +4999,10 @@ The following commands are available:
(org--set-faces-extend '(org-block-begin-line org-block-end-line)
org-fontify-whole-block-delimiter-line)
(org--set-faces-extend org-level-faces org-fontify-whole-heading-line)
- (setq-local org-mode-loading nil))
+ (setq-local org-mode-loading nil)
+
+ ;; `yank-media' handler and DND support.
+ (org-setup-yank-dnd-handlers))
;; Update `customize-package-emacs-version-alist'
(add-to-list 'customize-package-emacs-version-alist
@@ -20254,6 +20257,249 @@ it has a `diary' type."
(org-format-timestamp timestamp fmt t))
(org-format-timestamp timestamp fmt (eq boundary 'end)))))))
+;;; Yank media handler and DND
+(defun org-setup-yank-dnd-handlers ()
+ "Setup the `yank-media' and DND handlers for buffer."
+ (setq-local dnd-protocol-alist
+ (cons (cons "^file:///"
+ (if (>= emacs-major-version 30)
+ #'org--dnd-multi-local-file-handler
+ #'org--dnd-local-file-handler))
+ dnd-protocol-alist))
+ (when (fboundp 'yank-media-handler)
+ (yank-media-handler "image/.*" #'org--image-yank-media-handler)
+ ;; Looks like different DEs go for different handler names,
+ ;; https://larsee.com/blog/2019/05/clipboard-files/.
+ (yank-media-handler "x/special-\\(?:gnome\|KDE\|mate\\)-files"
+ #'org--copied-files-yank-media-handler))
+ (when (boundp 'x-dnd-direct-save-function)
+ (setq-local x-dnd-direct-save-function #'org--dnd-xds-function)))
+
+(defcustom org-yank-image-save-method 'attach
+ "Method to save images yanked from clipboard and dropped to Emacs.
+It can be the symbol `attach' to add it as an attachment, or a
+directory name to copy/cut the image to that directory."
+ :group 'org
+ :package-version '(Org . "9.7")
+ :type '(choice (const :tag "Add it as attachment" attach)
+ (directory :tag "Save it in directory"))
+ :safe (lambda (x) (eq x 'attach)))
+
+(defcustom org-yank-image-file-name-function #'org-yank-image-autogen-filename
+ "Function to generate filename for image yanked from clipboard.
+By default, this autogenerates a filename based on the current
+time.
+It is called with no arguments and should return a string without
+any extension which is used as the filename."
+ :group 'org
+ :package-version '(Org . "9.7")
+ :type '(radio (function-item :doc "Autogenerate filename"
+ org-yank-image-autogen-filename)
+ (function-item :doc "Ask for filename"
+ org-yank-image-read-filename)
+ function))
+
+(defun org-yank-image-autogen-filename ()
+ "Autogenerate filename for image in clipboard."
+ (format-time-string "clipboard-%Y%m%dT%H%M%S.%6N"))
+
+(defun org-yank-image-read-filename ()
+ "Read filename for image in clipboard."
+ (read-string "Basename for image file without extension: "))
+
+(declare-function org-attach-attach "org-attach" (file &optional visit-dir method))
+
+(defun org--image-yank-media-handler (mimetype data)
+ "Save image DATA of mime-type MIMETYPE and insert link at point.
+It is saved as per `org-yank-image-save-method'. The name for the
+image is prompted and the extension is automatically added to the
+end."
+ (let* ((ext (symbol-name (mailcap-mime-type-to-extension mimetype)))
+ (iname (funcall org-yank-image-file-name-function))
+ (filename (file-name-with-extension iname ext))
+ (absname (expand-file-name
+ filename
+ (if (eq org-yank-image-save-method 'attach)
+ temporary-file-directory
+ org-yank-image-save-method)))
+ link)
+ (when (and (not (eq org-yank-image-save-method 'attach))
+ (not (file-directory-p org-yank-image-save-method)))
+ (make-directory org-yank-image-save-method t))
+ (with-temp-file absname
+ (insert data))
+ (if (null (eq org-yank-image-save-method 'attach))
+ (setq link (org-link-make-string (concat "file:" (file-relative-name absname))))
+ (require 'org-attach)
+ (org-attach-attach absname nil 'mv)
+ (setq link (org-link-make-string (concat "attachment:" filename))))
+ (insert link)))
+
+;; I cannot find a spec for this but
+;; https://indigo.re/posts/2021-12-21-clipboard-data.html and pcmanfm
+;; suggests that this is the format.
+(defun org--copied-files-yank-media-handler (_mimetype data)
+ "Handle copied or cut files from file manager.
+They are handled as per `org-yank-dnd-method'.
+DATA is a string where the first line is the operation to
+perform: copy or cut. Rest of the lines are file: links to the
+concerned files."
+ ;; pcmanfm adds a null byte at the end for some reason.
+ (let* ((data (split-string data "[\0\n\r]" t))
+ (files (cdr data))
+ (action (if (equal (car data) "cut")
+ 'copy
+ 'move))
+ (sep (if (= (length files) 1) "" " ")))
+ (dolist (f files)
+ (if (file-readable-p f)
+ (org--dnd-local-file-handler f action sep)
+ (message "File `%s' is not readable, skipping" f)))))
+
+(defcustom org-yank-dnd-method 'ask
+ "Action to perform on the dropped and the pasted files.
+When the value is the symbol,
+ . `attach' -- attach dropped/pasted file
+ . `open' -- visit/open dropped/pasted file in Emacs
+ . `file-link' -- insert file: link to dropped/pasted file
+ . `ask' -- ask what to do out of the above."
+ :group 'org
+ :package-version '(Org . "9.7")
+ :type '(choice (const :tag "Attach" attach)
+ (const :tag "Open/Visit file" open)
+ (const :tag "Insert file: link" file-link)
+ (const :tag "Ask what to do" ask)))
+
+(defcustom org-yank-dnd-default-attach-method nil
+ "Default attach method to use when DND action is unspecified.
+This attach method is used when the DND action is `private'.
+This is also used when `org-yank-image-save-method' is nil.
+When nil, use `org-attach-method'."
+ :group 'org
+ :package-version '(Org . "9.7")
+ :type '(choice (const :tag "Default attach method" nil)
+ (const :tag "Copy" cp)
+ (const :tag "Move" mv)
+ (const :tag "Hard link" ln)
+ (const :tag "Symbolic link" lns)))
+
+(declare-function mailcap-file-name-to-mime-type "mailcap" (file-name))
+(defvar org-attach-method)
+
+(defun org--dnd-rmc (prompt choices)
+ (if (null (use-dialog-box-p))
+ (caddr (read-multiple-choice prompt choices))
+ (setq choices
+ (mapcar
+ (pcase-lambda (`(_key ,message ,val))
+ (cons (capitalize message) val))
+ choices))
+ (x-popup-menu t (list prompt (cons "" choices)))))
+
+(defun org--dnd-multi-local-file-handler (urls action)
+ "Handle file URLS as per ACTION.
+URLS is a list of file URL."
+ (let ((sep (if (= (length urls) 1) "" " ")))
+ (dolist (u urls)
+ (org--dnd-local-file-handler u action sep))))
+
+(put 'org--dnd-multi-local-file-handler 'dnd-multiple-handler t)
+
+(defun org--dnd-local-file-handler (url action &optional separator)
+ "Handle file URL as per ACTION.
+SEPARATOR is the string to insert after each link. It may be nil
+in which case, space is inserted."
+ (unless separator
+ (setq separator " "))
+ (let ((method (if (eq org-yank-dnd-method 'ask)
+ (org--dnd-rmc
+ "What to do with file?"
+ '((?a "attach" attach)
+ (?o "open" open)
+ (?f "insert file: link" file-link)))
+ org-yank-dnd-method)))
+ (pcase method
+ (`attach (org--dnd-attach-file url action separator))
+ (`open (dnd-open-local-file url action))
+ (`file-link
+ (let ((filename (dnd-get-local-file-name url)))
+ (insert (org-link-make-string (concat "file:" filename)) separator))))))
+
+(defun org--dnd-attach-file (url action separator)
+ "Attach filename given by URL using method pertaining to ACTION.
+If ACTION is `move', use `mv' attach method.
+If `copy', use `cp' attach method.
+If `ask', ask the user.
+If `private', use the method denoted in `org-yank-dnd-default-attach-method'.
+The action `private' is always returned.
+
+SEPARATOR is the string to insert after each link."
+ (require 'mailcap)
+ (let* ((filename (dnd-get-local-file-name url))
+ (mimetype (mailcap-file-name-to-mime-type filename))
+ (separatep (and (string-prefix-p "image/" mimetype)
+ (not (eq 'attach org-yank-image-save-method))))
+ (method (pcase action
+ ('copy 'cp)
+ ('move 'mv)
+ ('ask (org--dnd-rmc
+ "Attach using method"
+ '((?c "copy" cp)
+ (?m "move" mv)
+ (?l "hard link" ln)
+ (?s "symbolic link" lns))))
+ ('private (or org-yank-dnd-default-attach-method
+ org-attach-method)))))
+ (if separatep
+ (funcall
+ (pcase method
+ ('cp #'copy-file)
+ ('mv #'rename-file)
+ ('ln #'add-name-to-file)
+ ('lns #'make-symbolic-link))
+ filename
+ (expand-file-name (file-name-nondirectory filename)
+ org-yank-image-save-method))
+ (org-attach-attach filename nil method))
+ (insert
+ (org-link-make-string
+ (concat (if separatep
+ "file:"
+ "attachment:")
+ (if separatep
+ (expand-file-name (file-name-nondirectory filename)
+ org-yank-image-save-method)
+ (file-name-nondirectory filename))))
+ separator)
+ 'private))
+
+(defvar-local org--dnd-xds-method nil
+ "The method to use for dropped file.")
+(defun org--dnd-xds-function (need-name filename)
+ "Handle file with FILENAME dropped via XDS protocol.
+When NEED-NAME is t, FILNAME is the base name of the file to be
+saved.
+When NEED-NAME is nil, the drop is complete."
+ (if need-name
+ (let ((method (if (eq org-yank-dnd-method 'ask)
+ (org--dnd-rmc
+ "What to do with dropped file?"
+ '((?a "attach" attach)
+ (?o "open" open)
+ (?f "insert file: link" file-link)))
+ org-yank-dnd-method)))
+ (setq-local org--dnd-xds-method method)
+ (pcase method
+ (`attach (expand-file-name filename (org-attach-dir 'create)))
+ (`open (expand-file-name (make-temp-name "emacs.") temporary-file-directory))
+ (`file-link (read-file-name "Write file to: " nil nil nil filename))))
+ (pcase org--dnd-xds-method
+ (`attach (insert (org-link-make-string
+ (concat "attachment:" (file-name-nondirectory filename)))))
+ (`file-link (insert (org-link-make-string (concat "file:" filename))))
+ (`open (find-file filename)))
+ (setq-local org--dnd-xds-method nil)))
+
;;; Other stuff
(defvar reftex-docstruct-symbol)
--
2.42.0
next prev parent reply other threads:[~2023-12-05 13:19 UTC|newest]
Thread overview: 49+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <87bkdccihf.fsf.ref@yahoo.com>
2023-09-22 14:52 ` [BUG] [PATCH] Add yank-media and DND handler [9.6.7 (9.6.7-g6eb773 @ /home/viz/lib/emacs/straight/build/org/)] Visuwesh
2023-09-22 16:51 ` Max Nikulin
2023-09-22 17:29 ` Visuwesh
2023-09-24 8:06 ` Max Nikulin
2023-09-23 10:28 ` Ihor Radchenko
2023-09-23 16:55 ` Visuwesh
2023-09-25 13:14 ` Visuwesh
2023-09-26 16:25 ` Max Nikulin
2023-09-27 8:33 ` Visuwesh
2023-10-07 11:56 ` Ihor Radchenko
2023-10-07 12:07 ` Ihor Radchenko
2023-10-07 12:27 ` Visuwesh
2023-10-07 12:36 ` Ihor Radchenko
2023-10-07 14:03 ` Visuwesh
2023-10-08 9:30 ` Ihor Radchenko
2023-10-08 11:21 ` Visuwesh
2023-10-09 11:12 ` Ihor Radchenko
2023-10-09 12:17 ` Visuwesh
2023-10-19 7:34 ` Visuwesh
2023-10-19 9:44 ` Ihor Radchenko
2023-10-20 1:52 ` Po Lu
2023-10-20 7:29 ` Ihor Radchenko
2023-10-20 7:46 ` Po Lu
2023-10-20 7:57 ` Ihor Radchenko
2023-10-20 8:29 ` Po Lu
2023-10-20 10:17 ` Visuwesh
2023-10-22 6:19 ` Visuwesh
2023-10-23 8:58 ` Ihor Radchenko
2023-10-23 10:12 ` Visuwesh
2023-10-26 11:39 ` Po Lu
2023-11-05 12:02 ` Ihor Radchenko
2023-11-05 17:45 ` Visuwesh
2023-12-05 13:18 ` Visuwesh [this message]
2023-12-10 13:53 ` Ihor Radchenko
2023-12-10 14:47 ` Bastien Guerry
2023-12-10 15:07 ` Ihor Radchenko
2023-09-24 14:58 ` Max Nikulin
2023-09-25 14:15 ` Visuwesh
2023-09-26 10:24 ` Ihor Radchenko
2023-09-27 8:29 ` Visuwesh
2023-09-28 12:01 ` Max Nikulin
2023-09-24 14:49 ` Max Nikulin
2023-10-06 7:34 ` Po Lu
2023-09-29 8:20 Liu Hui
2023-10-01 14:28 ` Visuwesh
2023-10-02 0:28 ` Liu Hui
-- strict thread matches above, loose matches on Subject: below --
2023-10-11 14:24 Liu Hui
2023-10-11 15:36 ` Visuwesh
2023-10-12 5:12 ` Liu Hui
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.orgmode.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87fs0gwzxl.fsf@gmail.com \
--to=visuweshm@gmail.com \
--cc=emacs-orgmode@gnu.org \
--cc=luangruo@yahoo.com \
--cc=yantar92@posteo.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).