]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-sound-manager.c
Merge branch 'sasl'
[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
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   /* A 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   GSettings *gsettings_sound;
82 };
83
84 static void
85 empathy_sound_manager_dispose (GObject *object)
86 {
87   EmpathySoundManager *self = (EmpathySoundManager *) object;
88
89   tp_clear_pointer (&self->priv->repeating_sounds, g_hash_table_unref);
90   tp_clear_object (&self->priv->gsettings_sound);
91
92   G_OBJECT_CLASS (empathy_sound_manager_parent_class)->dispose (object);
93 }
94
95 static void
96 empathy_sound_manager_class_init (EmpathySoundManagerClass *cls)
97 {
98   GObjectClass *object_class = G_OBJECT_CLASS (cls);
99
100   object_class->dispose = empathy_sound_manager_dispose;
101
102   g_type_class_add_private (cls, sizeof (EmpathySoundManagerPrivate));
103 }
104
105 static void
106 empathy_sound_widget_destroyed_cb (GtkWidget *widget,
107     gpointer user_data)
108 {
109   EmpathyRepeatableSound *repeatable_sound = user_data;
110
111   /* The sound must be stopped... If it is waiting for replay, remove
112    * it from hash table to cancel. Otherwise playing_finished_cb will be
113    * called with an error. */
114   if (repeatable_sound->replay_timeout_id != 0)
115     {
116       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
117           GINT_TO_POINTER (repeatable_sound->sound_id));
118     }
119 }
120
121 static void
122 repeating_sounds_item_delete (gpointer data)
123 {
124   EmpathyRepeatableSound *repeatable_sound = data;
125
126   if (repeatable_sound->replay_timeout_id != 0)
127     g_source_remove (repeatable_sound->replay_timeout_id);
128
129   g_signal_handlers_disconnect_by_func (repeatable_sound->widget,
130       empathy_sound_widget_destroyed_cb, repeatable_sound);
131
132   g_object_unref (repeatable_sound->self);
133
134   g_slice_free (EmpathyRepeatableSound, repeatable_sound);
135 }
136
137 static void
138 empathy_sound_manager_init (EmpathySoundManager *self)
139 {
140   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
141       EMPATHY_TYPE_SOUND_MANAGER, EmpathySoundManagerPrivate);
142
143   self->priv->repeating_sounds = g_hash_table_new_full (NULL, NULL,
144       NULL, repeating_sounds_item_delete);
145
146   self->priv->gsettings_sound = g_settings_new (EMPATHY_PREFS_SOUNDS_SCHEMA);
147 }
148
149 EmpathySoundManager *
150 empathy_sound_manager_dup_singleton (void)
151 {
152   static EmpathySoundManager *manager = NULL;
153
154   if (G_LIKELY (manager != NULL))
155       return g_object_ref (manager);
156
157   manager = g_object_new (EMPATHY_TYPE_SOUND_MANAGER, NULL);
158
159   g_object_add_weak_pointer (G_OBJECT (manager), (gpointer *) &manager);
160   return manager;
161 }
162
163 static gboolean
164 empathy_sound_pref_is_enabled (EmpathySoundManager *self,
165     EmpathySound sound_id)
166 {
167   EmpathySoundEntry *entry;
168
169   entry = &(sound_entries[sound_id]);
170   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
171
172   if (entry->key == NULL)
173     return TRUE;
174
175   if (! g_settings_get_boolean (self->priv->gsettings_sound,
176       EMPATHY_PREFS_SOUNDS_ENABLED))
177     return FALSE;
178
179   if (!empathy_check_available_state ())
180     {
181       if (g_settings_get_boolean (self->priv->gsettings_sound,
182             EMPATHY_PREFS_SOUNDS_DISABLED_AWAY))
183         return FALSE;
184     }
185
186   return g_settings_get_boolean (self->priv->gsettings_sound, entry->key);
187 }
188
189 /**
190  * empathy_sound_manager_stop:
191  * @self: a #EmpathySoundManager
192  * @sound_id: The #EmpathySound to stop playing.
193  *
194  * Stop playing a sound. If it has been stated in loop with
195  * empathy_sound_start_playing(), it will also stop replaying.
196  */
197 void
198 empathy_sound_manager_stop (EmpathySoundManager *self,
199     EmpathySound sound_id)
200 {
201   EmpathySoundEntry *entry;
202   EmpathyRepeatableSound *repeatable_sound;
203
204   g_return_if_fail (sound_id < LAST_EMPATHY_SOUND);
205
206   entry = &(sound_entries[sound_id]);
207   g_return_if_fail (entry->sound_id == sound_id);
208
209   repeatable_sound = g_hash_table_lookup (self->priv->repeating_sounds,
210       GINT_TO_POINTER (sound_id));
211   if (repeatable_sound != NULL)
212     {
213       /* The sound must be stopped... If it is waiting for replay, remove
214        * it from hash table to cancel. Otherwise we'll cancel the sound
215        * being played. */
216       if (repeatable_sound->replay_timeout_id != 0)
217         {
218           g_hash_table_remove (self->priv->repeating_sounds,
219               GINT_TO_POINTER (sound_id));
220           return;
221         }
222     }
223
224   ca_context_cancel (ca_gtk_context_get (), entry->sound_id);
225 }
226
227 static gboolean
228 empathy_sound_play_internal (GtkWidget *widget, EmpathySound sound_id,
229   ca_finish_callback_t callback, gpointer user_data)
230 {
231   EmpathySoundEntry *entry;
232   ca_context *c;
233   ca_proplist *p = NULL;
234
235   entry = &(sound_entries[sound_id]);
236   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
237
238   c = ca_gtk_context_get ();
239   ca_context_cancel (c, entry->sound_id);
240
241   DEBUG ("Play sound \"%s\" (%s)",
242          entry->event_ca_id,
243          entry->event_ca_description);
244
245   if (ca_proplist_create (&p) < 0)
246     goto failed;
247
248   if (ca_proplist_sets (p, CA_PROP_EVENT_ID, entry->event_ca_id) < 0)
249     goto failed;
250
251   if (ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION,
252           gettext (entry->event_ca_description)) < 0)
253     goto failed;
254
255   if (ca_gtk_proplist_set_for_widget (p, widget) < 0)
256     goto failed;
257
258   ca_context_play_full (ca_gtk_context_get (), entry->sound_id, p, callback,
259       user_data);
260
261   ca_proplist_destroy (p);
262
263   return TRUE;
264
265 failed:
266   if (p != NULL)
267     ca_proplist_destroy (p);
268
269   return FALSE;
270 }
271
272 /**
273  * empathy_sound_manager_play_full:
274  * @self: a #EmpathySoundManager
275  * @widget: The #GtkWidget from which the sound is originating.
276  * @sound_id: The #EmpathySound to play.
277  * @callback: The #ca_finish_callback_t function that will be called when the
278  *            sound  has stopped playing.
279  * @user_data: user data to pass to the function.
280  *
281  * Plays a sound.
282  *
283  * Returns %TRUE if the sound has successfully started playing, otherwise
284  * returning %FALSE and @callback won't be called.
285  *
286  * This function returns %FALSE if the sound is already playing in loop using
287  * %empathy_sound_start_playing.
288  *
289  * This function returns %FALSE if the sound is disabled in empathy preferences.
290  *
291  * Return value: %TRUE if the sound has successfully started playing, %FALSE
292  *               otherwise.
293  */
294 gboolean
295 empathy_sound_manager_play_full (EmpathySoundManager *self,
296     GtkWidget *widget,
297     EmpathySound sound_id,
298     ca_finish_callback_t callback,
299     gpointer user_data)
300 {
301   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
302   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
303
304   if (!empathy_sound_pref_is_enabled (self, sound_id))
305     return FALSE;
306
307   /* The sound might already be playing repeatedly. If it's the case, we
308    * immediadely return since there's no need to make it play again */
309   if (g_hash_table_lookup (self->priv->repeating_sounds,
310         GINT_TO_POINTER (sound_id)) != NULL)
311     return FALSE;
312
313   return empathy_sound_play_internal (widget, sound_id, callback, user_data);
314 }
315
316 /**
317  * empathy_sound_manager_play:
318  * @self: a #EmpathySoundManager
319  * @widget: The #GtkWidget from which the sound is originating.
320  * @sound_id: The #EmpathySound to play.
321  *
322  * Plays a sound. See %empathy_sound_manager_play_full for details.'
323  *
324  * Return value: %TRUE if the sound has successfully started playing, %FALSE
325  *               otherwise.
326  */
327 gboolean
328 empathy_sound_manager_play (EmpathySoundManager *self,
329     GtkWidget *widget,
330     EmpathySound sound_id)
331 {
332   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
333   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
334
335   return empathy_sound_manager_play_full (self, widget, sound_id, NULL, NULL);
336 }
337
338 static void playing_finished_cb (ca_context *c, guint id, int error_code,
339   gpointer user_data);
340
341 static gboolean
342 playing_timeout_cb (gpointer data)
343 {
344   EmpathyRepeatableSound *repeatable_sound = data;
345   gboolean playing;
346
347   repeatable_sound->replay_timeout_id = 0;
348
349   playing = empathy_sound_play_internal (repeatable_sound->widget,
350       repeatable_sound->sound_id, playing_finished_cb, data);
351
352   if (!playing)
353     {
354       DEBUG ("Failed to replay sound, stop repeating");
355       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
356           GINT_TO_POINTER (repeatable_sound->sound_id));
357     }
358
359   return FALSE;
360 }
361
362 static void
363 playing_finished_cb (ca_context *c, guint id, int error_code,
364   gpointer user_data)
365 {
366   EmpathyRepeatableSound *repeatable_sound = user_data;
367
368   if (error_code != CA_SUCCESS)
369     {
370       DEBUG ("Error: %s", ca_strerror (error_code));
371       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
372           GINT_TO_POINTER (repeatable_sound->sound_id));
373       return;
374     }
375
376   repeatable_sound->replay_timeout_id = g_timeout_add (
377       repeatable_sound->play_interval, playing_timeout_cb, user_data);
378 }
379
380 /**
381  * empathy_sound_manager_start_playing:
382  * @self: a #EmpathySoundManager
383  * @widget: The #GtkWidget from which the sound is originating.
384  * @sound_id: The #EmpathySound to play.
385  * @timeout_before_replay: The amount of time, in milliseconds, between two
386  *                         consecutive play.
387  *
388  * Start playing a sound in loop. To stop the sound, call empathy_call_stop ()
389  * by passing it the same @sound_id. Note that if you start playing a sound
390  * multiple times, you'll have to call %empathy_sound_stop the same number of
391  * times.
392  *
393  * Return value: %TRUE if the sound has successfully started playing.
394  */
395 gboolean
396 empathy_sound_manager_start_playing (EmpathySoundManager *self,
397     GtkWidget *widget,
398     EmpathySound sound_id,
399     guint timeout_before_replay)
400 {
401   EmpathyRepeatableSound *repeatable_sound;
402   gboolean playing = FALSE;
403
404   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
405   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
406
407   if (!empathy_sound_pref_is_enabled (self, sound_id))
408     return FALSE;
409
410   if (g_hash_table_lookup (self->priv->repeating_sounds,
411                GINT_TO_POINTER (sound_id)) != NULL)
412     {
413       /* The sound is already playing in loop. No need to continue. */
414       return FALSE;
415     }
416
417   repeatable_sound = g_slice_new0 (EmpathyRepeatableSound);
418   repeatable_sound->widget = widget;
419   repeatable_sound->sound_id = sound_id;
420   repeatable_sound->play_interval = timeout_before_replay;
421   repeatable_sound->replay_timeout_id = 0;
422   repeatable_sound->self = g_object_ref (self);
423
424   g_hash_table_insert (self->priv->repeating_sounds, GINT_TO_POINTER (sound_id),
425       repeatable_sound);
426
427   g_signal_connect (G_OBJECT (widget), "destroy",
428       G_CALLBACK (empathy_sound_widget_destroyed_cb),
429       repeatable_sound);
430
431   playing = empathy_sound_play_internal (widget, sound_id, playing_finished_cb,
432         repeatable_sound);
433
434   if (!playing)
435       g_hash_table_remove (self->priv->repeating_sounds,
436           GINT_TO_POINTER (sound_id));
437
438   return playing;
439 }