2 * @file gtksound.c GTK+ Sound
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
37 #endif /* USE_GSTREAMER */
43 #include "sound-theme.h"
44 #include "theme-manager.h"
50 struct pidgin_sound_event
{
56 static guint mute_login_sounds_timeout
= 0;
57 static gboolean mute_login_sounds
= FALSE
;
60 static gboolean gst_init_failed
;
61 #endif /* USE_GSTREAMER */
63 static const struct pidgin_sound_event sounds
[PURPLE_NUM_SOUNDS
] = {
64 {N_("Buddy logs in"), "login", "login.wav"},
65 {N_("Buddy logs out"), "logout", "logout.wav"},
66 {N_("Message received"), "im_recv", "receive.wav"},
67 {N_("Message received begins conversation"), "first_im_recv", "receive.wav"},
68 {N_("Message sent"), "send_im", "send.wav"},
69 {N_("Person enters chat"), "join_chat", "login.wav"},
70 {N_("Person leaves chat"), "left_chat", "logout.wav"},
71 {N_("You talk in chat"), "send_chat_msg", "send.wav"},
72 {N_("Others talk in chat"), "chat_msg_recv", "receive.wav"},
73 /* this isn't a terminator, it's the buddy pounce default sound event ;-) */
74 {NULL
, "pounce_default", "alert.wav"},
75 {N_("Someone says your username in chat"), "nick_said", "alert.wav"},
76 {N_("Attention received"), "got_attention", "alert.wav"}
80 unmute_login_sounds_cb(gpointer data
)
82 mute_login_sounds
= FALSE
;
83 mute_login_sounds_timeout
= 0;
88 chat_nick_matches_name(PurpleConversation
*conv
, const char *aname
)
90 PurpleConvChat
*chat
= NULL
;
94 chat
= purple_conversation_get_chat_data(conv
);
99 nick
= g_strdup(purple_normalize(conv
->account
, chat
->nick
));
100 name
= g_strdup(purple_normalize(conv
->account
, aname
));
102 if (g_utf8_collate(nick
, name
) == 0)
112 * play a sound event for a conversation, honoring make_sound flag
113 * of conversation and checking for focus if conv_focus pref is set
116 play_conv_event(PurpleConversation
*conv
, PurpleSoundEventID event
)
118 /* If we should not play the sound for some reason, then exit early */
119 if (conv
!= NULL
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
121 PidginConversation
*gtkconv
;
124 gtkconv
= PIDGIN_CONVERSATION(conv
);
125 has_focus
= purple_conversation_has_focus(conv
);
127 if (!gtkconv
->make_sound
||
128 (has_focus
&& !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/sound/conv_focus")))
134 purple_sound_play_event(event
, conv
? purple_conversation_get_account(conv
) : NULL
);
138 buddy_state_cb(PurpleBuddy
*buddy
, PurpleSoundEventID event
)
140 purple_sound_play_event(event
, purple_buddy_get_account(buddy
));
144 im_msg_received_cb(PurpleAccount
*account
, char *sender
,
145 char *message
, PurpleConversation
*conv
,
146 PurpleMessageFlags flags
, PurpleSoundEventID event
)
148 if (flags
& PURPLE_MESSAGE_DELAYED
|| flags
& PURPLE_MESSAGE_NOTIFY
)
152 purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE
, account
);
154 play_conv_event(conv
, event
);
158 im_msg_sent_cb(PurpleAccount
*account
, const char *receiver
,
159 const char *message
, PurpleSoundEventID event
)
161 PurpleConversation
*conv
= purple_find_conversation_with_account(
162 PURPLE_CONV_TYPE_IM
, receiver
, account
);
163 play_conv_event(conv
, event
);
167 chat_buddy_join_cb(PurpleConversation
*conv
, const char *name
,
168 PurpleConvChatBuddyFlags flags
, gboolean new_arrival
,
169 PurpleSoundEventID event
)
171 if (new_arrival
&& !chat_nick_matches_name(conv
, name
))
172 play_conv_event(conv
, event
);
176 chat_buddy_left_cb(PurpleConversation
*conv
, const char *name
,
177 const char *reason
, PurpleSoundEventID event
)
179 if (!chat_nick_matches_name(conv
, name
))
180 play_conv_event(conv
, event
);
184 chat_msg_sent_cb(PurpleAccount
*account
, const char *message
,
185 int id
, PurpleSoundEventID event
)
187 PurpleConnection
*conn
= purple_account_get_connection(account
);
188 PurpleConversation
*conv
= NULL
;
191 conv
= purple_find_chat(conn
,id
);
193 play_conv_event(conv
, event
);
197 chat_msg_received_cb(PurpleAccount
*account
, char *sender
,
198 char *message
, PurpleConversation
*conv
,
199 PurpleMessageFlags flags
, PurpleSoundEventID event
)
201 PurpleConvChat
*chat
;
203 if (flags
& PURPLE_MESSAGE_DELAYED
|| flags
& PURPLE_MESSAGE_NOTIFY
)
206 chat
= purple_conversation_get_chat_data(conv
);
207 g_return_if_fail(chat
!= NULL
);
209 if (purple_conv_chat_is_user_ignored(chat
, sender
))
212 if (chat_nick_matches_name(conv
, sender
))
215 if (flags
& PURPLE_MESSAGE_NICK
|| purple_utf8_has_word(message
, chat
->nick
))
216 /* This isn't quite right; if you have the PURPLE_SOUND_CHAT_NICK event disabled
217 * and the PURPLE_SOUND_CHAT_SAY event enabled, you won't get a sound at all */
218 play_conv_event(conv
, PURPLE_SOUND_CHAT_NICK
);
220 play_conv_event(conv
, event
);
224 got_attention_cb(PurpleAccount
*account
, const char *who
,
225 PurpleConversation
*conv
, guint type
, PurpleSoundEventID event
)
227 play_conv_event(conv
, event
);
231 * We mute sounds for the 10 seconds after you log in so that
232 * you don't get flooded with sounds when the blist shows all
233 * your buddies logging in.
236 account_signon_cb(PurpleConnection
*gc
, gpointer data
)
238 if (mute_login_sounds_timeout
!= 0)
239 purple_timeout_remove(mute_login_sounds_timeout
);
240 mute_login_sounds
= TRUE
;
241 mute_login_sounds_timeout
= purple_timeout_add_seconds(10, unmute_login_sounds_cb
, NULL
);
245 pidgin_sound_get_event_option(PurpleSoundEventID event
)
247 if(event
>= PURPLE_NUM_SOUNDS
)
250 return sounds
[event
].pref
;
254 pidgin_sound_get_event_label(PurpleSoundEventID event
)
256 if(event
>= PURPLE_NUM_SOUNDS
)
259 return sounds
[event
].label
;
263 pidgin_sound_get_handle()
271 pidgin_sound_init(void)
273 void *gtk_sound_handle
= pidgin_sound_get_handle();
274 void *blist_handle
= purple_blist_get_handle();
275 void *conv_handle
= purple_conversations_get_handle();
277 GError
*error
= NULL
;
280 purple_signal_connect(purple_connections_get_handle(), "signed-on",
281 gtk_sound_handle
, PURPLE_CALLBACK(account_signon_cb
),
284 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/sound");
285 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/sound/enabled");
286 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/sound/file");
287 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/login", TRUE
);
288 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/login", "");
289 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/logout", TRUE
);
290 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/logout", "");
291 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/im_recv", TRUE
);
292 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/im_recv", "");
293 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/first_im_recv", FALSE
);
294 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/first_im_recv", "");
295 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/send_im", TRUE
);
296 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/send_im", "");
297 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/join_chat", FALSE
);
298 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/join_chat", "");
299 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/left_chat", FALSE
);
300 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/left_chat", "");
301 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/send_chat_msg", FALSE
);
302 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/send_chat_msg", "");
303 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/chat_msg_recv", FALSE
);
304 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/chat_msg_recv", "");
305 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/nick_said", FALSE
);
306 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/nick_said", "");
307 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/pounce_default", TRUE
);
308 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/pounce_default", "");
309 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/sound/theme", "");
310 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/sent_attention", TRUE
);
311 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/sent_attention", "");
312 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/enabled/got_attention", TRUE
);
313 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/file/got_attention", "");
314 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/conv_focus", TRUE
);
315 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/sound/mute", FALSE
);
316 purple_prefs_add_path(PIDGIN_PREFS_ROOT
"/sound/command", "");
317 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/sound/method", "automatic");
318 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/sound/volume", 50);
321 purple_debug_info("sound", "Initializing sound output drivers.\n");
322 #ifdef GST_CAN_DISABLE_FORKING
323 gst_registry_fork_set_enabled (FALSE
);
325 if ((gst_init_failed
= !gst_init_check(NULL
, NULL
, &error
))) {
326 purple_notify_error(NULL
, _("GStreamer Failure"),
327 _("GStreamer failed to initialize."),
328 error
? error
->message
: "");
334 #endif /* USE_GSTREAMER */
336 purple_signal_connect(blist_handle
, "buddy-signed-on",
337 gtk_sound_handle
, PURPLE_CALLBACK(buddy_state_cb
),
338 GINT_TO_POINTER(PURPLE_SOUND_BUDDY_ARRIVE
));
339 purple_signal_connect(blist_handle
, "buddy-signed-off",
340 gtk_sound_handle
, PURPLE_CALLBACK(buddy_state_cb
),
341 GINT_TO_POINTER(PURPLE_SOUND_BUDDY_LEAVE
));
342 purple_signal_connect(conv_handle
, "received-im-msg",
343 gtk_sound_handle
, PURPLE_CALLBACK(im_msg_received_cb
),
344 GINT_TO_POINTER(PURPLE_SOUND_RECEIVE
));
345 purple_signal_connect(conv_handle
, "sent-im-msg",
346 gtk_sound_handle
, PURPLE_CALLBACK(im_msg_sent_cb
),
347 GINT_TO_POINTER(PURPLE_SOUND_SEND
));
348 purple_signal_connect(conv_handle
, "chat-buddy-joined",
349 gtk_sound_handle
, PURPLE_CALLBACK(chat_buddy_join_cb
),
350 GINT_TO_POINTER(PURPLE_SOUND_CHAT_JOIN
));
351 purple_signal_connect(conv_handle
, "chat-buddy-left",
352 gtk_sound_handle
, PURPLE_CALLBACK(chat_buddy_left_cb
),
353 GINT_TO_POINTER(PURPLE_SOUND_CHAT_LEAVE
));
354 purple_signal_connect(conv_handle
, "sent-chat-msg",
355 gtk_sound_handle
, PURPLE_CALLBACK(chat_msg_sent_cb
),
356 GINT_TO_POINTER(PURPLE_SOUND_CHAT_YOU_SAY
));
357 purple_signal_connect(conv_handle
, "received-chat-msg",
358 gtk_sound_handle
, PURPLE_CALLBACK(chat_msg_received_cb
),
359 GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY
));
360 purple_signal_connect(conv_handle
, "got-attention", gtk_sound_handle
,
361 PURPLE_CALLBACK(got_attention_cb
),
362 GINT_TO_POINTER(PURPLE_SOUND_GOT_ATTENTION
));
363 /* for the time being, don't handle sent-attention here, since playing a
364 sound would result induplicate sounds. And fixing that would require changing the
365 conversation signal for msg-recv */
369 pidgin_sound_uninit(void)
372 if (!gst_init_failed
)
376 purple_signals_disconnect_by_handle(pidgin_sound_get_handle());
381 bus_call (GstBus
*bus
,
385 GstElement
*play
= data
;
388 switch (GST_MESSAGE_TYPE (msg
)) {
389 case GST_MESSAGE_ERROR
:
390 gst_message_parse_error(msg
, &err
, NULL
);
391 purple_debug_error("gstreamer", "%s\n", err
->message
);
393 /* fall-through and clean up */
394 case GST_MESSAGE_EOS
:
395 gst_element_set_state(play
, GST_STATE_NULL
);
396 gst_object_unref(GST_OBJECT(play
));
398 case GST_MESSAGE_WARNING
:
399 gst_message_parse_warning(msg
, &err
, NULL
);
400 purple_debug_warning("gstreamer", "%s\n", err
->message
);
412 expire_old_child(gpointer data
)
414 pid_t pid
= GPOINTER_TO_INT(data
);
416 if (waitpid(pid
, NULL
, WNOHANG
| WUNTRACED
) < 0) {
420 purple_debug_warning("gtksound", "Child is ill, pid: %d (%s)\n", pid
, strerror(errno
));
423 if (kill(pid
, SIGKILL
) < 0)
424 purple_debug_error("gtksound", "Killing process %d failed (%s)\n", pid
, strerror(errno
));
431 pidgin_sound_play_file(const char *filename
)
437 GstElement
*sink
= NULL
;
438 GstElement
*play
= NULL
;
442 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/sound/mute"))
445 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
447 if (!strcmp(method
, "none")) {
449 } else if (!strcmp(method
, "beep")) {
454 if (!g_file_test(filename
, G_FILE_TEST_EXISTS
)) {
455 purple_debug_error("gtksound", "sound file (%s) does not exist.\n", filename
);
460 if (!strcmp(method
, "custom")) {
461 const char *sound_cmd
;
465 GError
*error
= NULL
;
468 sound_cmd
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/sound/command");
470 if (!sound_cmd
|| *sound_cmd
== '\0') {
471 purple_debug_error("gtksound",
472 "'Command' sound method has been chosen, "
473 "but no command has been set.\n");
477 esc_filename
= g_shell_quote(filename
);
479 if(strstr(sound_cmd
, "%s"))
480 command
= purple_strreplace(sound_cmd
, "%s", esc_filename
);
482 command
= g_strdup_printf("%s %s", sound_cmd
, esc_filename
);
484 if (!g_shell_parse_argv(command
, NULL
, &argv
, &error
)) {
485 purple_debug_error("gtksound", "error parsing command %s (%s)\n",
486 command
, error
->message
);
488 g_free(esc_filename
);
493 if (!g_spawn_async(NULL
, argv
, NULL
, G_SPAWN_SEARCH_PATH
| G_SPAWN_DO_NOT_REAP_CHILD
,
494 NULL
, NULL
, &pid
, &error
)) {
495 purple_debug_error("gtksound", "sound command could not be launched: %s\n",
499 purple_timeout_add_seconds(15, expire_old_child
, GINT_TO_POINTER(pid
));
503 g_free(esc_filename
);
510 if (gst_init_failed
) /* Perhaps do gdk_beep instead? */
512 volume
= (float)(CLAMP(purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/sound/volume"),0,100)) / 50;
513 if (!strcmp(method
, "automatic")) {
514 sink
= gst_element_factory_make("gconfaudiosink", "sink");
517 else if (!strcmp(method
, "esd")) {
518 sink
= gst_element_factory_make("esdsink", "sink");
519 } else if (!strcmp(method
, "alsa")) {
520 sink
= gst_element_factory_make("alsasink", "sink");
524 purple_debug_error("sound", "Unknown sound method '%s'\n", method
);
528 if (strcmp(method
, "automatic") != 0 && !sink
) {
529 purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
533 play
= gst_element_factory_make("playbin", "play");
539 uri
= g_strdup_printf("file://%s", filename
);
541 g_object_set(G_OBJECT(play
), "uri", uri
,
543 "audio-sink", sink
, NULL
);
545 bus
= gst_pipeline_get_bus(GST_PIPELINE(play
));
546 gst_bus_add_watch(bus
, bus_call
, play
);
548 gst_element_set_state(play
, GST_STATE_PLAYING
);
550 gst_object_unref(bus
);
553 #else /* #ifdef USE_GSTREAMER */
558 purple_debug_info("sound", "Playing %s\n", filename
);
561 wchar_t *wc_filename
= g_utf8_to_utf16(filename
,
562 -1, NULL
, NULL
, NULL
);
563 if (!PlaySoundW(wc_filename
, NULL
, SND_ASYNC
| SND_FILENAME
))
564 purple_debug(PURPLE_DEBUG_ERROR
, "sound", "Error playing sound.\n");
569 #endif /* USE_GSTREAMER */
573 pidgin_sound_play_event(PurpleSoundEventID event
)
577 const char *theme_name
;
578 PurpleSoundTheme
*theme
;
580 if ((event
== PURPLE_SOUND_BUDDY_ARRIVE
) && mute_login_sounds
)
583 if (event
>= PURPLE_NUM_SOUNDS
) {
584 purple_debug_error("sound", "got request for unknown sound: %d\n", event
);
588 enable_pref
= g_strdup_printf(PIDGIN_PREFS_ROOT
"/sound/enabled/%s",
590 file_pref
= g_strdup_printf(PIDGIN_PREFS_ROOT
"/sound/file/%s", sounds
[event
].pref
);
592 /* check NULL for sounds that don't have an option, ie buddy pounce */
593 if (purple_prefs_get_bool(enable_pref
)) {
594 char *filename
= g_strdup(purple_prefs_get_path(file_pref
));
595 theme_name
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/theme");
597 if (theme_name
&& *theme_name
&& (!filename
|| !*filename
)) {
601 theme
= PURPLE_SOUND_THEME(purple_theme_manager_find_theme(theme_name
, "sound"));
602 filename
= purple_sound_theme_get_file_full(theme
, sounds
[event
].pref
);
604 if(!g_file_test(filename
, G_FILE_TEST_IS_REGULAR
)){ /* Use Default sound in this case */
605 purple_debug_error("sound", "The file: (%s) %s\n from theme: %s, was not found or wasn't readable\n",
606 sounds
[event
].pref
, filename
, theme_name
);
612 if (!filename
|| !strlen(filename
)) { /* Use Default sounds */
615 /* XXX Consider creating a constant for "sounds/purple" to be shared with Finch */
616 filename
= g_build_filename(DATADIR
, "sounds", "purple", sounds
[event
].def
, NULL
);
619 purple_sound_play_file(filename
, NULL
);
629 pidgin_sound_is_customized(void)
635 for (i
= 0; i
< PURPLE_NUM_SOUNDS
; i
++) {
636 path
= g_strdup_printf(PIDGIN_PREFS_ROOT
"/sound/file/%s", sounds
[i
].pref
);
637 file
= purple_prefs_get_path(path
);
640 if (file
&& file
[0] != '\0')
648 static PurpleSoundUiOps sound_ui_ops
=
652 pidgin_sound_play_file
,
653 pidgin_sound_play_event
,
661 pidgin_sound_get_ui_ops(void)
663 return &sound_ui_ops
;