Add support for random Face headers
[gnus] / lisp / gnus-fun.el
1 ;;; gnus-fun.el --- various frivolous extension functions to Gnus
2
3 ;; Copyright (C) 2002-2014 Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;;; Code:
26
27 ;; For Emacs <22.2 and XEmacs.
28 (eval-and-compile
29   (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
30
31 (eval-when-compile
32   (require 'cl))
33
34 (require 'mm-util)
35 (require 'gnus-ems)
36 (require 'gnus-util)
37 (require 'gnus)
38
39 (defvar gnus-face-properties-alist)
40
41 (defcustom gnus-x-face-directory (expand-file-name "x-faces" gnus-directory)
42   "*Directory where X-Face PBM files are stored."
43   :version "22.1"
44   :group 'gnus-fun
45   :type 'directory)
46
47 (defcustom gnus-x-face-omit-files nil
48   "Regexp to match faces in `gnus-x-face-directory' to be omitted."
49   :version "24.3"
50   :group 'gnus-fun
51   :type 'string)
52
53 (defcustom gnus-face-directory (expand-file-name "faces" gnus-directory)
54   "*Directory where Face PNG files are stored."
55   :version "24.3"
56   :group 'gnus-fun
57   :type 'directory)
58
59 (defcustom gnus-face-omit-files nil
60   "Regexp to match faces in `gnus-face-directory' to be omitted."
61   :version "24.3"
62   :group 'gnus-fun
63   :type 'string)
64
65 (defcustom gnus-convert-pbm-to-x-face-command "pbmtoxbm %s | compface"
66   "Command for converting a PBM to an X-Face."
67   :version "22.1"
68   :group 'gnus-fun
69   :type 'string)
70
71 (defcustom gnus-convert-image-to-x-face-command
72   "convert -scale 48x48! %s xbm:- | xbm2xface.pl"
73   "Command for converting an image to an X-Face.
74 The command must take a image filename (use \"%s\") as input.
75 The output must be the X-Face header data on stdout."
76   :version "22.1"
77   :group 'gnus-fun
78   :type '(choice (const :tag "giftopnm, netpbm (GIF input only)"
79                         "giftopnm %s | ppmnorm | pnmscale -width 48 -height 48 | ppmtopgm | pgmtopbm | pbmtoxbm | compface")
80                  (const :tag "convert"
81                         "convert -scale 48x48! %s xbm:- | xbm2xface.pl")
82                  (string)))
83
84 (defcustom gnus-convert-image-to-face-command
85   "convert -scale 48x48! %s -colors %d png:-"
86   "Command for converting an image to a Face.
87
88 The command must take an image filename (first format argument
89 \"%s\") and the number of colors (second format argument: \"%d\")
90 as input.  The output must be the Face header data on stdout in
91 PNG format."
92   :version "22.1"
93   :group 'gnus-fun
94   :type '(choice (const :tag "djpeg, netpbm (JPG input only)"
95                         "djpeg %s | ppmnorm | pnmscale -width 48 -height 48 | ppmquant %d | pnmtopng")
96                  (const :tag "convert"
97                         "convert -scale 48x48! %s -colors %d png:-")
98                  (string)))
99
100 (defun gnus-shell-command-to-string (command)
101   "Like `shell-command-to-string' except not mingling ERROR."
102   (with-output-to-string
103     (call-process shell-file-name nil (list standard-output nil)
104                   nil shell-command-switch command)))
105
106 ;;;###autoload
107 (defun gnus--random-face-with-type (dir ext omit fun)
108   "Return file from DIR with extension EXT, omitting matches of OMIT, processed by FUN."
109   (when (file-exists-p dir)
110     (let* ((files
111             (remove nil (mapcar
112                          (lambda (f) (unless (string-match (or omit "^$") f) f))
113                          (directory-files dir t ext))))
114            (file (nth (random (length files)) files)))
115       (when file
116         (funcall fun file)))))
117
118 ;;;###autoload
119 (autoload 'message-goto-eoh "message" nil t)
120 (autoload 'message-insert-header "message" nil t)
121
122 (defun gnus--insert-random-face-with-type (fun type)
123   "Get a random face using FUN and insert it as a header TYPE.
124
125 For instance, to insert an X-Face use `gnus-random-x-face' as FUN
126   and \"X-Face\" as TYPE."
127   (let ((data (funcall fun)))
128     (save-excursion
129       (if data
130           (progn (message-goto-eoh)
131                  (insert  type ": " data "\n"))
132         (message
133          "No face returned by the function %s." (symbol-name fun))))))
134
135
136
137 ;;;###autoload
138 (defun gnus-random-x-face ()
139   "Return X-Face header data chosen randomly from `gnus-x-face-directory'.
140
141 Files matching `gnus-x-face-omit-files' are not considered."
142   (interactive)
143   (gnus--random-face-with-type gnus-x-face-directory "\\.pbm$" gnus-x-face-omit-files
144                          (lambda (file)
145                            (gnus-shell-command-to-string
146                             (format gnus-convert-pbm-to-x-face-command
147                                     (shell-quote-argument file))))))
148
149 ;;;###autoload
150 (defun gnus-insert-random-x-face-header ()
151   "Insert a random X-Face header from `gnus-x-face-directory'."
152   (interactive)
153   (gnus--insert-random-face-with-type 'gnus-random-x-face 'X-Face))
154
155 ;;;###autoload
156 (defun gnus-x-face-from-file (file)
157   "Insert an X-Face header based on an image FILE.
158
159 Depending on `gnus-convert-image-to-x-face-command' it may accept
160 different input formats."
161   (interactive "fImage file name: ")
162   (when (file-exists-p file)
163     (gnus-shell-command-to-string
164      (format gnus-convert-image-to-x-face-command
165              (shell-quote-argument (expand-file-name file))))))
166
167 ;;;###autoload
168 (defun gnus-face-from-file (file)
169   "Return a Face header based on an image FILE.
170
171 Depending on `gnus-convert-image-to-face-command' it may accept
172 different input formats."
173   (interactive "fImage file name: ")
174   (when (file-exists-p file)
175     (let ((done nil)
176           (attempt "")
177           (quant 16))
178       (while (and (not done)
179                   (> quant 1))
180         (setq attempt
181               (let ((coding-system-for-read 'binary))
182                 (gnus-shell-command-to-string
183                  (format gnus-convert-image-to-face-command
184                          (shell-quote-argument (expand-file-name file))
185                          quant))))
186         (if (> (length attempt) 726)
187             (progn
188               (setq quant (- quant (if (< quant 10) 1 2)))
189               (gnus-message 9 "Length %d; trying quant %d"
190                             (length attempt) quant))
191           (setq done t)))
192       (if done
193           (mm-with-unibyte-buffer
194             (insert attempt)
195             (gnus-face-encode))
196         nil))))
197
198 (defun gnus-face-encode ()
199   (let ((step 72))
200     (base64-encode-region (point-min) (point-max))
201     (goto-char (point-min))
202     (while (search-forward "\n" nil t)
203       (replace-match ""))
204     (goto-char (point-min))
205     (while (> (- (point-max) (point))
206               step)
207       (forward-char step)
208       (insert "\n ")
209       (setq step 76))
210     (buffer-string)))
211
212 ;;;###autoload
213 (defun gnus-convert-face-to-png (face)
214   "Convert FACE (which is base64-encoded) to a PNG.
215 The PNG is returned as a string."
216   (mm-with-unibyte-buffer
217     (insert face)
218     (ignore-errors
219       (base64-decode-region (point-min) (point-max)))
220     (buffer-string)))
221
222 ;;;###autoload
223 (defun gnus-convert-png-to-face (file)
224   "Convert FILE to a Face.
225 FILE should be a PNG file that's 48x48 and smaller than or equal to
226 726 bytes."
227   (mm-with-unibyte-buffer
228     (insert-file-contents file)
229     (when (> (buffer-size) 726)
230       (error "The file is %d bytes long, which is too long"
231              (buffer-size)))
232     (gnus-face-encode)))
233
234 ;;;###autoload
235 (defun gnus-random-face ()
236   "Return randomly chosen Face from `gnus-face-directory'.
237
238 Files matching `gnus-face-omit-files' are not considered."
239   (interactive)
240   (gnus--random-face-with-type gnus-face-directory "\\.png$"
241                          gnus-face-omit-files
242                          'gnus-convert-png-to-face))
243
244 ;;;###autoload
245 (defun gnus-insert-random-face-header ()
246   "Insert a randome Face header from `gnus-face-directory'."
247   (gnus--insert-random-face-with-type 'gnus-random-face 'Face))
248
249 (defface gnus-x-face '((t (:foreground "black" :background "white")))
250   "Face to show X-Face.
251 The colors from this face are used as the foreground and background
252 colors of the displayed X-Faces."
253   :group 'gnus-article-headers)
254
255 (declare-function article-narrow-to-head   "gnus-art" ())
256 (declare-function gnus-article-goto-header "gnus-art" (header))
257 (declare-function gnus-add-image           "gnus-art" (category image))
258 (declare-function gnus-add-wash-type       "gnus-art" (type))
259
260 (defun gnus-display-x-face-in-from (data)
261   "Display the X-Face DATA in the From header."
262   (require 'gnus-art)
263   (let (pbm)
264     (when (or (gnus-image-type-available-p 'xface)
265               (and (gnus-image-type-available-p 'pbm)
266                    (setq pbm (uncompface data))))
267       (save-excursion
268         (save-restriction
269           (article-narrow-to-head)
270           (gnus-article-goto-header "from")
271           (when (bobp)
272             (insert "From: [no `from' set]\n")
273             (forward-char -17))
274           (gnus-add-image
275            'xface
276            (gnus-put-image
277             (if (gnus-image-type-available-p 'xface)
278                 (apply 'gnus-create-image (concat "X-Face: " data) 'xface t
279                        (cdr (assq 'xface gnus-face-properties-alist)))
280               (apply 'gnus-create-image pbm 'pbm t
281                      (cdr (assq 'pbm gnus-face-properties-alist))))
282             nil 'xface))
283           (gnus-add-wash-type 'xface))))))
284
285 (defun gnus-grab-cam-x-face ()
286   "Grab a picture off the camera and make it into an X-Face."
287   (interactive)
288   (shell-command "xawtv-remote snap ppm")
289   (let ((file nil))
290     (while (null (setq file (directory-files "/tftpboot/sparky/tmp"
291                                              t "snap.*ppm")))
292       (sleep-for 1))
293     (setq file (car file))
294     (with-temp-buffer
295       (shell-command
296        (format "pnmcut -left 110 -top 30 -width 144 -height 144 '%s' | ppmnorm 2>/dev/null | pnmscale -width 48 | ppmtopgm | pgmtopbm -threshold -value 0.92 | pbmtoxbm | compface"
297                file)
298        (current-buffer))
299       ;;(sleep-for 3)
300       (delete-file file)
301       (buffer-string))))
302
303 (defun gnus-grab-cam-face ()
304   "Grab a picture off the camera and make it into an X-Face."
305   (interactive)
306   (shell-command "xawtv-remote snap ppm")
307   (let ((file nil)
308         result)
309     (while (null (setq file (directory-files "/tftpboot/sparky/tmp"
310                                              t "snap.*ppm")))
311       (sleep-for 1))
312     (setq file (car file))
313     (shell-command
314      (format "pnmcut -left 110 -top 30 -width 144 -height 144 '%s' | pnmscale -width 48 -height 48 | ppmtopgm > /tmp/gnus.face.ppm"
315              file))
316     (let ((gnus-convert-image-to-face-command
317            (format "cat '%%s' | ppmquant %%d | ppmchange %s | pnmtopng"
318                    (gnus-fun-ppm-change-string))))
319       (setq result (gnus-face-from-file "/tmp/gnus.face.ppm")))
320     (delete-file file)
321     ;;(delete-file "/tmp/gnus.face.ppm")
322     result))
323
324 (defun gnus-fun-ppm-change-string ()
325   (let* ((possibilities '("%02x0000" "00%02x00" "0000%02x"
326                           "%02x%02x00" "00%02x%02x" "%02x00%02x"))
327          (format (concat "'#%02x%02x%02x' '#"
328                          (nth (random 6) possibilities)
329                          "'"))
330          (values nil))
331   (dotimes (i 255)
332     (push (format format i i i i i i)
333           values))
334   (mapconcat 'identity values " ")))
335
336 (defun gnus-funcall-no-warning (function &rest args)
337   (when (fboundp function)
338     (apply function args)))
339
340 (provide 'gnus-fun)
341
342 ;;; gnus-fun.el ends here