[சனி செப்டம்பர் 23, 2023] Visuwesh wrote:

>>> +(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-media-image-save-type'.  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 (read-string "Insert filename for image: "))
>>
>> It would be nice if we auto-generate the file name here by default. It
>> is what I would expect from yanking an image at least.
>
> Hmm, I think I prefer having a descriptive name for the file.  It would
> aid in searching for the attachment later (in the org document, and
> using find & friends).  We can add a user option but how should the name
> be auto-generated? Timestamp?  I have no ideas in this regard.

Attached patch considers your review and also another defcustom to tell
how to generate the filename which by default autogenerates a filename
based on current time.

>From c9f149cde047a60b6c2a256d96616864f5f66352 Mon Sep 17 00:00:00 2001
From: Visuwesh <visuwe...@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-type, org-dnd-default-attach-method)
(org-yank-image-file-name-function): New defcustoms.
(org--image-yank-media-handler, org--copied-files-yank-media-handler)
(org--dnd-local-file-handler): Add yank-media and DND handlers.

* etc/ORG-NEWS: Advertise the new features.
---
 etc/ORG-NEWS |  20 ++++++
 lisp/org.el  | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 188 insertions(+), 1 deletion(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 252c5a9f9..c4a58dd4d 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -596,6 +596,26 @@ 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 attached
+
+Org can now attach images in clipboard and files copied/cut to the
+clipboard from file managers using the ~yank-media~ command which also
+inserts a link to the attached file.  This command was added in Emacs 29.
+
+Images can be saved to a separate directory instead of being attached,
+customize ~org-yank-image-save-type~.
+
+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
+
+Attachment method other than ~org-attach-method~ for dropped files can
+be specified using ~org-dnd-default-attach-method~.
+
+Images dropped also respect the value of ~org-yank-image-save-type~.
+
 ** 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..d69f9ec70 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,170 @@ 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 '("^file:///" . 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)))
+
+(defcustom org-yank-image-save-type '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) (or (stringp 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-%d-%H:%M"))
+
+(defun org-yank-image-read-filename ()
+  "Read filename for image in clipboard."
+  (read-string "Filename for image 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-type'.  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-type 'attach)
+                       temporary-file-directory
+                     org-yank-image-save-type)))
+         link)
+    (when (and (not (eq org-yank-image-save-type 'attach))
+               (not (file-directory-p org-yank-image-save-type)))
+      (make-directory org-yank-image-save-type t))
+    (with-temp-file absname
+      (insert data))
+    (if (null (eq org-yank-image-save-type 'attach))
+        (setq link (org-link-make-string
+                    (concat "file:" (file-relative-name absname))
+                    filename))
+      (require 'org-attach)
+      (org-attach-attach absname nil 'mv)
+      (setq link (org-link-make-string
+                  (concat "attachment:" filename)
+                  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)
+  "Attach copied or cut files from file manager.
+If the files were cut from the file manager, then the `mv' attach
+method is used; `cp' otherwise.
+
+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."
+  (require 'org-attach)
+  ;; pcmanfm adds a null byte at the end for some reason.
+  (let* ((data (split-string data "[\0\n\r]" t "^file://"))
+         (files (cdr data))
+         (method (if (equal (car data) "cut")
+                     'mv
+                   'cp)))
+    (dolist (f files)
+      (setq f (url-unhex-string f))
+      (if (file-readable-p f)
+          (org-attach-attach f nil method)
+        (message "File `%s' is not readable, skipping" f)))))
+
+(defcustom org-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-type' 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 "Symbol link" lns)))
+
+(declare-function mailcap-file-name-to-mime-type "mailcap" (file-name))
+(defvar org-attach-method)
+
+(defun org--dnd-local-file-handler (url action)
+  "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 `vz/org-dnd-default-attach-action'.
+The action `private' is always returned."
+  (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-type))))
+         (method (pcase action
+                   ('copy 'cp)
+                   ('move 'mv)
+                   ('ask (caddr (read-multiple-choice
+                                 "Attach using method"
+                                 '((?c "cp" cp)
+                                   (?m "mv" mv)
+                                   (?l "ln hard link" ln)
+                                   (?s "symbolic link" lns)))))
+                   ('private (or org-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-type))
+      (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-type)
+                (file-name-nondirectory filename)))
+      (file-name-nondirectory filename))
+     "\n")
+    'private))
+
 ;;; Other stuff
 
 (defvar reftex-docstruct-symbol)
-- 
2.40.1

Reply via email to