2 * empathy-sound-manager.c - Various sound related utility functions.
3 * Copyright (C) 2009-2010 Collabora Ltd.
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.
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.
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
21 #include "empathy-sound-manager.h"
23 #include <glib/gi18n-lib.h>
25 #include "empathy-gsettings.h"
26 #include "empathy-presence-manager.h"
27 #include "empathy-utils.h"
29 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
30 #include "empathy-debug.h"
33 EmpathySound sound_id
;
34 const char * event_ca_id
;
35 const char * event_ca_description
;
43 guint replay_timeout_id
;
44 EmpathySoundManager
*self
;
45 } EmpathyRepeatableSound
;
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
},
71 G_DEFINE_TYPE (EmpathySoundManager
, empathy_sound_manager
, G_TYPE_OBJECT
)
73 struct _EmpathySoundManagerPrivate
75 /* A hash table containing currently repeating sounds. The format is the
77 * Key: An EmpathySound
78 * Value : The EmpathyRepeatableSound associated with that EmpathySound. */
79 GHashTable
*repeating_sounds
;
80 GSettings
*gsettings_sound
;
84 empathy_sound_manager_dispose (GObject
*object
)
86 EmpathySoundManager
*self
= (EmpathySoundManager
*) object
;
88 tp_clear_pointer (&self
->priv
->repeating_sounds
, g_hash_table_unref
);
89 tp_clear_object (&self
->priv
->gsettings_sound
);
91 G_OBJECT_CLASS (empathy_sound_manager_parent_class
)->dispose (object
);
95 empathy_sound_manager_class_init (EmpathySoundManagerClass
*cls
)
97 GObjectClass
*object_class
= G_OBJECT_CLASS (cls
);
99 object_class
->dispose
= empathy_sound_manager_dispose
;
101 g_type_class_add_private (cls
, sizeof (EmpathySoundManagerPrivate
));
105 empathy_sound_widget_destroyed_cb (GtkWidget
*widget
,
108 EmpathyRepeatableSound
*repeatable_sound
= user_data
;
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)
115 g_hash_table_remove (repeatable_sound
->self
->priv
->repeating_sounds
,
116 GINT_TO_POINTER (repeatable_sound
->sound_id
));
121 repeating_sounds_item_delete (gpointer data
)
123 EmpathyRepeatableSound
*repeatable_sound
= data
;
125 if (repeatable_sound
->replay_timeout_id
!= 0)
126 g_source_remove (repeatable_sound
->replay_timeout_id
);
128 if (repeatable_sound
->widget
!= NULL
)
130 g_signal_handlers_disconnect_by_func (repeatable_sound
->widget
,
131 empathy_sound_widget_destroyed_cb
, repeatable_sound
);
134 g_object_unref (repeatable_sound
->self
);
136 g_slice_free (EmpathyRepeatableSound
, repeatable_sound
);
140 empathy_sound_manager_init (EmpathySoundManager
*self
)
142 self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
143 EMPATHY_TYPE_SOUND_MANAGER
, EmpathySoundManagerPrivate
);
145 self
->priv
->repeating_sounds
= g_hash_table_new_full (NULL
, NULL
,
146 NULL
, repeating_sounds_item_delete
);
148 self
->priv
->gsettings_sound
= g_settings_new (EMPATHY_PREFS_SOUNDS_SCHEMA
);
151 EmpathySoundManager
*
152 empathy_sound_manager_dup_singleton (void)
154 static EmpathySoundManager
*manager
= NULL
;
156 if (G_LIKELY (manager
!= NULL
))
157 return g_object_ref (manager
);
159 manager
= g_object_new (EMPATHY_TYPE_SOUND_MANAGER
, NULL
);
161 g_object_add_weak_pointer (G_OBJECT (manager
), (gpointer
*) &manager
);
166 empathy_check_available_state (void)
168 TpConnectionPresenceType most_available_requested_presence
;
169 TpAccountManager
*am
;
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
)
181 TpAccount
*account
= accounts
->data
;
182 TpConnectionPresenceType requested_presence
;
184 requested_presence
= tp_account_get_requested_presence (account
,
187 if (tp_connection_presence_type_cmp_availability (requested_presence
,
188 most_available_requested_presence
) > 0)
189 most_available_requested_presence
= requested_presence
;
191 g_object_unref (account
);
192 accounts
= g_list_delete_link (accounts
, accounts
);
197 if (most_available_requested_presence
!= TP_CONNECTION_PRESENCE_TYPE_AVAILABLE
&&
198 most_available_requested_presence
!= TP_CONNECTION_PRESENCE_TYPE_UNSET
)
205 empathy_sound_pref_is_enabled (EmpathySoundManager
*self
,
206 EmpathySound sound_id
)
208 EmpathySoundEntry
*entry
;
210 entry
= &(sound_entries
[sound_id
]);
211 g_return_val_if_fail (entry
->sound_id
== sound_id
, FALSE
);
213 if (entry
->key
== NULL
)
216 if (! g_settings_get_boolean (self
->priv
->gsettings_sound
,
217 EMPATHY_PREFS_SOUNDS_ENABLED
))
220 if (!empathy_check_available_state ())
222 if (g_settings_get_boolean (self
->priv
->gsettings_sound
,
223 EMPATHY_PREFS_SOUNDS_DISABLED_AWAY
))
227 return g_settings_get_boolean (self
->priv
->gsettings_sound
, entry
->key
);
231 * empathy_sound_manager_stop:
232 * @self: a #EmpathySoundManager
233 * @sound_id: The #EmpathySound to stop playing.
235 * Stop playing a sound. If it has been stated in loop with
236 * empathy_sound_start_playing(), it will also stop replaying.
239 empathy_sound_manager_stop (EmpathySoundManager
*self
,
240 EmpathySound sound_id
)
242 EmpathySoundEntry
*entry
;
243 EmpathyRepeatableSound
*repeatable_sound
;
245 g_return_if_fail (sound_id
< LAST_EMPATHY_SOUND
);
247 entry
= &(sound_entries
[sound_id
]);
248 g_return_if_fail (entry
->sound_id
== sound_id
);
250 repeatable_sound
= g_hash_table_lookup (self
->priv
->repeating_sounds
,
251 GINT_TO_POINTER (sound_id
));
252 if (repeatable_sound
!= NULL
)
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
257 if (repeatable_sound
->replay_timeout_id
!= 0)
259 g_hash_table_remove (self
->priv
->repeating_sounds
,
260 GINT_TO_POINTER (sound_id
));
265 ca_context_cancel (ca_gtk_context_get (), entry
->sound_id
);
269 empathy_sound_play_internal (GtkWidget
*widget
, EmpathySound sound_id
,
270 ca_finish_callback_t callback
, gpointer user_data
)
272 EmpathySoundEntry
*entry
;
274 ca_proplist
*p
= NULL
;
276 entry
= &(sound_entries
[sound_id
]);
277 g_return_val_if_fail (entry
->sound_id
== sound_id
, FALSE
);
279 c
= ca_gtk_context_get ();
280 ca_context_cancel (c
, entry
->sound_id
);
282 DEBUG ("Play sound \"%s\" (%s)",
284 entry
->event_ca_description
);
286 if (ca_proplist_create (&p
) < 0)
289 if (ca_proplist_sets (p
, CA_PROP_EVENT_ID
, entry
->event_ca_id
) < 0)
292 if (ca_proplist_sets (p
, CA_PROP_EVENT_DESCRIPTION
,
293 gettext (entry
->event_ca_description
)) < 0)
298 if (ca_gtk_proplist_set_for_widget (p
, widget
) < 0)
302 ca_context_play_full (ca_gtk_context_get (), entry
->sound_id
, p
, callback
,
305 ca_proplist_destroy (p
);
311 ca_proplist_destroy (p
);
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.
327 * Returns %TRUE if the sound has successfully started playing, otherwise
328 * returning %FALSE and @callback won't be called.
330 * This function returns %FALSE if the sound is already playing in loop using
331 * %empathy_sound_start_playing.
333 * This function returns %FALSE if the sound is disabled in empathy preferences.
335 * Return value: %TRUE if the sound has successfully started playing, %FALSE
339 empathy_sound_manager_play_full (EmpathySoundManager
*self
,
341 EmpathySound sound_id
,
342 ca_finish_callback_t callback
,
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
);
348 if (!empathy_sound_pref_is_enabled (self
, sound_id
))
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
)
357 return empathy_sound_play_internal (widget
, sound_id
, callback
, user_data
);
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.
366 * Plays a sound. See %empathy_sound_manager_play_full for details.'
368 * Return value: %TRUE if the sound has successfully started playing, %FALSE
372 empathy_sound_manager_play (EmpathySoundManager
*self
,
374 EmpathySound sound_id
)
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
);
379 return empathy_sound_manager_play_full (self
, widget
, sound_id
, NULL
, NULL
);
382 static void playing_finished_cb (ca_context
*c
, guint id
, int error_code
,
386 playing_timeout_cb (gpointer data
)
388 EmpathyRepeatableSound
*repeatable_sound
= data
;
391 repeatable_sound
->replay_timeout_id
= 0;
393 playing
= empathy_sound_play_internal (repeatable_sound
->widget
,
394 repeatable_sound
->sound_id
, playing_finished_cb
, data
);
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
));
407 playing_finished_cb (ca_context
*c
, guint id
, int error_code
,
410 EmpathyRepeatableSound
*repeatable_sound
= user_data
;
412 if (error_code
!= CA_SUCCESS
)
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
));
420 repeatable_sound
->replay_timeout_id
= g_timeout_add (
421 repeatable_sound
->play_interval
, playing_timeout_cb
, user_data
);
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
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
437 * Return value: %TRUE if the sound has successfully started playing.
440 empathy_sound_manager_start_playing (EmpathySoundManager
*self
,
442 EmpathySound sound_id
,
443 guint timeout_before_replay
)
445 EmpathyRepeatableSound
*repeatable_sound
;
446 gboolean playing
= FALSE
;
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
);
451 if (!empathy_sound_pref_is_enabled (self
, sound_id
))
454 if (g_hash_table_lookup (self
->priv
->repeating_sounds
,
455 GINT_TO_POINTER (sound_id
)) != NULL
)
457 /* The sound is already playing in loop. No need to continue. */
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
);
468 g_hash_table_insert (self
->priv
->repeating_sounds
, GINT_TO_POINTER (sound_id
),
473 g_signal_connect (G_OBJECT (widget
), "destroy",
474 G_CALLBACK (empathy_sound_widget_destroyed_cb
),
478 playing
= empathy_sound_play_internal (widget
, sound_id
, playing_finished_cb
,
482 g_hash_table_remove (self
->priv
->repeating_sounds
,
483 GINT_TO_POINTER (sound_id
));