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