I just noticed I didn't differentiate bn_IN from bn when I added this translation.
[pidgin-git.git] / pidgin / gtksound.c
blobbcdc198ad177ac0506b038a401bb1ae8c120aab0
1 /*
2 * @file gtksound.c GTK+ Sound
3 * @ingroup pidgin
4 */
6 /* pidgin
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
27 #include "internal.h"
28 #include "pidgin.h"
30 #ifdef _WIN32
31 #include <windows.h>
32 #include <mmsystem.h>
33 #endif
35 #ifdef USE_GSTREAMER
36 # include <gst/gst.h>
37 #endif /* USE_GSTREAMER */
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"
47 #include "gtkconv.h"
48 #include "gtksound.h"
50 struct pidgin_sound_event {
51 char *label;
52 char *pref;
53 char *def;
56 static guint mute_login_sounds_timeout = 0;
57 static gboolean mute_login_sounds = FALSE;
59 #ifdef USE_GSTREAMER
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"}
79 static gboolean
80 unmute_login_sounds_cb(gpointer data)
82 mute_login_sounds = FALSE;
83 mute_login_sounds_timeout = 0;
84 return FALSE;
87 static gboolean
88 chat_nick_matches_name(PurpleConversation *conv, const char *aname)
90 PurpleConvChat *chat = NULL;
91 char *nick = NULL;
92 char *name = NULL;
93 gboolean ret = FALSE;
94 chat = purple_conversation_get_chat_data(conv);
96 if (chat==NULL)
97 return ret;
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)
103 ret = TRUE;
105 g_free(nick);
106 g_free(name);
108 return ret;
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
115 static void
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;
122 gboolean has_focus;
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")))
130 return;
134 purple_sound_play_event(event, conv ? purple_conversation_get_account(conv) : NULL);
137 static void
138 buddy_state_cb(PurpleBuddy *buddy, PurpleSoundEventID event)
140 purple_sound_play_event(event, purple_buddy_get_account(buddy));
143 static void
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)
149 return;
151 if (conv==NULL)
152 purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE, account);
153 else
154 play_conv_event(conv, event);
157 static void
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);
166 static void
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);
175 static void
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);
183 static void
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;
190 if (conn!=NULL)
191 conv = purple_find_chat(conn,id);
193 play_conv_event(conv, event);
196 static void
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)
204 return;
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))
210 return;
212 if (chat_nick_matches_name(conv, sender))
213 return;
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);
223 static void
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.
235 static void
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);
244 const char *
245 pidgin_sound_get_event_option(PurpleSoundEventID event)
247 if(event >= PURPLE_NUM_SOUNDS)
248 return 0;
250 return sounds[event].pref;
253 const char *
254 pidgin_sound_get_event_label(PurpleSoundEventID event)
256 if(event >= PURPLE_NUM_SOUNDS)
257 return NULL;
259 return sounds[event].label;
262 void *
263 pidgin_sound_get_handle()
265 static int handle;
267 return &handle;
270 static void
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();
276 #ifdef USE_GSTREAMER
277 GError *error = NULL;
278 #endif
280 purple_signal_connect(purple_connections_get_handle(), "signed-on",
281 gtk_sound_handle, PURPLE_CALLBACK(account_signon_cb),
282 NULL);
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);
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;
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 */
368 static void
369 pidgin_sound_uninit(void)
371 #ifdef USE_GSTREAMER
372 if (!gst_init_failed)
373 gst_deinit();
374 #endif
376 purple_signals_disconnect_by_handle(pidgin_sound_get_handle());
379 #ifdef USE_GSTREAMER
380 static gboolean
381 bus_call (GstBus *bus,
382 GstMessage *msg,
383 gpointer data)
385 GstElement *play = data;
386 GError *err = NULL;
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 break;
398 case GST_MESSAGE_WARNING:
399 gst_message_parse_warning(msg, &err, NULL);
400 purple_debug_warning("gstreamer", "%s\n", err->message);
401 g_error_free(err);
402 break;
403 default:
404 break;
406 return TRUE;
408 #endif
410 #ifndef _WIN32
411 static gboolean
412 expire_old_child(gpointer data)
414 pid_t pid = GPOINTER_TO_INT(data);
416 if (waitpid(pid, NULL, WNOHANG | WUNTRACED) < 0) {
417 if (errno == ECHILD)
418 return FALSE;
419 else
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));
426 return FALSE;
428 #endif
430 static void
431 pidgin_sound_play_file(const char *filename)
433 const char *method;
434 #ifdef USE_GSTREAMER
435 float volume;
436 char *uri;
437 GstElement *sink = NULL;
438 GstElement *play = NULL;
439 GstBus *bus = NULL;
440 #endif
442 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"))
443 return;
445 method = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method");
447 if (!strcmp(method, "none")) {
448 return;
449 } else if (!strcmp(method, "beep")) {
450 gdk_beep();
451 return;
454 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
455 purple_debug_error("gtksound", "sound file (%s) does not exist.\n", filename);
456 return;
459 #ifndef _WIN32
460 if (!strcmp(method, "custom")) {
461 const char *sound_cmd;
462 char *command;
463 char *esc_filename;
464 char **argv = NULL;
465 GError *error = NULL;
466 GPid pid;
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");
474 return;
477 esc_filename = g_shell_quote(filename);
479 if(strstr(sound_cmd, "%s"))
480 command = purple_strreplace(sound_cmd, "%s", esc_filename);
481 else
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);
487 g_error_free(error);
488 g_free(esc_filename);
489 g_free(command);
490 return;
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",
496 error->message);
497 g_error_free(error);
498 } else {
499 purple_timeout_add_seconds(15, expire_old_child, GINT_TO_POINTER(pid));
502 g_strfreev(argv);
503 g_free(esc_filename);
504 g_free(command);
505 return;
507 #endif /* _WIN32 */
509 #ifdef USE_GSTREAMER
510 if (gst_init_failed) /* Perhaps do gdk_beep instead? */
511 return;
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");
516 #ifndef _WIN32
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");
522 #endif
523 else {
524 purple_debug_error("sound", "Unknown sound method '%s'\n", method);
525 return;
528 if (strcmp(method, "automatic") != 0 && !sink) {
529 purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
530 return;
533 play = gst_element_factory_make("playbin", "play");
535 if (play == NULL) {
536 return;
539 uri = g_strdup_printf("file://%s", filename);
541 g_object_set(G_OBJECT(play), "uri", uri,
542 "volume", volume,
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);
551 g_free(uri);
553 #else /* #ifdef USE_GSTREAMER */
555 #ifndef _WIN32
556 gdk_beep();
557 #else /* _WIN32 */
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");
565 g_free(wc_filename);
567 #endif /* _WIN32 */
569 #endif /* USE_GSTREAMER */
572 static void
573 pidgin_sound_play_event(PurpleSoundEventID event)
575 char *enable_pref;
576 char *file_pref;
577 const char *theme_name;
578 PurpleSoundTheme *theme;
580 if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds)
581 return;
583 if (event >= PURPLE_NUM_SOUNDS) {
584 purple_debug_error("sound", "got request for unknown sound: %d\n", event);
585 return;
588 enable_pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/enabled/%s",
589 sounds[event].pref);
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)) {
598 /* Use theme */
599 g_free(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);
607 g_free(filename);
608 filename = NULL;
612 if (!filename || !strlen(filename)) { /* Use Default sounds */
613 g_free(filename);
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);
621 g_free(filename);
624 g_free(enable_pref);
625 g_free(file_pref);
628 gboolean
629 pidgin_sound_is_customized(void)
631 gint i;
632 gchar *path;
633 const char *file;
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);
638 g_free(path);
640 if (file && file[0] != '\0')
641 return TRUE;
644 return FALSE;
648 static PurpleSoundUiOps sound_ui_ops =
650 pidgin_sound_init,
651 pidgin_sound_uninit,
652 pidgin_sound_play_file,
653 pidgin_sound_play_event,
654 NULL,
655 NULL,
656 NULL,
657 NULL
660 PurpleSoundUiOps *
661 pidgin_sound_get_ui_ops(void)
663 return &sound_ui_ops;