aboutsummaryrefslogtreecommitdiff
path: root/gnus-desktop-notify.el
blob: c8d77c140d12a735bea2443c0467d062ebfd20ed (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
;;; gnus-desktop-notify.el --- Gnus Desktop Notification global minor mode

;; Author: Yuri D'Elia <wavexx AT thregr.org>
;; Contributors: Philipp Haselwarter <philipp.haselwarter AT gmx.de>
;; Version: 1.4
;; URL: http://www.thregr.org/~wavexx/software/gnus-desktop-notify.el/
;; GIT: git://src.thregr.org/gnus-desktop-notify.el/
;; Package-Requires: ((gnus "1.0"))
;; Package-Suggests: ((alert "1.0"))

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;;
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Change Log:

;; 1.4:
;; * Use `alert' package by default if available (`gnus-desktop-notify-alert').
;;
;; 1.3:
;; * Use `notifications' in emacs 24 (if available) by default (new function
;;   `gnus-desktop-notify-dbus').
;; * Renamed `gnus-desktop-notify-send' to `gnus-desktop-notify-behavior'.
;; * `gnus-desktop-notify-behavior' is now consistent across all notification
;;   functions (exec/send/dbus).
;;
;; 1.2:
;; * Collapse group names by default (see
;;   `gnus-desktop-notify-uncollapsed-levels').

;;; Commentary:

;; Desktop notification integration in Gnus!? Ohh goody!
;;
;; ``gnus-desktop-notify.el`` provides a simple mechanism to notify the user
;; when new messages are received. For basic usage, to be used in conjunction
;; with `gnus-daemon', put the following:
;;
;; (require 'gnus-desktop-notify)
;; (gnus-desktop-notify-mode)
;; (gnus-demon-add-scanmail)
;;
;; into your ``.gnus`` file. The default is to use `alert' if available, which
;; works on every operating system and allows the user to customize the
;; notification through emacs. See https://github.com/jwiegley/alert#for-users
;; for further info. If not available, the `notifications' library (part of
;; emacs >= 24) is used, so no external dependencies are required. With emacs
;; <= 23 instead the generic ``notify-send`` program is used, which (in Debian
;; or Ubuntu) is available in the ``libnotify-bin`` package.
;;
;; You can also call any program directly by changing the
;; `gnus-desktop-notify-exec-program' variable, or change the behavior entirely
;; by setting a different `gnus-desktop-notify-function' function.
;;
;; By default, all groups are notified when new messages are received. You can
;; exclude a single group by setting the `group-notify' group parameter to
;; t. You can also selectively monitor groups instead by changing the
;; `gnus-desktop-notify-groups' variable to `gnus-desktop-notify-explicit' and
;; then manually selecting which groups to include. Press 'G c' in the group
;; buffer to customize group parameters interactively.
;;
;; The behavior of the notification can be tuned by changing the
;; `gnus-desktop-notify-behavior' variable.
;;
;; See the `gnus-desktop-notify' customization group for more details.
;;
;; Feel free to send suggestions and patches to wavexx AT thregr.org

;;; Code:
(require 'assoc)
(require 'gnus-group)
(require 'format-spec)
(unless (require 'alert nil t)
  (require 'notifications nil t))

(defgroup gnus-desktop-notify nil
  "Gnus external notification framework"
  :group 'gnus)

;;;###autoload
(define-minor-mode gnus-desktop-notify-mode
  "Gnus Desktop Notification mode uses libnotify's 'notify-send'
program to generate popup messages or call external executables
whenever a group receives new messages through gnus-demon (see
`gnus-demon-add-handler').

  You can actually call any program by changing the
`gnus-desktop-notify-exec-program' variable, or change the
behavior entirely by setting a different
`gnus-desktop-notify-function' function.

  See the `gnus-desktop-notify' customization group for more
details."
  :init-value nil
  :group 'gnus-desktop-notify
  :require 'gnus
  :global t
  (cond
    (gnus-desktop-notify-mode
      (add-hook 'gnus-after-getting-new-news-hook 'gnus-desktop-notify-check)
      (add-hook 'gnus-started-hook 'gnus-desktop-notify-check))
    (t
      (remove-hook 'gnus-after-getting-new-news-hook 'gnus-desktop-notify-check)
      (remove-hook 'gnus-started-hook 'gnus-desktop-notify-check))))


;; Custom variables
(defcustom gnus-desktop-notify-function
  (cond ((featurep 'alert) 'gnus-desktop-notify-alert)
	((featurep 'notifications) 'gnus-desktop-notify-dbus)
	(t 'gnus-desktop-notify-send))
  "Function called when a group receives new messages. The first
argument will be an alist containing the groups and the number of
new messages. The default is to use `gnus-desktop-notify-alert'
if the `alert' package is available, `gnus-desktop-notify-dbus'
on emacs >= 24, or fallback to the generic
`gnus-desktop-notify-send' otherwise.

  The following functions available (see the documentation for
each function):

`gnus-desktop-notify-alert': use the `alert' library.
`gnus-desktop-notify-dbus': use the `notifications' library.
`gnus-desktop-notify-send': call the 'notify-send' program.
`gnus-desktop-notify-exec': call a customizable program."
  :type 'function)

(defcustom gnus-desktop-notify-exec-program "xmessage"
  "Executable called by the `gnus-desktop-notify-exec'
function. Each argument will be formatted according to
`gnus-desktop-notify-format'"
  :type 'file)

(defcustom gnus-desktop-notify-send-program
  "notify-send -i /usr/share/icons/gnome/32x32/actions/mail_new.png"
  "Path and default arguments to the 'notify-send' program (part
of libnotify's utilities)."
  :type 'file)

(defcustom gnus-desktop-notify-behavior 'gnus-desktop-notify-multi
  "Desktop notification behavior. Can be either:

'gnus-desktop-notify-single: display a single notification for
			     each group.
'gnus-desktop-notify-multi: display a multi-line notification for
			    all groups at once."
  :type 'symbol)

(defcustom gnus-desktop-notify-send-subject "New mail"
  "Text used in the notification subject when new messages are received.
Depending on your notification agent, some HTML formatting may be
supported (awesome and KDE are known to work)."
  :type 'string)

(defcustom gnus-desktop-notify-format "%n:%G"
  "Format used to generate the notification text. When using
notifications, some agents may support HTML formatting (awesome
and KDE are known to work).

%n    Number of new messages in the group
%G    Group name"
  :type 'string)

(defcustom gnus-desktop-notify-uncollapsed-levels gnus-group-uncollapsed-levels
  "Number of group name elements to leave alone when making a shortened name
for display from a group name.
Value can be `gnus-group-uncollapsed-levels', an integer or nil to
deactivate shortening completely."
  :type '(choice (const :tag "Standard `gnus-group-uncollapsed-levels'"
                        gnus-group-uncollapsed-levels)
                 (integer)
                 (const :tag "nil (deactivate feature)" nil)))

(defcustom gnus-desktop-notify-groups 'gnus-desktop-notify-all-except
  "Gnus group notification mode. Can be either:

'gnus-desktop-notify-all-except: monitor all groups by
				 default except excluded ones,
'gnus-desktop-notify-explicit: monitor only requested groups.

  Groups can be included or excluded by setting the
'group-notify' group parameter to 't'.  This can be set either in
the `gnus-parameters' variable, or interactively by pressing 'G
c' in the group buffer."
  :type 'symbol)


;; Group parameters
(gnus-define-group-parameter
  group-notify
   :type bool
   :parameter-type '(const :tag "Include/exclude this group from
the notification of new messages (depending on the value of
`gnus-desktop-notify-groups')." t))

;; Functions
(defun gnus-desktop-notify-escape-html-entities (str)
  (setq str (replace-regexp-in-string "&" "&amp;" str))
  (setq str (replace-regexp-in-string "<" "&lt;" str))
  (setq str (replace-regexp-in-string ">" "&gt;" str))
  str)

(defun gnus-desktop-notify-arg (group)
  (format-spec gnus-desktop-notify-format
    (format-spec-make
      ?n (cdr group)
      ?G (gnus-desktop-notify-escape-html-entities (car group)))))

(defun gnus-desktop-notify-exec (groups)
  "Call a program defined by `gnus-desktop-notify-exec-program'.
with each argument being a group formatted according to
`gnus-desktop-notify-format' and calling behavior is defined by
`gnus-desktop-notify-behavior'."
  (let ((groups (mapcar 'gnus-desktop-notify-arg groups)))
    (case gnus-desktop-notify-behavior
      ('gnus-desktop-notify-single
       (dolist (g groups)
	 (call-process-shell-command gnus-desktop-notify-exec-program nil 0 nil
				     (shell-quote-argument g))))
      ('gnus-desktop-notify-multi
       (call-process-shell-command gnus-desktop-notify-exec-program nil 0 nil
				   (mapconcat 'shell-quote-argument groups " "))))))

(defun gnus-desktop-notify-send (groups)
  "Call 'notify-send' (as defined by `gnus-desktop-notify-send-program'),
with the behavior defined by `gnus-desktop-notify-behavior'."
  (let ((groups (mapcar 'gnus-desktop-notify-arg groups))
	(subject (shell-quote-argument gnus-desktop-notify-send-subject)))
    (case gnus-desktop-notify-behavior
      ('gnus-desktop-notify-single
       (dolist (g groups)
	 (call-process-shell-command gnus-desktop-notify-send-program nil 0 nil "--"
				     subject (shell-quote-argument g))))
      ('gnus-desktop-notify-multi
       (call-process-shell-command gnus-desktop-notify-send-program nil 0 nil "--"
				   subject (mapconcat 'shell-quote-argument groups "\C-m"))))))

(defun gnus-desktop-notify-dbus (groups)
  "Generate a notification directly using `notifications' with
the behavior defined by `gnus-desktop-notify-behavior'."
  (let ((groups (mapcar 'gnus-desktop-notify-arg groups)))
    (case gnus-desktop-notify-behavior
      ('gnus-desktop-notify-single
       (dolist (g groups)
	 (notifications-notify :title gnus-desktop-notify-send-subject :body g)))
      ('gnus-desktop-notify-multi
       (notifications-notify :title gnus-desktop-notify-send-subject
			     :body (mapconcat 'identity groups "\C-m"))))))

(defun gnus-desktop-notify-alert (groups)
  "Generate a notification directly using `alert' with
the behavior defined by `gnus-desktop-notify-behavior'."
  (let ((groups (mapcar 'gnus-desktop-notify-arg groups)))
    (case gnus-desktop-notify-behavior
      ('gnus-desktop-notify-single
       (dolist (g groups)
	 (alert g :title gnus-desktop-notify-send-subject)))
      ('gnus-desktop-notify-multi
       (alert (mapconcat 'identity groups "\n")
	      :title gnus-desktop-notify-send-subject)))))


;; Internals
(setq gnus-desktop-notify-counts '())

(defun gnus-desktop-notify-read-count (group)
  (let ( (count (gnus-last-element (gnus-range-normalize (gnus-info-read group)))) )
    (if (listp count) (cdr count) count)))

(defun gnus-desktop-notify-check (&rest ignored)
  (interactive)
  (let ( (updated-groups '()) )
    (dolist (g gnus-newsrc-alist)
      (let* ( (name (gnus-info-group g))
	      (read (gnus-desktop-notify-read-count g))
	      (unread (gnus-group-unread name)) )
	(when (and (numberp read) (numberp unread))
	  (let ( (count (+ read unread))
		 (old-count (cdr (assoc name gnus-desktop-notify-counts)))
		 (notify (gnus-group-find-parameter name 'group-notify)) )
	    (when (or
		    (and (eq gnus-desktop-notify-groups 'gnus-desktop-notify-all-except) (not notify))
		    (and (eq gnus-desktop-notify-groups 'gnus-desktop-notify-explicit) notify))
	      (aput 'gnus-desktop-notify-counts name count)
	      (when (and
		      unread (> unread 0)
		      old-count (> count old-count))
		(setq updated-groups
		  (cons (cons (if gnus-desktop-notify-uncollapsed-levels
                          (gnus-short-group-name name gnus-desktop-notify-uncollapsed-levels)
                        name)
                      (- count old-count))
		    updated-groups))))))))
    (when (and updated-groups (not (called-interactively-p 'any)))
      (funcall gnus-desktop-notify-function updated-groups))))


(provide 'gnus-desktop-notify)
;;; gnus-desktop-notify.el ends here