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