]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-sound-manager.c
Updated Kannada translation
[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 #include "empathy-sound-manager.h"
22
23 #include <glib/gi18n-lib.h>
24
25 #include "empathy-gsettings.h"
26 #include "empathy-presence-manager.h"
27 #include "empathy-utils.h"
28
29 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
30 #include "empathy-debug.h"
31
32 typedef struct {
33   EmpathySound sound_id;
34   const char * event_ca_id;
35   const char * event_ca_description;
36   const char * key;
37 } EmpathySoundEntry;
38
39 typedef struct {
40   GtkWidget *widget;
41   gint sound_id;
42   guint play_interval;
43   guint replay_timeout_id;
44   EmpathySoundManager *self;
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   /* A hash table containing currently repeating sounds. The format is the
76    * following:
77    * Key: An EmpathySound
78    * Value : The EmpathyRepeatableSound associated with that EmpathySound. */
79   GHashTable *repeating_sounds;
80   GSettings *gsettings_sound;
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   tp_clear_object (&self->priv->gsettings_sound);
90
91   G_OBJECT_CLASS (empathy_sound_manager_parent_class)->dispose (object);
92 }
93
94 static void
95 empathy_sound_manager_class_init (EmpathySoundManagerClass *cls)
96 {
97   GObjectClass *object_class = G_OBJECT_CLASS (cls);
98
99   object_class->dispose = empathy_sound_manager_dispose;
100
101   g_type_class_add_private (cls, sizeof (EmpathySoundManagerPrivate));
102 }
103
104 static void
105 empathy_sound_widget_destroyed_cb (GtkWidget *widget,
106     gpointer user_data)
107 {
108   EmpathyRepeatableSound *repeatable_sound = user_data;
109
110   /* The sound must be stopped... If it is waiting for replay, remove
111    * it from hash table to cancel. Otherwise playing_finished_cb will be
112    * called with an error. */
113   if (repeatable_sound->replay_timeout_id != 0)
114     {
115       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
116           GINT_TO_POINTER (repeatable_sound->sound_id));
117     }
118 }
119
120 static void
121 repeating_sounds_item_delete (gpointer data)
122 {
123   EmpathyRepeatableSound *repeatable_sound = data;
124
125   if (repeatable_sound->replay_timeout_id != 0)
126     g_source_remove (repeatable_sound->replay_timeout_id);
127
128   if (repeatable_sound->widget != NULL)
129     {
130       g_signal_handlers_disconnect_by_func (repeatable_sound->widget,
131           empathy_sound_widget_destroyed_cb, repeatable_sound);
132     }
133
134   g_object_unref (repeatable_sound->self);
135
136   g_slice_free (EmpathyRepeatableSound, repeatable_sound);
137 }
138
139 static void
140 empathy_sound_manager_init (EmpathySoundManager *self)
141 {
142   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
143       EMPATHY_TYPE_SOUND_MANAGER, EmpathySoundManagerPrivate);
144
145   self->priv->repeating_sounds = g_hash_table_new_full (NULL, NULL,
146       NULL, repeating_sounds_item_delete);
147
148   self->priv->gsettings_sound = g_settings_new (EMPATHY_PREFS_SOUNDS_SCHEMA);
149 }
150
151 EmpathySoundManager *
152 empathy_sound_manager_dup_singleton (void)
153 {
154   static EmpathySoundManager *manager = NULL;
155
156   if (G_LIKELY (manager != NULL))
157       return g_object_ref (manager);
158
159   manager = g_object_new (EMPATHY_TYPE_SOUND_MANAGER, NULL);
160
161   g_object_add_weak_pointer (G_OBJECT (manager), (gpointer *) &manager);
162   return manager;
163 }
164
165 static gboolean
166 empathy_check_available_state (void)
167 {
168   TpConnectionPresenceType most_available_requested_presence;
169   TpAccountManager *am;
170   GList *accounts;
171
172   /* We cannot use tp_account_manager_get_most_available_presence() or
173    * empathy_presence_manager_get_state() because it is the requested presence
174    * that matters, not the current presence.
175    * See https://bugzilla.gnome.org/show_bug.cgi?id=704454 */
176   most_available_requested_presence = TP_CONNECTION_PRESENCE_TYPE_UNSET;
177   am = tp_account_manager_dup ();
178   accounts = tp_account_manager_dup_valid_accounts (am);
179   while (accounts != NULL)
180     {
181       TpAccount *account = accounts->data;
182       TpConnectionPresenceType requested_presence;
183
184       requested_presence = tp_account_get_requested_presence (account,
185           NULL, NULL);
186
187       if (tp_connection_presence_type_cmp_availability (requested_presence,
188               most_available_requested_presence) > 0)
189         most_available_requested_presence = requested_presence;
190
191       g_object_unref (account);
192       accounts = g_list_delete_link (accounts, accounts);
193     }
194
195   g_object_unref (am);
196
197   if (most_available_requested_presence != TP_CONNECTION_PRESENCE_TYPE_AVAILABLE &&
198     most_available_requested_presence != TP_CONNECTION_PRESENCE_TYPE_UNSET)
199     return FALSE;
200
201   return TRUE;
202 }
203
204 static gboolean
205 empathy_sound_pref_is_enabled (EmpathySoundManager *self,
206     EmpathySound sound_id)
207 {
208   EmpathySoundEntry *entry;
209
210   entry = &(sound_entries[sound_id]);
211   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
212
213   if (entry->key == NULL)
214     return TRUE;
215
216   if (! g_settings_get_boolean (self->priv->gsettings_sound,
217       EMPATHY_PREFS_SOUNDS_ENABLED))
218     return FALSE;
219
220   if (!empathy_check_available_state ())
221     {
222       if (g_settings_get_boolean (self->priv->gsettings_sound,
223             EMPATHY_PREFS_SOUNDS_DISABLED_AWAY))
224         return FALSE;
225     }
226
227   return g_settings_get_boolean (self->priv->gsettings_sound, entry->key);
228 }
229
230 /**
231  * empathy_sound_manager_stop:
232  * @self: a #EmpathySoundManager
233  * @sound_id: The #EmpathySound to stop playing.
234  *
235  * Stop playing a sound. If it has been stated in loop with
236  * empathy_sound_start_playing(), it will also stop replaying.
237  */
238 void
239 empathy_sound_manager_stop (EmpathySoundManager *self,
240     EmpathySound sound_id)
241 {
242   EmpathySoundEntry *entry;
243   EmpathyRepeatableSound *repeatable_sound;
244
245   g_return_if_fail (sound_id < LAST_EMPATHY_SOUND);
246
247   entry = &(sound_entries[sound_id]);
248   g_return_if_fail (entry->sound_id == sound_id);
249
250   repeatable_sound = g_hash_table_lookup (self->priv->repeating_sounds,
251       GINT_TO_POINTER (sound_id));
252   if (repeatable_sound != NULL)
253     {
254       /* The sound must be stopped... If it is waiting for replay, remove
255        * it from hash table to cancel. Otherwise we'll cancel the sound
256        * being played. */
257       if (repeatable_sound->replay_timeout_id != 0)
258         {
259           g_hash_table_remove (self->priv->repeating_sounds,
260               GINT_TO_POINTER (sound_id));
261           return;
262         }
263     }
264
265   ca_context_cancel (ca_gtk_context_get (), entry->sound_id);
266 }
267
268 static gboolean
269 empathy_sound_play_internal (GtkWidget *widget, EmpathySound sound_id,
270   ca_finish_callback_t callback, gpointer user_data)
271 {
272   EmpathySoundEntry *entry;
273   ca_context *c;
274   ca_proplist *p = NULL;
275
276   entry = &(sound_entries[sound_id]);
277   g_return_val_if_fail (entry->sound_id == sound_id, FALSE);
278
279   c = ca_gtk_context_get ();
280   ca_context_cancel (c, entry->sound_id);
281
282   DEBUG ("Play sound \"%s\" (%s)",
283          entry->event_ca_id,
284          entry->event_ca_description);
285
286   if (ca_proplist_create (&p) < 0)
287     goto failed;
288
289   if (ca_proplist_sets (p, CA_PROP_EVENT_ID, entry->event_ca_id) < 0)
290     goto failed;
291
292   if (ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION,
293           gettext (entry->event_ca_description)) < 0)
294     goto failed;
295
296   if (widget != NULL)
297     {
298       if (ca_gtk_proplist_set_for_widget (p, widget) < 0)
299         goto failed;
300     }
301
302   ca_context_play_full (ca_gtk_context_get (), entry->sound_id, p, callback,
303       user_data);
304
305   ca_proplist_destroy (p);
306
307   return TRUE;
308
309 failed:
310   if (p != NULL)
311     ca_proplist_destroy (p);
312
313   return FALSE;
314 }
315
316 /**
317  * empathy_sound_manager_play_full:
318  * @self: a #EmpathySoundManager
319  * @widget: The #GtkWidget from which the sound is originating.
320  * @sound_id: The #EmpathySound to play.
321  * @callback: The #ca_finish_callback_t function that will be called when the
322  *            sound  has stopped playing.
323  * @user_data: user data to pass to the function.
324  *
325  * Plays a sound.
326  *
327  * Returns %TRUE if the sound has successfully started playing, otherwise
328  * returning %FALSE and @callback won't be called.
329  *
330  * This function returns %FALSE if the sound is already playing in loop using
331  * %empathy_sound_start_playing.
332  *
333  * This function returns %FALSE if the sound is disabled in empathy preferences.
334  *
335  * Return value: %TRUE if the sound has successfully started playing, %FALSE
336  *               otherwise.
337  */
338 gboolean
339 empathy_sound_manager_play_full (EmpathySoundManager *self,
340     GtkWidget *widget,
341     EmpathySound sound_id,
342     ca_finish_callback_t callback,
343     gpointer user_data)
344 {
345   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), FALSE);
346   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
347
348   if (!empathy_sound_pref_is_enabled (self, sound_id))
349     return FALSE;
350
351   /* The sound might already be playing repeatedly. If it's the case, we
352    * immediadely return since there's no need to make it play again */
353   if (g_hash_table_lookup (self->priv->repeating_sounds,
354         GINT_TO_POINTER (sound_id)) != NULL)
355     return FALSE;
356
357   return empathy_sound_play_internal (widget, sound_id, callback, user_data);
358 }
359
360 /**
361  * empathy_sound_manager_play:
362  * @self: a #EmpathySoundManager
363  * @widget: The #GtkWidget from which the sound is originating.
364  * @sound_id: The #EmpathySound to play.
365  *
366  * Plays a sound. See %empathy_sound_manager_play_full for details.'
367  *
368  * Return value: %TRUE if the sound has successfully started playing, %FALSE
369  *               otherwise.
370  */
371 gboolean
372 empathy_sound_manager_play (EmpathySoundManager *self,
373     GtkWidget *widget,
374     EmpathySound sound_id)
375 {
376   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), FALSE);
377   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
378
379   return empathy_sound_manager_play_full (self, widget, sound_id, NULL, NULL);
380 }
381
382 static void playing_finished_cb (ca_context *c, guint id, int error_code,
383   gpointer user_data);
384
385 static gboolean
386 playing_timeout_cb (gpointer data)
387 {
388   EmpathyRepeatableSound *repeatable_sound = data;
389   gboolean playing;
390
391   repeatable_sound->replay_timeout_id = 0;
392
393   playing = empathy_sound_play_internal (repeatable_sound->widget,
394       repeatable_sound->sound_id, playing_finished_cb, data);
395
396   if (!playing)
397     {
398       DEBUG ("Failed to replay sound, stop repeating");
399       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
400           GINT_TO_POINTER (repeatable_sound->sound_id));
401     }
402
403   return FALSE;
404 }
405
406 static void
407 playing_finished_cb (ca_context *c, guint id, int error_code,
408   gpointer user_data)
409 {
410   EmpathyRepeatableSound *repeatable_sound = user_data;
411
412   if (error_code != CA_SUCCESS)
413     {
414       DEBUG ("Error: %s", ca_strerror (error_code));
415       g_hash_table_remove (repeatable_sound->self->priv->repeating_sounds,
416           GINT_TO_POINTER (repeatable_sound->sound_id));
417       return;
418     }
419
420   repeatable_sound->replay_timeout_id = g_timeout_add (
421       repeatable_sound->play_interval, playing_timeout_cb, user_data);
422 }
423
424 /**
425  * empathy_sound_manager_start_playing:
426  * @self: a #EmpathySoundManager
427  * @widget: The #GtkWidget from which the sound is originating.
428  * @sound_id: The #EmpathySound to play.
429  * @timeout_before_replay: The amount of time, in milliseconds, between two
430  *                         consecutive play.
431  *
432  * Start playing a sound in loop. To stop the sound, call empathy_call_stop ()
433  * by passing it the same @sound_id. Note that if you start playing a sound
434  * multiple times, you'll have to call %empathy_sound_stop the same number of
435  * times.
436  *
437  * Return value: %TRUE if the sound has successfully started playing.
438  */
439 gboolean
440 empathy_sound_manager_start_playing (EmpathySoundManager *self,
441     GtkWidget *widget,
442     EmpathySound sound_id,
443     guint timeout_before_replay)
444 {
445   EmpathyRepeatableSound *repeatable_sound;
446   gboolean playing = FALSE;
447
448   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), FALSE);
449   g_return_val_if_fail (sound_id < LAST_EMPATHY_SOUND, FALSE);
450
451   if (!empathy_sound_pref_is_enabled (self, sound_id))
452     return FALSE;
453
454   if (g_hash_table_lookup (self->priv->repeating_sounds,
455                GINT_TO_POINTER (sound_id)) != NULL)
456     {
457       /* The sound is already playing in loop. No need to continue. */
458       return FALSE;
459     }
460
461   repeatable_sound = g_slice_new0 (EmpathyRepeatableSound);
462   repeatable_sound->widget = widget;
463   repeatable_sound->sound_id = sound_id;
464   repeatable_sound->play_interval = timeout_before_replay;
465   repeatable_sound->replay_timeout_id = 0;
466   repeatable_sound->self = g_object_ref (self);
467
468   g_hash_table_insert (self->priv->repeating_sounds, GINT_TO_POINTER (sound_id),
469       repeatable_sound);
470
471   if (widget != NULL)
472     {
473       g_signal_connect (G_OBJECT (widget), "destroy",
474           G_CALLBACK (empathy_sound_widget_destroyed_cb),
475           repeatable_sound);
476     }
477
478   playing = empathy_sound_play_internal (widget, sound_id, playing_finished_cb,
479         repeatable_sound);
480
481   if (!playing)
482       g_hash_table_remove (self->priv->repeating_sounds,
483           GINT_TO_POINTER (sound_id));
484
485   return playing;
486 }