summaryrefslogtreecommitdiff
path: root/jabber-events.el
diff options
context:
space:
mode:
Diffstat (limited to 'jabber-events.el')
-rw-r--r--jabber-events.el245
1 files changed, 245 insertions, 0 deletions
diff --git a/jabber-events.el b/jabber-events.el
new file mode 100644
index 0000000..f78030a
--- /dev/null
+++ b/jabber-events.el
@@ -0,0 +1,245 @@
+;;; 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 '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