Fix translation of the menu items on the media window.
[pidgin-git.git] / pidgin / gtkmedia.c
blob93308022f04754d6a25744d6d89e0f21a4974afe
1 /**
2 * @file media.c Account API
3 * @ingroup core
5 * Pidgin
7 * Pidgin is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "internal.h"
27 #include "debug.h"
28 #include "connection.h"
29 #include "media.h"
30 #include "mediamanager.h"
31 #include "pidgin.h"
32 #include "request.h"
34 #include "gtkmedia.h"
35 #include "gtkutils.h"
36 #include "pidginstock.h"
38 #ifdef USE_VV
39 #include "media-gst.h"
41 #ifdef _WIN32
42 #include <gdk/gdkwin32.h>
43 #endif
45 #include <gst/interfaces/xoverlay.h>
47 #define PIDGIN_TYPE_MEDIA (pidgin_media_get_type())
48 #define PIDGIN_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia))
49 #define PIDGIN_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass))
50 #define PIDGIN_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA))
51 #define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA))
52 #define PIDGIN_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass))
54 typedef struct _PidginMedia PidginMedia;
55 typedef struct _PidginMediaClass PidginMediaClass;
56 typedef struct _PidginMediaPrivate PidginMediaPrivate;
58 typedef enum
60 /* Waiting for response */
61 PIDGIN_MEDIA_WAITING = 1,
62 /* Got request */
63 PIDGIN_MEDIA_REQUESTED,
64 /* Accepted call */
65 PIDGIN_MEDIA_ACCEPTED,
66 /* Rejected call */
67 PIDGIN_MEDIA_REJECTED,
68 } PidginMediaState;
70 struct _PidginMediaClass
72 GtkWindowClass parent_class;
75 struct _PidginMedia
77 GtkWindow parent;
78 PidginMediaPrivate *priv;
81 struct _PidginMediaPrivate
83 PurpleMedia *media;
84 gchar *screenname;
85 gulong level_handler_id;
87 GtkUIManager *ui;
88 GtkWidget *menubar;
89 GtkWidget *statusbar;
91 GtkWidget *hold;
92 GtkWidget *mute;
93 GtkWidget *pause;
95 GtkWidget *send_progress;
96 GHashTable *recv_progressbars;
98 PidginMediaState state;
100 GtkWidget *display;
101 GtkWidget *send_widget;
102 GtkWidget *recv_widget;
103 GtkWidget *button_widget;
104 GtkWidget *local_video;
105 GHashTable *remote_videos;
107 guint timeout_id;
108 PurpleMediaSessionType request_type;
111 #define PIDGIN_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_MEDIA, PidginMediaPrivate))
113 static void pidgin_media_class_init (PidginMediaClass *klass);
114 static void pidgin_media_init (PidginMedia *media);
115 static void pidgin_media_dispose (GObject *object);
116 static void pidgin_media_finalize (GObject *object);
117 static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
118 static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
119 static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state);
121 static GtkWindowClass *parent_class = NULL;
124 #if 0
125 enum {
126 LAST_SIGNAL
128 static guint pidgin_media_signals[LAST_SIGNAL] = {0};
129 #endif
131 enum {
132 PROP_0,
133 PROP_MEDIA,
134 PROP_SCREENNAME
137 static GType
138 pidgin_media_get_type(void)
140 static GType type = 0;
142 if (type == 0) {
143 static const GTypeInfo info = {
144 sizeof(PidginMediaClass),
145 NULL,
146 NULL,
147 (GClassInitFunc) pidgin_media_class_init,
148 NULL,
149 NULL,
150 sizeof(PidginMedia),
152 (GInstanceInitFunc) pidgin_media_init,
153 NULL
155 type = g_type_register_static(GTK_TYPE_WINDOW, "PidginMedia", &info, 0);
157 return type;
161 static void
162 pidgin_media_class_init (PidginMediaClass *klass)
164 GObjectClass *gobject_class = (GObjectClass*)klass;
165 /* GtkContainerClass *container_class = (GtkContainerClass*)klass; */
166 parent_class = g_type_class_peek_parent(klass);
168 gobject_class->dispose = pidgin_media_dispose;
169 gobject_class->finalize = pidgin_media_finalize;
170 gobject_class->set_property = pidgin_media_set_property;
171 gobject_class->get_property = pidgin_media_get_property;
173 g_object_class_install_property(gobject_class, PROP_MEDIA,
174 g_param_spec_object("media",
175 "PurpleMedia",
176 "The PurpleMedia associated with this media.",
177 PURPLE_TYPE_MEDIA,
178 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
179 g_object_class_install_property(gobject_class, PROP_SCREENNAME,
180 g_param_spec_string("screenname",
181 "Screenname",
182 "The screenname of the user this session is with.",
183 NULL,
184 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
186 g_type_class_add_private(klass, sizeof(PidginMediaPrivate));
189 static void
190 pidgin_media_hold_toggled(GtkToggleButton *toggle, PidginMedia *media)
192 purple_media_stream_info(media->priv->media,
193 gtk_toggle_button_get_active(toggle) ?
194 PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD,
195 NULL, NULL, TRUE);
198 static void
199 pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media)
201 purple_media_stream_info(media->priv->media,
202 gtk_toggle_button_get_active(toggle) ?
203 PURPLE_MEDIA_INFO_MUTE : PURPLE_MEDIA_INFO_UNMUTE,
204 NULL, NULL, TRUE);
207 static void
208 pidgin_media_pause_toggled(GtkToggleButton *toggle, PidginMedia *media)
210 purple_media_stream_info(media->priv->media,
211 gtk_toggle_button_get_active(toggle) ?
212 PURPLE_MEDIA_INFO_PAUSE : PURPLE_MEDIA_INFO_UNPAUSE,
213 NULL, NULL, TRUE);
216 static gboolean
217 pidgin_media_delete_event_cb(GtkWidget *widget,
218 GdkEvent *event, PidginMedia *media)
220 if (media->priv->media)
221 purple_media_stream_info(media->priv->media,
222 PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE);
223 return FALSE;
226 #ifdef HAVE_X11
227 static int
228 pidgin_x_error_handler(Display *display, XErrorEvent *event)
230 const gchar *error_type;
231 switch (event->error_code) {
232 #define XERRORCASE(type) case type: error_type = #type; break
233 XERRORCASE(BadAccess);
234 XERRORCASE(BadAlloc);
235 XERRORCASE(BadAtom);
236 XERRORCASE(BadColor);
237 XERRORCASE(BadCursor);
238 XERRORCASE(BadDrawable);
239 XERRORCASE(BadFont);
240 XERRORCASE(BadGC);
241 XERRORCASE(BadIDChoice);
242 XERRORCASE(BadImplementation);
243 XERRORCASE(BadLength);
244 XERRORCASE(BadMatch);
245 XERRORCASE(BadName);
246 XERRORCASE(BadPixmap);
247 XERRORCASE(BadRequest);
248 XERRORCASE(BadValue);
249 XERRORCASE(BadWindow);
250 #undef XERRORCASE
251 default:
252 error_type = "unknown";
253 break;
255 purple_debug_error("media", "A %s Xlib error has occurred. "
256 "The program would normally crash now.\n",
257 error_type);
258 return 0;
260 #endif
262 static void
263 menu_hangup(GtkAction *action, gpointer data)
265 PidginMedia *gtkmedia = PIDGIN_MEDIA(data);
266 purple_media_stream_info(gtkmedia->priv->media,
267 PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE);
270 static const GtkActionEntry menu_entries[] = {
271 { "MediaMenu", NULL, N_("_Media"), NULL, NULL, NULL },
272 { "Hangup", NULL, N_("_Hangup"), NULL, NULL, G_CALLBACK(menu_hangup) },
275 static const char *media_menu =
276 "<ui>"
277 "<menubar name='Media'>"
278 "<menu action='MediaMenu'>"
279 "<menuitem action='Hangup'/>"
280 "</menu>"
281 "</menubar>"
282 "</ui>";
284 static GtkWidget *
285 setup_menubar(PidginMedia *window)
287 GtkActionGroup *action_group;
288 GError *error;
289 GtkAccelGroup *accel_group;
290 GtkWidget *menu;
292 action_group = gtk_action_group_new("MediaActions");
293 #ifdef ENABLE_NLS
294 gtk_action_group_set_translation_domain(action_group,
295 PACKAGE);
296 #endif
297 gtk_action_group_add_actions(action_group,
298 menu_entries,
299 G_N_ELEMENTS(menu_entries),
300 GTK_WINDOW(window));
302 window->priv->ui = gtk_ui_manager_new();
303 gtk_ui_manager_insert_action_group(window->priv->ui, action_group, 0);
305 accel_group = gtk_ui_manager_get_accel_group(window->priv->ui);
306 gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
308 error = NULL;
309 if (!gtk_ui_manager_add_ui_from_string(window->priv->ui, media_menu, -1, &error))
311 g_message("building menus failed: %s", error->message);
312 g_error_free(error);
313 exit(EXIT_FAILURE);
316 menu = gtk_ui_manager_get_widget(window->priv->ui, "/Media");
318 gtk_widget_show(menu);
319 return menu;
322 static void
323 pidgin_media_init (PidginMedia *media)
325 GtkWidget *vbox;
326 media->priv = PIDGIN_MEDIA_GET_PRIVATE(media);
328 #ifdef HAVE_X11
329 XSetErrorHandler(pidgin_x_error_handler);
330 #endif
332 vbox = gtk_vbox_new(FALSE, 0);
333 gtk_container_add(GTK_CONTAINER(media), vbox);
335 media->priv->statusbar = gtk_statusbar_new();
336 gtk_box_pack_end(GTK_BOX(vbox), media->priv->statusbar,
337 FALSE, FALSE, 0);
338 gtk_statusbar_push(GTK_STATUSBAR(media->priv->statusbar),
339 0, _("Calling..."));
340 gtk_widget_show(media->priv->statusbar);
342 media->priv->menubar = setup_menubar(media);
343 gtk_box_pack_start(GTK_BOX(vbox), media->priv->menubar,
344 FALSE, TRUE, 0);
346 media->priv->display = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
347 gtk_container_set_border_width(GTK_CONTAINER(media->priv->display),
348 PIDGIN_HIG_BOX_SPACE);
349 gtk_box_pack_start(GTK_BOX(vbox), media->priv->display,
350 TRUE, TRUE, PIDGIN_HIG_BOX_SPACE);
351 gtk_widget_show(vbox);
353 g_signal_connect(G_OBJECT(media), "delete-event",
354 G_CALLBACK(pidgin_media_delete_event_cb), media);
356 media->priv->recv_progressbars =
357 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
358 media->priv->remote_videos =
359 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
362 static gchar *
363 create_key(const gchar *session_id, const gchar *participant)
365 return g_strdup_printf("%s_%s", session_id, participant);
368 static void
369 pidgin_media_insert_widget(PidginMedia *gtkmedia, GtkWidget *widget,
370 const gchar *session_id, const gchar *participant)
372 gchar *key = create_key(session_id, participant);
373 PurpleMediaSessionType type =
374 purple_media_get_session_type(gtkmedia->priv->media, session_id);
376 if (type & PURPLE_MEDIA_AUDIO)
377 g_hash_table_insert(gtkmedia->priv->recv_progressbars, key, widget);
378 else if (type & PURPLE_MEDIA_VIDEO)
379 g_hash_table_insert(gtkmedia->priv->remote_videos, key, widget);
382 static GtkWidget *
383 pidgin_media_get_widget(PidginMedia *gtkmedia,
384 const gchar *session_id, const gchar *participant)
386 GtkWidget *widget = NULL;
387 gchar *key = create_key(session_id, participant);
388 PurpleMediaSessionType type =
389 purple_media_get_session_type(gtkmedia->priv->media, session_id);
391 if (type & PURPLE_MEDIA_AUDIO)
392 widget = g_hash_table_lookup(gtkmedia->priv->recv_progressbars, key);
393 else if (type & PURPLE_MEDIA_VIDEO)
394 widget = g_hash_table_lookup(gtkmedia->priv->remote_videos, key);
396 g_free(key);
397 return widget;
400 static void
401 pidgin_media_remove_widget(PidginMedia *gtkmedia,
402 const gchar *session_id, const gchar *participant)
404 GtkWidget *widget = pidgin_media_get_widget(gtkmedia, session_id, participant);
406 if (widget) {
407 PurpleMediaSessionType type =
408 purple_media_get_session_type(gtkmedia->priv->media, session_id);
409 gchar *key = create_key(session_id, participant);
410 GtkRequisition req;
412 if (type & PURPLE_MEDIA_AUDIO) {
413 g_hash_table_remove(gtkmedia->priv->recv_progressbars, key);
415 if (g_hash_table_size(gtkmedia->priv->recv_progressbars) == 0 &&
416 gtkmedia->priv->send_progress) {
418 gtk_widget_destroy(gtkmedia->priv->send_progress);
419 gtkmedia->priv->send_progress = NULL;
421 gtk_widget_destroy(gtkmedia->priv->mute);
422 gtkmedia->priv->mute = NULL;
424 } else if (type & PURPLE_MEDIA_VIDEO) {
425 g_hash_table_remove(gtkmedia->priv->remote_videos, key);
427 if (g_hash_table_size(gtkmedia->priv->remote_videos) == 0 &&
428 gtkmedia->priv->local_video) {
430 gtk_widget_destroy(gtkmedia->priv->local_video);
431 gtkmedia->priv->local_video = NULL;
433 gtk_widget_destroy(gtkmedia->priv->pause);
434 gtkmedia->priv->pause = NULL;
438 g_free(key);
440 gtk_widget_destroy(widget);
442 gtk_widget_size_request(GTK_WIDGET(gtkmedia), &req);
443 gtk_window_resize(GTK_WINDOW(gtkmedia), req.width, req.height);
447 static void
448 level_message_cb(PurpleMedia *media, gchar *session_id, gchar *participant,
449 double level, PidginMedia *gtkmedia)
451 GtkWidget *progress = NULL;
452 if (participant == NULL)
453 progress = gtkmedia->priv->send_progress;
454 else
455 progress = pidgin_media_get_widget(gtkmedia, session_id, participant);
457 if (progress)
458 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level * 5);
462 static void
463 pidgin_media_disconnect_levels(PurpleMedia *media, PidginMedia *gtkmedia)
465 PurpleMediaManager *manager = purple_media_get_manager(media);
466 GstElement *element = purple_media_manager_get_pipeline(manager);
467 gulong handler_id = g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
468 G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0,
469 NULL, G_CALLBACK(level_message_cb), gtkmedia);
470 if (handler_id)
471 g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
472 handler_id);
475 static void
476 pidgin_media_dispose(GObject *media)
478 PidginMedia *gtkmedia = PIDGIN_MEDIA(media);
479 purple_debug_info("gtkmedia", "pidgin_media_dispose\n");
481 if (gtkmedia->priv->media) {
482 purple_request_close_with_handle(gtkmedia);
483 purple_media_remove_output_windows(gtkmedia->priv->media);
484 pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia);
485 g_object_unref(gtkmedia->priv->media);
486 gtkmedia->priv->media = NULL;
489 if (gtkmedia->priv->ui) {
490 g_object_unref(gtkmedia->priv->ui);
491 gtkmedia->priv->ui = NULL;
494 if (gtkmedia->priv->timeout_id != 0)
495 g_source_remove(gtkmedia->priv->timeout_id);
497 if (gtkmedia->priv->recv_progressbars) {
498 g_hash_table_destroy(gtkmedia->priv->recv_progressbars);
499 g_hash_table_destroy(gtkmedia->priv->remote_videos);
500 gtkmedia->priv->recv_progressbars = NULL;
501 gtkmedia->priv->remote_videos = NULL;
504 G_OBJECT_CLASS(parent_class)->dispose(media);
507 static void
508 pidgin_media_finalize(GObject *media)
510 /* PidginMedia *gtkmedia = PIDGIN_MEDIA(media); */
511 purple_debug_info("gtkmedia", "pidgin_media_finalize\n");
513 G_OBJECT_CLASS(parent_class)->finalize(media);
516 static void
517 pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg)
519 PurpleConversation *conv = purple_find_conversation_with_account(
520 PURPLE_CONV_TYPE_ANY, gtkmedia->priv->screenname,
521 purple_media_get_account(gtkmedia->priv->media));
522 if (conv != NULL)
523 purple_conversation_write(conv, NULL, msg,
524 PURPLE_MESSAGE_SYSTEM, time(NULL));
527 typedef struct
529 PidginMedia *gtkmedia;
530 gchar *session_id;
531 gchar *participant;
532 } PidginMediaRealizeData;
534 static gboolean
535 realize_cb_cb(PidginMediaRealizeData *data)
537 PidginMediaPrivate *priv = data->gtkmedia->priv;
538 GdkWindow *window = NULL;
540 if (data->participant == NULL)
541 #if GTK_CHECK_VERSION(2, 14, 0)
542 window = gtk_widget_get_window(priv->local_video);
543 #else
544 window = (priv->local_video)->window;
545 #endif
546 else {
547 GtkWidget *widget = pidgin_media_get_widget(data->gtkmedia,
548 data->session_id, data->participant);
549 if (widget)
550 #if GTK_CHECK_VERSION(2, 14, 0)
551 window = gtk_widget_get_window(widget);
552 #else
553 window = widget->window;
554 #endif
557 if (window) {
558 gulong window_id;
559 #ifdef _WIN32
560 window_id = GDK_WINDOW_HWND(window);
561 #elif defined(HAVE_X11)
562 window_id = GDK_WINDOW_XWINDOW(window);
563 #else
564 # error "Unsupported windowing system"
565 #endif
567 purple_media_set_output_window(priv->media, data->session_id,
568 data->participant, window_id);
571 g_free(data->session_id);
572 g_free(data->participant);
573 g_free(data);
574 return FALSE;
577 static void
578 realize_cb(GtkWidget *widget, PidginMediaRealizeData *data)
580 g_timeout_add(0, (GSourceFunc)realize_cb_cb, data);
583 static void
584 pidgin_media_error_cb(PidginMedia *media, const char *error, PidginMedia *gtkmedia)
586 PurpleConversation *conv = purple_find_conversation_with_account(
587 PURPLE_CONV_TYPE_ANY, gtkmedia->priv->screenname,
588 purple_media_get_account(gtkmedia->priv->media));
589 if (conv != NULL)
590 purple_conversation_write(conv, NULL, error,
591 PURPLE_MESSAGE_ERROR, time(NULL));
592 gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
593 0, error);
596 static void
597 pidgin_media_accept_cb(PurpleMedia *media, int index)
599 purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT,
600 NULL, NULL, TRUE);
603 static void
604 pidgin_media_reject_cb(PurpleMedia *media, int index)
606 GList *iter = purple_media_get_session_ids(media);
607 for (; iter; iter = g_list_delete_link(iter, iter)) {
608 const gchar *sessionid = iter->data;
609 if (!purple_media_accepted(media, sessionid, NULL))
610 purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT,
611 sessionid, NULL, TRUE);
615 static gboolean
616 pidgin_request_timeout_cb(PidginMedia *gtkmedia)
618 PurpleAccount *account;
619 PurpleBuddy *buddy;
620 const gchar *alias;
621 PurpleMediaSessionType type;
622 gchar *message = NULL;
624 account = purple_media_get_account(gtkmedia->priv->media);
625 buddy = purple_find_buddy(account, gtkmedia->priv->screenname);
626 alias = buddy ? purple_buddy_get_contact_alias(buddy) :
627 gtkmedia->priv->screenname;
628 type = gtkmedia->priv->request_type;
629 gtkmedia->priv->timeout_id = 0;
631 if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) {
632 message = g_strdup_printf(_("%s wishes to start an audio/video session with you."),
633 alias);
634 } else if (type & PURPLE_MEDIA_AUDIO) {
635 message = g_strdup_printf(_("%s wishes to start an audio session with you."),
636 alias);
637 } else if (type & PURPLE_MEDIA_VIDEO) {
638 message = g_strdup_printf(_("%s wishes to start a video session with you."),
639 alias);
642 gtkmedia->priv->request_type = PURPLE_MEDIA_NONE;
643 if (!purple_media_accepted(gtkmedia->priv->media, NULL, NULL)) {
644 purple_request_accept_cancel(gtkmedia, _("Incoming Call"),
645 message, NULL, PURPLE_DEFAULT_ACTION_NONE,
646 (void*)account, gtkmedia->priv->screenname,
647 NULL, gtkmedia->priv->media,
648 pidgin_media_accept_cb,
649 pidgin_media_reject_cb);
651 pidgin_media_emit_message(gtkmedia, message);
652 g_free(message);
653 return FALSE;
656 static void
657 #if GTK_CHECK_VERSION(2,12,0)
658 pidgin_media_input_volume_changed(GtkScaleButton *range, double value,
659 PurpleMedia *media)
661 double val = (double)value * 100.0;
662 #else
663 pidgin_media_input_volume_changed(GtkRange *range, PurpleMedia *media)
665 double val = (double)gtk_range_get_value(GTK_RANGE(range));
666 #endif
667 purple_media_set_input_volume(media, NULL, val);
670 static void
671 #if GTK_CHECK_VERSION(2,12,0)
672 pidgin_media_output_volume_changed(GtkScaleButton *range, double value,
673 PurpleMedia *media)
675 double val = (double)value * 100.0;
676 #else
677 pidgin_media_output_volume_changed(GtkRange *range, PurpleMedia *media)
679 double val = (double)gtk_range_get_value(GTK_RANGE(range));
680 #endif
681 purple_media_set_output_volume(media, NULL, NULL, val);
684 static void
685 destroy_parent_widget_cb(GtkWidget *widget, GtkWidget *parent)
687 g_return_if_fail(GTK_IS_WIDGET(parent));
689 gtk_widget_destroy(parent);
692 static GtkWidget *
693 pidgin_media_add_audio_widget(PidginMedia *gtkmedia,
694 PurpleMediaSessionType type, const gchar *sid)
696 GtkWidget *volume_widget, *progress_parent, *volume, *progress;
697 double value;
699 if (type & PURPLE_MEDIA_SEND_AUDIO) {
700 value = purple_prefs_get_int(
701 "/purple/media/audio/volume/input");
702 } else if (type & PURPLE_MEDIA_RECV_AUDIO) {
703 value = purple_prefs_get_int(
704 "/purple/media/audio/volume/output");
705 } else
706 g_return_val_if_reached(NULL);
708 #if GTK_CHECK_VERSION(2,12,0)
709 /* Setup widget structure */
710 volume_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
711 progress_parent = gtk_vbox_new(FALSE, 0);
712 gtk_box_pack_start(GTK_BOX(volume_widget),
713 progress_parent, TRUE, TRUE, 0);
715 /* Volume button */
716 volume = gtk_volume_button_new();
717 gtk_scale_button_set_value(GTK_SCALE_BUTTON(volume), value/100.0);
718 gtk_box_pack_end(GTK_BOX(volume_widget),
719 volume, FALSE, FALSE, 0);
720 #else
721 /* Setup widget structure */
722 volume_widget = gtk_vbox_new(FALSE, 0);
723 progress_parent = volume_widget;
725 /* Volume slider */
726 volume = gtk_hscale_new_with_range(0.0, 100.0, 5.0);
727 gtk_range_set_increments(GTK_RANGE(volume), 5.0, 25.0);
728 gtk_range_set_value(GTK_RANGE(volume), value);
729 gtk_scale_set_draw_value(GTK_SCALE(volume), FALSE);
730 gtk_box_pack_end(GTK_BOX(volume_widget),
731 volume, TRUE, FALSE, 0);
732 #endif
734 /* Volume level indicator */
735 progress = gtk_progress_bar_new();
736 gtk_widget_set_size_request(progress, 250, 10);
737 gtk_box_pack_end(GTK_BOX(progress_parent), progress, TRUE, FALSE, 0);
739 if (type & PURPLE_MEDIA_SEND_AUDIO) {
740 g_signal_connect (G_OBJECT(volume), "value-changed",
741 G_CALLBACK(pidgin_media_input_volume_changed),
742 gtkmedia->priv->media);
743 gtkmedia->priv->send_progress = progress;
744 } else if (type & PURPLE_MEDIA_RECV_AUDIO) {
745 g_signal_connect (G_OBJECT(volume), "value-changed",
746 G_CALLBACK(pidgin_media_output_volume_changed),
747 gtkmedia->priv->media);
749 pidgin_media_insert_widget(gtkmedia, progress, sid, gtkmedia->priv->screenname);
752 g_signal_connect(G_OBJECT(progress), "destroy",
753 G_CALLBACK(destroy_parent_widget_cb),
754 volume_widget);
756 gtk_widget_show_all(volume_widget);
758 return volume_widget;
761 static void
762 pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *sid)
764 GtkWidget *send_widget = NULL, *recv_widget = NULL, *button_widget = NULL;
765 PurpleMediaSessionType type =
766 purple_media_get_session_type(media, sid);
767 GdkPixbuf *icon = NULL;
769 if (gtkmedia->priv->recv_widget == NULL
770 && type & (PURPLE_MEDIA_RECV_VIDEO |
771 PURPLE_MEDIA_RECV_AUDIO)) {
772 recv_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
773 gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display),
774 recv_widget, TRUE, TRUE, 0);
775 gtk_widget_show(recv_widget);
776 } else {
777 recv_widget = gtkmedia->priv->recv_widget;
779 if (gtkmedia->priv->send_widget == NULL
780 && type & (PURPLE_MEDIA_SEND_VIDEO |
781 PURPLE_MEDIA_SEND_AUDIO)) {
782 send_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
783 gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display),
784 send_widget, FALSE, TRUE, 0);
785 button_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
786 gtk_box_pack_end(GTK_BOX(recv_widget), button_widget,
787 FALSE, TRUE, 0);
788 gtk_widget_show(send_widget);
790 /* Hold button */
791 gtkmedia->priv->hold =
792 gtk_toggle_button_new_with_mnemonic("_Hold");
793 gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
794 FALSE, FALSE, 0);
795 gtk_widget_show(gtkmedia->priv->hold);
796 g_signal_connect(gtkmedia->priv->hold, "toggled",
797 G_CALLBACK(pidgin_media_hold_toggled),
798 gtkmedia);
799 } else {
800 send_widget = gtkmedia->priv->send_widget;
801 button_widget = gtkmedia->priv->button_widget;
804 if (type & PURPLE_MEDIA_RECV_VIDEO) {
805 PidginMediaRealizeData *data;
806 GtkWidget *aspect;
807 GtkWidget *remote_video;
808 GdkColor color = {0, 0, 0, 0};
810 aspect = gtk_aspect_frame_new(NULL, 0, 0, 4.0/3.0, FALSE);
811 gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
812 gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0);
814 data = g_new0(PidginMediaRealizeData, 1);
815 data->gtkmedia = gtkmedia;
816 data->session_id = g_strdup(sid);
817 data->participant = g_strdup(gtkmedia->priv->screenname);
819 remote_video = gtk_drawing_area_new();
820 gtk_widget_modify_bg(remote_video, GTK_STATE_NORMAL, &color);
821 g_signal_connect(G_OBJECT(remote_video), "realize",
822 G_CALLBACK(realize_cb), data);
823 gtk_container_add(GTK_CONTAINER(aspect), remote_video);
824 gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240);
825 g_signal_connect(G_OBJECT(remote_video), "destroy",
826 G_CALLBACK(destroy_parent_widget_cb), aspect);
828 gtk_widget_show(remote_video);
829 gtk_widget_show(aspect);
831 pidgin_media_insert_widget(gtkmedia, remote_video,
832 data->session_id, data->participant);
835 if (type & PURPLE_MEDIA_SEND_VIDEO && !gtkmedia->priv->local_video) {
836 PidginMediaRealizeData *data;
837 GtkWidget *aspect;
838 GtkWidget *local_video;
839 GdkColor color = {0, 0, 0, 0};
841 aspect = gtk_aspect_frame_new(NULL, 0, 0, 4.0/3.0, TRUE);
842 gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
843 gtk_box_pack_start(GTK_BOX(send_widget), aspect, FALSE, TRUE, 0);
845 data = g_new0(PidginMediaRealizeData, 1);
846 data->gtkmedia = gtkmedia;
847 data->session_id = g_strdup(sid);
848 data->participant = NULL;
850 local_video = gtk_drawing_area_new();
851 gtk_widget_modify_bg(local_video, GTK_STATE_NORMAL, &color);
852 g_signal_connect(G_OBJECT(local_video), "realize",
853 G_CALLBACK(realize_cb), data);
854 gtk_container_add(GTK_CONTAINER(aspect), local_video);
855 gtk_widget_set_size_request (GTK_WIDGET(local_video), 80, 60);
856 g_signal_connect(G_OBJECT(local_video), "destroy",
857 G_CALLBACK(destroy_parent_widget_cb), aspect);
859 gtk_widget_show(local_video);
860 gtk_widget_show(aspect);
862 gtkmedia->priv->pause =
863 gtk_toggle_button_new_with_mnemonic(_("_Pause"));
864 gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->pause,
865 FALSE, FALSE, 0);
866 gtk_widget_show(gtkmedia->priv->pause);
867 g_signal_connect(gtkmedia->priv->pause, "toggled",
868 G_CALLBACK(pidgin_media_pause_toggled),
869 gtkmedia);
871 gtkmedia->priv->local_video = local_video;
873 if (type & PURPLE_MEDIA_RECV_AUDIO) {
874 gtk_box_pack_end(GTK_BOX(recv_widget),
875 pidgin_media_add_audio_widget(gtkmedia,
876 PURPLE_MEDIA_RECV_AUDIO, sid), FALSE, FALSE, 0);
879 if (type & PURPLE_MEDIA_SEND_AUDIO) {
880 gtkmedia->priv->mute =
881 gtk_toggle_button_new_with_mnemonic("_Mute");
882 gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->mute,
883 FALSE, FALSE, 0);
884 gtk_widget_show(gtkmedia->priv->mute);
885 g_signal_connect(gtkmedia->priv->mute, "toggled",
886 G_CALLBACK(pidgin_media_mute_toggled),
887 gtkmedia);
889 gtk_box_pack_end(GTK_BOX(recv_widget),
890 pidgin_media_add_audio_widget(gtkmedia,
891 PURPLE_MEDIA_SEND_AUDIO, NULL), FALSE, FALSE, 0);
894 if (type & PURPLE_MEDIA_AUDIO &&
895 gtkmedia->priv->level_handler_id == 0) {
896 gtkmedia->priv->level_handler_id = g_signal_connect(
897 media, "level", G_CALLBACK(level_message_cb),
898 gtkmedia);
901 if (send_widget != NULL)
902 gtkmedia->priv->send_widget = send_widget;
903 if (recv_widget != NULL)
904 gtkmedia->priv->recv_widget = recv_widget;
905 if (button_widget != NULL) {
906 gtkmedia->priv->button_widget = button_widget;
907 gtk_widget_show(GTK_WIDGET(button_widget));
910 if (purple_media_is_initiator(media, sid, NULL) == FALSE) {
911 if (gtkmedia->priv->timeout_id != 0)
912 g_source_remove(gtkmedia->priv->timeout_id);
913 gtkmedia->priv->request_type |= type;
914 gtkmedia->priv->timeout_id = g_timeout_add(500,
915 (GSourceFunc)pidgin_request_timeout_cb,
916 gtkmedia);
919 /* set the window icon according to the type */
920 if (type & PURPLE_MEDIA_VIDEO) {
921 icon = gtk_widget_render_icon(GTK_WIDGET(gtkmedia),
922 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
923 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE), NULL);
924 } else if (type & PURPLE_MEDIA_AUDIO) {
925 icon = gtk_widget_render_icon(GTK_WIDGET(gtkmedia),
926 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
927 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE), NULL);
930 if (icon) {
931 gtk_window_set_icon(GTK_WINDOW(gtkmedia), icon);
932 g_object_unref(icon);
935 gtk_widget_show(gtkmedia->priv->display);
938 static void
939 pidgin_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state,
940 gchar *sid, gchar *name, PidginMedia *gtkmedia)
942 purple_debug_info("gtkmedia", "state: %d sid: %s name: %s\n",
943 state, sid ? sid : "(null)", name ? name : "(null)");
944 if (state == PURPLE_MEDIA_STATE_END) {
945 if (sid != NULL && name != NULL) {
946 pidgin_media_remove_widget(gtkmedia, sid, name);
947 } else if (sid == NULL && name == NULL) {
948 pidgin_media_emit_message(gtkmedia,
949 _("The call has been terminated."));
950 gtk_widget_destroy(GTK_WIDGET(gtkmedia));
952 } else if (state == PURPLE_MEDIA_STATE_NEW &&
953 sid != NULL && name != NULL) {
954 pidgin_media_ready_cb(media, gtkmedia, sid);
958 static void
959 pidgin_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
960 gchar *sid, gchar *name, gboolean local,
961 PidginMedia *gtkmedia)
963 if (type == PURPLE_MEDIA_INFO_REJECT) {
964 pidgin_media_emit_message(gtkmedia,
965 _("You have rejected the call."));
966 } else if (type == PURPLE_MEDIA_INFO_ACCEPT) {
967 if (local == TRUE)
968 purple_request_close_with_handle(gtkmedia);
969 pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED);
970 pidgin_media_emit_message(gtkmedia, _("Call in progress."));
971 gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
972 0, _("Call in progress."));
973 gtk_widget_show(GTK_WIDGET(gtkmedia));
977 static void
978 pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
980 PidginMedia *media;
981 g_return_if_fail(PIDGIN_IS_MEDIA(object));
983 media = PIDGIN_MEDIA(object);
984 switch (prop_id) {
985 case PROP_MEDIA:
987 if (media->priv->media)
988 g_object_unref(media->priv->media);
989 media->priv->media = g_value_get_object(value);
990 g_object_ref(media->priv->media);
992 if (purple_media_is_initiator(media->priv->media,
993 NULL, NULL) == TRUE)
994 pidgin_media_set_state(media, PIDGIN_MEDIA_WAITING);
995 else
996 pidgin_media_set_state(media, PIDGIN_MEDIA_REQUESTED);
998 g_signal_connect(G_OBJECT(media->priv->media), "error",
999 G_CALLBACK(pidgin_media_error_cb), media);
1000 g_signal_connect(G_OBJECT(media->priv->media), "state-changed",
1001 G_CALLBACK(pidgin_media_state_changed_cb), media);
1002 g_signal_connect(G_OBJECT(media->priv->media), "stream-info",
1003 G_CALLBACK(pidgin_media_stream_info_cb), media);
1004 break;
1006 case PROP_SCREENNAME:
1007 if (media->priv->screenname)
1008 g_free(media->priv->screenname);
1009 media->priv->screenname = g_value_dup_string(value);
1010 break;
1011 default:
1012 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1013 break;
1017 static void
1018 pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
1020 PidginMedia *media;
1021 g_return_if_fail(PIDGIN_IS_MEDIA(object));
1023 media = PIDGIN_MEDIA(object);
1025 switch (prop_id) {
1026 case PROP_MEDIA:
1027 g_value_set_object(value, media->priv->media);
1028 break;
1029 case PROP_SCREENNAME:
1030 g_value_set_string(value, media->priv->screenname);
1031 break;
1032 default:
1033 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1034 break;
1038 static GtkWidget *
1039 pidgin_media_new(PurpleMedia *media, const gchar *screenname)
1041 PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(),
1042 "media", media,
1043 "screenname", screenname, NULL);
1044 return GTK_WIDGET(gtkmedia);
1047 static void
1048 pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state)
1050 gtkmedia->priv->state = state;
1053 static gboolean
1054 pidgin_media_new_cb(PurpleMediaManager *manager, PurpleMedia *media,
1055 PurpleAccount *account, gchar *screenname, gpointer nul)
1057 PidginMedia *gtkmedia = PIDGIN_MEDIA(
1058 pidgin_media_new(media, screenname));
1059 PurpleBuddy *buddy = purple_find_buddy(account, screenname);
1060 const gchar *alias = buddy ?
1061 purple_buddy_get_contact_alias(buddy) : screenname;
1062 gtk_window_set_title(GTK_WINDOW(gtkmedia), alias);
1064 if (purple_media_is_initiator(media, NULL, NULL) == TRUE)
1065 gtk_widget_show(GTK_WIDGET(gtkmedia));
1067 return TRUE;
1070 static GstElement *
1071 create_default_video_src(PurpleMedia *media,
1072 const gchar *session_id, const gchar *participant)
1074 GstElement *sendbin, *src;
1075 GstPad *pad;
1076 GstPad *ghost;
1078 #ifdef _WIN32
1079 /* autovideosrc doesn't pick ksvideosrc for some reason */
1080 src = gst_element_factory_make("ksvideosrc", NULL);
1081 if (src == NULL)
1082 src = gst_element_factory_make("dshowvideosrc", NULL);
1083 if (src == NULL)
1084 src = gst_element_factory_make("autovideosrc", NULL);
1085 #else
1086 src = gst_element_factory_make("gconfvideosrc", NULL);
1087 if (src == NULL)
1088 src = gst_element_factory_make("autovideosrc", NULL);
1089 if (src == NULL)
1090 src = gst_element_factory_make("v4l2src", NULL);
1091 if (src == NULL)
1092 src = gst_element_factory_make("v4lsrc", NULL);
1093 #endif
1094 if (src == NULL) {
1095 purple_debug_error("gtkmedia", "Unable to find a suitable "
1096 "element for the default video source.\n");
1097 return NULL;
1100 sendbin = gst_bin_new("pidgindefaultvideosrc");
1102 gst_bin_add(GST_BIN(sendbin), src);
1104 pad = gst_element_get_static_pad(src, "src");
1105 ghost = gst_ghost_pad_new("ghostsrc", pad);
1106 gst_object_unref(pad);
1107 gst_element_add_pad(sendbin, ghost);
1109 return sendbin;
1112 static GstElement *
1113 create_default_video_sink(PurpleMedia *media,
1114 const gchar *session_id, const gchar *participant)
1116 GstElement *sink = gst_element_factory_make("gconfvideosink", NULL);
1117 if (sink == NULL)
1118 sink = gst_element_factory_make("autovideosink", NULL);
1119 if (sink == NULL)
1120 purple_debug_error("gtkmedia", "Unable to find a suitable "
1121 "element for the default video sink.\n");
1122 return sink;
1125 static GstElement *
1126 create_default_audio_src(PurpleMedia *media,
1127 const gchar *session_id, const gchar *participant)
1129 GstElement *src;
1130 src = gst_element_factory_make("gconfaudiosrc", NULL);
1131 if (src == NULL)
1132 src = gst_element_factory_make("autoaudiosrc", NULL);
1133 if (src == NULL)
1134 src = gst_element_factory_make("alsasrc", NULL);
1135 if (src == NULL)
1136 src = gst_element_factory_make("osssrc", NULL);
1137 if (src == NULL)
1138 src = gst_element_factory_make("dshowaudiosrc", NULL);
1139 if (src == NULL) {
1140 purple_debug_error("gtkmedia", "Unable to find a suitable "
1141 "element for the default audio source.\n");
1142 return NULL;
1144 gst_element_set_name(src, "pidgindefaultaudiosrc");
1145 return src;
1148 static GstElement *
1149 create_default_audio_sink(PurpleMedia *media,
1150 const gchar *session_id, const gchar *participant)
1152 GstElement *sink;
1153 sink = gst_element_factory_make("gconfaudiosink", NULL);
1154 if (sink == NULL)
1155 sink = gst_element_factory_make("autoaudiosink",NULL);
1156 if (sink == NULL) {
1157 purple_debug_error("gtkmedia", "Unable to find a suitable "
1158 "element for the default audio sink.\n");
1159 return NULL;
1161 return sink;
1163 #endif /* USE_VV */
1165 void
1166 pidgin_medias_init(void)
1168 #ifdef USE_VV
1169 PurpleMediaManager *manager = purple_media_manager_get();
1170 PurpleMediaElementInfo *default_video_src =
1171 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1172 "id", "pidgindefaultvideosrc",
1173 "name", "Pidgin Default Video Source",
1174 "type", PURPLE_MEDIA_ELEMENT_VIDEO
1175 | PURPLE_MEDIA_ELEMENT_SRC
1176 | PURPLE_MEDIA_ELEMENT_ONE_SRC
1177 | PURPLE_MEDIA_ELEMENT_UNIQUE,
1178 "create-cb", create_default_video_src, NULL);
1179 PurpleMediaElementInfo *default_video_sink =
1180 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1181 "id", "pidgindefaultvideosink",
1182 "name", "Pidgin Default Video Sink",
1183 "type", PURPLE_MEDIA_ELEMENT_VIDEO
1184 | PURPLE_MEDIA_ELEMENT_SINK
1185 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
1186 "create-cb", create_default_video_sink, NULL);
1187 PurpleMediaElementInfo *default_audio_src =
1188 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1189 "id", "pidgindefaultaudiosrc",
1190 "name", "Pidgin Default Audio Source",
1191 "type", PURPLE_MEDIA_ELEMENT_AUDIO
1192 | PURPLE_MEDIA_ELEMENT_SRC
1193 | PURPLE_MEDIA_ELEMENT_ONE_SRC
1194 | PURPLE_MEDIA_ELEMENT_UNIQUE,
1195 "create-cb", create_default_audio_src, NULL);
1196 PurpleMediaElementInfo *default_audio_sink =
1197 g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1198 "id", "pidgindefaultaudiosink",
1199 "name", "Pidgin Default Audio Sink",
1200 "type", PURPLE_MEDIA_ELEMENT_AUDIO
1201 | PURPLE_MEDIA_ELEMENT_SINK
1202 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
1203 "create-cb", create_default_audio_sink, NULL);
1205 g_signal_connect(G_OBJECT(manager), "init-media",
1206 G_CALLBACK(pidgin_media_new_cb), NULL);
1208 purple_media_manager_set_ui_caps(manager,
1209 PURPLE_MEDIA_CAPS_AUDIO |
1210 PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION |
1211 PURPLE_MEDIA_CAPS_VIDEO |
1212 PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION |
1213 PURPLE_MEDIA_CAPS_AUDIO_VIDEO);
1215 purple_debug_info("gtkmedia", "Registering media element types\n");
1216 purple_media_manager_set_active_element(manager, default_video_src);
1217 purple_media_manager_set_active_element(manager, default_video_sink);
1218 purple_media_manager_set_active_element(manager, default_audio_src);
1219 purple_media_manager_set_active_element(manager, default_audio_sink);
1220 #endif