diff --git a/tox/Makefile.am b/tox/Makefile.am new file mode 100644 index 0000000..66751ea --- /dev/null +++ b/tox/Makefile.am @@ -0,0 +1,31 @@ +libexec_PROGRAMS=tox +tox_SOURCES=main.c tox.c tox-session.c tox.h tox-session.h \ + $(BUILT_SOURCES) + +EXTRA_DIST=tox-object.xml tox-session.xml tox-marshal.list + +BUILT_SOURCES=tox-object-glue.h tox-session-glue.h tox-marshal.c tox-marshal.h + +tox-object-glue.h : tox-object.xml + $(DBUS_BINDING_TOOL) --mode=glib-server --output=$@ --prefix=tox $(srcdir)/tox-object.xml + +tox-session-glue.h : tox-session.xml + $(DBUS_BINDING_TOOL) --mode=glib-server --output=$@ --prefix=tox_session $(srcdir)/tox-session.xml + +tox-marshal.c : tox-marshal.list + $(GLIB_GENMARSHAL) --prefix=tox_marshal $(srcdir)/tox-marshal.list --header --body > $@.tmp + mv $@.tmp $@ + +tox-marshal.h : tox-marshal.list + $(GLIB_GENMARSHAL) --prefix=tox_marshal $(srcdir)/tox-marshal.list --header > $@.tmp + mv $@.tmp $@ + + +dbusservicedir=$(datadir)/dbus-1/services +dbusservice_DATA=tox.service +EXTRA_DIST+=tox.service.in +CLEANFILES=tox.service +tox.service: tox.service.in config.status + sed -e "s,@""libexecdir@,$(libexecdir)," $(srcdir)/tox.service.in > $@ + +dist_lisp_LISP=jabber-tox.el diff --git a/tox/README b/tox/README new file mode 100644 index 0000000..79494d7 --- /dev/null +++ b/tox/README @@ -0,0 +1,98 @@ +Tox (Talk Over XMPP, or something) is a utility for adding Jingle +functionality to a Jabber client, primarily voice communication. It +interfaces to the Jabber client using DBus. + +To actually get it to compile, you need the Farsight library. If you +use Debian, the packages "libfarsight0.1-dev" and +"gstreamer0.10-plugins-farsight" and their dependencies should be +enough. If you need to compile them yourself, look for Farsight at +http://farsight.freedesktop.org/ and Libjingle at +http://tapioca-voip.sourceforge.net/. Do _not_ use Google's +libjingle. + +The DBus interface is described below. From this you should be able +to piece together a Jingle client. Good luck! + +Tox registers the well-known name net.sourceforge.emacs-jabber.Tox +(NB: hyphen, not underscore) on the session bus. +/net/sourceforge/emacs_jabber/Tox (NB: underscore, not hyphen) is the +path to the main object. + +This object has one method in the interface +net.sourceforge.emacs_jabber.Tox (NB: underscore, not hyphen): + +object_path CreateSession(byte direction) + + Creates an audio session. direction is 1 for "send only", 2 for + "receive only", and 3 for "send and receive". The path to the new + session is returned. + +The session object in turn has methods in the interface +net.sourceforge.emacs_jabber.ToxSession (NB: underscore, not hyphen): + +void Destroy() + + Destroys the session. + +void SetDefaultAudioSink() + + Create an "autoaudiosink" GStreamer element and connect it to the + session. This usually means that you will hear things in your + speakers. + +void SetOggVorbisAudioSource(string filename) + + Set the named Ogg Vorbis file as audio source, i.e. what to send + over the session. + +void AddRemoteCandidate(array components) + + Add a transport candidate of the remote party, consisting of the + given components. "components" is an array of structs with + signature "(susqsssyyss)" and meaning: + + - Candidate ID + - Component (starting from 1) + - IP number (as a string) + - Port number + - Protocol ("tcp" or "udp") + - Protocol subtype (only "RTP" supported) + - Protocol profile (only "AVP" supported) + - Preference, between 0 and 100 + - Type. 0 means local, 1 means derived (e.g. through + STUN), 2 means relay + - Username (may be empty) + - Password (may be empty) + +signal NewNativeCandidate(array components) + + Signalled when a new local candidate has been determined. The + argument is the same as to AddRemoteCandidate. + +signal NativeCandidatesPrepared(array components) + + Signalled when the local candidates have been determined, and are + ready to send to the other party. The argument is the same as to + AddRemoteCandidate. + +void SetRemoteCodecs(array codecs) + + Set the codecs that the remote party claims to support. codecs is + an array of structs with signature "(isyuua{ss})" and meaning: + + - numeric identifier + - codec name + - media type: 0 is audio, 1 is video + - clock rate + - number of channels + - optional parameters + +array GetLocalCodecs() + + Get the codecs supported by this implementation. The return value + is of the same type as the argument to SetRemoteCodecs. + +array GetCodecIntersection() + + Get the intersection of supported codecs of remote and local + parties. The return value is like the argument to SetRemoteCodecs. diff --git a/tox/configure.ac b/tox/configure.ac new file mode 100644 index 0000000..9aff06d --- /dev/null +++ b/tox/configure.ac @@ -0,0 +1,20 @@ +AC_INIT([Talk over XMPP], [0.0.0], [emacs-jabber-general@lists.sourceforge.net], [tox]) +AM_INIT_AUTOMAKE([foreign dist-bzip2 -Wall -Werror]) + +AC_PROG_CC + +AC_PATH_PROG(DBUS_BINDING_TOOL, dbus-binding-tool, + [$am_missing_run dbus-binding-tool]) +AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal, + [$am_missing_run glib-genmarshal]) + +PKG_CHECK_MODULES(FARSIGHT, [farsight-0.1], [], AC_MSG_ERROR([farsight not found; see README])) +PKG_CHECK_MODULES(DBUS, [dbus-glib-1], [], AC_MSG_ERROR([dbus-glib not found; see README])) + +AC_SUBST(AM_CFLAGS, "$FARSIGHT_CFLAGS $DBUS_CFLAGS") +AC_SUBST(LDADD, "$FARSIGHT_LIBS $DBUS_LIBS") + +AM_PATH_LISPDIR + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/tox/jabber-tox.el b/tox/jabber-tox.el new file mode 100644 index 0000000..1513af9 --- /dev/null +++ b/tox/jabber-tox.el @@ -0,0 +1,497 @@ +;; jabber-tox.el - Jingle support using TOX + +;; Copyright (C) 2008 - Magnus Henoch - mange@freemail.hu + +;; This file is (soon) a part of jabber.el. + +;; This program 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 of the License, or +;; (at your option) any later version. + +;; This program 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 this program; if not, write to the Free Software +;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +(require 'dbus) +(require 'fsm) +(require 'cl) + +(require 'jabber-xml) + +(defconst jingle-ns "urn:xmpp:jingle:0" + "Jingle namespace (XEP-0166)") + +(defconst jingle-error-ns "urn:xmpp:jingle:errors:0" + "Jingle error namespace (XEP-0166)") + +(defconst jingle-rtp-ns "urn:xmpp:jingle:apps:rtp:0" + "Jingle RTP Sessions namespace (XEP-0167)") + +(defconst jingle-ice-udp-ns "urn:xmpp:jingle:transports:ice-udp:0" + "Jingle ICE namespace (XEP-0176)") + +(defvar jingle-acct-sid-map (make-hash-table :test 'equal) + "Mapping from pairs of JIDs and Jingle SIDs to FSMs. +The JID is the full JID of the account using the session.") + +(defconst tox-name "net.sourceforge.emacs-jabber.Tox" + "Well-known D-BUS name of the tox service.") + +(defconst tox-path "/net/sourceforge/emacs_jabber/Tox" + "Well-known path of the main Tox object.") + +(defconst tox-interface "net.sourceforge.emacs_jabber.Tox" + "Interface of main Tox object.") + +(defconst tox-session-interface "net.sourceforge.emacs_jabber.ToxSession" + "Interface of ToxSession object.") + +(defvar tox-my-ogg-answering-machine + (replace-regexp-in-string + "\n" "" + (shell-command-to-string "locate '*.ogg' | head -1")) + "The Ogg file to play to anyone who calls to us. +This should go away once we have hooked up everything properly, +with microphone and so on. (Or maybe not...)") + +(define-fsm jingle + :start ((jc sid role jid) "Start a Jingle FSM. +\(Specifically, for Jingle Audio, as that's all we support for now.) +JC is the account we're using. +SID is a string, the session ID. +ROLE is either :initiator or :target. +JID is the full JID of the partner." + (let ((state-data (list :jc jc :jid jid :sid sid :role role))) + (setq state-data (jingle-create-tox-session fsm state-data)) + (list + (ecase (plist-get state-data :role) + (:initiator + :initiate) + (:target + :wait-for-initiate)) + state-data)))) + +(defun jingle-create-tox-session (fsm state-data) + "Helper function to create a Tox session. +Accepts, modifies, and returns STATE-DATA." + ;; XXX: should this always be bidirectional? + (let* ((tox-session + (dbus-call-method :session tox-name tox-path tox-interface + "CreateSession" + :byte 3)) ;3=bidirectional stream + ;; Find the codecs that we support + (our-codecs + (dbus-call-method :session tox-name tox-session tox-session-interface + "GetLocalCodecs"))) + (setq state-data (plist-put state-data :tox-session tox-session)) + (setq state-data (plist-put state-data :our-codecs our-codecs)) + + (fsm-debug-output "tox-session: %S, our-codecs: %S" tox-session our-codecs) + ;; Set up the pipeline, so we can search for transport candidates. + (fsm-debug-output "About to call SetDefaultAudioSink") + (dbus-call-method :session tox-name tox-session tox-session-interface + "SetDefaultAudioSink") + (fsm-debug-output "About to call SetOggVorbisAudioSource") + (dbus-call-method :session tox-name tox-session tox-session-interface + "SetOggVorbisAudioSource" + tox-my-ogg-answering-machine) + ;; There, now we just wait for the NativeCandidatesPrepared signal... + ;; NO! Don't do like those dead people do! That signal will never + ;; come. I don't know why, though... + (fsm-debug-output "About to register signal") + (dbus-register-signal :session tox-name tox-session tox-session-interface + "NativeCandidatesPrepared" + (lexical-let ((fsm fsm)) + (lambda (components) + (fsm-send-sync + fsm + (cons :native-candidates-prepared components))))) + ;; This is more like it. At least it will provide us with some + ;; debugging information. + (dbus-register-signal :session tox-name tox-session tox-session-interface + "NewNativeCandidate" + (lexical-let ((fsm fsm)) + (lambda (components) + (fsm-send-sync + fsm + (cons :new-native-candidate components))))) + ;; And we also want to know about state changes. + (dbus-register-signal :session tox-name tox-session tox-session-interface + "StateChanged" + (lexical-let ((fsm fsm)) + (lambda (state direction) + (fsm-send-sync + fsm + (list :state-changed state direction))))) + ;; And about active candidate pairs. + (dbus-register-signal :session tox-name tox-session tox-session-interface + "NewActiveCandidatePair" + (lexical-let ((fsm fsm)) + (lambda (native-candidate remote-candidate) + (fsm-send-sync + fsm + (list :new-active-candidate-pair + native-candidate remote-candidate))))) + (fsm-debug-output "Waiting") + state-data)) + +(define-enter-state jingle nil + (fsm state-data) + ;; XXX: send termination stanza, if appropriate + ;; clean up + (let ((tox-session (plist-get state-data :tox-session))) + (when tox-session + (ignore-errors + (dbus-call-method :session tox-name tox-session tox-session-interface + "Destroy")))) + (remhash (cons (jabber-connection-jid (plist-get state-data :jc)) + (plist-get state-data :sid)) + jingle-acct-sid-map) + (list nil nil)) + +(define-enter-state jingle :initiate + (fsm state-data) + (let ((jc (plist-get state-data :jc)) + (jid (plist-get state-data :jid)) + (sid (plist-get state-data :sid)) + (payload-types (mapcar + (lambda (codec) + `(payload-type + ((id . ,(number-to-string (nth 0 codec))) + (name . ,(nth 1 codec)) + ;; (nth 2 codec) is media type; + ;; should we filter out + ;; non-audio codecs? then + ;; again, the value seems to be + ;; bogus... + (clockrate . ,(number-to-string (nth 3 codec))) + ,@(let ((channels (nth 4 codec))) + (unless (= channels 0) + `((channels . ,(number-to-string channels)))))) + ,@(mapcar + (lambda (param) + `(parameter + ((name . ,(nth 0 param)) + (value . ,(nth 1 param))))) + (nth 5 codec)))) + (plist-get state-data :our-codecs)))) + (jabber-send-iq jc jid "set" + `(jingle ((xmlns . ,jingle-ns) + (action . "session-initiate") + (initiator . ,(jabber-connection-jid jc)) + (sid . ,sid)) + (content + ((creator . "initiator") + (name . "foo") + (senders . "initiator")) + (description + ((xmlns . ,jingle-rtp-ns) + (media . "audio")) + ,@payload-types) + (transport ((xmlns . ,jingle-ice-udp-ns))))) + (lambda (jc iq fsm) + (fsm-send-sync fsm (cons :iq-result iq))) + fsm + (lambda (jc iq fsm) + (fsm-send-sync fsm (cons :iq-error iq))) + fsm) + (list state-data nil))) + +(define-state jingle :initiate + (fsm state-data event callback) + (case (car-safe event) + (:iq-result + ;; Receiver provisionally accepted the session request. Move on + ;; to PENDING. + (list :pending state-data)) + (:iq-error + (message "Couldn't initiate Jingle audio session: %s" + (jabber-parse-error (jabber-iq-error (cdr event)))) + (list nil state-data)) + (:new-native-candidate + (let ((components (cdr event))) + (jingle-send-native-candidate state-data components) + (list :initiate state-data))))) + +(define-state jingle :wait-for-initiate + (fsm state-data event callback) + (case (car-safe event) + (:iq-set + (let* ((jc (plist-get state-data :jc)) + (iq (cdr event)) + (from (jabber-xml-get-attribute iq 'from)) + (id (jabber-xml-get-attribute iq 'id)) + (jingle (jabber-iq-query iq)) + (action (jabber-xml-get-attribute jingle 'action)) + ;; XXX: could be more than one... + (content (car (jabber-xml-get-children jingle 'content))) + ;; XXX: is it really audio? + (audio-content (find jingle-rtp-ns (jabber-xml-node-children content) + :test 'string= + :key 'jabber-xml-get-xmlns)) + (payload-types (jabber-xml-get-children audio-content 'payload-type))) + ;; There are very few reasons for which we should not send an + ;; acknowledgement here; see section 6.3.2 of XEP-0166. + ;; Notably, we might want to check that there is a presence + ;; subscription. + (jabber-send-iq jc from "result" () + nil nil nil nil id) + + (unless (string= action "session-initiate") + (fsm-debug-output "Action is %S. Why is it not \"session-initiate\"?" action)) + + (cond + ;; Make sure audio is in the list of contents. We can + ;; negotiate away other content types later. + ((null audio-content) + (jingle-send-iq state-data "session-terminate" + '((reason () (unsupported-applications)))) + (list nil state-data)) + + ;; Make sure ICE is in the list of transports. + ((not (member* jingle-ice-udp-ns + (jabber-xml-get-children content 'transport) + :test 'string= + :key 'jabber-xml-get-xmlns)) + (jingle-send-iq state-data "session-terminate" + '((reason () (unsupported-transports)))) + (list nil state-data)) + + (t + (let ((tox-session (plist-get state-data :tox-session)) + (their-codecs (mapcar + (lambda (pt) + (jabber-xml-let-attributes + (id name clockrate channels) pt + (list :struct + :int32 (string-to-number id) + :string name + :byte 0 + :uint32 (string-to-number clockrate) + :uint32 (if channels + (string-to-number channels) + 1) + (cons + :array + (or + (mapcar + (lambda (param) + (jabber-xml-let-attributes + (name value) param + (list :dict-entry :string name :string value))) + (jabber-xml-get-children pt 'parameter)) + (list :signature "{ss}")))))) + payload-types))) + (fsm-debug-output "Their codecs are %S" their-codecs) + ;; Tell tox what codecs the remote side supports + (dbus-call-method + :session tox-name tox-session tox-session-interface + "SetRemoteCodecs" + ;;'((array (struct int32 string byte uint32 uint32 (array (dict-entry string string))))) + their-codecs) + + ;; Check if we have any codecs in common + (let ((codec-intersection + (dbus-call-method + :session tox-name tox-session tox-session-interface + "GetCodecIntersection"))) + (fsm-debug-output "The codec intersection is %S" codec-intersection) + (setq state-data + (plist-put + state-data + :codec-intersection codec-intersection)) + + (if codec-intersection + ;; So, now we know that we stand a basic chance of fulfilling + ;; the request. Let's move on to PENDING. + (list :pending state-data) + + ;; Or, it might turn out that we don't have any codecs + ;; in common with our partner. + (jingle-send-iq state-data "session-terminate" + '((reason () (media-error)))) + (list nil state-data)))))))))) + +;; Thu Jan 1 16:49:23 2009: Warning: event (:new-native-candidate ("L1" 1 "127.0.0.1" 33582 "udp" "RTP" "AVP" 100 0 "Pmc4YPYhJyGPWKIv" "GKQ5/XFIE0pp8+6y")) ignored in state jingle/:pending +;; Thu Jan 1 16:49:23 2009: Warning: event (:iq-set iq ((from . "legoscia@jabber.cd.chalmers.se/2868723341230824321901526") (to . "magnus.henoch@jabber.se/2635526438123082419775630") (type . "set") (id . "emacs-iq-18780.58883.21969")) (jingle ((xmlns . "urn:xmpp:jingle:0") (action . "transport-info") (initiator . "legoscia@jabber.cd.chalmers.se/2868723341230824321901526") (sid . "emacs-sid-18780.58881.712027")) (content ((creator . "initiator") (name . "foo")) (transport ((xmlns . "urn:xmpp:jingle:transports:ice-udp:0")) (candidate ((component . "1") (ip . "127.0.0.1") (port . "44319") (protocol . "udp") (priority . "100"))))))) ignored in state jingle/:pending +;; Thu Jan 1 16:50:07 2009: Warning: event (:state-changed 0 0) ignored in state jingle/:pending + +(define-state jingle :pending + (fsm state-data event callback) + + (case (car-safe event) + (:state-changed + (let ((state (car (assq (second event) + '((0 . :disconnected) + (1 . :connecting) + (2 . :connected))))) + (direction (car (assq (third event) + '((0 . nil) + (1 . :send-only) + (2 . :receive-only) + (3 . :send-and-receive)))))) + (fsm-debug-output "Got :state-changed; new state %s, new direction %s" + state direction) + + (case state + (0 + ;; Do we have enough information to send the termination stanza? + (list nil state-data))) + ;; Still, not sure what we should do here... + )) + + (:new-native-candidate + (let ((components (cdr event))) + (jingle-send-native-candidate state-data components) + (list :pending state-data))) + + (:iq-set + (fsm-debug-output "iq-set event is %S" event) + (let* ((jc (plist-get state-data :jc)) + (iq (cdr event))) + (jabber-xml-let-attributes (action) (jabber-iq-query iq) + (fsm-debug-output "action is %S" action) + (cond + ((string= action "transport-info") + (fsm-debug-output "transport-info is %S" iq) + (let ((tox-session (plist-get state-data :tox-session)) + (candidates + (jabber-xml-get-children + (jabber-xml-path + iq + `(jingle content (,jingle-ice-udp-ns . "transport"))) + 'candidate))) + ;; XXX: send iq error for no candidates + (when candidates + (fsm-debug-output "Adding remote candidate...") + (dbus-call-method :session tox-name tox-session tox-session-interface + "AddRemoteCandidate" + (mapcar + 'jingle-parse-candidate + candidates)) + ;; XXX: iq result + (list :pending state-data) + ))) + (t + ;; XXX: send "bad-request" or something + ))))))) + +(defun jingle-send-iq (state-data action payload) + "Send a Jingle IQ stanza from within a Jingle FSM. +STATE-DATA is the state data plist of the FSM. +ACTION is the value of the action attribute of the +element. +PAYLOAD is a list of XML elements to include as children +of the element. +The recipient and the SID are determined from STATE-DATA." + (let ((jc (plist-get state-data :jc)) + (jid (plist-get state-data :jid)) + (role (plist-get state-data :role)) + (sid (plist-get state-data :sid))) + (jabber-send-iq + jc jid "set" + `(jingle ((xmlns . ,jingle-ns) + (action . ,action) + (initiator + . ,(ecase role + (:initiator + (jabber-connection-jid jc)) + (:target + jid))) + (sid . ,sid)) + ,@payload) + ;; XXX: we probably want error checking, to see if our partner + ;; went offline. + nil nil nil nil))) + +(defun jingle-send-native-candidate (state-data candidate) + "Send a native candidate for ICE-UDP. +The CANDIDATE is a list of components, as provided by the +NewNativeCandidate signal of Tox." + ;; XXX: check against XEP-0176 + (jingle-send-iq state-data "transport-info" + `((content + ((creator . "initiator") + (name . "foo")) + (transport + ((xmlns . ,jingle-ice-udp-ns)) + ,@(mapcar + (lambda (c) + `(candidate + ((id . ,(nth 0 c)) + (component . ,(number-to-string (nth 1 c))) + ;; foundation? + ;; generation? + (ip . ,(nth 2 c)) + ;; network? + (port . ,(number-to-string (nth 3 c))) + (protocol . ,(nth 4 c)) + ;; (nth 5 c) is always "RTP" + ;; (nth 6 c) is always "AVP" + (priority . ,(nth 7 c)) + ;; (nth 8 c) is type. how to translate it? + (username . ,(nth 9 c)) + (password . ,(nth 10 c)) + ))) + candidate)))))) + +(defun jingle-parse-candidate (candidate) + "Parse an XEP-0176 element into DBus format. +Specifically, the signature is \"(susqsssyyss)\"." + ;; XXX: check against XEP-0176 again + (jabber-xml-let-attributes + (id component foundation generation + ip port protocol priority type + username password) + candidate + (list :string id + :uint32 (string-to-number component) + :string ip + :uint16 (string-to-number port) + "udp" "RTP" "AVP" + :byte (string-to-number priority) ;XXX: priority is preference? + :byte 0 ;XXX: fix type + :string (or username "") + :string (or password "")))) + +(add-to-list 'jabber-iq-set-xmlns-alist + (cons jingle-ns 'jabber-jingle-incoming-iq)) +(defun jabber-jingle-incoming-iq (jc iq) + (jabber-xml-let-attributes + (sid action) (jabber-iq-query iq) + (unless (and sid action) + (jabber-signal-error "modify" 'bad-request)) + (let ((fsm (gethash (cons (jabber-connection-jid jc) sid) jingle-acct-sid-map))) + (cond + (fsm + (fsm-send-sync fsm (cons :iq-set iq))) + ((string= action "session-initiate") + (condition-case e + (setq fsm (start-jingle jc sid :target (jabber-xml-get-attribute iq 'from))) + (error + (jabber-signal-error "wait" 'internal-server-error + (concat "Couldn't accept Jingle session: " + (error-message-string e))))) + (puthash (cons (jabber-connection-jid jc) sid) fsm jingle-acct-sid-map) + (fsm-send-sync fsm (cons :iq-set iq))) + (t + (jabber-signal-error "modify" 'bad-request + (format "Session \"%s\" unknown" sid) + `((unknown-session ((xmlns . ,jingle-error-ns)))))))))) + +(defun jabber-jingle-start-audio-session (jc jid) + (interactive + (list (jabber-read-account) + (jabber-read-jid-completing "Voice call to: " nil nil nil 'full))) + (let* ((sid (apply 'format "emacs-sid-%d.%d.%d" (current-time))) + (fsm (start-jingle jc sid :initiator jid))) + (puthash (cons (jabber-connection-jid jc) sid) fsm jingle-acct-sid-map))) + + +(provide 'jabber-tox) diff --git a/tox/main.c b/tox/main.c new file mode 100644 index 0000000..39909f3 --- /dev/null +++ b/tox/main.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include "tox.h" + +int +main(int argc, char *argv[]) +{ + GMainLoop *loop; + DBusGConnection *connection; + DBusGProxy *bus_proxy; + GError *error = NULL; + ToxObject *obj; + guint32 x; + + g_type_init(); + gst_init(&argc, &argv); + + { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + /* not aborting on warnings because of: + farsight-rtp-WARNING **: Not enough information in rtp caps + */ + fatal_mask |= /*G_LOG_LEVEL_WARNING | */ G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + } + + connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error); + if (connection == NULL) { + g_printerr("Failed to open connection to bus: %s\n", + error->message); + exit(1); + } + + bus_proxy = dbus_g_proxy_new_for_name(connection, "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + + g_debug("About to request D-Bus name...\n"); + if (!dbus_g_proxy_call(bus_proxy, "RequestName", &error, + G_TYPE_STRING, "net.sourceforge.emacs-jabber.Tox", + G_TYPE_UINT, + /* these settings are good for debugging. we + should listen for the NameLost signal, though. */ + DBUS_NAME_FLAG_ALLOW_REPLACEMENT + | DBUS_NAME_FLAG_REPLACE_EXISTING + | DBUS_NAME_FLAG_DO_NOT_QUEUE, + G_TYPE_INVALID, + G_TYPE_UINT, &x, + G_TYPE_INVALID)) { + g_printerr("Couldn't acquire name: %s\n", error->message); + exit(1); + } + else if (x != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + g_printerr("Couldn't acquire name: RequestName returned %u\n", x); + exit(1); + } + g_debug("D-Bus name acquired\n"); + + obj = g_object_new(TOX_TYPE_OBJECT, "dbus-connection", connection, NULL); + dbus_g_connection_register_g_object(connection, "/net/sourceforge/emacs_jabber/Tox", G_OBJECT(obj)); + + loop = g_main_loop_new(NULL, FALSE); + + g_main_loop_run(loop); + return 0; +} diff --git a/tox/tox-marshal.list b/tox/tox-marshal.list new file mode 100644 index 0000000..0b42850 --- /dev/null +++ b/tox/tox-marshal.list @@ -0,0 +1,3 @@ +VOID:BOXED +VOID:UCHAR,UCHAR +VOID:STRING,STRING diff --git a/tox/tox-object.xml b/tox/tox-object.xml new file mode 100644 index 0000000..f0a18a2 --- /dev/null +++ b/tox/tox-object.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/tox/tox-session.c b/tox/tox-session.c new file mode 100644 index 0000000..f373598 --- /dev/null +++ b/tox/tox-session.c @@ -0,0 +1,752 @@ +#include +#include +#include +#include +#include +#include +#include "tox-session.h" +#include "tox-marshal.h" + +G_DEFINE_TYPE(ToxSession, tox_session, G_TYPE_OBJECT) + +typedef struct _ToxSessionPrivate { + FarsightSession *session; + /* for now, one stream is enough */ + FarsightStream *stream; + + int have_source, have_sink; + + gboolean dispose_has_run; +} ToxSessionPrivate; + +gboolean tox_session_destroy(ToxSession *obj, GError **error); +gboolean tox_session_set_default_audio_sink(ToxSession *obj, GError **error); +gboolean tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error); +gboolean tox_session_add_remote_candidate(ToxSession *obj, GPtrArray *candidate, GError **error); +gboolean tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error); +gboolean tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error); +gboolean tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error); + +/* properties */ +enum { + DIRECTION = 1 +}; +static void tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec); + +/* signals */ +enum { + NEW_NATIVE_CANDIDATE, + NATIVE_CANDIDATES_PREPARED, + STATE_CHANGED, + NEW_ACTIVE_CANDIDATE_PAIR, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +#include "tox-session-glue.h" + +static GstElement *prepare_source(const char *filename); +static void new_pad(GstElement *, GstPad *, gpointer); +static GstElement *prepare_sink(void); +static void stream_done(ToxSession *); + +static GValueArray * candidate_list_to_dbus_array(const GList *candidates); + +static void tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data); +static void tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self); +static void tox_session_state_changed(FarsightStream *stream, gint state, gint direction, ToxSession *self); +static void tox_session_new_active_candidate_pair(FarsightStream *stream, gchar *native_candidate_id, gchar *remote_candidate_id, ToxSession *self); + +void +tox_session_init(ToxSession *obj) +{ + ToxSessionPrivate *priv; + priv = g_new0(ToxSessionPrivate, 1); + obj->priv = priv; + + priv->session = farsight_session_factory_make("rtp"); + g_assert(priv->session); + + /* we need to know the direction to create a stream, + so we do that when that property is set. + */ + +} + +static GObjectClass *parent_class = NULL; + +static void +tox_session_dispose(GObject *obj) +{ + ToxSession *self = (ToxSession *)obj; + + if (self->priv->dispose_has_run) { + return; + } + g_debug("in tox_session_dispose\n"); + self->priv->dispose_has_run = TRUE; + + if (self->priv->stream) + g_object_unref(self->priv->stream); + if (self->priv->session) + g_object_unref(self->priv->session); + + self->priv->stream = NULL; + self->priv->session = NULL; + + G_OBJECT_CLASS(parent_class)->dispose(obj); +} + +static void +tox_session_finalize(GObject *obj) +{ + ToxSession *self = (ToxSession *)obj; + + g_debug("in tox_session_finalize\n"); + g_free(self->priv); + + G_OBJECT_CLASS(parent_class)->finalize(obj); +} + +void +tox_session_class_init(ToxSessionClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GParamSpec *direction_param_spec; + + gobject_class->dispose = tox_session_dispose; + gobject_class->finalize = tox_session_finalize; + + gobject_class->set_property = tox_session_set_property; + + direction_param_spec = g_param_spec_uint("direction", + "stream direction", + "1 means send-only, 2 means receive-only, 3 means both", + 1, + 3, + 1, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE); + g_object_class_install_property(gobject_class, + DIRECTION, + direction_param_spec); + + signals[NEW_NATIVE_CANDIDATE] = + g_signal_new("new-native-candidate", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + tox_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + G_TYPE_VALUE_ARRAY); + + signals[NATIVE_CANDIDATES_PREPARED] = + g_signal_new("native-candidates-prepared", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + tox_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + G_TYPE_VALUE_ARRAY); + + signals[STATE_CHANGED] = + g_signal_new("state-changed", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + tox_marshal_VOID__UCHAR_UCHAR, + G_TYPE_NONE, + 2, + G_TYPE_UCHAR, + G_TYPE_UCHAR); + + signals[NEW_ACTIVE_CANDIDATE_PAIR] = + g_signal_new("new-active-candidate-pair", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + tox_marshal_VOID__STRING_STRING, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + dbus_g_object_type_install_info(TOX_TYPE_SESSION, &dbus_glib_tox_session_object_info); + + parent_class = g_type_class_peek_parent (klass); +} + +static void +tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec) +{ + ToxSession *self = (ToxSession *)obj; + guint dir; + + switch(property_id) { + case DIRECTION: + if (self->priv->stream) + g_object_unref(self->priv->stream); + /* Now we know the direction, so we create a stream. */ + dir = g_value_get_uint(value); + self->priv->stream = farsight_session_create_stream(self->priv->session, + FARSIGHT_MEDIA_TYPE_AUDIO, + dir); + g_assert(self->priv->stream); + + g_object_set(G_OBJECT(self->priv->stream), "transmitter", "libjingle", NULL); + + /* XXX: should we set null source/sink here? */ + switch (dir) { + case 1: + /* send-only, we need no sink */ + self->priv->have_sink = 1; + break; + case 2: + /* receive-only, we need no source */ + self->priv->have_source = 1; + break; + } + + /* start preparing native candidates */ + g_debug("About to prepare native candidates...\n"); + g_signal_connect(self->priv->stream, "new-native-candidate", + (GCallback)tox_session_new_native_candidate, (gpointer)self); + g_signal_connect(self->priv->stream, "native-candidates-prepared", + (GCallback)tox_session_native_candidates_prepared, (gpointer)self); + /* but we can't actually do it until we have a pipeline. + so, we'll call stream_done when we do. */ + + /* Other signals we want to forward */ + g_signal_connect(self->priv->stream, "state-changed", + (GCallback)tox_session_state_changed, (gpointer)self); + g_signal_connect(self->priv->stream, "new-active-candidate-pair", + (GCallback)tox_session_new_active_candidate_pair, (gpointer)self); + + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec); + } +} + +gboolean +tox_session_destroy(ToxSession *obj, GError **error) +{ + g_object_unref(obj); + return TRUE; +} + +gboolean +tox_session_set_default_audio_sink(ToxSession *obj, GError **error) +{ + GstElement *sink = prepare_sink(); + farsight_stream_set_sink(obj->priv->stream, sink); + obj->priv->have_sink = 1; + + if (obj->priv->have_sink && obj->priv->have_source) + stream_done(obj); + + return TRUE; +} + +gboolean +tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error) +{ + GstElement *source = prepare_source(filename); + farsight_stream_set_source(obj->priv->stream, source); + obj->priv->have_source = 1; + + if (obj->priv->have_sink && obj->priv->have_source) + stream_done(obj); + + return TRUE; +} + +static GstElement * +prepare_source(const char *filename) +{ + GstElement *bin, *filesrc, *demux, *decode; + + bin = gst_bin_new("mysource"); + filesrc = gst_element_factory_make("filesrc", "file-source"); + g_object_set(G_OBJECT(filesrc), "location", filename, NULL); + + demux = gst_element_factory_make("oggdemux", "ogg-parser"); + decode = gst_element_factory_make("vorbisdec", "vorbis-decoder"); + + gst_element_link(filesrc, demux); + g_signal_connect(demux, "pad-added", G_CALLBACK(new_pad), decode); + + gst_bin_add_many(GST_BIN(bin), filesrc, demux, decode, NULL); + + return bin; +} + +static void +new_pad(GstElement *demux, GstPad *pad, gpointer data) +{ + GstElement *decode = (GstElement*)data; + GstPad *decoder_pad; + + decoder_pad = gst_element_get_pad(decode, "sink"); + gst_pad_link(pad, decoder_pad); + + gst_object_unref(decoder_pad); +} + +static GstElement * +prepare_sink(void) +{ + GstElement *bin, *converter, *audiosink; + + bin = gst_bin_new("mysink"); + converter = gst_element_factory_make("audioconvert", "converter"); + audiosink = gst_element_factory_make("autoaudiosink", "audiosink"); + gst_element_link(converter, audiosink); + + gst_bin_add_many(GST_BIN(bin), converter, audiosink, NULL); + + return bin; +} + +static void +stream_done(ToxSession *self) +{ + farsight_stream_prepare_transports(self->priv->stream); +} + +gboolean +tox_session_add_remote_candidate(ToxSession *self, GPtrArray *candidate, GError **error) +{ + int i; + guint n; + GList *candidate_list; + + candidate_list = NULL; + + /* Here we convert the array of structs into a GList of + FarsightTransportInfo. The argument list is described in + tox-session.xml. */ + for (i = 0; i < candidate->len; i++) { + GValueArray *component; + FarsightTransportInfo *info; + gchar *s; + + component = g_ptr_array_index(candidate, i); + info = g_new0(FarsightTransportInfo, 1); + + info->candidate_id = + g_value_dup_string( + g_value_array_get_nth(component, 0)); + info->component = + g_value_get_uint( + g_value_array_get_nth(component, 1)); + info->ip = + g_value_dup_string( + g_value_array_get_nth(component, 2)); + info->port = + g_value_get_uint( + g_value_array_get_nth(component, 3)); + + s = g_value_dup_string(g_value_array_get_nth(component, 4)); + if (strcmp(s, "tcp") == 0) + info->proto = FARSIGHT_NETWORK_PROTOCOL_TCP; + else if (strcmp(s, "udp") == 0) + info->proto = FARSIGHT_NETWORK_PROTOCOL_UDP; + else { + g_set_error(error, DBUS_GERROR, + DBUS_GERROR_REMOTE_EXCEPTION, + "Unexpected protocol '%s'", + s); + g_free(s); + g_free(info); + goto fail; + } + g_free(s); + + /* this should be "RTP" */ + info->proto_subtype = + g_value_dup_string( + g_value_array_get_nth(component, 5)); + /* this should be "AVP" */ + info->proto_profile = + g_value_dup_string( + g_value_array_get_nth(component, 6)); + + info->preference = + g_value_get_uint( + g_value_array_get_nth(component, 7)) + * 0.01; + + n = g_value_get_uint(g_value_array_get_nth(component, 8)); + switch (n) { + case 0: + info->type = FARSIGHT_CANDIDATE_TYPE_LOCAL; + break; + case 1: + info->type = FARSIGHT_CANDIDATE_TYPE_DERIVED; + break; + case 2: + info->type = FARSIGHT_CANDIDATE_TYPE_RELAY; + break; + default: + g_set_error(error, DBUS_GERROR, + DBUS_GERROR_REMOTE_EXCEPTION, + "Unexpected type %u", + n); + g_free(info); + goto fail; + } + + info->username = g_value_dup_string( + g_value_array_get_nth(component, 9)); + info->password = g_value_dup_string( + g_value_array_get_nth(component, 10)); + + candidate_list = g_list_append(candidate_list, info); + } + + farsight_stream_add_remote_candidate(self->priv->stream, candidate_list); + return TRUE; + + fail: + farsight_transport_list_destroy(candidate_list); + return FALSE; +} + +gboolean +tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error) +{ + GList *codec_list; + int i; + /* GList *j; */ + + codec_list = NULL; + + for (i = 0; i < codecs->len; i++) { + GValueArray *codec_in; + FarsightCodec *codec_out; + + codec_in = g_ptr_array_index(codecs, i); + codec_out = g_new0(FarsightCodec, 1); + + codec_out->id = g_value_get_int(g_value_array_get_nth(codec_in, 0)); + codec_out->encoding_name = g_value_dup_string(g_value_array_get_nth(codec_in, 1)); + /* maybe check range of media_type... */ + codec_out->media_type = g_value_get_uchar(g_value_array_get_nth(codec_in, 2)); + codec_out->clock_rate = g_value_get_uint(g_value_array_get_nth(codec_in, 3)); + codec_out->channels = g_value_get_uint(g_value_array_get_nth(codec_in, 4)); + + codec_list = g_list_append(codec_list, codec_out); + } + + farsight_stream_set_remote_codecs(obj->priv->stream, codec_list); + + /* should the elements be freed, or just the list itself? */ + /*for (j = codec_list; j; j = g_list_next(j)) { + g_free(j->data); + }*/ + g_list_free(codec_list); + + return TRUE; +} + +gboolean +tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error) +{ + FarsightStream *stream; + const GList *codec_list; + + GType hash_string_string; + + stream = obj->priv->stream; + codec_list = farsight_stream_get_local_codecs(stream); + + hash_string_string = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_STRING); + + *codecs = g_ptr_array_sized_new(g_list_length(codec_list)); + for (; codec_list; codec_list = g_list_next(codec_list)) { + GValueArray *codec_struct; + const FarsightCodec *codec; + GValue value; + GHashTable *parameters; + GList *p; + + memset(&value, 0, sizeof value); + + codec = (const FarsightCodec*)codec_list->data; + codec_struct = g_value_array_new(2); + + g_value_init(&value, G_TYPE_INT); + g_value_set_int(&value, codec->id); + g_value_array_append(codec_struct, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, codec->encoding_name); + g_value_array_append(codec_struct, &value); + g_value_unset(&value); + + /* XXX: why is this the same as id? */ + g_value_init(&value, G_TYPE_UCHAR); + g_value_set_uchar(&value, codec->media_type); + g_value_array_append(codec_struct, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_UINT); + g_value_set_uint(&value, codec->clock_rate); + g_value_array_append(codec_struct, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_UINT); + g_value_set_uint(&value, codec->channels); + g_value_array_append(codec_struct, &value); + g_value_unset(&value); + + /* optional parameters - this should be a hash table, I think */ + /* parameters = g_hash_table_new(g_str_hash, g_str_equal); */ + parameters = dbus_g_type_specialized_construct(hash_string_string); + for (p = codec->optional_params; p; p = g_list_next(p)) { + FarsightCodecParameter *param = p->data; + + g_hash_table_insert(parameters, param->name, param->value); + } + g_value_init(&value, hash_string_string); + g_value_set_boxed(&value, parameters); + g_value_array_append(codec_struct, &value); + g_value_unset(&value); + g_hash_table_unref(parameters); + + g_assert(codec_struct->n_values == 6); + + g_ptr_array_add(*codecs, codec_struct); + + g_debug("Local codec: %s\n", codec->encoding_name); + } + + return TRUE; +} + +gboolean +tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error) +{ + FarsightStream *stream; + GList *codec_list; + + stream = obj->priv->stream; + codec_list = farsight_stream_get_codec_intersection(stream); + + *codecs = g_ptr_array_sized_new(g_list_length(codec_list)); + for (; codec_list; codec_list = g_list_next(codec_list)) { + GValueArray *codec_struct; + FarsightCodec *codec; + GValue *value; + + codec = (FarsightCodec*)codec_list->data; + codec_struct = g_value_array_new(6); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, codec->id); + g_value_array_append(codec_struct, value); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, codec->encoding_name); + g_value_array_append(codec_struct, value); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_UCHAR); + g_value_set_uchar(value, codec->media_type); + g_value_array_append(codec_struct, value); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_UINT); + g_value_set_uint(value, codec->clock_rate); + g_value_array_append(codec_struct, value); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_UINT); + g_value_set_uint(value, codec->channels); + g_value_array_append(codec_struct, value); + + /* XXX: do something about optional parameters */ + value = g_new0(GValue, 1); + g_value_init(value, DBUS_TYPE_G_STRING_STRING_HASHTABLE); + g_value_set_boxed(value, g_hash_table_new(g_str_hash, g_str_equal)); + g_value_array_append(codec_struct, value); + + g_assert(codec_struct->n_values == 6); + + g_ptr_array_add(*codecs, codec_struct); + } + + return TRUE; +} + +static void +tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data) +{ + ToxSession *self = (ToxSession *)user_data; + const GList *candidates; + GValueArray *array; + + candidates = farsight_stream_get_native_candidate_list(stream); + + array = candidate_list_to_dbus_array(candidates); + + g_debug("Sending signal NativeCandidatesPrepared!\n"); + g_signal_emit(self, signals[NATIVE_CANDIDATES_PREPARED], 0, array); +} + +static void +tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self) +{ + GList *candidate = + farsight_stream_get_native_candidate (stream, candidate_id); + FarsightTransportInfo *trans = candidate->data; + GValueArray *array; + + g_debug ("tox_session_new_native_candidate: New native candidate" + " with %d components, the first being: " + "", + g_list_length(candidate), + trans->candidate_id, trans->component, + trans->ip, trans->port, trans->proto, trans->proto_subtype, + trans->proto_profile, trans->preference, + trans->type, trans->username, trans->password); + + array = candidate_list_to_dbus_array(candidate); + g_debug("Sending signal NewNativeCandidate!\n"); + g_signal_emit(self, signals[NEW_NATIVE_CANDIDATE], 0, array); +} + +static GValueArray * +candidate_list_to_dbus_array(const GList *candidates) +{ + GValueArray *array; + + array = g_value_array_new(1); + + for (; candidates; candidates = g_list_next(candidates)) { + GValueArray *candidate; + FarsightTransportInfo *info; + GValue value; + + info = (FarsightTransportInfo*)candidates->data; + candidate = g_value_array_new(11); + + memset(&value, 0, sizeof value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, info->candidate_id); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_UINT); + g_value_set_uint(&value, info->component); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, info->ip); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_UINT); + g_value_set_uint(&value, info->port); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + switch(info->proto) { + case FARSIGHT_NETWORK_PROTOCOL_UDP: + g_value_set_static_string(&value, "udp"); + break; + case FARSIGHT_NETWORK_PROTOCOL_TCP: + g_value_set_static_string(&value, "tcp"); + break; + default: + g_error("Unknown protocol value %u\n", info->proto); + } + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, info->proto_subtype); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, info->proto_profile); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_UINT); + g_value_set_uint(&value, (guint)(info->preference * 100)); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_UINT); + switch(info->type) { + case FARSIGHT_CANDIDATE_TYPE_LOCAL: + g_value_set_uint(&value, 0); + break; + case FARSIGHT_CANDIDATE_TYPE_DERIVED: + g_value_set_uint(&value, 1); + break; + case FARSIGHT_CANDIDATE_TYPE_RELAY: + g_value_set_uint(&value, 2); + break; + default: + g_error("Unknown candidate type %u\n", info->proto); + } + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, info->username); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, info->password); + g_value_array_append(candidate, &value); + g_value_unset(&value); + + g_assert(candidate->n_values == 11); + + g_value_init(&value, G_TYPE_VALUE_ARRAY); + /* apparently GValueArray is a "boxed" type */ + g_value_set_boxed(&value, candidate); + g_value_array_append(array, &value); + g_value_unset(&value); + } + + return array; +} + +static void +tox_session_state_changed(FarsightStream *stream, gint state, gint direction, ToxSession *self) +{ + g_signal_emit(self, signals[STATE_CHANGED], 0, state, direction); +} + +static void +tox_session_new_active_candidate_pair(FarsightStream *stream, gchar *native_candidate_id, gchar *remote_candidate_id, ToxSession *self) +{ + g_signal_emit(self, signals[NEW_ACTIVE_CANDIDATE_PAIR], 0, native_candidate_id, remote_candidate_id); +} diff --git a/tox/tox-session.h b/tox/tox-session.h new file mode 100644 index 0000000..0c24906 --- /dev/null +++ b/tox/tox-session.h @@ -0,0 +1,24 @@ +#ifndef __TOX_SESSION_H__ +#define __TOX_SESSION_H__ + +typedef struct ToxSession +{ + GObject parent; + + struct _ToxSessionPrivate *priv; +} ToxSession; + +typedef struct ToxSessionClass +{ + GObjectClass parent; +} ToxSessionClass; + +GType tox_session_get_type(void); +#define TOX_TYPE_SESSION (tox_session_get_type()) +#define TOX_SESSION(object) (G_TYPE_CHECK_INSTANCE_CAST((object), TOX_TYPE_SESSION, ToxSession)) +#define TOX_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TOX_TYPE_SESSION, ToxSessionClass)) +#define TOX_IS_SESSION(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), TOX_TYPE_SESSION)) +#define TOX_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TOX_TYPE_SESSION)) +#define TOX_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOX_TYPE_SESSION, ToxSessionClass)) + +#endif /* __TOX_SESSION_H__ */ diff --git a/tox/tox-session.xml b/tox/tox-session.xml new file mode 100644 index 0000000..50a0587 --- /dev/null +++ b/tox/tox-session.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tox/tox.c b/tox/tox.c new file mode 100644 index 0000000..f96f931 --- /dev/null +++ b/tox/tox.c @@ -0,0 +1,110 @@ +#include +#include +#include "tox.h" +#include "tox-session.h" + +typedef struct _ToxObjectPrivate { + guint session_counter; + DBusGConnection *connection; +} ToxObjectPrivate; + +G_DEFINE_TYPE(ToxObject, tox_object, G_TYPE_OBJECT) + +gboolean tox_create_session(ToxObject *obj, guint8 dir, char **ret, GError **error); + +/* properties */ +enum { + TOX_DBUS_CONNECTION = 1, +}; + +static void tox_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec); +static void tox_get_property(GObject *obj, guint property_id, GValue *value, GParamSpec *pspec); + +#include "tox-object-glue.h" + +void +tox_object_init(ToxObject *obj) +{ + obj->priv = g_new0(ToxObjectPrivate, 1); +} + +void +tox_object_class_init(ToxObjectClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GParamSpec *connection_param_spec; + + gobject_class->set_property = tox_set_property; + gobject_class->get_property = tox_get_property; + + connection_param_spec = g_param_spec_boxed("dbus-connection", + "D-Bus connection", + "Connection where new sessions are registered", + DBUS_TYPE_G_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + g_object_class_install_property(gobject_class, + TOX_DBUS_CONNECTION, + connection_param_spec); + + dbus_g_object_type_install_info(TOX_TYPE_OBJECT, &dbus_glib_tox_object_info); + +} + +static void +tox_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec) +{ + ToxObject *self = (ToxObject *)obj; + + switch(property_id) { + case TOX_DBUS_CONNECTION: + /* XXX: this should be a weak ref */ + /* hm? */ +/* if (self->priv->connection) */ +/* dbus_g_connection_unref(self->priv->connection); */ + self->priv->connection = (DBusGConnection *)g_value_get_boxed(value); +/* dbus_g_connection_ref(self->priv->connection); */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec); + } +} + +static void +tox_get_property(GObject *obj, guint property_id, GValue *value, GParamSpec *pspec) +{ + ToxObject *self = (ToxObject *)obj; + + switch(property_id) { + case TOX_DBUS_CONNECTION: + g_value_set_boxed(value, self->priv->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec); + } +} + +gboolean +tox_create_session(ToxObject *obj, guint8 dir, char **ret, GError **error) +{ + guint session_number = ++obj->priv->session_counter; + DBusGConnection *connection = obj->priv->connection; + ToxSession *session; + + if (dir < 1 || dir > 3) { + /* XXX: how to set the D-Bus error name? */ + g_set_error(error, DBUS_GERROR, + DBUS_GERROR_REMOTE_EXCEPTION, + "Direction out of range (was %u, should be 1 <= dir <= 3)", + dir); + return FALSE; + } + + session = g_object_new(TOX_TYPE_SESSION, "direction", dir, NULL); + + *ret = g_strdup_printf("/net/sourceforge/emacs_jabber/Tox/%u", session_number); + dbus_g_connection_register_g_object(connection, *ret, G_OBJECT(session)); + + g_debug("CreateSession called, returning %s\n", *ret); + + return TRUE; +} diff --git a/tox/tox.h b/tox/tox.h new file mode 100644 index 0000000..d03ae92 --- /dev/null +++ b/tox/tox.h @@ -0,0 +1,24 @@ +#ifndef __TOX_H__ +#define __TOX_H__ + +typedef struct ToxObject +{ + GObject parent; + + struct _ToxObjectPrivate *priv; +} ToxObject; + +typedef struct ToxObjectClass +{ + GObjectClass parent; +} ToxObjectClass; + +GType tox_object_get_type(void); +#define TOX_TYPE_OBJECT (tox_object_get_type()) +#define TOX_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST((object), TOX_TYPE_OBJECT, ToxObject)) +#define TOX_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TOX_TYPE_OBJECT, ToxObjectClass)) +#define TOX_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), TOX_TYPE_OBJECT)) +#define TOX_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TOX_TYPE_OBJECT)) +#define TOX_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOX_TYPE_OBJECT, ToxObjectClass)) + +#endif /* __TOX_H__ */ diff --git a/tox/tox.service.in b/tox/tox.service.in new file mode 100644 index 0000000..64e293b --- /dev/null +++ b/tox/tox.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=net.sourceforge.emacs-jabber.Tox +Exec=@libexecdir@/tox diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..315a63d --- /dev/null +++ b/www/index.html @@ -0,0 +1,305 @@ + + + +jabber.el + + + + + + +

jabber.el

+ +SourceForge.net Logo + +jabber.el is a Jabber client for Emacs. + + + +

+  +For the impatient: +

+
    +
  • +Latest release: 0.7.1 (31-Jan-2007) +
  • +
  • +Latest pretest release: 0.7.93 (19-Feb-2009) +
  • +
  • +Full list of releases, including lists of features and changes + +
  • +
+

External resources: +

+ +
+

What is Jabber?

+ + +

+Jabber is an open instant messaging (IM) system, also known as XMPP. +It is described in RFCs 3920-3923 and a series of XEPs (XMPP Extension +Proposals). That is, unlike legacy IM systems (such as ICQ or MSN +Messenger), the protocol is published in the open, free for anyone to +implement. Therefore you shouldn't be surprised that there is a +Jabber client for Emacs, too. (If you are not of the Emacs +persuasion, see this list of Jabber clients.) +

+

+Jabber is, like e-mail, a decentralised system. A Jabber identifier +(JID) is of the form username@server, just like an e-mail address, and +every Jabber user whose server is open to Internet connections can +communicate with any other user. This is in stark contrast to the +legacy "walled-garden" IM systems, where you need a separate account +for each system to be able to communicate with its users. Note that +the Google Talk service is an XMPP service, so if you have a GMail +account, you already have a JID ending with @gmail.com. +

+

+In Jabber, you have a "roster", a list of contacts. You can see +which of them are online at the moment, and chat with them. (You can +also send messages to an offline contact, and the message will be +stored on the server for later delivery.) Jabber also supports +IRC-style chat rooms. +

+
+ +
+

What is jabber.el?

+ + +

+jabber.el is a Jabber client for Emacs. It may seem strange to have a +chat client in an editor, but consider that chatting is, after all, +just a special case of text editing. +

+
+ +
+

Downloading and installing

+ + + +
+

Prepackaged

+ + +

+Note that jabber.el is available as emacs-jabber in Debian, and in +Ubuntu's "universe" section. +

+
+ +
+

Dependencies

+ + +

+jabber.el requires either GNU Emacs 22, or GNU Emacs 21 combined with +Gnus 5.10. You should get these from your distribution, or from the +Emacs and Gnus web sites. +

+

+However, if you want to connect to servers that use SRV records +(e.g. Google Talk), it is recommended to use either No Gnus or Gnus +5.13. +

+

+If you want encrypted connections (some servers, e.g. Google Talk, +require it), you need GnuTLS installed. +

+
+ +
+

Downloading

+ + +

+Download the latest release and unpack it in a suitable location. Add +something like the following to your .emacs file: +

+

+

+ ;; adjust this path:
+ (add-to-list 'load-path "/path/to/emacs-jabber")
+ ;; For 0.7.1 and below:
+ (require 'jabber)
+ ;; For 0.7.90 and above:
+ (require 'jabber-autoloads)
+
+

+

+Either evaluate those lines, or restart Emacs. +

+
+ +
+

Using

+ + +

+See the Basic Operation in the manual. +

+ + +
+
+ +
+

Reporting bugs

+ + +

+Bug reports can be sent either to the mailing list +emacs-jabber-general or to the bug tracker. +

+ +
+ +
+

Latest source

+ + +

+jabber.el releases are, unfortunately, less frequent than they would +be in an ideal world. You can get the latest development version +using various version control systems. +

+ +
+

CVS

+ + +

+See the Sourceforge CVS page. In short, run the following command: +

+

+

+ cvs -z3 -d:pserver:anonymous@emacs-jabber.cvs.sourceforge.net:/cvsroot/emacs-jabber co -P emacs-jabber
+
+

+
+ +
+

GNU Arch

+ + +

+The GNU Arch mirror is no longer being updated. +

+
+ +
+

git

+ + +

+

+ git-clone git://git.catap.ru/emacs-jabber.git
+
+

+ +
+
+ +
+

List of releases

+ + +

+The list of releases has been moved to a separate page. +

+
+

Date: 2009/02/19 1:31:21 AM

+
+ diff --git a/www/index.org b/www/index.org new file mode 100644 index 0000000..2be4e8d --- /dev/null +++ b/www/index.org @@ -0,0 +1,149 @@ +#+TITLE: jabber.el +#+OPTIONS: num:nil author:nil skip:nil + +#+BEGIN_HTML + +SourceForge.net Logo +#+END_HTML + +jabber.el is a Jabber client for Emacs. + +[TABLE-OF-CONTENTS] + +# <> +For the impatient: + + - Latest release: [[http://sourceforge.net/project/showfiles.php?group_id=88346&package_id=92339&release_id=482983][0.7.1]] (31-Jan-2007) + - Latest pretest release: [[http://sourceforge.net/project/showfiles.php?group_id=88346&package_id=278300&release_id=662240][0.7.93]] (19-Feb-2009) + - Full [[list-of-releases][list of releases]], including lists of features and changes + +External resources: + + - [[http://sourceforge.net/projects/emacs-jabber/][Sourceforge project page]], containing: + - trackers for + [[http://sourceforge.net/tracker/?atid=586350&group_id=88346&func=browse][bugs]], + [[http://sourceforge.net/tracker/?atid=586353&group_id=88346&func=browse][feature requests]], + [[http://sourceforge.net/tracker/?atid=586352&group_id=88346&func=browse][patches]] and + [[http://sourceforge.net/tracker/?atid=586351&group_id=88346&func=browse][support requests]], open for anonymous posters + - [[http://sourceforge.net/forum/?group_id=88346][forums]], open for anonymous posters + - [[http://www.emacswiki.org/cgi-bin/wiki/JabberEl][the JabberEl page of EmacsWiki]] + - Mailing lists (also available at [[http://gmane.net][Gmane]]): + - [[http://lists.sourceforge.net/lists/listinfo/emacs-jabber-general][emacs-jabber-general]], for posts by humans (also [[http://dir.gmane.org/gmane.emacs.jabber.general][gmane.emacs.jabber.general]]) + - [[http://lists.sourceforge.net/lists/listinfo/emacs-jabber-commit][emacs-jabber-commit]], read-only mailing list of CVS commits (also [[http://dir.gmane.org/gmane.emacs.jabber.scm][gmane.emacs.jabber.scm]]) + - [[http://lists.sourceforge.net/lists/listinfo/emacs-jabber-bugs][emacs-jabber-bugs]], read-only mailing list of bug tracker activity (also [[http://dir.gmane.org/gmane.emacs.jabber.bugs][gmane.emacs.jabber.bugs]]) + - the Jabber chat room +#+HTML: jabber.el@conference.jabber.se + - the [[file:manual-0.7.1/][manual for jabber.el 0.7.1]] (also included in the release files) + +* What is Jabber? + +Jabber is an open instant messaging (IM) system, also known as XMPP. +It is described in [[http://www.xmpp.org/rfcs/][RFCs 3920-3923]] and a series of [[http://www.xmpp.org/extensions/][XEPs]] (XMPP Extension +Proposals). That is, unlike legacy IM systems (such as ICQ or MSN +Messenger), the protocol is published in the open, free for anyone to +implement. Therefore you shouldn't be surprised that there is a +Jabber client for Emacs, too. (If you are not of the Emacs +persuasion, see this [[http://www.jabber.org/software/clients.shtml][list of Jabber clients]].) + +Jabber is, like e-mail, a decentralised system. A Jabber identifier +(JID) is of the form =username@server=, just like an e-mail address, and +every Jabber user whose server is open to Internet connections can +communicate with any other user. This is in stark contrast to the +legacy "walled-garden" IM systems, where you need a separate account +for each system to be able to communicate with its users. Note that +the [[http://talk.google.com][Google Talk]] service is an XMPP service, so if you have a GMail +account, you already have a JID ending with =@gmail.com=. + +In Jabber, you have a "roster", a list of contacts. You can see +which of them are online at the moment, and chat with them. (You can +also send messages to an offline contact, and the message will be +stored on the server for later delivery.) Jabber also supports +IRC-style chat rooms. + +* What is jabber.el? + +jabber.el is a Jabber client for Emacs. It may seem strange to have a +chat client in an editor, but consider that chatting is, after all, +just a special case of text editing. + +* Downloading and installing + +** Prepackaged + +Note that jabber.el is available as [[http://packages.debian.org/emacs-jabber][emacs-jabber in Debian]], and in +[[http://packages.ubuntu.com/emacs-jabber][Ubuntu's "universe" section]]. + +** Dependencies + +jabber.el requires either GNU Emacs 22, or GNU Emacs 21 combined with +Gnus 5.10. You should get these from your distribution, or from the +[[http://www.gnu.org/software/emacs/][Emacs]] and [[http://gnus.org][Gnus]] web sites. + +However, if you want to connect to servers that use SRV records +(e.g. Google Talk), it is recommended to use either No Gnus or Gnus +5.13. + +If you want encrypted connections (some servers, e.g. Google Talk, +require it), you need [[http://www.gnu.org/software/gnutls/][GnuTLS]] installed. + +** Downloading + +Download the [[impatient][latest release]] and unpack it in a suitable location. Add +something like the following to your =.emacs= file: + +#+BEGIN_EXAMPLE +;; adjust this path: +(add-to-list 'load-path "/path/to/emacs-jabber") +;; For 0.7.1 and below: +(require 'jabber) +;; For 0.7.90 and above: +(require 'jabber-autoloads) +#+END_EXAMPLE + +Either evaluate those lines, or restart Emacs. + +** Using + +See the [[file:manual-0.7.1/Basic-operation.html][Basic Operation]] in the manual. + + + +* Reporting bugs + +Bug reports can be sent either to the mailing list +[[http://lists.sourceforge.net/lists/listinfo/emacs-jabber-general][emacs-jabber-general]] or to the [[http://sourceforge.net/tracker/?group_id=88346&atid=586350][bug tracker]]. + + +* Latest source + +jabber.el releases are, unfortunately, less frequent than they would +be in an ideal world. You can get the latest development version +using various version control systems. + +** CVS + +See [[http://sourceforge.net/cvs/?group_id=88346][the Sourceforge CVS page]]. In short, run the following command: + +#+BEGIN_EXAMPLE +cvs -z3 -d:pserver:anonymous@emacs-jabber.cvs.sourceforge.net:/cvsroot/emacs-jabber co -P emacs-jabber +#+END_EXAMPLE + +** GNU Arch + +The GNU Arch mirror is no longer being updated. + +** git + +#+BEGIN_EXAMPLE +git-clone git://git.catap.ru/emacs-jabber.git +#+END_EXAMPLE + + +* List of releases + +The list of releases has been moved to a [[file:list-of-releases.org][separate page]]. + +# Local variables: +# mode: org +# eval: (progn (add-to-list 'org-link-types "xmpp") (org-make-link-regexps)) +# End: diff --git a/www/list-of-releases.html b/www/list-of-releases.html new file mode 100644 index 0000000..b46edef --- /dev/null +++ b/www/list-of-releases.html @@ -0,0 +1,200 @@ + + + +List of releases - jabber.el + + + + + + +

List of releases - jabber.el

+Back to main page + + + +

version 0.7.1 - 31-Jan-2007

+ +
    +
  • STARTTLS
  • +
  • SRV records (requires No Gnus)
  • +
  • Message composition buffer
  • +
  • XMPP URIs are handled (see manual for setup)
  • +
  • Autoaway
  • +
  • MUC improvements + +
      +
    • Don't display alerts for your own messages
    • +
    • Presence changes are sent to MUC rooms too
    • +
    • Check room features before joining
    • +
    + +
  • Avatars
  • + +
  • File transfer
  • + +
  • Sound files per contact for alerts
  • + +
  • New function: jabber-send-directed-presence
  • + +
  • Entity time supported (XEP-0090)
  • + +
  • Last activity supported (XEP-0012)
  • +
+ +

Read the manual.

+ + +

version 0.7 - 27-Dec-2005

+ +
    +
  • SSL connections possible
  • +
  • Chat buffers rewritten
  • +
  • MUC improved
  • +
  • Global key bindings under C-x C-j
  • +
  • Vcard viewer and editor
  • +
  • Roster export
  • +
  • Message events (JEP-0022)
  • +
  • Easy way to define external notifiers
  • +
  • Activity mode improved
  • +
  • Roster display optimized
  • +
  • Optionally use per-contact history files
  • +
  • Jabber menu in menubar not enabled by default
  • +
  • Flyspell in chat buffers
  • +
  • Different time formats for instant and delayed messages
  • +
  • Chat buffers in inactive windows are scrolled
  • +
  • Roster is sorted by name also
  • +
  • Countless bugfixes
  • +
+ +

Read the manual.

+ +

version 0.6.1 - 27-Dec-2004

+ +
    +
  • Now works with ejabberd
  • +
  • Message history (Mathias Dahl)
  • +
  • Backlogs
  • +
  • Activity tracking on the mode line (Carl Henrik Lunde)
  • +
  • Receive an alert when a specific person goes online (Mathias Dahl)
  • +
  • Support for /me in chats (Nolan Eakins)
  • +
  • Message alerts for current buffer can be disabled
  • +
  • Basic moderation support in MUC
  • +
  • MUC alerts are separated from ordinary message alerts
  • +
+ +

Read the +manual, +in which there is now a section on +protocol support.

+ +

version 0.6 - 17-Oct-2004

+ +
    +
  • Chat buffers no longer use the minibuffer, making it easier to use + ordinary editor commands and to pause writing halfway
  • +
  • The roster display and the chat buffer prompts are now customizable
  • +
  • The online status of the contact is displayed in the chat buffer + (Emacs only)
  • +
  • The connect function automatically sends presence after connecting
  • +
  • The names of the roster buffer and chat buffers are customizable
  • +
  • RET and C-k do what you would expect in the roster buffer
  • +
+ +

Read the manual.

+ +

version 0.5.1 - 08-May-2004

+ +
    +
  • Nicknames are accepted whenever a JID is asked for.
  • +
  • Roster entry spacing is customizable (default is none)
  • +
  • Bug fix regarding mixed-case JIDs
  • +
  • Experimental code for receiving files - see the README
  • +
  • Read the manual
  • +
+ +

version 0.5 - 03-April-2004

+ +
    +
  • Modular design; plugins easier to write
  • +
  • Support for Ad-Hoc Commands and setting presence remotely
  • +
  • Browse documentation here
  • +
+ +

version 0.4.1 - 16-February-2004

+
    +
  • XEmacs support - basically works, needs testing and care.
  • +
  • "Set status" menu fixed
  • +
+ +

version 0.4 - 05-February-2004

+
    +
  • SHA-1 password hashing
  • +
  • customisation hooks
  • +
  • browsing improved
  • +
  • added service discovery
  • +
  • added in-band registration
  • +
  • added searching
  • +
  • new manual
  • +
+

version 0.3 - 21-April-2002

+
    +
  • improved groupchat support (thanks to nimrod for tip)
  • +
  • improved roster (buddy-list) display
  • +
  • infoquery rewritten - great improvement
  • +
  • improved minimal browsing support !
  • +
  • added customizable faces
  • +
  • added more customizable variables
  • +
  • more (helpful) comments in code
  • +
  • added explicit licensing (GPL, like emacs itself)
  • +

+

version 0.2 - 05-April-2002

+
    +
  • added groupchat support !
  • +
  • added minimal browsing support !
  • +
  • fixed bug in chat mode
  • +
  • abolished message mode (i only use chat mode anyway)
  • +
  • fixed some problems with incoming xml
  • +
  • added a pull-down menu
  • +
  • added customizable variables
  • +

+
+

features currently missing (TODO)

+
    +
  • global keymaps
  • +
  • logging (history)
  • +
  • overall exception handling
  • +
  • sanity checks (for incoming xml, and for myself :)
  • +
  • clickable URLs in messages
  • +
  • support for versions other than gnu-emacs-21 (older emacs, xemacs, jemacs)
  • +
  • and more...
  • +

+ +

Date: 2009/01/31 3:00:13 AM

+
+ diff --git a/www/list-of-releases.org b/www/list-of-releases.org new file mode 100644 index 0000000..0e6f807 --- /dev/null +++ b/www/list-of-releases.org @@ -0,0 +1,167 @@ +#+TITLE: List of releases - jabber.el +#+OPTIONS: author:nil skip:nil + +[[file:index.org][Back to main page]] + +#+BEGIN_HTML +

version 0.7.1 - 31-Jan-2007

+ +
    +
  • STARTTLS
  • +
  • SRV records (requires No Gnus)
  • +
  • Message composition buffer
  • +
  • XMPP URIs are handled (see manual for setup)
  • +
  • Autoaway
  • +
  • MUC improvements + +
      +
    • Don't display alerts for your own messages
    • +
    • Presence changes are sent to MUC rooms too
    • +
    • Check room features before joining
    • +
    + +
  • Avatars
  • + +
  • File transfer
  • + +
  • Sound files per contact for alerts
  • + +
  • New function: jabber-send-directed-presence
  • + +
  • Entity time supported (XEP-0090)
  • + +
  • Last activity supported (XEP-0012)
  • +
+ +

Read the manual.

+ + +

version 0.7 - 27-Dec-2005

+ +
    +
  • SSL connections possible
  • +
  • Chat buffers rewritten
  • +
  • MUC improved
  • +
  • Global key bindings under C-x C-j
  • +
  • Vcard viewer and editor
  • +
  • Roster export
  • +
  • Message events (JEP-0022)
  • +
  • Easy way to define external notifiers
  • +
  • Activity mode improved
  • +
  • Roster display optimized
  • +
  • Optionally use per-contact history files
  • +
  • Jabber menu in menubar not enabled by default
  • +
  • Flyspell in chat buffers
  • +
  • Different time formats for instant and delayed messages
  • +
  • Chat buffers in inactive windows are scrolled
  • +
  • Roster is sorted by name also
  • +
  • Countless bugfixes
  • +
+ +

Read the manual.

+ +

version 0.6.1 - 27-Dec-2004

+ +
    +
  • Now works with ejabberd
  • +
  • Message history (Mathias Dahl)
  • +
  • Backlogs
  • +
  • Activity tracking on the mode line (Carl Henrik Lunde)
  • +
  • Receive an alert when a specific person goes online (Mathias Dahl)
  • +
  • Support for /me in chats (Nolan Eakins)
  • +
  • Message alerts for current buffer can be disabled
  • +
  • Basic moderation support in MUC
  • +
  • MUC alerts are separated from ordinary message alerts
  • +
+ +

Read the +manual, +in which there is now a section on +protocol support.

+ +

version 0.6 - 17-Oct-2004

+ +
    +
  • Chat buffers no longer use the minibuffer, making it easier to use + ordinary editor commands and to pause writing halfway
  • +
  • The roster display and the chat buffer prompts are now customizable
  • +
  • The online status of the contact is displayed in the chat buffer + (Emacs only)
  • +
  • The connect function automatically sends presence after connecting
  • +
  • The names of the roster buffer and chat buffers are customizable
  • +
  • RET and C-k do what you would expect in the roster buffer
  • +
+ +

Read the manual.

+ +

version 0.5.1 - 08-May-2004

+ +
    +
  • Nicknames are accepted whenever a JID is asked for.
  • +
  • Roster entry spacing is customizable (default is none)
  • +
  • Bug fix regarding mixed-case JIDs
  • +
  • Experimental code for receiving files - see the README
  • +
  • Read the manual
  • +
+ +

version 0.5 - 03-April-2004

+ +
    +
  • Modular design; plugins easier to write
  • +
  • Support for Ad-Hoc Commands and setting presence remotely
  • +
  • Browse documentation here
  • +
+ +

version 0.4.1 - 16-February-2004

+
    +
  • XEmacs support - basically works, needs testing and care.
  • +
  • "Set status" menu fixed
  • +
+ +

version 0.4 - 05-February-2004

+
    +
  • SHA-1 password hashing
  • +
  • customisation hooks
  • +
  • browsing improved
  • +
  • added service discovery
  • +
  • added in-band registration
  • +
  • added searching
  • +
  • new manual
  • +
+

version 0.3 - 21-April-2002

+
    +
  • improved groupchat support (thanks to nimrod for tip)
  • +
  • improved roster (buddy-list) display
  • +
  • infoquery rewritten - great improvement
  • +
  • improved minimal browsing support !
  • +
  • added customizable faces
  • +
  • added more customizable variables
  • +
  • more (helpful) comments in code
  • +
  • added explicit licensing (GPL, like emacs itself)
  • +

+

version 0.2 - 05-April-2002

+
    +
  • added groupchat support !
  • +
  • added minimal browsing support !
  • +
  • fixed bug in chat mode
  • +
  • abolished message mode (i only use chat mode anyway)
  • +
  • fixed some problems with incoming xml
  • +
  • added a pull-down menu
  • +
  • added customizable variables
  • +

+
+

features currently missing (TODO)

+
    +
  • global keymaps
  • +
  • logging (history)
  • +
  • overall exception handling
  • +
  • sanity checks (for incoming xml, and for myself :)
  • +
  • clickable URLs in messages
  • +
  • support for versions other than gnu-emacs-21 (older emacs, xemacs, jemacs)
  • +
  • and more...
  • +

+#+END_HTML + +# Local variables: +# mode: org +# End: diff --git a/www/oldindex.html b/www/oldindex.html new file mode 100644 index 0000000..57f2408 --- /dev/null +++ b/www/oldindex.html @@ -0,0 +1,217 @@ + + + +jabber.el + + +

jabber.el


+

a minimal jabber client for emacs


+ + +SourceForge.net Logo + +

see also the +JabberEl page of EmacsWiki, +the mailing list and the chat room (it's jabber.el@conference.jabber.se, if your web browser is not configured for XMPP URIs).

+ +

if you use GNU Arch, you can use the +Arch mirror of the CVS repository. these commands get it for you:

+ +
tla register-archive http://www.dtek.chalmers.se/~henoch/archive-2005
+tla get mange@freemail.hu--2005/emacs-jabber--cvs-head--0 emacs-jabber
+ +

it's manually maintained, so bug Magnus (JID: legoscia@jabber.cd.chalmers.se) if it +doesn't seem up to date.

+ +

version 0.7.1 - 31-Jan-2007

+ +
    +
  • STARTTLS
  • +
  • SRV records (requires No Gnus)
  • +
  • Message composition buffer
  • +
  • XMPP URIs are handled (see manual for setup)
  • +
  • Autoaway
  • +
  • MUC improvements + +
      +
    • Don't display alerts for your own messages
    • +
    • Presence changes are sent to MUC rooms too
    • +
    • Check room features before joining
    • +
    + +
  • Avatars
  • + +
  • File transfer
  • + +
  • Sound files per contact for alerts
  • + +
  • New function: jabber-send-directed-presence
  • + +
  • Entity time supported (XEP-0090)
  • + +
  • Last activity supported (XEP-0012)
  • +
+ +

Read the manual.

+ + +

version 0.7 - 27-Dec-2005

+ +
    +
  • SSL connections possible
  • +
  • Chat buffers rewritten
  • +
  • MUC improved
  • +
  • Global key bindings under C-x C-j
  • +
  • Vcard viewer and editor
  • +
  • Roster export
  • +
  • Message events (JEP-0022)
  • +
  • Easy way to define external notifiers
  • +
  • Activity mode improved
  • +
  • Roster display optimized
  • +
  • Optionally use per-contact history files
  • +
  • Jabber menu in menubar not enabled by default
  • +
  • Flyspell in chat buffers
  • +
  • Different time formats for instant and delayed messages
  • +
  • Chat buffers in inactive windows are scrolled
  • +
  • Roster is sorted by name also
  • +
  • Countless bugfixes
  • +
+ +

Read the manual.

+ +

version 0.6.1 - 27-Dec-2004

+ +
    +
  • Now works with ejabberd
  • +
  • Message history (Mathias Dahl)
  • +
  • Backlogs
  • +
  • Activity tracking on the mode line (Carl Henrik Lunde)
  • +
  • Receive an alert when a specific person goes online (Mathias Dahl)
  • +
  • Support for /me in chats (Nolan Eakins)
  • +
  • Message alerts for current buffer can be disabled
  • +
  • Basic moderation support in MUC
  • +
  • MUC alerts are separated from ordinary message alerts
  • +
+ +

Read the +manual, +in which there is now a section on +protocol support.

+ +

version 0.6 - 17-Oct-2004

+ +
    +
  • Chat buffers no longer use the minibuffer, making it easier to use + ordinary editor commands and to pause writing halfway
  • +
  • The roster display and the chat buffer prompts are now customizable
  • +
  • The online status of the contact is displayed in the chat buffer + (Emacs only)
  • +
  • The connect function automatically sends presence after connecting
  • +
  • The names of the roster buffer and chat buffers are customizable
  • +
  • RET and C-k do what you would expect in the roster buffer
  • +
+ +

Read the manual.

+ +

version 0.5.1 - 08-May-2004

+ +
    +
  • Nicknames are accepted whenever a JID is asked for.
  • +
  • Roster entry spacing is customizable (default is none)
  • +
  • Bug fix regarding mixed-case JIDs
  • +
  • Experimental code for receiving files - see the README
  • +
  • Read the manual
  • +
+ +

version 0.5 - 03-April-2004

+ +
    +
  • Modular design; plugins easier to write
  • +
  • Support for Ad-Hoc Commands and setting presence remotely
  • +
  • Browse documentation here
  • +
+ +

version 0.4.1 - 16-February-2004

+
    +
  • XEmacs support - basically works, needs testing and care.
  • +
  • "Set status" menu fixed
  • +
+ +

version 0.4 - 05-February-2004

+
    +
  • SHA-1 password hashing
  • +
  • customisation hooks
  • +
  • browsing improved
  • +
  • added service discovery
  • +
  • added in-band registration
  • +
  • added searching
  • +
  • new manual
  • +
+

version 0.3 - 21-April-2002

+
    +
  • improved groupchat support (thanks to nimrod for tip)
  • +
  • improved roster (buddy-list) display
  • +
  • infoquery rewritten - great improvement
  • +
  • improved minimal browsing support !
  • +
  • added customizable faces
  • +
  • added more customizable variables
  • +
  • more (helpful) comments in code
  • +
  • added explicit licensing (GPL, like emacs itself)
  • +

+

version 0.2 - 05-April-2002

+
    +
  • added groupchat support !
  • +
  • added minimal browsing support !
  • +
  • fixed bug in chat mode
  • +
  • abolished message mode (i only use chat mode anyway)
  • +
  • fixed some problems with incoming xml
  • +
  • added a pull-down menu
  • +
  • added customizable variables
  • +

+
+

features currently missing (TODO)

+
    +
  • global keymaps
  • +
  • logging (history)
  • +
  • overall exception handling
  • +
  • sanity checks (for incoming xml, and for myself :)
  • +
  • clickable URLs in messages
  • +
  • support for versions other than gnu-emacs-21 (older emacs, xemacs, jemacs)
  • +
  • and more...
  • +

+
+

installation

+

get the module emacs-jabber from Sourceforge CVS. +additionally, if you don't have Gnus 5.10 (M-x gnus-version will tell), you will need hex-util.el +and sha1-el.el from Gnus CVS. +copy these to a directory in your emacs load path.

+

in your .emacs file add the call (require 'jabber). you can also use auto-load.

+

set your account details with M-x jabber-customize. then, type M-x jabber-connect to connect, +and M-x jabber-send-presence to send initial presence.

+

please note that the current version is dependant on gnu-emacs-21 - it depends on xml.el (which you can download separately) and on some newer functions.

+

howto

+

as jabber.el is still very young, you'll have to be rather emacs-savvy to use it (or at least have the willingness to learn a little).

+

to connect to the server, call (jabber-connect).
+call it from the pull-down menu or call interactively : [M-x jabber-connect RET] and enter the parameters from the minibuffer.

+

after a successful connection, a new buffer should appear, named *-jabber-*. this buffer is your roster view. click any user with the mouse to start a 1-on-1 chat.

+

alternatively, you can also call (jabber-chat-with jid).
+for example : (jabber-chat-with "object@magaf.org")
+or [M-x jabber-chat-with RET]. in that case you will be asked for a jid. use the TAB key to complete a partial jid from the roster. (nice, eh?)

+

to send a message, call (jabber-send-message to body subject type)
+for example : (jabber-send-message "object@magaf.org" "hi there" "" "") will appear as an instant message.
+(jabber-send-message "object@magaf.org" "tom, you are the best" "the truth, nothing but the truth" "normal") too.
+(jabber-send-message "object@magaf.org" "hya" "" "chat") will appear as a 1-on-1 chat
+of course, this too can be called interactively : [M-x jabber-send-message RET] (with jid completion, like anywhere else).

+

to change your status, call (jabber-send-presence show status).
+for example : (jabber-send-presence "" "Online") to appear online, or (jabber-send-presence "dnd" "copulating") to appear lucky.
+needless to say, this can be called from the minibuffer too. do [M-x jabber-send-presence RET] and enjoy TAB completion from "", "away", "xa", "dnd" or "chat" for the first parameter (show).

+

when a new chat message arrives, it is sent to the chat buffer ("*jabber-chat-:-user*"). if such a buffer does not exist it is created. in this buffer, all straight keystrokes are being captured for starting the reply command. to send a line, simply start typing...

+

all messages are converted to 1-on-1 chats (simply because i find that more convinient).

+

contact

+

jabber.el is written by Tom Berger (Jabber: object@jabber.org.uk) and +Magnus Henoch (Jabber: legoscia@jabber.cd.chalmers.se). feel free to contact us. you can also report bugs at the +Sourceforge project page.

+ + diff --git a/www/upload.sh b/www/upload.sh new file mode 100644 index 0000000..4065ae8 --- /dev/null +++ b/www/upload.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# In ~/.ssh/config, include the following +# +# Host emacs-jabber-www +# HostName web.sourceforge.net +# User SFUSERNAME,emacs-jabber + +if [ ! -f index.org ]; then + echo "Error: must be run in www checkout root" 1>&2 + exit 1 +fi + +rsync -v *.html emacs-jabber-www:htdocs/