]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-sound-manager.c
Merge branch 'gnome-3-8'
[empathy.git] / libempathy-gtk / empathy-sound-manager.c
1 /*
2  * empathy-sound-manager.c - Various sound related utility functions.
3  * Copyright (C) 2009-2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #include "config.h"
21 #include "empathy-sound-manager.h"
22
23 #include <glib/gi18n-lib.h>
24
25 #include "empathy-gsettings.h"
26 #include "empathy-utils.h"
27
28 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
29 #include "empathy-debug.h"
30
31 typedef struct {
32   EmpathySound sound_id;
33   const char * event_ca_id;
34   const char * event_ca_description;
35   const char * key;
36 } EmpathySoundEntry;
37
38 typedef struct {
39   GtkWidget *widget;
40   gint sound_id;
41   guint play_interval;
42   guint replay_timeout_id;
43   EmpathySoundManager *self;
44 } EmpathyRepeatableSound;
45
46 /* NOTE: these entries MUST be in the same order than EmpathySound enum */
47 static EmpathySoundEntry sound_entries[LAST_EMPATHY_SOUND] = {
48   { EMPATHY_SOUND_MESSAGE_INCOMING, "message-new-instant",
49     N_("Received an instant message"), EMPATHY_PREFS_SOUNDS_INCOMING_MESSAGE } ,
50   { EMPATHY_SOUND_MESSAGE_OUTGOING, "message-sent-instant",
51     N_("Sent an instant message"), EMPATHY_PREFS_SOUNDS_OUTGOING_MESSAGE } ,
52   { EMPATHY_SOUND_CONVERSATION_NEW, "message-new-instant",
53     N_("Incoming chat request"), EMPATHY_PREFS_SOUNDS_NEW_CONVERSATION },
54   { EMPATHY_SOUND_CONTACT_CONNECTED, "service-login",
55     N_("Contact connected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGIN },
56   { EMPATHY_SOUND_CONTACT_DISCONNECTED, "service-logout",
57     N_("Contact disconnected"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGOUT },
58   { EMPATHY_SOUND_ACCOUNT_CONNECTED, "service-login",
59     N_("Connected to server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGIN },
60   { EMPATHY_SOUND_ACCOUNT_DISCONNECTED, "service-logout",
61     N_("Disconnected from server"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGOUT },
62   { EMPATHY_SOUND_PHONE_INCOMING, "phone-incoming-call",
63     N_("Incoming voice call"), NULL },
64   { EMPATHY_SOUND_PHONE_OUTGOING, "phone-outgoing-calling",
65     N_("Outgoing voice call"), NULL },
66   { EMPATHY_SOUND_PHONE_HANGUP, "phone-hangup",
67     N_("Voice call ended"), NULL },
68 };
69
70 G_DEFINE_TYPE (EmpathySoundManager, empathy_sound_manager, G_TYPE_OBJECT)
71
72 struct _EmpathySoundManagerPrivate
73 {
74   /* A hash table containing currently repeating sounds. The format is the
75    * following:
76    * Key: An EmpathySound
77    * Value : The EmpathyRepeatableSound associated with that EmpathySound. */
78   GHashTable *repeating_sounds;
79   GSettings *gsettings_sound;
80 };
81
82 static void
83 empathy_sound_manager_dispose (GObject *object)
84 {
85   EmpathySoundManager *self = (EmpathySoundManager *) object;
86
87   tp_clear_pointer (&self->priv->repeating_sounds, g_hash_table_unref);
88   tp_clear_object (&self->priv->gsettings_sound);
89
90   G_OBJECT_CLASS (empathy_sound_manager_parent_class)->dispose (object);
91 }
92
93 static void
94 empathy_sound_manager_class_init (EmpathySoundManagerClass *cls)
95 {
96   GObjectClass *object_class = G_OBJECT_CLASS (cls);
97
98   object_class->dispose = empathy_sound_manager_dispose;
99
100   g_type_class_add_private (cls, sizeof (EmpathySoundManagerPrivate));
101 }
102
103 static void
104 empathy_sound_widget_destroyed_cb (GtkWidget *widget,
105     gpointer user_data)
106 {
107   EmpathyRepeatableSound *repeatable_sound = user_data;
108
109   /* The sound must be stopped... If it is waiting for replay, remove
110    * it from hash table to cancel. Otherwise playing_finished_cb will be
111    * called with an error. */
112   if (repeatable_sound->replay_timeout_id != 0)
113     {
114       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
115           GINT_TO_POINTER (repeatable_sound->sound_id));
116     }
117 }
118
119 static void
120 repeating_sounds_item_delete (gpointer data)
121 {
122   EmpathyRepeatableSound *repeatable_sound = data;
123
124   if (repeatable_sound->replay_timeout_id != 0)
125     g_source_remove (repeatable_sound->replay_timeout_id);
126
127   if (repeatable_sound->widget != NULL)
128     {
129       g_signal_handlers_disconnect_by_func (repeatable_sound->widget,
130           empathy_sound_widget_destroyed_cb, repeatable_sound);
131     }
132
133   g_object_unref (repeatable_sound->self);
134
135   g_slice_free (EmpathyRepeatableSound, repeatable_sound);
136 }
137
138 static void
139 empathy_sound_manager_init (EmpathySoundManager *self)
140 {
141   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
142       EMPATHY_TYPE_SOUND_MANAGER, EmpathySoundManagerPrivate);
143
144   self->priv->repeating_sounds = g_hash_table_new_full (NULL, NULL,
145       NULL, repeating_sounds_item_delete);
146
147   self->priv->gsettings_sound = g_settings_new (EMPATHY_PREFS_SOUNDS_SCHEMA);
148 }
149
150 EmpathySoundManager *
151 empathy_sound_manager_dup_singleton (void)
152 {
153   static EmpathySoundManager *manager = NULL;
154
155   if (G_LIKELY (manager != NULL))
156       return g_object_ref (manager);
157
158   manager = g_object_new (EMPATHY_TYPE_SOUND_MANAGER, NULL);
159
160   g_object_add_weak_pointer (G_OBJECT (manager), (gpointer *) &manager);
161   return manager;
162 }
163
164 static gboolean
165 empathy_sound_pref_is_enabled (EmpathySoundManager *self,
166     EmpathySound sound_id)
167 {
168   EmpathySoundEntry *entry;
169
170   entry = &(sound_entries[sound_id]);
171   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
172
173   if (entry->key == NULL)
174     return TRUE;
175
176   if (! g_settings_get_boolean (self->priv->gsettings_sound,
177       EMPATHY_PREFS_SOUNDS_ENABLED))
178     return FALSE;
179
180   if (!empathy_check_available_state ())
181     {
182       if (g_settings_get_boolean (self->priv->gsettings_sound,
183             EMPATHY_PREFS_SOUNDS_DISABLED_AWAY))
184         return FALSE;
185     }
186
187   return g_settings_get_boolean (self->priv->gsettings_sound, entry->key);
188 }
189
190 /**
191  * empathy_sound_manager_stop:
192  * @self: a #EmpathySoundManager
193  * @sound_id: The #EmpathySound to stop playing.
194  *
195  * Stop playing a sound. If it has been stated in loop with
196  * empathy_sound_start_playing(), it will also stop replaying.
197  */
198 void
199 empathy_sound_manager_stop (EmpathySoundManager *self,
200     EmpathySound sound_id)
201 {
202   EmpathySoundEntry *entry;
203   EmpathyRepeatableSound *repeatable_sound;
204
205   g_return_if_fail (sound_id < LAST_EMPATHY_SOUND);
206
207   entry = &(sound_entries[sound_id]);
208   g_return_if_fail (entry->sound_id == sound_id);
209
210   repeatable_sound = g_hash_table_lookup (self->priv->repeating_sounds,
211       GINT_TO_POINTER (sound_id));
212   if (repeatable_sound != NULL)
213     {
214       /* The sound must be stopped... If it is waiting for replay, remove
215        * it from hash table to cancel. Otherwise we'll cancel the sound
216        * being played. */
217       if (repeatable_sound->replay_timeout_id != 0)
218         {
219           g_hash_table_remove (self->priv->repeating_sounds,
220               GINT_TO_POINTER (sound_id));
221           return;
222         }
223     }
224
225   ca_context_cancel (ca_gtk_context_get (), entry->sound_id);
226 }
227
228 static gboolean
229 empathy_sound_play_internal (GtkWidget *widget, EmpathySound sound_id,
230   ca_finish_callback_t callback, gpointer user_data)
231 {
232   EmpathySoundEntry *entry;
233   ca_context *c;
234   ca_proplist *p = NULL;
235
236   entry = &(sound_entries[sound_id]);
237   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
238
239   c = ca_gtk_context_get ();
240   ca_context_cancel (c, entry->sound_id);
241
242   DEBUG ("Play sound \"%s\" (%s)",
243          entry->event_ca_id,
244          entry->event_ca_description);
245
246   if (ca_proplist_create (&p) < 0)
247     goto failed;
248
249   if (ca_proplist_sets (p, CA_PROP_EVENT_ID, entry->event_ca_id) < 0)
250     goto failed;
251
252   if (ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION,
253           gettext (entry->event_ca_description)) < 0)
254     goto failed;
255
256   if (widget != NULL)
257     {
258       if (ca_gtk_proplist_set_for_widget (p, widget) < 0)
259         goto failed;
260     }
261
262   ca_context_play_full (ca_gtk_context_get (), entry->sound_id, p, callback,
263       user_data);
264
265   ca_proplist_destroy (p);
266
267   return TRUE;
268
269 failed:
270   if (p != NULL)
271     ca_proplist_destroy (p);
272
273   return FALSE;
274 }
275
276 /**
277  * empathy_sound_manager_play_full:
278  * @self: a #EmpathySoundManager
279  * @widget: The #GtkWidget from which the sound is originating.
280  * @sound_id: The #EmpathySound to play.
281  * @callback: The #ca_finish_callback_t function that will be called when the
282  *            sound  has stopped playing.
283  * @user_data: user data to pass to the function.
284  *
285  * Plays a sound.
286  *
287  * Returns %TRUE if the sound has successfully started playing, otherwise
288  * returning %FALSE and @callback won't be called.
289  *
290  * This function returns %FALSE if the sound is already playing in loop using
291  * %empathy_sound_start_playing.
292  *
293  * This function returns %FALSE if the sound is disabled in empathy preferences.
294  *
295  * Return value: %TRUE if the sound has successfully started playing, %FALSE
296  *               otherwise.
297  */
298 gboolean
299 empathy_sound_manager_play_full (EmpathySoundManager *self,
300     GtkWidget *widget,
301     EmpathySound sound_id,
302     ca_finish_callback_t callback,
303     gpointer user_data)
304 {
305   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), FALSE);
306   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
307
308   if (!empathy_sound_pref_is_enabled (self, sound_id))
309     return FALSE;
310
311   /* The sound might already be playing repeatedly. If it's the case, we
312    * immediadely return since there's no need to make it play again */
313   if (g_hash_table_lookup (self->priv->repeating_sounds,
314         GINT_TO_POINTER (sound_id)) != NULL)
315     return FALSE;
316
317   return empathy_sound_play_internal (widget, sound_id, callback, user_data);
318 }
319
320 /**
321  * empathy_sound_manager_play:
322  * @self: a #EmpathySoundManager
323  * @widget: The #GtkWidget from which the sound is originating.
324  * @sound_id: The #EmpathySound to play.
325  *
326  * Plays a sound. See %empathy_sound_manager_play_full for details.'
327  *
328  * Return value: %TRUE if the sound has successfully started playing, %FALSE
329  *               otherwise.
330  */
331 gboolean
332 empathy_sound_manager_play (EmpathySoundManager *self,
333     GtkWidget *widget,
334     EmpathySound sound_id)
335 {
336   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), FALSE);
337   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
338
339   return empathy_sound_manager_play_full (self, widget, sound_id, NULL, NULL);
340 }
341
342 static void playing_finished_cb (ca_context *c, guint id, int error_code,
343   gpointer user_data);
344
345 static gboolean
346 playing_timeout_cb (gpointer data)
347 {
348   EmpathyRepeatableSound *repeatable_sound = data;
349   gboolean playing;
350
351   repeatable_sound->replay_timeout_id = 0;
352
353   playing = empathy_sound_play_internal (repeatable_sound->widget,
354       repeatable_sound->sound_id, playing_finished_cb, data);
355
356   if (!playing)
357     {
358       DEBUG ("Failed to replay sound, stop repeating");
359       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
360           GINT_TO_POINTER (repeatable_sound->sound_id));
361     }
362
363   return FALSE;
364 }
365
366 static void
367 playing_finished_cb (ca_context *c, guint id, int error_code,
368   gpointer user_data)
369 {
370   EmpathyRepeatableSound *repeatable_sound = user_data;
371
372   if (error_code != CA_SUCCESS)
373     {
374       DEBUG ("Error: %s", ca_strerror (error_code));
375       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
376           GINT_TO_POINTER (repeatable_sound->sound_id));
377       return;
378     }
379
380   repeatable_sound->replay_timeout_id = g_timeout_add (
381       repeatable_sound->play_interval, playing_timeout_cb, user_data);
382 }
383
384 /**
385  * empathy_sound_manager_start_playing:
386  * @self: a #EmpathySoundManager
387  * @widget: The #GtkWidget from which the sound is originating.
388  * @sound_id: The #EmpathySound to play.
389  * @timeout_before_replay: The amount of time, in milliseconds, between two
390  *                         consecutive play.
391  *
392  * Start playing a sound in loop. To stop the sound, call empathy_call_stop ()
393  * by passing it the same @sound_id. Note that if you start playing a sound
394  * multiple times, you'll have to call %empathy_sound_stop the same number of
395  * times.
396  *
397  * Return value: %TRUE if the sound has successfully started playing.
398  */
399 gboolean
400 empathy_sound_manager_start_playing (EmpathySoundManager *self,
401     GtkWidget *widget,
402     EmpathySound sound_id,
403     guint timeout_before_replay)
404 {
405   EmpathyRepeatableSound *repeatable_sound;
406   gboolean playing = FALSE;
407
408   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), FALSE);
409   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
410
411   if (!empathy_sound_pref_is_enabled (self, sound_id))
412     return FALSE;
413
414   if (g_hash_table_lookup (self->priv->repeating_sounds,
415                GINT_TO_POINTER (sound_id)) != NULL)
416     {
417       /* The sound is already playing in loop. No need to continue. */
418       return FALSE;
419     }
420
421   repeatable_sound = g_slice_new0 (EmpathyRepeatableSound);
422   repeatable_sound->widget = widget;
423   repeatable_sound->sound_id = sound_id;
424   repeatable_sound->play_interval = timeout_before_replay;
425   repeatable_sound->replay_timeout_id = 0;
426   repeatable_sound->self = g_object_ref (self);
427
428   g_hash_table_insert (self->priv->repeating_sounds, GINT_TO_POINTER (sound_id),
429       repeatable_sound);
430
431   if (widget != NULL)
432     {
433       g_signal_connect (G_OBJECT (widget), "destroy",
434           G_CALLBACK (empathy_sound_widget_destroyed_cb),
435           repeatable_sound);
436     }
437
438   playing = empathy_sound_play_internal (widget, sound_id, playing_finished_cb,
439         repeatable_sound);
440
441   if (!playing)
442       g_hash_table_remove (self->priv->repeating_sounds,
443           GINT_TO_POINTER (sound_id));
444
445   return playing;
446 }