From 312b6f0eabaab949f3830099d105c95849bb8a47 Mon Sep 17 00:00:00 2001 From: Daneel Date: Mon, 23 Feb 2026 14:07:21 +0100 Subject: [PATCH] macos: clipboard image paste via cmd+v (pngpaste) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit my/smart-paste bound to s-v (cmd+v) in org-mode and markdown-mode: - clipboard has image → my/paste-image-from-clipboard - clipboard has text → normal yank my/paste-image-from-clipboard: - saves to attachments/ relative to current file - filename: image-YYYYMMDD-HHMMSS.png - org-mode: [[./attachments/image-....png]] - markdown-mode: ![filename](./attachments/image-....png) - creates attachments/ dir if missing Requires: brew install pngpaste --- config.el | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/config.el b/config.el index 259dbac..f70d6bc 100644 --- a/config.el +++ b/config.el @@ -84,6 +84,64 @@ (after! evil (setq evil-want-clipboard t)) +;;; ============================================================ +;;; CLIPBOARD IMAGE PASTE (macOS — cmd+v) +;;; ============================================================ +;; Requires: brew install pngpaste +;; +;; cmd+v behaviour: +;; clipboard has image → saves to attachments/ → inserts link +;; clipboard has text → normal paste (yank) +;; +;; Org: [[./attachments/image-TIMESTAMP.png]] +;; Markdown: ![image-TIMESTAMP.png](./attachments/image-TIMESTAMP.png) +;; +;; attachments/ is always relative to the current file's directory. + +(defun my/clipboard-has-image-p () + "Return non-nil if macOS clipboard contains an image (requires pngpaste)." + (and (executable-find "pngpaste") + (let ((tmp (make-temp-file "emacs-imgcheck" nil ".png"))) + (prog1 (= 0 (call-process "pngpaste" nil nil nil tmp)) + (ignore-errors (delete-file tmp)))))) + +(defun my/paste-image-from-clipboard () + "Save clipboard image to attachments/ subdir and insert link at point. +File: image-YYYYMMDD-HHMMSS.png. Supports org-mode and markdown-mode." + (interactive) + (let* ((base-dir (if buffer-file-name + (file-name-directory (buffer-file-name)) + default-directory)) + (attach-dir (expand-file-name "attachments" base-dir)) + (filename (format-time-string "image-%Y%m%d-%H%M%S.png")) + (filepath (expand-file-name filename attach-dir)) + (relpath (concat "attachments/" filename))) + (make-directory attach-dir t) + (if (= 0 (call-process "pngpaste" nil nil nil filepath)) + (progn + (insert (pcase major-mode + ('org-mode (format "[[./%s]]" relpath)) + ('markdown-mode (format "![%s](./%s)" filename relpath)) + (_ relpath))) + (message "✓ Image saved: %s" relpath)) + (error "pngpaste failed — no image in clipboard?")))) + +(defun my/smart-paste () + "Paste image if clipboard has one, else normal yank (text paste). +Bound to cmd+v in org-mode and markdown-mode." + (interactive) + (if (my/clipboard-has-image-p) + (my/paste-image-from-clipboard) + (yank))) + +;; Bind cmd+v to smart paste in org and markdown +(map! :after org + :map org-mode-map + "s-v" #'my/smart-paste) +(map! :after markdown-mode + :map markdown-mode-map + "s-v" #'my/smart-paste) + ;; macOS Zoom accessibility — cancel persp-mode's 2.5s cache timer after startup ;; (reduces unnecessary redraws that cause Zoom to jump) (run-with-timer 3 nil