org-screenshot.el 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. ;;; org-screenshot.el --- Take and manage screenshots in Org-mode files
  2. ;;
  3. ;; Copyright (C) 2009-2013
  4. ;; Free Software Foundation, Inc.
  5. ;;
  6. ;; Author: Max Mikhanosha <max@openchat.com>
  7. ;; Keywords: outlines, hypermedia, calendar, wp
  8. ;; Homepage: http://orgmode.org
  9. ;; Version: 8.0
  10. ;;
  11. ;; Released under the GNU General Public License version 3
  12. ;; see: http://www.gnu.org/licenses/gpl-3.0.html
  13. ;;
  14. ;; This file is not part of GNU Emacs.
  15. ;;
  16. ;; This program is free software: you can redistribute it and/or modify
  17. ;; it under the terms of the GNU General Public License as published by
  18. ;; the Free Software Foundation, either version 3 of the License, or
  19. ;; (at your option) any later version.
  20. ;; This program is distributed in the hope that it will be useful,
  21. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. ;; GNU General Public License for more details.
  24. ;; You should have received a copy of the GNU General Public License
  25. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  26. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  27. ;;
  28. ;;; Commentary:
  29. ;;
  30. ;; NOTE: This library requires external screenshot taking executable "scrot",
  31. ;; which is available as a package from all major Linux distribution. If your
  32. ;; distribution does not have it, source can be found at:
  33. ;;
  34. ;; http://freecode.com/projects/scrot
  35. ;;
  36. ;; org-screenshot.el have been tested with scrot version 0.8.
  37. ;;
  38. ;; Usage:
  39. ;;
  40. ;; (require 'org-screenshot)
  41. ;;
  42. ;; Available commands with default bindings
  43. ;;
  44. ;; `org-screenshot-take' C-c M-s M-t and C-c M-s M-s
  45. ;;
  46. ;; Take the screenshot, C-u argument delays 1 second, double C-u 2 seconds
  47. ;; triple C-u 3 seconds, and subsequent C-u add 2 seconds to the delay.
  48. ;;
  49. ;; Screenshot area is selected with the mouse, or left-click on the window
  50. ;; for an entire window.
  51. ;;
  52. ;; `org-screenshot-rotate-prev' C-c M-s M-p and C-c M-s C-p
  53. ;;
  54. ;; Rotate screenshot before the point to one before it (sorted by date)
  55. ;;
  56. ;; `org-screenshot-rotate-next' C-c M-s M-n and C-c M-s C-n
  57. ;;
  58. ;; Rotate screenshot before the point to one after it
  59. ;;
  60. ;; `org-screenshot-show-unused' C-c M-s M-u and C-c M-s u
  61. ;;
  62. ;; Open dired buffer with screenshots that are not used in current
  63. ;; Org buffer marked
  64. ;;
  65. ;; The screenshot take and rotate commands will update the inline images
  66. ;; if they are already shown, if you are inserting first screenshot in the Org
  67. ;; Buffer (and there are no other images shown), you need to manually display
  68. ;; inline images with C-c C-x C-v
  69. ;;
  70. ;; Screenshot take and rotate commands offer user to continue by by using single
  71. ;; keys, in a manner similar to to "repeat-char" of keyboard macros, user can
  72. ;; continue rotating screenshots by pressing just the last key of the binding
  73. ;;
  74. ;; For example: C-c M-s M-t creates the screenshot and then user can
  75. ;; repeatedly press M-p or M-n to rotate it back and forth with
  76. ;; previously taken ones.
  77. ;;
  78. (require 'org)
  79. (require 'dired)
  80. (defgroup org-screenshot nil
  81. "Options for taking and managing screen-shots"
  82. :tag "Org Startup"
  83. :group 'org-link)
  84. (defcustom org-screenshot-image-directory "./images/"
  85. "Directory in which screenshot image files will be stored, it
  86. be automatically created if it does't already exist."
  87. :type 'string
  88. :group 'org-screenshot)
  89. (defcustom org-screenshot-file-name-format "screenshot-%2.2d.png"
  90. "The string used to generate screenshot file name.
  91. Any %d format string recipe will be expanded with `format'
  92. function with the argument of a screenshot sequence number.
  93. A sequence like %XXXX will be replaced with string of the same
  94. length as there are X's, consisting of random characters in the
  95. range of [A-Za-z]."
  96. :type 'string
  97. :group 'org-screenshot)
  98. (defcustom org-screenshot-max-tries 200
  99. "Number of times we will try to generate generate filename that
  100. does not exist. With default `org-screenshot-name-format' its the
  101. limit for number of screenshots, before `org-screenshot-take' is
  102. unable to come up with a unique name."
  103. :type 'integer
  104. :group 'org-screenshot)
  105. (defvar org-screenshot-map (make-sparse-keymap)
  106. "Map for OrgMode screenshot related commands")
  107. ;; prefix
  108. (org-defkey org-mode-map (kbd "C-c M-s") org-screenshot-map)
  109. ;; Mnemonic is Control-C Meta "Screenshot" "Take"
  110. (org-defkey org-screenshot-map (kbd "M-t") 'org-screenshot-take)
  111. (org-defkey org-screenshot-map (kbd "M-s") 'org-screenshot-take)
  112. ;; No reason to require meta key, since its our own keymap
  113. (org-defkey org-screenshot-map "s" 'org-screenshot-take)
  114. (org-defkey org-screenshot-map "t" 'org-screenshot-take)
  115. ;; Rotations, the fast rotation user hint, would prefer the modifier
  116. ;; used by the original command that started the rotation
  117. (org-defkey org-screenshot-map (kbd "M-n") 'org-screenshot-rotate-next)
  118. (org-defkey org-screenshot-map (kbd "M-p") 'org-screenshot-rotate-prev)
  119. (org-defkey org-screenshot-map (kbd "C-n") 'org-screenshot-rotate-next)
  120. (org-defkey org-screenshot-map (kbd "C-p") 'org-screenshot-rotate-prev)
  121. ;; Show unused image files in Dired
  122. (org-defkey org-screenshot-map (kbd "M-u") 'org-screenshot-show-unused)
  123. (org-defkey org-screenshot-map (kbd "u") 'org-screenshot-show-unused)
  124. (random t)
  125. (defun org-screenshot-random-string (length)
  126. "Generate a random string of LENGTH consisting of random upper
  127. case and lower case letters."
  128. (let ((name (make-string length ?x)))
  129. (dotimes (i length)
  130. (let ((n (random 52)))
  131. (aset name i (if (< n 26)
  132. (+ ?a n)
  133. (+ ?A n -26)))))
  134. name))
  135. (defvar org-screenshot-process nil
  136. "Currently running screenshot process")
  137. (defvar org-screenshot-directory-seq-numbers (make-hash-table :test 'equal))
  138. (defun org-screenshot-update-seq-number (directory &optional reset)
  139. "Set `org-screenshot-file-name-format' sequence number for the directory.
  140. When RESET is NIL, increments the number stored, otherwise sets
  141. RESET as a new number. Intended to be called if screenshot was
  142. successful. Updating of sequence number is done in two steps, so
  143. aborted/canceled screenshot attempts don't increase the number"
  144. (setq directory (file-name-as-directory directory))
  145. (puthash directory (if reset
  146. (if (numberp reset) reset 1)
  147. (1+ (gethash directory
  148. org-screenshot-directory-seq-numbers
  149. 0)))
  150. org-screenshot-directory-seq-numbers))
  151. (defun org-screenshot-generate-file-name (directory)
  152. "Use `org-screenshot-name-format' to generate new screenshot
  153. file name for a specific directory. Keeps re-generating name if
  154. it already exists, up to `org-screenshot-max-tries'
  155. times. Returns just the file, without directory part"
  156. (setq directory (file-name-as-directory directory))
  157. (when (file-exists-p directory)
  158. (let ((tries 0)
  159. name
  160. had-seq
  161. (case-fold-search nil))
  162. (while (and (< tries org-screenshot-max-tries)
  163. (not name))
  164. (incf tries)
  165. (let ((tmp org-screenshot-file-name-format)
  166. (seq-re "%[-0-9.]*d")
  167. (rand-re "%X+"))
  168. (when (string-match seq-re tmp)
  169. (let ((seq (gethash
  170. directory
  171. org-screenshot-directory-seq-numbers 1)))
  172. (setq tmp
  173. (replace-regexp-in-string
  174. seq-re (format (match-string 0 tmp) seq)
  175. tmp)
  176. had-seq t)))
  177. (when (string-match rand-re tmp)
  178. (setq tmp
  179. (replace-regexp-in-string
  180. rand-re (org-screenshot-random-string
  181. (1- (length (match-string 0 tmp))))
  182. tmp t)))
  183. (let ((fullname (concat directory tmp)))
  184. (if (file-exists-p fullname)
  185. (when had-seq (org-screenshot-update-seq-number directory))
  186. (setq name tmp)))))
  187. name)))
  188. (defun org-screenshot-image-directory ()
  189. "Return the `org-screenshot-image-directory', ensuring there is
  190. trailing slash, and that it exists"
  191. (let ((dir (file-name-as-directory org-screenshot-image-directory)))
  192. (if (file-exists-p dir)
  193. dir
  194. (make-directory dir t)
  195. dir)))
  196. (defvar org-screenshot-last-file nil
  197. "File name of the last taken or rotated screenshot file,
  198. without directory")
  199. (defun org-screenshot-process-done (process event file
  200. orig-buffer
  201. orig-delay
  202. orig-event)
  203. "Called when \"scrot\" process exits. PROCESS and EVENT are
  204. same arguments as in `set-process-sentinel'. ORIG-BUFFER,
  205. ORIG-DELAY and ORIG-EVENT are Org Buffer, the screenshot delay
  206. used, and LAST-INPUT-EVENT values from when screenshot was
  207. initiated.
  208. "
  209. (setq org-screenshot-process nil)
  210. (with-current-buffer (process-buffer process)
  211. (if (not (equal event "finished\n"))
  212. (progn
  213. (insert event)
  214. (cond ((save-excursion
  215. (goto-char (point-min))
  216. (re-search-forward "Key was pressed" nil t))
  217. (ding)
  218. (message "Key was pressed, screenshot aborted"))
  219. (t
  220. (display-buffer (process-buffer process))
  221. (message "Error running \"scrot\" program")
  222. (ding))))
  223. (with-current-buffer orig-buffer
  224. (let ((link (format "[[file:%s]]" file)))
  225. (setq org-screenshot-last-file (file-name-nondirectory file))
  226. (let ((beg (point)))
  227. (insert link)
  228. (when org-inline-image-overlays
  229. (org-display-inline-images nil t beg (point))))
  230. (unless (< orig-delay 3)
  231. (ding))
  232. (org-screenshot-rotate-continue t orig-event))))))
  233. ;;;###autoload
  234. (defun org-screenshot-take (&optional delay)
  235. "Take a screenshot and insert link to it at point, if image
  236. display is already on (see \\[org-toggle-inline-images])
  237. screenshot will be displayed as an image
  238. Screen area for the screenshot is selected with the mouse, left
  239. click on a window screenshots that window, while left click and
  240. drag selects a region. Pressing any key cancels the screen shot
  241. With `C-u' universal argument waits one second after target is
  242. selected before taking the screenshot. With double `C-u' wait two
  243. seconds.
  244. With triple `C-u' wait 3 seconds, and also rings the bell when
  245. screenshot is done, any more `C-u' after that increases delay by
  246. 2 seconds
  247. "
  248. (interactive "P")
  249. ;; probably easier way to count number of C-u C-u out there
  250. (setq delay
  251. (cond ((null delay) 0)
  252. ((integerp delay) delay)
  253. ((and (consp delay)
  254. (integerp (car delay))
  255. (plusp (car delay)))
  256. (let ((num 1)
  257. (limit (car delay))
  258. (cnt 0))
  259. (while (< num limit)
  260. (setq num (* num 4)
  261. cnt (+ cnt (if (< cnt 3) 1 2))))
  262. cnt))
  263. (t (error "Invald delay"))))
  264. (when (and org-screenshot-process
  265. (member (process-status org-screenshot-process)
  266. '(run stop)))
  267. (error "scrot process is still running"))
  268. (let* ((name (org-screenshot-generate-file-name (org-screenshot-image-directory)))
  269. (file (format "%s%s" (org-screenshot-image-directory)
  270. name))
  271. (path (expand-file-name file)))
  272. (when (get-buffer "*scrot*")
  273. (with-current-buffer (get-buffer "*scrot*")
  274. (erase-buffer)))
  275. (setq org-screenshot-process
  276. (or
  277. (apply 'start-process
  278. (append
  279. (list "scrot" "*scrot*" "scrot" "-s" path)
  280. (when (plusp delay)
  281. (list "-d" (format "%d" delay)))))
  282. (error "Unable to start scrot process")))
  283. (when org-screenshot-process
  284. (if (plusp delay)
  285. (message "Click on a window, or select a rectangle (delay is %d sec)..."
  286. delay)
  287. (message "Click on a window, or select a rectangle..."))
  288. (set-process-sentinel
  289. org-screenshot-process
  290. `(lambda (process event)
  291. (org-screenshot-process-done
  292. process event ,file ,(current-buffer) ,delay ,last-input-event))))))
  293. (defvar org-screenshot-file-list nil
  294. "List of files in `org-screenshot-image-directory' used by
  295. `org-screenshot-rotate-prev' and `org-screenshot-rotate-next'")
  296. (defvar org-screenshot-rotation-index -1)
  297. (make-variable-buffer-local 'org-screenshot-file-list)
  298. (make-variable-buffer-local 'org-screenshot-rotation-index)
  299. (defun org-screenshot-rotation-init (lastfile)
  300. "Initialize variable `org-screenshot-file-list' variabel with
  301. the list of PNG files in `org-screenshot-image-directory' sorted
  302. by most recent first"
  303. (setq
  304. org-screenshot-rotation-index -1
  305. org-screenshot-file-list
  306. (let ((files (directory-files org-screenshot-image-directory
  307. t (org-image-file-name-regexp) t)))
  308. (mapcar 'file-name-nondirectory
  309. (sort files
  310. (lambda (file1 file2)
  311. (let ((mtime1 (nth 5 (file-attributes file1)))
  312. (mtime2 (nth 5 (file-attributes file2))))
  313. (setq mtime1 (+ (ash (first mtime1) 16)
  314. (second mtime1)))
  315. (setq mtime2 (+ (ash (first mtime2) 16)
  316. (second mtime2)))
  317. (> mtime1 mtime2)))))))
  318. (let ((n -1) (list org-screenshot-file-list))
  319. (while (and list (not (equal (pop list) lastfile)))
  320. (incf n))
  321. (setq org-screenshot-rotation-index n)))
  322. (defun org-screenshot-do-rotate (dir from-continue-rotating)
  323. "Rotate last screenshot with one of the previously taken
  324. screenshots from the same directory. If DIR is negative, in the
  325. other direction"
  326. (setq org-screenshot-last-file nil)
  327. (let* ((ourdir (file-name-as-directory (org-screenshot-image-directory)))
  328. done
  329. (link-re
  330. ;; taken from `org-display-inline-images'
  331. (concat "\\[\\[\\(\\(file:\\)\\|\\([./~]\\)\\)\\([^]\n]+?"
  332. (substring (org-image-file-name-regexp) 0 -2)
  333. "\\)\\]"))
  334. newfile oldfile)
  335. (save-excursion
  336. ;; Search for link to image file in the same directory before the point
  337. (while (not done)
  338. (if (not (re-search-backward link-re (point-min) t))
  339. (error "Unable to find link to image from %S directory before point" ourdir)
  340. (let ((file (concat (or (match-string 3) "") (match-string 4))))
  341. (when (equal (file-name-directory file)
  342. ourdir)
  343. (setq done t
  344. oldfile (file-name-nondirectory file))))))
  345. (when (or (null org-screenshot-file-list)
  346. (and (not from-continue-rotating)
  347. (not (member last-command
  348. '(org-screenshot-rotate-prev
  349. org-screenshot-rotate-next)))))
  350. (org-screenshot-rotation-init oldfile))
  351. (unless (> (length org-screenshot-file-list) 1)
  352. (error "Can't rotate a single image file"))
  353. (replace-match "" nil nil nil 1)
  354. (setq org-screenshot-rotation-index
  355. (mod (+ org-screenshot-rotation-index dir)
  356. (length org-screenshot-file-list))
  357. newfile (nth org-screenshot-rotation-index
  358. org-screenshot-file-list))
  359. ;; in case we started rotating from the file we just inserted,
  360. ;; advance one more time
  361. (when (equal oldfile newfile)
  362. (setq org-screenshot-rotation-index
  363. (mod (+ org-screenshot-rotation-index (if (plusp dir) 1 -1))
  364. (length org-screenshot-file-list))
  365. newfile (nth org-screenshot-rotation-index
  366. org-screenshot-file-list)))
  367. (replace-match (concat "file:" ourdir
  368. newfile)
  369. t t nil 4))
  370. ;; out of save-excursion
  371. (setq org-screenshot-last-file newfile)
  372. (when org-inline-image-overlays
  373. (org-display-inline-images nil t (match-beginning 0) (point)))))
  374. ;;;###autoload
  375. (defun org-screenshot-rotate-prev (dir)
  376. "Rotate last screenshot with one of the previously taken
  377. screenshots from the same directory. If DIR is negative, rotate
  378. in the other direction"
  379. (interactive "p")
  380. (org-screenshot-do-rotate dir nil)
  381. (when org-screenshot-last-file
  382. (org-screenshot-rotate-continue nil nil)))
  383. ;;;###autoload
  384. (defun org-screenshot-rotate-next (dir)
  385. "Rotate last screenshot with one of the previously taken
  386. screenshots from the same directory. If DIR is negative, rotate
  387. in the other direction"
  388. (interactive "p")
  389. (org-screenshot-do-rotate (- dir) nil)
  390. (when org-screenshot-last-file
  391. (org-screenshot-rotate-continue nil nil)))
  392. (defun org-screenshot-prefer-same-modifiers (list event)
  393. (if (not (eventp nil)) (car list)
  394. (let (ret (keys list))
  395. (while (and (null ret) keys)
  396. (let ((key (car keys)))
  397. (if (and (= 1 (length key))
  398. (equal (event-modifiers event)
  399. (event-modifiers (elt key 0))))
  400. (setq ret (car keys))
  401. (setq keys (cdr keys)))))
  402. (or ret (car list)))))
  403. (defun org-screenshot-rotate-continue (from-take-screenshot orig-event)
  404. "Display the message with the name of the last changed
  405. image-file and inform user that they can rotate by pressing keys
  406. bound to `org-screenshot-rotate-next' and
  407. `org-screenshot-rotate-prev' in `org-screenshot-map'
  408. This works similarly to `kmacro-end-or-call-macro' so that user
  409. can press a long key sequence to invoke the first command, and
  410. then uses single keys to rotate, until unregognized key is
  411. entered, at which point event will be unread"
  412. (let* ((event (if from-take-screenshot orig-event
  413. last-input-event))
  414. done
  415. (prev-key
  416. (org-screenshot-prefer-same-modifiers
  417. (where-is-internal 'org-screenshot-rotate-prev
  418. org-screenshot-map nil)
  419. event))
  420. (next-key
  421. (org-screenshot-prefer-same-modifiers
  422. (where-is-internal 'org-screenshot-rotate-next
  423. org-screenshot-map nil)
  424. event))
  425. prev-key-str next-key-str)
  426. (when (and (= (length prev-key) 1)
  427. (= (length next-key) 1))
  428. (setq
  429. prev-key-str (format-kbd-macro prev-key nil)
  430. next-key-str (format-kbd-macro next-key nil)
  431. prev-key (elt prev-key 0)
  432. next-key (elt next-key 0))
  433. (while (not done)
  434. (message "%S - '%s' and '%s' to rotate"
  435. org-screenshot-last-file prev-key-str next-key-str)
  436. (setq event (read-event))
  437. (cond ((equal event prev-key)
  438. (clear-this-command-keys t)
  439. (org-screenshot-do-rotate 1 t)
  440. (setq last-input-event nil))
  441. ((equal event next-key)
  442. (clear-this-command-keys t)
  443. (org-screenshot-do-rotate -1 t)
  444. (setq last-input-event nil))
  445. (t (setq done t))))
  446. (when last-input-event
  447. (clear-this-command-keys t)
  448. (setq unread-command-events (list last-input-event))))))
  449. ;;;###autoload
  450. (defun org-screenshot-show-unused ()
  451. "Open A Dired buffer with unused screenshots marked"
  452. (interactive)
  453. (let ((files-in-buffer)
  454. dired-buffer
  455. had-any
  456. (image-re (org-image-file-name-regexp))
  457. beg end)
  458. (save-excursion
  459. (save-restriction
  460. (widen)
  461. (setq beg (or beg (point-min)) end (or end (point-max)))
  462. (goto-char beg)
  463. (let ((re (concat "\\[\\[\\(\\(file:\\)\\|\\([./~]\\)\\)\\([^]\n]+?"
  464. (substring (org-image-file-name-regexp) 0 -2)
  465. "\\)\\]"))
  466. (case-fold-search t)
  467. old file ov img type attrwidth width)
  468. (while (re-search-forward re end t)
  469. (setq file (concat (or (match-string 3) "") (match-string 4)))
  470. (when (and (file-exists-p file)
  471. (equal (file-name-directory file)
  472. (org-screenshot-image-directory)))
  473. (push (file-name-nondirectory file)
  474. files-in-buffer))))))
  475. (setq dired-buffer (dired-noselect (org-screenshot-image-directory)))
  476. (with-current-buffer dired-buffer
  477. (dired-unmark-all-files ?\r)
  478. (dired-mark-if
  479. (let ((file (dired-get-filename 'no-dir t)))
  480. (and file (string-match image-re file)
  481. (not (member file files-in-buffer))
  482. (setq had-any t)))
  483. "Unused screenshot"))
  484. (when had-any (pop-to-buffer dired-buffer))))
  485. (provide 'org-screenshot)