* @file gtksound.c GTK+ Sound * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #endif /* USE_GSTREAMER */ #include "theme-manager.h" struct pidgin_sound_event { static guint mute_login_sounds_timeout = 0; static gboolean mute_login_sounds = FALSE; static gboolean gst_init_failed; #endif /* USE_GSTREAMER */ static const struct pidgin_sound_event sounds[PURPLE_NUM_SOUNDS] = { {N_("Buddy logs in"), "login", "login.wav"}, {N_("Buddy logs out"), "logout", "logout.wav"}, {N_("Message received"), "im_recv", "receive.wav"}, {N_("Message received begins conversation"), "first_im_recv", "receive.wav"}, {N_("Message sent"), "send_im", "send.wav"}, {N_("Person enters chat"), "join_chat", "login.wav"}, {N_("Person leaves chat"), "left_chat", "logout.wav"}, {N_("You talk in chat"), "send_chat_msg", "send.wav"}, {N_("Others talk in chat"), "chat_msg_recv", "receive.wav"}, /* this isn't a terminator, it's the buddy pounce default sound event ;-) */ {NULL, "pounce_default", "alert.wav"}, {N_("Someone says your username in chat"), "nick_said", "alert.wav"}, {N_("Attention received"), "got_attention", "alert.wav"} unmute_login_sounds_cb(gpointer data) mute_login_sounds = FALSE; mute_login_sounds_timeout = 0; chat_nick_matches_name(PurpleChatConversation *chat, const char *aname) nick = g_strdup(purple_normalize(purple_conversation_get_account( PURPLE_CONVERSATION(chat)), purple_chat_conversation_get_nick(chat))); name = g_strdup(purple_normalize(purple_conversation_get_account( PURPLE_CONVERSATION(chat)), aname)); if (g_utf8_collate(nick, name) == 0) * play a sound event for a conversation, honoring make_sound flag * of conversation and checking for focus if conv_focus pref is set play_conv_event(PurpleConversation *conv, PurpleSoundEventID event) /* If we should not play the sound for some reason, then exit early */ if (conv != NULL && PIDGIN_IS_PIDGIN_CONVERSATION(conv)) PidginConversation *gtkconv; gtkconv = PIDGIN_CONVERSATION(conv); has_focus = purple_conversation_has_focus(conv); if (!gtkconv->make_sound || (has_focus && !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/conv_focus"))) purple_sound_play_event(event, conv ? purple_conversation_get_account(conv) : NULL); buddy_state_cb(PurpleBuddy *buddy, PurpleSoundEventID event) purple_sound_play_event(event, purple_buddy_get_account(buddy)); im_msg_received_cb(PurpleAccount *account, char *sender, char *message, PurpleConversation *conv, PurpleMessageFlags flags, PurpleSoundEventID event) if (flags & PURPLE_MESSAGE_DELAYED || flags & PURPLE_MESSAGE_NOTIFY) purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE, account); play_conv_event(conv, event); im_msg_sent_cb(PurpleAccount *account, const char *receiver, const char *message, PurpleSoundEventID event) PurpleConversation *conv = PURPLE_CONVERSATION( purple_conversations_find_im_with_account(receiver, account)); play_conv_event(conv, event); chat_user_join_cb(PurpleChatConversation *chat, const char *name, PurpleChatUserFlags flags, gboolean new_arrival, PurpleSoundEventID event) if (new_arrival && !chat_nick_matches_name(chat, name)) play_conv_event(PURPLE_CONVERSATION(chat), event); chat_user_left_cb(PurpleChatConversation *chat, const char *name, const char *reason, PurpleSoundEventID event) if (!chat_nick_matches_name(chat, name)) play_conv_event(PURPLE_CONVERSATION(chat), event); chat_msg_sent_cb(PurpleAccount *account, const char *message, int id, PurpleSoundEventID event) PurpleConnection *conn = purple_account_get_connection(account); PurpleConversation *conv = NULL; conv = PURPLE_CONVERSATION(purple_conversations_find_chat(conn,id)); play_conv_event(conv, event); chat_msg_received_cb(PurpleAccount *account, char *sender, char *message, PurpleChatConversation *chat, PurpleMessageFlags flags, PurpleSoundEventID event) PurpleConversation *conv = PURPLE_CONVERSATION(chat); if (flags & PURPLE_MESSAGE_DELAYED || flags & PURPLE_MESSAGE_NOTIFY) g_return_if_fail(conv != NULL); if (purple_chat_conversation_is_ignored_user(chat, sender)) if (chat_nick_matches_name(chat, sender)) if (flags & PURPLE_MESSAGE_NICK || purple_utf8_has_word(message, purple_chat_conversation_get_nick(chat))) /* This isn't quite right; if you have the PURPLE_SOUND_CHAT_NICK event disabled * and the PURPLE_SOUND_CHAT_SAY event enabled, you won't get a sound at all */ play_conv_event(conv, PURPLE_SOUND_CHAT_NICK); play_conv_event(conv, event); got_attention_cb(PurpleAccount *account, const char *who, PurpleConversation *conv, guint type, PurpleSoundEventID event) play_conv_event(conv, event); * We mute sounds for the 10 seconds after you log in so that * you don't get flooded with sounds when the blist shows all * your buddies logging in. account_signon_cb(PurpleConnection *gc, gpointer data) if (mute_login_sounds_timeout != 0) purple_timeout_remove(mute_login_sounds_timeout); mute_login_sounds = TRUE; mute_login_sounds_timeout = purple_timeout_add_seconds(10, unmute_login_sounds_cb, NULL); pidgin_sound_get_event_option(PurpleSoundEventID event) if(event >= PURPLE_NUM_SOUNDS) return sounds[event].pref; pidgin_sound_get_event_label(PurpleSoundEventID event) if(event >= PURPLE_NUM_SOUNDS) return sounds[event].label; pidgin_sound_get_handle() void *gtk_sound_handle = pidgin_sound_get_handle(); void *blist_handle = purple_blist_get_handle(); void *conv_handle = purple_conversations_get_handle(); purple_signal_connect(purple_connections_get_handle(), "signed-on", gtk_sound_handle, PURPLE_CALLBACK(account_signon_cb), purple_prefs_add_none(PIDGIN_PREFS_ROOT "/sound"); purple_prefs_add_none(PIDGIN_PREFS_ROOT "/sound/enabled"); purple_prefs_add_none(PIDGIN_PREFS_ROOT "/sound/file"); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/login", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/login", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/logout", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/logout", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/im_recv", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/im_recv", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/first_im_recv", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/first_im_recv", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/send_im", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/send_im", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/join_chat", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/join_chat", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/left_chat", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/left_chat", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/send_chat_msg", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/send_chat_msg", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/chat_msg_recv", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/chat_msg_recv", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/nick_said", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/nick_said", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/pounce_default", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/pounce_default", ""); purple_prefs_add_string(PIDGIN_PREFS_ROOT "/sound/theme", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/sent_attention", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/sent_attention", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/got_attention", TRUE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/got_attention", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/conv_focus", TRUE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/mute", FALSE); purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/command", ""); purple_prefs_add_string(PIDGIN_PREFS_ROOT "/sound/method", "automatic"); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/sound/volume", 50); purple_debug_info("sound", "Initializing sound output drivers.\n"); gst_registry_fork_set_enabled(FALSE); if ((gst_init_failed = !gst_init_check(NULL, NULL, &error))) { purple_notify_error(NULL, _("GStreamer Failure"), _("GStreamer failed to initialize."), error ? error->message : "", NULL); #endif /* USE_GSTREAMER */ purple_signal_connect(blist_handle, "buddy-signed-on", gtk_sound_handle, PURPLE_CALLBACK(buddy_state_cb), GINT_TO_POINTER(PURPLE_SOUND_BUDDY_ARRIVE)); purple_signal_connect(blist_handle, "buddy-signed-off", gtk_sound_handle, PURPLE_CALLBACK(buddy_state_cb), GINT_TO_POINTER(PURPLE_SOUND_BUDDY_LEAVE)); purple_signal_connect(conv_handle, "received-im-msg", gtk_sound_handle, PURPLE_CALLBACK(im_msg_received_cb), GINT_TO_POINTER(PURPLE_SOUND_RECEIVE)); purple_signal_connect(conv_handle, "sent-im-msg", gtk_sound_handle, PURPLE_CALLBACK(im_msg_sent_cb), GINT_TO_POINTER(PURPLE_SOUND_SEND)); purple_signal_connect(conv_handle, "chat-user-joined", gtk_sound_handle, PURPLE_CALLBACK(chat_user_join_cb), GINT_TO_POINTER(PURPLE_SOUND_CHAT_JOIN)); purple_signal_connect(conv_handle, "chat-user-left", gtk_sound_handle, PURPLE_CALLBACK(chat_user_left_cb), GINT_TO_POINTER(PURPLE_SOUND_CHAT_LEAVE)); purple_signal_connect(conv_handle, "sent-chat-msg", gtk_sound_handle, PURPLE_CALLBACK(chat_msg_sent_cb), GINT_TO_POINTER(PURPLE_SOUND_CHAT_YOU_SAY)); purple_signal_connect(conv_handle, "received-chat-msg", gtk_sound_handle, PURPLE_CALLBACK(chat_msg_received_cb), GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY)); purple_signal_connect(conv_handle, "got-attention", gtk_sound_handle, PURPLE_CALLBACK(got_attention_cb), GINT_TO_POINTER(PURPLE_SOUND_GOT_ATTENTION)); /* for the time being, don't handle sent-attention here, since playing a sound would result induplicate sounds. And fixing that would require changing the conversation signal for msg-recv */ pidgin_sound_uninit(void) purple_signals_disconnect_by_handle(pidgin_sound_get_handle()); switch (GST_MESSAGE_TYPE (msg)) { gst_message_parse_error(msg, &err, NULL); purple_debug_error("gstreamer", "%s\n", err->message); /* fall-through and clean up */ gst_element_set_state(play, GST_STATE_NULL); gst_object_unref(GST_OBJECT(play)); case GST_MESSAGE_WARNING: gst_message_parse_warning(msg, &err, NULL); purple_debug_warning("gstreamer", "%s\n", err->message); expire_old_child(gpointer data) pid_t pid = GPOINTER_TO_INT(data); if (waitpid(pid, NULL, WNOHANG | WUNTRACED) < 0) { purple_debug_warning("gtksound", "Child is ill, pid: %d (%s)\n", pid, strerror(errno)); if (kill(pid, SIGKILL) < 0) purple_debug_error("gtksound", "Killing process %d failed (%s)\n", pid, strerror(errno)); pidgin_sound_play_file_win32(const char *filename) wchar_t *wc_filename = g_utf8_to_utf16(filename, if (!PlaySoundW(wc_filename, NULL, SND_ASYNC | SND_FILENAME)) purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n"); pidgin_sound_play_file(const char *filename) if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute")) method = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"); if (!strcmp(method, "none")) { } else if (!strcmp(method, "beep")) { else if (!strcmp(method, "playsoundw")) { pidgin_sound_play_file_win32(filename); if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { purple_debug_error("gtksound", "sound file (%s) does not exist.\n", filename); if (!strcmp(method, "custom")) { sound_cmd = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/sound/command"); if (!sound_cmd || *sound_cmd == '\0') { purple_debug_error("gtksound", "'Command' sound method has been chosen, " "but no command has been set.\n"); esc_filename = g_shell_quote(filename); if(strstr(sound_cmd, "%s")) command = purple_strreplace(sound_cmd, "%s", esc_filename); command = g_strdup_printf("%s %s", sound_cmd, esc_filename); if (!g_shell_parse_argv(command, NULL, &argv, &error)) { purple_debug_error("gtksound", "error parsing command %s (%s)\n", command, error->message); if (!g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, &error)) { purple_debug_error("gtksound", "sound command could not be launched: %s\n", purple_timeout_add_seconds(15, expire_old_child, GINT_TO_POINTER(pid)); if (gst_init_failed) /* Perhaps do gdk_beep instead? */ volume = (float)(CLAMP(purple_prefs_get_int(PIDGIN_PREFS_ROOT "/sound/volume"),0,100)) / 50; if (!strcmp(method, "automatic")) { sink = gst_element_factory_make("directsoundsink", "sink"); sink = gst_element_factory_make("waveformsink", "sink"); sink = gst_element_factory_make("gconfaudiosink", "sink"); } else if (!strcmp(method, "directsound")) { sink = gst_element_factory_make("directsoundsink", "sink"); } else if (!strcmp(method, "waveform")) { sink = gst_element_factory_make("waveformsink", "sink"); if (!strcmp(method, "automatic")) { sink = gst_element_factory_make("gconfaudiosink", "sink"); } else if (!strcmp(method, "esd")) { sink = gst_element_factory_make("esdsink", "sink"); } else if (!strcmp(method, "alsa")) { sink = gst_element_factory_make("alsasink", "sink"); purple_debug_error("sound", "Unknown sound method '%s'\n", method); if (strcmp(method, "automatic") != 0 && !sink) { purple_debug_error("sound", "Unable to create GStreamer audiosink.\n"); #if GST_CHECK_VERSION(1,0,0) play = gst_element_factory_make("playbin", "play"); play = gst_element_factory_make("playbin2", "play"); uri = g_strdup_printf("file:///%s", filename); g_strdelimit(uri, "\\", '/'); uri = g_strdup_printf("file://%s", filename); g_object_set(G_OBJECT(play), "uri", uri, "audio-sink", sink, NULL); bus = gst_pipeline_get_bus(GST_PIPELINE(play)); gst_bus_add_watch(bus, bus_call, play); gst_element_set_state(play, GST_STATE_PLAYING); #else /* #ifdef USE_GSTREAMER */ pidgin_sound_play_file_win32(filename); #endif /* USE_GSTREAMER */ pidgin_sound_play_event(PurpleSoundEventID event) if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds) if (event >= PURPLE_NUM_SOUNDS) { purple_debug_error("sound", "got request for unknown sound: %d\n", event); enable_pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/enabled/%s", file_pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s", sounds[event].pref); /* check NULL for sounds that don't have an option, ie buddy pounce */ if (purple_prefs_get_bool(enable_pref)) { char *filename = g_strdup(purple_prefs_get_path(file_pref)); theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/theme"); if (theme_name && *theme_name && (!filename || !*filename)) { theme = PURPLE_SOUND_THEME(purple_theme_manager_find_theme(theme_name, "sound")); filename = purple_sound_theme_get_file_full(theme, sounds[event].pref); if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)){ /* Use Default sound in this case */ purple_debug_error("sound", "The file: (%s) %s\n from theme: %s, was not found or wasn't readable\n", sounds[event].pref, filename, theme_name); if (!filename || !strlen(filename)) { /* Use Default sounds */ /* XXX Consider creating a constant for "sounds/purple" to be shared with Finch */ filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL); purple_sound_play_file(filename, NULL); pidgin_sound_is_customized(void) for (i = 0; i < PURPLE_NUM_SOUNDS; i++) { path = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s", sounds[i].pref); file = purple_prefs_get_path(path); if (file && file[0] != '\0') static PurpleSoundUiOps sound_ui_ops = pidgin_sound_get_ui_ops(void)