3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "connection.h"
26 #include "mediamanager.h"
32 #include "pidginstock.h"
35 #include "media-gst.h"
37 #ifdef GDK_WINDOWING_WIN32
38 #include <gdk/gdkwin32.h>
40 #ifdef GDK_WINDOWING_X11
43 #ifdef GDK_WINDOWING_QUARTZ
44 #include <gdk/gdkquartz.h>
46 #include <gdk/gdkkeysyms.h>
48 #include "gtk3compat.h"
50 #define PIDGIN_TYPE_MEDIA (pidgin_media_get_type())
51 #define PIDGIN_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia))
52 #define PIDGIN_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass))
53 #define PIDGIN_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA))
54 #define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA))
55 #define PIDGIN_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass))
57 typedef struct _PidginMedia PidginMedia
;
58 typedef struct _PidginMediaClass PidginMediaClass
;
59 typedef struct _PidginMediaPrivate PidginMediaPrivate
;
63 /* Waiting for response */
64 PIDGIN_MEDIA_WAITING
= 1,
66 PIDGIN_MEDIA_REQUESTED
,
68 PIDGIN_MEDIA_ACCEPTED
,
70 PIDGIN_MEDIA_REJECTED
,
73 struct _PidginMediaClass
75 GtkApplicationWindowClass parent_class
;
80 GtkApplicationWindow parent
;
81 PidginMediaPrivate
*priv
;
84 struct _PidginMediaPrivate
88 gulong level_handler_id
;
98 GtkWidget
*send_progress
;
99 GHashTable
*recv_progressbars
;
101 PidginMediaState state
;
104 GtkWidget
*send_widget
;
105 GtkWidget
*recv_widget
;
106 GtkWidget
*button_widget
;
107 GtkWidget
*local_video
;
108 GHashTable
*remote_videos
;
111 PurpleMediaSessionType request_type
;
114 static GType
pidgin_media_get_type(void);
116 G_DEFINE_TYPE_WITH_PRIVATE(PidginMedia
, pidgin_media
,
117 GTK_TYPE_APPLICATION_WINDOW
);
119 static void pidgin_media_dispose (GObject
*object
);
120 static void pidgin_media_finalize (GObject
*object
);
121 static void pidgin_media_get_property (GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
);
122 static void pidgin_media_set_property (GObject
*object
, guint prop_id
, const GValue
*value
, GParamSpec
*pspec
);
123 static void pidgin_media_set_state(PidginMedia
*gtkmedia
, PidginMediaState state
);
129 static guint pidgin_media_signals
[LAST_SIGNAL
] = {0};
140 pidgin_media_class_init (PidginMediaClass
*klass
)
142 GObjectClass
*gobject_class
= G_OBJECT_CLASS(klass
);
143 /* GtkContainerClass *container_class = (GtkContainerClass*)klass; */
145 gobject_class
->dispose
= pidgin_media_dispose
;
146 gobject_class
->finalize
= pidgin_media_finalize
;
147 gobject_class
->set_property
= pidgin_media_set_property
;
148 gobject_class
->get_property
= pidgin_media_get_property
;
150 g_object_class_install_property(gobject_class
, PROP_MEDIA
,
151 g_param_spec_object("media",
153 "The PurpleMedia associated with this media.",
155 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
));
156 g_object_class_install_property(gobject_class
, PROP_SCREENNAME
,
157 g_param_spec_string("screenname",
159 "The screenname of the user this session is with.",
161 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
));
165 pidgin_media_hangup_activate_cb(GSimpleAction
*action
, GVariant
*parameter
,
168 PidginMedia
*media
= PIDGIN_MEDIA(user_data
);
170 purple_media_stream_info(media
->priv
->media
,
171 PURPLE_MEDIA_INFO_HANGUP
, NULL
, NULL
, TRUE
);
175 pidgin_media_hold_change_state_cb(GSimpleAction
*action
, GVariant
*value
,
178 PidginMedia
*media
= PIDGIN_MEDIA(user_data
);
180 purple_media_stream_info(media
->priv
->media
,
181 g_variant_get_boolean(value
) ?
182 PURPLE_MEDIA_INFO_HOLD
: PURPLE_MEDIA_INFO_UNHOLD
,
185 g_simple_action_set_state(action
, value
);
189 pidgin_media_mute_change_state_cb(GSimpleAction
*action
, GVariant
*value
,
192 PidginMedia
*media
= PIDGIN_MEDIA(user_data
);
194 purple_media_stream_info(media
->priv
->media
,
195 g_variant_get_boolean(value
) ?
196 PURPLE_MEDIA_INFO_MUTE
: PURPLE_MEDIA_INFO_UNMUTE
,
199 g_simple_action_set_state(action
, value
);
203 pidgin_media_pause_change_state_cb(GSimpleAction
*action
, GVariant
*value
,
206 PidginMedia
*media
= PIDGIN_MEDIA(user_data
);
208 purple_media_stream_info(media
->priv
->media
,
209 g_variant_get_boolean(value
) ?
210 PURPLE_MEDIA_INFO_PAUSE
: PURPLE_MEDIA_INFO_UNPAUSE
,
213 g_simple_action_set_state(action
, value
);
217 pidgin_media_delete_event_cb(GtkWidget
*widget
,
218 GdkEvent
*event
, PidginMedia
*media
)
220 if (media
->priv
->media
)
221 g_action_group_activate_action(G_ACTION_GROUP(media
),
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
);
236 XERRORCASE(BadColor
);
237 XERRORCASE(BadCursor
);
238 XERRORCASE(BadDrawable
);
241 XERRORCASE(BadIDChoice
);
242 XERRORCASE(BadImplementation
);
243 XERRORCASE(BadLength
);
244 XERRORCASE(BadMatch
);
246 XERRORCASE(BadPixmap
);
247 XERRORCASE(BadRequest
);
248 XERRORCASE(BadValue
);
249 XERRORCASE(BadWindow
);
252 error_type
= "unknown";
255 purple_debug_error("media", "A %s Xlib error has occurred. "
256 "The program would normally crash now.\n",
262 static const GActionEntry media_action_entries
[] = {
263 { "Hangup", pidgin_media_hangup_activate_cb
},
264 { "Hold", NULL
, NULL
, "false", pidgin_media_hold_change_state_cb
},
265 { "Mute", NULL
, NULL
, "false", pidgin_media_mute_change_state_cb
},
266 { "Pause", NULL
, NULL
, "false", pidgin_media_pause_change_state_cb
},
269 static const gchar
*media_menu
=
271 "<menu id='MediaMenu'>"
273 "<attribute name='label' translatable='yes'>_Media</attribute>"
276 "<attribute name='label' translatable='yes'>_Hangup</attribute>"
277 "<attribute name='action'>win.Hangup</attribute>"
285 setup_menubar(PidginMedia
*window
)
290 window
->priv
->ui
= gtk_builder_new();
291 gtk_builder_set_translation_domain(window
->priv
->ui
, PACKAGE
);
294 if (!gtk_builder_add_from_string(window
->priv
->ui
, media_menu
, -1,
297 g_message("building menus failed: %s", error
->message
);
302 menu
= gtk_menu_bar_new_from_model(G_MENU_MODEL(
303 gtk_builder_get_object(window
->priv
->ui
,
306 gtk_widget_show(menu
);
311 pidgin_media_init (PidginMedia
*media
)
314 media
->priv
= pidgin_media_get_instance_private(media
);
317 XSetErrorHandler(pidgin_x_error_handler
);
320 g_action_map_add_action_entries(G_ACTION_MAP(media
),
321 media_action_entries
,
322 G_N_ELEMENTS(media_action_entries
), media
);
324 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
325 gtk_container_add(GTK_CONTAINER(media
), vbox
);
327 media
->priv
->statusbar
= gtk_statusbar_new();
328 gtk_box_pack_end(GTK_BOX(vbox
), media
->priv
->statusbar
,
330 gtk_statusbar_push(GTK_STATUSBAR(media
->priv
->statusbar
),
332 gtk_widget_show(media
->priv
->statusbar
);
334 media
->priv
->menubar
= setup_menubar(media
);
335 gtk_box_pack_start(GTK_BOX(vbox
), media
->priv
->menubar
,
338 media
->priv
->display
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
339 gtk_container_set_border_width(GTK_CONTAINER(media
->priv
->display
),
340 PIDGIN_HIG_BOX_SPACE
);
341 gtk_box_pack_start(GTK_BOX(vbox
), media
->priv
->display
,
342 TRUE
, TRUE
, PIDGIN_HIG_BOX_SPACE
);
343 gtk_widget_show(vbox
);
345 g_signal_connect(G_OBJECT(media
), "delete-event",
346 G_CALLBACK(pidgin_media_delete_event_cb
), media
);
348 media
->priv
->recv_progressbars
=
349 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
350 media
->priv
->remote_videos
=
351 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
355 create_key(const gchar
*session_id
, const gchar
*participant
)
357 return g_strdup_printf("%s_%s", session_id
, participant
);
361 pidgin_media_insert_widget(PidginMedia
*gtkmedia
, GtkWidget
*widget
,
362 const gchar
*session_id
, const gchar
*participant
)
364 gchar
*key
= create_key(session_id
, participant
);
365 PurpleMediaSessionType type
=
366 purple_media_get_session_type(gtkmedia
->priv
->media
, session_id
);
368 if (type
& PURPLE_MEDIA_AUDIO
)
369 g_hash_table_insert(gtkmedia
->priv
->recv_progressbars
, key
, widget
);
370 else if (type
& PURPLE_MEDIA_VIDEO
)
371 g_hash_table_insert(gtkmedia
->priv
->remote_videos
, key
, widget
);
375 pidgin_media_get_widget(PidginMedia
*gtkmedia
,
376 const gchar
*session_id
, const gchar
*participant
)
378 GtkWidget
*widget
= NULL
;
379 gchar
*key
= create_key(session_id
, participant
);
380 PurpleMediaSessionType type
=
381 purple_media_get_session_type(gtkmedia
->priv
->media
, session_id
);
383 if (type
& PURPLE_MEDIA_AUDIO
)
384 widget
= g_hash_table_lookup(gtkmedia
->priv
->recv_progressbars
, key
);
385 else if (type
& PURPLE_MEDIA_VIDEO
)
386 widget
= g_hash_table_lookup(gtkmedia
->priv
->remote_videos
, key
);
393 pidgin_media_remove_widget(PidginMedia
*gtkmedia
,
394 const gchar
*session_id
, const gchar
*participant
)
396 GtkWidget
*widget
= pidgin_media_get_widget(gtkmedia
, session_id
, participant
);
399 PurpleMediaSessionType type
=
400 purple_media_get_session_type(gtkmedia
->priv
->media
, session_id
);
401 gchar
*key
= create_key(session_id
, participant
);
404 if (type
& PURPLE_MEDIA_AUDIO
) {
405 g_hash_table_remove(gtkmedia
->priv
->recv_progressbars
, key
);
407 if (g_hash_table_size(gtkmedia
->priv
->recv_progressbars
) == 0 &&
408 gtkmedia
->priv
->send_progress
) {
410 gtk_widget_destroy(gtkmedia
->priv
->send_progress
);
411 gtkmedia
->priv
->send_progress
= NULL
;
413 gtk_widget_destroy(gtkmedia
->priv
->mute
);
414 gtkmedia
->priv
->mute
= NULL
;
416 } else if (type
& PURPLE_MEDIA_VIDEO
) {
417 g_hash_table_remove(gtkmedia
->priv
->remote_videos
, key
);
419 if (g_hash_table_size(gtkmedia
->priv
->remote_videos
) == 0 &&
420 gtkmedia
->priv
->local_video
) {
422 gtk_widget_destroy(gtkmedia
->priv
->local_video
);
423 gtkmedia
->priv
->local_video
= NULL
;
425 gtk_widget_destroy(gtkmedia
->priv
->pause
);
426 gtkmedia
->priv
->pause
= NULL
;
432 gtk_widget_destroy(widget
);
434 gtk_widget_get_preferred_size(GTK_WIDGET(gtkmedia
), NULL
, &req
);
435 gtk_window_resize(GTK_WINDOW(gtkmedia
), req
.width
, req
.height
);
440 level_message_cb(PurpleMedia
*media
, gchar
*session_id
, gchar
*participant
,
441 double level
, PidginMedia
*gtkmedia
)
443 GtkWidget
*progress
= NULL
;
444 if (participant
== NULL
)
445 progress
= gtkmedia
->priv
->send_progress
;
447 progress
= pidgin_media_get_widget(gtkmedia
, session_id
, participant
);
450 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress
), level
);
455 pidgin_media_disconnect_levels(PurpleMedia
*media
, PidginMedia
*gtkmedia
)
457 PurpleMediaManager
*manager
= purple_media_get_manager(media
);
458 GstElement
*element
= purple_media_manager_get_pipeline(manager
);
459 gulong handler_id
= g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element
))),
460 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
, 0, 0,
461 NULL
, G_CALLBACK(level_message_cb
), gtkmedia
);
463 g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element
))),
468 pidgin_media_dispose(GObject
*media
)
470 PidginMedia
*gtkmedia
= PIDGIN_MEDIA(media
);
471 purple_debug_info("gtkmedia", "pidgin_media_dispose\n");
473 if (gtkmedia
->priv
->media
) {
474 purple_request_close_with_handle(gtkmedia
);
475 purple_media_remove_output_windows(gtkmedia
->priv
->media
);
476 pidgin_media_disconnect_levels(gtkmedia
->priv
->media
, gtkmedia
);
477 g_object_unref(gtkmedia
->priv
->media
);
478 gtkmedia
->priv
->media
= NULL
;
481 if (gtkmedia
->priv
->ui
) {
482 g_object_unref(gtkmedia
->priv
->ui
);
483 gtkmedia
->priv
->ui
= NULL
;
486 if (gtkmedia
->priv
->timeout_id
!= 0)
487 g_source_remove(gtkmedia
->priv
->timeout_id
);
489 if (gtkmedia
->priv
->recv_progressbars
) {
490 g_hash_table_destroy(gtkmedia
->priv
->recv_progressbars
);
491 g_hash_table_destroy(gtkmedia
->priv
->remote_videos
);
492 gtkmedia
->priv
->recv_progressbars
= NULL
;
493 gtkmedia
->priv
->remote_videos
= NULL
;
496 G_OBJECT_CLASS(pidgin_media_parent_class
)->dispose(media
);
500 pidgin_media_finalize(GObject
*media
)
502 /* PidginMedia *gtkmedia = PIDGIN_MEDIA(media); */
503 purple_debug_info("gtkmedia", "pidgin_media_finalize\n");
505 G_OBJECT_CLASS(pidgin_media_parent_class
)->finalize(media
);
509 pidgin_media_emit_message(PidginMedia
*gtkmedia
, const char *msg
)
511 PurpleConversation
*conv
= purple_conversations_find_with_account(
512 gtkmedia
->priv
->screenname
,
513 purple_media_get_account(gtkmedia
->priv
->media
));
515 purple_conversation_write_system_message(conv
, msg
, 0);
520 PidginMedia
*gtkmedia
;
523 } PidginMediaRealizeData
;
526 realize_cb_cb(PidginMediaRealizeData
*data
)
528 PidginMediaPrivate
*priv
= data
->gtkmedia
->priv
;
529 GdkWindow
*window
= NULL
;
531 if (data
->participant
== NULL
)
532 window
= gtk_widget_get_window(priv
->local_video
);
534 GtkWidget
*widget
= pidgin_media_get_widget(data
->gtkmedia
,
535 data
->session_id
, data
->participant
);
537 window
= gtk_widget_get_window(widget
);
541 gulong window_id
= 0;
542 #ifdef GDK_WINDOWING_WIN32
543 if (GDK_IS_WIN32_WINDOW(window
))
544 window_id
= GPOINTER_TO_UINT(GDK_WINDOW_HWND(window
));
547 #ifdef GDK_WINDOWING_X11
548 if (GDK_IS_X11_WINDOW(window
))
549 window_id
= gdk_x11_window_get_xid(window
);
552 #ifdef GDK_WINDOWING_QUARTZ
553 if (GDK_IS_QUARTZ_WINDOW(window
))
554 window_id
= (gulong
)gdk_quartz_window_get_nsview(window
);
557 g_warning("Unsupported GDK backend");
558 #if !(defined(GDK_WINDOWING_WIN32) \
559 || defined(GDK_WINDOWING_X11) \
560 || defined(GDK_WINDOWING_QUARTZ))
561 # error "Unsupported GDK windowing system"
564 purple_media_set_output_window(priv
->media
, data
->session_id
,
565 data
->participant
, window_id
);
568 g_free(data
->session_id
);
569 g_free(data
->participant
);
575 realize_cb(GtkWidget
*widget
, PidginMediaRealizeData
*data
)
577 g_timeout_add(0, (GSourceFunc
)realize_cb_cb
, data
);
581 pidgin_media_error_cb(PidginMedia
*media
, const char *error
, PidginMedia
*gtkmedia
)
583 PurpleConversation
*conv
= purple_conversations_find_with_account(
584 gtkmedia
->priv
->screenname
,
585 purple_media_get_account(gtkmedia
->priv
->media
));
587 purple_conversation_write_system_message(
588 conv
, error
, PURPLE_MESSAGE_ERROR
);
590 purple_notify_error(NULL
, NULL
, _("Media error"), error
,
591 purple_request_cpar_from_conversation(conv
));
594 gtk_statusbar_push(GTK_STATUSBAR(gtkmedia
->priv
->statusbar
),
599 pidgin_media_accept_cb(PurpleMedia
*media
, int index
)
601 purple_media_stream_info(media
, PURPLE_MEDIA_INFO_ACCEPT
,
606 pidgin_media_reject_cb(PurpleMedia
*media
, int index
)
608 GList
*iter
= purple_media_get_session_ids(media
);
609 for (; iter
; iter
= g_list_delete_link(iter
, iter
)) {
610 const gchar
*sessionid
= iter
->data
;
611 if (!purple_media_accepted(media
, sessionid
, NULL
))
612 purple_media_stream_info(media
, PURPLE_MEDIA_INFO_REJECT
,
613 sessionid
, NULL
, TRUE
);
618 pidgin_request_timeout_cb(PidginMedia
*gtkmedia
)
620 PurpleAccount
*account
;
623 PurpleMediaSessionType type
;
624 gchar
*message
= NULL
;
626 account
= purple_media_get_account(gtkmedia
->priv
->media
);
627 buddy
= purple_blist_find_buddy(account
, gtkmedia
->priv
->screenname
);
628 alias
= buddy
? purple_buddy_get_contact_alias(buddy
) :
629 gtkmedia
->priv
->screenname
;
630 type
= gtkmedia
->priv
->request_type
;
631 gtkmedia
->priv
->timeout_id
= 0;
633 if (type
& PURPLE_MEDIA_AUDIO
&& type
& PURPLE_MEDIA_VIDEO
) {
634 message
= g_strdup_printf(_("%s wishes to start an audio/video session with you."),
636 } else if (type
& PURPLE_MEDIA_AUDIO
) {
637 message
= g_strdup_printf(_("%s wishes to start an audio session with you."),
639 } else if (type
& PURPLE_MEDIA_VIDEO
) {
640 message
= g_strdup_printf(_("%s wishes to start a video session with you."),
644 gtkmedia
->priv
->request_type
= PURPLE_MEDIA_NONE
;
645 if (!purple_media_accepted(gtkmedia
->priv
->media
, NULL
, NULL
)) {
646 purple_request_accept_cancel(gtkmedia
, _("Incoming Call"),
647 message
, NULL
, PURPLE_DEFAULT_ACTION_NONE
,
648 purple_request_cpar_from_account(account
),
649 gtkmedia
->priv
->media
, pidgin_media_accept_cb
,
650 pidgin_media_reject_cb
);
652 pidgin_media_emit_message(gtkmedia
, message
);
658 pidgin_media_input_volume_changed(GtkScaleButton
*range
, double value
,
661 double val
= (double)value
* 100.0;
662 purple_media_set_input_volume(media
, NULL
, val
);
666 pidgin_media_output_volume_changed(GtkScaleButton
*range
, double value
,
669 double val
= (double)value
* 100.0;
670 purple_media_set_output_volume(media
, NULL
, NULL
, val
);
674 destroy_parent_widget_cb(GtkWidget
*widget
, GtkWidget
*parent
)
676 g_return_if_fail(GTK_IS_WIDGET(parent
));
678 gtk_widget_destroy(parent
);
682 pidgin_media_add_audio_widget(PidginMedia
*gtkmedia
,
683 PurpleMediaSessionType type
, const gchar
*sid
)
685 GtkWidget
*volume_widget
, *progress_parent
, *volume
, *progress
;
688 static const gchar
* input_volume_icons
[] = {
689 "microphone-sensitivity-muted-symbolic",
690 "microphone-sensitivity-high-symbolic",
691 "microphone-sensitivity-low-symbolic",
692 "microphone-sensitivity-medium-symbolic",
696 if (type
& PURPLE_MEDIA_SEND_AUDIO
) {
697 value
= purple_prefs_get_int(
698 "/purple/media/audio/volume/input");
699 } else if (type
& PURPLE_MEDIA_RECV_AUDIO
) {
700 value
= purple_prefs_get_int(
701 "/purple/media/audio/volume/output");
703 g_return_val_if_reached(NULL
);
705 /* Setup widget structure */
706 volume_widget
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
707 progress_parent
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
708 gtk_box_pack_start(GTK_BOX(volume_widget
),
709 progress_parent
, TRUE
, TRUE
, 0);
712 volume
= gtk_volume_button_new();
713 gtk_scale_button_set_value(GTK_SCALE_BUTTON(volume
), value
/100.0);
714 gtk_box_pack_end(GTK_BOX(volume_widget
),
715 volume
, FALSE
, FALSE
, 0);
717 /* Volume level indicator */
718 progress
= gtk_progress_bar_new();
719 gtk_widget_set_size_request(progress
, 250, 10);
720 gtk_box_pack_end(GTK_BOX(progress_parent
), progress
, TRUE
, FALSE
, 0);
722 if (type
& PURPLE_MEDIA_SEND_AUDIO
) {
723 g_signal_connect (G_OBJECT(volume
), "value-changed",
724 G_CALLBACK(pidgin_media_input_volume_changed
),
725 gtkmedia
->priv
->media
);
726 gtk_scale_button_set_icons(GTK_SCALE_BUTTON(volume
),
729 gtkmedia
->priv
->send_progress
= progress
;
730 } else if (type
& PURPLE_MEDIA_RECV_AUDIO
) {
731 g_signal_connect (G_OBJECT(volume
), "value-changed",
732 G_CALLBACK(pidgin_media_output_volume_changed
),
733 gtkmedia
->priv
->media
);
735 pidgin_media_insert_widget(gtkmedia
, progress
, sid
, gtkmedia
->priv
->screenname
);
738 g_signal_connect(G_OBJECT(progress
), "destroy",
739 G_CALLBACK(destroy_parent_widget_cb
),
742 gtk_widget_show_all(volume_widget
);
744 return volume_widget
;
748 phone_dtmf_pressed_cb(GtkButton
*button
, gpointer user_data
)
750 PidginMedia
*gtkmedia
= user_data
;
754 num
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button
), "dtmf-digit"));
755 sid
= g_object_get_data(G_OBJECT(button
), "session-id");
757 purple_media_send_dtmf(gtkmedia
->priv
->media
, sid
, num
, 25, 50);
760 static inline GtkWidget
*
761 phone_create_button(const gchar
*text_hi
, const gchar
*text_lo
)
767 const gchar
*text_hi_local
;
770 text_hi_local
= _(text_hi
);
774 grid
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
775 gtk_box_set_homogeneous(GTK_BOX(grid
), TRUE
);
777 button
= gtk_button_new();
778 label_hi
= gtk_label_new(text_hi_local
);
779 gtk_box_pack_end(GTK_BOX(grid
), label_hi
, FALSE
, TRUE
, 0);
780 label_lo
= gtk_label_new(text_lo
);
781 gtk_label_set_use_markup(GTK_LABEL(label_lo
), TRUE
);
782 gtk_box_pack_end(GTK_BOX(grid
), label_lo
, FALSE
, TRUE
, 0);
783 gtk_container_add(GTK_CONTAINER(button
), grid
);
788 static struct phone_label
{
793 {"<b>1</b>", NULL
, '1'},
794 /* Translators note: These are the letters on the keys of a numeric
795 keypad; translate according to the tables in §7 of ETSI ES 202 130:
796 http://webapp.etsi.org/WorkProgram/Report_WorkItem.asp?WKI_ID=11730
798 /* Letters on the '2' key of a numeric keypad */
799 {"<b>2</b>", N_("ABC"), '2'},
800 /* Letters on the '3' key of a numeric keypad */
801 {"<b>3</b>", N_("DEF"), '3'},
802 /* Letters on the '4' key of a numeric keypad */
803 {"<b>4</b>", N_("GHI"), '4'},
804 /* Letters on the '5' key of a numeric keypad */
805 {"<b>5</b>", N_("JKL"), '5'},
806 /* Letters on the '6' key of a numeric keypad */
807 {"<b>6</b>", N_("MNO"), '6'},
808 /* Letters on the '7' key of a numeric keypad */
809 {"<b>7</b>", N_("PQRS"), '7'},
810 /* Letters on the '8' key of a numeric keypad */
811 {"<b>8</b>", N_("TUV"), '8'},
812 /* Letters on the '9' key of a numeric keypad */
813 {"<b>9</b>", N_("WXYZ"), '9'},
814 {"<b>*</b>", NULL
, '*'},
815 {"<b>0</b>", NULL
, '0'},
816 {"<b>#</b>", NULL
, '#'},
821 pidgin_media_dtmf_key_press_event_cb(GtkWidget
*widget
,
822 GdkEvent
*event
, gpointer user_data
)
824 PidginMedia
*gtkmedia
= user_data
;
825 GdkEventKey
*key
= (GdkEventKey
*) event
;
827 if (event
->type
!= GDK_KEY_PRESS
) {
831 if ((key
->keyval
>= GDK_KEY_0
&& key
->keyval
<= GDK_KEY_9
) ||
832 key
->keyval
== GDK_KEY_asterisk
||
833 key
->keyval
== GDK_KEY_numbersign
) {
834 gchar
*sid
= g_object_get_data(G_OBJECT(widget
), "session-id");
836 purple_media_send_dtmf(gtkmedia
->priv
->media
, sid
, key
->keyval
, 25, 50);
843 pidgin_media_add_dtmf_widget(PidginMedia
*gtkmedia
,
844 PurpleMediaSessionType type
, const gchar
*_sid
)
846 GtkWidget
*grid
= gtk_grid_new();
849 GtkApplicationWindow
*win
= >kmedia
->parent
;
851 gtk_grid_set_row_homogeneous(GTK_GRID(grid
), TRUE
);
852 gtk_grid_set_column_homogeneous(GTK_GRID(grid
), TRUE
);
855 for (index
= 0; phone_labels
[index
].subtext
!= NULL
; index
++) {
856 button
= phone_create_button(phone_labels
[index
].text
,
857 phone_labels
[index
].subtext
);
858 g_signal_connect(button
, "pressed",
859 G_CALLBACK(phone_dtmf_pressed_cb
), gtkmedia
);
860 g_object_set_data(G_OBJECT(button
), "dtmf-digit",
861 GINT_TO_POINTER(phone_labels
[index
].chr
));
862 g_object_set_data_full(G_OBJECT(button
), "session-id",
863 g_strdup(_sid
), g_free
);
864 gtk_grid_attach(GTK_GRID(grid
), button
,
865 index
% 3, index
/ 3, 1, 1);
866 g_object_set(button
, "expand", TRUE
, "margin", 2, NULL
);
869 g_signal_connect(G_OBJECT(win
), "key-press-event",
870 G_CALLBACK(pidgin_media_dtmf_key_press_event_cb
), gtkmedia
);
871 g_object_set_data_full(G_OBJECT(win
), "session-id",
872 g_strdup(_sid
), g_free
);
874 gtk_widget_show_all(grid
);
880 pidgin_media_ready_cb(PurpleMedia
*media
, PidginMedia
*gtkmedia
, const gchar
*sid
)
882 GtkWidget
*send_widget
= NULL
, *recv_widget
= NULL
, *button_widget
= NULL
;
883 PurpleMediaSessionType type
=
884 purple_media_get_session_type(media
, sid
);
886 if (gtkmedia
->priv
->recv_widget
== NULL
887 && type
& (PURPLE_MEDIA_RECV_VIDEO
|
888 PURPLE_MEDIA_RECV_AUDIO
)) {
889 recv_widget
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
890 gtk_box_pack_start(GTK_BOX(gtkmedia
->priv
->display
),
891 recv_widget
, TRUE
, TRUE
, 0);
892 gtk_widget_show(recv_widget
);
894 recv_widget
= gtkmedia
->priv
->recv_widget
;
896 if (gtkmedia
->priv
->send_widget
== NULL
897 && type
& (PURPLE_MEDIA_SEND_VIDEO
|
898 PURPLE_MEDIA_SEND_AUDIO
)) {
899 send_widget
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
900 gtk_box_pack_start(GTK_BOX(gtkmedia
->priv
->display
),
901 send_widget
, FALSE
, TRUE
, 0);
902 button_widget
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
903 gtk_box_pack_end(GTK_BOX(recv_widget
), button_widget
,
905 gtk_widget_show(send_widget
);
908 gtkmedia
->priv
->hold
=
909 gtk_toggle_button_new_with_mnemonic(_("_Hold"));
910 gtk_box_pack_end(GTK_BOX(button_widget
), gtkmedia
->priv
->hold
,
912 gtk_widget_show(gtkmedia
->priv
->hold
);
913 gtk_actionable_set_action_name(
914 GTK_ACTIONABLE(gtkmedia
->priv
->hold
),
917 send_widget
= gtkmedia
->priv
->send_widget
;
918 button_widget
= gtkmedia
->priv
->button_widget
;
921 if (type
& PURPLE_MEDIA_RECV_VIDEO
) {
922 PidginMediaRealizeData
*data
;
924 GtkWidget
*remote_video
;
926 aspect
= gtk_aspect_frame_new(NULL
, 0, 0, 4.0/3.0, FALSE
);
927 gtk_frame_set_shadow_type(GTK_FRAME(aspect
), GTK_SHADOW_IN
);
928 gtk_box_pack_start(GTK_BOX(recv_widget
), aspect
, TRUE
, TRUE
, 0);
930 data
= g_new0(PidginMediaRealizeData
, 1);
931 data
->gtkmedia
= gtkmedia
;
932 data
->session_id
= g_strdup(sid
);
933 data
->participant
= g_strdup(gtkmedia
->priv
->screenname
);
935 remote_video
= pidgin_create_video_widget();
936 g_signal_connect(G_OBJECT(remote_video
), "realize",
937 G_CALLBACK(realize_cb
), data
);
938 gtk_container_add(GTK_CONTAINER(aspect
), remote_video
);
939 gtk_widget_set_size_request (GTK_WIDGET(remote_video
), 320, 240);
940 g_signal_connect(G_OBJECT(remote_video
), "destroy",
941 G_CALLBACK(destroy_parent_widget_cb
), aspect
);
943 gtk_widget_show(remote_video
);
944 gtk_widget_show(aspect
);
946 pidgin_media_insert_widget(gtkmedia
, remote_video
,
947 data
->session_id
, data
->participant
);
950 if (type
& PURPLE_MEDIA_SEND_VIDEO
&& !gtkmedia
->priv
->local_video
) {
951 PidginMediaRealizeData
*data
;
953 GtkWidget
*local_video
;
955 aspect
= gtk_aspect_frame_new(NULL
, 0, 0, 4.0/3.0, TRUE
);
956 gtk_frame_set_shadow_type(GTK_FRAME(aspect
), GTK_SHADOW_IN
);
957 gtk_box_pack_start(GTK_BOX(send_widget
), aspect
, FALSE
, TRUE
, 0);
959 data
= g_new0(PidginMediaRealizeData
, 1);
960 data
->gtkmedia
= gtkmedia
;
961 data
->session_id
= g_strdup(sid
);
962 data
->participant
= NULL
;
964 local_video
= pidgin_create_video_widget();
965 g_signal_connect(G_OBJECT(local_video
), "realize",
966 G_CALLBACK(realize_cb
), data
);
967 gtk_container_add(GTK_CONTAINER(aspect
), local_video
);
968 gtk_widget_set_size_request (GTK_WIDGET(local_video
), 80, 60);
969 g_signal_connect(G_OBJECT(local_video
), "destroy",
970 G_CALLBACK(destroy_parent_widget_cb
), aspect
);
972 gtk_widget_show(local_video
);
973 gtk_widget_show(aspect
);
975 gtkmedia
->priv
->pause
=
976 gtk_toggle_button_new_with_mnemonic(_("_Pause"));
977 gtk_box_pack_end(GTK_BOX(button_widget
), gtkmedia
->priv
->pause
,
979 gtk_widget_show(gtkmedia
->priv
->pause
);
980 gtk_actionable_set_action_name(
981 GTK_ACTIONABLE(gtkmedia
->priv
->pause
),
984 gtkmedia
->priv
->local_video
= local_video
;
986 if (type
& PURPLE_MEDIA_RECV_AUDIO
) {
987 gtk_box_pack_end(GTK_BOX(recv_widget
),
988 pidgin_media_add_audio_widget(gtkmedia
,
989 PURPLE_MEDIA_RECV_AUDIO
, sid
), FALSE
, FALSE
, 0);
992 if (type
& PURPLE_MEDIA_SEND_AUDIO
) {
993 gtkmedia
->priv
->mute
=
994 gtk_toggle_button_new_with_mnemonic(_("_Mute"));
995 gtk_box_pack_end(GTK_BOX(button_widget
), gtkmedia
->priv
->mute
,
997 gtk_widget_show(gtkmedia
->priv
->mute
);
998 gtk_actionable_set_action_name(
999 GTK_ACTIONABLE(gtkmedia
->priv
->mute
),
1002 gtk_box_pack_end(GTK_BOX(recv_widget
),
1003 pidgin_media_add_audio_widget(gtkmedia
,
1004 PURPLE_MEDIA_SEND_AUDIO
, sid
), FALSE
, FALSE
, 0);
1006 gtk_box_pack_end(GTK_BOX(recv_widget
),
1007 pidgin_media_add_dtmf_widget(gtkmedia
,
1008 PURPLE_MEDIA_SEND_AUDIO
, sid
), FALSE
, FALSE
, 0);
1011 if (type
& PURPLE_MEDIA_AUDIO
&&
1012 gtkmedia
->priv
->level_handler_id
== 0) {
1013 gtkmedia
->priv
->level_handler_id
= g_signal_connect(
1014 media
, "level", G_CALLBACK(level_message_cb
),
1018 if (send_widget
!= NULL
)
1019 gtkmedia
->priv
->send_widget
= send_widget
;
1020 if (recv_widget
!= NULL
)
1021 gtkmedia
->priv
->recv_widget
= recv_widget
;
1022 if (button_widget
!= NULL
) {
1023 gtkmedia
->priv
->button_widget
= button_widget
;
1024 gtk_widget_show(GTK_WIDGET(button_widget
));
1027 if (purple_media_is_initiator(media
, sid
, NULL
) == FALSE
) {
1028 if (gtkmedia
->priv
->timeout_id
!= 0)
1029 g_source_remove(gtkmedia
->priv
->timeout_id
);
1030 gtkmedia
->priv
->request_type
|= type
;
1031 gtkmedia
->priv
->timeout_id
= g_timeout_add(500,
1032 (GSourceFunc
)pidgin_request_timeout_cb
,
1036 /* set the window icon according to the type */
1037 if (type
& PURPLE_MEDIA_VIDEO
) {
1038 gtk_window_set_icon_name(GTK_WINDOW(gtkmedia
), "video-call");
1039 } else if (type
& PURPLE_MEDIA_AUDIO
) {
1040 gtk_window_set_icon_name(GTK_WINDOW(gtkmedia
), "audio-call");
1043 gtk_widget_show(gtkmedia
->priv
->display
);
1047 pidgin_media_state_changed_cb(PurpleMedia
*media
, PurpleMediaState state
,
1048 gchar
*sid
, gchar
*name
, PidginMedia
*gtkmedia
)
1050 purple_debug_info("gtkmedia", "state: %d sid: %s name: %s\n",
1051 state
, sid
? sid
: "(null)", name
? name
: "(null)");
1052 if (state
== PURPLE_MEDIA_STATE_END
) {
1053 if (sid
!= NULL
&& name
!= NULL
) {
1054 pidgin_media_remove_widget(gtkmedia
, sid
, name
);
1055 } else if (sid
== NULL
&& name
== NULL
) {
1056 pidgin_media_emit_message(gtkmedia
,
1057 _("The call has been terminated."));
1058 gtk_widget_destroy(GTK_WIDGET(gtkmedia
));
1060 } else if (state
== PURPLE_MEDIA_STATE_NEW
&&
1061 sid
!= NULL
&& name
!= NULL
) {
1062 pidgin_media_ready_cb(media
, gtkmedia
, sid
);
1067 pidgin_media_stream_info_cb(PurpleMedia
*media
, PurpleMediaInfoType type
,
1068 gchar
*sid
, gchar
*name
, gboolean local
,
1069 PidginMedia
*gtkmedia
)
1071 if (type
== PURPLE_MEDIA_INFO_REJECT
) {
1072 pidgin_media_emit_message(gtkmedia
,
1073 _("You have rejected the call."));
1074 } else if (type
== PURPLE_MEDIA_INFO_ACCEPT
) {
1076 purple_request_close_with_handle(gtkmedia
);
1077 pidgin_media_set_state(gtkmedia
, PIDGIN_MEDIA_ACCEPTED
);
1078 pidgin_media_emit_message(gtkmedia
, _("Call in progress."));
1079 gtk_statusbar_push(GTK_STATUSBAR(gtkmedia
->priv
->statusbar
),
1080 0, _("Call in progress"));
1081 gtk_widget_show(GTK_WIDGET(gtkmedia
));
1082 } else if (type
== PURPLE_MEDIA_INFO_MUTE
&& !local
) {
1083 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkmedia
->priv
->mute
), TRUE
);
1084 } else if (type
== PURPLE_MEDIA_INFO_UNMUTE
&& !local
) {
1085 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkmedia
->priv
->mute
), FALSE
);
1090 pidgin_media_set_property (GObject
*object
, guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
1093 g_return_if_fail(PIDGIN_IS_MEDIA(object
));
1095 media
= PIDGIN_MEDIA(object
);
1099 if (media
->priv
->media
)
1100 g_object_unref(media
->priv
->media
);
1101 media
->priv
->media
= g_value_dup_object(value
);
1103 if (purple_media_is_initiator(media
->priv
->media
,
1104 NULL
, NULL
) == TRUE
)
1105 pidgin_media_set_state(media
, PIDGIN_MEDIA_WAITING
);
1107 pidgin_media_set_state(media
, PIDGIN_MEDIA_REQUESTED
);
1109 g_signal_connect(G_OBJECT(media
->priv
->media
), "error",
1110 G_CALLBACK(pidgin_media_error_cb
), media
);
1111 g_signal_connect(G_OBJECT(media
->priv
->media
), "state-changed",
1112 G_CALLBACK(pidgin_media_state_changed_cb
), media
);
1113 g_signal_connect(G_OBJECT(media
->priv
->media
), "stream-info",
1114 G_CALLBACK(pidgin_media_stream_info_cb
), media
);
1117 case PROP_SCREENNAME
:
1118 g_free(media
->priv
->screenname
);
1119 media
->priv
->screenname
= g_value_dup_string(value
);
1122 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
1128 pidgin_media_get_property (GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
1131 g_return_if_fail(PIDGIN_IS_MEDIA(object
));
1133 media
= PIDGIN_MEDIA(object
);
1137 g_value_set_object(value
, media
->priv
->media
);
1139 case PROP_SCREENNAME
:
1140 g_value_set_string(value
, media
->priv
->screenname
);
1143 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
1149 pidgin_media_new(PurpleMedia
*media
, const gchar
*screenname
)
1151 PidginMedia
*gtkmedia
= g_object_new(pidgin_media_get_type(),
1153 "screenname", screenname
, NULL
);
1154 return GTK_WIDGET(gtkmedia
);
1158 pidgin_media_set_state(PidginMedia
*gtkmedia
, PidginMediaState state
)
1160 gtkmedia
->priv
->state
= state
;
1164 pidgin_media_new_cb(PurpleMediaManager
*manager
, PurpleMedia
*media
,
1165 PurpleAccount
*account
, gchar
*screenname
, gpointer nul
)
1167 PidginMedia
*gtkmedia
= PIDGIN_MEDIA(
1168 pidgin_media_new(media
, screenname
));
1169 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, screenname
);
1170 const gchar
*alias
= buddy
?
1171 purple_buddy_get_contact_alias(buddy
) : screenname
;
1172 gtk_window_set_title(GTK_WINDOW(gtkmedia
), alias
);
1174 if (purple_media_is_initiator(media
, NULL
, NULL
) == TRUE
)
1175 gtk_widget_show(GTK_WIDGET(gtkmedia
));
1182 pidgin_medias_init(void)
1185 PurpleMediaManager
*manager
= purple_media_manager_get();
1186 PurpleMediaElementInfo
*video_src
= NULL
;
1187 PurpleMediaElementInfo
*video_sink
= NULL
;
1188 PurpleMediaElementInfo
*audio_src
= NULL
;
1189 PurpleMediaElementInfo
*audio_sink
= NULL
;
1192 pref
= purple_prefs_get_string(
1193 PIDGIN_PREFS_ROOT
"/vvconfig/video/src/device");
1195 video_src
= purple_media_manager_get_element_info(manager
, pref
);
1197 pref
= "autovideosrc";
1198 purple_prefs_set_string(
1199 PIDGIN_PREFS_ROOT
"/vvconfig/video/src/device", pref
);
1200 video_src
= purple_media_manager_get_element_info(manager
,
1204 pref
= purple_prefs_get_string(
1205 PIDGIN_PREFS_ROOT
"/vvconfig/video/sink/device");
1207 video_sink
= purple_media_manager_get_element_info(manager
, pref
);
1209 pref
= "autovideosink";
1210 purple_prefs_set_string(
1211 PIDGIN_PREFS_ROOT
"/vvconfig/video/sink/device", pref
);
1212 video_sink
= purple_media_manager_get_element_info(manager
,
1216 pref
= purple_prefs_get_string(
1217 PIDGIN_PREFS_ROOT
"/vvconfig/audio/src/device");
1219 audio_src
= purple_media_manager_get_element_info(manager
, pref
);
1221 pref
= "autoaudiosrc";
1222 purple_prefs_set_string(
1223 PIDGIN_PREFS_ROOT
"/vvconfig/audio/src/device", pref
);
1224 audio_src
= purple_media_manager_get_element_info(manager
,
1228 pref
= purple_prefs_get_string(
1229 PIDGIN_PREFS_ROOT
"/vvconfig/audio/sink/device");
1231 audio_sink
= purple_media_manager_get_element_info(manager
, pref
);
1233 pref
= "autoaudiosink";
1234 purple_prefs_set_string(
1235 PIDGIN_PREFS_ROOT
"/vvconfig/audio/sink/device", pref
);
1236 audio_sink
= purple_media_manager_get_element_info(manager
,
1240 g_signal_connect(G_OBJECT(manager
), "init-media",
1241 G_CALLBACK(pidgin_media_new_cb
), NULL
);
1243 purple_media_manager_set_ui_caps(manager
,
1244 PURPLE_MEDIA_CAPS_AUDIO
|
1245 PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION
|
1246 PURPLE_MEDIA_CAPS_VIDEO
|
1247 PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION
|
1248 PURPLE_MEDIA_CAPS_AUDIO_VIDEO
);
1250 purple_debug_info("gtkmedia", "Registering media element types\n");
1251 purple_media_manager_set_active_element(manager
, video_src
);
1252 purple_media_manager_set_active_element(manager
, video_sink
);
1253 purple_media_manager_set_active_element(manager
, audio_src
);
1254 purple_media_manager_set_active_element(manager
, audio_sink
);