]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-sound.c
add myself to AUTHORS
[empathy.git] / libempathy-gtk / empathy-sound.c
1 /*
2  * empathy-sound.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.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-utils.h>
31
32 #include "empathy-conf.h"
33
34 typedef struct {
35   EmpathySound sound_id;
36   const char * event_ca_id;
37   const char * event_ca_description;
38   const char * gconf_key;
39 } EmpathySoundEntry;
40
41 typedef struct {
42   GtkWidget *widget;
43   gint sound_id;
44   guint play_interval;
45   guint replay_timeout_id;
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 /* An hash table containing currently repeating sounds. The format is the
73  * following:
74  * Key: An EmpathySound
75  * Value : The EmpathyRepeatableSound associated with that EmpathySound. */
76 static GHashTable *repeating_sounds;
77
78 static gboolean
79 empathy_sound_pref_is_enabled (EmpathySound sound_id)
80 {
81   EmpathySoundEntry *entry;
82   EmpathyConf *conf;
83   gboolean res;
84
85   entry = &(sound_entries[sound_id]);
86   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
87
88   if (entry->gconf_key == NULL)
89     return TRUE;
90
91   conf = empathy_conf_get ();
92   res = FALSE;
93
94   empathy_conf_get_bool (conf, EMPATHY_PREFS_SOUNDS_ENABLED, &res);
95
96   if (!res)
97     return FALSE;
98
99   if (!empathy_check_available_state ())
100     {
101       empathy_conf_get_bool (conf, EMPATHY_PREFS_SOUNDS_DISABLED_AWAY, &res);
102
103       if (res)
104         return FALSE;
105     }
106
107   empathy_conf_get_bool (conf, entry->gconf_key, &res);
108
109   return res;
110 }
111
112 /**
113  * empathy_sound_stop:
114  * @sound_id: The #EmpathySound to stop playing.
115  *
116  * Stop playing a sound. If it has been stated in loop with
117  * empathy_sound_start_playing(), it will also stop replaying.
118  */
119 void
120 empathy_sound_stop (EmpathySound sound_id)
121 {
122   EmpathySoundEntry *entry;
123
124   g_return_if_fail (sound_id < LAST_EMPATHY_SOUND);
125
126   entry = &(sound_entries[sound_id]);
127   g_return_if_fail (entry->sound_id == sound_id);
128
129   if (repeating_sounds != NULL)
130     {
131       EmpathyRepeatableSound *repeatable_sound;
132
133       repeatable_sound = g_hash_table_lookup (repeating_sounds,
134           GINT_TO_POINTER (sound_id));
135       if (repeatable_sound != NULL)
136         {
137           /* The sound must be stopped... If it is waiting for replay, remove
138            * it from hash table to cancel. Otherwise we'll cancel the sound
139            * being played. */
140           if (repeatable_sound->replay_timeout_id != 0)
141             {
142               g_hash_table_remove (repeating_sounds, GINT_TO_POINTER (sound_id));
143               return;
144             }
145         }
146     }
147
148   ca_context_cancel (ca_gtk_context_get (), entry->sound_id);
149 }
150
151 static gboolean
152 empathy_sound_play_internal (GtkWidget *widget, EmpathySound sound_id,
153   ca_finish_callback_t callback, gpointer user_data)
154 {
155   EmpathySoundEntry *entry;
156   ca_context *c;
157   ca_proplist *p = NULL;
158
159   entry = &(sound_entries[sound_id]);
160   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
161
162   c = ca_gtk_context_get ();
163   ca_context_cancel (c, entry->sound_id);
164
165   DEBUG ("Play sound \"%s\" (%s)",
166          entry->event_ca_id,
167          entry->event_ca_description);
168
169   if (ca_proplist_create (&p) < 0)
170     goto failed;
171
172   if (ca_proplist_sets (p, CA_PROP_EVENT_ID, entry->event_ca_id) < 0)
173     goto failed;
174
175   if (ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION,
176           gettext (entry->event_ca_description)) < 0)
177     goto failed;
178
179   if (ca_gtk_proplist_set_for_widget (p, widget) < 0)
180     goto failed;
181
182   ca_context_play_full (ca_gtk_context_get (), entry->sound_id, p, callback,
183       user_data);
184
185   ca_proplist_destroy (p);
186
187   return TRUE;
188
189 failed:
190   if (p != NULL)
191     ca_proplist_destroy (p);
192
193   return FALSE;
194 }
195
196 /**
197  * empathy_sound_play_full:
198  * @widget: The #GtkWidget from which the sound is originating.
199  * @sound_id: The #EmpathySound to play.
200  * @callback: The #ca_finish_callback_t function that will be called when the
201  *            sound  has stopped playing.
202  * @user_data: user data to pass to the function.
203  *
204  * Plays a sound.
205  *
206  * Returns %TRUE if the sound has successfully started playing, otherwise
207  * returning %FALSE and @callback won't be called.
208  *
209  * This function returns %FALSE if the sound is already playing in loop using
210  * %empathy_sound_start_playing.
211  *
212  * This function returns %FALSE if the sound is disabled in empathy preferences.
213  *
214  * Return value: %TRUE if the sound has successfully started playing, %FALSE
215  *               otherwise.
216  */
217 gboolean
218 empathy_sound_play_full (GtkWidget *widget, EmpathySound sound_id,
219   ca_finish_callback_t callback, gpointer user_data)
220 {
221   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
222   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
223
224   if (!empathy_sound_pref_is_enabled (sound_id))
225     return FALSE;
226
227   /* The sound might already be playing repeatedly. If it's the case, we
228    * immediadely return since there's no need to make it play again */
229   if (repeating_sounds != NULL &&
230       g_hash_table_lookup (repeating_sounds, GINT_TO_POINTER (sound_id)) != NULL)
231     return FALSE;
232
233   return empathy_sound_play_internal (widget, sound_id, callback, user_data);
234 }
235
236 /**
237  * empathy_sound_play:
238  * @widget: The #GtkWidget from which the sound is originating.
239  * @sound_id: The #EmpathySound to play.
240  *
241  * Plays a sound. See %empathy_sound_play_full for details.'
242  *
243  * Return value: %TRUE if the sound has successfully started playing, %FALSE
244  *               otherwise.
245  */
246 gboolean
247 empathy_sound_play (GtkWidget *widget, EmpathySound sound_id)
248 {
249   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
250   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
251
252   return empathy_sound_play_full (widget, sound_id, NULL, NULL);
253 }
254
255 static void playing_finished_cb (ca_context *c, guint id, int error_code,
256   gpointer user_data);
257
258 static gboolean
259 playing_timeout_cb (gpointer data)
260 {
261   EmpathyRepeatableSound *repeatable_sound = data;
262   gboolean playing;
263
264   repeatable_sound->replay_timeout_id = 0;
265
266   playing = empathy_sound_play_internal (repeatable_sound->widget,
267       repeatable_sound->sound_id, playing_finished_cb, data);
268
269   if (!playing)
270     {
271       DEBUG ("Failed to replay sound, stop repeating");
272       g_hash_table_remove (repeating_sounds,
273           GINT_TO_POINTER (repeatable_sound->sound_id));
274     }
275
276   return FALSE;
277 }
278
279 static void
280 playing_finished_cb (ca_context *c, guint id, int error_code,
281   gpointer user_data)
282 {
283   EmpathyRepeatableSound *repeatable_sound = user_data;
284
285   if (error_code != CA_SUCCESS)
286     {
287       DEBUG ("Error: %s", ca_strerror (error_code));
288       g_hash_table_remove (repeating_sounds,
289           GINT_TO_POINTER (repeatable_sound->sound_id));
290       return;
291     }
292
293   repeatable_sound->replay_timeout_id = g_timeout_add (
294       repeatable_sound->play_interval, playing_timeout_cb, user_data);
295 }
296
297 static void
298 empathy_sound_widget_destroyed_cb (GtkWidget *widget, gpointer user_data)
299 {
300   EmpathyRepeatableSound *repeatable_sound = user_data;
301
302   /* The sound must be stopped... If it is waiting for replay, remove
303    * it from hash table to cancel. Otherwise playing_finished_cb will be
304    * called with an error. */
305   if (repeatable_sound->replay_timeout_id != 0)
306     {
307       g_hash_table_remove (repeating_sounds,
308           GINT_TO_POINTER (repeatable_sound->sound_id));
309     }
310 }
311
312 static void
313 repeating_sounds_item_delete (gpointer data)
314 {
315   EmpathyRepeatableSound *repeatable_sound = data;
316
317   if (repeatable_sound->replay_timeout_id != 0)
318     g_source_remove (repeatable_sound->replay_timeout_id);
319
320   g_signal_handlers_disconnect_by_func (repeatable_sound->widget,
321       empathy_sound_widget_destroyed_cb, repeatable_sound);
322
323   g_slice_free (EmpathyRepeatableSound, repeatable_sound);
324 }
325
326 /**
327  * empathy_sound_start_playing:
328  * @widget: The #GtkWidget from which the sound is originating.
329  * @sound_id: The #EmpathySound to play.
330  * @timeout_before_replay: The amount of time, in milliseconds, between two
331  *                         consecutive play.
332  *
333  * Start playing a sound in loop. To stop the sound, call empathy_call_stop ()
334  * by passing it the same @sound_id. Note that if you start playing a sound
335  * multiple times, you'll have to call %empathy_sound_stop the same number of
336  * times.
337  *
338  * Return value: %TRUE if the sound has successfully started playing.
339  */
340 gboolean
341 empathy_sound_start_playing (GtkWidget *widget, EmpathySound sound_id,
342     guint timeout_before_replay)
343 {
344   EmpathyRepeatableSound *repeatable_sound;
345   gboolean playing = FALSE;
346
347   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
348   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
349
350   if (!empathy_sound_pref_is_enabled (sound_id))
351     return FALSE;
352
353   if (repeating_sounds == NULL)
354     {
355       repeating_sounds = g_hash_table_new_full (g_direct_hash, g_direct_equal,
356           NULL, repeating_sounds_item_delete);
357     }
358   else if (g_hash_table_lookup (repeating_sounds,
359                GINT_TO_POINTER (sound_id)) != NULL)
360     {
361       /* The sound is already playing in loop. No need to continue. */
362       return FALSE;
363     }
364
365   repeatable_sound = g_slice_new0 (EmpathyRepeatableSound);
366   repeatable_sound->widget = widget;
367   repeatable_sound->sound_id = sound_id;
368   repeatable_sound->play_interval = timeout_before_replay;
369   repeatable_sound->replay_timeout_id = 0;
370
371   g_hash_table_insert (repeating_sounds, GINT_TO_POINTER (sound_id),
372       repeatable_sound);
373
374   g_signal_connect (G_OBJECT (widget), "destroy",
375       G_CALLBACK (empathy_sound_widget_destroyed_cb),
376       repeatable_sound);
377
378   playing = empathy_sound_play_internal (widget, sound_id, playing_finished_cb,
379         repeatable_sound);
380
381   if (!playing)
382       g_hash_table_remove (repeating_sounds, GINT_TO_POINTER (sound_id));
383
384   return playing;
385 }