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