diff options
Diffstat (limited to 'src')
30 files changed, 3284 insertions, 1213 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index bcf17de31..0232ffe0a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,6 +157,8 @@ empathy_call_SOURCES = \ empathy-call-window.h \ empathy-call-window-fullscreen.c \ empathy-call-window-fullscreen.h \ + empathy-about-dialog.c \ + empathy-about-dialog.h \ empathy-audio-sink.c \ empathy-audio-sink.h \ empathy-audio-src.c \ @@ -165,8 +167,16 @@ empathy_call_SOURCES = \ empathy-video-src.h \ empathy-video-widget.c \ empathy-video-widget.h \ + empathy-preferences.c \ + empathy-preferences.h \ ev-sidebar.c \ - ev-sidebar.h + ev-sidebar.h \ + empathy-camera-menu.c \ + empathy-camera-menu.h \ + empathy-mic-menu.c \ + empathy-mic-menu.h \ + empathy-rounded-actor.c \ + empathy-rounded-actor.h nodist_empathy_call_SOURCES = $(BUILT_SOURCES) @@ -259,10 +269,10 @@ dist_man_MANS = \ empathy.1 \ empathy-accounts.1 -src-marshal.list: $(empathy_SOURCES) Makefile.am +src-marshal.list: $(empathy_SOURCES) $(empathy_call_SOURCES) Makefile.am $(AM_V_GEN)( cd $(srcdir) && \ sed -n -e 's/.*src_marshal_\([[:upper:][:digit:]]*__[[:upper:][:digit:]_]*\).*/\1/p' \ - $(empathy_SOURCES) $(empathy_av_SOURCES) ) \ + $(empathy_SOURCES) $(empathy_av_SOURCES) $(empathy_call_SOURCES) ) \ | sed -e 's/__/:/' -e 'y/_/,/' | sort -u > $@.tmp @if cmp -s $@.tmp $@; then \ rm $@.tmp; \ diff --git a/src/empathy-accounts-dialog.c b/src/empathy-accounts-dialog.c index 75b74812b..0eeec826c 100644 --- a/src/empathy-accounts-dialog.c +++ b/src/empathy-accounts-dialog.c @@ -2517,68 +2517,30 @@ empathy_accounts_dialog_show_application (GdkScreen *screen, gboolean if_needed, gboolean hidden) { - GError *error = NULL; - GdkDisplay *display; - GString *cmd; - gchar *path; - GAppInfo *app_info; - GdkAppLaunchContext *context = NULL; + GString *args; - g_return_if_fail (GDK_IS_SCREEN (screen)); g_return_if_fail (!selected_account || TP_IS_ACCOUNT (selected_account)); - /* Try to run from source directory if possible */ - path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src", - "empathy-accounts", NULL); - - if (!g_file_test (path, G_FILE_TEST_EXISTS)) - { - g_free (path); - path = g_build_filename (BIN_DIR, "empathy-accounts", NULL); - } - - cmd = g_string_new (path); - g_free (path); + args = g_string_new (NULL); if (selected_account != NULL) - { - g_string_append_printf (cmd, " --select-account=%s", - tp_account_get_path_suffix (selected_account)); - } + g_string_append_printf (args, " --select-account=%s", + tp_account_get_path_suffix (selected_account)); if (if_needed) - g_string_append_printf (cmd, " --if-needed"); + g_string_append_printf (args, " --if-needed"); if (hidden) - g_string_append_printf (cmd, " --hidden"); + g_string_append_printf (args, " --hidden"); DEBUG ("Launching empathy-accounts (if_needed: %d, hidden: %d, account: %s)", if_needed, hidden, selected_account == NULL ? "<none selected>" : tp_proxy_get_object_path (TP_PROXY (selected_account))); - app_info = g_app_info_create_from_commandline (cmd->str, NULL, 0, &error); - if (app_info == NULL) - { - DEBUG ("Failed to create app info: %s", error->message); - g_error_free (error); - goto out; - } - - display = gdk_screen_get_display (screen); - context = gdk_display_get_app_launch_context (display); - - if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context, - &error)) - { - g_warning ("Failed to open accounts dialog: %s", error->message); - g_error_free (error); - } + empathy_launch_program (BIN_DIR, "empathy-accounts", args->str); -out: - tp_clear_object (&app_info); - tp_clear_object (&context); - g_string_free (cmd, TRUE); + g_string_free (args, TRUE); } gboolean diff --git a/src/empathy-audio-sink.c b/src/empathy-audio-sink.c index c410d7a30..3d4496cd1 100644 --- a/src/empathy-audio-sink.c +++ b/src/empathy-audio-sink.c @@ -23,10 +23,16 @@ #include <stdlib.h> #include <gst/audio/audio.h> -#include <gst/farsight/fs-element-added-notifier.h> +#include <gst/interfaces/streamvolume.h> + +#include <telepathy-glib/telepathy-glib.h> + +#include <libempathy-gtk/empathy-call-utils.h> #include "empathy-audio-sink.h" +#define DEBUG_FLAG EMPATHY_DEBUG_VOIP +#include <libempathy/empathy-debug.h> G_DEFINE_TYPE(EmpathyGstAudioSink, empathy_audio_sink, GST_TYPE_BIN) @@ -39,36 +45,6 @@ enum static guint signals[LAST_SIGNAL] = {0}; #endif -typedef struct { - GstPad *pad; - GstElement *bin; - GstElement *volume; - GstElement *sink; -} AudioBin; - -static AudioBin * -audio_bin_new (GstPad *pad, - GstElement *bin, - GstElement *volume, - GstElement *sink) -{ - AudioBin *result = g_slice_new0 (AudioBin); - - result->pad = pad; - result->bin = bin; - result->volume = gst_object_ref (volume); - result->sink = sink; - - return result; -} - -static void -audio_bin_free (AudioBin *bin) -{ - gst_object_unref (bin->volume); - g_slice_free (AudioBin, bin); -} - static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE( "sink%d", @@ -84,16 +60,7 @@ enum { struct _EmpathyGstAudioSinkPrivate { - gboolean dispose_has_run; - FsElementAddedNotifier *notifier; - - gdouble volume; - - /* Pad -> *owned* subbin hash */ - GHashTable *audio_bins; - - /* Mutex to hold while change the hash table */ - GMutex *audio_bins_lock; + GstElement *sink; }; #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \ @@ -101,72 +68,11 @@ struct _EmpathyGstAudioSinkPrivate EmpathyGstAudioSinkPrivate)) static void -empathy_audio_sink_element_added_cb (FsElementAddedNotifier *notifier, - GstBin *bin, GstElement *element, EmpathyGstAudioSink *self) -{ - EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); - - if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume")) - { - /* An element was added with a volume property, lets find its subbin and - * update the volume in it */ - GHashTableIter iter; - AudioBin *audio_bin = NULL; - gpointer value; - - g_mutex_lock (self->priv->audio_bins_lock); - g_hash_table_iter_init (&iter, priv->audio_bins); - - while (g_hash_table_iter_next (&iter, NULL, &value)) - { - AudioBin *b = value; - - if (gst_object_has_ancestor (GST_OBJECT (element), - GST_OBJECT (b->bin))) - { - audio_bin = b; - break; - } - } - - if (audio_bin == NULL) - { - g_warning ("Element added that doesn't belong to us ?"); - return; - } - - /* Set the old volume to 1 and the new volume to the volume */ - g_object_set (audio_bin->volume, "volume", 1.0, NULL); - gst_object_unref (audio_bin->volume); - - audio_bin->volume = gst_object_ref (element); - g_object_set (audio_bin->volume, "volume", self->priv->volume, NULL); - g_mutex_unlock (self->priv->audio_bins_lock); - } -} - -static void empathy_audio_sink_init (EmpathyGstAudioSink *self) { - EmpathyGstAudioSinkPrivate *priv; - - priv = self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); - - priv->volume = 1.0; - - priv->audio_bins = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, (GDestroyNotify) audio_bin_free); - - priv->audio_bins_lock = g_mutex_new (); - - priv->notifier = fs_element_added_notifier_new (); - g_signal_connect (priv->notifier, "element-added", - G_CALLBACK (empathy_audio_sink_element_added_cb), self); + self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); } -static void empathy_audio_sink_dispose (GObject *object); -static void empathy_audio_sink_finalize (GObject *object); - static GstPad * empathy_audio_sink_request_new_pad (GstElement *self, GstPadTemplate *templ, const gchar* name); @@ -219,9 +125,6 @@ empathy_audio_sink_class_init (EmpathyGstAudioSinkClass g_type_class_add_private (empathy_audio_sink_class, sizeof (EmpathyGstAudioSinkPrivate)); - object_class->dispose = empathy_audio_sink_dispose; - object_class->finalize = empathy_audio_sink_finalize; - object_class->set_property = empathy_audio_sink_set_property; object_class->get_property = empathy_audio_sink_get_property; @@ -234,45 +137,6 @@ empathy_audio_sink_class_init (EmpathyGstAudioSinkClass g_object_class_install_property (object_class, PROP_VOLUME, param_spec); } -void -empathy_audio_sink_dispose (GObject *object) -{ - EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object); - EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); - - if (priv->dispose_has_run) - return; - - priv->dispose_has_run = TRUE; - - if (priv->notifier != NULL) - g_object_unref (priv->notifier); - priv->notifier = NULL; - - if (priv->audio_bins != NULL) - g_hash_table_unref (priv->audio_bins); - priv->audio_bins = NULL; - - if (priv->audio_bins_lock != NULL) - g_mutex_free (priv->audio_bins_lock); - priv->audio_bins_lock = NULL; - - if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose) - G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object); -} - -void -empathy_audio_sink_finalize (GObject *object) -{ - //EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object); - //EmpathyGstAudioSinkPrivate *priv = - // EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); - - /* free any data held directly by the object here */ - - G_OBJECT_CLASS (empathy_audio_sink_parent_class)->finalize (object); -} - GstElement * empathy_audio_sink_new (void) { @@ -287,32 +151,75 @@ empathy_audio_sink_new (void) return gst_element_factory_make ("empathyaudiosink", NULL); } +static gboolean +check_volume_support (EmpathyGstAudioSink *self) +{ + gchar *name; + + if (GST_IS_STREAM_VOLUME (self->priv->sink)) + return TRUE; + + name = gst_element_get_name (self->priv->sink); + DEBUG ("Element %s doesn't support volume", name); + + g_free (name); + return FALSE; +} + void empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume) { EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink); - GHashTableIter iter; - gpointer value; - - priv->volume = volume; - - g_mutex_lock (priv->audio_bins_lock); - g_hash_table_iter_init (&iter, priv->audio_bins); - while (g_hash_table_iter_next (&iter, NULL, &value)) - { - AudioBin *b = value; - g_object_set (b->volume, "volume", volume, NULL); - } + if (!check_volume_support (sink)) + return; - g_mutex_unlock (priv->audio_bins_lock); + g_object_set (priv->sink, "volume", volume, NULL); } gdouble empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink) { EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink); - return priv->volume; + gdouble volume; + + if (!check_volume_support (sink)) + return 1.0; + + g_object_get (priv->sink, "volume", &volume, NULL); + return volume; +} + +static GstElement * +create_sink (void) +{ + GstElement *sink; + const gchar *description; + + description = g_getenv ("EMPATHY_AUDIO_SINK"); + + if (description != NULL) + { + GError *error = NULL; + + sink = gst_parse_bin_from_description (description, TRUE, &error); + if (sink == NULL) + { + DEBUG ("Failed to create bin %s: %s", description, error->message); + g_error_free (error); + } + + return sink; + } + + /* Use pulsesink as default */ + sink = gst_element_factory_make ("pulsesink", NULL); + if (sink == NULL) + return NULL; + + empathy_call_set_stream_properties (sink); + + return sink; } static GstPad * @@ -321,10 +228,9 @@ empathy_audio_sink_request_new_pad (GstElement *element, const gchar* name) { EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element); - GstElement *bin, *sink, *volume, *resample, *audioconvert0, *audioconvert1; + GstElement *bin, *volume, *resample, *audioconvert0, *audioconvert1; GstPad *pad = NULL; GstPad *subpad, *filterpad; - AudioBin *audiobin; bin = gst_bin_new (NULL); @@ -352,15 +258,14 @@ empathy_audio_sink_request_new_pad (GstElement *element, gst_bin_add (GST_BIN (bin), volume); - sink = gst_element_factory_make ("gconfaudiosink", NULL); - if (sink == NULL) + self->priv->sink = create_sink (); + if (self->priv->sink == NULL) goto error; - gst_bin_add (GST_BIN (bin), sink); - fs_element_added_notifier_add (self->priv->notifier, GST_BIN (sink)); + gst_bin_add (GST_BIN (bin), self->priv->sink); if (!gst_element_link_many (audioconvert0, resample, audioconvert1, - volume, sink, NULL)) + volume, self->priv->sink, NULL)) goto error; filterpad = gst_element_get_static_pad (audioconvert0, "sink"); @@ -372,25 +277,11 @@ empathy_audio_sink_request_new_pad (GstElement *element, if (!gst_element_add_pad (GST_ELEMENT (bin), subpad)) goto error; - - /* Ensure that state changes only happen _after_ the element has been added - * to the hash table. But add it to the bin first so we can create our - * ghostpad (if we create the ghostpad before adding it to the bin it will - * get unlinked) */ - gst_element_set_locked_state (GST_ELEMENT (bin), TRUE); gst_bin_add (GST_BIN (self), bin); pad = gst_ghost_pad_new (name, subpad); g_assert (pad != NULL); - audiobin = audio_bin_new (pad, bin, volume, sink); - - g_mutex_lock (self->priv->audio_bins_lock); - g_hash_table_insert (self->priv->audio_bins, pad, audiobin); - g_mutex_unlock (self->priv->audio_bins_lock); - - gst_element_set_locked_state (GST_ELEMENT (bin), FALSE); - if (!gst_element_sync_state_with_parent (bin)) goto error; @@ -400,16 +291,11 @@ empathy_audio_sink_request_new_pad (GstElement *element, if (!gst_element_add_pad (GST_ELEMENT (self), pad)) goto error; - return pad; error: if (pad != NULL) { - g_mutex_lock (self->priv->audio_bins_lock); - g_hash_table_remove (self->priv->audio_bins, pad); - g_mutex_unlock (self->priv->audio_bins_lock); - gst_object_unref (pad); } @@ -422,26 +308,6 @@ static void empathy_audio_sink_release_pad (GstElement *element, GstPad *pad) { - EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element); - AudioBin *abin; - - g_mutex_lock (self->priv->audio_bins_lock); - abin = g_hash_table_lookup (self->priv->audio_bins, pad); - g_hash_table_steal (self->priv->audio_bins, pad); - g_mutex_unlock (self->priv->audio_bins_lock); - - if (abin == NULL) - { - g_warning ("Releasing a pad that doesn't belong to us ?"); - return; - } - gst_pad_set_active (pad, FALSE); gst_element_remove_pad (element, pad); - - gst_element_set_locked_state (abin->bin, TRUE); - gst_element_set_state (abin->bin, GST_STATE_NULL); - gst_bin_remove (GST_BIN (self), abin->bin); - - audio_bin_free (abin); } diff --git a/src/empathy-audio-src.c b/src/empathy-audio-src.c index a3416f2ea..8f7a0599f 100644 --- a/src/empathy-audio-src.c +++ b/src/empathy-audio-src.c @@ -22,9 +22,19 @@ #include <stdio.h> #include <stdlib.h> -#include <gst/farsight/fs-element-added-notifier.h> +#include <pulse/pulseaudio.h> +#include <pulse/glib-mainloop.h> + +#include <libempathy/empathy-utils.h> +#include <libempathy-gtk/empathy-call-utils.h> + #include "empathy-audio-src.h" +#include "src-marshal.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_VOIP +#include <libempathy/empathy-debug.h> + G_DEFINE_TYPE(EmpathyGstAudioSrc, empathy_audio_src, GST_TYPE_BIN) /* signal enum */ @@ -32,6 +42,8 @@ enum { PEAK_LEVEL_CHANGED, RMS_LEVEL_CHANGED, + MICROPHONE_ADDED, + MICROPHONE_REMOVED, LAST_SIGNAL }; @@ -41,6 +53,7 @@ enum { PROP_VOLUME = 1, PROP_RMS_LEVEL, PROP_PEAK_LEVEL, + PROP_MICROPHONE, }; /* private structure */ @@ -52,7 +65,15 @@ struct _EmpathyGstAudioSrcPrivate GstElement *src; GstElement *volume; GstElement *level; - FsElementAddedNotifier *notifier; + + pa_glib_mainloop *loop; + pa_context *context; + GQueue *operations; + + /* 0 if not known yet */ + guint source_output_idx; + /* G_MAXUINT if not known yet */ + guint source_idx; gdouble peak_level; gdouble rms_level; @@ -65,29 +86,331 @@ struct _EmpathyGstAudioSrcPrivate (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \ EmpathyGstAudioSrcPrivate)) +static gboolean +empathy_audio_src_supports_changing_mic (EmpathyGstAudioSrc *self) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + GObjectClass *object_class; + + object_class = G_OBJECT_GET_CLASS (priv->src); + + return (g_object_class_find_property (object_class, + "source-output-index") != NULL); +} + +typedef void (*OperationFunc) (EmpathyGstAudioSrc *, GSimpleAsyncResult *); + +typedef struct +{ + OperationFunc func; + GSimpleAsyncResult *result; +} Operation; + +static Operation * +operation_new (OperationFunc func, + GSimpleAsyncResult *result) +{ + Operation *o = g_slice_new0 (Operation); + + o->func = func; + o->result = result; + + return o; +} + +static void +operation_free (Operation *o, + gboolean cancelled) +{ + if (cancelled) + { + g_simple_async_result_set_error (o->result, + G_IO_ERROR, G_IO_ERROR_CANCELLED, + "The audio source was disposed"); + g_simple_async_result_complete (o->result); + g_object_unref (o->result); + } + + g_slice_free (Operation, o); +} + +static void +operation_get_microphones_free (gpointer data) +{ + GQueue *queue = data; + GList *l; + + for (l = queue->head; l != NULL; l = l->next) + { + EmpathyAudioSrcMicrophone *mic = l->data; + + g_free (mic->name); + g_free (mic->description); + g_slice_free (EmpathyAudioSrcMicrophone, mic); + } + + g_queue_free (queue); +} + +static void +operation_get_microphones_cb (pa_context *context, + const pa_source_info *info, + int eol, + void *userdata) +{ + GSimpleAsyncResult *result = userdata; + EmpathyAudioSrcMicrophone *mic; + GQueue *queue; + + if (eol) + { + g_simple_async_result_complete (result); + g_object_unref (result); + return; + } + + mic = g_slice_new0 (EmpathyAudioSrcMicrophone); + mic->index = info->index; + mic->name = g_strdup (info->name); + mic->description = g_strdup (info->description); + mic->is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); + + /* add it to the queue */ + queue = g_simple_async_result_get_op_res_gpointer (result); + g_queue_push_tail (queue, mic); +} + +static void +operation_get_microphones (EmpathyGstAudioSrc *self, + GSimpleAsyncResult *result) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + + g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); + + g_simple_async_result_set_op_res_gpointer (result, g_queue_new (), + operation_get_microphones_free); + + pa_context_get_source_info_list (priv->context, + operation_get_microphones_cb, result); +} + +static void +operation_change_microphone_cb (pa_context *context, + int success, + void *userdata) +{ + GSimpleAsyncResult *result = userdata; + + if (!success) + { + g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to change microphone. Reason unknown."); + } + + g_simple_async_result_complete (result); + g_object_unref (result); +} + +static void +operation_change_microphone (EmpathyGstAudioSrc *self, + GSimpleAsyncResult *result) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + guint source_output_idx, microphone; + + g_object_get (priv->src, "source-output-index", &source_output_idx, NULL); + + g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); + g_assert_cmpuint (source_output_idx, !=, PA_INVALID_INDEX); + + microphone = GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer (result)); + + pa_context_move_source_output_by_index (priv->context, source_output_idx, microphone, + operation_change_microphone_cb, result); +} + +static void +operations_run (EmpathyGstAudioSrc *self) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + pa_context_state_t state = pa_context_get_state (priv->context); + GList *l; + + if (state != PA_CONTEXT_READY) + return; + + for (l = priv->operations->head; l != NULL; l = l->next) + { + Operation *o = l->data; + + o->func (self, o->result); + + operation_free (o, FALSE); + } + + g_queue_clear (priv->operations); +} + +static void +empathy_audio_src_source_output_info_cb (pa_context *context, + const pa_source_output_info *info, + int eol, + void *userdata) +{ + EmpathyGstAudioSrc *self = userdata; + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + + if (eol) + return; + + /* There should only be one call here. */ + + if (priv->source_idx == info->source) + return; + + priv->source_idx = info->source; + g_object_notify (G_OBJECT (self), "microphone"); +} + +static void +empathy_audio_src_source_info_cb (pa_context *context, + const pa_source_info *info, + int eol, + void *userdata) +{ + EmpathyGstAudioSrc *self = userdata; + gboolean is_monitor; + + if (eol) + return; + + is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); + + g_signal_emit (self, signals[MICROPHONE_ADDED], 0, + info->index, info->name, info->description, is_monitor); +} + static void -empathy_audio_src_element_added_cb (FsElementAddedNotifier *notifier, - GstBin *bin, GstElement *element, EmpathyGstAudioSrc *self) +empathy_audio_src_pa_event_cb (pa_context *context, + pa_subscription_event_type_t type, + uint32_t idx, + void *userdata) { + EmpathyGstAudioSrc *self = userdata; EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); - if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume")) + if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT + && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE + && idx == priv->source_output_idx) + { + /* Microphone in the source output has changed */ + pa_context_get_source_output_info (context, idx, + empathy_audio_src_source_output_info_cb, self); + } + else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE + && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - gdouble volume; + /* A mic has been removed */ + g_signal_emit (self, signals[MICROPHONE_REMOVED], 0, idx); + } + else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE + && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + /* A mic has been plugged in */ + pa_context_get_source_info_by_index (context, idx, + empathy_audio_src_source_info_cb, self); + } +} - volume = empathy_audio_src_get_volume (self); - empathy_audio_src_set_volume (self, 1.0); +static void +empathy_audio_src_pa_subscribe_cb (pa_context *context, + int success, + void *userdata) +{ + if (!success) + DEBUG ("Failed to subscribe to PulseAudio events"); +} - if (priv->volume != NULL) - g_object_unref (priv->volume); - priv->volume = g_object_ref (element); +static void +empathy_audio_src_pa_state_change_cb (pa_context *context, + void *userdata) +{ + EmpathyGstAudioSrc *self = userdata; + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + pa_context_state_t state = pa_context_get_state (priv->context); - if (volume != 1.0) - empathy_audio_src_set_volume (self, volume); + if (state == PA_CONTEXT_READY) + { + /* Listen to pulseaudio events so we know when sources are + * added and when the microphone is changed. */ + pa_context_set_subscribe_callback (priv->context, + empathy_audio_src_pa_event_cb, self); + pa_context_subscribe (priv->context, + PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, + empathy_audio_src_pa_subscribe_cb, NULL); + + operations_run (self); } } static void +empathy_audio_src_source_output_index_notify (GObject *object, + GParamSpec *pspec, + EmpathyGstAudioSrc *self) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + guint source_output_idx = PA_INVALID_INDEX; + + g_object_get (priv->src, "source-output-index", &source_output_idx, NULL); + + if (source_output_idx == PA_INVALID_INDEX) + return; + + if (priv->source_output_idx == source_output_idx) + return; + + /* It's actually changed. */ + priv->source_output_idx = source_output_idx; + + pa_context_get_source_output_info (priv->context, source_output_idx, + empathy_audio_src_source_output_info_cb, self); +} + +static GstElement * +create_src (void) +{ + GstElement *src; + const gchar *description; + + description = g_getenv ("EMPATHY_AUDIO_SRC"); + + if (description != NULL) + { + GError *error = NULL; + + src = gst_parse_bin_from_description (description, TRUE, &error); + if (src == NULL) + { + DEBUG ("Failed to create bin %s: %s", description, error->message); + g_error_free (error); + } + + return src; + } + + /* Use pulsesrc as default */ + src = gst_element_factory_make ("pulsesrc", NULL); + if (src == NULL) + return NULL; + + empathy_call_set_stream_properties (src); + + return src; +} + +static void empathy_audio_src_init (EmpathyGstAudioSrc *obj) { EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj); @@ -96,15 +419,12 @@ empathy_audio_src_init (EmpathyGstAudioSrc *obj) priv->peak_level = -G_MAXDOUBLE; priv->lock = g_mutex_new (); - priv->notifier = fs_element_added_notifier_new (); - g_signal_connect (priv->notifier, "element-added", - G_CALLBACK (empathy_audio_src_element_added_cb), obj); + priv->src = create_src (); + if (priv->src == NULL) + return; - priv->src = gst_element_factory_make ("gconfaudiosrc", NULL); gst_bin_add (GST_BIN (obj), priv->src); - fs_element_added_notifier_add (priv->notifier, GST_BIN (priv->src)); - priv->volume = gst_element_factory_make ("volume", NULL); g_object_ref (priv->volume); @@ -121,6 +441,27 @@ empathy_audio_src_init (EmpathyGstAudioSrc *obj) gst_element_add_pad (GST_ELEMENT (obj), ghost); gst_object_unref (G_OBJECT (src)); + + /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so + * Pulse can use the mainloop that GTK has created for us. */ + priv->loop = pa_glib_mainloop_new (NULL); + priv->context = pa_context_new (pa_glib_mainloop_get_api (priv->loop), + "EmpathyAudioSrc"); + + /* Listen to changes to GstPulseSrc:source-output-index so we know when + * it's no longer PA_INVALID_INDEX (starting for the first time) or if it + * changes (READY->NULL->READY...) */ + g_signal_connect (priv->src, "notify::source-output-index", + G_CALLBACK (empathy_audio_src_source_output_index_notify), + obj); + + /* Finally listen for state changes so we know when we've + * connected. */ + pa_context_set_state_callback (priv->context, + empathy_audio_src_pa_state_change_cb, obj); + pa_context_connect (priv->context, NULL, 0, NULL); + + priv->operations = g_queue_new (); } static void empathy_audio_src_dispose (GObject *object); @@ -168,6 +509,9 @@ empathy_audio_src_get_property (GObject *object, g_value_set_double (value, priv->rms_level); g_mutex_unlock (priv->lock); break; + case PROP_MICROPHONE: + g_value_set_uint (value, priv->source_idx); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -201,7 +545,12 @@ empathy_audio_src_class_init (EmpathyGstAudioSrcClass param_spec = g_param_spec_double ("peak-level", "peak level", "peak level", -G_MAXDOUBLE, G_MAXDOUBLE, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_VOLUME, param_spec); + g_object_class_install_property (object_class, PROP_PEAK_LEVEL, param_spec); + + param_spec = g_param_spec_uint ("microphone", "microphone", "microphone", + 0, G_MAXUINT, G_MAXUINT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_MICROPHONE, param_spec); signals[PEAK_LEVEL_CHANGED] = g_signal_new ("peak-level-changed", G_TYPE_FROM_CLASS (empathy_audio_src_class), @@ -214,7 +563,7 @@ empathy_audio_src_class_init (EmpathyGstAudioSrcClass param_spec = g_param_spec_double ("rms-level", "RMS level", "RMS level", -G_MAXDOUBLE, G_MAXDOUBLE, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_VOLUME, param_spec); + g_object_class_install_property (object_class, PROP_RMS_LEVEL, param_spec); signals[RMS_LEVEL_CHANGED] = g_signal_new ("rms-level-changed", @@ -224,6 +573,22 @@ empathy_audio_src_class_init (EmpathyGstAudioSrcClass NULL, NULL, g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE); + + signals[MICROPHONE_ADDED] = g_signal_new ("microphone-added", + G_TYPE_FROM_CLASS (empathy_audio_src_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _src_marshal_VOID__UINT_STRING_STRING_BOOLEAN, + G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); + + signals[MICROPHONE_REMOVED] = g_signal_new ("microphone-removed", + G_TYPE_FROM_CLASS (empathy_audio_src_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); } void @@ -242,6 +607,14 @@ empathy_audio_src_dispose (GObject *object) priv->idle_id = 0; + if (priv->context != NULL) + pa_context_unref (priv->context); + priv->context = NULL; + + if (priv->loop != NULL) + pa_glib_mainloop_free (priv->loop); + priv->loop = NULL; + /* release any references held by the object here */ if (G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose) @@ -257,6 +630,10 @@ empathy_audio_src_finalize (GObject *object) /* free any data held directly by the object here */ g_mutex_free (priv->lock); + g_queue_foreach (priv->operations, (GFunc) operation_free, + GUINT_TO_POINTER (TRUE)); + g_queue_free (priv->operations); + G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object); } @@ -384,4 +761,112 @@ empathy_audio_src_get_volume (EmpathyGstAudioSrc *src) return volume; } +void +empathy_audio_src_get_microphones_async (EmpathyGstAudioSrc *src, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src); + Operation *operation; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data, + empathy_audio_src_get_microphones_async); + + /* If we can't change mic let's not pretend we can by returning the + * list of available mics. */ + if (!empathy_audio_src_supports_changing_mic (src)) + { + g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED, + "pulsesrc is not new enough to support changing microphone"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + operation = operation_new (operation_get_microphones, simple); + g_queue_push_tail (priv->operations, operation); + + /* gogogogo */ + operations_run (src); +} + +const GList * +empathy_audio_src_get_microphones_finish (EmpathyGstAudioSrc *src, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + GQueue *queue; + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, + G_OBJECT (src), empathy_audio_src_get_microphones_async), + NULL); + queue = g_simple_async_result_get_op_res_gpointer (simple); + return queue->head; +} + +guint +empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src); + + return priv->source_idx; +} + +void +empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src, + guint microphone, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src); + guint source_output_idx; + GSimpleAsyncResult *simple; + Operation *operation; + + simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data, + empathy_audio_src_change_microphone_async); + + if (!empathy_audio_src_supports_changing_mic (src)) + { + g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED, + "pulsesrc is not new enough to support changing microphone"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + g_object_get (priv->src, "source-output-index", &source_output_idx, NULL); + + if (source_output_idx == PA_INVALID_INDEX) + { + g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED, + "pulsesrc is not yet PLAYING"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + g_simple_async_result_set_op_res_gpointer (simple, + GUINT_TO_POINTER (microphone), NULL); + + operation = operation_new (operation_change_microphone, simple); + g_queue_push_tail (priv->operations, operation); + + /* gogogogo */ + operations_run (src); +} + +gboolean +empathy_audio_src_change_microphone_finish (EmpathyGstAudioSrc *src, + GAsyncResult *result, + GError **error) +{ + empathy_implement_finish_void (src, + empathy_audio_src_change_microphone_async); +} diff --git a/src/empathy-audio-src.h b/src/empathy-audio-src.h index 4bca31b61..05e3c46cc 100644 --- a/src/empathy-audio-src.h +++ b/src/empathy-audio-src.h @@ -23,6 +23,7 @@ #include <glib-object.h> #include <gst/gst.h> +#include <gio/gio.h> G_BEGIN_DECLS @@ -61,6 +62,26 @@ GstElement *empathy_audio_src_new (void); void empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume); gdouble empathy_audio_src_get_volume (EmpathyGstAudioSrc *src); +typedef struct +{ + guint index; + gchar *name; + gchar *description; + gboolean is_monitor; +} EmpathyAudioSrcMicrophone; + +void empathy_audio_src_get_microphones_async (EmpathyGstAudioSrc *src, + GAsyncReadyCallback callback, gpointer user_data); +const GList * empathy_audio_src_get_microphones_finish (EmpathyGstAudioSrc *src, + GAsyncResult *result, GError **error); + +guint empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src); + +void empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src, + guint microphone, GAsyncReadyCallback callback, gpointer user_data); +gboolean empathy_audio_src_change_microphone_finish (EmpathyGstAudioSrc *src, + GAsyncResult *result, GError **error); + G_END_DECLS #endif /* #ifndef __EMPATHY_GST_AUDIO_SRC_H__*/ diff --git a/src/empathy-av.c b/src/empathy-av.c index 6f8c5ba61..70acfc4f3 100644 --- a/src/empathy-av.c +++ b/src/empathy-av.c @@ -132,7 +132,6 @@ main (int argc, empathy_gtk_init (); g_set_application_name (_("Empathy Audio/Video Client")); - g_setenv ("PULSE_PROP_media.role", "phone", TRUE); /* Make empathy and empathy-av appear as the same app in gnome-shell */ gdk_set_program_class ("Empathy"); diff --git a/src/empathy-call-handler.c b/src/empathy-call-handler.c index 1db5a34ef..c141b9ffa 100644 --- a/src/empathy-call-handler.c +++ b/src/empathy-call-handler.c @@ -391,7 +391,7 @@ empathy_call_handler_class_init (EmpathyCallHandlerClass *klass) param_spec = g_param_spec_boolean ("initial-video", "initial-video", "Whether the call should start with video", FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_INITIAL_VIDEO, param_spec); diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c index 343c69244..295af9383 100644 --- a/src/empathy-call-window.c +++ b/src/empathy-call-window.c @@ -41,8 +41,10 @@ #include <gst/farsight/fs-utils.h> #include <libempathy/empathy-camera-monitor.h> +#include <libempathy/empathy-gsettings.h> #include <libempathy/empathy-tp-contact-factory.h> #include <libempathy/empathy-utils.h> + #include <libempathy-gtk/empathy-avatar-image.h> #include <libempathy-gtk/empathy-ui-utils.h> #include <libempathy-gtk/empathy-sound-manager.h> @@ -56,19 +58,27 @@ #include "empathy-call-window-fullscreen.h" #include "empathy-call-factory.h" #include "empathy-video-widget.h" +#include "empathy-about-dialog.h" #include "empathy-audio-src.h" #include "empathy-audio-sink.h" #include "empathy-video-src.h" -#include "ev-sidebar.h" - -#define BUTTON_ID "empathy-call-dtmf-button-id" +#include "empathy-mic-menu.h" +#include "empathy-preferences.h" +#include "empathy-rounded-actor.h" +#include "empathy-camera-menu.h" #define CONTENT_HBOX_BORDER_WIDTH 6 #define CONTENT_HBOX_SPACING 3 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3 #define SELF_VIDEO_SECTION_WIDTH 120 -#define SELF_VIDEO_SECTION_HEIGTH 90 +#define SELF_VIDEO_SECTION_HEIGHT 90 +#define SELF_VIDEO_SECTION_MARGIN 10 + +#define FLOATING_TOOLBAR_OPACITY 192 +#define FLOATING_TOOLBAR_WIDTH 280 +#define FLOATING_TOOLBAR_HEIGHT 36 +#define FLOATING_TOOLBAR_SPACING 20 /* The avatar's default width and height are set to the same value because we want a square icon. */ @@ -76,6 +86,8 @@ #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \ EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT +#define SMALL_TOOLBAR_SIZE 36 + /* If an video input error occurs, the error message will start with "v4l" */ #define VIDEO_INPUT_ERROR_PREFIX "v4l" @@ -101,6 +113,14 @@ typedef enum { CAMERA_STATE_ON, } CameraState; +typedef enum { + PREVIEW_POS_NONE, + PREVIEW_POS_TOP_LEFT, + PREVIEW_POS_TOP_RIGHT, + PREVIEW_POS_BOTTOM_LEFT, + PREVIEW_POS_BOTTOM_RIGHT, +} PreviewPosition; + struct _EmpathyCallWindowPriv { gboolean dispose_has_run; @@ -120,26 +140,44 @@ struct _EmpathyCallWindowPriv ClutterActor *video_output; ClutterActor *video_preview; ClutterActor *preview_hidden_button; + ClutterActor *preview_rectangle1; + ClutterActor *preview_rectangle2; + ClutterActor *preview_rectangle3; + ClutterActor *preview_rectangle4; + ClutterActor *preview_rectangle_box1; + ClutterActor *preview_rectangle_box2; + ClutterActor *preview_rectangle_box3; + ClutterActor *preview_rectangle_box4; GtkWidget *video_container; GtkWidget *remote_user_avatar_widget; - GtkWidget *sidebar; - GtkWidget *statusbar; - GtkWidget *volume_button; - GtkWidget *redial_button; + GtkWidget *remote_user_avatar_toolbar; + GtkWidget *remote_user_name_toolbar; + GtkWidget *status_label; + GtkWidget *hangup_button; + GtkWidget *audio_call_button; + GtkWidget *video_call_button; GtkWidget *mic_button; GtkWidget *camera_button; GtkWidget *dialpad_button; GtkWidget *toolbar; + GtkWidget *bottom_toolbar; + ClutterActor *floating_toolbar; GtkWidget *pane; - GtkAction *redial; - GtkAction *menu_sidebar; GtkAction *menu_fullscreen; + GtkAction *menu_swap_camera; + + ClutterState *transitions; /* The box that contains self and remote avatar and video input/output. When we redial, we destroy and re-create the box */ ClutterActor *video_box; ClutterLayoutManager *video_layout; + /* Coordinates of the preview drag event's start. */ + PreviewPosition preview_pos; + gfloat event_x; + gfloat event_y; + /* We keep a reference on the hbox which contains the main content so we can easilly repack everything when toggling fullscreen */ GtkWidget *content_hbox; @@ -148,9 +186,6 @@ struct _EmpathyCallWindowPriv guint bus_message_source_id; gdouble volume; - GtkWidget *volume_scale; - GtkWidget *volume_progress_bar; - GtkAdjustment *audio_input_adj; GtkWidget *dtmf_panel; @@ -182,15 +217,9 @@ struct _EmpathyCallWindowPriv GList *notifiers; - guint context_id; - GTimer *timer; guint timer_id; - GtkWidget *video_contrast; - GtkWidget *video_brightness; - GtkWidget *video_gamma; - GMutex *lock; gboolean call_started; gboolean sending_video; @@ -202,13 +231,15 @@ struct _EmpathyCallWindowPriv gboolean got_video; guint got_video_src; + guint inactivity_src; + /* Those fields represent the state of the window before it actually was in fullscreen mode. */ - gboolean sidebar_was_visible_before_fs; + gboolean dialpad_was_visible_before_fs; gint original_width_before_fs; gint original_height_before_fs; - gint x, y, w, h, sidebar_width; + gint x, y, w, h, dialpad_width; gboolean maximized; /* TRUE if the call should be started when the pipeline is playing */ @@ -217,6 +248,10 @@ struct _EmpathyCallWindowPriv gboolean pipeline_playing; EmpathySoundManager *sound_mgr; + + GSettings *settings; + EmpathyMicMenu *mic_menu; + EmpathyCameraMenu *camera_menu; }; #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv) @@ -236,19 +271,6 @@ static void empathy_call_window_set_send_video (EmpathyCallWindow *window, static void empathy_call_window_mic_toggled_cb ( GtkToggleToolButton *toggle, EmpathyCallWindow *window); -static void empathy_call_window_sidebar_cb (GtkToggleAction *menu, - EmpathyCallWindow *self); - -static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar, - EmpathyCallWindow *window); - -static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar, - EmpathyCallWindow *window); - -static void empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar, - const gchar *page, - EmpathyCallWindow *window); - static void empathy_call_window_hangup_cb (gpointer object, EmpathyCallWindow *window); @@ -269,9 +291,6 @@ static gboolean empathy_call_window_video_output_motion_notify ( static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window, guint button); -static void empathy_call_window_redial_cb (gpointer object, - EmpathyCallWindow *window); - static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button, EmpathyCallWindow *window); @@ -288,30 +307,29 @@ empathy_call_window_volume_changed_cb (GtkScaleButton *button, gdouble value, EmpathyCallWindow *window); static void -empathy_call_window_setup_toolbar (EmpathyCallWindow *self) +empathy_call_window_show_hangup_button (EmpathyCallWindow *self, + gboolean show) { - EmpathyCallWindowPriv *priv = GET_PRIV (self); - GtkToolItem *tool_item; - - /* Add an empty expanded GtkToolItem so the volume button is at the end of - * the toolbar. */ - tool_item = gtk_tool_item_new (); - gtk_tool_item_set_expand (tool_item, TRUE); - gtk_widget_show (GTK_WIDGET (tool_item)); - gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1); + gtk_widget_set_visible (self->priv->hangup_button, show); + gtk_widget_set_visible (self->priv->audio_call_button, !show); + gtk_widget_set_visible (self->priv->video_call_button, !show); +} - priv->volume_button = gtk_volume_button_new (); - /* FIXME listen to the audiosinks signals and update the button according to - * that, for now starting out at 1.0 and assuming only the app changes the - * volume will do */ - gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0); - g_signal_connect (G_OBJECT (priv->volume_button), "value-changed", - G_CALLBACK (empathy_call_window_volume_changed_cb), self); +static void +empathy_call_window_audio_call_cb (GtkToggleToolButton *button, + EmpathyCallWindow *self) +{ + g_object_set (self->priv->handler, "initial-video", FALSE, NULL); + empathy_call_window_restart_call (self); +} - tool_item = gtk_tool_item_new (); - gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button); - gtk_widget_show_all (GTK_WIDGET (tool_item)); - gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1); +static void +empathy_call_window_video_call_cb (GtkToggleToolButton *button, + EmpathyCallWindow *self) +{ + empathy_call_window_set_send_video (self, CAMERA_STATE_ON); + g_object_set (self->priv->handler, "initial-video", TRUE, NULL); + empathy_call_window_restart_call (self); } static void @@ -324,7 +342,7 @@ dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window) g_object_get (priv->handler, "call-channel", &call, NULL); - button_quark = g_quark_from_static_string (BUTTON_ID); + button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID); event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button), button_quark)); @@ -346,182 +364,14 @@ dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window) g_object_unref (call); } -static GtkWidget * -empathy_call_window_create_dtmf (EmpathyCallWindow *self) -{ - GtkWidget *table; - int i; - GQuark button_quark; - struct { - const gchar *label; - TpDTMFEvent event; - } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 }, - { "2", TP_DTMF_EVENT_DIGIT_2 }, - { "3", TP_DTMF_EVENT_DIGIT_3 }, - { "4", TP_DTMF_EVENT_DIGIT_4 }, - { "5", TP_DTMF_EVENT_DIGIT_5 }, - { "6", TP_DTMF_EVENT_DIGIT_6 }, - { "7", TP_DTMF_EVENT_DIGIT_7 }, - { "8", TP_DTMF_EVENT_DIGIT_8 }, - { "9", TP_DTMF_EVENT_DIGIT_9 }, - { "#", TP_DTMF_EVENT_HASH }, - { "0", TP_DTMF_EVENT_DIGIT_0 }, - { "*", TP_DTMF_EVENT_ASTERISK }, - { NULL, } }; - - button_quark = g_quark_from_static_string (BUTTON_ID); - - table = gtk_table_new (4, 3, TRUE); - - for (i = 0; dtmfbuttons[i].label != NULL; i++) - { - GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label); - gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1, - i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1); - - g_object_set_qdata (G_OBJECT (button), button_quark, - GUINT_TO_POINTER (dtmfbuttons[i].event)); - - g_signal_connect (G_OBJECT (button), "pressed", - G_CALLBACK (dtmf_button_pressed_cb), self); - g_signal_connect (G_OBJECT (button), "released", - G_CALLBACK (dtmf_button_released_cb), self); - } - - return table; -} - -static GtkWidget * -empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self, - gchar *label_text, GtkWidget *bin) -{ - GtkWidget *vbox = gtk_vbox_new (FALSE, 2); - GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10); - GtkWidget *label = gtk_label_new (label_text); - - gtk_widget_set_sensitive (scale, FALSE); - - gtk_container_add (GTK_CONTAINER (bin), vbox); - - gtk_range_set_inverted (GTK_RANGE (scale), TRUE); - gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); - - return scale; -} - static void -empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj, - EmpathyCallWindow *self) - -{ - EmpathyCallWindowPriv *priv = GET_PRIV (self); - - empathy_video_src_set_channel (priv->video_input, - EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj)); -} - -static void -empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj, - EmpathyCallWindow *self) - -{ - EmpathyCallWindowPriv *priv = GET_PRIV (self); - - empathy_video_src_set_channel (priv->video_input, - EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj)); -} - -static void -empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj, - EmpathyCallWindow *self) - -{ - EmpathyCallWindowPriv *priv = GET_PRIV (self); - - empathy_video_src_set_channel (priv->video_input, - EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj)); -} - - -static GtkWidget * -empathy_call_window_create_video_input (EmpathyCallWindow *self) -{ - EmpathyCallWindowPriv *priv = GET_PRIV (self); - GtkWidget *hbox; - - hbox = gtk_hbox_new (TRUE, 3); - - priv->video_contrast = empathy_call_window_create_video_input_add_slider ( - self, _("Contrast"), hbox); - - priv->video_brightness = empathy_call_window_create_video_input_add_slider ( - self, _("Brightness"), hbox); - - priv->video_gamma = empathy_call_window_create_video_input_add_slider ( - self, _("Gamma"), hbox); - - return hbox; -} - -static void -empathy_call_window_setup_video_input (EmpathyCallWindow *self) -{ - EmpathyCallWindowPriv *priv = GET_PRIV (self); - guint supported; - GtkAdjustment *adj; - - supported = empathy_video_src_get_supported_channels (priv->video_input); - - if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST) - { - adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast)); - - gtk_adjustment_set_value (adj, - empathy_video_src_get_channel (priv->video_input, - EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST)); - - g_signal_connect (G_OBJECT (adj), "value-changed", - G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self); - - gtk_widget_set_sensitive (priv->video_contrast, TRUE); - } - - if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS) - { - adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness)); - - gtk_adjustment_set_value (adj, - empathy_video_src_get_channel (priv->video_input, - EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS)); - - g_signal_connect (G_OBJECT (adj), "value-changed", - G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self); - gtk_widget_set_sensitive (priv->video_brightness, TRUE); - } - - if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA) - { - adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma)); - - gtk_adjustment_set_value (adj, - empathy_video_src_get_channel (priv->video_input, - EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA)); - - g_signal_connect (G_OBJECT (adj), "value-changed", - G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self); - gtk_widget_set_sensitive (priv->video_gamma, TRUE); - } -} - -static void -empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj, - EmpathyCallWindow *self) +empathy_call_window_mic_volume_changed (EmpathyCallWindow *self) { EmpathyCallWindowPriv *priv = GET_PRIV (self); gdouble volume; - volume = gtk_adjustment_get_value (adj)/100.0; + volume = g_settings_get_double (priv->settings, + EMPATHY_PREFS_CALL_SOUND_VOLUME) / 100.0; /* Don't store the volume because of muting */ if (volume > 0 || gtk_toggle_tool_button_get_active ( @@ -540,56 +390,22 @@ empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj, } static void -empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src, - gdouble level, EmpathyCallWindow *window) +empathy_call_window_prefs_volume_changed_cb (GSettings *settings, + gchar *key, + EmpathyCallWindow *self) { - gdouble value; - EmpathyCallWindowPriv *priv = GET_PRIV (window); - - value = CLAMP (pow (10, level / 20), 0.0, 1.0); - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar), - value); + empathy_call_window_mic_volume_changed (self); } -static GtkWidget * -empathy_call_window_create_audio_input (EmpathyCallWindow *self) +static void +empathy_call_window_raise_actors (EmpathyCallWindow *self) { - EmpathyCallWindowPriv *priv = GET_PRIV (self); - GtkWidget *hbox, *vbox, *label; - - hbox = gtk_hbox_new (TRUE, 3); - - vbox = gtk_vbox_new (FALSE, 3); - gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3); - - priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100); - gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE); - label = gtk_label_new (_("Volume")); - - priv->audio_input_adj = gtk_range_get_adjustment ( - GTK_RANGE (priv->volume_scale)); - priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC - (priv->audio_input)); - gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100); - - g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed", - G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self); - - gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3); - - priv->volume_progress_bar = gtk_progress_bar_new (); + clutter_actor_raise_top (self->priv->floating_toolbar); - gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar), - GTK_ORIENTATION_VERTICAL); - - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar), - 0); - - gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE, - 3); - - return hbox; + clutter_actor_raise_top (self->priv->preview_rectangle_box1); + clutter_actor_raise_top (self->priv->preview_rectangle_box2); + clutter_actor_raise_top (self->priv->preview_rectangle_box3); + clutter_actor_raise_top (self->priv->preview_rectangle_box4); } static void @@ -600,6 +416,8 @@ empathy_call_window_show_video_output (EmpathyCallWindow *self, g_object_set (self->priv->video_output, "visible", show, NULL); gtk_widget_set_visible (self->priv->remote_user_avatar_widget, !show); + + empathy_call_window_raise_actors (self); } static void @@ -647,10 +465,6 @@ create_audio_input (EmpathyCallWindow *self) priv->audio_input = empathy_audio_src_new (); gst_object_ref (priv->audio_input); gst_object_sink (priv->audio_input); - - tp_g_signal_connect_object (priv->audio_input, "peak-level-changed", - G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), - self, 0); } static void @@ -718,6 +532,67 @@ empathy_call_window_maximise_camera_cb (GtkAction *action, } static void +empathy_call_window_swap_camera_cb (GtkAction *action, + EmpathyCallWindow *self) +{ + const GList *cameras, *l; + gchar *current_cam; + + DEBUG ("Swapping the camera"); + + cameras = empathy_camera_monitor_get_cameras (self->priv->camera_monitor); + current_cam = empathy_video_src_dup_device ( + EMPATHY_GST_VIDEO_SRC (self->priv->video_input)); + + for (l = cameras; l != NULL; l = l->next) + { + EmpathyCamera *camera = l->data; + + if (!tp_strdiff (camera->device, current_cam)) + { + EmpathyCamera *next; + + if (l->next != NULL) + next = l->next->data; + else + next = cameras->data; + + /* EmpathyCameraMenu will update itself and do the actual change + * for us */ + g_settings_set_string (self->priv->settings, + EMPATHY_PREFS_CALL_CAMERA_DEVICE, + next->device); + + break; + } + } + + g_free (current_cam); +} + +static void +empathy_call_window_camera_added_cb (EmpathyCameraMonitor *monitor, + EmpathyCamera *camera, + EmpathyCallWindow *self) +{ + const GList *cameras = empathy_camera_monitor_get_cameras (monitor); + + gtk_action_set_visible (self->priv->menu_swap_camera, + g_list_length ((GList *) cameras) >= 2); +} + +static void +empathy_call_window_camera_removed_cb (EmpathyCameraMonitor *monitor, + EmpathyCamera *camera, + EmpathyCallWindow *self) +{ + const GList *cameras = empathy_camera_monitor_get_cameras (monitor); + + gtk_action_set_visible (self->priv->menu_swap_camera, + g_list_length ((GList *) cameras) >= 2); +} + +static void empathy_call_window_preview_button_clicked_cb (GtkButton *button, EmpathyCallWindow *self) { @@ -743,21 +618,353 @@ empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button, gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); } +static ClutterActor * +empathy_call_window_create_preview_rectangle (EmpathyCallWindow *self, + ClutterActor **box, + ClutterBinAlignment x, + ClutterBinAlignment y) +{ + ClutterLayoutManager *layout1, *layout2; + ClutterActor *rectangle; + ClutterActor *box1, *box2; + + layout1 = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_START); + + box1 = clutter_box_new (layout1); + + *box = box1; + + rectangle = clutter_rectangle_new_with_color ( + CLUTTER_COLOR_Transparent); + + clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), + 1); + + clutter_actor_set_size (box1, + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN + + FLOATING_TOOLBAR_HEIGHT + FLOATING_TOOLBAR_SPACING); + + layout2 = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_CENTER); + + /* We have a box with the margins and the video in the middle inside + * a bigger box with an extra bottom margin so we're not on top of + * the floating toolbar. */ + box2 = clutter_box_new (layout2); + + clutter_actor_set_size (box2, + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN); + + clutter_actor_set_size (rectangle, + SELF_VIDEO_SECTION_WIDTH + 5, SELF_VIDEO_SECTION_HEIGHT + 5); + + clutter_container_add_actor (CLUTTER_CONTAINER (box1), box2); + clutter_container_add_actor (CLUTTER_CONTAINER (box2), rectangle); + + clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (self->priv->video_layout), + box1, x, y); + + clutter_actor_hide (rectangle); + + return rectangle; +} + +static void +empathy_call_window_create_preview_rectangles (EmpathyCallWindow *self) +{ + self->priv->preview_rectangle1 = + empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box1, + CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_START); + self->priv->preview_rectangle2 = + empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box2, + CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END); + self->priv->preview_rectangle3 = + empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box3, + CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_START); + self->priv->preview_rectangle4 = + empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box4, + CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_END); +} + +static void +empathy_call_window_show_preview_rectangles (EmpathyCallWindow *self, + gboolean show) +{ + g_object_set (self->priv->preview_rectangle1, "visible", show, NULL); + g_object_set (self->priv->preview_rectangle2, "visible", show, NULL); + g_object_set (self->priv->preview_rectangle3, "visible", show, NULL); + g_object_set (self->priv->preview_rectangle4, "visible", show, NULL); +} + +static PreviewPosition +empathy_call_window_get_preview_position (EmpathyCallWindow *self, + gfloat event_x, + gfloat event_y) +{ + ClutterGeometry box; + PreviewPosition pos = PREVIEW_POS_NONE; + + clutter_actor_get_geometry (self->priv->video_box, &box); + + if (0 + SELF_VIDEO_SECTION_MARGIN <= event_x && + event_x <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) && + 0 + SELF_VIDEO_SECTION_MARGIN <= event_y && + event_y <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGHT)) + { + pos = PREVIEW_POS_TOP_LEFT; + } + else if (box.width - SELF_VIDEO_SECTION_MARGIN >= event_x && + event_x >= (box.width - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) && + 0 + SELF_VIDEO_SECTION_MARGIN <= event_y && + event_y <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGHT)) + { + pos = PREVIEW_POS_TOP_RIGHT; + } + else if (0 + SELF_VIDEO_SECTION_MARGIN <= event_x && + event_x <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) && + box.height - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING >= event_y && + event_y >= (box.height - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING - (gint) SELF_VIDEO_SECTION_HEIGHT)) + { + pos = PREVIEW_POS_BOTTOM_LEFT; + } + else if (box.width - SELF_VIDEO_SECTION_MARGIN >= event_x && + event_x >= (box.width - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) && + box.height - SELF_VIDEO_SECTION_MARGIN - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING >= event_y && + event_y >= (box.height - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING - (gint) SELF_VIDEO_SECTION_HEIGHT)) + { + pos = PREVIEW_POS_BOTTOM_RIGHT; + } + + return pos; +} + +static ClutterActor * +empathy_call_window_get_preview_rectangle (EmpathyCallWindow *self, + PreviewPosition pos) +{ + ClutterActor *rectangle; + + switch (pos) + { + case PREVIEW_POS_TOP_LEFT: + rectangle = self->priv->preview_rectangle1; + break; + case PREVIEW_POS_TOP_RIGHT: + rectangle = self->priv->preview_rectangle3; + break; + case PREVIEW_POS_BOTTOM_LEFT: + rectangle = self->priv->preview_rectangle2; + break; + case PREVIEW_POS_BOTTOM_RIGHT: + rectangle = self->priv->preview_rectangle4; + break; + default: + rectangle = NULL; + } + + return rectangle; +} + +static void +empathy_call_window_move_video_preview (EmpathyCallWindow *self, + PreviewPosition pos) +{ + ClutterBinLayout *layout = CLUTTER_BIN_LAYOUT (self->priv->video_layout); + + DEBUG ("moving the video preview to %d", pos); + + self->priv->preview_pos = pos; + + switch (pos) + { + case PREVIEW_POS_TOP_LEFT: + clutter_bin_layout_set_alignment (layout, + self->priv->video_preview, + CLUTTER_BIN_ALIGNMENT_START, + CLUTTER_BIN_ALIGNMENT_START); + break; + case PREVIEW_POS_TOP_RIGHT: + clutter_bin_layout_set_alignment (layout, + self->priv->video_preview, + CLUTTER_BIN_ALIGNMENT_END, + CLUTTER_BIN_ALIGNMENT_START); + break; + case PREVIEW_POS_BOTTOM_LEFT: + clutter_bin_layout_set_alignment (layout, + self->priv->video_preview, + CLUTTER_BIN_ALIGNMENT_START, + CLUTTER_BIN_ALIGNMENT_END); + break; + case PREVIEW_POS_BOTTOM_RIGHT: + clutter_bin_layout_set_alignment (layout, + self->priv->video_preview, + CLUTTER_BIN_ALIGNMENT_END, + CLUTTER_BIN_ALIGNMENT_END); + break; + default: + g_warn_if_reached (); + } +} + +static void +empathy_call_window_highlight_preview_rectangle (EmpathyCallWindow *self, + PreviewPosition pos) +{ + ClutterActor *rectangle; + + rectangle = empathy_call_window_get_preview_rectangle (self, pos); + + clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), 3); + clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rectangle), + CLUTTER_COLOR_Red); +} + +static void +empathy_call_window_darken_preview_rectangle (EmpathyCallWindow *self, + ClutterActor *rectangle) +{ + clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), 1); + clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rectangle), + CLUTTER_COLOR_Black); +} + +static void +empathy_call_window_darken_preview_rectangles (EmpathyCallWindow *self) +{ + ClutterActor *rectangle; + + rectangle = empathy_call_window_get_preview_rectangle (self, + self->priv->preview_pos); + + /* We don't want to darken the rectangle where the preview + * currently is. */ + + if (self->priv->preview_rectangle1 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle1); + + if (self->priv->preview_rectangle2 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle2); + + if (self->priv->preview_rectangle3 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle3); + + if (self->priv->preview_rectangle4 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle4); +} + +static void +empathy_call_window_preview_on_drag_begin_cb (ClutterDragAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y, + ClutterModifierType modifiers, + EmpathyCallWindow *self) +{ + empathy_call_window_show_preview_rectangles (self, TRUE); + empathy_call_window_darken_preview_rectangles (self); + + self->priv->event_x = event_x; + self->priv->event_y = event_y; +} + +static void +empathy_call_window_preview_on_drag_end_cb (ClutterDragAction *action, + ClutterActor *actor, + gfloat event_x, + gfloat event_y, + ClutterModifierType modifiers, + EmpathyCallWindow *self) +{ + PreviewPosition pos; + + pos = empathy_call_window_get_preview_position (self, event_x, event_y); + + if (pos != PREVIEW_POS_NONE) + empathy_call_window_move_video_preview (self, pos); + + empathy_call_window_show_preview_rectangles (self, FALSE); +} + +static void +empathy_call_window_preview_on_drag_motion_cb (ClutterDragAction *action, + ClutterActor *actor, + gfloat delta_x, + gfloat delta_y, + EmpathyCallWindow *self) +{ + PreviewPosition pos; + + pos = empathy_call_window_get_preview_position (self, + self->priv->event_x - delta_x, self->priv->event_y + delta_y); + + if (pos != PREVIEW_POS_NONE) + empathy_call_window_highlight_preview_rectangle (self, pos); + else + empathy_call_window_darken_preview_rectangles (self); +} + +static gboolean +empathy_call_window_preview_enter_event_cb (ClutterActor *actor, + ClutterCrossingEvent *event, + EmpathyCallWindow *self) +{ + ClutterActor *rectangle; + + rectangle = empathy_call_window_get_preview_rectangle (self, + self->priv->preview_pos); + + empathy_call_window_highlight_preview_rectangle (self, + self->priv->preview_pos); + + clutter_actor_show (rectangle); + + return FALSE; +} + +static gboolean +empathy_call_window_preview_leave_event_cb (ClutterActor *actor, + ClutterCrossingEvent *event, + EmpathyCallWindow *self) +{ + ClutterActor *rectangle; + + rectangle = empathy_call_window_get_preview_rectangle (self, + self->priv->preview_pos); + + empathy_call_window_darken_preview_rectangle (self, rectangle); + + clutter_actor_hide (rectangle); + + return FALSE; +} + static void create_video_preview (EmpathyCallWindow *self) { EmpathyCallWindowPriv *priv = GET_PRIV (self); - ClutterLayoutManager *layout, *layout_center; + ClutterLayoutManager *layout, *layout_center, *layout_end; ClutterActor *preview; ClutterActor *box; ClutterActor *b; + ClutterAction *action; GtkWidget *button; g_assert (priv->video_preview == NULL); preview = clutter_texture_new (); clutter_actor_set_size (preview, - SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH); + SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGHT); priv->video_preview_sink = clutter_gst_video_sink_new ( CLUTTER_TEXTURE (preview)); @@ -770,17 +977,23 @@ create_video_preview (EmpathyCallWindow *self) 0.0); /* Add a little offset to the video preview */ - layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END, + layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_START); priv->video_preview = clutter_box_new (layout); clutter_actor_set_size (priv->video_preview, - SELF_VIDEO_SECTION_WIDTH + 10, SELF_VIDEO_SECTION_HEIGTH + 10); + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN + + FLOATING_TOOLBAR_HEIGHT + FLOATING_TOOLBAR_SPACING); + /* We have a box with the margins and the video in the middle inside + * a bigger box with an extra bottom margin so we're not on top of + * the floating toolbar. */ layout_center = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_CENTER); box = clutter_box_new (layout_center); clutter_actor_set_size (box, - SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH); + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN); clutter_container_add_actor (CLUTTER_CONTAINER (box), preview); clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box); @@ -795,10 +1008,15 @@ create_video_preview (EmpathyCallWindow *self) button = gtk_button_new_with_label (_("i")); b = gtk_clutter_actor_new_with_contents (button); - clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout_center), - b, - CLUTTER_BIN_ALIGNMENT_END, + layout_end = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_END); + box = clutter_box_new (layout_end); + clutter_actor_set_size (box, + SELF_VIDEO_SECTION_WIDTH, + SELF_VIDEO_SECTION_HEIGHT + SELF_VIDEO_SECTION_MARGIN); + + clutter_container_add_actor (CLUTTER_CONTAINER (box), b); + clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box); g_signal_connect (button, "clicked", G_CALLBACK (empathy_call_window_preview_button_clicked_cb), @@ -815,6 +1033,8 @@ create_video_preview (EmpathyCallWindow *self) CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END); + self->priv->preview_pos = PREVIEW_POS_BOTTOM_LEFT; + clutter_actor_hide (priv->preview_hidden_button); g_signal_connect (button, "clicked", @@ -825,6 +1045,22 @@ create_video_preview (EmpathyCallWindow *self) priv->video_preview, CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END); + + action = clutter_drag_action_new (); + g_signal_connect (action, "drag-begin", + G_CALLBACK (empathy_call_window_preview_on_drag_begin_cb), self); + g_signal_connect (action, "drag-end", + G_CALLBACK (empathy_call_window_preview_on_drag_end_cb), self); + g_signal_connect (action, "drag-motion", + G_CALLBACK (empathy_call_window_preview_on_drag_motion_cb), self); + + g_signal_connect (preview, "enter-event", + G_CALLBACK (empathy_call_window_preview_enter_event_cb), self); + g_signal_connect (preview, "leave-event", + G_CALLBACK (empathy_call_window_preview_leave_event_cb), self); + + clutter_actor_add_action (preview, action); + clutter_actor_set_reactive (preview, TRUE); } static void @@ -866,6 +1102,7 @@ display_video_preview (EmpathyCallWindow *self, play_camera (self, TRUE); clutter_actor_show (priv->video_preview); + clutter_actor_raise_top (priv->floating_toolbar); } else { @@ -966,6 +1203,65 @@ create_pipeline (EmpathyCallWindow *self) g_object_unref (bus); } +static void +empathy_call_window_settings_cb (GtkAction *action, + EmpathyCallWindow *self) +{ + gchar *args = g_strdup_printf ("-p %s", + empathy_preferences_tab_to_string (EMPATHY_PREFERENCES_TAB_CALLS)); + + empathy_launch_program (BIN_DIR, "empathy", args); + + g_free (args); +} + +static void +empathy_call_window_contents_cb (GtkAction *action, + EmpathyCallWindow *self) +{ + empathy_url_show (GTK_WIDGET (self), "ghelp:empathy?audio-video"); +} + +static void +empathy_call_window_debug_cb (GtkAction *action, + EmpathyCallWindow *self) +{ + empathy_launch_program (BIN_DIR, "empathy-debugger", "-s Empathy.Call"); +} + +static void +empathy_call_window_about_cb (GtkAction *action, + EmpathyCallWindow *self) +{ + empathy_about_dialog_new (GTK_WINDOW (self)); +} + +static gboolean +empathy_call_window_toolbar_timeout (gpointer data) +{ + EmpathyCallWindow *self = data; + + clutter_state_set_state (self->priv->transitions, "fade-out"); + + return TRUE; +} + +static gboolean +empathy_call_window_motion_notify_cb (GtkWidget *widget, + GdkEvent *event, + EmpathyCallWindow *self) +{ + clutter_state_set_state (self->priv->transitions, "fade-in"); + + if (self->priv->inactivity_src > 0) + g_source_remove (self->priv->inactivity_src); + + self->priv->inactivity_src = g_timeout_add_seconds (3, + empathy_call_window_toolbar_timeout, self); + + return FALSE; +} + static gboolean empathy_call_window_configure_event_cb (GtkWidget *widget, GdkEvent *event, @@ -977,8 +1273,8 @@ empathy_call_window_configure_event_cb (GtkWidget *widget, gtk_window_get_position (GTK_WINDOW (self), &self->priv->x, &self->priv->y); gtk_window_get_size (GTK_WINDOW (self), &self->priv->w, &self->priv->h); - gtk_widget_get_preferred_width (self->priv->sidebar, - &self->priv->sidebar_width, NULL); + gtk_widget_get_preferred_width (self->priv->dtmf_panel, + &self->priv->dialpad_width, NULL); gdk_window = gtk_widget_get_window (widget); window_state = gdk_window_get_state (gdk_window); @@ -991,26 +1287,38 @@ static void empathy_call_window_destroyed_cb (GtkWidget *object, EmpathyCallWindow *self) { - if (gtk_widget_get_visible (self->priv->sidebar)) + if (gtk_widget_get_visible (self->priv->dtmf_panel)) { - /* Save the geometry as if the sidebar was hidden. */ + /* Save the geometry as if the dialpad was hidden. */ empathy_geometry_save_values (GTK_WINDOW (self), self->priv->x, self->priv->y, - self->priv->w - self->priv->sidebar_width, self->priv->h, + self->priv->w - self->priv->dialpad_width, self->priv->h, self->priv->maximized); } } static void +empathy_call_window_stage_allocation_changed_cb (ClutterActor *stage, + GParamSpec *pspec, + ClutterBindConstraint *constraint) +{ + ClutterActorBox allocation; + + clutter_actor_get_allocation_box (stage, &allocation); + + clutter_bind_constraint_set_offset (constraint, + allocation.y2 - allocation.y1 - + FLOATING_TOOLBAR_SPACING - FLOATING_TOOLBAR_HEIGHT); +} + +static void empathy_call_window_init (EmpathyCallWindow *self) { EmpathyCallWindowPriv *priv; GtkBuilder *gui; GtkWidget *top_vbox; - GtkWidget *page; gchar *filename; - GtkWidget *scroll; - ClutterConstraint *size_constraint; + ClutterConstraint *constraint; ClutterActor *remote_avatar; GtkStyleContext *context; GdkRGBA rgba; @@ -1024,16 +1332,20 @@ empathy_call_window_init (EmpathyCallWindow *self) "call_window_vbox", &top_vbox, "errors_vbox", &priv->errors_vbox, "pane", &priv->pane, - "statusbar", &priv->statusbar, - "redial", &priv->redial_button, + "remote_user_name_toolbar", &priv->remote_user_name_toolbar, + "remote_user_avatar_toolbar", &priv->remote_user_avatar_toolbar, + "status_label", &priv->status_label, + "audiocall", &priv->audio_call_button, + "videocall", &priv->video_call_button, "microphone", &priv->mic_button, "camera", &priv->camera_button, + "hangup", &priv->hangup_button, "dialpad", &priv->dialpad_button, "toolbar", &priv->toolbar, - "menuredial", &priv->redial, - "menusidebar", &priv->menu_sidebar, + "bottom_toolbar", &priv->bottom_toolbar, "ui_manager", &priv->ui_manager, "menufullscreen", &priv->menu_fullscreen, + "menupreviewswap", &priv->menu_swap_camera, "details_vbox", &priv->details_vbox, "vcodec_encoding_label", &priv->vcodec_encoding_label, "acodec_encoding_label", &priv->acodec_encoding_label, @@ -1051,18 +1363,22 @@ empathy_call_window_init (EmpathyCallWindow *self) g_free (filename); empathy_builder_connect (gui, self, - "menuhangup", "activate", empathy_call_window_hangup_cb, "hangup", "clicked", empathy_call_window_hangup_cb, - "menuredial", "activate", empathy_call_window_redial_cb, - "redial", "clicked", empathy_call_window_redial_cb, - "menusidebar", "toggled", empathy_call_window_sidebar_cb, + "audiocall", "clicked", empathy_call_window_audio_call_cb, + "videocall", "clicked", empathy_call_window_video_call_cb, + "volume", "value-changed", empathy_call_window_volume_changed_cb, "microphone", "toggled", empathy_call_window_mic_toggled_cb, "camera", "toggled", empathy_call_window_camera_toggled_cb, "dialpad", "toggled", empathy_call_window_dialpad_cb, "menufullscreen", "activate", empathy_call_window_fullscreen_cb, + "menusettings", "activate", empathy_call_window_settings_cb, + "menucontents", "activate", empathy_call_window_contents_cb, + "menudebug", "activate", empathy_call_window_debug_cb, + "menuabout", "activate", empathy_call_window_about_cb, "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb, "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb, "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb, + "menupreviewswap", "activate", empathy_call_window_swap_camera_cb, NULL); gtk_action_set_sensitive (priv->menu_fullscreen, FALSE); @@ -1073,6 +1389,11 @@ empathy_call_window_init (EmpathyCallWindow *self) priv->camera_button, "sensitive", G_BINDING_SYNC_CREATE); + g_signal_connect (priv->camera_monitor, "added", + G_CALLBACK (empathy_call_window_camera_added_cb), self); + g_signal_connect (priv->camera_monitor, "removed", + G_CALLBACK (empathy_call_window_camera_removed_cb), self); + priv->lock = g_mutex_new (); gtk_container_add (GTK_CONTAINER (self), top_vbox); @@ -1080,7 +1401,8 @@ empathy_call_window_init (EmpathyCallWindow *self) priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING); gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox), CONTENT_HBOX_BORDER_WIDTH); - gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE); + gtk_box_pack_start (GTK_BOX (priv->pane), priv->content_hbox, + TRUE, TRUE, 0); /* avatar/video box */ priv->video_layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, @@ -1109,10 +1431,10 @@ empathy_call_window_init (EmpathyCallWindow *self) priv->video_box, NULL); - size_constraint = clutter_bind_constraint_new ( + constraint = clutter_bind_constraint_new ( gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)), CLUTTER_BIND_SIZE, 0); - clutter_actor_add_constraint (priv->video_box, size_constraint); + clutter_actor_add_constraint (priv->video_box, constraint); priv->remote_user_avatar_widget = gtk_image_new (); remote_avatar = gtk_clutter_actor_new_with_contents ( @@ -1121,6 +1443,8 @@ empathy_call_window_init (EmpathyCallWindow *self) clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box), remote_avatar); + empathy_call_window_create_preview_rectangles (self); + gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->video_container, TRUE, TRUE, CONTENT_HBOX_CHILDREN_PACKING_PADDING); @@ -1130,52 +1454,85 @@ empathy_call_window_init (EmpathyCallWindow *self) create_audio_input (self); create_video_input (self); - /* The call will be started as soon the pipeline is playing */ - priv->start_call_when_playing = TRUE; + priv->floating_toolbar = empathy_rounded_actor_new (); - empathy_call_window_setup_toolbar (self); + gtk_widget_reparent (priv->bottom_toolbar, + gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->floating_toolbar))); - priv->sidebar = ev_sidebar_new (); - g_signal_connect (G_OBJECT (priv->sidebar), - "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self); - g_signal_connect (G_OBJECT (priv->sidebar), - "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self); - g_signal_connect (G_OBJECT (priv->sidebar), "changed", - G_CALLBACK (empathy_call_window_sidebar_changed_cb), self); - gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE); + constraint = clutter_bind_constraint_new ( + gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)), + CLUTTER_BIND_Y, 0); - page = empathy_call_window_create_audio_input (self); - ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input", - _("Audio input"), page); + clutter_actor_add_constraint (priv->floating_toolbar, constraint); - page = empathy_call_window_create_video_input (self); - ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input", - _("Video input"), page); + g_signal_connect ( + gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)), + "notify::allocation", + G_CALLBACK (empathy_call_window_stage_allocation_changed_cb), + constraint); - priv->dtmf_panel = empathy_call_window_create_dtmf (self); - ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad", - _("Dialpad"), priv->dtmf_panel); + clutter_actor_set_size (priv->floating_toolbar, + FLOATING_TOOLBAR_WIDTH, FLOATING_TOOLBAR_HEIGHT); + clutter_actor_set_opacity (priv->floating_toolbar, FLOATING_TOOLBAR_OPACITY); - gtk_widget_set_sensitive (priv->dtmf_panel, FALSE); + clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout), + priv->floating_toolbar, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_END); + + clutter_actor_raise_top (priv->floating_toolbar); + + /* Transitions for the floating toolbar */ + priv->transitions = clutter_state_new (); - /* Put the details vbox in a scroll window as it can require a lot of - * horizontal space. */ - scroll = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll), - priv->details_vbox); + /* all transitions last for 2s */ + clutter_state_set_duration (priv->transitions, NULL, NULL, 2000); - ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details", _("Details"), - scroll); + /* transition from any state to "fade-out" state */ + clutter_state_set (priv->transitions, NULL, "fade-out", + priv->floating_toolbar, + "opacity", CLUTTER_EASE_OUT_QUAD, 0, + NULL); + + /* transition from any state to "fade-in" state */ + clutter_state_set (priv->transitions, NULL, "fade-in", + priv->floating_toolbar, + "opacity", CLUTTER_EASE_OUT_QUAD, FLOATING_TOOLBAR_OPACITY, + NULL); + + /* put the actor into the "fade-in" state with no animation */ + clutter_state_warp_to_state (priv->transitions, "fade-in"); + + /* The call will be started as soon the pipeline is playing */ + priv->start_call_when_playing = TRUE; + + priv->dtmf_panel = empathy_create_dtmf_dialpad (G_OBJECT (self), + G_CALLBACK (dtmf_button_pressed_cb), + G_CALLBACK (dtmf_button_released_cb)); + + gtk_box_pack_start (GTK_BOX (priv->pane), priv->dtmf_panel, + FALSE, FALSE, 6); + + gtk_box_pack_start (GTK_BOX (priv->pane), priv->details_vbox, + FALSE, FALSE, 0); + + gtk_widget_set_sensitive (priv->dtmf_panel, FALSE); gtk_widget_show_all (top_vbox); - gtk_widget_hide (priv->sidebar); + gtk_widget_hide (priv->dtmf_panel); + gtk_widget_hide (priv->details_vbox); priv->fullscreen = empathy_call_window_fullscreen_new (self); empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_container); + /* We hide the bottom toolbar after 3s of inactivity and show it + * again on mouse movement */ + priv->inactivity_src = g_timeout_add_seconds (3, + empathy_call_window_toolbar_timeout, self); + g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button), "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self); @@ -1191,23 +1548,42 @@ empathy_call_window_init (EmpathyCallWindow *self) g_signal_connect (G_OBJECT (self), "key-press-event", G_CALLBACK (empathy_call_window_key_press_cb), self); + g_signal_connect (self, "motion-notify-event", + G_CALLBACK (empathy_call_window_motion_notify_cb), self); + priv->timer = g_timer_new (); g_object_ref (priv->ui_manager); g_object_unref (gui); priv->sound_mgr = empathy_sound_manager_dup_singleton (); + priv->mic_menu = empathy_mic_menu_new (self); + priv->camera_menu = empathy_camera_menu_new (self); + + empathy_call_window_show_hangup_button (self, TRUE); + + priv->settings = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA); + + /* Retrieve initial volume */ + priv->volume = g_settings_get_double (priv->settings, + EMPATHY_PREFS_CALL_SOUND_VOLUME) / 100.0; + + g_signal_connect (priv->settings, "changed::"EMPATHY_PREFS_CALL_SOUND_VOLUME, + G_CALLBACK (empathy_call_window_prefs_volume_changed_cb), self); empathy_geometry_bind (GTK_WINDOW (self), "call-window"); /* These signals are used to track the window position and save it * when the window is destroyed. We need to do this as we don't want - * the window geometry to be saved with the sidebar taken into account. */ + * the window geometry to be saved with the dialpad taken into account. */ g_signal_connect (self, "destroy", G_CALLBACK (empathy_call_window_destroyed_cb), self); g_signal_connect (self, "configure-event", G_CALLBACK (empathy_call_window_configure_event_cb), self); g_signal_connect (self, "window-state-event", G_CALLBACK (empathy_call_window_configure_event_cb), self); + + /* Don't display labels in both toolbars */ + gtk_toolbar_set_style (GTK_TOOLBAR (priv->toolbar), GTK_TOOLBAR_ICONS); } /* Instead of specifying a width and a height, we specify only one size. That's @@ -1254,23 +1630,51 @@ set_window_title (EmpathyCallWindow *self) } else { - gtk_window_set_title (GTK_WINDOW (self), _("Call with %d participants")); + g_warning ("Unknown remote contact!"); } } static void +set_remote_user_name (EmpathyCallWindow *self, + EmpathyContact *contact) +{ + const gchar *alias = empathy_contact_get_alias (contact); + const gchar *status = empathy_contact_get_status (contact); + gchar *label; + + label = g_strdup_printf ("%s\n<small>%s</small>", alias, status); + gtk_label_set_markup (GTK_LABEL (self->priv->remote_user_name_toolbar), + label); + g_free (label); +} + +static void contact_name_changed_cb (EmpathyContact *contact, - GParamSpec *pspec, EmpathyCallWindow *self) + GParamSpec *pspec, + EmpathyCallWindow *self) { set_window_title (self); + set_remote_user_name (self, contact); +} + +static void +contact_presence_changed_cb (EmpathyContact *contact, + GParamSpec *pspec, + EmpathyCallWindow *self) +{ + set_remote_user_name (self, contact); } static void contact_avatar_changed_cb (EmpathyContact *contact, - GParamSpec *pspec, GtkWidget *avatar_widget) + GParamSpec *pspec, + EmpathyCallWindow *self) { int size; GtkAllocation allocation; + GtkWidget *avatar_widget; + + avatar_widget = self->priv->remote_user_avatar_widget; gtk_widget_get_allocation (avatar_widget, &allocation); size = allocation.height; @@ -1283,6 +1687,19 @@ contact_avatar_changed_cb (EmpathyContact *contact, } init_contact_avatar_with_size (contact, avatar_widget, size); + + avatar_widget = self->priv->remote_user_avatar_toolbar; + + gtk_widget_get_allocation (avatar_widget, &allocation); + size = allocation.height; + + if (size == 0) + { + /* the widget is not allocated yet, set a default size */ + size = SMALL_TOOLBAR_SIZE; + } + + init_contact_avatar_with_size (contact, avatar_widget, size); } static void @@ -1291,19 +1708,25 @@ empathy_call_window_setup_avatars (EmpathyCallWindow *self, { EmpathyCallWindowPriv *priv = GET_PRIV (self); - g_signal_connect (priv->contact, "notify::name", - G_CALLBACK (contact_name_changed_cb), self); - g_signal_connect (priv->contact, "notify::avatar", - G_CALLBACK (contact_avatar_changed_cb), - priv->remote_user_avatar_widget); + tp_g_signal_connect_object (priv->contact, "notify::name", + G_CALLBACK (contact_name_changed_cb), self, 0); + tp_g_signal_connect_object (priv->contact, "notify::avatar", + G_CALLBACK (contact_avatar_changed_cb), self, 0); + tp_g_signal_connect_object (priv->contact, "notify::presence", + G_CALLBACK (contact_presence_changed_cb), self, 0); set_window_title (self); + set_remote_user_name (self, priv->contact); init_contact_avatar_with_size (priv->contact, priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH, REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT)); + init_contact_avatar_with_size (priv->contact, + priv->remote_user_avatar_toolbar, + SMALL_TOOLBAR_SIZE); + /* The remote avatar is shown by default and will be hidden when we receive video from the remote side. */ clutter_actor_hide (priv->video_output); @@ -1653,6 +2076,12 @@ empathy_call_window_dispose (GObject *object) priv->got_video_src = 0; } + if (priv->inactivity_src > 0) + { + g_source_remove (priv->inactivity_src); + priv->inactivity_src = 0; + } + tp_clear_object (&priv->pipeline); tp_clear_object (&priv->video_input); tp_clear_object (&priv->audio_input); @@ -1660,6 +2089,10 @@ empathy_call_window_dispose (GObject *object) tp_clear_object (&priv->ui_manager); tp_clear_object (&priv->fullscreen); tp_clear_object (&priv->camera_monitor); + tp_clear_object (&priv->settings); + tp_clear_object (&priv->sound_mgr); + tp_clear_object (&priv->mic_menu); + tp_clear_object (&priv->camera_menu); g_list_free_full (priv->notifiers, g_object_unref); @@ -1674,9 +2107,6 @@ empathy_call_window_dispose (GObject *object) priv->contact = NULL; } - - tp_clear_object (&priv->sound_mgr); - G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object); } @@ -1778,9 +2208,6 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *self) g_object_unref (priv->pipeline); priv->pipeline = NULL; - g_signal_handlers_disconnect_by_func (priv->audio_input_adj, - empathy_call_window_mic_volume_changed_cb, self); - if (priv->audio_output != NULL) g_object_unref (priv->audio_output); priv->audio_output = NULL; @@ -1863,8 +2290,7 @@ empathy_call_window_disconnected (EmpathyCallWindow *self, empathy_call_window_status_message (self, _("Disconnected")); - gtk_action_set_sensitive (priv->redial, TRUE); - gtk_widget_set_sensitive (priv->redial_button, TRUE); + empathy_call_window_show_hangup_button (self, FALSE); /* Unsensitive the camera and mic button */ gtk_widget_set_sensitive (priv->camera_button, FALSE); @@ -1880,9 +2306,6 @@ empathy_call_window_disconnected (EmpathyCallWindow *self, display_video_preview (self, TRUE); } - gtk_progress_bar_set_fraction ( - GTK_PROGRESS_BAR (priv->volume_progress_bar), 0); - /* destroy the video output; it will be recreated when we'll redial */ disconnect_video_output_motion_handler (self); if (priv->video_output != NULL) @@ -2106,14 +2529,23 @@ empathy_call_window_update_timer (gpointer user_data) { EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data); EmpathyCallWindowPriv *priv = GET_PRIV (self); + const gchar *status; gchar *str; gdouble time_; time_ = g_timer_elapsed (priv->timer, NULL); + if (priv->call_state == HELD) + status = _("On hold"); + else if (!gtk_toggle_tool_button_get_active ( + GTK_TOGGLE_TOOL_BUTTON (priv->mic_button))) + status = _("Mute"); + else + status = _("Duration"); + /* Translators: 'status - minutes:seconds' the caller has been connected */ str = g_strdup_printf (_("%s — %d:%02dm"), - priv->call_state == HELD ? _("On hold") : _("Connected"), + status, (int) time_ / 60, (int) time_ % 60); empathy_call_window_status_message (self, str); g_free (str); @@ -2349,8 +2781,7 @@ empathy_call_window_state_changed_cb (EmpathyCallHandler *handler, gtk_widget_set_sensitive (priv->camera_button, can_send_video); - gtk_action_set_sensitive (priv->redial, FALSE); - gtk_widget_set_sensitive (priv->redial_button, FALSE); + empathy_call_window_show_hangup_button (self, TRUE); gtk_widget_set_sensitive (priv->mic_button, TRUE); @@ -2380,6 +2811,7 @@ empathy_call_window_show_video_output_cb (gpointer user_data) { gtk_widget_hide (self->priv->remote_user_avatar_widget); clutter_actor_show (self->priv->video_output); + empathy_call_window_raise_actors (self); } return FALSE; @@ -2654,8 +3086,6 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message, if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input)) { gst_message_parse_state_changed (message, NULL, &newstate, NULL); - if (newstate == GST_STATE_PAUSED) - empathy_call_window_setup_video_input (self); } if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) && !priv->call_started) @@ -2782,6 +3212,11 @@ empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window) { EmpathyCallWindowPriv *priv = GET_PRIV (window); TpyCallChannel *call; + gint width; + + /* Make the hangup button twice as wide */ + width = gtk_widget_get_allocated_width (priv->hangup_button); + gtk_widget_set_size_request (priv->hangup_button, width * 2, -1); g_signal_connect (priv->handler, "state-changed", G_CALLBACK (empathy_call_window_state_changed_cb), window); @@ -2849,18 +3284,16 @@ show_controls (EmpathyCallWindow *window, gboolean set_fullscreen) if (set_fullscreen) { - gtk_widget_hide (priv->sidebar); + gtk_widget_hide (priv->dtmf_panel); gtk_widget_hide (menu); - gtk_widget_hide (priv->statusbar); gtk_widget_hide (priv->toolbar); } else { - if (priv->sidebar_was_visible_before_fs) - gtk_widget_show (priv->sidebar); + if (priv->dialpad_was_visible_before_fs) + gtk_widget_show (priv->dtmf_panel); gtk_widget_show (menu); - gtk_widget_show (priv->statusbar); gtk_widget_show (priv->toolbar); gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs, @@ -2901,7 +3334,7 @@ empathy_call_window_state_event_cb (GtkWidget *widget, if (set_fullscreen) { - gboolean sidebar_was_visible; + gboolean dialpad_was_visible; GtkAllocation allocation; gint original_width, original_height; @@ -2909,9 +3342,11 @@ empathy_call_window_state_event_cb (GtkWidget *widget, original_width = allocation.width; original_height = allocation.height; - g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL); + g_object_get (priv->dtmf_panel, + "visible", &dialpad_was_visible, + NULL); - priv->sidebar_was_visible_before_fs = sidebar_was_visible; + priv->dialpad_was_visible_before_fs = dialpad_was_visible; priv->original_width_before_fs = original_width; priv->original_height_before_fs = original_height; @@ -2942,68 +3377,32 @@ empathy_call_window_state_event_cb (GtkWidget *widget, } static void -empathy_call_window_update_sidebar_buttons (EmpathyCallWindow *window, - gboolean toggled) -{ - EmpathyCallWindowPriv *priv = GET_PRIV (window); - - /* Update dialpad button */ - g_signal_handlers_block_by_func (priv->dialpad_button, - empathy_call_window_dialpad_cb, window); - gtk_toggle_tool_button_set_active ( - GTK_TOGGLE_TOOL_BUTTON (priv->dialpad_button), - toggled); - g_signal_handlers_unblock_by_func (priv->dialpad_button, - empathy_call_window_dialpad_cb, window); - - /* Update sidebar menu */ - g_signal_handlers_block_by_func (priv->menu_sidebar, - empathy_call_window_sidebar_cb, window); - gtk_toggle_action_set_active ( - GTK_TOGGLE_ACTION (priv->menu_sidebar), - gtk_widget_get_visible (priv->sidebar)); - g_signal_handlers_unblock_by_func (priv->menu_sidebar, - empathy_call_window_sidebar_cb, window); -} - -static void -empathy_call_window_show_sidebar (EmpathyCallWindow *window, +empathy_call_window_show_dialpad (EmpathyCallWindow *window, gboolean active) { EmpathyCallWindowPriv *priv = GET_PRIV (window); - int w, h, sidebar_width, handle_size; + int w, h, dialpad_width; GtkAllocation allocation; - gchar *page; - gboolean dialpad_shown; gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); w = allocation.width; h = allocation.height; - gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL); - - gtk_widget_get_preferred_width (priv->sidebar, &sidebar_width, NULL); + gtk_widget_get_preferred_width (priv->dtmf_panel, &dialpad_width, NULL); if (active) { - gtk_widget_show (priv->sidebar); - w += sidebar_width + handle_size; + gtk_widget_show (priv->dtmf_panel); + w += dialpad_width; } else { - w -= sidebar_width + handle_size; - gtk_widget_hide (priv->sidebar); + w -= dialpad_width; + gtk_widget_hide (priv->dtmf_panel); } if (w > 0 && h > 0) gtk_window_resize (GTK_WINDOW (window), w, h); - - /* Update the 'Dialpad' menu */ - page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar)); - dialpad_shown = active && !tp_strdiff (page, "dialpad"); - g_free (page); - - empathy_call_window_update_sidebar_buttons (window, dialpad_shown); } static void @@ -3037,18 +3436,22 @@ empathy_call_window_set_send_video (EmpathyCallWindow *window, static void empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle, - EmpathyCallWindow *window) + EmpathyCallWindow *self) { - EmpathyCallWindowPriv *priv = GET_PRIV (window); + EmpathyCallWindowPriv *priv = GET_PRIV (self); gboolean active; active = (gtk_toggle_tool_button_get_active (toggle)); + /* We don't want the settings callback to react to this change to avoid + * a loop. */ + g_signal_handlers_block_by_func (priv->settings, + empathy_call_window_prefs_volume_changed_cb, self); + if (active) { - empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input), - priv->volume); - gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100); + g_settings_set_double (priv->settings, EMPATHY_PREFS_CALL_SOUND_VOLUME, + priv->volume * 100); } else { @@ -3057,45 +3460,21 @@ empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle, * sides mute at the same time on certain CMs AFAIK. Need to revisit this * in the future. GNOME #574574 */ - empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input), - 0); - gtk_adjustment_set_value (priv->audio_input_adj, 0); + g_settings_set_double (priv->settings, EMPATHY_PREFS_CALL_SOUND_VOLUME, + 0); } -} - -static void -empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar, - EmpathyCallWindow *window) -{ - empathy_call_window_show_sidebar (window, FALSE); -} - -static void -empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar, - EmpathyCallWindow *window) -{ - empathy_call_window_show_sidebar (window, TRUE); -} -static void -empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar, - const gchar *page, - EmpathyCallWindow *window) -{ - empathy_call_window_update_sidebar_buttons (window, - !tp_strdiff (page, "dialpad")); + g_signal_handlers_unblock_by_func (priv->settings, + empathy_call_window_prefs_volume_changed_cb, self); } static void empathy_call_window_hangup_cb (gpointer object, - EmpathyCallWindow *window) + EmpathyCallWindow *self) { - EmpathyCallWindowPriv *priv = GET_PRIV (window); - - empathy_call_handler_stop_call (priv->handler); + empathy_call_handler_stop_call (self->priv->handler); - if (empathy_call_window_disconnected (window, FALSE)) - gtk_widget_destroy (GTK_WIDGET (window)); + empathy_call_window_disconnected (self, TRUE); } static void @@ -3109,13 +3488,10 @@ empathy_call_window_restart_call (EmpathyCallWindow *window) create_video_output_widget (window); - g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed", - G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window); - /* While the call was disconnected, the input volume might have changed. * However, since the audio_input source was destroyed, its volume has not * been updated during that time. That's why we manually update it here */ - empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window); + empathy_call_window_mic_volume_changed (window); priv->outgoing = TRUE; empathy_call_window_set_state_connecting (window); @@ -3126,49 +3502,20 @@ empathy_call_window_restart_call (EmpathyCallWindow *window) /* call will be started when the pipeline is ready */ priv->start_call_when_playing = TRUE; - empathy_call_window_setup_avatars (window, priv->handler); - gtk_action_set_sensitive (priv->redial, FALSE); - gtk_widget_set_sensitive (priv->redial_button, FALSE); -} - -static void -empathy_call_window_redial_cb (gpointer object, - EmpathyCallWindow *window) -{ - EmpathyCallWindowPriv *priv = GET_PRIV (window); - - if (priv->call_state == CONNECTED) - priv->call_state = REDIALING; - - empathy_call_handler_stop_call (priv->handler); - - if (priv->call_state != CONNECTED) - empathy_call_window_restart_call (window); + empathy_call_window_show_hangup_button (window, TRUE); } static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button, EmpathyCallWindow *window) { - EmpathyCallWindowPriv *priv = GET_PRIV (window); gboolean active; active = gtk_toggle_tool_button_get_active (button); - if (active) - ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad"); - - empathy_call_window_show_sidebar (window, active); -} - -static void -empathy_call_window_sidebar_cb (GtkToggleAction *menu, - EmpathyCallWindow *self) -{ - empathy_call_window_show_sidebar (self, - gtk_toggle_action_get_active (menu)); + empathy_call_window_show_dialpad (window, active); } static void @@ -3228,6 +3575,9 @@ empathy_call_window_video_output_motion_notify (GtkWidget *widget, if (priv->is_fullscreen) { empathy_call_window_fullscreen_show_popup (priv->fullscreen); + + /* Show the bottom toolbar */ + empathy_call_window_motion_notify_cb (NULL, NULL, window); return TRUE; } return FALSE; @@ -3248,23 +3598,10 @@ empathy_call_window_video_menu_popup (EmpathyCallWindow *window, } static void -empathy_call_window_status_message (EmpathyCallWindow *window, +empathy_call_window_status_message (EmpathyCallWindow *self, gchar *message) { - EmpathyCallWindowPriv *priv = GET_PRIV (window); - - if (priv->context_id == 0) - { - priv->context_id = gtk_statusbar_get_context_id ( - GTK_STATUSBAR (priv->statusbar), "voip call status messages"); - } - else - { - gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id); - } - - gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id, - message); + gtk_label_set_label (GTK_LABEL (self->priv->status_label), message); } static void @@ -3279,3 +3616,25 @@ empathy_call_window_volume_changed_cb (GtkScaleButton *button, empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output), value); } + +GtkUIManager * +empathy_call_window_get_ui_manager (EmpathyCallWindow *window) +{ + EmpathyCallWindowPriv *priv = GET_PRIV (window); + + return priv->ui_manager; +} + +EmpathyGstAudioSrc * +empathy_call_window_get_audio_src (EmpathyCallWindow *window) +{ + EmpathyCallWindowPriv *priv = GET_PRIV (window); + + return (EmpathyGstAudioSrc *) priv->audio_input; +} + +EmpathyGstVideoSrc * +empathy_call_window_get_video_src (EmpathyCallWindow *self) +{ + return EMPATHY_GST_VIDEO_SRC (self->priv->video_input); +} diff --git a/src/empathy-call-window.h b/src/empathy-call-window.h index 11237fff6..912a79173 100644 --- a/src/empathy-call-window.h +++ b/src/empathy-call-window.h @@ -25,6 +25,8 @@ #include <gtk/gtk.h> #include "empathy-call-handler.h" +#include "empathy-audio-src.h" +#include "empathy-video-src.h" G_BEGIN_DECLS @@ -62,6 +64,11 @@ GType empathy_call_window_get_type (void); EmpathyCallWindow *empathy_call_window_new (EmpathyCallHandler *handler); +GtkUIManager *empathy_call_window_get_ui_manager (EmpathyCallWindow *window); + +EmpathyGstAudioSrc *empathy_call_window_get_audio_src (EmpathyCallWindow *window); +EmpathyGstVideoSrc *empathy_call_window_get_video_src (EmpathyCallWindow *window); + G_END_DECLS #endif /* #ifndef __EMPATHY_CALL_WINDOW_H__*/ diff --git a/src/empathy-call-window.ui b/src/empathy-call-window.ui index 7e41961f5..0dea20869 100644 --- a/src/empathy-call-window.ui +++ b/src/empathy-call-window.ui @@ -11,24 +11,37 @@ </object> </child> <child> - <object class="GtkAction" id="menuhangup"> - <property name="icon_name">call-stop</property> - <property name="name">menuhangup</property> - <property name="label" translatable="yes">Hang up</property> + <object class="GtkAction" id="menufullscreen"> + <property name="stock_id">gtk-fullscreen</property> + <property name="name">menufullscreen</property> + </object> + <accelerator key="F11"/> + </child> + <child> + <object class="GtkAction" id="edit"> + <property name="name">edit</property> + <property name="label" translatable="yes">_Edit</property> </object> </child> <child> - <object class="GtkAction" id="menuredial"> - <property name="stock_id">gtk-refresh</property> - <property name="name">menuredial</property> - <property name="label" translatable="yes">Redial</property> - <property name="sensitive">False</property> + <object class="GtkAction" id="menumicrophone"> + <property name="label" translatable="yes">_Microphone</property> + <property name="name">menumicrophone</property> + <property name="icon_name">gnome-stock-mic</property> </object> </child> <child> - <object class="GtkToggleAction" id="menusidebar"> - <property name="name">menusidebar</property> - <property name="label" translatable="yes">_Sidebar</property> + <object class="GtkAction" id="menucamera"> + <property name="label" translatable="yes">_Camera</property> + <property name="name">menucamera</property> + <property name="icon_name">camera-web</property> + </object> + </child> + <child> + <object class="GtkAction" id="menusettings"> + <property name="stock_id">gtk-preferences</property> + <property name="name">menusettings</property> + <property name="label" translatable="yes">_Settings</property> </object> </child> <child> @@ -38,11 +51,36 @@ </object> </child> <child> - <object class="GtkAction" id="menufullscreen"> - <property name="stock_id">gtk-fullscreen</property> - <property name="name">menufullscreen</property> + <object class="GtkAction" id="help"> + <property name="name">help</property> + <property name="label" translatable="yes">_Help</property> + </object> + </child> + <child> + <object class="GtkAction" id="menucontents"> + <property name="stock_id">gtk-help</property> + <property name="name">menucontents</property> + <property name="label" translatable="yes">_Contents</property> + </object> + <accelerator key="F1" modifiers=""/> + </child> + <child> + <object class="GtkAction" id="menudebug"> + <property name="name">menudebug</property> + <property name="label" translatable="yes">_Debug</property> + </object> + </child> + <child> + <object class="GtkAction" id="menuabout"> + <property name="stock_id">gtk-about</property> + <property name="name">menuabout</property> + </object> + </child> + <child> + <object class="GtkAction" id="menupreviewswap"> + <property name="name">menupreviewswap</property> + <property name="label" translatable="yes">Swap camera</property> </object> - <accelerator key="F11"/> </child> <child> <object class="GtkAction" id="menupreviewminimise"> @@ -67,22 +105,29 @@ <ui> <menubar name="menubar1"> <menu action="call"> - <menuitem action="menuhangup"/> - <menuitem action="menuredial"/> - </menu> - <menu action="view"> <menuitem action="menufullscreen"/> - <menuitem action="menusidebar"/> + </menu> + <menu action="edit"> + <menu action="menumicrophone"/> + <menu action="menucamera"/> + <menuitem name="menusettings" action="menusettings"/> + </menu> + <menu action="help"> + <menuitem name="menucontents" action="menucontents"/> + <menuitem name="menudebug" action="menudebug"/> + <menuitem name="menuabout" action="menuabout"/> </menu> </menubar> <popup name="video-popup"> <menuitem name="menufullscreen" action="menufullscreen"/> </popup> <popup name="preview-menu"> + <menuitem name="menupreviewswap" action="menupreviewswap"/> <menuitem name="menupreviewminimise" action="menupreviewminimise"/> <menuitem name="menupreviewdisable" action="menupreviewdisable"/> </popup> <popup name="preview-hidden-menu"> + <menuitem name="menupreviewswap" action="menupreviewswap"/> <menuitem name="menupreviewmaximise" action="menupreviewmaximise"/> <menuitem name="menupreviewdisable" action="menupreviewdisable"/> </popup> @@ -105,57 +150,68 @@ <class name="primary-toolbar"/> </style> <child> - <object class="GtkToolButton" id="hangup"> + <object class="GtkToolItem" id="toolitem1"> + <property name="visible">True</property> + <child> + <object class="GtkImage" id="remote_user_avatar_toolbar"> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton1"> + <property name="draw">False</property> <property name="visible">True</property> - <property name="is_important">True</property> - <property name="label" translatable="yes">Hang up</property> - <property name="icon_name">call-stop</property> - <property name="tooltip_text" translatable="yes">Hang up current call</property> </object> <packing> <property name="homogeneous">True</property> </packing> </child> <child> - <object class="GtkToolButton" id="redial"> + <object class="GtkToolItem" id="toolitem2"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="remote_user_name_toolbar"> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkToolItem" id="toolbar_space1"> <property name="visible">True</property> - <property name="is_important">True</property> - <property name="label" translatable="yes">Redial</property> - <property name="stock_id">gtk-refresh</property> - <property name="sensitive">False</property> - <property name="tooltip_text" translatable="yes">Call the contact again</property> </object> <packing> - <property name="homogeneous">True</property> + <property name="expand">True</property> </packing> </child> <child> - <object class="GtkSeparatorToolItem" id="toolbutton1"> + <object class="GtkToolButton" id="hangup"> <property name="visible">True</property> + <property name="label" translatable="yes">Hang up</property> + <property name="icon_name">call-stop</property> + <property name="tooltip_text" translatable="yes">Hang up current call</property> </object> <packing> - <property name="homogeneous">True</property> + <property name="homogeneous">False</property> </packing> </child> <child> - <object class="GtkToggleToolButton" id="microphone"> - <property name="visible">True</property> - <property name="active">True</property> - <property name="label" translatable="yes">Send Audio</property> - <property name="icon_name">gnome-stock-mic</property> - <property name="tooltip_text" translatable="yes">Toggle audio transmission</property> + <object class="GtkToolButton" id="videocall"> + <property name="label" translatable="yes">Video call</property> + <property name="icon_name">camera-web</property> + <property name="tooltip_text" translatable="yes">Start a video call</property> </object> <packing> <property name="homogeneous">True</property> </packing> </child> <child> - <object class="GtkToggleToolButton" id="camera"> - <property name="visible">True</property> - <property name="label" translatable="yes">Send Video</property> - <property name="icon_name">camera-web</property> - <property name="sensitive">False</property> - <property name="tooltip_text" translatable="yes">Toggle video transmission</property> + <object class="GtkToolButton" id="audiocall"> + <property name="label" translatable="yes">Call</property> + <property name="icon_name">call-start</property> + <property name="tooltip_text" translatable="yes">Start an audio call</property> </object> <packing> <property name="homogeneous">True</property> @@ -165,7 +221,7 @@ <object class="GtkToggleToolButton" id="dialpad"> <property name="visible">True</property> <property name="label" translatable="yes">Show dialpad</property> - <property name="icon_name">accessories-calculator</property> + <property name="icon_name">input-dialpad</property> <property name="tooltip_text" translatable="yes">Display the dialpad</property> </object> <packing> @@ -188,24 +244,87 @@ </packing> </child> <child> - <object class="GtkHPaned" id="pane"> + <object class="GtkHBox" id="pane"> <property name="visible">True</property> <property name="can_focus">True</property> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> </object> <packing> <property name="position">3</property> </packing> </child> <child> - <object class="GtkStatusbar" id="statusbar"> + <object class="GtkHBox" id="bottom_toolbar"> <property name="visible">True</property> - <property name="spacing">2</property> + <property name="homogeneous">False</property> + <child> + <object class="GtkToggleToolButton" id="camera"> + <property name="visible">True</property> + <property name="label" translatable="yes">Send Video</property> + <property name="icon_name">camera-web</property> + <property name="sensitive">False</property> + <property name="tooltip_text" translatable="yes">Toggle video transmission</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolitem3"> + <property name="visible">True</property> + <child> + <object class="GtkVolumeButton" id="volume"> + <property name="visible">True</property> + <property name="value">1.0</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="microphone"> + <property name="visible">True</property> + <property name="active">True</property> + <property name="label" translatable="yes">Send Audio</property> + <property name="icon_name">gnome-stock-mic</property> + <property name="tooltip_text" translatable="yes">Toggle audio transmission</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolbar_space2"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolitem4"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="status_label"> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton2"> + <property name="draw">False</property> + <property name="visible">True</property> + </object> + </child> </object> <packing> <property name="expand">False</property> diff --git a/src/empathy-call.c b/src/empathy-call.c index 7503532c5..1f60217db 100644 --- a/src/empathy-call.c +++ b/src/empathy-call.c @@ -121,9 +121,16 @@ main (int argc, /* Init */ g_thread_init (NULL); + /* Clutter needs this */ + gdk_disable_multidevice (); + optcontext = g_option_context_new (N_("- Empathy Audio/Video Client")); g_option_context_add_group (optcontext, gst_init_get_option_group ()); g_option_context_add_group (optcontext, gtk_get_option_group (TRUE)); + g_option_context_add_group (optcontext, cogl_get_option_group ()); + g_option_context_add_group (optcontext, + clutter_get_option_group_without_init ()); + g_option_context_add_group (optcontext, gtk_clutter_get_option_group ()); g_option_context_add_main_entries (optcontext, options, GETTEXT_PACKAGE); if (!g_option_context_parse (optcontext, &argc, &argv, &error)) { @@ -143,7 +150,6 @@ main (int argc, empathy_gtk_init (); g_set_application_name (_("Empathy Audio/Video Client")); - g_setenv ("PULSE_PROP_media.role", "phone", TRUE); /* Make empathy and empathy-call appear as the same app in gnome-shell */ gdk_set_program_class ("Empathy"); diff --git a/src/empathy-camera-menu.c b/src/empathy-camera-menu.c new file mode 100644 index 000000000..6a7d20c15 --- /dev/null +++ b/src/empathy-camera-menu.c @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * GtkAction code based on gnome-terminal's TerminalTabsMenu object. + * Thanks guys! + */ + +#include <config.h> + +#include <gtk/gtk.h> + +#include <libempathy/empathy-camera-monitor.h> +#include <libempathy/empathy-gsettings.h> + +#include "empathy-camera-menu.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_VOIP +#include <libempathy/empathy-debug.h> + +struct _EmpathyCameraMenuPrivate +{ + /* Borrowed ref; the call window actually owns us. */ + EmpathyCallWindow *window; + + /* Given away ref; the call window's UI manager now owns this. */ + GtkActionGroup *action_group; + + /* An invisible radio action so new cameras are always in the + * same radio group. */ + GtkAction *anchor_action; + + /* The merge ID used with the UI manager. We need to keep this + * around so in _clean we can remove all the items we've added + * before and start again. */ + guint ui_id; + + /* TRUE if we're in _update and so calling _set_active. */ + gboolean in_update; + + /* Queue of GtkRadioActions. */ + GQueue *cameras; + + EmpathyCameraMonitor *camera_monitor; + + GSettings *settings; +}; + +G_DEFINE_TYPE (EmpathyCameraMenu, empathy_camera_menu, G_TYPE_OBJECT); + +enum +{ + PROP_WINDOW = 1, +}; + +static void empathy_camera_menu_update (EmpathyCameraMenu *self); + +static void +empathy_camera_menu_init (EmpathyCameraMenu *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_CAMERA_MENU, EmpathyCameraMenuPrivate); +} + +static void +empathy_camera_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (object); + + switch (property_id) + { + case PROP_WINDOW: + self->priv->window = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +empathy_camera_menu_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (object); + + switch (property_id) + { + case PROP_WINDOW: + g_value_set_object (value, self->priv->window); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +empathy_camera_menu_clean (EmpathyCameraMenu *self) +{ + GtkUIManager *ui_manager; + + if (self->priv->ui_id == 0) + return; + + ui_manager = empathy_call_window_get_ui_manager (self->priv->window); + + gtk_ui_manager_remove_ui (ui_manager, self->priv->ui_id); + gtk_ui_manager_ensure_update (ui_manager); + self->priv->ui_id = 0; +} + +static void +empathy_camera_menu_activate_cb (GtkAction *action, + EmpathyCameraMenu *self) +{ + EmpathyGstVideoSrc *video; + const gchar *device; + + if (self->priv->in_update) + return; + + video = empathy_call_window_get_video_src (self->priv->window); + + device = gtk_action_get_name (action); + + empathy_video_src_change_device (video, device); +} + +static void +empathy_camera_menu_update (EmpathyCameraMenu *self) +{ + GList *l; + GtkUIManager *ui_manager; + EmpathyGstVideoSrc *video; + gchar *current_camera; + + ui_manager = empathy_call_window_get_ui_manager (self->priv->window); + + video = empathy_call_window_get_video_src (self->priv->window); + current_camera = empathy_video_src_dup_device (video); + + empathy_camera_menu_clean (self); + self->priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager); + + for (l = self->priv->cameras->head; l != NULL; l = g_list_next (l)) + { + GtkRadioAction *action = l->data; + const gchar *name = gtk_action_get_name (GTK_ACTION (action)); + + if (!tp_strdiff (current_camera, name)) + { + self->priv->in_update = TRUE; + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + self->priv->in_update = FALSE; + } + + gtk_ui_manager_add_ui (ui_manager, self->priv->ui_id, + /* TODO: this should probably be passed from the call + * window, seeing that it's a reference to + * empathy-call-window.ui. */ + "/menubar1/edit/menucamera", + name, name, GTK_UI_MANAGER_MENUITEM, FALSE); + } + + g_free (current_camera); +} + +static void +empathy_camera_menu_add_camera (EmpathyCameraMenu *self, + EmpathyCamera *camera) +{ + GtkRadioAction *action; + GSList *group; + + action = gtk_radio_action_new (camera->device, camera->name, NULL, NULL, 0); + gtk_action_group_add_action (self->priv->action_group, GTK_ACTION (action)); + + group = gtk_radio_action_get_group ( + GTK_RADIO_ACTION (self->priv->anchor_action)); + gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group); + + g_queue_push_tail (self->priv->cameras, action); + + g_signal_connect (action, "activate", + G_CALLBACK (empathy_camera_menu_activate_cb), self); +} + +static void +empathy_camera_menu_camera_added_cb (EmpathyCameraMonitor *monitor, + EmpathyCamera *camera, + EmpathyCameraMenu *self) +{ + empathy_camera_menu_add_camera (self, camera); + empathy_camera_menu_update (self); +} + +static void +empathy_camera_menu_camera_removed_cb (EmpathyCameraMonitor *monitor, + EmpathyCamera *camera, + EmpathyCameraMenu *self) +{ + GList *l; + + for (l = self->priv->cameras->head; l != NULL; l = g_list_next (l)) + { + GtkAction *action = l->data; + const gchar *device; + + device = gtk_action_get_name (action); + + if (tp_strdiff (device, camera->device)) + continue; + + g_signal_handlers_disconnect_by_func (action, + G_CALLBACK (empathy_camera_menu_activate_cb), self); + + gtk_action_group_remove_action (self->priv->action_group, + action); + g_queue_remove (self->priv->cameras, action); + break; + } + + empathy_camera_menu_update (self); +} + +static void +empathy_camera_menu_prefs_camera_changed_cb (GSettings *settings, + gchar *key, + EmpathyCameraMenu *self) +{ + gchar *device = g_settings_get_string (settings, key); + GtkRadioAction *action = NULL; + gboolean found = FALSE; + GList *l; + + for (l = self->priv->cameras->head; l != NULL; l = g_list_next (l)) + { + const gchar *name; + + action = l->data; + name = gtk_action_get_name (GTK_ACTION (action)); + + if (!tp_strdiff (device, name)) + { + found = TRUE; + break; + } + } + + /* If the selected camera isn't found, we connect the first + * available one */ + if (!found && self->priv->cameras->head != NULL) + action = self->priv->cameras->head->data; + + if (action != NULL && + !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + { + g_signal_handlers_block_by_func (settings, + empathy_camera_menu_prefs_camera_changed_cb, self); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + g_signal_handlers_unblock_by_func (settings, + empathy_camera_menu_prefs_camera_changed_cb, self); + } + + g_free (device); +} + +static void +empathy_camera_menu_get_cameras (EmpathyCameraMenu *self) +{ + const GList *cameras; + + cameras = empathy_camera_monitor_get_cameras (self->priv->camera_monitor); + + for (; cameras != NULL; cameras = g_list_next (cameras)) + { + EmpathyCamera *camera = cameras->data; + + empathy_camera_menu_add_camera (self, camera); + } + + empathy_camera_menu_update (self); + + /* Do as if the gsettings key had changed, so we select the key that + * was last set. */ + empathy_camera_menu_prefs_camera_changed_cb (self->priv->settings, + EMPATHY_PREFS_CALL_CAMERA_DEVICE, self); +} + +static void +empathy_camera_menu_constructed (GObject *obj) +{ + EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (obj); + GtkUIManager *ui_manager; + + g_assert (EMPATHY_IS_CALL_WINDOW (self->priv->window)); + + ui_manager = empathy_call_window_get_ui_manager (self->priv->window); + + g_assert (GTK_IS_UI_MANAGER (ui_manager)); + + /* Okay let's go go go. */ + + self->priv->action_group = gtk_action_group_new ("EmpathyCameraMenu"); + gtk_ui_manager_insert_action_group (ui_manager, self->priv->action_group, -1); + /* the UI manager now owns this */ + g_object_unref (self->priv->action_group); + + self->priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION, + "name", "EmpathyCameraMenuAnchorAction", + NULL); + gtk_action_group_add_action (self->priv->action_group, + self->priv->anchor_action); + g_object_unref (self->priv->anchor_action); + + self->priv->camera_monitor = empathy_camera_monitor_new (); + + tp_g_signal_connect_object (self->priv->camera_monitor, "added", + G_CALLBACK (empathy_camera_menu_camera_added_cb), self, 0); + tp_g_signal_connect_object (self->priv->camera_monitor, "removed", + G_CALLBACK (empathy_camera_menu_camera_removed_cb), self, 0); + + self->priv->settings = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA); + g_signal_connect (self->priv->settings, + "changed::"EMPATHY_PREFS_CALL_CAMERA_DEVICE, + G_CALLBACK (empathy_camera_menu_prefs_camera_changed_cb), self); + + self->priv->cameras = g_queue_new (); + + empathy_camera_menu_get_cameras (self); +} + +static void +empathy_camera_menu_dispose (GObject *obj) +{ + EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (obj); + + tp_clear_pointer (&self->priv->cameras, g_queue_free); + + tp_clear_object (&self->priv->camera_monitor); + tp_clear_object (&self->priv->settings); + + G_OBJECT_CLASS (empathy_camera_menu_parent_class)->dispose (obj); +} + +static void +empathy_camera_menu_class_init (EmpathyCameraMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = empathy_camera_menu_set_property; + object_class->get_property = empathy_camera_menu_get_property; + object_class->constructed = empathy_camera_menu_constructed; + object_class->dispose = empathy_camera_menu_dispose; + + g_object_class_install_property (object_class, PROP_WINDOW, + g_param_spec_object ("window", "window", "window", + EMPATHY_TYPE_CALL_WINDOW, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof (EmpathyCameraMenuPrivate)); +} + +EmpathyCameraMenu * +empathy_camera_menu_new (EmpathyCallWindow *window) +{ + return g_object_new (EMPATHY_TYPE_CAMERA_MENU, + "window", window, + NULL); +} diff --git a/src/empathy-camera-menu.h b/src/empathy-camera-menu.h new file mode 100644 index 000000000..f105baf22 --- /dev/null +++ b/src/empathy-camera-menu.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __EMPATHY_CAMERA_MENU_H__ +#define __EMPATHY_CAMERA_MENU_H__ + +#include <glib-object.h> + +#include "empathy-call-window.h" + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_CAMERA_MENU (empathy_camera_menu_get_type ()) +#define EMPATHY_CAMERA_MENU(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CAMERA_MENU, EmpathyCameraMenu)) +#define EMPATHY_CAMERA_MENU_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_CAMERA_MENU, EmpathyCameraMenuClass)) +#define EMPATHY_IS_CAMERA_MENU(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CAMERA_MENU)) +#define EMPATHY_IS_CAMERA_MENU_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CAMERA_MENU)) +#define EMPATHY_CAMERA_MENU_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CAMERA_MENU, EmpathyCameraMenuClass)) + +typedef struct _EmpathyCameraMenu EmpathyCameraMenu; +typedef struct _EmpathyCameraMenuPrivate EmpathyCameraMenuPrivate; +typedef struct _EmpathyCameraMenuClass EmpathyCameraMenuClass; + +struct _EmpathyCameraMenu +{ + GObject parent; + EmpathyCameraMenuPrivate *priv; +}; + +struct _EmpathyCameraMenuClass +{ + GObjectClass parent_class; +}; + +GType empathy_camera_menu_get_type (void) G_GNUC_CONST; + +EmpathyCameraMenu * empathy_camera_menu_new (EmpathyCallWindow *window); + +G_END_DECLS + +#endif /* __EMPATHY_CAMERA_MENU_H__ */ diff --git a/src/empathy-debug-window.c b/src/empathy-debug-window.c index 3874fe8d0..294c6afe0 100644 --- a/src/empathy-debug-window.c +++ b/src/empathy-debug-window.c @@ -115,6 +115,9 @@ typedef struct /* Service (CM, Client) chooser store */ GtkListStore *service_store; + /* Debug to show upon creation */ + gchar *select_name; + /* Misc. */ gboolean dispose_run; TpAccountManager *am; @@ -697,6 +700,13 @@ debug_window_get_name_owner_cb (TpDBusDaemon *proxy, COL_UNIQUE_NAME, out, -1); + if (priv->select_name != NULL && + !tp_strdiff (name, priv->select_name)) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->chooser), &iter); + tp_clear_pointer (&priv->select_name, g_free); + } + g_free (name); } @@ -1369,6 +1379,37 @@ tree_view_search_equal_func_cb (GtkTreeModel *model, } static void +empathy_debug_window_select_name (EmpathyDebugWindow *self, + const gchar *name) +{ + EmpathyDebugWindowPriv *priv = GET_PRIV (self); + GtkTreeModel *model = GTK_TREE_MODEL (priv->service_store); + GtkTreeIter iter; + gchar *iter_name; + gboolean valid, found = FALSE; + + for (valid = gtk_tree_model_get_iter_first (model, &iter); + valid; + valid = gtk_tree_model_iter_next (model, &iter)) + { + gtk_tree_model_get (model, &iter, + COL_NAME, &iter_name, + -1); + + if (!tp_strdiff (name, iter_name)) + found = TRUE; + + g_free (iter_name); + + if (found) + break; + } + + if (found) + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->chooser), &iter); +} + +static void am_prepared_cb (GObject *am, GAsyncResult *res, gpointer user_data) @@ -1670,6 +1711,8 @@ debug_window_finalize (GObject *object) char *key; GList *values; + g_free (priv->select_name); + g_hash_table_iter_init (&iter, priv->cache); while (g_hash_table_iter_next (&iter, (gpointer *) &key, @@ -1700,6 +1743,9 @@ debug_window_dispose (GObject *object) if (priv->name_owner_changed_signal != NULL) tp_proxy_signal_connection_disconnect (priv->name_owner_changed_signal); + if (priv->new_debug_message_signal != NULL) + tp_proxy_signal_connection_disconnect (priv->new_debug_message_signal); + if (priv->proxy != NULL) { debug_window_set_enabled (EMPATHY_DEBUG_WINDOW (object), FALSE); @@ -1707,9 +1753,6 @@ debug_window_dispose (GObject *object) g_object_unref (priv->proxy); } - if (priv->new_debug_message_signal != NULL) - tp_proxy_signal_connection_disconnect (priv->new_debug_message_signal); - if (priv->service_store != NULL) g_object_unref (priv->service_store); @@ -1747,3 +1790,20 @@ empathy_debug_window_new (GtkWindow *parent) return GTK_WIDGET (g_object_new (EMPATHY_TYPE_DEBUG_WINDOW, "transient-for", parent, NULL)); } + +void +empathy_debug_window_show (EmpathyDebugWindow *self, + const gchar *name) +{ + EmpathyDebugWindowPriv *priv = GET_PRIV (self); + + if (priv->service_store != NULL) + { + empathy_debug_window_select_name (self, name); + } + else + { + g_free (priv->select_name); + priv->select_name = g_strdup (name); + } +} diff --git a/src/empathy-debug-window.h b/src/empathy-debug-window.h index 22eec3f6f..9815c85de 100644 --- a/src/empathy-debug-window.h +++ b/src/empathy-debug-window.h @@ -58,6 +58,9 @@ GType empathy_debug_window_get_type (void) G_GNUC_CONST; GtkWidget * empathy_debug_window_new (GtkWindow *parent); +void empathy_debug_window_show (EmpathyDebugWindow *self, + const gchar *name); + G_END_DECLS #endif /* __EMPATHY_DEBUG_WINDOW_H__ */ diff --git a/src/empathy-debugger.c b/src/empathy-debugger.c index 3d38e7103..664dc211a 100644 --- a/src/empathy-debugger.c +++ b/src/empathy-debugger.c @@ -30,6 +30,7 @@ #define EMPATHY_DEBUGGER_DBUS_NAME "org.gnome.Empathy.Debugger" static GtkWidget *window = NULL; +static gchar *service = NULL; static void activate_cb (GApplication *app) @@ -46,6 +47,52 @@ activate_cb (GApplication *app) { gtk_window_present (GTK_WINDOW (window)); } + + if (service != NULL) + empathy_debug_window_show (EMPATHY_DEBUG_WINDOW (window), + service); +} + +static gint +command_line_cb (GApplication *application, + GApplicationCommandLine *command_line, + gpointer user_data) +{ + GError *error = NULL; + gchar **argv; + gint argc; + gint retval = 0; + + GOptionContext *optcontext; + GOptionEntry options[] = { + { "show-service", 's', + 0, G_OPTION_ARG_STRING, &service, + N_("Show a particular service"), + NULL }, + { NULL } + }; + + optcontext = g_option_context_new (N_("- Empathy Debugger")); + g_option_context_add_group (optcontext, gtk_get_option_group (TRUE)); + g_option_context_add_main_entries (optcontext, options, GETTEXT_PACKAGE); + + argv = g_application_command_line_get_arguments (command_line, &argc); + + if (!g_option_context_parse (optcontext, &argc, &argv, &error)) + { + g_print ("%s\nRun '%s --help' to see a full list of available command " + "line options.\n", + error->message, argv[0]); + + retval = 1; + } + + g_option_context_free (optcontext); + g_strfreev (argv); + + g_application_activate (application); + + return retval; } int @@ -60,8 +107,9 @@ main (int argc, empathy_gtk_init (); app = gtk_application_new (EMPATHY_DEBUGGER_DBUS_NAME, - G_APPLICATION_FLAGS_NONE); + G_APPLICATION_HANDLES_COMMAND_LINE); g_signal_connect (app, "activate", G_CALLBACK (activate_cb), NULL); + g_signal_connect (app, "command-line", G_CALLBACK (command_line_cb), NULL); g_set_application_name (_("Empathy Debugger")); diff --git a/src/empathy-invite-participant-dialog.c b/src/empathy-invite-participant-dialog.c index 1551b475d..ec5a275d4 100644 --- a/src/empathy-invite-participant-dialog.c +++ b/src/empathy-invite-participant-dialog.c @@ -14,6 +14,7 @@ #include "empathy-invite-participant-dialog.h" +#include <libempathy-gtk/empathy-contact-chooser.h> #include <libempathy-gtk/empathy-individual-view.h> #include <libempathy-gtk/empathy-ui-utils.h> @@ -25,24 +26,12 @@ enum PROP_TP_CHAT = 1 }; -typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx; - struct _EmpathyInviteParticipantDialogPrivate { EmpathyTpChat *tp_chat; - TpAccountManager *account_mgr; - - EmpathyIndividualStore *store; - EmpathyIndividualView *view; + GtkWidget *chooser; GtkWidget *invite_button; - - GPtrArray *search_words; - gchar *search_str; - - /* Context representing the FolksIndividual which are added because of the - * current search from the user. */ - AddTemporaryIndividualCtx *add_temp_ctx; }; static void @@ -86,93 +75,24 @@ invite_participant_dialog_set_property (GObject *object, }; } -struct _AddTemporaryIndividualCtx -{ - EmpathyInviteParticipantDialog *self; - /* List of owned FolksIndividual */ - GList *individuals; -}; - -static AddTemporaryIndividualCtx * -add_temporary_individual_ctx_new (EmpathyInviteParticipantDialog *self) -{ - AddTemporaryIndividualCtx *ctx = g_slice_new0 (AddTemporaryIndividualCtx); - - ctx->self = self; - return ctx; -} - -static void -add_temporary_individual_ctx_free (AddTemporaryIndividualCtx *ctx) -{ - GList *l; - - /* Remove all the individuals from the model */ - for (l = ctx->individuals; l != NULL; l = g_list_next (l)) - { - FolksIndividual *individual = l->data; - - individual_store_remove_individual_and_disconnect (ctx->self->priv->store, - individual); - - g_object_unref (individual); - } - - g_list_free (ctx->individuals); - g_slice_free (AddTemporaryIndividualCtx, ctx); -} - static void invite_participant_dialog_dispose (GObject *object) { EmpathyInviteParticipantDialog *self = (EmpathyInviteParticipantDialog *) object; - tp_clear_pointer (&self->priv->add_temp_ctx, - add_temporary_individual_ctx_free); - tp_clear_object (&self->priv->tp_chat); - tp_clear_object (&self->priv->store); - tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref); - tp_clear_pointer (&self->priv->search_str, g_free); - - tp_clear_object (&self->priv->account_mgr); G_OBJECT_CLASS (empathy_invite_participant_dialog_parent_class)->dispose ( object); } static void -empathy_invite_participant_dialog_class_init ( - EmpathyInviteParticipantDialogClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->get_property = invite_participant_dialog_get_property; - object_class->set_property = invite_participant_dialog_set_property; - object_class->dispose = invite_participant_dialog_dispose; - - g_type_class_add_private (object_class, - sizeof (EmpathyInviteParticipantDialogPrivate)); - - g_object_class_install_property (object_class, - PROP_TP_CHAT, - g_param_spec_object ("tp-chat", "EmpathyTpChat", "EmpathyTpChat", - EMPATHY_TYPE_TP_CHAT, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); -} - -static void -view_selection_changed_cb (GtkWidget *treeview, +selection_changed_cb (GtkWidget *treeview, + FolksIndividual *selected, EmpathyInviteParticipantDialog *self) { - FolksIndividual *individual; - - individual = empathy_individual_view_dup_selected (self->priv->view); - - gtk_widget_set_sensitive (self->priv->invite_button, individual != NULL); - - tp_clear_object (&individual); + gtk_widget_set_sensitive (self->priv->invite_button, selected != NULL); } /* Return the TpContact of @individual which is on the same connection as the @@ -186,7 +106,7 @@ get_tp_contact_for_chat (EmpathyInviteParticipantDialog *self, GeeSet *personas; GeeIterator *iter; - chat_conn = tp_channel_borrow_connection ((TpChannel *) self->priv->tp_chat); + chat_conn = tp_channel_borrow_connection (TP_CHANNEL (self->priv->tp_chat)); personas = folks_individual_get_personas (individual); iter = gee_iterable_iterator (GEE_ITERABLE (personas)); @@ -217,51 +137,32 @@ get_tp_contact_for_chat (EmpathyInviteParticipantDialog *self, } static gboolean -filter_func (GtkTreeModel *model, - GtkTreeIter *iter, +filter_individual (EmpathyContactChooser *chooser, + FolksIndividual *individual, + gboolean is_online, + gboolean searching, gpointer user_data) { EmpathyInviteParticipantDialog *self = user_data; - FolksIndividual *individual; - TpContact *contact; - gboolean is_online; GList *members, *l; - gboolean display = FALSE; - - gtk_tree_model_get (model, iter, - EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, - EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online, - -1); - - if (individual == NULL) - goto out; + TpContact *contact; + gboolean display = TRUE; - if (self->priv->search_words == NULL) - { - /* Not searching, display online contacts */ - if (!is_online) - goto out; - } - else - { - if (!empathy_individual_match_string (individual, - self->priv->search_str, self->priv->search_words)) - goto out; - } + /* Filter out offline contacts if we are not searching */ + if (!searching && !is_online) + return FALSE; /* Filter out individuals not having a persona on the same connection as the * EmpathyTpChat. */ contact = get_tp_contact_for_chat (self, individual); if (contact == NULL) - goto out; + return FALSE; /* Filter out contacts which are already in the chat */ members = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST ( self->priv->tp_chat)); - display = TRUE; - for (l = members; l != NULL; l = g_list_next (l)) { EmpathyContact *member = l->data; @@ -283,143 +184,18 @@ filter_func (GtkTreeModel *model, g_list_free_full (members, g_object_unref); -out: - tp_clear_object (&individual); return display; } static void -get_contacts_cb (TpConnection *connection, - guint n_contacts, - TpContact * const *contacts, - const gchar * const *requested_ids, - GHashTable *failed_id_errors, - const GError *error, - gpointer user_data, - GObject *weak_object) +invite_participant_dialog_constructed (GObject *object) { EmpathyInviteParticipantDialog *self = - (EmpathyInviteParticipantDialog *) weak_object; - AddTemporaryIndividualCtx *ctx = user_data; - TpAccount *account; - TpfPersonaStore *store; - FolksIndividual *individual; - TpfPersona *persona_new; - GeeSet *personas; - - if (self->priv->add_temp_ctx != ctx) - /* another request has been started */ - return; - - if (n_contacts != 1) - return; - - account = g_object_get_data (G_OBJECT (connection), "account"); - - store = tpf_persona_store_new (account); - personas = GEE_SET ( - gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref, - g_direct_hash, g_direct_equal)); - persona_new = tpf_persona_new (contacts[0], store); - gee_collection_add (GEE_COLLECTION (personas), - tpf_persona_new (contacts[0], store)); - - individual = folks_individual_new (personas); - - /* Pass ownership to the list */ - ctx->individuals = g_list_prepend (ctx->individuals, individual); - - individual_store_add_individual_and_connect (self->priv->store, individual); - - g_clear_object (&persona_new); - g_clear_object (&personas); - g_object_unref (store); -} - -static void -add_temporary_individuals (EmpathyInviteParticipantDialog *self, - const gchar *id) -{ - GList *accounts, *l; - - tp_clear_pointer (&self->priv->add_temp_ctx, - add_temporary_individual_ctx_free); - - if (tp_str_empty (id)) - return; - - self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self); - - /* Try to add an individual for each connected account */ - accounts = tp_account_manager_get_valid_accounts (self->priv->account_mgr); - for (l = accounts; l != NULL; l = g_list_next (l)) - { - TpAccount *account = l->data; - TpConnection *conn; - TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS, - TP_CONTACT_FEATURE_AVATAR_DATA, - TP_CONTACT_FEATURE_PRESENCE, - TP_CONTACT_FEATURE_CAPABILITIES }; - - conn = tp_account_get_connection (account); - if (conn == NULL) - continue; - - /* One day we'll have tp_connection_get_account()... */ - g_object_set_data_full (G_OBJECT (conn), "account", - g_object_ref (account), g_object_unref); - - tp_connection_get_contacts_by_id (conn, 1, &id, G_N_ELEMENTS (features), - features, get_contacts_cb, self->priv->add_temp_ctx, NULL, - G_OBJECT (self)); - } - - g_list_free (accounts); -} - -static void -search_text_changed (GtkEntry *entry, - EmpathyInviteParticipantDialog *self) -{ - const gchar *id; - - tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref); - tp_clear_pointer (&self->priv->search_str, g_free); - - id = gtk_entry_get_text (entry); - - self->priv->search_words = empathy_live_search_strip_utf8_string (id); - self->priv->search_str = g_strdup (id); - - add_temporary_individuals (self, id); - - empathy_individual_view_refilter (self->priv->view); -} - -static void -empathy_invite_participant_dialog_init (EmpathyInviteParticipantDialog *self) -{ + (EmpathyInviteParticipantDialog *) object; GtkDialog *dialog = GTK_DIALOG (self); GtkWidget *label; char *str; GtkWidget *content; - EmpathyIndividualManager *mgr; - GtkTreeSelection *selection; - GtkWidget *scroll; - GtkWidget *search_entry; - GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 }; - - self->priv = G_TYPE_INSTANCE_GET_PRIVATE ( - self, EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG, - EmpathyInviteParticipantDialogPrivate); - - self->priv->account_mgr = tp_account_manager_dup (); - - /* We don't wait for the CORE feature to be prepared, which is fine as we - * won't use the account manager until user starts searching. Furthermore, - * the AM has probably already been prepared by another Empathy - * component. */ - tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL); content = gtk_dialog_get_content_area (dialog); @@ -436,39 +212,17 @@ empathy_invite_participant_dialog_init (EmpathyInviteParticipantDialog *self) gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); - /* Search entry */ - search_entry = gtk_entry_new (); - gtk_box_pack_start (GTK_BOX (content), search_entry, FALSE, TRUE, 6); - gtk_widget_show (search_entry); - - g_signal_connect (search_entry, "changed", - G_CALLBACK (search_text_changed), self); - - /* Add the treeview */ - mgr = empathy_individual_manager_dup_singleton (); - self->priv->store = empathy_individual_store_new (mgr); - g_object_unref (mgr); + /* contact chooser */ + self->priv->chooser = empathy_contact_chooser_new (); - empathy_individual_store_set_show_groups (self->priv->store, FALSE); + empathy_contact_chooser_set_filter_func ( + EMPATHY_CONTACT_CHOOSER (self->priv->chooser), filter_individual, self); - self->priv->view = empathy_individual_view_new (self->priv->store, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE); + gtk_box_pack_start (GTK_BOX (content), self->priv->chooser, TRUE, TRUE, 6); + gtk_widget_show (self->priv->chooser); - empathy_individual_view_set_custom_filter (self->priv->view, - filter_func, self); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view)); - - g_signal_connect (selection, "changed", - G_CALLBACK (view_selection_changed_cb), self); - - scroll = gtk_scrolled_window_new (NULL, NULL); - - gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (self->priv->view)); - - gtk_box_pack_start (GTK_BOX (content), scroll, TRUE, TRUE, 6); - gtk_widget_show (GTK_WIDGET (self->priv->view)); - gtk_widget_show (scroll); + g_signal_connect (self->priv->chooser, "selection-changed", + G_CALLBACK (selection_changed_cb), self); self->priv->invite_button = gtk_dialog_add_button (dialog, _("Invite"), GTK_RESPONSE_ACCEPT); @@ -481,6 +235,35 @@ empathy_invite_participant_dialog_init (EmpathyInviteParticipantDialog *self) gtk_window_set_default_size (GTK_WINDOW (self), -1, 400); } +static void +empathy_invite_participant_dialog_class_init ( + EmpathyInviteParticipantDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = invite_participant_dialog_get_property; + object_class->set_property = invite_participant_dialog_set_property; + object_class->constructed = invite_participant_dialog_constructed; + object_class->dispose = invite_participant_dialog_dispose; + + g_type_class_add_private (object_class, + sizeof (EmpathyInviteParticipantDialogPrivate)); + + g_object_class_install_property (object_class, + PROP_TP_CHAT, + g_param_spec_object ("tp-chat", "EmpathyTpChat", "EmpathyTpChat", + EMPATHY_TYPE_TP_CHAT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +empathy_invite_participant_dialog_init (EmpathyInviteParticipantDialog *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + self, EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG, + EmpathyInviteParticipantDialogPrivate); +} + GtkWidget * empathy_invite_participant_dialog_new (GtkWindow *parent, EmpathyTpChat *tp_chat) @@ -504,7 +287,8 @@ empathy_invite_participant_dialog_get_selected ( FolksIndividual *individual; TpContact *contact; - individual = empathy_individual_view_dup_selected (self->priv->view); + individual = empathy_contact_chooser_dup_selected ( + EMPATHY_CONTACT_CHOOSER (self->priv->chooser)); if (individual == NULL) return NULL; diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c index a9e76a286..3ecb939a4 100644 --- a/src/empathy-main-window.c +++ b/src/empathy-main-window.c @@ -1875,9 +1875,9 @@ main_window_edit_blocked_contacts_cb (GtkAction *action, G_CALLBACK (gtk_widget_destroy), NULL); } -static void -main_window_edit_preferences_cb (GtkAction *action, - EmpathyMainWindow *window) +void +empathy_main_window_show_preferences (EmpathyMainWindow *window, + const gchar *tab) { EmpathyMainWindowPriv *priv = GET_PRIV (window); @@ -1890,6 +1890,17 @@ main_window_edit_preferences_cb (GtkAction *action, } else { gtk_window_present (GTK_WINDOW (priv->preferences)); } + + if (tab != NULL) + empathy_preferences_show_tab ( + EMPATHY_PREFERENCES (priv->preferences), tab); +} + +static void +main_window_edit_preferences_cb (GtkAction *action, + EmpathyMainWindow *window) +{ + empathy_main_window_show_preferences (window, NULL); } static void @@ -1903,42 +1914,7 @@ static void main_window_help_debug_cb (GtkAction *action, EmpathyMainWindow *window) { - GdkDisplay *display; - GError *error = NULL; - gchar *path; - GAppInfo *app_info; - GdkAppLaunchContext *context = NULL; - - /* Try to run from source directory if possible */ - path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src", - "empathy-debugger", NULL); - - if (!g_file_test (path, G_FILE_TEST_EXISTS)) { - g_free (path); - path = g_build_filename (BIN_DIR, "empathy-debugger", NULL); - } - - app_info = g_app_info_create_from_commandline (path, NULL, 0, &error); - if (app_info == NULL) { - DEBUG ("Failed to create app info: %s", error->message); - g_error_free (error); - goto out; - } - - display = gdk_display_get_default (); - context = gdk_display_get_app_launch_context (display); - - if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context, - &error)) { - g_warning ("Failed to open debug window: %s", error->message); - g_error_free (error); - goto out; - } - -out: - tp_clear_object (&app_info); - tp_clear_object (&context); - g_free (path); + empathy_launch_program (BIN_DIR, "empathy-debugger", NULL); } static void diff --git a/src/empathy-main-window.h b/src/empathy-main-window.h index 38879fb29..cb0ae3cf0 100644 --- a/src/empathy-main-window.h +++ b/src/empathy-main-window.h @@ -53,6 +53,9 @@ GType empathy_main_window_get_type (void); GtkWidget *empathy_main_window_dup (void); +void empathy_main_window_show_preferences (EmpathyMainWindow *window, + const gchar *tab); + G_END_DECLS #endif /* __EMPATHY_MAIN_WINDOW_H__ */ diff --git a/src/empathy-mic-menu.c b/src/empathy-mic-menu.c new file mode 100644 index 000000000..dc3d0f006 --- /dev/null +++ b/src/empathy-mic-menu.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * GtkAction code based on gnome-terminal's TerminalTabsMenu object. + * Thanks guys! + */ + +#include <config.h> + +#include <gtk/gtk.h> + +#include "empathy-mic-menu.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_VOIP +#include <libempathy/empathy-debug.h> + +struct _EmpathyMicMenuPrivate +{ + /* Borrowed ref; the call window actually owns us. */ + EmpathyCallWindow *window; + + /* Given away ref; the call window's UI manager now owns this. */ + GtkActionGroup *action_group; + + /* An invisible radio action so new microphones are always in the + * same radio group. */ + GtkAction *anchor_action; + + /* The merge ID used with the UI manager. We need to keep this + * around so in _clean we can remove all the items we've added + * before and start again. */ + guint ui_id; + + /* TRUE if we're in _update and so calling _set_active. */ + gboolean in_update; + + /* Queue of GtkRadioActions. */ + GQueue *microphones; +}; + +G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT); + +#define MONITOR_KEY "empathy-mic-menu-is-monitor" + +enum +{ + PROP_WINDOW = 1, +}; + +static void empathy_mic_menu_update (EmpathyMicMenu *self); + +static void +empathy_mic_menu_init (EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuPrivate); + + self->priv = priv; +} + +static void +empathy_mic_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (object); + EmpathyMicMenuPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_WINDOW: + priv->window = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +empathy_mic_menu_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (object); + EmpathyMicMenuPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_WINDOW: + g_value_set_object (value, priv->window); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +empathy_mic_menu_clean (EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GtkUIManager *ui_manager; + + if (priv->ui_id == 0) + return; + + ui_manager = empathy_call_window_get_ui_manager (priv->window); + + gtk_ui_manager_remove_ui (ui_manager, priv->ui_id); + gtk_ui_manager_ensure_update (ui_manager); + priv->ui_id = 0; +} + +static void +empathy_mic_menu_change_mic_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object); + EmpathyMicMenu *self = user_data; + GError *error = NULL; + + if (!empathy_audio_src_change_microphone_finish (audio, result, &error)) + { + DEBUG ("Failed to change microphone: %s", error->message); + g_clear_error (&error); + + /* We call update here because if this change operation failed + * and we don't update the menu items, it'll point to the wrong + * device. We don't want to call it if the change was successful + * because we'll get the notify::microphone signal fired in a + * bit and the current value hasn't changed so it'd keep jumping + * between these values like there's no tomorrow, etc. */ + empathy_mic_menu_update (self); + } +} + +static void +empathy_mic_menu_activate_cb (GtkToggleAction *action, + EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + EmpathyGstAudioSrc *audio; + gint value; + + if (priv->in_update) + return; + + audio = empathy_call_window_get_audio_src (priv->window); + + g_object_get (action, "value", &value, NULL); + + empathy_audio_src_change_microphone_async (audio, value, + empathy_mic_menu_change_mic_cb, self); +} + +static void +empathy_mic_menu_update (EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GList *l; + GtkUIManager *ui_manager; + EmpathyGstAudioSrc *audio; + guint current_mic; + + ui_manager = empathy_call_window_get_ui_manager (priv->window); + + audio = empathy_call_window_get_audio_src (priv->window); + current_mic = empathy_audio_src_get_microphone (audio); + + empathy_mic_menu_clean (self); + priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager); + + for (l = priv->microphones->head; l != NULL; l = l->next) + { + GtkRadioAction *action = l->data; + const gchar *name = gtk_action_get_name (GTK_ACTION (action)); + gint value; + gboolean active; + + g_object_get (action, "value", &value, NULL); + + active = (value == (gint) current_mic); + + if (active) + { + priv->in_update = TRUE; + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + priv->in_update = FALSE; + } + + /* If action is a monitor then don't show it in the UI, BUT do + * display it regardless if it is the current device. This is so + * we don't have a rubbish UI by showing monitor devices in + * Empathy, but still show the correct device when someone plays + * with pavucontrol. */ + if (g_object_get_data (G_OBJECT (action), MONITOR_KEY) != NULL + && !active) + continue; + + gtk_ui_manager_add_ui (ui_manager, priv->ui_id, + /* TODO: this should probably be passed from the call + * window, seeing that it's a reference to + * empathy-call-window.ui. */ + "/menubar1/edit/menumicrophone", + name, name, GTK_UI_MANAGER_MENUITEM, FALSE); + } +} + +static void +empathy_mic_menu_add_microphone (EmpathyMicMenu *self, + const gchar *name, + const gchar *description, + guint source_idx, + gboolean is_monitor) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GtkRadioAction *action; + GSList *group; + + action = gtk_radio_action_new (name, description, NULL, NULL, source_idx); + gtk_action_group_add_action_with_accel (priv->action_group, + GTK_ACTION (action), NULL); + + /* Set MONITOR_KEY on the action to non-NULL if it's a monitor + * because we don't want to show monitors if we can help it. */ + if (is_monitor) + { + g_object_set_data (G_OBJECT (action), MONITOR_KEY, + GUINT_TO_POINTER (TRUE)); + } + + group = gtk_radio_action_get_group (GTK_RADIO_ACTION (priv->anchor_action)); + gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group); + + g_queue_push_tail (priv->microphones, action); + + g_signal_connect (action, "activate", + G_CALLBACK (empathy_mic_menu_activate_cb), self); +} + +static void +empathy_mic_menu_notify_microphone_cb (EmpathyGstAudioSrc *audio, + GParamSpec *pspec, + EmpathyMicMenu *self) +{ + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio, + guint source_idx, + const gchar *name, + const gchar *description, + gboolean is_monitor, + EmpathyMicMenu *self) +{ + empathy_mic_menu_add_microphone (self, name, description, + source_idx, is_monitor); + + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio, + guint source_idx, + EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GList *l; + + for (l = priv->microphones->head; l != NULL; l = l->next) + { + GtkRadioAction *action = l->data; + gint value; + + g_object_get (action, "value", &value, NULL); + + if (value != (gint) source_idx) + { + action = NULL; + continue; + } + + g_signal_handlers_disconnect_by_func (action, + G_CALLBACK (empathy_mic_menu_activate_cb), self); + + gtk_action_group_remove_action (priv->action_group, GTK_ACTION (action)); + g_queue_remove (priv->microphones, action); + break; + } + + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_get_microphones_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object); + EmpathyMicMenu *self = user_data; + GError *error = NULL; + const GList *mics = NULL; + + mics = empathy_audio_src_get_microphones_finish (audio, result, &error); + + if (error != NULL) + { + DEBUG ("Failed to get microphone list: %s", error->message); + g_clear_error (&error); + return; + } + + for (; mics != NULL; mics = mics->next) + { + EmpathyAudioSrcMicrophone *mic = mics->data; + + empathy_mic_menu_add_microphone (self, mic->name, + mic->description, mic->index, mic->is_monitor); + } + + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_constructed (GObject *obj) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj); + EmpathyMicMenuPrivate *priv = self->priv; + GtkUIManager *ui_manager; + EmpathyGstAudioSrc *audio; + + g_assert (EMPATHY_IS_CALL_WINDOW (priv->window)); + + ui_manager = empathy_call_window_get_ui_manager (priv->window); + audio = empathy_call_window_get_audio_src (priv->window); + + g_assert (GTK_IS_UI_MANAGER (ui_manager)); + g_assert (EMPATHY_IS_GST_AUDIO_SRC (audio)); + + /* Okay let's go go go. */ + + priv->action_group = gtk_action_group_new ("EmpathyMicMenu"); + gtk_ui_manager_insert_action_group (ui_manager, priv->action_group, -1); + /* the UI manager now owns this */ + g_object_unref (priv->action_group); + + priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION, + "name", "EmpathyMicMenuAnchorAction", + NULL); + gtk_action_group_add_action (priv->action_group, priv->anchor_action); + g_object_unref (priv->anchor_action); + + tp_g_signal_connect_object (audio, "notify::microphone", + G_CALLBACK (empathy_mic_menu_notify_microphone_cb), self, 0); + tp_g_signal_connect_object (audio, "microphone-added", + G_CALLBACK (empathy_mic_menu_microphone_added_cb), self, 0); + tp_g_signal_connect_object (audio, "microphone-removed", + G_CALLBACK (empathy_mic_menu_microphone_removed_cb), self, 0); + + priv->microphones = g_queue_new (); + + empathy_audio_src_get_microphones_async (audio, + empathy_mic_menu_get_microphones_cb, self); +} + +static void +empathy_mic_menu_dispose (GObject *obj) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj); + EmpathyMicMenuPrivate *priv = self->priv; + + if (priv->microphones != NULL) + g_queue_free (priv->microphones); + priv->microphones = NULL; + + G_OBJECT_CLASS (empathy_mic_menu_parent_class)->dispose (obj); +} + +static void +empathy_mic_menu_class_init (EmpathyMicMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = empathy_mic_menu_set_property; + object_class->get_property = empathy_mic_menu_get_property; + object_class->constructed = empathy_mic_menu_constructed; + object_class->dispose = empathy_mic_menu_dispose; + + g_object_class_install_property (object_class, PROP_WINDOW, + g_param_spec_object ("window", "window", "window", + EMPATHY_TYPE_CALL_WINDOW, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof (EmpathyMicMenuPrivate)); +} + +EmpathyMicMenu * +empathy_mic_menu_new (EmpathyCallWindow *window) +{ + return g_object_new (EMPATHY_TYPE_MIC_MENU, + "window", window, + NULL); +} diff --git a/src/empathy-mic-menu.h b/src/empathy-mic-menu.h new file mode 100644 index 000000000..87691ae6d --- /dev/null +++ b/src/empathy-mic-menu.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __EMPATHY_MIC_MENU_H__ +#define __EMPATHY_MIC_MENU_H__ + +#include <glib-object.h> + +#include "empathy-call-window.h" + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_MIC_MENU (empathy_mic_menu_get_type ()) +#define EMPATHY_MIC_MENU(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_MIC_MENU, EmpathyMicMenu)) +#define EMPATHY_MIC_MENU_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuClass)) +#define EMPATHY_IS_MIC_MENU(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_MIC_MENU)) +#define EMPATHY_IS_MIC_MENU_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_MIC_MENU)) +#define EMPATHY_MIC_MENU_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuClass)) + +typedef struct _EmpathyMicMenu EmpathyMicMenu; +typedef struct _EmpathyMicMenuPrivate EmpathyMicMenuPrivate; +typedef struct _EmpathyMicMenuClass EmpathyMicMenuClass; + +struct _EmpathyMicMenu +{ + GObject parent; + EmpathyMicMenuPrivate *priv; +}; + +struct _EmpathyMicMenuClass +{ + GObjectClass parent_class; +}; + +GType empathy_mic_menu_get_type (void) G_GNUC_CONST; + +EmpathyMicMenu * empathy_mic_menu_new (EmpathyCallWindow *window); + +G_END_DECLS + +#endif /* __EMPATHY_MIC_MENU_H__ */ diff --git a/src/empathy-preferences.c b/src/empathy-preferences.c index 9c737b991..9dc7f58bf 100644 --- a/src/empathy-preferences.c +++ b/src/empathy-preferences.c @@ -52,6 +52,17 @@ G_DEFINE_TYPE (EmpathyPreferences, empathy_preferences, GTK_TYPE_DIALOG); #define GET_PRIV(self) ((EmpathyPreferencesPriv *)((EmpathyPreferences *) self)->priv) +static const gchar * empathy_preferences_tabs[] = +{ + "general", + "notifications", + "sounds", + "calls", + "location", + "spell", + "themes", +}; + struct _EmpathyPreferencesPriv { GtkWidget *notebook; @@ -72,6 +83,10 @@ struct _EmpathyPreferencesPriv { GtkWidget *checkbutton_notifications_contact_signin; GtkWidget *checkbutton_notifications_contact_signout; + GtkWidget *scale_call_volume; + GtkWidget *adj_call_volume; + GtkWidget *echo_cancellation; + GtkWidget *treeview_spell_checker; GtkWidget *checkbutton_location_publish; @@ -90,6 +105,7 @@ struct _EmpathyPreferencesPriv { GSettings *gsettings; GSettings *gsettings_chat; + GSettings *gsettings_call; GSettings *gsettings_loc; GSettings *gsettings_notify; GSettings *gsettings_sound; @@ -256,6 +272,18 @@ preferences_setup_widgets (EmpathyPreferences *preferences) "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (priv->gsettings_call, + EMPATHY_PREFS_CALL_SOUND_VOLUME, + priv->adj_call_volume, + "value", + G_SETTINGS_BIND_DEFAULT); + + g_settings_bind (priv->gsettings_call, + EMPATHY_PREFS_CALL_ECHO_CANCELLATION, + priv->echo_cancellation, + "active", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind (priv->gsettings, EMPATHY_PREFS_AUTOCONNECT, priv->checkbutton_autoconnect, @@ -1071,6 +1099,13 @@ preferences_themes_setup (EmpathyPreferences *preferences) preferences); } +static gchar * +preferences_call_format_volume_cb (GtkScale *scale, + gdouble value) +{ + return g_strdup_printf ("%g%%", value); +} + static void empathy_preferences_response (GtkDialog *widget, gint response) @@ -1087,6 +1122,7 @@ empathy_preferences_finalize (GObject *self) g_object_unref (priv->gsettings); g_object_unref (priv->gsettings_chat); + g_object_unref (priv->gsettings_call); g_object_unref (priv->gsettings_loc); g_object_unref (priv->gsettings_notify); g_object_unref (priv->gsettings_sound); @@ -1117,6 +1153,8 @@ empathy_preferences_init (EmpathyPreferences *preferences) GtkBuilder *gui; gchar *filename; GtkWidget *page; + GtkWidget *call_volume_scale_box; + GtkWidget *call_volume_bar_box; priv = preferences->priv = G_TYPE_INSTANCE_GET_PRIVATE (preferences, EMPATHY_TYPE_PREFERENCES, EmpathyPreferencesPriv); @@ -1159,6 +1197,11 @@ empathy_preferences_init (EmpathyPreferences *preferences) "checkbutton_location_resource_network", &priv->checkbutton_location_resource_network, "checkbutton_location_resource_cell", &priv->checkbutton_location_resource_cell, "checkbutton_location_resource_gps", &priv->checkbutton_location_resource_gps, + "call_volume_scale_box", &call_volume_scale_box, + "call_volume_bar_box", &call_volume_bar_box, + "call_volume_scale", &priv->scale_call_volume, + "call_volume_adjustment", &priv->adj_call_volume, + "call_echo_cancellation", &priv->echo_cancellation, NULL); g_free (filename); @@ -1169,6 +1212,7 @@ empathy_preferences_init (EmpathyPreferences *preferences) priv->gsettings = g_settings_new (EMPATHY_PREFS_SCHEMA); priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA); + priv->gsettings_call = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA); priv->gsettings_loc = g_settings_new (EMPATHY_PREFS_LOCATION_SCHEMA); priv->gsettings_notify = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA); priv->gsettings_sound = g_settings_new (EMPATHY_PREFS_SOUNDS_SCHEMA); @@ -1182,6 +1226,15 @@ empathy_preferences_init (EmpathyPreferences *preferences) preferences, 0); preferences_preview_theme_changed_cb (priv->theme_manager, preferences); + g_signal_connect (priv->scale_call_volume, "format-value", + G_CALLBACK (preferences_call_format_volume_cb), + preferences); + +#ifndef HAVE_CALL + gtk_widget_hide (call_volume_scale_box); + gtk_widget_hide (call_volume_bar_box); +#endif + preferences_themes_setup (preferences); preferences_setup_widgets (preferences); @@ -1194,11 +1247,11 @@ empathy_preferences_init (EmpathyPreferences *preferences) preferences_sound_load (preferences); if (empathy_spell_supported ()) { - page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 2); + page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), EMPATHY_PREFERENCES_TAB_SPELL); gtk_widget_show (page); } - page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 3); + page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), EMPATHY_PREFERENCES_TAB_LOCATION); #ifdef HAVE_GEOCLUE gtk_widget_show (page); #else @@ -1206,6 +1259,29 @@ empathy_preferences_init (EmpathyPreferences *preferences) #endif } +static EmpathyPreferencesTab +empathy_preferences_tab_from_string (const gchar *str) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (empathy_preferences_tabs); i++) + { + if (!tp_strdiff (str, empathy_preferences_tabs[i])) + return i; + } + + g_warn_if_reached (); + return -1; +} + +const gchar * +empathy_preferences_tab_to_string (EmpathyPreferencesTab tab) +{ + g_return_val_if_fail (tab < G_N_ELEMENTS (empathy_preferences_tabs), NULL); + + return empathy_preferences_tabs[tab]; +} + GtkWidget * empathy_preferences_new (GtkWindow *parent) { @@ -1222,3 +1298,13 @@ empathy_preferences_new (GtkWindow *parent) return self; } + +void +empathy_preferences_show_tab (EmpathyPreferences *self, + const gchar *page) +{ + EmpathyPreferencesPriv *priv = GET_PRIV (self); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), + empathy_preferences_tab_from_string (page)); +} diff --git a/src/empathy-preferences.h b/src/empathy-preferences.h index 0d7204fcc..fef0646d7 100644 --- a/src/empathy-preferences.h +++ b/src/empathy-preferences.h @@ -50,12 +50,28 @@ struct _EmpathyPreferencesClass { GtkDialogClass parent_class; }; +/* Keep this enum and the array in empathy-preferences.c in sync */ +typedef enum +{ + EMPATHY_PREFERENCES_TAB_GENERAL, + EMPATHY_PREFERENCES_TAB_NOTIFICATIONS, + EMPATHY_PREFERENCES_TAB_SOUNDS, + EMPATHY_PREFERENCES_TAB_CALLS, + EMPATHY_PREFERENCES_TAB_LOCATION, + EMPATHY_PREFERENCES_TAB_SPELL, + EMPATHY_PREFERENCES_TAB_THEMES, +} EmpathyPreferencesTab; + GType empathy_preferences_get_type (void); GtkWidget *empathy_preferences_new (GtkWindow *parent); -G_END_DECLS +void empathy_preferences_show_tab (EmpathyPreferences *self, + const gchar *tab); -#endif /* __EMPATHY_PREFERENCES_H__ */ +const gchar * +empathy_preferences_tab_to_string (EmpathyPreferencesTab tab); +G_END_DECLS +#endif /* __EMPATHY_PREFERENCES_H__ */ diff --git a/src/empathy-preferences.ui b/src/empathy-preferences.ui index 2549d51ad..791f3fbd9 100644 --- a/src/empathy-preferences.ui +++ b/src/empathy-preferences.ui @@ -1,6 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <interface> <requires lib="gtk+" version="2.16"/> + <object class="GtkAdjustment" id="call_volume_adjustment"> + <property name="upper">150</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> <object class="GtkNotebook" id="notebook"> <property name="visible">True</property> <property name="can_focus">True</property> @@ -457,6 +462,163 @@ </packing> </child> <child> + <object class="GtkBox" id="vbox6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">12</property> + <property name="orientation">vertical</property> + <property name="spacing">18</property> + <child> + <object class="GtkFrame" id="frame2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="left_padding">12</property> + <child> + <object class="GtkBox" id="box4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="call_volume_scale_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Input volume</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale" id="call_volume_scale"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">call_volume_adjustment</property> + <property name="round_digits">0</property> + <property name="digits">0</property> + <property name="value_pos">bottom</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="call_volume_bar_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Input level</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="progressbar2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="call_echo_cancellation"> + <property name="label">_Echo Cancellation</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Audio</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label608"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Calls</property> + </object> + <packing> + <property name="position">3</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> <object class="GtkBox" id="vbox1"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -667,7 +829,7 @@ </child> </object> <packing> - <property name="position">3</property> + <property name="position">4</property> </packing> </child> <child type="tab"> @@ -677,7 +839,7 @@ <property name="label" translatable="yes">Location</property> </object> <packing> - <property name="position">3</property> + <property name="position">4</property> <property name="tab_fill">False</property> </packing> </child> @@ -796,7 +958,7 @@ </child> </object> <packing> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> <child type="tab"> @@ -806,7 +968,7 @@ <property name="label" translatable="yes">Spell Checking</property> </object> <packing> - <property name="position">4</property> + <property name="position">5</property> <property name="tab_fill">False</property> </packing> </child> @@ -946,7 +1108,7 @@ </child> </object> <packing> - <property name="position">5</property> + <property name="position">6</property> </packing> </child> <child type="tab"> @@ -956,9 +1118,15 @@ <property name="label" translatable="yes">Themes</property> </object> <packing> - <property name="position">5</property> + <property name="position">6</property> <property name="tab_fill">False</property> </packing> </child> </object> + <object class="GtkSizeGroup" id="sizegroup1"> + <widgets> + <widget name="label12"/> + <widget name="label13"/> + </widgets> + </object> </interface> diff --git a/src/empathy-rounded-actor.c b/src/empathy-rounded-actor.c new file mode 100644 index 000000000..463a73c61 --- /dev/null +++ b/src/empathy-rounded-actor.c @@ -0,0 +1,68 @@ +/* + * empathy-rounded-actor.c - Source for EmpathyRoundedActor + * Copyright (C) 2011 Collabora Ltd. + * @author Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <clutter/clutter.h> +#include <clutter-gtk/clutter-gtk.h> + +#include "empathy-rounded-actor.h" + +G_DEFINE_TYPE(EmpathyRoundedActor, empathy_rounded_actor, GTK_CLUTTER_TYPE_ACTOR) + +static void +empathy_rounded_actor_paint (ClutterActor *actor) +{ + ClutterActorBox allocation = { 0, }; + gfloat width, height; + + clutter_actor_get_allocation_box (actor, &allocation); + clutter_actor_box_get_size (&allocation, &width, &height); + + cogl_path_new (); + + /* create and store a path describing a rounded rectangle */ + cogl_path_round_rectangle (0, 0, width, height, height / 2, 0.1); + + cogl_clip_push_from_path (); + + CLUTTER_ACTOR_CLASS (empathy_rounded_actor_parent_class)->paint (actor); + + cogl_clip_pop (); +} + +static void +empathy_rounded_actor_init (EmpathyRoundedActor *self) +{ +} + +static void +empathy_rounded_actor_class_init (EmpathyRoundedActorClass *klass) +{ + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + actor_class->paint = empathy_rounded_actor_paint; +} + +ClutterActor * +empathy_rounded_actor_new (void) +{ + return CLUTTER_ACTOR ( + g_object_new (EMPATHY_TYPE_ROUNDED_ACTOR, NULL)); +} diff --git a/src/empathy-rounded-actor.h b/src/empathy-rounded-actor.h new file mode 100644 index 000000000..e4c83b078 --- /dev/null +++ b/src/empathy-rounded-actor.h @@ -0,0 +1,63 @@ +/* + * empathy-rounded-actor.h - Header for EmpathyRoundedActor + * Copyright (C) 2011 Collabora Ltd. + * @author Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __EMPATHY_ROUNDED_ACTOR_H__ +#define __EMPATHY_ROUNDED_ACTOR_H__ + +#include <glib-object.h> +#include <clutter-gtk/clutter-gtk.h> + +G_BEGIN_DECLS + +typedef struct _EmpathyRoundedActor EmpathyRoundedActor; +typedef struct _EmpathyRoundedActorClass EmpathyRoundedActorClass; + +struct _EmpathyRoundedActorClass { + GtkClutterActorClass parent_class; +}; + +struct _EmpathyRoundedActor { + GtkClutterActor parent; +}; + +GType empathy_rounded_actor_get_type (void); + +/* TYPE MACROS */ +#define EMPATHY_TYPE_ROUNDED_ACTOR \ + (empathy_rounded_actor_get_type ()) +#define EMPATHY_ROUNDED_ACTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_ROUNDED_ACTOR, \ + EmpathyRoundedActor)) +#define EMPATHY_ROUNDED_ACTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_ROUNDED_ACTOR, \ + EmpathyRoundedActorClass)) +#define EMPATHY_IS_ROUNDED_ACTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_ROUNDED_ACTOR)) +#define EMPATHY_IS_ROUNDED_ACTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_ROUNDED_ACTOR)) +#define EMPATHY_ROUNDED_ACTOR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_ROUNDED_ACTOR, \ + EmpathyRoundedActorClass)) + +ClutterActor *empathy_rounded_actor_new (void); + +G_END_DECLS + +#endif /* #ifndef __EMPATHY_ROUNDED_ACTOR_H__*/ diff --git a/src/empathy-streamed-media-window.c b/src/empathy-streamed-media-window.c index 19b7b2fad..d60c0753b 100644 --- a/src/empathy-streamed-media-window.c +++ b/src/empathy-streamed-media-window.c @@ -54,8 +54,6 @@ #include "empathy-video-src.h" #include "ev-sidebar.h" -#define BUTTON_ID "empathy-call-dtmf-button-id" - #define CONTENT_HBOX_BORDER_WIDTH 6 #define CONTENT_HBOX_SPACING 3 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3 @@ -346,7 +344,7 @@ dtmf_button_pressed_cb (GtkButton *button, EmpathyStreamedMediaWindow *window) g_object_get (priv->handler, "tp-call", &call, NULL); - button_quark = g_quark_from_static_string (BUTTON_ID); + button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID); event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button), button_quark)); @@ -369,51 +367,6 @@ dtmf_button_released_cb (GtkButton *button, EmpathyStreamedMediaWindow *window) } static GtkWidget * -empathy_streamed_media_window_create_dtmf (EmpathyStreamedMediaWindow *self) -{ - GtkWidget *table; - int i; - GQuark button_quark; - struct { - const gchar *label; - TpDTMFEvent event; - } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 }, - { "2", TP_DTMF_EVENT_DIGIT_2 }, - { "3", TP_DTMF_EVENT_DIGIT_3 }, - { "4", TP_DTMF_EVENT_DIGIT_4 }, - { "5", TP_DTMF_EVENT_DIGIT_5 }, - { "6", TP_DTMF_EVENT_DIGIT_6 }, - { "7", TP_DTMF_EVENT_DIGIT_7 }, - { "8", TP_DTMF_EVENT_DIGIT_8 }, - { "9", TP_DTMF_EVENT_DIGIT_9 }, - { "#", TP_DTMF_EVENT_HASH }, - { "0", TP_DTMF_EVENT_DIGIT_0 }, - { "*", TP_DTMF_EVENT_ASTERISK }, - { NULL, } }; - - button_quark = g_quark_from_static_string (BUTTON_ID); - - table = gtk_table_new (4, 3, TRUE); - - for (i = 0; dtmfbuttons[i].label != NULL; i++) - { - GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label); - gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1, - i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1); - - g_object_set_qdata (G_OBJECT (button), button_quark, - GUINT_TO_POINTER (dtmfbuttons[i].event)); - - g_signal_connect (G_OBJECT (button), "pressed", - G_CALLBACK (dtmf_button_pressed_cb), self); - g_signal_connect (G_OBJECT (button), "released", - G_CALLBACK (dtmf_button_released_cb), self); - } - - return table; -} - -static GtkWidget * empathy_streamed_media_window_create_video_input_add_slider (EmpathyStreamedMediaWindow *self, gchar *label_text, GtkWidget *bin) { @@ -1180,7 +1133,9 @@ empathy_streamed_media_window_init (EmpathyStreamedMediaWindow *self) ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input", _("Video input"), page); - priv->dtmf_panel = empathy_streamed_media_window_create_dtmf (self); + priv->dtmf_panel = empathy_create_dtmf_dialpad (G_OBJECT (self), + G_CALLBACK (dtmf_button_pressed_cb), + G_CALLBACK (dtmf_button_released_cb)); ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad", _("Dialpad"), priv->dtmf_panel); diff --git a/src/empathy-video-src.c b/src/empathy-video-src.c index 15d8f79e1..ceb344215 100644 --- a/src/empathy-video-src.c +++ b/src/empathy-video-src.c @@ -123,8 +123,8 @@ empathy_video_src_init (EmpathyGstVideoSrc *obj) /* allocate any data required by the object here */ if ((element = empathy_gst_add_to_bin (GST_BIN (obj), - NULL, "gconfvideosrc")) == NULL) - g_error ("Couldn't add \"gconfvideosrc\" (gst-plugins-good missing?)"); + NULL, "v4l2src")) == NULL) + g_error ("Couldn't add \"v4l2src\" (gst-plugins-good missing?)"); /* we need to save our source to priv->src */ priv->src = element; @@ -361,3 +361,27 @@ out: return result; } +void +empathy_video_src_change_device (EmpathyGstVideoSrc *self, + const gchar *device) +{ + EmpathyGstVideoSrcPrivate *priv = EMPATHY_GST_VIDEO_SRC_GET_PRIVATE (self); + GstState state; + + gst_element_get_state (priv->src, &state, NULL, 0); + + gst_element_set_state (priv->src, GST_STATE_NULL); + g_object_set (priv->src, "device", device, NULL); + gst_element_set_state (priv->src, state); +} + +gchar * +empathy_video_src_dup_device (EmpathyGstVideoSrc *self) +{ + EmpathyGstVideoSrcPrivate *priv = EMPATHY_GST_VIDEO_SRC_GET_PRIVATE (self); + gchar *device; + + g_object_get (priv->src, "device", &device, NULL); + + return device; +} diff --git a/src/empathy-video-src.h b/src/empathy-video-src.h index fae5563eb..6a88b79eb 100644 --- a/src/empathy-video-src.h +++ b/src/empathy-video-src.h @@ -81,6 +81,10 @@ void empathy_video_src_set_channel (GstElement *src, guint empathy_video_src_get_channel (GstElement *src, EmpathyGstVideoSrcChannel channel); +void empathy_video_src_change_device (EmpathyGstVideoSrc *self, + const gchar *device); +gchar * empathy_video_src_dup_device (EmpathyGstVideoSrc *self); + G_END_DECLS #endif /* #ifndef __EMPATHY_GST_VIDEO_SRC_H__*/ diff --git a/src/empathy.c b/src/empathy.c index 6e2b165dc..3506319fa 100644 --- a/src/empathy.c +++ b/src/empathy.c @@ -106,6 +106,8 @@ struct _EmpathyApp /* Properties */ gboolean no_connect; gboolean start_hidden; + gboolean show_preferences; + gchar *preferences_tab; gboolean activated; @@ -171,6 +173,8 @@ empathy_app_finalize (GObject *object) void (*finalize) (GObject *) = G_OBJECT_CLASS (empathy_app_parent_class)->finalize; + g_free (self->preferences_tab); + if (self->window != NULL) gtk_widget_destroy (self->window); @@ -230,11 +234,31 @@ new_ft_handler_cb (EmpathyFTFactory *factory, g_object_unref (handler); } +static gboolean +empathy_app_local_command_line (GApplication *app, + gchar ***arguments, + gint *exit_status); + static int empathy_app_command_line (GApplication *app, GApplicationCommandLine *cmdline) { EmpathyApp *self = (EmpathyApp *) app; + gchar **args, **argv; + gint argc, exit_status, i; + + args = g_application_command_line_get_arguments (cmdline, &argc); + /* We have to make an extra copy of the array, since g_option_context_parse() + * assumes that it can remove strings from the array without freeing them. */ + argv = g_new (gchar*, argc + 1); + for (i = 0; i <= argc; i++) + argv[i] = args[i]; + + if (empathy_app_local_command_line (app, &argv, &exit_status)) + DEBUG ("failed to parse command line!"); + + g_free (argv); + g_strfreev (args); if (!self->activated) { @@ -273,6 +297,10 @@ empathy_app_command_line (GApplication *app, self->start_hidden = FALSE; } + if (self->show_preferences) + empathy_main_window_show_preferences (EMPATHY_MAIN_WINDOW (self->window), + self->preferences_tab); + if (!self->start_hidden) empathy_window_present (GTK_WINDOW (self->window)); @@ -284,6 +312,22 @@ empathy_app_command_line (GApplication *app, } static gboolean +preferences_cb (const char *option_name, + const char *value, + gpointer data, + GError **error) +{ + EmpathyApp *self = data; + + self->show_preferences = TRUE; + + g_free (self->preferences_tab); + self->preferences_tab = g_strdup (value); + + return TRUE; +} + +static gboolean show_version_cb (const char *option_name, const char *value, gpointer data, @@ -303,6 +347,7 @@ empathy_app_local_command_line (GApplication *app, gboolean no_connect = FALSE, start_hidden = FALSE; GOptionContext *optcontext; + GOptionGroup *group; GOptionEntry options[] = { { "no-connect", 'n', 0, G_OPTION_ARG_NONE, &no_connect, @@ -312,19 +357,32 @@ empathy_app_local_command_line (GApplication *app, 0, G_OPTION_ARG_NONE, &start_hidden, N_("Don't display the contact list or any other dialogs on startup"), NULL }, + { "show-preferences", 'p', + G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, &preferences_cb, + NULL, NULL }, { "version", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version_cb, NULL, NULL }, { NULL } }; + /* We create a group so that GOptionArgFuncs get the user data */ + group = g_option_group_new ("empathy", NULL, NULL, app, NULL); + g_option_group_add_entries (group, options); + optcontext = g_option_context_new (N_("- Empathy IM Client")); g_option_context_add_group (optcontext, gtk_get_option_group (TRUE)); - g_option_context_add_main_entries (optcontext, options, GETTEXT_PACKAGE); + g_option_context_set_main_group (optcontext, group); + g_option_context_set_translation_domain (optcontext, GETTEXT_PACKAGE); - argv = *arguments; - for (i = 0; argv[i] != NULL; i++) - argc++; + argc = g_strv_length (*arguments); + + /* We dup the args because g_option_context_parse() sets things to NULL, + * but we want to parse all the command line to the primary instance + * if necessary. */ + argv = g_new (gchar*, argc + 1); + for (i = 0; i <= argc; i++) + argv[i] = (*arguments)[i]; if (!g_option_context_parse (optcontext, &argc, &argv, &error)) { @@ -337,6 +395,8 @@ empathy_app_local_command_line (GApplication *app, retval = TRUE; } + g_free (argv); + g_option_context_free (optcontext); self->no_connect = no_connect; |