1 ;; jabber-tox.el - Jingle support using TOX
3 ;; Copyright (C) 2008 - Magnus Henoch - mange@freemail.hu
5 ;; This file is (soon) a part of jabber.el.
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program; if not, write to the Free Software
19 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 (defconst jingle-ns
"http://www.xmpp.org/extensions/xep-0166.html#ns"
26 "Jingle namespace (XEP-0166)")
28 (defconst jingle-error-ns
"http://www.xmpp.org/extensions/xep-0166.html#ns-errors"
29 "Jingle error namespace (XEP-0166)")
31 (defconst jingle-audio-ns
"http://www.xmpp.org/extensions/xep-0167.html#ns"
32 "Jingle Audio namespace (XEP-0167)")
34 (defconst jingle-ice-udp-ns
"http://www.xmpp.org/extensions/xep-0176.html#ns-udp"
35 "Jingle ICE namespace (XEP-0176)")
37 (defconst jingle-udp-ns
"http://www.xmpp.org/extensions/xep-0177.html"
38 "Jingle Raw UDP namespace (XEP-0177)")
40 (defvar jingle-acct-sid-map
(make-hash-table :test
'equal
)
41 "Mapping from pairs of JIDs and Jingle SIDs to FSMs.
42 The JID is the full JID of the account using the session.")
44 (defconst tox-name
"net.sourceforge.emacs-jabber.Tox"
45 "Well-known D-BUS name of the tox service.")
47 (defconst tox-path
"/net/sourceforge/emacs_jabber/Tox"
48 "Well-known path of the main Tox object.")
50 (defconst tox-interface
"net.sourceforge.emacs_jabber.Tox"
51 "Interface of main Tox object.")
53 (defconst tox-session-interface
"net.sourceforge.emacs_jabber.ToxSession"
54 "Interface of ToxSession object.")
57 :start
((jc sid role jid
) "Start a Jingle FSM.
58 \(Specifically, for Jingle Audio, as that's all we support for now.)
59 JC is the account we're using.
60 SID is a string, the session ID.
61 ROLE is either :initiator or :target.
62 JID is the full JID of the partner."
64 (list :jc jc
:jid jid
:sid sid
:role role
))))
66 (define-enter-state jingle
:allocate
68 ;; Allocate a ToxSession object.
70 (dbus-call-method :session tox-name tox-path tox-interface
72 :byte
3)) ;3=bidirectional stream
73 ;; Find the codecs that we support
75 (dbus-call-method :session tox-name tox-session tox-session-interface
77 (fsm-debug-output "tox-session: %S, our-codecs: %S" tox-session our-codecs
)
78 ;; Set up the pipeline, so we can search for transport candidates.
79 (fsm-debug-output "About to call SetDefaultAudioSink")
80 (dbus-call-method :session tox-name tox-session tox-session-interface
81 "SetDefaultAudioSink")
82 (fsm-debug-output "About to call SetOggVorbisAudioSource")
83 (dbus-call-method :session tox-name tox-session tox-session-interface
84 "SetOggVorbisAudioSource"
85 "/home/magnus/musik/computervirus.ogg")
86 ;; There, now we just wait for the NativeCandidatesPrepared signal...
87 ;; NO! Don't do like those dead people do! That signal will never
88 ;; come. I don't know why, though...
89 (fsm-debug-output "About to register signal")
90 (dbus-register-signal :session tox-name tox-session tox-session-interface
91 "NativeCandidatesPrepared"
92 (lexical-let ((fsm fsm
))
96 (cons :native-candidates-prepared components
)))))
97 ;; This is more like it. At least it will provide us with some
98 ;; debugging information.
99 (dbus-register-signal :session tox-name tox-session tox-session-interface
101 (lexical-let ((fsm fsm
))
105 (cons :new-native-candidate components
)))))
106 (fsm-debug-output "Waiting")
110 :tox-session tox-session
)
111 :our-codecs our-codecs
)
114 (define-enter-state jingle nil
117 (let ((tox-session (plist-get state-data
:tox-session
)))
120 (dbus-call-method :session tox-name tox-session tox-session-interface
124 (define-state jingle
:allocate
125 (fsm state-data event callback
)
126 (case (car-safe event
)
127 (:new-native-candidate
128 (setq state-data
(plist-put state-data
:native-candidates
(list (cdr event
))))
129 (list (ecase (plist-get state-data
:role
)
136 (:native-candidates-prepared
137 ;; this code is currently unnecessary, as we never get the
138 ;; native-candidates-prepared signal
139 (let ((components (cdr event
)))
140 (fsm-debug-output "The native candidates are %S" components
)
141 (setq state-data
(plist-put state-data
:native-candidates components
))
142 (list (ecase (plist-get state-data
:role
)
152 (define-enter-state jingle
:initiate
154 (let ((jc (plist-get state-data
:jc
))
155 (jid (plist-get state-data
:jid
))
156 (sid (plist-get state-data
:sid
)))
157 (jabber-send-iq jc jid
"set"
158 `(jingle ((xmlns .
,jingle-ns
)
159 (action .
"session-initiate")
160 (initiator .
,(jabber-connection-jid jc
))
163 ((creator .
,(jabber-connection-bare-jid jc
))
165 (senders .
"initiator"))
166 (transport ((xmlns .
,jingle-ice-udp-ns
)))))
168 (fsm-send-sync fsm
(cons :iq-result iq
)))
171 (fsm-send-sync fsm
(cons :iq-error iq
)))
173 (list state-data nil
)))
175 (define-state jingle
:initiate
176 (fsm state-data event callback
)
177 (case (car-safe event
)
179 ;; Receiver provisionally accepted the session request. Move on
181 (list :pending state-data
))
183 (message "Couldn't initiate Jingle audio session: %s"
184 (jabber-parse-error (jabber-iq-error (cdr event
))))
185 (list nil state-data
))))
187 (define-state jingle
:wait-for-initiate
188 (fsm state-data event callback
)
189 (case (car-safe event
)
191 (let* ((jc (plist-get state-data
:jc
))
193 (from (jabber-xml-get-attribute iq
'from
))
194 (id (jabber-xml-get-attribute iq
'id
))
195 (jingle (jabber-iq-query iq
))
196 (contents (car (jabber-xml-get-children jingle
'content
)))
197 (audio-content (find jingle-audio-ns
(jabber-xml-node-children contents
)
199 :key
'jabber-xml-get-xmlns
))
200 (payload-types (jabber-xml-get-children audio-content
'payload-type
)))
202 ;; Make sure audio is in the list of contents. We can
203 ;; negotiate away other content types later.
204 ((null audio-content
)
205 (jabber-send-iq-error jc from id
208 'feature-not-implemented
210 `((unsupported-content ((xmlns .
,jingle-error-ns
)))))
211 (list nil state-data
))
213 ;; Make sure ICE is in the list of transports.
214 ((not (member* jingle-ice-udp-ns
215 (jabber-xml-get-children audio-content
'transport
)
217 :key
'jabber-xml-get-xmlns
))
218 (jabber-send-iq-error jc from id
221 'feature-not-implemented
223 `((unsupported-transports ((xmlns .
,jingle-error-ns
)))))
224 (list nil state-data
))
227 ;; Allocate a new ToxSession object.
228 (dbus-call-method nil tox-name tox-path tox-interface
230 (lexical-let ((jingle-fsm fsm
))
231 (lambda (dbus-fsm msg
)
232 (ecase (dbus-message-type msg
)
236 (dbus-error-to-string msg
))))
240 (first (dbus-message-body-values msg
))))))))
243 (list :tox-wait
(plist-put
244 (plist-put state-data
:iq-id id
)
245 :payload-types payload-types
))))))))
247 (define-state jingle
:tox-wait
248 (fsm state-data event callback
)
249 ;; Wait for our ToxSession to be allocated
250 (case (car-safe event
)
252 ;; Some error occurred. Bail out.
253 (let ((jc (plist-get state-data
:jc
))
254 (jid (plist-get state-data
:jid
))
255 (id (plist-get state-data
:iq-id
)))
256 (jabber-send-iq-error jc jid id nil
"wait" 'internal-server-error
(cdr event
))
258 (list nil state-data
)))
261 ;; ToxSession created.
262 (let ((tox-session (cdr event
)))
263 (setq state-data
(plist-put state-data
:tox-session tox-session
))
265 ;; Tell tox what codecs the remote side supports
266 (dbus-call-method nil tox-name tox-session tox-session-interface
269 '((array (struct int32 string byte uint32 uint32
(array (dict-entry string string
)))))
272 (jabber-xml-let-attributes
273 (id name clockrate channels
) pt
274 (list id name
0 clockrate channels
277 (jabber-xml-let-attributes
280 (jabber-xml-get-children pt
'parameter
)))))
281 (plist-get state-data
:payload-types
)))
283 ;; Check if we have any codecs in common
284 (dbus-call-method nil tox-name tox-session tox-session-interface
285 "GetCodecIntersection"
286 (lexical-let ((jingle-fsm fsm
))
287 (lambda (dbus-fsm msg
)
288 (ecase (dbus-message-type msg
)
292 (dbus-error-to-string msg
))))
296 (first (dbus-message-body-values msg
)))))))))
298 (list :wait-for-codec-intersection state-data
)))))
300 (define-state jingle
:wait-for-codec-intersection
301 (fsm state-data event callback
)
303 ;; ;; Good enough, provisionally accept.
304 ;; (jabber-send-iq jc from "result" ()
305 ;; nil nil nil nil id)
306 ;; (list :pending state-data)))))))
310 (add-to-list 'jabber-iq-set-xmlns-alist
311 (cons jingle-ns
'jabber-jingle-incoming-iq
))
312 (defun jabber-jingle-incoming-iq (jc iq
)
313 (jabber-xml-let-attributes
314 (sid action
) (jabber-iq-query iq
)
315 (unless (and sid action
)
316 (jabber-signal-error "modify" 'bad-request
))
317 (let ((fsm (gethash (cons (jabber-connection-jid jc
) sid
) jingle-acct-sid-map
)))
320 (fsm-send-sync fsm
(cons :iq-set iq
)))
321 ((string= action
"session-initiate")
322 (setq fsm
(start-jingle jc sid
:target
(jabber-xml-get-attribute iq
'from
)))
323 (puthash (cons (jabber-connection-jid jc
) sid
) fsm jingle-acct-sid-map
)
324 (fsm-send-sync fsm
(cons :iq-set iq
)))
326 (jabber-signal-error "modify" 'unexpected-request
327 (format "Session \"%s\" unknown" sid
)))))))
329 (defun jabber-jingle-start-audio-session (jc jid
)
331 (list (jabber-read-account)
332 (jabber-read-jid-completing "Voice call to: " nil nil nil
'full
)))
333 (let* ((sid (apply 'format
"emacs-sid-%d.%d.%d" (current-time)))
334 (fsm (start-jingle jc sid
:initiator jid
)))
335 (puthash (cons (jabber-connection-jid jc
) sid
) fsm jingle-acct-sid-map
)))
338 (provide 'jabber-tox
)