appshare: Skype for Business compatibility
[siplcs.git] / src / core / sipe-appshare.c
blob480079d2079d6e1206b6550593db2b141a6fb493
1 /**
2 * @file sipe-appshare.c
4 * pidgin-sipe
6 * Copyright (C) 2014-2017 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include <glib.h>
24 #include <string.h>
26 #include <gio/gio.h>
28 #include "sipmsg.h"
29 #include "sipe-appshare.h"
30 #include "sipe-appshare-client.h"
31 #include "sipe-backend.h"
32 #include "sipe-buddy.h"
33 #include "sipe-chat.h"
34 #include "sipe-common.h"
35 #include "sipe-conf.h"
36 #include "sipe-core.h"
37 #include "sipe-core-private.h"
38 #include "sipe-media.h"
39 #include "sipe-nls.h"
40 #include "sipe-schedule.h"
41 #include "sipe-user.h"
42 #include "sipe-utils.h"
43 #include "sdpmsg.h"
45 struct sipe_appshare {
46 struct sipe_media_stream *stream;
47 GSocket *socket;
48 GIOChannel *channel;
49 guint rdp_channel_readable_watch_id;
50 guint rdp_channel_writable_watch_id;
51 struct sipe_user_ask_ctx *ask_ctx;
53 gchar rdp_channel_buffer[0x800];
54 gchar *rdp_channel_buffer_pos;
55 gsize rdp_channel_buffer_len;
57 struct sipe_rdp_client client;
60 static void
61 sipe_appshare_free(struct sipe_appshare *appshare)
63 if (appshare->rdp_channel_readable_watch_id != 0) {
64 g_source_destroy(g_main_context_find_source_by_id(NULL,
65 appshare->rdp_channel_readable_watch_id));
68 if (appshare->rdp_channel_writable_watch_id != 0) {
69 g_source_destroy(g_main_context_find_source_by_id(NULL,
70 appshare->rdp_channel_writable_watch_id));
73 if (appshare->channel) {
74 GError *error = NULL;
76 g_io_channel_shutdown(appshare->channel, TRUE, &error);
77 if (error) {
78 SIPE_DEBUG_ERROR("Error shutting down RDP channel: %s",
79 error->message);
80 g_error_free(error);
82 g_io_channel_unref(appshare->channel);
85 if (appshare->socket) {
86 g_object_unref(appshare->socket);
89 if (appshare->ask_ctx) {
90 sipe_user_close_ask(appshare->ask_ctx);
93 g_free(appshare->client.cmdline);
94 if (appshare->client.free_cb) {
95 appshare->client.free_cb(&appshare->client);
98 g_free(appshare);
101 static gboolean
102 rdp_channel_readable_cb(GIOChannel *channel,
103 GIOCondition condition,
104 gpointer data)
106 struct sipe_appshare *appshare = data;
107 GError *error = NULL;
108 gchar *buffer;
109 gsize bytes_read;
111 if (condition & G_IO_HUP) {
112 struct sipe_media_call *call = appshare->stream->call;
114 sipe_backend_media_hangup(call->backend_private, TRUE);
115 return FALSE;
118 buffer = g_malloc(2048);
119 while (sipe_media_stream_is_writable(appshare->stream)) {
120 GIOStatus status;
122 status = g_io_channel_read_chars(channel,
123 buffer, 2048,
124 &bytes_read, &error);
125 if (error) {
126 struct sipe_media_call *call = appshare->stream->call;
128 SIPE_DEBUG_ERROR("Error reading from RDP channel: %s",
129 error->message);
130 g_error_free(error);
131 sipe_backend_media_hangup(call->backend_private, TRUE);
132 g_free(buffer);
133 return FALSE;
136 if (status == G_IO_STATUS_EOF) {
137 struct sipe_media_call *call = appshare->stream->call;
139 sipe_backend_media_hangup(call->backend_private, TRUE);
140 g_free(buffer);
141 return FALSE;
144 if (bytes_read == 0) {
145 break;
148 sipe_media_stream_write(appshare->stream, (guint8 *)buffer,
149 bytes_read);
150 SIPE_DEBUG_INFO("Written: %" G_GSIZE_FORMAT "\n", bytes_read);
152 g_free(buffer);
154 return TRUE;
157 static gboolean
158 socket_connect_cb(SIPE_UNUSED_PARAMETER GIOChannel *channel,
159 SIPE_UNUSED_PARAMETER GIOCondition condition,
160 gpointer data)
162 struct sipe_appshare *appshare = data;
163 GError *error = NULL;
164 GSocket *data_socket;
165 int fd;
167 data_socket = g_socket_accept(appshare->socket, NULL, &error);
168 if (error) {
169 struct sipe_media_call *call = appshare->stream->call;
171 SIPE_DEBUG_ERROR("Error accepting RDP client connection: %s",
172 error->message);
173 g_error_free(error);
174 sipe_backend_media_hangup(call->backend_private, TRUE);
175 return FALSE;
178 g_io_channel_shutdown(appshare->channel, TRUE, &error);
179 if (error) {
180 struct sipe_media_call *call = appshare->stream->call;
182 SIPE_DEBUG_ERROR("Error shutting down RDP channel: %s",
183 error->message);
184 g_error_free(error);
185 g_object_unref(data_socket);
186 sipe_backend_media_hangup(call->backend_private, TRUE);
187 return FALSE;
189 g_io_channel_unref(appshare->channel);
191 g_object_unref(appshare->socket);
192 appshare->socket = data_socket;
194 fd = g_socket_get_fd(appshare->socket);
195 if (fd < 0) {
196 struct sipe_media_call *call = appshare->stream->call;
198 SIPE_DEBUG_ERROR_NOFORMAT("Invalid file descriptor for RDP client connection socket");
199 sipe_backend_media_hangup(call->backend_private, TRUE);
200 return FALSE;
202 appshare->channel = g_io_channel_unix_new(fd);
204 // No encoding for binary data
205 g_io_channel_set_encoding(appshare->channel, NULL, &error);
206 if (error) {
207 struct sipe_media_call *call = appshare->stream->call;
209 SIPE_DEBUG_ERROR("Error setting RDP channel encoding: %s",
210 error->message);
211 g_error_free(error);
212 sipe_backend_media_hangup(call->backend_private, TRUE);
213 return FALSE;
216 appshare->rdp_channel_readable_watch_id =
217 g_io_add_watch(appshare->channel, G_IO_IN | G_IO_HUP,
218 rdp_channel_readable_cb, appshare);
220 return FALSE;
223 static void
224 launch_rdp_client(struct sipe_appshare *appshare)
226 struct sipe_rdp_client *client = &appshare->client;
227 struct sipe_media_call *call = appshare->stream->call;
228 GSocketAddress *address;
229 GError *error = NULL;
230 int fd;
232 address = client->get_listen_address_cb(client);
233 if (!address) {
234 sipe_backend_media_hangup(call->backend_private, TRUE);
235 return;
238 appshare->socket = g_socket_new(g_socket_address_get_family(address),
239 G_SOCKET_TYPE_STREAM,
240 G_SOCKET_PROTOCOL_DEFAULT,
241 &error);
242 if (error) {
243 SIPE_DEBUG_ERROR("Can't create RDP client listen socket: %s",
244 error->message);
245 g_error_free(error);
246 g_object_unref(address);
247 sipe_backend_media_hangup(call->backend_private, TRUE);
248 return;
251 g_socket_set_blocking(appshare->socket, FALSE);
253 g_socket_bind(appshare->socket, address, TRUE, &error);
254 g_object_unref(address);
255 if (error) {
256 SIPE_DEBUG_ERROR("Can't bind to RDP client socket: %s",
257 error->message);
258 g_error_free(error);
259 sipe_backend_media_hangup(call->backend_private, TRUE);
260 return;
263 g_socket_listen(appshare->socket, &error);
264 if (error) {
265 SIPE_DEBUG_ERROR("Can't listen on RDP client socket: %s",
266 error->message);
267 g_error_free(error);
268 sipe_backend_media_hangup(call->backend_private, TRUE);
269 return;
272 fd = g_socket_get_fd(appshare->socket);
273 if (fd < 0) {
274 SIPE_DEBUG_ERROR_NOFORMAT("Invalid file descriptor for RDP client listen socket");
275 sipe_backend_media_hangup(call->backend_private, TRUE);
276 return;
278 appshare->channel = g_io_channel_unix_new(fd);
280 appshare->rdp_channel_readable_watch_id =
281 g_io_add_watch(appshare->channel, G_IO_IN,
282 socket_connect_cb, appshare);
284 address = g_socket_get_local_address(appshare->socket, &error);
285 if (error) {
286 SIPE_DEBUG_ERROR("Couldn't get appshare socket address: %s",
287 error->message);
288 g_error_free(error);
289 sipe_backend_media_hangup(call->backend_private, TRUE);
290 return;
293 if (!client->launch_cb(client, address, appshare->stream)) {
294 sipe_backend_media_hangup(call->backend_private, TRUE);
297 g_object_unref(address);
300 static gssize
301 rdp_client_channel_write(struct sipe_appshare *appshare)
303 gsize bytes_written;
304 GError *error = NULL;
306 g_io_channel_write_chars(appshare->channel,
307 appshare->rdp_channel_buffer_pos,
308 appshare->rdp_channel_buffer_len,
309 &bytes_written, &error);
310 if (error) {
311 SIPE_DEBUG_ERROR("Couldn't write data to RDP client: %s",
312 error->message);
313 g_error_free(error);
314 return -1;
317 g_io_channel_flush(appshare->channel, &error);
318 if (error) {
319 if (g_error_matches(error, G_IO_CHANNEL_ERROR,
320 G_IO_CHANNEL_ERROR_PIPE)) {
321 /* Ignore broken pipe here and wait for the call to be
322 * hung up upon G_IO_HUP in client_channel_cb(). */
323 g_error_free(error);
324 return 0;
327 SIPE_DEBUG_ERROR("Couldn't flush RDP channel: %s",
328 error->message);
329 g_error_free(error);
330 return -1;
333 appshare->rdp_channel_buffer_pos += bytes_written;
334 appshare->rdp_channel_buffer_len -= bytes_written;
336 return bytes_written;
339 static void
340 delayed_hangup_cb(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
341 gpointer data)
343 struct sipe_media_call *call = data;
345 sipe_backend_media_hangup(call->backend_private, TRUE);
348 static gboolean
349 rdp_channel_writable_cb(SIPE_UNUSED_PARAMETER GIOChannel *channel,
350 SIPE_UNUSED_PARAMETER GIOCondition condition,
351 gpointer data)
353 struct sipe_appshare *appshare = data;
354 struct sipe_media_call *call = appshare->stream->call;
356 if (rdp_client_channel_write(appshare) < 0) {
357 sipe_backend_media_hangup(call->backend_private, TRUE);
358 return FALSE;
361 if (appshare->rdp_channel_buffer_len == 0) {
362 // Writing done, disconnect writable watch.
363 appshare->rdp_channel_writable_watch_id = 0;
364 return FALSE;
367 return TRUE;
370 static void
371 read_cb(struct sipe_media_stream *stream)
373 struct sipe_appshare *appshare = sipe_media_stream_get_data(stream);
374 gint bytes_read = 0;
375 gssize bytes_written = 0;
377 if (appshare->rdp_channel_writable_watch_id != 0) {
378 // Data still in the buffer. Let the client read it first.
379 return;
382 while (bytes_read == (gint)bytes_written) {
383 bytes_read = sipe_backend_media_stream_read(stream,
384 (guint8 *)appshare->rdp_channel_buffer,
385 sizeof (appshare->rdp_channel_buffer));
386 if (bytes_read == 0) {
387 return;
390 appshare->rdp_channel_buffer_pos = appshare->rdp_channel_buffer;
391 appshare->rdp_channel_buffer_len = bytes_read;
393 bytes_written = rdp_client_channel_write(appshare);
395 if (bytes_written < 0) {
396 /* Don't deallocate stream while in its read callback.
397 * Schedule call hangup to be executed after we're back
398 * in the message loop. */
399 sipe_schedule_seconds(sipe_media_get_sipe_core_private(stream->call),
400 "appshare delayed hangup",
401 stream->call->backend_private,
403 delayed_hangup_cb,
404 NULL);
405 return;
409 if (bytes_read != (gint)bytes_written) {
410 /* Schedule writing of the buffer's remainder to when
411 * RDP channel becomes writable again. */
412 appshare->rdp_channel_writable_watch_id =
413 g_io_add_watch(appshare->channel, G_IO_OUT,
414 rdp_channel_writable_cb,
415 appshare);
419 static void
420 writable_cb(struct sipe_media_stream *stream)
422 struct sipe_appshare *appshare = sipe_media_stream_get_data(stream);
424 if (!appshare->socket) {
425 launch_rdp_client(appshare);
429 static void
430 accept_cb(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
431 gpointer data)
433 struct sipe_appshare *appshare = data;
434 appshare->ask_ctx = NULL;
436 sipe_backend_media_accept(appshare->stream->call->backend_private, TRUE);
439 static void
440 decline_cb(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
441 gpointer data)
443 struct sipe_appshare *appshare = data;
444 appshare->ask_ctx = NULL;
446 sipe_backend_media_hangup(appshare->stream->call->backend_private, TRUE);
449 static struct sipe_user_ask_ctx *
450 ask_accept_applicationsharing(struct sipe_core_private *sipe_private,
451 const gchar *from,
452 SipeUserAskCb accept_cb,
453 SipeUserAskCb decline_cb,
454 gpointer user_data)
456 struct sipe_user_ask_ctx *ctx;
457 gchar *alias = sipe_buddy_get_alias(sipe_private, from);
458 gchar *ask_msg = g_strdup_printf(_("%s wants to start presenting"),
459 alias ? alias : from);
461 ctx = sipe_user_ask(sipe_private, ask_msg,
462 _("Accept"), accept_cb,
463 _("Decline"), decline_cb,
464 user_data);
466 g_free(ask_msg);
467 g_free(alias);
469 return ctx;
472 static struct sipe_appshare *
473 initialize_appshare(struct sipe_media_stream *stream)
475 struct sipe_appshare *appshare;
476 struct sipe_media_call *call;
477 struct sipe_core_private *sipe_private;
478 const gchar *cmdline;
480 call = stream->call;
481 sipe_private = sipe_media_get_sipe_core_private(call);
483 appshare = g_new0(struct sipe_appshare, 1);
484 appshare->stream = stream;
486 sipe_media_stream_set_data(stream, appshare,
487 (GDestroyNotify)sipe_appshare_free);
489 cmdline = sipe_backend_setting(SIPE_CORE_PUBLIC,
490 SIPE_SETTING_RDP_CLIENT);
491 if (is_empty(cmdline))
492 cmdline = "remmina";
493 appshare->client.cmdline = g_strdup(cmdline);
495 if (strstr(cmdline, "xfreerdp")) {
496 sipe_appshare_xfreerdp_init(&appshare->client);
497 } else if (strstr(cmdline, "remmina")) {
498 sipe_appshare_remmina_init(&appshare->client);
499 } else {
500 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
501 _("Application sharing error"),
502 _("Unknown remote desktop client configured."));
503 sipe_backend_media_hangup(call->backend_private, TRUE);
504 return NULL;
507 sipe_media_stream_add_extra_attribute(stream,
508 "x-applicationsharing-session-id", "1");
509 sipe_media_stream_add_extra_attribute(stream,
510 "x-applicationsharing-role", "viewer");
511 sipe_media_stream_add_extra_attribute(stream,
512 "x-applicationsharing-media-type", "rdp");
514 stream->read_cb = read_cb;
515 stream->writable_cb = writable_cb;
517 return appshare;
520 void
521 process_incoming_invite_appshare(struct sipe_core_private *sipe_private,
522 struct sipmsg *msg)
524 struct sipe_media_call *call;
525 struct sipe_media_stream *stream;
526 struct sipe_appshare *appshare;
527 struct sdpmsg *sdpmsg;
528 GSList *i;
530 sdpmsg = sdpmsg_parse_msg(msg->body);
532 /* Skype for Business compatibility - ignore desktop video. */
533 i = sdpmsg->media;
534 while (i) {
535 struct sdpmedia *media = i->data;
536 const gchar *label;
538 i = i->next;
540 label = sipe_utils_nameval_find(media->attributes, "label");
542 if (sipe_strequal(media->name, "video") &&
543 sipe_strequal(label, "applicationsharing-video")) {
544 sdpmsg->media = g_slist_remove(sdpmsg->media, media);
545 sdpmedia_free(media);
549 call = process_incoming_invite_call(sipe_private, msg, sdpmsg);
550 if (!call) {
551 return;
554 stream = sipe_core_media_get_stream_by_id(call, "applicationsharing");
555 if (!stream) {
556 sipe_backend_media_hangup(call->backend_private, TRUE);
557 return;
560 appshare = initialize_appshare(stream);
562 if (appshare) {
563 gchar *from;
565 from = parse_from(sipmsg_find_header(msg, "From"));
566 appshare->ask_ctx = ask_accept_applicationsharing(sipe_private, from,
567 accept_cb,
568 decline_cb,
569 appshare);
570 g_free(from);
574 static void
575 connect_conference(struct sipe_core_private *sipe_private,
576 struct sipe_chat_session *chat_session)
578 struct sipe_media_call *call;
579 struct sipe_media_stream *stream;
580 gchar * uri;
582 chat_session->appshare_ask_ctx = NULL;
584 uri = sipe_conf_build_uri(chat_session->id, "applicationsharing");
586 call = sipe_media_call_new(sipe_private, uri, NULL,
587 SIPE_ICE_RFC_5245,
588 SIPE_MEDIA_CALL_NO_UI);
590 g_free(uri);
592 stream = sipe_media_stream_add(call, "applicationsharing",
593 SIPE_MEDIA_APPLICATION,
594 SIPE_ICE_RFC_5245, TRUE, 0);
595 if (!stream) {
596 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
597 _("Application sharing error"),
598 _("Couldn't connect application sharing"));
599 sipe_backend_media_hangup(call->backend_private, FALSE);
602 sipe_media_stream_add_extra_attribute(stream, "connection", "new");
603 sipe_media_stream_add_extra_attribute(stream, "setup", "active");
605 initialize_appshare(stream);
608 void
609 sipe_core_appshare_connect_conference(struct sipe_core_public *sipe_public,
610 struct sipe_chat_session *chat_session,
611 gboolean user_must_accept)
613 if (user_must_accept) {
614 const gchar *from;
616 if (chat_session->appshare_ask_ctx) {
617 // Accept dialog already opened.
618 return;
621 if (chat_session->title) {
622 from = chat_session->title;
623 } else if (chat_session->organizer) {
624 from = chat_session->organizer;
625 } else {
626 from = chat_session->id;
629 chat_session->appshare_ask_ctx =
630 ask_accept_applicationsharing(SIPE_CORE_PRIVATE,
631 from,
632 (SipeUserAskCb)connect_conference,
633 NULL,
634 chat_session);
635 } else {
636 connect_conference(SIPE_CORE_PRIVATE, chat_session);
641 Local Variables:
642 mode: c
643 c-file-style: "bsd"
644 indent-tabs-mode: t
645 tab-width: 8
646 End: