File: jabber-events.el

package info (click to toggle)
emacs-jabber 0.8.0-2
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 1,564 kB
  • ctags: 899
  • sloc: lisp: 10,857; sh: 686; makefile: 109
file content (246 lines) | stat: -rw-r--r-- 9,055 bytes parent folder | download | duplicates (2)
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
;;; jabber-events.el --- Message events (JEP-0022) implementation

;; Copyright (C) 2005, 2008  Magnus Henoch

;; Author: Magnus Henoch <mange@freemail.hu>

;; 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.

(require 'jabber-autoloads)
(require 'cl)

(defgroup jabber-events nil 
  "Message events and notifications."
  :group 'jabber)

;;; INCOMING
;;; Code for requesting event notifications from others and handling
;;; them.

(defcustom jabber-events-request-these '(offline
					 delivered
					 displayed
					 composing)
  "Request these kinds of event notifications from others."
  :type '(set (const :tag "Delivered to offline storage" offline)
	      (const :tag "Delivered to user's client" delivered)
	      (const :tag "Displayed to user" displayed)
	      (const :tag "User is typing a reply" composing))
  :group 'jabber-events)

(defvar jabber-events-composing-p nil
  "Is the other person composing a message?")
(make-variable-buffer-local 'jabber-events-composing-p)

(defvar jabber-events-arrived nil
  "In what way has the message reached the recipient?
Possible values are nil (no information available), offline
\(queued for delivery when recipient is online), delivered
\(message has reached the client) and displayed (user is
probably reading the message).")
(make-variable-buffer-local 'jabber-events-arrived)

(defvar jabber-events-message ""
  "Human-readable presentation of event information")
(make-variable-buffer-local 'jabber-events-message)

(defun jabber-events-update-message ()
  (setq jabber-events-message 
	(concat (cdr (assq jabber-events-arrived
			   '((offline . "In offline storage")
			     (delivered . "Delivered")
			     (displayed . "Displayed"))))
		(when jabber-events-composing-p
		  " (typing a message)"))))

(add-hook 'jabber-chat-send-hooks 'jabber-events-when-sending)
(defun jabber-events-when-sending (text id)
  (setq jabber-events-arrived nil)
  (jabber-events-update-message)
  `((x ((xmlns . "jabber:x:event"))
       ,@(mapcar #'list jabber-events-request-these))))

;;; OUTGOING
;;; Code for handling requests for event notifications and providing
;;; them, modulo user preferences.

(defcustom jabber-events-confirm-delivered t
  "Send delivery confirmation if requested?"
  :group 'jabber-events
  :type 'boolean)

(defcustom jabber-events-confirm-displayed t
  "Send display confirmation if requested?"
  :group 'jabber-events
  :type 'boolean)

(defcustom jabber-events-confirm-composing t
  "Send notifications about typing a reply?"
  :group 'jabber-events
  :type 'boolean)

(defvar jabber-events-requested ()
  "List of events requested")
(make-variable-buffer-local 'jabber-events-requested)

(defvar jabber-events-last-id nil
  "Id of last message received, or nil if none.")
(make-variable-buffer-local 'jabber-events-last-id)

(defvar jabber-events-delivery-confirmed nil
  "Has delivery confirmation been sent?")
(make-variable-buffer-local 'jabber-events-delivery-confirmed)

(defvar jabber-events-display-confirmed nil
  "Has display confirmation been sent?")
(make-variable-buffer-local 'jabber-events-display-confirmed)

(defvar jabber-events-composing-sent nil
  "Has composing notification been sent?
It can be sent and cancelled several times.")

(add-hook 'window-configuration-change-hook
	  'jabber-events-confirm-display)
(defun jabber-events-confirm-display ()
  "Send display confirmation if appropriate.
That is, if user allows it, if the other user requested it,
and it hasn't been sent before."
  (walk-windows #'jabber-events-confirm-display-in-window))

(defun jabber-events-confirm-display-in-window (window)
  (with-current-buffer (window-buffer window)
    (when (and jabber-events-confirm-displayed
	       (not jabber-events-display-confirmed)
	       (memq 'displayed jabber-events-requested)
	       ;; XXX: if jabber-events-requested is non-nil, how can
	       ;; jabber-chatting-with be nil?  See
	       ;; http://sourceforge.net/tracker/index.php?func=detail&aid=1872560&group_id=88346&atid=586350
	       jabber-chatting-with
	       ;; don't send to bare jids
	       (jabber-jid-resource jabber-chatting-with))
      (jabber-send-sexp 
       jabber-buffer-connection
       `(message 
	 ((to . ,jabber-chatting-with))
	 (x ((xmlns . "jabber:x:event"))
	    (displayed)
	    (id () ,jabber-events-last-id))))
      (setq jabber-events-display-confirmed t))))

(defun jabber-events-after-change ()
  (let ((composing-now (not (= (point-max) jabber-point-insert))))
    (when (and jabber-events-confirm-composing
	       jabber-chatting-with
	       (not (eq composing-now jabber-events-composing-sent)))
      (jabber-send-sexp 
       jabber-buffer-connection
       `(message 
	 ((to . ,jabber-chatting-with))
	 (x ((xmlns . "jabber:x:event"))
	    ,@(if composing-now '((composing)) nil)
	    (id () ,jabber-events-last-id))))
      (setq jabber-events-composing-sent composing-now))))

;;; COMMON

;; Add function last in chain, so a chat buffer is already created.
(add-to-list 'jabber-message-chain 'jabber-handle-incoming-message-events t)

(defun jabber-handle-incoming-message-events (jc xml-data)
  (when (and (not (jabber-muc-message-p xml-data))
	     (get-buffer (jabber-chat-get-buffer (jabber-xml-get-attribute xml-data 'from))))
    (with-current-buffer (jabber-chat-get-buffer (jabber-xml-get-attribute xml-data 'from))
      (let ((x (find "jabber:x:event"
		     (jabber-xml-get-children xml-data 'x)
		     :key #'(lambda (x) (jabber-xml-get-attribute x 'xmlns))
		     :test #'string=)))
	(cond
	 ;; If we get an error message, we shouldn't report any
	 ;; events, as the requests are mirrored from us.
	 ((string= (jabber-xml-get-attribute xml-data 'type) "error")
	  (remove-hook 'post-command-hook 'jabber-events-after-change t)
	  (setq jabber-events-requested nil))
	  
	 ;; If there's a body, it's not an incoming message event.
	 ((jabber-xml-get-children xml-data 'body)
	  ;; User is done composing, obviously.
	  (setq jabber-events-composing-p nil)
	  (jabber-events-update-message)

	  ;; Reset variables
	  (setq jabber-events-display-confirmed nil)
	  (setq jabber-events-delivery-confirmed nil)

	  ;; User requests message events
	  (setq jabber-events-requested 
		;; There might be empty strings in the XML data,
		;; which car chokes on.  Having nil values in
		;; the list won't hurt, therefore car-safe.
		(mapcar #'car-safe 
			(jabber-xml-node-children x)))
	  (setq jabber-events-last-id (jabber-xml-get-attribute
				       xml-data 'id))

	  ;; Send notifications we already know about
	  (flet ((send-notification 
		  (type)
		  (jabber-send-sexp 
		   jc
		   `(message 
		     ((to . ,(jabber-xml-get-attribute xml-data 'from)))
		     (x ((xmlns . "jabber:x:event"))
			(,type)
			(id () ,jabber-events-last-id))))))
	    ;; Send delivery confirmation if appropriate
	    (when (and jabber-events-confirm-delivered
		       (memq 'delivered jabber-events-requested))
	      (send-notification 'delivered)
	      (setq jabber-events-delivery-confirmed t))

	    ;; Send display confirmation if appropriate
	    (when (and jabber-events-confirm-displayed
		       (get-buffer-window (current-buffer) 'visible)
		       (memq 'displayed jabber-events-requested))
	      (send-notification 'displayed)
	      (setq jabber-events-display-confirmed t))

	    ;; Set up hooks for composition notification
	    (when (and jabber-events-confirm-composing
		       (memq 'composing jabber-events-requested))
	      (add-hook 'post-command-hook 'jabber-events-after-change
			nil t))))
	 (t
	  ;; So it has no body.  If it's a message event,
	  ;; the <x/> node should be the only child of the
	  ;; message, and it should contain an <id/> node.
	  ;; We check the latter.
	  (when (and x (jabber-xml-get-children x 'id))
	    ;; Currently we don't care about the <id/> node.
	
	    ;; There's only one node except for the id.
	    (unless
		(dolist (possible-node '(offline delivered displayed))
		  (when (jabber-xml-get-children x possible-node)
		    (setq jabber-events-arrived possible-node)
		    (jabber-events-update-message)
		    (return t)))
	      ;; Or maybe even zero, which is a negative composing node.
	      (setq jabber-events-composing-p
		    (not (null (jabber-xml-get-children x 'composing))))
	      (jabber-events-update-message)))))))))

(provide 'jabber-events)
;; arch-tag: 7b6e61fe-a9b3-11d9-afca-000a95c2fcd0