1 ;;; jabber-chatstate.el --- Chat state notification (XEP-0085) implementation -*- lexical-binding: t; -*-
3 ;; Author: Ami Fischman <ami@fischman.org>
4 ;; (based entirely on jabber-events.el by Magnus Henoch <mange@freemail.hu>)
6 ;; This file is free software; you can redistribute it and/or modify
7 ;; it under the terms of the GNU General Public License as published by
8 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; This file is distributed in the hope that it will be useful,
12 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;; GNU General Public License for more details.
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with GNU Emacs; see the file COPYING. If not, write to
18 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 ;; Boston, MA 02111-1307, USA.
22 ;; - Currently only active/composing notifications are /sent/ though all 5
23 ;; notifications are handled on receipt.
27 (defgroup jabber-chatstates nil
28 "Chat state notifications."
31 (defconst jabber-chatstates-xmlns "http://jabber.org/protocol/chatstates"
32 "XML namespace for the chatstates feature.")
34 (defcustom jabber-chatstates-confirm t
35 "Send notifications about chat states?"
36 :group 'jabber-chatstates
39 (defvar jabber-chatstates-requested 'first-time
40 "Whether or not chat states notification was requested.
41 This is one of the following:
42 first-time - send state in first stanza, then switch to nil
44 nil - don't send states")
45 (make-variable-buffer-local 'jabber-chatstates-requested)
47 (defvar jabber-chatstates-last-state nil
48 "The last seen chat state.")
49 (make-variable-buffer-local 'jabber-chatstates-last-state)
51 (defvar jabber-chatstates-message ""
52 "Human-readable presentation of chat state information.")
53 (make-variable-buffer-local 'jabber-chatstates-message)
56 ;;; Code for requesting chat state notifications from others and handling
59 (defun jabber-chatstates-update-message ()
60 (setq jabber-chatstates-message
61 (if (and jabber-chatstates-last-state
62 (not (eq 'active jabber-chatstates-last-state)))
63 (format " (%s)" (symbol-name jabber-chatstates-last-state))
66 (add-hook 'jabber-chat-send-hooks 'jabber-chatstates-when-sending)
67 (defun jabber-chatstates-when-sending (text id)
68 (jabber-chatstates-update-message)
69 (jabber-chatstates-stop-timer)
70 (when (and jabber-chatstates-confirm jabber-chatstates-requested)
71 (when (eq jabber-chatstates-requested 'first-time)
72 ;; don't send more notifications until we know that the other
74 (setq jabber-chatstates-requested nil))
75 (setq jabber-chatstates-composing-sent nil)
76 `((active ((xmlns . ,jabber-chatstates-xmlns))))))
79 ;;; Code for handling requests for chat state notifications and providing
80 ;;; them, modulo user preferences.
82 (defvar jabber-chatstates-composing-sent nil
83 "Has composing notification been sent?
84 It can be sent and cancelled several times.")
85 (make-variable-buffer-local 'jabber-chatstates-composing-sent)
87 (defvar jabber-chatstates-paused-timer nil
88 "Timer that counts down from 'composing state to 'paused.")
89 (make-variable-buffer-local 'jabber-chatstates-paused-timer)
91 (defun jabber-chatstates-stop-timer ()
92 "Stop the 'paused timer."
93 (when jabber-chatstates-paused-timer
94 (cancel-timer jabber-chatstates-paused-timer)))
96 (defun jabber-chatstates-kick-timer ()
97 "Start (or restart) the 'paused timer as approriate."
98 (jabber-chatstates-stop-timer)
99 (setq jabber-chatstates-paused-timer
100 (run-with-timer 5 nil 'jabber-chatstates-send-paused)))
102 (defun jabber-chatstates-send-paused ()
103 "Send an 'paused state notification."
104 (when (and jabber-chatstates-requested jabber-chatting-with)
105 (setq jabber-chatstates-composing-sent nil)
106 (jabber-send-sexp-if-connected
107 jabber-buffer-connection
109 ((to . ,jabber-chatting-with)
111 (paused ((xmlns . ,jabber-chatstates-xmlns)))))))
113 (defun jabber-chatstates-after-change ()
114 (let* ((composing-now (not (= (point-max) jabber-point-insert)))
115 (state (if composing-now 'composing 'active)))
116 (when (and jabber-chatstates-confirm
118 jabber-chatstates-requested
119 (not (eq composing-now jabber-chatstates-composing-sent)))
120 (jabber-send-sexp-if-connected
121 jabber-buffer-connection
123 ((to . ,jabber-chatting-with)
125 (,state ((xmlns . ,jabber-chatstates-xmlns)))))
126 (when (setq jabber-chatstates-composing-sent composing-now)
127 (jabber-chatstates-kick-timer)))))
131 (defun jabber-handle-incoming-message-chatstates (jc xml-data)
132 (when (get-buffer (jabber-chat-get-buffer (jabber-xml-get-attribute xml-data 'from)))
133 (with-current-buffer (jabber-chat-get-buffer (jabber-xml-get-attribute xml-data 'from))
135 ;; If we get an error message, we shouldn't report any
136 ;; events, as the requests are mirrored from us.
137 ((string= (jabber-xml-get-attribute xml-data 'type) "error")
138 (remove-hook 'post-command-hook 'jabber-chatstates-after-change t)
139 (setq jabber-chatstates-requested nil))
145 (cl-find jabber-chatstates-xmlns
146 (jabber-xml-node-children xml-data)
147 :key #'(lambda (x) (jabber-xml-get-attribute x 'xmlns))
149 (jabber-xml-node-name node))
151 ;; XXX: this is how we interoperate with
152 ;; Google Talk. We should really use a
153 ;; namespace-aware XML parser.
154 (cl-find jabber-chatstates-xmlns
155 (jabber-xml-node-children xml-data)
156 :key #'(lambda (x) (jabber-xml-get-attribute x 'xmlns:cha))
159 ;; Strip the "cha:" prefix
160 (let ((name (symbol-name (jabber-xml-node-name node))))
161 (when (> (length name) 4)
162 (intern (substring name 4)))))))))
163 ;; Set up hooks for composition notification
164 (when (and jabber-chatstates-confirm state)
165 (setq jabber-chatstates-requested t)
166 (add-hook 'post-command-hook 'jabber-chatstates-after-change nil t))
168 (setq jabber-chatstates-last-state state)
169 (jabber-chatstates-update-message)))))))
171 ;; Add function last in chain, so a chat buffer is already created.
172 (add-to-list 'jabber-message-chain 'jabber-handle-incoming-message-chatstates t)
174 (jabber-disco-advertise-feature "http://jabber.org/protocol/chatstates")
176 (provide 'jabber-chatstates)
177 ;; arch-tag: d879de90-51e1-11dc-909d-000a95c2fcd0