1 /*
2  * @file gtksound.c GTK+ Sound
3  * @ingroup pidgin
4  */
5 
6 /* pidgin
7  *
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.
11  *
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.
16  *
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.
21  *
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
25  *
26  */
27 #include "internal.h"
28 #include "pidgin.h"
29 
30 #ifdef _WIN32
31 #include <windows.h>
32 #include <mmsystem.h>
33 #endif
34 
35 #ifdef USE_GSTREAMER
36 # include <gst/gst.h>
37 #endif /* USE_GSTREAMER */
38 
39 #include "debug.h"
40 #include "notify.h"
41 #include "prefs.h"
42 #include "sound.h"
43 #include "sound-theme.h"
44 #include "theme-manager.h"
45 #include "util.h"
46 
47 #include "gtkconv.h"
48 #include "gtksound.h"
49 
50 struct pidgin_sound_event {
51 	char *label;
52 	char *pref;
53 	char *def;
54 };
55 
56 static guint mute_login_sounds_timeout = 0;
57 static gboolean mute_login_sounds = FALSE;
58 
59 #ifdef USE_GSTREAMER
60 static gboolean gst_init_failed;
61 #endif /* USE_GSTREAMER */
62 
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"}
77 };
78 
79 static gboolean
unmute_login_sounds_cb(gpointer data)80 unmute_login_sounds_cb(gpointer data)
81 {
82 	mute_login_sounds = FALSE;
83 	mute_login_sounds_timeout = 0;
84 	return FALSE;
85 }
86 
87 static gboolean
chat_nick_matches_name(PurpleConversation * conv,const char * aname)88 chat_nick_matches_name(PurpleConversation *conv, const char *aname)
89 {
90 	PurpleConvChat *chat = NULL;
91 	char *nick = NULL;
92 	char *name = NULL;
93 	gboolean ret = FALSE;
94 	chat = purple_conversation_get_chat_data(conv);
95 
96 	if (chat==NULL)
97 		return ret;
98 
99 	nick = g_strdup(purple_normalize(conv->account, chat->nick));
100 	name = g_strdup(purple_normalize(conv->account, aname));
101 
102 	if (g_utf8_collate(nick, name) == 0)
103 		ret = TRUE;
104 
105 	g_free(nick);
106 	g_free(name);
107 
108 	return ret;
109 }
110 
111 /*
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
114  */
115 static void
play_conv_event(PurpleConversation * conv,PurpleSoundEventID event)116 play_conv_event(PurpleConversation *conv, PurpleSoundEventID event)
117 {
118 	/* If we should not play the sound for some reason, then exit early */
119 	if (conv != NULL && PIDGIN_IS_PIDGIN_CONVERSATION(conv))
120 	{
121 		PidginConversation *gtkconv;
122 		gboolean has_focus;
123 
124 		gtkconv = PIDGIN_CONVERSATION(conv);
125 		has_focus = purple_conversation_has_focus(conv);
126 
127 		if (!gtkconv->make_sound ||
128 			(has_focus && !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/conv_focus")))
129 		{
130 			return;
131 		}
132 	}
133 
134 	purple_sound_play_event(event, conv ? purple_conversation_get_account(conv) : NULL);
135 }
136 
137 static void
buddy_state_cb(PurpleBuddy * buddy,PurpleSoundEventID event)138 buddy_state_cb(PurpleBuddy *buddy, PurpleSoundEventID event)
139 {
140 	purple_sound_play_event(event, purple_buddy_get_account(buddy));
141 }
142 
143 static void
im_msg_received_cb(PurpleAccount * account,char * sender,char * message,PurpleConversation * conv,PurpleMessageFlags flags,PurpleSoundEventID event)144 im_msg_received_cb(PurpleAccount *account, char *sender,
145 				   char *message, PurpleConversation *conv,
146 				   PurpleMessageFlags flags, PurpleSoundEventID event)
147 {
148 	if (flags & PURPLE_MESSAGE_DELAYED || flags & PURPLE_MESSAGE_NOTIFY)
149 		return;
150 
151 	if (conv==NULL)
152 		purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE, account);
153 	else
154 		play_conv_event(conv, event);
155 }
156 
157 static void
im_msg_sent_cb(PurpleAccount * account,const char * receiver,const char * message,PurpleSoundEventID event)158 im_msg_sent_cb(PurpleAccount *account, const char *receiver,
159 			   const char *message, PurpleSoundEventID event)
160 {
161 	PurpleConversation *conv = purple_find_conversation_with_account(
162 		PURPLE_CONV_TYPE_IM, receiver, account);
163 	play_conv_event(conv, event);
164 }
165 
166 static void
chat_buddy_join_cb(PurpleConversation * conv,const char * name,PurpleConvChatBuddyFlags flags,gboolean new_arrival,PurpleSoundEventID event)167 chat_buddy_join_cb(PurpleConversation *conv, const char *name,
168 				   PurpleConvChatBuddyFlags flags, gboolean new_arrival,
169 				   PurpleSoundEventID event)
170 {
171 	if (new_arrival && !chat_nick_matches_name(conv, name))
172 		play_conv_event(conv, event);
173 }
174 
175 static void
chat_buddy_left_cb(PurpleConversation * conv,const char * name,const char * reason,PurpleSoundEventID event)176 chat_buddy_left_cb(PurpleConversation *conv, const char *name,
177 				   const char *reason, PurpleSoundEventID event)
178 {
179 	if (!chat_nick_matches_name(conv, name))
180 		play_conv_event(conv, event);
181 }
182 
183 static void
chat_msg_sent_cb(PurpleAccount * account,const char * message,int id,PurpleSoundEventID event)184 chat_msg_sent_cb(PurpleAccount *account, const char *message,
185 				 int id, PurpleSoundEventID event)
186 {
187 	PurpleConnection *conn = purple_account_get_connection(account);
188 	PurpleConversation *conv = NULL;
189 
190 	if (conn!=NULL)
191 		conv = purple_find_chat(conn,id);
192 
193 	play_conv_event(conv, event);
194 }
195 
196 static void
chat_msg_received_cb(PurpleAccount * account,char * sender,char * message,PurpleConversation * conv,PurpleMessageFlags flags,PurpleSoundEventID event)197 chat_msg_received_cb(PurpleAccount *account, char *sender,
198 					 char *message, PurpleConversation *conv,
199 					 PurpleMessageFlags flags, PurpleSoundEventID event)
200 {
201 	PurpleConvChat *chat;
202 
203 	if (flags & PURPLE_MESSAGE_DELAYED || flags & PURPLE_MESSAGE_NOTIFY)
204 		return;
205 
206 	chat = purple_conversation_get_chat_data(conv);
207 	g_return_if_fail(chat != NULL);
208 
209 	if (purple_conv_chat_is_user_ignored(chat, sender))
210 		return;
211 
212 	if (chat_nick_matches_name(conv, sender))
213 		return;
214 
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);
219 	else
220 		play_conv_event(conv, event);
221 }
222 
223 static void
got_attention_cb(PurpleAccount * account,const char * who,PurpleConversation * conv,guint type,PurpleSoundEventID event)224 got_attention_cb(PurpleAccount *account, const char *who,
225 	PurpleConversation *conv, guint type, PurpleSoundEventID event)
226 {
227 	play_conv_event(conv, event);
228 }
229 
230 /*
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.
234  */
235 static void
account_signon_cb(PurpleConnection * gc,gpointer data)236 account_signon_cb(PurpleConnection *gc, gpointer data)
237 {
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);
242 }
243 
244 const char *
pidgin_sound_get_event_option(PurpleSoundEventID event)245 pidgin_sound_get_event_option(PurpleSoundEventID event)
246 {
247 	if(event >= PURPLE_NUM_SOUNDS)
248 		return 0;
249 
250 	return sounds[event].pref;
251 }
252 
253 const char *
pidgin_sound_get_event_label(PurpleSoundEventID event)254 pidgin_sound_get_event_label(PurpleSoundEventID event)
255 {
256 	if(event >= PURPLE_NUM_SOUNDS)
257 		return NULL;
258 
259 	return sounds[event].label;
260 }
261 
262 void *
pidgin_sound_get_handle()263 pidgin_sound_get_handle()
264 {
265 	static int handle;
266 
267 	return &handle;
268 }
269 
270 static void
pidgin_sound_init(void)271 pidgin_sound_init(void)
272 {
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();
276 #ifdef USE_GSTREAMER
277 	GError *error = NULL;
278 #endif
279 
280 	purple_signal_connect(purple_connections_get_handle(), "signed-on",
281 						gtk_sound_handle, PURPLE_CALLBACK(account_signon_cb),
282 						NULL);
283 
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);
319 
320 #ifdef USE_GSTREAMER
321 	purple_debug_info("sound", "Initializing sound output drivers.\n");
322 #ifdef GST_CAN_DISABLE_FORKING
323 	gst_registry_fork_set_enabled (FALSE);
324 #endif
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 : "");
329 		if (error) {
330 			g_error_free(error);
331 			error = NULL;
332 		}
333 	}
334 #endif /* USE_GSTREAMER */
335 
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 */
366 }
367 
368 static void
pidgin_sound_uninit(void)369 pidgin_sound_uninit(void)
370 {
371 #ifdef USE_GSTREAMER
372 	if (!gst_init_failed)
373 		gst_deinit();
374 #endif
375 
376 	purple_signals_disconnect_by_handle(pidgin_sound_get_handle());
377 }
378 
379 #ifdef USE_GSTREAMER
380 static gboolean
bus_call(GstBus * bus,GstMessage * msg,gpointer data)381 bus_call (GstBus     *bus,
382 	  GstMessage *msg,
383 	  gpointer    data)
384 {
385 	GstElement *play = data;
386 	GError *err = NULL;
387 
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);
392 		g_error_free(err);
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));
397 		return FALSE;
398 		break;
399 	case GST_MESSAGE_WARNING:
400 		gst_message_parse_warning(msg, &err, NULL);
401 		purple_debug_warning("gstreamer", "%s\n", err->message);
402 		g_error_free(err);
403 		break;
404 	default:
405 		break;
406 	}
407 	return TRUE;
408 }
409 #endif
410 
411 #ifndef _WIN32
412 static gboolean
expire_old_child(gpointer data)413 expire_old_child(gpointer data)
414 {
415 	pid_t pid = GPOINTER_TO_INT(data);
416 
417 	if (waitpid(pid, NULL, WNOHANG | WUNTRACED) < 0) {
418 		if (errno == ECHILD)
419 			return FALSE;
420 		else
421 			purple_debug_warning("gtksound", "Child is ill, pid: %d (%s)\n", pid, strerror(errno));
422 	}
423 
424 	if (kill(pid, SIGKILL) < 0)
425 		purple_debug_error("gtksound", "Killing process %d failed (%s)\n", pid, strerror(errno));
426 
427 	return FALSE;
428 }
429 #endif
430 
431 static void
pidgin_sound_play_file(const char * filename)432 pidgin_sound_play_file(const char *filename)
433 {
434 	const char *method;
435 #ifdef USE_GSTREAMER
436 	float volume;
437 	char *uri;
438 	GstElement *sink = NULL;
439 	GstElement *play = NULL;
440 	GstBus *bus = NULL;
441 #endif
442 
443 	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"))
444 		return;
445 
446 	method = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method");
447 
448 	if (purple_strequal(method, "none")) {
449 		return;
450 	} else if (purple_strequal(method, "beep")) {
451 		gdk_beep();
452 		return;
453 	}
454 
455 	if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
456 		purple_debug_error("gtksound", "sound file (%s) does not exist.\n", filename);
457 		return;
458 	}
459 
460 #ifndef _WIN32
461 	if (purple_strequal(method, "custom")) {
462 		const char *sound_cmd;
463 		char *command;
464 		char *esc_filename;
465 		char **argv = NULL;
466 		GError *error = NULL;
467 		GPid pid;
468 
469 		sound_cmd = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/sound/command");
470 
471 		if (!sound_cmd || *sound_cmd == '\0') {
472 			purple_debug_error("gtksound",
473 					 "'Command' sound method has been chosen, "
474 					 "but no command has been set.\n");
475 			return;
476 		}
477 
478 		esc_filename = g_shell_quote(filename);
479 
480 		if(strstr(sound_cmd, "%s"))
481 			command = purple_strreplace(sound_cmd, "%s", esc_filename);
482 		else
483 			command = g_strdup_printf("%s %s", sound_cmd, esc_filename);
484 
485 		if (!g_shell_parse_argv(command, NULL, &argv, &error)) {
486 			purple_debug_error("gtksound", "error parsing command %s (%s)\n",
487 							   command, error->message);
488 			g_error_free(error);
489 			g_free(esc_filename);
490 			g_free(command);
491 			return;
492 		}
493 
494 		if (!g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
495 						  NULL, NULL, &pid, &error)) {
496 			purple_debug_error("gtksound", "sound command could not be launched: %s\n",
497 							   error->message);
498 			g_error_free(error);
499 		} else {
500 			purple_timeout_add_seconds(15, expire_old_child, GINT_TO_POINTER(pid));
501 		}
502 
503 		g_strfreev(argv);
504 		g_free(esc_filename);
505 		g_free(command);
506 		return;
507 	}
508 #endif /* _WIN32 */
509 
510 #ifdef USE_GSTREAMER
511 	if (gst_init_failed)  /* Perhaps do gdk_beep instead? */
512 		return;
513 	volume = (float)(CLAMP(purple_prefs_get_int(PIDGIN_PREFS_ROOT "/sound/volume"),0,100)) / 50;
514 	if (purple_strequal(method, "automatic")) {
515 		sink = gst_element_factory_make("gconfaudiosink", "sink");
516 	}
517 #ifndef _WIN32
518 	else if (purple_strequal(method, "esd")) {
519 		sink = gst_element_factory_make("esdsink", "sink");
520 	} else if (purple_strequal(method, "alsa")) {
521 		sink = gst_element_factory_make("alsasink", "sink");
522 	}
523 #endif
524 	else {
525 		purple_debug_error("sound", "Unknown sound method '%s'\n", method);
526 		return;
527 	}
528 
529 	if (!purple_strequal(method, "automatic") && !sink) {
530 		purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
531 		return;
532 	}
533 
534 	play = gst_element_factory_make("playbin", "play");
535 
536 	if (play == NULL) {
537 		return;
538 	}
539 
540 	uri = g_strdup_printf("file://%s", filename);
541 
542 	g_object_set(G_OBJECT(play), "uri", uri,
543 		                     "volume", volume,
544 		                     "audio-sink", sink, NULL);
545 
546 	bus = gst_pipeline_get_bus(GST_PIPELINE(play));
547 	gst_bus_add_watch(bus, bus_call, play);
548 
549 	gst_element_set_state(play, GST_STATE_PLAYING);
550 
551 	gst_object_unref(bus);
552 	g_free(uri);
553 
554 #else /* #ifdef USE_GSTREAMER */
555 
556 #ifndef _WIN32
557 	gdk_beep();
558 #else /* _WIN32 */
559 	purple_debug_info("sound", "Playing %s\n", filename);
560 
561 	{
562 		wchar_t *wc_filename = g_utf8_to_utf16(filename,
563 				-1, NULL, NULL, NULL);
564 		if (!PlaySoundW(wc_filename, NULL, SND_ASYNC | SND_FILENAME))
565 			purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n");
566 		g_free(wc_filename);
567 	}
568 #endif /* _WIN32 */
569 
570 #endif /* USE_GSTREAMER */
571 }
572 
573 static void
pidgin_sound_play_event(PurpleSoundEventID event)574 pidgin_sound_play_event(PurpleSoundEventID event)
575 {
576 	char *enable_pref;
577 	char *file_pref;
578 	const char *theme_name;
579 	PurpleSoundTheme *theme;
580 
581 	if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds)
582 		return;
583 
584 	if (event >= PURPLE_NUM_SOUNDS) {
585 		purple_debug_error("sound", "got request for unknown sound: %d\n", event);
586 		return;
587 	}
588 
589 	enable_pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/enabled/%s",
590 			sounds[event].pref);
591 	file_pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s", sounds[event].pref);
592 
593 	/* check NULL for sounds that don't have an option, ie buddy pounce */
594 	if (purple_prefs_get_bool(enable_pref)) {
595 		char *filename = g_strdup(purple_prefs_get_path(file_pref));
596 		theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/theme");
597 
598 		if (theme_name && *theme_name && (!filename || !*filename)) {
599 			/* Use theme */
600 			g_free(filename);
601 
602 			theme = PURPLE_SOUND_THEME(purple_theme_manager_find_theme(theme_name, "sound"));
603 			filename = purple_sound_theme_get_file_full(theme, sounds[event].pref);
604 
605 			if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)){ /* Use Default sound in this case */
606 				purple_debug_error("sound", "The file: (%s) %s\n from theme: %s, was not found or wasn't readable\n",
607 							sounds[event].pref, filename, theme_name);
608 				g_free(filename);
609 				filename = NULL;
610 			}
611 		}
612 
613 		if (!filename || !strlen(filename)) {			    /* Use Default sounds */
614 			g_free(filename);
615 
616 			/* XXX Consider creating a constant for "sounds/purple" to be shared with Finch */
617 			filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL);
618 		}
619 
620 		purple_sound_play_file(filename, NULL);
621 
622 		g_free(filename);
623 	}
624 
625 	g_free(enable_pref);
626 	g_free(file_pref);
627 }
628 
629 gboolean
pidgin_sound_is_customized(void)630 pidgin_sound_is_customized(void)
631 {
632 	gint i;
633 	gchar *path;
634 	const char *file;
635 
636 	for (i = 0; i < PURPLE_NUM_SOUNDS; i++) {
637 		path = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s", sounds[i].pref);
638 		file = purple_prefs_get_path(path);
639 		g_free(path);
640 
641 		if (file && file[0] != '\0')
642 			return TRUE;
643 	}
644 
645 	return FALSE;
646 
647 }
648 
649 static PurpleSoundUiOps sound_ui_ops =
650 {
651 	pidgin_sound_init,
652 	pidgin_sound_uninit,
653 	pidgin_sound_play_file,
654 	pidgin_sound_play_event,
655 	NULL,
656 	NULL,
657 	NULL,
658 	NULL
659 };
660 
661 PurpleSoundUiOps *
pidgin_sound_get_ui_ops(void)662 pidgin_sound_get_ui_ops(void)
663 {
664 	return &sound_ui_ops;
665 }
666