Mercurial > pidgin
comparison libpurple/protocols/jabber/google.c @ 23759:315151da0dc6
Basic Google Talk voice call support. No UI; receiving a call auto-accepts it.
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Wed, 05 Sep 2007 00:47:58 +0000 |
parents | 0580b246b2c5 |
children | 70cdff43ec76 |
comparison
equal
deleted
inserted
replaced
23758:1e5d5d55a231 | 23759:315151da0dc6 |
---|---|
16 * You should have received a copy of the GNU General Public License | 16 * You should have received a copy of the GNU General Public License |
17 * along with this program; if not, write to the Free Software | 17 * along with this program; if not, write to the Free Software |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
19 */ | 19 */ |
20 | 20 |
21 #include <farsight/farsight-transport.h> | |
22 | |
21 #include "internal.h" | 23 #include "internal.h" |
22 #include "debug.h" | 24 #include "debug.h" |
25 #include "mediamanager.h" | |
23 #include "util.h" | 26 #include "util.h" |
24 #include "privacy.h" | 27 #include "privacy.h" |
25 | 28 |
26 #include "buddy.h" | 29 #include "buddy.h" |
27 #include "google.h" | 30 #include "google.h" |
28 #include "jabber.h" | 31 #include "jabber.h" |
29 #include "presence.h" | 32 #include "presence.h" |
30 #include "iq.h" | 33 #include "iq.h" |
34 | |
35 typedef struct { | |
36 char *id; | |
37 char *initiator; | |
38 } GoogleSessionId; | |
39 | |
40 typedef enum { | |
41 UNINIT, | |
42 SENT_INITIATE, | |
43 RECEIVED_INITIATE, | |
44 IN_PRORESS, | |
45 TERMINATED | |
46 } GoogleSessionState; | |
47 | |
48 typedef struct { | |
49 GoogleSessionId id; | |
50 GoogleSessionState state; | |
51 PurpleMedia *media; | |
52 FarsightStream *stream; | |
53 JabberStream *js; | |
54 char *remote_jid; | |
55 } GoogleSession; | |
56 | |
57 | |
58 static guint | |
59 google_session_id_hash(gconstpointer key) | |
60 { | |
61 GoogleSessionId *id = (GoogleSessionId*)key; | |
62 | |
63 guint id_hash = g_str_hash(id->id); | |
64 guint init_hash = g_str_hash(id->initiator); | |
65 | |
66 return 23 * id_hash + init_hash; | |
67 } | |
68 | |
69 static gboolean | |
70 google_session_id_equal(gconstpointer a, gconstpointer b) | |
71 { | |
72 GoogleSessionId *c = (GoogleSessionId*)a; | |
73 GoogleSessionId *d = (GoogleSessionId*)b; | |
74 | |
75 return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator); | |
76 } | |
77 | |
78 GHashTable *sessions = NULL; | |
79 | |
80 static xmlnode * | |
81 google_session_create_xmlnode(GoogleSession *session, const char *type) | |
82 { | |
83 xmlnode *node = xmlnode_new("session"); | |
84 xmlnode_set_namespace(node, "http://www.google.com/session"); | |
85 xmlnode_set_attrib(node, "id", session->id.id); | |
86 xmlnode_set_attrib(node, "initiator", session->id.initiator); | |
87 xmlnode_set_attrib(node, "type", type); | |
88 return node; | |
89 } | |
90 | |
91 static void | |
92 google_session_send_accept(GoogleSession *session) | |
93 { | |
94 xmlnode *sess, *desc, *payload; | |
95 GList *codecs = farsight_stream_get_codec_intersection(session->stream); | |
96 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
97 | |
98 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
99 sess = google_session_create_xmlnode(session, "accept"); | |
100 xmlnode_insert_child(iq->node, sess); | |
101 desc = xmlnode_new_child(sess, "description"); | |
102 xmlnode_set_namespace(desc, "http://www.google.com/session/phone"); | |
103 | |
104 for (;codecs; codecs = codecs->next) { | |
105 FarsightCodec *codec = (FarsightCodec*)codecs->data; | |
106 char id[8], clockrate[10]; | |
107 payload = xmlnode_new_child(desc, "payload-type"); | |
108 g_snprintf(id, sizeof(id), "%d", codec->id); | |
109 g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate); | |
110 xmlnode_set_attrib(payload, "name", codec->encoding_name); | |
111 xmlnode_set_attrib(payload, "id", id); | |
112 xmlnode_set_attrib(payload, "clockrate", clockrate); | |
113 } | |
114 | |
115 jabber_iq_send(iq); | |
116 farsight_stream_start(session->stream); | |
117 } | |
118 | |
119 static void | |
120 google_session_candidates_prepared (FarsightStream *stream, gchar *candidate_id, GoogleSession *session) | |
121 { | |
122 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
123 GList *candidates = farsight_stream_get_native_candidate_list(stream); | |
124 FarsightTransportInfo *transport; | |
125 xmlnode *sess; | |
126 xmlnode *candidate; | |
127 sess = google_session_create_xmlnode(session, "candidates"); | |
128 xmlnode_insert_child(iq->node, sess); | |
129 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
130 | |
131 for (;candidates;candidates = candidates->next) { | |
132 transport = (FarsightTransportInfo*)(candidates->data); | |
133 char port[8]; | |
134 char pref[8]; | |
135 | |
136 if (!strcmp(transport->ip, "127.0.0.1")) | |
137 continue; | |
138 | |
139 candidate = xmlnode_new("candidate"); | |
140 | |
141 g_snprintf(port, sizeof(port), "%d", transport->port); | |
142 g_snprintf(pref, sizeof(pref), "%f", transport->preference); | |
143 | |
144 xmlnode_set_attrib(candidate, "address", transport->ip); | |
145 xmlnode_set_attrib(candidate, "port", port); | |
146 xmlnode_set_attrib(candidate, "name", "rtp"); | |
147 xmlnode_set_attrib(candidate, "username", transport->username); | |
148 xmlnode_set_attrib(candidate, "password", transport->password); | |
149 xmlnode_set_attrib(candidate, "preference", pref); | |
150 xmlnode_set_attrib(candidate, "protocol", transport->proto == FARSIGHT_NETWORK_PROTOCOL_UDP ? "udp" : "tcp"); | |
151 xmlnode_set_attrib(candidate, "type", transport->type == FARSIGHT_CANDIDATE_TYPE_LOCAL ? "local" : | |
152 transport->type == FARSIGHT_CANDIDATE_TYPE_DERIVED ? "stun" : | |
153 transport->type == FARSIGHT_CANDIDATE_TYPE_RELAY ? "relay" : NULL); | |
154 xmlnode_set_attrib(candidate, "generation", "0"); | |
155 xmlnode_set_attrib(candidate, "network", "0"); | |
156 xmlnode_insert_child(sess, candidate); | |
157 | |
158 } | |
159 jabber_iq_send(iq); | |
160 } | |
161 | |
162 static gboolean | |
163 google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet) | |
164 { | |
165 PurpleMedia *media; | |
166 FarsightSession *fs; | |
167 GList *codecs = NULL; | |
168 xmlnode *desc_element, *codec_element; | |
169 FarsightCodec *codec; | |
170 const char *id, *encoding_name, *clock_rate; | |
171 int res; | |
172 | |
173 | |
174 if (session->state != UNINIT) { | |
175 purple_debug_error("jabber", "Received initiate for active session.\n"); | |
176 return FALSE; | |
177 } | |
178 | |
179 fs = farsight_session_factory_make("rtp"); | |
180 if (!fs) { | |
181 purple_debug_error("jabber", "Farsight's rtp plugin not installed"); | |
182 return FALSE; | |
183 } | |
184 session->stream = farsight_session_create_stream(fs, FARSIGHT_MEDIA_TYPE_AUDIO, FARSIGHT_STREAM_DIRECTION_BOTH); | |
185 | |
186 g_object_set(G_OBJECT(session->stream), "transmitter", "libjingle", NULL); | |
187 | |
188 desc_element = xmlnode_get_child(packet, "description"); | |
189 | |
190 for (codec_element = xmlnode_get_child(desc_element, "payload-type"); | |
191 codec_element; | |
192 codec_element = xmlnode_get_next_twin(codec_element)) { | |
193 encoding_name = xmlnode_get_attrib(codec_element, "name"); | |
194 id = xmlnode_get_attrib(codec_element, "id"); | |
195 clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); | |
196 | |
197 codec = g_new0(FarsightCodec, 1); | |
198 farsight_codec_init(codec, atoi(id), encoding_name, FARSIGHT_MEDIA_TYPE_AUDIO, clock_rate ? atoi(clock_rate) : 0); | |
199 codecs = g_list_append(codecs, codec); | |
200 } | |
201 GstElement *e = gst_element_factory_make("alsasrc", "source"); | |
202 farsight_stream_set_source(session->stream, e); | |
203 farsight_stream_set_source_filter(session->stream, gst_caps_new_simple("audio/x-raw-int", "rate",G_TYPE_INT,8000, NULL)); | |
204 gst_object_unref(e); | |
205 | |
206 e = gst_element_factory_make("alsasink", "fakes"); | |
207 g_object_set(e, "sync", FALSE, NULL); | |
208 farsight_stream_set_sink(session->stream, e); | |
209 gst_object_unref(e); | |
210 | |
211 farsight_stream_prepare_transports(session->stream); | |
212 res = farsight_stream_set_remote_codecs(session->stream, codecs); | |
213 | |
214 | |
215 farsight_codec_list_destroy(codecs); | |
216 g_signal_connect(G_OBJECT(session->stream), "new-native-candidate", G_CALLBACK(google_session_candidates_prepared), session); | |
217 google_session_send_accept(session); | |
218 media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, session->remote_jid); | |
219 return res; | |
220 } | |
221 | |
222 static gboolean | |
223 google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *sess) | |
224 { | |
225 GList *list = NULL; | |
226 xmlnode *cand; | |
227 static int name = 0; | |
228 char n[4]; | |
229 | |
230 for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { | |
231 FarsightTransportInfo *info = g_new0(FarsightTransportInfo, 1); | |
232 g_snprintf(n, sizeof(n), "S%d", name++); | |
233 info->ip = xmlnode_get_attrib(cand, "address"); | |
234 info->port = atoi(xmlnode_get_attrib(cand, "port")); | |
235 info->proto = !strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ? FARSIGHT_NETWORK_PROTOCOL_UDP : FARSIGHT_NETWORK_PROTOCOL_TCP; | |
236 info->preference = atof(xmlnode_get_attrib(cand, "preference")); | |
237 info->type = !strcmp(xmlnode_get_attrib(cand, "type"), "local") ? FARSIGHT_CANDIDATE_TYPE_LOCAL : | |
238 !strcmp(xmlnode_get_attrib(cand, "type"), "stun") ? FARSIGHT_CANDIDATE_TYPE_DERIVED : | |
239 !strcmp(xmlnode_get_attrib(cand, "type"), "relay") ? FARSIGHT_CANDIDATE_TYPE_RELAY : FARSIGHT_CANDIDATE_TYPE_LOCAL; | |
240 info->candidate_id = n; | |
241 info->username = xmlnode_get_attrib(cand, "username"); | |
242 info->password = xmlnode_get_attrib(cand, "password"); | |
243 list = g_list_append(list, info); | |
244 } | |
245 | |
246 farsight_stream_add_remote_candidate(session->stream, list); | |
247 g_list_foreach(list, g_free, NULL); | |
248 g_list_free(list); | |
249 return TRUE; | |
250 } | |
251 | |
252 static void | |
253 google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet) | |
254 { | |
255 JabberIq *result; | |
256 gboolean valid = TRUE; | |
257 xmlnode *sess = xmlnode_get_child(packet, "session"); | |
258 const char *type = xmlnode_get_attrib(sess, "type"); | |
259 | |
260 if (!strcmp(type, "initiate")) { | |
261 valid = google_session_handle_initiate(js, session, sess); | |
262 } else if (!strcmp(type, "accept")) { | |
263 } else if (!strcmp(type, "reject")) { | |
264 } else if (!strcmp(type, "terminate")) { | |
265 } else if (!strcmp(type, "candidates")) { | |
266 valid = google_session_handle_candidates(js, session, sess); | |
267 } | |
268 | |
269 if (valid) { | |
270 result = jabber_iq_new(js, JABBER_IQ_RESULT); | |
271 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); | |
272 xmlnode_set_attrib(result->node, "to", session->remote_jid); | |
273 jabber_iq_send(result); | |
274 } | |
275 } | |
276 | |
277 void | |
278 jabber_google_session_parse(JabberStream *js, xmlnode *packet) | |
279 { | |
280 GoogleSession *session; | |
281 GoogleSessionId id; | |
282 JabberIq *result; | |
283 | |
284 xmlnode *session_node; | |
285 xmlnode *desc_node; | |
286 | |
287 if (strcmp(xmlnode_get_attrib(packet, "type"), "set")) | |
288 return; | |
289 | |
290 session_node = xmlnode_get_child(packet, "session"); | |
291 if (!session_node) | |
292 return; | |
293 | |
294 id.id = xmlnode_get_attrib(session_node, "id"); | |
295 if (!id.id) | |
296 return; | |
297 | |
298 id.initiator = xmlnode_get_attrib(session_node, "initiator"); | |
299 if (!id.initiator) | |
300 return; | |
301 | |
302 if (sessions == NULL) | |
303 sessions = g_hash_table_new(google_session_id_hash, google_session_id_equal); | |
304 session = (GoogleSession*)g_hash_table_lookup(sessions, &id); | |
305 | |
306 if (session) { | |
307 google_session_parse_iq(js, session, packet); | |
308 return; | |
309 } | |
310 | |
311 /* If the session doesn't exist, this has to be an initiate message */ | |
312 if (strcmp(xmlnode_get_attrib(session_node, "type"), "initiate")) | |
313 return; | |
314 desc_node = xmlnode_get_child(session_node, "description"); | |
315 if (!desc_node) | |
316 return; | |
317 session = g_new0(GoogleSession, 1); | |
318 session->id.id = g_strdup(id.id); | |
319 session->id.initiator = g_strdup(id.initiator); | |
320 session->state = UNINIT; | |
321 session->js = js; | |
322 session->remote_jid = g_strdup(session->id.initiator); | |
323 g_hash_table_insert(sessions, &(session->id), session); | |
324 | |
325 google_session_parse_iq(js, session, packet); | |
326 } | |
31 | 327 |
32 static void | 328 static void |
33 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) | 329 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) |
34 { | 330 { |
35 const char *type = xmlnode_get_attrib(packet, "type"); | 331 const char *type = xmlnode_get_attrib(packet, "type"); |