Wed, 05 Feb 2020 02:01:25 -0600
Fix whitespace
Signed-off-by: Richard Laager <rlaager@pidgin.im>
/* pidgin * * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "debug.h" #include "connection.h" #include "media.h" #include "mediamanager.h" #include "pidgin.h" #include "request.h" #include "gtkmedia.h" #include "gtkutils.h" #include "pidginstock.h" #ifdef USE_VV #include "media-gst.h" #ifdef GDK_WINDOWING_WIN32 #include <gdk/gdkwin32.h> #endif #ifdef GDK_WINDOWING_X11 #include <gdk/gdkx.h> #endif #ifdef GDK_WINDOWING_QUARTZ #include <gdk/gdkquartz.h> #endif #include <gdk/gdkkeysyms.h> #define PIDGIN_TYPE_MEDIA (pidgin_media_get_type()) #define PIDGIN_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia)) #define PIDGIN_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass)) #define PIDGIN_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA)) #define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA)) #define PIDGIN_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass)) typedef struct _PidginMedia PidginMedia; typedef struct _PidginMediaClass PidginMediaClass; typedef struct _PidginMediaPrivate PidginMediaPrivate; typedef enum { /* Waiting for response */ PIDGIN_MEDIA_WAITING = 1, /* Got request */ PIDGIN_MEDIA_REQUESTED, /* Accepted call */ PIDGIN_MEDIA_ACCEPTED, /* Rejected call */ PIDGIN_MEDIA_REJECTED, } PidginMediaState; struct _PidginMediaClass { GtkApplicationWindowClass parent_class; }; struct _PidginMedia { GtkApplicationWindow parent; PidginMediaPrivate *priv; }; struct _PidginMediaPrivate { PurpleMedia *media; gchar *screenname; gulong level_handler_id; GtkBuilder *ui; GtkWidget *menubar; GtkWidget *statusbar; GtkWidget *hold; GtkWidget *mute; GtkWidget *pause; GtkWidget *send_progress; GHashTable *recv_progressbars; PidginMediaState state; GtkWidget *display; GtkWidget *send_widget; GtkWidget *recv_widget; GtkWidget *button_widget; GtkWidget *local_video; GHashTable *remote_videos; guint timeout_id; PurpleMediaSessionType request_type; }; static GType pidgin_media_get_type(void); G_DEFINE_TYPE_WITH_PRIVATE(PidginMedia, pidgin_media, GTK_TYPE_APPLICATION_WINDOW); static void pidgin_media_dispose (GObject *object); static void pidgin_media_finalize (GObject *object); static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state); #if 0 enum { LAST_SIGNAL }; static guint pidgin_media_signals[LAST_SIGNAL] = {0}; #endif enum { PROP_0, PROP_MEDIA, PROP_SCREENNAME }; static void pidgin_media_class_init (PidginMediaClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); /* GtkContainerClass *container_class = (GtkContainerClass*)klass; */ gobject_class->dispose = pidgin_media_dispose; gobject_class->finalize = pidgin_media_finalize; gobject_class->set_property = pidgin_media_set_property; gobject_class->get_property = pidgin_media_get_property; g_object_class_install_property(gobject_class, PROP_MEDIA, g_param_spec_object("media", "PurpleMedia", "The PurpleMedia associated with this media.", PURPLE_TYPE_MEDIA, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(gobject_class, PROP_SCREENNAME, g_param_spec_string("screenname", "Screenname", "The screenname of the user this session is with.", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void pidgin_media_hangup_activate_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) { PidginMedia *media = PIDGIN_MEDIA(user_data); purple_media_stream_info(media->priv->media, PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); } static void pidgin_media_hold_change_state_cb(GSimpleAction *action, GVariant *value, gpointer user_data) { PidginMedia *media = PIDGIN_MEDIA(user_data); purple_media_stream_info(media->priv->media, g_variant_get_boolean(value) ? PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD, NULL, NULL, TRUE); g_simple_action_set_state(action, value); } static void pidgin_media_mute_change_state_cb(GSimpleAction *action, GVariant *value, gpointer user_data) { PidginMedia *media = PIDGIN_MEDIA(user_data); purple_media_stream_info(media->priv->media, g_variant_get_boolean(value) ? PURPLE_MEDIA_INFO_MUTE : PURPLE_MEDIA_INFO_UNMUTE, NULL, NULL, TRUE); g_simple_action_set_state(action, value); } static void pidgin_media_pause_change_state_cb(GSimpleAction *action, GVariant *value, gpointer user_data) { PidginMedia *media = PIDGIN_MEDIA(user_data); purple_media_stream_info(media->priv->media, g_variant_get_boolean(value) ? PURPLE_MEDIA_INFO_PAUSE : PURPLE_MEDIA_INFO_UNPAUSE, NULL, NULL, TRUE); g_simple_action_set_state(action, value); } static gboolean pidgin_media_delete_event_cb(GtkWidget *widget, GdkEvent *event, PidginMedia *media) { if (media->priv->media) g_action_group_activate_action(G_ACTION_GROUP(media), "Hangup", NULL); return FALSE; } #ifdef HAVE_X11 static int pidgin_x_error_handler(Display *display, XErrorEvent *event) { const gchar *error_type; switch (event->error_code) { #define XERRORCASE(type) case type: error_type = #type; break XERRORCASE(BadAccess); XERRORCASE(BadAlloc); XERRORCASE(BadAtom); XERRORCASE(BadColor); XERRORCASE(BadCursor); XERRORCASE(BadDrawable); XERRORCASE(BadFont); XERRORCASE(BadGC); XERRORCASE(BadIDChoice); XERRORCASE(BadImplementation); XERRORCASE(BadLength); XERRORCASE(BadMatch); XERRORCASE(BadName); XERRORCASE(BadPixmap); XERRORCASE(BadRequest); XERRORCASE(BadValue); XERRORCASE(BadWindow); #undef XERRORCASE default: error_type = "unknown"; break; } purple_debug_error("media", "A %s Xlib error has occurred. " "The program would normally crash now.\n", error_type); return 0; } #endif static const GActionEntry media_action_entries[] = { { "Hangup", pidgin_media_hangup_activate_cb }, { "Hold", NULL, NULL, "false", pidgin_media_hold_change_state_cb }, { "Mute", NULL, NULL, "false", pidgin_media_mute_change_state_cb }, { "Pause", NULL, NULL, "false", pidgin_media_pause_change_state_cb }, }; static const gchar *media_menu = "<interface>" "<menu id='MediaMenu'>" "<submenu>" "<attribute name='label' translatable='yes'>_Media</attribute>" "<section>" "<item>" "<attribute name='label' translatable='yes'>_Hangup</attribute>" "<attribute name='action'>win.Hangup</attribute>" "</item>" "</section>" "</submenu>" "</menu>" "</interface>"; static GtkWidget * setup_menubar(PidginMedia *window) { GError *error; GtkWidget *menu; window->priv->ui = gtk_builder_new(); gtk_builder_set_translation_domain(window->priv->ui, PACKAGE); error = NULL; if (!gtk_builder_add_from_string(window->priv->ui, media_menu, -1, &error)) { g_message("building menus failed: %s", error->message); g_error_free(error); exit(EXIT_FAILURE); } menu = gtk_menu_bar_new_from_model(G_MENU_MODEL( gtk_builder_get_object(window->priv->ui, "MediaMenu"))); gtk_widget_show(menu); return menu; } static void pidgin_media_init (PidginMedia *media) { GtkWidget *vbox; media->priv = pidgin_media_get_instance_private(media); #ifdef HAVE_X11 XSetErrorHandler(pidgin_x_error_handler); #endif g_action_map_add_action_entries(G_ACTION_MAP(media), media_action_entries, G_N_ELEMENTS(media_action_entries), media); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(media), vbox); media->priv->statusbar = gtk_statusbar_new(); gtk_box_pack_end(GTK_BOX(vbox), media->priv->statusbar, FALSE, FALSE, 0); gtk_statusbar_push(GTK_STATUSBAR(media->priv->statusbar), 0, _("Calling...")); gtk_widget_show(media->priv->statusbar); media->priv->menubar = setup_menubar(media); gtk_box_pack_start(GTK_BOX(vbox), media->priv->menubar, FALSE, TRUE, 0); media->priv->display = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE); gtk_container_set_border_width(GTK_CONTAINER(media->priv->display), PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(vbox), media->priv->display, TRUE, TRUE, PIDGIN_HIG_BOX_SPACE); gtk_widget_show(vbox); g_signal_connect(G_OBJECT(media), "delete-event", G_CALLBACK(pidgin_media_delete_event_cb), media); media->priv->recv_progressbars = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); media->priv->remote_videos = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); } static gchar * create_key(const gchar *session_id, const gchar *participant) { return g_strdup_printf("%s_%s", session_id, participant); } static void pidgin_media_insert_widget(PidginMedia *gtkmedia, GtkWidget *widget, const gchar *session_id, const gchar *participant) { gchar *key = create_key(session_id, participant); PurpleMediaSessionType type = purple_media_get_session_type(gtkmedia->priv->media, session_id); if (type & PURPLE_MEDIA_AUDIO) g_hash_table_insert(gtkmedia->priv->recv_progressbars, key, widget); else if (type & PURPLE_MEDIA_VIDEO) g_hash_table_insert(gtkmedia->priv->remote_videos, key, widget); } static GtkWidget * pidgin_media_get_widget(PidginMedia *gtkmedia, const gchar *session_id, const gchar *participant) { GtkWidget *widget = NULL; gchar *key = create_key(session_id, participant); PurpleMediaSessionType type = purple_media_get_session_type(gtkmedia->priv->media, session_id); if (type & PURPLE_MEDIA_AUDIO) widget = g_hash_table_lookup(gtkmedia->priv->recv_progressbars, key); else if (type & PURPLE_MEDIA_VIDEO) widget = g_hash_table_lookup(gtkmedia->priv->remote_videos, key); g_free(key); return widget; } static void pidgin_media_remove_widget(PidginMedia *gtkmedia, const gchar *session_id, const gchar *participant) { GtkWidget *widget = pidgin_media_get_widget(gtkmedia, session_id, participant); if (widget) { PurpleMediaSessionType type = purple_media_get_session_type(gtkmedia->priv->media, session_id); gchar *key = create_key(session_id, participant); GtkRequisition req; if (type & PURPLE_MEDIA_AUDIO) { g_hash_table_remove(gtkmedia->priv->recv_progressbars, key); if (g_hash_table_size(gtkmedia->priv->recv_progressbars) == 0 && gtkmedia->priv->send_progress) { gtk_widget_destroy(gtkmedia->priv->send_progress); gtkmedia->priv->send_progress = NULL; gtk_widget_destroy(gtkmedia->priv->mute); gtkmedia->priv->mute = NULL; } } else if (type & PURPLE_MEDIA_VIDEO) { g_hash_table_remove(gtkmedia->priv->remote_videos, key); if (g_hash_table_size(gtkmedia->priv->remote_videos) == 0 && gtkmedia->priv->local_video) { gtk_widget_destroy(gtkmedia->priv->local_video); gtkmedia->priv->local_video = NULL; gtk_widget_destroy(gtkmedia->priv->pause); gtkmedia->priv->pause = NULL; } } g_free(key); gtk_widget_destroy(widget); gtk_widget_get_preferred_size(GTK_WIDGET(gtkmedia), NULL, &req); gtk_window_resize(GTK_WINDOW(gtkmedia), req.width, req.height); } } static void level_message_cb(PurpleMedia *media, gchar *session_id, gchar *participant, double level, PidginMedia *gtkmedia) { GtkWidget *progress = NULL; if (participant == NULL) progress = gtkmedia->priv->send_progress; else progress = pidgin_media_get_widget(gtkmedia, session_id, participant); if (progress) gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level); } static void pidgin_media_disconnect_levels(PurpleMedia *media, PidginMedia *gtkmedia) { PurpleMediaManager *manager = purple_media_get_manager(media); GstElement *element = purple_media_manager_get_pipeline(manager); gulong handler_id = g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(level_message_cb), gtkmedia); if (handler_id) g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), handler_id); } static void pidgin_media_dispose(GObject *media) { PidginMedia *gtkmedia = PIDGIN_MEDIA(media); purple_debug_info("gtkmedia", "pidgin_media_dispose\n"); if (gtkmedia->priv->media) { purple_request_close_with_handle(gtkmedia); purple_media_remove_output_windows(gtkmedia->priv->media); pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia); g_object_unref(gtkmedia->priv->media); gtkmedia->priv->media = NULL; } if (gtkmedia->priv->ui) { g_object_unref(gtkmedia->priv->ui); gtkmedia->priv->ui = NULL; } if (gtkmedia->priv->timeout_id != 0) g_source_remove(gtkmedia->priv->timeout_id); if (gtkmedia->priv->recv_progressbars) { g_hash_table_destroy(gtkmedia->priv->recv_progressbars); g_hash_table_destroy(gtkmedia->priv->remote_videos); gtkmedia->priv->recv_progressbars = NULL; gtkmedia->priv->remote_videos = NULL; } if (gtkmedia->priv->screenname) { g_free(gtkmedia->priv->screenname); gtkmedia->priv->screenname = NULL; } G_OBJECT_CLASS(pidgin_media_parent_class)->dispose(media); } static void pidgin_media_finalize(GObject *media) { /* PidginMedia *gtkmedia = PIDGIN_MEDIA(media); */ purple_debug_info("gtkmedia", "pidgin_media_finalize\n"); G_OBJECT_CLASS(pidgin_media_parent_class)->finalize(media); } static void pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg) { PurpleAccount *account = purple_media_get_account( gtkmedia->priv->media); PurpleConversation *conv = purple_conversations_find_with_account( gtkmedia->priv->screenname, account); if (conv != NULL) purple_conversation_write_system_message(conv, msg, 0); g_object_unref(account); } typedef struct { PidginMedia *gtkmedia; gchar *session_id; gchar *participant; } PidginMediaRealizeData; static gboolean realize_cb_cb(PidginMediaRealizeData *data) { PidginMediaPrivate *priv = data->gtkmedia->priv; GdkWindow *window = NULL; if (data->participant == NULL) window = gtk_widget_get_window(priv->local_video); else { GtkWidget *widget = pidgin_media_get_widget(data->gtkmedia, data->session_id, data->participant); if (widget) window = gtk_widget_get_window(widget); } if (window) { gulong window_id = 0; #ifdef GDK_WINDOWING_WIN32 if (GDK_IS_WIN32_WINDOW(window)) window_id = GPOINTER_TO_UINT(GDK_WINDOW_HWND(window)); else #endif #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_WINDOW(window)) window_id = gdk_x11_window_get_xid(window); else #endif #ifdef GDK_WINDOWING_QUARTZ if (GDK_IS_QUARTZ_WINDOW(window)) window_id = (gulong)gdk_quartz_window_get_nsview(window); else #endif g_warning("Unsupported GDK backend"); #if !(defined(GDK_WINDOWING_WIN32) \ || defined(GDK_WINDOWING_X11) \ || defined(GDK_WINDOWING_QUARTZ)) # error "Unsupported GDK windowing system" #endif purple_media_set_output_window(priv->media, data->session_id, data->participant, window_id); } g_free(data->session_id); g_free(data->participant); g_free(data); return FALSE; } static void realize_cb(GtkWidget *widget, PidginMediaRealizeData *data) { g_timeout_add(0, (GSourceFunc)realize_cb_cb, data); } static void pidgin_media_error_cb(PidginMedia *media, const char *error, PidginMedia *gtkmedia) { PurpleAccount *account = purple_media_get_account( gtkmedia->priv->media); PurpleConversation *conv = purple_conversations_find_with_account( gtkmedia->priv->screenname, account); if (conv != NULL) { purple_conversation_write_system_message( conv, error, PURPLE_MESSAGE_ERROR); } else { purple_notify_error(NULL, NULL, _("Media error"), error, purple_request_cpar_from_conversation(conv)); } gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar), 0, error); g_object_unref(account); } static void pidgin_media_accept_cb(PurpleMedia *media, int index) { purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, NULL, NULL, TRUE); } static void pidgin_media_reject_cb(PurpleMedia *media, int index) { GList *iter = purple_media_get_session_ids(media); for (; iter; iter = g_list_delete_link(iter, iter)) { const gchar *sessionid = iter->data; if (!purple_media_accepted(media, sessionid, NULL)) purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT, sessionid, NULL, TRUE); } } static gboolean pidgin_request_timeout_cb(PidginMedia *gtkmedia) { PurpleAccount *account; PurpleBuddy *buddy; const gchar *alias; PurpleMediaSessionType type; gchar *message = NULL; account = purple_media_get_account(gtkmedia->priv->media); buddy = purple_blist_find_buddy(account, gtkmedia->priv->screenname); alias = buddy ? purple_buddy_get_contact_alias(buddy) : gtkmedia->priv->screenname; type = gtkmedia->priv->request_type; gtkmedia->priv->timeout_id = 0; if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) { message = g_strdup_printf(_("%s wishes to start an audio/video session with you."), alias); } else if (type & PURPLE_MEDIA_AUDIO) { message = g_strdup_printf(_("%s wishes to start an audio session with you."), alias); } else if (type & PURPLE_MEDIA_VIDEO) { message = g_strdup_printf(_("%s wishes to start a video session with you."), alias); } gtkmedia->priv->request_type = PURPLE_MEDIA_NONE; if (!purple_media_accepted(gtkmedia->priv->media, NULL, NULL)) { purple_request_accept_cancel(gtkmedia, _("Incoming Call"), message, NULL, PURPLE_DEFAULT_ACTION_NONE, purple_request_cpar_from_account(account), gtkmedia->priv->media, pidgin_media_accept_cb, pidgin_media_reject_cb); } pidgin_media_emit_message(gtkmedia, message); g_free(message); g_object_unref(account); return FALSE; } static void pidgin_media_input_volume_changed(GtkScaleButton *range, double value, PurpleMedia *media) { double val = (double)value * 100.0; purple_media_set_input_volume(media, NULL, val); } static void pidgin_media_output_volume_changed(GtkScaleButton *range, double value, PurpleMedia *media) { double val = (double)value * 100.0; purple_media_set_output_volume(media, NULL, NULL, val); } static void destroy_parent_widget_cb(GtkWidget *widget, GtkWidget *parent) { g_return_if_fail(GTK_IS_WIDGET(parent)); gtk_widget_destroy(parent); } static GtkWidget * pidgin_media_add_audio_widget(PidginMedia *gtkmedia, PurpleMediaSessionType type, const gchar *sid) { GtkWidget *volume_widget, *progress_parent, *volume, *progress; double value; static const gchar * input_volume_icons[] = { "microphone-sensitivity-muted-symbolic", "microphone-sensitivity-high-symbolic", "microphone-sensitivity-low-symbolic", "microphone-sensitivity-medium-symbolic", NULL }; if (type & PURPLE_MEDIA_SEND_AUDIO) { value = purple_prefs_get_int( "/purple/media/audio/volume/input"); } else if (type & PURPLE_MEDIA_RECV_AUDIO) { value = purple_prefs_get_int( "/purple/media/audio/volume/output"); } else g_return_val_if_reached(NULL); /* Setup widget structure */ volume_widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE); progress_parent = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_box_pack_start(GTK_BOX(volume_widget), progress_parent, TRUE, TRUE, 0); /* Volume button */ volume = gtk_volume_button_new(); gtk_scale_button_set_value(GTK_SCALE_BUTTON(volume), value/100.0); gtk_box_pack_end(GTK_BOX(volume_widget), volume, FALSE, FALSE, 0); /* Volume level indicator */ progress = gtk_progress_bar_new(); gtk_widget_set_size_request(progress, 250, 10); gtk_box_pack_end(GTK_BOX(progress_parent), progress, TRUE, FALSE, 0); if (type & PURPLE_MEDIA_SEND_AUDIO) { g_signal_connect (G_OBJECT(volume), "value-changed", G_CALLBACK(pidgin_media_input_volume_changed), gtkmedia->priv->media); gtk_scale_button_set_icons(GTK_SCALE_BUTTON(volume), input_volume_icons); gtkmedia->priv->send_progress = progress; } else if (type & PURPLE_MEDIA_RECV_AUDIO) { g_signal_connect (G_OBJECT(volume), "value-changed", G_CALLBACK(pidgin_media_output_volume_changed), gtkmedia->priv->media); pidgin_media_insert_widget(gtkmedia, progress, sid, gtkmedia->priv->screenname); } g_signal_connect(G_OBJECT(progress), "destroy", G_CALLBACK(destroy_parent_widget_cb), volume_widget); gtk_widget_show_all(volume_widget); return volume_widget; } static void phone_dtmf_pressed_cb(GtkButton *button, gpointer user_data) { PidginMedia *gtkmedia = user_data; gint num; gchar *sid; num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "dtmf-digit")); sid = g_object_get_data(G_OBJECT(button), "session-id"); purple_media_send_dtmf(gtkmedia->priv->media, sid, num, 25, 50); } static inline GtkWidget * phone_create_button(const gchar *text_hi, const gchar *text_lo) { GtkWidget *button; GtkWidget *label_hi; GtkWidget *label_lo; GtkWidget *grid; const gchar *text_hi_local; if (text_hi) text_hi_local = _(text_hi); else text_hi_local = ""; grid = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_box_set_homogeneous(GTK_BOX(grid), TRUE); button = gtk_button_new(); label_hi = gtk_label_new(text_hi_local); gtk_box_pack_end(GTK_BOX(grid), label_hi, FALSE, TRUE, 0); label_lo = gtk_label_new(text_lo); gtk_label_set_use_markup(GTK_LABEL(label_lo), TRUE); gtk_box_pack_end(GTK_BOX(grid), label_lo, FALSE, TRUE, 0); gtk_container_add(GTK_CONTAINER(button), grid); return button; } static struct phone_label { gchar *subtext; gchar *text; gchar chr; } phone_labels[] = { {"<b>1</b>", NULL, '1'}, /* Translators note: These are the letters on the keys of a numeric keypad; translate according to the tables in ยง7 of ETSI ES 202 130: http://webapp.etsi.org/WorkProgram/Report_WorkItem.asp?WKI_ID=11730 */ /* Letters on the '2' key of a numeric keypad */ {"<b>2</b>", N_("ABC"), '2'}, /* Letters on the '3' key of a numeric keypad */ {"<b>3</b>", N_("DEF"), '3'}, /* Letters on the '4' key of a numeric keypad */ {"<b>4</b>", N_("GHI"), '4'}, /* Letters on the '5' key of a numeric keypad */ {"<b>5</b>", N_("JKL"), '5'}, /* Letters on the '6' key of a numeric keypad */ {"<b>6</b>", N_("MNO"), '6'}, /* Letters on the '7' key of a numeric keypad */ {"<b>7</b>", N_("PQRS"), '7'}, /* Letters on the '8' key of a numeric keypad */ {"<b>8</b>", N_("TUV"), '8'}, /* Letters on the '9' key of a numeric keypad */ {"<b>9</b>", N_("WXYZ"), '9'}, {"<b>*</b>", NULL, '*'}, {"<b>0</b>", NULL, '0'}, {"<b>#</b>", NULL, '#'}, {NULL, NULL, 0} }; static gboolean pidgin_media_dtmf_key_press_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) { PidginMedia *gtkmedia = user_data; GdkEventKey *key = (GdkEventKey *) event; if (event->type != GDK_KEY_PRESS) { return FALSE; } if ((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) || key->keyval == GDK_KEY_asterisk || key->keyval == GDK_KEY_numbersign) { gchar *sid = g_object_get_data(G_OBJECT(widget), "session-id"); purple_media_send_dtmf(gtkmedia->priv->media, sid, key->keyval, 25, 50); } return FALSE; } static GtkWidget * pidgin_media_add_dtmf_widget(PidginMedia *gtkmedia, PurpleMediaSessionType type, const gchar *_sid) { GtkWidget *grid = gtk_grid_new(); GtkWidget *button; gint index = 0; GtkApplicationWindow *win = >kmedia->parent; gtk_grid_set_row_homogeneous(GTK_GRID(grid), TRUE); gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); /* Add buttons */ for (index = 0; phone_labels[index].subtext != NULL; index++) { button = phone_create_button(phone_labels[index].text, phone_labels[index].subtext); g_signal_connect(button, "pressed", G_CALLBACK(phone_dtmf_pressed_cb), gtkmedia); g_object_set_data(G_OBJECT(button), "dtmf-digit", GINT_TO_POINTER(phone_labels[index].chr)); g_object_set_data_full(G_OBJECT(button), "session-id", g_strdup(_sid), g_free); gtk_grid_attach(GTK_GRID(grid), button, index % 3, index / 3, 1, 1); g_object_set(button, "expand", TRUE, "margin", 2, NULL); } g_signal_connect(G_OBJECT(win), "key-press-event", G_CALLBACK(pidgin_media_dtmf_key_press_event_cb), gtkmedia); g_object_set_data_full(G_OBJECT(win), "session-id", g_strdup(_sid), g_free); gtk_widget_show_all(grid); return grid; } static void pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *sid) { GtkWidget *send_widget = NULL, *recv_widget = NULL, *button_widget = NULL; PurpleMediaSessionType type = purple_media_get_session_type(media, sid); if (gtkmedia->priv->recv_widget == NULL && type & (PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_RECV_AUDIO)) { recv_widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), recv_widget, TRUE, TRUE, 0); gtk_widget_show(recv_widget); } else { recv_widget = gtkmedia->priv->recv_widget; } if (gtkmedia->priv->send_widget == NULL && type & (PURPLE_MEDIA_SEND_VIDEO | PURPLE_MEDIA_SEND_AUDIO)) { send_widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), send_widget, FALSE, TRUE, 0); button_widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_end(GTK_BOX(recv_widget), button_widget, FALSE, TRUE, 0); gtk_widget_show(send_widget); /* Hold button */ gtkmedia->priv->hold = gtk_toggle_button_new_with_mnemonic(_("_Hold")); gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold, FALSE, FALSE, 0); gtk_widget_show(gtkmedia->priv->hold); gtk_actionable_set_action_name( GTK_ACTIONABLE(gtkmedia->priv->hold), "win.Hold"); } else { send_widget = gtkmedia->priv->send_widget; button_widget = gtkmedia->priv->button_widget; } if (type & PURPLE_MEDIA_RECV_VIDEO) { PidginMediaRealizeData *data; GtkWidget *aspect; GtkWidget *remote_video; aspect = gtk_aspect_frame_new(NULL, 0, 0, 4.0/3.0, FALSE); gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN); gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0); data = g_new0(PidginMediaRealizeData, 1); data->gtkmedia = gtkmedia; data->session_id = g_strdup(sid); data->participant = g_strdup(gtkmedia->priv->screenname); remote_video = pidgin_create_video_widget(); g_signal_connect(G_OBJECT(remote_video), "realize", G_CALLBACK(realize_cb), data); gtk_container_add(GTK_CONTAINER(aspect), remote_video); gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240); g_signal_connect(G_OBJECT(remote_video), "destroy", G_CALLBACK(destroy_parent_widget_cb), aspect); gtk_widget_show(remote_video); gtk_widget_show(aspect); pidgin_media_insert_widget(gtkmedia, remote_video, data->session_id, data->participant); } if (type & PURPLE_MEDIA_SEND_VIDEO && !gtkmedia->priv->local_video) { PidginMediaRealizeData *data; GtkWidget *aspect; GtkWidget *local_video; aspect = gtk_aspect_frame_new(NULL, 0, 0, 4.0/3.0, TRUE); gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN); gtk_box_pack_start(GTK_BOX(send_widget), aspect, FALSE, TRUE, 0); data = g_new0(PidginMediaRealizeData, 1); data->gtkmedia = gtkmedia; data->session_id = g_strdup(sid); data->participant = NULL; local_video = pidgin_create_video_widget(); g_signal_connect(G_OBJECT(local_video), "realize", G_CALLBACK(realize_cb), data); gtk_container_add(GTK_CONTAINER(aspect), local_video); gtk_widget_set_size_request (GTK_WIDGET(local_video), 80, 60); g_signal_connect(G_OBJECT(local_video), "destroy", G_CALLBACK(destroy_parent_widget_cb), aspect); gtk_widget_show(local_video); gtk_widget_show(aspect); gtkmedia->priv->pause = gtk_toggle_button_new_with_mnemonic(_("_Pause")); gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->pause, FALSE, FALSE, 0); gtk_widget_show(gtkmedia->priv->pause); gtk_actionable_set_action_name( GTK_ACTIONABLE(gtkmedia->priv->pause), "win.Pause"); gtkmedia->priv->local_video = local_video; } if (type & PURPLE_MEDIA_RECV_AUDIO) { gtk_box_pack_end(GTK_BOX(recv_widget), pidgin_media_add_audio_widget(gtkmedia, PURPLE_MEDIA_RECV_AUDIO, sid), FALSE, FALSE, 0); } if (type & PURPLE_MEDIA_SEND_AUDIO) { gtkmedia->priv->mute = gtk_toggle_button_new_with_mnemonic(_("_Mute")); gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->mute, FALSE, FALSE, 0); gtk_widget_show(gtkmedia->priv->mute); gtk_actionable_set_action_name( GTK_ACTIONABLE(gtkmedia->priv->mute), "win.Mute"); gtk_box_pack_end(GTK_BOX(recv_widget), pidgin_media_add_audio_widget(gtkmedia, PURPLE_MEDIA_SEND_AUDIO, sid), FALSE, FALSE, 0); gtk_box_pack_end(GTK_BOX(recv_widget), pidgin_media_add_dtmf_widget(gtkmedia, PURPLE_MEDIA_SEND_AUDIO, sid), FALSE, FALSE, 0); } if (type & PURPLE_MEDIA_AUDIO && gtkmedia->priv->level_handler_id == 0) { gtkmedia->priv->level_handler_id = g_signal_connect( media, "level", G_CALLBACK(level_message_cb), gtkmedia); } if (send_widget != NULL) gtkmedia->priv->send_widget = send_widget; if (recv_widget != NULL) gtkmedia->priv->recv_widget = recv_widget; if (button_widget != NULL) { gtkmedia->priv->button_widget = button_widget; gtk_widget_show(GTK_WIDGET(button_widget)); } if (purple_media_is_initiator(media, sid, NULL) == FALSE) { if (gtkmedia->priv->timeout_id != 0) g_source_remove(gtkmedia->priv->timeout_id); gtkmedia->priv->request_type |= type; gtkmedia->priv->timeout_id = g_timeout_add(500, (GSourceFunc)pidgin_request_timeout_cb, gtkmedia); } /* set the window icon according to the type */ if (type & PURPLE_MEDIA_VIDEO) { gtk_window_set_icon_name(GTK_WINDOW(gtkmedia), "video-call"); } else if (type & PURPLE_MEDIA_AUDIO) { gtk_window_set_icon_name(GTK_WINDOW(gtkmedia), "audio-call"); } gtk_widget_show(gtkmedia->priv->display); } static void pidgin_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, PidginMedia *gtkmedia) { purple_debug_info("gtkmedia", "state: %d sid: %s name: %s\n", state, sid ? sid : "(null)", name ? name : "(null)"); if (state == PURPLE_MEDIA_STATE_END) { if (sid != NULL && name != NULL) { pidgin_media_remove_widget(gtkmedia, sid, name); } else if (sid == NULL && name == NULL) { pidgin_media_emit_message(gtkmedia, _("The call has been terminated.")); gtk_widget_destroy(GTK_WIDGET(gtkmedia)); } } else if (state == PURPLE_MEDIA_STATE_NEW && sid != NULL && name != NULL) { pidgin_media_ready_cb(media, gtkmedia, sid); } } static void pidgin_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, gchar *sid, gchar *name, gboolean local, PidginMedia *gtkmedia) { if (type == PURPLE_MEDIA_INFO_REJECT) { pidgin_media_emit_message(gtkmedia, _("You have rejected the call.")); } else if (type == PURPLE_MEDIA_INFO_ACCEPT) { if (local == TRUE) purple_request_close_with_handle(gtkmedia); pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED); pidgin_media_emit_message(gtkmedia, _("Call in progress.")); gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar), 0, _("Call in progress")); gtk_widget_show(GTK_WIDGET(gtkmedia)); } else if (type == PURPLE_MEDIA_INFO_MUTE && !local) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkmedia->priv->mute), TRUE); } else if (type == PURPLE_MEDIA_INFO_UNMUTE && !local) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkmedia->priv->mute), FALSE); } } static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PidginMedia *media; g_return_if_fail(PIDGIN_IS_MEDIA(object)); media = PIDGIN_MEDIA(object); switch (prop_id) { case PROP_MEDIA: { if (media->priv->media) g_object_unref(media->priv->media); media->priv->media = g_value_dup_object(value); if (purple_media_is_initiator(media->priv->media, NULL, NULL) == TRUE) pidgin_media_set_state(media, PIDGIN_MEDIA_WAITING); else pidgin_media_set_state(media, PIDGIN_MEDIA_REQUESTED); g_signal_connect(G_OBJECT(media->priv->media), "error", G_CALLBACK(pidgin_media_error_cb), media); g_signal_connect(G_OBJECT(media->priv->media), "state-changed", G_CALLBACK(pidgin_media_state_changed_cb), media); g_signal_connect(G_OBJECT(media->priv->media), "stream-info", G_CALLBACK(pidgin_media_stream_info_cb), media); break; } case PROP_SCREENNAME: g_free(media->priv->screenname); media->priv->screenname = g_value_dup_string(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PidginMedia *media; g_return_if_fail(PIDGIN_IS_MEDIA(object)); media = PIDGIN_MEDIA(object); switch (prop_id) { case PROP_MEDIA: g_value_set_object(value, media->priv->media); break; case PROP_SCREENNAME: g_value_set_string(value, media->priv->screenname); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GtkWidget * pidgin_media_new(PurpleMedia *media, const gchar *screenname) { PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(), "media", media, "screenname", screenname, NULL); return GTK_WIDGET(gtkmedia); } static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state) { gtkmedia->priv->state = state; } static gboolean pidgin_media_new_cb(PurpleMediaManager *manager, PurpleMedia *media, PurpleAccount *account, gchar *screenname, gpointer nul) { PidginMedia *gtkmedia = PIDGIN_MEDIA( pidgin_media_new(media, screenname)); PurpleBuddy *buddy = purple_blist_find_buddy(account, screenname); const gchar *alias = buddy ? purple_buddy_get_contact_alias(buddy) : screenname; gtk_window_set_title(GTK_WINDOW(gtkmedia), alias); if (purple_media_is_initiator(media, NULL, NULL) == TRUE) gtk_widget_show(GTK_WIDGET(gtkmedia)); return TRUE; } #endif /* USE_VV */ void pidgin_medias_init(void) { #ifdef USE_VV PurpleMediaManager *manager = purple_media_manager_get(); PurpleMediaElementInfo *video_src = NULL; PurpleMediaElementInfo *video_sink = NULL; PurpleMediaElementInfo *audio_src = NULL; PurpleMediaElementInfo *audio_sink = NULL; const char *pref; pref = purple_prefs_get_string( PIDGIN_PREFS_ROOT "/vvconfig/video/src/device"); if (pref) video_src = purple_media_manager_get_element_info(manager, pref); if (!video_src) { pref = "autovideosrc"; purple_prefs_set_string( PIDGIN_PREFS_ROOT "/vvconfig/video/src/device", pref); video_src = purple_media_manager_get_element_info(manager, pref); } pref = purple_prefs_get_string( PIDGIN_PREFS_ROOT "/vvconfig/video/sink/device"); if (pref) video_sink = purple_media_manager_get_element_info(manager, pref); if (!video_sink) { pref = "autovideosink"; purple_prefs_set_string( PIDGIN_PREFS_ROOT "/vvconfig/video/sink/device", pref); video_sink = purple_media_manager_get_element_info(manager, pref); } pref = purple_prefs_get_string( PIDGIN_PREFS_ROOT "/vvconfig/audio/src/device"); if (pref) audio_src = purple_media_manager_get_element_info(manager, pref); if (!audio_src) { pref = "autoaudiosrc"; purple_prefs_set_string( PIDGIN_PREFS_ROOT "/vvconfig/audio/src/device", pref); audio_src = purple_media_manager_get_element_info(manager, pref); } pref = purple_prefs_get_string( PIDGIN_PREFS_ROOT "/vvconfig/audio/sink/device"); if (pref) audio_sink = purple_media_manager_get_element_info(manager, pref); if (!audio_sink) { pref = "autoaudiosink"; purple_prefs_set_string( PIDGIN_PREFS_ROOT "/vvconfig/audio/sink/device", pref); audio_sink = purple_media_manager_get_element_info(manager, pref); } g_signal_connect(G_OBJECT(manager), "init-media", G_CALLBACK(pidgin_media_new_cb), NULL); purple_media_manager_set_ui_caps(manager, PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION | PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION | PURPLE_MEDIA_CAPS_AUDIO_VIDEO); purple_debug_info("gtkmedia", "Registering media element types\n"); purple_media_manager_set_active_element(manager, video_src); purple_media_manager_set_active_element(manager, video_sink); purple_media_manager_set_active_element(manager, audio_src); purple_media_manager_set_active_element(manager, audio_sink); #endif }